openvsx-webui-test 0.20.0-dev.5 → 0.20.2-dev.0

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 (136) hide show
  1. package/lib/components/scan-admin/scan-card/scan-card-expanded-content.d.ts +2 -1
  2. package/lib/components/scan-admin/scan-card/scan-card-expanded-content.d.ts.map +1 -1
  3. package/lib/components/scan-admin/scan-card/scan-card-expanded-content.js.map +1 -1
  4. package/lib/components/scan-admin/scan-card/scan-card-header.js +1 -1
  5. package/lib/components/scan-admin/scan-card/scan-card-header.js.map +1 -1
  6. package/lib/components/scan-admin/scan-card/utils.js +1 -1
  7. package/lib/components/scan-admin/scan-card/utils.js.map +1 -1
  8. package/lib/components/sidepanel/navigation-item.d.ts +0 -1
  9. package/lib/components/sidepanel/navigation-item.d.ts.map +1 -1
  10. package/lib/components/sidepanel/navigation-item.js +22 -8
  11. package/lib/components/sidepanel/navigation-item.js.map +1 -1
  12. package/lib/components/sidepanel/sidebar-context.d.ts +16 -0
  13. package/lib/components/sidepanel/sidebar-context.d.ts.map +1 -0
  14. package/lib/components/sidepanel/sidebar-context.js +15 -0
  15. package/lib/components/sidepanel/sidebar-context.js.map +1 -0
  16. package/lib/components/sidepanel/sidepanel.d.ts +4 -4
  17. package/lib/components/sidepanel/sidepanel.d.ts.map +1 -1
  18. package/lib/components/sidepanel/sidepanel.js +47 -10
  19. package/lib/components/sidepanel/sidepanel.js.map +1 -1
  20. package/lib/default/menu-content.d.ts +1 -1
  21. package/lib/default/menu-content.js +1 -1
  22. package/lib/default/menu-content.js.map +1 -1
  23. package/lib/extension-registry-service.d.ts +6 -0
  24. package/lib/extension-registry-service.d.ts.map +1 -1
  25. package/lib/extension-registry-service.js +17 -0
  26. package/lib/extension-registry-service.js.map +1 -1
  27. package/lib/hooks/use-local-storage.d.ts +23 -0
  28. package/lib/hooks/use-local-storage.d.ts.map +1 -0
  29. package/lib/hooks/use-local-storage.js +62 -0
  30. package/lib/hooks/use-local-storage.js.map +1 -0
  31. package/lib/main.d.ts.map +1 -1
  32. package/lib/main.js +5 -5
  33. package/lib/main.js.map +1 -1
  34. package/lib/other-pages.d.ts.map +1 -1
  35. package/lib/other-pages.js +7 -7
  36. package/lib/other-pages.js.map +1 -1
  37. package/lib/pages/admin-dashboard/{admin-routes.d.ts → admin-dashboard-routes.d.ts} +6 -9
  38. package/lib/pages/admin-dashboard/admin-dashboard-routes.d.ts.map +1 -0
  39. package/lib/pages/admin-dashboard/{admin-routes.js → admin-dashboard-routes.js} +6 -9
  40. package/lib/pages/admin-dashboard/admin-dashboard-routes.js.map +1 -0
  41. package/lib/pages/admin-dashboard/admin-dashboard.d.ts.map +1 -1
  42. package/lib/pages/admin-dashboard/admin-dashboard.js +45 -101
  43. package/lib/pages/admin-dashboard/admin-dashboard.js.map +1 -1
  44. package/lib/pages/admin-dashboard/admin-header.d.ts +19 -0
  45. package/lib/pages/admin-dashboard/admin-header.d.ts.map +1 -0
  46. package/lib/pages/admin-dashboard/admin-header.js +16 -0
  47. package/lib/pages/admin-dashboard/admin-header.js.map +1 -0
  48. package/lib/pages/admin-dashboard/admin-sidepanel.d.ts +19 -0
  49. package/lib/pages/admin-dashboard/admin-sidepanel.d.ts.map +1 -0
  50. package/lib/pages/admin-dashboard/admin-sidepanel.js +17 -0
  51. package/lib/pages/admin-dashboard/admin-sidepanel.js.map +1 -0
  52. package/lib/pages/admin-dashboard/customers/customer-member-list.js +1 -1
  53. package/lib/pages/admin-dashboard/customers/customer-member-list.js.map +1 -1
  54. package/lib/pages/admin-dashboard/customers/customers.js +1 -1
  55. package/lib/pages/admin-dashboard/customers/customers.js.map +1 -1
  56. package/lib/pages/admin-dashboard/namespace-admin.d.ts.map +1 -1
  57. package/lib/pages/admin-dashboard/namespace-admin.js +4 -1
  58. package/lib/pages/admin-dashboard/namespace-admin.js.map +1 -1
  59. package/lib/pages/admin-dashboard/namespace-change-dialog.d.ts.map +1 -1
  60. package/lib/pages/admin-dashboard/namespace-change-dialog.js +6 -1
  61. package/lib/pages/admin-dashboard/namespace-change-dialog.js.map +1 -1
  62. package/lib/pages/admin-dashboard/namespace-delete-dialog.d.ts +23 -0
  63. package/lib/pages/admin-dashboard/namespace-delete-dialog.d.ts.map +1 -0
  64. package/lib/pages/admin-dashboard/namespace-delete-dialog.js +53 -0
  65. package/lib/pages/admin-dashboard/namespace-delete-dialog.js.map +1 -0
  66. package/lib/pages/admin-dashboard/nav-types.d.ts +27 -0
  67. package/lib/pages/admin-dashboard/nav-types.d.ts.map +1 -0
  68. package/lib/pages/admin-dashboard/nav-types.js +14 -0
  69. package/lib/pages/admin-dashboard/nav-types.js.map +1 -0
  70. package/lib/pages/admin-dashboard/publisher-admin.js +1 -1
  71. package/lib/pages/admin-dashboard/publisher-admin.js.map +1 -1
  72. package/lib/pages/admin-dashboard/scan-admin.d.ts.map +1 -1
  73. package/lib/pages/admin-dashboard/scan-admin.js +2 -5
  74. package/lib/pages/admin-dashboard/scan-admin.js.map +1 -1
  75. package/lib/pages/admin-dashboard/usage-stats/usage-stats.js +1 -1
  76. package/lib/pages/admin-dashboard/usage-stats/usage-stats.js.map +1 -1
  77. package/lib/pages/admin-dashboard/welcome.d.ts +5 -1
  78. package/lib/pages/admin-dashboard/welcome.d.ts.map +1 -1
  79. package/lib/pages/admin-dashboard/welcome.js +18 -16
  80. package/lib/pages/admin-dashboard/welcome.js.map +1 -1
  81. package/lib/pages/extension-detail/extension-detail-routes.d.ts +0 -1
  82. package/lib/pages/extension-detail/extension-detail-routes.d.ts.map +1 -1
  83. package/lib/pages/extension-detail/extension-detail-routes.js +2 -3
  84. package/lib/pages/extension-detail/extension-detail-routes.js.map +1 -1
  85. package/lib/pages/extension-detail/extension-detail.d.ts.map +1 -1
  86. package/lib/pages/extension-detail/extension-detail.js +120 -249
  87. package/lib/pages/extension-detail/extension-detail.js.map +1 -1
  88. package/lib/pages/extension-detail/use-extension-details.d.ts +23 -0
  89. package/lib/pages/extension-detail/use-extension-details.d.ts.map +1 -0
  90. package/lib/pages/extension-detail/use-extension-details.js +80 -0
  91. package/lib/pages/extension-detail/use-extension-details.js.map +1 -0
  92. package/lib/pages/user/avatar.js +1 -1
  93. package/lib/pages/user/avatar.js.map +1 -1
  94. package/lib/pages/user/user-setting-tabs.d.ts.map +1 -1
  95. package/lib/pages/user/user-setting-tabs.js +1 -1
  96. package/lib/pages/user/user-setting-tabs.js.map +1 -1
  97. package/lib/pages/user/user-settings-namespace-detail.d.ts +1 -0
  98. package/lib/pages/user/user-settings-namespace-detail.d.ts.map +1 -1
  99. package/lib/pages/user/user-settings-namespace-detail.js +19 -3
  100. package/lib/pages/user/user-settings-namespace-detail.js.map +1 -1
  101. package/lib/pages/user/user-settings-namespaces.js.map +1 -1
  102. package/package.json +3 -1
  103. package/src/components/scan-admin/scan-card/scan-card-expanded-content.tsx +4 -4
  104. package/src/components/scan-admin/scan-card/scan-card-header.tsx +1 -1
  105. package/src/components/scan-admin/scan-card/utils.ts +1 -1
  106. package/src/components/sidepanel/navigation-item.tsx +79 -23
  107. package/src/components/sidepanel/sidebar-context.tsx +17 -0
  108. package/src/components/sidepanel/sidepanel.tsx +57 -29
  109. package/src/default/menu-content.tsx +1 -1
  110. package/src/extension-registry-service.ts +18 -0
  111. package/src/hooks/use-local-storage.ts +67 -0
  112. package/src/main.tsx +11 -6
  113. package/src/other-pages.tsx +20 -16
  114. package/src/pages/admin-dashboard/{admin-routes.ts → admin-dashboard-routes.ts} +5 -8
  115. package/src/pages/admin-dashboard/admin-dashboard.tsx +71 -216
  116. package/src/pages/admin-dashboard/admin-header.tsx +59 -0
  117. package/src/pages/admin-dashboard/admin-sidepanel.tsx +59 -0
  118. package/src/pages/admin-dashboard/customers/customer-member-list.tsx +1 -1
  119. package/src/pages/admin-dashboard/customers/customers.tsx +1 -1
  120. package/src/pages/admin-dashboard/namespace-admin.tsx +5 -0
  121. package/src/pages/admin-dashboard/namespace-change-dialog.tsx +55 -46
  122. package/src/pages/admin-dashboard/namespace-delete-dialog.tsx +89 -0
  123. package/src/pages/admin-dashboard/nav-types.ts +31 -0
  124. package/src/pages/admin-dashboard/publisher-admin.tsx +1 -1
  125. package/src/pages/admin-dashboard/scan-admin.tsx +2 -5
  126. package/src/pages/admin-dashboard/usage-stats/usage-stats.tsx +1 -1
  127. package/src/pages/admin-dashboard/welcome.tsx +80 -48
  128. package/src/pages/extension-detail/extension-detail-routes.ts +2 -3
  129. package/src/pages/extension-detail/extension-detail.tsx +290 -409
  130. package/src/pages/extension-detail/use-extension-details.tsx +101 -0
  131. package/src/pages/user/avatar.tsx +1 -1
  132. package/src/pages/user/user-setting-tabs.tsx +3 -2
  133. package/src/pages/user/user-settings-namespace-detail.tsx +38 -5
  134. package/src/pages/user/user-settings-namespaces.tsx +1 -1
  135. package/lib/pages/admin-dashboard/admin-routes.d.ts.map +0 -1
  136. package/lib/pages/admin-dashboard/admin-routes.js.map +0 -1
