From f1f0c362b4776602125604fbd60b4e4080dfce93 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Mon, 6 Sep 2021 14:43:36 +0200 Subject: [PATCH] Add button on authentication flows (#1119) --- .../integration/authentication_test.spec.ts | 22 +++ .../manage/authentication/FlowDetail.ts | 24 +++ package-lock.json | 93 +++++++++--- package.json | 2 +- src/authentication/FlowDetails.tsx | 45 ++++-- .../components/EditFlowDropdown.tsx | 91 +++++++++++ src/authentication/components/FlowDiagram.tsx | 3 +- src/authentication/components/FlowRow.tsx | 17 ++- .../components/modals/AddStepModal.tsx | 142 ++++++++++++++++++ .../components/modals/AddSubFlowModal.tsx | 113 ++++++++++++++ src/authentication/messages.ts | 3 + src/clients/credentials/Credentials.tsx | 16 +- 12 files changed, 518 insertions(+), 53 deletions(-) create mode 100644 src/authentication/components/EditFlowDropdown.tsx create mode 100644 src/authentication/components/modals/AddStepModal.tsx create mode 100644 src/authentication/components/modals/AddSubFlowModal.tsx diff --git a/cypress/integration/authentication_test.spec.ts b/cypress/integration/authentication_test.spec.ts index 8ec680ae6d..de0bcab44e 100644 --- a/cypress/integration/authentication_test.spec.ts +++ b/cypress/integration/authentication_test.spec.ts @@ -56,4 +56,26 @@ describe("Authentication test", () => { cy.get(".react-flow").should("exist"); }); + + it("should add a execution", () => { + listingPage.goToItemDetails("Copy of browser"); + detailPage.addExecution( + "Copy of browser forms", + "console-username-password" + ); + + masthead.checkNotificationMessage("Flow successfully updated"); + detailPage.executionExists("Username Password Challenge"); + }); + + it("should add a condition", () => { + listingPage.goToItemDetails("Copy of browser"); + detailPage.addCondition( + "Copy of browser Browser - Conditional OTP", + "conditional-user-role" + ); + + masthead.checkNotificationMessage("Flow successfully updated"); + detailPage.executionExists("Username Password Challenge"); + }); }); diff --git a/cypress/support/pages/admin_console/manage/authentication/FlowDetail.ts b/cypress/support/pages/admin_console/manage/authentication/FlowDetail.ts index 36679d013b..60cb4ff9e8 100644 --- a/cypress/support/pages/admin_console/manage/authentication/FlowDetail.ts +++ b/cypress/support/pages/admin_console/manage/authentication/FlowDetail.ts @@ -36,4 +36,28 @@ export default class FlowDetails { cy.get("#diagramView").click(); return this; } + + private clickEditDropdownForFlow(subFlowName: string, option: string) { + cy.getId(`${subFlowName}-edit-dropdown`).click().contains(option).click(); + } + + addExecution(subFlowName: string, executionTestId: string) { + this.clickEditDropdownForFlow(subFlowName, "Add step"); + + cy.get(".pf-c-pagination").should("exist"); + cy.getId(executionTestId).click(); + cy.getId("modal-add").click(); + + return this; + } + + addCondition(subFlowName: string, executionTestId: string) { + this.clickEditDropdownForFlow(subFlowName, "Add condition"); + + cy.get(".pf-c-pagination").should("not.exist"); + cy.getId(executionTestId).click(); + cy.getId("modal-add").click(); + + return this; + } } diff --git a/package-lock.json b/package-lock.json index 5d943753e8..1a914bed71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "version": "0.0.1", "license": "Apache", "dependencies": { - "@keycloak/keycloak-admin-client": "^16.0.0-dev.2", + "@keycloak/keycloak-admin-client": "^16.0.0-dev.4", "@patternfly/patternfly": "^4.132.2", "@patternfly/react-core": "4.152.4", "@patternfly/react-icons": "4.11.14", @@ -2541,9 +2541,9 @@ } }, "node_modules/@keycloak/keycloak-admin-client": { - "version": "16.0.0-dev.2", - "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-16.0.0-dev.2.tgz", - "integrity": "sha512-eLeN4/O5OWjU0fIIvndv0oLSRQN3q0bdMep19E+09/qL4jeNeBR4ltxLKd0mnW1FY53cZJ+QWLaq/lfgRXFfzA==", + "version": "16.0.0-dev.4", + "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-16.0.0-dev.4.tgz", + "integrity": "sha512-8Wp/hqi6TWnt+woxoRwhklPP1SBw+EGzMGQQdErHrkTvfXCd0JFeECRzIZyGcmeFLbvpyLMwLPYFYknC20CENQ==", "dependencies": { "axios": "^0.21.0", "camelize": "^1.0.0", @@ -2587,9 +2587,9 @@ } }, "node_modules/@npmcli/arborist": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.7.1.tgz", - "integrity": "sha512-EGDHJs6dna/52BrStr/6aaRcMLrYxGbSjT4V3JzvoTBY9/w5i2+1KNepmsG80CAsGADdo6nuNnFwb7sDRm8ZAw==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.8.2.tgz", + "integrity": "sha512-6E1XJ0YXBaI9J+25gcTF110MGNx3jv6npr4Rz1U0UAqkuVV7bbDznVJvNqi6F0p8vgrE+Smf9jDTn1DR+7uBjQ==", "dev": true, "dependencies": { "@npmcli/installed-package-contents": "^1.0.7", @@ -2608,10 +2608,10 @@ "mkdirp": "^1.0.4", "mkdirp-infer-owner": "^2.0.0", "npm-install-checks": "^4.0.0", - "npm-package-arg": "^8.1.0", + "npm-package-arg": "^8.1.5", "npm-pick-manifest": "^6.1.0", "npm-registry-fetch": "^11.0.0", - "pacote": "^11.2.6", + "pacote": "^11.3.5", "parse-conflict-json": "^1.1.1", "proc-log": "^1.0.0", "promise-all-reject-late": "^1.0.0", @@ -2621,7 +2621,6 @@ "rimraf": "^3.0.2", "semver": "^7.3.5", "ssri": "^8.0.1", - "tar": "^6.1.0", "treeverse": "^1.0.4", "walk-up-path": "^1.0.0" }, @@ -8277,6 +8276,20 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", @@ -14975,6 +14988,21 @@ "@rollup/plugin-inject": "^4.0.0" } }, + "node_modules/rollup/node_modules/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/rsvp": { "version": "4.8.5", "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", @@ -16504,9 +16532,9 @@ } }, "node_modules/tar": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.8.tgz", - "integrity": "sha512-sb9b0cp855NbkMJcskdSYA7b11Q8JsX4qe4pyUAfHp+Y6jBjJeek2ZVlwEfWayshEIwlIzXx0Fain3QG9JPm2A==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dev": true, "dependencies": { "chownr": "^2.0.0", @@ -19360,9 +19388,9 @@ } }, "@keycloak/keycloak-admin-client": { - "version": "16.0.0-dev.2", - "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-16.0.0-dev.2.tgz", - "integrity": "sha512-eLeN4/O5OWjU0fIIvndv0oLSRQN3q0bdMep19E+09/qL4jeNeBR4ltxLKd0mnW1FY53cZJ+QWLaq/lfgRXFfzA==", + "version": "16.0.0-dev.4", + "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-16.0.0-dev.4.tgz", + "integrity": "sha512-8Wp/hqi6TWnt+woxoRwhklPP1SBw+EGzMGQQdErHrkTvfXCd0JFeECRzIZyGcmeFLbvpyLMwLPYFYknC20CENQ==", "requires": { "axios": "^0.21.0", "camelize": "^1.0.0", @@ -19397,9 +19425,9 @@ } }, "@npmcli/arborist": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.7.1.tgz", - "integrity": "sha512-EGDHJs6dna/52BrStr/6aaRcMLrYxGbSjT4V3JzvoTBY9/w5i2+1KNepmsG80CAsGADdo6nuNnFwb7sDRm8ZAw==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.8.2.tgz", + "integrity": "sha512-6E1XJ0YXBaI9J+25gcTF110MGNx3jv6npr4Rz1U0UAqkuVV7bbDznVJvNqi6F0p8vgrE+Smf9jDTn1DR+7uBjQ==", "dev": true, "requires": { "@npmcli/installed-package-contents": "^1.0.7", @@ -19418,10 +19446,10 @@ "mkdirp": "^1.0.4", "mkdirp-infer-owner": "^2.0.0", "npm-install-checks": "^4.0.0", - "npm-package-arg": "^8.1.0", + "npm-package-arg": "^8.1.5", "npm-pick-manifest": "^6.1.0", "npm-registry-fetch": "^11.0.0", - "pacote": "^11.2.6", + "pacote": "^11.3.5", "parse-conflict-json": "^1.1.1", "proc-log": "^1.0.0", "promise-all-reject-late": "^1.0.0", @@ -19431,7 +19459,6 @@ "rimraf": "^3.0.2", "semver": "^7.3.5", "ssri": "^8.0.1", - "tar": "^6.1.0", "treeverse": "^1.0.4", "walk-up-path": "^1.0.0" }, @@ -23837,6 +23864,13 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", @@ -28791,6 +28825,15 @@ "dev": true, "requires": { "fsevents": "~2.1.2" + }, + "dependencies": { + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + } } }, "rollup-plugin-polyfill-node": { @@ -30003,9 +30046,9 @@ } }, "tar": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.8.tgz", - "integrity": "sha512-sb9b0cp855NbkMJcskdSYA7b11Q8JsX4qe4pyUAfHp+Y6jBjJeek2ZVlwEfWayshEIwlIzXx0Fain3QG9JPm2A==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dev": true, "requires": { "chownr": "^2.0.0", diff --git a/package.json b/package.json index eb1e33f2dc..53036d4413 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "prepare": "husky install" }, "dependencies": { - "@keycloak/keycloak-admin-client": "^16.0.0-dev.2", + "@keycloak/keycloak-admin-client": "^16.0.0-dev.4", "@patternfly/patternfly": "^4.132.2", "@patternfly/react-core": "4.152.4", "@patternfly/react-icons": "4.11.14", diff --git a/src/authentication/FlowDetails.tsx b/src/authentication/FlowDetails.tsx index 20de84a0af..01b3a9ad71 100644 --- a/src/authentication/FlowDetails.tsx +++ b/src/authentication/FlowDetails.tsx @@ -14,6 +14,7 @@ import { import { CheckCircleIcon, 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 type { FlowParams } from "./routes/Flow"; import { ViewHeader } from "../components/view-header/ViewHeader"; @@ -22,14 +23,23 @@ import { EmptyExecutionState } from "./EmptyExecutionState"; import { toUpperCase } from "../util"; import { FlowHeader } from "./components/FlowHeader"; import { FlowRow } from "./components/FlowRow"; -import { ExecutionList, IndexChange, LevelChange } from "./execution-model"; +import { + ExecutionList, + ExpandableExecution, + IndexChange, + LevelChange, +} from "./execution-model"; import { FlowDiagram } from "./components/FlowDiagram"; import { useAlerts } from "../components/alert/Alerts"; +export const providerConditionFilter = ( + value: AuthenticationProviderRepresentation +) => value.displayName?.startsWith("Condition "); + export const FlowDetails = () => { const { t } = useTranslation("authentication"); const adminClient = useAdminClient(); - const { addAlert } = useAlerts(); + const { addAlert, addError } = useAlerts(); const { id, usedBy, builtIn } = useParams(); const [key, setKey] = useState(0); const refresh = () => setKey(new Date().getTime()); @@ -94,12 +104,7 @@ export const FlowDetails = () => { refresh(); addAlert(t("updateFlowSuccess"), AlertVariant.success); } catch (error: any) { - addAlert( - t("updateFlowError", { - error: error.response?.data?.errorMessage || error, - }), - AlertVariant.danger - ); + addError("authentication:updateFlowError", error); } }; @@ -114,12 +119,23 @@ export const FlowDetails = () => { refresh(); addAlert(t("updateFlowSuccess"), AlertVariant.success); } catch (error: any) { - addAlert( - t("updateFlowError", { - error: error.response?.data?.errorMessage || error, - }), - AlertVariant.danger - ); + addError("authentication:updateFlowError", error); + } + }; + + const addExecution = async ( + execution: ExpandableExecution, + type: AuthenticationProviderRepresentation + ) => { + try { + await adminClient.authenticationManagement.addExecutionToFlow({ + flow: execution.displayName!, + provider: type.id!, + }); + refresh(); + addAlert(t("updateFlowSuccess"), AlertVariant.success); + } catch (error) { + addError("authentication:updateFlowError", error); } }; @@ -215,6 +231,7 @@ export const FlowDetails = () => { setExecutionList(executionList.clone()); }} onRowChange={update} + onAddExecution={addExecution} /> ))} diff --git a/src/authentication/components/EditFlowDropdown.tsx b/src/authentication/components/EditFlowDropdown.tsx new file mode 100644 index 0000000000..aa1295153d --- /dev/null +++ b/src/authentication/components/EditFlowDropdown.tsx @@ -0,0 +1,91 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Dropdown, DropdownItem, DropdownToggle } from "@patternfly/react-core"; +import { PlusIcon } from "@patternfly/react-icons"; + +import type { AuthenticationProviderRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigRepresentation"; +import type { ExpandableExecution } from "../execution-model"; +import { AddStepModal, FlowType } from "./modals/AddStepModal"; +import { useAdminClient, useFetch } from "../../context/auth/AdminClient"; +import { AddSubFlowModal } from "./modals/AddSubFlowModal"; + +type EditFlowDropdownProps = { + execution: ExpandableExecution; + onAddExecution: ( + execution: ExpandableExecution, + type: AuthenticationProviderRepresentation + ) => void; +}; + +export const EditFlowDropdown = ({ + execution, + onAddExecution, +}: EditFlowDropdownProps) => { + const { t } = useTranslation("authentication"); + const adminClient = useAdminClient(); + + const [open, setOpen] = useState(false); + const [type, setType] = useState(); + const [providerId, setProviderId] = useState(); + + useFetch( + () => + adminClient.authenticationManagement.getFlow({ + flowId: execution.flowId!, + }), + ({ providerId }) => setProviderId(providerId), + [] + ); + + return ( + <> + setOpen(open)}> + + + } + dropdownItems={[ + + setType(providerId === "form-flow" ? "form" : "basic") + } + > + {t("addStep")} + , + setType("condition")}> + {t("addCondition")} + , + setType("subFlow")}> + {t("addSubFlow")} + , + ]} + onSelect={() => setOpen(false)} + /> + {type && type !== "subFlow" && ( + { + if (type) { + onAddExecution(execution, type); + } + setType(undefined); + }} + /> + )} + {type === "subFlow" && ( + setType(undefined)} + onConfirm={() => setType(undefined)} + /> + )} + + ); +}; diff --git a/src/authentication/components/FlowDiagram.tsx b/src/authentication/components/FlowDiagram.tsx index 706e42d235..853c146d71 100644 --- a/src/authentication/components/FlowDiagram.tsx +++ b/src/authentication/components/FlowDiagram.tsx @@ -26,6 +26,7 @@ import { EndSubFlowNode, StartSubFlowNode } from "./diagram/SubFlowNode"; import { ConditionalNode } from "./diagram/ConditionalNode"; import { ButtonEdge } from "./diagram/ButtonEdge"; import { getLayoutedElements } from "./diagram/auto-layout"; +import { providerConditionFilter } from "../FlowDetails"; import "./flow-diagram.css"; @@ -54,7 +55,7 @@ const createNode = (ex: ExpandableExecution) => { if (ex.executionList) { nodeType = "startSubFlow"; } - if (ex.displayName?.startsWith("Condition")) { + if (providerConditionFilter(ex)) { nodeType = "conditional"; } return { diff --git a/src/authentication/components/FlowRow.tsx b/src/authentication/components/FlowRow.tsx index 29998f4e0c..e1ce015628 100644 --- a/src/authentication/components/FlowRow.tsx +++ b/src/authentication/components/FlowRow.tsx @@ -13,26 +13,34 @@ import { } from "@patternfly/react-core"; import type AuthenticationExecutionInfoRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationExecutionInfoRepresentation"; +import type { AuthenticationProviderRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigRepresentation"; import type { ExpandableExecution } from "../execution-model"; import { FlowTitle } from "./FlowTitle"; import { FlowRequirementDropdown } from "./FlowRequirementDropdown"; +import { ExecutionConfigModal } from "./ExecutionConfigModal"; +import { EditFlowDropdown } from "./EditFlowDropdown"; import "./flow-row.css"; -import { ExecutionConfigModal } from "./ExecutionConfigModal"; type FlowRowProps = { execution: ExpandableExecution; onRowClick: (execution: ExpandableExecution) => void; onRowChange: (execution: AuthenticationExecutionInfoRepresentation) => void; + onAddExecution: ( + execution: ExpandableExecution, + type: AuthenticationProviderRepresentation + ) => void; }; export const FlowRow = ({ execution, onRowClick, onRowChange, + onAddExecution, }: FlowRowProps) => { const { t } = useTranslation("authentication"); const hasSubList = !!execution.executionList?.length; + return ( <> )} + {execution.authenticationFlow && ( + + )} , ]} /> @@ -99,6 +113,7 @@ export const FlowRow = ({ execution={execution} onRowClick={onRowClick} onRowChange={onRowChange} + onAddExecution={onAddExecution} /> ))} diff --git a/src/authentication/components/modals/AddStepModal.tsx b/src/authentication/components/modals/AddStepModal.tsx new file mode 100644 index 0000000000..91083832f9 --- /dev/null +++ b/src/authentication/components/modals/AddStepModal.tsx @@ -0,0 +1,142 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + Button, + ButtonVariant, + Form, + Modal, + ModalVariant, + PageSection, + Radio, +} from "@patternfly/react-core"; + +import type { AuthenticationProviderRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigRepresentation"; +import { PaginatingTableToolbar } from "../../../components/table-toolbar/PaginatingTableToolbar"; +import { useAdminClient, useFetch } from "../../../context/auth/AdminClient"; +import { providerConditionFilter } from "../../FlowDetails"; + +type AuthenticationProviderListProps = { + list?: AuthenticationProviderRepresentation[]; + setValue: (provider?: AuthenticationProviderRepresentation) => void; +}; + +const AuthenticationProviderList = ({ + list, + setValue, +}: AuthenticationProviderListProps) => { + return ( + +
+ {list?.map((provider) => ( + { + const { id } = event.currentTarget; + const value = list.find((p) => p.id === id); + setValue(value); + }} + /> + ))} + +
+ ); +}; + +export type FlowType = "client" | "form" | "basic" | "condition" | "subFlow"; + +type AddStepModalProps = { + name: string; + type: FlowType; + onSelect: (value?: AuthenticationProviderRepresentation) => void; +}; + +export const AddStepModal = ({ name, type, onSelect }: AddStepModalProps) => { + const { t } = useTranslation("authentication"); + const adminClient = useAdminClient(); + + const [value, setValue] = useState(); + const [providers, setProviders] = + useState(); + const [max, setMax] = useState(10); + const [first, setFirst] = useState(0); + + useFetch( + async () => { + switch (type) { + case "client": + return adminClient.authenticationManagement.getClientAuthenticatorProviders(); + case "form": + return adminClient.authenticationManagement.getFormActionProviders(); + case "condition": { + const providers = + await adminClient.authenticationManagement.getAuthenticatorProviders(); + return providers.filter(providerConditionFilter); + } + case "basic": + default: { + const providers = + await adminClient.authenticationManagement.getAuthenticatorProviders(); + return providers.filter((p) => !providerConditionFilter(p)); + } + } + }, + (providers) => setProviders(providers), + [] + ); + + const page = providers?.slice(first, first + max + 1); + + return ( + onSelect()} + actions={[ + , + , + ]} + > + {providers && providers.length > max && ( + { + setFirst(first); + setMax(max); + }} + > + + + )} + {providers && providers.length <= max && ( + + )} + + ); +}; diff --git a/src/authentication/components/modals/AddSubFlowModal.tsx b/src/authentication/components/modals/AddSubFlowModal.tsx new file mode 100644 index 0000000000..e8023ea7ff --- /dev/null +++ b/src/authentication/components/modals/AddSubFlowModal.tsx @@ -0,0 +1,113 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { useForm } from "react-hook-form"; +import { + Button, + ButtonVariant, + Form, + FormGroup, + Modal, + ModalVariant, + TextInput, + ValidatedOptions, +} from "@patternfly/react-core"; + +import { HelpItem } from "../../../components/help-enabler/HelpItem"; + +type AddSubFlowProps = { + name: string; + onConfirm: () => void; + onCancel: () => void; +}; + +export const AddSubFlowModal = ({ + name, + onConfirm, + onCancel, +}: AddSubFlowProps) => { + const { t } = useTranslation("authentication"); + const { register, errors, handleSubmit } = useForm(); + + return ( + onCancel()} + actions={[ + , + , + ]} + > +
+ + } + > + + + + } + > + + +
+
+ ); +}; diff --git a/src/authentication/messages.ts b/src/authentication/messages.ts index 560fdb429b..40a254224a 100644 --- a/src/authentication/messages.ts +++ b/src/authentication/messages.ts @@ -41,6 +41,9 @@ export default { addExecution: "Add execution", addSubFlowTitle: "Add a sub-flow", addSubFlow: "Add sub-flow", + addCondition: "Add condition", + addStep: "Add step", + addStepTo: "Add step to {{name}}", steps: "Steps", requirement: "Requirement", requirements: { diff --git a/src/clients/credentials/Credentials.tsx b/src/clients/credentials/Credentials.tsx index f0c48f7076..ef7732ad30 100644 --- a/src/clients/credentials/Credentials.tsx +++ b/src/clients/credentials/Credentials.tsx @@ -18,6 +18,7 @@ import { SplitItem, } from "@patternfly/react-core"; import type CredentialRepresentation from "@keycloak/keycloak-admin-client/lib/defs/credentialRepresentation"; +import type { AuthenticationProviderRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigRepresentation"; import { useAlerts } from "../../components/alert/Alerts"; import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; @@ -29,11 +30,6 @@ import { ClientSecret } from "./ClientSecret"; import { SignedJWT } from "./SignedJWT"; import { X509 } from "./X509"; -type ClientAuthenticatorProviders = { - id: string; - displayName: string; -}; - type AccessToken = { registrationAccessToken: string; }; @@ -48,9 +44,9 @@ export const Credentials = ({ clientId, save }: CredentialsProps) => { const adminClient = useAdminClient(); const { addAlert, addError } = useAlerts(); - const [providers, setProviders] = useState( - [] - ); + const [providers, setProviders] = useState< + AuthenticationProviderRepresentation[] + >([]); const { control, @@ -69,9 +65,7 @@ export const Credentials = ({ clientId, save }: CredentialsProps) => { useFetch( async () => { const providers = - await adminClient.authenticationManagement.getClientAuthenticatorProviders( - { id: clientId } - ); + await adminClient.authenticationManagement.getClientAuthenticatorProviders(); const secret = await adminClient.clients.getClientSecret({ id: clientId,