Files
sencho/frontend/src/hooks/useTrivyStatus.ts
T
Anso 61bac08027 feat(security): one-click managed Trivy install (#643)
* feat(security): one-click managed Trivy install

Add a Vulnerability Scanner card to Settings, Security with install,
update, uninstall, and auto-update controls (Admiral-only). The installer
downloads a verified Trivy release into the existing data volume at
/app/data/bin/trivy and defaults the cache to /app/data/trivy-cache, so
no host mounts or extra env vars are required. Detection probes the
managed path, a TRIVY_BIN override, and the host PATH, distinguishing
managed vs host installs. A daily scheduled check surfaces available
Trivy updates, installs them automatically when opted in, and dedupes
notifications per version.

* fix(frontend): silence react-hooks/set-state-in-effect in useTrivyStatus

The initial status fetch and managed-source update check both call
setState from the effect body. Match the existing pattern used in
useDashboardData / SSOSection and disable the rule at the call site.
2026-04-16 21:29:44 -04:00

71 lines
2.0 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react';
import { apiFetch } from '@/lib/api';
import type { TrivyStatus, TrivyUpdateCheck } from '@/types/security';
const INITIAL_STATUS: TrivyStatus = {
available: false,
version: null,
source: 'none',
autoUpdate: false,
busy: false,
};
export interface UseTrivyStatusResult {
status: TrivyStatus;
updateCheck: TrivyUpdateCheck | null;
refresh: () => Promise<void>;
refreshUpdateCheck: () => Promise<void>;
}
export function useTrivyStatus(): UseTrivyStatusResult {
const [status, setStatus] = useState<TrivyStatus>(INITIAL_STATUS);
const [updateCheck, setUpdateCheck] = useState<TrivyUpdateCheck | null>(null);
const refresh = useCallback(async () => {
try {
const r = await apiFetch('/security/trivy-status');
if (!r.ok) return;
const d = await r.json();
setStatus({
available: !!d.available,
version: typeof d.version === 'string' ? d.version : null,
source: d.source === 'managed' || d.source === 'host' ? d.source : 'none',
autoUpdate: !!d.autoUpdate,
busy: !!d.busy,
});
} catch (err) {
console.error('Failed to fetch Trivy status:', err);
}
}, []);
const refreshUpdateCheck = useCallback(async () => {
try {
const r = await apiFetch('/security/trivy-update-check');
if (!r.ok) {
setUpdateCheck(null);
return;
}
const d = (await r.json()) as TrivyUpdateCheck;
setUpdateCheck(d);
} catch {
setUpdateCheck(null);
}
}, []);
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
void refresh();
}, [refresh]);
useEffect(() => {
if (status.source === 'managed') {
// eslint-disable-next-line react-hooks/set-state-in-effect
void refreshUpdateCheck();
} else {
setUpdateCheck(null);
}
}, [status.source, refreshUpdateCheck]);
return { status, updateCheck, refresh, refreshUpdateCheck };
}