From c8cdd846c0f1527a77ee0316dfd06f58cc034732 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sat, 11 Apr 2026 23:12:30 +0200 Subject: [PATCH] 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 --- ui/src/api/queries/capabilities.ts | 29 +++++++++++++++++++++++++++++ ui/src/components/LayoutShell.tsx | 8 ++++++-- ui/src/components/sidebar-utils.ts | 10 ++++++---- 3 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 ui/src/api/queries/capabilities.ts diff --git a/ui/src/api/queries/capabilities.ts b/ui/src/api/queries/capabilities.ts new file mode 100644 index 00000000..9466c92b --- /dev/null +++ b/ui/src/api/queries/capabilities.ts @@ -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, + }); +} diff --git a/ui/src/components/LayoutShell.tsx b/ui/src/components/LayoutShell.tsx index 41b60146..16b7ca46 100644 --- a/ui/src/components/LayoutShell.tsx +++ b/ui/src/components/LayoutShell.tsx @@ -46,6 +46,7 @@ import { readCollapsed, writeCollapsed, } from './sidebar-utils'; +import { useServerCapabilities } from '../api/queries/capabilities'; import type { SidebarApp } from './sidebar-utils'; /* ------------------------------------------------------------------ */ @@ -290,6 +291,9 @@ function LayoutContent() { const globalFilters = useGlobalFilters(); const { timeRange, autoRefresh, refreshTimeRange } = globalFilters; + // --- Server capabilities ------------------------------------------ + const { data: capabilities } = useServerCapabilities(); + // --- Role checks ---------------------------------------------------- const isAdmin = useIsAdmin(); @@ -432,8 +436,8 @@ function LayoutContent() { ); const adminTreeNodes: SidebarTreeNode[] = useMemo( - () => buildAdminTreeNodes(), - [], + () => buildAdminTreeNodes({ infrastructureEndpoints: capabilities?.infrastructureEndpoints }), + [capabilities?.infrastructureEndpoints], ); // --- Starred items ------------------------------------------------ diff --git a/ui/src/components/sidebar-utils.ts b/ui/src/components/sidebar-utils.ts index 41a34dce..2a3bbd79 100644 --- a/ui/src/components/sidebar-utils.ts +++ b/ui/src/components/sidebar-utils.ts @@ -99,13 +99,15 @@ export function buildAppTreeNodes( /** * Admin tree — static nodes, alphabetically sorted. */ -export function buildAdminTreeNodes(): SidebarTreeNode[] { - return [ +export function buildAdminTreeNodes(opts?: { infrastructureEndpoints?: boolean }): SidebarTreeNode[] { + const showInfra = opts?.infrastructureEndpoints !== false; + const nodes: SidebarTreeNode[] = [ { id: 'admin:audit', label: 'Audit Log', path: '/admin/audit' }, - { id: 'admin:clickhouse', label: 'ClickHouse', path: '/admin/clickhouse' }, - { id: 'admin:database', label: 'Database', path: '/admin/database' }, + ...(showInfra ? [{ id: 'admin:clickhouse', label: 'ClickHouse', path: '/admin/clickhouse' }] : []), + ...(showInfra ? [{ id: 'admin:database', label: 'Database', path: '/admin/database' }] : []), { id: 'admin:environments', label: 'Environments', path: '/admin/environments' }, { id: 'admin:oidc', label: 'OIDC', path: '/admin/oidc' }, { id: 'admin:rbac', label: 'Users & Roles', path: '/admin/rbac' }, ]; + return nodes; }