openvsx-webui-test 0.20.0-dev.0 → 0.20.0-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/lib/components/error-dialog.d.ts.map +1 -1
  2. package/lib/components/error-dialog.js +3 -5
  3. package/lib/components/error-dialog.js.map +1 -1
  4. package/lib/components/generate-token-dialog.d.ts +22 -0
  5. package/lib/components/generate-token-dialog.d.ts.map +1 -0
  6. package/lib/components/generate-token-dialog.js +91 -0
  7. package/lib/components/generate-token-dialog.js.map +1 -0
  8. package/lib/components/rate-limiting/usage-stats/usage-stats-chart.d.ts.map +1 -1
  9. package/lib/components/rate-limiting/usage-stats/usage-stats-chart.js +14 -11
  10. package/lib/components/rate-limiting/usage-stats/usage-stats-chart.js.map +1 -1
  11. package/lib/components/timestamp.d.ts +1 -0
  12. package/lib/components/timestamp.d.ts.map +1 -1
  13. package/lib/components/timestamp.js +2 -1
  14. package/lib/components/timestamp.js.map +1 -1
  15. package/lib/default/menu-content.d.ts +1 -1
  16. package/lib/extension-registry-service.d.ts +7 -1
  17. package/lib/extension-registry-service.d.ts.map +1 -1
  18. package/lib/extension-registry-service.js +41 -0
  19. package/lib/extension-registry-service.js.map +1 -1
  20. package/lib/extension-registry-types.d.ts +6 -0
  21. package/lib/extension-registry-types.d.ts.map +1 -1
  22. package/lib/pages/admin-dashboard/admin-dashboard.d.ts.map +1 -1
  23. package/lib/pages/admin-dashboard/admin-dashboard.js +17 -27
  24. package/lib/pages/admin-dashboard/admin-dashboard.js.map +1 -1
  25. package/lib/pages/admin-dashboard/customers/customer-details.d.ts.map +1 -1
  26. package/lib/pages/admin-dashboard/customers/customer-details.js +2 -1
  27. package/lib/pages/admin-dashboard/customers/customer-details.js.map +1 -1
  28. package/lib/pages/admin-dashboard/customers/customer-token-list.d.ts +19 -0
  29. package/lib/pages/admin-dashboard/customers/customer-token-list.d.ts.map +1 -0
  30. package/lib/pages/admin-dashboard/customers/customer-token-list.js +70 -0
  31. package/lib/pages/admin-dashboard/customers/customer-token-list.js.map +1 -0
  32. package/lib/pages/extension-detail/extension-detail.js +1 -1
  33. package/lib/pages/extension-detail/extension-detail.js.map +1 -1
  34. package/lib/pages/user/{generate-token-dialog.d.ts → generate-access-token-dialog.d.ts} +2 -2
  35. package/lib/pages/user/generate-access-token-dialog.d.ts.map +1 -0
  36. package/lib/pages/user/generate-access-token-dialog.js +33 -0
  37. package/lib/pages/user/generate-access-token-dialog.js.map +1 -0
  38. package/lib/pages/user/user-settings-profile.d.ts.map +1 -1
  39. package/lib/pages/user/user-settings-profile.js +1 -1
  40. package/lib/pages/user/user-settings-profile.js.map +1 -1
  41. package/lib/pages/user/user-settings-tokens.js +4 -4
  42. package/lib/pages/user/user-settings-tokens.js.map +1 -1
  43. package/lib/utils.d.ts +1 -1
  44. package/lib/utils.d.ts.map +1 -1
  45. package/lib/utils.js +10 -5
  46. package/lib/utils.js.map +1 -1
  47. package/package.json +9 -9
  48. package/src/components/error-dialog.tsx +3 -5
  49. package/src/components/generate-token-dialog.tsx +167 -0
  50. package/src/components/rate-limiting/usage-stats/usage-stats-chart.tsx +18 -12
  51. package/src/components/timestamp.tsx +3 -1
  52. package/src/extension-registry-service.ts +49 -1
  53. package/src/extension-registry-types.ts +7 -0
  54. package/src/pages/admin-dashboard/admin-dashboard.tsx +17 -27
  55. package/src/pages/admin-dashboard/customers/customer-details.tsx +4 -0
  56. package/src/pages/admin-dashboard/customers/customer-token-list.tsx +135 -0
  57. package/src/pages/extension-detail/extension-detail.tsx +1 -1
  58. package/src/pages/user/generate-access-token-dialog.tsx +49 -0
  59. package/src/pages/user/user-settings-profile.tsx +1 -1
  60. package/src/pages/user/user-settings-tokens.tsx +4 -4
  61. package/src/utils.ts +9 -5
  62. package/lib/components/copy-to-clipboard.d.ts +0 -21
  63. package/lib/components/copy-to-clipboard.d.ts.map +0 -1
  64. package/lib/components/copy-to-clipboard.js +0 -25
  65. package/lib/components/copy-to-clipboard.js.map +0 -1
  66. package/lib/pages/user/generate-token-dialog.d.ts.map +0 -1
  67. package/lib/pages/user/generate-token-dialog.js +0 -79
  68. package/lib/pages/user/generate-token-dialog.js.map +0 -1
  69. package/src/components/copy-to-clipboard.tsx +0 -50
  70. package/src/pages/user/generate-token-dialog.tsx +0 -157
