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:
@@ -90,15 +90,6 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chartCard {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-card);
|
||||
padding: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chartHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -125,121 +116,6 @@
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
/* Log viewer */
|
||||
.logCard {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-card);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 420px;
|
||||
}
|
||||
|
||||
.logHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.headerActions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.sortBtn,
|
||||
.refreshBtn {
|
||||
background: none;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
padding: 2px 6px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.sortBtn:hover,
|
||||
.refreshBtn:hover {
|
||||
color: var(--text-primary);
|
||||
border-color: var(--amber);
|
||||
}
|
||||
|
||||
.logToolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
background: var(--bg-surface);
|
||||
}
|
||||
|
||||
.logSearchWrap {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.logSearchInput {
|
||||
width: 100%;
|
||||
padding: 5px 28px 5px 10px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-body);
|
||||
color: var(--text-primary);
|
||||
font-size: 12px;
|
||||
font-family: var(--font-body);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.logSearchInput:focus {
|
||||
border-color: var(--amber);
|
||||
}
|
||||
|
||||
.logSearchInput::placeholder {
|
||||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
.logSearchClear {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
padding: 0 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.logClearFilters {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
padding: 2px 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.logClearFilters:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Empty state (shared) */
|
||||
.logEmpty {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: var(--text-faint);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Timeline card */
|
||||
.timelineCard {
|
||||
background: var(--bg-surface);
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
} from '@cameleer/design-system';
|
||||
import type { FeedEvent, LogEntry, ButtonGroupItem } from '@cameleer/design-system';
|
||||
import styles from './AgentInstance.module.css';
|
||||
import logStyles from '../../styles/log-panel.module.css';
|
||||
import chartCardStyles from '../../styles/chart-card.module.css';
|
||||
import { useAgents, useAgentEvents } from '../../api/queries/agents';
|
||||
import { useApplicationLogs } from '../../api/queries/logs';
|
||||
import { useStatsTimeseries } from '../../api/queries/executions';
|
||||
@@ -290,7 +292,7 @@ export default function AgentInstance() {
|
||||
|
||||
{/* Charts grid — 3x2 */}
|
||||
<div className={styles.chartsGrid}>
|
||||
<div className={styles.chartCard}>
|
||||
<div className={chartCardStyles.chartCard}>
|
||||
<div className={styles.chartHeader}>
|
||||
<span className={styles.chartTitle}>CPU Usage</span>
|
||||
<span className={styles.chartMeta}>
|
||||
@@ -309,7 +311,7 @@ export default function AgentInstance() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.chartCard}>
|
||||
<div className={chartCardStyles.chartCard}>
|
||||
<div className={styles.chartHeader}>
|
||||
<span className={styles.chartTitle}>Memory (Heap)</span>
|
||||
<span className={styles.chartMeta}>
|
||||
@@ -325,7 +327,7 @@ export default function AgentInstance() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.chartCard}>
|
||||
<div className={chartCardStyles.chartCard}>
|
||||
<div className={styles.chartHeader}>
|
||||
<span className={styles.chartTitle}>Throughput</span>
|
||||
<span className={styles.chartMeta}>
|
||||
@@ -339,7 +341,7 @@ export default function AgentInstance() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.chartCard}>
|
||||
<div className={chartCardStyles.chartCard}>
|
||||
<div className={styles.chartHeader}>
|
||||
<span className={styles.chartTitle}>Error Rate</span>
|
||||
<span className={styles.chartMeta}>
|
||||
@@ -353,7 +355,7 @@ export default function AgentInstance() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.chartCard}>
|
||||
<div className={chartCardStyles.chartCard}>
|
||||
<div className={styles.chartHeader}>
|
||||
<span className={styles.chartTitle}>Thread Count</span>
|
||||
<span className={styles.chartMeta}>
|
||||
@@ -369,7 +371,7 @@ export default function AgentInstance() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.chartCard}>
|
||||
<div className={chartCardStyles.chartCard}>
|
||||
<div className={styles.chartHeader}>
|
||||
<span className={styles.chartTitle}>GC Pauses</span>
|
||||
<span className={styles.chartMeta} />
|
||||
@@ -384,24 +386,24 @@ export default function AgentInstance() {
|
||||
|
||||
{/* Log + Timeline side by side */}
|
||||
<div className={styles.bottomRow}>
|
||||
<div className={styles.logCard}>
|
||||
<div className={styles.logHeader}>
|
||||
<div className={logStyles.logCard}>
|
||||
<div className={logStyles.logHeader}>
|
||||
<SectionHeader>Application Log</SectionHeader>
|
||||
<div className={styles.headerActions}>
|
||||
<div className={logStyles.headerActions}>
|
||||
<span className={styles.chartMeta}>{logEntries.length} entries</span>
|
||||
<button className={styles.sortBtn} onClick={() => setLogSortAsc((v) => !v)} title={logSortAsc ? 'Oldest first' : 'Newest first'}>
|
||||
<button className={logStyles.sortBtn} onClick={() => setLogSortAsc((v) => !v)} title={logSortAsc ? 'Oldest first' : 'Newest first'}>
|
||||
{logSortAsc ? '\u2191' : '\u2193'}
|
||||
</button>
|
||||
<button className={styles.refreshBtn} onClick={() => setLogRefreshTo(new Date().toISOString())} title="Refresh">
|
||||
<button className={logStyles.refreshBtn} onClick={() => setLogRefreshTo(new Date().toISOString())} title="Refresh">
|
||||
<RefreshCw size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.logToolbar}>
|
||||
<div className={styles.logSearchWrap}>
|
||||
<div className={logStyles.logToolbar}>
|
||||
<div className={logStyles.logSearchWrap}>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.logSearchInput}
|
||||
className={logStyles.logSearchInput}
|
||||
placeholder="Search logs\u2026"
|
||||
value={logSearch}
|
||||
onChange={(e) => setLogSearch(e.target.value)}
|
||||
@@ -410,7 +412,7 @@ export default function AgentInstance() {
|
||||
{logSearch && (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.logSearchClear}
|
||||
className={logStyles.logSearchClear}
|
||||
onClick={() => setLogSearch('')}
|
||||
aria-label="Clear search"
|
||||
>
|
||||
@@ -420,7 +422,7 @@ export default function AgentInstance() {
|
||||
</div>
|
||||
<ButtonGroup items={LOG_LEVEL_ITEMS} value={logLevels} onChange={setLogLevels} />
|
||||
{logLevels.size > 0 && (
|
||||
<button className={styles.logClearFilters} onClick={() => setLogLevels(new Set())}>
|
||||
<button className={logStyles.logClearFilters} onClick={() => setLogLevels(new Set())}>
|
||||
Clear
|
||||
</button>
|
||||
)}
|
||||
@@ -428,7 +430,7 @@ export default function AgentInstance() {
|
||||
{filteredLogs.length > 0 ? (
|
||||
<LogViewer entries={filteredLogs} maxHeight={360} />
|
||||
) : (
|
||||
<div className={styles.logEmpty}>
|
||||
<div className={logStyles.logEmpty}>
|
||||
{logSearch || logLevels.size > 0 ? 'No matching log entries' : 'No log entries available'}
|
||||
</div>
|
||||
)}
|
||||
@@ -437,12 +439,12 @@ export default function AgentInstance() {
|
||||
<div className={styles.timelineCard}>
|
||||
<div className={styles.timelineHeader}>
|
||||
<span className={styles.chartTitle}>Timeline</span>
|
||||
<div className={styles.headerActions}>
|
||||
<div className={logStyles.headerActions}>
|
||||
<span className={styles.chartMeta}>{feedEvents.length} events</span>
|
||||
<button className={styles.sortBtn} onClick={() => setEventSortAsc((v) => !v)} title={eventSortAsc ? 'Oldest first' : 'Newest first'}>
|
||||
<button className={logStyles.sortBtn} onClick={() => setEventSortAsc((v) => !v)} title={eventSortAsc ? 'Oldest first' : 'Newest first'}>
|
||||
{eventSortAsc ? '\u2191' : '\u2193'}
|
||||
</button>
|
||||
<button className={styles.refreshBtn} onClick={() => setEventRefreshTo(new Date().toISOString())} title="Refresh">
|
||||
<button className={logStyles.refreshBtn} onClick={() => setEventRefreshTo(new Date().toISOString())} title="Refresh">
|
||||
<RefreshCw size={14} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -450,7 +452,7 @@ export default function AgentInstance() {
|
||||
{feedEvents.length > 0 ? (
|
||||
<EventFeed events={feedEvents} maxItems={50} />
|
||||
) : (
|
||||
<div className={styles.logEmpty}>No events in the selected time range.</div>
|
||||
<div className={logStyles.logEmpty}>No events in the selected time range.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user