@keycloakify/keycloak-admin-ui
Version:
Repackaged Keycloak Admin UI
170 lines • 6.59 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useMemo, useState } from "react";
import { Background, Controls, MiniMap, Position, ReactFlow, useEdgesState, useNodesState, } from "reactflow";
import { useUpdateEffect } from "../../utils/useUpdateEffect";
import { providerConditionFilter } from "../../authentication/FlowDetails";
import { getLayoutedEdges, getLayoutedNodes } from "../../authentication/components/diagram/auto-layout";
import { ButtonEdge } from "../../authentication/components/diagram/ButtonEdge";
import { ConditionalNode } from "../../authentication/components/diagram/ConditionalNode";
import { EndSubFlowNode, StartSubFlowNode } from "../../authentication/components/diagram/SubFlowNode";
import "reactflow/dist/style.css";
import "./flow-diagram.css";
const nodeTypes = {
conditional: ConditionalNode,
startSubFlow: StartSubFlowNode,
endSubFlow: EndSubFlowNode,
};
const inOutClasses = new Map([
["input", "keycloak__authentication__input_node"],
["output", "keycloak__authentication__output_node"],
]);
function pairwise(fn, arr) {
const result = [];
for (let index = 0; index < arr.length - 1; index++) {
result.push(fn(arr[index], arr[index + 1]));
}
return result;
}
const isBypassable = (execution) => execution.requirement === "ALTERNATIVE" ||
execution.requirement === "CONDITIONAL";
const createEdge = (fromNode, toNode, label) => ({
id: `edge-${fromNode}-to-${toNode}`,
type: "buttonEdge",
source: fromNode,
target: toNode,
label: label,
data: {
onEdgeClick: (evt, id) => {
evt.stopPropagation();
alert(`hello ${id}`);
},
},
});
const createNode = (ex, nodeType) => {
return {
id: ex.id,
type: nodeType,
sourcePosition: nodeType === "output" ? undefined : Position.Right,
targetPosition: nodeType === "input" ? undefined : Position.Left,
data: { label: ex.displayName },
position: { x: 0, y: 0 },
className: inOutClasses.get(nodeType || ""),
};
};
const consecutiveBypassableFlows = (executionList) => {
const result = [];
for (let index = 0; index < executionList.length; index++) {
const execution = executionList[index];
if (!isBypassable(execution)) {
break;
}
result.push(execution);
}
return result;
};
const borderStep = (node, continuing = true) => ({
startId: node.id,
nodes: [node],
edges: [],
nextLinkFns: continuing ? [(id) => createEdge(node.id, id)] : [],
});
const renderSubFlow = (execution) => {
if (!execution.executionList)
throw new Error("Execution list is required for subflow");
const graph = createGraph(createConcurrentGroupings(execution.executionList));
graph.nextLinkFns.push(...execution.executionList
.filter((e) => providerConditionFilter(e))
.map((e) => (id) => createEdge(e.id, id, "false")));
return graph;
};
const groupConcurrentSteps = (executionList) => {
const executions = consecutiveBypassableFlows(executionList);
if (executions.length > 0) {
return executions;
}
return [executionList[0]];
};
const createConcurrentSteps = (executionList) => {
if (executionList.length === 0) {
return [];
}
const executions = groupConcurrentSteps(executionList);
return executions.map((execution) => {
if (execution.executionList) {
return renderSubFlow(execution);
}
const isConditional = providerConditionFilter(execution);
const edgeLabel = (() => {
if (isConditional) {
return "true";
}
if (execution.requirement === "ALTERNATIVE") {
return "success";
}
})();
return {
startId: execution.id,
nodes: [createNode(execution, isConditional ? "conditional" : undefined)],
edges: [],
nextLinkFns: [(id) => createEdge(execution.id, id, edgeLabel)],
};
});
};
const createConcurrentGroupings = (executionList) => {
if (executionList.length === 0) {
return [];
}
const steps = createConcurrentSteps(executionList);
return [
steps,
...createConcurrentGroupings(executionList.slice(steps.length)),
];
};
const createGraph = (groupings) => {
const nodes = [];
const edges = [];
let nextLinkFns = [];
for (const group of groupings) {
nodes.push(...group.flatMap((g) => g.nodes));
edges.push(...group.flatMap((g) => g.edges), ...nextLinkFns.map((fn) => fn(group[0].startId)), ...pairwise((prev, current) => createEdge(prev.startId, current.startId, "attempted"), group));
nextLinkFns = group.flatMap((g) => g.nextLinkFns);
}
return {
startId: groupings[0][0].startId,
nodes,
edges,
nextLinkFns,
};
};
const edgeTypes = {
buttonEdge: ButtonEdge,
};
function renderGraph(executionList) {
const executionListNoDisabled = executionList.filter((e) => e.requirement !== "DISABLED");
const groupings = [
[borderStep(createNode({ id: "start", displayName: "Start" }, "input"))],
...createConcurrentGroupings(executionListNoDisabled),
[
borderStep(createNode({ id: "end", displayName: "End" }, "output"), false),
],
];
const { nodes, edges } = createGraph(groupings);
return [getLayoutedNodes(nodes), getLayoutedEdges(edges)];
}
export const FlowDiagram = ({ executionList: { expandableList }, }) => {
const [expandDrawer, setExpandDrawer] = useState(false);
const [initialNodes, initialEdges] = useMemo(() => renderGraph(expandableList), []);
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
useUpdateEffect(() => {
const [nodes, edges] = renderGraph(expandableList);
setNodes(nodes);
setEdges(edges);
}, [expandableList]);
const onInit = (reactFlowInstance) => reactFlowInstance.fitView({ duration: 100 });
const onNodeClick = () => {
setExpandDrawer(!expandDrawer);
};
return (_jsxs(ReactFlow, { nodes: nodes, edges: edges, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, onInit: onInit, nodeTypes: nodeTypes, edgeTypes: edgeTypes, onNodeClick: onNodeClick, nodesConnectable: false, children: [_jsx(MiniMap, {}), _jsx(Controls, {}), _jsx(Background, {})] }));
};
//# sourceMappingURL=FlowDiagram.js.map