refactor: replace raw HTML tables in AgentHealth with DataTable composite
Replace hand-rolled <table>/<thead>/<tbody> markup in the AgentHealth page with the existing DataTable composite, using column definitions with custom render functions for StatusDot, Badge, and MonoText cells. Uses flush prop for seamless GroupCard integration and pageSize=50 to avoid pagination. Removes unused table-specific CSS classes (.instanceTable, .instanceRow, .thStatus, .tdStatus, .instanceRowActive, .instanceCountBadge). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -96,16 +96,6 @@
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Instance count badge in group header */
|
|
||||||
.instanceCountBadge {
|
|
||||||
font-size: 11px;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
color: var(--text-muted);
|
|
||||||
background: var(--bg-inset);
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Group meta row */
|
/* Group meta row */
|
||||||
.groupMeta {
|
.groupMeta {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -138,62 +128,6 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Instance table */
|
|
||||||
.instanceTable {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.instanceTable thead th {
|
|
||||||
padding: 4px 12px;
|
|
||||||
font-size: 9px;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
color: var(--text-faint);
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid var(--border-subtle);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thStatus {
|
|
||||||
width: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tdStatus {
|
|
||||||
width: 12px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Instance row */
|
|
||||||
.instanceRow {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.instanceRow td {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-bottom: 1px solid var(--border-subtle);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.instanceRow:last-child td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.instanceRow:hover td {
|
|
||||||
background: var(--bg-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.instanceRowActive td {
|
|
||||||
background: var(--amber-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.instanceRowActive td:first-child {
|
|
||||||
box-shadow: inset 3px 0 0 var(--amber);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Instance fields */
|
/* Instance fields */
|
||||||
.instanceName {
|
.instanceName {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import { TopBar } from '../../design-system/layout/TopBar/TopBar'
|
|||||||
|
|
||||||
// Composites
|
// Composites
|
||||||
import { GroupCard } from '../../design-system/composites/GroupCard/GroupCard'
|
import { GroupCard } from '../../design-system/composites/GroupCard/GroupCard'
|
||||||
|
import { DataTable } from '../../design-system/composites/DataTable/DataTable'
|
||||||
import { LineChart } from '../../design-system/composites/LineChart/LineChart'
|
import { LineChart } from '../../design-system/composites/LineChart/LineChart'
|
||||||
import { EventFeed } from '../../design-system/composites/EventFeed/EventFeed'
|
import { EventFeed } from '../../design-system/composites/EventFeed/EventFeed'
|
||||||
import { DetailPanel } from '../../design-system/composites/DetailPanel/DetailPanel'
|
import { DetailPanel } from '../../design-system/composites/DetailPanel/DetailPanel'
|
||||||
|
import type { Column } from '../../design-system/composites/DataTable/types'
|
||||||
|
|
||||||
// Primitives
|
// Primitives
|
||||||
import { StatusDot } from '../../design-system/primitives/StatusDot/StatusDot'
|
import { StatusDot } from '../../design-system/primitives/StatusDot/StatusDot'
|
||||||
@@ -143,6 +145,72 @@ export function AgentHealth() {
|
|||||||
// Build trend data for selected instance
|
// Build trend data for selected instance
|
||||||
const trendData = selectedInstance ? buildTrendData(selectedInstance) : null
|
const trendData = selectedInstance ? buildTrendData(selectedInstance) : null
|
||||||
|
|
||||||
|
// Column definitions for the instance DataTable
|
||||||
|
const instanceColumns: Column<AgentHealthData>[] = useMemo(() => [
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
header: '',
|
||||||
|
width: '12px',
|
||||||
|
render: (_val, row) => (
|
||||||
|
<StatusDot variant={row.status === 'live' ? 'live' : row.status === 'stale' ? 'stale' : 'dead'} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'name',
|
||||||
|
header: 'Instance',
|
||||||
|
render: (_val, row) => (
|
||||||
|
<MonoText size="sm" className={styles.instanceName}>{row.name}</MonoText>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'state',
|
||||||
|
header: 'State',
|
||||||
|
render: (_val, row) => (
|
||||||
|
<Badge
|
||||||
|
label={row.status.toUpperCase()}
|
||||||
|
color={row.status === 'live' ? 'success' : row.status === 'stale' ? 'warning' : 'error'}
|
||||||
|
variant="filled"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'uptime',
|
||||||
|
header: 'Uptime',
|
||||||
|
render: (_val, row) => (
|
||||||
|
<MonoText size="xs" className={styles.instanceMeta}>{row.uptime}</MonoText>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tps',
|
||||||
|
header: 'TPS',
|
||||||
|
render: (_val, row) => (
|
||||||
|
<MonoText size="xs" className={styles.instanceMeta}>{row.tps.toFixed(1)}/s</MonoText>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'errorRate',
|
||||||
|
header: 'Errors',
|
||||||
|
render: (_val, row) => (
|
||||||
|
<MonoText size="xs" className={row.errorRate ? styles.instanceError : styles.instanceMeta}>
|
||||||
|
{row.errorRate ?? '0 err/h'}
|
||||||
|
</MonoText>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'lastSeen',
|
||||||
|
header: 'Heartbeat',
|
||||||
|
render: (_val, row) => (
|
||||||
|
<MonoText size="xs" className={
|
||||||
|
row.status === 'dead' ? styles.instanceHeartbeatDead :
|
||||||
|
row.status === 'stale' ? styles.instanceHeartbeatStale :
|
||||||
|
styles.instanceMeta
|
||||||
|
}>
|
||||||
|
{row.lastSeen}
|
||||||
|
</MonoText>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
], [])
|
||||||
|
|
||||||
function handleInstanceClick(inst: AgentHealthData) {
|
function handleInstanceClick(inst: AgentHealthData) {
|
||||||
setSelectedInstance(inst)
|
setSelectedInstance(inst)
|
||||||
setPanelOpen(true)
|
setPanelOpen(true)
|
||||||
@@ -362,65 +430,14 @@ export function AgentHealth() {
|
|||||||
</div>
|
</div>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
>
|
>
|
||||||
<table className={styles.instanceTable}>
|
<DataTable<AgentHealthData>
|
||||||
<thead>
|
columns={instanceColumns}
|
||||||
<tr>
|
data={group.instances}
|
||||||
<th className={styles.thStatus} />
|
onRowClick={handleInstanceClick}
|
||||||
<th>Instance</th>
|
selectedId={panelOpen ? selectedInstance?.id : undefined}
|
||||||
<th>State</th>
|
pageSize={50}
|
||||||
<th>Uptime</th>
|
flush
|
||||||
<th>TPS</th>
|
/>
|
||||||
<th>Errors</th>
|
|
||||||
<th>Heartbeat</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{group.instances.map((inst) => (
|
|
||||||
<tr
|
|
||||||
key={inst.id}
|
|
||||||
className={[
|
|
||||||
styles.instanceRow,
|
|
||||||
selectedInstance?.id === inst.id && panelOpen ? styles.instanceRowActive : '',
|
|
||||||
].filter(Boolean).join(' ')}
|
|
||||||
onClick={() => handleInstanceClick(inst)}
|
|
||||||
>
|
|
||||||
<td className={styles.tdStatus}>
|
|
||||||
<StatusDot variant={inst.status === 'live' ? 'live' : inst.status === 'stale' ? 'stale' : 'dead'} />
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<MonoText size="sm" className={styles.instanceName}>{inst.name}</MonoText>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Badge
|
|
||||||
label={inst.status.toUpperCase()}
|
|
||||||
color={inst.status === 'live' ? 'success' : inst.status === 'stale' ? 'warning' : 'error'}
|
|
||||||
variant="filled"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<MonoText size="xs" className={styles.instanceMeta}>{inst.uptime}</MonoText>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<MonoText size="xs" className={styles.instanceMeta}>{inst.tps.toFixed(1)}/s</MonoText>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<MonoText size="xs" className={inst.errorRate ? styles.instanceError : styles.instanceMeta}>
|
|
||||||
{inst.errorRate ?? '0 err/h'}
|
|
||||||
</MonoText>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<MonoText size="xs" className={
|
|
||||||
inst.status === 'dead' ? styles.instanceHeartbeatDead :
|
|
||||||
inst.status === 'stale' ? styles.instanceHeartbeatStale :
|
|
||||||
styles.instanceMeta
|
|
||||||
}>
|
|
||||||
{inst.lastSeen}
|
|
||||||
</MonoText>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</GroupCard>
|
</GroupCard>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user