feat: scan details can be shared now in the URL

This commit is contained in:
Pablo Lara
2024-11-08 10:36:28 +01:00
parent fdb644fc6d
commit 39f1796da6
6 changed files with 34 additions and 134 deletions
+8 -6
View File
@@ -16,10 +16,10 @@ export default async function Scans({
}: {
searchParams: SearchParamsProps;
}) {
const searchParamsKey = JSON.stringify(searchParams || {});
// const providersData = await getProviders({
// filters: { "filter[connected]": "true" },
// });
const filteredParams = { ...searchParams };
delete filteredParams.scanId;
const searchParamsKey = JSON.stringify(filteredParams);
const providersData = await getProviders({});
const providerInfo = providersData?.data?.length
@@ -59,9 +59,11 @@ const SSRDataTableScans = async ({
const page = parseInt(searchParams.page?.toString() || "1", 10);
const sort = searchParams.sort?.toString();
// Extract all filter parameters
// Extract all filter parameters, excluding scanId
const filters = Object.fromEntries(
Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")),
Object.entries(searchParams).filter(
([key]) => key.startsWith("filter[") && key !== "scanId",
),
);
// Extract query from filters
-1
View File
@@ -1,3 +1,2 @@
export * from "./edit-scan-form";
export * from "./scan-on-demand-form";
export * from "./schedule-form";
@@ -1,126 +0,0 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { RocketIcon } from "lucide-react";
import { Dispatch, SetStateAction } from "react";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { scanOnDemand } from "@/actions/scans";
import { useToast } from "@/components/ui";
import { CustomButton, CustomInput } from "@/components/ui/custom";
import { Form } from "@/components/ui/form";
import { onDemandScanFormSchema } from "@/types";
export const ScanOnDemandForm = ({
providerId,
scanName,
scannerArgs,
setIsOpen,
}: {
providerId: string;
scanName?: string;
scannerArgs?: { checksToExecute: string[] };
setIsOpen: Dispatch<SetStateAction<boolean>>;
}) => {
const formSchema = onDemandScanFormSchema();
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
providerId: providerId,
scanName: scanName,
scannerArgs: scannerArgs,
},
});
const { toast } = useToast();
const isLoading = form.formState.isSubmitting;
const onSubmitClient = async (values: z.infer<typeof formSchema>) => {
const formData = new FormData();
// Loop through form values and add to formData, converting objects to JSON strings
Object.entries(values).forEach(
([key, value]) =>
value !== undefined &&
formData.append(
key,
typeof value === "object" ? JSON.stringify(value) : value,
),
);
const data = await scanOnDemand(formData);
if (data?.errors && data.errors.length > 0) {
const error = data.errors[0];
const errorMessage = `${error.detail}`;
// show error
toast({
variant: "destructive",
title: "Oops! Something went wrong",
description: errorMessage,
});
} else {
toast({
title: "Success!",
description: "The scan was launched successfully.",
});
setIsOpen(false);
}
};
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmitClient)}
className="flex flex-col space-y-4"
>
<input type="hidden" name="providerId" value={providerId} />
<div>
<CustomInput
control={form.control}
name="scanName"
type="text"
label="Scan Name"
labelPlacement="outside"
placeholder={scanName}
variant="bordered"
isRequired={false}
isInvalid={!!form.formState.errors.scanName}
/>
</div>
<div className="flex w-full justify-center sm:space-x-6">
<CustomButton
type="button"
ariaLabel="Cancel"
className="w-full bg-transparent"
variant="faded"
size="lg"
radius="lg"
onPress={() => setIsOpen(false)}
isDisabled={isLoading}
>
<span>Cancel</span>
</CustomButton>
<CustomButton
type="submit"
ariaLabel="Start scan now"
className="w-full"
variant="solid"
color="action"
size="lg"
isLoading={isLoading}
startContent={!isLoading && <RocketIcon size={24} />}
>
{isLoading ? <>Loading</> : <span>Start now</span>}
</CustomButton>
</div>
</form>
</Form>
);
};
@@ -1,6 +1,7 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
import { useSearchParams } from "next/navigation";
import { InfoIcon } from "@/components/icons";
import { DateWithTime, EntityInfoShort } from "@/components/ui/entities";
@@ -125,11 +126,16 @@ export const ColumnGetScans: ColumnDef<ScanProps>[] = [
id: "moreInfo",
header: "Details",
cell: ({ row }) => {
const searchParams = useSearchParams();
const scanId = searchParams.get("scanId");
const isOpen = scanId === row.original.id;
return (
<TriggerSheet
triggerComponent={<InfoIcon className="text-primary" size={16} />}
title="Scan Details"
description="View the scan details"
defaultOpen={isOpen}
>
<DataTableRowDetails entityId={row.original.id} />
</TriggerSheet>
@@ -1,5 +1,6 @@
"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import { getScan } from "@/actions/scans";
@@ -7,9 +8,25 @@ import { ScanDetail, SkeletonTableScans } from "@/components/scans/table";
import { ScanProps } from "@/types";
export const DataTableRowDetails = ({ entityId }: { entityId: string }) => {
const router = useRouter();
const searchParams = useSearchParams();
const [scanDetails, setScanDetails] = useState<ScanProps | null>(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 {
+3 -1
View File
@@ -12,6 +12,7 @@ interface TriggerSheetProps {
title: string;
description: string;
children: React.ReactNode;
defaultOpen?: boolean;
}
export function TriggerSheet({
@@ -19,9 +20,10 @@ export function TriggerSheet({
title,
description,
children,
defaultOpen = false,
}: TriggerSheetProps) {
return (
<Sheet>
<Sheet defaultOpen={defaultOpen}>
<SheetTrigger className="flex items-center gap-2">
{triggerComponent}
</SheetTrigger>