fix: persist environment selection in Zustand store instead of URL params
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m5s
CI / docker (push) Successful in 57s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s

Environment selector was losing its value on navigation because URL search
params were silently dropped by navigate() calls. Moved to a Zustand store
with localStorage persistence so the selection survives navigation, page
refresh, and new tabs. Switching environment now resets all filters, clears
URL params, invalidates queries, and remounts pages via Outlet key. Also
syncs openapi.json schema with running backend.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-04 17:12:16 +02:00
parent 37eb56332a
commit 69055f7d74
4 changed files with 157 additions and 42 deletions

View File

@@ -1,4 +1,4 @@
import { Outlet, useNavigate, useLocation, useSearchParams } from 'react-router';
import { Outlet, useNavigate, useLocation } from 'react-router';
import {
AppShell,
Sidebar,
@@ -23,6 +23,7 @@ import { useSearchExecutions, useAttributeKeys } from '../api/queries/executions
import { useUsers, useGroups, useRoles } from '../api/queries/admin/rbac';
import type { UserDetail, GroupDetail, RoleDetail } from '../api/queries/admin/rbac';
import { useAuthStore } from '../auth/auth-store';
import { useEnvironmentStore } from '../api/environment-store';
import { useState, useMemo, useCallback, useEffect, useRef, createElement } from 'react';
import type { ReactNode } from 'react';
import { ContentTabs } from './ContentTabs';
@@ -272,23 +273,12 @@ const SK_COLLAPSED = 'sidebar:collapsed';
function LayoutContent() {
const navigate = useNavigate();
const location = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const queryClient = useQueryClient();
const { timeRange, autoRefresh, refreshTimeRange } = useGlobalFilters();
// --- Environment filtering -----------------------------------------
const selectedEnv = searchParams.get('env') || undefined;
const setSelectedEnv = useCallback((env: string | undefined) => {
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
if (env) {
next.set('env', env);
} else {
next.delete('env');
}
return next;
}, { replace: true });
}, [setSearchParams]);
const selectedEnv = useEnvironmentStore((s) => s.environment);
const setSelectedEnvRaw = useEnvironmentStore((s) => s.setEnvironment);
const { data: catalog } = useRouteCatalog(timeRange.start.toISOString(), timeRange.end.toISOString(), selectedEnv);
const { data: allAgents } = useAgents(); // unfiltered — for environment discovery
@@ -330,6 +320,15 @@ function LayoutContent() {
// --- Sidebar filter -----------------------------------------------
const [filterQuery, setFilterQuery] = useState('');
const setSelectedEnv = useCallback((env: string | undefined) => {
setSelectedEnvRaw(env);
setFilterQuery('');
if (location.search) {
navigate(location.pathname, { replace: true });
}
queryClient.invalidateQueries();
}, [setSelectedEnvRaw, navigate, location.pathname, location.search, queryClient]);
// --- Section open states ------------------------------------------
const [appsOpen, setAppsOpen] = useState(() => isAdminPage ? false : readCollapsed(SK_APPS, true));
const [adminOpen, setAdminOpen] = useState(() => isAdminPage ? true : readCollapsed(SK_ADMIN, false));
@@ -537,6 +536,7 @@ function LayoutContent() {
// --- Callbacks ----------------------------------------------------
const handleLogout = useCallback(() => {
logout();
useEnvironmentStore.getState().setEnvironment(undefined);
navigate('/login');
}, [logout, navigate]);
@@ -729,7 +729,7 @@ function LayoutContent() {
)}
<main style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0 }}>
<Outlet />
<Outlet key={selectedEnv ?? '__all__'} />
</main>
</AppShell>
);