diff --git a/server/src/dev/resources/application.yml b/server/src/dev/resources/application.yml
index dcce629a4..4aa55fdf2 100644
--- a/server/src/dev/resources/application.yml
+++ b/server/src/dev/resources/application.yml
@@ -148,7 +148,7 @@ ovsx:
databasesearch:
enabled: false
elasticsearch:
- enabled: true
+ enabled: false
clear-on-start: true
redis:
enabled: false
diff --git a/webui/src/default/agreement.tsx b/webui/src/default/agreement.tsx
new file mode 100644
index 000000000..7f1432112
--- /dev/null
+++ b/webui/src/default/agreement.tsx
@@ -0,0 +1,32 @@
+import { Box, styled, Theme, Typography } from "@mui/material";
+import { Link as RouteLink } from 'react-router-dom';
+import React, { FC } from "react";
+import { PageSettings } from "../page-settings";
+import { UserSettingsRoutes } from "../pages/user/user-settings";
+
+const link = ({ theme }: { theme: Theme }) => ({
+ color: theme.palette.secondary.main,
+ textDecoration: 'none',
+ '&:hover': {
+ textDecoration: 'underline'
+ }
+});
+
+const EmptyTypography = styled(Typography)(({ theme }: { theme: Theme }) => ({
+ [theme.breakpoints.down('sm')]: {
+ textAlign: 'center'
+ }
+}));
+
+const StyledRouteLink = styled(RouteLink)(link);
+
+
+export const DefaultAgreementNotSignedContent: FC<{ pageSettings: PageSettings }> = ({ pageSettings }) => (
+
+
+ Access tokens cannot be created as you currently do not have a {pageSettings.agreement.name} signed. Please return to
+ your Profile page
+ to sign the Publisher Agreement.
+
+
+);
\ No newline at end of file
diff --git a/webui/src/default/page-settings.tsx b/webui/src/default/page-settings.tsx
index d03eba95f..91626379b 100644
--- a/webui/src/default/page-settings.tsx
+++ b/webui/src/default/page-settings.tsx
@@ -21,6 +21,7 @@ import { DefaultMenuContent, MobileMenuContent } from './menu-content';
import OpenVSXLogo from './openvsx-registry-logo';
import About from './about';
import { createAbsoluteURL } from '../utils';
+import { DefaultAgreementNotSignedContent } from './agreement';
export default function createPageSettings(prefersDarkMode: boolean, serverUrl: string, serverVersionPromise: Promise): PageSettings {
const toolbarContent: FunctionComponent = () =>
@@ -124,7 +125,13 @@ export default function createPageSettings(prefersDarkMode: boolean, serverUrl:
additionalRoutes,
mainHeadTags,
extensionHeadTags,
- namespaceHeadTags
+ namespaceHeadTags,
+ agreement: {
+ notSignedContent: DefaultAgreementNotSignedContent,
+ }
+ },
+ agreement: {
+ name: 'Eclipse Foundation Open VSX Publisher Agreement',
},
urls: {
extensionDefaultIcon: '/default-icon.png',
diff --git a/webui/src/extension-registry-service.ts b/webui/src/extension-registry-service.ts
index c90357ba0..625728afb 100644
--- a/webui/src/extension-registry-service.ts
+++ b/webui/src/extension-registry-service.ts
@@ -12,7 +12,7 @@ import {
Extension, UserData, ExtensionCategory, ExtensionReviewList, PersonalAccessToken, SearchResult, NewReview,
SuccessResult, ErrorResult, CsrfTokenJson, isError, Namespace, NamespaceDetails, MembershipRole, SortBy,
SortOrder, UrlString, NamespaceMembershipList, PublisherInfo, SearchEntry, RegistryVersion,
- LoginProviders
+ LoginProviders, Tier, RefillStrategy
} from './extension-registry-types';
import { createAbsoluteURL, addQuery } from './utils';
import { sendRequest, ErrorResponse } from './server-request';
@@ -486,15 +486,47 @@ export interface AdminService {
getPublisherInfo(abortController: AbortController, provider: string, login: string): Promise>
revokePublisherContributions(abortController: AbortController, provider: string, login: string): Promise>
revokeAccessTokens(abortController: AbortController, provider: string, login: string): Promise>
+ getAllTiers(): Promise>;
+ getTierById(id: number): Promise>;
+ createTier(tier: Omit): Promise>;
+ updateTier(id: number, tier: Omit): Promise>;
+ deleteTier(id: number): Promise>;
}
-export interface AdminServiceConstructor {
- new (registry: ExtensionRegistryService): AdminService
-}
+export type AdminServiceConstructor = new (registry: ExtensionRegistryService) => AdminService;
export class AdminServiceImpl implements AdminService {
- constructor(readonly registry: ExtensionRegistryService) {}
+ constructor(readonly registry: ExtensionRegistryService) {
+ this.initializeMockTiers();
+ }
+
+ private readonly tierCounter = { value: 1 };
+ private readonly mockTiers: Map = new Map();
+
+ private initializeMockTiers(): void {
+ if (this.mockTiers.size === 0) {
+ const sampleTiers: Tier[] = [
+ {
+ id: this.tierCounter.value++,
+ name: 'Free',
+ description: 'Free tier with basic rate limiting',
+ capacity: 100,
+ duration: 3600,
+ refillStrategy: RefillStrategy.GREEDY
+ },
+ {
+ id: this.tierCounter.value++,
+ name: 'Professional',
+ description: 'Professional tier with higher rate limits',
+ capacity: 1000,
+ duration: 3600,
+ refillStrategy: RefillStrategy.GREEDY
+ }
+ ];
+ sampleTiers.forEach(tier => this.mockTiers.set(tier.id, tier));
+ }
+ }
getExtension(abortController: AbortController, namespace: string, extension: string): Promise> {
return sendRequest({
@@ -609,6 +641,48 @@ export class AdminServiceImpl implements AdminService {
headers
});
}
+
+ async getAllTiers(): Promise> {
+ return Array.from(this.mockTiers.values());
+ }
+
+ async getTierById(id: number): Promise> {
+ const tier = this.mockTiers.get(id);
+ if (!tier) {
+ throw new Error(`Tier with ID ${id} not found`);
+ }
+ return tier;
+ }
+
+ async createTier(tier: Omit): Promise> {
+ const newTier: Tier = {
+ id: this.tierCounter.value++,
+ ...tier
+ };
+ this.mockTiers.set(newTier.id, newTier);
+ return newTier;
+ }
+
+ async updateTier(id: number, tier: Omit): Promise> {
+ const existingTier = this.mockTiers.get(id);
+ if (!existingTier) {
+ throw new Error(`Tier with ID ${id} not found`);
+ }
+
+ const updatedTier: Tier = {
+ ...existingTier,
+ ...tier
+ };
+ this.mockTiers.set(id, updatedTier);
+ return updatedTier;
+ }
+
+ async deleteTier(id: number): Promise {
+ if (!this.mockTiers.has(id)) {
+ throw new Error(`Tier with ID ${id} not found`);
+ }
+ this.mockTiers.delete(id);
+ }
}
export interface ExtensionFilter {
diff --git a/webui/src/extension-registry-types.ts b/webui/src/extension-registry-types.ts
index 6d50efb48..9d789ff1b 100644
--- a/webui/src/extension-registry-types.ts
+++ b/webui/src/extension-registry-types.ts
@@ -258,3 +258,17 @@ export interface LoginProviders {
export type MembershipRole = 'contributor' | 'owner';
export type SortBy = 'relevance' | 'timestamp' | 'rating' | 'downloadCount';
export type SortOrder = 'asc' | 'desc';
+
+export enum RefillStrategy {
+ GREEDY = 'GREEDY',
+ INTERVAL = 'INTERVAL',
+}
+
+export interface Tier {
+ id: number;
+ name: string;
+ description?: string;
+ capacity: number;
+ duration: number;
+ refillStrategy: RefillStrategy;
+}
\ No newline at end of file
diff --git a/webui/src/page-settings.ts b/webui/src/page-settings.ts
index 93f0d1aef..ccf3d0688 100644
--- a/webui/src/page-settings.ts
+++ b/webui/src/page-settings.ts
@@ -46,6 +46,12 @@ export interface PageSettings {
mainHeadTags?: ComponentType<{ pageSettings: PageSettings }>;
extensionHeadTags?: ComponentType<{ extension?: Extension, pageSettings: PageSettings }>;
namespaceHeadTags?: ComponentType<{ namespaceDetails?: NamespaceDetails, name: string, pageSettings: PageSettings }>;
+ agreement: {
+ notSignedContent: ComponentType<{ pageSettings: PageSettings }>;
+ }
+ };
+ agreement: {
+ name: string;
};
urls: {
extensionDefaultIcon: string;
diff --git a/webui/src/pages/admin-dashboard/admin-dashboard.tsx b/webui/src/pages/admin-dashboard/admin-dashboard.tsx
index 4df98e83f..8f1b2ed3c 100644
--- a/webui/src/pages/admin-dashboard/admin-dashboard.tsx
+++ b/webui/src/pages/admin-dashboard/admin-dashboard.tsx
@@ -23,6 +23,8 @@ import HighlightOffIcon from '@mui/icons-material/HighlightOff';
import { Welcome } from './welcome';
import { PublisherAdmin } from './publisher-admin';
import PersonIcon from '@mui/icons-material/Person';
+import StarIcon from '@mui/icons-material/Star';
+import { Tiers } from './tiers/tiers';
export namespace AdminDashboardRoutes {
export const ROOT = 'admin-dashboard';
@@ -30,6 +32,7 @@ export namespace AdminDashboardRoutes {
export const NAMESPACE_ADMIN = createRoute([ROOT, 'namespaces']);
export const EXTENSION_ADMIN = createRoute([ROOT, 'extensions']);
export const PUBLISHER_ADMIN = createRoute([ROOT, 'publisher']);
+ export const TIERS = createRoute([ROOT, 'tiers']);
}
const Message: FunctionComponent<{message: string}> = ({ message }) => {
@@ -59,6 +62,7 @@ export const AdminDashboard: FunctionComponent = props => {
} route={AdminDashboardRoutes.NAMESPACE_ADMIN} />
} route={AdminDashboardRoutes.EXTENSION_ADMIN} />
} route={AdminDashboardRoutes.PUBLISHER_ADMIN} />
+ } route={AdminDashboardRoutes.TIERS} />
@@ -69,6 +73,7 @@ export const AdminDashboard: FunctionComponent = props => {
} />
} />
} />
+ } />
} />
diff --git a/webui/src/pages/admin-dashboard/publisher-admin.tsx b/webui/src/pages/admin-dashboard/publisher-admin.tsx
index 7595ab14e..d3eabebd7 100644
--- a/webui/src/pages/admin-dashboard/publisher-admin.tsx
+++ b/webui/src/pages/admin-dashboard/publisher-admin.tsx
@@ -39,13 +39,13 @@ export const PublisherAdmin: FunctionComponent = props => {
const publisherName = inputValue;
try {
setLoading(true);
- if (publisherName !== '') {
- const publisher = await service.admin.getPublisherInfo(abortController.current, 'github', publisherName);
+ if (publisherName === '') {
setNotFound('');
- setPublisher(publisher);
+ setPublisher(undefined);
} else {
+ const publisher = await service.admin.getPublisherInfo(abortController.current, 'github', publisherName);
setNotFound('');
- setPublisher(undefined);
+ setPublisher(publisher);
}
setLoading(false);
} catch (err) {
@@ -66,7 +66,7 @@ export const PublisherAdmin: FunctionComponent = props => {
let listContainer: ReactNode = '';
if (publisher && pageSettings && user) {
listContainer =
-
+
;
} else if (notFound) {
listContainer =
diff --git a/webui/src/pages/admin-dashboard/publisher-details.tsx b/webui/src/pages/admin-dashboard/publisher-details.tsx
index 4a0219c56..fd6c0c84f 100644
--- a/webui/src/pages/admin-dashboard/publisher-details.tsx
+++ b/webui/src/pages/admin-dashboard/publisher-details.tsx
@@ -15,14 +15,16 @@ import { UserExtensionList } from '../user/user-extension-list';
import { Box, Typography } from '@mui/material';
import { PublisherRevokeDialog } from './publisher-revoke-dialog';
import { PublisherRevokeTokensButton } from './publisher-revoke-tokens-button';
+import { PageSettings } from '../../page-settings';
interface PublisherDetailsProps {
publisherInfo: PublisherInfo;
+ agreement: PageSettings['agreement'];
}
export const PublisherDetails: FunctionComponent = props => {
return
-
+
Access Tokens
diff --git a/webui/src/pages/admin-dashboard/tiers/delete-tier-dialog.tsx b/webui/src/pages/admin-dashboard/tiers/delete-tier-dialog.tsx
new file mode 100644
index 000000000..474216e82
--- /dev/null
+++ b/webui/src/pages/admin-dashboard/tiers/delete-tier-dialog.tsx
@@ -0,0 +1,68 @@
+import React, { FC, useState } from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ Typography,
+ CircularProgress,
+ Alert
+} from '@mui/material';
+import type { Tier } from '../../../extension-registry-types';
+
+interface DeleteTierDialogProps {
+ open: boolean;
+ tier?: Tier;
+ onClose: () => void;
+ onConfirm: () => Promise;
+}
+
+export const DeleteTierDialog: FC = ({ open, tier, onClose, onConfirm }) => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const handleConfirm = async () => {
+ try {
+ setError(null);
+ setLoading(true);
+ await onConfirm();
+ onClose();
+ } catch (err: any) {
+ setError(err.message || 'An error occurred while deleting the tier');
+ setLoading(false);
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/webui/src/pages/admin-dashboard/tiers/tier-form-dialog.tsx b/webui/src/pages/admin-dashboard/tiers/tier-form-dialog.tsx
new file mode 100644
index 000000000..366e8aae2
--- /dev/null
+++ b/webui/src/pages/admin-dashboard/tiers/tier-form-dialog.tsx
@@ -0,0 +1,240 @@
+import React, { FC, useState, useEffect } from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ TextField,
+ Button,
+ FormControl,
+ InputLabel,
+ Select,
+ MenuItem,
+ CircularProgress,
+ Alert,
+ Box
+} from '@mui/material';
+import type { SelectChangeEvent } from '@mui/material';
+import { RefillStrategy, type Tier } from "../../../extension-registry-types";
+
+type DurationUnit = 'seconds' | 'minutes' | 'hours' | 'days';
+
+const DURATION_MULTIPLIERS: Record = {
+ seconds: 1,
+ minutes: 60,
+ hours: 3600,
+ days: 86400
+};
+
+interface TierFormDialogProps {
+ open: boolean;
+ tier?: Tier;
+ onClose: () => void;
+ onSubmit: (formData: Omit) => Promise;
+}
+
+export const TierFormDialog: FC = ({ open, tier, onClose, onSubmit }) => {
+ const [formData, setFormData] = useState>({
+ name: '',
+ description: '',
+ capacity: 100,
+ duration: 3600,
+ refillStrategy: RefillStrategy.INTERVAL
+ });
+ const [durationValue, setDurationValue] = useState(1);
+ const [durationUnit, setDurationUnit] = useState('hours');
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const getDurationInSeconds = (): number => {
+ return durationValue * DURATION_MULTIPLIERS[durationUnit];
+ };
+
+ useEffect(() => {
+ if (tier) {
+ setFormData(prev => ({
+ name: tier.name,
+ description: tier.description || '',
+ capacity: tier.capacity,
+ duration: tier.duration,
+ refillStrategy: tier.refillStrategy as any
+ }));
+ // Convert duration seconds to hours for display
+ setDurationValue(Math.floor(tier.duration / 3600));
+ setDurationUnit('hours');
+ } else {
+ setFormData(prev => ({
+ ...prev,
+ name: '',
+ description: '',
+ capacity: 100,
+ duration: 3600,
+ refillStrategy: RefillStrategy.INTERVAL
+ }));
+ setDurationValue(1);
+ setDurationUnit('hours');
+ }
+ setError(null);
+ }, [open, tier]);
+
+ const handleChange = (e: React.ChangeEvent | SelectChangeEvent) => {
+ const { name, value } = e.target as any;
+ setFormData((prev: Omit) => ({
+ ...prev,
+ [name]: name === 'capacity' || name === 'duration' ? Number.parseInt(value as string, 10) : value
+ } as Omit));
+ };
+
+ const handleSubmit = async () => {
+ try {
+ setError(null);
+ setLoading(true);
+
+ // Basic validation
+ if (!formData.name.trim()) {
+ setError('Tier name is required');
+ setLoading(false);
+ return;
+ }
+
+ if (formData.capacity <= 0) {
+ setError('Capacity must be greater than 0');
+ setLoading(false);
+ return;
+ }
+
+ if (durationValue <= 0) {
+ setError('Duration must be greater than 0');
+ setLoading(false);
+ return;
+ }
+
+ const durationInSeconds = getDurationInSeconds();
+ await onSubmit({
+ ...formData,
+ duration: durationInSeconds
+ });
+ onClose();
+ } catch (err: any) {
+ setError(err.message || 'An error occurred while saving the tier');
+ setLoading(false);
+ }
+ };
+
+ const isEditMode = !!tier;
+ const title = isEditMode ? 'Edit Tier' : 'Create New Tier';
+
+ return (
+
+ );
+};
diff --git a/webui/src/pages/admin-dashboard/tiers/tiers.tsx b/webui/src/pages/admin-dashboard/tiers/tiers.tsx
new file mode 100644
index 000000000..f5cdc02c1
--- /dev/null
+++ b/webui/src/pages/admin-dashboard/tiers/tiers.tsx
@@ -0,0 +1,209 @@
+import React, { FC, useState, useEffect } from "react";
+import {
+ Box,
+ Button,
+ Paper,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Typography,
+ CircularProgress,
+ Alert,
+ IconButton,
+ Stack
+} from "@mui/material";
+import EditIcon from "@mui/icons-material/Edit";
+import DeleteIcon from "@mui/icons-material/Delete";
+import AddIcon from "@mui/icons-material/Add";
+import { MainContext } from "../../../context";
+import type { Tier, TierFormData } from "../../../extension-registry-types";
+import { TierFormDialog } from "./tier-form-dialog";
+import { DeleteTierDialog } from "./delete-tier-dialog";
+
+export const Tiers: FC = () => {
+ const { service } = React.useContext(MainContext);
+ const [tiers, setTiers] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [formDialogOpen, setFormDialogOpen] = useState(false);
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+ const [selectedTier, setSelectedTier] = useState();
+
+ // Load all tiers
+ const loadTiers = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+ const data = await service.admin.getAllTiers();
+ setTiers(data as Tier[]);
+ } catch (err: any) {
+ setError(err.message || "Failed to load tiers");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ loadTiers();
+ }, []);
+
+ const handleCreateClick = () => {
+ setSelectedTier(undefined);
+ setFormDialogOpen(true);
+ };
+
+ const handleEditClick = (tier: Tier) => {
+ setSelectedTier(tier);
+ setFormDialogOpen(true);
+ };
+
+ const handleDeleteClick = (tier: Tier) => {
+ setSelectedTier(tier);
+ setDeleteDialogOpen(true);
+ };
+
+ const handleFormSubmit = async (formData: TierFormData) => {
+ try {
+ if (selectedTier) {
+ // Update existing tier
+ await service.admin.updateTier(selectedTier.id, formData);
+ } else {
+ // Create new tier
+ await service.admin.createTier(formData);
+ }
+ await loadTiers();
+ } catch (err: any) {
+ throw new Error(err.message || "Failed to save tier");
+ }
+ };
+
+ const handleDeleteConfirm = async () => {
+ try {
+ if (selectedTier) {
+ await service.admin.deleteTier(selectedTier.id);
+ await loadTiers();
+ }
+ } catch (err: any) {
+ throw new Error(err.message || "Failed to delete tier");
+ }
+ };
+
+ const handleFormDialogClose = () => {
+ setFormDialogOpen(false);
+ setSelectedTier(undefined);
+ };
+
+ const handleDeleteDialogClose = () => {
+ setDeleteDialogOpen(false);
+ setSelectedTier(undefined);
+ };
+
+ return (
+
+
+
+ Tiers Management
+
+ }
+ onClick={handleCreateClick}
+ disabled={loading}
+ >
+ Create Tier
+
+
+
+ {error && (
+ setError(null)}>
+ {error}
+
+ )}
+
+ {loading && (
+
+
+
+ )}
+
+ {!loading && tiers.length === 0 && (
+
+
+ No tiers found. Create one to get started.
+
+
+ )}
+
+ {!loading && tiers.length > 0 && (
+
+
+
+
+ Name
+ Description
+
+ Capacity
+
+
+ Duration (s)
+
+ Refill Strategy
+
+ Actions
+
+
+
+
+ {tiers.map(tier => (
+
+ {tier.name}
+ {tier.description || "-"}
+ {tier.capacity.toLocaleString()}
+ {tier.duration.toLocaleString()}
+ {tier.refillStrategy}
+
+
+ handleEditClick(tier)}
+ title='Edit tier'
+ color='primary'
+ >
+
+
+ handleDeleteClick(tier)}
+ title='Delete tier'
+ color='error'
+ >
+
+
+
+
+
+ ))}
+
+
+
+ )}
+
+
+
+
+
+ );
+};
diff --git a/webui/src/pages/user/user-publisher-agreement.tsx b/webui/src/pages/user/user-publisher-agreement.tsx
index 4fb00234b..dc760ee65 100644
--- a/webui/src/pages/user/user-publisher-agreement.tsx
+++ b/webui/src/pages/user/user-publisher-agreement.tsx
@@ -94,14 +94,14 @@ export const UserPublisherAgreement: FunctionComponent
{
user.publisherAgreement.timestamp
- ? <>You signed the Eclipse Foundation Open VSX Publisher Agreement .>
- : 'You signed the Eclipse Foundation Open VSX Publisher Agreement.'
+ ? <>You signed the {pageSettings.agreement.name} .>
+ : <>You signed the {pageSettings.agreement.name}.>
}
;
} else if (user.additionalLogins?.find(login => login.provider === 'eclipse')) {
content = <>
- You need to sign the Eclipse Foundation Open VSX Publisher Agreement before you can publish
+ You need to sign the {pageSettings.agreement.name} before you can publish
any extension to this registry.
@@ -113,7 +113,7 @@ export const UserPublisherAgreement: FunctionComponent
- You need to sign the Eclipse Foundation Open VSX Publisher Agreement before you can publish
+ You need to sign the {pageSettings.agreement.name} before you can publish
any extension to this registry. To start the signing process, please log in with
an Eclipse Foundation account.
diff --git a/webui/src/pages/user/user-settings-profile.tsx b/webui/src/pages/user/user-settings-profile.tsx
index b2f242818..7012c27a4 100644
--- a/webui/src/pages/user/user-settings-profile.tsx
+++ b/webui/src/pages/user/user-settings-profile.tsx
@@ -14,6 +14,7 @@ import { toLocalTime } from '../../utils';
import { UserData } from '../../extension-registry-types';
import { UserPublisherAgreement } from './user-publisher-agreement';
import styled from '@mui/material/styles/styled';
+import { PageSettings } from '../../page-settings';
const ProfileGrid = styled(Grid)(({ theme }: {theme: Theme}) => ({
[theme.breakpoints.up('lg')]: {
@@ -37,12 +38,11 @@ const ProfileGrid = styled(Grid)(({ theme }: {theme: Theme}) => ({
marginBottom: theme.spacing(2)
}));
-export const UserSettingsProfile: FunctionComponent = props => {
+export const UserSettingsProfile: FunctionComponent = ({ user, agreement, isAdmin }) => {
- const user = props.user;
let publisherAgreementPanel: ReactNode = null;
if (user.publisherAgreement) {
- if (props.isAdmin) {
+ if (isAdmin) {
let statusText = 'has not signed';
if (user.publisherAgreement.status === 'signed') {
statusText = 'has signed';
@@ -51,7 +51,7 @@ export const UserSettingsProfile: FunctionComponent =
}
publisherAgreementPanel =
- {user.loginName} {statusText} the Eclipse publisher agreement.
+ {user.loginName} {statusText} the {agreement.name}.
;
} else {
publisherAgreementPanel =
@@ -88,5 +88,6 @@ export const UserSettingsProfile: FunctionComponent =
export interface UserSettingsProfileProps {
user: UserData;
+ agreement: PageSettings['agreement'];
isAdmin?: boolean;
}
\ No newline at end of file
diff --git a/webui/src/pages/user/user-settings-tokens.tsx b/webui/src/pages/user/user-settings-tokens.tsx
index ebc39c30a..9cc24deaa 100644
--- a/webui/src/pages/user/user-settings-tokens.tsx
+++ b/webui/src/pages/user/user-settings-tokens.tsx
@@ -9,27 +9,14 @@
********************************************************************************/
import React, { FunctionComponent, ReactNode, useContext, useEffect, useState, useRef } from 'react';
-import { Theme, Typography, Box, Paper, Button, Link, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@mui/material';
-import { Link as RouteLink } from 'react-router-dom';
+import { Theme, Typography, Box, Paper, Button, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@mui/material';
import { DelayedLoadIndicator } from '../../components/delayed-load-indicator';
import { Timestamp } from '../../components/timestamp';
import { PersonalAccessToken } from '../../extension-registry-types';
import { MainContext } from '../../context';
import { GenerateTokenDialog } from './generate-token-dialog';
-import { UserSettingsRoutes } from './user-settings';
import styled from '@mui/material/styles/styled';
-const link = ({ theme }: { theme: Theme }) => ({
- color: theme.palette.secondary.main,
- textDecoration: 'none',
- '&:hover': {
- textDecoration: 'underline'
- }
-});
-
-const StyledLink = styled(Link)(link);
-const StyledRouteLink = styled(RouteLink)(link);
-
const EmptyTypography = styled(Typography)(({ theme }: { theme: Theme }) => ({
[theme.breakpoints.down('sm')]: {
textAlign: 'center'
@@ -43,7 +30,7 @@ const DeleteButton = styled(Button)(({ theme }: { theme: Theme }) => ({
export const UserSettingsTokens: FunctionComponent = () => {
- const { service, user, handleError } = useContext(MainContext);
+ const { service, user, handleError, pageSettings } = useContext(MainContext);
const [tokens, setTokens] = useState(new Array());
const [loading, setLoading] = useState(true);
@@ -119,16 +106,9 @@ export const UserSettingsTokens: FunctionComponent = () => {
};
const agreement = user?.publisherAgreement;
+ const PublisherAgreementNotSignedContent = pageSettings.elements.agreement.notSignedContent;
if (agreement && (agreement.status === 'none' || agreement.status === 'outdated')) {
- return
-
- Access tokens cannot be created as you currently do not have an Eclipse Foundation Open VSX
- Publisher Agreement signed. Please return to
- your Profile page
- to sign the Publisher Agreement. Should you believe this is in error, please
- contact license@eclipse.org.
-
- ;
+ return ;
}
return <>
diff --git a/webui/src/pages/user/user-settings.tsx b/webui/src/pages/user/user-settings.tsx
index f0ac53e10..9de3c553b 100644
--- a/webui/src/pages/user/user-settings.tsx
+++ b/webui/src/pages/user/user-settings.tsx
@@ -46,7 +46,7 @@ export const UserSettings: FunctionComponent = props => {
switch (tab) {
case 'profile':
- return ;
+ return ;
case 'tokens':
return ;
case 'namespaces':