From 27f25036409dc0099539e2d25ef16113abe42c81 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 12 Apr 2026 22:33:44 +0200 Subject: [PATCH] fix: clean up runtime UI and harden session expiry handling Remove redundant "X/X LIVE" badge from runtime page, breadcrumb trail and routes section from agent detail page (pills moved into Process Information card). Fix session expiry: guard against concurrent 401 refresh races and skip re-entrant triggers on auth endpoints. Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/src/api/client.ts | 15 ++++- ui/src/auth/use-auth.ts | 17 ++++-- ui/src/pages/AgentHealth/AgentHealth.tsx | 8 --- .../AgentInstance/AgentInstance.module.css | 46 +++------------- ui/src/pages/AgentInstance/AgentInstance.tsx | 55 ++++++------------- 5 files changed, 50 insertions(+), 91 deletions(-) diff --git a/ui/src/api/client.ts b/ui/src/api/client.ts index 63779088..753b9e49 100644 --- a/ui/src/api/client.ts +++ b/ui/src/api/client.ts @@ -6,6 +6,7 @@ import { useAuthStore } from '../auth/auth-store'; let getAccessToken: () => string | null = () => useAuthStore.getState().accessToken; let onUnauthorized: () => void = () => {}; +let refreshing: Promise | null = null; export function configureAuth(opts: { getAccessToken?: () => string | null; @@ -24,9 +25,19 @@ const authMiddleware: Middleware = { request.headers.set('X-Cameleer-Protocol-Version', '1'); return request; }, - async onResponse({ response }) { + async onResponse({ request, response }) { if (response.status === 401 || response.status === 403) { - onUnauthorized(); + // Don't re-trigger for auth endpoints (refresh/login) to avoid loops + const url = new URL(request.url); + if (url.pathname.endsWith('/auth/refresh') || url.pathname.endsWith('/auth/login')) { + return response; + } + // Coalesce concurrent 401s into a single refresh attempt + if (!refreshing) { + refreshing = (async () => { + try { onUnauthorized(); } finally { refreshing = null; } + })(); + } } return response; }, diff --git a/ui/src/auth/use-auth.ts b/ui/src/auth/use-auth.ts index 525464b7..25e46fdf 100644 --- a/ui/src/auth/use-auth.ts +++ b/ui/src/auth/use-auth.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import { useAuthStore } from './auth-store'; import { configureAuth } from '../api/client'; import { useNavigate } from 'react-router'; @@ -6,14 +6,21 @@ import { useNavigate } from 'react-router'; export function useAuth() { const { accessToken, isAuthenticated, refresh, logout } = useAuthStore(); const navigate = useNavigate(); + const refreshingRef = useRef(false); useEffect(() => { configureAuth({ onUnauthorized: async () => { - const ok = await useAuthStore.getState().refresh(); - if (!ok) { - useAuthStore.getState().logout(); - navigate('/login', { replace: true }); + if (refreshingRef.current) return; + refreshingRef.current = true; + try { + const ok = await useAuthStore.getState().refresh(); + if (!ok) { + useAuthStore.getState().logout(); + navigate('/login', { replace: true }); + } + } finally { + refreshingRef.current = false; } }, }); diff --git a/ui/src/pages/AgentHealth/AgentHealth.tsx b/ui/src/pages/AgentHealth/AgentHealth.tsx index dcc46b47..11f5b73f 100644 --- a/ui/src/pages/AgentHealth/AgentHealth.tsx +++ b/ui/src/pages/AgentHealth/AgentHealth.tsx @@ -400,14 +400,6 @@ export default function AgentHealth() { /> -
- 0 ? 'error' : staleCount > 0 ? 'warning' : 'success'} - variant="filled" - /> -
- {/* Application config bar */} {appId && appConfig && (
diff --git a/ui/src/pages/AgentInstance/AgentInstance.module.css b/ui/src/pages/AgentInstance/AgentInstance.module.css index ffd4b200..bbcf8454 100644 --- a/ui/src/pages/AgentInstance/AgentInstance.module.css +++ b/ui/src/pages/AgentInstance/AgentInstance.module.css @@ -14,42 +14,20 @@ margin-bottom: 16px; } -/* Scope trail — matches /agents */ -.scopeTrail { - display: flex; - align-items: center; - gap: 6px; - margin-bottom: 12px; - font-size: 12px; -} - -.scopeLink { - color: var(--amber); - text-decoration: none; - font-weight: 500; -} - -.scopeLink:hover { - text-decoration: underline; -} - -.scopeSep { - color: var(--text-muted); - font-size: 12px; -} - -.scopeCurrent { - color: var(--text-primary); - font-weight: 600; - font-family: var(--font-mono); -} - /* Process info card — card styling via sectionStyles.section */ .processCard { padding: 16px; margin-bottom: 20px; } +.processBadges { + display: flex; + align-items: center; + gap: 6px; + margin-top: 8px; + margin-bottom: 4px; +} + .processGrid { display: grid; grid-template-columns: auto 1fr auto 1fr; @@ -70,14 +48,6 @@ flex-wrap: wrap; } -/* Route badges */ -.routeBadges { - display: flex; - gap: 6px; - flex-wrap: wrap; - margin-bottom: 20px; -} - /* Charts 3x2 grid */ .chartsGrid { display: grid; diff --git a/ui/src/pages/AgentInstance/AgentInstance.tsx b/ui/src/pages/AgentInstance/AgentInstance.tsx index eb0a008a..38486fc3 100644 --- a/ui/src/pages/AgentInstance/AgentInstance.tsx +++ b/ui/src/pages/AgentInstance/AgentInstance.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from 'react'; -import { useParams, Link } from 'react-router'; -import { RefreshCw, ChevronRight } from 'lucide-react'; +import { useParams } from 'react-router'; +import { RefreshCw } from 'lucide-react'; import { StatCard, StatusDot, Badge, ThemedChart, Line, Area, ReferenceLine, CHART_COLORS, EventFeed, Spinner, EmptyState, SectionHeader, MonoText, @@ -213,36 +213,25 @@ export default function AgentInstance() { />
- {/* Scope trail + badges */} {agent && ( <> -
- - All Agents - - - - {appId} - - - {agent.displayName} - - - {agent.containerStatus && agent.containerStatus !== 'UNKNOWN' && ( - - )} - {agent.version && } - -
- {/* Process info card */}
Process Information +
+ + + {agent.containerStatus && agent.containerStatus !== 'UNKNOWN' && ( + + )} + {agent.version && } + +
{agent.capabilities?.jvmVersion && ( <> @@ -281,17 +270,7 @@ export default function AgentInstance() {
- {/* Routes */} - {(agent.routeIds?.length ?? 0) > 0 && ( - <> - Routes -
- {(agent.routeIds || []).map((r: string) => ( - - ))} -
- - )} + )}