@@ -11,25 +11,27 @@
11
11
  * SPDX-License-Identifier: EPL-2.0
12
12
  *****************************************************************************/
13
13
 
14
- import { FunctionComponent, useContext, useEffect, useState } from 'react';
14
+ import { FunctionComponent, useContext, useEffect, useState, lazy, Suspense } from 'react';
15
15
  import { Routes, Route } from 'react-router-dom';
16
16
  import { AppBar, Box, Toolbar } from '@mui/material';
17
17
  import { styled, Theme } from '@mui/material/styles';
18
18
  import { Banner } from './components/banner';
19
19
  import { MainContext } from './context';
20
20
  import { HeaderMenu } from './header-menu';
21
- import { ExtensionListContainer } from './pages/extension-list/extension-list-container';
22
- import { ExtensionListRoutes } from "./pages/extension-list/extension-list-routes";
23
- import { UserSettings } from './pages/user/user-settings';
21
+ import { ExtensionListRoutes } from './pages/extension-list/extension-list-routes';
24
22
  import { UserSettingsRoutes } from './pages/user/user-settings-routes';
25
- import { NamespaceDetail } from './pages/namespace-detail/namespace-detail';
26
23
  import { NamespaceDetailRoutes } from './pages/namespace-detail/namespace-detail-routes';
