From 1c5ecb02e3ef3fd8ed2438c9182d228082353d12 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:50:31 +0200 Subject: [PATCH] fix: make environment list accessible to all authenticated users The list endpoint on EnvironmentAdminController now overrides the class-level ADMIN guard with isAuthenticated(), so VIEWERs can see the environment selector. The LayoutShell merges environments from both the table and agent heartbeats, so the selector always shows configured environments even when no agents are connected. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../controller/EnvironmentAdminController.java | 1 + ui/src/components/LayoutShell.tsx | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/EnvironmentAdminController.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/EnvironmentAdminController.java index fb36c4cf..f558435a 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/EnvironmentAdminController.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/EnvironmentAdminController.java @@ -27,6 +27,7 @@ public class EnvironmentAdminController { @GetMapping @Operation(summary = "List all environments") + @PreAuthorize("isAuthenticated()") public ResponseEntity> listEnvironments() { return ResponseEntity.ok(environmentService.listAll()); } diff --git a/ui/src/components/LayoutShell.tsx b/ui/src/components/LayoutShell.tsx index b5dfca2b..44b8761f 100644 --- a/ui/src/components/LayoutShell.tsx +++ b/ui/src/components/LayoutShell.tsx @@ -25,6 +25,7 @@ import { useRouteCatalog } from '../api/queries/catalog'; import { useAgents } from '../api/queries/agents'; import { useSearchExecutions, useAttributeKeys } from '../api/queries/executions'; import { useUsers, useGroups, useRoles } from '../api/queries/admin/rbac'; +import { useEnvironments } from '../api/queries/admin/environments'; import type { UserDetail, GroupDetail, RoleDetail } from '../api/queries/admin/rbac'; import { useAuthStore, useIsAdmin } from '../auth/auth-store'; import { useEnvironmentStore } from '../api/environment-store'; @@ -291,16 +292,20 @@ function LayoutContent() { const { data: allAgents } = useAgents(); // unfiltered — for environment discovery const { data: agents } = useAgents(undefined, undefined, selectedEnv); // filtered — for sidebar/search const { data: attributeKeys } = useAttributeKeys(); + const { data: envRecords = [] } = useEnvironments(); - // Extract distinct environments from ALL agents (not filtered by selected env) + // Merge environments from both the environments table and agent heartbeats const environments: string[] = useMemo(() => { - if (!allAgents) return ['default']; const envSet = new Set(); - for (const a of allAgents as any[]) { - envSet.add(a.environmentId || 'default'); + for (const e of envRecords) envSet.add(e.slug); + if (allAgents) { + for (const a of allAgents as any[]) { + envSet.add(a.environmentId || 'default'); + } } + if (envSet.size === 0) envSet.add('default'); return [...envSet].sort(); - }, [allAgents]); + }, [allAgents, envRecords]); // --- Admin search data (only fetched on admin pages) ---------------- const isAdminPage = location.pathname.startsWith('/admin');