diff --git a/frontend/src/component/admin/instance-admin/InstanceAdmin.tsx b/frontend/src/component/admin/instance-admin/InstanceAdmin.tsx new file mode 100644 index 000000000000..273e1babc233 --- /dev/null +++ b/frontend/src/component/admin/instance-admin/InstanceAdmin.tsx @@ -0,0 +1,11 @@ +import AdminMenu from '../menu/AdminMenu'; +import { InstanceStats } from './InstanceStats/InstanceStats'; + +export const InstanceAdmin = () => { + return ( +
+ + +
+ ); +}; diff --git a/frontend/src/component/admin/instance-admin/InstanceStats/InstanceStats.tsx b/frontend/src/component/admin/instance-admin/InstanceStats/InstanceStats.tsx new file mode 100644 index 000000000000..6386934224ca --- /dev/null +++ b/frontend/src/component/admin/instance-admin/InstanceStats/InstanceStats.tsx @@ -0,0 +1,82 @@ +import { Save } from '@mui/icons-material'; +import { Button, IconButton, Link, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material'; +import { Box } from '@mui/system'; +import { VFC } from 'react'; +import { useInstanceStats } from '../../../../hooks/api/getters/useInstanceStats/useInstanceStats'; +import { formatApiPath } from '../../../../utils/formatPath'; +import { PageContent } from '../../../common/PageContent/PageContent'; +import { PageHeader } from '../../../common/PageHeader/PageHeader'; + +export const InstanceStats: VFC = () => { + const { stats, loading } = useInstanceStats(); + + + let versionTitle; + let version; + + if(stats?.versionEnterprise) { + versionTitle = 'Unleash Enterprise version'; + version = stats.versionEnterprise; + } else { + versionTitle = 'Unleash OSS version'; + version = stats?.versionOSS; + } + + + + const rows = [ + {title: 'Instance Id', value: stats?.instanceId}, + {title: versionTitle, value: version}, + {title: 'Users', value: stats?.users}, + {title: 'Feature toggles', value: stats?.featureToggles}, + {title: 'Projects', value: stats?.projects}, + {title: 'Environments', value: stats?.environments}, + {title: 'Roles', value: stats?.roles}, + {title: 'Groups', value: stats?.groups}, + {title: 'Context fields', value: stats?.contextFields}, + {title: 'Strategies', value: stats?.strategies}, + ]; + + if(stats?.versionEnterprise) { + rows.push( + {title: 'SAML enabled', value: stats?.SAMLenabled ? 'Yes' : 'No'}, + {title: 'OIDC enabled', value: stats?.OIDCenabled ? 'Yes' : 'No'}, + ); + } + + return ( + }> + + + + + Item + Value + + + + {rows.map(row => ( + + {row.title} + {row.value} + + ))} + +
+ + + +
+ +
+ ) +} \ No newline at end of file diff --git a/frontend/src/component/admin/menu/AdminMenu.tsx b/frontend/src/component/admin/menu/AdminMenu.tsx index 249859676712..f53997956a3b 100644 --- a/frontend/src/component/admin/menu/AdminMenu.tsx +++ b/frontend/src/component/admin/menu/AdminMenu.tsx @@ -135,6 +135,19 @@ function AdminMenu() { } /> + + createNavLinkStyle({ isActive, theme }) + } + > + Instance admin + + } + /> {isBilling && ( void; + loading: boolean; + error?: Error; +} + +export const mapGroupUsers = (users: any[]) => + users.map(user => ({ + ...user.user, + joinedAt: new Date(user.joinedAt), + })); + +export const useInstanceStats = (): IInstanceStatsResponse => { + const { data, error, mutate } = useSWR( + formatApiPath(`api/admin/instance-admin/statistics`), + fetcher + ); + + return useMemo( + () => ({ + stats: data, + loading: !error && !data, + refetchGroup: () => mutate(), + error, + }), + [data, error, mutate] + ); +}; + +const fetcher = (path: string) => { + return fetch(path) + .then(handleErrorResponses('Instance Stats')) + .then(res => res.json()); +}; \ No newline at end of file diff --git a/src/lib/routes/admin-api/instance-admin.ts b/src/lib/routes/admin-api/instance-admin.ts index 3784a7f49a85..c4e2b2850340 100644 --- a/src/lib/routes/admin-api/instance-admin.ts +++ b/src/lib/routes/admin-api/instance-admin.ts @@ -6,7 +6,10 @@ import { IUnleashConfig } from '../../types/option'; import Controller from '../controller'; import { NONE } from '../../types/permissions'; import { UiConfigSchema } from '../../openapi/spec/ui-config-schema'; -import { InstanceStatsService } from '../../services/instance-stats-service'; +import { + InstanceStats, + InstanceStatsService, +} from '../../services/instance-stats-service'; import { OpenApiService } from '../../services/openapi-service'; import { createResponseSchema } from '../../openapi/util/create-response-schema'; @@ -37,7 +40,7 @@ class InstanceAdminController extends Controller { this.route({ method: 'get', path: '/statistics', - handler: this.getStatisticsCSV, + handler: this.getStatistics, permission: NONE, middleware: [ openApiService.validPath({ @@ -52,6 +55,14 @@ class InstanceAdminController extends Controller { }); } + async getStatistics( + req: AuthedRequest, + res: Response, + ): Promise { + const instanceStats = await this.instanceStatsService.getStats(); + res.json(instanceStats); + } + async getStatisticsCSV( req: AuthedRequest, res: Response, diff --git a/src/lib/services/instance-stats-service.ts b/src/lib/services/instance-stats-service.ts index 68e1857696af..b7b66267d6b8 100644 --- a/src/lib/services/instance-stats-service.ts +++ b/src/lib/services/instance-stats-service.ts @@ -15,7 +15,7 @@ import VersionService from './version-service'; import { ISettingStore } from '../types/stores/settings-store'; // eslint-disable-next-line @typescript-eslint/no-unused-vars -interface InstanceStats { +export interface InstanceStats { instanceId: string; timestamp: Date; versionOSS: string;