Skip to content
Draft
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
14 changes: 14 additions & 0 deletions server/src/main/java/org/eclipse/openvsx/UserAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ public UserJson getUserData() {
json.setRole(user.getRole());
json.setTokensUrl(createApiUrl(serverUrl, "user", "tokens"));
json.setCreateTokenUrl(createApiUrl(serverUrl, "user", "token", "create"));
json.setDeleteAllTokensUrl(createApiUrl(serverUrl, "user", "tokens", "delete"));
eclipse.enrichUserJsonWithPublisherAgreement(json, user);
return json;
}
Expand Down Expand Up @@ -189,6 +190,19 @@ public ResponseEntity<ResultJson> deleteAccessToken(@PathVariable long id) {
}
}

@PostMapping(
path = "/user/tokens/delete",
produces = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<ResultJson> deleteAllAccessTokens() {
var user = users.findLoggedInUser();
if (user == null) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
}

return ResponseEntity.ok(users.deleteAllAccessTokens(user));
}

@GetMapping(
path = "/user/extensions",
produces = MediaType.APPLICATION_JSON_VALUE
Expand Down
10 changes: 10 additions & 0 deletions server/src/main/java/org/eclipse/openvsx/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,16 @@ public ResultJson deleteAccessToken(UserData user, long id) {
return ResultJson.success("Deleted access token for user " + user.getLoginName() + ".");
}

@Transactional
public ResultJson deleteAllAccessTokens(UserData user) {
var tokens = repositories.findAccessTokens(user);
for(var token : tokens) {
token.setActive(false);
}

return ResultJson.success("Deleted access tokens for user " + user.getLoginName() + ".");
}

public boolean canLogin() {
return !getLoginProviders().isEmpty();
}
Expand Down
26 changes: 25 additions & 1 deletion server/src/main/java/org/eclipse/openvsx/json/UserJson.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public static UserJson error(String message) {
@Schema(hidden = true)
private String createTokenUrl;

@Schema(hidden = true)
private String deleteAllTokensUrl;

@Schema(hidden = true)
private String role;

Expand Down Expand Up @@ -85,6 +88,14 @@ public void setCreateTokenUrl(String createTokenUrl) {
this.createTokenUrl = createTokenUrl;
}

public String getDeleteAllTokensUrl() {
return deleteAllTokensUrl;
}

public void setDeleteAllTokensUrl(String deleteAllTokensUrl) {
this.deleteAllTokensUrl = deleteAllTokensUrl;
}

public String getRole() {
return role;
}
Expand Down Expand Up @@ -200,6 +211,7 @@ public boolean equals(Object o) {
return Objects.equals(loginName, userJson.loginName)
&& Objects.equals(tokensUrl, userJson.tokensUrl)
&& Objects.equals(createTokenUrl, userJson.createTokenUrl)
&& Objects.equals(deleteAllTokensUrl, userJson.deleteAllTokensUrl)
&& Objects.equals(role, userJson.role)
&& Objects.equals(fullName, userJson.fullName)
&& Objects.equals(avatarUrl, userJson.avatarUrl)
Expand All @@ -211,6 +223,18 @@ public boolean equals(Object o) {

@Override
public int hashCode() {
return Objects.hash(loginName, tokensUrl, createTokenUrl, role, fullName, avatarUrl, homepage, provider, publisherAgreement, additionalLogins);
return Objects.hash(
loginName,
tokensUrl,
createTokenUrl,
deleteAllTokensUrl,
role,
fullName,
avatarUrl,
homepage,
provider,
publisherAgreement,
additionalLogins
);
}
}
4 changes: 3 additions & 1 deletion webui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "openvsx-webui",
"version": "0.17.1",
"version": "0.18.0",
"description": "User interface for Eclipse Open VSX",
"keywords": [
"react",
Expand Down Expand Up @@ -41,6 +41,7 @@
"@mui/base": "^5.0.0-beta.9",
"@mui/icons-material": "^5.13.7",
"@mui/material": "^5.13.7",
"@reduxjs/toolkit": "^2.11.0",
"clipboard-copy": "^4.0.1",
"clsx": "^1.2.1",
"dompurify": "^3.0.4",
Expand All @@ -56,6 +57,7 @@
"react-dropzone": "^14.2.3",
"react-helmet-async": "^1.3.0",
"react-infinite-scroller": "^1.2.6",
"react-redux": "^9.2.0",
"react-router": "^6.14.2",
"react-router-dom": "^6.14.1"
},
Expand Down
33 changes: 14 additions & 19 deletions webui/src/components/error-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ import React, { FunctionComponent, ReactNode, useContext, useEffect } from 'reac
import { Dialog, DialogTitle, DialogContent, Button, DialogContentText, DialogActions, Box, Link } from '@mui/material';
import { MainContext } from '../context';
import { styled, Theme } from '@mui/material/styles';
import { useAppDispatch, useAppSelector } from '../store/hooks';
import { hideError, selectError } from '../store/error';

const ErrorLink = styled(Link)(({ theme }: { theme: Theme }) => ({
textDecoration: 'underline',
color: theme.palette.primary.contrastText
}));

export const ErrorDialog: FunctionComponent<ErrorDialogProps> = props => {
export const ErrorDialog: FunctionComponent = () => {
const dispatch = useAppDispatch();
const error = useAppSelector(selectError);

useEffect(() => {
document.addEventListener('keydown', handleEnter);
Expand All @@ -27,16 +31,14 @@ export const ErrorDialog: FunctionComponent<ErrorDialogProps> = props => {

const handleEnter = (event: KeyboardEvent): void => {
if (event.code === 'Enter') {
props.handleCloseDialog();
handleCloseDialog();
}
};

const getContentForCode = (): ReactNode => {
if (!props.errorCode) {
return null;
}
const handleCloseDialog = () => dispatch(hideError());

switch (props.errorCode) {
const getContentForCode = (): ReactNode => {
switch (error.code) {
case 'eclipse-missing-github-id':
return <>
Please fill in the &ldquo;GitHub Username&rdquo; field
Expand Down Expand Up @@ -65,12 +67,12 @@ export const ErrorDialog: FunctionComponent<ErrorDialogProps> = props => {
const context = useContext(MainContext);
const codeContent = getContentForCode();
return <Dialog
open={props.isErrorDialogOpen}
onClose={props.handleCloseDialog} >
open={error.show}
onClose={handleCloseDialog} >
<DialogTitle>Error</DialogTitle>
<DialogContent>
<DialogContentText sx={{ color: context.pageSettings.themeType === 'dark' ? '#ff849e' : '#c54a64' }}>
{props.errorMessage}
{error.message}
{
codeContent ?
<Box mt={2}>
Expand All @@ -81,16 +83,9 @@ export const ErrorDialog: FunctionComponent<ErrorDialogProps> = props => {
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={props.handleCloseDialog}>
<Button onClick={handleCloseDialog}>
Close
</Button>
</DialogActions>
</Dialog>;
};

export interface ErrorDialogProps {
errorMessage: string;
errorCode?: number | string;
isErrorDialogOpen: boolean;
handleCloseDialog: () => void;
}
};
5 changes: 0 additions & 5 deletions webui/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,11 @@

import { createContext } from 'react';
import { PageSettings } from './page-settings';
import { ExtensionRegistryService } from './extension-registry-service';
import { UserData } from './extension-registry-types';
import { ErrorResponse } from './server-request';

export interface MainContext {
service: ExtensionRegistryService;
pageSettings: PageSettings;
handleError: (err: Error | Partial<ErrorResponse>) => void;
user?: UserData;
updateUser: () => void;
loginProviders?: Record<string, string>;
}

Expand Down
48 changes: 12 additions & 36 deletions webui/src/default/default-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,57 +14,33 @@ import { HelmetProvider } from 'react-helmet-async';
import { BrowserRouter } from 'react-router-dom';
import { ThemeProvider } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import { ExtensionRegistryService } from '../extension-registry-service';
import { Main } from '../main';
import createPageSettings from './page-settings';
import createDefaultTheme from './theme';
import { store } from '../store/store';
import { Provider } from 'react-redux';

// This is the default entry point for the webui Docker image and for development.
// The production code for open-vsx.org is at https://github.com/eclipse/open-vsx.org


let serverHost = location.host;
if (serverHost.startsWith('3000-')) {
// Gitpod dev environment: the frontend runs on port 3000, but the server runs on port 8080
serverHost = '8080-' + serverHost.substring(5);
} else if (location.port === '3000') {
// Localhost dev environment
serverHost = location.hostname + ':8080';
} else if (serverHost.includes('che-webui')) {
// Eclipse Che dev environment.
// If serverHost contains 'che-webui', replace it with 'che-server'
serverHost = serverHost.replace('che-webui', 'che-server');
}
const service = new ExtensionRegistryService(`${location.protocol}//${serverHost}`);

async function getServerVersion(): Promise<string> {
const abortController = new AbortController();
try {
const result = await service.getRegistryVersion(abortController);
return result.version;
} catch (error) {
console.error('Could not determine server version');
return 'unknown';
}
}

const App = () => {
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const theme = useMemo(
() => createDefaultTheme(prefersDarkMode ? 'dark' : 'light'),
[prefersDarkMode],
);

const pageSettings = createPageSettings(prefersDarkMode, service.serverUrl, getServerVersion());
const pageSettings = createPageSettings(prefersDarkMode);
return (
<HelmetProvider>
<ThemeProvider theme={theme}>
<Main
service={service}
pageSettings={pageSettings}
/>
</ThemeProvider>
</HelmetProvider>
<Provider store={store}>
<HelmetProvider>
<ThemeProvider theme={theme}>
<Main
pageSettings={pageSettings}
/>
</ThemeProvider>
</HelmetProvider>
</Provider>
);
};

Expand Down
10 changes: 6 additions & 4 deletions webui/src/default/menu-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import LogoutIcon from '@mui/icons-material/Logout';
import { AdminDashboardRoutes } from '../pages/admin-dashboard/admin-dashboard';
import { LogoutForm } from '../pages/user/logout';
import { LoginComponent } from './login';
import { useGetUserQuery } from '../store/api';

//-------------------- Mobile View --------------------//

Expand All @@ -54,8 +55,7 @@ export const MobileMenuItemText: FunctionComponent<PropsWithChildren> = ({ child
};

export const MobileUserAvatar: FunctionComponent = () => {
const context = useContext(MainContext);
const user = context.user;
const { data: user } = useGetUserQuery();
if (!user) {
return null;
}
Expand Down Expand Up @@ -118,7 +118,8 @@ export const MobileUserAvatar: FunctionComponent = () => {

export const MobileMenuContent: FunctionComponent = () => {
const location = useLocation();
const { user, loginProviders } = useContext(MainContext);
const { data: user } = useGetUserQuery();
const { loginProviders } = useContext(MainContext);

return <>
{loginProviders && (
Expand Down Expand Up @@ -205,7 +206,8 @@ export const MenuLink = styled(Link)(headerItem);
export const MenuRouteLink = styled(RouteLink)(headerItem);

export const DefaultMenuContent: FunctionComponent = () => {
const { user, loginProviders } = useContext(MainContext);
const { data: user } = useGetUserQuery();
const { loginProviders } = useContext(MainContext);
return <>
<MenuLink href='https://github.com/eclipse/openvsx/wiki'>
Documentation
Expand Down
19 changes: 10 additions & 9 deletions webui/src/default/page-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/

import React, { FunctionComponent, ReactNode, Suspense, lazy } from 'react';
import React, { FunctionComponent, ReactNode } from 'react';
import { Helmet } from 'react-helmet-async';
import { styled, Theme } from '@mui/material/styles';
import { Link, Typography, Box } from '@mui/material';
Expand All @@ -21,8 +21,9 @@ import { DefaultMenuContent, MobileMenuContent } from './menu-content';
import OpenVSXLogo from './openvsx-registry-logo';
import About from './about';
import { createAbsoluteURL } from '../utils';
import { useGetRegistryVersionQuery, serverUrl } from '../store/api';

export default function createPageSettings(prefersDarkMode: boolean, serverUrl: string, serverVersionPromise: Promise<string>): PageSettings {
export default function createPageSettings(prefersDarkMode: boolean): PageSettings {
const toolbarContent: FunctionComponent = () =>
<RouteLink to={ExtensionListRoutes.MAIN} aria-label={`Home - Open VSX Registry`}>
<OpenVSXLogo width='auto' height='40px' marginTop='8px' prefersDarkMode={prefersDarkMode} />
Expand All @@ -39,10 +40,12 @@ export default function createPageSettings(prefersDarkMode: boolean, serverUrl:

const StyledRouteLink = styled(RouteLink)(link);

const ServerVersion = lazy(async () => {
const version = await serverVersionPromise;
return { default: () => <Typography variant='body2' sx={{ fontSize: '0.8rem' }}>Server Version: {version}</Typography> };
});
const ServerVersion: FunctionComponent = () => {
const { data } = useGetRegistryVersionQuery();
return data != null
? <Typography variant='body2' sx={{ fontSize: '0.8rem' }}>Server Version: {data.version}</Typography>
: <div>Loading version...</div>;
};


const footerContent: FunctionComponent<{ expanded: boolean }> = () =>
Expand All @@ -65,9 +68,7 @@ export default function createPageSettings(prefersDarkMode: boolean, serverUrl:
>
<GitHubIcon />&nbsp;eclipse/openvsx
</Link>
<Suspense fallback={<div>Loading version...</div>}>
<ServerVersion/>
</Suspense>
<ServerVersion/>
<StyledRouteLink to='/about'>
About This Service
</StyledRouteLink>
Expand Down
Loading
Loading