Added bind type dialog (#2173)
This commit is contained in:
parent
e3c0bb82a1
commit
1104f9ee20
4 changed files with 190 additions and 42 deletions
|
@ -1,6 +1,7 @@
|
|||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { sortBy } from "lodash-es";
|
||||
import {
|
||||
AlertVariant,
|
||||
Button,
|
||||
|
@ -30,9 +31,10 @@ import { toCreateFlow } from "./routes/CreateFlow";
|
|||
import { toFlow } from "./routes/Flow";
|
||||
import { RequiredActions } from "./RequiredActions";
|
||||
import { Policies } from "./policies/Policies";
|
||||
import helpUrls from "../help-urls";
|
||||
import { BindFlowDialog } from "./BindFlowDialog";
|
||||
|
||||
import "./authentication-section.css";
|
||||
import helpUrls from "../help-urls";
|
||||
|
||||
type UsedBy = "specificClients" | "default" | "specificProviders";
|
||||
|
||||
|
@ -40,7 +42,7 @@ type AuthenticationType = AuthenticationFlowRepresentation & {
|
|||
usedBy: { type?: UsedBy; values: string[] };
|
||||
};
|
||||
|
||||
const realmFlows = [
|
||||
export const REALM_FLOWS = [
|
||||
"browserFlow",
|
||||
"registrationFlow",
|
||||
"directGrantFlow",
|
||||
|
@ -54,27 +56,29 @@ export default function AuthenticationSection() {
|
|||
const adminClient = useAdminClient();
|
||||
const { realm } = useRealm();
|
||||
const [key, setKey] = useState(0);
|
||||
const refresh = () => setKey(new Date().getTime());
|
||||
const refresh = () => setKey(key + 1);
|
||||
const { addAlert, addError } = useAlerts();
|
||||
|
||||
const [selectedFlow, setSelectedFlow] = useState<AuthenticationType>();
|
||||
const [open, toggleOpen, setOpen] = useToggle();
|
||||
const [open, toggleOpen] = useToggle();
|
||||
const [bindFlowOpen, toggleBindFlow] = useToggle();
|
||||
|
||||
const loader = async () => {
|
||||
const clients = await adminClient.clients.find();
|
||||
const idps = await adminClient.identityProviders.find();
|
||||
const realmRep = await adminClient.realms.findOne({ realm });
|
||||
const [clients, idps, realmRep, flows] = await Promise.all([
|
||||
adminClient.clients.find(),
|
||||
adminClient.identityProviders.find(),
|
||||
adminClient.realms.findOne({ realm }),
|
||||
adminClient.authenticationManagement.getFlows(),
|
||||
]);
|
||||
if (!realmRep) {
|
||||
throw new Error(t("common:notFound"));
|
||||
}
|
||||
|
||||
const defaultFlows = Object.entries(realmRep)
|
||||
.filter((entry) => realmFlows.includes(entry[0]))
|
||||
.filter((entry) => REALM_FLOWS.includes(entry[0]))
|
||||
.map((entry) => entry[1]);
|
||||
|
||||
const flows =
|
||||
(await adminClient.authenticationManagement.getFlows()) as AuthenticationType[];
|
||||
for (const flow of flows) {
|
||||
for (const flow of flows as AuthenticationType[]) {
|
||||
flow.usedBy = { values: [] };
|
||||
const client = clients.find(
|
||||
(client) =>
|
||||
|
@ -104,8 +108,9 @@ export default function AuthenticationSection() {
|
|||
}
|
||||
}
|
||||
|
||||
return flows;
|
||||
return sortBy(flows as AuthenticationType[], (flow) => flow.usedBy.type);
|
||||
};
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: "authentication:deleteConfirmFlow",
|
||||
children: (
|
||||
|
@ -150,29 +155,25 @@ export default function AuthenticationSection() {
|
|||
</div>
|
||||
}
|
||||
>
|
||||
<Button variant={ButtonVariant.link} key={`button-${id}`}>
|
||||
<>
|
||||
<CheckCircleIcon
|
||||
className="keycloak_authentication-section__usedby"
|
||||
key={`icon-${id}`}
|
||||
/>{" "}
|
||||
{t(type)}
|
||||
</Button>
|
||||
</>
|
||||
</Popover>
|
||||
)}
|
||||
{type === "default" && (
|
||||
<Button key={id} variant={ButtonVariant.link} isDisabled>
|
||||
<>
|
||||
<CheckCircleIcon
|
||||
className="keycloak_authentication-section__usedby"
|
||||
key={`icon-${id}`}
|
||||
/>{" "}
|
||||
{t("default")}
|
||||
</Button>
|
||||
)}
|
||||
{!type && (
|
||||
<Button key={id} variant={ButtonVariant.link} isDisabled>
|
||||
{t("notInUse")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{!type && t("notInUse")}
|
||||
</>
|
||||
);
|
||||
|
||||
|
@ -208,10 +209,19 @@ export default function AuthenticationSection() {
|
|||
toggleDialog={toggleOpen}
|
||||
onComplete={() => {
|
||||
refresh();
|
||||
setOpen(false);
|
||||
toggleOpen();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{bindFlowOpen && (
|
||||
<BindFlowDialog
|
||||
onClose={() => {
|
||||
toggleBindFlow();
|
||||
refresh();
|
||||
}}
|
||||
flowAlias={selectedFlow?.alias!}
|
||||
/>
|
||||
)}
|
||||
<ViewHeader
|
||||
titleKey="authentication:title"
|
||||
subKey="authentication:authenticationExplain"
|
||||
|
@ -245,10 +255,21 @@ export default function AuthenticationSection() {
|
|||
{
|
||||
title: t("duplicate"),
|
||||
onClick: () => {
|
||||
setOpen(true);
|
||||
toggleOpen();
|
||||
setSelectedFlow(data);
|
||||
},
|
||||
},
|
||||
...(data.providerId !== "client-flow"
|
||||
? [
|
||||
{
|
||||
title: t("bindFlow"),
|
||||
onClick: () => {
|
||||
toggleBindFlow();
|
||||
setSelectedFlow(data);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
// remove delete when it's in use or default flow
|
||||
if (data.builtIn || data.usedBy.values.length > 0) {
|
||||
|
|
122
src/authentication/BindFlowDialog.tsx
Normal file
122
src/authentication/BindFlowDialog.tsx
Normal file
|
@ -0,0 +1,122 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import {
|
||||
Modal,
|
||||
Button,
|
||||
ButtonVariant,
|
||||
Form,
|
||||
FormGroup,
|
||||
Select,
|
||||
SelectVariant,
|
||||
SelectOption,
|
||||
AlertVariant,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
import useToggle from "../utils/useToggle";
|
||||
import { REALM_FLOWS } from "./AuthenticationSection";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
|
||||
type BindingForm = {
|
||||
bindingType: keyof RealmRepresentation;
|
||||
};
|
||||
|
||||
type BindFlowDialogProps = {
|
||||
flowAlias: string;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const BindFlowDialog = ({ flowAlias, onClose }: BindFlowDialogProps) => {
|
||||
const { t } = useTranslation("authentication");
|
||||
const { control, handleSubmit } = useForm<BindingForm>();
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const { realm } = useRealm();
|
||||
const [open, toggle] = useToggle();
|
||||
|
||||
const save = async ({ bindingType }: BindingForm) => {
|
||||
const realmRep = await adminClient.realms.findOne({ realm });
|
||||
|
||||
try {
|
||||
await adminClient.realms.update(
|
||||
{ realm },
|
||||
{ ...realmRep, [bindingType]: flowAlias }
|
||||
);
|
||||
addAlert(t("updateFlowSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addError("authentication:updateFlowError", error);
|
||||
}
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t("bindFlow")}
|
||||
isOpen
|
||||
variant="small"
|
||||
onClose={onClose}
|
||||
actions={[
|
||||
<Button
|
||||
id="modal-confirm"
|
||||
key="confirm"
|
||||
data-testid="save"
|
||||
type="submit"
|
||||
form="bind-form"
|
||||
>
|
||||
{t("common:save")}
|
||||
</Button>,
|
||||
<Button
|
||||
data-testid="cancel"
|
||||
id="modal-cancel"
|
||||
key="cancel"
|
||||
variant={ButtonVariant.link}
|
||||
onClick={onClose}
|
||||
>
|
||||
{t("common:cancel")}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Form id="bind-form" isHorizontal onSubmit={handleSubmit(save)}>
|
||||
<FormGroup label={t("chooseBindingType")} fieldId="chooseBindingType">
|
||||
<Controller
|
||||
name="bindingType"
|
||||
defaultValue={REALM_FLOWS[0]}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="chooseBindingType"
|
||||
onToggle={toggle}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value.toString());
|
||||
toggle();
|
||||
}}
|
||||
selections={t(`flow.${value}`)}
|
||||
variant={SelectVariant.single}
|
||||
aria-label={t("bindingFlow")}
|
||||
isOpen={open}
|
||||
menuAppendTo="parent"
|
||||
>
|
||||
{REALM_FLOWS.filter(
|
||||
(f) => f !== "dockerAuthenticationFlow"
|
||||
).map((flow) => (
|
||||
<SelectOption
|
||||
selected={flow === value}
|
||||
key={flow}
|
||||
value={flow}
|
||||
>
|
||||
{t(`flow.${flow}`)}
|
||||
</SelectOption>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
|
@ -20,7 +20,7 @@ import { CheckCircleIcon, PlusIcon, TableIcon } from "@patternfly/react-icons";
|
|||
import type AuthenticationExecutionInfoRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationExecutionInfoRepresentation";
|
||||
import type { AuthenticationProviderRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigRepresentation";
|
||||
import type AuthenticationFlowRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationFlowRepresentation";
|
||||
import { FlowParams, toFlow } from "./routes/Flow";
|
||||
import type { FlowParams } from "./routes/Flow";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||
import { EmptyExecutionState } from "./EmptyExecutionState";
|
||||
|
@ -43,6 +43,7 @@ import { useRealm } from "../context/realm-context/RealmContext";
|
|||
import useToggle from "../utils/useToggle";
|
||||
import { toAuthentication } from "./routes/Authentication";
|
||||
import { EditFlowModal } from "./EditFlowModal";
|
||||
import { BindFlowDialog } from "./BindFlowDialog";
|
||||
|
||||
export const providerConditionFilter = (
|
||||
value: AuthenticationProviderRepresentation
|
||||
|
@ -72,6 +73,7 @@ export default function FlowDetails() {
|
|||
useState<ExpandableExecution>();
|
||||
const [open, toggleOpen, setOpen] = useToggle();
|
||||
const [edit, setEdit] = useState(false);
|
||||
const [bindFlowOpen, toggleBindFlow] = useToggle();
|
||||
|
||||
useFetch(
|
||||
async () => {
|
||||
|
@ -178,20 +180,6 @@ export default function FlowDetails() {
|
|||
}
|
||||
};
|
||||
|
||||
const setAsDefault = async () => {
|
||||
try {
|
||||
const r = await adminClient.realms.findOne({ realm });
|
||||
await adminClient.realms.update(
|
||||
{ realm },
|
||||
{ ...r, browserFlow: flow?.alias }
|
||||
);
|
||||
addAlert(t("updateFlowSuccess"), AlertVariant.success);
|
||||
history.push(toFlow({ id, realm, usedBy: "default", builtIn }));
|
||||
} catch (error) {
|
||||
addError("authentication:updateFlowError", error);
|
||||
}
|
||||
};
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: "authentication:deleteConfirmExecution",
|
||||
children: (
|
||||
|
@ -241,14 +229,14 @@ export default function FlowDetails() {
|
|||
const hasExecutions = executionList?.expandableList.length !== 0;
|
||||
|
||||
const dropdownItems = [
|
||||
...(usedBy !== "default"
|
||||
...(usedBy !== "default" && flow?.providerId !== "client-flow"
|
||||
? [
|
||||
<DropdownItem
|
||||
data-testid="set-as-default"
|
||||
key="default"
|
||||
onClick={() => setAsDefault()}
|
||||
onClick={toggleBindFlow}
|
||||
>
|
||||
{t("setAsDefault")}
|
||||
{t("bindFlow")}
|
||||
</DropdownItem>,
|
||||
]
|
||||
: []),
|
||||
|
@ -277,6 +265,15 @@ export default function FlowDetails() {
|
|||
|
||||
return (
|
||||
<>
|
||||
{bindFlowOpen && (
|
||||
<BindFlowDialog
|
||||
flowAlias={flow?.alias!}
|
||||
onClose={() => {
|
||||
toggleBindFlow();
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{open && (
|
||||
<DuplicateFlowModal
|
||||
name={flow?.alias!}
|
||||
|
|
|
@ -82,7 +82,15 @@ export default {
|
|||
default: "Default",
|
||||
notInUse: "Not in use",
|
||||
duplicate: "Duplicate",
|
||||
setAsDefault: "Set as default",
|
||||
bindFlow: "Bind flow",
|
||||
chooseBindingType: "Choose binding type",
|
||||
flow: {
|
||||
browserFlow: "Browser flow",
|
||||
registrationFlow: "Registration flow",
|
||||
directGrantFlow: "Direct grant flow",
|
||||
resetCredentialsFlow: "Reset credentials flow",
|
||||
clientAuthenticationFlow: "Client authentication flow",
|
||||
},
|
||||
editInfo: "Edit info",
|
||||
editFlow: "Edit flow",
|
||||
edit: "Edit",
|
||||
|
|
Loading…
Reference in a new issue