@@ -15,7 +15,7 @@ import {
15
15
  LoginProviders, ScanResultJson, ScanCounts, ScanResultsResponse, ScanFilterOptions,
16
16
  FilesResponse, FileDecisionCountsJson, ScanDecisionRequest, ScanDecisionResponse,
17
17
  FileDecisionRequest, FileDecisionResponse, FileDecisionDeleteRequest, FileDecisionDeleteResponse,
18
- Tier, TierList, Customer, CustomerList, UsageStatsList, LogPageableList, CustomerMembershipList,
18
+ Tier, TierList, Customer, CustomerList, UsageStatsList, LogPageableList, CustomerMembershipList, RateLimitToken,
19
19
  } from './extension-registry-types';
20
20
  import { createAbsoluteURL, addQuery } from './utils';
21
21
  import { sendRequest, ErrorResponse } from './server-request';
@@ -533,6 +533,9 @@ export interface AdminService {
533
533
  removeCustomerMember(abortController: AbortController, name: string, user: UserData): Promise<Readonly<SuccessResult | ErrorResult>>;
534
534
  getUsageStats(abortController: AbortController, customerName: string, date: Date): Promise<Readonly<UsageStatsList>>;
535
535
  getLogs(abortController: AbortController, page?: number, size?: number, period?: string): Promise<Readonly<LogPageableList>>;
536
+ getCustomerRateLimitTokens(abortController: AbortController, customerName: string): Promise<Readonly<RateLimitToken[]>>;
537
+ createCustomerRateLimitToken(abortController: AbortController, customerName: string, description: string): Promise<Readonly<RateLimitToken>>;
538
+ deleteCustomerRateLimitToken(abortController: AbortController, customerName: string, tokenId: number): Promise<Readonly<SuccessResult | ErrorResult>>;
536
539
  }
537
540
 
538
541
  export interface AdminServiceConstructor {
@@ -1065,6 +1068,51 @@ export class AdminServiceImpl implements AdminService {
1065
1068
  credentials: true
1066
1069
  }, false);
1067
1070
  }
1071
+
1072
+ async getCustomerRateLimitTokens(abortController: AbortController, customerName: string): Promise<Readonly<RateLimitToken[]>> {
1073
+ return sendRequest({
1074
+ abortController,
1075
+ credentials: true,
1076
+ endpoint: createAbsoluteURL([this.registry.serverUrl, 'admin', 'ratelimit', 'customers', customerName, 'tokens']),
1077
+ }, false);
1078
+ }
1079
+
1080
+ async createCustomerRateLimitToken(abortController: AbortController, customerName: string, description?: string): Promise<Readonly<RateLimitToken>> {
1081
+ const csrfResponse = await this.registry.getCsrfToken(abortController);
1082
+ const headers: Record<string, string> = {};
1083
+ if (!isError(csrfResponse)) {
1084
+ const csrfToken = csrfResponse as CsrfTokenJson;
1085
+ headers[csrfToken.header] = csrfToken.value;
1086
+ }
1087
+
1088
+ const url = createAbsoluteURL([this.registry.serverUrl, 'admin', 'ratelimit', 'customers', customerName, 'tokens']);
1089
+ const endpoint = description !== undefined ? addQuery(url, [{ key: 'description', value: description }]) : url;
1090
+ return sendRequest({
1091
+ abortController,
1092
+ method: 'POST',
1093
+ credentials: true,
1094
+ endpoint,
1095
+ headers
1096
+ }, false);
1097
+ }
1098
+
1099
+ async deleteCustomerRateLimitToken(abortController: AbortController, customerName: string, tokenId: number): Promise<Readonly<SuccessResult | ErrorResult>> {
1100
+ const csrfResponse = await this.registry.getCsrfToken(abortController);
1101
+ const headers: Record<string, string> = {
1102
+ 'Content-Type': 'application/json;charset=UTF-8'
1103
+ };
1104
+ if (!isError(csrfResponse)) {
1105
+ const csrfToken = csrfResponse as CsrfTokenJson;
1106
+ headers[csrfToken.header] = csrfToken.value;
1107
+ }
1108
+ return sendRequest({
1109
+ abortController,
1110
+ method: 'DELETE',
1111
+ credentials: true,
1112
+ endpoint: createAbsoluteURL([this.registry.serverUrl, 'admin', 'ratelimit', 'customers', customerName, 'tokens', `${tokenId}`]),
1113
+ headers
1114
+ }, false);
1115
+ }
1068
1116
  }
1069
1117
 
