@anlyx/ui 0.1.0 → 0.1.2
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/AnlyxAppShell.d.ts +1 -1
- package/dist/components/AnlyxAppShell.js +150 -15
- package/dist/components/ApiCallList.d.ts +0 -1
- package/dist/components/ApiCallList.js +0 -1
- 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/InspectorPanel.d.ts +8 -3
- package/dist/components/InspectorPanel.js +17 -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 +5 -3
- package/dist/components/ProcessFlowView.d.ts +21 -0
- package/dist/components/ProcessFlowView.js +9 -0
- package/dist/components/ProcessTimeline.d.ts +9 -0
- package/dist/components/ProcessTimeline.js +46 -0
- package/dist/components/ReplayControls.d.ts +5 -2
- package/dist/components/ReplayControls.js +4 -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 +6 -3
- 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 +0 -1
- 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 +826 -100
- package/dist/viewer/ViewerApp.d.ts +0 -1
- package/dist/viewer/ViewerApp.js +0 -1
- package/dist/viewer/viewer-entry.d.ts +1 -1
- package/dist/viewer/viewer-entry.js +1 -1
- package/package.json +11 -7
- 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
|
@@ -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 = "structure" | "frontend" | "process";
|
|
5
6
|
export declare function AnlyxAppShell({ data }: AnlyxAppShellProps): JSX.Element;
|
|
6
|
-
//# sourceMappingURL=AnlyxAppShell.d.ts.map
|
|
@@ -1,39 +1,93 @@
|
|
|
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";
|
|
4
5
|
import { InspectorPanel } from "./InspectorPanel.js";
|
|
5
6
|
import { PageStoryboardView } from "./PageStoryboardView.js";
|
|
6
|
-
import {
|
|
7
|
+
import { ProcessFlowView } from "./ProcessFlowView.js";
|
|
7
8
|
import { Sidebar } from "./Sidebar.js";
|
|
8
9
|
import { useReplayLite } from "../replay/use-replay-lite.js";
|
|
10
|
+
const STORAGE_KEYS = {
|
|
11
|
+
leftCollapsed: "anlyx:ui:leftCollapsed",
|
|
12
|
+
panelLayout: "anlyx:ui:panelLayout",
|
|
13
|
+
rightCollapsed: "anlyx:ui:rightCollapsed",
|
|
14
|
+
selectedEndpointId: "anlyx:ui:selectedEndpointId",
|
|
15
|
+
selectedPageId: "anlyx:ui:selectedPageId"
|
|
16
|
+
};
|
|
17
|
+
const DEFAULT_PANEL_LAYOUT = {
|
|
18
|
+
left: 22,
|
|
19
|
+
center: 52,
|
|
20
|
+
right: 26
|
|
21
|
+
};
|
|
9
22
|
export function AnlyxAppShell({ data }) {
|
|
10
|
-
const [activeView, setActiveView] = useState("
|
|
11
|
-
const [selectedEndpointId, setSelectedEndpointId] =
|
|
12
|
-
const [selectedPageId, setSelectedPageId] =
|
|
23
|
+
const [activeView, setActiveView] = useState("structure");
|
|
24
|
+
const [selectedEndpointId, setSelectedEndpointId] = usePersistentString(STORAGE_KEYS.selectedEndpointId, selectInitialEndpointId(data));
|
|
25
|
+
const [selectedPageId, setSelectedPageId] = usePersistentString(STORAGE_KEYS.selectedPageId, data.pages[0]?.id);
|
|
26
|
+
const leftPanelRef = usePanelRef();
|
|
27
|
+
const rightPanelRef = usePanelRef();
|
|
28
|
+
const [leftCollapsed, setLeftCollapsed] = usePersistentBoolean(STORAGE_KEYS.leftCollapsed, false);
|
|
29
|
+
const [rightCollapsed, setRightCollapsed] = usePersistentBoolean(STORAGE_KEYS.rightCollapsed, false);
|
|
30
|
+
const panelLayout = useMemo(() => readPanelLayout(), []);
|
|
31
|
+
const [replaySpeed, setReplaySpeed] = useState(800);
|
|
13
32
|
const selectedEndpoint = data.endpoints.find((endpoint) => endpoint.id === selectedEndpointId) ?? data.endpoints[0];
|
|
14
33
|
const selectedPage = data.pages.find((page) => page.id === selectedPageId) ?? data.pages[0];
|
|
15
34
|
const selectedFlow = useMemo(() => data.flows.find((flow) => flow.endpointId === selectedEndpoint?.id), [data.flows, selectedEndpoint?.id]);
|
|
16
35
|
const replayMainPath = useMemo(() => getReplayMainPath(selectedFlow, selectedEndpoint?.id), [selectedEndpoint?.id, selectedFlow]);
|
|
17
|
-
const replay = useReplayLite({ mainPath: replayMainPath });
|
|
36
|
+
const replay = useReplayLite({ intervalMs: replaySpeed, mainPath: replayMainPath });
|
|
18
37
|
const replayUnavailable = replayMainPath.length === 0;
|
|
19
38
|
const [selectedNodeId, setSelectedNodeId] = useState(() => findDefaultNode(selectedFlow)?.id);
|
|
20
39
|
const selectedNode = findFlowNode(selectedFlow, selectedNodeId) ?? findDefaultNode(selectedFlow);
|
|
21
40
|
useEffect(() => {
|
|
22
|
-
setSelectedEndpointId((current) => data.endpoints.some((endpoint) => endpoint.id === current)
|
|
23
|
-
|
|
41
|
+
setSelectedEndpointId((current) => data.endpoints.some((endpoint) => endpoint.id === current)
|
|
42
|
+
? current
|
|
43
|
+
: selectInitialEndpointId(data));
|
|
44
|
+
}, [data, setSelectedEndpointId]);
|
|
24
45
|
useEffect(() => {
|
|
25
46
|
setSelectedPageId((current) => data.pages.some((page) => page.id === current) ? current : data.pages[0]?.id);
|
|
26
47
|
}, [data.pages]);
|
|
27
48
|
useEffect(() => {
|
|
28
49
|
setSelectedNodeId(findDefaultNode(selectedFlow)?.id);
|
|
29
50
|
}, [selectedFlow]);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (leftCollapsed) {
|
|
53
|
+
leftPanelRef.current?.collapse();
|
|
54
|
+
}
|
|
55
|
+
}, [leftCollapsed, leftPanelRef]);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (rightCollapsed) {
|
|
58
|
+
rightPanelRef.current?.collapse();
|
|
59
|
+
}
|
|
60
|
+
}, [rightCollapsed, rightPanelRef]);
|
|
61
|
+
const toggleLeftPanel = () => {
|
|
62
|
+
if (leftCollapsed) {
|
|
63
|
+
leftPanelRef.current?.expand();
|
|
64
|
+
setLeftCollapsed(false);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
leftPanelRef.current?.collapse();
|
|
68
|
+
setLeftCollapsed(true);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const toggleRightPanel = () => {
|
|
72
|
+
if (rightCollapsed) {
|
|
73
|
+
rightPanelRef.current?.expand();
|
|
74
|
+
setRightCollapsed(false);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
rightPanelRef.current?.collapse();
|
|
78
|
+
setRightCollapsed(true);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
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) => {
|
|
82
|
+
setSelectedEndpointId(endpoint.id);
|
|
83
|
+
setActiveView("structure");
|
|
84
|
+
}, onSelectPage: (page) => {
|
|
85
|
+
setSelectedPageId(page.id);
|
|
86
|
+
setActiveView("frontend");
|
|
87
|
+
} }) }), _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 === "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] })] }));
|
|
88
|
+
}
|
|
89
|
+
function StructureToolbar() {
|
|
90
|
+
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
91
|
}
|
|
38
92
|
function getReplayMainPath(flow, endpointId) {
|
|
39
93
|
if (!flow || flow.mainPath.length === 0) {
|
|
@@ -45,6 +99,20 @@ function getReplayMainPath(flow, endpointId) {
|
|
|
45
99
|
}
|
|
46
100
|
return flow.mainPath;
|
|
47
101
|
}
|
|
102
|
+
function selectInitialEndpointId(data) {
|
|
103
|
+
const flowByEndpointId = new Map(data.flows.map((flow) => [flow.endpointId, flow]));
|
|
104
|
+
const endpointsByScore = [...data.endpoints].sort((left, right) => scoreEndpoint(right, flowByEndpointId) - scoreEndpoint(left, flowByEndpointId));
|
|
105
|
+
return endpointsByScore[0]?.id;
|
|
106
|
+
}
|
|
107
|
+
function scoreEndpoint(endpoint, flowByEndpointId) {
|
|
108
|
+
const flow = flowByEndpointId.get(endpoint.id);
|
|
109
|
+
const hasDatabase = flow?.nodes.some((node) => node.type === "database") ? 1 : 0;
|
|
110
|
+
const confidenceScore = endpoint.confidence === "high" ? 4 : endpoint.confidence === "medium" ? 2 : 0;
|
|
111
|
+
return ((flow?.mainPath.length ?? 0) * 8 +
|
|
112
|
+
(flow?.subFlows.length ?? 0) * 5 +
|
|
113
|
+
hasDatabase * 6 +
|
|
114
|
+
confidenceScore);
|
|
115
|
+
}
|
|
48
116
|
function findDefaultNode(flow) {
|
|
49
117
|
if (!flow) {
|
|
50
118
|
return undefined;
|
|
@@ -58,4 +126,71 @@ function findFlowNode(flow, nodeId) {
|
|
|
58
126
|
return (flow.nodes.find((node) => node.id === nodeId) ??
|
|
59
127
|
flow.subFlows.flatMap((subFlow) => subFlow.nodes).find((node) => node.id === nodeId));
|
|
60
128
|
}
|
|
61
|
-
|
|
129
|
+
function usePersistentBoolean(key, defaultValue) {
|
|
130
|
+
const [value, setValue] = useState(() => {
|
|
131
|
+
const storedValue = readLocalStorage(key);
|
|
132
|
+
if (storedValue === "true") {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
if (storedValue === "false") {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
return defaultValue;
|
|
139
|
+
});
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
writeLocalStorage(key, String(value));
|
|
142
|
+
}, [key, value]);
|
|
143
|
+
return [value, setValue];
|
|
144
|
+
}
|
|
145
|
+
function usePersistentString(key, fallback) {
|
|
146
|
+
const [value, setValue] = useState(() => {
|
|
147
|
+
if (typeof window === "undefined") {
|
|
148
|
+
return fallback;
|
|
149
|
+
}
|
|
150
|
+
return window.localStorage.getItem(key) ?? fallback;
|
|
151
|
+
});
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (typeof window === "undefined") {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (value) {
|
|
157
|
+
window.localStorage.setItem(key, value);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
window.localStorage.removeItem(key);
|
|
161
|
+
}
|
|
162
|
+
}, [key, value]);
|
|
163
|
+
return [value, setValue];
|
|
164
|
+
}
|
|
165
|
+
function readLocalStorage(key) {
|
|
166
|
+
if (typeof window === "undefined") {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return window.localStorage.getItem(key);
|
|
170
|
+
}
|
|
171
|
+
function writeLocalStorage(key, value) {
|
|
172
|
+
if (typeof window === "undefined") {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
window.localStorage.setItem(key, value);
|
|
176
|
+
}
|
|
177
|
+
function readPanelLayout() {
|
|
178
|
+
const storedValue = readLocalStorage(STORAGE_KEYS.panelLayout);
|
|
179
|
+
if (!storedValue) {
|
|
180
|
+
return DEFAULT_PANEL_LAYOUT;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const parsedValue = JSON.parse(storedValue);
|
|
184
|
+
return {
|
|
185
|
+
left: sanitizePanelSize(parsedValue.left, DEFAULT_PANEL_LAYOUT.left),
|
|
186
|
+
center: sanitizePanelSize(parsedValue.center, DEFAULT_PANEL_LAYOUT.center),
|
|
187
|
+
right: sanitizePanelSize(parsedValue.right, DEFAULT_PANEL_LAYOUT.right)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return DEFAULT_PANEL_LAYOUT;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function sanitizePanelSize(value, fallback) {
|
|
195
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
196
|
+
}
|
|
@@ -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 })] })] })) : (_jsx("div", { className: "anlyx-endpoint-map-empty", children: _jsx("p", { children: "No flow available for this endpoint yet." }) })) })] }));
|
|
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
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import type { EndpointFlow, FlowNode, ScanResult } from "@anlyx/core";
|
|
1
|
+
import type { EndpointFlow, FlowNode, PageStoryboard, ScanResult } from "@anlyx/core";
|
|
2
|
+
import type { ReplayLiteState } from "../replay/use-replay-lite.js";
|
|
2
3
|
type InspectorPanelProps = {
|
|
3
4
|
data: ScanResult;
|
|
5
|
+
activeView: "structure" | "frontend" | "process";
|
|
6
|
+
collapsed: boolean;
|
|
4
7
|
selectedFlow: EndpointFlow | undefined;
|
|
5
8
|
selectedNode: FlowNode | undefined;
|
|
9
|
+
selectedPage: PageStoryboard | undefined;
|
|
10
|
+
replayState: ReplayLiteState;
|
|
11
|
+
onToggleCollapsed: () => void;
|
|
6
12
|
};
|
|
7
|
-
export declare function InspectorPanel({ data, selectedFlow, selectedNode }: InspectorPanelProps): JSX.Element;
|
|
13
|
+
export declare function InspectorPanel({ data, activeView, collapsed, selectedFlow, selectedNode, selectedPage, replayState, onToggleCollapsed }: InspectorPanelProps): JSX.Element;
|
|
8
14
|
export {};
|
|
9
|
-
//# sourceMappingURL=InspectorPanel.d.ts.map
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { StatusBadge } from "./StatusBadge.js";
|
|
3
|
-
export function InspectorPanel({ data, selectedFlow, selectedNode }) {
|
|
3
|
+
export function InspectorPanel({ data, activeView, collapsed, selectedFlow, selectedNode, selectedPage, replayState, onToggleCollapsed }) {
|
|
4
|
+
if (collapsed) {
|
|
5
|
+
return (_jsxs("aside", { className: "anlyx-inspector anlyx-inspector--collapsed", role: "complementary", "aria-label": "Inspector", children: [_jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Expand inspector panel", onClick: onToggleCollapsed, children: "Open" }), _jsx("span", { className: "anlyx-collapsed-label", children: "Inspector" })] }));
|
|
6
|
+
}
|
|
7
|
+
if (activeView === "frontend") {
|
|
8
|
+
return (_jsxs("aside", { className: "anlyx-inspector", role: "complementary", "aria-label": "Inspector", children: [_jsxs("div", { className: "anlyx-panel-heading", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Inspector" }), _jsx("h2", { children: "Frontend Page" })] }), _jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Collapse inspector panel", onClick: onToggleCollapsed, children: "Collapse" })] }), selectedPage ? (_jsxs("div", { className: "anlyx-inspector-stack", children: [_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Details", children: [_jsx("h3", { children: "Details" }), _jsx(Field, { label: "Route", value: selectedPage.route }), _jsx(Field, { label: "File path", value: selectedPage.filePath ?? "Manual or unknown" }), _jsxs("div", { className: "anlyx-field", children: [_jsx("span", { className: "anlyx-field__label", children: "Capture status" }), _jsx(StatusBadge, { tone: selectedPage.captureStatus, children: selectedPage.captureStatus })] }), _jsx(Field, { label: "Screenshots", value: String(selectedPage.screenshots.length) }), _jsx(Field, { label: "API calls", value: String(selectedPage.apiCalls.length) })] }), selectedPage.errorMessage ? (_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Capture error", children: [_jsx("h3", { children: "Capture error" }), _jsx("p", { children: selectedPage.errorMessage })] })) : null] })) : (_jsx("p", { className: "anlyx-empty", children: "No page selected" }))] }));
|
|
9
|
+
}
|
|
4
10
|
const linkedPages = selectedNode ? findLinkedPages(data, selectedNode.id) : [];
|
|
5
|
-
return (_jsxs("aside", { className: "anlyx-inspector", role: "complementary", "aria-label": "Inspector", children: [_jsxs("div", { className: "anlyx-panel-heading", children: [_jsx("p", { className: "anlyx-eyebrow", children: "Inspector" }), _jsx("h2", { children: "
|
|
11
|
+
return (_jsxs("aside", { className: "anlyx-inspector", role: "complementary", "aria-label": "Inspector", children: [_jsxs("div", { className: "anlyx-panel-heading", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Inspector" }), _jsx("h2", { children: activeView === "process" ? "Process Step" : "Backend Node" })] }), _jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Collapse inspector panel", onClick: onToggleCollapsed, children: "Collapse" })] }), selectedNode ? (_jsxs("div", { className: "anlyx-inspector-stack", children: [activeView === "process" ? (_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Replay state", children: [_jsx("h3", { children: "Process replay" }), _jsx(Field, { label: "Active Step", value: replayState.phase }), _jsx(Field, { label: "Step", value: String(replayState.currentStepIndex + 1) }), _jsx(Field, { label: "Active Node", value: replayState.activeNodeId ?? "none" }), _jsx(Field, { label: "Active Edge", value: formatActiveEdge(replayState) }), _jsx("p", { className: "anlyx-inspector-note", children: "Source: scanned static flow graph" })] })) : null, _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Details", children: [_jsx("h3", { children: "Details" }), _jsx(Field, { label: "Type", value: selectedNode.type }), _jsx(Field, { label: "Label", value: selectedNode.label }), _jsx(Field, { label: "File path", value: selectedNode.filePath ?? "Unknown" }), _jsx(Field, { label: "Line number", value: formatLineNumber(selectedNode.lineNumber) })] }), selectedNode.metadata ? (_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Metadata", children: [_jsxs("div", { className: "anlyx-inspector-group__heading", children: [_jsx("h3", { children: "Metadata" }), _jsx("button", { className: "anlyx-copy-button", type: "button", onClick: () => copyToClipboard(JSON.stringify(selectedNode.metadata, null, 2)), children: "Copy" })] }), _jsx("pre", { className: "anlyx-metadata", children: JSON.stringify(selectedNode.metadata, null, 2) })] })) : null, _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Confidence", children: [_jsx("h3", { children: "Confidence" }), _jsx(StatusBadge, { tone: selectedNode.confidence ?? "unknown", label: "confidence", children: selectedNode.confidence ?? "unknown" })] }), _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Linked pages", children: [_jsx("h3", { children: "Linked pages" }), linkedPages.length > 0 ? (_jsx("ul", { children: linkedPages.map((page) => (_jsx("li", { children: page.route }, page.id))) })) : (_jsx("p", { children: "None" }))] }), _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Sub flows", children: [_jsx("h3", { children: "Sub flows" }), _jsxs("p", { children: [selectedFlow?.subFlows.length ?? 0, " collapsed"] })] }), _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "DB tables", children: [_jsx("h3", { children: "DB tables" }), _jsx("p", { children: findDatabaseLabel(selectedFlow) ?? "None" })] })] })) : (_jsx("p", { className: "anlyx-empty", children: "No node selected" }))] }));
|
|
6
12
|
}
|
|
7
13
|
function Field({ label, value }) {
|
|
8
14
|
return (_jsxs("div", { className: "anlyx-field", children: [_jsx("span", { className: "anlyx-field__label", children: label }), _jsx("span", { className: "anlyx-field__value", children: value })] }));
|
|
@@ -10,10 +16,18 @@ function Field({ label, value }) {
|
|
|
10
16
|
function formatLineNumber(lineNumber) {
|
|
11
17
|
return lineNumber === undefined ? "Unknown" : String(lineNumber);
|
|
12
18
|
}
|
|
19
|
+
function formatActiveEdge(replayState) {
|
|
20
|
+
if (!replayState.activeEdge) {
|
|
21
|
+
return "none";
|
|
22
|
+
}
|
|
23
|
+
return `${replayState.activeEdge.from} -> ${replayState.activeEdge.to}`;
|
|
24
|
+
}
|
|
25
|
+
function copyToClipboard(value) {
|
|
26
|
+
void navigator.clipboard?.writeText(value);
|
|
27
|
+
}
|
|
13
28
|
function findLinkedPages(data, nodeId) {
|
|
14
29
|
return data.pages.filter((page) => page.apiCalls.some((apiCall) => apiCall.endpointId === nodeId));
|
|
15
30
|
}
|
|
16
31
|
function findDatabaseLabel(flow) {
|
|
17
32
|
return flow?.nodes.find((node) => node.type === "database")?.label;
|
|
18
33
|
}
|
|
19
|
-
//# sourceMappingURL=InspectorPanel.js.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { StatusBadge } from "./StatusBadge.js";
|
|
3
3
|
export function PageList({ pages, selectedPageId, onSelectPage }) {
|
|
4
|
-
return (_jsxs("section", { className: "anlyx-sidebar-section", "aria-labelledby": "anlyx-pages-heading", children: [_jsx("div", { className: "anlyx-section-heading", id: "anlyx-pages-heading", children: "Pages" }), _jsx("ul", { className: "anlyx-list", "aria-label": "Page list", children: pages.map((page) => (_jsx("li", { className: "anlyx-list-item", children: _jsxs("button", { className: "anlyx-page-button", type: "button", "aria-current": page.id === selectedPageId ? "true" : undefined, "aria-label": `${page.route} ${page.captureStatus} ${page.apiCalls.length} API calls ${page.screenshots.length} screenshots`, onClick: () => onSelectPage(page), children: [_jsx("span", { className: "anlyx-list-item__line", children: _jsx("span", { className: "anlyx-list-item__primary", children: page.route }) }), _jsxs("span", { className: "anlyx-list-item__meta", children: [_jsx(StatusBadge, { tone: page.captureStatus, children: page.captureStatus }), _jsxs("span", { children: [page.apiCalls.length, " API calls"] }), _jsxs("span", { children: [page.screenshots.length, " screenshots"] })] }), page.errorMessage ? (_jsx("span", { className: "anlyx-list-item__meta", children: page.errorMessage })) : null] }) }, page.id))) })] }));
|
|
4
|
+
return (_jsxs("section", { className: "anlyx-sidebar-section", "aria-labelledby": "anlyx-pages-heading", children: [_jsx("div", { className: "anlyx-section-heading", id: "anlyx-pages-heading", children: "Frontend Pages" }), _jsx("ul", { className: "anlyx-list", "aria-label": "Page list", children: pages.map((page) => (_jsx("li", { className: "anlyx-list-item", children: _jsxs("button", { className: "anlyx-page-button", type: "button", "aria-current": page.id === selectedPageId ? "true" : undefined, "aria-label": `${page.route} ${page.captureStatus} ${page.apiCalls.length} API calls ${page.screenshots.length} screenshots`, onClick: () => onSelectPage(page), children: [_jsx("span", { className: "anlyx-list-item__line", children: _jsx("span", { className: "anlyx-list-item__primary", children: page.route }) }), _jsxs("span", { className: "anlyx-list-item__meta", children: [_jsx(StatusBadge, { tone: page.captureStatus, children: page.captureStatus }), _jsxs("span", { children: [page.apiCalls.length, " API calls"] }), _jsxs("span", { children: [page.screenshots.length, " screenshots"] })] }), page.errorMessage ? (_jsx("span", { className: "anlyx-list-item__meta", children: page.errorMessage })) : null] }) }, page.id))) })] }));
|
|
5
5
|
}
|
|
6
|
-
//# sourceMappingURL=PageList.js.map
|