added missing action menu on flow details (#1311)
This commit is contained in:
parent
7ba966b517
commit
0b4cb21134
7 changed files with 217 additions and 13 deletions
|
@ -5,7 +5,7 @@ export default class DuplicateFlowModal {
|
||||||
|
|
||||||
fill(name?: string, description?: string) {
|
fill(name?: string, description?: string) {
|
||||||
if (name) {
|
if (name) {
|
||||||
cy.findByTestId(this.aliasInput).type(name);
|
cy.findByTestId(this.aliasInput).clear().type(name);
|
||||||
if (description) cy.get(this.descriptionInput).type(description);
|
if (description) cy.get(this.descriptionInput).type(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -219,7 +219,7 @@ export const AuthenticationSection = () => {
|
||||||
key={key}
|
key={key}
|
||||||
loader={loader}
|
loader={loader}
|
||||||
ariaLabelKey="authentication:title"
|
ariaLabelKey="authentication:title"
|
||||||
searchPlaceholderKey="authentication:searchForEvent"
|
searchPlaceholderKey="authentication:searchForFlow"
|
||||||
toolbarItem={
|
toolbarItem={
|
||||||
<ToolbarItem>
|
<ToolbarItem>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -37,7 +37,7 @@ export const DuplicateFlowModal = ({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue("description", description);
|
setValue("description", description);
|
||||||
setValue("name", t("copyOf", { name }));
|
setValue("alias", t("copyOf", { name }));
|
||||||
}, [name, description, setValue]);
|
}, [name, description, setValue]);
|
||||||
|
|
||||||
const save = async () => {
|
const save = async () => {
|
||||||
|
|
89
src/authentication/EditFlowModal.tsx
Normal file
89
src/authentication/EditFlowModal.tsx
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
AlertVariant,
|
||||||
|
Button,
|
||||||
|
ButtonVariant,
|
||||||
|
Form,
|
||||||
|
Modal,
|
||||||
|
ModalVariant,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import type AuthenticationFlowRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationFlowRepresentation";
|
||||||
|
import { useAdminClient } from "../context/auth/AdminClient";
|
||||||
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
|
import { NameDescription } from "./form/NameDescription";
|
||||||
|
|
||||||
|
type EditFlowModalProps = {
|
||||||
|
flow: AuthenticationFlowRepresentation;
|
||||||
|
toggleDialog: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type EditForm = {
|
||||||
|
alias: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditFlowModal = ({ flow, toggleDialog }: EditFlowModalProps) => {
|
||||||
|
const { t } = useTranslation("authentication");
|
||||||
|
const form = useForm<EditForm>({
|
||||||
|
shouldUnregister: false,
|
||||||
|
});
|
||||||
|
const { reset, handleSubmit } = form;
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const { addAlert, addError } = useAlerts();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset(flow);
|
||||||
|
}, [flow, reset]);
|
||||||
|
|
||||||
|
const save = async (values: EditForm) => {
|
||||||
|
try {
|
||||||
|
await adminClient.authenticationManagement.updateFlow(
|
||||||
|
{ flowId: flow.id! },
|
||||||
|
{ ...flow, ...values }
|
||||||
|
);
|
||||||
|
addAlert(t("updateFlowSuccess"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addError("authentication:updateFlowError", error);
|
||||||
|
}
|
||||||
|
toggleDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={t("editFlow")}
|
||||||
|
isOpen={true}
|
||||||
|
onClose={toggleDialog}
|
||||||
|
variant={ModalVariant.small}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
id="modal-confirm"
|
||||||
|
key="confirm"
|
||||||
|
onClick={handleSubmit(save)}
|
||||||
|
data-testid="confirm"
|
||||||
|
>
|
||||||
|
{t("edit")}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
data-testid="cancel"
|
||||||
|
id="modal-cancel"
|
||||||
|
key="cancel"
|
||||||
|
variant={ButtonVariant.link}
|
||||||
|
onClick={() => {
|
||||||
|
toggleDialog();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("common:cancel")}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<Form isHorizontal>
|
||||||
|
<NameDescription />
|
||||||
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useHistory, useParams } from "react-router-dom";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
DataList,
|
DataList,
|
||||||
|
@ -13,13 +13,14 @@ import {
|
||||||
ActionGroup,
|
ActionGroup,
|
||||||
Button,
|
Button,
|
||||||
ButtonVariant,
|
ButtonVariant,
|
||||||
|
DropdownItem,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { CheckCircleIcon, PlusIcon, TableIcon } from "@patternfly/react-icons";
|
import { CheckCircleIcon, PlusIcon, TableIcon } from "@patternfly/react-icons";
|
||||||
|
|
||||||
import type AuthenticationExecutionInfoRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationExecutionInfoRepresentation";
|
import type AuthenticationExecutionInfoRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationExecutionInfoRepresentation";
|
||||||
import type { AuthenticationProviderRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigRepresentation";
|
import type { AuthenticationProviderRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigRepresentation";
|
||||||
import type AuthenticationFlowRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationFlowRepresentation";
|
import type AuthenticationFlowRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationFlowRepresentation";
|
||||||
import type { FlowParams } from "./routes/Flow";
|
import { FlowParams, toFlow } from "./routes/Flow";
|
||||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||||
import { EmptyExecutionState } from "./EmptyExecutionState";
|
import { EmptyExecutionState } from "./EmptyExecutionState";
|
||||||
|
@ -37,6 +38,10 @@ import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { AddStepModal } from "./components/modals/AddStepModal";
|
import { AddStepModal } from "./components/modals/AddStepModal";
|
||||||
import { AddSubFlowModal, Flow } from "./components/modals/AddSubFlowModal";
|
import { AddSubFlowModal, Flow } from "./components/modals/AddSubFlowModal";
|
||||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
|
import { DuplicateFlowModal } from "./DuplicateFlowModal";
|
||||||
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
|
import { toAuthentication } from "./routes/Authentication";
|
||||||
|
import { EditFlowModal } from "./EditFlowModal";
|
||||||
|
|
||||||
export const providerConditionFilter = (
|
export const providerConditionFilter = (
|
||||||
value: AuthenticationProviderRepresentation
|
value: AuthenticationProviderRepresentation
|
||||||
|
@ -45,8 +50,10 @@ export const providerConditionFilter = (
|
||||||
export const FlowDetails = () => {
|
export const FlowDetails = () => {
|
||||||
const { t } = useTranslation("authentication");
|
const { t } = useTranslation("authentication");
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
|
const { realm } = useRealm();
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
const { id, usedBy, builtIn } = useParams<FlowParams>();
|
const { id, usedBy, builtIn } = useParams<FlowParams>();
|
||||||
|
const history = useHistory();
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
const refresh = () => setKey(new Date().getTime());
|
const refresh = () => setKey(new Date().getTime());
|
||||||
|
|
||||||
|
@ -62,6 +69,8 @@ export const FlowDetails = () => {
|
||||||
const [showAddSubFlowDialog, setShowSubFlowDialog] = useState<boolean>();
|
const [showAddSubFlowDialog, setShowSubFlowDialog] = useState<boolean>();
|
||||||
const [selectedExecution, setSelectedExecution] =
|
const [selectedExecution, setSelectedExecution] =
|
||||||
useState<ExpandableExecution>();
|
useState<ExpandableExecution>();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [edit, setEdit] = useState(false);
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
async () => {
|
async () => {
|
||||||
|
@ -168,6 +177,20 @@ export const 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({
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||||
titleKey: "authentication:deleteConfirmExecution",
|
titleKey: "authentication:deleteConfirmExecution",
|
||||||
children: (
|
children: (
|
||||||
|
@ -191,10 +214,90 @@ export const FlowDetails = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [toggleDeleteFlow, DeleteFlowConfirm] = useConfirmDialog({
|
||||||
|
titleKey: "authentication:deleteConfirmFlow",
|
||||||
|
children: (
|
||||||
|
<Trans i18nKey="authentication:deleteConfirmFlowMessage">
|
||||||
|
{" "}
|
||||||
|
<strong>{{ flow: flow?.alias || "" }}</strong>.
|
||||||
|
</Trans>
|
||||||
|
),
|
||||||
|
continueButtonLabel: "common:delete",
|
||||||
|
continueButtonVariant: ButtonVariant.danger,
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
await adminClient.authenticationManagement.deleteFlow({
|
||||||
|
flowId: flow!.id!,
|
||||||
|
});
|
||||||
|
history.push(toAuthentication({ realm }));
|
||||||
|
addAlert(t("deleteFlowSuccess"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addError("authentication:deleteFlowError", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const hasExecutions = executionList?.expandableList.length !== 0;
|
const hasExecutions = executionList?.expandableList.length !== 0;
|
||||||
|
|
||||||
|
const dropdownItems = [
|
||||||
|
...(usedBy !== "default"
|
||||||
|
? [
|
||||||
|
<DropdownItem
|
||||||
|
data-testid="set-as-default"
|
||||||
|
key="default"
|
||||||
|
onClick={() => setAsDefault()}
|
||||||
|
>
|
||||||
|
{t("setAsDefault")}
|
||||||
|
</DropdownItem>,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
<DropdownItem key="duplicate" onClick={() => setOpen(true)}>
|
||||||
|
{t("duplicate")}
|
||||||
|
</DropdownItem>,
|
||||||
|
...(!builtIn
|
||||||
|
? [
|
||||||
|
<DropdownItem
|
||||||
|
data-testid="edit-flow"
|
||||||
|
key="edit"
|
||||||
|
onClick={() => setEdit(true)}
|
||||||
|
>
|
||||||
|
{t("editInfo")}
|
||||||
|
</DropdownItem>,
|
||||||
|
<DropdownItem
|
||||||
|
data-testid="delete-flow"
|
||||||
|
key="delete"
|
||||||
|
onClick={() => toggleDeleteFlow()}
|
||||||
|
>
|
||||||
|
{t("common:delete")}
|
||||||
|
</DropdownItem>,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{open && (
|
||||||
|
<DuplicateFlowModal
|
||||||
|
name={flow?.alias!}
|
||||||
|
description={flow?.description!}
|
||||||
|
toggleDialog={() => setOpen(!open)}
|
||||||
|
onComplete={() => {
|
||||||
|
refresh();
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{edit && (
|
||||||
|
<EditFlowModal
|
||||||
|
flow={flow!}
|
||||||
|
toggleDialog={() => {
|
||||||
|
setEdit(!edit);
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<DeleteFlowConfirm />
|
||||||
|
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
titleKey={toUpperCase(flow?.alias || "")}
|
titleKey={toUpperCase(flow?.alias || "")}
|
||||||
badges={[
|
badges={[
|
||||||
|
@ -213,6 +316,7 @@ export const FlowDetails = () => {
|
||||||
}
|
}
|
||||||
: {},
|
: {},
|
||||||
]}
|
]}
|
||||||
|
dropdownItems={dropdownItems}
|
||||||
/>
|
/>
|
||||||
<PageSection variant="light">
|
<PageSection variant="light">
|
||||||
{hasExecutions && (
|
{hasExecutions && (
|
||||||
|
@ -279,6 +383,7 @@ export const FlowDetails = () => {
|
||||||
<>
|
<>
|
||||||
{executionList.expandableList.map((execution) => (
|
{executionList.expandableList.map((execution) => (
|
||||||
<FlowRow
|
<FlowRow
|
||||||
|
builtIn={!!builtIn}
|
||||||
key={execution.id}
|
key={execution.id}
|
||||||
execution={execution}
|
execution={execution}
|
||||||
onRowClick={(execution) => {
|
onRowClick={(execution) => {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { EditFlowDropdown } from "./EditFlowDropdown";
|
||||||
import "./flow-row.css";
|
import "./flow-row.css";
|
||||||
|
|
||||||
type FlowRowProps = {
|
type FlowRowProps = {
|
||||||
|
builtIn: boolean;
|
||||||
execution: ExpandableExecution;
|
execution: ExpandableExecution;
|
||||||
onRowClick: (execution: ExpandableExecution) => void;
|
onRowClick: (execution: ExpandableExecution) => void;
|
||||||
onRowChange: (execution: AuthenticationExecutionInfoRepresentation) => void;
|
onRowChange: (execution: AuthenticationExecutionInfoRepresentation) => void;
|
||||||
|
@ -38,6 +39,7 @@ type FlowRowProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FlowRow = ({
|
export const FlowRow = ({
|
||||||
|
builtIn,
|
||||||
execution,
|
execution,
|
||||||
onRowClick,
|
onRowClick,
|
||||||
onRowChange,
|
onRowChange,
|
||||||
|
@ -101,20 +103,22 @@ export const FlowRow = ({
|
||||||
{execution.configurable && (
|
{execution.configurable && (
|
||||||
<ExecutionConfigModal execution={execution} />
|
<ExecutionConfigModal execution={execution} />
|
||||||
)}
|
)}
|
||||||
{execution.authenticationFlow && (
|
{execution.authenticationFlow && !builtIn && (
|
||||||
<EditFlowDropdown
|
<EditFlowDropdown
|
||||||
execution={execution}
|
execution={execution}
|
||||||
onAddExecution={onAddExecution}
|
onAddExecution={onAddExecution}
|
||||||
onAddFlow={onAddFlow}
|
onAddFlow={onAddFlow}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Button
|
{!builtIn && (
|
||||||
variant="plain"
|
<Button
|
||||||
aria-label={t("common:delete")}
|
variant="plain"
|
||||||
onClick={onDelete}
|
aria-label={t("common:delete")}
|
||||||
>
|
onClick={onDelete}
|
||||||
<TrashIcon />
|
>
|
||||||
</Button>
|
<TrashIcon />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</DataListCell>,
|
</DataListCell>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
@ -124,6 +128,7 @@ export const FlowRow = ({
|
||||||
hasSubList &&
|
hasSubList &&
|
||||||
execution.executionList?.map((execution) => (
|
execution.executionList?.map((execution) => (
|
||||||
<FlowRow
|
<FlowRow
|
||||||
|
builtIn={builtIn}
|
||||||
key={execution.id}
|
key={execution.id}
|
||||||
execution={execution}
|
execution={execution}
|
||||||
onRowClick={onRowClick}
|
onRowClick={onRowClick}
|
||||||
|
|
|
@ -3,6 +3,7 @@ export default {
|
||||||
title: "Authentication",
|
title: "Authentication",
|
||||||
flows: "Flows",
|
flows: "Flows",
|
||||||
flowName: "Flow name",
|
flowName: "Flow name",
|
||||||
|
searchForFlow: "Search for flow",
|
||||||
usedBy: "Used by",
|
usedBy: "Used by",
|
||||||
buildIn: "Built-in",
|
buildIn: "Built-in",
|
||||||
appliedByProviders: "Applied by the following providers",
|
appliedByProviders: "Applied by the following providers",
|
||||||
|
@ -12,6 +13,10 @@ export default {
|
||||||
default: "Default",
|
default: "Default",
|
||||||
notInUse: "Not in use",
|
notInUse: "Not in use",
|
||||||
duplicate: "Duplicate",
|
duplicate: "Duplicate",
|
||||||
|
setAsDefault: "Set as default",
|
||||||
|
editInfo: "Edit info",
|
||||||
|
editFlow: "Edit flow",
|
||||||
|
edit: "Edit",
|
||||||
deleteConfirmFlow: "Delete flow?",
|
deleteConfirmFlow: "Delete flow?",
|
||||||
deleteConfirmFlowMessage:
|
deleteConfirmFlowMessage:
|
||||||
'Are you sure you want to permanently delete the flow "<1>{{flow}}</1>".',
|
'Are you sure you want to permanently delete the flow "<1>{{flow}}</1>".',
|
||||||
|
|
Loading…
Reference in a new issue