feat: show log indices on OpenSearch admin page
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 54s
CI / docker (push) Successful in 47s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 35s

Add prefix query parameter to /admin/opensearch/indices endpoint so
the UI can fetch execution and log indices separately. OpenSearch admin
page now shows two card sections: Execution Indices and Log Indices,
each with doc count and size summary. Page restyled with CSS module
replacing inline styles. Delete endpoint also allows log index deletion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-26 16:47:44 +01:00
parent 0d94132c98
commit 25ca8d5132
4 changed files with 117 additions and 28 deletions

View File

@@ -71,13 +71,14 @@ export function usePipelineStats() {
});
}
export function useOpenSearchIndices(page = 0, size = 20, search = '') {
export function useOpenSearchIndices(page = 0, size = 20, search = '', prefix = 'executions') {
return useQuery({
queryKey: ['admin', 'opensearch', 'indices', page, size, search],
queryKey: ['admin', 'opensearch', 'indices', prefix, page, size, search],
queryFn: () => {
const params = new URLSearchParams();
params.set('page', String(page));
params.set('size', String(size));
params.set('prefix', prefix);
if (search) params.set('search', search);
return adminFetch<IndicesPage>(`/opensearch/indices?${params}`);
},

View File

@@ -0,0 +1,63 @@
.statStrip {
display: flex;
gap: 10px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.pipelineCard {
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: 16px;
}
.pipelineTitle {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 8px;
}
.pipelineMetrics {
display: flex;
gap: 24px;
margin-top: 8px;
font-size: 12px;
color: var(--text-muted);
}
.pipelineMetrics span {
font-family: var(--font-mono);
}
.indexSection {
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;
}
.indexHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--border-subtle);
}
.indexTitle {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.indexMeta {
font-size: 11px;
color: var(--text-muted);
font-family: var(--font-mono);
}

View File

@@ -1,13 +1,14 @@
import { StatCard, Card, DataTable, Badge, ProgressBar, Spinner } from '@cameleer/design-system';
import { StatCard, DataTable, Badge, ProgressBar } from '@cameleer/design-system';
import type { Column } from '@cameleer/design-system';
import { useOpenSearchStatus, usePipelineStats, useOpenSearchIndices, useOpenSearchPerformance, useDeleteIndex } from '../../api/queries/admin/opensearch';
import { useState } from 'react';
import styles from './OpenSearchAdminPage.module.css';
export default function OpenSearchAdminPage() {
const { data: status } = useOpenSearchStatus();
const { data: pipeline } = usePipelineStats();
const { data: perf } = useOpenSearchPerformance();
const { data: indicesData } = useOpenSearchIndices();
const { data: execIndices } = useOpenSearchIndices(0, 50, '', 'executions');
const { data: logIndices } = useOpenSearchIndices(0, 50, '', 'logs');
const deleteIndex = useDeleteIndex();
const indexColumns: Column<any>[] = [
@@ -20,37 +21,55 @@ export default function OpenSearchAdminPage() {
return (
<div>
<h2 style={{ marginBottom: '1rem' }}>OpenSearch Administration</h2>
<div style={{ display: 'flex', gap: '1rem', marginBottom: '1.5rem', flexWrap: 'wrap' }}>
<div className={styles.statStrip}>
<StatCard label="Status" value={status?.reachable ? 'Connected' : 'Disconnected'} accent={status?.reachable ? 'success' : 'error'} />
<StatCard label="Health" value={status?.clusterHealth ?? ''} accent={status?.clusterHealth === 'green' ? 'success' : 'warning'} />
<StatCard label="Version" value={status?.version ?? ''} />
<StatCard label="Health" value={status?.clusterHealth ?? '\u2014'} accent={status?.clusterHealth === 'green' ? 'success' : 'warning'} />
<StatCard label="Version" value={status?.version ?? '\u2014'} />
<StatCard label="Nodes" value={status?.nodeCount ?? 0} />
</div>
{pipeline && (
<Card>
<div style={{ padding: '1rem' }}>
<h3 style={{ marginBottom: '0.5rem' }}>Indexing Pipeline</h3>
<ProgressBar value={(pipeline.queueDepth / pipeline.maxQueueSize) * 100} />
<div style={{ display: 'flex', gap: '2rem', marginTop: '0.5rem', fontSize: '0.875rem' }}>
<span>Queue: {pipeline.queueDepth}/{pipeline.maxQueueSize}</span>
<span>Indexed: {pipeline.indexedCount}</span>
<span>Failed: {pipeline.failedCount}</span>
<span>Rate: {pipeline.indexingRate}/s</span>
</div>
<div className={styles.pipelineCard}>
<div className={styles.pipelineTitle}>Indexing Pipeline</div>
<ProgressBar value={(pipeline.queueDepth / pipeline.maxQueueSize) * 100} />
<div className={styles.pipelineMetrics}>
<span>Queue: {pipeline.queueDepth}/{pipeline.maxQueueSize}</span>
<span>Indexed: {pipeline.indexedCount.toLocaleString()}</span>
<span>Failed: {pipeline.failedCount}</span>
<span>Rate: {pipeline.indexingRate}/s</span>
</div>
</Card>
</div>
)}
<div style={{ marginTop: '1.5rem' }}>
<h3 style={{ marginBottom: '0.75rem' }}>Indices</h3>
<div className={styles.indexSection}>
<div className={styles.indexHeader}>
<span className={styles.indexTitle}>Execution Indices ({execIndices?.totalIndices ?? 0})</span>
<span className={styles.indexMeta}>
{execIndices ? `${execIndices.totalDocs.toLocaleString()} docs \u00b7 ${execIndices.totalSize}` : ''}
</span>
</div>
<DataTable
columns={indexColumns}
data={(indicesData?.indices || []).map((i: any) => ({ ...i, id: i.name }))}
data={(execIndices?.indices || []).map((i: any) => ({ ...i, id: i.name }))}
sortable
pageSize={20}
flush
/>
</div>
<div className={styles.indexSection}>
<div className={styles.indexHeader}>
<span className={styles.indexTitle}>Log Indices ({logIndices?.totalIndices ?? 0})</span>
<span className={styles.indexMeta}>
{logIndices ? `${logIndices.totalDocs.toLocaleString()} docs \u00b7 ${logIndices.totalSize}` : ''}
</span>
</div>
<DataTable
columns={indexColumns}
data={(logIndices?.indices || []).map((i: any) => ({ ...i, id: i.name }))}
sortable
pageSize={20}
flush
/>
</div>
</div>