From 7190f942b87295d13ae681d3b7c3756bc91b09bb Mon Sep 17 00:00:00 2001 From: Haksung Jang Date: Fri, 14 Aug 2026 13:42:54 +0900 Subject: [PATCH] feat(web): confirm a scan delete and trap focus in modals Deleting a scan removed its output folder on a single click, from both the scan table and the top-bar menu, and the files leave the disk with no copy kept. Both paths now go through a confirm dialog that names the scan, opens focus on Cancel, and reports the delete once it happens. The prompt comes from a shared Modal that owns the focus contract: focus enters the panel on open, Tab cycles inside it, Escape and a backdrop click close it, and focus returns to the control that opened it. FileViewer declared a focus trap in a comment but only handled Escape; it now uses the shared one. --- CHANGELOG.md | 4 + .../frontend/src/components/FileViewer.tsx | 90 ++++----- .../web/frontend/src/components/NextApp.tsx | 39 +++- .../web/frontend/src/components/ui/dialog.tsx | 172 ++++++++++++++++++ docker/web/frontend/src/lib/focus.test.ts | 41 +++++ docker/web/frontend/src/lib/focus.ts | 43 +++++ .../web/frontend/src/locales/en/common.json | 6 + .../web/frontend/src/locales/ko/common.json | 6 + docker/web/frontend/tests/ui/dialog.spec.ts | 171 +++++++++++++++++ docker/web/frontend/tests/ui/shell.spec.ts | 4 +- docs/reference/ui.ko.md | 2 +- docs/reference/ui.md | 2 +- 12 files changed, 521 insertions(+), 59 deletions(-) create mode 100644 docker/web/frontend/src/components/ui/dialog.tsx create mode 100644 docker/web/frontend/src/lib/focus.test.ts create mode 100644 docker/web/frontend/src/lib/focus.ts create mode 100644 docker/web/frontend/tests/ui/dialog.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f026c7e..bc9e9511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Deleting a scan asks for confirmation first, naming the scan it is about to remove. The delete control in the scan table and the one in the top bar's scan menu both removed a scan's output folder on a single click, and because the files are gone from disk with no copy kept, a mis-click could not be taken back. The prompt opens on Cancel, and a confirmed delete says so. Modal dialogs also hold the keyboard now: focus moves into the panel when one opens, Tab stays inside it, and it returns to whatever opened the dialog on close. + ### Added - A scanned AI model file is checked for whether loading it runs code. Pickle-format weights (`.pkl`, and the pickle inside a PyTorch archive or an `object`-dtype `.npz` member) are analyzed with picklescan, now installed in the base image, and the verdict feeds the file-security axis of the model risk assessment: a dangerous global reads `caution`, globals that need a human reads `review`, and a format that cannot execute code on load reads `ok`. BomLens reported this for models on HuggingFace by reading the Hub's own scan; a file that was never published had no such record and therefore no verdict at all. A clean result states its scope — it is a pickle analysis, not a malware scan — and a scan that could not run leaves no security axis rather than implying the file is safe. diff --git a/docker/web/frontend/src/components/FileViewer.tsx b/docker/web/frontend/src/components/FileViewer.tsx index 4de7a40e..344a022c 100644 --- a/docker/web/frontend/src/components/FileViewer.tsx +++ b/docker/web/frontend/src/components/FileViewer.tsx @@ -6,6 +6,7 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; +import { Modal } from "@/components/ui/dialog"; import { fileUrl } from "@/lib/api"; interface Props { @@ -18,8 +19,7 @@ interface Props { /** * Lightweight modal artifact viewer. HTML reports render in an iframe (so the * report's own styles apply); JSON is pretty-printed; text/markdown shown raw. - * No @radix-ui/react-dialog dependency — a focus-trapped overlay is enough for - * this single-purpose viewer. + * The overlay, focus handling and Escape come from the shared Modal. */ export function FileViewer({ name, scanId, onClose }: Props) { const { t } = useTranslation(); @@ -42,14 +42,6 @@ export function FileViewer({ name, scanId, onClose }: Props) { }; }, [name, scanId, isHtml]); - useEffect(() => { - const onKey = (e: KeyboardEvent) => { - if (e.key === "Escape") onClose(); - }; - if (name) window.addEventListener("keydown", onKey); - return () => window.removeEventListener("keydown", onKey); - }, [name, onClose]); - if (!name) return null; let body = text; @@ -62,50 +54,44 @@ export function FileViewer({ name, scanId, onClose }: Props) { } return ( -
-
-
-
- {name} - -
-
- {isHtml ? ( -