From ac109ad931b1b4a57dd527028ec2c22ed27c3440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Tue, 28 Jul 2026 11:33:47 +0200 Subject: [PATCH 1/2] feat: adding TP enabled flag in version response --- .../org/eclipse/openvsx/LocalRegistryService.java | 4 ++++ .../org/eclipse/openvsx/json/RegistryVersionJson.java | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java b/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java index c7918fea4..7f92b6748 100644 --- a/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java +++ b/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java @@ -117,6 +117,9 @@ public LocalRegistryService( @Value("${ovsx.registry.version:}") String registryVersion; + @Value("${ovsx.trusted-publishing.enabled:false}") + boolean trustedPublishingEnabled; + @Override public NamespaceJson getNamespace(String namespaceName) { return getNamespace(namespaceName, false); @@ -1323,6 +1326,7 @@ public RegistryVersionJson getRegistryVersion() { var json = new RegistryVersionJson(); json.setVersion(registryVersion); json.setMaxExtensionSize(publishingConfig.getMaxContentSize()); + json.setTrustedPublishingEnabled(trustedPublishingEnabled); return json; } } diff --git a/server/src/main/java/org/eclipse/openvsx/json/RegistryVersionJson.java b/server/src/main/java/org/eclipse/openvsx/json/RegistryVersionJson.java index f27bdbf2d..ea5eb46c1 100644 --- a/server/src/main/java/org/eclipse/openvsx/json/RegistryVersionJson.java +++ b/server/src/main/java/org/eclipse/openvsx/json/RegistryVersionJson.java @@ -32,6 +32,9 @@ public static RegistryVersionJson error(String message) { @Schema(description = "Maximum allowed extension package size in bytes") private long maxExtensionSize; + @Schema(description = "Whether trusted publishing is enabled on the registry") + private boolean trustedPublishingEnabled; + public String getVersion() { return version; } @@ -47,4 +50,12 @@ public long getMaxExtensionSize() { public void setMaxExtensionSize(long maxExtensionSize) { this.maxExtensionSize = maxExtensionSize; } + + public boolean isTrustedPublishingEnabled() { + return trustedPublishingEnabled; + } + + public void setTrustedPublishingEnabled(boolean trustedPublishingEnabled) { + this.trustedPublishingEnabled = trustedPublishingEnabled; + } } From cce5d51c41f1be2e01e260ccb873e63458b28072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Tue, 28 Jul 2026 11:34:54 +0200 Subject: [PATCH 2/2] feat(webui): using TP enabled check to show/hide entrypoints --- .../extension/extension-detail-view.tsx | 7 ++++- .../namespace/namespace-detail-view.tsx | 9 ++++++ webui/src/extension-registry-types.ts | 1 + webui/src/hooks/use-registry-value.ts | 28 +++++++++++++++++++ .../pages/user/publish-extension-dialog.tsx | 3 +- .../use-trusted-publishers.ts | 8 ++++++ webui/src/pages/user/user-setting-tabs.tsx | 28 +++++++++++-------- webui/src/pages/user/user-settings-tokens.tsx | 11 ++++++-- webui/src/pages/user/user-settings.tsx | 5 +++- 9 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 webui/src/hooks/use-registry-value.ts diff --git a/webui/src/components/extension/extension-detail-view.tsx b/webui/src/components/extension/extension-detail-view.tsx index b36bd7cb1..e99382420 100644 --- a/webui/src/components/extension/extension-detail-view.tsx +++ b/webui/src/components/extension/extension-detail-view.tsx @@ -21,6 +21,8 @@ import { ExtensionVersionTable } from './extension-version-table'; import { DeleteVersionDialog, VersionDeleteTarget } from './extension-version-delete-dialog'; import { DeleteAllVersionsDialog } from './extension-delete-all-versions-dialog'; import { ExtensionTrustedPublishers } from '../../pages/user/trusted-publishing/trusted-publishers-section'; +import { isTrustedPublishingEnabled } from '../../pages/user/trusted-publishing/use-trusted-publishers'; +import { useRegistryValue } from '../../hooks/use-registry-value'; import { ExtensionDetailRoutes } from '../../pages/extension-detail/extension-detail-routes'; import { createRoute } from '../../utils'; @@ -33,6 +35,7 @@ export const ExtensionDetailView: FunctionComponent = const [purgeDialogVersion, setPurgeDialogVersion] = useState(null); const [deleteAllOpen, setDeleteAllOpen] = useState(false); const [purgeAllOpen, setPurgeAllOpen] = useState(false); + const trustedPublishingEnabled = useRegistryValue(isTrustedPublishingEnabled); useEffect(() => { setPage(0); @@ -81,7 +84,9 @@ export const ExtensionDetailView: FunctionComponent = )} {actions} - + {trustedPublishingEnabled ? ( + + ) : null} Versions diff --git a/webui/src/components/namespace/namespace-detail-view.tsx b/webui/src/components/namespace/namespace-detail-view.tsx index abaac4a09..a79096c48 100644 --- a/webui/src/components/namespace/namespace-detail-view.tsx +++ b/webui/src/components/namespace/namespace-detail-view.tsx @@ -16,6 +16,9 @@ import { NamespaceExtensionList, FetchNamespaceExtension } from './namespace-ext import { NamespaceMemberList } from './namespace-member-list'; import { NamespaceDetails } from './namespace-details'; import { Namespace, UserData } from '../../extension-registry-types'; +import { UserNamespaceTrustedPublishers } from '../../pages/user/trusted-publishing/trusted-publishers-section'; +import { isTrustedPublishingEnabled } from '../../pages/user/trusted-publishing/use-trusted-publishers'; +import { useRegistryValue } from '../../hooks/use-registry-value'; export interface NamespaceDetailConfig { defaultMemberRole?: 'contributor' | 'owner'; @@ -64,6 +67,7 @@ const NamespaceHeader = styled(Box)(({ theme }: { theme: Theme }) => ({ * - `fetchExtension` selects the endpoint used to load each extension (public vs. admin). */ export const NamespaceDetailView: FunctionComponent = props => { + const trustedPublishingEnabled = useRegistryValue(isTrustedPublishingEnabled); const warningColor = props.theme === 'dark' ? '#fff' : '#151515'; return ( @@ -110,6 +114,11 @@ export const NamespaceDetailView: FunctionComponent = ) : null} + {props.namespace.trustedPublishingUrl && trustedPublishingEnabled ? ( + + + + ) : null} (select: (version: RegistryVersion) => T): T | undefined => { + const { version } = useContext(MainContext); + return version != null ? select(version) : undefined; +}; diff --git a/webui/src/pages/user/publish-extension-dialog.tsx b/webui/src/pages/user/publish-extension-dialog.tsx index 38e698791..1494d27db 100644 --- a/webui/src/pages/user/publish-extension-dialog.tsx +++ b/webui/src/pages/user/publish-extension-dialog.tsx @@ -15,6 +15,7 @@ import Dropzone, { FileRejection } from 'react-dropzone'; import { ButtonWithProgress } from '../../components/button-with-progress'; import { ErrorResult, isError } from '../../extension-registry-types'; import { MainContext } from '../../context'; +import { useRegistryValue } from '../../hooks/use-registry-value'; import { styled, Theme } from '@mui/material/styles'; const getColor = (isFocused: boolean, isDragAccept: boolean, isDragReject: boolean) => { @@ -52,7 +53,7 @@ export const PublishExtensionDialog: FunctionComponent(); const context = useContext(MainContext); - const effectiveMaxSize = context.version?.maxExtensionSize ?? 512 * 1024 * 1024; + const effectiveMaxSize = useRegistryValue(version => version.maxExtensionSize) ?? 512 * 1024 * 1024; const abortController = useRef(new AbortController()); useEffect(() => { diff --git a/webui/src/pages/user/trusted-publishing/use-trusted-publishers.ts b/webui/src/pages/user/trusted-publishing/use-trusted-publishers.ts index b95c5a329..8bf0f2968 100644 --- a/webui/src/pages/user/trusted-publishing/use-trusted-publishers.ts +++ b/webui/src/pages/user/trusted-publishing/use-trusted-publishers.ts @@ -15,6 +15,7 @@ import { useContext } from 'react'; import { useMutation, useQueries, useQuery, useQueryClient } from '@tanstack/react-query'; import { MainContext } from '../../../context'; import { + RegistryVersion, TrustedPublisher, TrustedPublisherProvider, TrustedPublisherRequest, @@ -22,6 +23,13 @@ import { } from '../../../extension-registry-types'; import { controllerFromSignal } from '../../../query-client'; +/** + * Whether the registry has trusted publishing enabled server-side. Pass this to + * `useRegistryValue` to hide trusted-publishing UI when the feature is off. + */ +export const isTrustedPublishingEnabled = (version: RegistryVersion): boolean => + Boolean(version.trustedPublishingEnabled); + // keyed by the namespace's trusted-publishing URL (the endpoint we actually query) export const trustedPublisherKeys = { providers: (trustedPublishingUrl?: UrlString) => diff --git a/webui/src/pages/user/user-setting-tabs.tsx b/webui/src/pages/user/user-setting-tabs.tsx index 2591ed529..fa454aa22 100644 --- a/webui/src/pages/user/user-setting-tabs.tsx +++ b/webui/src/pages/user/user-setting-tabs.tsx @@ -8,26 +8,20 @@ * SPDX-License-Identifier: EPL-2.0 ********************************************************************************/ -import { ReactElement } from 'react'; +import { ReactElement, useMemo } from 'react'; import { Tabs, Tab, useTheme, useMediaQuery } from '@mui/material'; import { useNavigate, useParams } from 'react-router'; import { createRoute } from '../../utils'; import { UserSettingsRoutes } from './user-settings-routes'; - -const TABS = [ - { value: 'profile', label: 'Profile' }, - { value: 'tokens', label: 'Access Tokens' }, - { value: 'trusted-publishers', label: 'Trusted Publishers' }, - { value: 'namespaces', label: 'Namespaces' }, - { value: 'extensions', label: 'Extensions' }, - { value: 'customers', label: 'Rate Limiting' } -]; +import { useRegistryValue } from '../../hooks/use-registry-value'; +import { isTrustedPublishingEnabled } from './trusted-publishing/use-trusted-publishers'; export const UserSettingTabs = (): ReactElement => { const theme = useTheme(); const isATablet = useMediaQuery(theme.breakpoints.down('md')); const isAMobile = useMediaQuery(theme.breakpoints.down('sm')); const { tab } = useParams(); + const trustedPublishingEnabled = useRegistryValue(isTrustedPublishingEnabled); const navigate = useNavigate(); @@ -35,13 +29,25 @@ export const UserSettingTabs = (): ReactElement => { return createRoute([UserSettingsRoutes.ROOT, tab]); }; + const tabs = useMemo( + () => [ + { value: 'profile', label: 'Profile' }, + { value: 'tokens', label: 'Access Tokens' }, + ...(trustedPublishingEnabled ? [{ value: 'trusted-publishers', label: 'Trusted Publishers' }] : []), + { value: 'namespaces', label: 'Namespaces' }, + { value: 'extensions', label: 'Extensions' }, + { value: 'customers', label: 'Rate Limiting' } + ], + [trustedPublishingEnabled] + ); + return ( - {TABS.map(({ value, label }) => ( + {tabs.map(({ value, label }) => ( // MUI's Tabs only fires `onChange` when the clicked tab differs from the // currently selected one, so a per-Tab `onClick` is used instead to also // navigate when the already-active tab is clicked. diff --git a/webui/src/pages/user/user-settings-tokens.tsx b/webui/src/pages/user/user-settings-tokens.tsx index c6af1ff0e..5f93ec3d1 100644 --- a/webui/src/pages/user/user-settings-tokens.tsx +++ b/webui/src/pages/user/user-settings-tokens.tsx @@ -30,6 +30,8 @@ import { PersonalAccessToken } from '../../extension-registry-types'; import { MainContext } from '../../context'; import { GenerateAccessTokenDialog } from './generate-access-token-dialog'; import { TrustedPublishingPromo } from './trusted-publishing/trusted-publishing-promo'; +import { isTrustedPublishingEnabled } from './trusted-publishing/use-trusted-publishers'; +import { useRegistryValue } from '../../hooks/use-registry-value'; import { UserSettingsRoutes } from './user-settings-routes'; const link = ({ theme }: { theme: Theme }) => ({ @@ -56,6 +58,7 @@ const DeleteButton = styled(Button)(({ theme }: { theme: Theme }) => ({ export const UserSettingsTokens: FunctionComponent = () => { const { service, user, handleError, pageSettings } = useContext(MainContext); + const trustedPublishingEnabled = useRegistryValue(isTrustedPublishingEnabled); const [tokens, setTokens] = useState(new Array()); const [loading, setLoading] = useState(true); @@ -203,9 +206,11 @@ export const UserSettingsTokens: FunctionComponent = () => { - - - + {trustedPublishingEnabled ? ( + + + + ) : null} {tokens.length === 0 && !loading ? ( You currently have no tokens. diff --git a/webui/src/pages/user/user-settings.tsx b/webui/src/pages/user/user-settings.tsx index 0b95f081e..1cc59c189 100644 --- a/webui/src/pages/user/user-settings.tsx +++ b/webui/src/pages/user/user-settings.tsx @@ -17,6 +17,8 @@ import { DelayedLoadIndicator } from '../../components/delayed-load-indicator'; import { UserSettingTabs } from './user-setting-tabs'; import { UserSettingsTokens } from './user-settings-tokens'; import { UserSettingsTrustedPublishers } from './trusted-publishing/user-settings-trusted-publishers'; +import { isTrustedPublishingEnabled } from './trusted-publishing/use-trusted-publishers'; +import { useRegistryValue } from '../../hooks/use-registry-value'; import { UserSettingsProfile } from './user-settings-profile'; import { UserSettingsNamespaces } from './user-settings-namespaces'; import { UserSettingsExtensions } from './user-settings-extensions'; @@ -29,6 +31,7 @@ import { UserSettingsExtensionSettings } from './user-settings-extension'; export const UserSettings: FunctionComponent = props => { const { pageSettings, user, loginProviders } = useContext(MainContext); const { tab, namespace, extension } = useParams(); + const trustedPublishingEnabled = useRegistryValue(isTrustedPublishingEnabled); const renderTab = (user: UserData, tab?: string, namespace?: string, extension?: string): ReactNode => { if (tab == null && namespace != null && extension != null) { @@ -41,7 +44,7 @@ export const UserSettings: FunctionComponent = props => { case 'tokens': return ; case 'trusted-publishers': - return ; + return trustedPublishingEnabled ? : null; case 'namespaces': return ; case 'extensions':