import type ClientPolicyExecutorRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientPolicyExecutorRepresentation"; import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation"; import type ClientProfilesRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfilesRepresentation"; import { ActionGroup, AlertVariant, Button, ButtonVariant, DataList, DataListCell, DataListItem, DataListItemCells, DataListItemRow, Divider, DropdownItem, Flex, FlexItem, FormGroup, Label, PageSection, Text, TextVariants, ValidatedOptions, } from "@patternfly/react-core"; import { PlusCircleIcon, TrashIcon } from "@patternfly/react-icons"; import { Fragment, useMemo, useState } from "react"; import { useFieldArray, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { Link, useNavigate } from "react-router-dom"; import { useAlerts } from "../components/alert/Alerts"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { FormAccess } from "../components/form-access/FormAccess"; import { HelpItem } from "../components/help-enabler/HelpItem"; import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner"; import { KeycloakTextArea } from "../components/keycloak-text-area/KeycloakTextArea"; import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { useAdminClient, useFetch } from "../context/auth/AdminClient"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { useParams } from "../utils/useParams"; import { toAddExecutor } from "./routes/AddExecutor"; import { toClientPolicies } from "./routes/ClientPolicies"; import { ClientProfileParams, toClientProfile } from "./routes/ClientProfile"; import { toExecutor } from "./routes/Executor"; import "./realm-settings-section.css"; type ClientProfileForm = Required; const defaultValues: ClientProfileForm = { name: "", description: "", executors: [], }; export default function ClientProfileForm() { const { t } = useTranslation("realm-settings"); const navigate = useNavigate(); const { handleSubmit, setValue, getValues, register, formState: { isDirty, errors }, control, } = useForm({ defaultValues, mode: "onChange", }); const { fields: profileExecutors, remove } = useFieldArray({ name: "executors", control, }); const { addAlert, addError } = useAlerts(); const { adminClient } = useAdminClient(); const [profiles, setProfiles] = useState(); const [isGlobalProfile, setIsGlobalProfile] = useState(false); const { realm, profileName } = useParams(); const serverInfo = useServerInfo(); const executorTypes = useMemo( () => serverInfo.componentTypes?.[ "org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider" ], [] ); const [executorToDelete, setExecutorToDelete] = useState<{ idx: number; name: string; }>(); const editMode = profileName ? true : false; const [key, setKey] = useState(0); const reload = () => setKey(key + 1); useFetch( () => adminClient.clientPolicies.listProfiles({ includeGlobalProfiles: true }), (profiles) => { setProfiles({ globalProfiles: profiles.globalProfiles, profiles: profiles.profiles?.filter((p) => p.name !== profileName), }); const globalProfile = profiles.globalProfiles?.find( (p) => p.name === profileName ); const profile = profiles.profiles?.find((p) => p.name === profileName); setIsGlobalProfile(globalProfile !== undefined); setValue("name", globalProfile?.name ?? profile?.name); setValue( "description", globalProfile?.description ?? profile?.description ); setValue("executors", globalProfile?.executors ?? profile?.executors); }, [key] ); const save = async (form: ClientProfileForm) => { const updatedProfiles = form; try { await adminClient.clientPolicies.createProfiles({ ...profiles, profiles: [...(profiles?.profiles || []), updatedProfiles], }); addAlert( editMode ? t("realm-settings:updateClientProfileSuccess") : t("realm-settings:createClientProfileSuccess"), AlertVariant.success ); navigate(toClientProfile({ realm, profileName: form.name })); } catch (error) { addError( editMode ? "realm-settings:updateClientProfileError" : "realm-settings:createClientProfileError", error ); } }; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: executorToDelete?.name! ? t("deleteExecutorProfileConfirmTitle") : t("deleteClientProfileConfirmTitle"), messageKey: executorToDelete?.name! ? t("deleteExecutorProfileConfirm", { executorName: executorToDelete.name!, }) : t("deleteClientProfileConfirm", { profileName, }), continueButtonLabel: t("delete"), continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { if (executorToDelete?.name!) { remove(executorToDelete.idx); try { await adminClient.clientPolicies.createProfiles({ ...profiles, profiles: [...(profiles!.profiles || []), getValues()], }); addAlert(t("deleteExecutorSuccess"), AlertVariant.success); navigate(toClientProfile({ realm, profileName })); } catch (error) { addError(t("deleteExecutorError"), error); } } else { try { await adminClient.clientPolicies.createProfiles(profiles); addAlert(t("deleteClientSuccess"), AlertVariant.success); navigate(toClientPolicies({ realm, tab: "profiles" })); } catch (error) { addError(t("deleteClientError"), error); } } }, }); if (!profiles) { return ; } return ( <> {t("global")} ) : ( "" ), }, ]} divider dropdownItems={ editMode && !isGlobalProfile ? [ {t("deleteClientProfile")} , ] : undefined } /> {!isGlobalProfile && ( )} {editMode && !isGlobalProfile && ( )} {!editMode && !isGlobalProfile && ( )} {editMode && ( <> {t("executors")} {!isGlobalProfile && ( )} {profileExecutors.length > 0 && ( <> {profileExecutors.map((executor, idx) => ( {executor.configuration ? ( ) : ( {executor.executor} )} {executorTypes ?.filter( (type) => type.id === executor.executor ) .map((type) => ( {!isGlobalProfile && ( )} )} {profileExecutors.length === 0 && ( <> {t("realm-settings:emptyExecutors")} )} )} ); }