1070
1118
  export interface ExtensionFilter {
@@ -467,6 +467,13 @@ export interface CustomerMembershipList {
467
467
  customerMemberships: CustomerMembership[];
468
468
  }
469
469
 
470
+ export interface RateLimitToken {
471
+ id: number;
472
+ value?: string;
473
+ description?: string;
474
+ createdTimestamp: TimestampString;
475
+ }
476
+
470
477
  export interface UsageStats {
471
478
  windowStart: number; // epoch seconds in UTC
472
479
  duration: number; // in seconds
@@ -119,24 +119,19 @@ interface AppBarProps extends MuiAppBarProps {
119
119
 
120
120
  const AppBar = styled(MuiAppBar, {
121
121
  shouldForwardProp: (prop) => prop !== 'open',
122
- })<AppBarProps>(({ theme }) => ({
122
+ })<AppBarProps>(({ theme, open }) => ({
123
123
  transition: theme.transitions.create(['margin', 'width'], {
124
124
  easing: theme.transitions.easing.sharp,
125
125
  duration: theme.transitions.duration.leavingScreen,
126
126
  }),
127
- variants: [
128
- {
129
- props: ({ open }) => open,
130
- style: {
131
- width: `calc(100% - ${drawerWidth}px)`,
132
- marginLeft: `${drawerWidth}px`,
133
- transition: theme.transitions.create(['margin', 'width'], {
134
- easing: theme.transitions.easing.easeOut,
135
- duration: theme.transitions.duration.enteringScreen,
136
- }),
137
- },
138
- },
139
- ],
127
+ ...(open && {
128
+ width: `calc(100% - ${drawerWidth}px)`,
129
+ marginLeft: `${drawerWidth}px`,
130
+ transition: theme.transitions.create(['margin', 'width'], {
131
+ easing: theme.transitions.easing.easeOut,
132
+ duration: theme.transitions.duration.enteringScreen,
133
+ }),
134
+ }),
140
135
  }));
141
136
 
142
137
  interface LinkRouterProps extends LinkProps {
@@ -178,7 +173,7 @@ const BreadcrumbsComponent = () => {
178
173
 
179
174
  const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{
180
175
  open?: boolean;
181
- }>(({ theme }) => ({
176
+ }>(({ theme, open }) => ({
182
177
  flexGrow: 1,
183
178
  padding: theme.spacing(3),
184
179
  transition: theme.transitions.create('margin', {
@@ -186,18 +181,13 @@ const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{
186
181
  duration: theme.transitions.duration.leavingScreen,
187
182
  }),
188
183
  marginLeft: `-${drawerWidth}px`,
189
- variants: [
190
- {
191
- props: ({ open }) => open,
192
- style: {
193
- transition: theme.transitions.create('margin', {
194
- easing: theme.transitions.easing.easeOut,
195
- duration: theme.transitions.duration.enteringScreen,
196
- }),
197
- marginLeft: 0,
198
- },
199
- },
200
- ],
184
+ ...(open && {
185
+ transition: theme.transitions.create('margin', {
186
+ easing: theme.transitions.easing.easeOut,
187
+ duration: theme.transitions.duration.enteringScreen,
188
+ }),
189
+ marginLeft: 0,
190
+ }),
201
191
  }));
202
192
 
203
193
  export const AdminDashboard: FunctionComponent<AdminDashboardProps> = props => {
@@ -28,6 +28,7 @@ import { useAdminUsageStats } from '../usage-stats/use-usage-stats';
28
28
  import { GeneralDetails, UsageStats } from '../../../components/rate-limiting/customer';
29
29
  import { CustomerFormDialog } from './customer-form-dialog';
30
30
  import { CustomerMemberList } from './customer-member-list';
31
+ import { CustomerTokenList } from './customer-token-list';
31
32
 
32
33
  const CustomerDetailsLoading: FC = () => (
33
34
  <Box sx={{ p: 3 }}>
@@ -115,6 +116,9 @@ export const CustomerDetails: FC = () => {
115
116
  <CustomerMemberList
116
117
  customer={customer}
117
118
  />
119
+ <CustomerTokenList
120
+ customer={customer}
121
+ />
118
122
  <UsageStats usageStats={usageStats} customer={customer} startDate={startDate} onStartDateChange={setStartDate} />
119
123
  </Box>
120
124
 
@@ -0,0 +1,135 @@
1
+ /******************************************************************************
2
+ * Copyright (c) 2026 Contributors to the Eclipse Foundation.
3
+ *
4
+ * See the NOTICE file(s) distributed with this work for additional
5
+ * information regarding copyright ownership.
6
+ *
7
+ * This program and the accompanying materials are made available under the
8
+ * terms of the Eclipse Public License 2.0 which is available at
9
+ * https://www.eclipse.org/legal/epl-2.0.
10
+ *
11
+ * SPDX-License-Identifier: EPL-2.0
12
+ *****************************************************************************/
13
+
14
+ import { FunctionComponent, useEffect, useState, useContext, useRef } from 'react';
15
+ import {
16
+ Box,
17
+ Typography,
18
+ Divider,
19
+ List,
20
+ ListItem,
21
+ ListItemText,
22
+ IconButton,
23
+ Paper,
24
+ Button,
25
+ type PaperProps
26
+ } from '@mui/material';
27
+ import DeleteIcon from '@mui/icons-material/Delete';
28
+ import AddIcon from '@mui/icons-material/Add';
29
+ import { MainContext } from '../../../context';
30
+ import { Customer, isError, RateLimitToken } from '../../../extension-registry-types';
31
+ import { GenerateTokenDialog } from '../../../components/generate-token-dialog';
32
+ import { Timestamp } from "../../../components/timestamp";
33
+
34
+ const sectionPaperProps: PaperProps = { elevation: 1, sx: { p: 3, mb: 3 } };
35
+
36
+ export const CustomerTokenList: FunctionComponent<CustomerTokenListProps> = props => {
37
+ const { service, handleError } = useContext(MainContext);
38
+ const [tokens, setTokens] = useState<RateLimitToken[]>([]);
39
+ const [dialogOpen, setDialogOpen] = useState(false);
40
+ const abortController = useRef<AbortController>(new AbortController());
41
+
42
+ useEffect(() => {
43
+ fetchTokens();
44
+ }, [props.customer]);
45
+
46
+ useEffect(() => {
47
+ return () => {
48
+ abortController.current.abort();
49
+ };
50
+ }, []);
51
+
52
+ const fetchTokens = async () => {
53
+ try {
54
+ const result = await service.admin.getCustomerRateLimitTokens(abortController.current, props.customer.name);
55
+ setTokens([...result]);
56
+ } catch (err) {
57
+ handleError(err);
58
+ }
59
+ };
60
+
61
+ const handleGenerate = async (description: string): Promise<string> => {
62
+ const token = await service.admin.createCustomerRateLimitToken(abortController.current, props.customer.name, description);
63
+ await fetchTokens();
64
+ return token.value ?? '';
65
+ };
66
+
67
+ const handleDelete = async (tokenId: number) => {
68
+ try {
69
+ const result = await service.admin.deleteCustomerRateLimitToken(abortController.current, props.customer.name, tokenId);
70
+ if (isError(result)) {
71
+ throw result;
72
+ }
73
+ await fetchTokens();
74
+ } catch (err) {
75
+ handleError(err);
76
+ }
77
+ };
78
+
79
+ return <Paper {...sectionPaperProps}>
80
+ <Box
81
+ sx={{
82
+ display: 'flex',
83
+ justifyContent: 'space-between',
84
+ alignItems: 'center',
85
+ mb: 1,
86
+ flexDirection: { xs: 'column', sm: 'column', md: 'row', lg: 'row', xl: 'row' }
87
+ }}
88
+ >
89
+ <Typography variant='h6'>Tokens</Typography>
90
+ <Button size='small' startIcon={<AddIcon />} onClick={() => setDialogOpen(true)}>
91
+ Generate Token
92
+ </Button>
93
+ </Box>
94
+ <Divider sx={{ mb: 1 }} />
95
+ {tokens.length === 0 ? (
96
+ <Typography variant='body2' color='text.secondary' sx={{ py: 1 }}>
97
+ No rate limiting tokens for this customer.
98
+ </Typography>
99
+ ) : (
100
+ <List dense disablePadding>
101
+ {tokens.map(token => (
102
+ <ListItem
103
+ key={token.id}
104
+ secondaryAction={
105
+ <IconButton edge='end' size='small' color='error' onClick={() => handleDelete(token.id)} title='Delete token'>
106
+ <DeleteIcon fontSize='small' />
107
+ </IconButton>
108
+ }
109
+ >
110
+ <ListItemText
111
+ primary={
112
+ <Typography sx={{ fontWeight: 'bold', overflow: 'hidden', textOverflow: 'ellipsis' }}>{token.description || 'N/A'}</Typography>
113
+ }
114
+ secondary={
115
+ <Typography variant='body2'>Created: <Timestamp value={token.createdTimestamp}/></Typography>
116
+ }
117
+ />
118
+ </ListItem>
119
+ ))}
120
+ </List>
121
+ )}
122
+
123
+ <GenerateTokenDialog
124
+ open={dialogOpen}
125
+ onClose={() => setDialogOpen(false)}
126
+ onGenerate={handleGenerate}
127
+ onError={handleError}
128
+ title='Generate token'
129
+ />
130
+ </Paper>;
131
+ };
132
+
133
+ export interface CustomerTokenListProps {
134
+ customer: Customer;
135
+ }
@@ -13,6 +13,7 @@ import {
13
13
  Typography, Box, Theme, Container, Link, Avatar, Paper, Badge, SxProps, Tabs, Tab, Stack, useTheme, PaletteMode,
14
14
  decomposeColor
15
15
  } from '@mui/material';
16
+ import { styled } from '@mui/material/styles';
16
17
  import { Link as RouteLink, useNavigate, useParams } from 'react-router-dom';
17
18
  import SaveAltIcon from '@mui/icons-material/SaveAlt';
18
19
  import VerifiedUserIcon from '@mui/icons-material/VerifiedUser';
@@ -29,7 +30,6 @@ import { ExtensionDetailOverview } from './extension-detail-overview';
29
30
  import { ExtensionDetailChanges } from './extension-detail-changes';
30
31
  import { ExtensionDetailReviews } from './extension-detail-reviews';
31
32
  import { ExtensionDetailRoutes } from './extension-detail-routes';
32
- import styled from '@mui/material/styles/styled';
33
33
 
34
34
  const alignVertically = {
35
35
  display: 'flex',
@@ -0,0 +1,49 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2019 TypeFox and others
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the Eclipse Public License v. 2.0 which is available at
6
+ * http://www.eclipse.org/legal/epl-2.0.
7
+ *
8
+ * SPDX-License-Identifier: EPL-2.0
9
+ ********************************************************************************/
10
+
11
+ import { FunctionComponent, useContext, useRef, useState } from 'react';
12
+ import { Button } from '@mui/material';
13
+ import { GenerateTokenDialog } from '../../components/generate-token-dialog';
14
+ import { isError } from '../../extension-registry-types';
15
+ import { MainContext } from '../../context';
16
+
17
+ export const GenerateAccessTokenDialog: FunctionComponent<GenerateTokenDialogProps> = props => {
18
+ const context = useContext(MainContext);
19
+ const abortController = useRef<AbortController>(new AbortController());
20
+ const [open, setOpen] = useState(false);
21
+
22
+ const handleGenerate = async (description: string): Promise<string> => {
23
+ if (!context.user) {
24
+ throw new Error('Not logged in');
25
+ }
26
+ const token = await context.service.createAccessToken(abortController.current, context.user, description);
27
+ if (isError(token)) {
28
+ throw token;
29
+ }
30
+
31
+ props.handleTokenGenerated();
32
+
33
+ return token.value!;
34
+ };
35
+
36
+ return <>
37
+ <Button variant='outlined' onClick={() => setOpen(true)}>Generate new token</Button>
38
+ <GenerateTokenDialog
39
+ open={open}
40
+ onClose={() => setOpen(false)}
41
+ onGenerate={handleGenerate}
42
+ onError={context.handleError}
43
+ />
44
+ </>;
45
+ };
46
+
47
+ export interface GenerateTokenDialogProps {
48
+ handleTokenGenerated: () => void;
49
+ }
@@ -10,10 +10,10 @@
10
10
 
11
11
  import { FunctionComponent, ReactNode, useContext } from 'react';
12
12
  import { Theme, Grid, Typography, Avatar } from '@mui/material';
13
+ import { styled } from '@mui/material/styles';
13
14
  import { toLocalTime } from '../../utils';
14
15
  import { UserData } from '../../extension-registry-types';
15
16
  import { UserPublisherAgreement } from './user-publisher-agreement';
16
- import styled from '@mui/material/styles/styled';
17
17
  import { MainContext } from "../../context";
18
18
 
19
19
  const ProfileGrid = styled(Grid)(({ theme }: {theme: Theme}) => ({
@@ -10,14 +10,14 @@
10
10
 
11
11
  import { FunctionComponent, ReactNode, useContext, useEffect, useState, useRef } from 'react';
12
12
  import { Theme, Typography, Box, Paper, Button, Link, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@mui/material';
13
+ import { styled } from '@mui/material/styles';
13
14
  import { Link as RouteLink } from 'react-router-dom';
14
15
  import { DelayedLoadIndicator } from '../../components/delayed-load-indicator';
15
16
  import { Timestamp } from '../../components/timestamp';
16
17
  import { PersonalAccessToken } from '../../extension-registry-types';
17
18
  import { MainContext } from '../../context';
18
- import { GenerateTokenDialog } from './generate-token-dialog';
19
+ import { GenerateAccessTokenDialog } from './generate-access-token-dialog';
19
20
  import { UserSettingsRoutes } from './user-settings-routes';
20
- import styled from '@mui/material/styles/styled';
21
21
 
22
22
  const link = ({ theme }: { theme: Theme }) => ({
23
23
  color: theme.palette.secondary.main,
@@ -105,7 +105,7 @@ export const UserSettingsTokens: FunctionComponent = () => {
105
105
  <Box alignItems='center' overflow='auto'>
106
106
  <Typography sx={{ fontWeight: 'bold', overflow: 'hidden', textOverflow: 'ellipsis' }}>{token.description}</Typography>
107
107
  <Typography variant='body2'>Created: <Timestamp value={token.createdTimestamp}/></Typography>
108
- <Typography variant='body2'>Expires: {token.expiresTimestamp ? <Timestamp value={token.expiresTimestamp}/> : 'never'}</Typography>
108
+ <Typography variant='body2'>Expires: {token.expiresTimestamp ? <Timestamp value={token.expiresTimestamp} isFutureTime={true} /> : 'never'}</Typography>
109
109
  <Typography variant='body2'>Accessed: {token.accessedTimestamp ? <Timestamp value={token.accessedTimestamp}/> : 'never'}</Typography>
110
110
  </Box>
111
111
  <Box display='flex' alignItems='center'>
@@ -159,7 +159,7 @@ export const UserSettingsTokens: FunctionComponent = () => {
159
159
  }}
160
160
  >
161
161
  <Box mr={1} mb={1}>
162
- <GenerateTokenDialog
162
+ <GenerateAccessTokenDialog
163
163
  handleTokenGenerated={handleTokenGenerated}
164
164
  />
165
165
  </Box>
package/src/utils.ts CHANGED
@@ -52,22 +52,26 @@ export function toLocalTime(timestamp?: string): string | undefined {
52
52
  return new Intl.DateTimeFormat(undefined, options).format(date);
53
53
  }
54
54
 
55
- const msPerMinute = 60 * 1000;
55
+ const msPerSecond = 1000;
56
+ const msPerMinute = msPerSecond * 60;
56
57
  const msPerHour = msPerMinute * 60;
57
58
  const msPerDay = msPerHour * 24;
58
59
  const msPerMonth = msPerDay * 30.4;
59
60
  const msPerYear = msPerDay * 365;
60
- export function toRelativeTime(timestamp?: string): string | undefined {
61
+ export function toRelativeTime(timestamp?: string, isFutureTime: boolean = false): string | undefined {
61
62
  if (!timestamp) {
62
63
  return undefined;
63
64
  }
64
65
  const date = new Date(timestamp);
65
66
  const elapsed = Date.now() - date.getTime();
66
67
 
67
- if (elapsed < 0) {
68
+ if (isFutureTime) {
68
69
  const remaining = -elapsed;
69
- if (remaining < msPerMinute) {
70
- return 'now';
70
+ if (remaining < 0) {
71
+ return "now";
72
+ } else if (remaining < msPerMinute) {
73
+ const value = Math.round(remaining / msPerSecond);
74
+ return `in ${value} second${value !== 1 ? 's' : ''}`;
71
75
  } else if (remaining < msPerHour) {
72
76
  const value = Math.round(remaining / msPerMinute);
73
77
  return `in ${value} minute${value !== 1 ? 's' : ''}`;
@@ -1,21 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2020 TypeFox and others
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the Eclipse Public License v. 2.0 which is available at
6
- * http://www.eclipse.org/legal/epl-2.0.
7
- *
8
- * SPDX-License-Identifier: EPL-2.0
9
- ********************************************************************************/
10
- import { FunctionComponent, ReactElement } from 'react';
11
- import { TooltipProps } from '@mui/material';
12
- export declare const CopyToClipboard: FunctionComponent<CopyToClipboardProps>;
13
- interface ChildProps {
14
- copy: (content: any) => void;
15
- }
16
- export interface CopyToClipboardProps {
17
- tooltipProps?: Partial<TooltipProps>;
18
- children: (props: ChildProps) => ReactElement<any>;
19
- }
20
- export {};
21
- //# sourceMappingURL=copy-to-clipboard.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"copy-to-clipboard.d.ts","sourceRoot":"","sources":["../../src/components/copy-to-clipboard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;kFAQkF;AAElF,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAY,MAAM,OAAO,CAAC;AAElE,OAAO,EAAW,YAAY,EAAE,MAAM,eAAe,CAAC;AAEtD,eAAO,MAAM,eAAe,EAAE,iBAAiB,CAAC,oBAAoB,CA0BnE,CAAC;AAEF,UAAU,UAAU;IAChB,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACjC,YAAY,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,YAAY,CAAC,GAAG,CAAC,CAAC;CACtD"}
@@ -1,25 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- /********************************************************************************
3
- * Copyright (c) 2020 TypeFox and others
4
- *
5
- * This program and the accompanying materials are made available under the
6
- * terms of the Eclipse Public License v. 2.0 which is available at
7
- * http://www.eclipse.org/legal/epl-2.0.
8
- *
9
- * SPDX-License-Identifier: EPL-2.0
10
- ********************************************************************************/
11
- import { useState } from 'react';
12
- import copy from 'clipboard-copy';
13
- import { Tooltip } from '@mui/material';
14
- export const CopyToClipboard = props => {
15
- const [showTooltip, setTooltip] = useState(false);
16
- const handleOnTooltipClose = () => {
17
- setTooltip(false);
18
- };
19
- const onCopy = (content) => {
20
- copy(content);
21
- setTooltip(true);
22
- };
23
- return (_jsx(Tooltip, { open: showTooltip, title: 'Copied to clipboard!', leaveDelay: 1500, onClose: handleOnTooltipClose, disableHoverListener: true, ...props.tooltipProps ?? {}, children: props.children({ copy: onCopy }) }));
24
- };
25
- //# sourceMappingURL=copy-to-clipboard.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"copy-to-clipboard.js","sourceRoot":"","sources":["../../src/components/copy-to-clipboard.tsx"],"names":[],"mappings":";AAAA;;;;;;;;kFAQkF;AAElF,OAAO,EAAmC,QAAQ,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,IAAI,MAAM,gBAAgB,CAAC;AAClC,OAAO,EAAE,OAAO,EAAgB,MAAM,eAAe,CAAC;AAEtD,MAAM,CAAC,MAAM,eAAe,GAA4C,KAAK,CAAC,EAAE;IAC5E,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IAE3D,MAAM,oBAAoB,GAAG,GAAG,EAAE;QAC9B,UAAU,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,OAAa,EAAE,EAAE;QAC7B,IAAI,CAAC,OAAO,CAAC,CAAC;QACd,UAAU,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC,CAAC;IAEF,OAAO,CACH,KAAC,OAAO,IACJ,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,sBAAsB,EAC7B,UAAU,EAAE,IAAI,EAChB,OAAO,EAAE,oBAAoB,EAC7B,oBAAoB,WAChB,KAAK,CAAC,YAAY,IAAI,EAAE,YAGxB,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAsB,GAEnD,CACb,CAAC;AACN,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-token-dialog.d.ts","sourceRoot":"","sources":["../../../src/pages/user/generate-token-dialog.tsx"],"names":[],"mappings":"AAAA;;;;;;;;kFAQkF;AAElF,OAAO,EAAe,iBAAiB,EAA2C,MAAM,OAAO,CAAC;AAShG,eAAO,MAAM,mBAAmB,EAAE,iBAAiB,CAAC,wBAAwB,CAqI3E,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACrC,oBAAoB,EAAE,MAAM,IAAI,CAAC;CACpC"}
@@ -1,79 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- /********************************************************************************
3
- * Copyright (c) 2019 TypeFox and others
4
- *
5
- * This program and the accompanying materials are made available under the
6
- * terms of the Eclipse Public License v. 2.0 which is available at
7
- * http://www.eclipse.org/legal/epl-2.0.
8
- *
9
- * SPDX-License-Identifier: EPL-2.0
10
- ********************************************************************************/
11
- import { useContext, useEffect, useState, useRef } from 'react';
12
- import { Button, Dialog, DialogTitle, DialogContent, DialogContentText, Box, TextField, DialogActions, Typography } from '@mui/material';
13
- import { ButtonWithProgress } from '../../components/button-with-progress';
14
- import { CopyToClipboard } from '../../components/copy-to-clipboard';
15
- import { isError } from '../../extension-registry-types';
16
- import { MainContext } from '../../context';
17
- const TOKEN_DESCRIPTION_SIZE = 255;
18
- export const GenerateTokenDialog = props => {
19
- const [open, setOpen] = useState(false);
20
- const [posted, setPosted] = useState(false);
21
- const [description, setDescription] = useState('');
22
- const [descriptionError, setDescriptionError] = useState();
23
- const [token, setToken] = useState();
24
- const context = useContext(MainContext);
25
- const abortController = useRef(new AbortController());
26
- useEffect(() => {
27
- document.addEventListener('keydown', handleEnter);
28
- return () => {
29
- abortController.current.abort();
30
- document.removeEventListener('keydown', handleEnter);
31
- };
32
- }, []);
33
- const handleOpenDialog = () => {
34
- setOpen(true);
35
- setPosted(false);
36
- setDescription('');
37
- setToken(undefined);
38
- };
39
- const handleClose = () => setOpen(false);
40
- const handleDescriptionChange = (event) => {
41
- const description = event.target.value;
42
- let descriptionError;
43
- if (description.length > TOKEN_DESCRIPTION_SIZE) {
44
- descriptionError = `The description must not be longer than ${TOKEN_DESCRIPTION_SIZE} characters.`;
45
- }
46
- setDescription(description);
47
- setDescriptionError(descriptionError);
48
- };
49
- const handleGenerate = async () => {
50
- if (!context.user) {
51
- return;
52
- }
53
- setPosted(true);
54
- try {
55
- const token = await context.service.createAccessToken(abortController.current, context.user, description);
56
- if (isError(token)) {
57
- throw token;
58
- }
59
- setToken(token);
60
- props.handleTokenGenerated();
61
- }
62
- catch (err) {
63
- context.handleError(err);
64
- }
65
- };
66
- const handleEnter = (e) => {
67
- if (e.code === 'Enter' && open && !token) {
68
- handleGenerate();
69
- }
70
- };
71
- return _jsxs(_Fragment, { children: [_jsx(Button, { variant: 'outlined', onClick: handleOpenDialog, children: "Generate new token" }), _jsxs(Dialog, { open: open, onClose: handleClose, children: [_jsx(DialogTitle, { children: "Generate new token" }), _jsxs(DialogContent, { children: [_jsx(DialogContentText, { children: "Describe where you will use this token." }), _jsx(Box, { my: 2, children: _jsx(TextField, { disabled: Boolean(token), fullWidth: true, label: 'Token Description', error: Boolean(descriptionError), helperText: descriptionError, onChange: handleDescriptionChange }) }), _jsx(TextField, { disabled: !token, margin: 'dense', label: 'Generated Token...', fullWidth: true, multiline: true, variant: 'outlined', rows: 4, value: token ? token.value : '' }), token ?
72
- _jsx(Box, { children: _jsx(Typography, { sx: { color: 'red', fontWeight: 'bold' }, children: "Copy and paste this token to a safe place. It will not be displayed again." }) }) : null] }), _jsxs(DialogActions, { children: [token ?
73
- _jsx(CopyToClipboard, { tooltipProps: { placement: 'left' }, children: ({ copy }) => (_jsx(Button, { autoFocus: true, variant: 'contained', color: 'secondary', onClick: () => {
74
- copy(token.value);
75
- setTimeout(handleClose, 700);
76
- }, children: "Copy" })) }) : null, _jsx(Button, { onClick: handleClose, color: 'secondary', children: token ? 'Close' : 'Cancel' }), !token ?
77
- _jsx(ButtonWithProgress, { autoFocus: true, sx: { ml: 1 }, error: Boolean(descriptionError), working: posted, onClick: handleGenerate, children: "Generate Token" }) : null] })] })] });
78
- };
79
- //# sourceMappingURL=generate-token-dialog.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-token-dialog.js","sourceRoot":"","sources":["../../../src/pages/user/generate-token-dialog.tsx"],"names":[],"mappings":";AAAA;;;;;;;;kFAQkF;AAElF,OAAO,EAAkC,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,iBAAiB,EAAE,GAAG,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACzI,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,EAAuB,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAEnC,MAAM,CAAC,MAAM,mBAAmB,GAAgD,KAAK,CAAC,EAAE;IACpF,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IACjD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IACrD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAS,EAAE,CAAC,CAAC;IAC3D,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,EAAU,CAAC;IACnE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,EAAuB,CAAC;IAE1D,MAAM,OAAO,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACxC,MAAM,eAAe,GAAG,MAAM,CAAkB,IAAI,eAAe,EAAE,CAAC,CAAC;IAEvE,SAAS,CAAC,GAAG,EAAE;QACX,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QAClD,OAAO,GAAG,EAAE;YACR,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAChC,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QACzD,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,gBAAgB,GAAG,GAAG,EAAE;QAC1B,OAAO,CAAC,IAAI,CAAC,CAAC;QACd,SAAS,CAAC,KAAK,CAAC,CAAC;QACjB,cAAc,CAAC,EAAE,CAAC,CAAC;QACnB,QAAQ,CAAC,SAAS,CAAC,CAAC;IACxB,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAEzC,MAAM,uBAAuB,GAAG,CAAC,KAAoC,EAAE,EAAE;QACrE,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC;QACvC,IAAI,gBAAoC,CAAC;QACzC,IAAI,WAAW,CAAC,MAAM,GAAG,sBAAsB,EAAE,CAAC;YAC9C,gBAAgB,GAAG,2CAA2C,sBAAsB,cAAc,CAAC;QACvG,CAAC;QAED,cAAc,CAAC,WAAW,CAAC,CAAC;QAC5B,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;IAC1C,CAAC,CAAC;IAEF,MAAM,cAAc,GAAG,KAAK,IAAI,EAAE;QAC9B,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAChB,OAAO;QACX,CAAC;QACD,SAAS,CAAC,IAAI,CAAC,CAAC;QAChB,IAAI,CAAC;YACD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YAC1G,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACjB,MAAM,KAAK,CAAC;YAChB,CAAC;YACD,QAAQ,CAAC,KAAK,CAAC,CAAC;YAChB,KAAK,CAAC,oBAAoB,EAAE,CAAC;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,CAAgB,EAAE,EAAE;QACrC,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACvC,cAAc,EAAE,CAAC;QACrB,CAAC;IACL,CAAC,CAAC;IAEF,OAAO,8BACH,KAAC,MAAM,IAAC,OAAO,EAAC,UAAU,EAAC,OAAO,EAAE,gBAAgB,mCAA6B,EACjF,MAAC,MAAM,IAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,aACpC,KAAC,WAAW,qCAAiC,EAC7C,MAAC,aAAa,eACV,KAAC,iBAAiB,0DAEE,EACpB,KAAC,GAAG,IAAC,EAAE,EAAE,CAAC,YACN,KAAC,SAAS,IACN,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,EACxB,SAAS,QACT,KAAK,EAAC,mBAAmB,EACzB,KAAK,EAAE,OAAO,CAAC,gBAAgB,CAAC,EAChC,UAAU,EAAE,gBAAgB,EAC5B,QAAQ,EAAE,uBAAuB,GAAI,GACvC,EACN,KAAC,SAAS,IACN,QAAQ,EAAE,CAAC,KAAK,EAChB,MAAM,EAAC,OAAO,EACd,KAAK,EAAC,oBAAoB,EAC1B,SAAS,QACT,SAAS,QACT,OAAO,EAAC,UAAU,EAClB,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GACjC,EAEE,KAAK,CAAC,CAAC;gCACP,KAAC,GAAG,cACA,KAAC,UAAU,IAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,2FAEvC,GACX,CAAC,CAAC,CAAC,IAAI,IAEL,EAChB,MAAC,aAAa,eAEN,KAAK,CAAC,CAAC;gCACP,KAAC,eAAe,IAAC,YAAY,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,YAC/C,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CACX,KAAC,MAAM,IACH,SAAS,QACT,OAAO,EAAC,WAAW,EACnB,KAAK,EAAC,WAAW,EACjB,OAAO,EAAE,GAAG,EAAE;4CACV,IAAI,CAAC,KAAM,CAAC,KAAK,CAAC,CAAC;4CACnB,UAAU,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;wCACjC,CAAC,qBAGI,CACZ,GACa,CAAC,CAAC,CAAC,IAAI,EAE7B,KAAC,MAAM,IAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAC,WAAW,YAC1C,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,GACtB,EAEL,CAAC,KAAK,CAAC,CAAC;gCACR,KAAC,kBAAkB,IACX,SAAS,QACT,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,EACb,KAAK,EAAE,OAAO,CAAC,gBAAgB,CAAC,EAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,cAAc,+BAEV,CAAC,CAAC,CAAC,IAAI,IAEpB,IACX,IACV,CAAC;AACR,CAAC,CAAC"}