Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -1323,6 +1326,7 @@ public RegistryVersionJson getRegistryVersion() {
var json = new RegistryVersionJson();
json.setVersion(registryVersion);
json.setMaxExtensionSize(publishingConfig.getMaxContentSize());
json.setTrustedPublishingEnabled(trustedPublishingEnabled);
return json;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
}
7 changes: 6 additions & 1 deletion webui/src/components/extension/extension-detail-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -33,6 +35,7 @@ export const ExtensionDetailView: FunctionComponent<ExtensionDetailViewProps> =
const [purgeDialogVersion, setPurgeDialogVersion] = useState<VersionTargetPlatforms | null>(null);
const [deleteAllOpen, setDeleteAllOpen] = useState(false);
const [purgeAllOpen, setPurgeAllOpen] = useState(false);
const trustedPublishingEnabled = useRegistryValue(isTrustedPublishingEnabled);

useEffect(() => {
setPage(0);
Expand Down Expand Up @@ -81,7 +84,9 @@ export const ExtensionDetailView: FunctionComponent<ExtensionDetailViewProps> =
)}
{actions}
</Stack>
<ExtensionTrustedPublishers namespace={extension.namespace} extension={extension.name} />
{trustedPublishingEnabled ? (
<ExtensionTrustedPublishers namespace={extension.namespace} extension={extension.name} />
) : null}
<Typography variant='h6' gutterBottom sx={{ mt: 4 }}>
Versions
</Typography>
Expand Down
9 changes: 9 additions & 0 deletions webui/src/components/namespace/namespace-detail-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<NamespaceDetailViewProps> = props => {
const trustedPublishingEnabled = useRegistryValue(isTrustedPublishingEnabled);
const warningColor = props.theme === 'dark' ? '#fff' : '#151515';
return (
<NamespaceDetailContainer container direction='column' spacing={4}>
Expand Down Expand Up @@ -110,6 +114,11 @@ export const NamespaceDetailView: FunctionComponent<NamespaceDetailViewProps> =
<NamespaceDetails namespace={props.namespace} />
</Grid>
) : null}
{props.namespace.trustedPublishingUrl && trustedPublishingEnabled ? (
<Grid item>
<UserNamespaceTrustedPublishers namespace={props.namespace} />
</Grid>
) : null}
<Grid item>
<NamespaceExtensionList
namespace={props.namespace}
Expand Down
1 change: 1 addition & 0 deletions webui/src/extension-registry-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ export interface TargetPlatformVersion {
export interface RegistryVersion {
version: string;
maxExtensionSize?: number;
trustedPublishingEnabled?: boolean;
}

export interface LoginProviders {
Expand Down
28 changes: 28 additions & 0 deletions webui/src/hooks/use-registry-value.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/******************************************************************************
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*****************************************************************************/

import { useContext } from 'react';
import { MainContext } from '../context';
import { RegistryVersion } from '../extension-registry-types';

/**
* Derives a value from the registry version (from `MainContext`, populated from
* `/api/version`) via `select`, so consumers can read server-provided config or
* feature flags without touching the context. Returns `undefined` until the
* version has loaded, or if it failed to load — so feature gates that treat that
* as falsy fail closed, and other callers can supply their own fallback (`??`).
*/
export const useRegistryValue = <T>(select: (version: RegistryVersion) => T): T | undefined => {
const { version } = useContext(MainContext);
return version != null ? select(version) : undefined;
};
3 changes: 2 additions & 1 deletion webui/src/pages/user/publish-extension-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -52,7 +53,7 @@ export const PublishExtensionDialog: FunctionComponent<PublishExtensionDialogPro
const [rejectedFile, setRejectedFile] = useState<File>();

const context = useContext(MainContext);
const effectiveMaxSize = context.version?.maxExtensionSize ?? 512 * 1024 * 1024;
const effectiveMaxSize = useRegistryValue(version => version.maxExtensionSize) ?? 512 * 1024 * 1024;
const abortController = useRef<AbortController>(new AbortController());

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,21 @@ import { useContext } from 'react';
import { useMutation, useQueries, useQuery, useQueryClient } from '@tanstack/react-query';
import { MainContext } from '../../../context';
import {
RegistryVersion,
TrustedPublisher,
TrustedPublisherProvider,
TrustedPublisherRequest,
UrlString
} 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) =>
Expand Down
28 changes: 17 additions & 11 deletions webui/src/pages/user/user-setting-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,46 @@
* 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();

const generateRoute = (tab: string) => {
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
value={tab ?? 'extensions'}
orientation={isATablet ? 'horizontal' : 'vertical'}
centered={isAMobile}
indicatorColor='secondary'>
{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.
Expand Down
11 changes: 8 additions & 3 deletions webui/src/pages/user/user-settings-tokens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => ({
Expand All @@ -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<PersonalAccessToken>());
const [loading, setLoading] = useState(true);
Expand Down Expand Up @@ -203,9 +206,11 @@ export const UserSettingsTokens: FunctionComponent = () => {
</Box>
</Box>
</Box>
<Box mt={2}>
<TrustedPublishingPromo />
</Box>
{trustedPublishingEnabled ? (
<Box mt={2}>
<TrustedPublishingPromo />
</Box>
) : null}
<Box mt={2}>
{tokens.length === 0 && !loading ? (
<EmptyTypography variant='body1'>You currently have no tokens.</EmptyTypography>
Expand Down
5 changes: 4 additions & 1 deletion webui/src/pages/user/user-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -29,6 +31,7 @@ import { UserSettingsExtensionSettings } from './user-settings-extension';
export const UserSettings: FunctionComponent<UserSettingsProps> = 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) {
Expand All @@ -41,7 +44,7 @@ export const UserSettings: FunctionComponent<UserSettingsProps> = props => {
case 'tokens':
return <UserSettingsTokens />;
case 'trusted-publishers':
return <UserSettingsTrustedPublishers />;
return trustedPublishingEnabled ? <UserSettingsTrustedPublishers /> : null;
case 'namespaces':
return <UserSettingsNamespaces />;
case 'extensions':
Expand Down