mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-29 23:04:23 +00:00
125 lines
4.4 KiB
TypeScript
125 lines
4.4 KiB
TypeScript
import "@/styles/globals.css";
|
|
|
|
import * as Sentry from "@sentry/nextjs";
|
|
import { Metadata, Viewport } from "next";
|
|
import { ReactNode, Suspense } from "react";
|
|
|
|
import { getProviders } from "@/actions/providers";
|
|
import { getScansByState } from "@/actions/scans/scans";
|
|
import MainLayout from "@/components/layout/main-layout/main-layout";
|
|
import {
|
|
OnboardingCheckpointWatcher,
|
|
OnboardingGate,
|
|
OnboardingSequenceBanner,
|
|
} from "@/components/onboarding";
|
|
import { RuntimePublicConfig } from "@/components/runtime-config/runtime-public-config";
|
|
import { NavigationProgress } from "@/components/shadcn/navigation-progress";
|
|
import { Toaster } from "@/components/shadcn/toast";
|
|
import { TaskPollingWatcher } from "@/components/shared/task-polling-watcher";
|
|
import { GlobalSidePanel } from "@/components/side-panel";
|
|
import { FeedbackSurvey } from "@/components/survey/feedback-survey";
|
|
import { fontMono, fontSans } from "@/config/fonts";
|
|
import { siteConfig } from "@/config/site";
|
|
import { isCloud } from "@/lib/shared/env";
|
|
import { cn } from "@/lib/utils";
|
|
import { StoreInitializer } from "@/store/ui/store-initializer";
|
|
import { SCAN_STATES } from "@/types/attack-paths";
|
|
|
|
import { Providers } from "../providers";
|
|
|
|
export const metadata: Metadata = {
|
|
title: {
|
|
default: siteConfig.name,
|
|
template: `%s - ${siteConfig.name}`,
|
|
},
|
|
description: siteConfig.description,
|
|
icons: {
|
|
icon: "/favicon.ico",
|
|
},
|
|
other: {
|
|
...Sentry.getTraceData(),
|
|
},
|
|
};
|
|
|
|
export const viewport: Viewport = {
|
|
themeColor: [
|
|
{ media: "(prefers-color-scheme: light)", color: "white" },
|
|
{ media: "(prefers-color-scheme: dark)", color: "black" },
|
|
],
|
|
};
|
|
|
|
export default async function RootLayout({
|
|
children,
|
|
}: {
|
|
children: ReactNode;
|
|
}) {
|
|
// Skip Cloud-only onboarding fetches and orchestrators in OSS.
|
|
const cloudEnabled = isCloud();
|
|
|
|
// Fail-open: unknown scan state is treated as "has data" so the banner never blocks
|
|
// progression on a fetch error.
|
|
let hasCompletedScan = true;
|
|
// Tri-state: true = has providers, false = zero providers, undefined = fetch failed (gate fails open).
|
|
let hasProviders: boolean | undefined = false;
|
|
|
|
if (cloudEnabled) {
|
|
const [providersData, scansByState] = await Promise.all([
|
|
getProviders({ page: 1, pageSize: 1 }),
|
|
getScansByState(),
|
|
]);
|
|
hasCompletedScan = Array.isArray(scansByState?.data)
|
|
? scansByState.data.some(
|
|
(scan: { attributes?: { state?: string } }) =>
|
|
scan.attributes?.state === SCAN_STATES.COMPLETED,
|
|
)
|
|
: true;
|
|
hasProviders = Array.isArray(providersData?.data)
|
|
? providersData.data.length > 0
|
|
: undefined;
|
|
}
|
|
|
|
return (
|
|
<html suppressHydrationWarning lang="en">
|
|
<head>
|
|
<RuntimePublicConfig />
|
|
</head>
|
|
<body
|
|
suppressHydrationWarning
|
|
className={cn(
|
|
"bg-bg-neutral-primary min-h-screen font-sans antialiased",
|
|
fontSans.variable,
|
|
fontMono.variable,
|
|
)}
|
|
>
|
|
<Providers themeProps={{ attribute: "class", defaultTheme: "dark" }}>
|
|
{/* Suspense contains the useSearchParams() CSR bailout so statically
|
|
prerendered pages don't fail the build (matches the auth layout). */}
|
|
<Suspense>
|
|
<NavigationProgress />
|
|
</Suspense>
|
|
{/* Store uses boolean; gate receives tri-state to fail open on fetch errors. */}
|
|
<StoreInitializer values={{ hasProviders: hasProviders ?? false }} />
|
|
{cloudEnabled && (
|
|
<>
|
|
<OnboardingGate hasProviders={hasProviders} />
|
|
{/* Single mount point so the watcher survives post-connect navigation. */}
|
|
<OnboardingCheckpointWatcher />
|
|
{/* Persistent banner shown only while a guided sequence is active. */}
|
|
<OnboardingSequenceBanner hasCompletedScan={hasCompletedScan} />
|
|
</>
|
|
)}
|
|
<MainLayout>{children}</MainLayout>
|
|
{cloudEnabled && <FeedbackSurvey />}
|
|
{/* Always mounted: it hosts the detail (finding/resource) views in
|
|
every deployment; the AI tab inside is cloud-gated on its own. */}
|
|
<GlobalSidePanel />
|
|
{/* Resumes persisted background-task polling (e.g. cross-provider
|
|
PDF generation) so completion toasts survive hard reloads. */}
|
|
<TaskPollingWatcher />
|
|
<Toaster />
|
|
</Providers>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|