diff --git a/ui/components/scans/auto-refresh.tsx b/ui/components/scans/auto-refresh.tsx
index 218d5ccc32..fa2c93424f 100644
--- a/ui/components/scans/auto-refresh.tsx
+++ b/ui/components/scans/auto-refresh.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useRouter } from "next/navigation";
+import { useRouter, useSearchParams } from "next/navigation";
import { useEffect } from "react";
interface AutoRefreshProps {
@@ -9,16 +9,21 @@ interface AutoRefreshProps {
export function AutoRefresh({ hasExecutingScan }: AutoRefreshProps) {
const router = useRouter();
+ const searchParams = useSearchParams();
useEffect(() => {
if (!hasExecutingScan) return;
+ // Don't auto-refresh if scan details drawer is open
+ const scanId = searchParams.get("scanId");
+ if (scanId) return;
+
const interval = setInterval(() => {
router.refresh();
}, 5000);
return () => clearInterval(interval);
- }, [hasExecutingScan, router]);
+ }, [hasExecutingScan, router, searchParams]);
return null;
}
diff --git a/ui/components/scans/table/scans/column-get-scans.tsx b/ui/components/scans/table/scans/column-get-scans.tsx
index 918d2a80c1..f3f2e3ac62 100644
--- a/ui/components/scans/table/scans/column-get-scans.tsx
+++ b/ui/components/scans/table/scans/column-get-scans.tsx
@@ -2,7 +2,7 @@
import { Tooltip } from "@heroui/tooltip";
import { ColumnDef } from "@tanstack/react-table";
-import { useSearchParams } from "next/navigation";
+import { useRouter, useSearchParams } from "next/navigation";
import { InfoIcon } from "@/components/icons";
import { TableLink } from "@/components/ui/custom";
@@ -21,19 +21,44 @@ const getScanData = (row: { original: ScanProps }) => {
};
const ScanDetailsCell = ({ row }: { row: any }) => {
+ const router = useRouter();
const searchParams = useSearchParams();
const scanId = searchParams.get("scanId");
const isOpen = scanId === row.original.id;
+ const scanState = row.original.attributes?.state;
+ const isExecuting = scanState === "executing" || scanState === "available";
+
+ const handleOpenChange = (open: boolean) => {
+ if (isExecuting) return;
+
+ const params = new URLSearchParams(searchParams.toString());
+
+ if (open) {
+ params.set("scanId", row.original.id);
+ } else {
+ params.delete("scanId");
+ }
+
+ router.push(`?${params.toString()}`, { scroll: false });
+ };
return (
}
+ triggerComponent={
+
+ }
title="Scan Details"
description="View the scan details"
- defaultOpen={isOpen}
+ open={isOpen}
+ onOpenChange={handleOpenChange}
>
-
+ {isOpen && }
);
diff --git a/ui/components/scans/table/scans/data-table-row-details.tsx b/ui/components/scans/table/scans/data-table-row-details.tsx
index 307f7676f5..1b15a665ab 100644
--- a/ui/components/scans/table/scans/data-table-row-details.tsx
+++ b/ui/components/scans/table/scans/data-table-row-details.tsx
@@ -1,36 +1,20 @@
"use client";
-import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import { getProvider } from "@/actions/providers";
import { getScan } from "@/actions/scans";
import { getTask } from "@/actions/task";
import { ScanDetail } from "@/components/scans/table";
-import { Alert } from "@/components/ui/alert/Alert";
import { checkTaskStatus } from "@/lib";
import { ScanProps } from "@/types";
+import { SkeletonScanDetail } from "./skeleton-scan-detail";
+
export const DataTableRowDetails = ({ entityId }: { entityId: string }) => {
- const router = useRouter();
- const searchParams = useSearchParams();
const [scanDetails, setScanDetails] = useState(null);
const [isLoading, setIsLoading] = useState(true);
- useEffect(() => {
- // Add scanId to URL
- const params = new URLSearchParams(searchParams.toString());
- params.set("scanId", entityId);
- router.push(`?${params.toString()}`, { scroll: false });
-
- // Cleanup function: remove scanId from URL when component unmounts
- return () => {
- const newParams = new URLSearchParams(searchParams.toString());
- newParams.delete("scanId");
- router.push(`?${newParams.toString()}`, { scroll: false });
- };
- }, [entityId, router, searchParams]);
-
useEffect(() => {
const fetchScanDetails = async () => {
try {
@@ -76,12 +60,7 @@ export const DataTableRowDetails = ({ entityId }: { entityId: string }) => {
}, [entityId]);
if (isLoading) {
- return (
-
- Scan details are loading and will be available once the scan is
- completed.
-
- );
+ return ;
}
if (!scanDetails) {
diff --git a/ui/components/scans/table/scans/skeleton-scan-detail.tsx b/ui/components/scans/table/scans/skeleton-scan-detail.tsx
new file mode 100644
index 0000000000..5e69becb48
--- /dev/null
+++ b/ui/components/scans/table/scans/skeleton-scan-detail.tsx
@@ -0,0 +1,58 @@
+export const SkeletonScanDetail = () => {
+ return (
+
+ {/* Header Skeleton */}
+
+
+ {/* Scan Details Section Skeleton */}
+
+
+
+ {/* First grid row */}
+
+ {Array.from({ length: 3 }).map((_, index) => (
+
+ ))}
+
+
+ {/* Second grid row */}
+
+ {Array.from({ length: 3 }).map((_, index) => (
+
+ ))}
+
+
+ {/* Scan ID field */}
+
+
+ {/* Third grid row */}
+
+ {Array.from({ length: 3 }).map((_, index) => (
+
+ ))}
+
+
+
+ );
+};
diff --git a/ui/components/ui/sheet/trigger-sheet.tsx b/ui/components/ui/sheet/trigger-sheet.tsx
index dcb8e793f4..fb51a65c47 100644
--- a/ui/components/ui/sheet/trigger-sheet.tsx
+++ b/ui/components/ui/sheet/trigger-sheet.tsx
@@ -12,6 +12,7 @@ interface TriggerSheetProps {
title: string;
description: string;
children: React.ReactNode;
+ open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
}
@@ -21,11 +22,12 @@ export function TriggerSheet({
title,
description,
children,
+ open,
defaultOpen = false,
onOpenChange,
}: TriggerSheetProps) {
return (
-
+
{triggerComponent}