feat: fetch server capabilities and hide infra tabs when disabled

Adds a useServerCapabilities hook that fetches /api/v1/health once per
session (staleTime: Infinity) and extracts the infrastructureEndpoints
flag. buildAdminTreeNodes now accepts an opts parameter so ClickHouse
and Database tabs are hidden when the server reports infra endpoints as
disabled. LayoutShell wires the hook result into the admin tree memo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-11 23:12:30 +02:00
parent 9de51014e7
commit c8cdd846c0
3 changed files with 41 additions and 6 deletions

View File

@@ -0,0 +1,29 @@
import { useQuery } from '@tanstack/react-query';
import { config } from '../../config';
interface HealthResponse {
status: string;
components?: {
serverCapabilities?: {
details?: {
infrastructureEndpoints?: boolean;
};
};
};
}
export function useServerCapabilities() {
return useQuery<{ infrastructureEndpoints: boolean }>({
queryKey: ['server-capabilities'],
queryFn: async () => {
const res = await fetch(config.apiBaseUrl + '/health');
if (!res.ok) return { infrastructureEndpoints: true };
const data: HealthResponse = await res.json();
return {
infrastructureEndpoints:
data.components?.serverCapabilities?.details?.infrastructureEndpoints ?? true,
};
},
staleTime: Infinity,
});
}

View File

@@ -46,6 +46,7 @@ import {
readCollapsed, readCollapsed,
writeCollapsed, writeCollapsed,
} from './sidebar-utils'; } from './sidebar-utils';
import { useServerCapabilities } from '../api/queries/capabilities';
import type { SidebarApp } from './sidebar-utils'; import type { SidebarApp } from './sidebar-utils';
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -290,6 +291,9 @@ function LayoutContent() {
const globalFilters = useGlobalFilters(); const globalFilters = useGlobalFilters();
const { timeRange, autoRefresh, refreshTimeRange } = globalFilters; const { timeRange, autoRefresh, refreshTimeRange } = globalFilters;
// --- Server capabilities ------------------------------------------
const { data: capabilities } = useServerCapabilities();
// --- Role checks ---------------------------------------------------- // --- Role checks ----------------------------------------------------
const isAdmin = useIsAdmin(); const isAdmin = useIsAdmin();
@@ -432,8 +436,8 @@ function LayoutContent() {
); );
const adminTreeNodes: SidebarTreeNode[] = useMemo( const adminTreeNodes: SidebarTreeNode[] = useMemo(
() => buildAdminTreeNodes(), () => buildAdminTreeNodes({ infrastructureEndpoints: capabilities?.infrastructureEndpoints }),
[], [capabilities?.infrastructureEndpoints],
); );
// --- Starred items ------------------------------------------------ // --- Starred items ------------------------------------------------

View File

@@ -99,13 +99,15 @@ export function buildAppTreeNodes(
/** /**
* Admin tree — static nodes, alphabetically sorted. * Admin tree — static nodes, alphabetically sorted.
*/ */
export function buildAdminTreeNodes(): SidebarTreeNode[] { export function buildAdminTreeNodes(opts?: { infrastructureEndpoints?: boolean }): SidebarTreeNode[] {
return [ const showInfra = opts?.infrastructureEndpoints !== false;
const nodes: SidebarTreeNode[] = [
{ id: 'admin:audit', label: 'Audit Log', path: '/admin/audit' }, { id: 'admin:audit', label: 'Audit Log', path: '/admin/audit' },
{ id: 'admin:clickhouse', label: 'ClickHouse', path: '/admin/clickhouse' }, ...(showInfra ? [{ id: 'admin:clickhouse', label: 'ClickHouse', path: '/admin/clickhouse' }] : []),
{ id: 'admin:database', label: 'Database', path: '/admin/database' }, ...(showInfra ? [{ id: 'admin:database', label: 'Database', path: '/admin/database' }] : []),
{ id: 'admin:environments', label: 'Environments', path: '/admin/environments' }, { id: 'admin:environments', label: 'Environments', path: '/admin/environments' },
{ id: 'admin:oidc', label: 'OIDC', path: '/admin/oidc' }, { id: 'admin:oidc', label: 'OIDC', path: '/admin/oidc' },
{ id: 'admin:rbac', label: 'Users & Roles', path: '/admin/rbac' }, { id: 'admin:rbac', label: 'Users & Roles', path: '/admin/rbac' },
]; ];
return nodes;
} }