fix: improve ClickHouse admin page, fix AgentHealth type error
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m13s
CI / docker (push) Successful in 3m46s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 58s

Rewrite ClickHouse admin to show useful storage metrics instead of
often-empty system.events data. Add active queries section.

- Replace performance endpoint: query system.parts for disk size,
  uncompressed size, compression ratio, total rows, part count
- Add /queries endpoint querying system.processes for active queries
- Frontend: storage overview strip, tables with total size, active
  queries DataTable
- Fix AgentHealth.tsx type: agentId → instanceId in inline type cast

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-01 20:18:06 +02:00
parent 188810e54b
commit f82aa26371
7 changed files with 159 additions and 37 deletions

View File

@@ -1,15 +1,19 @@
import { StatCard, DataTable, ProgressBar } from '@cameleer/design-system';
import type { Column } from '@cameleer/design-system';
import { useClickHouseStatus, useClickHouseTables, useClickHousePerformance, useIndexerPipeline } from '../../api/queries/admin/clickhouse';
import { useClickHouseStatus, useClickHouseTables, useClickHousePerformance, useClickHouseQueries, useIndexerPipeline } from '../../api/queries/admin/clickhouse';
import styles from './ClickHouseAdminPage.module.css';
export default function ClickHouseAdminPage() {
const { data: status, isError: statusError } = useClickHouseStatus();
const { data: tables } = useClickHouseTables();
const { data: perf } = useClickHousePerformance();
const { data: queries } = useClickHouseQueries();
const { data: pipeline } = useIndexerPipeline();
const unreachable = statusError || (status && !status.reachable);
const totalSize = (tables || []).reduce((sum, t) => sum + (t.dataSizeBytes || 0), 0);
const totalSizeLabel = totalSize > 0 ? formatBytes(totalSize) : '';
const tableColumns: Column<any>[] = [
{ key: 'name', header: 'Table', sortable: true },
{ key: 'engine', header: 'Engine' },
@@ -18,14 +22,35 @@ export default function ClickHouseAdminPage() {
{ key: 'partitionCount', header: 'Partitions', sortable: true },
];
const queryColumns: Column<any>[] = [
{ key: 'elapsedSeconds', header: 'Elapsed', render: (v) => `${v}s` },
{ key: 'memory', header: 'Memory' },
{ key: 'readRows', header: 'Rows Read', render: (v) => Number(v).toLocaleString() },
{ key: 'query', header: 'Query', render: (v) => <span className={styles.queryText}>{String(v).slice(0, 100)}</span> },
];
return (
<div>
{/* Status */}
<div className={styles.statStrip}>
<StatCard label="Status" value={unreachable ? 'Disconnected' : status ? 'Connected' : '\u2014'} accent={unreachable ? 'error' : status ? 'success' : undefined} />
<StatCard label="Version" value={status?.version ?? '\u2014'} />
<StatCard label="Uptime" value={status?.uptime ?? '\u2014'} />
</div>
{/* Storage overview */}
{perf && (
<div className={styles.statStrip}>
<StatCard label="Disk Size" value={perf.diskSize} />
<StatCard label="Uncompressed" value={perf.uncompressedSize} />
<StatCard label="Compression" value={`${perf.compressionRatio}x`} />
<StatCard label="Total Rows" value={perf.totalRows.toLocaleString()} />
<StatCard label="Parts" value={perf.partCount.toLocaleString()} />
<StatCard label="Memory" value={perf.memoryUsage} />
</div>
)}
{/* Pipeline */}
{pipeline && (
<div className={styles.pipelineCard}>
<div className={styles.pipelineTitle}>Indexer Pipeline</div>
@@ -39,18 +64,11 @@ export default function ClickHouseAdminPage() {
</div>
)}
{perf && (
<div className={styles.statStrip}>
<StatCard label="Select Queries" value={perf.queryCount.toLocaleString()} />
<StatCard label="Insert Queries" value={perf.insertQueryCount.toLocaleString()} />
<StatCard label="Memory" value={perf.memoryUsage} />
<StatCard label="Rows Read" value={perf.readRows.toLocaleString()} />
</div>
)}
{/* Tables */}
<div className={styles.tableSection}>
<div className={styles.tableHeader}>
<span className={styles.tableTitle}>Tables ({(tables || []).length})</span>
{totalSizeLabel && <span className={styles.tableMeta}>{totalSizeLabel} total</span>}
</div>
<DataTable
columns={tableColumns}
@@ -60,6 +78,26 @@ export default function ClickHouseAdminPage() {
flush
/>
</div>
{/* Active Queries */}
<div className={styles.tableSection}>
<div className={styles.tableHeader}>
<span className={styles.tableTitle}>Active Queries ({(queries || []).length})</span>
</div>
<DataTable
columns={queryColumns}
data={(queries || []).map((q: any, i: number) => ({ ...q, id: q.queryId || String(i) }))}
flush
/>
</div>
</div>
);
}
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B';
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
const val = bytes / Math.pow(1024, i);
return `${val.toFixed(val < 10 ? 2 : 1)} ${units[i]}`;
}