mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
feat: add new functionality for adding provider and remove the old one
This commit is contained in:
@@ -117,15 +117,14 @@ export const updateProvider = async (formData: FormData) => {
|
||||
};
|
||||
|
||||
export const addProvider = async (formData: FormData) => {
|
||||
console.log(formData);
|
||||
const session = await auth();
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
|
||||
const tenantId = session?.user.tenantId;
|
||||
|
||||
const provider = formData.get("provider");
|
||||
const providerType = formData.get("providerType");
|
||||
const providerId = formData.get("providerId");
|
||||
const alias = formData.get("alias");
|
||||
const providerAlias = formData.get("providerAlias");
|
||||
|
||||
const url = new URL(`${keyServer}/providers`);
|
||||
|
||||
@@ -141,9 +140,9 @@ export const addProvider = async (formData: FormData) => {
|
||||
data: {
|
||||
type: "Provider",
|
||||
attributes: {
|
||||
provider: provider,
|
||||
provider: providerType,
|
||||
uid: providerId,
|
||||
alias: alias,
|
||||
alias: providerAlias,
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Suspense } from "react";
|
||||
|
||||
import { getProviders } from "@/actions";
|
||||
import { FilterControls } from "@/components/filters";
|
||||
import { AddProviderModal } from "@/components/providers";
|
||||
import { AddProvider } from "@/components/providers";
|
||||
import {
|
||||
ColumnsProvider,
|
||||
DataTableProvider,
|
||||
@@ -22,18 +22,18 @@ export default async function Providers({
|
||||
return (
|
||||
<>
|
||||
<Header title="Providers" icon="fluent:cloud-sync-24-regular" />
|
||||
|
||||
<Spacer y={4} />
|
||||
<FilterControls search providers />
|
||||
<Spacer y={4} />
|
||||
<div className="flex flex-col items-end w-full">
|
||||
<div className="flex space-x-6">
|
||||
<AddProviderModal />
|
||||
</div>
|
||||
<Spacer y={6} />
|
||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableProvider />}>
|
||||
<SSRDataTable searchParams={searchParams} />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
<AddProvider />
|
||||
<Spacer y={4} />
|
||||
|
||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableProvider />}>
|
||||
<SSRDataTable searchParams={searchParams} />
|
||||
</Suspense>
|
||||
{/* <NewTable /> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -757,3 +757,29 @@ export const SaveIcon: React.FC<IconSvgProps> = ({
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const AddIcon: React.FC<IconSvgProps> = ({
|
||||
size,
|
||||
height,
|
||||
width,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
height={size || height || 20}
|
||||
viewBox="0 0 24 24"
|
||||
width={size || width || 20}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10m.75-13a.75.75 0 0 0-1.5 0v2.25H9a.75.75 0 0 0 0 1.5h2.25V15a.75.75 0 0 0 1.5 0v-2.25H15a.75.75 0 0 0 0-1.5h-2.25z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useRef } from "react";
|
||||
|
||||
import { addProvider } from "@/actions";
|
||||
|
||||
import { useToast } from "../ui/toast";
|
||||
import { ButtonAddProvider } from "./ButtonAddProvider";
|
||||
|
||||
export const AddProvider = () => {
|
||||
const ref = useRef<HTMLFormElement>(null);
|
||||
const { toast } = useToast();
|
||||
|
||||
async function clientAction(formData: FormData) {
|
||||
// reset the form
|
||||
ref.current?.reset();
|
||||
// client-side validation
|
||||
const data = await addProvider(formData);
|
||||
if (data?.errors) {
|
||||
data.errors.forEach((error: { detail: string }) => {
|
||||
const errorMessage = `${error.detail}`;
|
||||
// show error
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Oops! Something went wrong",
|
||||
description: errorMessage,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: "Success!",
|
||||
description: "The provider was added successfully.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form ref={ref} action={clientAction} className="flex gap-x-2">
|
||||
<input
|
||||
type="text"
|
||||
name="provider"
|
||||
placeholder="Provider"
|
||||
aria-label="Provider"
|
||||
className="py-2 px-3 rounded-sm"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="id"
|
||||
placeholder="Provider ID"
|
||||
aria-label="Provider ID"
|
||||
className="py-2 px-3 rounded-sm"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="alias"
|
||||
placeholder="Alias"
|
||||
aria-label="Alias"
|
||||
className="py-2 px-3 rounded-sm"
|
||||
/>
|
||||
<ButtonAddProvider />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -1,102 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Button, Input } from "@nextui-org/react";
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { addProvider } from "@/actions";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
useToast,
|
||||
} from "@/components/ui";
|
||||
|
||||
import { ButtonAddProvider } from "./ButtonAddProvider";
|
||||
import { CustomRadioProvider } from "./CustomRadioProvider";
|
||||
|
||||
export const AddProviderModal = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const ref = useRef<HTMLFormElement>(null);
|
||||
const { toast } = useToast();
|
||||
|
||||
async function clientAction(formData: FormData) {
|
||||
// reset the form
|
||||
ref.current?.reset();
|
||||
// client-side validation
|
||||
const data = await addProvider(formData);
|
||||
if (data?.errors) {
|
||||
data.errors.forEach((error: { detail: string }) => {
|
||||
const errorMessage = `${error.detail}`;
|
||||
// show error
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Oops! Something went wrong",
|
||||
description: errorMessage,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: "Success!",
|
||||
description: "The provider was added successfully.",
|
||||
});
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button aria-label="Add Cloud Account" variant="ghost">
|
||||
Add Cloud Account
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="flex flex-col sm:max-w-md md:max-w-4xl">
|
||||
<DialogHeader className="mb-6 space-y-3">
|
||||
<DialogTitle className="text-2xl text-center">
|
||||
Add cloud account
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-md">
|
||||
You must manually deploy a new read-only IAM role for each account
|
||||
you want to add. The following links will provide detailed
|
||||
instructions how to do this:
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form
|
||||
ref={ref}
|
||||
action={clientAction}
|
||||
onSubmit={() => setOpen(false)}
|
||||
className="grid sm:grid-cols-2 gap-6"
|
||||
>
|
||||
<div className="col-span-1">
|
||||
<CustomRadioProvider />
|
||||
</div>
|
||||
<div className="col-span-1 flex flex-col gap-y-2 my-auto">
|
||||
<Input
|
||||
type="text"
|
||||
name="id"
|
||||
label="Provider ID"
|
||||
labelPlacement="outside"
|
||||
placeholder="Provider ID"
|
||||
className="w-full rounded-sm"
|
||||
aria-label="Enter Provider ID"
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
name="alias"
|
||||
label="Alias"
|
||||
labelPlacement="outside"
|
||||
placeholder="alias"
|
||||
className="w-full rounded-sm"
|
||||
aria-label="Enter Provider alias"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 flex justify-center mt-4">
|
||||
<ButtonAddProvider />
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Button, CircularProgress } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
import { useFormStatus } from "react-dom";
|
||||
|
||||
export const ButtonAddProvider = () => {
|
||||
const { pending } = useFormStatus();
|
||||
return (
|
||||
<Button type="submit" area-disabled={pending}>
|
||||
{pending ? <CircularProgress aria-label="Loading..." /> : "Add provider"}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -3,9 +3,15 @@
|
||||
import { UseRadioProps } from "@nextui-org/radio/dist/use-radio";
|
||||
import { cn, RadioGroup, useRadio, VisuallyHidden } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { addProviderFormSchema } from "@/types";
|
||||
|
||||
import { AWSProviderBadge, AzureProviderBadge } from "../icons/providers-badge";
|
||||
import { GCPProviderBadge } from "../icons/providers-badge/GCPProviderBadge";
|
||||
import { KS8ProviderBadge } from "../icons/providers-badge/KS8ProviderBadge";
|
||||
import { FormMessage } from "../ui/form";
|
||||
|
||||
interface CustomRadioProps extends UseRadioProps {
|
||||
description?: string;
|
||||
@@ -52,27 +58,48 @@ export const CustomRadio: React.FC<CustomRadioProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomRadioProvider = () => {
|
||||
interface CustomRadioProviderProps {
|
||||
control: Control<z.infer<typeof addProviderFormSchema>>;
|
||||
}
|
||||
|
||||
export const CustomRadioProvider: React.FC<CustomRadioProviderProps> = ({
|
||||
control,
|
||||
}) => {
|
||||
return (
|
||||
<RadioGroup label="Select one provider" name="provider">
|
||||
<CustomRadio description="Amazon Web Services" value="aws">
|
||||
<div className="flex items-center">
|
||||
<AWSProviderBadge size={26} />
|
||||
<span className="ml-2">AWS</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="Google Cloud Platform" value="gcp">
|
||||
<div className="flex items-center">
|
||||
<GCPProviderBadge size={26} />
|
||||
<span className="ml-2">GCP</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="Microsoft Azure" value="azure">
|
||||
<div className="flex items-center">
|
||||
<AzureProviderBadge size={26} />
|
||||
<span className="ml-2">Azure</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</RadioGroup>
|
||||
<Controller
|
||||
name="providerType"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<RadioGroup label="Select one provider" {...field}>
|
||||
<CustomRadio description="Amazon Web Services" value="aws">
|
||||
<div className="flex items-center">
|
||||
<AWSProviderBadge size={26} />
|
||||
<span className="ml-2">AWS</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="Google Cloud Platform" value="gcp">
|
||||
<div className="flex items-center">
|
||||
<GCPProviderBadge size={26} />
|
||||
<span className="ml-2">GCP</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="Microsoft Azure" value="azure">
|
||||
<div className="flex items-center">
|
||||
<AzureProviderBadge size={26} />
|
||||
<span className="ml-2">Azure</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="Kubernetes" value="kubernetes">
|
||||
<div className="flex items-center">
|
||||
<KS8ProviderBadge size={26} />
|
||||
<span className="ml-2">Kubernetes</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</RadioGroup>
|
||||
<FormMessage className="text-system-error dark:text-system-error" />
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { Chip } from "@nextui-org/react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { AddIcon } from "../icons";
|
||||
import { CustomAlertModal, CustomButton } from "../ui/custom";
|
||||
import { AddForm } from "./forms";
|
||||
|
||||
export const AddProvider = () => {
|
||||
const [isAddOpen, setIsAddOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomAlertModal
|
||||
isOpen={isAddOpen}
|
||||
onOpenChange={setIsAddOpen}
|
||||
title="Add Cloud Provider"
|
||||
description={
|
||||
"You must manually deploy a new read-only IAM role for each account you want to add. The following links will provide detailed instructions how to do this:"
|
||||
}
|
||||
>
|
||||
<AddForm setIsOpen={setIsAddOpen} />
|
||||
</CustomAlertModal>
|
||||
|
||||
<div className="w-full flex items-center justify-between">
|
||||
<div className="flex w-fit items-center gap-2">
|
||||
<h1 className="text-2xl font-[700] leading-[32px]">TBD</h1>
|
||||
<Chip
|
||||
className="hidden items-center text-default-500 sm:flex"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
3
|
||||
</Chip>
|
||||
</div>
|
||||
<CustomButton
|
||||
variant="solid"
|
||||
color="action"
|
||||
size="md"
|
||||
onPress={() => setIsAddOpen(true)}
|
||||
endContent={<AddIcon size={20} />}
|
||||
>
|
||||
Add Account
|
||||
</CustomButton>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
import { addProvider } from "@/actions";
|
||||
import { SaveIcon } from "@/components/icons";
|
||||
import { useToast } from "@/components/ui";
|
||||
import { CustomButton, CustomInput } from "@/components/ui/custom";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { addProviderFormSchema } from "@/types";
|
||||
|
||||
import { CustomRadioProvider } from "../CustomRadioProvider";
|
||||
|
||||
export const AddForm = ({
|
||||
setIsOpen,
|
||||
}: {
|
||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
||||
}) => {
|
||||
const formSchema = addProviderFormSchema;
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
providerType: "",
|
||||
providerId: "",
|
||||
providerAlias: "",
|
||||
},
|
||||
});
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const isLoading = form.formState.isSubmitting;
|
||||
|
||||
const onSubmitClient = async (values: z.infer<typeof formSchema>) => {
|
||||
console.log(values);
|
||||
const formData = new FormData();
|
||||
|
||||
Object.entries(values).forEach(
|
||||
([key, value]) => value !== undefined && formData.append(key, value),
|
||||
);
|
||||
|
||||
const data = await addProvider(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 provider was updated successfully.",
|
||||
});
|
||||
setIsOpen(false); // Close the modal on success
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmitClient)}
|
||||
className="flex flex-col space-y-4"
|
||||
>
|
||||
<CustomRadioProvider control={form.control} />
|
||||
<CustomInput
|
||||
control={form.control}
|
||||
name="providerId"
|
||||
type="text"
|
||||
label="Provider ID"
|
||||
labelPlacement="inside"
|
||||
placeholder={"Enter the provider ID"}
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!form.formState.errors.providerId}
|
||||
/>
|
||||
<CustomInput
|
||||
control={form.control}
|
||||
name="providerAlias"
|
||||
type="text"
|
||||
label="Alias"
|
||||
labelPlacement="inside"
|
||||
placeholder={"Enter the provider alias"}
|
||||
variant="bordered"
|
||||
isRequired={false}
|
||||
isInvalid={!!form.formState.errors.providerAlias}
|
||||
/>
|
||||
|
||||
<div className="w-full flex justify-center sm:space-x-6">
|
||||
<CustomButton
|
||||
type="button"
|
||||
className="w-full bg-transparent"
|
||||
variant="faded"
|
||||
size="lg"
|
||||
radius="lg"
|
||||
onPress={() => setIsOpen(false)}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<span>Cancel</span>
|
||||
</CustomButton>
|
||||
|
||||
<CustomButton
|
||||
type="submit"
|
||||
className="w-full"
|
||||
variant="solid"
|
||||
color="action"
|
||||
size="lg"
|
||||
isLoading={isLoading}
|
||||
startContent={!isLoading && <SaveIcon size={24} />}
|
||||
>
|
||||
{isLoading ? <>Loading</> : <span>Confirm</span>}
|
||||
</CustomButton>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./AddForm";
|
||||
export * from "./DeleteForm";
|
||||
export * from "./EditForm";
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
export * from "./AddProvider";
|
||||
export * from "./AddProviderModal";
|
||||
export * from "./ButtonAddProvider";
|
||||
export * from "./add-provider";
|
||||
export * from "./CheckConnectionProvider";
|
||||
export * from "./CustomRadioProvider";
|
||||
export * from "./DateWithTime";
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@nextui-org/react";
|
||||
// import {
|
||||
// Button,
|
||||
// Input,
|
||||
// Popover,
|
||||
// PopoverContent,
|
||||
// PopoverTrigger,
|
||||
// RadioGroup,
|
||||
// } from "@nextui-org/react";
|
||||
// import { Icon, Radio, SearchIcon } from "lucide-react";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
Input,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@nextui-org/react";
|
||||
import { SearchIcon } from "lucide-react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useMemo } from "react";
|
||||
|
||||
import { CustomFilterIcon, PlusCircleIcon } from "@/components/icons";
|
||||
import { CustomButton } from "@/components/ui/custom";
|
||||
|
||||
interface DataTableFilterCustomProps {
|
||||
filters: { key: string; values: string[] }[];
|
||||
}
|
||||
@@ -53,7 +56,7 @@ export function DataTableFilterCustom({ filters }: DataTableFilterCustomProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <div className="flex items-center gap-4 overflow-auto px-[6px] py-[4px]">
|
||||
<div className="flex items-center gap-4 overflow-auto px-[6px] py-[4px]">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<Input
|
||||
@@ -70,60 +73,50 @@ export function DataTableFilterCustom({ filters }: DataTableFilterCustomProps) {
|
||||
<div>
|
||||
<Popover placement="bottom">
|
||||
<PopoverTrigger>
|
||||
<Button className="bg-default-100 text-default-800" size="sm">
|
||||
<Button
|
||||
className="bg-default-100 text-default-800"
|
||||
startContent={
|
||||
<PlusCircleIcon className="text-default-400" width={16} />
|
||||
}
|
||||
size="sm"
|
||||
>
|
||||
Filter
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80">
|
||||
<div className="flex w-full flex-col gap-6 px-2 py-4">
|
||||
<RadioGroup
|
||||
label="Worker Type"
|
||||
// value={workerTypeFilter}
|
||||
// onValueChange={setWorkerTypeFilter}
|
||||
<CheckboxGroup
|
||||
label="Select cities"
|
||||
defaultValue={["buenos-aires", "london"]}
|
||||
>
|
||||
<Radio value="all">All</Radio>
|
||||
<Radio value="employee">Employee</Radio>
|
||||
<Radio value="contractor">Contractor</Radio>
|
||||
</RadioGroup>
|
||||
|
||||
<RadioGroup
|
||||
label="Status"
|
||||
// value={statusFilter}
|
||||
// onValueChange={setStatusFilter}
|
||||
>
|
||||
<Radio value="all">All</Radio>
|
||||
<Radio value="active">Active</Radio>
|
||||
<Radio value="inactive">Inactive</Radio>
|
||||
<Radio value="paused">Paused</Radio>
|
||||
<Radio value="vacation">Vacation</Radio>
|
||||
</RadioGroup>
|
||||
|
||||
<RadioGroup
|
||||
label="Start Date"
|
||||
// value={startDateFilter}
|
||||
// onValueChange={setStartDateFilter}
|
||||
>
|
||||
<Radio value="all">All</Radio>
|
||||
<Radio value="last7Days">Last 7 days</Radio>
|
||||
<Radio value="last30Days">Last 30 days</Radio>
|
||||
<Radio value="last60Days">Last 60 days</Radio>
|
||||
</RadioGroup>
|
||||
<Checkbox value="buenos-aires">Buenos Aires</Checkbox>
|
||||
<Checkbox value="sydney">Sydney</Checkbox>
|
||||
<Checkbox value="san-francisco">San Francisco</Checkbox>
|
||||
<Checkbox value="london">London</Checkbox>
|
||||
<Checkbox value="tokyo">Tokyo</Checkbox>
|
||||
</CheckboxGroup>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center space-x-2">
|
||||
<CustomButton
|
||||
variant="dashed"
|
||||
size="sm"
|
||||
startContent={<CustomFilterIcon size={16} />}
|
||||
>
|
||||
Show Filters
|
||||
</CustomButton>
|
||||
{filters.flatMap(({ key, values }) =>
|
||||
values.map((value) => (
|
||||
<Button
|
||||
key={`${key}-${value}`}
|
||||
onClick={() => applyFilter(key, value)}
|
||||
onPress={() => applyFilter(key, value)}
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
variant={activeFilters[key] === value ? "faded" : "light"}
|
||||
size="sm"
|
||||
>
|
||||
{value || "All"}
|
||||
</Button>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { Button, Chip } from "@nextui-org/react";
|
||||
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
@@ -12,7 +11,7 @@ import {
|
||||
SortingState,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import {
|
||||
Table,
|
||||
@@ -65,33 +64,9 @@ export function DataTableProvider<TData, TValue>({
|
||||
{ key: "connected", values: ["false", "true"] },
|
||||
// Add more filter categories as needed
|
||||
];
|
||||
const topBar = useMemo(() => {
|
||||
return (
|
||||
<div className="mb-[18px] w-full flex items-center justify-between">
|
||||
<div className="flex w-fit items-center gap-2">
|
||||
<h1 className="text-2xl font-[700] leading-[32px]">Providers</h1>
|
||||
<Chip
|
||||
className="hidden items-center text-default-500 sm:flex"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
3
|
||||
</Chip>
|
||||
</div>
|
||||
<Button
|
||||
color="primary"
|
||||
endContent={<Icon icon="solar:add-circle-bold" width={20} />}
|
||||
>
|
||||
Add Account
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{topBar}
|
||||
|
||||
<DataTableFilterCustom filters={filters} />
|
||||
|
||||
<div className="p-4 z-0 flex flex-col relative justify-between gap-4 bg-content1 overflow-auto rounded-large shadow-small w-full ">
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ export interface ProviderProps {
|
||||
id: string;
|
||||
type: "providers";
|
||||
attributes: {
|
||||
provider: "aws" | "azure" | "gcp";
|
||||
provider: "aws" | "azure" | "gcp" | "kubernetes";
|
||||
uid: string;
|
||||
alias: string;
|
||||
status: "completed" | "pending" | "cancelled";
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const addProviderFormSchema = z.object({
|
||||
providerType: z.string(),
|
||||
providerAlias: z.string(),
|
||||
providerId: z.string(),
|
||||
});
|
||||
|
||||
export const editProviderFormSchema = (currentAlias: string) =>
|
||||
z.object({
|
||||
alias: z
|
||||
|
||||
Reference in New Issue
Block a user