fix: resolve bugs in benchmark lifecycle, snapshot filtering, and dark mode

- Fix PVC bind loop leak on unmount via cancellation ref
- Fix DeleteOptions body structure for proper foreground propagation
- Filter snapshots to tns-csi driver only (was showing all drivers)
- Fix stale closures in Escape key handlers with useCallback
- Add loading state to cleanup delete button, remove window.confirm/alert
- Use CSS custom properties for protocol chart colors (dark mode support)
- Fix all 35 ESLint warnings (import sort, indent, boolean attrs)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
DevContainer User
2026-03-04 12:47:33 +00:00
parent 6f35c6c81b
commit c1c5e8a37d
19 changed files with 113 additions and 76 deletions
+31 -19
View File
@@ -15,7 +15,7 @@ import {
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useTnsCsiContext } from '../api/TnsCsiDataContext';
import { formatAge } from '../api/k8s';
import type { BenchmarkState, KbenchJobSummary, KbenchResult } from '../api/kbench';
import {
createJob,
@@ -32,7 +32,7 @@ import {
listKbenchJobs,
parseKbenchLog,
} from '../api/kbench';
import { formatAge } from '../api/k8s';
import { useTnsCsiContext } from '../api/TnsCsiDataContext';
// ---------------------------------------------------------------------------
// Result display components
@@ -184,8 +184,8 @@ function KbenchResultDisplay({ result }: { result: KbenchResult }) {
]}
/>
</SectionBox>
<ResultTable title="IOPS (Read/Write)" rows={iopsRows} higherIsBetter={true} />
<ResultTable title="Bandwidth" rows={bwRows} higherIsBetter={true} />
<ResultTable title="IOPS (Read/Write)" rows={iopsRows} higherIsBetter />
<ResultTable title="Bandwidth" rows={bwRows} higherIsBetter />
<ResultTable title="Latency" rows={latRows} higherIsBetter={false} />
</>
);
@@ -601,6 +601,8 @@ export default function BenchmarkPage() {
const [currentResult, setCurrentResult] = useState<KbenchResult | null>(null);
const [lastNamespace, setLastNamespace] = useState('default');
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
const cancelledRef = useRef(false);
const [cleaningUp, setCleaningUp] = useState(false);
const scNames = storageClasses.map(sc => sc.metadata.name);
@@ -618,6 +620,7 @@ export default function BenchmarkPage() {
mode: string;
}) {
stopPolling();
cancelledRef.current = false;
setCurrentResult(null);
setLastNamespace(opts.namespace);
@@ -650,7 +653,7 @@ export default function BenchmarkPage() {
setBenchState({ status: 'waiting-pvc', pvcName });
const pvcDeadline = Date.now() + MAX_PVC_WAIT_MS;
let pvcBound = false;
while (Date.now() < pvcDeadline) {
while (Date.now() < pvcDeadline && !cancelledRef.current) {
try {
const pvc = (await ApiProxy.request(
`/api/v1/namespaces/${opts.namespace}/persistentvolumeclaims/${pvcName}`
@@ -664,6 +667,7 @@ export default function BenchmarkPage() {
}
await new Promise(r => setTimeout(r, 5000));
}
if (cancelledRef.current) return;
if (!pvcBound) {
setBenchState({
status: 'failed',
@@ -746,8 +750,14 @@ export default function BenchmarkPage() {
}, POLL_INTERVAL_MS);
}
// Clean up polling on unmount
useEffect(() => () => stopPolling(), []);
// Clean up polling and cancel async loops on unmount
useEffect(
() => () => {
cancelledRef.current = true;
stopPolling();
},
[]
);
const isRunning =
benchState.status !== 'idle' &&
@@ -808,24 +818,25 @@ export default function BenchmarkPage() {
<button
onClick={async () => {
const state = benchState;
if (state.status !== 'complete') return;
if (
!window.confirm(
`Delete job "${state.jobName}" and PVC "${state.pvcName}"?`
)
)
return;
if (state.status !== 'complete' || cleaningUp) return;
setCleaningUp(true);
try {
await deleteJob(state.jobName, lastNamespace);
await deletePvc(state.pvcName, lastNamespace);
setBenchState({ status: 'idle' });
setCurrentResult(null);
} catch (err: unknown) {
alert(
`Cleanup error: ${err instanceof Error ? err.message : String(err)}`
);
setBenchState({
status: 'failed',
error: `Cleanup error: ${err instanceof Error ? err.message : String(err)}`,
jobName: state.jobName,
pvcName: state.pvcName,
});
} finally {
setCleaningUp(false);
}
}}
disabled={cleaningUp}
aria-label="Delete benchmark job and PVC"
style={{
padding: '6px 14px',
@@ -833,11 +844,12 @@ export default function BenchmarkPage() {
color: 'var(--mui-palette-error-main, #d32f2f)',
background: 'transparent',
borderRadius: '4px',
cursor: 'pointer',
cursor: cleaningUp ? 'not-allowed' : 'pointer',
fontSize: '13px',
opacity: cleaningUp ? 0.6 : 1,
}}
>
Delete Job + PVC
{cleaningUp ? 'Deleting...' : 'Delete Job + PVC'}
</button>
),
},