Files
prowler/ui/app/(prowler)/_new-overview/_components/accounts-selector.tsx

178 lines
6.2 KiB
TypeScript

"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { ReactNode } from "react";
import {
AWSProviderBadge,
AzureProviderBadge,
GCPProviderBadge,
GitHubProviderBadge,
IacProviderBadge,
KS8ProviderBadge,
M365ProviderBadge,
MongoDBAtlasProviderBadge,
OracleCloudProviderBadge,
} from "@/components/icons/providers-badge";
import {
MultiSelect,
MultiSelectContent,
MultiSelectItem,
MultiSelectTrigger,
MultiSelectValue,
} from "@/components/shadcn/select/multiselect";
import type { ProviderProps, ProviderType } from "@/types/providers";
const PROVIDER_ICON: Record<ProviderType, ReactNode> = {
aws: <AWSProviderBadge width={18} height={18} />,
azure: <AzureProviderBadge width={18} height={18} />,
gcp: <GCPProviderBadge width={18} height={18} />,
kubernetes: <KS8ProviderBadge width={18} height={18} />,
m365: <M365ProviderBadge width={18} height={18} />,
github: <GitHubProviderBadge width={18} height={18} />,
iac: <IacProviderBadge width={18} height={18} />,
oraclecloud: <OracleCloudProviderBadge width={18} height={18} />,
mongodbatlas: <MongoDBAtlasProviderBadge width={18} height={18} />,
};
interface AccountsSelectorProps {
providers: ProviderProps[];
}
export function AccountsSelector({ providers }: AccountsSelectorProps) {
const router = useRouter();
const searchParams = useSearchParams();
const current = searchParams.get("filter[provider_id__in]") || "";
const selectedTypes = searchParams.get("filter[provider_type__in]") || "";
const selectedTypesList = selectedTypes
? selectedTypes.split(",").filter(Boolean)
: [];
const selectedIds = current ? current.split(",").filter(Boolean) : [];
const visibleProviders = providers
// .filter((p) => p.attributes.connection?.connected)
.filter((p) =>
selectedTypesList.length > 0
? selectedTypesList.includes(p.attributes.provider)
: true,
);
const handleMultiValueChange = (ids: string[]) => {
const params = new URLSearchParams(searchParams.toString());
if (ids.length > 0) {
params.set("filter[provider_id__in]", ids.join(","));
} else {
params.delete("filter[provider_id__in]");
}
// Auto-deselect provider types that no longer have any selected accounts
if (selectedTypesList.length > 0) {
// Get provider types of currently selected accounts
const selectedProviders = providers.filter((p) => ids.includes(p.id));
const selectedProviderTypes = new Set(
selectedProviders.map((p) => p.attributes.provider),
);
// Keep only provider types that still have selected accounts
const remainingProviderTypes = selectedTypesList.filter((type) =>
selectedProviderTypes.has(type as ProviderType),
);
// Update provider_type__in filter
if (remainingProviderTypes.length > 0) {
params.set(
"filter[provider_type__in]",
remainingProviderTypes.join(","),
);
} else {
params.delete("filter[provider_type__in]");
}
}
router.push(`?${params.toString()}`, { scroll: false });
};
const selectedLabel = () => {
if (selectedIds.length === 0) return null;
if (selectedIds.length === 1) {
const p = providers.find((pr) => pr.id === selectedIds[0]);
const name = p ? p.attributes.alias || p.attributes.uid : selectedIds[0];
return <span className="truncate">{name}</span>;
}
return (
<span className="truncate">{selectedIds.length} accounts selected</span>
);
};
const filterDescription =
selectedTypesList.length > 0
? `Showing accounts for ${selectedTypesList.join(", ")} providers`
: "All connected cloud provider accounts";
return (
<div className="relative">
<label
htmlFor="accounts-selector"
className="sr-only"
id="accounts-label"
>
Filter by cloud provider account. {filterDescription}. Select one or
more accounts to view findings.
</label>
<MultiSelect values={selectedIds} onValuesChange={handleMultiValueChange}>
<MultiSelectTrigger
id="accounts-selector"
aria-labelledby="accounts-label"
>
{selectedLabel() || <MultiSelectValue placeholder="All accounts" />}
</MultiSelectTrigger>
<MultiSelectContent search={false}>
{visibleProviders.length > 0 ? (
<>
<div
role="option"
aria-selected={selectedIds.length === 0}
aria-label="Select all accounts (clears current selection to show all)"
tabIndex={0}
className="text-text-neutral-secondary flex w-full cursor-pointer items-center gap-3 rounded-lg px-4 py-3 text-sm font-semibold hover:bg-slate-200 dark:hover:bg-slate-700/50"
onClick={() => handleMultiValueChange([])}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleMultiValueChange([]);
}
}}
>
Select All
</div>
{visibleProviders.map((p) => {
const id = p.id;
const displayName = p.attributes.alias || p.attributes.uid;
const providerType = p.attributes.provider as ProviderType;
const icon = PROVIDER_ICON[providerType];
return (
<MultiSelectItem
key={id}
value={id}
badgeLabel={displayName}
aria-label={`${displayName} account (${providerType.toUpperCase()})`}
>
<span aria-hidden="true">{icon}</span>
<span className="truncate">{displayName}</span>
</MultiSelectItem>
);
})}
</>
) : (
<div className="px-3 py-2 text-sm text-slate-500 dark:text-slate-400">
{selectedTypesList.length > 0
? "No accounts available for selected providers"
: "No connected accounts available"}
</div>
)}
</MultiSelectContent>
</MultiSelect>
</div>
);
}