diff --git a/src/realm-settings/AddClientProfileModal.tsx b/src/realm-settings/AddClientProfileModal.tsx new file mode 100644 index 0000000000..0386b5a98c --- /dev/null +++ b/src/realm-settings/AddClientProfileModal.tsx @@ -0,0 +1,137 @@ +import React, { useState } from "react"; +import { + Button, + Label, + Modal, + ModalVariant, + Spinner, +} from "@patternfly/react-core"; +import { useTranslation } from "react-i18next"; +import { useFetch, useAdminClient } from "../context/auth/AdminClient"; +import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation"; +import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; +import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; +import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation"; + +type ClientProfile = ClientProfileRepresentation & { + global: boolean; +}; + +export type AddClientProfileModalProps = { + open: boolean; + toggleDialog: () => void; + onConfirm: (newReps: RoleRepresentation[]) => void; + allProfiles: string[]; +}; + +export const AddClientProfileModal = (props: AddClientProfileModalProps) => { + const { t } = useTranslation("roles"); + const adminClient = useAdminClient(); + const [selectedRows, setSelectedRows] = useState([]); + + const [tableProfiles, setTableProfiles] = useState(); + + useFetch( + () => + adminClient.clientPolicies.listProfiles({ + includeGlobalProfiles: true, + }), + (allProfiles) => { + const globalProfiles = allProfiles.globalProfiles?.map( + (globalProfiles) => ({ + ...globalProfiles, + global: true, + }) + ); + + const profiles = allProfiles.profiles?.map((profiles) => ({ + ...profiles, + global: false, + })); + + setTableProfiles([...(globalProfiles ?? []), ...(profiles ?? [])]); + }, + [] + ); + + const loader = async () => tableProfiles ?? []; + + if (!tableProfiles) { + return ( +
+ +
+ ); + } + + const AliasRenderer = ({ name }: ClientProfile) => ( + <> + {name && } {name} + + ); + + return ( + { + props.toggleDialog(); + props.onConfirm(selectedRows); + }} + > + {t("common:add")} + , + , + ]} + > + + props.allProfiles.includes(value.name!) || false + } + ariaLabelKey="realm-settings:profilesList" + searchPlaceholderKey="realm-settings:searchProfile" + canSelectAll + onSelect={(rows) => { + setSelectedRows([...rows]); + }} + columns={[ + { + name: "name", + displayKey: "realm-settings:clientProfileName", + cellRenderer: AliasRenderer, + }, + { + name: "description", + displayKey: "common:description", + }, + ]} + emptyState={ + + } + /> + + ); +}; diff --git a/src/realm-settings/NewClientPolicyForm.tsx b/src/realm-settings/NewClientPolicyForm.tsx index 367ecb20cf..4913ba46df 100644 --- a/src/realm-settings/NewClientPolicyForm.tsx +++ b/src/realm-settings/NewClientPolicyForm.tsx @@ -38,6 +38,8 @@ import { toClientPolicies } from "./routes/ClientPolicies"; import { toNewClientPolicyCondition } from "./routes/AddCondition"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import type { EditClientPolicyParams } from "./routes/EditClientPolicy"; +import { AddClientProfileModal } from "./AddClientProfileModal"; +import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation"; type NewClientPolicyForm = Required; @@ -49,6 +51,11 @@ const defaultValues: NewClientPolicyForm = { profiles: [], }; +type PolicyDetailAttributes = { + idx: number; + name: string; +}; + export const NewClientPolicyForm = () => { const { t } = useTranslation("realm-settings"); const { errors, reset: resetForm } = useForm({ @@ -58,6 +65,10 @@ export const NewClientPolicyForm = () => { const { addAlert, addError } = useAlerts(); const adminClient = useAdminClient(); const [policies, setPolicies] = useState([]); + const [clientProfiles, setClientProfiles] = useState< + ClientProfileRepresentation[] + >([]); + const [currentPolicy, setCurrentPolicy] = useState(); const [ @@ -66,7 +77,12 @@ export const NewClientPolicyForm = () => { ] = useState(false); const [conditionToDelete, setConditionToDelete] = - useState<{ idx: number; name: string }>(); + useState(); + + const [profilesModalOpen, setProfilesModalOpen] = useState(false); + + const [profileToDelete, setProfileToDelete] = + useState(); const { policyName } = useParams(); @@ -78,14 +94,30 @@ export const NewClientPolicyForm = () => { const refresh = () => setKey(new Date().getTime()); useFetch( - () => adminClient.clientPolicies.listPolicies(), - (policies) => { + async () => { + const [policies, profiles] = await Promise.all([ + adminClient.clientPolicies.listPolicies(), + adminClient.clientPolicies.listProfiles({ + includeGlobalProfiles: true, + }), + ]); + + return { policies, profiles }; + }, + ({ policies, profiles }) => { const currentPolicy = policies.policies?.find( (item) => item.name === policyName ); + + const allClientProfiles = [ + ...(profiles.globalProfiles ?? []), + ...(profiles.profiles ?? []), + ]; + setPolicies(policies.policies ?? []); if (currentPolicy) { setupForm(currentPolicy); + setClientProfiles(allClientProfiles); setCurrentPolicy(currentPolicy); setShowAddConditionsAndProfilesForm(true); } @@ -102,6 +134,7 @@ export const NewClientPolicyForm = () => { const policy = policies.filter((policy) => policy.name === policyName); const policyConditions = policy[0]?.conditions || []; + const policyProfiles = policy[0]?.profiles || []; const serverInfo = useServerInfo(); @@ -110,8 +143,10 @@ export const NewClientPolicyForm = () => { "org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider" ]; + const formValues = form.getValues(); + const save = async () => { - const createdForm = form.getValues(); + const createdForm = formValues; const createdPolicy = { ...createdForm, profiles: [], @@ -137,9 +172,7 @@ export const NewClientPolicyForm = () => { AlertVariant.success ); history.push( - `/${realm}/realm-settings/clientPolicies/${ - form.getValues().name - }/edit-policy` + `/${realm}/realm-settings/clientPolicies/${formValues.name}/edit-policy` ); setShowAddConditionsAndProfilesForm(true); refresh(); @@ -189,10 +222,9 @@ export const NewClientPolicyForm = () => { }); addAlert(t("deleteConditionSuccess"), AlertVariant.success); history.push( - `/${realm}/realm-settings/clientPolicies/${ - form.getValues().name - }/edit-policy` + `/${realm}/realm-settings/clientPolicies/${formValues.name}/edit-policy` ); + refresh(); } catch (error) { addError(t("deleteConditionError"), error); } @@ -214,15 +246,109 @@ export const NewClientPolicyForm = () => { }, }); + const [toggleDeleteProfileDialog, DeleteProfileConfirm] = useConfirmDialog({ + titleKey: t("deleteClientPolicyProfileConfirmTitle"), + messageKey: t("deleteClientPolicyProfileConfirm", { + profileName: profileToDelete?.name, + policyName, + }), + continueButtonLabel: t("delete"), + continueButtonVariant: ButtonVariant.danger, + onConfirm: async () => { + if (profileToDelete?.name) { + currentPolicy?.profiles?.splice(profileToDelete.idx!, 1); + try { + await adminClient.clientPolicies.updatePolicy({ + policies: policies, + }); + addAlert(t("deleteClientPolicyProfileSuccess"), AlertVariant.success); + history.push( + `/${realm}/realm-settings/clientPolicies/${formValues.name}/edit-policy` + ); + } catch (error) { + addError(t("deleteClientPolicyProfileError"), error); + } + } else { + const updatedPolicies = policies.filter( + (policy) => policy.name !== policyName + ); + + try { + await adminClient.clientPolicies.updatePolicy({ + policies: updatedPolicies, + }); + addAlert(t("deleteClientSuccess"), AlertVariant.success); + history.push(toClientPolicies({ realm })); + } catch (error) { + addError(t("deleteClientError"), error); + } + } + }, + }); + const reset = () => { form.setValue("name", currentPolicy?.name); form.setValue("description", currentPolicy?.description); }; + const toggleModal = () => { + setProfilesModalOpen(!profilesModalOpen); + }; + + const addProfiles = async (profiles: string[]) => { + const createdPolicy = { + ...currentPolicy, + profiles: (currentPolicy?.profiles ?? []).concat(profiles), + conditions: currentPolicy?.conditions, + }; + + const index = policies.findIndex( + (policy) => createdPolicy.name === policy.name + ); + + if (index === -1) { + return; + } + + const newPolicies = [ + ...policies.slice(0, index), + createdPolicy, + ...policies.slice(index + 1), + ]; + + try { + await adminClient.clientPolicies.updatePolicy({ + policies: newPolicies, + }); + setPolicies(newPolicies); + history.push( + `/${realm}/realm-settings/clientPolicies/${formValues.name}/edit-policy` + ); + addAlert( + t("realm-settings:addClientProfileSuccess"), + AlertVariant.success + ); + refresh(); + } catch (error) { + addError("realm-settings:addClientProfileError", error); + } + }; + + console.log("NY", policyConditions); + return ( <> + + { + addProfiles(profiles.map((item) => item.name!)); + }} + allProfiles={policyProfiles} + open={profilesModalOpen} + toggleDialog={toggleModal} + /> { variant="primary" type="submit" data-testid="saveCreatePolicy" + isDisabled={!formValues.name} > {t("common:save")} @@ -325,7 +452,7 @@ export const NewClientPolicyForm = () => { {...props} to={toNewClientPolicyCondition({ realm, - policyName: form.getValues().name!, + policyName: formValues.name!, })} > )} @@ -342,9 +469,10 @@ export const NewClientPolicyForm = () => { {policyConditions.map((condition, idx) => ( { - - - {t("realm-settings:emptyProfiles")} - + {policyProfiles.length > 0 ? ( + + {policyProfiles.map((profile, idx) => ( + + + + {profile && ( + + {profile} + + )} + {policyProfiles + .filter((type) => type === profile) + .map((type) => ( + <> + type === profile.name + )?.description + } + forLabel={profile} + forID={t(`common:helpLabel`, { + label: profile, + })} + /> + + + ))} + , + ]} + /> + + + ))} + + ) : ( + <> + + + {t("realm-settings:emptyProfiles")} + + + )} )} diff --git a/src/realm-settings/PoliciesTab.tsx b/src/realm-settings/PoliciesTab.tsx index e121ae7570..8a63bc42a3 100644 --- a/src/realm-settings/PoliciesTab.tsx +++ b/src/realm-settings/PoliciesTab.tsx @@ -26,7 +26,7 @@ import { useAlerts } from "../components/alert/Alerts"; import "./RealmSettingsSection.css"; import { useRealm } from "../context/realm-context/RealmContext"; -import { toNewClientPolicy } from "./routes/NewClientPolicy"; +import { toAddClientPolicy } from "./routes/AddClientPolicy"; import { toEditClientPolicy } from "./routes/EditClientPolicy"; export const PoliciesTab = () => { const { t } = useTranslation("realm-settings"); @@ -161,7 +161,7 @@ export const PoliciesTab = () => { message={t("realm-settings:noClientPolicies")} instructions={t("realm-settings:noClientPoliciesInstructions")} primaryActionText={t("realm-settings:createClientPolicy")} - onPrimaryAction={() => history.push(toNewClientPolicy({ realm }))} + onPrimaryAction={() => history.push(toAddClientPolicy({ realm }))} /> } ariaLabelKey="realm-settings:clientPolicies" @@ -172,7 +172,7 @@ export const PoliciesTab = () => {