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>
This commit is contained in:
@@ -29,6 +29,9 @@ import {
|
||||
type HealthStatus,
|
||||
} from './dashboard-utils';
|
||||
import styles from './DashboardTab.module.css';
|
||||
import tableStyles from '../../styles/table-section.module.css';
|
||||
import refreshStyles from '../../styles/refresh-indicator.module.css';
|
||||
import rateStyles from '../../styles/rate-colors.module.css';
|
||||
|
||||
// ── Row type for application health table ───────────────────────────────────
|
||||
|
||||
@@ -75,7 +78,7 @@ const APP_COLUMNS: Column<AppRow>[] = [
|
||||
sortable: true,
|
||||
render: (_, row) => {
|
||||
const pct = row.successRate;
|
||||
const cls = pct >= 99 ? styles.rateGood : pct >= 97 ? styles.rateWarn : styles.rateBad;
|
||||
const cls = pct >= 99 ? rateStyles.rateGood : pct >= 97 ? rateStyles.rateWarn : rateStyles.rateBad;
|
||||
return <MonoText size="sm" className={cls}>{pct.toFixed(1)}%</MonoText>;
|
||||
},
|
||||
},
|
||||
@@ -84,7 +87,7 @@ const APP_COLUMNS: Column<AppRow>[] = [
|
||||
header: 'P99',
|
||||
sortable: true,
|
||||
render: (_, row) => {
|
||||
const cls = row.p99DurationMs > 300 ? styles.rateBad : row.p99DurationMs > 200 ? styles.rateWarn : styles.rateGood;
|
||||
const cls = row.p99DurationMs > 300 ? rateStyles.rateBad : row.p99DurationMs > 200 ? rateStyles.rateWarn : rateStyles.rateGood;
|
||||
return <MonoText size="sm" className={cls}>{Math.round(row.p99DurationMs)}ms</MonoText>;
|
||||
},
|
||||
},
|
||||
@@ -93,7 +96,7 @@ const APP_COLUMNS: Column<AppRow>[] = [
|
||||
header: 'SLA %',
|
||||
sortable: true,
|
||||
render: (_, row) => {
|
||||
const cls = row.slaCompliance >= 99 ? styles.rateGood : row.slaCompliance >= 95 ? styles.rateWarn : styles.rateBad;
|
||||
const cls = row.slaCompliance >= 99 ? rateStyles.rateGood : row.slaCompliance >= 95 ? rateStyles.rateWarn : rateStyles.rateBad;
|
||||
return <MonoText size="sm" className={cls}>{formatSlaCompliance(row.slaCompliance)}</MonoText>;
|
||||
},
|
||||
},
|
||||
@@ -102,7 +105,7 @@ const APP_COLUMNS: Column<AppRow>[] = [
|
||||
header: 'Errors',
|
||||
sortable: true,
|
||||
render: (_, row) => {
|
||||
const cls = row.errorCount > 10 ? styles.rateBad : row.errorCount > 0 ? styles.rateWarn : styles.rateGood;
|
||||
const cls = row.errorCount > 10 ? rateStyles.rateBad : row.errorCount > 0 ? rateStyles.rateWarn : rateStyles.rateGood;
|
||||
return <MonoText size="sm" className={cls}>{row.errorCount.toLocaleString()}</MonoText>;
|
||||
},
|
||||
},
|
||||
@@ -399,20 +402,20 @@ export default function DashboardL1() {
|
||||
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.refreshIndicator}>
|
||||
<span className={styles.refreshDot} />
|
||||
<span className={styles.refreshText}>Auto-refresh: 30s</span>
|
||||
<div className={refreshStyles.refreshIndicator}>
|
||||
<span className={refreshStyles.refreshDot} />
|
||||
<span className={refreshStyles.refreshText}>Auto-refresh: 30s</span>
|
||||
</div>
|
||||
|
||||
{/* KPI header cards */}
|
||||
<KpiStrip items={kpiItems} />
|
||||
|
||||
{/* Application Health table */}
|
||||
<div className={styles.tableSection}>
|
||||
<div className={styles.tableHeader}>
|
||||
<span className={styles.tableTitle}>Application Health</span>
|
||||
<div className={styles.tableRight}>
|
||||
<span className={styles.tableMeta}>{appRows.length} applications</span>
|
||||
<div className={tableStyles.tableSection}>
|
||||
<div className={tableStyles.tableHeader}>
|
||||
<span className={tableStyles.tableTitle}>Application Health</span>
|
||||
<div className={tableStyles.tableRight}>
|
||||
<span className={tableStyles.tableMeta}>{appRows.length} applications</span>
|
||||
<Badge label="ALL" color="auto" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,9 @@ import {
|
||||
formatRelativeTime,
|
||||
} from './dashboard-utils';
|
||||
import styles from './DashboardTab.module.css';
|
||||
import tableStyles from '../../styles/table-section.module.css';
|
||||
import refreshStyles from '../../styles/refresh-indicator.module.css';
|
||||
import rateStyles from '../../styles/rate-colors.module.css';
|
||||
|
||||
// ── Route table row type ────────────────────────────────────────────────────
|
||||
|
||||
@@ -72,7 +75,7 @@ const ROUTE_COLUMNS: Column<RouteRow>[] = [
|
||||
sortable: true,
|
||||
render: (_, row) => {
|
||||
const pct = row.successRate * 100;
|
||||
const cls = pct >= 99 ? styles.rateGood : pct >= 97 ? styles.rateWarn : styles.rateBad;
|
||||
const cls = pct >= 99 ? rateStyles.rateGood : pct >= 97 ? rateStyles.rateWarn : rateStyles.rateBad;
|
||||
return <MonoText size="sm" className={cls}>{pct.toFixed(1)}%</MonoText>;
|
||||
},
|
||||
},
|
||||
@@ -89,7 +92,7 @@ const ROUTE_COLUMNS: Column<RouteRow>[] = [
|
||||
header: 'P99(ms)',
|
||||
sortable: true,
|
||||
render: (_, row) => {
|
||||
const cls = row.p99DurationMs > 300 ? styles.rateBad : row.p99DurationMs > 200 ? styles.rateWarn : styles.rateGood;
|
||||
const cls = row.p99DurationMs > 300 ? rateStyles.rateBad : row.p99DurationMs > 200 ? rateStyles.rateWarn : rateStyles.rateGood;
|
||||
return <MonoText size="sm" className={cls}>{Math.round(row.p99DurationMs)}</MonoText>;
|
||||
},
|
||||
},
|
||||
@@ -98,7 +101,7 @@ const ROUTE_COLUMNS: Column<RouteRow>[] = [
|
||||
header: 'SLA%',
|
||||
sortable: true,
|
||||
render: (_, row) => {
|
||||
const cls = row.slaCompliance >= 99 ? styles.rateGood : row.slaCompliance >= 95 ? styles.rateWarn : styles.rateBad;
|
||||
const cls = row.slaCompliance >= 99 ? rateStyles.rateGood : row.slaCompliance >= 95 ? rateStyles.rateWarn : rateStyles.rateBad;
|
||||
return <MonoText size="sm" className={cls}>{formatSlaCompliance(row.slaCompliance)}</MonoText>;
|
||||
},
|
||||
},
|
||||
@@ -146,9 +149,9 @@ const ERROR_COLUMNS: Column<ErrorRow>[] = [
|
||||
sortable: true,
|
||||
render: (_, row) => {
|
||||
const arrow = trendArrow(row.trend);
|
||||
const cls = row.trend === 'accelerating' ? styles.rateBad
|
||||
: row.trend === 'decelerating' ? styles.rateGood
|
||||
: styles.rateNeutral;
|
||||
const cls = row.trend === 'accelerating' ? rateStyles.rateBad
|
||||
: row.trend === 'decelerating' ? rateStyles.rateGood
|
||||
: rateStyles.rateNeutral;
|
||||
return <MonoText size="sm" className={cls}>{row.velocity.toFixed(1)}/min {arrow}</MonoText>;
|
||||
},
|
||||
},
|
||||
@@ -364,20 +367,20 @@ export default function DashboardL2() {
|
||||
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.refreshIndicator}>
|
||||
<span className={styles.refreshDot} />
|
||||
<span className={styles.refreshText}>Auto-refresh: 30s</span>
|
||||
<div className={refreshStyles.refreshIndicator}>
|
||||
<span className={refreshStyles.refreshDot} />
|
||||
<span className={refreshStyles.refreshText}>Auto-refresh: 30s</span>
|
||||
</div>
|
||||
|
||||
{/* KPI Strip */}
|
||||
<KpiStrip items={kpiItems} />
|
||||
|
||||
{/* Route Performance Table */}
|
||||
<div className={styles.tableSection}>
|
||||
<div className={styles.tableHeader}>
|
||||
<span className={styles.tableTitle}>Route Performance</span>
|
||||
<div className={styles.tableRight}>
|
||||
<span className={styles.tableMeta}>{routeRows.length} routes</span>
|
||||
<div className={tableStyles.tableSection}>
|
||||
<div className={tableStyles.tableHeader}>
|
||||
<span className={tableStyles.tableTitle}>Route Performance</span>
|
||||
<div className={tableStyles.tableRight}>
|
||||
<span className={tableStyles.tableMeta}>{routeRows.length} routes</span>
|
||||
<Badge label="AUTO" color="success" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -416,9 +419,9 @@ export default function DashboardL2() {
|
||||
{/* Top 5 Errors — hidden when empty */}
|
||||
{errorRows.length > 0 && (
|
||||
<div className={styles.errorsSection}>
|
||||
<div className={styles.tableHeader}>
|
||||
<span className={styles.tableTitle}>Top Errors</span>
|
||||
<span className={styles.tableMeta}>{errorRows.length} error types</span>
|
||||
<div className={tableStyles.tableHeader}>
|
||||
<span className={tableStyles.tableTitle}>Top Errors</span>
|
||||
<span className={tableStyles.tableMeta}>{errorRows.length} error types</span>
|
||||
</div>
|
||||
<DataTable
|
||||
columns={ERROR_COLUMNS}
|
||||
|
||||
@@ -25,6 +25,9 @@ import {
|
||||
trendIndicator,
|
||||
} from './dashboard-utils';
|
||||
import styles from './DashboardTab.module.css';
|
||||
import tableStyles from '../../styles/table-section.module.css';
|
||||
import refreshStyles from '../../styles/refresh-indicator.module.css';
|
||||
import rateStyles from '../../styles/rate-colors.module.css';
|
||||
|
||||
// ── Row types ───────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -80,10 +83,10 @@ const PROCESSOR_COLUMNS: Column<ProcessorRow>[] = [
|
||||
sortable: true,
|
||||
render: (_, row) => {
|
||||
const cls = row.p99DurationMs > 300
|
||||
? styles.rateBad
|
||||
? rateStyles.rateBad
|
||||
: row.p99DurationMs > 200
|
||||
? styles.rateWarn
|
||||
: styles.rateGood;
|
||||
? rateStyles.rateWarn
|
||||
: rateStyles.rateGood;
|
||||
return <MonoText size="sm" className={cls}>{Math.round(row.p99DurationMs)}</MonoText>;
|
||||
},
|
||||
},
|
||||
@@ -93,7 +96,7 @@ const PROCESSOR_COLUMNS: Column<ProcessorRow>[] = [
|
||||
sortable: true,
|
||||
render: (_, row) => {
|
||||
const pct = row.errorRate * 100;
|
||||
const cls = pct > 5 ? styles.rateBad : pct > 1 ? styles.rateWarn : styles.rateGood;
|
||||
const cls = pct > 5 ? rateStyles.rateBad : pct > 1 ? rateStyles.rateWarn : rateStyles.rateGood;
|
||||
return <MonoText size="sm" className={cls}>{pct.toFixed(2)}%</MonoText>;
|
||||
},
|
||||
},
|
||||
@@ -347,9 +350,9 @@ export default function DashboardL3() {
|
||||
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.refreshIndicator}>
|
||||
<span className={styles.refreshDot} />
|
||||
<span className={styles.refreshText}>Auto-refresh: 30s</span>
|
||||
<div className={refreshStyles.refreshIndicator}>
|
||||
<span className={refreshStyles.refreshDot} />
|
||||
<span className={refreshStyles.refreshText}>Auto-refresh: 30s</span>
|
||||
</div>
|
||||
|
||||
{/* KPI Strip */}
|
||||
@@ -398,11 +401,11 @@ export default function DashboardL3() {
|
||||
)}
|
||||
|
||||
{/* Processor Metrics Table */}
|
||||
<div className={styles.tableSection}>
|
||||
<div className={styles.tableHeader}>
|
||||
<span className={styles.tableTitle}>Processor Metrics</span>
|
||||
<div className={tableStyles.tableSection}>
|
||||
<div className={tableStyles.tableHeader}>
|
||||
<span className={tableStyles.tableTitle}>Processor Metrics</span>
|
||||
<div>
|
||||
<span className={styles.tableMeta}>
|
||||
<span className={tableStyles.tableMeta}>
|
||||
{processorRows.length} processor{processorRows.length !== 1 ? 's' : ''}
|
||||
</span>
|
||||
</div>
|
||||
@@ -417,8 +420,8 @@ export default function DashboardL3() {
|
||||
{/* Top 5 Errors — hidden if empty */}
|
||||
{errorRows.length > 0 && (
|
||||
<div className={styles.errorsSection}>
|
||||
<div className={styles.tableHeader}>
|
||||
<span className={styles.tableTitle}>Top 5 Errors</span>
|
||||
<div className={tableStyles.tableHeader}>
|
||||
<span className={tableStyles.tableTitle}>Top 5 Errors</span>
|
||||
<Badge label={`${errorRows.length}`} color="error" />
|
||||
</div>
|
||||
<DataTable
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { useParams } from 'react-router';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { Spinner } from '@cameleer/design-system';
|
||||
import { PageLoader } from '../../components/PageLoader';
|
||||
|
||||
const DashboardL1 = lazy(() => import('./DashboardL1'));
|
||||
const DashboardL2 = lazy(() => import('./DashboardL2'));
|
||||
const DashboardL3 = lazy(() => import('./DashboardL3'));
|
||||
|
||||
const Fallback = <div style={{ display: 'flex', justifyContent: 'center', padding: '4rem' }}><Spinner size="lg" /></div>;
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { appId, routeId } = useParams<{ appId?: string; routeId?: string }>();
|
||||
|
||||
if (routeId && appId) {
|
||||
return <Suspense fallback={Fallback}><DashboardL3 /></Suspense>;
|
||||
return <Suspense fallback={<PageLoader />}><DashboardL3 /></Suspense>;
|
||||
}
|
||||
if (appId) {
|
||||
return <Suspense fallback={Fallback}><DashboardL2 /></Suspense>;
|
||||
return <Suspense fallback={<PageLoader />}><DashboardL2 /></Suspense>;
|
||||
}
|
||||
return <Suspense fallback={Fallback}><DashboardL1 /></Suspense>;
|
||||
return <Suspense fallback={<PageLoader />}><DashboardL1 /></Suspense>;
|
||||
}
|
||||
|
||||
@@ -8,61 +8,6 @@
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.refreshIndicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.refreshDot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--success);
|
||||
box-shadow: 0 0 4px rgba(61, 124, 71, 0.5);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.refreshText {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.tableSection {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-card);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tableHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.tableTitle {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.tableMeta {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* Charts */
|
||||
.chartGrid {
|
||||
@@ -92,12 +37,6 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Rate coloring */
|
||||
.rateGood { color: var(--success); }
|
||||
.rateWarn { color: var(--warning); }
|
||||
.rateBad { color: var(--error); }
|
||||
.rateNeutral { color: var(--text-secondary); }
|
||||
|
||||
/* Diagram container */
|
||||
.diagramSection {
|
||||
background: var(--bg-surface);
|
||||
@@ -108,13 +47,6 @@
|
||||
height: 280px;
|
||||
}
|
||||
|
||||
/* Table right side (meta + badge) */
|
||||
.tableRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Chart fill */
|
||||
.chart {
|
||||
width: 100%;
|
||||
|
||||
Reference in New Issue
Block a user