27
- import { ExtensionDetail } from './pages/extension-detail/extension-detail';
28
24
  import { ExtensionDetailRoutes } from './pages/extension-detail/extension-detail-routes';
29
25
  import { getCookieValueByKey, setCookie } from './utils';
30
26
  import { UserData } from './extension-registry-types';
27
+
28
+ import { ExtensionDetail } from './pages/extension-detail/extension-detail';
29
+ import { ExtensionListContainer } from './pages/extension-list/extension-list-container';
30
+ import { NamespaceDetail } from './pages/namespace-detail/namespace-detail';
31
31
  import { NotFound } from './not-found';
32
32
 
33
+ const UserSettings = lazy(() => import('./pages/user/user-settings').then(m => ({ default: m.UserSettings })));
34
+
33
35
  const ToolbarItem = styled(Box)({
34
36
  display: 'flex',
35
37
  alignItems: 'center'
@@ -122,16 +124,18 @@ export const OtherPages: FunctionComponent<OtherPagesProps> = (props) => {
122
124
  : null
123
125
  }
124
126
  <Box pb={`${getContentPadding()}px`}>
125
- <Routes>
126
- <Route path={ExtensionListRoutes.MAIN} element={ <ExtensionListContainer /> } />
127
- <Route path={UserSettingsRoutes.MAIN} element={<UserSettings userLoading={props.userLoading} />} />
128
- <Route path={UserSettingsRoutes.DELETE_EXTENSION} element={<UserSettings userLoading={props.userLoading} />} />
129
- <Route path={NamespaceDetailRoutes.MAIN} element={ <NamespaceDetail /> } />
130
- <Route path={ExtensionDetailRoutes.MAIN} element={<ExtensionDetail />} />
131
- <Route path={ExtensionDetailRoutes.MAIN_TARGET} element={<ExtensionDetail />} />
132
- {AdditionalRoutes ?? null}
133
- <Route path='*' element={<NotFound />} />
134
- </Routes>
127
+ <Suspense fallback={null}>
128
+ <Routes>
129
+ <Route path={ExtensionListRoutes.MAIN} element={ <ExtensionListContainer /> } />
130
+ <Route path={UserSettingsRoutes.MAIN} element={<UserSettings userLoading={props.userLoading} />} />
131
+ <Route path={UserSettingsRoutes.DELETE_EXTENSION} element={<UserSettings userLoading={props.userLoading} />} />
132
+ <Route path={NamespaceDetailRoutes.MAIN} element={ <NamespaceDetail /> } />
133
+ <Route path={ExtensionDetailRoutes.MAIN} element={<ExtensionDetail />} />
134
+ <Route path={ExtensionDetailRoutes.MAIN_TARGET} element={<ExtensionDetail />} />
135
+ {AdditionalRoutes ?? null}
136
+ <Route path='*' element={<NotFound />} />
137
+ </Routes>
138
+ </Suspense>
135
139
  </Box>
136
140
  {
137
141
  FooterComponent ?
@@ -1,15 +1,12 @@
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.
1
+ /********************************************************************************
2
+ * Copyright (c) 2020 TypeFox and others
6
3
  *
7
4
  * 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.
5
+ * terms of the Eclipse Public License v. 2.0 which is available at
6
+ * http://www.eclipse.org/legal/epl-2.0.
10
7
  *
11
8
  * SPDX-License-Identifier: EPL-2.0
12
- *****************************************************************************/
9
+ ********************************************************************************/
13
10
 
14
11
  import { createRoute } from '../../utils';
15
12
 
@@ -8,95 +8,62 @@
8
8
  * SPDX-License-Identifier: EPL-2.0
9
9
  ********************************************************************************/
10
10
 
11
- import { FunctionComponent, ReactNode, useContext, useState } from 'react';
11
+ import { FunctionComponent, ReactNode, useContext, lazy, Suspense } from 'react';
12
12
  import {
13
13
  Box,
14
14
  Container,
15
15
  CssBaseline,
16
16
  Typography,
17
17
  IconButton,
18
- Breadcrumbs,
19
- LinkProps,
20
- Link,
21
- Toolbar
22
18
  } from '@mui/material';
23
- import MuiAppBar, { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar';
24
- import { styled } from "@mui/material/styles";
25
- import { Link as RouterLink, Route, Routes, useNavigate, useLocation } from 'react-router-dom';
19
+ import { styled } from '@mui/material/styles';
20
+ import { Route, Routes, useNavigate } from 'react-router-dom';
26
21
  import AccountBoxIcon from '@mui/icons-material/AccountBox';
27
22
  import AssignmentIndIcon from '@mui/icons-material/AssignmentInd';
28
23
  import BarChartIcon from '@mui/icons-material/BarChart';
29
24
  import ExtensionSharpIcon from '@mui/icons-material/ExtensionSharp';
30
- import HighlightOffIcon from '@mui/icons-material/HighlightOff';
31
25
  import HistoryIcon from '@mui/icons-material/History';
32
- import MenuIcon from '@mui/icons-material/Menu';
33
26
  import PeopleIcon from '@mui/icons-material/People';
34
27
  import PersonIcon from '@mui/icons-material/Person';
35
28
  import SecurityIcon from '@mui/icons-material/Security';
36
29
  import SpeedIcon from '@mui/icons-material/Speed';
37
30
  import StarIcon from '@mui/icons-material/Star';
38
- import { CustomerDetails } from './customers/customer-details';
39
- import { Customers } from './customers/customers';
40
- import { DrawerHeader } from '../../components/sidepanel/drawer-header';
41
- import { Sidepanel } from "../../components/sidepanel/sidepanel";
42
- import { ExtensionAdmin } from './extension-admin';
43
31
  import { LoginComponent } from "../../default/login";
44
- import { Logs } from './logs/logs';
45
32
  import { MainContext } from '../../context';
33
+ import { AdminDashboardRoutes } from './admin-dashboard-routes';
34
+ import { AdminSidepanel } from './admin-sidepanel';
35
+ import { AdminHeader } from './admin-header';
36
+ import { isNavGroup, NavEntry } from './nav-types';
37
+
46
38
  import { NamespaceAdmin } from './namespace-admin';
47
- import { NavigationItem } from '../../components/sidepanel/navigation-item';
48
39
  import { PublisherAdmin } from './publisher-admin';
49
40
  import { ScanAdmin } from './scan-admin';
50
41
  import { Tiers } from './tiers/tiers';
51
- import { UsageStatsView } from './usage-stats/usage-stats';
42
+ import { Customers } from './customers/customers';
43
+ import { CustomerDetails } from './customers/customer-details';
44
+ import { Logs } from './logs/logs';
52
45
  import { Welcome } from './welcome';
53
- import { AdminDashboardRoutes } from "./admin-routes";
54
-
55
- const Message: FunctionComponent<{message: string}> = ({ message }) => {
56
- return (<Box sx={{
57
- display: 'flex',
58
- justifyContent: 'center',
59
- alignItems: 'center',
60
- width: '100%'
61
- }}>
62
- <Typography variant='h6'>{message}</Typography>
63
- </Box>);
64
- };
65
-
66
- interface RouteEntry {
67
- path: string;
68
- name: string;
69
- icon: ReactNode;
70
- }
71
-
72
- interface NavGroup {
73
- name: string;
74
- icon: ReactNode;
75
- children: RouteEntry[];
76
- }
77
46
 
78
- type NavEntry = RouteEntry | NavGroup;
79
-
80
- const isNavGroup = (entry: NavEntry): entry is NavGroup => 'children' in entry;
47
+ const ExtensionAdmin = lazy(() => import('./extension-admin').then(m => ({ default: m.ExtensionAdmin })));
48
+ const UsageStatsView = lazy(() => import('./usage-stats/usage-stats').then(m => ({ default: m.UsageStatsView })));
81
49
 
82
50
  const navConfig: NavEntry[] = [
83
- { path: AdminDashboardRoutes.NAMESPACE_ADMIN, name: 'Namespaces', icon: <AssignmentIndIcon /> },
84
- { path: AdminDashboardRoutes.EXTENSION_ADMIN, name: 'Extensions', icon: <ExtensionSharpIcon /> },
85
- { path: AdminDashboardRoutes.PUBLISHER_ADMIN, name: 'Publisher', icon: <PersonIcon /> },
86
- { path: AdminDashboardRoutes.SCANS_ADMIN, name: 'Scans', icon: <SecurityIcon /> },
51
+ { path: AdminDashboardRoutes.NAMESPACE_ADMIN, name: 'Namespaces', icon: <AssignmentIndIcon />, description: 'Manage user roles and create new namespaces' },
52
+ { path: AdminDashboardRoutes.EXTENSION_ADMIN, name: 'Extensions', icon: <ExtensionSharpIcon />, description: 'Search for extensions and remove certain versions' },
53
+ { path: AdminDashboardRoutes.PUBLISHER_ADMIN, name: 'Publisher', icon: <PersonIcon />, description: 'Search for publishers and revoke their contributions' },
54
+ { path: AdminDashboardRoutes.SCANS_ADMIN, name: 'Scans', icon: <SecurityIcon />, description: 'View security scan results and manage quarantined extensions' },
87
55
  {
88
56
  name: 'Rate Limiting',
89
57
  icon: <SpeedIcon />,
90
58
  children: [
91
- { path: AdminDashboardRoutes.TIERS, name: 'Tiers', icon: <StarIcon /> },
92
- { path: AdminDashboardRoutes.CUSTOMERS, name: 'Customers', icon: <PeopleIcon /> },
93
- { path: AdminDashboardRoutes.USAGE_STATS, name: 'Usage Stats', icon: <BarChartIcon /> },
59
+ { path: AdminDashboardRoutes.TIERS, name: 'Tiers', icon: <StarIcon />, description: 'Manage rate-limit tiers' },
60
+ { path: AdminDashboardRoutes.CUSTOMERS, name: 'Customers', icon: <PeopleIcon />, description: 'Manage rate-limit customers' },
61
+ { path: AdminDashboardRoutes.USAGE_STATS, name: 'Usage Stats', icon: <BarChartIcon />, description: 'Show usage stats for customers' },
94
62
  ],
95
63
  },
96
- { path: AdminDashboardRoutes.LOGS, name: 'Logs', icon: <HistoryIcon /> },
64
+ { path: AdminDashboardRoutes.LOGS, name: 'Logs', icon: <HistoryIcon />, description: 'Browse admin activity logs' },
97
65
  ];
98
66
 
99
- // Flat name lookup for breadcrumbs
100
67
  const routeNames: { [key: string]: string } = {
101
68
  [AdminDashboardRoutes.MAIN]: 'Admin Dashboard',
102
69
  ...navConfig.reduce<{ [key: string]: string }>((acc, entry) => {
@@ -111,183 +78,71 @@ const routeNames: { [key: string]: string } = {
111
78
  }, {}),
112
79
  };
113
80
 
114
- const drawerWidth = 240;
115
-
116
- interface AppBarProps extends MuiAppBarProps {
117
- open?: boolean;
118
- }
119
-
120
- const AppBar = styled(MuiAppBar, {
121
- shouldForwardProp: (prop) => prop !== 'open',
122
- })<AppBarProps>(({ theme, open }) => ({
123
- transition: theme.transitions.create(['margin', 'width'], {
124
- easing: theme.transitions.easing.sharp,
125
- duration: theme.transitions.duration.leavingScreen,
126
- }),
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
- }),
81
+ const ScrollableContent = styled(Box)(({ theme }) => ({
82
+ flex: 1,
83
+ overflowY: 'auto',
84
+ '&::-webkit-scrollbar': {
85
+ width: '12px',
86
+ },
87
+ '&::-webkit-scrollbar-track': {
88
+ backgroundColor: theme.palette.action.hover,
89
+ },
90
+ '&::-webkit-scrollbar-thumb': {
91
+ backgroundColor: theme.palette.action.selected,
92
+ borderRadius: '6px',
93
+ '&:hover': {
94
+ backgroundColor: theme.palette.action.focus,
95
+ },
96
+ },
135
97
  }));
136
98
 
137
- interface LinkRouterProps extends LinkProps {
138
- to: string;
139
- replace?: boolean;
140
- }
141
-
142
- const LinkRouter = (props: LinkRouterProps) => (
143
- <Link {...props} component={RouterLink as any} />
144
- );
145
-
146
- const BreadcrumbsComponent = () => {
147
- const { pathname } = useLocation();
148
-
149
- const pathnames = pathname.split("/").filter((segment) => segment);
150
-
151
- return (
152
- <Breadcrumbs aria-label='breadcrumb' sx={{ pt: 2, pb: 2, px: 4 }} >
153
- <LinkRouter underline='hover' color='inherit' to='/'>
154
- Home
155
- </LinkRouter>
156
- {pathnames.map((value, index) => {
157
- const last = index === pathnames.length - 1;
158
- const to = `/${pathnames.slice(0, index + 1).join("/")}`;
159
-
160
- return last ? (
161
- <Typography color='text.primary' key={to}>
162
- {routeNames[to] ?? value}
163
- </Typography>
164
- ) : (
165
- <LinkRouter underline='hover' color='inherit' to={to} key={to}>
166
- {routeNames[to]}
167
- </LinkRouter>
168
- );
169
- })}
170
- </Breadcrumbs>
171
- );
99
+ const Message: FunctionComponent<{message: string}> = ({ message }) => {
100
+ return (<Box sx={{
101
+ display: 'flex',
102
+ justifyContent: 'center',
103
+ alignItems: 'center',
104
+ width: '100%'
105
+ }}>
106
+ <Typography variant='h6'>{message}</Typography>
107
+ </Box>);
172
108
  };
173
109
 
174
- const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{
175
- open?: boolean;
176
- }>(({ theme, open }) => ({
177
- flexGrow: 1,
178
- padding: theme.spacing(3),
179
- transition: theme.transitions.create('margin', {
180
- easing: theme.transitions.easing.sharp,
181
- duration: theme.transitions.duration.leavingScreen,
182
- }),
183
- marginLeft: `-${drawerWidth}px`,
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
- }),
191
- }));
192
-
193
110
  export const AdminDashboard: FunctionComponent<AdminDashboardProps> = props => {
194
111
  const { user, loginProviders } = useContext(MainContext);
195
- const [drawerOpen, setDrawerOpen] = useState(true);
196
112
 
197
113
  const navigate = useNavigate();
198
114
  const toMainPage = () => navigate('/');
199
115
 
200
- const [currentPage, setCurrentPage] = useState<string | undefined>(useLocation().pathname);
201
- const handleOpenRoute = (route: string) => {
202
- setCurrentPage(route);
203
- };
204
-
205
116
  let content: ReactNode = null;
206
117
  if (user?.role === 'admin') {
207
118
  content =
208
- <Box sx={{ display: 'flex', width: '100%' }}>
119
+ <Box sx={{ display: 'flex', width: '100%', height: '100%' }}>
209
120
  <CssBaseline />
210
- <AppBar position='fixed' open={drawerOpen} color='default' enableColorOnDark elevation={0}>
211
- <Toolbar>
212
- <IconButton
213
- aria-label='open drawer'
214
- onClick={() => setDrawerOpen(true)}
215
- edge='start'
216
- sx={[{ mr: 2 }, drawerOpen && { display: 'none' }]}
217
- >
218
- <MenuIcon />
219
- </IconButton>
220
- <Box sx={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
221
- <BreadcrumbsComponent />
222
- <IconButton onClick={toMainPage} sx={{ mt: 1, mr: 1 }}>
223
- <HighlightOffIcon/>
224
- </IconButton>
225
- </Box>
226
- </Toolbar>
227
- </AppBar>
228
- <Sidepanel width={drawerWidth} open={drawerOpen} handleDrawerClose={() => setDrawerOpen(false)} >
229
- {navConfig.map((entry) => {
230
- if (isNavGroup(entry)) {
231
- return (
232
- <NavigationItem
233
- key={entry.name}
234
- label={entry.name}
235
- icon={entry.icon}
236
- >
237
- {entry.children.map((child) => (
238
- <NavigationItem key={child.path} onOpenRoute={handleOpenRoute} active={currentPage?.startsWith(child.path)}
239
- label={child.name} icon={child.icon} route={child.path}/>
240
- ))}
241
- </NavigationItem>
242
- );
243
- }
244
- return (
245
- <NavigationItem key={entry.path} onOpenRoute={handleOpenRoute} active={currentPage?.startsWith(entry.path)}
246
- label={entry.name} icon={entry.icon} route={entry.path}/>
247
- );
248
- })}
249
- </Sidepanel>
250
- <Main open={drawerOpen} >
251
- <DrawerHeader />
252
- <Box
253
- overflow='auto'
254
- flex={1}
255
- sx={{
256
- overflowY: 'scroll',
257
- '&::-webkit-scrollbar': {
258
- width: '12px',
259
- },
260
- '&::-webkit-scrollbar-track': {
261
- backgroundColor: 'rgba(0, 0, 0, 0.2)',
262
- },
263
- '&::-webkit-scrollbar-thumb': {
264
- backgroundColor: 'rgba(255, 255, 255, 0.2)',
265
- borderRadius: '6px',
266
- '&:hover': {
267
- backgroundColor: 'rgba(255, 255, 255, 0.3)',
268
- },
269
- },
270
- }}
271
- >
272
- <Container sx={{ pt: 2, pb: 4, px: 3 }} maxWidth='xl'>
273
- <Routes>
274
- <Route path='/namespaces' element={<NamespaceAdmin/>} />
275
- <Route path='/extensions' element={<ExtensionAdmin/>} />
276
- <Route path='/extensions/:namespace/:extension' element={<ExtensionAdmin/>} />
277
- <Route path='/publisher' element={<PublisherAdmin/>} />
278
- <Route path='/publisher/:publisher' element={<PublisherAdmin/>} />
279
- <Route path='/scans' element={<ScanAdmin/>} />
280
- <Route path='/tiers' element={<Tiers/>} />
281
- <Route path='/customers' element={<Customers/>} />
282
- <Route path='/customers/:customer' element={<CustomerDetails/>} />
283
- <Route path='/usage' element={<UsageStatsView/>} />
284
- <Route path='/usage/:customer' element={<UsageStatsView/>} />
285
- <Route path='/logs' element={<Logs/>} />
286
- <Route path='*' element={<Welcome/>} />
287
- </Routes>
121
+ <AdminSidepanel items={navConfig} />
122
+ <Box sx={{ display: 'flex', flexDirection: 'column', flex: 1, overflow: 'hidden' }}>
123
+ <AdminHeader routeNames={routeNames} onClose={toMainPage} />
124
+ <ScrollableContent>
125
+ <Container sx={{ pt: 3, pb: 4, px: 3 }} maxWidth='xl'>
126
+ <Suspense fallback={null}>
127
+ <Routes>
128
+ <Route path='/namespaces' element={<NamespaceAdmin/>} />
129
+ <Route path='/extensions' element={<ExtensionAdmin/>} />
130
+ <Route path='/extensions/:namespace/:extension' element={<ExtensionAdmin/>} />
131
+ <Route path='/publisher' element={<PublisherAdmin/>} />
132
+ <Route path='/publisher/:publisher' element={<PublisherAdmin/>} />
133
+ <Route path='/scans' element={<ScanAdmin/>} />
134
+ <Route path='/tiers' element={<Tiers/>} />
135
+ <Route path='/customers' element={<Customers/>} />
136
+ <Route path='/customers/:customer' element={<CustomerDetails/>} />
137
+ <Route path='/usage' element={<UsageStatsView/>} />
138
+ <Route path='/usage/:customer' element={<UsageStatsView/>} />
139
+ <Route path='/logs' element={<Logs/>} />
140
+ <Route path='*' element={<Welcome items={navConfig} />} />
141
+ </Routes>
142
+ </Suspense>
288
143
  </Container>
289
- </Box>
290
- </Main>
144
+ </ScrollableContent>
145
+ </Box>
291
146
  </Box>;
292
147
  } else if (user) {
293
148
  content = <Message message='You are not authorized as administrator.'/>;
@@ -0,0 +1,59 @@
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 } from 'react';
15
+ import { AppBar, Breadcrumbs, IconButton, Link, Toolbar, Typography } from '@mui/material';
16
+ import HighlightOffIcon from '@mui/icons-material/HighlightOff';
17
+ import { Link as RouterLink, useLocation } from 'react-router-dom';
18
+
19
+ export interface AdminHeaderProps {
20
+ routeNames: Record<string, string>;
21
+ onClose: () => void;
22
+ }
23
+
24
+ const BreadcrumbsNav: FunctionComponent<{ routeNames: Record<string, string> }> = ({ routeNames }) => {
25
+ const { pathname } = useLocation();
26
+ const segments = pathname.split('/').filter(Boolean);
27
+
28
+ return (
29
+ <Breadcrumbs aria-label='breadcrumb' sx={{ pt: 2, pb: 2, px: 4 }}>
30
+ <Link component={RouterLink} to='/' underline='hover' color='inherit'>
31
+ Home
32
+ </Link>
33
+ {segments.map((value, index) => {
34
+ const to = `/${segments.slice(0, index + 1).join('/')}`;
35
+ const label = routeNames[to] ?? value;
36
+ const isLast = index === segments.length - 1;
37
+
38
+ return isLast ? (
39
+ <Typography color='text.primary' key={to}>{label}</Typography>
40
+ ) : (
41
+ <Link component={RouterLink} to={to} underline='hover' color='inherit' key={to}>
42
+ {label}
43
+ </Link>
44
+ );
45
+ })}
46
+ </Breadcrumbs>
47
+ );
48
+ };
49
+
50
+ export const AdminHeader: FunctionComponent<AdminHeaderProps> = ({ routeNames, onClose }) => (
51
+ <AppBar position='sticky' color='default' enableColorOnDark elevation={0}>
52
+ <Toolbar sx={{ display: 'flex', justifyContent: 'space-between' }}>
53
+ <BreadcrumbsNav routeNames={routeNames} />
54
+ <IconButton onClick={onClose} aria-label='close admin dashboard' sx={{ mt: 1, mr: 1 }}>
55
+ <HighlightOffIcon />
56
+ </IconButton>
57
+ </Toolbar>
58
+ </AppBar>
59
+ );
@@ -0,0 +1,59 @@
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 } from 'react';
15
+ import { useLocation } from 'react-router-dom';
16
+ import { Sidepanel } from '../../components/sidepanel/sidepanel';
17
+ import { NavigationItem } from '../../components/sidepanel/navigation-item';
18
+ import { isNavGroup, NavEntry } from './nav-types';
19
+ import { useLocalStorage } from '../../hooks/use-local-storage';
20
+
21
+ export interface AdminSidepanelProps {
22
+ items: NavEntry[];
23
+ }
24
+
25
+ export const AdminSidepanel: FunctionComponent<AdminSidepanelProps> = ({ items }) => {
26
+ const [open, setOpen] = useLocalStorage('openvsx-admin-sidepanel-open', true);
27
+ const { pathname } = useLocation();
28
+
29
+ return (
30
+ <Sidepanel open={open} onToggle={() => setOpen(prev => !prev)}>
31
+ {items.map((entry) => {
32
+ if (isNavGroup(entry)) {
33
+ return (
34
+ <NavigationItem key={entry.name} label={entry.name} icon={entry.icon}>
35
+ {entry.children.map((child) => (
36
+ <NavigationItem
37
+ key={child.path}
38
+ active={pathname.startsWith(child.path)}
39
+ label={child.name}
40
+ icon={child.icon}
41
+ route={child.path}
42
+ />
43
+ ))}
44
+ </NavigationItem>
45
+ );
46
+ }
47
+ return (
48
+ <NavigationItem
49
+ key={entry.path}
50
+ active={pathname.startsWith(entry.path)}
51
+ label={entry.name}
52
+ icon={entry.icon}
53
+ route={entry.path}
54
+ />
55
+ );
56
+ })}
57
+ </Sidepanel>
58
+ );
59
+ };
@@ -24,7 +24,7 @@ import {
24
24
  ListItemText, IconButton, type PaperProps, Paper
25
25
  } from '@mui/material';
26
26
  import { Link as RouterLink } from 'react-router-dom';
27
- import { AdminDashboardRoutes } from '../admin-routes';
27
+ import { AdminDashboardRoutes } from '../admin-dashboard-routes';
28
28
  import { MainContext } from '../../../context';
29
29
  import { CustomerMembership, Customer, UserData, isError } from '../../../extension-registry-types';
30
30
  import { AddUserDialog } from '../../user/add-user-dialog';
@@ -34,7 +34,7 @@ import { CustomerFormDialog } from "./customer-form-dialog";
34
34
  import { DeleteCustomerDialog } from "./delete-customer-dialog";
35
35
  import { createRoute, handleError } from "../../../utils";
36
36
  import { createMultiSelectFilterOperators, createArrayContainsFilterOperators } from "../components";
37
- import { AdminDashboardRoutes } from "../admin-routes";
37
+ import { AdminDashboardRoutes } from "../admin-dashboard-routes";
38
38
  import { Link } from "react-router-dom";
39
39
 
40
40
  export const Customers: FC = () => {
@@ -31,6 +31,10 @@ export const NamespaceAdmin: FunctionComponent = props => {
31
31
  };
32
32
  }, []);
33
33
 
34
+ const handleDeleteNamespace = () => {
35
+ setCurrentNamespace(undefined);
36
+ };
37
+
34
38
  const fetchNamespace = async (namespaceName: string) => {
35
39
  if (!namespaceName) {
36
40
  setCurrentNamespace(undefined);
@@ -82,6 +86,7 @@ export const NamespaceAdmin: FunctionComponent = props => {
82
86
  listContainer = <NamespaceDetailConfigContext.Provider value={{ defaultMemberRole: 'owner' }}>
83
87
  <NamespaceDetail
84
88
  setLoadingState={setLoading}
89
+ onDelete={handleDeleteNamespace}
85
90
  namespace={currentNamespace}
86
91
  filterUsers={() => true}
87
92
  fixSelf={false}