mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-11-02 01:15:31 +00:00
feat: upgrade to React 19, Next.js 15, React Compiler, HeroUI and Tailwind 4 (#8748)
Co-authored-by: Alan Buscaglia <alanbuscaglia@MacBook-Pro.local> Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com> Co-authored-by: César Arroba <cesar@prowler.com> Co-authored-by: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com>
This commit is contained in:
261 files changed
+6339
-6126
No files matched your search
@@ -47,7 +47,7 @@ export default async function RootLayout({
|
||||
<body
|
||||
suppressHydrationWarning
|
||||
className={cn(
|
||||
"min-h-screen bg-background font-sans antialiased",
|
||||
"bg-background min-h-screen font-sans antialiased",
|
||||
fontSans.variable,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -3,10 +3,15 @@ import { getAuthUrl, isGithubOAuthEnabled } from "@/lib/helper";
|
||||
import { isGoogleOAuthEnabled } from "@/lib/helper";
|
||||
import { SearchParamsProps } from "@/types";
|
||||
|
||||
const SignUp = ({ searchParams }: { searchParams: SearchParamsProps }) => {
|
||||
const SignUp = async ({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<SearchParamsProps>;
|
||||
}) => {
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const invitationToken =
|
||||
typeof searchParams?.invitation_token === "string"
|
||||
? searchParams.invitation_token
|
||||
typeof resolvedSearchParams?.invitation_token === "string"
|
||||
? resolvedSearchParams.invitation_token
|
||||
: null;
|
||||
|
||||
const GOOGLE_AUTH_URL = getAuthUrl("google");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
import Image from "next/image";
|
||||
import React, { Suspense } from "react";
|
||||
|
||||
@@ -48,12 +48,12 @@ const ComplianceIconSmall = ({
|
||||
title: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className="relative h-6 w-6 flex-shrink-0">
|
||||
<div className="relative h-6 w-6 shrink-0">
|
||||
<Image
|
||||
src={logoPath}
|
||||
alt={`${title} logo`}
|
||||
fill
|
||||
className="h-8 w-8 min-w-8 rounded-md border-1 border-gray-300 bg-white object-contain p-[2px]"
|
||||
className="h-8 w-8 min-w-8 rounded-md border border-gray-300 bg-white object-contain p-[2px]"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -76,18 +76,19 @@ export default async function ComplianceDetail({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: { compliancetitle: string };
|
||||
searchParams: ComplianceDetailSearchParams;
|
||||
params: Promise<{ compliancetitle: string }>;
|
||||
searchParams: Promise<ComplianceDetailSearchParams>;
|
||||
}) {
|
||||
const { compliancetitle } = params;
|
||||
const { complianceId, version, scanId, scanData } = searchParams;
|
||||
const regionFilter = searchParams["filter[region__in]"];
|
||||
const cisProfileFilter = searchParams["filter[cis_profile_level]"];
|
||||
const { compliancetitle } = await params;
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const { complianceId, version, scanId, scanData } = resolvedSearchParams;
|
||||
const regionFilter = resolvedSearchParams["filter[region__in]"];
|
||||
const cisProfileFilter = resolvedSearchParams["filter[cis_profile_level]"];
|
||||
const logoPath = getComplianceIcon(compliancetitle);
|
||||
|
||||
// Create a key that excludes pagination parameters to preserve accordion state avoiding reloads with pagination
|
||||
const paramsForKey = Object.fromEntries(
|
||||
Object.entries(searchParams).filter(
|
||||
Object.entries(resolvedSearchParams).filter(
|
||||
([key]) => key !== "page" && key !== "pageSize",
|
||||
),
|
||||
);
|
||||
@@ -153,7 +154,7 @@ export default async function ComplianceDetail({
|
||||
<Suspense
|
||||
key={searchParamsKey}
|
||||
fallback={
|
||||
<div className="space-y-8">
|
||||
<div className="flex flex-col gap-8">
|
||||
<ChartsWrapper logoPath={logoPath}>
|
||||
<PieChartSkeleton />
|
||||
<BarChartSkeleton />
|
||||
@@ -200,7 +201,7 @@ const SSRComplianceContent = async ({
|
||||
|
||||
if (!scanId || type === "tasks") {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="flex flex-col gap-8">
|
||||
<ChartsWrapper logoPath={logoPath}>
|
||||
<PieChart pass={0} fail={0} manual={0} />
|
||||
<BarChart sections={[]} />
|
||||
@@ -231,7 +232,7 @@ const SSRComplianceContent = async ({
|
||||
const topFailedSections = mapper.getTopFailedSections(data);
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="flex flex-col gap-8">
|
||||
<ChartsWrapper logoPath={logoPath}>
|
||||
<PieChart
|
||||
pass={totalRequirements.pass}
|
||||
|
||||
@@ -22,12 +22,15 @@ import { ComplianceOverviewData } from "@/types/compliance";
|
||||
export default async function Compliance({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
searchParams: Promise<SearchParamsProps>;
|
||||
}) {
|
||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||
|
||||
const filters = Object.fromEntries(
|
||||
Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")),
|
||||
Object.entries(resolvedSearchParams).filter(([key]) =>
|
||||
key.startsWith("filter["),
|
||||
),
|
||||
);
|
||||
|
||||
const scansData = await getScans({
|
||||
@@ -72,7 +75,7 @@ export default async function Compliance({
|
||||
.filter(Boolean) as ExpandedScanData[];
|
||||
|
||||
const selectedScanId =
|
||||
searchParams.scanId || expandedScansData[0]?.id || null;
|
||||
resolvedSearchParams.scanId || expandedScansData[0]?.id || null;
|
||||
const query = (filters["filter[search]"] as string) || "";
|
||||
|
||||
// Find the selected scan
|
||||
@@ -103,7 +106,7 @@ export default async function Compliance({
|
||||
const uniqueRegions = metadataInfoData?.data?.attributes?.regions || [];
|
||||
|
||||
return (
|
||||
<ContentLayout title="Compliance" icon="fluent-mdl2:compliance-audit">
|
||||
<ContentLayout title="Compliance" icon="lucide:shield-check">
|
||||
{selectedScanId ? (
|
||||
<>
|
||||
<ComplianceHeader
|
||||
@@ -112,7 +115,7 @@ export default async function Compliance({
|
||||
/>
|
||||
<Suspense key={searchParamsKey} fallback={<ComplianceSkeletonGrid />}>
|
||||
<SSRComplianceGrid
|
||||
searchParams={searchParams}
|
||||
searchParams={resolvedSearchParams}
|
||||
selectedScan={selectedScanData}
|
||||
/>
|
||||
</Suspense>
|
||||
@@ -163,7 +166,7 @@ const SSRComplianceGrid = async ({
|
||||
) {
|
||||
return (
|
||||
<div className="flex h-full items-center">
|
||||
<div className="text-sm text-default-500">
|
||||
<div className="text-default-500 text-sm">
|
||||
No compliance data available for the selected scan.
|
||||
</div>
|
||||
</div>
|
||||
@@ -174,7 +177,7 @@ const SSRComplianceGrid = async ({
|
||||
if (compliancesData?.errors?.length > 0) {
|
||||
return (
|
||||
<div className="flex h-full items-center">
|
||||
<div className="text-sm text-default-500">Provide a valid scan ID.</div>
|
||||
<div className="text-default-500 text-sm">Provide a valid scan ID.</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
import React, { Suspense } from "react";
|
||||
|
||||
import {
|
||||
@@ -33,13 +33,15 @@ import { FindingProps, SearchParamsProps } from "@/types/components";
|
||||
export default async function Findings({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
searchParams: Promise<SearchParamsProps>;
|
||||
}) {
|
||||
const { searchParamsKey, encodedSort } = extractSortAndKey(searchParams);
|
||||
const { filters, query } = extractFiltersAndQuery(searchParams);
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const { searchParamsKey, encodedSort } =
|
||||
extractSortAndKey(resolvedSearchParams);
|
||||
const { filters, query } = extractFiltersAndQuery(resolvedSearchParams);
|
||||
|
||||
// Check if the searchParams contain any date or scan filter
|
||||
const hasDateOrScan = hasDateOrScanFilter(searchParams);
|
||||
const hasDateOrScan = hasDateOrScanFilter(resolvedSearchParams);
|
||||
|
||||
const [metadataInfoData, providersData, scansData] = await Promise.all([
|
||||
(hasDateOrScan ? getMetadataInfo : getLatestMetadataInfo)({
|
||||
@@ -81,7 +83,7 @@ export default async function Findings({
|
||||
) as { [uid: string]: ScanEntity }[];
|
||||
|
||||
return (
|
||||
<ContentLayout title="Findings" icon="carbon:data-view-alt">
|
||||
<ContentLayout title="Findings" icon="lucide:tag">
|
||||
<FindingsFilters
|
||||
providerUIDs={providerUIDs}
|
||||
providerDetails={providerDetails}
|
||||
@@ -94,7 +96,7 @@ export default async function Findings({
|
||||
/>
|
||||
<Spacer y={8} />
|
||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableFindings />}>
|
||||
<SSRDataTable searchParams={searchParams} />
|
||||
<SSRDataTable searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
</ContentLayout>
|
||||
);
|
||||
@@ -157,12 +159,13 @@ const SSRDataTable = async ({
|
||||
return (
|
||||
<>
|
||||
{findingsData?.errors && (
|
||||
<div className="mb-4 flex rounded-lg border border-red-500 bg-red-100 p-2 text-small text-red-700">
|
||||
<div className="text-small mb-4 flex rounded-lg border border-red-500 bg-red-100 p-2 text-red-700">
|
||||
<p className="mr-2 font-semibold">Error:</p>
|
||||
<p>{findingsData.errors[0].detail}</p>
|
||||
</div>
|
||||
)}
|
||||
<DataTable
|
||||
key={Date.now()}
|
||||
columns={ColumnFindings}
|
||||
data={expandedResponse?.data || []}
|
||||
metadata={findingsData?.meta}
|
||||
|
||||
@@ -6,19 +6,25 @@ import { S3IntegrationsManager } from "@/components/integrations/s3/s3-integrati
|
||||
import { ContentLayout } from "@/components/ui";
|
||||
|
||||
interface S3IntegrationsProps {
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||
}
|
||||
|
||||
export default async function S3Integrations({
|
||||
searchParams,
|
||||
}: S3IntegrationsProps) {
|
||||
const page = parseInt(searchParams.page?.toString() || "1", 10);
|
||||
const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10);
|
||||
const sort = searchParams.sort?.toString();
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const page = parseInt(resolvedSearchParams.page?.toString() || "1", 10);
|
||||
const pageSize = parseInt(
|
||||
resolvedSearchParams.pageSize?.toString() || "10",
|
||||
10,
|
||||
);
|
||||
const sort = resolvedSearchParams.sort?.toString();
|
||||
|
||||
// Extract all filter parameters
|
||||
const filters = Object.fromEntries(
|
||||
Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")),
|
||||
Object.entries(resolvedSearchParams).filter(([key]) =>
|
||||
key.startsWith("filter["),
|
||||
),
|
||||
);
|
||||
|
||||
const urlSearchParams = new URLSearchParams();
|
||||
@@ -49,8 +55,8 @@ export default async function S3Integrations({
|
||||
|
||||
return (
|
||||
<ContentLayout title="Amazon S3">
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Configure Amazon S3 integration to automatically export your scan
|
||||
results to S3 buckets.
|
||||
@@ -60,7 +66,7 @@ export default async function S3Integrations({
|
||||
<h3 className="mb-3 text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
Features:
|
||||
</h3>
|
||||
<ul className="grid grid-cols-1 gap-2 text-sm text-gray-600 dark:text-gray-300 md:grid-cols-2">
|
||||
<ul className="grid grid-cols-1 gap-2 text-sm text-gray-600 md:grid-cols-2 dark:text-gray-300">
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
||||
Automated scan result exports
|
||||
|
||||
@@ -6,18 +6,24 @@ import { SecurityHubIntegrationsManager } from "@/components/integrations/securi
|
||||
import { ContentLayout } from "@/components/ui";
|
||||
|
||||
interface SecurityHubIntegrationsProps {
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||
}
|
||||
|
||||
export default async function SecurityHubIntegrations({
|
||||
searchParams,
|
||||
}: SecurityHubIntegrationsProps) {
|
||||
const page = parseInt(searchParams.page?.toString() || "1", 10);
|
||||
const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10);
|
||||
const sort = searchParams.sort?.toString();
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const page = parseInt(resolvedSearchParams.page?.toString() || "1", 10);
|
||||
const pageSize = parseInt(
|
||||
resolvedSearchParams.pageSize?.toString() || "10",
|
||||
10,
|
||||
);
|
||||
const sort = resolvedSearchParams.sort?.toString();
|
||||
|
||||
const filters = Object.fromEntries(
|
||||
Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")),
|
||||
Object.entries(resolvedSearchParams).filter(([key]) =>
|
||||
key.startsWith("filter["),
|
||||
),
|
||||
);
|
||||
|
||||
const urlSearchParams = new URLSearchParams();
|
||||
@@ -47,8 +53,8 @@ export default async function SecurityHubIntegrations({
|
||||
|
||||
return (
|
||||
<ContentLayout title="AWS Security Hub">
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Configure AWS Security Hub integration to automatically send your
|
||||
security findings for centralized monitoring and compliance.
|
||||
@@ -58,7 +64,7 @@ export default async function SecurityHubIntegrations({
|
||||
<h3 className="mb-3 text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
Features:
|
||||
</h3>
|
||||
<ul className="grid grid-cols-1 gap-2 text-sm text-gray-600 dark:text-gray-300 md:grid-cols-2">
|
||||
<ul className="grid grid-cols-1 gap-2 text-sm text-gray-600 md:grid-cols-2 dark:text-gray-300">
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
||||
Automated findings export
|
||||
|
||||
@@ -3,19 +3,25 @@ import { JiraIntegrationsManager } from "@/components/integrations/jira/jira-int
|
||||
import { ContentLayout } from "@/components/ui";
|
||||
|
||||
interface JiraIntegrationsProps {
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||
}
|
||||
|
||||
export default async function JiraIntegrations({
|
||||
searchParams,
|
||||
}: JiraIntegrationsProps) {
|
||||
const page = parseInt(searchParams.page?.toString() || "1", 10);
|
||||
const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10);
|
||||
const sort = searchParams.sort?.toString();
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const page = parseInt(resolvedSearchParams.page?.toString() || "1", 10);
|
||||
const pageSize = parseInt(
|
||||
resolvedSearchParams.pageSize?.toString() || "10",
|
||||
10,
|
||||
);
|
||||
const sort = resolvedSearchParams.sort?.toString();
|
||||
|
||||
// Extract all filter parameters
|
||||
const filters = Object.fromEntries(
|
||||
Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")),
|
||||
Object.entries(resolvedSearchParams).filter(([key]) =>
|
||||
key.startsWith("filter["),
|
||||
),
|
||||
);
|
||||
|
||||
const urlSearchParams = new URLSearchParams();
|
||||
@@ -42,8 +48,8 @@ export default async function JiraIntegrations({
|
||||
|
||||
return (
|
||||
<ContentLayout title="Jira">
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Configure Jira integration to automatically create issues for
|
||||
security findings in your Jira projects.
|
||||
@@ -53,7 +59,7 @@ export default async function JiraIntegrations({
|
||||
<h3 className="mb-3 text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
Features:
|
||||
</h3>
|
||||
<ul className="grid grid-cols-1 gap-2 text-sm text-gray-600 dark:text-gray-300 md:grid-cols-2">
|
||||
<ul className="grid grid-cols-1 gap-2 text-sm text-gray-600 md:grid-cols-2 dark:text-gray-300">
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
||||
Automated issue creation
|
||||
|
||||
@@ -10,8 +10,8 @@ import { ContentLayout } from "@/components/ui";
|
||||
export default async function Integrations() {
|
||||
return (
|
||||
<ContentLayout title="Integrations" icon="lucide:puzzle">
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
Connect external services to enhance your security workflow and
|
||||
automatically export your scan results.
|
||||
|
||||
@@ -8,13 +8,14 @@ import { SearchParamsProps } from "@/types";
|
||||
export default async function CheckDetailsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
searchParams: Promise<SearchParamsProps>;
|
||||
}) {
|
||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||
|
||||
return (
|
||||
<Suspense key={searchParamsKey} fallback={<SkeletonInvitationInfo />}>
|
||||
<SSRDataInvitation searchParams={searchParams} />
|
||||
<SSRDataInvitation searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "@/styles/globals.css";
|
||||
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
import React from "react";
|
||||
|
||||
import { WorkflowSendInvite } from "@/components/invitations/workflow";
|
||||
@@ -19,8 +19,8 @@ export default function InvitationLayout({ children }: InvitationLayoutProps) {
|
||||
href="/invitations"
|
||||
/>
|
||||
<Spacer y={16} />
|
||||
<div className="grid grid-cols-1 gap-8 lg:grid-cols-12">
|
||||
<div className="order-1 my-auto hidden h-full lg:col-span-4 lg:col-start-2 lg:block">
|
||||
<div className="grid grid-cols-1 gap-8 px-4 lg:grid-cols-12 lg:px-0">
|
||||
<div className="order-1 my-auto h-full lg:col-span-4 lg:col-start-2">
|
||||
<WorkflowSendInvite />
|
||||
</div>
|
||||
<div className="order-2 my-auto lg:col-span-5 lg:col-start-6">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
import React, { Suspense } from "react";
|
||||
|
||||
import { getInvitations } from "@/actions/invitations/invitation";
|
||||
@@ -17,12 +17,13 @@ import { InvitationProps, Role, SearchParamsProps } from "@/types";
|
||||
export default async function Invitations({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
searchParams: Promise<SearchParamsProps>;
|
||||
}) {
|
||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||
|
||||
return (
|
||||
<ContentLayout title="Invitations" icon="ci:users">
|
||||
<ContentLayout title="Invitations" icon="lucide:mail">
|
||||
<FilterControls search />
|
||||
<Spacer y={8} />
|
||||
<SendInvitationButton />
|
||||
@@ -31,7 +32,7 @@ export default async function Invitations({
|
||||
<Spacer y={8} />
|
||||
|
||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableInvitation />}>
|
||||
<SSRDataTable searchParams={searchParams} />
|
||||
<SSRDataTable searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
</ContentLayout>
|
||||
);
|
||||
@@ -110,6 +111,7 @@ const SSRDataTable = async ({
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
key={Date.now()}
|
||||
columns={ColumnsInvitation}
|
||||
data={expandedResponse?.data || []}
|
||||
metadata={invitationsData?.meta}
|
||||
|
||||
@@ -45,7 +45,7 @@ export default async function RootLayout({
|
||||
<body
|
||||
suppressHydrationWarning
|
||||
className={cn(
|
||||
"min-h-screen bg-background font-sans antialiased",
|
||||
"bg-background min-h-screen font-sans antialiased",
|
||||
fontSans.variable,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -10,10 +10,7 @@ interface ProviderLayoutProps {
|
||||
|
||||
export default function ProviderLayout({ children }: ProviderLayoutProps) {
|
||||
return (
|
||||
<ContentLayout
|
||||
title="Manage Groups"
|
||||
icon="solar:users-group-two-rounded-outline"
|
||||
>
|
||||
<ContentLayout title="Manage Groups" icon="lucide:group">
|
||||
{children}
|
||||
</ContentLayout>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Divider, Spacer } from "@nextui-org/react";
|
||||
import { Divider } from "@heroui/divider";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
import { redirect } from "next/navigation";
|
||||
import React, { Suspense } from "react";
|
||||
|
||||
@@ -15,26 +16,27 @@ import { ColumnGroups } from "@/components/manage-groups/table";
|
||||
import { DataTable } from "@/components/ui/table";
|
||||
import { ProviderProps, Role, SearchParamsProps } from "@/types";
|
||||
|
||||
export default function ManageGroupsPage({
|
||||
export default async function ManageGroupsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
searchParams: Promise<SearchParamsProps>;
|
||||
}) {
|
||||
const searchParamsKey = JSON.stringify(searchParams);
|
||||
const providerGroupId = searchParams.groupId;
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const searchParamsKey = JSON.stringify(resolvedSearchParams);
|
||||
const providerGroupId = resolvedSearchParams.groupId;
|
||||
|
||||
return (
|
||||
<div className="grid min-h-[70vh] grid-cols-1 items-center justify-center gap-4 md:grid-cols-12">
|
||||
<div className="col-span-1 flex justify-end md:col-span-4">
|
||||
<Suspense key={searchParamsKey} fallback={<SkeletonManageGroups />}>
|
||||
{providerGroupId ? (
|
||||
<SSRDataEditGroup searchParams={searchParams} />
|
||||
<SSRDataEditGroup searchParams={resolvedSearchParams} />
|
||||
) : (
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2 text-xl font-medium" id="getting-started">
|
||||
Create a new provider group
|
||||
</h1>
|
||||
<p className="mb-5 text-small text-default-500">
|
||||
<p className="text-small text-default-500 mb-5">
|
||||
Create a new provider group to manage the providers and roles.
|
||||
</p>
|
||||
<SSRAddGroupForm />
|
||||
@@ -50,7 +52,7 @@ export default function ManageGroupsPage({
|
||||
<Spacer y={8} />
|
||||
<h3 className="mb-4 text-sm font-bold uppercase">Provider Groups</h3>
|
||||
<Suspense key={searchParamsKey} fallback={<SkeletonManageGroups />}>
|
||||
<SSRDataTable searchParams={searchParams} />
|
||||
<SSRDataTable searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,7 +145,7 @@ const SSRDataEditGroup = async ({
|
||||
<h1 className="mb-2 text-xl font-medium" id="getting-started">
|
||||
Edit provider group
|
||||
</h1>
|
||||
<p className="mb-5 text-small text-default-500">
|
||||
<p className="text-small text-default-500 mb-5">
|
||||
Edit the provider group to manage the providers and roles.
|
||||
</p>
|
||||
<EditGroupForm
|
||||
@@ -181,11 +183,15 @@ const SSRDataTable = async ({
|
||||
filters,
|
||||
pageSize,
|
||||
});
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
columns={ColumnGroups}
|
||||
data={providerGroupsData?.data || []}
|
||||
metadata={providerGroupsData?.meta}
|
||||
/>
|
||||
<>
|
||||
<DataTable
|
||||
key={`groups-${Date.now()}`}
|
||||
columns={ColumnGroups}
|
||||
data={providerGroupsData?.data || []}
|
||||
metadata={providerGroupsData?.meta}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { getLatestFindings } from "@/actions/findings/findings";
|
||||
@@ -37,14 +37,15 @@ function pickFilterParams(
|
||||
);
|
||||
}
|
||||
|
||||
export default function Home({
|
||||
export default async function Home({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
searchParams: Promise<SearchParamsProps>;
|
||||
}) {
|
||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||
return (
|
||||
<ContentLayout title="Overview" icon="solar:pie-chart-2-outline">
|
||||
<ContentLayout title="Overview" icon="lucide:square-chart-gantt">
|
||||
<FilterControls providers mutedFindings showClearButton={false} />
|
||||
|
||||
<div className="grid grid-cols-12 gap-12 lg:gap-6">
|
||||
@@ -56,13 +57,13 @@ export default function Home({
|
||||
|
||||
<div className="col-span-12 lg:col-span-4">
|
||||
<Suspense fallback={<SkeletonFindingsBySeverityChart />}>
|
||||
<SSRFindingsBySeverity searchParams={searchParams} />
|
||||
<SSRFindingsBySeverity searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
<div className="col-span-12 lg:col-span-4">
|
||||
<Suspense fallback={<SkeletonFindingsByStatusChart />}>
|
||||
<SSRFindingsByStatus searchParams={searchParams} />
|
||||
<SSRFindingsByStatus searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +73,7 @@ export default function Home({
|
||||
key={searchParamsKey}
|
||||
fallback={<SkeletonTableNewFindings />}
|
||||
>
|
||||
<SSRDataNewFindingsTable searchParams={searchParams} />
|
||||
<SSRDataNewFindingsTable searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
@@ -205,6 +206,7 @@ const SSRDataNewFindingsTable = async ({
|
||||
<LighthouseBanner />
|
||||
|
||||
<DataTable
|
||||
key={`dashboard-${Date.now()}`}
|
||||
columns={ColumnNewFindingsToDate}
|
||||
data={expandedResponse?.data || []}
|
||||
// metadata={findingsData?.meta}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
|
||||
export default async function Profile() {
|
||||
return (
|
||||
<ContentLayout title="User Profile" icon="ci:users">
|
||||
<ContentLayout title="User Profile" icon="lucide:users">
|
||||
<Suspense fallback={<SkeletonUserInfo />}>
|
||||
<SSRDataUser />
|
||||
</Suspense>
|
||||
@@ -96,10 +96,10 @@ const SSRDataUser = async () => {
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
<UserBasicInfoCard user={userData} tenantId={userTenantId || ""} />
|
||||
<div className="flex flex-col gap-6 xl:flex-row">
|
||||
<div className="w-full lg:w-2/3 xl:w-1/2">
|
||||
<div className="w-full">
|
||||
<RolesCard roles={roleDetails} roleDetails={roleDetailsMap} />
|
||||
</div>
|
||||
<div className="w-full lg:w-2/3 xl:w-1/2">
|
||||
<div className="w-full">
|
||||
<MembershipsCard
|
||||
memberships={membershipsIncluded}
|
||||
tenantsMap={tenantsMap}
|
||||
@@ -108,7 +108,7 @@ const SSRDataUser = async () => {
|
||||
</div>
|
||||
</div>
|
||||
{hasManageIntegrations && (
|
||||
<div className="w-full pr-0 lg:w-2/3 xl:w-1/2 xl:pr-3">
|
||||
<div className="w-full pr-0 xl:w-1/2 xl:pr-3">
|
||||
<SamlIntegrationCard samlConfig={samlConfig?.data?.[0]} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -14,11 +14,12 @@ import { getProviderFormType } from "@/lib/provider-helpers";
|
||||
import { ProviderType } from "@/types/providers";
|
||||
|
||||
interface Props {
|
||||
searchParams: { type: ProviderType; id: string; via?: string };
|
||||
searchParams: Promise<{ type: ProviderType; id: string; via?: string }>;
|
||||
}
|
||||
|
||||
export default function AddCredentialsPage({ searchParams }: Props) {
|
||||
const { type: providerType, via } = searchParams;
|
||||
export default async function AddCredentialsPage({ searchParams }: Props) {
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const { type: providerType, via } = resolvedSearchParams;
|
||||
const formType = getProviderFormType(providerType, via);
|
||||
|
||||
switch (formType) {
|
||||
@@ -30,13 +31,13 @@ export default function AddCredentialsPage({ searchParams }: Props) {
|
||||
return null;
|
||||
|
||||
case "credentials":
|
||||
return <AddViaCredentialsForm searchParams={searchParams} />;
|
||||
return <AddViaCredentialsForm searchParams={resolvedSearchParams} />;
|
||||
|
||||
case "role":
|
||||
return <AddViaRoleForm searchParams={searchParams} />;
|
||||
return <AddViaRoleForm searchParams={resolvedSearchParams} />;
|
||||
|
||||
case "service-account":
|
||||
return <AddViaServiceAccountForm searchParams={searchParams} />;
|
||||
return <AddViaServiceAccountForm searchParams={resolvedSearchParams} />;
|
||||
|
||||
default:
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "@/styles/globals.css";
|
||||
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
import React from "react";
|
||||
|
||||
import { WorkflowAddProvider } from "@/components/providers/workflow";
|
||||
|
||||
@@ -6,11 +6,12 @@ import { SkeletonProviderWorkflow } from "@/components/providers/workflow";
|
||||
import { TestConnectionForm } from "@/components/providers/workflow/forms";
|
||||
|
||||
interface Props {
|
||||
searchParams: { type: string; id: string; updated: string };
|
||||
searchParams: Promise<{ type: string; id: string; updated: string }>;
|
||||
}
|
||||
|
||||
export default async function TestConnectionPage({ searchParams }: Props) {
|
||||
const providerId = searchParams.id;
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const providerId = resolvedSearchParams.id;
|
||||
|
||||
if (!providerId) {
|
||||
redirect("/providers/connect-account");
|
||||
@@ -18,7 +19,7 @@ export default async function TestConnectionPage({ searchParams }: Props) {
|
||||
|
||||
return (
|
||||
<Suspense fallback={<SkeletonProviderWorkflow />}>
|
||||
<SSRTestConnection searchParams={searchParams} />
|
||||
<SSRTestConnection searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,16 +10,17 @@ import { getProviderFormType } from "@/lib/provider-helpers";
|
||||
import { ProviderType } from "@/types/providers";
|
||||
|
||||
interface Props {
|
||||
searchParams: {
|
||||
searchParams: Promise<{
|
||||
type: ProviderType;
|
||||
id: string;
|
||||
via?: string;
|
||||
secretId?: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export default function UpdateCredentialsPage({ searchParams }: Props) {
|
||||
const { type: providerType, via } = searchParams;
|
||||
export default async function UpdateCredentialsPage({ searchParams }: Props) {
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const { type: providerType, via } = resolvedSearchParams;
|
||||
const formType = getProviderFormType(providerType, via);
|
||||
|
||||
switch (formType) {
|
||||
@@ -29,13 +30,15 @@ export default function UpdateCredentialsPage({ searchParams }: Props) {
|
||||
);
|
||||
|
||||
case "credentials":
|
||||
return <UpdateViaCredentialsForm searchParams={searchParams} />;
|
||||
return <UpdateViaCredentialsForm searchParams={resolvedSearchParams} />;
|
||||
|
||||
case "role":
|
||||
return <UpdateViaRoleForm searchParams={searchParams} />;
|
||||
return <UpdateViaRoleForm searchParams={resolvedSearchParams} />;
|
||||
|
||||
case "service-account":
|
||||
return <UpdateViaServiceAccountForm searchParams={searchParams} />;
|
||||
return (
|
||||
<UpdateViaServiceAccountForm searchParams={resolvedSearchParams} />
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { getProviders } from "@/actions/providers";
|
||||
@@ -19,12 +19,13 @@ import { ProviderProps, SearchParamsProps } from "@/types";
|
||||
export default async function Providers({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
searchParams: Promise<SearchParamsProps>;
|
||||
}) {
|
||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||
|
||||
return (
|
||||
<ContentLayout title="Cloud Providers" icon="fluent:cloud-sync-24-regular">
|
||||
<ContentLayout title="Cloud Providers" icon="lucide:cloud-cog">
|
||||
<FilterControls search customFilters={filterProviders || []} />
|
||||
<Spacer y={8} />
|
||||
<Suspense
|
||||
@@ -45,7 +46,7 @@ export default async function Providers({
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ProvidersContent searchParams={searchParams} />
|
||||
<ProvidersContent searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
</ContentLayout>
|
||||
);
|
||||
@@ -106,6 +107,7 @@ const ProvidersContent = async ({
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
<div className="col-span-12">
|
||||
<DataTable
|
||||
key={`providers-${Date.now()}`}
|
||||
columns={ColumnProviders}
|
||||
data={enrichedProviders || []}
|
||||
metadata={providersData?.meta}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import {
|
||||
@@ -24,14 +24,16 @@ import { ResourceProps, SearchParamsProps } from "@/types";
|
||||
export default async function Resources({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
searchParams: Promise<SearchParamsProps>;
|
||||
}) {
|
||||
const { searchParamsKey, encodedSort } = extractSortAndKey(searchParams);
|
||||
const { filters, query } = extractFiltersAndQuery(searchParams);
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const { searchParamsKey, encodedSort } =
|
||||
extractSortAndKey(resolvedSearchParams);
|
||||
const { filters, query } = extractFiltersAndQuery(resolvedSearchParams);
|
||||
const outputFilters = replaceFieldKey(filters, "inserted_at", "updated_at");
|
||||
|
||||
// Check if the searchParams contain any date or scan filter
|
||||
const hasDateOrScan = hasDateOrScanFilter(searchParams);
|
||||
const hasDateOrScan = hasDateOrScanFilter(resolvedSearchParams);
|
||||
|
||||
const metadataInfoData = await (
|
||||
hasDateOrScan ? getMetadataInfo : getLatestMetadataInfo
|
||||
@@ -47,7 +49,7 @@ export default async function Resources({
|
||||
const uniqueResourceTypes = metadataInfoData?.data?.attributes?.types || [];
|
||||
|
||||
return (
|
||||
<ContentLayout title="Resources" icon="carbon:data-view">
|
||||
<ContentLayout title="Resources" icon="lucide:warehouse">
|
||||
<FilterControls search date />
|
||||
<DataTableFilterCustom
|
||||
filters={[
|
||||
@@ -71,7 +73,7 @@ export default async function Resources({
|
||||
/>
|
||||
<Spacer y={8} />
|
||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableResources />}>
|
||||
<SSRDataTable searchParams={searchParams} />
|
||||
<SSRDataTable searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
</ContentLayout>
|
||||
);
|
||||
@@ -140,12 +142,13 @@ const SSRDataTable = async ({
|
||||
return (
|
||||
<>
|
||||
{resourcesData?.errors && (
|
||||
<div className="mb-4 flex rounded-lg border border-red-500 bg-red-100 p-2 text-small text-red-700">
|
||||
<div className="text-small mb-4 flex rounded-lg border border-red-500 bg-red-100 p-2 text-red-700">
|
||||
<p className="mr-2 font-semibold">Error:</p>
|
||||
<p>{resourcesData.errors[0].detail}</p>
|
||||
</div>
|
||||
)}
|
||||
<DataTable
|
||||
key={`resources-${Date.now()}`}
|
||||
columns={ColumnResources}
|
||||
data={expandedResources || []}
|
||||
metadata={resourcesData?.meta}
|
||||
|
||||
@@ -10,13 +10,14 @@ import { SearchParamsProps } from "@/types";
|
||||
export default async function EditRolePage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
searchParams: Promise<SearchParamsProps>;
|
||||
}) {
|
||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||
|
||||
return (
|
||||
<Suspense key={searchParamsKey} fallback={<SkeletonRoleForm />}>
|
||||
<SSRDataRole searchParams={searchParams} />
|
||||
<SSRDataRole searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "@/styles/globals.css";
|
||||
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
import React from "react";
|
||||
|
||||
import { WorkflowAddEditRole } from "@/components/roles/workflow";
|
||||
@@ -19,7 +19,7 @@ export default function RoleLayout({ children }: RoleLayoutProps) {
|
||||
href="/roles"
|
||||
/>
|
||||
<Spacer y={16} />
|
||||
<div className="grid grid-cols-1 gap-8 lg:grid-cols-12">
|
||||
<div className="grid grid-cols-1 gap-8 px-4 sm:px-6 lg:grid-cols-12 lg:px-0">
|
||||
<div className="order-1 my-auto hidden h-full lg:col-span-4 lg:col-start-2 lg:block">
|
||||
<WorkflowAddEditRole />
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { getRoles } from "@/actions/roles";
|
||||
@@ -14,12 +14,13 @@ import { SearchParamsProps } from "@/types";
|
||||
export default async function Roles({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
searchParams: Promise<SearchParamsProps>;
|
||||
}) {
|
||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||
|
||||
return (
|
||||
<ContentLayout title="Roles" icon="mdi:account-key-outline">
|
||||
<ContentLayout title="Roles" icon="lucide:user-cog">
|
||||
<FilterControls search />
|
||||
<Spacer y={8} />
|
||||
<AddRoleButton />
|
||||
@@ -28,7 +29,7 @@ export default async function Roles({
|
||||
<Spacer y={8} />
|
||||
|
||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableRoles />}>
|
||||
<SSRDataTable searchParams={searchParams} />
|
||||
<SSRDataTable searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
</ContentLayout>
|
||||
);
|
||||
@@ -55,6 +56,7 @@ const SSRDataTable = async ({
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
key={`roles-${Date.now()}`}
|
||||
columns={ColumnsRoles}
|
||||
data={rolesData?.data || []}
|
||||
metadata={rolesData?.meta}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { getProviders } from "@/actions/providers";
|
||||
@@ -26,12 +26,12 @@ import { ProviderProps, ScanProps, SearchParamsProps } from "@/types";
|
||||
export default async function Scans({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
searchParams: Promise<SearchParamsProps>;
|
||||
}) {
|
||||
const session = await auth();
|
||||
const filteredParams = { ...searchParams };
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const filteredParams = { ...resolvedSearchParams };
|
||||
delete filteredParams.scanId;
|
||||
const searchParamsKey = JSON.stringify(filteredParams);
|
||||
|
||||
const providersData = await getProviders({
|
||||
pageSize: 50,
|
||||
@@ -77,14 +77,14 @@ export default async function Scans({
|
||||
|
||||
if (thereIsNoProviders) {
|
||||
return (
|
||||
<ContentLayout title="Scans" icon="lucide:scan-search">
|
||||
<ContentLayout title="Scans" icon="lucide:timer">
|
||||
<NoProvidersAdded />
|
||||
</ContentLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentLayout title="Scans" icon="lucide:scan-search">
|
||||
<ContentLayout title="Scans" icon="lucide:timer">
|
||||
<AutoRefresh hasExecutingScan={hasExecutingScan} />
|
||||
<>
|
||||
{!hasManageScansPermission ? (
|
||||
@@ -111,8 +111,8 @@ export default async function Scans({
|
||||
<MutedFindingsConfigButton />
|
||||
</div>
|
||||
<Spacer y={8} />
|
||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableScans />}>
|
||||
<SSRDataTableScans searchParams={searchParams} />
|
||||
<Suspense fallback={<SkeletonTableScans />}>
|
||||
<SSRDataTableScans searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
</>
|
||||
</ContentLayout>
|
||||
@@ -178,6 +178,7 @@ const SSRDataTableScans = async ({
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
key={`scans-${Date.now()}`}
|
||||
columns={ColumnGetScans}
|
||||
data={expandedScansData || []}
|
||||
metadata={scansData?.meta}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
|
||||
import { FilterControls } from "@/components/filters";
|
||||
import { ContentLayout } from "@/components/ui";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { Spacer } from "@heroui/spacer";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { getUsers } from "@/actions/users/users";
|
||||
@@ -13,12 +13,13 @@ import { Role, SearchParamsProps, UserProps } from "@/types";
|
||||
export default async function Users({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
searchParams: Promise<SearchParamsProps>;
|
||||
}) {
|
||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||
|
||||
return (
|
||||
<ContentLayout title="Users" icon="ci:users">
|
||||
<ContentLayout title="Users" icon="lucide:user">
|
||||
<FilterControls search />
|
||||
<Spacer y={8} />
|
||||
<AddUserButton />
|
||||
@@ -27,7 +28,7 @@ export default async function Users({
|
||||
<Spacer y={8} />
|
||||
|
||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableUser />}>
|
||||
<SSRDataTable searchParams={searchParams} />
|
||||
<SSRDataTable searchParams={resolvedSearchParams} />
|
||||
</Suspense>
|
||||
</ContentLayout>
|
||||
);
|
||||
@@ -91,6 +92,7 @@ const SSRDataTable = async ({
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
key={`scans-${Date.now()}`}
|
||||
columns={ColumnsUser}
|
||||
data={expandedUsers || []}
|
||||
metadata={usersData?.meta}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { NextUIProvider } from "@nextui-org/system";
|
||||
import { HeroUIProvider } from "@heroui/system";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||
@@ -17,9 +17,9 @@ export function Providers({ children, themeProps }: ProvidersProps) {
|
||||
|
||||
return (
|
||||
<SessionProvider>
|
||||
<NextUIProvider navigate={router.push}>
|
||||
<HeroUIProvider navigate={router.push}>
|
||||
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
|
||||
</NextUIProvider>
|
||||
</HeroUIProvider>
|
||||
</SessionProvider>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user