feat(ui): add Local Server Cloud upgrade prompts (#11982)

This commit is contained in:
Alejandro Bailo
2026-07-15 12:11:28 +02:00
committed by GitHub
parent 077dff7f68
commit faa74f4c6c
68 changed files with 1687 additions and 429 deletions
+84
View File
@@ -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);
});
});
+152
View File
@@ -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);
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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 },
],