mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-09 21:14:22 +00:00
feat(ui): add Local Server Cloud upgrade prompts (#11982)
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { CLOUD_UPGRADE_FEATURE } from "@/types/cloud-upgrade";
|
||||
|
||||
import {
|
||||
CLOUD_UPGRADE_CONTENT,
|
||||
CLOUD_UPGRADE_FOOTER_NOTE,
|
||||
getCloudUpgradeCompareUrl,
|
||||
getCloudUpgradePrimaryUrl,
|
||||
} from "./cloud-upgrade";
|
||||
|
||||
describe("cloud upgrade content", () => {
|
||||
it("should use title case for every Cloud modal title", () => {
|
||||
// Given / When
|
||||
const titles = Object.values(CLOUD_UPGRADE_CONTENT).map(
|
||||
(content) => content.title,
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(titles).toEqual([
|
||||
"Keep Every Provider Checked Automatically",
|
||||
"Turn Findings into Alerts",
|
||||
"Add Your Entire AWS Organization",
|
||||
"Bring CLI Findings into One Cloud View",
|
||||
"See Compliance Across Every Provider",
|
||||
"Coordinate Finding Remediation",
|
||||
"Use The Agent Cloud Defender",
|
||||
"Scale Prowler Without Operating It",
|
||||
"Configure Every Scan Once",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should explain that Prowler Local Server remains unchanged", () => {
|
||||
// Given / When / Then
|
||||
expect(CLOUD_UPGRADE_FOOTER_NOTE).toBe(
|
||||
"Prowler Cloud opens in a new tab. Your Prowler Local Server remains unchanged.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cloud upgrade URLs", () => {
|
||||
it("should attribute the primary Cloud destination", () => {
|
||||
// Given / When
|
||||
const url = getCloudUpgradePrimaryUrl(CLOUD_UPGRADE_FEATURE.ALERTS);
|
||||
|
||||
// Then
|
||||
expect(url).toBe(
|
||||
"https://cloud.prowler.com/sign-up?utm_source=prowler-local-server&utm_content=alerts",
|
||||
);
|
||||
});
|
||||
|
||||
it("should attribute the plans and pricing destination", () => {
|
||||
// Given / When
|
||||
const url = getCloudUpgradeCompareUrl(
|
||||
CLOUD_UPGRADE_FEATURE.CROSS_PROVIDER_COMPLIANCE,
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(url).toBe(
|
||||
"https://prowler.com/pricing?utm_source=prowler-local-server&utm_content=cross-provider-compliance",
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[CLOUD_UPGRADE_FEATURE.ADVANCED_SCHEDULING, "advanced-scheduling"],
|
||||
[CLOUD_UPGRADE_FEATURE.ALERTS, "alerts"],
|
||||
[CLOUD_UPGRADE_FEATURE.AWS_ORGANIZATIONS, "organization"],
|
||||
[CLOUD_UPGRADE_FEATURE.CLI_IMPORT, "cli-import"],
|
||||
[
|
||||
CLOUD_UPGRADE_FEATURE.CROSS_PROVIDER_COMPLIANCE,
|
||||
"cross-provider-compliance",
|
||||
],
|
||||
[CLOUD_UPGRADE_FEATURE.FINDING_TRIAGE, "findings"],
|
||||
[CLOUD_UPGRADE_FEATURE.LIGHTHOUSE_AI, "lighthouse-ai"],
|
||||
[CLOUD_UPGRADE_FEATURE.GENERAL, "general"],
|
||||
[CLOUD_UPGRADE_FEATURE.SCAN_CONFIGURATION, "scan-configuration"],
|
||||
])("should use the canonical content slug for %s", (feature, contentSlug) => {
|
||||
// Given / When
|
||||
const url = new URL(getCloudUpgradePrimaryUrl(feature));
|
||||
|
||||
// Then
|
||||
expect(url.searchParams.get("utm_content")).toBe(contentSlug);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
import {
|
||||
CLOUD_UPGRADE_FEATURE,
|
||||
type CloudUpgradeFeature,
|
||||
} from "@/types/cloud-upgrade";
|
||||
|
||||
interface CloudUpgradeContent {
|
||||
title: string;
|
||||
description: string;
|
||||
benefits: readonly [string, string, string, ...string[]];
|
||||
primaryCta: string;
|
||||
}
|
||||
|
||||
export const CLOUD_UPGRADE_SECONDARY_CTA = "View Plans & Pricing";
|
||||
export const CLOUD_UPGRADE_FOOTER_NOTE =
|
||||
"Prowler Cloud opens in a new tab. Your Prowler Local Server remains unchanged.";
|
||||
|
||||
const CLOUD_SIGN_UP_URL = "https://cloud.prowler.com/sign-up";
|
||||
const PRICING_URL = "https://prowler.com/pricing";
|
||||
const LOCAL_SERVER_UTM_SOURCE = "prowler-local-server";
|
||||
|
||||
const CLOUD_UPGRADE_UTM_CONTENT = {
|
||||
[CLOUD_UPGRADE_FEATURE.ADVANCED_SCHEDULING]: "advanced-scheduling",
|
||||
[CLOUD_UPGRADE_FEATURE.ALERTS]: "alerts",
|
||||
[CLOUD_UPGRADE_FEATURE.AWS_ORGANIZATIONS]: "organization",
|
||||
[CLOUD_UPGRADE_FEATURE.CLI_IMPORT]: "cli-import",
|
||||
[CLOUD_UPGRADE_FEATURE.CROSS_PROVIDER_COMPLIANCE]:
|
||||
"cross-provider-compliance",
|
||||
[CLOUD_UPGRADE_FEATURE.FINDING_TRIAGE]: "findings",
|
||||
[CLOUD_UPGRADE_FEATURE.LIGHTHOUSE_AI]: "lighthouse-ai",
|
||||
[CLOUD_UPGRADE_FEATURE.GENERAL]: "general",
|
||||
[CLOUD_UPGRADE_FEATURE.SCAN_CONFIGURATION]: "scan-configuration",
|
||||
} as const satisfies Record<CloudUpgradeFeature, string>;
|
||||
|
||||
export const CLOUD_UPGRADE_CONTENT = {
|
||||
[CLOUD_UPGRADE_FEATURE.ADVANCED_SCHEDULING]: {
|
||||
title: "Keep Every Provider Checked Automatically",
|
||||
description:
|
||||
"Run scans on the cadence you choose without maintaining scheduling infrastructure.",
|
||||
benefits: [
|
||||
"Choose daily, interval, weekly, or monthly scans",
|
||||
"Set scan times in your preferred timezone",
|
||||
"Manage schedules alongside scan history",
|
||||
],
|
||||
primaryCta: "Schedule Scans in Prowler Cloud",
|
||||
},
|
||||
[CLOUD_UPGRADE_FEATURE.ALERTS]: {
|
||||
title: "Turn Findings into Alerts",
|
||||
description:
|
||||
"Get notified when the findings you care about appear in a scan.",
|
||||
benefits: [
|
||||
"Get alerted on what matters most",
|
||||
"Notify the right people after every scan",
|
||||
"Manage alert rules from one place",
|
||||
],
|
||||
primaryCta: "Create Alerts in Prowler Cloud",
|
||||
},
|
||||
[CLOUD_UPGRADE_FEATURE.AWS_ORGANIZATIONS]: {
|
||||
title: "Add Your Entire AWS Organization",
|
||||
description:
|
||||
"Discover accounts and organizational units, then manage them from one place.",
|
||||
benefits: [
|
||||
"Discover accounts and organizational units automatically",
|
||||
"Choose exactly which accounts to onboard",
|
||||
"Apply schedules across the selected accounts",
|
||||
],
|
||||
primaryCta: "Set Up AWS Organizations in Prowler Cloud",
|
||||
},
|
||||
[CLOUD_UPGRADE_FEATURE.CLI_IMPORT]: {
|
||||
title: "Bring CLI Findings into One Cloud View",
|
||||
description:
|
||||
"Send Prowler CLI scan results to Prowler Cloud for centralized analysis and collaboration.",
|
||||
benefits: [
|
||||
"Push results directly with --push-to-cloud",
|
||||
"Track CLI and managed scans in one place",
|
||||
"Automate findings ingestion from CI/CD pipelines",
|
||||
],
|
||||
primaryCta: "Import CLI Findings in Prowler Cloud",
|
||||
},
|
||||
[CLOUD_UPGRADE_FEATURE.CROSS_PROVIDER_COMPLIANCE]: {
|
||||
title: "See Compliance Across Every Provider",
|
||||
description:
|
||||
"Replace separate scan reports with a consolidated compliance view.",
|
||||
benefits: [
|
||||
"Compare framework posture across providers",
|
||||
"Find coverage gaps without switching scans",
|
||||
"Generate a consolidated compliance report",
|
||||
],
|
||||
primaryCta: "Consolidate Compliance in Prowler Cloud",
|
||||
},
|
||||
[CLOUD_UPGRADE_FEATURE.FINDING_TRIAGE]: {
|
||||
title: "Coordinate Finding Remediation",
|
||||
description:
|
||||
"Add investigation notes and move findings through a shared remediation workflow.",
|
||||
benefits: [
|
||||
"Preserve investigation context on each finding",
|
||||
"Track review and remediation status",
|
||||
"Keep triage history with future scans",
|
||||
],
|
||||
primaryCta: "Triage Findings in Prowler Cloud",
|
||||
},
|
||||
[CLOUD_UPGRADE_FEATURE.LIGHTHOUSE_AI]: {
|
||||
title: "Use The Agent Cloud Defender",
|
||||
description:
|
||||
"Investigate and act on your security posture without operating an AI stack.",
|
||||
benefits: [
|
||||
"Start without provisioning or managing OpenAI API keys",
|
||||
"Automate security workflows through the hosted remote MCP server",
|
||||
"Keep Lighthouse actions grounded in your Prowler Cloud data",
|
||||
],
|
||||
primaryCta: "Open Lighthouse AI in Prowler Cloud",
|
||||
},
|
||||
[CLOUD_UPGRADE_FEATURE.GENERAL]: {
|
||||
title: "Scale Prowler Without Operating It",
|
||||
description:
|
||||
"Add managed automation and collaboration while Prowler operates the platform.",
|
||||
benefits: [
|
||||
"Onboard AWS Organizations and automate scans and alerts",
|
||||
"Triage findings and consolidate compliance across providers",
|
||||
"Investigate and remediate with Lighthouse AI and Agentic View",
|
||||
"Use managed infrastructure, support, and backups",
|
||||
],
|
||||
primaryCta: "Start a Prowler Cloud Trial",
|
||||
},
|
||||
[CLOUD_UPGRADE_FEATURE.SCAN_CONFIGURATION]: {
|
||||
title: "Configure Every Scan Once",
|
||||
description:
|
||||
"Create reusable scan configurations instead of rebuilding options for each run.",
|
||||
benefits: [
|
||||
"Reduce noise by fine-tuning scan configurations",
|
||||
"Apply consistent configurations to providers",
|
||||
"Manage scan behavior from one place",
|
||||
],
|
||||
primaryCta: "Configure Scans in Prowler Cloud",
|
||||
},
|
||||
} as const satisfies Record<CloudUpgradeFeature, CloudUpgradeContent>;
|
||||
|
||||
const buildCloudUpgradeUrl = (
|
||||
baseUrl: string,
|
||||
feature: CloudUpgradeFeature,
|
||||
) => {
|
||||
const url = new URL(baseUrl);
|
||||
url.searchParams.set("utm_source", LOCAL_SERVER_UTM_SOURCE);
|
||||
url.searchParams.set("utm_content", CLOUD_UPGRADE_UTM_CONTENT[feature]);
|
||||
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
export const getCloudUpgradePrimaryUrl = (feature: CloudUpgradeFeature) =>
|
||||
buildCloudUpgradeUrl(CLOUD_SIGN_UP_URL, feature);
|
||||
|
||||
export const getCloudUpgradeCompareUrl = (feature: CloudUpgradeFeature) =>
|
||||
buildCloudUpgradeUrl(PRICING_URL, feature);
|
||||
@@ -13,7 +13,7 @@ You are an Autonomous Cloud Security Analyst, the best cloud security chatbot po
|
||||
Your goal is to help users solve their cloud security problems effectively.
|
||||
|
||||
You have access to tools from multiple sources:
|
||||
- **Prowler App**: User's Prowler providers data, configurations and security overview
|
||||
- **Prowler Local Server**: User's Prowler providers data, configurations and security overview
|
||||
- **Prowler Hub**: Generic automatic detections, remediations and compliance framework that are available for Prowler
|
||||
- **Prowler Docs**: Documentation and knowledge base. Here you can find information about Prowler capabilities, configuration tutorials, guides, and more
|
||||
|
||||
@@ -233,7 +233,7 @@ When providing proactive recommendations to secure users' cloud accounts, follow
|
||||
## Sources and Domain Knowledge
|
||||
|
||||
- Prowler website: https://prowler.com/
|
||||
- Prowler App: https://cloud.prowler.com/
|
||||
- Prowler Cloud: https://cloud.prowler.com/
|
||||
- Prowler GitHub repository: https://github.com/prowler-cloud/prowler
|
||||
- Prowler Documentation: https://docs.prowler.com/
|
||||
`;
|
||||
|
||||
+36
-12
@@ -1,5 +1,8 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { CLOUD_UPGRADE_FEATURE } from "@/types/cloud-upgrade";
|
||||
import { SUBMENU_KIND } from "@/types/components";
|
||||
|
||||
import { getMenuList } from "./menu-list";
|
||||
|
||||
const findMenu = (label: string) =>
|
||||
@@ -76,20 +79,18 @@ describe("getMenuList", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should show Alerts as disabled Cloud-only in OSS when Cloud is disabled", () => {
|
||||
it("should show Alerts as a contextual Cloud upgrade in Local Server", () => {
|
||||
// Given / When
|
||||
const alerts = findSubmenu("Alerts");
|
||||
|
||||
// Then
|
||||
expect(alerts).toEqual(
|
||||
expect.objectContaining({
|
||||
href: "/alerts",
|
||||
disabled: true,
|
||||
cloudOnly: true,
|
||||
highlight: true,
|
||||
active: false,
|
||||
kind: SUBMENU_KIND.CLOUD_UPGRADE,
|
||||
cloudUpgradeFeature: CLOUD_UPGRADE_FEATURE.ALERTS,
|
||||
}),
|
||||
);
|
||||
expect(alerts).not.toHaveProperty("href");
|
||||
});
|
||||
|
||||
it("should show Alerts as new under Configuration when Cloud is enabled", () => {
|
||||
@@ -109,20 +110,43 @@ describe("getMenuList", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should show Scan as disabled Cloud-only in OSS when Cloud is disabled", () => {
|
||||
it("should show Scan as a contextual Cloud upgrade in Local Server", () => {
|
||||
// Given / When
|
||||
const scanConfig = findSubmenu("Scan");
|
||||
|
||||
// Then
|
||||
expect(scanConfig).toEqual(
|
||||
expect.objectContaining({
|
||||
href: "/scans/config",
|
||||
disabled: true,
|
||||
cloudOnly: true,
|
||||
highlight: true,
|
||||
active: false,
|
||||
kind: SUBMENU_KIND.CLOUD_UPGRADE,
|
||||
cloudUpgradeFeature: CLOUD_UPGRADE_FEATURE.SCAN_CONFIGURATION,
|
||||
}),
|
||||
);
|
||||
expect(scanConfig).not.toHaveProperty("href");
|
||||
});
|
||||
|
||||
it("should expose CLI Import as a contextual Cloud upgrade in Local Server", () => {
|
||||
// Given / When
|
||||
const cliImport = findSubmenu("CLI Import");
|
||||
|
||||
// Then
|
||||
expect(cliImport).toEqual(
|
||||
expect.objectContaining({
|
||||
kind: SUBMENU_KIND.CLOUD_UPGRADE,
|
||||
cloudUpgradeFeature: CLOUD_UPGRADE_FEATURE.CLI_IMPORT,
|
||||
}),
|
||||
);
|
||||
expect(cliImport).not.toHaveProperty("href");
|
||||
});
|
||||
|
||||
it("should omit CLI Import from the Cloud menu", () => {
|
||||
// Given
|
||||
process.env.NEXT_PUBLIC_IS_CLOUD_ENV = "true";
|
||||
|
||||
// When
|
||||
const cliImport = findSubmenu("CLI Import");
|
||||
|
||||
// Then
|
||||
expect(cliImport).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should show Scan as new under Configuration when Cloud is enabled", () => {
|
||||
|
||||
+67
-14
@@ -12,6 +12,7 @@ import {
|
||||
SquareChartGantt,
|
||||
Tag,
|
||||
Timer,
|
||||
Upload,
|
||||
User,
|
||||
UserCog,
|
||||
Users,
|
||||
@@ -27,7 +28,15 @@ import {
|
||||
LighthouseIcon,
|
||||
SupportIcon,
|
||||
} from "@/components/icons/Icons";
|
||||
import { GroupProps } from "@/types";
|
||||
import { isCloud } from "@/lib/shared/env";
|
||||
import {
|
||||
type CloudUpgradeFeature,
|
||||
type GroupProps,
|
||||
type IconComponent,
|
||||
SUBMENU_KIND,
|
||||
type SubmenuProps,
|
||||
} from "@/types";
|
||||
import { CLOUD_UPGRADE_FEATURE } from "@/types/cloud-upgrade";
|
||||
|
||||
interface MenuListOptions {
|
||||
pathname: string;
|
||||
@@ -36,11 +45,47 @@ interface MenuListOptions {
|
||||
apiDocsUrl?: string | null;
|
||||
}
|
||||
|
||||
interface CloudFeatureSubmenuOptions {
|
||||
isCloudEnvironment: boolean;
|
||||
href: string;
|
||||
label: string;
|
||||
icon: IconComponent;
|
||||
active: boolean;
|
||||
feature: CloudUpgradeFeature;
|
||||
}
|
||||
|
||||
const getCloudFeatureSubmenu = ({
|
||||
isCloudEnvironment,
|
||||
href,
|
||||
label,
|
||||
icon,
|
||||
active,
|
||||
feature,
|
||||
}: CloudFeatureSubmenuOptions): SubmenuProps => {
|
||||
if (!isCloudEnvironment) {
|
||||
return {
|
||||
kind: SUBMENU_KIND.CLOUD_UPGRADE,
|
||||
label,
|
||||
icon,
|
||||
cloudUpgradeFeature: feature,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
kind: SUBMENU_KIND.LINK,
|
||||
href,
|
||||
label,
|
||||
icon,
|
||||
active,
|
||||
highlight: true,
|
||||
};
|
||||
};
|
||||
|
||||
export const getMenuList = ({
|
||||
pathname,
|
||||
apiDocsUrl = null,
|
||||
}: MenuListOptions): GroupProps[] => {
|
||||
const isCloudEnv = process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true";
|
||||
const isCloudEnv = isCloud();
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -134,30 +179,38 @@ export const getMenuList = ({
|
||||
icon: Settings,
|
||||
submenus: [
|
||||
{ href: "/providers", label: "Providers", icon: CloudCog },
|
||||
{
|
||||
getCloudFeatureSubmenu({
|
||||
isCloudEnvironment: isCloudEnv,
|
||||
href: "/alerts",
|
||||
label: "Alerts",
|
||||
icon: BellRing,
|
||||
active: isCloudEnv && pathname.startsWith("/alerts"),
|
||||
highlight: true,
|
||||
disabled: !isCloudEnv,
|
||||
cloudOnly: !isCloudEnv,
|
||||
},
|
||||
active: pathname.startsWith("/alerts"),
|
||||
feature: CLOUD_UPGRADE_FEATURE.ALERTS,
|
||||
}),
|
||||
{
|
||||
href: "/mutelist",
|
||||
label: "Mutelist",
|
||||
icon: VolumeX,
|
||||
active: pathname === "/mutelist",
|
||||
},
|
||||
{
|
||||
getCloudFeatureSubmenu({
|
||||
isCloudEnvironment: isCloudEnv,
|
||||
href: "/scans/config",
|
||||
label: "Scan",
|
||||
icon: SlidersHorizontal,
|
||||
active: isCloudEnv && pathname.startsWith("/scans/config"),
|
||||
highlight: true,
|
||||
disabled: !isCloudEnv,
|
||||
cloudOnly: !isCloudEnv,
|
||||
},
|
||||
active: pathname.startsWith("/scans/config"),
|
||||
feature: CLOUD_UPGRADE_FEATURE.SCAN_CONFIGURATION,
|
||||
}),
|
||||
...(!isCloudEnv
|
||||
? [
|
||||
{
|
||||
kind: SUBMENU_KIND.CLOUD_UPGRADE,
|
||||
label: "CLI Import",
|
||||
icon: Upload,
|
||||
cloudUpgradeFeature: CLOUD_UPGRADE_FEATURE.CLI_IMPORT,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ href: "/integrations", label: "Integrations", icon: Puzzle },
|
||||
{ href: "/lighthouse/settings", label: "Lighthouse AI", icon: Cog },
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user