Files
prowler/ui/components/providers/wizard/provider-wizard-modal.utils.ts
T

217 lines
6.8 KiB
TypeScript

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<string, string> = {
"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<string, string> = {
"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/<slug>/<page>` the
// provider slug is the segment right after `providers`, not the last one
// (which is a page name like `authentication` or `getting-started-<X>`).
// 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;
}
}