mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-11-08 12:24:45 +00:00
merge master into grouped Jira UI
This commit is contained in:
316 files changed
+10112
-4023
No files matched your search
@@ -17,8 +17,8 @@ import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
useToast,
|
||||
} from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { CustomInput } from "@/components/shadcn/custom";
|
||||
import { Form } from "@/components/shadcn/form";
|
||||
import { getSafeCallbackPath } from "@/lib/auth-callback-url";
|
||||
|
||||
@@ -15,8 +15,7 @@ import { AuthFooterLink } from "@/components/auth/oss/auth-footer-link";
|
||||
import { AuthLayout } from "@/components/auth/oss/auth-layout";
|
||||
import { PasswordRequirementsMessage } from "@/components/auth/oss/password-validator";
|
||||
import { SocialButtons } from "@/components/auth/oss/social-buttons";
|
||||
import { Button, Checkbox } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, Checkbox, useToast } from "@/components/shadcn";
|
||||
import { CustomInput } from "@/components/shadcn/custom";
|
||||
import { CustomLink } from "@/components/shadcn/custom/custom-link";
|
||||
import {
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { SocialButtons } from "./social-buttons";
|
||||
|
||||
// Stub Iconify: the real <Icon> fetches icon data over the network and its
|
||||
// retry timers can fire after the jsdom environment is torn down, crashing the
|
||||
// worker with "window is not defined".
|
||||
vi.mock("@iconify/react", () => ({
|
||||
Icon: ({ icon }: { icon: string }) => <span aria-label={icon} />,
|
||||
}));
|
||||
|
||||
describe("SocialButtons", () => {
|
||||
it("renders icon-only provider links that keep their accessible names", () => {
|
||||
render(
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { Accordion, AccordionItemProps } from "@/components/shadcn";
|
||||
import { Button, Accordion, AccordionItemProps } from "@/components/shadcn";
|
||||
import { Card } from "@/components/shadcn/card/card";
|
||||
|
||||
export const ClientAccordionWrapper = ({
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { ScanEntity } from "@/types/scans";
|
||||
|
||||
import { getComplianceIcon } from "../icons";
|
||||
|
||||
import { ComplianceDownloadContainer } from "./compliance-download-container";
|
||||
|
||||
interface ComplianceCardProps {
|
||||
|
||||
@@ -4,6 +4,17 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { useCloudUpgradeStore } from "@/store/cloud-upgrade/store";
|
||||
import { CLOUD_UPGRADE_FEATURE } from "@/types/cloud-upgrade";
|
||||
import {
|
||||
FINDING_TRIAGE_DISABLED_REASON,
|
||||
FINDING_TRIAGE_STATUS,
|
||||
type FindingTriageSummary,
|
||||
} from "@/types/findings-triage";
|
||||
|
||||
import {
|
||||
DataTableRowActions,
|
||||
type FindingRowData,
|
||||
} from "./data-table-row-actions";
|
||||
import { FindingsSelectionContext } from "./findings-selection-context";
|
||||
|
||||
const { MuteFindingsModalMock, isGroupedJiraDispatchEnabledMock } = vi.hoisted(
|
||||
() => ({
|
||||
@@ -97,18 +108,6 @@ vi.mock("./finding-note-modal", () => ({
|
||||
) : null,
|
||||
}));
|
||||
|
||||
import {
|
||||
FINDING_TRIAGE_DISABLED_REASON,
|
||||
FINDING_TRIAGE_STATUS,
|
||||
type FindingTriageSummary,
|
||||
} from "@/types/findings-triage";
|
||||
|
||||
import {
|
||||
DataTableRowActions,
|
||||
type FindingRowData,
|
||||
} from "./data-table-row-actions";
|
||||
import { FindingsSelectionContext } from "./findings-selection-context";
|
||||
|
||||
function deferredPromise<T>() {
|
||||
let resolve!: (value: T) => void;
|
||||
let reject!: (reason?: unknown) => void;
|
||||
|
||||
@@ -22,8 +22,9 @@ import {
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
SeverityBadge,
|
||||
StatusFindingBadge,
|
||||
} from "@/components/shadcn/table";
|
||||
import { SeverityBadge, StatusFindingBadge } from "@/components/shadcn/table";
|
||||
import { useFindingGroupResourceState } from "@/hooks/use-finding-group-resource-state";
|
||||
import { cn, hasHistoricalFindingFilter } from "@/lib";
|
||||
import { isGroupedJiraDispatchEnabled } from "@/lib/deployment";
|
||||
@@ -37,6 +38,7 @@ import { FindingGroupRow } from "@/types";
|
||||
import { JIRA_DISPATCH_MODE, JIRA_DISPATCH_TARGET } from "@/types/integrations";
|
||||
|
||||
import { FloatingMuteButton } from "../floating-mute-button";
|
||||
|
||||
import { getColumnFindingResources } from "./column-finding-resources";
|
||||
import { FindingsSelectionContext } from "./findings-selection-context";
|
||||
import { ImpactedResourcesCell } from "./impacted-resources-cell";
|
||||
|
||||
@@ -21,6 +21,7 @@ import { FindingGroupRow, MetaDataProps } from "@/types";
|
||||
import { JIRA_DISPATCH_MODE, JIRA_DISPATCH_TARGET } from "@/types/integrations";
|
||||
|
||||
import { FloatingMuteButton } from "../floating-mute-button";
|
||||
|
||||
import { getColumnFindingGroups } from "./column-finding-groups";
|
||||
import { canMuteFindingGroup } from "./finding-group-selection";
|
||||
import { FindingsSelectionContext } from "./findings-selection-context";
|
||||
|
||||
+2
-2
@@ -71,8 +71,7 @@ import {
|
||||
type QueryEditorLanguage,
|
||||
} from "@/components/shared/query-code-editor";
|
||||
import { ResourceMetadataPanel } from "@/components/shared/resource-metadata-panel";
|
||||
import { getFailingForLabel } from "@/lib/date-utils";
|
||||
import { formatDuration } from "@/lib/date-utils";
|
||||
import { getFailingForLabel, formatDuration } from "@/lib/date-utils";
|
||||
import { shouldRefreshAfterTriageUpdate } from "@/lib/finding-triage";
|
||||
import { createJiraTargetSelection } from "@/lib/jira-dispatch-selection";
|
||||
import { buildFindingAnalysisPrompt } from "@/lib/lighthouse/prompts";
|
||||
@@ -91,6 +90,7 @@ import {
|
||||
FindingTriageStatusCell,
|
||||
} from "../finding-triage-cells";
|
||||
import { DeltaValues, NotificationIndicator } from "../notification-indicator";
|
||||
|
||||
import { ResourceDetailSkeleton } from "./resource-detail-skeleton";
|
||||
import type { CheckMeta } from "./use-resource-detail-drawer";
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { formatYAxisTick } from "./line-chart.utils";
|
||||
|
||||
describe("formatYAxisTick", () => {
|
||||
describe("when findings counts are large", () => {
|
||||
it("should compact six-digit values so Y-axis labels do not overflow", () => {
|
||||
// Given
|
||||
const tickValue = 150000;
|
||||
|
||||
// When
|
||||
const formattedValue = formatYAxisTick(tickValue);
|
||||
|
||||
// Then
|
||||
expect(formattedValue).toBe("150K");
|
||||
});
|
||||
|
||||
it("should compact million-scale values", () => {
|
||||
// Given
|
||||
const tickValue = 1200000;
|
||||
|
||||
// When
|
||||
const formattedValue = formatYAxisTick(tickValue);
|
||||
|
||||
// Then
|
||||
expect(formattedValue).toBe("1.2M");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when findings counts are small", () => {
|
||||
it("should keep values below 1000 readable without compact notation", () => {
|
||||
// Given
|
||||
const tickValue = 999;
|
||||
|
||||
// When
|
||||
const formattedValue = formatYAxisTick(tickValue);
|
||||
|
||||
// Then
|
||||
expect(formattedValue).toBe("999");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
ChartTooltip,
|
||||
} from "@/components/shadcn/chart/Chart";
|
||||
|
||||
import { formatYAxisTick } from "./line-chart.utils";
|
||||
import { AlertPill } from "./shared/alert-pill";
|
||||
import { ChartLegend } from "./shared/chart-legend";
|
||||
import { CustomActiveDot, PointClickData } from "./shared/custom-active-dot";
|
||||
@@ -222,6 +223,8 @@ export function LineChart({
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={formatYAxisTick}
|
||||
width={56}
|
||||
padding={{ top: 20 }}
|
||||
tick={{
|
||||
fill: "var(--color-text-neutral-secondary)",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
const Y_AXIS_TICK_FORMATTER = new Intl.NumberFormat("en-US", {
|
||||
notation: "compact",
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
|
||||
export function formatYAxisTick(value: number) {
|
||||
return Y_AXIS_TICK_FORMATTER.format(value);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ describe("ProwlerBrand", () => {
|
||||
|
||||
it("should render the Local Server lockups outside Cloud", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
// When
|
||||
render(<ProwlerBrand />);
|
||||
@@ -30,7 +30,7 @@ describe("ProwlerBrand", () => {
|
||||
|
||||
it("should render the Prowler Cloud lockups in Cloud", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
|
||||
// When
|
||||
render(<ProwlerBrand />);
|
||||
|
||||
@@ -4,11 +4,9 @@ import { SettingsIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { JiraIcon } from "@/components/icons/services/IconServices";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { Button, Card, CardContent, CardHeader } from "@/components/shadcn";
|
||||
import { CustomLink } from "@/components/shadcn/custom/custom-link";
|
||||
|
||||
import { Card, CardContent, CardHeader } from "../../shadcn";
|
||||
|
||||
export const JiraIntegrationCard = () => {
|
||||
return (
|
||||
<Card variant="base" padding="lg">
|
||||
|
||||
@@ -15,15 +15,19 @@ import {
|
||||
IntegrationCardHeader,
|
||||
IntegrationSkeleton,
|
||||
} from "@/components/integrations/shared";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import {
|
||||
Button,
|
||||
useToast,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
} from "@/components/shadcn";
|
||||
import { Modal } from "@/components/shadcn/modal";
|
||||
import { DataTablePagination } from "@/components/shadcn/table/data-table-pagination";
|
||||
import { triggerTestConnectionWithDelay } from "@/lib/integrations/test-connection-helper";
|
||||
import { MetaDataProps } from "@/types";
|
||||
import { IntegrationProps } from "@/types/integrations";
|
||||
|
||||
import { Card, CardContent, CardHeader } from "../../shadcn";
|
||||
import { JiraIntegrationForm } from "./jira-integration-form";
|
||||
|
||||
interface JiraIntegrationsManagerProps {
|
||||
|
||||
@@ -4,11 +4,9 @@ import { SettingsIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { AmazonS3Icon } from "@/components/icons/services/IconServices";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { Button, Card, CardContent, CardHeader } from "@/components/shadcn";
|
||||
import { CustomLink } from "@/components/shadcn/custom/custom-link";
|
||||
|
||||
import { Card, CardContent, CardHeader } from "../../shadcn";
|
||||
|
||||
export const S3IntegrationCard = () => {
|
||||
return (
|
||||
<Card variant="base" padding="lg">
|
||||
|
||||
@@ -13,8 +13,7 @@ import {
|
||||
ProviderTypeIcon,
|
||||
} from "@/components/icons/providers-badge/provider-type-icon";
|
||||
import { AWSRoleCredentialsForm } from "@/components/providers/workflow/forms/select-credentials-type/aws/credentials-type/aws-role-credentials-form";
|
||||
import { Separator } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Separator, useToast } from "@/components/shadcn";
|
||||
import { CustomInput } from "@/components/shadcn/custom";
|
||||
import { CustomLink } from "@/components/shadcn/custom/custom-link";
|
||||
import {
|
||||
@@ -26,6 +25,7 @@ import {
|
||||
import { FormButtons } from "@/components/shadcn/form/form-buttons";
|
||||
import { EnhancedMultiSelect } from "@/components/shadcn/select/enhanced-multi-select";
|
||||
import { getAWSCredentialsTemplateLinks } from "@/lib";
|
||||
import { isCloud } from "@/lib/shared/env";
|
||||
import type { AWSCredentialsRole } from "@/types";
|
||||
import type { IntegrationProps } from "@/types/integrations";
|
||||
import {
|
||||
@@ -78,10 +78,9 @@ export const S3IntegrationForm = ({
|
||||
const isEditingConfig = editMode === "configuration";
|
||||
const isEditingCredentials = editMode === "credentials";
|
||||
|
||||
const defaultCredentialsType =
|
||||
process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true"
|
||||
? "aws-sdk-default"
|
||||
: "access-secret-key";
|
||||
const defaultCredentialsType = isCloud()
|
||||
? "aws-sdk-default"
|
||||
: "access-secret-key";
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(
|
||||
|
||||
@@ -15,8 +15,13 @@ import {
|
||||
IntegrationCardHeader,
|
||||
IntegrationSkeleton,
|
||||
} from "@/components/integrations/shared";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import {
|
||||
Button,
|
||||
useToast,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
} from "@/components/shadcn";
|
||||
import { Modal } from "@/components/shadcn/modal";
|
||||
import { DataTablePagination } from "@/components/shadcn/table/data-table-pagination";
|
||||
import { triggerTestConnectionWithDelay } from "@/lib/integrations/test-connection-helper";
|
||||
@@ -24,7 +29,6 @@ import { MetaDataProps } from "@/types";
|
||||
import { IntegrationProps } from "@/types/integrations";
|
||||
import { ProviderProps } from "@/types/providers";
|
||||
|
||||
import { Card, CardContent, CardHeader } from "../../shadcn";
|
||||
import { S3IntegrationForm } from "./s3-integration-form";
|
||||
|
||||
interface S3IntegrationsManagerProps {
|
||||
|
||||
@@ -12,8 +12,13 @@ import { z } from "zod";
|
||||
|
||||
import { createSamlConfig, updateSamlConfig } from "@/actions/integrations";
|
||||
import { AddIcon } from "@/components/icons";
|
||||
import { Button, Card, CardContent, CardHeader } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
useToast,
|
||||
} from "@/components/shadcn";
|
||||
import { CodeSnippet } from "@/components/shadcn/code-snippet/code-snippet";
|
||||
import { CustomServerInput } from "@/components/shadcn/custom";
|
||||
import { CustomLink } from "@/components/shadcn/custom/custom-link";
|
||||
|
||||
@@ -4,11 +4,9 @@ import { SettingsIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { AWSSecurityHubIcon } from "@/components/icons/services/IconServices";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { Button, Card, CardContent, CardHeader } from "@/components/shadcn";
|
||||
import { CustomLink } from "@/components/shadcn/custom/custom-link";
|
||||
|
||||
import { Card, CardContent, CardHeader } from "../../shadcn";
|
||||
|
||||
export const SecurityHubIntegrationCard = () => {
|
||||
return (
|
||||
<Card variant="base" padding="lg">
|
||||
|
||||
@@ -12,8 +12,7 @@ import {
|
||||
ProviderTypeIcon,
|
||||
} from "@/components/icons/providers-badge/provider-type-icon";
|
||||
import { AWSRoleCredentialsForm } from "@/components/providers/workflow/forms/select-credentials-type/aws/credentials-type/aws-role-credentials-form";
|
||||
import { Checkbox, Separator } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Checkbox, Separator, useToast } from "@/components/shadcn";
|
||||
import { CustomLink } from "@/components/shadcn/custom/custom-link";
|
||||
import {
|
||||
Form,
|
||||
|
||||
@@ -15,8 +15,14 @@ import {
|
||||
IntegrationCardHeader,
|
||||
IntegrationSkeleton,
|
||||
} from "@/components/integrations/shared";
|
||||
import { Badge, Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
useToast,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
} from "@/components/shadcn";
|
||||
import { Modal } from "@/components/shadcn/modal";
|
||||
import { DataTablePagination } from "@/components/shadcn/table/data-table-pagination";
|
||||
import { triggerTestConnectionWithDelay } from "@/lib/integrations/test-connection-helper";
|
||||
@@ -24,7 +30,6 @@ import { MetaDataProps } from "@/types";
|
||||
import { IntegrationProps } from "@/types/integrations";
|
||||
import { ProviderProps } from "@/types/providers";
|
||||
|
||||
import { Card, CardContent, CardHeader } from "../../shadcn";
|
||||
import { SecurityHubIntegrationForm } from "./security-hub-integration-form";
|
||||
|
||||
interface SecurityHubIntegrationsManagerProps {
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
import { ExternalLinkIcon, LucideIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { Button, Card, CardContent, CardHeader } from "@/components/shadcn";
|
||||
import { CustomLink } from "@/components/shadcn/custom/custom-link";
|
||||
|
||||
import { Card, CardContent, CardHeader } from "../../shadcn";
|
||||
|
||||
interface LinkCardProps {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
|
||||
@@ -7,8 +7,7 @@ import * as z from "zod";
|
||||
|
||||
import { revokeInvite } from "@/actions/invitations/invitation";
|
||||
import { DeleteIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, useToast } from "@/components/shadcn";
|
||||
import { Form } from "@/components/shadcn/form";
|
||||
|
||||
const formSchema = z.object({
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
import { updateInvite } from "@/actions/invitations/invitation";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { useToast, Card, CardContent } from "@/components/shadcn";
|
||||
import { CustomInput } from "@/components/shadcn/custom";
|
||||
import { Form, FormButtons } from "@/components/shadcn/form";
|
||||
import {
|
||||
@@ -17,8 +17,6 @@ import {
|
||||
} from "@/components/shadcn/select/select";
|
||||
import { editInviteFormSchema } from "@/types";
|
||||
|
||||
import { Card, CardContent } from "../../shadcn";
|
||||
|
||||
export const EditForm = ({
|
||||
invitationId,
|
||||
invitationEmail,
|
||||
|
||||
@@ -7,8 +7,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
import { sendInvite } from "@/actions/invitations/invitation";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, useToast } from "@/components/shadcn";
|
||||
import { CustomInput } from "@/components/shadcn/custom";
|
||||
import { Form } from "@/components/shadcn/form";
|
||||
import {
|
||||
|
||||
@@ -2,19 +2,18 @@
|
||||
|
||||
import { useControlledState } from "@react-stately/utils";
|
||||
import { domAnimation, LazyMotion, m } from "framer-motion";
|
||||
import type { ComponentProps } from "react";
|
||||
import React from "react";
|
||||
import { forwardRef, useMemo } from "react";
|
||||
import type { ComponentProps, HTMLAttributes, ReactNode } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export type VerticalStepProps = {
|
||||
className?: string;
|
||||
description?: React.ReactNode;
|
||||
title?: React.ReactNode;
|
||||
description?: ReactNode;
|
||||
title?: ReactNode;
|
||||
};
|
||||
|
||||
export interface VerticalStepsProps
|
||||
extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
export interface VerticalStepsProps extends HTMLAttributes<HTMLButtonElement> {
|
||||
/**
|
||||
* An array of steps.
|
||||
*
|
||||
@@ -89,10 +88,7 @@ function CheckIcon(props: ComponentProps<"svg">) {
|
||||
);
|
||||
}
|
||||
|
||||
export const VerticalSteps = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
VerticalStepsProps
|
||||
>(
|
||||
export const VerticalSteps = forwardRef<HTMLButtonElement, VerticalStepsProps>(
|
||||
(
|
||||
{
|
||||
color = "primary",
|
||||
@@ -113,7 +109,7 @@ export const VerticalSteps = React.forwardRef<
|
||||
onStepChange,
|
||||
);
|
||||
|
||||
const colors = React.useMemo(() => {
|
||||
const colors = useMemo(() => {
|
||||
let userColor;
|
||||
let fgColor;
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ describe("AppSidebarContent", () => {
|
||||
|
||||
it("shares the brand, Launch Scan action and Local Server Cloud affordances", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
vi.stubEnv("NEXT_PUBLIC_PROWLER_RELEASE_VERSION", "5.8.0");
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -91,7 +91,7 @@ describe("AppSidebarContent", () => {
|
||||
|
||||
it("keeps the existing Lighthouse chat sidebar in Cloud Chat mode", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
useAppSidebarMode.setState({ mode: APP_SIDEBAR_MODE.CHAT });
|
||||
|
||||
// When
|
||||
@@ -109,7 +109,7 @@ describe("AppSidebarContent", () => {
|
||||
|
||||
it("opens the current scan modal instead of navigating from the scans route", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
pathnameValue.current = "/scans";
|
||||
const user = userEvent.setup();
|
||||
|
||||
|
||||
@@ -24,15 +24,20 @@ interface AppSidebarContentProps {
|
||||
export function AppSidebarContent({ onSelect }: AppSidebarContentProps) {
|
||||
const pathname = usePathname();
|
||||
const { permissions } = useAuth();
|
||||
const { apiDocsUrl } = useRuntimeConfig();
|
||||
const { apiDocsUrl, cloudBillingEnabled } = useRuntimeConfig();
|
||||
const mode = useAppSidebarMode((state) => state.mode);
|
||||
const isCloudEnvironment = isCloud();
|
||||
const sections = getNavigationConfig({ pathname, apiDocsUrl, permissions });
|
||||
const sections = getNavigationConfig({
|
||||
pathname,
|
||||
apiDocsUrl,
|
||||
cloudBillingEnabled,
|
||||
permissions,
|
||||
});
|
||||
const showChat = isCloudEnvironment && mode === APP_SIDEBAR_MODE.CHAT;
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full min-h-0 w-full flex-col overflow-hidden">
|
||||
<div className="shrink-0 px-5 pt-6 pb-7">
|
||||
<div className="shrink-0 px-5 pt-8 pb-7">
|
||||
<Link
|
||||
href="/"
|
||||
aria-label="Prowler home"
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("getNavigationConfig", () => {
|
||||
|
||||
it("groups the Local Server navigation without losing available features", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
// When
|
||||
const sections = getNavigationConfig({
|
||||
@@ -71,7 +71,7 @@ describe("getNavigationConfig", () => {
|
||||
|
||||
it("models Local Server Cloud features as contextual upgrade actions", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
// When
|
||||
const children = getConfigurationChildren();
|
||||
@@ -108,7 +108,7 @@ describe("getNavigationConfig", () => {
|
||||
|
||||
it("uses Cloud destinations and current New badges", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
|
||||
// When
|
||||
const sections = getNavigationConfig({
|
||||
@@ -148,7 +148,7 @@ describe("getNavigationConfig", () => {
|
||||
|
||||
it("keeps the Cloud Billing destination for users with billing permission", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const permissions = {
|
||||
manage_billing: true,
|
||||
} as RolePermissionAttributes;
|
||||
@@ -157,6 +157,7 @@ describe("getNavigationConfig", () => {
|
||||
const billing = getNavigationConfig({
|
||||
pathname: "/billing",
|
||||
apiDocsUrl: null,
|
||||
cloudBillingEnabled: true,
|
||||
permissions,
|
||||
})
|
||||
.flatMap((section) => section.items)
|
||||
@@ -177,29 +178,40 @@ describe("getNavigationConfig", () => {
|
||||
const permissions = {
|
||||
manage_billing: false,
|
||||
} as RolePermissionAttributes;
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
|
||||
// When
|
||||
const cloudItems = getNavigationConfig({
|
||||
pathname: "/",
|
||||
apiDocsUrl: null,
|
||||
cloudBillingEnabled: true,
|
||||
permissions,
|
||||
}).flatMap((section) => section.items);
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
const enterpriseItems = getNavigationConfig({
|
||||
pathname: "/",
|
||||
apiDocsUrl: null,
|
||||
cloudBillingEnabled: false,
|
||||
permissions: { ...permissions, manage_billing: true },
|
||||
}).flatMap((section) => section.items);
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
const localItems = getNavigationConfig({
|
||||
pathname: "/",
|
||||
apiDocsUrl: null,
|
||||
cloudBillingEnabled: true,
|
||||
permissions: { ...permissions, manage_billing: true },
|
||||
}).flatMap((section) => section.items);
|
||||
|
||||
// Then
|
||||
expect(cloudItems.find((item) => item.label === "Billing")).toBeUndefined();
|
||||
expect(
|
||||
enterpriseItems.find((item) => item.label === "Billing"),
|
||||
).toBeUndefined();
|
||||
expect(localItems.find((item) => item.label === "Billing")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps environment-specific API documentation destinations", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
// When
|
||||
const localApiReference = getNavigationConfig({
|
||||
@@ -209,7 +221,7 @@ describe("getNavigationConfig", () => {
|
||||
.flatMap((section) => section.items)
|
||||
.find((item) => item.label === "API Reference");
|
||||
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const cloudApiReference = getNavigationConfig({
|
||||
pathname: "/",
|
||||
apiDocsUrl: "https://ignored.example/docs",
|
||||
@@ -228,7 +240,7 @@ describe("getNavigationConfig", () => {
|
||||
|
||||
it("omits the Local Server API reference when no URL is configured", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
// When
|
||||
const items = getNavigationConfig({
|
||||
@@ -244,7 +256,7 @@ describe("getNavigationConfig", () => {
|
||||
|
||||
it("filters navigation by required permission after visible copy changes", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const sections = getNavigationConfig({
|
||||
pathname: "/integrations",
|
||||
apiDocsUrl: null,
|
||||
@@ -282,7 +294,7 @@ describe("getNavigationConfig", () => {
|
||||
|
||||
it("keeps navigation when the required permission is granted", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const permissions = {
|
||||
manage_integrations: true,
|
||||
} as RolePermissionAttributes;
|
||||
@@ -308,7 +320,7 @@ describe("getNavigationConfig", () => {
|
||||
|
||||
it("matches complete route segments without stealing nested settings routes", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
// When
|
||||
const scanDetails = getNavigationConfig({
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
interface NavigationConfigOptions {
|
||||
pathname: string;
|
||||
apiDocsUrl?: string | null;
|
||||
cloudBillingEnabled?: boolean;
|
||||
permissions?: RolePermissionAttributes;
|
||||
}
|
||||
|
||||
@@ -106,6 +107,7 @@ export function filterNavigationByPermissions(
|
||||
export function getNavigationConfig({
|
||||
pathname,
|
||||
apiDocsUrl = null,
|
||||
cloudBillingEnabled = false,
|
||||
permissions,
|
||||
}: NavigationConfigOptions): NavigationSection[] {
|
||||
const isCloudEnvironment = isCloud();
|
||||
@@ -265,7 +267,7 @@ export function getNavigationConfig({
|
||||
},
|
||||
],
|
||||
},
|
||||
...(isCloudEnvironment
|
||||
...(isCloudEnvironment && cloudBillingEnabled
|
||||
? [
|
||||
{
|
||||
kind: NAVIGATION_ITEM_KIND.LINK,
|
||||
|
||||
@@ -67,7 +67,7 @@ describe("NavbarClient", () => {
|
||||
navigationMocks.searchParams = new URLSearchParams();
|
||||
window.localStorage.clear();
|
||||
// Replay icon is Cloud-only.
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
// Default: the current route's content has loaded, so the icon is enabled.
|
||||
usePageReadyStore.setState({ readyPath: "/findings" });
|
||||
useSidePanelStore.setState({
|
||||
@@ -223,7 +223,7 @@ describe("NavbarClient", () => {
|
||||
});
|
||||
|
||||
it("hides the replay icon entirely in self-hosted (OSS) deployments", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
render(
|
||||
<NavbarClient
|
||||
@@ -280,7 +280,7 @@ describe("NavbarClient", () => {
|
||||
|
||||
it("hides the Lighthouse AI side-panel trigger in self-hosted (OSS) deployments", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
// When
|
||||
render(<NavbarClient title="Findings" />);
|
||||
|
||||
@@ -34,8 +34,8 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Combobox,
|
||||
useToast,
|
||||
} from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { CustomLink } from "@/components/shadcn/custom/custom-link";
|
||||
import { useMountEffect } from "@/hooks/use-mount-effect";
|
||||
import type { LighthouseProvider } from "@/types/lighthouse-v1";
|
||||
|
||||
@@ -17,8 +17,8 @@ import {
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
useToast,
|
||||
} from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { CustomTextarea } from "@/components/shadcn/custom";
|
||||
import { Form } from "@/components/shadcn/form";
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
import { createProviderGroup } from "@/actions/manage-groups";
|
||||
import { Button, Separator } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, Separator, useToast } from "@/components/shadcn";
|
||||
import { CustomInput } from "@/components/shadcn/custom";
|
||||
import { Form } from "@/components/shadcn/form";
|
||||
import { EnhancedMultiSelect } from "@/components/shadcn/select/enhanced-multi-select";
|
||||
|
||||
@@ -8,8 +8,7 @@ import * as z from "zod";
|
||||
|
||||
import { deleteProviderGroup } from "@/actions/manage-groups/manage-groups";
|
||||
import { DeleteIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, useToast } from "@/components/shadcn";
|
||||
import { Form } from "@/components/shadcn/form";
|
||||
|
||||
const formSchema = z.object({
|
||||
|
||||
@@ -7,8 +7,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
import { updateProviderGroup } from "@/actions/manage-groups/manage-groups";
|
||||
import { Button, Separator } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, Separator, useToast } from "@/components/shadcn";
|
||||
import { CustomInput } from "@/components/shadcn/custom";
|
||||
import { Form } from "@/components/shadcn/form";
|
||||
import { EnhancedMultiSelect } from "@/components/shadcn/select/enhanced-multi-select";
|
||||
|
||||
@@ -84,7 +84,7 @@ describe("OnboardingTrigger", () => {
|
||||
useDriverTourMock.mockClear();
|
||||
capturedOnClosed = undefined;
|
||||
// Trigger only resolves in cloud.
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
searchParamsValue = new URLSearchParams();
|
||||
sliceState = {
|
||||
active: false,
|
||||
@@ -196,7 +196,7 @@ describe("OnboardingTrigger", () => {
|
||||
|
||||
describe("in self-hosted (OSS) deployments", () => {
|
||||
it("renders null and never starts the tour, even with a matching param", async () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
searchParamsValue = new URLSearchParams("onboarding=add-provider");
|
||||
|
||||
const { container } = render(
|
||||
@@ -208,7 +208,7 @@ describe("OnboardingTrigger", () => {
|
||||
});
|
||||
|
||||
it("ignores an active sequence slice in OSS", async () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
setSlice({
|
||||
active: true,
|
||||
currentFlowId: "add-provider",
|
||||
|
||||
@@ -7,8 +7,7 @@ import * as z from "zod";
|
||||
|
||||
import { deleteProvider } from "@/actions/providers";
|
||||
import { DeleteIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, useToast } from "@/components/shadcn";
|
||||
import { Form } from "@/components/shadcn/form";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
deleteOrganizationalUnit,
|
||||
} from "@/actions/organizations/organizations";
|
||||
import { DeleteIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, useToast } from "@/components/shadcn";
|
||||
import {
|
||||
PROVIDERS_GROUP_KIND,
|
||||
ProvidersGroupKind,
|
||||
|
||||
@@ -4,8 +4,7 @@ import type { Dispatch, FormEvent, SetStateAction } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { SaveIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, useToast } from "@/components/shadcn";
|
||||
import { Input } from "@/components/shadcn/input/input";
|
||||
|
||||
interface EditNameFormProps {
|
||||
|
||||
@@ -15,7 +15,7 @@ describe("AwsMethodSelector", () => {
|
||||
|
||||
it("opens the AWS Organizations upgrade in Local Server", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
const user = userEvent.setup();
|
||||
const onSelectOrganizations = vi.fn();
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
pollConnectionTask,
|
||||
runWithConcurrencyLimit,
|
||||
} from "../org-account-selection.utils";
|
||||
|
||||
import { extractErrorMessage } from "./error-utils";
|
||||
|
||||
interface SelectionState {
|
||||
|
||||
@@ -115,7 +115,7 @@ describe("OrgSetupForm", () => {
|
||||
|
||||
expect(params.get("param_DeployFromDelegatedAdmin")).toBe("true");
|
||||
expect(
|
||||
screen.getByLabelText("Delegated Administrator Account Role ARN"),
|
||||
screen.getByLabelText("Delegated Administrator Account IAM Role ARN"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -41,7 +41,7 @@ const orgSetupSchema = z.object({
|
||||
roleArn: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Role ARN is required")
|
||||
.min(1, "IAM Role ARN is required")
|
||||
.regex(
|
||||
/^arn:aws:iam::\d{12}:role\//,
|
||||
"Must be a valid IAM Role ARN (e.g., arn:aws:iam::123456789012:role/ProwlerScan)",
|
||||
@@ -400,7 +400,7 @@ export function OrgSetupForm({
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-text-neutral-primary text-sm leading-7 font-normal">
|
||||
1) Choose the AWS <strong>Organizational Unit</strong> (or root)
|
||||
to deploy to. Prowler creates the role in your deployment
|
||||
to deploy to. Prowler creates the IAM Role in your deployment
|
||||
account and rolls it out to every member account under this
|
||||
target.
|
||||
</p>
|
||||
@@ -422,7 +422,7 @@ export function OrgSetupForm({
|
||||
to the whole organization, or an <strong>OU ID</strong> (starts
|
||||
with <code>ou-</code>) to target a specific unit.
|
||||
</p>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Controller
|
||||
name="deployFromDelegatedAdmin"
|
||||
control={control}
|
||||
@@ -430,7 +430,7 @@ export function OrgSetupForm({
|
||||
<>
|
||||
<Checkbox
|
||||
id="deployFromDelegatedAdmin"
|
||||
className="mt-0.5"
|
||||
size="sm"
|
||||
checked={field.value}
|
||||
onCheckedChange={(checked) =>
|
||||
field.onChange(Boolean(checked))
|
||||
@@ -438,10 +438,10 @@ export function OrgSetupForm({
|
||||
/>
|
||||
<label
|
||||
htmlFor="deployFromDelegatedAdmin"
|
||||
className="text-text-neutral-tertiary text-xs leading-5 font-normal"
|
||||
className="text-text-neutral-secondary text-sm leading-5 font-medium"
|
||||
>
|
||||
I'm deploying from a delegated administrator
|
||||
account (not the Organization management account)
|
||||
I'm deploying from a Delegated Administrator
|
||||
Account (not the Management Account)
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
@@ -454,12 +454,12 @@ export function OrgSetupForm({
|
||||
<p className="text-text-neutral-primary text-sm leading-7 font-normal">
|
||||
2) Create the CloudFormation Stack in your{" "}
|
||||
<strong>{deploymentAccountName}</strong>. It deploys the
|
||||
ProwlerScan role and a service-managed StackSet that rolls the
|
||||
role out to your member accounts in one step.
|
||||
ProwlerScan IAM Role and a service-managed StackSet that rolls
|
||||
the IAM Role out to your member accounts in one step.
|
||||
</p>
|
||||
{orgQuickLink ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
variant="default"
|
||||
size="xl"
|
||||
className="w-full justify-start"
|
||||
asChild
|
||||
@@ -476,7 +476,7 @@ export function OrgSetupForm({
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
variant="default"
|
||||
size="xl"
|
||||
className="w-full justify-start"
|
||||
disabled
|
||||
@@ -486,7 +486,7 @@ export function OrgSetupForm({
|
||||
</Button>
|
||||
)}
|
||||
{!isOrgUnitIdValid && (
|
||||
<p className="text-text-neutral-tertiary text-xs leading-5">
|
||||
<p className="text-text-error-primary text-xs leading-5">
|
||||
Enter a valid Organizational Unit or Root ID above to enable
|
||||
deployment.
|
||||
</p>
|
||||
@@ -496,15 +496,15 @@ export function OrgSetupForm({
|
||||
{/* Step 3: Role ARN + confirm */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-text-neutral-primary text-sm leading-7 font-normal">
|
||||
3) Paste the {deploymentAccountName} Role ARN and confirm the
|
||||
deployment is complete.
|
||||
3) Paste the {deploymentAccountName} IAM Role ARN and confirm
|
||||
the deployment is complete.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="roleArn"
|
||||
label={`${deploymentAccountLabel} Role ARN`}
|
||||
label={`${deploymentAccountLabel} IAM Role ARN`}
|
||||
labelPlacement="outside"
|
||||
placeholder="e.g. arn:aws:iam::123456789012:role/ProwlerScan"
|
||||
isRequired={false}
|
||||
@@ -516,7 +516,7 @@ export function OrgSetupForm({
|
||||
ARN
|
||||
</p>
|
||||
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Controller
|
||||
name="stackSetDeployed"
|
||||
control={control}
|
||||
@@ -524,7 +524,7 @@ export function OrgSetupForm({
|
||||
<>
|
||||
<Checkbox
|
||||
id="stackSetDeployed"
|
||||
className="mt-0.5"
|
||||
size="sm"
|
||||
checked={field.value}
|
||||
onCheckedChange={(checked) =>
|
||||
field.onChange(Boolean(checked))
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { setScanConfigurationProviders } from "@/actions/scan-configurations";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, useToast } from "@/components/shadcn";
|
||||
import { CustomLink } from "@/components/shadcn/custom/custom-link";
|
||||
import { Modal } from "@/components/shadcn/modal";
|
||||
import {
|
||||
|
||||
@@ -31,6 +31,7 @@ import type {
|
||||
} from "@/types/schedules";
|
||||
|
||||
import { LinkToScans } from "../link-to-scans";
|
||||
|
||||
import { DataTableRowActions } from "./data-table-row-actions";
|
||||
|
||||
interface GroupNameChipsProps {
|
||||
|
||||
@@ -393,7 +393,7 @@ describe("DataTableRowActions", () => {
|
||||
|
||||
it("opens Edit Scan Schedule for Prowler Cloud subscribed provider rows", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
@@ -420,7 +420,7 @@ describe("DataTableRowActions", () => {
|
||||
|
||||
it("hides Edit Scan Schedule for manual-only Cloud provider rows", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
@@ -445,7 +445,7 @@ describe("DataTableRowActions", () => {
|
||||
|
||||
it("hides Edit Scan Schedule for blocked Cloud provider rows", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
@@ -470,7 +470,7 @@ describe("DataTableRowActions", () => {
|
||||
|
||||
it("opens scan config management with the precomputed current config id", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
@@ -500,7 +500,7 @@ describe("DataTableRowActions", () => {
|
||||
|
||||
it("shows scan config management as unavailable when scan configs failed to load", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
@@ -528,7 +528,7 @@ describe("DataTableRowActions", () => {
|
||||
|
||||
it("hides Edit Scan Configuration for dynamic providers in Prowler Cloud", async () => {
|
||||
// Given a dynamic provider in a Cloud tenant with scan configs available.
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "@/types/provider-wizard";
|
||||
|
||||
import type { ProviderWizardInitialData } from "../types";
|
||||
|
||||
import { useProviderWizardController } from "./use-provider-wizard-controller";
|
||||
|
||||
const { refreshMock, requestOpenOnWizardCloseMock } = vi.hoisted(() => ({
|
||||
@@ -46,7 +47,7 @@ describe("useProviderWizardController", () => {
|
||||
sessionStorage.clear();
|
||||
localStorage.clear();
|
||||
// Checkpoint is Cloud-only.
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
useProviderWizardStore.getState().reset();
|
||||
useOrgSetupStore.getState().reset();
|
||||
});
|
||||
@@ -117,7 +118,7 @@ describe("useProviderWizardController", () => {
|
||||
});
|
||||
|
||||
it("does not request the onboarding checkpoint in self-hosted (OSS) deployments", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
const onOpenChange = vi.fn();
|
||||
const { result } = renderHook(() =>
|
||||
useProviderWizardController({
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import { SelectViaGitHub } from "../../workflow/forms/select-credentials-type/github";
|
||||
import { SelectViaM365 } from "../../workflow/forms/select-credentials-type/m365";
|
||||
import { UpdateViaServiceAccountForm } from "../../workflow/forms/update-via-service-account-key-form";
|
||||
|
||||
import {
|
||||
WIZARD_FOOTER_ACTION_TYPE,
|
||||
WizardFooterConfig,
|
||||
|
||||
@@ -76,7 +76,7 @@ describe("LaunchStep", () => {
|
||||
|
||||
describe("Prowler OSS (non-Cloud)", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
scanOnDemandMock.mockResolvedValue({ data: { id: "scan-1" } });
|
||||
});
|
||||
|
||||
@@ -174,7 +174,7 @@ describe("LaunchStep", () => {
|
||||
|
||||
describe("Prowler Cloud subscribed", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
updateScheduleMock.mockResolvedValue({ data: { id: "provider-1" } });
|
||||
});
|
||||
|
||||
@@ -370,7 +370,7 @@ describe("LaunchStep", () => {
|
||||
|
||||
describe("Prowler Cloud trial/onboarding (manual scan only)", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
scanOnDemandMock.mockResolvedValue({ data: { id: "scan-1" } });
|
||||
});
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ import {
|
||||
saveScheduleWithInitialScan,
|
||||
} from "@/components/scans/schedule/save-schedule";
|
||||
import { ScanScheduleFields } from "@/components/scans/schedule/scan-schedule-fields";
|
||||
import { Field, FieldLabel } from "@/components/shadcn";
|
||||
import { ToastAction, useToast } from "@/components/shadcn";
|
||||
import { Field, FieldLabel, ToastAction, useToast } from "@/components/shadcn";
|
||||
import { Badge } from "@/components/shadcn/badge/badge";
|
||||
import { EntityInfo } from "@/components/shadcn/entities";
|
||||
import {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
TestConnectionForm,
|
||||
TestConnectionProviderData,
|
||||
} from "../../workflow/forms/test-connection-form";
|
||||
|
||||
import {
|
||||
WIZARD_FOOTER_ACTION_TYPE,
|
||||
WizardFooterConfig,
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
} from "@/types";
|
||||
|
||||
import { ProviderTitleDocs } from "../provider-title-docs";
|
||||
|
||||
import {
|
||||
AlibabaCloudRoleCredentialsForm,
|
||||
AlibabaCloudStaticCredentialsForm,
|
||||
|
||||
@@ -11,8 +11,7 @@ import { addProvider } from "@/actions/providers/providers";
|
||||
import { AwsMethodSelector } from "@/components/providers/organizations/aws-method-selector";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { ProviderTitleDocs } from "@/components/providers/workflow/provider-title-docs";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, useToast } from "@/components/shadcn";
|
||||
import { Form } from "@/components/shadcn/form";
|
||||
import {
|
||||
addProviderFormSchema,
|
||||
|
||||
+2
-1
@@ -14,6 +14,7 @@ import {
|
||||
} from "@/components/shadcn/select/select";
|
||||
import { Separator } from "@/components/shadcn/separator/separator";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { isCloud } from "@/lib/shared/env";
|
||||
import { AWSCredentialsRole } from "@/types";
|
||||
import { IntegrationType } from "@/types/integrations";
|
||||
|
||||
@@ -36,7 +37,7 @@ export const AWSRoleCredentialsForm = ({
|
||||
type?: "providers" | "integrations";
|
||||
integrationType?: IntegrationType;
|
||||
}) => {
|
||||
const isCloudEnv = process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true";
|
||||
const isCloudEnv = isCloud();
|
||||
const defaultCredentialsType = isCloudEnv
|
||||
? "aws-sdk-default"
|
||||
: "access-secret-key";
|
||||
|
||||
@@ -14,8 +14,7 @@ import { CheckIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { Form } from "@/components/shadcn/form";
|
||||
import { testProviderConnection } from "@/lib/provider-helpers";
|
||||
import { ProviderType } from "@/types";
|
||||
import { testConnectionFormSchema } from "@/types";
|
||||
import { ProviderType, testConnectionFormSchema } from "@/types";
|
||||
|
||||
import { ProviderConnectionInfo } from "./provider-connection-info";
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Control } from "react-hook-form";
|
||||
import { useWatch } from "react-hook-form";
|
||||
import { Control, useWatch } from "react-hook-form";
|
||||
|
||||
import { WizardTextareaField } from "@/components/providers/workflow/forms/fields";
|
||||
import { KubernetesCredentials } from "@/types";
|
||||
|
||||
-10
@@ -48,16 +48,6 @@ export const OracleCloudCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.OCI_REGION}
|
||||
type="text"
|
||||
label="Region"
|
||||
labelPlacement="inside"
|
||||
placeholder="e.g. us-ashburn-1"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<WizardTextareaField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.OCI_KEY_CONTENT}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { getProviderName } from "@/components/shadcn/entities/get-provider-logo";
|
||||
import { getProviderLogo } from "@/components/shadcn/entities/get-provider-logo";
|
||||
import {
|
||||
getProviderName,
|
||||
getProviderLogo,
|
||||
} from "@/components/shadcn/entities/get-provider-logo";
|
||||
import { ProviderType } from "@/types";
|
||||
|
||||
export const ProviderTitleDocs = ({
|
||||
|
||||
@@ -18,8 +18,6 @@ import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/shadcn";
|
||||
import {
|
||||
BreadcrumbNavigation,
|
||||
CustomBreadcrumbItem,
|
||||
} from "@/components/shadcn";
|
||||
|
||||
@@ -96,7 +96,7 @@ describe("AddRoleForm", () => {
|
||||
|
||||
it("shows Manage Alerts in Prowler Cloud", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
|
||||
// When
|
||||
render(<AddRoleForm groups={[]} />);
|
||||
@@ -108,7 +108,7 @@ describe("AddRoleForm", () => {
|
||||
|
||||
it("hides Manage Alerts outside Prowler Cloud", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
// When
|
||||
render(<AddRoleForm groups={[]} />);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { DefaultValues } from "react-hook-form";
|
||||
import { addRole } from "@/actions/roles/roles";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { getErrorMessage } from "@/lib";
|
||||
import { isCloud } from "@/lib/shared/env";
|
||||
import { RoleFormValues } from "@/types";
|
||||
|
||||
import { RoleForm, RoleFormSubmitContext, RoleGroupOption } from "./role-form";
|
||||
@@ -13,7 +14,7 @@ import { RoleForm, RoleFormSubmitContext, RoleGroupOption } from "./role-form";
|
||||
export const AddRoleForm = ({ groups }: { groups: RoleGroupOption[] }) => {
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const isCloudEnvironment = process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true";
|
||||
const isCloudEnvironment = isCloud();
|
||||
|
||||
const defaultValues: DefaultValues<RoleFormValues> = {
|
||||
name: "",
|
||||
|
||||
@@ -7,8 +7,7 @@ import * as z from "zod";
|
||||
|
||||
import { deleteRole } from "@/actions/roles";
|
||||
import { DeleteIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, useToast } from "@/components/shadcn";
|
||||
import { Form } from "@/components/shadcn/form";
|
||||
|
||||
const formSchema = z.object({
|
||||
|
||||
@@ -6,6 +6,7 @@ import { DefaultValues } from "react-hook-form";
|
||||
import { updateRole } from "@/actions/roles/roles";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { getErrorMessage } from "@/lib";
|
||||
import { isCloud } from "@/lib/shared/env";
|
||||
import { RoleFormValues } from "@/types";
|
||||
|
||||
import { RoleForm, RoleFormSubmitContext, RoleGroupOption } from "./role-form";
|
||||
@@ -30,7 +31,7 @@ export const EditRoleForm = ({
|
||||
}) => {
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const isCloudEnvironment = process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true";
|
||||
const isCloudEnvironment = isCloud();
|
||||
|
||||
const defaultValues: DefaultValues<RoleFormValues> = {
|
||||
...roleData.data.attributes,
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
getUnlimitedVisibilityField,
|
||||
getVisiblePermissionFormFields,
|
||||
} from "@/lib/role-permissions";
|
||||
import { isCloud } from "@/lib/shared/env";
|
||||
import { roleFormSchema, RoleFormValues } from "@/types";
|
||||
|
||||
import { UnlimitedVisibilityField } from "./unlimited-visibility-section";
|
||||
@@ -74,7 +75,7 @@ export const RoleForm = ({
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const isCloudEnvironment = process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true";
|
||||
const isCloudEnvironment = isCloud();
|
||||
const visiblePermissionFormFields =
|
||||
getVisiblePermissionFormFields(isCloudEnvironment);
|
||||
const showUnlimitedVisibilityField = !!getUnlimitedVisibilityField();
|
||||
|
||||
@@ -2,19 +2,18 @@
|
||||
|
||||
import { useControlledState } from "@react-stately/utils";
|
||||
import { domAnimation, LazyMotion, m } from "framer-motion";
|
||||
import type { ComponentProps } from "react";
|
||||
import React from "react";
|
||||
import { forwardRef, useMemo } from "react";
|
||||
import type { ComponentProps, HTMLAttributes, ReactNode } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export type VerticalStepProps = {
|
||||
className?: string;
|
||||
description?: React.ReactNode;
|
||||
title?: React.ReactNode;
|
||||
description?: ReactNode;
|
||||
title?: ReactNode;
|
||||
};
|
||||
|
||||
export interface VerticalStepsProps
|
||||
extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
export interface VerticalStepsProps extends HTMLAttributes<HTMLButtonElement> {
|
||||
/**
|
||||
* An array of steps.
|
||||
*
|
||||
@@ -89,10 +88,7 @@ function CheckIcon(props: ComponentProps<"svg">) {
|
||||
);
|
||||
}
|
||||
|
||||
export const VerticalSteps = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
VerticalStepsProps
|
||||
>(
|
||||
export const VerticalSteps = forwardRef<HTMLButtonElement, VerticalStepsProps>(
|
||||
(
|
||||
{
|
||||
color = "primary",
|
||||
@@ -113,7 +109,7 @@ export const VerticalSteps = React.forwardRef<
|
||||
onStepChange,
|
||||
);
|
||||
|
||||
const colors = React.useMemo(() => {
|
||||
const colors = useMemo(() => {
|
||||
let userColor;
|
||||
let fgColor;
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("does not render an imported findings tab", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
render(
|
||||
<ScansPageShell providers={providers} hasManageScansPermission>
|
||||
@@ -172,7 +172,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("uses the shared scan filter bar for scan filters", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
render(
|
||||
<ScansPageShell providers={providers} hasManageScansPermission>
|
||||
@@ -190,7 +190,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("clears the active sort when switching tabs", async () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
searchParamsValue.current = "tab=active&sort=trigger";
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -209,7 +209,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("uses a generic type filter label in Cloud", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
|
||||
render(
|
||||
<ScansPageShell providers={providers} hasManageScansPermission>
|
||||
@@ -221,7 +221,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("shows the CLI import banner in Cloud", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
|
||||
render(
|
||||
<ScansPageShell providers={providers} hasManageScansPermission>
|
||||
@@ -239,7 +239,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("hides the CLI import banner outside Cloud", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
render(
|
||||
<ScansPageShell providers={providers} hasManageScansPermission>
|
||||
@@ -251,7 +251,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("keeps launch scan with filters and mutelist with tabs", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
render(
|
||||
<ScansPageShell providers={providers} hasManageScansPermission>
|
||||
@@ -273,7 +273,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("shows the active scans count in the in progress tab", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
render(
|
||||
<ScansPageShell
|
||||
@@ -292,7 +292,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("opens the launch scan modal from the URL", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
searchParamsValue.current = "launchScan=true";
|
||||
|
||||
render(
|
||||
@@ -305,7 +305,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("strips the launchScan URL param via the History API when closing the URL-opened modal", async () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
searchParamsValue.current = "tab=completed&launchScan=true";
|
||||
const replaceStateSpy = vi.spyOn(window.history, "replaceState");
|
||||
const user = userEvent.setup();
|
||||
@@ -333,7 +333,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("opens and closes the launch scan modal from client state without navigation", async () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
const user = userEvent.setup();
|
||||
useScansStore.getState().openLaunchScanModal();
|
||||
|
||||
@@ -352,7 +352,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("shows the status filter only on the completed tab", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
searchParamsValue.current = "tab=completed";
|
||||
|
||||
render(
|
||||
@@ -367,7 +367,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("hides the status filter outside of the completed tab", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
searchParamsValue.current = "tab=active";
|
||||
|
||||
render(
|
||||
@@ -382,7 +382,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("clears status filter when switching scan tabs", async () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
searchParamsValue.current = "tab=completed&filter%5Bstate__in%5D=failed";
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -400,7 +400,7 @@ describe("ScansPageShell", () => {
|
||||
});
|
||||
|
||||
it("clears type filter when switching to scheduled scans", async () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
searchParamsValue.current = "tab=completed&filter%5Btrigger%5D=manual";
|
||||
const user = userEvent.setup();
|
||||
|
||||
|
||||
@@ -3,7 +3,11 @@ import { render, screen } from "@testing-library/react";
|
||||
import type { ReactNode } from "react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { ScanProps } from "@/types";
|
||||
import { type ScanProps, SCAN_JOBS_TAB, type ScanJobsTab } from "@/types";
|
||||
import {
|
||||
SCAN_SCHEDULE_CAPABILITY,
|
||||
type ScanScheduleCapability,
|
||||
} from "@/types/schedules";
|
||||
|
||||
vi.mock("@/components/shadcn", async (importOriginal) => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
@@ -65,12 +69,6 @@ vi.mock("./scan-jobs-row-actions", () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
import { SCAN_JOBS_TAB, type ScanJobsTab } from "@/types";
|
||||
import {
|
||||
SCAN_SCHEDULE_CAPABILITY,
|
||||
type ScanScheduleCapability,
|
||||
} from "@/types/schedules";
|
||||
|
||||
import { getScanJobsColumns } from "./scan-jobs-columns";
|
||||
|
||||
const getColumnIds = (tab: ScanJobsTab) =>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { SCAN_JOBS_TAB, type ScanJobsTab, type ScanProps } from "@/types";
|
||||
import type { ScanScheduleCapability } from "@/types/schedules";
|
||||
|
||||
import { formatScanDuration } from "../scans.utils";
|
||||
|
||||
import {
|
||||
AccountCell,
|
||||
ProgressCell,
|
||||
|
||||
@@ -151,7 +151,7 @@ describe("ScanJobsRowActions", () => {
|
||||
|
||||
it("opens Edit Scan Schedule for Prowler Cloud subscribed scan rows", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<ScanJobsRowActions scan={makeScan()} tab="scheduled" />);
|
||||
@@ -173,7 +173,7 @@ describe("ScanJobsRowActions", () => {
|
||||
|
||||
it("hides Edit Scan Schedule outside Prowler Cloud (OSS)", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<ScanJobsRowActions scan={makeScan()} tab="scheduled" />);
|
||||
@@ -191,7 +191,7 @@ describe("ScanJobsRowActions", () => {
|
||||
|
||||
it("hides Edit Scan Schedule outside the Scheduled tab even on Cloud", async () => {
|
||||
// Given - advanced capability (Cloud) but rendered in the Completed tab.
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
@@ -214,7 +214,7 @@ describe("ScanJobsRowActions", () => {
|
||||
|
||||
it("hides Edit Scan Schedule for manual-only Cloud scan rows", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
@@ -238,7 +238,7 @@ describe("ScanJobsRowActions", () => {
|
||||
|
||||
it("hides Edit Scan Schedule for blocked Cloud scan rows", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { ScanScheduleCapability } from "@/types/schedules";
|
||||
|
||||
import { AutoRefresh } from "../auto-refresh";
|
||||
import { NoScansEmptyState } from "../no-scans-empty-state";
|
||||
|
||||
import { getScanJobsColumns } from "./scan-jobs-columns";
|
||||
|
||||
interface ScanJobsTableProps {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ComponentProps, ReactNode, useEffect, useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip";
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
|
||||
@@ -7,16 +7,47 @@ import { CLOUD_UPGRADE_FEATURE } from "@/types/cloud-upgrade";
|
||||
|
||||
import { CloudUpgradeModal } from "./cloud-upgrade-modal";
|
||||
|
||||
const modalTestState = vi.hoisted(() => ({
|
||||
keepContentMounted: false,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/shadcn/modal", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import("@/components/shadcn/modal")>();
|
||||
const { createElement } = await import("react");
|
||||
|
||||
return {
|
||||
...actual,
|
||||
Modal: (props: Parameters<typeof actual.Modal>[0]) => {
|
||||
if (!modalTestState.keepContentMounted) {
|
||||
return createElement(actual.Modal, props);
|
||||
}
|
||||
|
||||
return createElement(
|
||||
"div",
|
||||
{ "aria-label": props.title, role: "dialog" },
|
||||
createElement(
|
||||
"button",
|
||||
{ onClick: () => props.onOpenChange?.(false), type: "button" },
|
||||
"Close",
|
||||
),
|
||||
props.children,
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe("CloudUpgradeModal", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
modalTestState.keepContentMounted = false;
|
||||
vi.unstubAllEnvs();
|
||||
useCloudUpgradeStore.getState().closeCloudUpgrade();
|
||||
});
|
||||
|
||||
it("renders the active contextual upgrade in Local Server", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
useCloudUpgradeStore
|
||||
.getState()
|
||||
.openCloudUpgrade(CLOUD_UPGRADE_FEATURE.ALERTS);
|
||||
@@ -45,7 +76,7 @@ describe("CloudUpgradeModal", () => {
|
||||
|
||||
it("uses the standard equal-width CTA layout", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
useCloudUpgradeStore
|
||||
.getState()
|
||||
.openCloudUpgrade(CLOUD_UPGRADE_FEATURE.AWS_ORGANIZATIONS);
|
||||
@@ -119,7 +150,7 @@ describe("CloudUpgradeModal", () => {
|
||||
|
||||
it("closes the active upgrade and returns focus to its trigger", async () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
@@ -152,9 +183,45 @@ describe("CloudUpgradeModal", () => {
|
||||
expect(useCloudUpgradeStore.getState().activeFeature).toBeNull();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
feature: CLOUD_UPGRADE_FEATURE.ALERTS,
|
||||
otherTitle: "Add Your Entire AWS Organization",
|
||||
title: "Turn Findings into Alerts",
|
||||
},
|
||||
{
|
||||
feature: CLOUD_UPGRADE_FEATURE.AWS_ORGANIZATIONS,
|
||||
otherTitle: "Turn Findings into Alerts",
|
||||
title: "Add Your Entire AWS Organization",
|
||||
},
|
||||
])(
|
||||
"does not replace $title with another upgrade while closing",
|
||||
async ({ feature, otherTitle, title }) => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
modalTestState.keepContentMounted = true;
|
||||
const user = userEvent.setup();
|
||||
useCloudUpgradeStore.getState().openCloudUpgrade(feature);
|
||||
|
||||
render(<CloudUpgradeModal />);
|
||||
expect(screen.getByRole("dialog", { name: title })).toBeVisible();
|
||||
|
||||
// When
|
||||
await user.click(screen.getByRole("button", { name: "Close" }));
|
||||
|
||||
// Then
|
||||
expect(useCloudUpgradeStore.getState().activeFeature).toBeNull();
|
||||
expect(screen.getByRole("dialog", { name: title })).toBeVisible();
|
||||
expect(
|
||||
screen.queryByText("Scale Prowler Without Operating It"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(otherTitle)).not.toBeInTheDocument();
|
||||
},
|
||||
);
|
||||
|
||||
it("does not render upgrade UI in Prowler Cloud", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
useCloudUpgradeStore
|
||||
.getState()
|
||||
.openCloudUpgrade(CLOUD_UPGRADE_FEATURE.ALERTS);
|
||||
|
||||
@@ -14,12 +14,14 @@ import {
|
||||
} from "@/lib/cloud-upgrade";
|
||||
import { isCloud } from "@/lib/shared/env";
|
||||
import { useCloudUpgradeStore } from "@/store";
|
||||
import { CLOUD_UPGRADE_FEATURE } from "@/types/cloud-upgrade";
|
||||
|
||||
const allowInitialAutoFocus = () => {};
|
||||
|
||||
export const CloudUpgradeModal = () => {
|
||||
const activeFeature = useCloudUpgradeStore((state) => state.activeFeature);
|
||||
const retainedFeature = useCloudUpgradeStore(
|
||||
(state) => state.retainedFeature,
|
||||
);
|
||||
const closeCloudUpgrade = useCloudUpgradeStore(
|
||||
(state) => state.closeCloudUpgrade,
|
||||
);
|
||||
@@ -29,7 +31,7 @@ export const CloudUpgradeModal = () => {
|
||||
|
||||
if (isCloud()) return null;
|
||||
|
||||
const feature = activeFeature ?? CLOUD_UPGRADE_FEATURE.GENERAL;
|
||||
const feature = activeFeature ?? retainedFeature;
|
||||
const content = CLOUD_UPGRADE_CONTENT[feature];
|
||||
|
||||
return (
|
||||
|
||||
@@ -7,8 +7,7 @@ import * as z from "zod";
|
||||
|
||||
import { deleteUser } from "@/actions/users/users";
|
||||
import { DeleteIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, useToast } from "@/components/shadcn";
|
||||
import { Form } from "@/components/shadcn/form";
|
||||
|
||||
const formSchema = z.object({
|
||||
|
||||
@@ -7,8 +7,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
import { updateUser, updateUserRole } from "@/actions/users/users";
|
||||
import { Card } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Card, useToast } from "@/components/shadcn";
|
||||
import { CustomInput } from "@/components/shadcn/custom";
|
||||
import { Form, FormButtons } from "@/components/shadcn/form";
|
||||
import {
|
||||
|
||||
@@ -4,8 +4,7 @@ import { Dispatch, SetStateAction, useTransition } from "react";
|
||||
|
||||
import { removeUserFromTenant } from "@/actions/users/users";
|
||||
import { DeleteIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/shadcn";
|
||||
import { Button, useToast } from "@/components/shadcn";
|
||||
|
||||
interface ExpelUserFormProps {
|
||||
userId: string;
|
||||
|
||||
@@ -34,7 +34,7 @@ describe("RoleItem", () => {
|
||||
|
||||
it("shows Manage Alerts in Prowler Cloud role details", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
|
||||
// When
|
||||
render(<RoleItem role={role} roleDetail={roleDetail} />);
|
||||
@@ -45,7 +45,7 @@ describe("RoleItem", () => {
|
||||
|
||||
it("hides Manage Alerts outside Prowler Cloud role details", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
||||
|
||||
// When
|
||||
render(<RoleItem role={role} roleDetail={roleDetail} />);
|
||||
@@ -56,7 +56,7 @@ describe("RoleItem", () => {
|
||||
|
||||
it("displays the permission state as a badge", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
|
||||
// When
|
||||
render(<RoleItem role={role} roleDetail={roleDetail} />);
|
||||
@@ -67,7 +67,7 @@ describe("RoleItem", () => {
|
||||
|
||||
it("does not render the details toggle", () => {
|
||||
// Given
|
||||
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
||||
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
||||
|
||||
// When
|
||||
render(<RoleItem role={role} roleDetail={roleDetail} />);
|
||||
|
||||
Reference in new issue
Block a user