From 6f00ff2e28ea0ed1ac798ea6282326b7ea31c312 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:48:30 +0200 Subject: [PATCH] fix: reduce ClickHouse log noise, admin query spam, and diagram scan perf - Set com.clickhouse log level to INFO and org.apache.hc.client5 to WARN - Admin hooks (useUsers/useGroups/useRoles) now only fetch on admin pages, eliminating AUDIT view_users entries on every UI click - Add ClickHouse projection on route_diagrams for (tenant_id, route_id, instance_id, created_at) to avoid full table scans on diagram lookups - Bump @cameleer/design-system to v0.1.28 (PAUSED mode time range fix, refreshTimeRange API) - Call refreshTimeRange before invalidateQueries in PAUSED mode manual refresh so sidebar clicks use current time window Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/main/resources/application.yml | 5 +++++ .../V11__route_diagrams_projection.sql | 8 ++++++++ ui/package-lock.json | 8 ++++---- ui/package.json | 2 +- ui/src/api/queries/admin/rbac.ts | 9 ++++++--- ui/src/components/LayoutShell.tsx | 20 +++++++++++-------- 6 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 cameleer3-server-app/src/main/resources/clickhouse/V11__route_diagrams_projection.sql diff --git a/cameleer3-server-app/src/main/resources/application.yml b/cameleer3-server-app/src/main/resources/application.yml index 93708b4f..656ee68f 100644 --- a/cameleer3-server-app/src/main/resources/application.yml +++ b/cameleer3-server-app/src/main/resources/application.yml @@ -66,6 +66,11 @@ clickhouse: username: ${CLICKHOUSE_USERNAME:default} password: ${CLICKHOUSE_PASSWORD:} +logging: + level: + com.clickhouse: INFO + org.apache.hc.client5: WARN + management: endpoints: web: diff --git a/cameleer3-server-app/src/main/resources/clickhouse/V11__route_diagrams_projection.sql b/cameleer3-server-app/src/main/resources/clickhouse/V11__route_diagrams_projection.sql new file mode 100644 index 00000000..64240f25 --- /dev/null +++ b/cameleer3-server-app/src/main/resources/clickhouse/V11__route_diagrams_projection.sql @@ -0,0 +1,8 @@ +-- Projection for fast route_id + instance_id lookups on route_diagrams. +-- The primary key is (tenant_id, content_hash) which serves hash-based lookups. +-- Queries filtering by route_id + instance_id were scanning millions of rows. +ALTER TABLE route_diagrams + ADD PROJECTION IF NOT EXISTS prj_route_instance + (SELECT content_hash, created_at ORDER BY tenant_id, route_id, instance_id, created_at); + +ALTER TABLE route_diagrams MATERIALIZE PROJECTION IF NOT EXISTS prj_route_instance diff --git a/ui/package-lock.json b/ui/package-lock.json index c464b601..dae2de19 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -8,7 +8,7 @@ "name": "ui", "version": "0.0.0", "dependencies": { - "@cameleer/design-system": "^0.1.26", + "@cameleer/design-system": "^0.1.28", "@tanstack/react-query": "^5.90.21", "lucide-react": "^1.7.0", "openapi-fetch": "^0.17.0", @@ -278,9 +278,9 @@ } }, "node_modules/@cameleer/design-system": { - "version": "0.1.26", - "resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.26/design-system-0.1.26.tgz", - "integrity": "sha512-lu76c4F1Vz6fdXLjv434zh5jm61uSrkyNwmRbLtrs3tZbjovL8JWwj0Ao6akBJ84axzUt3GCbugVJwiLZvUKdA==", + "version": "0.1.28", + "resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.28/design-system-0.1.28.tgz", + "integrity": "sha512-pDzuiIW2zpkYeX9mR1ENxO2Xk+GTMNUQzxsC7C+p0veEnOC1XObHse9Q06GKJQ9+mff0eFCk9cuYCwW3kwB0rA==", "dependencies": { "lucide-react": "^1.7.0", "react": "^19.0.0", diff --git a/ui/package.json b/ui/package.json index 54234e90..2023fa07 100644 --- a/ui/package.json +++ b/ui/package.json @@ -14,7 +14,7 @@ "generate-api:live": "curl -s http://localhost:8081/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts" }, "dependencies": { - "@cameleer/design-system": "^0.1.26", + "@cameleer/design-system": "^0.1.28", "@tanstack/react-query": "^5.90.21", "lucide-react": "^1.7.0", "openapi-fetch": "^0.17.0", diff --git a/ui/src/api/queries/admin/rbac.ts b/ui/src/api/queries/admin/rbac.ts index f50b8ec9..2c438447 100644 --- a/ui/src/api/queries/admin/rbac.ts +++ b/ui/src/api/queries/admin/rbac.ts @@ -108,10 +108,11 @@ export function useRbacStats() { // ── User Query Hooks ─────────────────────────────────────────────────── -export function useUsers() { +export function useUsers(enabled = true) { return useQuery({ queryKey: ['admin', 'users'], queryFn: () => adminFetch('/users'), + enabled, }); } @@ -125,10 +126,11 @@ export function useUser(userId: string | null) { // ── Role Query Hooks ─────────────────────────────────────────────────── -export function useRoles() { +export function useRoles(enabled = true) { return useQuery({ queryKey: ['admin', 'roles'], queryFn: () => adminFetch('/roles'), + enabled, }); } @@ -142,10 +144,11 @@ export function useRole(roleId: string | null) { // ── Group Query Hooks ────────────────────────────────────────────────── -export function useGroups() { +export function useGroups(enabled = true) { return useQuery({ queryKey: ['admin', 'groups'], queryFn: () => adminFetch('/groups'), + enabled, }); } diff --git a/ui/src/components/LayoutShell.tsx b/ui/src/components/LayoutShell.tsx index cfb2bdbd..1d5253ef 100644 --- a/ui/src/components/LayoutShell.tsx +++ b/ui/src/components/LayoutShell.tsx @@ -272,15 +272,16 @@ function LayoutContent() { const navigate = useNavigate(); const location = useLocation(); const queryClient = useQueryClient(); - const { timeRange, autoRefresh } = useGlobalFilters(); + const { timeRange, autoRefresh, refreshTimeRange } = useGlobalFilters(); const { data: catalog } = useRouteCatalog(timeRange.start.toISOString(), timeRange.end.toISOString()); const { data: agents } = useAgents(); const { data: attributeKeys } = useAttributeKeys(); // --- Admin search data (only fetched on admin pages) ---------------- - const { data: adminUsers } = useUsers(); - const { data: adminGroups } = useGroups(); - const { data: adminRoles } = useRoles(); + const isAdminPage = location.pathname.startsWith('/admin'); + const { data: adminUsers } = useUsers(isAdminPage); + const { data: adminGroups } = useGroups(isAdminPage); + const { data: adminRoles } = useRoles(isAdminPage); const { username, logout } = useAuthStore(); const { open: paletteOpen, setOpen: setPaletteOpen } = useCommandPalette(); @@ -302,7 +303,6 @@ function LayoutContent() { const [filterQuery, setFilterQuery] = useState(''); // --- Section open states ------------------------------------------ - const isAdminPage = location.pathname.startsWith('/admin'); const [appsOpen, setAppsOpen] = useState(() => isAdminPage ? false : readCollapsed(SK_APPS, true)); const [adminOpen, setAdminOpen] = useState(() => isAdminPage ? true : readCollapsed(SK_ADMIN, false)); const [starredOpen, setStarredOpen] = useState(true); @@ -331,13 +331,16 @@ function LayoutContent() { } if (appsOpen) { // Already open — navigate to all applications - if (!autoRefresh) queryClient.invalidateQueries(); + if (!autoRefresh) { + refreshTimeRange(); + queryClient.invalidateQueries(); + } navigate(`/${scope.tab}`); } else { setAppsOpen(true); writeCollapsed(SK_APPS, true); } - }, [isAdminPage, appsOpen, navigate, scope.tab, autoRefresh, queryClient]); + }, [isAdminPage, appsOpen, navigate, scope.tab, autoRefresh, refreshTimeRange, queryClient]); const toggleAdmin = useCallback(() => { if (!isAdminPage) { @@ -562,6 +565,7 @@ function LayoutContent() { // When not auto-refreshing, treat navigation as a manual refresh if (!autoRefresh) { + refreshTimeRange(); queryClient.invalidateQueries(); } @@ -580,7 +584,7 @@ function LayoutContent() { } navigate(path, { state }); - }, [navigate, scope.tab, autoRefresh, queryClient]); + }, [navigate, scope.tab, autoRefresh, refreshTimeRange, queryClient]); // --- Render ------------------------------------------------------- const camelLogo = (