mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-24 08:57:25 +00:00
feat(security): severity-aware scheduled scan notifications (#654)
Enrich scheduled vulnerability scan completion notifications with per-severity CVE counts so recipients can triage from the message body alone. Expose the scan action in the schedule creation UI, require an explicit node_id, and harden fire-and-forget alert dispatches so a failing webhook cannot crash the scheduler. Notification body now reports scanned/skipped/failed counts plus critical/high/medium totals aggregated across fresh and cached scans, reflecting the current node posture rather than only what was newly scanned on this run.
This commit is contained in:
@@ -22,6 +22,7 @@ const ACTION_OPTIONS = [
|
||||
{ value: 'restart', label: 'Restart Stack', targetType: 'stack' as const },
|
||||
{ value: 'snapshot', label: 'Fleet Snapshot', targetType: 'fleet' as const },
|
||||
{ value: 'prune', label: 'System Prune', targetType: 'system' as const },
|
||||
{ value: 'scan', label: 'Vulnerability Scan', targetType: 'system' as const },
|
||||
];
|
||||
|
||||
interface ScheduledOperationsViewProps {
|
||||
@@ -194,6 +195,9 @@ export default function ScheduledOperationsView({ filterNodeId, onClearFilter }:
|
||||
body.target_id = formTargetId;
|
||||
body.node_id = formNodeId ? parseInt(formNodeId, 10) : null;
|
||||
}
|
||||
if (formAction === 'scan') {
|
||||
body.node_id = formNodeId ? parseInt(formNodeId, 10) : null;
|
||||
}
|
||||
if (formAction === 'prune' && formPruneTargets.length > 0) {
|
||||
body.prune_targets = formPruneTargets;
|
||||
}
|
||||
@@ -480,6 +484,19 @@ export default function ScheduledOperationsView({ filterNodeId, onClearFilter }:
|
||||
</>
|
||||
)}
|
||||
|
||||
{formAction === 'scan' && (
|
||||
<div className="space-y-2">
|
||||
<Label>Node</Label>
|
||||
<Combobox
|
||||
options={nodes.map(n => ({ value: String(n.id), label: n.name }))}
|
||||
value={formNodeId}
|
||||
onValueChange={setFormNodeId}
|
||||
placeholder="Select node..."
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">Every image on the selected node will be scanned.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formAction === 'prune' && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
@@ -531,7 +548,7 @@ export default function ScheduledOperationsView({ filterNodeId, onClearFilter }:
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setDialogOpen(false)}>Cancel</Button>
|
||||
<Button onClick={handleSave} disabled={saving || !formName || !formCron || (targetType === 'stack' && (!formTargetId || !formNodeId)) || (formAction === 'prune' && formPruneTargets.length === 0)}>
|
||||
<Button onClick={handleSave} disabled={saving || !formName || !formCron || (targetType === 'stack' && (!formTargetId || !formNodeId)) || (formAction === 'scan' && !formNodeId) || (formAction === 'prune' && formPruneTargets.length === 0)}>
|
||||
{saving ? 'Saving...' : editingTask ? 'Update' : 'Create'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
Reference in New Issue
Block a user