mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 20:42:02 +00:00
feat: detail view for scans has been implemented
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { add } from "date-fns";
|
||||
|
||||
import { DateWithTime, SnippetId } from "@/components/ui/entities";
|
||||
import { DataTableColumnHeader, StatusBadge } from "@/components/ui/table";
|
||||
@@ -15,10 +14,10 @@ const getProviderData = (row: { original: ProviderProps }) => {
|
||||
};
|
||||
|
||||
export const ColumnProviders: ColumnDef<ProviderProps>[] = [
|
||||
{
|
||||
header: " ",
|
||||
cell: ({ row }) => <p className="text-medium">{row.index + 1}</p>,
|
||||
},
|
||||
// {
|
||||
// header: " ",
|
||||
// cell: ({ row }) => <p className="text-medium">{row.index + 1}</p>,
|
||||
// },
|
||||
{
|
||||
accessorKey: "account",
|
||||
header: ({ column }) => (
|
||||
@@ -73,27 +72,6 @@ export const ColumnProviders: ColumnDef<ProviderProps>[] = [
|
||||
return <DateWithTime dateTime={updated_at} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "nextScan",
|
||||
header: "Next Scan",
|
||||
cell: ({ row }) => {
|
||||
const {
|
||||
attributes: { updated_at },
|
||||
} = getProviderData(row);
|
||||
const nextDay = add(new Date(updated_at), {
|
||||
hours: 24,
|
||||
});
|
||||
return <DateWithTime dateTime={nextDay.toISOString()} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "resources",
|
||||
header: "Resources",
|
||||
cell: () => {
|
||||
// Temporarily overwriting the value until the API is functional.
|
||||
return <p className="font-medium">{288}</p>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "added",
|
||||
header: ({ column }) => (
|
||||
|
||||
@@ -1,85 +1,98 @@
|
||||
"use client";
|
||||
|
||||
import { Card, CardBody, CardHeader, Chip, Divider } from "@nextui-org/react";
|
||||
import { Card, CardBody, CardHeader, Divider } from "@nextui-org/react";
|
||||
|
||||
import { DateWithTime, SnippetId } from "@/components/ui/entities";
|
||||
import { StatusBadge } from "@/components/ui/table/status-badge";
|
||||
import { ScanProps } from "@/types";
|
||||
|
||||
export const ScanDetail = ({ scanDetails }: { scanDetails: ScanProps }) => {
|
||||
const scanOnDemand = scanDetails.attributes;
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Card className="relative w-full border-small border-default-100 p-3 shadow-lg">
|
||||
<CardHeader className="flex items-center justify-between py-2">
|
||||
<h2 className="text-2xl font-bold">Scan Details</h2>
|
||||
<Chip
|
||||
color={
|
||||
scanDetails.attributes.state === "failed" ? "danger" : "success"
|
||||
}
|
||||
variant="faded"
|
||||
>
|
||||
{scanDetails.attributes.state}
|
||||
</Chip>
|
||||
</CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col items-baseline md:flex-row md:gap-x-4">
|
||||
<h2 className="text-lg font-black uppercase">Scan Details - </h2>
|
||||
<p>{scanOnDemand.name}</p>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<CardBody className="p-4">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="space-y-4">
|
||||
<DetailItem label="ID" value={scanDetails.id} />
|
||||
<DetailItem label="Name" value={scanDetails.attributes.name} />
|
||||
<DetailItem
|
||||
label="Trigger"
|
||||
value={scanDetails.attributes.trigger}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Resource Count"
|
||||
value={scanDetails.attributes.unique_resource_count.toString()}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Progress"
|
||||
value={`${scanDetails.attributes.progress}%`}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Duration"
|
||||
value={`${scanDetails.attributes.duration} seconds`}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<DetailItem
|
||||
label="Started At"
|
||||
value={new Date(
|
||||
scanDetails.attributes.started_at,
|
||||
).toLocaleString()}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Completed At"
|
||||
value={new Date(
|
||||
scanDetails.attributes.completed_at,
|
||||
).toLocaleString()}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Scheduled At"
|
||||
value={
|
||||
scanDetails.attributes.scheduled_at
|
||||
? new Date(
|
||||
scanDetails.attributes.scheduled_at,
|
||||
).toLocaleString()
|
||||
: "Not Scheduled"
|
||||
}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Provider ID"
|
||||
value={scanDetails.relationships.provider.data.id}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Task ID"
|
||||
value={scanDetails.relationships.task.data.id}
|
||||
/>
|
||||
</div>
|
||||
<StatusBadge size="lg" status={scanOnDemand.state} />
|
||||
</div>
|
||||
<Divider />
|
||||
<div className="relative z-0 flex w-full flex-col justify-between gap-4 overflow-auto rounded-large bg-content1 p-4 shadow-small">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="space-y-4">
|
||||
<DetailItem
|
||||
label="ID"
|
||||
value={<SnippetId label="Type" entityId={scanDetails.id} />}
|
||||
/>
|
||||
<DetailItem label="Trigger" value={scanOnDemand.trigger} />
|
||||
<DetailItem
|
||||
label="Resource Count"
|
||||
value={scanOnDemand.unique_resource_count.toString()}
|
||||
/>
|
||||
<DetailItem label="Progress" value={`${scanOnDemand.progress}%`} />
|
||||
<DetailItem
|
||||
label="Duration"
|
||||
value={`${scanOnDemand.duration} seconds`}
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<div className="space-y-4">
|
||||
<DateItem
|
||||
label="Started At"
|
||||
value={
|
||||
scanOnDemand.started_at ? (
|
||||
<DateWithTime dateTime={scanOnDemand.started_at.toString()} />
|
||||
) : (
|
||||
"Not Started"
|
||||
)
|
||||
}
|
||||
/>
|
||||
<DateItem
|
||||
label="Completed At"
|
||||
value={
|
||||
scanOnDemand.completed_at ? (
|
||||
<DateWithTime
|
||||
dateTime={scanOnDemand.completed_at.toString()}
|
||||
/>
|
||||
) : (
|
||||
"Not Started"
|
||||
)
|
||||
}
|
||||
/>
|
||||
<DateItem
|
||||
label="Scheduled At"
|
||||
value={
|
||||
scanOnDemand.scheduled_at ? (
|
||||
<DateWithTime
|
||||
dateTime={scanOnDemand.scheduled_at.toString()}
|
||||
/>
|
||||
) : (
|
||||
"Not Scheduled"
|
||||
)
|
||||
}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Provider ID"
|
||||
value={
|
||||
<SnippetId
|
||||
label="Provider ID"
|
||||
entityId={scanDetails.relationships.provider.data.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Task ID"
|
||||
value={
|
||||
<SnippetId
|
||||
label="Task ID"
|
||||
entityId={scanDetails.relationships.task.data.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Card className="relative w-full border-small border-default-100 p-3 shadow-lg">
|
||||
<CardHeader className="py-2">
|
||||
<h2 className="text-2xl font-bold">Scan Arguments</h2>
|
||||
@@ -91,9 +104,9 @@ export const ScanDetail = ({ scanDetails }: { scanDetails: ScanProps }) => {
|
||||
<DetailItem
|
||||
label="Checks"
|
||||
value={
|
||||
(
|
||||
scanDetails.attributes.scanner_args as any
|
||||
)?.checks_to_execute?.join(", ") || "N/A"
|
||||
(scanOnDemand.scanner_args as any)?.checks_to_execute?.join(
|
||||
", ",
|
||||
) || "N/A"
|
||||
}
|
||||
/>
|
||||
</CardBody>
|
||||
@@ -102,7 +115,26 @@ export const ScanDetail = ({ scanDetails }: { scanDetails: ScanProps }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const DetailItem = ({ label, value }: { label: string; value: string }) => (
|
||||
const DateItem = ({
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
label: string;
|
||||
value: React.ReactNode;
|
||||
}) => (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-semibold text-default-500">{label}:</span>
|
||||
<span className="text-default-700">{value}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const DetailItem = ({
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
label: string;
|
||||
value: React.ReactNode;
|
||||
}) => (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-semibold text-default-500">{label}:</span>
|
||||
<span className="text-default-700">{value}</span>
|
||||
|
||||
@@ -31,7 +31,6 @@ export function DataTableRowActions<ScanProps>({
|
||||
row,
|
||||
}: DataTableRowActionsProps<ScanProps>) {
|
||||
const [isEditOpen, setIsEditOpen] = useState(false);
|
||||
// const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
const scanId = (row.original as { id: string }).id;
|
||||
const scanName = (row.original as any).attributes?.name;
|
||||
return (
|
||||
@@ -49,16 +48,6 @@ export function DataTableRowActions<ScanProps>({
|
||||
/>
|
||||
</CustomAlertModal>
|
||||
|
||||
{/* <CustomAlertModal
|
||||
isOpen={isDeleteOpen}
|
||||
onOpenChange={setIsDeleteOpen}
|
||||
title="Are you absolutely sure?"
|
||||
description="This action cannot be undone. This will permanently delete your scan."
|
||||
> */}
|
||||
{/* <p>Hello</p> */}
|
||||
{/* <DeleteForm providerId={providerId} setIsOpen={setIsDeleteOpen} /> */}
|
||||
{/* </CustomAlertModal> */}
|
||||
|
||||
<div className="relative flex items-center justify-end gap-2">
|
||||
<Dropdown className="shadow-xl" placement="bottom">
|
||||
<DropdownTrigger>
|
||||
@@ -83,23 +72,6 @@ export function DataTableRowActions<ScanProps>({
|
||||
Edit Scan
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
{/* <DropdownSection title="Danger zone">
|
||||
<DropdownItem
|
||||
key="delete"
|
||||
className="text-danger"
|
||||
color="danger"
|
||||
description="Delete the scan permanently"
|
||||
textValue="Delete Scan"
|
||||
startContent={
|
||||
<DeleteDocumentBulkIcon
|
||||
className={clsx(iconClasses, "!text-danger")}
|
||||
/>
|
||||
}
|
||||
onClick={() => setIsDeleteOpen(true)}
|
||||
>
|
||||
Delete Scan
|
||||
</DropdownItem>
|
||||
</DropdownSection> */}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
@@ -42,37 +42,22 @@ export const ColumnGetScansSchedule: ColumnDef<ScanProps>[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "started_at",
|
||||
accessorKey: "scheduled_at",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader
|
||||
column={column}
|
||||
title={"Started at"}
|
||||
param="started_at"
|
||||
title={"Scheduled at"}
|
||||
param="scheduled_at"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const {
|
||||
attributes: { started_at },
|
||||
attributes: { scheduled_at },
|
||||
} = getScanData(row);
|
||||
return <DateWithTime dateTime={started_at} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "lastScan",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader
|
||||
column={column}
|
||||
title={"Completed At"}
|
||||
param="completed_at"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const {
|
||||
attributes: { completed_at },
|
||||
} = getScanData(row);
|
||||
return <DateWithTime dateTime={completed_at} />;
|
||||
return <DateWithTime dateTime={scheduled_at} />;
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: "moreInfo",
|
||||
header: "Details",
|
||||
|
||||
@@ -23,7 +23,7 @@ export function TriggerSheet({
|
||||
return (
|
||||
<Sheet>
|
||||
<SheetTrigger>{triggerComponent}</SheetTrigger>
|
||||
<SheetContent className="max-w-[95vw] pt-10 md:max-w-[55vw]">
|
||||
<SheetContent className="max-w-[95vw] pt-10 md:max-w-[45vw]">
|
||||
<SheetHeader>
|
||||
<SheetTitle className="sr-only">{title}</SheetTitle>
|
||||
<SheetDescription className="sr-only">{description}</SheetDescription>
|
||||
|
||||
@@ -21,15 +21,23 @@ const statusColorMap: Record<
|
||||
cancelled: "danger",
|
||||
};
|
||||
|
||||
export const StatusBadge = ({ status }: { status: Status }) => {
|
||||
export const StatusBadge = ({
|
||||
status,
|
||||
size = "sm",
|
||||
...props
|
||||
}: {
|
||||
status: Status;
|
||||
size?: "sm" | "md" | "lg";
|
||||
}) => {
|
||||
const color = statusColorMap[status as keyof typeof statusColorMap];
|
||||
|
||||
return (
|
||||
<Chip
|
||||
className="gap-1 border-none capitalize text-default-600"
|
||||
size="sm"
|
||||
size={size}
|
||||
variant="flat"
|
||||
color={color}
|
||||
{...props}
|
||||
>
|
||||
{status}
|
||||
</Chip>
|
||||
|
||||
Reference in New Issue
Block a user