fix: resolve UI glitches and improve consistency
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m22s
CI / docker (push) Successful in 1m36s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s

- Sidebar: make +App button more subtle (lower opacity, brightens on hover)
- Sidebar: add filter chips to hide empty routes and offline/stale apps
- Sidebar: hide filter chips and +App button when sidebar is collapsed
- Exchange table: reorder columns to Status, Attributes, App, Route, Started, Duration; remove ExchangeId and Agent columns
- Exchange detail log tab: query by exchangeId only (no applicationId required), filter by processorId when processor selected
- KPI tooltips: styled tooltips with current/previous values, time period labels, percentage change, themed with DS variables
- KPI tooltips: fix overflow by left-aligning first two and right-aligning last two
- Exchange detail: show full datetime (YYYY-MM-DD HH:mm:ss.SSS) for start/end times
- Status labels: unify to title-case (Completed, Failed, Running) across all views
- Status filter buttons: match title-case labels (Completed, Warning, Failed, Running)
- Create app: show full external URL using routingDomain from env config or window.location.origin fallback
- Create app: add Runtime Type selector and Custom Arguments to Resources tab
- Create app: add Sensitive Keys tab with agent defaults, global keys, and app-specific keys (matching admin page design)
- Create app: add placeholder text to all Input fields for consistency
- Update design-system to 0.1.52 (sidebar collapse toggle fix)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-15 19:41:36 +02:00
parent 091dfb34d0
commit 457650012b
12 changed files with 493 additions and 102 deletions

View File

@@ -21,7 +21,7 @@ import {
} from '@cameleer/design-system';
import type { SearchResult, SidebarTreeNode, DropdownItem, ButtonGroupItem, ExchangeStatus } from '@cameleer/design-system';
import sidebarLogo from '@cameleer/design-system/assets/cameleer-logo.svg';
import { Box, Settings, FileText, ChevronRight, Square, Pause, Star, X, User, Plus } from 'lucide-react';
import { Box, Settings, FileText, ChevronRight, Square, Pause, Star, X, User, Plus, EyeOff } from 'lucide-react';
import { AboutMeDialog } from './AboutMeDialog';
import css from './LayoutShell.module.css';
import { useQueryClient } from '@tanstack/react-query';
@@ -270,9 +270,9 @@ function StarredList({ items, onNavigate, onRemove }: { items: StarredItem[]; on
/* ------------------------------------------------------------------ */
const STATUS_ITEMS: ButtonGroupItem[] = [
{ value: 'completed', label: 'OK', color: 'var(--success)' },
{ value: 'warning', label: 'Warn', color: 'var(--warning)' },
{ value: 'failed', label: 'Error', color: 'var(--error)' },
{ value: 'completed', label: 'Completed', color: 'var(--success)' },
{ value: 'warning', label: 'Warning', color: 'var(--warning)' },
{ value: 'failed', label: 'Failed', color: 'var(--error)' },
{ value: 'running', label: 'Running', color: 'var(--running)' },
]
@@ -345,6 +345,15 @@ function LayoutContent() {
// --- Sidebar filter -----------------------------------------------
const [filterQuery, setFilterQuery] = useState('');
const [hideEmptyRoutes, setHideEmptyRoutes] = useState(() => readCollapsed('sidebar:hideEmptyRoutes', false));
const [hideOfflineApps, setHideOfflineApps] = useState(() => readCollapsed('sidebar:hideOfflineApps', false));
const toggleHideEmptyRoutes = useCallback(() => {
setHideEmptyRoutes((prev) => { writeCollapsed('sidebar:hideEmptyRoutes', !prev); return !prev; });
}, []);
const toggleHideOfflineApps = useCallback(() => {
setHideOfflineApps((prev) => { writeCollapsed('sidebar:hideOfflineApps', !prev); return !prev; });
}, []);
const setSelectedEnv = useCallback((env: string | undefined) => {
setSelectedEnvRaw(env);
@@ -430,10 +439,27 @@ function LayoutContent() {
}));
}, [catalog]);
// --- Apply sidebar filters -----------------------------------------
const filteredSidebarApps: SidebarApp[] = useMemo(() => {
let apps = sidebarApps;
if (hideOfflineApps) {
apps = apps.filter((a) => a.health !== 'dead' && a.health !== 'stale');
}
if (hideEmptyRoutes) {
apps = apps
.map((a) => ({
...a,
routes: a.routes.filter((r) => r.exchangeCount > 0),
}))
.filter((a) => a.exchangeCount > 0 || a.routes.length > 0);
}
return apps;
}, [sidebarApps, hideOfflineApps, hideEmptyRoutes]);
// --- Tree nodes ---------------------------------------------------
const appTreeNodes: SidebarTreeNode[] = useMemo(
() => buildAppTreeNodes(sidebarApps, makeStatusDot, makeChevron, makeStopIcon, makePauseIcon),
[sidebarApps],
() => buildAppTreeNodes(filteredSidebarApps, makeStatusDot, makeChevron, makeStopIcon, makePauseIcon),
[filteredSidebarApps],
);
const adminTreeNodes: SidebarTreeNode[] = useMemo(
@@ -692,9 +718,29 @@ function LayoutContent() {
version={__APP_VERSION__}
/>
{/* Sidebar filters */}
{!sidebarCollapsed && <div className={css.sidebarFilters}>
<button
className={`${css.filterChip} ${hideEmptyRoutes ? css.filterChipActive : ''}`}
onClick={toggleHideEmptyRoutes}
title="Hide routes with 0 executions"
>
<span className={css.filterChipIcon}><EyeOff size={10} /></span>
Empty routes
</button>
<button
className={`${css.filterChip} ${hideOfflineApps ? css.filterChipActive : ''}`}
onClick={toggleHideOfflineApps}
title="Hide stale and disconnected apps"
>
<span className={css.filterChipIcon}><EyeOff size={10} /></span>
Offline apps
</button>
</div>}
{/* Applications section */}
<div className={css.appSectionWrap}>
{canControl && (
{canControl && !sidebarCollapsed && (
<button
className={css.addAppBtn}
onClick={(e) => { e.stopPropagation(); navigate('/apps/new'); }}