mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 20:42:02 +00:00
feat: the Providers table is rendering real data
This commit is contained in:
@@ -1,20 +1,11 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
import { CustomTable, Header, ModalWrap } from "@/components";
|
||||
import { ColumnsProviders, DataTable, Header, ModalWrap } from "@/components";
|
||||
import { getProvider } from "@/lib/actions";
|
||||
|
||||
const visibleColumns: string[] = [
|
||||
"account",
|
||||
"group",
|
||||
"scan_status",
|
||||
"last_scan",
|
||||
"next_scan",
|
||||
"resources",
|
||||
"added",
|
||||
"actions",
|
||||
];
|
||||
|
||||
export default function Providers() {
|
||||
export default async function Providers() {
|
||||
const providers = await getProvider();
|
||||
const onSave = async () => {
|
||||
"use server";
|
||||
// event we want to pass down, ex. console.log("### hello");
|
||||
@@ -24,23 +15,21 @@ export default function Providers() {
|
||||
<>
|
||||
<Header title="Providers" icon="fluent:cloud-sync-24-regular" />
|
||||
<Spacer />
|
||||
<CustomTable
|
||||
initialVisibleColumns={visibleColumns}
|
||||
initialRowsPerPage={10}
|
||||
selectionMode={"none"}
|
||||
/>
|
||||
<Spacer />
|
||||
<ModalWrap
|
||||
modalTitle="Modal Title"
|
||||
modalBody={
|
||||
<>
|
||||
<p>Modal body content</p>
|
||||
</>
|
||||
}
|
||||
actionButtonLabel="Save"
|
||||
onAction={onSave}
|
||||
openButtonLabel="Open Modal"
|
||||
/>
|
||||
<div className="flex flex-col items-end w-full">
|
||||
<ModalWrap
|
||||
modalTitle="Modal Title"
|
||||
modalBody={
|
||||
<>
|
||||
<p>Modal body content</p>
|
||||
</>
|
||||
}
|
||||
actionButtonLabel="Save"
|
||||
onAction={onSave}
|
||||
openButtonLabel="Add Cloud Accounts"
|
||||
/>
|
||||
<Spacer y={6} />
|
||||
<DataTable columns={ColumnsProviders} data={providers.providers.data} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
export * from "./providers/DateWithTime";
|
||||
export * from "./providers/ProviderInfo";
|
||||
export * from "./providers/ScanStatus";
|
||||
export * from "./providers/table/columns";
|
||||
export * from "./providers/table/ColumnsProviders";
|
||||
export * from "./providers/table/DataTable";
|
||||
export * from "./ui/header/Header";
|
||||
export * from "./ui/modal";
|
||||
|
||||
@@ -16,7 +16,7 @@ export const DateWithTime: React.FC<DateWithTimeProps> = ({
|
||||
|
||||
return (
|
||||
<div className="max-w-fit">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex flex-col items-start">
|
||||
<span className="text-md font-semibold">{formattedDate}</span>
|
||||
{showTime && (
|
||||
<span className="text-sm text-gray-500">{formattedTime}</span>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownTrigger,
|
||||
} from "@nextui-org/react";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { add } from "date-fns";
|
||||
|
||||
import { VerticalDotsIcon } from "@/components/icons";
|
||||
import { StatusBadge } from "@/components/ui/table/StatusBadge";
|
||||
import { ProviderProps } from "@/types";
|
||||
|
||||
import { DateWithTime } from "../DateWithTime";
|
||||
import { ProviderInfo } from "../ProviderInfo";
|
||||
|
||||
export const ColumnsProviders: ColumnDef<ProviderProps>[] = [
|
||||
{
|
||||
header: "ID",
|
||||
cell: ({ row }) => <p className="text-medium">{row.index + 1}</p>,
|
||||
},
|
||||
{
|
||||
accessorKey: "account",
|
||||
header: "Account",
|
||||
cell: ({ row }) => {
|
||||
const provider = row.original;
|
||||
return (
|
||||
<ProviderInfo
|
||||
connected={provider.attributes.connection.connected}
|
||||
provider={provider.attributes.provider}
|
||||
providerAlias={provider.attributes.alias}
|
||||
providerId={provider.attributes.provider_id}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: "Scan Status",
|
||||
cell: ({ row }) => {
|
||||
const provider = row.original;
|
||||
return <StatusBadge status={provider.attributes.status} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "lastScan",
|
||||
header: "Last Scan",
|
||||
cell: ({ row }) => {
|
||||
const provider = row.original;
|
||||
return <DateWithTime dateTime={provider.attributes.updated_at} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "nextScan",
|
||||
header: "Next Scan",
|
||||
cell: ({ row }) => {
|
||||
const provider = row.original;
|
||||
const nextDay = add(new Date(provider.attributes.updated_at), {
|
||||
hours: 24,
|
||||
});
|
||||
return <DateWithTime dateTime={nextDay.toISOString()} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "resources",
|
||||
header: "Resources",
|
||||
cell: ({ row }) => {
|
||||
const provider = row.original;
|
||||
return <p className="font-medium">{provider.attributes.resources}</p>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "added",
|
||||
header: "Added",
|
||||
cell: ({ row }) => {
|
||||
const provider = row.original;
|
||||
return (
|
||||
<DateWithTime
|
||||
dateTime={provider.attributes.inserted_at}
|
||||
showTime={false}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "actions",
|
||||
header: () => <div className="text-right">Actions</div>,
|
||||
id: "actions",
|
||||
cell: () => {
|
||||
// const provider = row.original;
|
||||
return (
|
||||
<div className="relative flex justify-end items-center gap-2">
|
||||
<Dropdown className="bg-background border-1 border-default-200">
|
||||
<DropdownTrigger>
|
||||
<Button isIconOnly radius="full" size="sm" variant="light">
|
||||
<VerticalDotsIcon className="text-default-400" />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu>
|
||||
<DropdownItem>Manage</DropdownItem>
|
||||
<DropdownItem>Delete</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,109 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@nextui-org/react";
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table/Table";
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
}
|
||||
|
||||
export function DataTable<TData, TValue>({
|
||||
columns,
|
||||
data,
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="rounded-md border w-full">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="flex items-center justify-end space-x-2 py-4">
|
||||
<Button
|
||||
variant="solid"
|
||||
size="sm"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
size="sm"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Chip } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
type Status = "completed" | "pending" | "cancelled";
|
||||
|
||||
export const statusColorMap: Record<Status, "success" | "danger" | "warning"> =
|
||||
{
|
||||
completed: "success",
|
||||
pending: "warning",
|
||||
cancelled: "danger",
|
||||
};
|
||||
|
||||
export const StatusBadge = ({ status }: { status: Status }) => {
|
||||
return (
|
||||
<Chip
|
||||
className="capitalize border-none gap-1 text-default-600"
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
color={statusColorMap[status]}
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
{status}
|
||||
</Chip>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
Table.displayName = "Table";
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
));
|
||||
TableHeader.displayName = "TableHeader";
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableBody.displayName = "TableBody";
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-t bg-slate-100/50 font-medium [&>tr]:last:border-b-0 dark:bg-slate-800/50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableFooter.displayName = "TableFooter";
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-slate-100/50 data-[state=selected]:bg-slate-100 dark:hover:bg-slate-800/50 dark:data-[state=selected]:bg-slate-800",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableRow.displayName = "TableRow";
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-12 px-4 text-left align-middle font-medium text-slate-500 [&:has([role=checkbox])]:pr-0 dark:text-slate-400",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableHead.displayName = "TableHead";
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableCell.displayName = "TableCell";
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-slate-500 dark:text-slate-400", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableCaption.displayName = "TableCaption";
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCaption,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
};
|
||||
@@ -3,8 +3,9 @@ import "server-only";
|
||||
import { parseStringify } from "../utils";
|
||||
|
||||
export const getProvider = async () => {
|
||||
const key = process.env.LOCAL_SITE_URL;
|
||||
try {
|
||||
const providers = await fetch("http://localhost:3000/api/providers");
|
||||
const providers = await fetch(`${key}/api/providers`);
|
||||
const data = await providers.json();
|
||||
return parseStringify(data);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user