diff --git a/frontend/src/component/admin/Admin.tsx b/frontend/src/component/admin/Admin.tsx index a0d556c1df..f8dfb87bb5 100644 --- a/frontend/src/component/admin/Admin.tsx +++ b/frontend/src/component/admin/Admin.tsx @@ -12,7 +12,7 @@ import { GroupsAdmin } from './groups/GroupsAdmin'; import { InstanceAdmin } from './instance-admin/InstanceAdmin'; import { InstancePrivacy } from './instance-privacy/InstancePrivacy'; import { MaintenanceAdmin } from './maintenance'; -import AdminMenu from './menu/AdminMenu'; +import { AdminTabsMenu } from './menu/AdminTabsMenu'; import { Network } from './network/Network'; import { Roles } from './roles/Roles'; import { ServiceAccounts } from './serviceAccounts/ServiceAccounts'; @@ -20,34 +20,67 @@ import CreateUser from './users/CreateUser/CreateUser'; import EditUser from './users/EditUser/EditUser'; import { InviteLink } from './users/InviteLink/InviteLink'; import UsersAdmin from './users/UsersAdmin'; +import { EnterpriseFeatureUpgradePage } from 'component/common/EnterpriseFeatureUpgradePage/EnterpriseFeatureUpgradePage'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; -export const Admin = () => ( - <> - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - -); +export const Admin = () => { + const { isEnterprise } = useUiConfig(); + + return ( + <> + + + } /> + } /> + } /> + } /> + + ) : ( + + ) + } + /> + } /> + } /> + } /> + } /> + } + /> + } /> + + ) : ( + + ) + } + /> + } /> + } /> + } /> + } /> + } /> + } + /> + } /> + } /> + + + ); +}; diff --git a/frontend/src/component/admin/menu/AdminMenu.tsx b/frontend/src/component/admin/menu/AdminMenu.tsx deleted file mode 100644 index 7b79869025..0000000000 --- a/frontend/src/component/admin/menu/AdminMenu.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { useLocation } from 'react-router-dom'; -import { Paper, styled, Tab, Tabs } from '@mui/material'; -import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; -import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus'; -import { CenteredNavLink } from './CenteredNavLink'; - -const StyledPaper = styled(Paper)(({ theme }) => ({ - marginBottom: '1rem', - borderRadius: '12.5px', - boxShadow: 'none', - padding: '0 2rem', -})); - -function AdminMenu() { - const { uiConfig, isEnterprise } = useUiConfig(); - const { pathname } = useLocation(); - const { isBilling } = useInstanceStatus(); - const { flags, networkViewEnabled } = uiConfig; - - const activeTab = pathname.split('/')[2]; - - return ( - - - - Users - - } - /> - {isEnterprise() && ( - - Service accounts - - } - /> - )} - {flags.UG && ( - - Groups - - } - /> - )} - {isEnterprise() && ( - - Roles - - } - /> - )} - - API access - - } - /> - {uiConfig.flags.embedProxyFrontend && ( - - CORS origins - - } - /> - )} - - Single sign-on - - } - /> - - Instance stats - - } - /> - {networkViewEnabled && ( - - Network - - } - /> - )} - - - Maintenance - - } - /> - - - Instance privacy - - } - /> - - {isBilling && ( - - Billing - - } - /> - )} - - - ); -} - -export default AdminMenu; diff --git a/frontend/src/component/admin/menu/AdminTabsMenu.tsx b/frontend/src/component/admin/menu/AdminTabsMenu.tsx new file mode 100644 index 0000000000..47663bf3f9 --- /dev/null +++ b/frontend/src/component/admin/menu/AdminTabsMenu.tsx @@ -0,0 +1,141 @@ +import { useLocation } from 'react-router-dom'; +import { Box, Paper, styled, Tab, Tabs } from '@mui/material'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus'; +import { CenteredNavLink } from './CenteredNavLink'; +import { VFC } from 'react'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { EnterpriseBadge } from 'component/common/EnterpriseBadge/EnterpriseBadge'; + +const StyledPaper = styled(Paper)(({ theme }) => ({ + marginBottom: '1rem', + borderRadius: `${theme.shape.borderRadiusLarge}px`, + boxShadow: 'none', + padding: theme.spacing(0, 2), +})); + +const StyledBadgeContainer = styled('div')(({ theme }) => ({ + marginLeft: theme.spacing(1), + display: 'flex', + alignItems: 'center', +})); + +export const AdminTabsMenu: VFC = () => { + const { uiConfig, isEnterprise, isPro } = useUiConfig(); + const { pathname } = useLocation(); + const { isBilling } = useInstanceStatus(); + const { flags, networkViewEnabled } = uiConfig; + + const activeTab = pathname.split('/')[2]; + + const showEnterpriseFeaturesInPro = + uiConfig?.flags?.frontendNavigationUpdate; + + const tabs = [ + { + value: 'users', + label: 'Users', + link: '/admin/users', + }, + { + value: 'service-accounts', + label: 'Service accounts', + link: '/admin/service-accounts', + condition: + isEnterprise() || (isPro() && showEnterpriseFeaturesInPro), + showEnterpriseBadge: true, + }, + { + value: 'groups', + label: 'Groups', + link: '/admin/groups', + condition: flags.UG, + }, + { + value: 'roles', + label: 'Roles', + link: '/admin/roles', + condition: + isEnterprise() || (isPro() && showEnterpriseFeaturesInPro), + showEnterpriseBadge: true, + }, + { + value: 'api', + label: 'API access', + link: '/admin/api', + }, + { + value: 'cors', + label: 'CORS origins', + link: '/admin/cors', + condition: uiConfig.flags.embedProxyFrontend, + }, + { + value: 'auth', + label: 'Single sign-on', + link: '/admin/auth', + }, + { + value: 'instance', + label: 'Instance stats', + link: '/admin/instance', + }, + { + value: 'network', + label: 'Network', + link: '/admin/network', + condition: networkViewEnabled, + }, + { + value: 'maintenance', + label: 'Maintenance', + link: '/admin/maintenance', + }, + { + value: 'instance-privacy', + label: 'Instance privacy', + link: '/admin/instance-privacy', + }, + { + value: 'billing', + label: 'Billing', + link: '/admin/billing', + condition: isBilling, + }, + ]; + + return ( + + + {tabs + .filter(tab => tab.condition || tab.condition === undefined) + .map(tab => ( + + {tab.label} + + + + } + /> + + } + /> + ))} + + + ); +}; diff --git a/frontend/src/component/common/EnterpriseBadge/EnterpriseBadge.tsx b/frontend/src/component/common/EnterpriseBadge/EnterpriseBadge.tsx new file mode 100644 index 0000000000..13dd5d0285 --- /dev/null +++ b/frontend/src/component/common/EnterpriseBadge/EnterpriseBadge.tsx @@ -0,0 +1,15 @@ +import { VFC } from 'react'; +import { ReactComponent as ProPlanIcon } from 'assets/icons/pro-enterprise-feature-badge.svg'; +import { ReactComponent as ProPlanIconLight } from 'assets/icons/pro-enterprise-feature-badge-light.svg'; +import { ThemeMode } from 'component/common/ThemeMode/ThemeMode'; + +type EnterpriseBadgeProps = { + size?: number; +}; + +export const EnterpriseBadge: VFC = ({ size = 16 }) => ( + } + lightmode={} + /> +); diff --git a/frontend/src/component/common/EnterpriseFeatureUpgradePage/EnterpriseFeatureUpgradePage.tsx b/frontend/src/component/common/EnterpriseFeatureUpgradePage/EnterpriseFeatureUpgradePage.tsx new file mode 100644 index 0000000000..75aa26b91d --- /dev/null +++ b/frontend/src/component/common/EnterpriseFeatureUpgradePage/EnterpriseFeatureUpgradePage.tsx @@ -0,0 +1,64 @@ +import { VFC } from 'react'; +import { Box, Button, Typography, styled } from '@mui/material'; +import { EnterpriseBadge } from 'component/common/EnterpriseBadge/EnterpriseBadge'; +import { PageContent } from '../PageContent/PageContent'; +import { PageHeader } from '../PageHeader/PageHeader'; + +type EnterpriseFeatureUpgradePageProps = { + title: string; + link: string; +}; + +const StyledContainer = styled(Box)(({ theme }) => ({ + background: theme.palette.background.elevation2, + padding: theme.spacing(8, 2), + borderRadius: `${theme.shape.borderRadiusMedium}px`, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + textAlign: 'center', +})); + +const StyledHeader = styled(Typography)(({ theme }) => ({ + marginBottom: theme.spacing(3), + display: 'flex', + alignItems: 'center', +})); + +const StyledBadgeContainer = styled('div')(({ theme }) => ({ + paddingRight: theme.spacing(1), + display: 'flex', + alignItems: 'center', +})); + +export const EnterpriseFeatureUpgradePage: VFC< + EnterpriseFeatureUpgradePageProps +> = ({ title, link }) => ( + }> + + + + + + Enterprise feature + + + {title} is a feature available for the + Enterprise plan. + + + You need to upgrade your plan if you want to use it. + + + + +); diff --git a/frontend/src/component/loginHistory/LoginHistory.tsx b/frontend/src/component/loginHistory/LoginHistory.tsx index 439f575f8a..f1ce9f1a78 100644 --- a/frontend/src/component/loginHistory/LoginHistory.tsx +++ b/frontend/src/component/loginHistory/LoginHistory.tsx @@ -1,11 +1,26 @@ import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard'; import { LoginHistoryTable } from './LoginHistoryTable/LoginHistoryTable'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import { EnterpriseFeatureUpgradePage } from 'component/common/EnterpriseFeatureUpgradePage/EnterpriseFeatureUpgradePage'; -export const LoginHistory = () => ( -
- - - -
-); +export const LoginHistory = () => { + const { isEnterprise } = useUiConfig(); + + if (!isEnterprise()) { + return ( + + ); + } + + return ( +
+ + + +
+ ); +}; diff --git a/frontend/src/component/menu/Header/Header.tsx b/frontend/src/component/menu/Header/Header.tsx index 967edd1569..a8f1f338c6 100644 --- a/frontend/src/component/menu/Header/Header.tsx +++ b/frontend/src/component/menu/Header/Header.tsx @@ -132,12 +132,23 @@ const Header: VFC = () => { const routes = getRoutes(); const filterByMode = (route: INavigationMenuItem): boolean => { - const { mode } = route.menu; - return ( - !mode || - (mode.includes('pro') && isPro()) || - (mode.includes('enterprise') && isEnterprise()) - ); + const { mode, showEnterpriseBadge } = route.menu; + + if (!mode) return true; + + if (isPro()) { + return ( + mode.includes('pro') || + (mode.includes('enterprise') && showEnterpriseBadge) || + false + ); + } + + if (isEnterprise()) { + return mode.includes('enterprise'); + } + + return false; }; const filteredMainRoutes = { diff --git a/frontend/src/component/menu/Header/NavigationMenu/NavigationMenu.tsx b/frontend/src/component/menu/Header/NavigationMenu/NavigationMenu.tsx index d2dc3cee48..8ad66515b3 100644 --- a/frontend/src/component/menu/Header/NavigationMenu/NavigationMenu.tsx +++ b/frontend/src/component/menu/Header/NavigationMenu/NavigationMenu.tsx @@ -2,11 +2,12 @@ import { Divider } from '@mui/material'; import { Menu, MenuItem, styled } from '@mui/material'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; -import { Fragment } from 'react'; +import { INavigationMenuItem } from 'interfaces/route'; import { Link } from 'react-router-dom'; +import { EnterpriseBadge } from '../../../common/EnterpriseBadge/EnterpriseBadge'; interface INavigationMenuProps { - options: any[]; + options: INavigationMenuItem[]; id: string; anchorEl: any; handleClose: () => void; @@ -35,6 +36,12 @@ const StyledSpan = styled('span')(({ theme }) => ({ borderRadius: '2px', })); +const StyledBadgeContainer = styled('div')(({ theme }) => ({ + marginLeft: 'auto', + paddingLeft: theme.spacing(2), + display: 'flex', +})); + export const NavigationMenu = ({ options, id, @@ -43,7 +50,7 @@ export const NavigationMenu = ({ style, }: INavigationMenuProps) => { const { uiConfig } = useUiConfig(); - const showDividers = uiConfig?.flags?.frontendNavigationUpdate; + const showUpdatedMenu = uiConfig?.flags?.frontendNavigationUpdate; return ( - {options.map((option, i) => ( - + {options + .map((option, i) => [ } - /> + elseShow={null} + />, {option.title} - - - ))} + + + + } + /> + , + ]) + .flat() + .filter(Boolean)} ); }; diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index 1ca59d362b..96095573f0 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -462,20 +462,32 @@ export const adminMenuRoutes: INavigationMenuItem[] = [ { path: '/admin/service-accounts', title: 'Service accounts', - menu: { adminSettings: true, mode: ['enterprise'] }, + menu: { + adminSettings: true, + mode: ['enterprise'], + showEnterpriseBadge: true, + }, group: 'users', }, { path: '/admin/groups', title: 'Groups', - menu: { adminSettings: true, mode: ['enterprise'] }, + menu: { + adminSettings: true, + mode: ['enterprise'], + showEnterpriseBadge: true, + }, flag: UG, group: 'users', }, { path: '/admin/roles/*', title: 'Roles', - menu: { adminSettings: true, mode: ['enterprise'] }, + menu: { + adminSettings: true, + mode: ['enterprise'], + showEnterpriseBadge: true, + }, group: 'users', }, { @@ -532,7 +544,11 @@ export const adminMenuRoutes: INavigationMenuItem[] = [ { path: '/admin/logins', title: 'Login history', - menu: { adminSettings: true, mode: ['enterprise'] }, + menu: { + adminSettings: true, + mode: ['enterprise'], + showEnterpriseBadge: true, + }, group: 'log', }, { diff --git a/frontend/src/interfaces/route.ts b/frontend/src/interfaces/route.ts index a5dde225b5..772a2096b5 100644 --- a/frontend/src/interfaces/route.ts +++ b/frontend/src/interfaces/route.ts @@ -30,4 +30,5 @@ interface IRouteMenu { advanced?: boolean; adminSettings?: boolean; mode?: Array<'pro' | 'enterprise'>; + showEnterpriseBadge?: boolean; }