fix: reduce ClickHouse log noise, admin query spam, and diagram scan perf
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m7s
CI / docker (push) Successful in 1m25s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s

- 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) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-03 14:48:30 +02:00
parent 2708bcec17
commit 6f00ff2e28
6 changed files with 36 additions and 16 deletions

View File

@@ -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:

View File

@@ -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

8
ui/package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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<UserDetail[]>('/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<RoleDetail[]>('/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<GroupDetail[]>('/groups'),
enabled,
});
}

View File

@@ -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 = (