@anlyx/ui 0.1.2 → 0.1.5
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/README.md +3 -2
- package/dist/capture/capture-runtime.d.ts +14 -0
- package/dist/capture/capture-runtime.js +300 -0
- 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 +16 -7
- package/dist/components/ApiCallList.d.ts +3 -2
- package/dist/components/ApiCallList.js +12 -2
- package/dist/components/CaptureStatusEmptyState.js +2 -2
- package/dist/components/EndpointMapCanvas.js +1 -1
- package/dist/components/FlowStoryView.d.ts +22 -0
- package/dist/components/FlowStoryView.js +117 -0
- package/dist/components/InspectorPanel.d.ts +1 -1
- package/dist/components/InspectorPanel.js +46 -1
- package/dist/components/PageStoryboardView.js +9 -1
- package/dist/components/ProcessFlowView.js +8 -1
- package/dist/components/ReplayControls.d.ts +2 -1
- package/dist/components/ReplayControls.js +29 -2
- package/dist/components/Sidebar.d.ts +2 -2
- package/dist/components/Sidebar.js +15 -3
- package/dist/components/StatusBadge.d.ts +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/mock-data.js +50 -4
- 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 +15 -0
- package/dist/readme-demo/ReadmeDemoApp.js +184 -0
- package/dist/readme-demo/readme-demo-entry.d.ts +1 -0
- package/dist/readme-demo/readme-demo-entry.js +8 -0
- package/dist/styles.css +1165 -38
- package/dist/viewer/ViewerApp.js +26 -16
- package/dist/viewer/styles.css +2639 -0
- package/dist/viewer/viewer-entry.d.ts +1 -0
- package/dist/viewer/viewer-entry.js +1 -0
- package/dist/viewer/workspace/anlyx-logo-transparent.png +0 -0
- package/dist/viewer/workspace/workspace.css +6354 -0
- package/dist/workspace/ScanTreeMap.d.ts +6 -0
- package/dist/workspace/ScanTreeMap.js +838 -0
- package/dist/workspace/WorkspaceApp.d.ts +8 -0
- package/dist/workspace/WorkspaceApp.js +2293 -0
- package/dist/workspace/project-view-model.d.ts +63 -0
- package/dist/workspace/project-view-model.js +170 -0
- package/dist/workspace/workspace.css +6354 -0
- package/package.json +10 -3
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Endpoint, EndpointFlow, PageStoryboard } from "@anlyx/core";
|
|
2
|
+
export type OverlayAction = {
|
|
3
|
+
id?: string;
|
|
4
|
+
type?: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
selector?: string;
|
|
7
|
+
};
|
|
8
|
+
export type OverlayApiEvent = {
|
|
9
|
+
id: string;
|
|
10
|
+
method: string;
|
|
11
|
+
path: string;
|
|
12
|
+
status: string | number;
|
|
13
|
+
durationMs: number;
|
|
14
|
+
count?: number;
|
|
15
|
+
lastSeenAt?: number;
|
|
16
|
+
source?: "action" | "background" | "health";
|
|
17
|
+
triggeredBy?: OverlayAction | null;
|
|
18
|
+
matchedEndpoint?: Endpoint | null;
|
|
19
|
+
matchedFlow?: EndpointFlow | null;
|
|
20
|
+
matchedPages?: PageStoryboard[];
|
|
21
|
+
};
|
|
22
|
+
export type OverlayScannedHint = {
|
|
23
|
+
pageRoute: string;
|
|
24
|
+
pageFilePath?: string;
|
|
25
|
+
method: string;
|
|
26
|
+
path: string;
|
|
27
|
+
endpointId?: string;
|
|
28
|
+
endpointLabel?: string;
|
|
29
|
+
evidence: "scanned-page" | "capture";
|
|
30
|
+
};
|
|
31
|
+
export type FlowDrawerProps = {
|
|
32
|
+
selectedEvent: OverlayApiEvent | null;
|
|
33
|
+
events: OverlayApiEvent[];
|
|
34
|
+
latestAction?: OverlayAction | null;
|
|
35
|
+
scannedHints?: OverlayScannedHint[];
|
|
36
|
+
loadError?: string | null;
|
|
37
|
+
runtimeBaseUrl?: string;
|
|
38
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export declare function Card({ children, className }: {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
className?: string;
|
|
5
|
+
}): JSX.Element;
|
|
6
|
+
export declare function Badge({ children, tone, className }: {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
tone?: "blue" | "green" | "amber" | "violet" | "gray" | "neutral";
|
|
9
|
+
className?: string;
|
|
10
|
+
}): JSX.Element;
|
|
11
|
+
export declare function Tooltip({ children, content }: {
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
content: string;
|
|
14
|
+
}): JSX.Element;
|
|
15
|
+
export declare function Table({ children, className }: {
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
}): JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
export function Card({ children, className = "" }) {
|
|
3
|
+
return _jsx("div", { className: `anlyx-ov-card ${className}`.trim(), children: children });
|
|
4
|
+
}
|
|
5
|
+
export function Badge({ children, tone = "neutral", className = "" }) {
|
|
6
|
+
return (_jsx("span", { className: `anlyx-ov-badge anlyx-ov-badge--${tone} ${className}`.trim(), children: children }));
|
|
7
|
+
}
|
|
8
|
+
export function Tooltip({ children, content }) {
|
|
9
|
+
return _jsx("span", { title: content, children: children });
|
|
10
|
+
}
|
|
11
|
+
export function Table({ children, className = "" }) {
|
|
12
|
+
return _jsx("table", { className: `anlyx-ov-table ${className}`.trim(), children: children });
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import "@xyflow/react/dist/style.css";
|
|
2
|
+
import "../overlay/overlay.css";
|
|
3
|
+
import "./readme-demo.css";
|
|
4
|
+
import type { Endpoint, EndpointFlow } from "@anlyx/core";
|
|
5
|
+
export type FixtureDetailFlow = {
|
|
6
|
+
endpoint: Endpoint;
|
|
7
|
+
flow: EndpointFlow;
|
|
8
|
+
};
|
|
9
|
+
export type ReadmeDemoAppProps = {
|
|
10
|
+
eyebrow?: string;
|
|
11
|
+
fixtureDetail?: FixtureDetailFlow | null;
|
|
12
|
+
iconSrc?: string;
|
|
13
|
+
logoSrc?: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function ReadmeDemoApp({ eyebrow, fixtureDetail: fixtureDetailProp, iconSrc, logoSrc }?: ReadmeDemoAppProps): JSX.Element;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import "@xyflow/react/dist/style.css";
|
|
3
|
+
import "../overlay/overlay.css";
|
|
4
|
+
import "./readme-demo.css";
|
|
5
|
+
import { useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { FlowDrawer } from "../overlay/FlowDrawer.js";
|
|
7
|
+
const demoOrder = ["detail", "search", "save", "admin"];
|
|
8
|
+
const fixtureEndpointsUrl = "/fixtures/spring-next-sample/expected/endpoints.json";
|
|
9
|
+
const fixtureFlowsUrl = "/fixtures/spring-next-sample/expected/flows.json";
|
|
10
|
+
const defaultLogoSrc = "/docs/assets/brand/anlyx-logo-transparent.png";
|
|
11
|
+
const defaultIconSrc = "/docs/assets/brand/anlyx-icon-transparent.png";
|
|
12
|
+
const flows = {
|
|
13
|
+
search: makeFlow("GET", "/api/public/search", [
|
|
14
|
+
["endpoint:search", "endpoint", "GET /api/public/search"],
|
|
15
|
+
["controller:search", "controller", "PublicViewController#search"],
|
|
16
|
+
["service:search", "service", "SearchIndexService#find"],
|
|
17
|
+
["repository:search", "repository", "BenefitSearchRepository#query"],
|
|
18
|
+
["database:search", "database", "benefits_search_index"]
|
|
19
|
+
]),
|
|
20
|
+
detail: makeFlow("GET", "/api/public/benefits/{id}", [
|
|
21
|
+
["endpoint:detail", "endpoint", "GET /api/public/benefits/{id}"],
|
|
22
|
+
["controller:detail", "controller", "PublicBenefitController#getDetail"],
|
|
23
|
+
["service:detail", "service", "PublicBenefitService#getDetail"],
|
|
24
|
+
["repository:detail", "repository", "BenefitRepository#findById"],
|
|
25
|
+
["database:detail", "database", "benefits"]
|
|
26
|
+
]),
|
|
27
|
+
save: makeFlow("POST", "/api/account/saved-benefits", [
|
|
28
|
+
["endpoint:save", "endpoint", "POST /api/account/saved-benefits"],
|
|
29
|
+
["controller:save", "controller", "SavedBenefitController#create"],
|
|
30
|
+
["service:save", "service", "SavedBenefitService#save"],
|
|
31
|
+
["repository:save", "repository", "SavedBenefitRepository#insert"],
|
|
32
|
+
["database:save", "database", "saved_benefit"]
|
|
33
|
+
]),
|
|
34
|
+
admin: makeFlow("POST", "/api/admin/benefits", [
|
|
35
|
+
["endpoint:admin", "endpoint", "POST /api/admin/benefits"],
|
|
36
|
+
["controller:admin", "controller", "AdminBenefitController#create"],
|
|
37
|
+
["policy:admin", "validator", "AdminRolePolicy#check"],
|
|
38
|
+
["service:admin", "service", "AdminBenefitService#create"],
|
|
39
|
+
["repository:admin", "repository", "BenefitRepository#save"]
|
|
40
|
+
])
|
|
41
|
+
};
|
|
42
|
+
const endpoints = {
|
|
43
|
+
search: endpoint("GET", "/api/public/search", "PublicViewController#search"),
|
|
44
|
+
detail: endpoint("GET", "/api/public/benefits/{id}", "PublicBenefitController#getDetail"),
|
|
45
|
+
save: endpoint("POST", "/api/account/saved-benefits", "SavedBenefitController#create"),
|
|
46
|
+
admin: endpoint("POST", "/api/admin/benefits", "AdminBenefitController#create")
|
|
47
|
+
};
|
|
48
|
+
const baseEvents = {
|
|
49
|
+
search: event("search", "GET", "/api/public/search", 200, 28, "Clicked Search benefits"),
|
|
50
|
+
detail: event("detail", "GET", "/api/public/benefits/123", 200, 34, "Opened benefit detail"),
|
|
51
|
+
save: event("save", "POST", "/api/account/saved-benefits", 401, 31, "Clicked Save to my box"),
|
|
52
|
+
admin: event("admin", "POST", "/api/admin/benefits", 403, 42, "Clicked Try admin action")
|
|
53
|
+
};
|
|
54
|
+
export function ReadmeDemoApp({ eyebrow = "Real UI preview", fixtureDetail: fixtureDetailProp, iconSrc = defaultIconSrc, logoSrc = defaultLogoSrc } = {}) {
|
|
55
|
+
const [selectedKey, setSelectedKey] = useState("detail");
|
|
56
|
+
const fetchedFixtureDetail = useFixtureDetailFlow(fixtureDetailProp !== undefined);
|
|
57
|
+
const fixtureDetail = fixtureDetailProp ?? fetchedFixtureDetail;
|
|
58
|
+
const hydratedEvents = useMemo(() => Object.fromEntries(demoOrder.map((key) => [key, hydrateEvent(key, key === "detail" ? fixtureDetail : null)])), [fixtureDetail]);
|
|
59
|
+
const selectedEvent = hydratedEvents[selectedKey];
|
|
60
|
+
const eventList = useMemo(() => [
|
|
61
|
+
selectedEvent,
|
|
62
|
+
{
|
|
63
|
+
id: "background-account",
|
|
64
|
+
method: "GET",
|
|
65
|
+
path: "/api/account/me",
|
|
66
|
+
status: 401,
|
|
67
|
+
durationMs: 19,
|
|
68
|
+
count: 3,
|
|
69
|
+
source: "background",
|
|
70
|
+
matchedEndpoint: endpoint("GET", "/api/account/me", "AccountController#me"),
|
|
71
|
+
matchedFlow: null
|
|
72
|
+
},
|
|
73
|
+
...demoOrder.filter((key) => key !== selectedKey).map((key) => hydratedEvents[key])
|
|
74
|
+
], [hydratedEvents, selectedEvent, selectedKey]);
|
|
75
|
+
return (_jsxs("main", { className: "anlyx-readme-demo", children: [_jsxs("section", { className: "anlyx-readme-demo__control", children: [_jsxs("div", { className: "anlyx-readme-demo__intro", children: [_jsx("img", { src: logoSrc, alt: "Anlyx" }), _jsxs("div", { children: [_jsx("p", { className: "anlyx-readme-demo__eyebrow", children: eyebrow }), _jsx("h1", { children: "Click an action. See the backend path." }), _jsx("p", { children: "Rendered by the same Flow Drawer component used by the Anlyx overlay." })] })] }), _jsxs("div", { className: "anlyx-readme-demo__actions", "aria-label": "Demo actions", children: [_jsxs("div", { className: "anlyx-readme-demo__actions-head", children: [_jsx("span", { children: "Demo buttons" }), _jsx("small", { children: "Pick one request" })] }), _jsx(DemoButton, { active: selectedKey === "search", index: "01", method: "GET", label: "Search benefits", meta: "GET /api/public/search", onClick: () => setSelectedKey("search"), demoKey: "search" }), _jsx(DemoButton, { active: selectedKey === "detail", index: "02", method: "GET", label: "Open benefit detail", meta: "GET /api/public/benefits/{id}", onClick: () => setSelectedKey("detail"), demoKey: "detail" }), _jsx(DemoButton, { active: selectedKey === "save", index: "03", method: "POST", label: "Save to my box", meta: "POST /api/account/saved-benefits", onClick: () => setSelectedKey("save"), demoKey: "save" }), _jsx(DemoButton, { active: selectedKey === "admin", index: "04", method: "POST", label: "Try admin action", meta: "POST /api/admin/benefits", onClick: () => setSelectedKey("admin"), demoKey: "admin" })] })] }), _jsxs("section", { className: "anlyx-readme-demo__drawer-shell", "aria-label": "Anlyx Flow Drawer", children: [_jsxs("header", { className: "anlyx-readme-demo__drawer-head", children: [_jsxs("div", { className: "anlyx-readme-demo__drawer-brand", children: [_jsx("img", { src: iconSrc, alt: "" }), _jsxs("div", { children: [_jsx("strong", { children: "Anlyx Flow Drawer" }), _jsx("span", { children: "Actual component preview" })] })] }), _jsxs("div", { className: "anlyx-readme-demo__drawer-tools", children: [_jsx("span", { children: "Live" }), _jsx("span", { children: "fixture-backed" })] })] }), _jsx(FlowDrawer, { events: eventList, latestAction: selectedEvent.triggeredBy ?? null, scannedHints: [], selectedEvent: selectedEvent })] })] }));
|
|
76
|
+
}
|
|
77
|
+
function useFixtureDetailFlow(skip) {
|
|
78
|
+
const [fixtureDetail, setFixtureDetail] = useState(null);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (skip) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const controller = new AbortController();
|
|
84
|
+
const loadFixture = async () => {
|
|
85
|
+
try {
|
|
86
|
+
const [endpointResponse, flowResponse] = await Promise.all([
|
|
87
|
+
fetch(fixtureEndpointsUrl, { signal: controller.signal }),
|
|
88
|
+
fetch(fixtureFlowsUrl, { signal: controller.signal })
|
|
89
|
+
]);
|
|
90
|
+
if (!endpointResponse.ok || !flowResponse.ok) {
|
|
91
|
+
throw new Error("README fixture request failed");
|
|
92
|
+
}
|
|
93
|
+
const fixtureEndpoints = (await endpointResponse.json());
|
|
94
|
+
const fixtureFlows = (await flowResponse.json());
|
|
95
|
+
const flow = fixtureFlows.find((candidate) => candidate.endpointId === "endpoint:get:/api/public/benefits/{id}");
|
|
96
|
+
const endpoint = fixtureEndpoints.find((candidate) => candidate.id === "endpoint:get:/api/public/benefits/{id}");
|
|
97
|
+
if (flow && endpoint) {
|
|
98
|
+
setFixtureDetail({ endpoint, flow: toBackendOnlyFlow(flow) });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (!controller.signal.aborted) {
|
|
103
|
+
console.warn("[anlyx] Falling back to embedded README demo data.", error);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
void loadFixture();
|
|
108
|
+
return () => controller.abort();
|
|
109
|
+
}, [skip]);
|
|
110
|
+
return fixtureDetail;
|
|
111
|
+
}
|
|
112
|
+
function toBackendOnlyFlow(flow) {
|
|
113
|
+
const backendNodeIds = new Set(flow.nodes.filter((node) => node.type !== "page").map((node) => node.id));
|
|
114
|
+
return {
|
|
115
|
+
...flow,
|
|
116
|
+
nodes: flow.nodes.filter((node) => backendNodeIds.has(node.id)),
|
|
117
|
+
edges: flow.edges.filter((edge) => backendNodeIds.has(edge.from) && backendNodeIds.has(edge.to)),
|
|
118
|
+
mainPath: flow.mainPath.filter((nodeId) => backendNodeIds.has(nodeId)),
|
|
119
|
+
subFlows: flow.subFlows.map((subFlow) => ({
|
|
120
|
+
...subFlow,
|
|
121
|
+
nodes: subFlow.nodes.filter((node) => node.type !== "page"),
|
|
122
|
+
edges: subFlow.edges.filter((edge) => !edge.from.startsWith("page:") && !edge.to.startsWith("page:"))
|
|
123
|
+
}))
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function hydrateEvent(key, fixtureDetail) {
|
|
127
|
+
return {
|
|
128
|
+
...baseEvents[key],
|
|
129
|
+
matchedEndpoint: fixtureDetail?.endpoint ?? endpoints[key],
|
|
130
|
+
matchedFlow: fixtureDetail?.flow ?? flows[key]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function DemoButton({ active, index, method, label, meta, onClick, demoKey }) {
|
|
134
|
+
return (_jsxs("button", { "aria-pressed": active, className: "anlyx-readme-demo__action", "data-demo": demoKey, type: "button", onClick: onClick, children: [_jsx("span", { className: "anlyx-readme-demo__action-index", children: index }), _jsxs("span", { className: "anlyx-readme-demo__action-copy", children: [_jsx("strong", { children: label }), _jsx("span", { children: meta })] }), _jsx("span", { className: "anlyx-readme-demo__method", children: method })] }));
|
|
135
|
+
}
|
|
136
|
+
function makeFlow(method, path, nodes) {
|
|
137
|
+
const mainPath = nodes.map(([id]) => id);
|
|
138
|
+
return {
|
|
139
|
+
endpointId: `${method}:${path}`,
|
|
140
|
+
nodes: nodes.map(([id, type, label]) => ({
|
|
141
|
+
id,
|
|
142
|
+
type,
|
|
143
|
+
label,
|
|
144
|
+
confidence: "high"
|
|
145
|
+
})),
|
|
146
|
+
edges: mainPath.slice(1).map((to, index) => ({
|
|
147
|
+
id: `edge:${mainPath[index]}:${to}`,
|
|
148
|
+
from: mainPath[index] ?? "",
|
|
149
|
+
to,
|
|
150
|
+
kind: "main",
|
|
151
|
+
confidence: "high"
|
|
152
|
+
})),
|
|
153
|
+
mainPath,
|
|
154
|
+
subFlows: []
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function endpoint(method, path, handler) {
|
|
158
|
+
return {
|
|
159
|
+
id: `${method}:${path}`,
|
|
160
|
+
method,
|
|
161
|
+
path,
|
|
162
|
+
supportLevel: "deep",
|
|
163
|
+
handler,
|
|
164
|
+
confidence: "high"
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function event(id, method, path, status, durationMs, label) {
|
|
168
|
+
return {
|
|
169
|
+
id,
|
|
170
|
+
method,
|
|
171
|
+
path,
|
|
172
|
+
status,
|
|
173
|
+
durationMs,
|
|
174
|
+
count: id === "save" ? 2 : 1,
|
|
175
|
+
source: "action",
|
|
176
|
+
triggeredBy: {
|
|
177
|
+
type: "Clicked",
|
|
178
|
+
label,
|
|
179
|
+
selector: `button[data-demo="${id}"]`
|
|
180
|
+
},
|
|
181
|
+
matchedEndpoint: null,
|
|
182
|
+
matchedFlow: null
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { ReadmeDemoApp } from "./ReadmeDemoApp.js";
|
|
4
|
+
const root = document.getElementById("root");
|
|
5
|
+
if (!root) {
|
|
6
|
+
throw new Error("Missing #root for Anlyx README demo");
|
|
7
|
+
}
|
|
8
|
+
createRoot(root).render(_jsx(ReadmeDemoApp, {}));
|