@anlyx/ui 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AnalysisEvidenceList.d.ts +5 -0
- package/dist/components/AnalysisEvidenceList.js +61 -0
- package/dist/components/AnlyxAppShell.d.ts +1 -1
- package/dist/components/AnlyxAppShell.js +159 -15
- package/dist/components/ApiCallList.d.ts +3 -3
- package/dist/components/ApiCallList.js +12 -3
- package/dist/components/CanvasPlaceholder.d.ts +0 -1
- package/dist/components/CanvasPlaceholder.js +0 -1
- package/dist/components/CaptureStatusEmptyState.d.ts +0 -1
- package/dist/components/CaptureStatusEmptyState.js +5 -4
- package/dist/components/EndpointList.d.ts +0 -1
- package/dist/components/EndpointList.js +1 -2
- package/dist/components/EndpointMapCanvas.d.ts +5 -2
- package/dist/components/EndpointMapCanvas.js +93 -13
- package/dist/components/FlowLegend.d.ts +4 -2
- package/dist/components/FlowLegend.js +4 -2
- package/dist/components/FlowNodeCard.d.ts +1 -2
- package/dist/components/FlowNodeCard.js +25 -3
- package/dist/components/FlowStoryView.d.ts +22 -0
- package/dist/components/FlowStoryView.js +117 -0
- package/dist/components/InspectorPanel.d.ts +8 -3
- package/dist/components/InspectorPanel.js +36 -3
- package/dist/components/PageList.d.ts +0 -1
- package/dist/components/PageList.js +1 -2
- package/dist/components/PageStoryboardCard.d.ts +0 -1
- package/dist/components/PageStoryboardCard.js +4 -2
- package/dist/components/PageStoryboardView.d.ts +4 -3
- package/dist/components/PageStoryboardView.js +13 -3
- package/dist/components/ProcessFlowView.d.ts +21 -0
- package/dist/components/ProcessFlowView.js +16 -0
- package/dist/components/ProcessTimeline.d.ts +9 -0
- package/dist/components/ProcessTimeline.js +46 -0
- package/dist/components/ReplayControls.d.ts +6 -2
- package/dist/components/ReplayControls.js +31 -3
- package/dist/components/ScreenshotSegmentCard.d.ts +0 -1
- package/dist/components/ScreenshotSegmentCard.js +0 -1
- package/dist/components/Sidebar.d.ts +5 -4
- package/dist/components/Sidebar.js +19 -4
- package/dist/components/StatusBadge.d.ts +0 -1
- package/dist/components/StatusBadge.js +0 -1
- package/dist/flow/build-react-flow-model.d.ts +6 -2
- package/dist/flow/build-react-flow-model.js +15 -18
- package/dist/flow/layout/elk-layout.d.ts +7 -0
- package/dist/flow/layout/elk-layout.js +74 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/mock-data.d.ts +0 -1
- package/dist/mock-data.js +50 -5
- package/dist/overlay/AnlyxFlowEdge.d.ts +2 -0
- package/dist/overlay/AnlyxFlowEdge.js +15 -0
- package/dist/overlay/AnlyxFlowNode.d.ts +13 -0
- package/dist/overlay/AnlyxFlowNode.js +28 -0
- package/dist/overlay/FlowDrawer.d.ts +2 -0
- package/dist/overlay/FlowDrawer.js +59 -0
- package/dist/overlay/MainFlowCanvas.d.ts +20 -0
- package/dist/overlay/MainFlowCanvas.js +285 -0
- package/dist/overlay/RecentApiEventsTable.d.ts +5 -0
- package/dist/overlay/RecentApiEventsTable.js +19 -0
- package/dist/overlay/overlay-entry.d.ts +8 -0
- package/dist/overlay/overlay-entry.js +14 -0
- package/dist/overlay/overlay-ui.css +2 -0
- package/dist/overlay/overlay-ui.js +14 -0
- package/dist/overlay/types.d.ts +38 -0
- package/dist/overlay/types.js +1 -0
- package/dist/overlay/ui.d.ts +18 -0
- package/dist/overlay/ui.js +13 -0
- package/dist/readme-demo/ReadmeDemoApp.d.ts +4 -0
- package/dist/readme-demo/ReadmeDemoApp.js +126 -0
- package/dist/readme-demo/readme-demo-entry.d.ts +1 -0
- package/dist/readme-demo/readme-demo-entry.js +8 -0
- package/dist/replay/build-replay-steps.d.ts +0 -1
- package/dist/replay/build-replay-steps.js +0 -1
- package/dist/replay/use-replay-lite.d.ts +2 -2
- package/dist/replay/use-replay-lite.js +1 -1
- package/dist/styles.css +2018 -178
- package/dist/viewer/ViewerApp.d.ts +0 -1
- package/dist/viewer/ViewerApp.js +13 -7
- package/dist/viewer/viewer-entry.d.ts +1 -1
- package/dist/viewer/viewer-entry.js +1 -1
- package/package.json +7 -3
- package/dist/components/AnlyxAppShell.d.ts.map +0 -1
- package/dist/components/AnlyxAppShell.js.map +0 -1
- package/dist/components/ApiCallList.d.ts.map +0 -1
- package/dist/components/ApiCallList.js.map +0 -1
- package/dist/components/CanvasPlaceholder.d.ts.map +0 -1
- package/dist/components/CanvasPlaceholder.js.map +0 -1
- package/dist/components/CaptureStatusEmptyState.d.ts.map +0 -1
- package/dist/components/CaptureStatusEmptyState.js.map +0 -1
- package/dist/components/EndpointList.d.ts.map +0 -1
- package/dist/components/EndpointList.js.map +0 -1
- package/dist/components/EndpointMapCanvas.d.ts.map +0 -1
- package/dist/components/EndpointMapCanvas.js.map +0 -1
- package/dist/components/FlowLegend.d.ts.map +0 -1
- package/dist/components/FlowLegend.js.map +0 -1
- package/dist/components/FlowNodeCard.d.ts.map +0 -1
- package/dist/components/FlowNodeCard.js.map +0 -1
- package/dist/components/InspectorPanel.d.ts.map +0 -1
- package/dist/components/InspectorPanel.js.map +0 -1
- package/dist/components/PageList.d.ts.map +0 -1
- package/dist/components/PageList.js.map +0 -1
- package/dist/components/PageStoryboardCard.d.ts.map +0 -1
- package/dist/components/PageStoryboardCard.js.map +0 -1
- package/dist/components/PageStoryboardView.d.ts.map +0 -1
- package/dist/components/PageStoryboardView.js.map +0 -1
- package/dist/components/ReplayControls.d.ts.map +0 -1
- package/dist/components/ReplayControls.js.map +0 -1
- package/dist/components/ScreenshotSegmentCard.d.ts.map +0 -1
- package/dist/components/ScreenshotSegmentCard.js.map +0 -1
- package/dist/components/Sidebar.d.ts.map +0 -1
- package/dist/components/Sidebar.js.map +0 -1
- package/dist/components/StatusBadge.d.ts.map +0 -1
- package/dist/components/StatusBadge.js.map +0 -1
- package/dist/flow/build-react-flow-model.d.ts.map +0 -1
- package/dist/flow/build-react-flow-model.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mock-data.d.ts.map +0 -1
- package/dist/mock-data.js.map +0 -1
- package/dist/replay/build-replay-steps.d.ts.map +0 -1
- package/dist/replay/build-replay-steps.js.map +0 -1
- package/dist/replay/use-replay-lite.d.ts.map +0 -1
- package/dist/replay/use-replay-lite.js.map +0 -1
- package/dist/viewer/ViewerApp.d.ts.map +0 -1
- package/dist/viewer/ViewerApp.js.map +0 -1
- package/dist/viewer/viewer-entry.d.ts.map +0 -1
- package/dist/viewer/viewer-entry.js.map +0 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CheckCircle2, CircleHelp, Info, TriangleAlert } from "lucide-react";
|
|
3
|
+
import { StatusBadge } from "./StatusBadge.js";
|
|
4
|
+
export function AnalysisEvidenceList({ node }) {
|
|
5
|
+
const evidence = getEvidence(node);
|
|
6
|
+
return (_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Analysis evidence", children: [_jsx("h3", { children: "Analysis evidence" }), _jsx("ul", { className: "anlyx-evidence-list", children: evidence.map((item, index) => {
|
|
7
|
+
const Icon = getEvidenceIcon(item.confidence ?? node.confidence ?? "unknown");
|
|
8
|
+
return (_jsxs("li", { children: [_jsx(Icon, { size: 14, strokeWidth: 2.5 }), _jsxs("span", { children: [_jsx("strong", { children: item.label }), item.detail ? _jsx("em", { children: item.detail }) : null] }), _jsx(StatusBadge, { tone: item.confidence ?? node.confidence ?? "unknown", children: item.confidence ?? node.confidence ?? "unknown" })] }, `${item.label}:${index}`));
|
|
9
|
+
}) })] }));
|
|
10
|
+
}
|
|
11
|
+
function getEvidence(node) {
|
|
12
|
+
if (node.evidence && node.evidence.length > 0) {
|
|
13
|
+
return node.evidence;
|
|
14
|
+
}
|
|
15
|
+
if (node.type === "unknown") {
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
label: "Analysis stopped",
|
|
19
|
+
detail: "Anlyx could not resolve this code element from the scanned source.",
|
|
20
|
+
confidence: "unknown"
|
|
21
|
+
}
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
if (node.type === "database") {
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
label: "Database table inferred",
|
|
28
|
+
detail: "Derived from repository entity metadata or entity naming fallback.",
|
|
29
|
+
confidence: node.confidence ?? "unknown"
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
if (node.type === "endpoint") {
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
label: "Endpoint matched",
|
|
37
|
+
detail: "Derived from the backend adapter endpoint list.",
|
|
38
|
+
confidence: node.confidence ?? "unknown"
|
|
39
|
+
}
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
label: "Code node resolved",
|
|
45
|
+
detail: "Resolved from the scanned static flow graph.",
|
|
46
|
+
confidence: node.confidence ?? "unknown"
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
function getEvidenceIcon(confidence) {
|
|
51
|
+
if (confidence === "high") {
|
|
52
|
+
return CheckCircle2;
|
|
53
|
+
}
|
|
54
|
+
if (confidence === "medium") {
|
|
55
|
+
return Info;
|
|
56
|
+
}
|
|
57
|
+
if (confidence === "low") {
|
|
58
|
+
return TriangleAlert;
|
|
59
|
+
}
|
|
60
|
+
return CircleHelp;
|
|
61
|
+
}
|
|
@@ -2,5 +2,5 @@ import type { ScanResult } from "@anlyx/core";
|
|
|
2
2
|
export type AnlyxAppShellProps = {
|
|
3
3
|
data: ScanResult;
|
|
4
4
|
};
|
|
5
|
+
export type ViewMode = "flowStory" | "structure" | "frontend" | "process";
|
|
5
6
|
export declare function AnlyxAppShell({ data }: AnlyxAppShellProps): JSX.Element;
|
|
6
|
-
//# sourceMappingURL=AnlyxAppShell.d.ts.map
|
|
@@ -1,39 +1,102 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { Group, Panel, Separator, usePanelRef } from "react-resizable-panels";
|
|
3
4
|
import { EndpointMapCanvas } from "./EndpointMapCanvas.js";
|
|
5
|
+
import { FlowStoryView } from "./FlowStoryView.js";
|
|
4
6
|
import { InspectorPanel } from "./InspectorPanel.js";
|
|
5
7
|
import { PageStoryboardView } from "./PageStoryboardView.js";
|
|
6
|
-
import {
|
|
8
|
+
import { ProcessFlowView } from "./ProcessFlowView.js";
|
|
7
9
|
import { Sidebar } from "./Sidebar.js";
|
|
8
10
|
import { useReplayLite } from "../replay/use-replay-lite.js";
|
|
11
|
+
const STORAGE_KEYS = {
|
|
12
|
+
leftCollapsed: "anlyx:ui:v2:leftCollapsed",
|
|
13
|
+
panelLayout: "anlyx:ui:v2:panelLayout",
|
|
14
|
+
rightCollapsed: "anlyx:ui:v2:rightCollapsed",
|
|
15
|
+
selectedEndpointId: "anlyx:ui:selectedEndpointId",
|
|
16
|
+
selectedPageId: "anlyx:ui:selectedPageId"
|
|
17
|
+
};
|
|
18
|
+
const DEFAULT_PANEL_LAYOUT = {
|
|
19
|
+
left: 22,
|
|
20
|
+
center: 52,
|
|
21
|
+
right: 26
|
|
22
|
+
};
|
|
9
23
|
export function AnlyxAppShell({ data }) {
|
|
10
|
-
const [activeView, setActiveView] = useState("
|
|
11
|
-
const [selectedEndpointId, setSelectedEndpointId] =
|
|
12
|
-
const [selectedPageId, setSelectedPageId] =
|
|
24
|
+
const [activeView, setActiveView] = useState("flowStory");
|
|
25
|
+
const [selectedEndpointId, setSelectedEndpointId] = usePersistentString(STORAGE_KEYS.selectedEndpointId, selectInitialEndpointId(data));
|
|
26
|
+
const [selectedPageId, setSelectedPageId] = usePersistentString(STORAGE_KEYS.selectedPageId, data.pages[0]?.id);
|
|
27
|
+
const leftPanelRef = usePanelRef();
|
|
28
|
+
const rightPanelRef = usePanelRef();
|
|
29
|
+
const [leftCollapsed, setLeftCollapsed] = usePersistentBoolean(STORAGE_KEYS.leftCollapsed, false);
|
|
30
|
+
const [rightCollapsed, setRightCollapsed] = usePersistentBoolean(STORAGE_KEYS.rightCollapsed, false);
|
|
31
|
+
const panelLayout = useMemo(() => readPanelLayout(), []);
|
|
32
|
+
const [replaySpeed, setReplaySpeed] = useState(800);
|
|
13
33
|
const selectedEndpoint = data.endpoints.find((endpoint) => endpoint.id === selectedEndpointId) ?? data.endpoints[0];
|
|
14
34
|
const selectedPage = data.pages.find((page) => page.id === selectedPageId) ?? data.pages[0];
|
|
15
35
|
const selectedFlow = useMemo(() => data.flows.find((flow) => flow.endpointId === selectedEndpoint?.id), [data.flows, selectedEndpoint?.id]);
|
|
16
36
|
const replayMainPath = useMemo(() => getReplayMainPath(selectedFlow, selectedEndpoint?.id), [selectedEndpoint?.id, selectedFlow]);
|
|
17
|
-
const replay = useReplayLite({ mainPath: replayMainPath });
|
|
37
|
+
const replay = useReplayLite({ intervalMs: replaySpeed, mainPath: replayMainPath });
|
|
18
38
|
const replayUnavailable = replayMainPath.length === 0;
|
|
19
39
|
const [selectedNodeId, setSelectedNodeId] = useState(() => findDefaultNode(selectedFlow)?.id);
|
|
20
40
|
const selectedNode = findFlowNode(selectedFlow, selectedNodeId) ?? findDefaultNode(selectedFlow);
|
|
21
41
|
useEffect(() => {
|
|
22
|
-
setSelectedEndpointId((current) => data.endpoints.some((endpoint) => endpoint.id === current)
|
|
23
|
-
|
|
42
|
+
setSelectedEndpointId((current) => data.endpoints.some((endpoint) => endpoint.id === current)
|
|
43
|
+
? current
|
|
44
|
+
: selectInitialEndpointId(data));
|
|
45
|
+
}, [data, setSelectedEndpointId]);
|
|
24
46
|
useEffect(() => {
|
|
25
47
|
setSelectedPageId((current) => data.pages.some((page) => page.id === current) ? current : data.pages[0]?.id);
|
|
26
48
|
}, [data.pages]);
|
|
27
49
|
useEffect(() => {
|
|
28
50
|
setSelectedNodeId(findDefaultNode(selectedFlow)?.id);
|
|
29
51
|
}, [selectedFlow]);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (leftCollapsed) {
|
|
54
|
+
leftPanelRef.current?.collapse();
|
|
55
|
+
}
|
|
56
|
+
}, [leftCollapsed, leftPanelRef]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (rightCollapsed) {
|
|
59
|
+
rightPanelRef.current?.collapse();
|
|
60
|
+
}
|
|
61
|
+
}, [rightCollapsed, rightPanelRef]);
|
|
62
|
+
const toggleLeftPanel = () => {
|
|
63
|
+
if (leftCollapsed) {
|
|
64
|
+
leftPanelRef.current?.expand();
|
|
65
|
+
setLeftCollapsed(false);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
leftPanelRef.current?.collapse();
|
|
69
|
+
setLeftCollapsed(true);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const toggleRightPanel = () => {
|
|
73
|
+
if (rightCollapsed) {
|
|
74
|
+
rightPanelRef.current?.expand();
|
|
75
|
+
setRightCollapsed(false);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
rightPanelRef.current?.collapse();
|
|
79
|
+
setRightCollapsed(true);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return (_jsxs("div", { className: "anlyx-shell", role: "application", "aria-label": "Anlyx application shell", children: [_jsxs(Group, { className: "anlyx-panel-group", defaultLayout: panelLayout, id: "anlyx-main-panels", orientation: "horizontal", onLayoutChanged: (layout) => writeLocalStorage(STORAGE_KEYS.panelLayout, JSON.stringify(layout)), children: [_jsx(Panel, { className: "anlyx-panel anlyx-panel--sidebar", collapsedSize: "52px", collapsible: true, defaultSize: "300px", id: "left", maxSize: "420px", minSize: "240px", panelRef: leftPanelRef, children: _jsx(Sidebar, { data: data, activeView: activeView, collapsed: leftCollapsed, selectedEndpointId: selectedEndpoint?.id, selectedPageId: selectedPage?.id, onSelectView: setActiveView, onToggleCollapsed: toggleLeftPanel, onSelectEndpoint: (endpoint) => {
|
|
83
|
+
setSelectedEndpointId(endpoint.id);
|
|
84
|
+
if (activeView !== "structure" && activeView !== "process") {
|
|
85
|
+
setActiveView("flowStory");
|
|
86
|
+
}
|
|
87
|
+
}, onSelectPage: (page) => {
|
|
88
|
+
setSelectedPageId(page.id);
|
|
89
|
+
const linkedEndpointId = page.apiCalls.find((apiCall) => apiCall.endpointId)?.endpointId;
|
|
90
|
+
if (linkedEndpointId) {
|
|
91
|
+
setSelectedEndpointId(linkedEndpointId);
|
|
92
|
+
}
|
|
93
|
+
if (activeView !== "frontend") {
|
|
94
|
+
setActiveView("flowStory");
|
|
95
|
+
}
|
|
96
|
+
} }) }), _jsx(Separator, { "aria-label": "Resize navigation panel", className: "anlyx-resize-handle", children: _jsx("span", { "aria-hidden": "true" }) }), _jsx(Panel, { className: "anlyx-panel anlyx-panel--main", id: "center", minSize: "420px", children: _jsxs("div", { className: activeView === "process" ? "anlyx-main anlyx-main--process" : "anlyx-main", "aria-live": "polite", children: [activeView === "flowStory" ? (_jsx(FlowStoryView, { data: data, endpoint: selectedEndpoint, flow: selectedFlow, page: selectedPage, replayDisabled: replayUnavailable, replayLoop: replay.loop, replaySpeed: replaySpeed, replayState: replay.state, replaySteps: replay.steps, selectedNodeId: selectedNode?.id, onPause: replay.pause, onPlay: replay.play, onRestart: replay.restart, onSelectNode: (node) => setSelectedNodeId(node.id), onSpeedChange: setReplaySpeed, onToggleLoop: replay.toggleLoop })) : null, activeView === "structure" ? (_jsx(EndpointMapCanvas, { eyebrow: "Backend API Structure", endpoint: selectedEndpoint, flow: selectedFlow, replayState: replay.state, selectedNodeId: selectedNode?.id, toolbar: _jsx(StructureToolbar, {}), onSelectNode: (node) => setSelectedNodeId(node.id) })) : null, activeView === "frontend" ? (_jsx(PageStoryboardView, { data: data, page: selectedPage, onViewProcessFlow: setActiveView })) : null, activeView === "process" ? (_jsx(ProcessFlowView, { endpoint: selectedEndpoint, flow: selectedFlow, replayDisabled: replayUnavailable, replayLoop: replay.loop, replaySpeed: replaySpeed, replayState: replay.state, replaySteps: replay.steps, selectedNodeId: selectedNode?.id, onPause: replay.pause, onPlay: replay.play, onRestart: replay.restart, onSelectNode: (node) => setSelectedNodeId(node.id), onSpeedChange: setReplaySpeed, onToggleLoop: replay.toggleLoop, onViewStructure: () => setActiveView("structure") })) : null] }) }), _jsx(Separator, { "aria-label": "Resize inspector panel", className: "anlyx-resize-handle", children: _jsx("span", { "aria-hidden": "true" }) }), _jsx(Panel, { className: "anlyx-panel anlyx-panel--inspector", collapsedSize: "52px", collapsible: true, defaultSize: "360px", id: "right", maxSize: "520px", minSize: "300px", panelRef: rightPanelRef, children: _jsx(InspectorPanel, { activeView: activeView, collapsed: rightCollapsed, data: data, replayState: replay.state, selectedFlow: selectedFlow, selectedNode: selectedNode, selectedPage: selectedPage, onToggleCollapsed: toggleRightPanel }) })] }), _jsxs("div", { className: "anlyx-generated-at", children: ["Generated ", data.generatedAt] })] }));
|
|
97
|
+
}
|
|
98
|
+
function StructureToolbar() {
|
|
99
|
+
return (_jsxs("div", { className: "anlyx-toolbar", "aria-label": "Structure view actions", children: [_jsx("button", { className: "anlyx-toolbar-button", type: "button", children: "Fit view" }), _jsxs("select", { "aria-label": "Zoom level", className: "anlyx-toolbar-select", defaultValue: "100", children: [_jsx("option", { value: "75", children: "75%" }), _jsx("option", { value: "100", children: "100%" }), _jsx("option", { value: "125", children: "125%" })] }), _jsx("button", { className: "anlyx-toolbar-button anlyx-toolbar-button--icon", type: "button", children: "More" })] }));
|
|
37
100
|
}
|
|
38
101
|
function getReplayMainPath(flow, endpointId) {
|
|
39
102
|
if (!flow || flow.mainPath.length === 0) {
|
|
@@ -45,6 +108,20 @@ function getReplayMainPath(flow, endpointId) {
|
|
|
45
108
|
}
|
|
46
109
|
return flow.mainPath;
|
|
47
110
|
}
|
|
111
|
+
function selectInitialEndpointId(data) {
|
|
112
|
+
const flowByEndpointId = new Map(data.flows.map((flow) => [flow.endpointId, flow]));
|
|
113
|
+
const endpointsByScore = [...data.endpoints].sort((left, right) => scoreEndpoint(right, flowByEndpointId) - scoreEndpoint(left, flowByEndpointId));
|
|
114
|
+
return endpointsByScore[0]?.id;
|
|
115
|
+
}
|
|
116
|
+
function scoreEndpoint(endpoint, flowByEndpointId) {
|
|
117
|
+
const flow = flowByEndpointId.get(endpoint.id);
|
|
118
|
+
const hasDatabase = flow?.nodes.some((node) => node.type === "database") ? 1 : 0;
|
|
119
|
+
const confidenceScore = endpoint.confidence === "high" ? 4 : endpoint.confidence === "medium" ? 2 : 0;
|
|
120
|
+
return ((flow?.mainPath.length ?? 0) * 8 +
|
|
121
|
+
(flow?.subFlows.length ?? 0) * 5 +
|
|
122
|
+
hasDatabase * 6 +
|
|
123
|
+
confidenceScore);
|
|
124
|
+
}
|
|
48
125
|
function findDefaultNode(flow) {
|
|
49
126
|
if (!flow) {
|
|
50
127
|
return undefined;
|
|
@@ -58,4 +135,71 @@ function findFlowNode(flow, nodeId) {
|
|
|
58
135
|
return (flow.nodes.find((node) => node.id === nodeId) ??
|
|
59
136
|
flow.subFlows.flatMap((subFlow) => subFlow.nodes).find((node) => node.id === nodeId));
|
|
60
137
|
}
|
|
61
|
-
|
|
138
|
+
function usePersistentBoolean(key, defaultValue) {
|
|
139
|
+
const [value, setValue] = useState(() => {
|
|
140
|
+
const storedValue = readLocalStorage(key);
|
|
141
|
+
if (storedValue === "true") {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
if (storedValue === "false") {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
return defaultValue;
|
|
148
|
+
});
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
writeLocalStorage(key, String(value));
|
|
151
|
+
}, [key, value]);
|
|
152
|
+
return [value, setValue];
|
|
153
|
+
}
|
|
154
|
+
function usePersistentString(key, fallback) {
|
|
155
|
+
const [value, setValue] = useState(() => {
|
|
156
|
+
if (typeof window === "undefined") {
|
|
157
|
+
return fallback;
|
|
158
|
+
}
|
|
159
|
+
return window.localStorage.getItem(key) ?? fallback;
|
|
160
|
+
});
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (typeof window === "undefined") {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (value) {
|
|
166
|
+
window.localStorage.setItem(key, value);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
window.localStorage.removeItem(key);
|
|
170
|
+
}
|
|
171
|
+
}, [key, value]);
|
|
172
|
+
return [value, setValue];
|
|
173
|
+
}
|
|
174
|
+
function readLocalStorage(key) {
|
|
175
|
+
if (typeof window === "undefined") {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return window.localStorage.getItem(key);
|
|
179
|
+
}
|
|
180
|
+
function writeLocalStorage(key, value) {
|
|
181
|
+
if (typeof window === "undefined") {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
window.localStorage.setItem(key, value);
|
|
185
|
+
}
|
|
186
|
+
function readPanelLayout() {
|
|
187
|
+
const storedValue = readLocalStorage(STORAGE_KEYS.panelLayout);
|
|
188
|
+
if (!storedValue) {
|
|
189
|
+
return DEFAULT_PANEL_LAYOUT;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
const parsedValue = JSON.parse(storedValue);
|
|
193
|
+
return {
|
|
194
|
+
left: sanitizePanelSize(parsedValue.left, DEFAULT_PANEL_LAYOUT.left),
|
|
195
|
+
center: sanitizePanelSize(parsedValue.center, DEFAULT_PANEL_LAYOUT.center),
|
|
196
|
+
right: sanitizePanelSize(parsedValue.right, DEFAULT_PANEL_LAYOUT.right)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return DEFAULT_PANEL_LAYOUT;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function sanitizePanelSize(value, fallback) {
|
|
204
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
205
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ApiCall } from "@anlyx/core";
|
|
1
|
+
import type { ApiCall, Endpoint } from "@anlyx/core";
|
|
2
2
|
export type ApiCallListProps = {
|
|
3
3
|
apiCalls: ApiCall[];
|
|
4
|
+
endpoints?: Endpoint[];
|
|
4
5
|
};
|
|
5
|
-
export declare function ApiCallList({ apiCalls }: ApiCallListProps): JSX.Element;
|
|
6
|
-
//# sourceMappingURL=ApiCallList.d.ts.map
|
|
6
|
+
export declare function ApiCallList({ apiCalls, endpoints }: ApiCallListProps): JSX.Element;
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { StatusBadge } from "./StatusBadge.js";
|
|
3
|
-
export function ApiCallList({ apiCalls }) {
|
|
4
|
-
|
|
3
|
+
export function ApiCallList({ apiCalls, endpoints = [] }) {
|
|
4
|
+
const endpointById = new Map(endpoints.map((endpoint) => [endpoint.id, endpoint]));
|
|
5
|
+
return (_jsxs("section", { className: "anlyx-storyboard-panel", "aria-label": "API Calls", children: [_jsxs("div", { className: "anlyx-storyboard-section-heading", children: [_jsx("h2", { children: "API Calls" }), _jsx("span", { children: apiCalls.length })] }), apiCalls.length > 0 ? (_jsx("ul", { className: "anlyx-api-call-list", children: apiCalls.map((apiCall, index) => {
|
|
6
|
+
const endpoint = apiCall.endpointId ? endpointById.get(apiCall.endpointId) : undefined;
|
|
7
|
+
return (_jsxs("li", { className: "anlyx-api-call", children: [_jsxs("div", { className: "anlyx-api-call__line", children: [_jsx(StatusBadge, { tone: apiCall.method, children: apiCall.method }), _jsx("span", { className: "anlyx-api-call__path", children: apiCall.path })] }), _jsxs("div", { className: "anlyx-api-call__meta", children: [_jsx(StatusBadge, { tone: statusTone(apiCall.status), children: apiCall.status === undefined ? "unknown" : String(apiCall.status) }), _jsx(StatusBadge, { tone: apiCall.endpointId ? "success" : "unknown", children: apiCall.endpointId ? "Linked endpoint" : "Unmatched" })] }), endpoint ? (_jsxs("div", { className: "anlyx-api-call__endpoint", children: [_jsx("span", { children: "Matched endpoint" }), _jsxs("strong", { children: [endpoint.method, " ", endpoint.path] }), endpoint.controller || endpoint.handler ? (_jsx("em", { children: formatEndpointHandler(endpoint) })) : null] })) : apiCall.endpointId ? (_jsxs("div", { className: "anlyx-api-call__endpoint", children: [_jsx("span", { children: "Matched endpoint" }), _jsx("strong", { children: apiCall.endpointId })] })) : null] }, `${apiCall.method}:${apiCall.path}:${index}`));
|
|
8
|
+
}) })) : (_jsx("p", { className: "anlyx-empty-inline", children: "No API calls captured yet." }))] }));
|
|
9
|
+
}
|
|
10
|
+
function formatEndpointHandler(endpoint) {
|
|
11
|
+
if (endpoint.controller && endpoint.handler) {
|
|
12
|
+
return `${endpoint.controller}#${endpoint.handler}`;
|
|
13
|
+
}
|
|
14
|
+
return endpoint.controller ?? endpoint.handler ?? "Unknown handler";
|
|
5
15
|
}
|
|
6
16
|
function statusTone(status) {
|
|
7
17
|
if (status === undefined) {
|
|
@@ -15,4 +25,3 @@ function statusTone(status) {
|
|
|
15
25
|
}
|
|
16
26
|
return "pending";
|
|
17
27
|
}
|
|
18
|
-
//# sourceMappingURL=ApiCallList.js.map
|
|
@@ -3,4 +3,3 @@ import { StatusBadge } from "./StatusBadge.js";
|
|
|
3
3
|
export function CanvasPlaceholder({ endpoint }) {
|
|
4
4
|
return (_jsxs("main", { className: "anlyx-workspace", "aria-label": "Workspace", children: [_jsxs("header", { className: "anlyx-workspace-header", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Selected endpoint" }), _jsx("h1", { children: endpoint ? `${endpoint.method} ${endpoint.path}` : "No endpoint selected" })] }), endpoint ? (_jsx(StatusBadge, { tone: endpoint.confidence ?? "unknown", children: endpoint.confidence ?? "unknown" })) : null] }), _jsx("section", { className: "anlyx-canvas-placeholder", "aria-label": "Endpoint map canvas placeholder", children: _jsxs("div", { className: "anlyx-canvas-placeholder__content", children: [_jsx("p", { className: "anlyx-eyebrow", children: "Canvas" }), _jsx("h2", { children: "Endpoint Map will render here" })] }) })] }));
|
|
5
5
|
}
|
|
6
|
-
//# sourceMappingURL=CanvasPlaceholder.js.map
|
|
@@ -3,8 +3,9 @@ export function CaptureStatusEmptyState({ status, reason }) {
|
|
|
3
3
|
if (status === "success") {
|
|
4
4
|
return null;
|
|
5
5
|
}
|
|
6
|
-
const title = status === "failed" ? "Capture failed" : "Capture
|
|
7
|
-
const fallbackReason = status === "pending"
|
|
8
|
-
|
|
6
|
+
const title = status === "failed" ? "Capture failed" : "Capture was skipped.";
|
|
7
|
+
const fallbackReason = status === "pending"
|
|
8
|
+
? "Run `anlyx scan` without `--skip-capture` while your frontend server is running."
|
|
9
|
+
: "Unknown";
|
|
10
|
+
return (_jsxs("section", { className: `anlyx-capture-state anlyx-capture-state--${status}`, children: [_jsx("h2", { children: title }), _jsxs("p", { children: ["Reason: ", reason ?? fallbackReason] }), status === "pending" ? (_jsx("p", { children: "Dynamic routes may require `sampleParams` in `anlyx.config.ts`." })) : null] }));
|
|
9
11
|
}
|
|
10
|
-
//# sourceMappingURL=CaptureStatusEmptyState.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { StatusBadge } from "./StatusBadge.js";
|
|
3
3
|
export function EndpointList({ endpoints, selectedEndpointId, onSelectEndpoint }) {
|
|
4
|
-
return (_jsxs("section", { className: "anlyx-sidebar-section", "aria-labelledby": "anlyx-endpoints-heading", children: [_jsx("div", { className: "anlyx-section-heading", id: "anlyx-endpoints-heading", children: "Endpoints" }), _jsx("ul", { className: "anlyx-list", "aria-label": "Endpoint list", children: endpoints.map((endpoint) => (_jsx("li", { className: "anlyx-list-item", children: _jsxs("button", { className: "anlyx-endpoint-button", type: "button", "aria-label": `${endpoint.method} ${endpoint.path} ${formatEndpointHandler(endpoint)}`, "aria-current": endpoint.id === selectedEndpointId ? "true" : undefined, onClick: () => onSelectEndpoint(endpoint), children: [_jsxs("span", { className: "anlyx-list-item__line", children: [_jsx(StatusBadge, { tone: endpoint.method, children: endpoint.method }), _jsx("span", { className: "anlyx-list-item__primary", children: endpoint.path })] }), _jsx("span", { className: "anlyx-list-item__meta", children: formatEndpointHandler(endpoint) })] }) }, endpoint.id))) })] }));
|
|
4
|
+
return (_jsxs("section", { className: "anlyx-sidebar-section", "aria-labelledby": "anlyx-endpoints-heading", children: [_jsx("div", { className: "anlyx-section-heading", id: "anlyx-endpoints-heading", children: "Backend Endpoints" }), _jsx("ul", { className: "anlyx-list", "aria-label": "Endpoint list", children: endpoints.map((endpoint) => (_jsx("li", { className: "anlyx-list-item", children: _jsxs("button", { className: "anlyx-endpoint-button", type: "button", "aria-label": `${endpoint.method} ${endpoint.path} ${formatEndpointHandler(endpoint)}`, "aria-current": endpoint.id === selectedEndpointId ? "true" : undefined, onClick: () => onSelectEndpoint(endpoint), children: [_jsxs("span", { className: "anlyx-list-item__line", children: [_jsx(StatusBadge, { tone: endpoint.method, children: endpoint.method }), _jsx("span", { className: "anlyx-list-item__primary", children: endpoint.path })] }), _jsx("span", { className: "anlyx-list-item__meta", children: formatEndpointHandler(endpoint) })] }) }, endpoint.id))) })] }));
|
|
5
5
|
}
|
|
6
6
|
function formatEndpointHandler(endpoint) {
|
|
7
7
|
if (endpoint.controller && endpoint.handler) {
|
|
@@ -9,4 +9,3 @@ function formatEndpointHandler(endpoint) {
|
|
|
9
9
|
}
|
|
10
10
|
return endpoint.handler ?? endpoint.controller ?? endpoint.id;
|
|
11
11
|
}
|
|
12
|
-
//# sourceMappingURL=EndpointList.js.map
|
|
@@ -4,8 +4,11 @@ export type EndpointMapCanvasProps = {
|
|
|
4
4
|
endpoint: Endpoint | undefined;
|
|
5
5
|
flow: EndpointFlow | undefined;
|
|
6
6
|
selectedNodeId: string | undefined;
|
|
7
|
+
title?: string;
|
|
8
|
+
eyebrow?: string;
|
|
9
|
+
variant?: "structure" | "process";
|
|
7
10
|
replayState?: ReplayLiteState;
|
|
11
|
+
toolbar?: JSX.Element;
|
|
8
12
|
onSelectNode: (node: FlowNode) => void;
|
|
9
13
|
};
|
|
10
|
-
export declare function EndpointMapCanvas({ endpoint, flow, selectedNodeId, replayState, onSelectNode }: EndpointMapCanvasProps): JSX.Element;
|
|
11
|
-
//# sourceMappingURL=EndpointMapCanvas.d.ts.map
|
|
14
|
+
export declare function EndpointMapCanvas({ endpoint, flow, selectedNodeId, title, eyebrow, variant, replayState, toolbar, onSelectNode }: EndpointMapCanvasProps): JSX.Element;
|
|
@@ -1,15 +1,42 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Background, BackgroundVariant, Controls, ReactFlow } from "@xyflow/react";
|
|
3
|
-
import { useMemo } from "react";
|
|
2
|
+
import { Background, BackgroundVariant, BaseEdge, Controls, ReactFlow, getSmoothStepPath } from "@xyflow/react";
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
4
|
import { FlowLegend } from "./FlowLegend.js";
|
|
5
5
|
import { FlowNodeCard } from "./FlowNodeCard.js";
|
|
6
6
|
import { StatusBadge } from "./StatusBadge.js";
|
|
7
7
|
import { buildReactFlowModel } from "../flow/build-react-flow-model.js";
|
|
8
|
+
import { layoutWithElk } from "../flow/layout/elk-layout.js";
|
|
8
9
|
const nodeTypes = {
|
|
9
10
|
anlyxNode: FlowNodeCard
|
|
10
11
|
};
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
const edgeTypes = {
|
|
13
|
+
anlyxEdge: FlowEdgeLine
|
|
14
|
+
};
|
|
15
|
+
export function EndpointMapCanvas({ endpoint, flow, selectedNodeId, title, eyebrow = "Backend Endpoint Map", variant = "structure", replayState, toolbar, onSelectNode }) {
|
|
16
|
+
const fallbackModel = useMemo(() => (flow ? buildReactFlowModel(flow, { variant }) : undefined), [flow, variant]);
|
|
17
|
+
const [layoutedModel, setLayoutedModel] = useState(fallbackModel);
|
|
18
|
+
const model = layoutedModel ?? fallbackModel;
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
let cancelled = false;
|
|
21
|
+
setLayoutedModel(fallbackModel);
|
|
22
|
+
if (!fallbackModel || isUnitTestRuntime()) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
void layoutWithElk(fallbackModel, { variant })
|
|
26
|
+
.then((nextModel) => {
|
|
27
|
+
if (!cancelled) {
|
|
28
|
+
setLayoutedModel(isUsableLayout(nextModel, variant) ? nextModel : fallbackModel);
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
.catch(() => {
|
|
32
|
+
if (!cancelled) {
|
|
33
|
+
setLayoutedModel(fallbackModel);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return () => {
|
|
37
|
+
cancelled = true;
|
|
38
|
+
};
|
|
39
|
+
}, [fallbackModel, variant]);
|
|
13
40
|
const nodes = useMemo(() => model?.nodes.map((node) => ({
|
|
14
41
|
...node,
|
|
15
42
|
selected: node.id === selectedNodeId,
|
|
@@ -21,21 +48,75 @@ export function EndpointMapCanvas({ endpoint, flow, selectedNodeId, replayState,
|
|
|
21
48
|
})) ?? [], [model, onSelectNode, replayState?.activeNodeId, selectedNodeId]);
|
|
22
49
|
const edges = useMemo(() => model?.edges.map((edge) => {
|
|
23
50
|
const isReplayActive = isReplayEdgeActive(edge, replayState?.activeEdge);
|
|
51
|
+
const replayClass = isReplayActive && replayState?.phase === "response"
|
|
52
|
+
? "anlyx-flow-edge--replay-response"
|
|
53
|
+
: isReplayActive
|
|
54
|
+
? "anlyx-flow-edge--replay-request"
|
|
55
|
+
: "";
|
|
24
56
|
const edgeData = edge.data;
|
|
57
|
+
const nextData = {
|
|
58
|
+
edge: edgeData.edge,
|
|
59
|
+
flowRole: edgeData.flowRole,
|
|
60
|
+
...(edgeData.confidence ? { confidence: edgeData.confidence } : {}),
|
|
61
|
+
isReplayActive,
|
|
62
|
+
...(replayState?.phase ? { replayPhase: replayState.phase } : {})
|
|
63
|
+
};
|
|
25
64
|
return {
|
|
26
65
|
...edge,
|
|
27
|
-
className: [
|
|
66
|
+
className: [
|
|
67
|
+
edge.className,
|
|
68
|
+
isReplayActive ? "anlyx-flow-edge--replay-active" : "",
|
|
69
|
+
replayClass
|
|
70
|
+
]
|
|
28
71
|
.filter(Boolean)
|
|
29
72
|
.join(" "),
|
|
30
|
-
data:
|
|
31
|
-
edge: edgeData.edge,
|
|
32
|
-
flowRole: edgeData.flowRole,
|
|
33
|
-
...(edgeData.confidence ? { confidence: edgeData.confidence } : {}),
|
|
34
|
-
isReplayActive
|
|
35
|
-
}
|
|
73
|
+
data: nextData
|
|
36
74
|
};
|
|
37
75
|
}) ?? [], [model, replayState?.activeEdge]);
|
|
38
|
-
return (_jsxs("main", { className:
|
|
76
|
+
return (_jsxs("main", { className: `anlyx-workspace anlyx-workspace--${variant}`, children: [_jsxs("header", { className: "anlyx-workspace-header", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: eyebrow }), _jsx("h1", { children: title ?? (endpoint ? `${endpoint.method} ${endpoint.path}` : "No endpoint selected") })] }), _jsxs("div", { className: "anlyx-workspace-actions", children: [toolbar, endpoint ? (_jsx(StatusBadge, { tone: endpoint.confidence ?? "unknown", children: endpoint.confidence ?? "unknown" })) : null] })] }), _jsx("section", { className: `anlyx-endpoint-map anlyx-endpoint-map--${variant}`, role: "region", "aria-label": variant === "process" ? "Process Flow map" : "Endpoint Map", children: flow && model && model.nodes.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(FlowLegend, { variant: variant }), _jsx("ul", { className: "anlyx-sr-only", "aria-label": "Endpoint map node list", children: nodes.map((node) => (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => onSelectNode(node.data.node), children: ["Select node ", node.data.label] }) }, node.id))) }), _jsx("ul", { className: "anlyx-sr-only", "aria-label": "Replay node state", children: nodes.map((node) => (_jsx("li", { "data-replay-active": String(Boolean(node.data.isReplayActive)), "data-testid": `replay-node-${node.id}`, children: node.id }, node.id))) }), _jsx("ul", { className: "anlyx-sr-only", "aria-label": "Replay edge state", children: edges.map((edge) => (_jsxs("li", { "data-replay-active": String(Boolean(edge.data?.isReplayActive)), "data-testid": `replay-edge-${edge.source}-${edge.target}`, children: [edge.source, " to ", edge.target] }, edge.id))) }), _jsxs(ReactFlow, { className: "anlyx-react-flow", edges: edges, fitView: true, fitViewOptions: { padding: 0.18 }, maxZoom: 1.35, minZoom: 0.62, nodes: nodes, nodesConnectable: false, nodesDraggable: false, edgeTypes: edgeTypes, nodeTypes: nodeTypes, onNodeClick: (_, node) => onSelectNode(node.data.node), panOnScroll: true, proOptions: { hideAttribution: true }, zoomOnDoubleClick: false, zoomOnScroll: false, children: [_jsx(Background, { color: "#dfe5ee", gap: 24, variant: BackgroundVariant.Dots }), _jsx(Controls, { showInteractive: false })] })] })) : (_jsxs("div", { className: "anlyx-endpoint-map-empty", role: "status", "aria-label": "Flow unavailable", children: [_jsx("span", { children: "Flow unavailable" }), _jsx("h2", { children: "No scanned flow for this endpoint yet" }), _jsx("p", { children: "Anlyx can list this endpoint, but no Controller -> Service -> Repository path was found." }), _jsx("p", { children: "Check that the backend source directory is configured, then run `anlyx scan` again." })] })) })] }));
|
|
77
|
+
}
|
|
78
|
+
function isUnitTestRuntime() {
|
|
79
|
+
return typeof process !== "undefined" && process.env.NODE_ENV === "test";
|
|
80
|
+
}
|
|
81
|
+
function isUsableLayout(model, variant) {
|
|
82
|
+
const positions = model.nodes.map((node) => node.position);
|
|
83
|
+
const minY = Math.min(...positions.map((position) => position.y));
|
|
84
|
+
const maxY = Math.max(...positions.map((position) => position.y));
|
|
85
|
+
const minX = Math.min(...positions.map((position) => position.x));
|
|
86
|
+
const maxX = Math.max(...positions.map((position) => position.x));
|
|
87
|
+
const height = maxY - minY;
|
|
88
|
+
const width = maxX - minX;
|
|
89
|
+
if (!Number.isFinite(height) || !Number.isFinite(width)) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const maxHeight = variant === "process" ? 360 : 300;
|
|
93
|
+
return height <= maxHeight && width > 0;
|
|
94
|
+
}
|
|
95
|
+
function FlowEdgeLine({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style, data }) {
|
|
96
|
+
const [edgePath] = getSmoothStepPath({
|
|
97
|
+
sourceX,
|
|
98
|
+
sourceY,
|
|
99
|
+
sourcePosition,
|
|
100
|
+
targetX,
|
|
101
|
+
targetY,
|
|
102
|
+
targetPosition
|
|
103
|
+
});
|
|
104
|
+
const isReplayActive = Boolean(data?.isReplayActive);
|
|
105
|
+
const flowRole = data?.flowRole ?? "secondary";
|
|
106
|
+
const particleRole = data?.replayPhase === "response" ? "response" : flowRole;
|
|
107
|
+
const className = [
|
|
108
|
+
"anlyx-flow-edge",
|
|
109
|
+
`anlyx-flow-edge--${flowRole}`,
|
|
110
|
+
isReplayActive ? "anlyx-flow-edge--replay-active" : "",
|
|
111
|
+
isReplayActive && data?.replayPhase === "response"
|
|
112
|
+
? "anlyx-flow-edge--replay-response"
|
|
113
|
+
: isReplayActive
|
|
114
|
+
? "anlyx-flow-edge--replay-request"
|
|
115
|
+
: ""
|
|
116
|
+
]
|
|
117
|
+
.filter(Boolean)
|
|
118
|
+
.join(" ");
|
|
119
|
+
return (_jsxs(_Fragment, { children: [_jsx(BaseEdge, { className: className, path: edgePath, style: style }), isReplayActive ? (_jsx("circle", { className: `anlyx-flow-particle anlyx-flow-particle--${particleRole}`, r: flowRole === "sub" ? 4 : 5, children: _jsx("animateMotion", { dur: "1.25s", repeatCount: "indefinite", path: edgePath }) })) : null] }));
|
|
39
120
|
}
|
|
40
121
|
function isReplayEdgeActive(edge, activeEdge) {
|
|
41
122
|
if (!activeEdge) {
|
|
@@ -44,4 +125,3 @@ function isReplayEdgeActive(edge, activeEdge) {
|
|
|
44
125
|
return ((edge.source === activeEdge.from && edge.target === activeEdge.to) ||
|
|
45
126
|
(edge.source === activeEdge.to && edge.target === activeEdge.from));
|
|
46
127
|
}
|
|
47
|
-
//# sourceMappingURL=EndpointMapCanvas.js.map
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
export function FlowLegend() {
|
|
2
|
+
export function FlowLegend({ variant = "structure" }) {
|
|
3
|
+
if (variant === "process") {
|
|
4
|
+
return (_jsxs("div", { className: "anlyx-flow-legend", "aria-label": "Flow legend", children: [_jsxs("span", { children: [_jsx("i", { className: "anlyx-flow-legend__mark anlyx-flow-legend__mark--main" }), "Request"] }), _jsxs("span", { children: [_jsx("i", { className: "anlyx-flow-legend__mark anlyx-flow-legend__mark--response" }), "Response"] }), _jsxs("span", { children: [_jsx("i", { className: "anlyx-flow-legend__mark anlyx-flow-legend__mark--sub" }), "Branch calls"] })] }));
|
|
5
|
+
}
|
|
3
6
|
return (_jsxs("div", { className: "anlyx-flow-legend", "aria-label": "Flow legend", children: [_jsxs("span", { children: [_jsx("i", { className: "anlyx-flow-legend__mark anlyx-flow-legend__mark--main" }), "Main Flow"] }), _jsxs("span", { children: [_jsx("i", { className: "anlyx-flow-legend__mark anlyx-flow-legend__mark--sub" }), "Sub Flow"] }), _jsxs("span", { children: [_jsx("i", { className: "anlyx-flow-legend__mark anlyx-flow-legend__mark--unknown" }), "Unknown"] })] }));
|
|
4
7
|
}
|
|
5
|
-
//# sourceMappingURL=FlowLegend.js.map
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type NodeProps } from "@xyflow/react";
|
|
2
2
|
import type { AnlyxReactFlowNode } from "../flow/build-react-flow-model.js";
|
|
3
3
|
export declare function FlowNodeCard({ data, selected }: NodeProps<AnlyxReactFlowNode>): JSX.Element;
|
|
4
|
-
//# sourceMappingURL=FlowNodeCard.d.ts.map
|
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Handle, Position } from "@xyflow/react";
|
|
3
|
+
import { Box, Braces, Code2, Database, GitBranch, Globe2, Layers3 } from "lucide-react";
|
|
4
|
+
import { motion } from "motion/react";
|
|
2
5
|
import { StatusBadge } from "./StatusBadge.js";
|
|
3
6
|
export function FlowNodeCard({ data, selected }) {
|
|
4
7
|
const confidence = data.confidence ?? "unknown";
|
|
5
|
-
|
|
8
|
+
const Icon = getTypeIcon(data.type, data.flowRole);
|
|
9
|
+
return (_jsxs(motion.button, { animate: data.isReplayActive ? { scale: [1, 1.018, 1] } : { scale: 1 }, className: [
|
|
6
10
|
"anlyx-flow-node",
|
|
7
11
|
`anlyx-flow-node--${data.type}`,
|
|
8
12
|
`anlyx-flow-node--${data.flowRole}`,
|
|
9
13
|
data.isReplayActive ? "anlyx-flow-node--replay-active" : "",
|
|
10
14
|
selected ? "anlyx-flow-node--selected" : ""
|
|
11
|
-
].join(" "), onClick: () => data.onSelectNode?.(data.node), type: "button", "aria-label": `Select node ${data.label}`, "data-replay-active": String(Boolean(data.isReplayActive)), "data-testid": `flow-node-${data.node.id}`, children: [_jsx("span", { className: "anlyx-flow-node__type", children: data.type }), _jsx("span", { className: "anlyx-flow-node__label", children: data.label }), _jsx(StatusBadge, { tone: confidence, label: "confidence", children: confidence })] }));
|
|
15
|
+
].join(" "), onClick: () => data.onSelectNode?.(data.node), type: "button", "aria-label": `Select node ${data.label}`, "data-replay-active": String(Boolean(data.isReplayActive)), "data-testid": `flow-node-${data.node.id}`, transition: { duration: 1.35, repeat: data.isReplayActive ? Infinity : 0 }, children: [_jsx(Handle, { className: "anlyx-flow-handle", position: Position.Left, type: "target" }), data.isReplayActive ? _jsx("span", { className: "anlyx-flow-node__pulse", "aria-hidden": "true" }) : null, _jsxs("span", { className: "anlyx-flow-node__header", children: [_jsx("span", { className: "anlyx-flow-node__icon", "aria-hidden": "true", children: _jsx(Icon, { size: 14, strokeWidth: 2.4 }) }), _jsx("span", { className: "anlyx-flow-node__type", children: data.type })] }), _jsx("span", { className: "anlyx-flow-node__label", children: data.label }), _jsx(StatusBadge, { tone: confidence, label: "confidence", children: confidence }), _jsx(Handle, { className: "anlyx-flow-handle", position: Position.Right, type: "source" })] }));
|
|
16
|
+
}
|
|
17
|
+
function getTypeIcon(type, flowRole) {
|
|
18
|
+
if (flowRole === "sub") {
|
|
19
|
+
return GitBranch;
|
|
20
|
+
}
|
|
21
|
+
switch (type) {
|
|
22
|
+
case "endpoint":
|
|
23
|
+
return Globe2;
|
|
24
|
+
case "controller":
|
|
25
|
+
return Code2;
|
|
26
|
+
case "service":
|
|
27
|
+
return Layers3;
|
|
28
|
+
case "repository":
|
|
29
|
+
return Braces;
|
|
30
|
+
case "database":
|
|
31
|
+
return Database;
|
|
32
|
+
default:
|
|
33
|
+
return Box;
|
|
34
|
+
}
|
|
12
35
|
}
|
|
13
|
-
//# sourceMappingURL=FlowNodeCard.js.map
|