mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-24 17:51:14 +02:00
feat: admin menu (#9617)
This commit is contained in:
parent
328c5368ed
commit
07a4106f48
@ -2,6 +2,7 @@ import { Routes, Route } from 'react-router-dom';
|
|||||||
import { ApiTokenPage } from './apiToken/ApiTokenPage/ApiTokenPage';
|
import { ApiTokenPage } from './apiToken/ApiTokenPage/ApiTokenPage';
|
||||||
import { CreateApiToken } from './apiToken/CreateApiToken/CreateApiToken';
|
import { CreateApiToken } from './apiToken/CreateApiToken/CreateApiToken';
|
||||||
import { AuthSettings } from './auth/AuthSettings';
|
import { AuthSettings } from './auth/AuthSettings';
|
||||||
|
import { OldAuthSettings } from './auth/OldAuthSettings';
|
||||||
import { Billing } from './billing/Billing';
|
import { Billing } from './billing/Billing';
|
||||||
import FlaggedBillingRedirect from './billing/FlaggedBillingRedirect/FlaggedBillingRedirect';
|
import FlaggedBillingRedirect from './billing/FlaggedBillingRedirect/FlaggedBillingRedirect';
|
||||||
import { CorsAdmin } from './cors';
|
import { CorsAdmin } from './cors';
|
||||||
@ -47,7 +48,11 @@ export const Admin = () => {
|
|||||||
<Route path='banners' element={<Banners />} />
|
<Route path='banners' element={<Banners />} />
|
||||||
<Route path='license' element={<License />} />
|
<Route path='license' element={<License />} />
|
||||||
<Route path='cors' element={<CorsAdmin />} />
|
<Route path='cors' element={<CorsAdmin />} />
|
||||||
<Route path='auth' element={<AuthSettings />} />
|
{newAdminUIEnabled ? (
|
||||||
|
<Route path='auth/*' element={<AuthSettings />} />
|
||||||
|
) : (
|
||||||
|
<Route path='auth' element={<OldAuthSettings />} />
|
||||||
|
)}
|
||||||
<Route
|
<Route
|
||||||
path='admin-invoices'
|
path='admin-invoices'
|
||||||
element={<FlaggedBillingRedirect />}
|
element={<FlaggedBillingRedirect />}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
import type { VFC } from 'react';
|
import type { VFC } from 'react';
|
||||||
import { adminGroups } from './adminRoutes';
|
import { adminGroups } from './oldAdminRoutes';
|
||||||
import type { INavigationMenuItem } from 'interfaces/route';
|
import type { INavigationMenuItem } from 'interfaces/route';
|
||||||
import { Box, Link, Typography } from '@mui/material';
|
import { Box, Link, Typography } from '@mui/material';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
@ -3,27 +3,26 @@ import type { INavigationMenuItem } from 'interfaces/route';
|
|||||||
export const adminGroups: Record<string, string> = {
|
export const adminGroups: Record<string, string> = {
|
||||||
users: 'User administration',
|
users: 'User administration',
|
||||||
access: 'Access control',
|
access: 'Access control',
|
||||||
|
sso: 'Single sign-on',
|
||||||
|
network: 'Network',
|
||||||
instance: 'Instance configuration',
|
instance: 'Instance configuration',
|
||||||
log: 'Logs',
|
|
||||||
other: 'Other',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const adminRoutes: INavigationMenuItem[] = [
|
export const adminRoutes: INavigationMenuItem[] = [
|
||||||
|
// Admin home
|
||||||
|
{
|
||||||
|
path: '/admin',
|
||||||
|
title: 'Admin home',
|
||||||
|
menu: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Users
|
||||||
{
|
{
|
||||||
path: '/admin/users',
|
path: '/admin/users',
|
||||||
title: 'Users',
|
title: 'Users',
|
||||||
menu: { adminSettings: true },
|
menu: { adminSettings: true },
|
||||||
group: 'users',
|
group: 'users',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/admin/service-accounts',
|
|
||||||
title: 'Service accounts',
|
|
||||||
menu: {
|
|
||||||
adminSettings: true,
|
|
||||||
mode: ['enterprise'],
|
|
||||||
},
|
|
||||||
group: 'users',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/admin/groups',
|
path: '/admin/groups',
|
||||||
title: 'Groups',
|
title: 'Groups',
|
||||||
@ -34,14 +33,44 @@ export const adminRoutes: INavigationMenuItem[] = [
|
|||||||
group: 'users',
|
group: 'users',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/admin/roles/*',
|
path: '/admin/roles',
|
||||||
title: 'Roles',
|
title: 'Root roles',
|
||||||
menu: {
|
menu: {
|
||||||
adminSettings: true,
|
adminSettings: true,
|
||||||
mode: ['enterprise'],
|
mode: ['enterprise'],
|
||||||
},
|
},
|
||||||
group: 'users',
|
group: 'users',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/roles/project-roles',
|
||||||
|
title: 'Project roles',
|
||||||
|
menu: {
|
||||||
|
adminSettings: true,
|
||||||
|
mode: ['enterprise'],
|
||||||
|
},
|
||||||
|
group: 'users',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/logins',
|
||||||
|
title: 'Login history',
|
||||||
|
menu: {
|
||||||
|
adminSettings: true,
|
||||||
|
mode: ['enterprise'],
|
||||||
|
},
|
||||||
|
group: 'users',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Service accounts
|
||||||
|
{
|
||||||
|
path: '/admin/service-accounts',
|
||||||
|
title: 'Service accounts',
|
||||||
|
menu: {
|
||||||
|
adminSettings: true,
|
||||||
|
mode: ['enterprise'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Access control
|
||||||
{
|
{
|
||||||
path: '/admin/api',
|
path: '/admin/api',
|
||||||
title: 'API access',
|
title: 'API access',
|
||||||
@ -55,18 +84,73 @@ export const adminRoutes: INavigationMenuItem[] = [
|
|||||||
menu: { adminSettings: true },
|
menu: { adminSettings: true },
|
||||||
group: 'access',
|
group: 'access',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Single sign-on/login
|
||||||
{
|
{
|
||||||
path: '/admin/auth',
|
path: '/admin/auth',
|
||||||
title: 'Single sign-on',
|
title: 'Open ID Connect',
|
||||||
menu: { adminSettings: true, mode: ['enterprise'] },
|
menu: { adminSettings: true, mode: ['enterprise'] },
|
||||||
group: 'access',
|
group: 'sso',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/admin/network/*',
|
path: '/admin/auth/saml',
|
||||||
title: 'Network',
|
title: 'SAML 2.0',
|
||||||
menu: { adminSettings: true, mode: ['pro', 'enterprise'] },
|
menu: { adminSettings: true, mode: ['enterprise'] },
|
||||||
group: 'instance',
|
group: 'sso',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/auth/password',
|
||||||
|
title: 'Password login',
|
||||||
|
menu: { adminSettings: true, mode: ['enterprise'] },
|
||||||
|
group: 'sso',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/auth/google',
|
||||||
|
title: 'Google',
|
||||||
|
menu: { adminSettings: true, mode: ['enterprise'] },
|
||||||
|
flag: 'googleAuthEnabled',
|
||||||
|
group: 'sso',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/auth/scim',
|
||||||
|
title: 'SCIM',
|
||||||
|
menu: { adminSettings: true, mode: ['enterprise'] },
|
||||||
|
group: 'sso',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Network
|
||||||
|
{
|
||||||
|
path: '/admin/network',
|
||||||
|
title: 'Overview',
|
||||||
|
menu: { adminSettings: true, mode: ['pro', 'enterprise'] },
|
||||||
|
group: 'network',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/network/traffic',
|
||||||
|
title: 'Traffic',
|
||||||
|
menu: { adminSettings: true, mode: ['pro', 'enterprise'] },
|
||||||
|
group: 'network',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/network/connected-edges',
|
||||||
|
title: 'Connected edges',
|
||||||
|
menu: { adminSettings: true, mode: ['pro', 'enterprise'] },
|
||||||
|
group: 'network',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/network/backend-connections',
|
||||||
|
title: 'Backend connections',
|
||||||
|
menu: { adminSettings: true, mode: ['pro', 'enterprise'] },
|
||||||
|
group: 'network',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/network/frontend-data-usage',
|
||||||
|
title: 'Frontend data usage',
|
||||||
|
menu: { adminSettings: true, mode: ['pro', 'enterprise'] },
|
||||||
|
group: 'network',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Instance configuration
|
||||||
{
|
{
|
||||||
path: '/admin/maintenance',
|
path: '/admin/maintenance',
|
||||||
title: 'Maintenance',
|
title: 'Maintenance',
|
||||||
@ -79,12 +163,6 @@ export const adminRoutes: INavigationMenuItem[] = [
|
|||||||
menu: { adminSettings: true, mode: ['enterprise'] },
|
menu: { adminSettings: true, mode: ['enterprise'] },
|
||||||
group: 'instance',
|
group: 'instance',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/admin/instance',
|
|
||||||
title: 'Instance stats',
|
|
||||||
menu: { adminSettings: true },
|
|
||||||
group: 'instance',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/admin/license',
|
path: '/admin/license',
|
||||||
title: 'License',
|
title: 'License',
|
||||||
@ -92,31 +170,30 @@ export const adminRoutes: INavigationMenuItem[] = [
|
|||||||
flag: 'enableLicense',
|
flag: 'enableLicense',
|
||||||
group: 'instance',
|
group: 'instance',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/instance',
|
||||||
|
title: 'Instance stats',
|
||||||
|
menu: { adminSettings: true },
|
||||||
|
group: 'instance',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/admin/instance-privacy',
|
path: '/admin/instance-privacy',
|
||||||
title: 'Instance privacy',
|
title: 'Instance privacy',
|
||||||
menu: { adminSettings: true },
|
menu: { adminSettings: true },
|
||||||
group: 'instance',
|
group: 'instance',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Billing
|
||||||
{
|
{
|
||||||
path: '/admin/admin-invoices',
|
path: '/admin/billing',
|
||||||
title: 'Billing & invoices',
|
title: 'Billing & invoices',
|
||||||
menu: { adminSettings: true, billing: true },
|
menu: { adminSettings: true, billing: true },
|
||||||
group: 'instance',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/admin/logins',
|
|
||||||
title: 'Login history',
|
|
||||||
menu: {
|
|
||||||
adminSettings: true,
|
|
||||||
mode: ['enterprise'],
|
|
||||||
},
|
|
||||||
group: 'log',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Event log
|
||||||
{
|
{
|
||||||
path: '/history',
|
path: '/history',
|
||||||
title: 'Event log',
|
title: 'Event log',
|
||||||
menu: { adminSettings: true },
|
menu: { adminSettings: true },
|
||||||
group: 'log',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Tab, Tabs } from '@mui/material';
|
import {} from '@mui/material';
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { OidcAuth } from './OidcAuth/OidcAuth';
|
import { OidcAuth } from './OidcAuth/OidcAuth';
|
||||||
@ -9,40 +9,42 @@ import { GoogleAuth } from './GoogleAuth/GoogleAuth';
|
|||||||
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
|
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
|
||||||
import { ADMIN, UPDATE_AUTH_CONFIGURATION } from '@server/types/permissions';
|
import { ADMIN, UPDATE_AUTH_CONFIGURATION } from '@server/types/permissions';
|
||||||
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
||||||
import { useState } from 'react';
|
import { Route, Routes, useLocation } from 'react-router-dom';
|
||||||
import { TabPanel } from 'component/common/TabNav/TabPanel/TabPanel';
|
|
||||||
import { usePageTitle } from 'hooks/usePageTitle';
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
|
||||||
export const AuthSettings = () => {
|
export const AuthSettings = () => {
|
||||||
const { uiConfig, isEnterprise } = useUiConfig();
|
const { isEnterprise } = useUiConfig();
|
||||||
|
const googleAuthEnabled = useUiFlag('googleAuthEnabled');
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: 'OpenID Connect',
|
label: 'Single sign-on: OpenID Connect',
|
||||||
component: <OidcAuth />,
|
path: '/admin/auth',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'SAML 2.0',
|
label: 'Single sign-on: SAML 2.0',
|
||||||
component: <SamlAuth />,
|
path: '/admin/auth/saml',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Password',
|
label: 'Password login',
|
||||||
component: <PasswordAuth />,
|
path: '/admin/auth/password',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Google',
|
label: 'Single sign-on: Google',
|
||||||
component: <GoogleAuth />,
|
path: '/admin/auth/google',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'SCIM',
|
label: 'Single sign-on: SCIM',
|
||||||
component: <ScimSettings />,
|
path: '/admin/auth/scim',
|
||||||
},
|
},
|
||||||
].filter(
|
];
|
||||||
(item) => uiConfig.flags?.googleAuthEnabled || item.label !== 'Google',
|
const { pathname } = useLocation();
|
||||||
);
|
const activeTab =
|
||||||
|
tabs.find((tab) => pathname === tab.path)?.label ||
|
||||||
|
'Single sign-on: OpenID Connect';
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
usePageTitle(activeTab);
|
||||||
usePageTitle(`Single sign-on: ${tabs[activeTab].label}`);
|
|
||||||
|
|
||||||
if (!isEnterprise()) {
|
if (!isEnterprise()) {
|
||||||
return <PremiumFeature feature='sso' page />;
|
return <PremiumFeature feature='sso' page />;
|
||||||
@ -51,44 +53,16 @@ export const AuthSettings = () => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PermissionGuard permissions={[ADMIN, UPDATE_AUTH_CONFIGURATION]}>
|
<PermissionGuard permissions={[ADMIN, UPDATE_AUTH_CONFIGURATION]}>
|
||||||
<PageContent
|
<PageContent header={activeTab}>
|
||||||
withTabs
|
<Routes>
|
||||||
header={
|
<Route path='/' element={<OidcAuth />} />
|
||||||
<Tabs
|
<Route path='/saml' element={<SamlAuth />} />
|
||||||
value={activeTab}
|
<Route path='/password' element={<PasswordAuth />} />
|
||||||
onChange={(_, tabId) => {
|
{googleAuthEnabled && (
|
||||||
setActiveTab(tabId);
|
<Route path='/google' element={<GoogleAuth />} />
|
||||||
}}
|
)}
|
||||||
indicatorColor='primary'
|
<Route path='/scim' element={<ScimSettings />} />
|
||||||
textColor='primary'
|
</Routes>
|
||||||
>
|
|
||||||
{tabs.map((tab, index) => (
|
|
||||||
<Tab
|
|
||||||
key={`${tab.label}_${index}`}
|
|
||||||
label={tab.label}
|
|
||||||
id={`tab-${index}`}
|
|
||||||
aria-controls={`tabpanel-${index}`}
|
|
||||||
sx={{
|
|
||||||
minWidth: {
|
|
||||||
lg: 160,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Tabs>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{tabs.map((tab, index) => (
|
|
||||||
<TabPanel
|
|
||||||
key={index}
|
|
||||||
value={activeTab}
|
|
||||||
index={index}
|
|
||||||
>
|
|
||||||
{tab.component}
|
|
||||||
</TabPanel>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</PermissionGuard>
|
</PermissionGuard>
|
||||||
</div>
|
</div>
|
||||||
|
96
frontend/src/component/admin/auth/OldAuthSettings.tsx
Normal file
96
frontend/src/component/admin/auth/OldAuthSettings.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { Tab, Tabs } from '@mui/material';
|
||||||
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { OidcAuth } from './OidcAuth/OidcAuth';
|
||||||
|
import { SamlAuth } from './SamlAuth/SamlAuth';
|
||||||
|
import { ScimSettings } from './ScimSettings/ScimSettings';
|
||||||
|
import { PasswordAuth } from './PasswordAuth/PasswordAuth';
|
||||||
|
import { GoogleAuth } from './GoogleAuth/GoogleAuth';
|
||||||
|
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
|
||||||
|
import { ADMIN, UPDATE_AUTH_CONFIGURATION } from '@server/types/permissions';
|
||||||
|
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { TabPanel } from 'component/common/TabNav/TabPanel/TabPanel';
|
||||||
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
|
|
||||||
|
export const OldAuthSettings = () => {
|
||||||
|
const { uiConfig, isEnterprise } = useUiConfig();
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
label: 'OpenID Connect',
|
||||||
|
component: <OidcAuth />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'SAML 2.0',
|
||||||
|
component: <SamlAuth />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Password',
|
||||||
|
component: <PasswordAuth />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Google',
|
||||||
|
component: <GoogleAuth />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'SCIM',
|
||||||
|
component: <ScimSettings />,
|
||||||
|
},
|
||||||
|
].filter(
|
||||||
|
(item) => uiConfig.flags?.googleAuthEnabled || item.label !== 'Google',
|
||||||
|
);
|
||||||
|
|
||||||
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
|
usePageTitle(`Single sign-on: ${tabs[activeTab].label}`);
|
||||||
|
|
||||||
|
if (!isEnterprise()) {
|
||||||
|
return <PremiumFeature feature='sso' page />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PermissionGuard permissions={[ADMIN, UPDATE_AUTH_CONFIGURATION]}>
|
||||||
|
<PageContent
|
||||||
|
withTabs
|
||||||
|
header={
|
||||||
|
<Tabs
|
||||||
|
value={activeTab}
|
||||||
|
onChange={(_, tabId) => {
|
||||||
|
setActiveTab(tabId);
|
||||||
|
}}
|
||||||
|
indicatorColor='primary'
|
||||||
|
textColor='primary'
|
||||||
|
>
|
||||||
|
{tabs.map((tab, index) => (
|
||||||
|
<Tab
|
||||||
|
key={`${tab.label}_${index}`}
|
||||||
|
label={tab.label}
|
||||||
|
id={`tab-${index}`}
|
||||||
|
aria-controls={`tabpanel-${index}`}
|
||||||
|
sx={{
|
||||||
|
minWidth: {
|
||||||
|
lg: 160,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{tabs.map((tab, index) => (
|
||||||
|
<TabPanel
|
||||||
|
key={index}
|
||||||
|
value={activeTab}
|
||||||
|
index={index}
|
||||||
|
>
|
||||||
|
{tab.component}
|
||||||
|
</TabPanel>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</PageContent>
|
||||||
|
</PermissionGuard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
122
frontend/src/component/admin/oldAdminRoutes.ts
Normal file
122
frontend/src/component/admin/oldAdminRoutes.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import type { INavigationMenuItem } from 'interfaces/route';
|
||||||
|
|
||||||
|
export const adminGroups: Record<string, string> = {
|
||||||
|
users: 'User administration',
|
||||||
|
access: 'Access control',
|
||||||
|
instance: 'Instance configuration',
|
||||||
|
log: 'Logs',
|
||||||
|
other: 'Other',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const adminRoutes: INavigationMenuItem[] = [
|
||||||
|
{
|
||||||
|
path: '/admin/users',
|
||||||
|
title: 'Users',
|
||||||
|
menu: { adminSettings: true },
|
||||||
|
group: 'users',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/service-accounts',
|
||||||
|
title: 'Service accounts',
|
||||||
|
menu: {
|
||||||
|
adminSettings: true,
|
||||||
|
mode: ['enterprise'],
|
||||||
|
},
|
||||||
|
group: 'users',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/groups',
|
||||||
|
title: 'Groups',
|
||||||
|
menu: {
|
||||||
|
adminSettings: true,
|
||||||
|
mode: ['enterprise'],
|
||||||
|
},
|
||||||
|
group: 'users',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/roles/*',
|
||||||
|
title: 'Roles',
|
||||||
|
menu: {
|
||||||
|
adminSettings: true,
|
||||||
|
mode: ['enterprise'],
|
||||||
|
},
|
||||||
|
group: 'users',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/api',
|
||||||
|
title: 'API access',
|
||||||
|
menu: { adminSettings: true },
|
||||||
|
group: 'access',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/cors',
|
||||||
|
title: 'CORS origins',
|
||||||
|
flag: 'embedProxyFrontend',
|
||||||
|
menu: { adminSettings: true },
|
||||||
|
group: 'access',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/auth',
|
||||||
|
title: 'Single sign-on',
|
||||||
|
menu: { adminSettings: true, mode: ['enterprise'] },
|
||||||
|
group: 'access',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/network/*',
|
||||||
|
title: 'Network',
|
||||||
|
menu: { adminSettings: true, mode: ['pro', 'enterprise'] },
|
||||||
|
group: 'instance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/maintenance',
|
||||||
|
title: 'Maintenance',
|
||||||
|
menu: { adminSettings: true },
|
||||||
|
group: 'instance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/banners',
|
||||||
|
title: 'Banners',
|
||||||
|
menu: { adminSettings: true, mode: ['enterprise'] },
|
||||||
|
group: 'instance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/instance',
|
||||||
|
title: 'Instance stats',
|
||||||
|
menu: { adminSettings: true },
|
||||||
|
group: 'instance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/license',
|
||||||
|
title: 'License',
|
||||||
|
menu: { adminSettings: true, mode: ['enterprise'] },
|
||||||
|
flag: 'enableLicense',
|
||||||
|
group: 'instance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/instance-privacy',
|
||||||
|
title: 'Instance privacy',
|
||||||
|
menu: { adminSettings: true },
|
||||||
|
group: 'instance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/admin-invoices',
|
||||||
|
title: 'Billing & invoices',
|
||||||
|
menu: { adminSettings: true, billing: true },
|
||||||
|
group: 'instance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/logins',
|
||||||
|
title: 'Login history',
|
||||||
|
menu: {
|
||||||
|
adminSettings: true,
|
||||||
|
mode: ['enterprise'],
|
||||||
|
},
|
||||||
|
group: 'log',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/history',
|
||||||
|
title: 'Event log',
|
||||||
|
menu: { adminSettings: true },
|
||||||
|
group: 'log',
|
||||||
|
},
|
||||||
|
];
|
@ -1,5 +1,5 @@
|
|||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { adminRoutes } from './adminRoutes';
|
import { adminRoutes } from './oldAdminRoutes';
|
||||||
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
|
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
|
||||||
import { filterAdminRoutes } from './filterAdminRoutes';
|
import { filterAdminRoutes } from './filterAdminRoutes';
|
||||||
import { filterByConfig, mapRouteLink } from 'component/common/util';
|
import { filterByConfig, mapRouteLink } from 'component/common/util';
|
||||||
|
@ -0,0 +1,170 @@
|
|||||||
|
import {
|
||||||
|
ListItem,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Typography,
|
||||||
|
styled,
|
||||||
|
Accordion,
|
||||||
|
AccordionSummary,
|
||||||
|
AccordionDetails,
|
||||||
|
} from '@mui/material';
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
import type { FC, ReactNode } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import type { Theme } from '@mui/material/styles/createTheme';
|
||||||
|
|
||||||
|
const listItemButtonStyle = (theme: Theme) => ({
|
||||||
|
borderRadius: theme.spacing(0.5),
|
||||||
|
borderLeft: `${theme.spacing(0.5)} solid transparent`,
|
||||||
|
m: 0,
|
||||||
|
'&.Mui-selected': {
|
||||||
|
borderLeft: `${theme.spacing(0.5)} solid ${theme.palette.primary.main}`,
|
||||||
|
},
|
||||||
|
minHeight: '0px',
|
||||||
|
'.MuiAccordionSummary-content': { margin: 0 },
|
||||||
|
'&>.MuiAccordionSummary-content.MuiAccordionSummary-content': {
|
||||||
|
margin: '0',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: theme.spacing(0.5, 0),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const subListItemButtonStyle = (theme: Theme) => ({
|
||||||
|
paddingLeft: theme.spacing(4),
|
||||||
|
borderRadius: theme.spacing(0.5),
|
||||||
|
borderLeft: `${theme.spacing(0.5)} solid transparent`,
|
||||||
|
m: 0,
|
||||||
|
'&.Mui-selected': {
|
||||||
|
borderLeft: `${theme.spacing(0.5)} solid ${theme.palette.primary.main}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const CappedText = styled(Typography)({
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
width: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledListItemIcon = styled(ListItemIcon)(({ theme }) => ({
|
||||||
|
minWidth: theme.spacing(4),
|
||||||
|
margin: theme.spacing(0.25, 0),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledListItemText = styled(ListItemText)(({ theme }) => ({
|
||||||
|
margin: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledAccordion = styled(Accordion)(({ theme }) => ({
|
||||||
|
paddingTop: theme.spacing(0),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.action.hover,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IMenuGroupProps {
|
||||||
|
title: string;
|
||||||
|
children: ReactNode;
|
||||||
|
icon: ReactNode;
|
||||||
|
activeIcon: ReactNode;
|
||||||
|
isActiveMenu: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MenuGroup = ({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
icon,
|
||||||
|
activeIcon,
|
||||||
|
isActiveMenu,
|
||||||
|
}: IMenuGroupProps) => {
|
||||||
|
return (
|
||||||
|
<StyledAccordion
|
||||||
|
disableGutters={true}
|
||||||
|
sx={{
|
||||||
|
boxShadow: 'none',
|
||||||
|
'&:before': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledAccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon />}
|
||||||
|
aria-controls='configure-content'
|
||||||
|
id='configure-header'
|
||||||
|
sx={listItemButtonStyle}
|
||||||
|
>
|
||||||
|
<StyledListItemIcon>
|
||||||
|
{isActiveMenu ? activeIcon : icon}
|
||||||
|
</StyledListItemIcon>
|
||||||
|
<StyledListItemText>
|
||||||
|
<CappedText
|
||||||
|
sx={{ fontWeight: isActiveMenu ? 'bold' : 'normal' }}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</CappedText>
|
||||||
|
</StyledListItemText>
|
||||||
|
</StyledAccordionSummary>
|
||||||
|
<AccordionDetails sx={{ p: 0 }}>{children}</AccordionDetails>
|
||||||
|
</StyledAccordion>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdminListItem: FC<{
|
||||||
|
href: string;
|
||||||
|
text: string;
|
||||||
|
badge?: ReactNode;
|
||||||
|
selected?: boolean;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
onClick: () => void;
|
||||||
|
}> = ({ href, text, badge, selected, children, onClick }) => {
|
||||||
|
return (
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton
|
||||||
|
dense={true}
|
||||||
|
component={Link}
|
||||||
|
to={href}
|
||||||
|
sx={listItemButtonStyle}
|
||||||
|
selected={selected}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<StyledListItemIcon>{children}</StyledListItemIcon>
|
||||||
|
<StyledListItemText>
|
||||||
|
<CappedText>{text}</CappedText>
|
||||||
|
</StyledListItemText>
|
||||||
|
{badge}
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AdminSubListItem: FC<{
|
||||||
|
href: string;
|
||||||
|
text: string;
|
||||||
|
badge?: ReactNode;
|
||||||
|
selected?: boolean;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
onClick: () => void;
|
||||||
|
}> = ({ href, text, badge, selected, children, onClick }) => {
|
||||||
|
return (
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton
|
||||||
|
dense={true}
|
||||||
|
component={Link}
|
||||||
|
to={href}
|
||||||
|
sx={subListItemButtonStyle}
|
||||||
|
selected={selected}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<StyledListItemIcon>{children}</StyledListItemIcon>
|
||||||
|
<StyledListItemText>
|
||||||
|
<CappedText>{text}</CappedText>
|
||||||
|
</StyledListItemText>
|
||||||
|
{badge}
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
};
|
258
frontend/src/component/layout/MainLayout/AdminMenu/AdminMenu.tsx
Normal file
258
frontend/src/component/layout/MainLayout/AdminMenu/AdminMenu.tsx
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
import {
|
||||||
|
Grid,
|
||||||
|
styled,
|
||||||
|
Paper,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
List,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
|
import StopRoundedIcon from '@mui/icons-material/StopRounded';
|
||||||
|
import { AdminListItem, AdminSubListItem, MenuGroup } from './AdminListItem';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { Sticky } from 'component/common/Sticky/Sticky';
|
||||||
|
import { adminRoutes, adminGroups } from 'component/admin/adminRoutes';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { filterByConfig } from 'component/common/util';
|
||||||
|
import { filterAdminRoutes } from 'component/admin/filterAdminRoutes';
|
||||||
|
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
|
||||||
|
import { IconRenderer } from './AdminMenuIcons';
|
||||||
|
|
||||||
|
interface IMenuLinkItem {
|
||||||
|
href: string;
|
||||||
|
text: string;
|
||||||
|
icon: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IMenuItem {
|
||||||
|
href: string;
|
||||||
|
text: string;
|
||||||
|
items?: IMenuLinkItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledAdminMainGrid = styled(Grid)(({ theme }) => ({
|
||||||
|
minWidth: 0, // this is a fix for overflowing flex
|
||||||
|
maxWidth: '1812px',
|
||||||
|
margin: '0 auto',
|
||||||
|
paddingLeft: theme.spacing(2),
|
||||||
|
paddingRight: theme.spacing(2),
|
||||||
|
[theme.breakpoints.up(2156)]: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down(2156)]: {
|
||||||
|
marginLeft: 0,
|
||||||
|
marginRight: 0,
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('lg')]: {
|
||||||
|
maxWidth: '1550px',
|
||||||
|
paddingLeft: theme.spacing(1),
|
||||||
|
paddingRight: theme.spacing(1),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down(1024)]: {
|
||||||
|
marginLeft: 0,
|
||||||
|
marginRight: 0,
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
minWidth: '100%',
|
||||||
|
},
|
||||||
|
minHeight: '94vh',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledMenuPaper = styled(Paper)(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
minWidth: 320,
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
marginTop: theme.spacing(6.5),
|
||||||
|
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||||
|
boxShadow: 'none',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StickyContainer = styled(Sticky)(({ theme }) => ({
|
||||||
|
position: 'sticky',
|
||||||
|
top: 0,
|
||||||
|
zIndex: theme.zIndex.sticky,
|
||||||
|
background: theme.palette.background.application,
|
||||||
|
transition: 'padding 0.3s ease',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SettingsHeader = styled(Typography)(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.mainHeader,
|
||||||
|
fontWeight: theme.fontWeight.bold,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledButton = styled(Button)(({ theme }) => ({
|
||||||
|
paddingLeft: theme.spacing(0),
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledStopRoundedIcon = styled(StopRoundedIcon)(({ theme }) => ({
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IWrapIfAdminSubpageProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WrapIfAdminSubpage = ({ children }: IWrapIfAdminSubpageProps) => {
|
||||||
|
const newAdminUIEnabled = useUiFlag('adminNavUI');
|
||||||
|
const theme = useTheme();
|
||||||
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('lg'));
|
||||||
|
const showAdminMenu =
|
||||||
|
!isSmallScreen &&
|
||||||
|
newAdminUIEnabled &&
|
||||||
|
location.pathname.indexOf('/admin') === 0;
|
||||||
|
|
||||||
|
if (showAdminMenu) {
|
||||||
|
return <AdminMenu>{children}</AdminMenu>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DashboardLink = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledButton
|
||||||
|
href='/personal'
|
||||||
|
rel='noreferrer'
|
||||||
|
startIcon={<ArrowBackIcon />}
|
||||||
|
>
|
||||||
|
Back to Unleash
|
||||||
|
</StyledButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IAdminMenuProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AdminMenu = ({ children }: IAdminMenuProps) => {
|
||||||
|
const { uiConfig, isPro, isEnterprise } = useUiConfig();
|
||||||
|
const { isBilling } = useInstanceStatus();
|
||||||
|
const isActiveItem = (item?: string) =>
|
||||||
|
item !== undefined && location.pathname === item;
|
||||||
|
const theme = useTheme();
|
||||||
|
const isBreakpoint = useMediaQuery(theme.breakpoints.down(1350));
|
||||||
|
const onClick = () => {
|
||||||
|
scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const location = useLocation();
|
||||||
|
const routes = adminRoutes
|
||||||
|
.filter(filterByConfig(uiConfig))
|
||||||
|
.filter((route) =>
|
||||||
|
filterAdminRoutes(route?.menu, {
|
||||||
|
enterprise: isEnterprise(),
|
||||||
|
pro: isPro(),
|
||||||
|
billing: isBilling,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuStructure = routes.reduce(
|
||||||
|
(acc: Record<string, IMenuItem>, route) => {
|
||||||
|
if (route.group && adminGroups[route.group]) {
|
||||||
|
if (!acc[route.group]) {
|
||||||
|
acc[route.group] = {
|
||||||
|
href: route.group,
|
||||||
|
text: adminGroups[route.group],
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
acc[route.group].items?.push({
|
||||||
|
href: route.path,
|
||||||
|
text: route.title,
|
||||||
|
icon: <StopRoundedIcon />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!route.group) {
|
||||||
|
acc[route.path] = {
|
||||||
|
href: route.path,
|
||||||
|
text: route.title,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = Object.values(menuStructure);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledAdminMainGrid container spacing={1}>
|
||||||
|
<Grid item>
|
||||||
|
<StickyContainer>
|
||||||
|
<StyledMenuPaper>
|
||||||
|
<SettingsHeader>Admin settings</SettingsHeader>
|
||||||
|
<DashboardLink />
|
||||||
|
<List>
|
||||||
|
{items.map((item) => {
|
||||||
|
if (item.items) {
|
||||||
|
const isActiveMenu = item.items.find(
|
||||||
|
(itm) => isActiveItem(itm.href),
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<MenuGroup
|
||||||
|
title={item.text}
|
||||||
|
icon={
|
||||||
|
<IconRenderer
|
||||||
|
path={item.href}
|
||||||
|
active={false}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
activeIcon={
|
||||||
|
<IconRenderer
|
||||||
|
path={item.href}
|
||||||
|
active={true}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
isActiveMenu={Boolean(isActiveMenu)}
|
||||||
|
key={item.text}
|
||||||
|
>
|
||||||
|
{item.items.map((subItem) => (
|
||||||
|
<AdminSubListItem
|
||||||
|
href={subItem.href}
|
||||||
|
text={subItem.text}
|
||||||
|
selected={isActiveItem(
|
||||||
|
subItem.href,
|
||||||
|
)}
|
||||||
|
onClick={onClick}
|
||||||
|
key={subItem.href}
|
||||||
|
>
|
||||||
|
<StyledStopRoundedIcon />
|
||||||
|
</AdminSubListItem>
|
||||||
|
))}
|
||||||
|
</MenuGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<AdminListItem
|
||||||
|
href={item.href}
|
||||||
|
text={item.text}
|
||||||
|
selected={isActiveItem(item.href)}
|
||||||
|
onClick={onClick}
|
||||||
|
key={item.href}
|
||||||
|
>
|
||||||
|
<IconRenderer
|
||||||
|
path={item.href}
|
||||||
|
active={false}
|
||||||
|
/>
|
||||||
|
</AdminListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
</StyledMenuPaper>
|
||||||
|
</StickyContainer>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={isBreakpoint ? true : 9}>
|
||||||
|
{children}
|
||||||
|
</Grid>
|
||||||
|
</StyledAdminMainGrid>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,43 @@
|
|||||||
|
import type SvgIcon from '@mui/material/SvgIcon/SvgIcon';
|
||||||
|
import HomeIcon from '@mui/icons-material/Home';
|
||||||
|
import LaptopIcon from '@mui/icons-material/Laptop';
|
||||||
|
import EventNoteIcon from '@mui/icons-material/EventNote';
|
||||||
|
import BillingIcon from '@mui/icons-material/CreditCardOutlined';
|
||||||
|
import PeopleOutlineRoundedIcon from '@mui/icons-material/PeopleOutlineRounded';
|
||||||
|
import KeyRoundedIcon from '@mui/icons-material/KeyRounded';
|
||||||
|
import CloudIcon from '@mui/icons-material/Cloud';
|
||||||
|
import HubOutlinedIcon from '@mui/icons-material/HubOutlined';
|
||||||
|
import BuildOutlinedIcon from '@mui/icons-material/BuildOutlined';
|
||||||
|
import EmptyIcon from '@mui/icons-material/CheckBoxOutlineBlankOutlined';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
const icons: Record<string, typeof SvgIcon> = {
|
||||||
|
'/admin': HomeIcon,
|
||||||
|
users: PeopleOutlineRoundedIcon,
|
||||||
|
'/admin/service-accounts': LaptopIcon,
|
||||||
|
access: KeyRoundedIcon,
|
||||||
|
sso: CloudIcon,
|
||||||
|
network: HubOutlinedIcon,
|
||||||
|
instance: BuildOutlinedIcon,
|
||||||
|
'/admin/billing': BillingIcon,
|
||||||
|
'/history': EventNoteIcon,
|
||||||
|
};
|
||||||
|
|
||||||
|
const findIcon = (key: string) => {
|
||||||
|
return icons[key] || EmptyIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IconRenderer: FC<{ path: string; active: boolean }> = ({
|
||||||
|
path,
|
||||||
|
active = false,
|
||||||
|
}) => {
|
||||||
|
const IconComponent = findIcon(path); // Fallback to 'default' if the type is not found
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconComponent
|
||||||
|
sx={{
|
||||||
|
color: active ? 'primary.main' : 'inherit',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -19,6 +19,7 @@ import { NavigationSidebar } from './NavigationSidebar/NavigationSidebar';
|
|||||||
import { EventTimelineProvider } from 'component/events/EventTimeline/EventTimelineProvider';
|
import { EventTimelineProvider } from 'component/events/EventTimeline/EventTimelineProvider';
|
||||||
import { NewInUnleash } from './NavigationSidebar/NewInUnleash/NewInUnleash';
|
import { NewInUnleash } from './NavigationSidebar/NewInUnleash/NewInUnleash';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
import { WrapIfAdminSubpage } from './AdminMenu/AdminMenu';
|
||||||
|
|
||||||
interface IMainLayoutProps {
|
interface IMainLayoutProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -145,14 +146,18 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
|
|||||||
>
|
>
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<MainLayoutContent>
|
<WrapIfAdminSubpage>
|
||||||
<SkipNavTarget />
|
<MainLayoutContent>
|
||||||
<MainLayoutContentContainer ref={ref}>
|
<SkipNavTarget />
|
||||||
<BreadcrumbNav />
|
<MainLayoutContentContainer ref={ref}>
|
||||||
<Proclamation toast={uiConfig.toast} />
|
<BreadcrumbNav />
|
||||||
{children}
|
<Proclamation
|
||||||
</MainLayoutContentContainer>
|
toast={uiConfig.toast}
|
||||||
</MainLayoutContent>
|
/>
|
||||||
|
{children}
|
||||||
|
</MainLayoutContentContainer>
|
||||||
|
</MainLayoutContent>
|
||||||
|
</WrapIfAdminSubpage>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user