Files
prowler/ui/components/scans/scans.utils.ts
2026-05-28 19:20:39 +02:00

220 lines
5.9 KiB
TypeScript

import {
DEFAULT_SCAN_JOBS_TAB,
SCAN_JOBS_TAB,
SCAN_STATE,
SCAN_TRIGGER,
type ScanAttributes,
type ScanFindingsSummary,
type ScanJobsTab,
type ScanProps,
type ScanState,
type ScanTrigger,
type SearchParamsProps,
} from "@/types";
export const SCAN_STATE_FILTER_KEYS = [
"filter[state]",
"filter[state__in]",
] as const;
const ALL_VALUE = "all";
const SCAN_JOBS_TAB_STATES: Record<ScanJobsTab, ScanState[]> = {
[SCAN_JOBS_TAB.ACTIVE]: [SCAN_STATE.AVAILABLE, SCAN_STATE.EXECUTING],
[SCAN_JOBS_TAB.COMPLETED]: [
SCAN_STATE.COMPLETED,
SCAN_STATE.FAILED,
SCAN_STATE.CANCELLED,
],
[SCAN_JOBS_TAB.SCHEDULED]: [SCAN_STATE.SCHEDULED],
};
const toStateFilter = (states: ScanState[]): Record<string, string> => ({
"filter[state__in]": states.join(","),
});
const SCAN_JOBS_TAB_FILTERS = Object.fromEntries(
Object.entries(SCAN_JOBS_TAB_STATES).map(([tab, states]) => [
tab,
toStateFilter(states),
]),
) as Record<ScanJobsTab, Record<string, string>>;
export interface ScanTriggerFilterOption {
value: typeof ALL_VALUE | ScanTrigger;
label: string;
}
export interface ScanStatusFilterOption {
value: typeof ALL_VALUE | ScanState;
label: string;
}
export function getScanTriggerFilterOptions(
isCloudEnvironment: boolean,
): ScanTriggerFilterOption[] {
const options: ScanTriggerFilterOption[] = [
{ value: ALL_VALUE, label: "All Types" },
{ value: SCAN_TRIGGER.MANUAL, label: "Manual" },
{ value: SCAN_TRIGGER.SCHEDULED, label: "Scheduled" },
];
if (isCloudEnvironment) {
options.push({ value: SCAN_TRIGGER.IMPORTED, label: "Imported" });
}
return options;
}
export function isScanStateFilterKey(key: string): boolean {
return SCAN_STATE_FILTER_KEYS.some((filterKey) => filterKey === key);
}
function isSearchParamValue(value: unknown): value is string | string[] {
return typeof value === "string" || Array.isArray(value);
}
export function getScanJobsUserFilters(
searchParams: SearchParamsProps,
): Record<string, string | string[]> {
return Object.entries(searchParams).reduce<Record<string, string | string[]>>(
(filters, [key, value]) => {
if (
key.startsWith("filter[") &&
!isScanStateFilterKey(key) &&
isSearchParamValue(value)
) {
filters[key] = value;
}
return filters;
},
{},
);
}
function parseStateFilter(value?: string | string[]): ScanState[] {
const rawValue = Array.isArray(value) ? value.join(",") : value;
if (!rawValue || rawValue === ALL_VALUE) return [];
return rawValue
.split(",")
.filter((item): item is ScanState =>
Object.values(SCAN_STATE).includes(item as ScanState),
);
}
export function getScanJobsTab(value?: string | string[]): ScanJobsTab {
const rawValue = Array.isArray(value) ? value[0] : value;
const tabs = Object.values(SCAN_JOBS_TAB);
return tabs.includes(rawValue as ScanJobsTab)
? (rawValue as ScanJobsTab)
: DEFAULT_SCAN_JOBS_TAB;
}
export function getScanJobsTabFilters(
tab: ScanJobsTab,
stateFilter?: string | string[],
): Record<string, string> {
const selectedStates = parseStateFilter(stateFilter);
const allowedStates = SCAN_JOBS_TAB_STATES[tab];
const matchingStates = selectedStates.filter((state) =>
allowedStates.includes(state),
);
if (matchingStates.length === 0) return { ...SCAN_JOBS_TAB_FILTERS[tab] };
return { "filter[state__in]": matchingStates.join(",") };
}
export function getScanAlias(scan: ScanProps): string {
if (scan.attributes.trigger === SCAN_TRIGGER.SCHEDULED)
return "scheduled scan";
return scan.attributes.name?.trim() || "-";
}
export function formatScanDuration(duration?: number | null): string {
if (duration === null || duration === undefined || duration < 0) return "-";
const totalSeconds = Math.round(duration);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
if (hours > 0) return `${hours}h ${remainingMinutes}m ${seconds}s`;
if (minutes > 0) return `${minutes} min ${seconds} sec`;
return `${seconds} sec`;
}
export function getScanScheduleLabel(trigger?: ScanTrigger | string): string {
if (trigger === "scheduled") return "Scheduled";
if (trigger === "manual") return "Manual";
if (trigger === "imported") return "Imported";
return "-";
}
export function getScanStatusLabel(state?: ScanState | string): string {
if (state === "available") return "Queued";
if (!state) return "-";
return state.charAt(0).toUpperCase() + state.slice(1);
}
export function getScanStatusFilterOptions(
tab: ScanJobsTab,
): ScanStatusFilterOption[] {
return [
{ value: ALL_VALUE, label: "All Statuses" },
...SCAN_JOBS_TAB_STATES[tab].map((state) => ({
value: state,
label: getScanStatusLabel(state),
})),
];
}
function getNumericValue(
source: Record<string, unknown>,
keys: string[],
): number | undefined {
for (const key of keys) {
const value = source[key];
if (typeof value === "number" && Number.isFinite(value)) return value;
}
return undefined;
}
export function getScanFindingsSummary(
attributes: ScanAttributes,
): ScanFindingsSummary | null {
const root = attributes as unknown as Record<string, unknown>;
const nested =
typeof root.findings === "object" && root.findings !== null
? (root.findings as Record<string, unknown>)
: {};
const source = { ...root, ...nested };
const fail = getNumericValue(source, [
"fail",
"failed",
"failed_findings",
"fail_findings",
]);
const pass = getNumericValue(source, [
"pass",
"passed",
"passed_findings",
"pass_findings",
]);
if (fail === undefined || pass === undefined) return null;
return {
fail,
pass,
failNew: getNumericValue(source, ["fail_new", "new_failed_findings"]),
passNew: getNumericValue(source, ["pass_new", "new_passed_findings"]),
};
}