diff --git a/public/resources/en/authentication.json b/public/resources/en/authentication.json index ec0371a5e7..d74c52b941 100644 --- a/public/resources/en/authentication.json +++ b/public/resources/en/authentication.json @@ -67,6 +67,8 @@ "flowName": "Flow name", "searchForFlow": "Search for flow", "usedBy": "Used by", + "flowUsedBy": "Flow used by", + "flowUsedByDescription": "This flow is applied by the following {{value}}", "buildIn": "Built-in", "appliedByProviders": "Applied by the following providers", "appliedByClients": "Applied by the following clients", diff --git a/src/authentication/AuthenticationSection.tsx b/src/authentication/AuthenticationSection.tsx index 76fb03bfb4..86a538e177 100644 --- a/src/authentication/AuthenticationSection.tsx +++ b/src/authentication/AuthenticationSection.tsx @@ -8,12 +8,10 @@ import { ButtonVariant, Label, PageSection, - Popover, Tab, TabTitleText, ToolbarItem, } from "@patternfly/react-core"; -import { CheckCircleIcon } from "@patternfly/react-icons"; import type AuthenticationFlowRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationFlowRepresentation"; import { useAdminClient } from "../context/auth/AdminClient"; @@ -33,12 +31,13 @@ import { RequiredActions } from "./RequiredActions"; import { Policies } from "./policies/Policies"; import helpUrls from "../help-urls"; import { BindFlowDialog } from "./BindFlowDialog"; +import { UsedBy } from "./components/UsedBy"; import "./authentication-section.css"; type UsedBy = "specificClients" | "default" | "specificProviders"; -type AuthenticationType = AuthenticationFlowRepresentation & { +export type AuthenticationType = AuthenticationFlowRepresentation & { usedBy: { type?: UsedBy; values: string[] }; }; @@ -64,7 +63,7 @@ export default function AuthenticationSection() { const [bindFlowOpen, toggleBindFlow] = useToggle(); const loader = async () => { - const [clients, idps, realmRep, flows] = await Promise.all([ + const [allClients, allIdps, realmRep, flows] = await Promise.all([ adminClient.clients.find(), adminClient.identityProviders.find(), adminClient.realms.findOne({ realm }), @@ -80,31 +79,32 @@ export default function AuthenticationSection() { for (const flow of flows as AuthenticationType[]) { flow.usedBy = { values: [] }; - const client = clients.find( + const clients = allClients.filter( (client) => client.authenticationFlowBindingOverrides && (client.authenticationFlowBindingOverrides["direct_grant"] === flow.id || client.authenticationFlowBindingOverrides["browser"] === flow.id) ); - if (client) { + if (clients.length > 0) { flow.usedBy.type = "specificClients"; - flow.usedBy.values.push(client.clientId!); + flow.usedBy.values = clients.map(({ clientId }) => clientId!); } - const idp = idps.find( + const idps = allIdps.filter( (idp) => idp.firstBrokerLoginFlowAlias === flow.alias || idp.postBrokerLoginFlowAlias === flow.alias ); - if (idp) { + if (idps.length > 0) { flow.usedBy.type = "specificProviders"; - flow.usedBy.values.push(idp.alias!); + flow.usedBy.values = idps.map(({ alias }) => alias!); } const isDefault = defaultFlows.includes(flow.alias); if (isDefault) { flow.usedBy.type = "default"; + flow.usedBy.values.push(flow.alias!); } } @@ -134,47 +134,8 @@ export default function AuthenticationSection() { }, }); - const UsedBy = ({ id, usedBy: { type, values } }: AuthenticationType) => ( - <> - {(type === "specificProviders" || type === "specificClients") && ( - - {t( - "appliedBy" + - (type === "specificClients" ? "Clients" : "Providers") - )}{" "} - {values.map((used, index) => ( - <> - {used} - {index < values.length - 1 ? ", " : ""} - - ))} - - } - > - <> - {" "} - {t(type)} - - - )} - {type === "default" && ( - <> - {" "} - {t("default")} - - )} - {!type && t("notInUse")} - + const UsedByRenderer = (authType: AuthenticationType) => ( + ); const AliasRenderer = ({ @@ -296,7 +257,7 @@ export default function AuthenticationSection() { { name: "usedBy", displayKey: "authentication:usedBy", - cellRenderer: UsedBy, + cellRenderer: UsedByRenderer, }, { name: "description", diff --git a/src/authentication/components/UsedBy.tsx b/src/authentication/components/UsedBy.tsx new file mode 100644 index 0000000000..2a6b760e6f --- /dev/null +++ b/src/authentication/components/UsedBy.tsx @@ -0,0 +1,142 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { + Button, + Modal, + ModalVariant, + Popover, + Text, + TextContent, + TextVariants, +} from "@patternfly/react-core"; +import { CheckCircleIcon } from "@patternfly/react-icons"; + +import type { AuthenticationType } from "../AuthenticationSection"; +import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable"; +import { toUpperCase } from "../../util"; +import useToggle from "../../utils/useToggle"; + +import "./used-by.css"; + +type UsedByProps = { + authType: AuthenticationType; +}; + +const Label = ({ label }: { label: string }) => ( + <> + {" "} + {label} + +); + +type UsedByModalProps = { + values: string[]; + onClose: () => void; + isSpecificClient: boolean; +}; + +const UsedByModal = ({ + values, + isSpecificClient, + onClose, +}: UsedByModalProps) => { + const { t } = useTranslation("authentication"); + return ( + + {t("flowUsedBy")} + + {t("flowUsedByDescription", { + value: isSpecificClient ? t("clients") : t("identiyProviders"), + })} + + + } + variant={ModalVariant.medium} + isOpen + onClose={onClose} + actions={[ + , + ]} + > + ({ name: value }))} + ariaLabelKey="authentication:usedBy" + searchPlaceholderKey="common:search" + columns={[ + { + name: "name", + }, + ]} + /> + + ); +}; + +export const UsedBy = ({ + authType: { + id, + usedBy: { type, values }, + }, +}: UsedByProps) => { + const { t } = useTranslation("authentication"); + const [open, toggle] = useToggle(); + + return ( + <> + {open && ( + + )} + {(type === "specificProviders" || type === "specificClients") && + (values.length < 8 ? ( + + {t( + "appliedBy" + + (type === "specificClients" ? "Clients" : "Providers") + )}{" "} + {values.map((used, index) => ( + <> + {used} + {index < values.length - 1 ? ", " : ""} + + ))} + + } + > + + + ) : ( + + ))} + {type === "default" &&