fix: align frontend interfaces with backend DTO field names

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-17 16:36:11 +01:00
parent 329e4b0b16
commit 038b663b8c
7 changed files with 153 additions and 168 deletions

View File

@@ -2,14 +2,16 @@ import { useQuery } from '@tanstack/react-query';
import { adminFetch } from './admin-api';
export interface AuditEvent {
id: string;
id: number;
timestamp: string;
username: string;
category: string;
action: string;
category: string;
target: string;
result: string;
detail: Record<string, unknown>;
result: string;
ipAddress: string;
userAgent: string;
}
export interface AuditLogParams {
@@ -18,13 +20,18 @@ export interface AuditLogParams {
username?: string;
category?: string;
search?: string;
sort?: string;
order?: string;
page?: number;
size?: number;
}
export interface AuditLogResponse {
events: AuditEvent[];
total: number;
items: AuditEvent[];
totalCount: number;
page: number;
pageSize: number;
totalPages: number;
}
export function useAuditLog(params: AuditLogParams) {
@@ -34,6 +41,8 @@ export function useAuditLog(params: AuditLogParams) {
if (params.username) query.set('username', params.username);
if (params.category) query.set('category', params.category);
if (params.search) query.set('search', params.search);
if (params.sort) query.set('sort', params.sort);
if (params.order) query.set('order', params.order);
if (params.page !== undefined) query.set('page', String(params.page));
if (params.size !== undefined) query.set('size', String(params.size));
const qs = query.toString();

View File

@@ -6,26 +6,29 @@ export interface DatabaseStatus {
version: string;
host: string;
schema: string;
timescaleDb: boolean;
}
export interface PoolStats {
activeConnections: number;
idleConnections: number;
pendingConnections: number;
maxConnections: number;
maxWaitMillis: number;
pendingThreads: number;
maxPoolSize: number;
maxWaitMs: number;
}
export interface TableInfo {
tableName: string;
rowEstimate: number;
rowCount: number;
dataSize: string;
indexSize: string;
dataSizeBytes: number;
indexSizeBytes: number;
}
export interface ActiveQuery {
pid: number;
durationMs: number;
durationSeconds: number;
state: string;
query: string;
}
@@ -64,7 +67,7 @@ export function useKillQuery() {
const qc = useQueryClient();
return useMutation({
mutationFn: async (pid: number) => {
await adminFetch<void>(`/database/queries/${pid}`, { method: 'DELETE' });
await adminFetch<void>(`/database/queries/${pid}/kill`, { method: 'POST' });
},
onSuccess: () => qc.invalidateQueries({ queryKey: ['admin', 'database', 'queries'] }),
});

View File

@@ -2,47 +2,54 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { adminFetch } from './admin-api';
export interface OpenSearchStatus {
connected: boolean;
clusterName: string;
reachable: boolean;
clusterHealth: string;
version: string;
numberOfNodes: number;
nodeCount: number;
host: string;
}
export interface PipelineStats {
queueDepth: number;
maxQueueSize: number;
totalIndexed: number;
totalFailed: number;
avgLatencyMs: number;
indexedCount: number;
failedCount: number;
debounceMs: number;
indexingRate: number;
lastIndexedAt: string | null;
}
export interface IndexInfo {
name: string;
health: string;
status: string;
docsCount: number;
storeSize: string;
docCount: number;
size: string;
sizeBytes: number;
primaryShards: number;
replicas: number;
replicaShards: number;
}
export interface IndicesPageResponse {
indices: IndexInfo[];
totalIndices: number;
totalDocs: number;
totalSize: string;
page: number;
pageSize: number;
totalPages: number;
}
export interface PerformanceStats {
queryCacheHitRate: number;
requestCacheHitRate: number;
avgQueryLatencyMs: number;
avgIndexLatencyMs: number;
jvmHeapUsedPercent: number;
searchLatencyMs: number;
indexingLatencyMs: number;
jvmHeapUsedBytes: number;
jvmHeapMaxBytes: number;
}
export interface IndicesParams {
search?: string;
health?: string;
sortBy?: string;
sortDir?: 'asc' | 'desc';
page?: number;
size?: number;
}
@@ -65,9 +72,6 @@ export function usePipelineStats() {
export function useIndices(params: IndicesParams) {
const query = new URLSearchParams();
if (params.search) query.set('search', params.search);
if (params.health) query.set('health', params.health);
if (params.sortBy) query.set('sortBy', params.sortBy);
if (params.sortDir) query.set('sortDir', params.sortDir);
if (params.page !== undefined) query.set('page', String(params.page));
if (params.size !== undefined) query.set('size', String(params.size));
const qs = query.toString();
@@ -75,7 +79,7 @@ export function useIndices(params: IndicesParams) {
return useQuery({
queryKey: ['admin', 'opensearch', 'indices', params],
queryFn: () =>
adminFetch<{ indices: IndexInfo[]; total: number }>(
adminFetch<IndicesPageResponse>(
`/opensearch/indices${qs ? `?${qs}` : ''}`,
),
});

View File

@@ -1,29 +1,41 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { adminFetch } from './admin-api';
export interface Thresholds {
poolWarningPercent: number;
poolCriticalPercent: number;
queryDurationWarningSeconds: number;
queryDurationCriticalSeconds: number;
osQueueWarningPercent: number;
osQueueCriticalPercent: number;
osHeapWarningPercent: number;
osHeapCriticalPercent: number;
export interface DatabaseThresholds {
connectionPoolWarning: number;
connectionPoolCritical: number;
queryDurationWarning: number;
queryDurationCritical: number;
}
export interface OpenSearchThresholds {
clusterHealthWarning: string;
clusterHealthCritical: string;
queueDepthWarning: number;
queueDepthCritical: number;
jvmHeapWarning: number;
jvmHeapCritical: number;
failedDocsWarning: number;
failedDocsCritical: number;
}
export interface ThresholdConfig {
database: DatabaseThresholds;
opensearch: OpenSearchThresholds;
}
export function useThresholds() {
return useQuery({
queryKey: ['admin', 'thresholds'],
queryFn: () => adminFetch<Thresholds>('/thresholds'),
queryFn: () => adminFetch<ThresholdConfig>('/thresholds'),
});
}
export function useSaveThresholds() {
const qc = useQueryClient();
return useMutation({
mutationFn: async (body: Thresholds) => {
await adminFetch<Thresholds>('/thresholds', {
mutationFn: async (body: ThresholdConfig) => {
await adminFetch<ThresholdConfig>('/thresholds', {
method: 'PUT',
body: JSON.stringify(body),
});

View File

@@ -36,7 +36,7 @@ function AuditLogContent() {
const [category, setCategory] = useState('');
const [search, setSearch] = useState('');
const [page, setPage] = useState(0);
const [expandedRow, setExpandedRow] = useState<string | null>(null);
const [expandedRow, setExpandedRow] = useState<number | null>(null);
const pageSize = 25;
const params: AuditLogParams = {
@@ -51,16 +51,16 @@ function AuditLogContent() {
const audit = useAuditLog(params);
const data = audit.data;
const totalPages = data ? Math.ceil(data.total / pageSize) : 0;
const showingFrom = data && data.total > 0 ? page * pageSize + 1 : 0;
const showingTo = data ? Math.min((page + 1) * pageSize, data.total) : 0;
const totalPages = data?.totalPages ?? 0;
const showingFrom = data && data.totalCount > 0 ? page * pageSize + 1 : 0;
const showingTo = data ? Math.min((page + 1) * pageSize, data.totalCount) : 0;
return (
<div className={styles.page}>
<div className={styles.header}>
<h1 className={styles.pageTitle}>Audit Log</h1>
{data && (
<span className={styles.totalCount}>{data.total.toLocaleString()} events</span>
<span className={styles.totalCount}>{data.totalCount.toLocaleString()} events</span>
)}
</div>
@@ -121,7 +121,7 @@ function AuditLogContent() {
{audit.isLoading ? (
<div className={styles.loading}>Loading...</div>
) : !data || data.events.length === 0 ? (
) : !data || data.items.length === 0 ? (
<div className={styles.emptyState}>No audit events found for the selected filters.</div>
) : (
<>
@@ -138,7 +138,7 @@ function AuditLogContent() {
</tr>
</thead>
<tbody>
{data.events.map((event) => (
{data.items.map((event) => (
<>
<tr
key={event.id}
@@ -191,7 +191,7 @@ function AuditLogContent() {
Previous
</button>
<span className={styles.pageInfo}>
Showing {showingFrom}-{showingTo} of {data.total.toLocaleString()}
Showing {showingFrom}-{showingTo} of {data.totalCount.toLocaleString()}
</span>
<button
type="button"

View File

@@ -10,7 +10,7 @@ import {
useDatabaseQueries,
useKillQuery,
} from '../../api/queries/admin/database';
import { useThresholds, useSaveThresholds, type Thresholds } from '../../api/queries/admin/thresholds';
import { useThresholds, useSaveThresholds, type ThresholdConfig } from '../../api/queries/admin/thresholds';
import styles from './DatabaseAdminPage.module.css';
export function DatabaseAdminPage() {
@@ -78,13 +78,13 @@ function DatabaseAdminContent() {
<PoolSection
pool={pool}
warningPct={thresholds.data?.poolWarningPercent}
criticalPct={thresholds.data?.poolCriticalPercent}
warningPct={thresholds.data?.database?.connectionPoolWarning}
criticalPct={thresholds.data?.database?.connectionPoolCritical}
/>
<TablesSection tables={tables} />
<QueriesSection
queries={queries}
warningSeconds={thresholds.data?.queryDurationWarningSeconds}
warningSeconds={thresholds.data?.database?.queryDurationWarning}
/>
<MaintenanceSection />
<ThresholdsSection thresholds={thresholds.data} />
@@ -104,8 +104,8 @@ function PoolSection({
const data = pool.data;
if (!data) return null;
const usagePct = data.maxConnections > 0
? Math.round((data.activeConnections / data.maxConnections) * 100)
const usagePct = data.maxPoolSize > 0
? Math.round((data.activeConnections / data.maxPoolSize) * 100)
: 0;
const barColor =
criticalPct && usagePct >= criticalPct ? '#ef4444'
@@ -121,7 +121,7 @@ function PoolSection({
>
<div className={styles.progressContainer}>
<div className={styles.progressLabel}>
{data.activeConnections} / {data.maxConnections} connections
{data.activeConnections} / {data.maxPoolSize} connections
<span className={styles.progressPct}>{usagePct}%</span>
</div>
<div className={styles.progressBar}>
@@ -141,11 +141,11 @@ function PoolSection({
<span className={styles.metricLabel}>Idle</span>
</div>
<div className={styles.metric}>
<span className={styles.metricValue}>{data.pendingConnections}</span>
<span className={styles.metricValue}>{data.pendingThreads}</span>
<span className={styles.metricLabel}>Pending</span>
</div>
<div className={styles.metric}>
<span className={styles.metricValue}>{data.maxWaitMillis}ms</span>
<span className={styles.metricValue}>{data.maxWaitMs}ms</span>
<span className={styles.metricLabel}>Max Wait</span>
</div>
</div>
@@ -179,7 +179,7 @@ function TablesSection({ tables }: { tables: ReturnType<typeof useDatabaseTables
{data.map((t) => (
<tr key={t.tableName}>
<td className={styles.mono}>{t.tableName}</td>
<td>{t.rowEstimate.toLocaleString()}</td>
<td>{t.rowCount.toLocaleString()}</td>
<td>{t.dataSize}</td>
<td>{t.indexSize}</td>
</tr>
@@ -203,7 +203,7 @@ function QueriesSection({
const killMutation = useKillQuery();
const data = queries.data;
const warningMs = (warningSeconds ?? 30) * 1000;
const warningSec = warningSeconds ?? 30;
return (
<RefreshableCard
@@ -230,10 +230,10 @@ function QueriesSection({
{data.map((q) => (
<tr
key={q.pid}
className={q.durationMs > warningMs ? styles.rowWarning : undefined}
className={q.durationSeconds > warningSec ? styles.rowWarning : undefined}
>
<td className={styles.mono}>{q.pid}</td>
<td>{formatDuration(q.durationMs)}</td>
<td>{formatDuration(q.durationSeconds)}</td>
<td>{q.state}</td>
<td className={styles.queryCell} title={q.query}>
{q.query.length > 100 ? `${q.query.slice(0, 100)}...` : q.query}
@@ -287,16 +287,19 @@ function MaintenanceSection() {
);
}
function ThresholdsSection({ thresholds }: { thresholds?: Thresholds }) {
const [form, setForm] = useState<Thresholds | null>(null);
function ThresholdsSection({ thresholds }: { thresholds?: ThresholdConfig }) {
const [form, setForm] = useState<ThresholdConfig | null>(null);
const saveMutation = useSaveThresholds();
const [status, setStatus] = useState<{ type: 'success' | 'error'; msg: string } | null>(null);
const current = form ?? thresholds;
if (!current) return null;
function update(key: keyof Thresholds, value: number) {
setForm((prev) => ({ ...(prev ?? thresholds!), [key]: value }));
function updateDb(key: keyof ThresholdConfig['database'], value: number) {
setForm((prev) => {
const base = prev ?? thresholds!;
return { ...base, database: { ...base.database, [key]: value } };
});
}
async function handleSave() {
@@ -319,8 +322,8 @@ function ThresholdsSection({ thresholds }: { thresholds?: Thresholds }) {
<input
type="number"
className={styles.thresholdInput}
value={current.poolWarningPercent}
onChange={(e) => update('poolWarningPercent', Number(e.target.value))}
value={current.database.connectionPoolWarning}
onChange={(e) => updateDb('connectionPoolWarning', Number(e.target.value))}
/>
</div>
<div className={styles.thresholdField}>
@@ -328,8 +331,8 @@ function ThresholdsSection({ thresholds }: { thresholds?: Thresholds }) {
<input
type="number"
className={styles.thresholdInput}
value={current.poolCriticalPercent}
onChange={(e) => update('poolCriticalPercent', Number(e.target.value))}
value={current.database.connectionPoolCritical}
onChange={(e) => updateDb('connectionPoolCritical', Number(e.target.value))}
/>
</div>
<div className={styles.thresholdField}>
@@ -337,8 +340,8 @@ function ThresholdsSection({ thresholds }: { thresholds?: Thresholds }) {
<input
type="number"
className={styles.thresholdInput}
value={current.queryDurationWarningSeconds}
onChange={(e) => update('queryDurationWarningSeconds', Number(e.target.value))}
value={current.database.queryDurationWarning}
onChange={(e) => updateDb('queryDurationWarning', Number(e.target.value))}
/>
</div>
<div className={styles.thresholdField}>
@@ -346,8 +349,8 @@ function ThresholdsSection({ thresholds }: { thresholds?: Thresholds }) {
<input
type="number"
className={styles.thresholdInput}
value={current.queryDurationCriticalSeconds}
onChange={(e) => update('queryDurationCriticalSeconds', Number(e.target.value))}
value={current.database.queryDurationCritical}
onChange={(e) => updateDb('queryDurationCritical', Number(e.target.value))}
/>
</div>
</div>
@@ -370,9 +373,9 @@ function ThresholdsSection({ thresholds }: { thresholds?: Thresholds }) {
);
}
function formatDuration(ms: number): string {
if (ms < 1000) return `${ms}ms`;
const s = Math.floor(ms / 1000);
function formatDuration(seconds: number): string {
if (seconds < 1) return `${Math.round(seconds * 1000)}ms`;
const s = Math.floor(seconds);
if (s < 60) return `${s}s`;
const m = Math.floor(s / 60);
return `${m}m ${s % 60}s`;

View File

@@ -11,7 +11,7 @@ import {
useDeleteIndex,
type IndicesParams,
} from '../../api/queries/admin/opensearch';
import { useThresholds, useSaveThresholds, type Thresholds } from '../../api/queries/admin/thresholds';
import { useThresholds, useSaveThresholds, type ThresholdConfig } from '../../api/queries/admin/thresholds';
import styles from './OpenSearchAdminPage.module.css';
function clusterHealthToStatus(health: string | undefined): Status {
@@ -67,8 +67,8 @@ function OpenSearchAdminContent() {
label={os?.clusterHealth ?? 'Unknown'}
/>
{os?.version && <span className={styles.metaItem}>v{os.version}</span>}
{os?.numberOfNodes !== undefined && (
<span className={styles.metaItem}>{os.numberOfNodes} node(s)</span>
{os?.nodeCount !== undefined && (
<span className={styles.metaItem}>{os.nodeCount} node(s)</span>
)}
{os?.host && <span className={styles.metaItem}>{os.host}</span>}
</div>
@@ -100,7 +100,7 @@ function PipelineSection({
thresholds,
}: {
pipeline: ReturnType<typeof usePipelineStats>;
thresholds?: Thresholds;
thresholds?: ThresholdConfig;
}) {
const data = pipeline.data;
if (!data) return null;
@@ -109,8 +109,8 @@ function PipelineSection({
? Math.round((data.queueDepth / data.maxQueueSize) * 100)
: 0;
const barColor =
thresholds?.osQueueCriticalPercent && queuePct >= thresholds.osQueueCriticalPercent ? '#ef4444'
: thresholds?.osQueueWarningPercent && queuePct >= thresholds.osQueueWarningPercent ? '#eab308'
thresholds?.opensearch?.queueDepthCritical && data.queueDepth >= thresholds.opensearch.queueDepthCritical ? '#ef4444'
: thresholds?.opensearch?.queueDepthWarning && data.queueDepth >= thresholds.opensearch.queueDepthWarning ? '#eab308'
: '#22c55e';
return (
@@ -134,16 +134,16 @@ function PipelineSection({
</div>
<div className={styles.metricsGrid}>
<div className={styles.metric}>
<span className={styles.metricValue}>{data.totalIndexed.toLocaleString()}</span>
<span className={styles.metricValue}>{data.indexedCount.toLocaleString()}</span>
<span className={styles.metricLabel}>Total Indexed</span>
</div>
<div className={styles.metric}>
<span className={styles.metricValue}>{data.totalFailed.toLocaleString()}</span>
<span className={styles.metricValue}>{data.failedCount.toLocaleString()}</span>
<span className={styles.metricLabel}>Total Failed</span>
</div>
<div className={styles.metric}>
<span className={styles.metricValue}>{data.avgLatencyMs}ms</span>
<span className={styles.metricLabel}>Avg Latency</span>
<span className={styles.metricValue}>{data.indexingRate.toFixed(1)}/s</span>
<span className={styles.metricLabel}>Indexing Rate</span>
</div>
</div>
</RefreshableCard>
@@ -152,18 +152,12 @@ function PipelineSection({
function IndicesSection() {
const [search, setSearch] = useState('');
const [healthFilter, setHealthFilter] = useState('');
const [sortBy, setSortBy] = useState('name');
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
const [page, setPage] = useState(0);
const pageSize = 10;
const [deleteTarget, setDeleteTarget] = useState<string | null>(null);
const params: IndicesParams = {
search: search || undefined,
health: healthFilter || undefined,
sortBy,
sortDir,
page,
size: pageSize,
};
@@ -171,18 +165,8 @@ function IndicesSection() {
const indices = useIndices(params);
const deleteMutation = useDeleteIndex();
function toggleSort(col: string) {
if (sortBy === col) {
setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));
} else {
setSortBy(col);
setSortDir('asc');
}
setPage(0);
}
const data = indices.data;
const totalPages = data ? Math.ceil(data.total / pageSize) : 0;
const totalPages = data?.totalPages ?? 0;
return (
<RefreshableCard
@@ -198,16 +182,6 @@ function IndicesSection() {
value={search}
onChange={(e) => { setSearch(e.target.value); setPage(0); }}
/>
<select
className={styles.filterSelect}
value={healthFilter}
onChange={(e) => { setHealthFilter(e.target.value); setPage(0); }}
>
<option value="">All Health</option>
<option value="green">Green</option>
<option value="yellow">Yellow</option>
<option value="red">Red</option>
</select>
</div>
{!data ? (
@@ -218,10 +192,10 @@ function IndicesSection() {
<table className={styles.table}>
<thead>
<tr>
<SortHeader label="Name" col="name" current={sortBy} dir={sortDir} onSort={toggleSort} />
<SortHeader label="Health" col="health" current={sortBy} dir={sortDir} onSort={toggleSort} />
<SortHeader label="Docs" col="docsCount" current={sortBy} dir={sortDir} onSort={toggleSort} />
<SortHeader label="Size" col="storeSize" current={sortBy} dir={sortDir} onSort={toggleSort} />
<th>Name</th>
<th>Health</th>
<th>Docs</th>
<th>Size</th>
<th>Shards</th>
<th></th>
</tr>
@@ -235,9 +209,9 @@ function IndicesSection() {
{idx.health}
</span>
</td>
<td>{idx.docsCount.toLocaleString()}</td>
<td>{idx.storeSize}</td>
<td>{idx.primaryShards}p / {idx.replicas}r</td>
<td>{idx.docCount.toLocaleString()}</td>
<td>{idx.size}</td>
<td>{idx.primaryShards}p / {idx.replicaShards}r</td>
<td>
<button
type="button"
@@ -300,45 +274,22 @@ function IndicesSection() {
);
}
function SortHeader({
label,
col,
current,
dir,
onSort,
}: {
label: string;
col: string;
current: string;
dir: 'asc' | 'desc';
onSort: (col: string) => void;
}) {
const isActive = current === col;
return (
<th
className={styles.sortableHeader}
onClick={() => onSort(col)}
>
{label}
{isActive && <span className={styles.sortArrow}>{dir === 'asc' ? ' \u25B2' : ' \u25BC'}</span>}
</th>
);
}
function PerformanceSection({
performance,
thresholds,
}: {
performance: ReturnType<typeof usePerformanceStats>;
thresholds?: Thresholds;
thresholds?: ThresholdConfig;
}) {
const data = performance.data;
if (!data) return null;
const heapPct = data.jvmHeapUsedPercent;
const heapPct = data.jvmHeapMaxBytes > 0
? Math.round((data.jvmHeapUsedBytes / data.jvmHeapMaxBytes) * 100)
: 0;
const heapColor =
thresholds?.osHeapCriticalPercent && heapPct >= thresholds.osHeapCriticalPercent ? '#ef4444'
: thresholds?.osHeapWarningPercent && heapPct >= thresholds.osHeapWarningPercent ? '#eab308'
thresholds?.opensearch?.jvmHeapCritical && heapPct >= thresholds.opensearch.jvmHeapCritical ? '#ef4444'
: thresholds?.opensearch?.jvmHeapWarning && heapPct >= thresholds.opensearch.jvmHeapWarning ? '#eab308'
: '#22c55e';
return (
@@ -358,11 +309,11 @@ function PerformanceSection({
<span className={styles.metricLabel}>Request Cache Hit</span>
</div>
<div className={styles.metric}>
<span className={styles.metricValue}>{data.avgQueryLatencyMs}ms</span>
<span className={styles.metricValue}>{data.searchLatencyMs.toFixed(1)}ms</span>
<span className={styles.metricLabel}>Query Latency</span>
</div>
<div className={styles.metric}>
<span className={styles.metricValue}>{data.avgIndexLatencyMs}ms</span>
<span className={styles.metricValue}>{data.indexingLatencyMs.toFixed(1)}ms</span>
<span className={styles.metricLabel}>Index Latency</span>
</div>
</div>
@@ -400,16 +351,19 @@ function OperationsSection() {
);
}
function OsThresholdsSection({ thresholds }: { thresholds?: Thresholds }) {
const [form, setForm] = useState<Thresholds | null>(null);
function OsThresholdsSection({ thresholds }: { thresholds?: ThresholdConfig }) {
const [form, setForm] = useState<ThresholdConfig | null>(null);
const saveMutation = useSaveThresholds();
const [status, setStatus] = useState<{ type: 'success' | 'error'; msg: string } | null>(null);
const current = form ?? thresholds;
if (!current) return null;
function update(key: keyof Thresholds, value: number) {
setForm((prev) => ({ ...(prev ?? thresholds!), [key]: value }));
function updateOs(key: keyof ThresholdConfig['opensearch'], value: number | string) {
setForm((prev) => {
const base = prev ?? thresholds!;
return { ...base, opensearch: { ...base.opensearch, [key]: value } };
});
}
async function handleSave() {
@@ -427,21 +381,21 @@ function OsThresholdsSection({ thresholds }: { thresholds?: Thresholds }) {
<RefreshableCard title="Thresholds" collapsible defaultCollapsed>
<div className={styles.thresholdGrid}>
<div className={styles.thresholdField}>
<label className={styles.thresholdLabel}>Queue Warning %</label>
<label className={styles.thresholdLabel}>Queue Warning</label>
<input
type="number"
className={styles.thresholdInput}
value={current.osQueueWarningPercent}
onChange={(e) => update('osQueueWarningPercent', Number(e.target.value))}
value={current.opensearch.queueDepthWarning}
onChange={(e) => updateOs('queueDepthWarning', Number(e.target.value))}
/>
</div>
<div className={styles.thresholdField}>
<label className={styles.thresholdLabel}>Queue Critical %</label>
<label className={styles.thresholdLabel}>Queue Critical</label>
<input
type="number"
className={styles.thresholdInput}
value={current.osQueueCriticalPercent}
onChange={(e) => update('osQueueCriticalPercent', Number(e.target.value))}
value={current.opensearch.queueDepthCritical}
onChange={(e) => updateOs('queueDepthCritical', Number(e.target.value))}
/>
</div>
<div className={styles.thresholdField}>
@@ -449,8 +403,8 @@ function OsThresholdsSection({ thresholds }: { thresholds?: Thresholds }) {
<input
type="number"
className={styles.thresholdInput}
value={current.osHeapWarningPercent}
onChange={(e) => update('osHeapWarningPercent', Number(e.target.value))}
value={current.opensearch.jvmHeapWarning}
onChange={(e) => updateOs('jvmHeapWarning', Number(e.target.value))}
/>
</div>
<div className={styles.thresholdField}>
@@ -458,8 +412,8 @@ function OsThresholdsSection({ thresholds }: { thresholds?: Thresholds }) {
<input
type="number"
className={styles.thresholdInput}
value={current.osHeapCriticalPercent}
onChange={(e) => update('osHeapCriticalPercent', Number(e.target.value))}
value={current.opensearch.jvmHeapCritical}
onChange={(e) => updateOs('jvmHeapCritical', Number(e.target.value))}
/>
</div>
</div>