import { ORG_SETUP_PHASE, ORG_WIZARD_STEP, OrgSetupPhase, OrgWizardStep, } from "@/types/organizations"; import { PROVIDER_WIZARD_MODE, PROVIDER_WIZARD_STEP, ProviderWizardMode, ProviderWizardStep, } from "@/types/provider-wizard"; import type { ProviderType } from "@/types/providers"; import { AWS_PROVIDER_WIZARD_STEPS, PROVIDER_WIZARD_STEPS, } from "./wizard-stepper"; const UPDATE_MODE_WIZARD_STEPS = PROVIDER_WIZARD_STEPS.slice( 0, PROVIDER_WIZARD_STEP.LAUNCH, ); const AWS_CONNECT_STEPPER_ROW = 0; const AWS_LAUNCH_STEPPER_ROW = 1; interface ProviderWizardStepperInput { mode: ProviderWizardMode; providerType: ProviderType | null; currentStep: ProviderWizardStep; // "Add credentials" on a registered account opens on CREDENTIALS and still walks // the separate steps, so it keeps the generic rows. isDirectCredentialsEntry?: boolean; } /** Rows for the provider-flow stepper plus the offset that maps `currentStep` onto them. */ export function getProviderWizardStepper({ mode, providerType, currentStep, isDirectCredentialsEntry = false, }: ProviderWizardStepperInput) { if (mode === PROVIDER_WIZARD_MODE.UPDATE) { return { steps: UPDATE_MODE_WIZARD_STEPS, stepOffset: 0 }; } if (providerType === "aws" && !isDirectCredentialsEntry) { // Only CONNECT and LAUNCH are reachable here; CREDENTIALS and TEST have no // row of their own, so anything short of LAUNCH folds onto the first row. const stepOffset = currentStep === PROVIDER_WIZARD_STEP.LAUNCH ? AWS_LAUNCH_STEPPER_ROW - PROVIDER_WIZARD_STEP.LAUNCH : AWS_CONNECT_STEPPER_ROW - currentStep; return { steps: AWS_PROVIDER_WIZARD_STEPS, stepOffset }; } return { steps: PROVIDER_WIZARD_STEPS, stepOffset: 0 }; } interface CredentialsRetryStepInput { mode: ProviderWizardMode; providerType: ProviderType | null; isDirectCredentialsEntry?: boolean; } /** Where "Back" from the connection test lands: AWS re-enters its one-step form. */ export function getCredentialsRetryStep({ mode, providerType, isDirectCredentialsEntry = false, }: CredentialsRetryStepInput): ProviderWizardStep { if ( mode === PROVIDER_WIZARD_MODE.ADD && providerType === "aws" && !isDirectCredentialsEntry ) { return PROVIDER_WIZARD_STEP.CONNECT; } return PROVIDER_WIZARD_STEP.CREDENTIALS; } interface LaunchBackStepInput { providerType: ProviderType | null; isDirectCredentialsEntry?: boolean; } /** Where "Back" from the launch step lands: AWS returns to its one-step form. */ export function getLaunchBackStep({ providerType, isDirectCredentialsEntry = false, }: LaunchBackStepInput): ProviderWizardStep { if (providerType === "aws" && !isDirectCredentialsEntry) { return PROVIDER_WIZARD_STEP.CONNECT; } return PROVIDER_WIZARD_STEP.TEST; } export function getOrganizationsStepperOffset( currentStep: OrgWizardStep, setupPhase: OrgSetupPhase, ) { if (currentStep === ORG_WIZARD_STEP.SETUP) { return setupPhase === ORG_SETUP_PHASE.ACCESS ? 1 : 0; } return 1; } export function getProviderWizardModalTitle(mode: ProviderWizardMode) { if (mode === PROVIDER_WIZARD_MODE.UPDATE) { return "Update Provider Credentials"; } return "Adding A Provider"; } export function getProviderWizardDocsDestination(docsLink: string) { const destinationLabelMap: Record = { "aws-organizations": "AWS Organizations", "azure-management-groups": "Azure Organizations", "gcp-organizations": "GCP Organizations", aws: "AWS", azure: "Azure", m365: "Microsoft 365", microsoft365: "Microsoft 365", gcp: "GCP", k8s: "Kubernetes", kubernetes: "Kubernetes", github: "GitHub", iac: "IaC", image: "Image", oci: "Oracle Cloud", oraclecloud: "Oracle Cloud", mongodbatlas: "MongoDB Atlas", alibabacloud: "Alibaba Cloud", cloudflare: "Cloudflare", openstack: "OpenStack", googleworkspace: "Google Workspace", vercel: "Vercel", okta: "Okta", help: "Provider", providers: "Provider", }; const stripUrlShapePrefix = (segment: string) => segment .replace(/^getting-started-/, "") .replace(/^provider-/, "") .replace(/^prowler-cloud-/, ""); try { const parsed = new URL(docsLink); // Labels for method-specific credentials-step deep links. Keyed by the // docs URL slug (which can differ from the wizard provider key — e.g. // the docs use `microsoft365` while the wizard uses `m365`). Providers // whose credentials-step URL is the general step anchor are omitted // here and fall back to the provider label ("AWS", "Google Workspace", // etc.) via the `destinationLabelMap` below. const docsSectionLabelMap: Record = { "aws#assume-role-recommended": "AWS Assume Role", "aws#credentials-static-access-keys": "AWS Credentials", "microsoft365#application-certificate-authentication-recommended": "M365 Certificate", "microsoft365#application-client-secret-authentication": "M365 Client Secret", "alibabacloud#ram-role-assumption-recommended": "Alibaba Cloud RAM Role", "alibabacloud#credentials-static-access-keys": "Alibaba Cloud Credentials", "cloudflare#user-api-token-authentication-recommended": "Cloudflare API Token", "cloudflare#api-key-and-email-authentication-legacy": "Cloudflare API Key", }; const pathSegments = parsed.pathname .split("/") .filter((segment) => segment.length > 0); const lastSegment = pathSegments.at(-1); if (!lastSegment) { return parsed.hostname; } // For docs URLs shaped as `/user-guide/providers//` the // provider slug is the segment right after `providers`, not the last one // (which is a page name like `authentication` or `getting-started-`). // Prefer that when present so pages like // `/user-guide/providers/aws/authentication` map to "AWS" instead of // the meaningless title-cased fallback ("Authentication"). const providersIndex = pathSegments.indexOf("providers"); const providerSlugFromPath = providersIndex >= 0 && providersIndex + 1 < pathSegments.length ? pathSegments[providersIndex + 1] : undefined; if (providerSlugFromPath && parsed.hash) { const sectionLabel = docsSectionLabelMap[`${providerSlugFromPath}${parsed.hash}`]; if (sectionLabel) return sectionLabel; } for (const candidate of [providerSlugFromPath, lastSegment]) { if (!candidate) continue; const compact = stripUrlShapePrefix(candidate); const mapped = destinationLabelMap[compact]; if (mapped) return mapped; } return stripUrlShapePrefix(lastSegment) .split("-") .map((word) => word.length === 0 ? word : word[0].toUpperCase() + word.slice(1), ) .join(" "); } catch { return docsLink; } }