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}
+
+ ))}
+
+
+
+ }
+ aria-label="Download instance statistics"
+ color="primary"
+ variant="contained"
+ target="_blank"
+ href={formatApiPath('/api/admin/instance-admin/statistics/csv')}
+ >
+ Download
+
+
+
+
+
+ )
+}
\ 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;