2026-03-26 16:15:27 +01:00
import { useEffect , useState , useMemo } from 'react' ;
import { useParams , useNavigate } from 'react-router' ;
2026-03-27 23:16:39 +01:00
import { ArrowLeft , Pencil , X } from 'lucide-react' ;
2026-03-26 16:15:27 +01:00
import {
2026-04-09 15:22:14 +02:00
Button , SectionHeader , MonoText , Badge , DataTable , Spinner , Toggle , Select , Label , useToast ,
2026-03-26 16:15:27 +01:00
} from '@cameleer/design-system' ;
import type { Column } from '@cameleer/design-system' ;
import { useApplicationConfig , useUpdateApplicationConfig } from '../../api/queries/commands' ;
2026-04-02 19:08:00 +02:00
import type { ApplicationConfig , TapDefinition , ConfigUpdateResponse } from '../../api/queries/commands' ;
2026-04-09 16:28:09 +02:00
import { useEnvironmentStore } from '../../api/environment-store' ;
2026-04-08 23:43:14 +02:00
import { useCatalog } from '../../api/queries/catalog' ;
import type { CatalogApp , CatalogRoute } from '../../api/queries/catalog' ;
2026-04-09 08:28:31 +02:00
import { applyTracedProcessorUpdate , applyRouteRecordingUpdate } from '../../utils/config-draft-utils' ;
2026-03-26 16:15:27 +01:00
import styles from './AppConfigDetailPage.module.css' ;
refactor: UI consistency — shared CSS, design system colors, no inline styles
Phase 1: Extract 6 shared CSS modules (table-section, log-panel,
rate-colors, refresh-indicator, chart-card, section-card) eliminating
~135 duplicate class definitions across 11 files.
Phase 2: Replace all hardcoded hex colors in CSS modules with design
system variables. Strip ~55 hex fallbacks from var() patterns. Fix 4
undefined variable names (--accent, --bg-base, --surface, --bg-surface-raised).
Phase 3: Replace ~45 hardcoded hex values in ProcessDiagram SVG
components with var() CSS custom properties. Fix Dashboard.tsx color prop.
Phase 4: Create CSS modules for AdminLayout, DatabaseAdminPage,
OidcCallback (previously 100% inline). Extract shared PageLoader
component (replaces 3 copy-pasted spinner patterns). Move AppsTab
static inline styles to CSS classes. Extract LayoutShell StarredList styles.
58 files changed, net -219 lines.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:55:54 +02:00
import sectionStyles from '../../styles/section-card.module.css' ;
2026-03-26 16:15:27 +01:00
type BadgeColor = 'primary' | 'success' | 'warning' | 'error' | 'running' | 'auto' ;
2026-03-26 18:48:14 +01:00
interface TracedTapRow {
id : string ;
processorId : string ;
captureMode : string | null ;
taps : TapDefinition [ ] ;
}
interface RouteRecordingRow {
id : string ;
routeId : string ;
recording : boolean ;
}
2026-03-26 16:15:27 +01:00
function formatTimestamp ( iso? : string ) : string {
if ( ! iso ) return '\u2014' ;
return new Date ( iso ) . toLocaleString ( 'en-GB' , {
day : '2-digit' , month : 'short' , year : 'numeric' ,
hour : '2-digit' , minute : '2-digit' , second : '2-digit' ,
} ) ;
}
function logLevelColor ( level? : string ) : BadgeColor {
switch ( level ? . toUpperCase ( ) ) {
case 'ERROR' : return 'error' ;
case 'WARN' : return 'warning' ;
case 'DEBUG' : return 'running' ;
2026-03-26 23:03:15 +01:00
case 'TRACE' : return 'auto' ;
2026-03-26 16:15:27 +01:00
default : return 'success' ;
}
}
function engineLevelColor ( level? : string ) : BadgeColor {
switch ( level ? . toUpperCase ( ) ) {
case 'NONE' : return 'error' ;
case 'MINIMAL' : return 'warning' ;
case 'COMPLETE' : return 'running' ;
default : return 'success' ;
}
}
function payloadColor ( mode? : string ) : BadgeColor {
switch ( mode ? . toUpperCase ( ) ) {
case 'INPUT' : case 'OUTPUT' : return 'warning' ;
case 'BOTH' : return 'running' ;
default : return 'auto' ;
}
}
function captureColor ( mode : string ) : BadgeColor {
switch ( mode ? . toUpperCase ( ) ) {
case 'INPUT' : case 'OUTPUT' : return 'warning' ;
case 'BOTH' : return 'running' ;
default : return 'auto' ;
}
}
export default function AppConfigDetailPage() {
const { appId } = useParams < { appId : string } > ( ) ;
const navigate = useNavigate ( ) ;
const { toast } = useToast ( ) ;
2026-04-09 16:28:09 +02:00
const selectedEnv = useEnvironmentStore ( ( s ) = > s . environment ) ;
2026-03-26 16:15:27 +01:00
const { data : config , isLoading } = useApplicationConfig ( appId ) ;
const updateConfig = useUpdateApplicationConfig ( ) ;
2026-04-08 23:43:14 +02:00
const { data : catalog } = useCatalog ( ) ;
2026-03-26 16:15:27 +01:00
const [ editing , setEditing ] = useState ( false ) ;
const [ form , setForm ] = useState < Partial < ApplicationConfig > | null > ( null ) ;
const [ tracedDraft , setTracedDraft ] = useState < Record < string , string > > ( { } ) ;
2026-03-26 18:48:14 +01:00
const [ routeRecordingDraft , setRouteRecordingDraft ] = useState < Record < string , boolean > > ( { } ) ;
// Find routes for this application from the catalog
2026-04-08 23:43:14 +02:00
const appRoutes : CatalogRoute [ ] = useMemo ( ( ) = > {
2026-03-26 18:48:14 +01:00
if ( ! catalog || ! appId ) return [ ] ;
2026-04-08 23:43:14 +02:00
const entry = ( catalog as CatalogApp [ ] ) . find ( ( e ) = > e . slug === appId ) ;
2026-03-26 18:48:14 +01:00
return entry ? . routes ? ? [ ] ;
} , [ catalog , appId ] ) ;
2026-03-26 16:15:27 +01:00
useEffect ( ( ) = > {
if ( config ) {
setForm ( {
2026-03-26 23:36:31 +01:00
applicationLogLevel : config.applicationLogLevel ? ? 'INFO' ,
agentLogLevel : config.agentLogLevel ? ? 'INFO' ,
2026-03-26 16:15:27 +01:00
engineLevel : config.engineLevel ? ? 'REGULAR' ,
2026-04-12 17:12:21 +02:00
payloadCaptureMode : config.payloadCaptureMode ? ? 'BOTH' ,
2026-03-26 16:15:27 +01:00
metricsEnabled : config.metricsEnabled ,
samplingRate : config.samplingRate ,
2026-03-26 18:48:14 +01:00
compressSuccess : config.compressSuccess ,
2026-03-26 16:15:27 +01:00
} ) ;
setTracedDraft ( { . . . config . tracedProcessors } ) ;
2026-03-26 18:48:14 +01:00
setRouteRecordingDraft ( { . . . config . routeRecording } ) ;
2026-03-26 16:15:27 +01:00
}
} , [ config ] ) ;
function startEditing() {
if ( ! config ) return ;
setForm ( {
2026-03-26 23:36:31 +01:00
applicationLogLevel : config.applicationLogLevel ? ? 'INFO' ,
agentLogLevel : config.agentLogLevel ? ? 'INFO' ,
2026-03-26 16:15:27 +01:00
engineLevel : config.engineLevel ? ? 'REGULAR' ,
2026-04-12 17:12:21 +02:00
payloadCaptureMode : config.payloadCaptureMode ? ? 'BOTH' ,
2026-03-26 16:15:27 +01:00
metricsEnabled : config.metricsEnabled ,
samplingRate : config.samplingRate ,
2026-03-26 18:48:14 +01:00
compressSuccess : config.compressSuccess ,
2026-03-26 16:15:27 +01:00
} ) ;
setTracedDraft ( { . . . config . tracedProcessors } ) ;
2026-03-26 18:48:14 +01:00
setRouteRecordingDraft ( { . . . config . routeRecording } ) ;
2026-03-26 16:15:27 +01:00
setEditing ( true ) ;
}
function cancelEditing() {
setEditing ( false ) ;
}
function updateField < K extends keyof ApplicationConfig > ( key : K , value : ApplicationConfig [ K ] ) {
setForm ( ( prev ) = > prev ? { . . . prev , [ key ] : value } : prev ) ;
}
function updateTracedProcessor ( processorId : string , mode : string ) {
2026-04-09 08:28:31 +02:00
setTracedDraft ( ( prev ) = > applyTracedProcessorUpdate ( prev , processorId , mode ) ) ;
2026-03-26 16:15:27 +01:00
}
2026-03-26 18:48:14 +01:00
function updateRouteRecording ( routeId : string , recording : boolean ) {
2026-04-09 08:28:31 +02:00
setRouteRecordingDraft ( ( prev ) = > applyRouteRecordingUpdate ( prev , routeId , recording ) ) ;
2026-03-26 18:48:14 +01:00
}
2026-03-26 16:15:27 +01:00
function handleSave() {
if ( ! config || ! form ) return ;
2026-03-26 18:48:14 +01:00
const updated : ApplicationConfig = {
. . . config ,
. . . form ,
tracedProcessors : tracedDraft ,
routeRecording : routeRecordingDraft ,
} as ApplicationConfig ;
2026-04-09 16:28:09 +02:00
updateConfig . mutate ( { config : updated , environment : selectedEnv } , {
2026-04-02 19:08:00 +02:00
onSuccess : ( saved : ConfigUpdateResponse ) = > {
2026-03-26 16:15:27 +01:00
setEditing ( false ) ;
2026-04-02 19:08:00 +02:00
if ( saved . pushResult . success ) {
toast ( { title : 'Config saved' , description : ` ${ appId } updated to v ${ saved . config . version } — pushed to ${ saved . pushResult . total } / ${ saved . pushResult . total } agents ` , variant : 'success' } ) ;
} else {
const failed = [ . . . saved . pushResult . responses . filter ( r = > r . status !== 'SUCCESS' ) . map ( r = > r . agentId ) , . . . saved . pushResult . timedOut ] ;
toast ( { title : 'Config saved — partial push failure' , description : ` ${ saved . pushResult . responded } / ${ saved . pushResult . total } responded. Failed: ${ failed . join ( ', ' ) } ` , variant : 'warning' , duration : 86_400_000 } ) ;
}
2026-03-26 16:15:27 +01:00
} ,
onError : ( ) = > {
2026-04-09 18:43:46 +02:00
toast ( { title : 'Failed to save configuration' , description : 'Could not update configuration' , variant : 'error' , duration : 86_400_000 } ) ;
2026-03-26 16:15:27 +01:00
} ,
} ) ;
}
2026-03-26 18:48:14 +01:00
// ── Traces & Taps merged rows ──────────────────────────────────────────────
const tracedTapRows : TracedTapRow [ ] = useMemo ( ( ) = > {
const traced = editing ? tracedDraft : ( config ? . tracedProcessors ? ? { } ) ;
const taps = config ? . taps ? ? [ ] ;
// Collect all unique processor IDs
const processorIds = new Set < string > ( [
. . . Object . keys ( traced ) ,
. . . taps . map ( ( t ) = > t . processorId ) ,
] ) ;
return Array . from ( processorIds ) . sort ( ) . map ( ( pid ) = > ( {
id : pid ,
processorId : pid ,
captureMode : traced [ pid ] ? ? null ,
taps : taps.filter ( ( t ) = > t . processorId === pid ) ,
} ) ) ;
} , [ editing , tracedDraft , config ? . tracedProcessors , config ? . taps ] ) ;
const tracedCount = useMemo ( ( ) = > {
2026-03-26 16:15:27 +01:00
const source = editing ? tracedDraft : ( config ? . tracedProcessors ? ? { } ) ;
2026-03-26 18:48:14 +01:00
return Object . keys ( source ) . length ;
2026-03-26 16:15:27 +01:00
} , [ editing , tracedDraft , config ? . tracedProcessors ] ) ;
2026-03-26 18:48:14 +01:00
const tapCount = config ? . taps ? . length ? ? 0 ;
const tracedTapColumns : Column < TracedTapRow > [ ] = useMemo ( ( ) = > [
{
key : 'processorId' ,
header : 'Processor' ,
render : ( _v , row ) = > < MonoText size = "xs" > { row . processorId } < / MonoText > ,
} ,
2026-03-26 16:15:27 +01:00
{
key : 'captureMode' ,
2026-03-26 18:48:14 +01:00
header : 'Capture' ,
2026-03-26 16:15:27 +01:00
render : ( _v , row ) = > {
2026-03-26 18:48:14 +01:00
if ( row . captureMode === null ) {
return < span className = { styles . hint } > & mdash ; < / span > ;
}
2026-03-26 16:15:27 +01:00
if ( editing ) {
return (
2026-04-09 15:22:14 +02:00
< Select
2026-03-26 18:48:14 +01:00
value = { row . captureMode }
2026-03-26 16:15:27 +01:00
onChange = { ( e ) = > updateTracedProcessor ( row . processorId , e . target . value ) }
2026-04-09 15:22:14 +02:00
options = { [
{ value : 'NONE' , label : 'None' } ,
{ value : 'INPUT' , label : 'Input' } ,
{ value : 'OUTPUT' , label : 'Output' } ,
{ value : 'BOTH' , label : 'Both' } ,
] }
/ >
2026-03-26 16:15:27 +01:00
) ;
}
return < Badge label = { row . captureMode } color = { captureColor ( row . captureMode ) } variant = "filled" / > ;
} ,
} ,
2026-03-26 18:48:14 +01:00
{
key : 'taps' ,
header : 'Taps' ,
render : ( _v , row ) = > {
if ( row . taps . length === 0 ) {
return < span className = { styles . hint } > & mdash ; < / span > ;
}
return (
< div className = { styles . tapBadges } >
{ row . taps . map ( ( t ) = > (
< Badge
key = { t . tapId }
label = { t . attributeName }
color = { t . enabled ? 'success' : 'auto' }
variant = "filled"
/ >
) ) }
< / div >
) ;
} ,
} ,
2026-03-26 16:15:27 +01:00
. . . ( editing ? [ {
key : '_remove' as const ,
header : '' ,
width : '36px' ,
2026-03-26 18:48:14 +01:00
render : ( _v : unknown , row : TracedTapRow ) = > {
if ( row . captureMode === null ) return null ;
return (
2026-04-09 15:22:14 +02:00
< Button variant = "ghost" size = "sm" title = "Remove" onClick = { ( ) = > updateTracedProcessor ( row . processorId , 'REMOVE' ) } >
2026-03-27 23:16:39 +01:00
< X size = { 14 } / >
2026-04-09 15:22:14 +02:00
< / Button >
2026-03-26 18:48:14 +01:00
) ;
} ,
2026-03-26 16:15:27 +01:00
} ] : [ ] ) ,
] , [ editing ] ) ;
2026-03-26 18:48:14 +01:00
// ── Route Recording rows ───────────────────────────────────────────────────
const routeRecordingRows : RouteRecordingRow [ ] = useMemo ( ( ) = > {
const recording = editing ? routeRecordingDraft : ( config ? . routeRecording ? ? { } ) ;
return appRoutes . map ( ( r ) = > ( {
id : r.routeId ,
routeId : r.routeId ,
recording : recording [ r . routeId ] !== false ,
} ) ) ;
} , [ editing , routeRecordingDraft , config ? . routeRecording , appRoutes ] ) ;
const recordingCount = routeRecordingRows . filter ( ( r ) = > r . recording ) . length ;
const routeRecordingColumns : Column < RouteRecordingRow > [ ] = useMemo ( ( ) = > [
{
key : 'routeId' ,
header : 'Route' ,
render : ( _v , row ) = > < MonoText size = "xs" > { row . routeId } < / MonoText > ,
} ,
{
key : 'recording' ,
header : 'Recording' ,
width : '100px' ,
render : ( _v , row ) = > (
< Toggle
checked = { row . recording }
onChange = { ( ) = > {
if ( editing ) updateRouteRecording ( row . routeId , ! row . recording ) ;
} }
disabled = { ! editing }
/ >
) ,
} ,
] , [ editing , routeRecordingDraft ] ) ;
// ── Render ─────────────────────────────────────────────────────────────────
2026-03-26 16:15:27 +01:00
if ( isLoading ) {
return < div className = { styles . loading } > < Spinner size = "lg" / > < / div > ;
}
if ( ! config || ! form ) {
return < div className = { styles . page } > No configuration found for & quot ; { appId } & quot ; . < / div > ;
}
return (
< div className = { styles . page } >
< div className = { styles . toolbar } >
2026-04-09 15:22:14 +02:00
< Button variant = "ghost" size = "sm" onClick = { ( ) = > navigate ( '/admin/appconfig' ) } > < ArrowLeft size = { 14 } / > Back < / Button >
2026-03-26 16:15:27 +01:00
{ editing ? (
< div className = { styles . toolbarActions } >
2026-04-09 18:39:22 +02:00
< Button variant = "ghost" size = "sm" onClick = { cancelEditing } > Cancel < / Button >
< Button variant = "primary" size = "sm" onClick = { handleSave } loading = { updateConfig . isPending } > Save < / Button >
2026-03-26 16:15:27 +01:00
< / div >
) : (
2026-04-09 15:22:14 +02:00
< Button variant = "secondary" size = "sm" onClick = { startEditing } > < Pencil size = { 14 } / > Edit < / Button >
2026-03-26 16:15:27 +01:00
) }
< / div >
2026-04-09 18:47:55 +02:00
{ editing && (
< div className = { styles . editBanner } >
Editing configuration . Changes are not saved until you click Save .
< / div >
) }
2026-03-26 16:15:27 +01:00
< div className = { styles . header } >
< h2 className = { styles . title } > < MonoText size = "md" > { appId } < / MonoText > < / h2 >
< div className = { styles . meta } >
Version < MonoText size = "xs" > { config . version } < / MonoText >
{ config . updatedAt && < > & middot ; Updated { formatTimestamp ( config . updatedAt ) } < / > }
< / div >
< / div >
2026-03-26 18:48:14 +01:00
{ /* ── Settings ──────────────────────────────────────────────────── */ }
refactor: UI consistency — shared CSS, design system colors, no inline styles
Phase 1: Extract 6 shared CSS modules (table-section, log-panel,
rate-colors, refresh-indicator, chart-card, section-card) eliminating
~135 duplicate class definitions across 11 files.
Phase 2: Replace all hardcoded hex colors in CSS modules with design
system variables. Strip ~55 hex fallbacks from var() patterns. Fix 4
undefined variable names (--accent, --bg-base, --surface, --bg-surface-raised).
Phase 3: Replace ~45 hardcoded hex values in ProcessDiagram SVG
components with var() CSS custom properties. Fix Dashboard.tsx color prop.
Phase 4: Create CSS modules for AdminLayout, DatabaseAdminPage,
OidcCallback (previously 100% inline). Extract shared PageLoader
component (replaces 3 copy-pasted spinner patterns). Move AppsTab
static inline styles to CSS classes. Extract LayoutShell StarredList styles.
58 files changed, net -219 lines.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:55:54 +02:00
< div className = { sectionStyles . section } >
2026-03-26 18:48:14 +01:00
< SectionHeader > Settings < / SectionHeader >
< div className = { styles . settingsGrid } >
< div className = { styles . field } >
2026-04-09 15:22:14 +02:00
< Label > App Log Level < / Label >
2026-03-26 18:48:14 +01:00
{ editing ? (
2026-04-09 15:22:14 +02:00
< Select value = { String ( form . applicationLogLevel ) }
onChange = { ( e ) = > updateField ( 'applicationLogLevel' , e . target . value ) }
options = { [
{ value : 'ERROR' , label : 'ERROR' } ,
{ value : 'WARN' , label : 'WARN' } ,
{ value : 'INFO' , label : 'INFO' } ,
{ value : 'DEBUG' , label : 'DEBUG' } ,
{ value : 'TRACE' , label : 'TRACE' } ,
] }
/ >
2026-03-26 18:48:14 +01:00
) : (
2026-03-26 23:36:31 +01:00
< Badge label = { String ( form . applicationLogLevel ) } color = { logLevelColor ( form . applicationLogLevel as string ) } variant = "filled" / >
) }
< / div >
< div className = { styles . field } >
2026-04-09 15:22:14 +02:00
< Label > Agent Log Level < / Label >
2026-03-26 23:36:31 +01:00
{ editing ? (
2026-04-09 15:22:14 +02:00
< Select value = { String ( form . agentLogLevel ? ? 'INFO' ) }
onChange = { ( e ) = > updateField ( 'agentLogLevel' , e . target . value ) }
options = { [
{ value : 'ERROR' , label : 'ERROR' } ,
{ value : 'WARN' , label : 'WARN' } ,
{ value : 'INFO' , label : 'INFO' } ,
{ value : 'DEBUG' , label : 'DEBUG' } ,
{ value : 'TRACE' , label : 'TRACE' } ,
] }
/ >
2026-03-26 23:36:31 +01:00
) : (
< Badge label = { String ( form . agentLogLevel ? ? 'INFO' ) } color = { logLevelColor ( form . agentLogLevel as string ) } variant = "filled" / >
2026-03-26 18:48:14 +01:00
) }
< / div >
< div className = { styles . field } >
2026-04-09 15:22:14 +02:00
< Label > Engine Level < / Label >
2026-03-26 18:48:14 +01:00
{ editing ? (
2026-04-09 15:22:14 +02:00
< Select value = { String ( form . engineLevel ) }
onChange = { ( e ) = > updateField ( 'engineLevel' , e . target . value ) }
options = { [
{ value : 'NONE' , label : 'None' } ,
{ value : 'MINIMAL' , label : 'Minimal' } ,
{ value : 'REGULAR' , label : 'Regular' } ,
{ value : 'COMPLETE' , label : 'Complete' } ,
] }
/ >
2026-03-26 18:48:14 +01:00
) : (
< Badge label = { String ( form . engineLevel ) } color = { engineLevelColor ( form . engineLevel as string ) } variant = "filled" / >
) }
< / div >
< div className = { styles . field } >
2026-04-09 15:22:14 +02:00
< Label > Payload Capture < / Label >
2026-03-26 18:48:14 +01:00
{ editing ? (
2026-04-09 15:22:14 +02:00
< Select value = { String ( form . payloadCaptureMode ) }
onChange = { ( e ) = > updateField ( 'payloadCaptureMode' , e . target . value ) }
options = { [
{ value : 'NONE' , label : 'None' } ,
{ value : 'INPUT' , label : 'Input' } ,
{ value : 'OUTPUT' , label : 'Output' } ,
{ value : 'BOTH' , label : 'Both' } ,
] }
/ >
2026-03-26 18:48:14 +01:00
) : (
< Badge label = { String ( form . payloadCaptureMode ) } color = { payloadColor ( form . payloadCaptureMode as string ) } variant = "filled" / >
) }
< / div >
< div className = { styles . field } >
2026-04-09 15:22:14 +02:00
< Label > Metrics < / Label >
2026-03-26 18:48:14 +01:00
{ editing ? (
2026-04-09 15:22:14 +02:00
< Toggle
checked = { Boolean ( form . metricsEnabled ) }
onChange = { ( e ) = > updateField ( 'metricsEnabled' , ( e . target as HTMLInputElement ) . checked ) }
label = { form . metricsEnabled ? 'Enabled' : 'Disabled' }
/ >
2026-03-26 18:48:14 +01:00
) : (
< Badge label = { form . metricsEnabled ? 'On' : 'Off' } color = { form . metricsEnabled ? 'success' : 'error' } variant = "filled" / >
) }
< / div >
< div className = { styles . field } >
2026-04-09 15:22:14 +02:00
< Label > Sampling Rate < / Label >
2026-03-26 18:48:14 +01:00
{ editing ? (
2026-04-09 15:22:14 +02:00
< input type = "number" className = { styles . numberInput } min = { 0 } max = { 1 } step = { 0.01 }
2026-03-26 16:15:27 +01:00
value = { form . samplingRate ? ? 1.0 }
onChange = { ( e ) = > updateField ( 'samplingRate' , parseFloat ( e . target . value ) || 0 ) } / >
2026-03-26 18:48:14 +01:00
) : (
< MonoText size = "xs" > { form . samplingRate } < / MonoText >
) }
< / div >
< div className = { styles . field } >
2026-04-09 15:22:14 +02:00
< Label > Compress Success < / Label >
2026-03-26 18:48:14 +01:00
{ editing ? (
2026-04-09 15:22:14 +02:00
< Toggle
checked = { Boolean ( form . compressSuccess ) }
onChange = { ( e ) = > updateField ( 'compressSuccess' , ( e . target as HTMLInputElement ) . checked ) }
label = { form . compressSuccess ? 'On' : 'Off' }
/ >
2026-03-26 18:48:14 +01:00
) : (
< Badge label = { form . compressSuccess ? 'On' : 'Off' } color = { form . compressSuccess ? 'success' : 'error' } variant = "filled" / >
) }
< / div >
2026-03-26 16:15:27 +01:00
< / div >
< / div >
2026-03-26 18:48:14 +01:00
{ /* ── Traces & Taps ─────────────────────────────────────────────── */ }
refactor: UI consistency — shared CSS, design system colors, no inline styles
Phase 1: Extract 6 shared CSS modules (table-section, log-panel,
rate-colors, refresh-indicator, chart-card, section-card) eliminating
~135 duplicate class definitions across 11 files.
Phase 2: Replace all hardcoded hex colors in CSS modules with design
system variables. Strip ~55 hex fallbacks from var() patterns. Fix 4
undefined variable names (--accent, --bg-base, --surface, --bg-surface-raised).
Phase 3: Replace ~45 hardcoded hex values in ProcessDiagram SVG
components with var() CSS custom properties. Fix Dashboard.tsx color prop.
Phase 4: Create CSS modules for AdminLayout, DatabaseAdminPage,
OidcCallback (previously 100% inline). Extract shared PageLoader
component (replaces 3 copy-pasted spinner patterns). Move AppsTab
static inline styles to CSS classes. Extract LayoutShell StarredList styles.
58 files changed, net -219 lines.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:55:54 +02:00
< div className = { sectionStyles . section } >
2026-03-26 18:48:14 +01:00
< SectionHeader > Traces & amp ; Taps < / SectionHeader >
< span className = { styles . sectionSummary } >
{ tracedCount } traced & middot ; { tapCount } taps & middot ; manage taps on route pages
< / span >
{ tracedTapRows . length > 0 ? (
< DataTable < TracedTapRow > columns = { tracedTapColumns } data = { tracedTapRows } pageSize = { 20 } / >
) : (
< span className = { styles . hint } >
No processors are individually traced and no taps are defined .
{ ! editing && ' Enable tracing per-processor on the exchange detail page, or add taps on route pages.' }
< / span >
) }
< / div >
{ /* ── Route Recording ───────────────────────────────────────────── */ }
refactor: UI consistency — shared CSS, design system colors, no inline styles
Phase 1: Extract 6 shared CSS modules (table-section, log-panel,
rate-colors, refresh-indicator, chart-card, section-card) eliminating
~135 duplicate class definitions across 11 files.
Phase 2: Replace all hardcoded hex colors in CSS modules with design
system variables. Strip ~55 hex fallbacks from var() patterns. Fix 4
undefined variable names (--accent, --bg-base, --surface, --bg-surface-raised).
Phase 3: Replace ~45 hardcoded hex values in ProcessDiagram SVG
components with var() CSS custom properties. Fix Dashboard.tsx color prop.
Phase 4: Create CSS modules for AdminLayout, DatabaseAdminPage,
OidcCallback (previously 100% inline). Extract shared PageLoader
component (replaces 3 copy-pasted spinner patterns). Move AppsTab
static inline styles to CSS classes. Extract LayoutShell StarredList styles.
58 files changed, net -219 lines.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:55:54 +02:00
< div className = { sectionStyles . section } >
2026-03-26 18:48:14 +01:00
< SectionHeader > Route Recording < / SectionHeader >
< span className = { styles . sectionSummary } >
{ recordingCount } of { routeRecordingRows . length } routes recording
< / span >
{ routeRecordingRows . length > 0 ? (
< DataTable < RouteRecordingRow > columns = { routeRecordingColumns } data = { routeRecordingRows } pageSize = { 20 } / >
2026-03-26 16:15:27 +01:00
) : (
< span className = { styles . hint } >
2026-03-26 18:48:14 +01:00
No routes found for this application . Routes appear once agents report data .
2026-03-26 16:15:27 +01:00
< / span >
) }
< / div >
< / div >
) ;
}