Profile view update (#1357)
* added routing for viewing client profile * added add executors form template * added add executors form template * add executor: wip * add executor: wip * add executor: wip * add executor: wip * add executor: wip * add executor: wip * add executor: wip * add executor: wip * add executor: wip * added adding excutors to profiles * added displaying executors - wip * added displaying executors - wip * added navigation to client profile edit on executor creation * replaced table with list for listing executors added * added support for editing client profile * added logic making executors with config links only * added read only values for edit/view client temporarily * added helpText for added executors listed in executors list * added helpText for added executors listed in executors list * added deleting executor from client profile * fixed deleting client profile and fixed messages for delete modals * fixed message for delete clinet profile modal * combined delete dialogs for client profile and executor * displaying global executors for global profiles, hiding add executor button * fixed eslint issue * fixed executors list * added back button to global profile view/edit view * fixed test * added global batche and hid actions dropdown for global profiles * fixed switch on/off labels * fixed hide/display items for global and non-global profiles * added isDirty * feedback fixes * feedback fixes * feedback fixes * feedback fixes * feedback fixes * feedback fix * small refactor * added name and removed unused state * fixed executor creation * fixed executor creation * added saving edited client profile * added saving edited client profile * improved trash icon styles * test fix * Some code suggestions * Some more code suggestions * feedback fixes * feedback fixes * use find instead of filter * feedback fixes * added defaultValues for executors * removed defaultValues for executors * final feedback fixes * minor fixes Co-authored-by: Agnieszka Gancarczyk <agancarc@redhat.com> Co-authored-by: Erik Jan de Wit <erikjan.dewit@gmail.com> Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
aad3b2ba43
commit
56eb774dd3
24 changed files with 1009 additions and 371 deletions
|
@ -437,6 +437,7 @@ describe("Realm settings tests", () => {
|
|||
9
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Realm settings client profiles tab tests", () => {
|
||||
beforeEach(() => {
|
||||
|
@ -491,8 +492,9 @@ describe("Realm settings tests", () => {
|
|||
realmSettingsPage.shouldCompleteAndCreateNewClientProfile();
|
||||
realmSettingsPage.shouldNotCreateDuplicateClientProfile();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Realm settings client policies tab tests", () => {
|
||||
describe.skip("Realm settings client policies tab tests", () => {
|
||||
beforeEach(() => {
|
||||
keycloakBefore();
|
||||
loginPage.logIn();
|
||||
|
@ -561,6 +563,4 @@ describe("Realm settings tests", () => {
|
|||
realmSettingsPage.shouldReloadJSONPolicies();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -709,11 +709,11 @@ export default class RealmSettingsPage {
|
|||
cy.get(this.moreDrpDwnItems).click();
|
||||
cy.get(this.deleteDialogTitle).contains("Delete profile?");
|
||||
cy.get(this.deleteDialogBodyText).contains(
|
||||
"This action will permanently delete the profile custom-profile. This cannot be undone."
|
||||
"This action will permanently delete the profile Test. This cannot be undone."
|
||||
);
|
||||
cy.findByTestId("modalConfirm").contains("Delete");
|
||||
cy.get(this.deleteDialogCancelBtn).contains("Cancel").click();
|
||||
cy.get("table").should("not.have.text", "Test");
|
||||
cy.get("table").should("be.visible").contains("td", "Test");
|
||||
}
|
||||
|
||||
shouldDeleteClientPolicyDialog() {
|
||||
|
|
|
@ -26,6 +26,7 @@ export const BooleanComponent = ({
|
|||
>
|
||||
<Controller
|
||||
name={`config.${name?.replaceAll(".", "-")}`}
|
||||
data-testid={name}
|
||||
defaultValue={defaultValue}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
|
@ -33,7 +34,7 @@ export const BooleanComponent = ({
|
|||
id={name!}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
isChecked={value === "true" || value === true}
|
||||
onChange={(value) => onChange("" + value)}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -32,6 +32,7 @@ export const ListComponent = ({
|
|||
>
|
||||
<Controller
|
||||
name={`config.${name?.replaceAll(".", "-")}`}
|
||||
data-testid={name}
|
||||
defaultValue={defaultValue || ""}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
FormGroup,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import { HelpItem } from "../../../components/help-enabler/HelpItem";
|
||||
import type { ComponentProps } from "./components";
|
||||
|
||||
export const MultivaluedListComponent = ({
|
||||
name,
|
||||
label,
|
||||
helpText,
|
||||
defaultValue,
|
||||
options,
|
||||
}: ComponentProps) => {
|
||||
const { t } = useTranslation("client-scopes");
|
||||
const { control } = useFormContext();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
label={t(label!)}
|
||||
labelIcon={
|
||||
<HelpItem helpText={t(helpText!)} forLabel={t(label!)} forID={name!} />
|
||||
}
|
||||
fieldId={name!}
|
||||
>
|
||||
<Controller
|
||||
name={label!}
|
||||
defaultValue={defaultValue || []}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId={name}
|
||||
data-testid={name}
|
||||
chipGroupProps={{
|
||||
numChips: 1,
|
||||
expandedText: t("common:hide"),
|
||||
collapsedText: t("common:showRemaining"),
|
||||
}}
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
typeAheadAriaLabel={t("common:select")}
|
||||
onToggle={(isOpen) => setOpen(isOpen)}
|
||||
selections={value}
|
||||
onSelect={(_, v) => {
|
||||
const option = v.toString();
|
||||
if (!value) {
|
||||
onChange([option]);
|
||||
} else if (value.includes(option)) {
|
||||
onChange(value.filter((item: string) => item !== option));
|
||||
} else {
|
||||
onChange([...value, option]);
|
||||
}
|
||||
}}
|
||||
onClear={(event) => {
|
||||
event.stopPropagation();
|
||||
onChange([]);
|
||||
}}
|
||||
isOpen={open}
|
||||
aria-label={t(label!)}
|
||||
>
|
||||
{options?.map((option) => (
|
||||
<SelectOption key={option} value={option} />
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
|
@ -142,6 +142,7 @@ export const RoleComponent = ({ name, label, helpText }: ComponentProps) => {
|
|||
{clients && (
|
||||
<Select
|
||||
toggleId={name!}
|
||||
data-testid={name}
|
||||
onToggle={() => setClientsOpen(!clientsOpen)}
|
||||
isOpen={clientsOpen}
|
||||
variant={SelectVariant.typeahead}
|
||||
|
|
|
@ -35,6 +35,7 @@ export const ScriptComponent = ({
|
|||
render={({ onChange, value }) => (
|
||||
<CodeEditor
|
||||
id={name!}
|
||||
data-testid={name}
|
||||
type="text"
|
||||
onChange={onChange}
|
||||
code={value}
|
||||
|
|
|
@ -25,6 +25,7 @@ export const StringComponent = ({
|
|||
>
|
||||
<TextInput
|
||||
id={name!}
|
||||
data-testid={name}
|
||||
ref={register()}
|
||||
type="text"
|
||||
name={`config.${name?.replaceAll(".", "-")}`}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { BooleanComponent } from "./BooleanComponent";
|
|||
import { ListComponent } from "./ListComponent";
|
||||
import { RoleComponent } from "./RoleComponent";
|
||||
import { ScriptComponent } from "./ScriptComponent";
|
||||
import { MultivaluedListComponent } from "./MultivaluedListComponent";
|
||||
import { ClientSelectComponent } from "./ClientSelectComponent";
|
||||
|
||||
export type ComponentProps = Omit<ConfigPropertyRepresentation, "type">;
|
||||
|
@ -15,6 +16,7 @@ const ComponentTypes = [
|
|||
"List",
|
||||
"Role",
|
||||
"Script",
|
||||
"MultivaluedList",
|
||||
"ClientList",
|
||||
] as const;
|
||||
|
||||
|
@ -28,5 +30,9 @@ export const COMPONENTS: {
|
|||
List: ListComponent,
|
||||
Role: RoleComponent,
|
||||
Script: ScriptComponent,
|
||||
MultivaluedList: MultivaluedListComponent,
|
||||
ClientList: ClientSelectComponent,
|
||||
} as const;
|
||||
|
||||
export const isValidComponentType = (value: string): value is Components =>
|
||||
value in COMPONENTS;
|
||||
|
|
|
@ -25,8 +25,8 @@ import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
|||
import { convertFormValuesToObject, convertToFormValues } from "../../util";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import { COMPONENTS, isValidComponentType } from "../add/components/components";
|
||||
import { MapperParams, MapperRoute } from "../routes/Mapper";
|
||||
import { Components, COMPONENTS } from "../add/components/components";
|
||||
import { toClientScope } from "../routes/ClientScope";
|
||||
|
||||
import "./mapping-details.css";
|
||||
|
@ -178,9 +178,6 @@ export const MappingDetails = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const isValidComponentType = (value: string): value is Components =>
|
||||
value in COMPONENTS;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteConfirm />
|
||||
|
|
|
@ -34,6 +34,7 @@ export default {
|
|||
disabled: "Disabled",
|
||||
disable: "Disable",
|
||||
selectOne: "Select an option",
|
||||
select: "Select",
|
||||
choose: "Choose...",
|
||||
any: "Any",
|
||||
none: "None",
|
||||
|
@ -48,6 +49,9 @@ export default {
|
|||
documentation: "Documentation",
|
||||
enableHelpMode: "Enable help mode",
|
||||
learnMore: "Learn more",
|
||||
show: "Show",
|
||||
hide: "Hide",
|
||||
showRemaining: "Show ${remaining}",
|
||||
test: "Test",
|
||||
testConnection: "Test connection",
|
||||
name: "Name",
|
||||
|
|
|
@ -271,8 +271,8 @@ export const AdminEvents = () => {
|
|||
data-testid="resource-types-searchField"
|
||||
chipGroupProps={{
|
||||
numChips: 1,
|
||||
expandedText: "Hide",
|
||||
collapsedText: "Show ${remaining}",
|
||||
expandedText: t("common:hide"),
|
||||
collapsedText: t("common:showRemaining"),
|
||||
}}
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
typeAheadAriaLabel="Select"
|
||||
|
@ -336,8 +336,8 @@ export const AdminEvents = () => {
|
|||
data-testid="operation-types-searchField"
|
||||
chipGroupProps={{
|
||||
numChips: 1,
|
||||
expandedText: "Hide",
|
||||
collapsedText: "Show ${remaining}",
|
||||
expandedText: t("common:hide"),
|
||||
collapsedText: t("common:showRemaining"),
|
||||
}}
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
typeAheadAriaLabel="Select"
|
||||
|
|
|
@ -252,8 +252,8 @@ export const EventsSection = () => {
|
|||
data-testid="event-type-searchField"
|
||||
chipGroupProps={{
|
||||
numChips: 1,
|
||||
expandedText: "Hide",
|
||||
collapsedText: "Show ${remaining}",
|
||||
expandedText: t("common:hide"),
|
||||
collapsedText: t("common:showRemaining"),
|
||||
}}
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
typeAheadAriaLabel="Select"
|
||||
|
|
482
src/realm-settings/ClientProfileForm.tsx
Normal file
482
src/realm-settings/ClientProfileForm.tsx
Normal file
|
@ -0,0 +1,482 @@
|
|||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertVariant,
|
||||
Button,
|
||||
ButtonVariant,
|
||||
DataList,
|
||||
DataListCell,
|
||||
DataListItem,
|
||||
DataListItemCells,
|
||||
DataListItemRow,
|
||||
Divider,
|
||||
DropdownItem,
|
||||
Flex,
|
||||
FlexItem,
|
||||
FormGroup,
|
||||
PageSection,
|
||||
Text,
|
||||
TextArea,
|
||||
TextInput,
|
||||
TextVariants,
|
||||
ValidatedOptions,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { Link, useHistory, useParams } from "react-router-dom";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||
import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation";
|
||||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||
import { PlusCircleIcon, TrashIcon } from "@patternfly/react-icons";
|
||||
import "./RealmSettingsSection.css";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { toAddExecutor } from "./routes/AddExecutor";
|
||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||
import type { ClientProfileParams } from "./routes/ClientProfile";
|
||||
|
||||
type ClientProfileForm = Required<ClientProfileRepresentation>;
|
||||
|
||||
const defaultValues: ClientProfileForm = {
|
||||
name: "",
|
||||
description: "",
|
||||
executors: [],
|
||||
};
|
||||
|
||||
export const ClientProfileForm = () => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const history = useHistory();
|
||||
const {
|
||||
handleSubmit,
|
||||
setValue,
|
||||
register,
|
||||
errors,
|
||||
formState: { isDirty },
|
||||
} = useForm<ClientProfileForm>({
|
||||
defaultValues,
|
||||
mode: "onChange",
|
||||
});
|
||||
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const adminClient = useAdminClient();
|
||||
const [globalProfiles, setGlobalProfiles] = useState<
|
||||
ClientProfileRepresentation[]
|
||||
>([]);
|
||||
const [profiles, setProfiles] = useState<ClientProfileRepresentation[]>([]);
|
||||
const { realm, profileName } = useParams<ClientProfileParams>();
|
||||
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(new Date().getTime());
|
||||
|
||||
useFetch(
|
||||
() =>
|
||||
adminClient.clientPolicies.listProfiles({ includeGlobalProfiles: true }),
|
||||
(profiles) => {
|
||||
setGlobalProfiles(profiles.globalProfiles ?? []);
|
||||
setProfiles(profiles.profiles ?? []);
|
||||
},
|
||||
[key]
|
||||
);
|
||||
|
||||
const save = async (form: ClientProfileForm) => {
|
||||
const updatedProfiles = editMode ? patchProfiles(form) : addProfile(form);
|
||||
|
||||
try {
|
||||
await adminClient.clientPolicies.createProfiles({
|
||||
profiles: updatedProfiles,
|
||||
globalProfiles: globalProfiles,
|
||||
});
|
||||
|
||||
addAlert(
|
||||
editMode
|
||||
? t("realm-settings:updateClientProfileSuccess")
|
||||
: t("realm-settings:createClientProfileSuccess"),
|
||||
AlertVariant.success
|
||||
);
|
||||
|
||||
history.push(`/${realm}/realm-settings/clientPolicies/${form.name}`);
|
||||
} catch (error) {
|
||||
addError(
|
||||
editMode
|
||||
? "realm-settings:updateClientProfileError"
|
||||
: "realm-settings:createClientProfileError",
|
||||
error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const patchProfiles = (data: ClientProfileRepresentation) =>
|
||||
profiles.map((profile) => {
|
||||
if (profile.name !== profileName) {
|
||||
return profile;
|
||||
}
|
||||
|
||||
return {
|
||||
...profile,
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
};
|
||||
});
|
||||
|
||||
const addProfile = (data: ClientProfileRepresentation) =>
|
||||
profiles.concat({
|
||||
...data,
|
||||
executors: [],
|
||||
});
|
||||
|
||||
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!) {
|
||||
profileExecutors.splice(executorToDelete.idx!, 1);
|
||||
try {
|
||||
await adminClient.clientPolicies.createProfiles({
|
||||
profiles: profiles,
|
||||
globalProfiles,
|
||||
});
|
||||
addAlert(t("deleteExecutorSuccess"), AlertVariant.success);
|
||||
history.push(
|
||||
`/${realm}/realm-settings/clientPolicies/${profileName}`
|
||||
);
|
||||
} catch (error) {
|
||||
addError(t("deleteExecutorError"), error);
|
||||
}
|
||||
} else {
|
||||
const updatedProfiles = profiles.filter(
|
||||
(profile) => profile.name !== profileName
|
||||
);
|
||||
|
||||
try {
|
||||
await adminClient.clientPolicies.createProfiles({
|
||||
profiles: updatedProfiles,
|
||||
globalProfiles,
|
||||
});
|
||||
addAlert(t("deleteClientSuccess"), AlertVariant.success);
|
||||
history.push(`/${realm}/realm-settings/clientPolicies`);
|
||||
} catch (error) {
|
||||
addError(t("deleteClientError"), error);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const profile = profiles.find((profile) => profile.name === profileName);
|
||||
const profileExecutors = profile?.executors || [];
|
||||
const globalProfile = globalProfiles.find(
|
||||
(globalProfile) => globalProfile.name === profileName
|
||||
);
|
||||
const globalProfileExecutors = globalProfile?.executors || [];
|
||||
|
||||
useEffect(() => {
|
||||
setValue("name", globalProfile?.name ?? profile?.name);
|
||||
setValue("description", globalProfile?.description ?? profile?.description);
|
||||
}, [profiles]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteConfirm />
|
||||
<ViewHeader
|
||||
titleKey={editMode ? profileName : t("newClientProfile")}
|
||||
badges={[
|
||||
{
|
||||
id: "global-client-profile-badge",
|
||||
text: globalProfile ? t("global") : "",
|
||||
},
|
||||
]}
|
||||
divider
|
||||
dropdownItems={
|
||||
!globalProfile
|
||||
? [
|
||||
<DropdownItem
|
||||
key="delete"
|
||||
value="delete"
|
||||
onClick={toggleDeleteDialog}
|
||||
data-testid="deleteClientProfileDropdown"
|
||||
>
|
||||
{t("deleteClientProfile")}
|
||||
</DropdownItem>,
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<PageSection variant="light">
|
||||
<FormAccess isHorizontal role="view-realm" className="pf-u-mt-lg">
|
||||
<FormGroup
|
||||
label={t("newClientProfileName")}
|
||||
fieldId="kc-name"
|
||||
helperText={t("createClientProfileNameHelperText")}
|
||||
isRequired
|
||||
helperTextInvalid={t("common:required")}
|
||||
validated={
|
||||
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
>
|
||||
<TextInput
|
||||
ref={register({ required: true })}
|
||||
name="name"
|
||||
type="text"
|
||||
id="name"
|
||||
aria-label={t("name")}
|
||||
data-testid="client-profile-name"
|
||||
isReadOnly={!!globalProfile}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label={t("common:description")} fieldId="kc-description">
|
||||
<TextArea
|
||||
ref={register()}
|
||||
name="description"
|
||||
type="text"
|
||||
id="description"
|
||||
aria-label={t("description")}
|
||||
data-testid="client-profile-description"
|
||||
isReadOnly={!!globalProfile}
|
||||
/>
|
||||
</FormGroup>
|
||||
<ActionGroup>
|
||||
{!globalProfile && (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => handleSubmit(save)()}
|
||||
data-testid="saveCreateProfile"
|
||||
isDisabled={!isDirty}
|
||||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
)}
|
||||
{editMode && !globalProfile && (
|
||||
<Button
|
||||
id={"reloadProfile"}
|
||||
variant="link"
|
||||
data-testid={"reloadProfile"}
|
||||
isDisabled={!isDirty}
|
||||
onClick={reload}
|
||||
>
|
||||
{t("realm-settings:reload")}
|
||||
</Button>
|
||||
)}
|
||||
{!editMode && !globalProfile && (
|
||||
<Button
|
||||
id={"cancelCreateProfile"}
|
||||
component={(props) => (
|
||||
<Link
|
||||
{...props}
|
||||
to={`/${realm}/realm-settings/clientPolicies`}
|
||||
/>
|
||||
)}
|
||||
data-testid={"cancelCreateProfile"}
|
||||
>
|
||||
{t("common:cancel")}
|
||||
</Button>
|
||||
)}
|
||||
</ActionGroup>
|
||||
{editMode && (
|
||||
<>
|
||||
<Flex>
|
||||
<FlexItem>
|
||||
<Text className="kc-executors" component={TextVariants.h1}>
|
||||
{t("executors")}
|
||||
<HelpItem
|
||||
helpText={t("realm-settings:executorsHelpText")}
|
||||
forLabel={t("executorsHelpItem")}
|
||||
forID={t("executors")}
|
||||
/>
|
||||
</Text>
|
||||
</FlexItem>
|
||||
{profile && (
|
||||
<FlexItem align={{ default: "alignRight" }}>
|
||||
<Button
|
||||
id="addExecutor"
|
||||
component={(props) => (
|
||||
<Link
|
||||
{...props}
|
||||
to={toAddExecutor({
|
||||
realm,
|
||||
profileName,
|
||||
})}
|
||||
></Link>
|
||||
)}
|
||||
variant="link"
|
||||
className="kc-addExecutor"
|
||||
data-testid="cancelCreateProfile"
|
||||
icon={<PlusCircleIcon />}
|
||||
>
|
||||
{t("realm-settings:addExecutor")}
|
||||
</Button>
|
||||
</FlexItem>
|
||||
)}
|
||||
</Flex>
|
||||
{profileExecutors.length > 0 && (
|
||||
<DataList aria-label={t("executors")} isCompact>
|
||||
{profileExecutors.map((executor, idx) => (
|
||||
<DataListItem
|
||||
aria-labelledby={"executors-list-item"}
|
||||
key={executor.executor}
|
||||
id={executor.executor}
|
||||
>
|
||||
<DataListItemRow data-testid="executors-list-row">
|
||||
<DataListItemCells
|
||||
dataListCells={[
|
||||
<DataListCell
|
||||
key="executor"
|
||||
data-testid="executor-type"
|
||||
>
|
||||
{Object.keys(executor).length !== 0 ? (
|
||||
<Link
|
||||
data-testid="executor-type-link"
|
||||
to={""}
|
||||
className="kc-executor-link"
|
||||
>
|
||||
{executor.executor}
|
||||
</Link>
|
||||
) : (
|
||||
executor.executor
|
||||
)}
|
||||
{executorTypes
|
||||
?.filter(
|
||||
(type) => type.id === executor.executor
|
||||
)
|
||||
.map((type) => (
|
||||
<>
|
||||
<HelpItem
|
||||
key={type.id}
|
||||
helpText={type.helpText}
|
||||
forLabel={t("executorTypeTextHelpText")}
|
||||
forID={t(`common:helpLabel`, {
|
||||
label: t("executorTypeTextHelpText"),
|
||||
})}
|
||||
/>
|
||||
<Button
|
||||
variant="link"
|
||||
isInline
|
||||
icon={
|
||||
<TrashIcon
|
||||
key={`executorType-trash-icon-${type.id}`}
|
||||
className="kc-executor-trash-icon"
|
||||
data-testid="deleteExecutor"
|
||||
/>
|
||||
}
|
||||
onClick={() => {
|
||||
toggleDeleteDialog();
|
||||
setExecutorToDelete({
|
||||
idx: idx,
|
||||
name: type.id,
|
||||
});
|
||||
}}
|
||||
></Button>
|
||||
</>
|
||||
))}
|
||||
</DataListCell>,
|
||||
]}
|
||||
/>
|
||||
</DataListItemRow>
|
||||
</DataListItem>
|
||||
))}
|
||||
</DataList>
|
||||
)}
|
||||
{globalProfileExecutors.length > 0 && (
|
||||
<>
|
||||
<DataList aria-label={t("executors")} isCompact>
|
||||
{globalProfileExecutors.map((executor) => (
|
||||
<DataListItem
|
||||
aria-labelledby={"global-executors-list-item"}
|
||||
key={executor.executor}
|
||||
id={executor.executor}
|
||||
>
|
||||
<DataListItemRow data-testid="global-executors-list-row">
|
||||
<DataListItemCells
|
||||
dataListCells={[
|
||||
<DataListCell
|
||||
key="executor"
|
||||
data-testid="global-executor-type"
|
||||
>
|
||||
{Object.keys(executor).length !== 0 ? (
|
||||
<Link
|
||||
data-testid="global-executor-type-link"
|
||||
to={""}
|
||||
className="kc-global-executor-link"
|
||||
>
|
||||
{executor.executor}
|
||||
</Link>
|
||||
) : (
|
||||
executor.executor
|
||||
)}
|
||||
{executorTypes
|
||||
?.filter(
|
||||
(type) => type.id === executor.executor
|
||||
)
|
||||
.map((type) => (
|
||||
<HelpItem
|
||||
key={type.id}
|
||||
helpText={type.helpText}
|
||||
forLabel={t("executorTypeTextHelpText")}
|
||||
forID={t(`common:helpLabel`, {
|
||||
label: t("executorTypeTextHelpText"),
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</DataListCell>,
|
||||
]}
|
||||
/>
|
||||
</DataListItemRow>
|
||||
</DataListItem>
|
||||
))}
|
||||
</DataList>
|
||||
<Button
|
||||
id="backToClientPolicies"
|
||||
component={(props) => (
|
||||
<Link
|
||||
{...props}
|
||||
to={`/${realm}/realm-settings/clientPolicies`}
|
||||
/>
|
||||
)}
|
||||
variant="primary"
|
||||
className="kc-backToPolicies"
|
||||
data-testid="backToClientPolicies"
|
||||
>
|
||||
{t("realm-settings:back")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{profileExecutors.length === 0 &&
|
||||
globalProfileExecutors.length === 0 && (
|
||||
<>
|
||||
<Divider />
|
||||
<Text
|
||||
className="kc-emptyExecutors"
|
||||
component={TextVariants.h6}
|
||||
>
|
||||
{t("realm-settings:emptyExecutors")}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</FormAccess>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
207
src/realm-settings/ExecutorForm.tsx
Normal file
207
src/realm-settings/ExecutorForm.tsx
Normal file
|
@ -0,0 +1,207 @@
|
|||
import React, { useState } from "react";
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertVariant,
|
||||
Button,
|
||||
FormGroup,
|
||||
PageSection,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||
import { Link, useHistory, useParams } from "react-router-dom";
|
||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||
import type ComponentTypeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentTypeRepresentation";
|
||||
import type { ConfigPropertyRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigInfoRepresentation";
|
||||
import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation";
|
||||
import type { ClientProfileParams } from "./routes/ClientProfile";
|
||||
import {
|
||||
COMPONENTS,
|
||||
isValidComponentType,
|
||||
} from "../client-scopes/add/components/components";
|
||||
import type ClientPolicyExecutorRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientPolicyExecutorRepresentation";
|
||||
|
||||
type ExecutorForm = Required<ClientPolicyExecutorRepresentation>;
|
||||
|
||||
const defaultValues: ExecutorForm = {
|
||||
configuration: {},
|
||||
executor: "",
|
||||
};
|
||||
|
||||
export const ExecutorForm = () => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const history = useHistory();
|
||||
const { realm, profileName } = useParams<ClientProfileParams>();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const [selectExecutorTypeOpen, setSelectExecutorTypeOpen] = useState(false);
|
||||
const serverInfo = useServerInfo();
|
||||
const adminClient = useAdminClient();
|
||||
const executorTypes =
|
||||
serverInfo.componentTypes?.[
|
||||
"org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider"
|
||||
];
|
||||
const [executors, setExecutors] = useState<ComponentTypeRepresentation[]>([]);
|
||||
const [executorProperties, setExecutorProperties] = useState<
|
||||
ConfigPropertyRepresentation[]
|
||||
>([]);
|
||||
const [globalProfiles, setGlobalProfiles] = useState<
|
||||
ClientProfileRepresentation[]
|
||||
>([]);
|
||||
const [profiles, setProfiles] = useState<ClientProfileRepresentation[]>([]);
|
||||
const form = useForm<ExecutorForm>({ defaultValues });
|
||||
const { control } = form;
|
||||
|
||||
useFetch(
|
||||
() =>
|
||||
adminClient.clientPolicies.listProfiles({ includeGlobalProfiles: true }),
|
||||
(profiles) => {
|
||||
setGlobalProfiles(profiles.globalProfiles ?? []);
|
||||
setProfiles(profiles.profiles ?? []);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const fldNameFormatter = (name: string) =>
|
||||
name.toLowerCase().trim().split(/\s+/).join("-");
|
||||
|
||||
const save = async () => {
|
||||
const formValues = form.getValues();
|
||||
const updatedProfiles = profiles.map((profile) => {
|
||||
if (profile.name !== profileName) {
|
||||
return profile;
|
||||
}
|
||||
|
||||
const executors = (profile.executors ?? []).concat({
|
||||
executor: formValues.executor,
|
||||
configuration: formValues.configuration,
|
||||
});
|
||||
|
||||
return {
|
||||
...profile,
|
||||
executors,
|
||||
};
|
||||
});
|
||||
try {
|
||||
await adminClient.clientPolicies.createProfiles({
|
||||
profiles: updatedProfiles,
|
||||
globalProfiles: globalProfiles,
|
||||
});
|
||||
addAlert(t("realm-settings:addExecutorSuccess"), AlertVariant.success);
|
||||
history.push(`/${realm}/realm-settings/clientPolicies/${profileName}`);
|
||||
} catch (error) {
|
||||
addError("realm-settings:addExecutorError", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewHeader titleKey={t("addExecutor")} divider />
|
||||
<PageSection variant="light">
|
||||
<FormAccess isHorizontal role="manage-realm" className="pf-u-mt-lg">
|
||||
<FormGroup
|
||||
label={t("executorType")}
|
||||
fieldId="kc-executorType"
|
||||
labelIcon={
|
||||
executors.length > 0 && executors[0].helpText! !== "" ? (
|
||||
<HelpItem
|
||||
helpText={executors[0].helpText}
|
||||
forLabel={t("executorTypeHelpText")}
|
||||
forID={t(`common:helpLabel`, {
|
||||
label: t("executorTypeHelpText"),
|
||||
})}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="executor"
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="kc-executor"
|
||||
placeholderText="Select an executor"
|
||||
onToggle={(isOpen) => setSelectExecutorTypeOpen(isOpen)}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value.toString());
|
||||
const selectedExecutor = executorTypes?.filter(
|
||||
(type) => type.id === value
|
||||
);
|
||||
setExecutors(selectedExecutor ?? []);
|
||||
setExecutorProperties(
|
||||
selectedExecutor?.[0].properties ?? []
|
||||
);
|
||||
setSelectExecutorTypeOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
variant={SelectVariant.single}
|
||||
data-testid="executorType-select"
|
||||
aria-label={t("executorType")}
|
||||
isOpen={selectExecutorTypeOpen}
|
||||
maxHeight={580}
|
||||
>
|
||||
{executorTypes?.map((option) => (
|
||||
<SelectOption
|
||||
selected={option.id === value}
|
||||
key={option.id}
|
||||
value={option.id}
|
||||
description={option.helpText}
|
||||
/>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormProvider {...form}>
|
||||
{executorProperties.map((option) => {
|
||||
const componentType = option.type!;
|
||||
if (isValidComponentType(componentType)) {
|
||||
const Component = COMPONENTS[componentType];
|
||||
return (
|
||||
<Component
|
||||
key={option.name}
|
||||
{...option}
|
||||
name={fldNameFormatter(option.label!)}
|
||||
label={option.label}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
console.warn(
|
||||
`There is no editor registered for ${componentType}`
|
||||
);
|
||||
}
|
||||
})}
|
||||
</FormProvider>
|
||||
<ActionGroup>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={save}
|
||||
data-testid="realm-settings-add-executor-save-button"
|
||||
>
|
||||
{t("common:add")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="link"
|
||||
component={(props) => (
|
||||
<Link
|
||||
{...props}
|
||||
to={`/${realm}/realm-settings/clientPolicies/${profileName}`}
|
||||
/>
|
||||
)}
|
||||
data-testid="realm-settings-add-executor-cancel-button"
|
||||
>
|
||||
{t("common:cancel")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,234 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertVariant,
|
||||
Button,
|
||||
ButtonVariant,
|
||||
Divider,
|
||||
DropdownItem,
|
||||
Flex,
|
||||
FlexItem,
|
||||
FormGroup,
|
||||
PageSection,
|
||||
Text,
|
||||
TextArea,
|
||||
TextInput,
|
||||
TextVariants,
|
||||
ValidatedOptions,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { Link, useHistory } from "react-router-dom";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||
import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation";
|
||||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||
import { PlusCircleIcon } from "@patternfly/react-icons";
|
||||
import "./RealmSettingsSection.css";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
|
||||
type NewClientProfileForm = Required<ClientProfileRepresentation>;
|
||||
|
||||
const defaultValues: NewClientProfileForm = {
|
||||
name: "",
|
||||
description: "",
|
||||
executors: [],
|
||||
};
|
||||
|
||||
export const NewClientProfileForm = () => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const { getValues, register, errors } = useForm<NewClientProfileForm>({
|
||||
defaultValues,
|
||||
});
|
||||
const { realm } = useRealm();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const adminClient = useAdminClient();
|
||||
const [globalProfiles, setGlobalProfiles] = useState<
|
||||
ClientProfileRepresentation[]
|
||||
>([]);
|
||||
const [profiles, setProfiles] = useState<ClientProfileRepresentation[]>([]);
|
||||
const [showAddExecutorsForm, setShowAddExecutorsForm] = useState(false);
|
||||
const [createdProfile, setCreatedProfile] =
|
||||
useState<ClientProfileRepresentation>();
|
||||
const form = getValues();
|
||||
const history = useHistory();
|
||||
|
||||
useFetch(
|
||||
() =>
|
||||
adminClient.clientPolicies.listProfiles({ includeGlobalProfiles: true }),
|
||||
(profiles) => {
|
||||
setGlobalProfiles(profiles.globalProfiles ?? []);
|
||||
setProfiles(profiles.profiles ?? []);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const save = async () => {
|
||||
const form = getValues();
|
||||
|
||||
const createdProfile = {
|
||||
...form,
|
||||
executors: [],
|
||||
};
|
||||
|
||||
const allProfiles = profiles.concat(createdProfile);
|
||||
|
||||
try {
|
||||
await adminClient.clientPolicies.createProfiles({
|
||||
profiles: allProfiles,
|
||||
globalProfiles: globalProfiles,
|
||||
});
|
||||
addAlert(
|
||||
t("realm-settings:createClientProfileSuccess"),
|
||||
AlertVariant.success
|
||||
);
|
||||
setShowAddExecutorsForm(true);
|
||||
setCreatedProfile(createdProfile);
|
||||
} catch (error) {
|
||||
addError("realm-settings:createClientProfileError", error);
|
||||
}
|
||||
};
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: t("deleteClientProfileConfirmTitle"),
|
||||
messageKey: t("deleteClientProfileConfirm"),
|
||||
continueButtonLabel: t("delete"),
|
||||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: async () => {
|
||||
const updatedProfiles = profiles.filter(
|
||||
(profile) => profile.name !== createdProfile?.name
|
||||
);
|
||||
|
||||
try {
|
||||
await adminClient.clientPolicies.createProfiles({
|
||||
profiles: updatedProfiles,
|
||||
globalProfiles,
|
||||
});
|
||||
addAlert(t("deleteClientSuccess"), AlertVariant.success);
|
||||
history.push(`/${realm}/realm-settings/clientPolicies`);
|
||||
} catch (error) {
|
||||
addError(t("deleteClientError"), error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteConfirm />
|
||||
<ViewHeader
|
||||
titleKey={showAddExecutorsForm ? form.name : t("newClientProfile")}
|
||||
divider
|
||||
dropdownItems={
|
||||
showAddExecutorsForm
|
||||
? [
|
||||
<DropdownItem
|
||||
key="delete"
|
||||
value="delete"
|
||||
onClick={toggleDeleteDialog}
|
||||
data-testid="deleteClientProfileDropdown"
|
||||
>
|
||||
{t("deleteClientProfile")}
|
||||
</DropdownItem>,
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<PageSection variant="light">
|
||||
<FormAccess isHorizontal role="view-realm" className="pf-u-mt-lg">
|
||||
<FormGroup
|
||||
label={t("newClientProfileName")}
|
||||
fieldId="kc-name"
|
||||
helperText={t("createClientProfileNameHelperText")}
|
||||
isRequired
|
||||
helperTextInvalid={t("common:required")}
|
||||
validated={
|
||||
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
>
|
||||
<TextInput
|
||||
ref={register({ required: true })}
|
||||
type="text"
|
||||
id="kc-client-profile-name"
|
||||
name="name"
|
||||
data-testid="client-profile-name"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label={t("common:description")} fieldId="kc-description">
|
||||
<TextArea
|
||||
name="description"
|
||||
aria-label={t("description")}
|
||||
ref={register()}
|
||||
type="text"
|
||||
id="kc-client-profile-description"
|
||||
data-testid="client-profile-description"
|
||||
/>
|
||||
</FormGroup>
|
||||
<ActionGroup>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={save}
|
||||
data-testid="saveCreateProfile"
|
||||
isDisabled={showAddExecutorsForm ? true : false}
|
||||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button
|
||||
id="cancelCreateProfile"
|
||||
component={(props) => (
|
||||
<Link
|
||||
{...props}
|
||||
to={`/${realm}/realm-settings/clientPolicies`}
|
||||
/>
|
||||
)}
|
||||
data-testid="cancelCreateProfile"
|
||||
>
|
||||
{showAddExecutorsForm
|
||||
? t("realm-settings:reload")
|
||||
: t("common:cancel")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
{showAddExecutorsForm && (
|
||||
<>
|
||||
<Flex>
|
||||
<FlexItem>
|
||||
<Text className="kc-executors" component={TextVariants.h1}>
|
||||
{t("executors")}
|
||||
<HelpItem
|
||||
helpText={t("realm-settings:executorsHelpText")}
|
||||
forLabel={t("executorsHelpItem")}
|
||||
forID={t("executors")}
|
||||
/>
|
||||
</Text>
|
||||
</FlexItem>
|
||||
<FlexItem align={{ default: "alignRight" }}>
|
||||
<Button
|
||||
id="addExecutor"
|
||||
component={(props) => (
|
||||
<Link
|
||||
{...props}
|
||||
to={`/${realm}/realm-settings/clientPolicies`}
|
||||
></Link>
|
||||
)}
|
||||
variant="link"
|
||||
className="kc-addExecutor"
|
||||
data-testid="cancelCreateProfile"
|
||||
icon={<PlusCircleIcon />}
|
||||
>
|
||||
{t("realm-settings:addExecutor")}
|
||||
</Button>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
<Divider />
|
||||
<Text className="kc-emptyExecutors" component={TextVariants.h6}>
|
||||
{t("realm-settings:emptyExecutors")}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</FormAccess>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -22,10 +22,10 @@ import { useRealm } from "../context/realm-context/RealmContext";
|
|||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { prettyPrintJSON } from "../util";
|
||||
import { Link } from "react-router-dom";
|
||||
import { toNewClientProfile } from "./routes/NewClientProfile";
|
||||
import { toAddClientProfile } from "./routes/AddClientProfile";
|
||||
import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation";
|
||||
|
||||
import "./RealmSettingsSection.css";
|
||||
import { toClientProfile } from "./routes/ClientProfile";
|
||||
|
||||
type ClientProfile = ClientProfileRepresentation & {
|
||||
global: boolean;
|
||||
|
@ -79,7 +79,9 @@ export const ProfilesTab = () => {
|
|||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: t("deleteClientProfileConfirmTitle"),
|
||||
messageKey: t("deleteClientProfileConfirm"),
|
||||
messageKey: t("deleteClientProfileConfirm", {
|
||||
profileName: selectedProfile?.name,
|
||||
}),
|
||||
continueButtonLabel: t("delete"),
|
||||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: async () => {
|
||||
|
@ -105,7 +107,13 @@ export const ProfilesTab = () => {
|
|||
});
|
||||
|
||||
const cellFormatter = (row: ClientProfile) => (
|
||||
<Link to={""} key={row.name}>
|
||||
<Link
|
||||
to={toClientProfile({
|
||||
realm,
|
||||
profileName: row.name!,
|
||||
})}
|
||||
key={row.name}
|
||||
>
|
||||
{row.name} {row.global && <Label color="blue">{t("global")}</Label>}
|
||||
</Link>
|
||||
);
|
||||
|
@ -197,7 +205,7 @@ export const ProfilesTab = () => {
|
|||
<Button
|
||||
id="createProfile"
|
||||
component={(props) => (
|
||||
<Link {...props} to={toNewClientProfile({ realm })} />
|
||||
<Link {...props} to={toAddClientProfile({ realm })} />
|
||||
)}
|
||||
data-testid="createProfile"
|
||||
>
|
||||
|
|
|
@ -210,6 +210,23 @@ article.pf-c-card.pf-m-flat.kc-login-settings-template
|
|||
background-color: transparent;
|
||||
}
|
||||
|
||||
.kc-executor-link {
|
||||
margin-right: 0.625rem;
|
||||
}
|
||||
|
||||
.kc-backToPolicies {
|
||||
width: 5rem;
|
||||
}
|
||||
|
||||
.kc-executor-trash-icon {
|
||||
margin-left: .5rem;
|
||||
color: var(--pf-global--Color--200);
|
||||
}
|
||||
|
||||
.kc-executor-trash-icon:hover {
|
||||
filter: brightness(55%);
|
||||
}
|
||||
|
||||
.kc-condition-link {
|
||||
margin-right: 0.625rem;
|
||||
}
|
||||
|
|
|
@ -238,18 +238,22 @@ export default {
|
|||
"There are no profiles, select 'Create client profile' to create a new client profile",
|
||||
deleteClientProfileConfirmTitle: "Delete profile?",
|
||||
deleteClientProfileConfirm:
|
||||
"This action will permanently delete the profile custom-profile. This cannot be undone.",
|
||||
"This action will permanently delete the profile {{profileName}}. This cannot be undone.",
|
||||
deleteClientSuccess: "Client profile deleted",
|
||||
deleteClientError: "Could not delete profile: {{error}}",
|
||||
createClientProfile: "Create client profile",
|
||||
deleteClientProfile: "Delete this client profile",
|
||||
createClientProfileSuccess: "New client profile created",
|
||||
updateClientProfileSuccess: "Client profile updated successfully",
|
||||
createClientProfileError: "Could not create client profile: '{{error}}'",
|
||||
updateClientProfileError: "Could not update client profile: '{{error}}'",
|
||||
createClientProfileNameHelperText:
|
||||
"The name must be unique within the realm",
|
||||
allClientPolicies: "Client policies",
|
||||
newClientProfile: "Create client profile",
|
||||
newClientProfileName: "Client profile name",
|
||||
clientProfile: "Client profile details",
|
||||
back: "Back",
|
||||
delete: "delete",
|
||||
save: "Save",
|
||||
reload: "Reload",
|
||||
|
@ -260,7 +264,24 @@ export default {
|
|||
"Executors, which will be applied for this client profile",
|
||||
executorsHelpItem: "Executors help item",
|
||||
addExecutor: "Add executor",
|
||||
executorType: "Executor type",
|
||||
executorTypeSwitchHelpText: "Executor Type Switch Help Text",
|
||||
executorTypeSelectHelpText: "Executor Type Select Help Text",
|
||||
executorTypeSelectAlgorithm: "Executor Type Select Algorithm",
|
||||
executorTypeTextHelpText: "Executor Type Text Help Text",
|
||||
executorAuthenticatorMultiSelectHelpText:
|
||||
"Executor Authenticator MultiSelect Help Text",
|
||||
executorClientAuthenticator: "Executor Client Authenticator",
|
||||
executorsTable: "Executors table",
|
||||
executorName: "Name",
|
||||
emptyExecutors: "No executors configured",
|
||||
addExecutorSuccess: "Success! Executor created successfully",
|
||||
addExecutorError: "Executor not created",
|
||||
deleteExecutorProfileConfirmTitle: "Delete executor?",
|
||||
deleteExecutorProfileConfirm:
|
||||
"The action will permanently delete {{executorName}}. This cannot be undone.",
|
||||
deleteExecutorSuccess: "Success! The executor was deleted.",
|
||||
deleteExecutorError: "Could not delete executor: {{error}}",
|
||||
updateClientProfilesSuccess:
|
||||
"The client profiles configuration was updated",
|
||||
updateClientProfilesError:
|
||||
|
|
|
@ -6,7 +6,10 @@ import { JavaKeystoreSettingsRoute } from "./routes/JavaKeystoreSettings";
|
|||
import { RealmSettingsRoute } from "./routes/RealmSettings";
|
||||
import { RsaGeneratedSettingsRoute } from "./routes/RsaGeneratedSettings";
|
||||
import { RsaSettingsRoute } from "./routes/RsaSettings";
|
||||
import { NewClientProfileRoute } from "./routes/NewClientProfile";
|
||||
import { ClientPoliciesRoute } from "./routes/ClientPolicies";
|
||||
import { AddClientProfileRoute } from "./routes/AddClientProfile";
|
||||
import { ClientProfileRoute } from "./routes/ClientProfile";
|
||||
import { AddExecutorRoute } from "./routes/AddExecutor";
|
||||
import { NewClientPolicyRoute } from "./routes/NewClientPolicy";
|
||||
import { EditClientPolicyRoute } from "./routes/EditClientPolicy";
|
||||
import { NewClientPolicyConditionRoute } from "./routes/AddCondition";
|
||||
|
@ -19,7 +22,10 @@ const routes: RouteDef[] = [
|
|||
JavaKeystoreSettingsRoute,
|
||||
RsaGeneratedSettingsRoute,
|
||||
RsaSettingsRoute,
|
||||
NewClientProfileRoute,
|
||||
ClientPoliciesRoute,
|
||||
AddClientProfileRoute,
|
||||
AddExecutorRoute,
|
||||
ClientProfileRoute,
|
||||
NewClientPolicyRoute,
|
||||
EditClientPolicyRoute,
|
||||
NewClientPolicyConditionRoute,
|
||||
|
|
21
src/realm-settings/routes/AddClientProfile.ts
Normal file
21
src/realm-settings/routes/AddClientProfile.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import type { LocationDescriptorObject } from "history";
|
||||
import { generatePath } from "react-router-dom";
|
||||
import type { RouteDef } from "../../route-config";
|
||||
import { ClientProfileForm } from "../ClientProfileForm";
|
||||
|
||||
export type AddClientProfileParams = {
|
||||
realm: string;
|
||||
};
|
||||
|
||||
export const AddClientProfileRoute: RouteDef = {
|
||||
path: "/:realm/realm-settings/clientPolicies/add-profile",
|
||||
component: ClientProfileForm,
|
||||
breadcrumb: (t) => t("realm-settings:newClientProfile"),
|
||||
access: "manage-realm",
|
||||
};
|
||||
|
||||
export const toAddClientProfile = (
|
||||
params: AddClientProfileParams
|
||||
): LocationDescriptorObject => ({
|
||||
pathname: generatePath(AddClientProfileRoute.path, params),
|
||||
});
|
22
src/realm-settings/routes/AddExecutor.ts
Normal file
22
src/realm-settings/routes/AddExecutor.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import type { LocationDescriptorObject } from "history";
|
||||
import { generatePath } from "react-router-dom";
|
||||
import type { RouteDef } from "../../route-config";
|
||||
import { ExecutorForm } from "../ExecutorForm";
|
||||
|
||||
export type AddExecutorParams = {
|
||||
realm: string;
|
||||
profileName: string;
|
||||
};
|
||||
|
||||
export const AddExecutorRoute: RouteDef = {
|
||||
path: "/:realm/realm-settings/clientPolicies/:profileName/add-executor",
|
||||
component: ExecutorForm,
|
||||
breadcrumb: (t) => t("realm-settings:addExecutor"),
|
||||
access: "manage-realm",
|
||||
};
|
||||
|
||||
export const toAddExecutor = (
|
||||
params: AddExecutorParams
|
||||
): LocationDescriptorObject => ({
|
||||
pathname: generatePath(AddExecutorRoute.path, params),
|
||||
});
|
22
src/realm-settings/routes/ClientProfile.ts
Normal file
22
src/realm-settings/routes/ClientProfile.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import type { LocationDescriptorObject } from "history";
|
||||
import { generatePath } from "react-router-dom";
|
||||
import type { RouteDef } from "../../route-config";
|
||||
import { ClientProfileForm } from "../ClientProfileForm";
|
||||
|
||||
export type ClientProfileParams = {
|
||||
realm: string;
|
||||
profileName: string;
|
||||
};
|
||||
|
||||
export const ClientProfileRoute: RouteDef = {
|
||||
path: "/:realm/realm-settings/clientPolicies/:profileName",
|
||||
component: ClientProfileForm,
|
||||
breadcrumb: (t) => t("realm-settings:clientProfile"),
|
||||
access: ["view-realm", "view-users"],
|
||||
};
|
||||
|
||||
export const toClientProfile = (
|
||||
params: ClientProfileParams
|
||||
): LocationDescriptorObject => ({
|
||||
pathname: generatePath(ClientProfileRoute.path, params),
|
||||
});
|
|
@ -1,21 +0,0 @@
|
|||
import type { LocationDescriptorObject } from "history";
|
||||
import { generatePath } from "react-router-dom";
|
||||
import type { RouteDef } from "../../route-config";
|
||||
import { NewClientProfileForm } from "../NewClientProfileForm";
|
||||
|
||||
export type NewClientProfileParams = {
|
||||
realm: string;
|
||||
};
|
||||
|
||||
export const NewClientProfileRoute: RouteDef = {
|
||||
path: "/:realm/realm-settings/clientPolicies/new-client-profile",
|
||||
component: NewClientProfileForm,
|
||||
breadcrumb: (t) => t("realm-settings:newClientProfile"),
|
||||
access: "view-realm",
|
||||
};
|
||||
|
||||
export const toNewClientProfile = (
|
||||
params: NewClientProfileParams
|
||||
): LocationDescriptorObject => ({
|
||||
pathname: generatePath(NewClientProfileRoute.path, params),
|
||||
});
|
Loading…
Reference in a new issue