import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { AlertVariant, Badge, Button, ButtonVariant, Checkbox, ToolbarItem, } from "@patternfly/react-core"; import type ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; import type RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation"; import { AddRoleMappingModal, MappingType } from "./AddRoleMappingModal"; import { KeycloakDataTable } from "../table-toolbar/KeycloakDataTable"; import { emptyFormatter } from "../../util"; import "./role-mapping.css"; import { useConfirmDialog } from "../confirm-dialog/ConfirmDialog"; import { useAdminClient } from "../../context/auth/AdminClient"; import { useAlerts } from "../alert/Alerts"; export type CompositeRole = RoleRepresentation & { parent: RoleRepresentation; isInherited?: boolean; }; export type Row = { client?: ClientRepresentation; role: RoleRepresentation | CompositeRole; }; export const mapRoles = ( assignedRoles: RoleRepresentation[], effectiveRoles: RoleRepresentation[], hide: boolean ) => { return [ ...(hide ? assignedRoles.map((role) => ({ role: { ...role, isInherited: false, }, })) : effectiveRoles.map((role) => ({ role: { ...role, isInherited: assignedRoles.find((r) => r.id === role.id) === undefined, }, }))), ]; }; export const ServiceRole = ({ role, client }: Row) => ( <> {client && ( {client.clientId} )} {role.name} ); type RoleMappingProps = { name: string; id: string; type: MappingType; loader: () => Promise; save: (rows: Row[]) => Promise; onHideRolesToggle: () => void; }; export const RoleMapping = ({ name, id, type, loader, save, onHideRolesToggle, }: RoleMappingProps) => { const { t } = useTranslation("clients"); const adminClient = useAdminClient(); const { addAlert } = useAlerts(); const [key, setKey] = useState(0); const refresh = () => setKey(new Date().getTime()); const [hide, setHide] = useState(false); const [showAssign, setShowAssign] = useState(false); const [selected, setSelected] = useState([]); const assignRoles = async (rows: Row[]) => { await save(rows); refresh(); }; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: "clients:removeMappingTitle", messageKey: t("removeMappingConfirm", { count: selected.length }), continueButtonLabel: "common:remove", continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { try { switch (type) { case "service-account": await Promise.all( selected.map((row) => { const role = { id: row.role.id!, name: row.role.name! }; if (row.client) { return adminClient.users.delClientRoleMappings({ id, clientUniqueId: row.client!.id!, roles: [role], }); } else { return adminClient.users.delRealmRoleMappings({ id, roles: [role], }); } }) ); break; case "client-scope": await Promise.all( selected.map((row) => { const role = { id: row.role.id!, name: row.role.name! }; if (row.client) { return adminClient.clientScopes.delClientScopeMappings( { id, client: row.client!.id!, }, [role] ); } else { return adminClient.clientScopes.delRealmScopeMappings( { id, }, [role] ); } }) ); break; } addAlert(t("clientScopeRemoveSuccess"), AlertVariant.success); refresh(); } catch (error) { addAlert(t("clientScopeRemoveError", { error }), AlertVariant.danger); } }, }); return ( <> {showAssign && ( setShowAssign(false)} /> )} setSelected(rows)} searchPlaceholderKey="clients:searchByName" ariaLabelKey="clients:clientScopeList" isRowDisabled={(value) => (value.role as CompositeRole).isInherited || false } toolbarItem={ <> { setHide(check); onHideRolesToggle(); refresh(); }} /> } columns={[ { name: "role.name", displayKey: t("name"), cellRenderer: ServiceRole, }, { name: "role.parent.name", displayKey: t("inherentFrom"), cellFormatters: [emptyFormatter()], }, { name: "role.description", displayKey: t("description"), cellFormatters: [emptyFormatter()], }, ]} /> ); };