mission canvas: security-scan + benchmark trigger buttons
Fills the frontend gap after slices 7 + 8 shipped the backend runners
without triggers. Each phase card in the Phases tab now grows an
action row when the mission is running or completed:
- security_scan phase → "Run scan" button (POSTs /security-scan)
- benchmark phase → "Baseline" + "After" buttons, iteration auto-
derived from the current benchmark_snapshots count
Findings from security_scan already surface in the Tasks tab via
the task-card parser (external_id = cargo_audit:RUSTSEC-... etc);
the tool prefix in the badge is enough to distinguish sources
without a separate grouped view.
Closes tasks #17 + #18.
This commit is contained in:
@@ -15,6 +15,8 @@ import {
|
||||
getMission,
|
||||
refineMission,
|
||||
setMissionStatus,
|
||||
triggerBenchmark,
|
||||
triggerSecurityScan,
|
||||
type MissionDetail,
|
||||
type MissionStatus,
|
||||
type PhaseKind,
|
||||
@@ -80,6 +82,7 @@ export function MissionCanvas({
|
||||
const [tab, setTab] = useState<Tab>("overview");
|
||||
const [launching, setLaunching] = useState(false);
|
||||
const [refining, setRefining] = useState(false);
|
||||
const [phaseBusy, setPhaseBusy] = useState<string | null>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
if (!selectedId) {
|
||||
@@ -118,6 +121,45 @@ export function MissionCanvas({
|
||||
}
|
||||
}, [mission, onChanged, load]);
|
||||
|
||||
const runSecurityScan = useCallback(
|
||||
async (phaseId: string) => {
|
||||
if (!mission) return;
|
||||
setPhaseBusy(`sec:${phaseId}`);
|
||||
setError(null);
|
||||
try {
|
||||
await triggerSecurityScan(mission.id, phaseId);
|
||||
await load();
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : "security scan failed");
|
||||
} finally {
|
||||
setPhaseBusy(null);
|
||||
}
|
||||
},
|
||||
[mission, load],
|
||||
);
|
||||
|
||||
const runBenchmark = useCallback(
|
||||
async (phaseId: string, slot: "baseline" | "after") => {
|
||||
if (!mission) return;
|
||||
setPhaseBusy(`bench:${phaseId}:${slot}`);
|
||||
setError(null);
|
||||
try {
|
||||
await triggerBenchmark(
|
||||
mission.id,
|
||||
phaseId,
|
||||
slot,
|
||||
slot === "after" ? Math.max(1, mission.benchmarks.length) : undefined,
|
||||
);
|
||||
await load();
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : "benchmark failed");
|
||||
} finally {
|
||||
setPhaseBusy(null);
|
||||
}
|
||||
},
|
||||
[mission, load],
|
||||
);
|
||||
|
||||
const launch = useCallback(async () => {
|
||||
if (!mission) return;
|
||||
setLaunching(true);
|
||||
@@ -398,6 +440,53 @@ export function MissionCanvas({
|
||||
: ""}
|
||||
</span>
|
||||
)}
|
||||
{mission.status === "running" || mission.status === "completed" ? (
|
||||
<div style={{ display: "flex", gap: 6, marginTop: 6 }}>
|
||||
{p.kind === "security_scan" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runSecurityScan(p.id)}
|
||||
disabled={phaseBusy === `sec:${p.id}`}
|
||||
style={{
|
||||
...secondaryBtn,
|
||||
opacity: phaseBusy === `sec:${p.id}` ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
{phaseBusy === `sec:${p.id}` ? "Scanning…" : "Run scan"}
|
||||
</button>
|
||||
)}
|
||||
{p.kind === "benchmark" && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runBenchmark(p.id, "baseline")}
|
||||
disabled={phaseBusy === `bench:${p.id}:baseline`}
|
||||
style={{
|
||||
...secondaryBtn,
|
||||
opacity:
|
||||
phaseBusy === `bench:${p.id}:baseline` ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
{phaseBusy === `bench:${p.id}:baseline`
|
||||
? "Running…"
|
||||
: "Baseline"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => runBenchmark(p.id, "after")}
|
||||
disabled={phaseBusy === `bench:${p.id}:after`}
|
||||
style={{
|
||||
...secondaryBtn,
|
||||
opacity:
|
||||
phaseBusy === `bench:${p.id}:after` ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
{phaseBusy === `bench:${p.id}:after` ? "Running…" : "After"}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user