style: add CSS modules to all pages matching design system mock layouts
All checks were successful
CI / build (push) Successful in 1m18s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 50s
CI / deploy (push) Successful in 50s
CI / deploy-feature (push) Has been skipped

Replace inline styles with semantic CSS module classes for proper visual
structure: card wrappers with borders/shadows, grid layouts for stat
strips and charts, section headers, and typography classes.

Pages updated: Dashboard, ExchangeDetail, RoutesMetrics, AgentHealth,
AgentInstance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-19 18:16:16 +01:00
parent f2744e3094
commit 4ff01681d4
10 changed files with 638 additions and 123 deletions

View File

@@ -0,0 +1,143 @@
.exchangeHeader {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
padding: 16px 20px;
margin-bottom: 14px;
}
.headerRow {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
}
.headerLeft {
display: flex;
align-items: flex-start;
gap: 12px;
flex: 1;
}
.headerRight {
display: flex;
gap: 20px;
flex-shrink: 0;
}
.headerStat {
text-align: center;
}
.headerStatLabel {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.6px;
color: var(--text-muted);
margin-bottom: 2px;
}
.headerStatValue {
font-size: 14px;
font-weight: 600;
font-family: var(--font-mono);
color: var(--text-primary);
}
.timelineSection {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
margin-bottom: 16px;
overflow: hidden;
}
.timelineHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--border-subtle);
}
.timelineTitle {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.timelineBody {
padding: 12px 16px;
}
.detailSplit {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 16px;
}
.detailPanel {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
overflow: hidden;
}
.panelHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
border-bottom: 1px solid var(--border-subtle);
background: var(--bg-raised);
}
.panelTitle {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.panelBody {
padding: 16px;
}
.headerKvRow {
display: grid;
grid-template-columns: 140px 1fr;
padding: 4px 0;
border-bottom: 1px solid var(--border-subtle);
font-size: 11px;
}
.headerKvRow:last-child {
border-bottom: none;
}
.headerKey {
font-family: var(--font-mono);
font-weight: 600;
color: var(--text-muted);
}
.headerValue {
font-family: var(--font-mono);
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
}
.sectionLabel {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.6px;
color: var(--text-muted);
margin-bottom: 6px;
}

View File

@@ -1,10 +1,11 @@
import { useState, useMemo } from 'react';
import { useParams, useNavigate } from 'react-router';
import {
Card, Badge, StatusDot, MonoText, CodeBlock, InfoCallout,
Badge, StatusDot, MonoText, CodeBlock, InfoCallout,
ProcessorTimeline, Breadcrumb, Spinner,
} from '@cameleer/design-system';
import { useExecutionDetail, useProcessorSnapshot } from '../../api/queries/executions';
import styles from './ExchangeDetail.module.css';
export default function ExchangeDetail() {
const { id } = useParams();
@@ -43,88 +44,96 @@ export default function ExchangeDetail() {
{ label: id?.slice(0, 12) || '' },
]} />
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '1rem', margin: '1.5rem 0' }}>
<Card>
<div style={{ padding: '1rem' }}>
<div style={{ fontSize: '0.75rem', color: 'var(--text-tertiary)' }}>Status</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginTop: '0.25rem' }}>
<StatusDot variant={detail.status === 'COMPLETED' ? 'success' : detail.status === 'FAILED' ? 'error' : 'running'} />
<div className={styles.exchangeHeader}>
<div className={styles.headerRow}>
<div className={styles.headerLeft}>
<StatusDot variant={detail.status === 'COMPLETED' ? 'success' : detail.status === 'FAILED' ? 'error' : 'running'} />
<div>
<Badge label={detail.status} color={detail.status === 'COMPLETED' ? 'success' : 'error'} />
<MonoText>{id}</MonoText>
</div>
</div>
</Card>
<Card>
<div style={{ padding: '1rem' }}>
<div style={{ fontSize: '0.75rem', color: 'var(--text-tertiary)' }}>Duration</div>
<div style={{ fontSize: '1.25rem', fontWeight: 600 }}>{detail.durationMs}ms</div>
<div className={styles.headerRight}>
<div className={styles.headerStat}>
<div className={styles.headerStatLabel}>Duration</div>
<div className={styles.headerStatValue}>{detail.durationMs}ms</div>
</div>
<div className={styles.headerStat}>
<div className={styles.headerStatLabel}>Route</div>
<div className={styles.headerStatValue}>{detail.routeId}</div>
</div>
<div className={styles.headerStat}>
<div className={styles.headerStatLabel}>Application</div>
<div className={styles.headerStatValue}>{detail.groupName || 'unknown'}</div>
</div>
</div>
</Card>
<Card>
<div style={{ padding: '1rem' }}>
<div style={{ fontSize: '0.75rem', color: 'var(--text-tertiary)' }}>Route</div>
<MonoText>{detail.routeId}</MonoText>
</div>
</Card>
<Card>
<div style={{ padding: '1rem' }}>
<div style={{ fontSize: '0.75rem', color: 'var(--text-tertiary)' }}>Application</div>
<Badge label={detail.groupName || 'unknown'} color="auto" />
</div>
</Card>
</div>
</div>
{detail.errorMessage && (
<div style={{ marginBottom: '1.5rem' }}>
<InfoCallout variant="error">
{detail.errorMessage}
</InfoCallout>
</div>
<InfoCallout variant="error">
{detail.errorMessage}
</InfoCallout>
)}
<h3 style={{ marginBottom: '0.75rem' }}>Processor Timeline</h3>
{processors.length > 0 ? (
<ProcessorTimeline
processors={processors}
totalMs={detail.durationMs}
onProcessorClick={(_p, i) => setSelectedProcessor(i)}
selectedIndex={selectedProcessor ?? undefined}
/>
) : (
<InfoCallout>No processor data available</InfoCallout>
)}
<div className={styles.timelineSection}>
<div className={styles.timelineHeader}>
<span className={styles.timelineTitle}>Processor Timeline</span>
</div>
<div className={styles.timelineBody}>
{processors.length > 0 ? (
<ProcessorTimeline
processors={processors}
totalMs={detail.durationMs}
onProcessorClick={(_p, i) => setSelectedProcessor(i)}
selectedIndex={selectedProcessor ?? undefined}
/>
) : (
<InfoCallout>No processor data available</InfoCallout>
)}
</div>
</div>
{snapshot && (
<div style={{ marginTop: '1.5rem' }}>
<h3 style={{ marginBottom: '0.75rem' }}>Exchange Snapshot</h3>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
<Card>
<div style={{ padding: '1rem' }}>
<h4 style={{ marginBottom: '0.5rem' }}>Input Body</h4>
<>
<div className={styles.sectionLabel}>Exchange Snapshot</div>
<div className={styles.detailSplit}>
<div className={styles.detailPanel}>
<div className={styles.panelHeader}>
<span className={styles.panelTitle}>Input Body</span>
</div>
<div className={styles.panelBody}>
<CodeBlock content={String(snapshot.inputBody ?? 'null')} />
</div>
</Card>
<Card>
<div style={{ padding: '1rem' }}>
<h4 style={{ marginBottom: '0.5rem' }}>Output Body</h4>
</div>
<div className={styles.detailPanel}>
<div className={styles.panelHeader}>
<span className={styles.panelTitle}>Output Body</span>
</div>
<div className={styles.panelBody}>
<CodeBlock content={String(snapshot.outputBody ?? 'null')} />
</div>
</Card>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem', marginTop: '1rem' }}>
<Card>
<div style={{ padding: '1rem' }}>
<h4 style={{ marginBottom: '0.5rem' }}>Input Headers</h4>
<div className={styles.detailSplit}>
<div className={styles.detailPanel}>
<div className={styles.panelHeader}>
<span className={styles.panelTitle}>Input Headers</span>
</div>
<div className={styles.panelBody}>
<CodeBlock content={JSON.stringify(snapshot.inputHeaders ?? {}, null, 2)} />
</div>
</Card>
<Card>
<div style={{ padding: '1rem' }}>
<h4 style={{ marginBottom: '0.5rem' }}>Output Headers</h4>
</div>
<div className={styles.detailPanel}>
<div className={styles.panelHeader}>
<span className={styles.panelTitle}>Output Headers</span>
</div>
<div className={styles.panelBody}>
<CodeBlock content={JSON.stringify(snapshot.outputHeaders ?? {}, null, 2)} />
</div>
</Card>
</div>
</div>
</div>
</>
)}
</div>
);