Changed to know where flow is used (#2532)
This commit is contained in:
parent
8170c267ef
commit
a061ceb4f2
4 changed files with 161 additions and 52 deletions
|
@ -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",
|
||||
|
|
|
@ -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") && (
|
||||
<Popover
|
||||
key={id}
|
||||
aria-label={t("usedBy")}
|
||||
bodyContent={
|
||||
<div key={`usedBy-${id}-${values}`}>
|
||||
{t(
|
||||
"appliedBy" +
|
||||
(type === "specificClients" ? "Clients" : "Providers")
|
||||
)}{" "}
|
||||
{values.map((used, index) => (
|
||||
<>
|
||||
<strong>{used}</strong>
|
||||
{index < values.length - 1 ? ", " : ""}
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<>
|
||||
<CheckCircleIcon
|
||||
className="keycloak_authentication-section__usedby"
|
||||
key={`icon-${id}`}
|
||||
/>{" "}
|
||||
{t(type)}
|
||||
</>
|
||||
</Popover>
|
||||
)}
|
||||
{type === "default" && (
|
||||
<>
|
||||
<CheckCircleIcon
|
||||
className="keycloak_authentication-section__usedby"
|
||||
key={`icon-${id}`}
|
||||
/>{" "}
|
||||
{t("default")}
|
||||
</>
|
||||
)}
|
||||
{!type && t("notInUse")}
|
||||
</>
|
||||
const UsedByRenderer = (authType: AuthenticationType) => (
|
||||
<UsedBy authType={authType} />
|
||||
);
|
||||
|
||||
const AliasRenderer = ({
|
||||
|
@ -296,7 +257,7 @@ export default function AuthenticationSection() {
|
|||
{
|
||||
name: "usedBy",
|
||||
displayKey: "authentication:usedBy",
|
||||
cellRenderer: UsedBy,
|
||||
cellRenderer: UsedByRenderer,
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
|
|
142
src/authentication/components/UsedBy.tsx
Normal file
142
src/authentication/components/UsedBy.tsx
Normal file
|
@ -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 }) => (
|
||||
<>
|
||||
<CheckCircleIcon className="keycloak_authentication-section__usedby" />{" "}
|
||||
{label}
|
||||
</>
|
||||
);
|
||||
|
||||
type UsedByModalProps = {
|
||||
values: string[];
|
||||
onClose: () => void;
|
||||
isSpecificClient: boolean;
|
||||
};
|
||||
|
||||
const UsedByModal = ({
|
||||
values,
|
||||
isSpecificClient,
|
||||
onClose,
|
||||
}: UsedByModalProps) => {
|
||||
const { t } = useTranslation("authentication");
|
||||
return (
|
||||
<Modal
|
||||
header={
|
||||
<TextContent>
|
||||
<Text component={TextVariants.h1}>{t("flowUsedBy")}</Text>
|
||||
<Text>
|
||||
{t("flowUsedByDescription", {
|
||||
value: isSpecificClient ? t("clients") : t("identiyProviders"),
|
||||
})}
|
||||
</Text>
|
||||
</TextContent>
|
||||
}
|
||||
variant={ModalVariant.medium}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
actions={[
|
||||
<Button
|
||||
data-testid="cancel"
|
||||
id="modal-cancel"
|
||||
key="cancel"
|
||||
onClick={onClose}
|
||||
>
|
||||
{t("common:close")}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<KeycloakDataTable
|
||||
loader={values.map((value) => ({ name: value }))}
|
||||
ariaLabelKey="authentication:usedBy"
|
||||
searchPlaceholderKey="common:search"
|
||||
columns={[
|
||||
{
|
||||
name: "name",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export const UsedBy = ({
|
||||
authType: {
|
||||
id,
|
||||
usedBy: { type, values },
|
||||
},
|
||||
}: UsedByProps) => {
|
||||
const { t } = useTranslation("authentication");
|
||||
const [open, toggle] = useToggle();
|
||||
|
||||
return (
|
||||
<>
|
||||
{open && (
|
||||
<UsedByModal
|
||||
values={values}
|
||||
onClose={toggle}
|
||||
isSpecificClient={type === "specificClients"}
|
||||
/>
|
||||
)}
|
||||
{(type === "specificProviders" || type === "specificClients") &&
|
||||
(values.length < 8 ? (
|
||||
<Popover
|
||||
key={id}
|
||||
aria-label={t("usedBy")}
|
||||
bodyContent={
|
||||
<div key={`usedBy-${id}-${values}`}>
|
||||
{t(
|
||||
"appliedBy" +
|
||||
(type === "specificClients" ? "Clients" : "Providers")
|
||||
)}{" "}
|
||||
{values.map((used, index) => (
|
||||
<>
|
||||
<strong>{used}</strong>
|
||||
{index < values.length - 1 ? ", " : ""}
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="link"
|
||||
className="keycloak__used-by__popover-button"
|
||||
>
|
||||
<Label label={t(type!)} />
|
||||
</Button>
|
||||
</Popover>
|
||||
) : (
|
||||
<Button
|
||||
variant="link"
|
||||
className="keycloak__used-by__popover-button"
|
||||
onClick={toggle}
|
||||
>
|
||||
<Label label={t(type!)} />
|
||||
</Button>
|
||||
))}
|
||||
{type === "default" && <Label label={toUpperCase(values[0])} />}
|
||||
{!type && t("notInUse")}
|
||||
</>
|
||||
);
|
||||
};
|
4
src/authentication/components/used-by.css
Normal file
4
src/authentication/components/used-by.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
.keycloak__used-by__popover-button {
|
||||
padding: 0;
|
||||
font-size: var(--pf-c-table--cell--FontSize);
|
||||
}
|
Loading…
Reference in a new issue