causal-inspector 0.1.4 → 0.1.6
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/CausalInspector.css +447 -20
- package/dist/components/CopyablePayload.js +8 -8
- package/dist/components/FilterBar.js +9 -6
- package/dist/components/JsonSyntax.js +8 -8
- package/dist/engines/query.js +15 -13
- package/dist/events.d.ts +6 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/panes/CausalFlowPane.js +25 -21
- package/dist/panes/CausalTreePane.js +38 -13
- package/dist/panes/CorrelationExplorerPane.js +7 -2
- package/dist/panes/LogsPane.js +16 -7
- package/dist/panes/TimelinePane.js +20 -12
- package/dist/panes/WaterfallPane.js +181 -68
- package/dist/queries.d.ts +1 -0
- package/dist/queries.js +14 -0
- package/dist/reducer.js +5 -0
- package/dist/state.d.ts +2 -1
- package/dist/state.js +3 -0
- package/dist/theme.js +4 -3
- package/dist/types.d.ts +12 -4
- package/package.json +1 -1
package/dist/engines/query.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { INSPECTOR_EVENTS, INSPECTOR_CAUSAL_TREE, INSPECTOR_CAUSAL_FLOW, INSPECTOR_CORRELATIONS, INSPECTOR_REACTOR_DEPENDENCIES, INSPECTOR_AGGREGATE_KEYS, INSPECTOR_AGGREGATE_LIFECYCLE, INSPECTOR_REACTOR_LOGS_BY_CORRELATION, INSPECTOR_REACTOR_DESCRIPTIONS, INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, INSPECTOR_AGGREGATE_TIMELINE, INSPECTOR_REACTOR_OUTCOMES, } from "../queries";
|
|
1
|
+
import { INSPECTOR_EVENTS, INSPECTOR_CAUSAL_TREE, INSPECTOR_CAUSAL_FLOW, INSPECTOR_CORRELATIONS, INSPECTOR_REACTOR_DEPENDENCIES, INSPECTOR_AGGREGATE_KEYS, INSPECTOR_AGGREGATE_LIFECYCLE, INSPECTOR_REACTOR_LOGS_BY_CORRELATION, INSPECTOR_REACTOR_DESCRIPTIONS, INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, INSPECTOR_AGGREGATE_TIMELINE, INSPECTOR_REACTOR_OUTCOMES, INSPECTOR_REACTOR_ATTEMPTS, } from "../queries";
|
|
2
2
|
/**
|
|
3
3
|
* Query engine — fetches data in response to state transitions.
|
|
4
4
|
*
|
|
@@ -69,11 +69,12 @@ export const createQueryEngine = (transport) => {
|
|
|
69
69
|
};
|
|
70
70
|
const fetchFlowMetadata = async (correlationId) => {
|
|
71
71
|
try {
|
|
72
|
-
const [descData, snapshotData, aggTimelineData, outcomeData] = await Promise.all([
|
|
72
|
+
const [descData, snapshotData, aggTimelineData, outcomeData, attemptData] = await Promise.all([
|
|
73
73
|
transport.query(INSPECTOR_REACTOR_DESCRIPTIONS, { correlationId }),
|
|
74
74
|
transport.query(INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, { correlationId }),
|
|
75
75
|
transport.query(INSPECTOR_AGGREGATE_TIMELINE, { correlationId }),
|
|
76
76
|
transport.query(INSPECTOR_REACTOR_OUTCOMES, { correlationId }),
|
|
77
|
+
transport.query(INSPECTOR_REACTOR_ATTEMPTS, { correlationId }),
|
|
77
78
|
]);
|
|
78
79
|
if (activeFlowCorrelationId !== correlationId)
|
|
79
80
|
return; // stale
|
|
@@ -105,6 +106,13 @@ export const createQueryEngine = (transport) => {
|
|
|
105
106
|
outcomes: outcomeData.inspectorReactorOutcomes,
|
|
106
107
|
},
|
|
107
108
|
});
|
|
109
|
+
dispatch({
|
|
110
|
+
type: "events/attempts_loaded",
|
|
111
|
+
payload: {
|
|
112
|
+
correlationId,
|
|
113
|
+
attempts: attemptData.inspectorReactorAttempts,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
108
116
|
}
|
|
109
117
|
catch (e) {
|
|
110
118
|
console.error("[causal-inspector] fetch flow metadata failed:", e);
|
|
@@ -119,23 +127,18 @@ export const createQueryEngine = (transport) => {
|
|
|
119
127
|
console.error("[causal-inspector] fetch logs failed:", e);
|
|
120
128
|
}
|
|
121
129
|
};
|
|
122
|
-
let correlationCursor = null;
|
|
123
130
|
const fetchCorrelations = async (opts) => {
|
|
124
|
-
const append = opts?.append ?? false;
|
|
125
|
-
const cursor = append ? correlationCursor : undefined;
|
|
126
131
|
try {
|
|
127
132
|
const data = await transport.query(INSPECTOR_CORRELATIONS, {
|
|
128
133
|
search: opts?.search || undefined,
|
|
129
|
-
limit:
|
|
130
|
-
cursor: cursor || undefined,
|
|
134
|
+
limit: 100,
|
|
131
135
|
});
|
|
132
|
-
correlationCursor = data.inspectorCorrelations.nextCursor;
|
|
133
136
|
dispatch({
|
|
134
137
|
type: "events/correlations_loaded",
|
|
135
138
|
payload: {
|
|
136
|
-
correlations: data.inspectorCorrelations
|
|
137
|
-
hasMore:
|
|
138
|
-
append,
|
|
139
|
+
correlations: data.inspectorCorrelations,
|
|
140
|
+
hasMore: false,
|
|
141
|
+
append: false,
|
|
139
142
|
},
|
|
140
143
|
});
|
|
141
144
|
}
|
|
@@ -229,10 +232,9 @@ export const createQueryEngine = (transport) => {
|
|
|
229
232
|
fetchEvents();
|
|
230
233
|
break;
|
|
231
234
|
case "ui/load_more_correlations_requested":
|
|
232
|
-
fetchCorrelations(
|
|
235
|
+
fetchCorrelations();
|
|
233
236
|
break;
|
|
234
237
|
case "ui/correlations_requested":
|
|
235
|
-
correlationCursor = null;
|
|
236
238
|
fetchCorrelations({ search: event.payload.search });
|
|
237
239
|
stopCorrelationPolling();
|
|
238
240
|
correlationPollTimer = setInterval(() => fetchCorrelations({ search: event.payload.search }), 5000);
|
package/dist/events.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BaseEvent } from "./machine";
|
|
2
|
-
import type { InspectorEvent, CorrelationSummary, ReactorDependency, AggregateLifecycleEntry, FilterState, FlowSelection, ReactorDescription, ReactorDescriptionSnapshot, AggregateTimelineEntry, ReactorLog, ReactorOutcome, PaneLayout } from "./types";
|
|
2
|
+
import type { InspectorEvent, CorrelationSummary, ReactorDependency, AggregateLifecycleEntry, FilterState, FlowSelection, ReactorDescription, ReactorDescriptionSnapshot, AggregateTimelineEntry, ReactorLog, ReactorOutcome, ReactorAttempt, PaneLayout } from "./types";
|
|
3
3
|
type EventsReceived = BaseEvent<"events/received", InspectorEvent[]>;
|
|
4
4
|
type SubscriptionConnected = BaseEvent<"events/subscription_connected">;
|
|
5
5
|
type SubscriptionError = BaseEvent<"events/subscription_error", {
|
|
@@ -31,6 +31,10 @@ type OutcomesLoaded = BaseEvent<"events/outcomes_loaded", {
|
|
|
31
31
|
correlationId: string;
|
|
32
32
|
outcomes: ReactorOutcome[];
|
|
33
33
|
}>;
|
|
34
|
+
type AttemptsLoaded = BaseEvent<"events/attempts_loaded", {
|
|
35
|
+
correlationId: string;
|
|
36
|
+
attempts: ReactorAttempt[];
|
|
37
|
+
}>;
|
|
34
38
|
type CorrelationsLoaded = BaseEvent<"events/correlations_loaded", {
|
|
35
39
|
correlations: CorrelationSummary[];
|
|
36
40
|
hasMore: boolean;
|
|
@@ -78,5 +82,5 @@ type LocationChanged = BaseEvent<"location/changed", {
|
|
|
78
82
|
correlationId: string | null;
|
|
79
83
|
handler: string | null;
|
|
80
84
|
}>;
|
|
81
|
-
export type InspectorMachineEvent = EventsReceived | SubscriptionConnected | SubscriptionError | PageLoaded | CausalTreeLoaded | FlowLoaded | LogsLoaded | DescriptionsLoaded | DescriptionSnapshotsLoaded | AggregateTimelineLoaded | OutcomesLoaded | CorrelationsLoaded | EventSelected | EventDeselected | FlowOpened | FlowClosed | FlowNodeSelected | FilterChanged | LoadMoreRequested | LayoutChanged | ScrubberStartChanged | ScrubberEndChanged | ScrubberPlayToggled | ScrubberSpeedChanged | CorrelationsRequested | ReactorDependenciesLoaded | AggregateKeysLoaded | HandlerSelected | LocationChanged | AggregateLifecycleLoaded | AggregateLifecycleRequested | LoadMoreCorrelationsRequested;
|
|
85
|
+
export type InspectorMachineEvent = EventsReceived | SubscriptionConnected | SubscriptionError | PageLoaded | CausalTreeLoaded | FlowLoaded | LogsLoaded | DescriptionsLoaded | DescriptionSnapshotsLoaded | AggregateTimelineLoaded | OutcomesLoaded | AttemptsLoaded | CorrelationsLoaded | EventSelected | EventDeselected | FlowOpened | FlowClosed | FlowNodeSelected | FilterChanged | LoadMoreRequested | LayoutChanged | ScrubberStartChanged | ScrubberEndChanged | ScrubberPlayToggled | ScrubberSpeedChanged | CorrelationsRequested | ReactorDependenciesLoaded | AggregateKeysLoaded | HandlerSelected | LocationChanged | AggregateLifecycleLoaded | AggregateLifecycleRequested | LoadMoreCorrelationsRequested;
|
|
82
86
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -5,10 +5,10 @@ export { reducer } from "./reducer";
|
|
|
5
5
|
export { initialState } from "./state";
|
|
6
6
|
export type { InspectorState } from "./state";
|
|
7
7
|
export type { InspectorMachineEvent } from "./events";
|
|
8
|
-
export type { InspectorEvent, InspectorEventsPage, InspectorCausalTree, InspectorCausalFlow, CorrelationSummary, ReactorDependency, AggregateLifecycleEntry, Block, ReactorLog, ReactorDescription, ReactorDescriptionSnapshot, AggregateStateEntry, AggregateTimelineEntry, ReactorOutcome, FilterState, LogsFilter, FlowSelection, PaneLayout, } from "./types";
|
|
8
|
+
export type { InspectorEvent, InspectorEventsPage, InspectorCausalTree, InspectorCausalFlow, CorrelationSummary, ReactorDependency, AggregateLifecycleEntry, Block, ReactorLog, ReactorDescription, ReactorDescriptionSnapshot, AggregateStateEntry, AggregateTimelineEntry, ReactorOutcome, ReactorAttempt, FilterState, LogsFilter, FlowSelection, PaneLayout, } from "./types";
|
|
9
9
|
export { createInspectorEngine, createSubscriptionEngine, createQueryEngine, createStorageEngine, } from "./engines";
|
|
10
10
|
export type { InspectorTransport, SubscriptionTransport, QueryTransport, StorageTransport, } from "./engines";
|
|
11
|
-
export { EVENTS_SUBSCRIPTION, INSPECTOR_EVENTS, INSPECTOR_CAUSAL_TREE, INSPECTOR_CAUSAL_FLOW, INSPECTOR_CORRELATIONS, INSPECTOR_REACTOR_LOGS, INSPECTOR_REACTOR_LOGS_BY_CORRELATION, INSPECTOR_REACTOR_DESCRIPTIONS, INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, INSPECTOR_AGGREGATE_TIMELINE, INSPECTOR_REACTOR_OUTCOMES, INSPECTOR_REACTOR_DEPENDENCIES, INSPECTOR_AGGREGATE_KEYS, INSPECTOR_AGGREGATE_LIFECYCLE, } from "./queries";
|
|
11
|
+
export { EVENTS_SUBSCRIPTION, INSPECTOR_EVENTS, INSPECTOR_CAUSAL_TREE, INSPECTOR_CAUSAL_FLOW, INSPECTOR_CORRELATIONS, INSPECTOR_REACTOR_LOGS, INSPECTOR_REACTOR_LOGS_BY_CORRELATION, INSPECTOR_REACTOR_DESCRIPTIONS, INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, INSPECTOR_AGGREGATE_TIMELINE, INSPECTOR_REACTOR_OUTCOMES, INSPECTOR_REACTOR_ATTEMPTS, INSPECTOR_REACTOR_DEPENDENCIES, INSPECTOR_AGGREGATE_KEYS, INSPECTOR_AGGREGATE_LIFECYCLE, } from "./queries";
|
|
12
12
|
export { eventHue, eventBg, eventBorder, eventTextColor, LOG_LEVEL_COLORS, } from "./theme";
|
|
13
13
|
export { formatTs, compactPayload, copyToClipboard } from "./utils";
|
|
14
14
|
export { CausalInspector } from "./CausalInspector";
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export { initialState } from "./state";
|
|
|
7
7
|
// ── Engines ──
|
|
8
8
|
export { createInspectorEngine, createSubscriptionEngine, createQueryEngine, createStorageEngine, } from "./engines";
|
|
9
9
|
// ── Queries ──
|
|
10
|
-
export { EVENTS_SUBSCRIPTION, INSPECTOR_EVENTS, INSPECTOR_CAUSAL_TREE, INSPECTOR_CAUSAL_FLOW, INSPECTOR_CORRELATIONS, INSPECTOR_REACTOR_LOGS, INSPECTOR_REACTOR_LOGS_BY_CORRELATION, INSPECTOR_REACTOR_DESCRIPTIONS, INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, INSPECTOR_AGGREGATE_TIMELINE, INSPECTOR_REACTOR_OUTCOMES, INSPECTOR_REACTOR_DEPENDENCIES, INSPECTOR_AGGREGATE_KEYS, INSPECTOR_AGGREGATE_LIFECYCLE, } from "./queries";
|
|
10
|
+
export { EVENTS_SUBSCRIPTION, INSPECTOR_EVENTS, INSPECTOR_CAUSAL_TREE, INSPECTOR_CAUSAL_FLOW, INSPECTOR_CORRELATIONS, INSPECTOR_REACTOR_LOGS, INSPECTOR_REACTOR_LOGS_BY_CORRELATION, INSPECTOR_REACTOR_DESCRIPTIONS, INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, INSPECTOR_AGGREGATE_TIMELINE, INSPECTOR_REACTOR_OUTCOMES, INSPECTOR_REACTOR_ATTEMPTS, INSPECTOR_REACTOR_DEPENDENCIES, INSPECTOR_AGGREGATE_KEYS, INSPECTOR_AGGREGATE_LIFECYCLE, } from "./queries";
|
|
11
11
|
// ── Theme ──
|
|
12
12
|
export { eventHue, eventBg, eventBorder, eventTextColor, LOG_LEVEL_COLORS, } from "./theme";
|
|
13
13
|
// ── Utilities ──
|
|
@@ -4,7 +4,7 @@ import { ReactFlow, Background, Controls, useReactFlow, Handle, MarkerType, Posi
|
|
|
4
4
|
import dagre from "@dagrejs/dagre";
|
|
5
5
|
import { useSelector, useDispatch } from "../machine";
|
|
6
6
|
import { eventBg, eventBorder, eventTextColor } from "../theme";
|
|
7
|
-
import { Filter } from "lucide-react";
|
|
7
|
+
import { Filter, ArrowRight, ArrowDown } from "lucide-react";
|
|
8
8
|
import { inScrubberRange } from "../utils";
|
|
9
9
|
/* eslint-disable-next-line @typescript-eslint/no-redeclare -- shadowing the tree-pane ReactorNode on purpose */
|
|
10
10
|
const NODE_WIDTH = 180;
|
|
@@ -60,6 +60,7 @@ const ReactorNode = memo(({ data }) => {
|
|
|
60
60
|
const d = data;
|
|
61
61
|
const blocks = d.blocks;
|
|
62
62
|
const outcome = d.outcome;
|
|
63
|
+
const dir = d.direction ?? "LR";
|
|
63
64
|
const hasBlocks = blocks && blocks.length > 0;
|
|
64
65
|
const borderColor = STATUS_BORDER[outcome?.status ?? "pending"] ?? "#2a2a35";
|
|
65
66
|
const isRunning = outcome?.status === "running";
|
|
@@ -79,10 +80,11 @@ const ReactorNode = memo(({ data }) => {
|
|
|
79
80
|
boxShadow: isRunning
|
|
80
81
|
? `0 0 12px ${borderColor}40`
|
|
81
82
|
: "0 2px 8px rgba(0, 0, 0, 0.3)",
|
|
82
|
-
}, children: [_jsx(Handle, { type: "target", position: Position.Left, style: { visibility: "hidden" } }), _jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8 }, children: [_jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", direction: "rtl", textAlign: "left" }, children: d.label }), duration && _jsx("span", { style: { fontSize: 9, color: "#60608a", fontStyle: "normal", whiteSpace: "nowrap", flexShrink: 0 }, children: duration })] }), hasBlocks && blocks.map((block, i) => _jsx(BlockRenderer, { block: block }, i)), outcome?.status === "error" && outcome.error && (_jsx("div", { style: { fontSize: 9, color: "#ef4444", marginTop: 4 }, children: outcome.error })), _jsx(Handle, { type: "source", position: Position.Right, style: { visibility: "hidden" } })] }));
|
|
83
|
+
}, children: [_jsx(Handle, { type: "target", position: dir === "LR" ? Position.Left : Position.Top, style: { visibility: "hidden" } }), _jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8 }, children: [_jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", direction: "rtl", textAlign: "left" }, children: d.label }), duration && _jsx("span", { style: { fontSize: 9, color: "#60608a", fontStyle: "normal", whiteSpace: "nowrap", flexShrink: 0 }, children: duration })] }), hasBlocks && blocks.map((block, i) => _jsx(BlockRenderer, { block: block }, i)), outcome?.status === "error" && outcome.error && (_jsx("div", { style: { fontSize: 9, color: "#ef4444", marginTop: 4 }, children: outcome.error })), _jsx(Handle, { type: "source", position: dir === "LR" ? Position.Right : Position.Bottom, style: { visibility: "hidden" } })] }));
|
|
83
84
|
});
|
|
84
85
|
const EventNode = memo(({ data }) => {
|
|
85
86
|
const d = data;
|
|
87
|
+
const dir = d.direction ?? "LR";
|
|
86
88
|
return (_jsxs("div", { style: {
|
|
87
89
|
background: eventBg(d.eventName),
|
|
88
90
|
border: `1px solid ${eventBorder(d.eventName)}`,
|
|
@@ -99,10 +101,10 @@ const EventNode = memo(({ data }) => {
|
|
|
99
101
|
boxShadow: `0 2px 8px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.04)`,
|
|
100
102
|
fontWeight: 500,
|
|
101
103
|
letterSpacing: "0.01em",
|
|
102
|
-
}, children: [_jsx(Handle, { type: "target", position: Position.Left, style: { visibility: "hidden" } }), d.label, _jsx(Handle, { type: "source", position: Position.Right, style: { visibility: "hidden" } })] }));
|
|
104
|
+
}, children: [_jsx(Handle, { type: "target", position: dir === "LR" ? Position.Left : Position.Top, style: { visibility: "hidden" } }), d.label, _jsx(Handle, { type: "source", position: dir === "LR" ? Position.Right : Position.Bottom, style: { visibility: "hidden" } })] }));
|
|
103
105
|
});
|
|
104
106
|
const nodeTypes = { reactor: ReactorNode, event: EventNode };
|
|
105
|
-
function buildFlowGraph(events, descriptions, outcomes, hiddenReactors) {
|
|
107
|
+
function buildFlowGraph(events, descriptions, outcomes, hiddenReactors, direction = "LR") {
|
|
106
108
|
// Group events by type name only (merge across reactors)
|
|
107
109
|
const eventGroups = new Map();
|
|
108
110
|
const reactorIds = new Set();
|
|
@@ -154,9 +156,10 @@ function buildFlowGraph(events, descriptions, outcomes, hiddenReactors) {
|
|
|
154
156
|
label: group.count > 1 ? `${group.name} (${group.count})` : group.name,
|
|
155
157
|
nodeKind: "event-type",
|
|
156
158
|
eventName: group.name,
|
|
159
|
+
direction,
|
|
157
160
|
},
|
|
158
|
-
sourcePosition: Position.Right,
|
|
159
|
-
targetPosition: Position.Left,
|
|
161
|
+
sourcePosition: direction === "LR" ? Position.Right : Position.Bottom,
|
|
162
|
+
targetPosition: direction === "LR" ? Position.Left : Position.Top,
|
|
160
163
|
});
|
|
161
164
|
}
|
|
162
165
|
// Reactor nodes
|
|
@@ -169,9 +172,9 @@ function buildFlowGraph(events, descriptions, outcomes, hiddenReactors) {
|
|
|
169
172
|
id: `hdl:${reactorId}`,
|
|
170
173
|
type: "reactor",
|
|
171
174
|
position: { x: 0, y: 0 },
|
|
172
|
-
data: { label: reactorId, nodeKind: "reactor", reactorId, blocks, outcome },
|
|
173
|
-
sourcePosition: Position.Right,
|
|
174
|
-
targetPosition: Position.Left,
|
|
175
|
+
data: { label: reactorId, nodeKind: "reactor", reactorId, blocks, outcome, direction },
|
|
176
|
+
sourcePosition: direction === "LR" ? Position.Right : Position.Bottom,
|
|
177
|
+
targetPosition: direction === "LR" ? Position.Left : Position.Top,
|
|
175
178
|
});
|
|
176
179
|
}
|
|
177
180
|
const arrowMarker = { type: MarkerType.ArrowClosed, color: "#3a3a4a", width: 14, height: 14 };
|
|
@@ -226,9 +229,9 @@ function buildFlowGraph(events, descriptions, outcomes, hiddenReactors) {
|
|
|
226
229
|
id: `hdl:${reactorId}`,
|
|
227
230
|
type: "reactor",
|
|
228
231
|
position: { x: 0, y: 0 },
|
|
229
|
-
data: { label: reactorId, nodeKind: "reactor", reactorId, blocks, outcome },
|
|
230
|
-
sourcePosition: Position.Bottom,
|
|
231
|
-
targetPosition: Position.Top,
|
|
232
|
+
data: { label: reactorId, nodeKind: "reactor", reactorId, blocks, outcome, direction },
|
|
233
|
+
sourcePosition: direction === "LR" ? Position.Right : Position.Bottom,
|
|
234
|
+
targetPosition: direction === "LR" ? Position.Left : Position.Top,
|
|
232
235
|
});
|
|
233
236
|
const isPending = outcome.status === "pending" || outcome.status === "running";
|
|
234
237
|
for (const eventId of outcome.triggeringEventIds ?? []) {
|
|
@@ -250,7 +253,7 @@ function buildFlowGraph(events, descriptions, outcomes, hiddenReactors) {
|
|
|
250
253
|
}
|
|
251
254
|
}
|
|
252
255
|
}
|
|
253
|
-
return layoutGraph(nodes, edges);
|
|
256
|
+
return layoutGraph(nodes, edges, direction);
|
|
254
257
|
}
|
|
255
258
|
function estimateReactorHeight(data) {
|
|
256
259
|
if (data.nodeKind !== "reactor")
|
|
@@ -274,10 +277,10 @@ function estimateReactorHeight(data) {
|
|
|
274
277
|
h += 14;
|
|
275
278
|
return h;
|
|
276
279
|
}
|
|
277
|
-
function layoutGraph(nodes, edges) {
|
|
280
|
+
function layoutGraph(nodes, edges, direction = "LR") {
|
|
278
281
|
const g = new dagre.graphlib.Graph();
|
|
279
282
|
g.setDefaultEdgeLabel(() => ({}));
|
|
280
|
-
g.setGraph({ rankdir:
|
|
283
|
+
g.setGraph({ rankdir: direction, nodesep: 40, ranksep: 80 });
|
|
281
284
|
const heights = new Map();
|
|
282
285
|
for (const node of nodes) {
|
|
283
286
|
const isReactor = node.id.startsWith("hdl:");
|
|
@@ -424,7 +427,7 @@ function ReactorFilter({ allReactorIds, hiddenReactors, setHiddenReactors }) {
|
|
|
424
427
|
? allReactorIds.filter(id => id.toLowerCase().includes(filter.toLowerCase()))
|
|
425
428
|
: allReactorIds;
|
|
426
429
|
const hiddenCount = hiddenReactors.size;
|
|
427
|
-
return (_jsxs("div", { ref: containerRef,
|
|
430
|
+
return (_jsxs("div", { ref: containerRef, style: { position: "relative" }, children: [_jsxs("button", { onClick: () => setOpen(v => !v), className: "ci-toolbar-btn", children: [_jsx(Filter, { size: 11, style: { display: "inline", marginRight: 4, verticalAlign: "-1px" } }), hiddenCount > 0 ? `${hiddenCount} hidden` : "Filter"] }), open && (_jsxs("div", { className: "ci-dropdown", style: { position: "absolute", top: "100%", right: 0, marginTop: 4, zIndex: 50, minWidth: 240 }, children: [_jsx("div", { style: { padding: "8px 12px", borderBottom: "1px solid var(--ci-border)" }, children: _jsx("input", { autoFocus: true, type: "text", value: filter, onChange: e => setFilter(e.target.value), placeholder: "Search reactors...", style: { width: "100%", fontSize: 12, background: "transparent", border: "none", outline: "none", color: "var(--ci-text)" } }) }), _jsxs("div", { style: { maxHeight: 256, overflowY: "auto", padding: "4px 0" }, children: [filtered.map(id => (_jsxs("label", { className: "ci-row", style: { gap: 8, padding: "6px 12px", cursor: "pointer", transition: "background 150ms" }, children: [_jsx("input", { type: "checkbox", checked: !hiddenReactors.has(id), onChange: () => toggle(id), style: { accentColor: "var(--ci-accent)" } }), _jsx("span", { className: "ci-mono ci-truncate", style: { fontSize: 11, color: "var(--ci-text)", opacity: 0.8 }, children: id })] }, id))), filtered.length === 0 && (_jsx("div", { style: { fontSize: 12, color: "var(--ci-text-muted)", opacity: 0.5, padding: "8px 12px" }, children: "No matches" }))] })] }))] }));
|
|
428
431
|
}
|
|
429
432
|
export function CausalFlowPane({ defaultHiddenReactors, headerExtra } = {}) {
|
|
430
433
|
const flowCorrelationId = useSelector((s) => s.flowCorrelationId);
|
|
@@ -460,6 +463,7 @@ export function CausalFlowPane({ defaultHiddenReactors, headerExtra } = {}) {
|
|
|
460
463
|
return map;
|
|
461
464
|
}, [outcomesMap, flowCorrelationId]);
|
|
462
465
|
const [hiddenReactors, setHiddenReactors] = useState(() => defaultHiddenReactors ?? new Set());
|
|
466
|
+
const [direction, setDirection] = useState("LR");
|
|
463
467
|
const allReactorIds = useMemo(() => {
|
|
464
468
|
const ids = new Set();
|
|
465
469
|
for (const evt of flowData) {
|
|
@@ -475,8 +479,8 @@ export function CausalFlowPane({ defaultHiddenReactors, headerExtra } = {}) {
|
|
|
475
479
|
const { nodes: fullNodes, edges: fullEdges } = useMemo(() => {
|
|
476
480
|
if (!flowData || flowData.length === 0)
|
|
477
481
|
return { nodes: [], edges: [] };
|
|
478
|
-
return buildFlowGraph(flowData, descriptions, outcomes, hiddenReactors);
|
|
479
|
-
}, [flowData, descriptions, outcomes, hiddenReactors]);
|
|
482
|
+
return buildFlowGraph(flowData, descriptions, outcomes, hiddenReactors, direction);
|
|
483
|
+
}, [flowData, descriptions, outcomes, hiddenReactors, direction]);
|
|
480
484
|
// Compute visible IDs when scrubber range is active
|
|
481
485
|
const visibleIds = useMemo(() => {
|
|
482
486
|
if (scrubberStart == null && scrubberEnd == null)
|
|
@@ -587,10 +591,10 @@ export function CausalFlowPane({ defaultHiddenReactors, headerExtra } = {}) {
|
|
|
587
591
|
}, [dispatch]);
|
|
588
592
|
const onNodesChange = useCallback((_changes) => { }, []);
|
|
589
593
|
if (!flowCorrelationId) {
|
|
590
|
-
return (_jsx("div", { className: "
|
|
594
|
+
return (_jsx("div", { className: "ci-empty", children: "Select an event to visualize its causal flow" }));
|
|
591
595
|
}
|
|
592
596
|
if (flowLoading) {
|
|
593
|
-
return (_jsxs("div", { className: "
|
|
597
|
+
return (_jsxs("div", { className: "ci-col", children: [_jsxs("div", { className: "ci-row", style: { gap: 8, padding: "6px 12px", borderBottom: "1px solid var(--ci-border)", flexShrink: 0 }, children: [_jsx("div", { className: "ci-muted-bg ci-pulse", style: { height: 12, width: 40 } }), _jsx("div", { className: "ci-muted-bg ci-pulse", style: { height: 12, width: 192 } })] }), _jsx("div", { style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }, children: _jsxs("div", { className: "ci-pulse", style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 12 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 32, width: 160, borderRadius: "var(--ci-radius-md)" } }), _jsx("div", { className: "ci-muted-bg", style: { height: 24, width: 1 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 24, width: 112, borderRadius: "var(--ci-radius-full)" } }), _jsxs("div", { style: { display: "flex", alignItems: "start", gap: 32 }, children: [_jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 12 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 24, width: 1 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 32, width: 144, borderRadius: "var(--ci-radius-md)" } })] }), _jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 12 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 24, width: 1 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 32, width: 144, borderRadius: "var(--ci-radius-md)" } })] })] })] }) })] }));
|
|
594
598
|
}
|
|
595
|
-
return (_jsxs("div", { className: "
|
|
599
|
+
return (_jsxs("div", { className: "ci-col", children: [_jsxs("div", { className: "ci-row ci-surface", style: { gap: 10, padding: "8px 12px", borderBottom: "1px solid var(--ci-border)", flexShrink: 0 }, children: [_jsx("h3", { className: "ci-header", children: "Flow" }), _jsx("span", { className: "ci-mono ci-truncate", style: { fontSize: 10, color: "var(--ci-text)", opacity: 0.8, padding: "2px 6px", borderRadius: "var(--ci-radius-sm)", background: "rgba(255, 255, 255, 0.03)", border: "1px solid var(--ci-border)" }, children: flowCorrelationId }), _jsxs("span", { className: "ci-tabular", style: { fontSize: 10, color: "var(--ci-text-muted)", opacity: 0.5 }, children: [flowData.length, " events \u00B7 ", nodes.length, " nodes"] }), headerExtra, _jsxs("div", { className: "ci-row", style: { marginLeft: "auto", gap: 6 }, children: [_jsx("button", { onClick: () => setDirection(d => d === "LR" ? "TB" : "LR"), className: "ci-toolbar-btn", title: direction === "LR" ? "Switch to vertical layout" : "Switch to horizontal layout", children: direction === "LR" ? _jsx(ArrowDown, { size: 11, style: { display: "inline", verticalAlign: "-1px" } }) : _jsx(ArrowRight, { size: 11, style: { display: "inline", verticalAlign: "-1px" } }) }), _jsx(ReactorFilter, { allReactorIds: allReactorIds, hiddenReactors: hiddenReactors, setHiddenReactors: setHiddenReactors })] })] }), _jsx("div", { style: { flex: 1, position: "relative" }, children: _jsxs(ReactFlow, { nodes: nodes, edges: edges, nodeTypes: nodeTypes, defaultEdgeOptions: { type: "smoothstep" }, onNodesChange: onNodesChange, onNodeClick: onNodeClick, onPaneClick: onPaneClick, minZoom: 0.25, proOptions: { hideAttribution: true }, nodesDraggable: false, nodesConnectable: false, elevateNodesOnSelect: false, colorMode: "dark", children: [_jsx(FitOnLoad, {}), _jsx(FocusOnSelection, { nodes: nodes, flowData: flowData }), _jsx(Background, { color: "rgba(255,255,255,0.03)", gap: 24, size: 1 }), _jsx(Controls, { showInteractive: false })] }) })] }));
|
|
596
600
|
}
|
|
@@ -4,7 +4,7 @@ import { useSelector, useDispatch } from "../machine";
|
|
|
4
4
|
import { CopyablePayload } from "../components/CopyablePayload";
|
|
5
5
|
import { eventTextColor } from "../theme";
|
|
6
6
|
import { formatTs, compactPayload, copyToClipboard, inScrubberRange } from "../utils";
|
|
7
|
-
import { Copy, Check, Search, X, ChevronRight, ChevronDown } from "lucide-react";
|
|
7
|
+
import { Copy, Check, Search, X, ChevronRight, ChevronDown, AlertTriangle } from "lucide-react";
|
|
8
8
|
function buildTreeJson(roots, childrenMap) {
|
|
9
9
|
function toNode(evt) {
|
|
10
10
|
const children = evt.id ? (childrenMap.get(evt.id) ?? []) : [];
|
|
@@ -23,7 +23,7 @@ function buildTreeJson(roots, childrenMap) {
|
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
24
|
// ReactorNode — intermediate node grouping children by reactor_id
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
|
-
function ReactorNode({ reactorId, parentEventId, children, childrenMap, depth, isHighlighted, onClickReactor, }) {
|
|
26
|
+
function ReactorNode({ reactorId, parentEventId, children, childrenMap, depth, isHighlighted, onClickReactor, outcome, outcomesByReactor, }) {
|
|
27
27
|
const [collapsed, setCollapsed] = useState(false);
|
|
28
28
|
const nodeRef = useRef(null);
|
|
29
29
|
useEffect(() => {
|
|
@@ -34,12 +34,24 @@ function ReactorNode({ reactorId, parentEventId, children, childrenMap, depth, i
|
|
|
34
34
|
const handleClick = useCallback(() => {
|
|
35
35
|
onClickReactor(reactorId, parentEventId);
|
|
36
36
|
}, [onClickReactor, parentEventId, reactorId]);
|
|
37
|
-
|
|
37
|
+
const isError = outcome?.status === "error";
|
|
38
|
+
const isRunning = outcome?.status === "running";
|
|
39
|
+
return (_jsxs("div", { style: depth > 0 ? { paddingLeft: 24 } : undefined, children: [_jsxs("div", { ref: isHighlighted ? nodeRef : undefined, className: `ci-tree-node${isHighlighted ? " ci-selected" : ""}`, style: isError ? { background: "rgba(239, 68, 68, 0.08)" } : undefined, children: [_jsxs("div", { className: "ci-row", style: { gap: 6, minWidth: 0 }, children: [_jsx("button", { onClick: (e) => { e.stopPropagation(); setCollapsed(v => !v); }, style: { fontSize: 10, color: "var(--ci-text-muted)", background: "none", border: "none", cursor: "pointer", flexShrink: 0, width: 12, textAlign: "center", padding: 0, transition: "color 150ms" }, children: collapsed ? _jsx(ChevronRight, { size: 10 }) : _jsx(ChevronDown, { size: 10 }) }), _jsxs("button", { onClick: handleClick, className: "ci-row", style: { gap: 6, minWidth: 0, background: "none", border: "none", cursor: "pointer", padding: 0 }, children: [_jsx("span", { style: {
|
|
40
|
+
padding: "2px 6px",
|
|
41
|
+
borderRadius: "var(--ci-radius-sm)",
|
|
42
|
+
fontSize: 9,
|
|
43
|
+
fontWeight: 500,
|
|
44
|
+
flexShrink: 0,
|
|
45
|
+
fontStyle: "italic",
|
|
46
|
+
background: isError ? "rgba(239, 68, 68, 0.1)" : isRunning ? "rgba(234, 179, 8, 0.1)" : "rgba(255, 255, 255, 0.04)",
|
|
47
|
+
color: isError ? "rgba(248, 113, 113, 0.8)" : isRunning ? "rgba(234, 179, 8, 0.8)" : "var(--ci-text-dim)",
|
|
48
|
+
border: `1px solid ${isError ? "rgba(239, 68, 68, 0.2)" : isRunning ? "rgba(234, 179, 8, 0.2)" : "var(--ci-border)"}`,
|
|
49
|
+
}, children: "reactor" }), _jsx("span", { className: "ci-mono", style: { fontSize: 10, color: "var(--ci-text)", opacity: 0.6, flexShrink: 0 }, children: reactorId }), isError && (_jsxs("span", { className: "flex items-center gap-1 text-[9px] text-red-400/80 shrink-0", title: outcome.error ?? "Error", children: [_jsx(AlertTriangle, { size: 10 }), outcome.attempts > 1 && _jsxs("span", { children: ["x", outcome.attempts] })] })), collapsed && (_jsxs("span", { style: { fontSize: 10, color: "var(--ci-text-muted)", flexShrink: 0 }, children: ["(", children.length, ")"] }))] })] }), isError && outcome.error && (_jsx("div", { className: "mt-1 ml-7 text-[9px] text-red-400/70 truncate", title: outcome.error, children: outcome.error }))] }), !collapsed && children.map((child) => (_jsx(TreeNode, { event: child, childrenMap: childrenMap, depth: depth + 1, onClickReactor: onClickReactor, outcomesByReactor: outcomesByReactor }, child.seq)))] }));
|
|
38
50
|
}
|
|
39
51
|
// ---------------------------------------------------------------------------
|
|
40
52
|
// TreeNode (recursive)
|
|
41
53
|
// ---------------------------------------------------------------------------
|
|
42
|
-
function TreeNode({ event, childrenMap, depth, onClickReactor, onInvestigate, }) {
|
|
54
|
+
function TreeNode({ event, childrenMap, depth, onClickReactor, onInvestigate, outcomesByReactor, }) {
|
|
43
55
|
const selectedSeq = useSelector((s) => s.selectedSeq);
|
|
44
56
|
const flowSelection = useSelector((s) => s.flowSelection);
|
|
45
57
|
const dispatch = useDispatch();
|
|
@@ -72,19 +84,19 @@ function TreeNode({ event, childrenMap, depth, onClickReactor, onInvestigate, })
|
|
|
72
84
|
return { reactorGroups: groups, directChildren: direct };
|
|
73
85
|
}, [children]);
|
|
74
86
|
const highlightedReactorId = flowSelection?.kind === "reactor" ? flowSelection.reactorId : null;
|
|
75
|
-
return (_jsxs("div", {
|
|
87
|
+
return (_jsxs("div", { style: depth > 0 ? { paddingLeft: 24 } : undefined, children: [_jsxs("div", { ref: isSelected ? nodeRef : undefined, onClick: () => {
|
|
76
88
|
dispatch({ type: "ui/event_selected", payload: { seq: event.seq } });
|
|
77
89
|
if (event.correlationId) {
|
|
78
90
|
dispatch({ type: "ui/flow_opened", payload: { correlationId: event.correlationId } });
|
|
79
91
|
}
|
|
80
|
-
}, className: `
|
|
92
|
+
}, className: `ci-tree-node${isSelected ? " ci-selected" : ""}`, style: { cursor: "pointer" }, children: [_jsxs("div", { className: "ci-row", style: { gap: 6, minWidth: 0 }, children: [hasChildren ? (_jsx("button", { onClick: (e) => { e.stopPropagation(); setCollapsed((v) => !v); }, style: { fontSize: 10, color: "var(--ci-text-muted)", background: "none", border: "none", cursor: "pointer", flexShrink: 0, width: 12, textAlign: "center", padding: 0, transition: "color 150ms" }, children: collapsed ? _jsx(ChevronRight, { size: 10 }) : _jsx(ChevronDown, { size: 10 }) })) : (_jsx("span", { style: { width: 12, flexShrink: 0 } })), _jsx("span", { className: "ci-mono", style: { fontSize: 10, flexShrink: 0, color: eventTextColor(event.name) }, children: event.name }), collapsed && hasChildren && (_jsxs("span", { style: { fontSize: 10, color: "var(--ci-text-muted)", flexShrink: 0 }, children: ["(", children.length, ")"] })), _jsx("span", { style: { fontSize: 10, color: "var(--ci-text-muted)", flexShrink: 0 }, children: formatTs(event.ts) }), _jsx("button", { onClick: (e) => {
|
|
81
93
|
e.stopPropagation();
|
|
82
94
|
const json = buildTreeJson([event], childrenMap);
|
|
83
95
|
const text = JSON.stringify(json[0], null, 2);
|
|
84
96
|
copyToClipboard(text);
|
|
85
97
|
setCopied(true);
|
|
86
98
|
setTimeout(() => setCopied(false), 1500);
|
|
87
|
-
}, className: "
|
|
99
|
+
}, className: "ci-tree-actions ci-btn", style: { marginLeft: "auto", flexShrink: 0, fontSize: 10 }, title: "Copy subtree as JSON", children: copied ? _jsx(Check, { size: 12 }) : _jsx(Copy, { size: 12 }) }), onInvestigate && (_jsx("button", { onClick: (e) => { e.stopPropagation(); onInvestigate(event); }, className: "ci-tree-actions ci-btn", style: { flexShrink: 0 }, title: "Investigate", children: _jsx(Search, { size: 12 }) }))] }), _jsx("button", { onClick: (e) => { e.stopPropagation(); setPayloadOpen((v) => !v); }, className: "ci-mono ci-truncate", style: { marginTop: 2, marginLeft: 12, fontSize: 10, color: "var(--ci-text-muted)", textAlign: "left", maxWidth: "100%", display: "block", background: "none", border: "none", cursor: "pointer", padding: 0, transition: "color 150ms" }, title: "Click to expand payload", children: event.summary ?? compactPayload(event.payload) }), payloadOpen && (_jsx("div", { style: { marginTop: 4, marginLeft: 12, maxHeight: 192 }, children: _jsx(CopyablePayload, { payload: event.payload }) }))] }), !collapsed && (_jsxs(_Fragment, { children: [directChildren.map((child) => (_jsx(TreeNode, { event: child, childrenMap: childrenMap, depth: depth + 1, onClickReactor: onClickReactor, onInvestigate: onInvestigate, outcomesByReactor: outcomesByReactor }, child.seq))), [...reactorGroups.entries()].map(([hid, group]) => (_jsx(ReactorNode, { reactorId: hid, parentEventId: event.id, children: group, childrenMap: childrenMap, depth: depth + 1, isHighlighted: hid === highlightedReactorId, onClickReactor: onClickReactor, outcome: outcomesByReactor?.get(hid), outcomesByReactor: outcomesByReactor }, hid)))] }))] }));
|
|
88
100
|
}
|
|
89
101
|
// ---------------------------------------------------------------------------
|
|
90
102
|
// matchesFlowSelection
|
|
@@ -103,7 +115,20 @@ export function CausalTreePane({ onInvestigate } = {}) {
|
|
|
103
115
|
const flowCorrelationId = useSelector((s) => s.flowCorrelationId);
|
|
104
116
|
const scrubberStart = useSelector((s) => s.scrubberStart);
|
|
105
117
|
const scrubberEnd = useSelector((s) => s.scrubberEnd);
|
|
118
|
+
const outcomesMap = useSelector((s) => s.outcomes);
|
|
106
119
|
const dispatch = useDispatch();
|
|
120
|
+
// Build reactor outcome lookup for current flow
|
|
121
|
+
const outcomesByReactor = useMemo(() => {
|
|
122
|
+
if (!flowCorrelationId)
|
|
123
|
+
return new Map();
|
|
124
|
+
const raw = outcomesMap[flowCorrelationId];
|
|
125
|
+
if (!raw)
|
|
126
|
+
return new Map();
|
|
127
|
+
const map = new Map();
|
|
128
|
+
for (const o of raw)
|
|
129
|
+
map.set(o.reactorId, o);
|
|
130
|
+
return map;
|
|
131
|
+
}, [outcomesMap, flowCorrelationId]);
|
|
107
132
|
const treeEvents = useMemo(() => {
|
|
108
133
|
const all = causalTree?.events ?? null;
|
|
109
134
|
if (all == null || (scrubberStart == null && scrubberEnd == null))
|
|
@@ -142,17 +167,17 @@ export function CausalTreePane({ onInvestigate } = {}) {
|
|
|
142
167
|
return { roots: rootList, childrenMap: cMap, totalCount: total, filteredCount: filtered };
|
|
143
168
|
}, [treeEvents, flowCorrelationId, flowSelection]);
|
|
144
169
|
if (treeLoading) {
|
|
145
|
-
return (_jsxs("div", { className: "
|
|
170
|
+
return (_jsxs("div", { className: "ci-pulse", style: { padding: 12, display: "flex", flexDirection: "column", gap: 6 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 12, width: 128, marginBottom: 12 } }), _jsxs("div", { className: "ci-row", style: { gap: 6 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 16, width: 48 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 16, width: 144 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 12, width: 96 } })] }), _jsxs("div", { style: { paddingLeft: 24, display: "flex", flexDirection: "column", gap: 6 }, children: [_jsxs("div", { className: "ci-row", style: { gap: 6 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 16, width: 56 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 16, width: 176 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 12, width: 96 } })] }), _jsxs("div", { className: "ci-row", style: { gap: 6 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 16, width: 40 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 16, width: 128 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 12, width: 96 } })] })] })] }));
|
|
146
171
|
}
|
|
147
172
|
if (!treeEvents) {
|
|
148
|
-
return (_jsx("div", { className: "
|
|
173
|
+
return (_jsx("div", { className: "ci-empty", children: "Select an event to view its causal tree" }));
|
|
149
174
|
}
|
|
150
175
|
if (roots.length === 0 && flowSelection) {
|
|
151
|
-
return (_jsxs("div", {
|
|
176
|
+
return (_jsxs("div", { style: { height: "100%", overflow: "auto", padding: 12 }, children: [_jsxs("div", { className: "ci-flow-filter ci-row", style: { gap: 8, marginBottom: 8 }, children: [_jsx("span", { children: flowSelection.kind === "event-type"
|
|
152
177
|
? flowSelection.name
|
|
153
|
-
: `outputs of ${flowSelection.reactorId}` }), _jsx("button", { onClick: () => dispatch({ type: "ui/flow_node_selected", payload: null }),
|
|
178
|
+
: `outputs of ${flowSelection.reactorId}` }), _jsx("button", { onClick: () => dispatch({ type: "ui/flow_node_selected", payload: null }), style: { marginLeft: "auto", background: "none", border: "none", cursor: "pointer", color: "inherit", padding: 0, transition: "color 150ms" }, children: _jsx(X, { size: 12 }) })] }), _jsx("div", { className: "ci-empty", style: { height: 128, fontSize: 14 }, children: "No events match the current filter" })] }));
|
|
154
179
|
}
|
|
155
|
-
return (_jsxs("div", {
|
|
180
|
+
return (_jsxs("div", { style: { height: "100%", overflow: "auto", padding: 12 }, children: [flowSelection && (_jsxs("div", { className: "ci-flow-filter ci-row", style: { gap: 8, marginBottom: 8 }, children: [_jsx("span", { children: flowSelection.kind === "event-type"
|
|
156
181
|
? flowSelection.name
|
|
157
|
-
: `outputs of ${flowSelection.reactorId}` }), _jsx("button", { onClick: () => dispatch({ type: "ui/flow_node_selected", payload: null }),
|
|
182
|
+
: `outputs of ${flowSelection.reactorId}` }), _jsx("button", { onClick: () => dispatch({ type: "ui/flow_node_selected", payload: null }), style: { marginLeft: "auto", background: "none", border: "none", cursor: "pointer", color: "inherit", padding: 0, transition: "color 150ms" }, children: _jsx(X, { size: 12 }) })] })), _jsxs("h3", { className: "ci-header", style: { marginBottom: 8 }, children: ["Causal Tree (", flowSelection ? `${filteredCount} of ${totalCount}` : totalCount, " events)"] }), roots.map(root => (_jsx(TreeNode, { event: root, childrenMap: childrenMap, depth: 0, onClickReactor: onClickReactor, onInvestigate: onInvestigate, outcomesByReactor: outcomesByReactor }, root.seq)))] }));
|
|
158
183
|
}
|
|
@@ -39,8 +39,13 @@ export function CorrelationExplorerPane() {
|
|
|
39
39
|
const handleCopy = useCallback((text) => {
|
|
40
40
|
navigator.clipboard.writeText(text).catch(() => { });
|
|
41
41
|
}, []);
|
|
42
|
-
return (_jsxs("div", { className: "
|
|
42
|
+
return (_jsxs("div", { className: "ci-col", children: [_jsx("div", { className: "ci-surface", style: { padding: "10px 12px", borderBottom: "1px solid var(--ci-border)" }, children: _jsx("input", { type: "text", placeholder: "Search by correlation ID or event type...", value: search, onChange: (e) => handleSearchChange(e.target.value), className: "ci-input", style: { width: "100%", padding: "6px 12px", fontSize: 12 } }) }), _jsxs("div", { className: "ci-row ci-header", style: { gap: 8, padding: "8px 12px", borderBottom: "1px solid var(--ci-border)", letterSpacing: "0.08em", fontSize: 9 }, children: [_jsx("span", { style: { width: 112, flexShrink: 0 }, children: "Root Event" }), _jsx("span", { style: { width: 96, flexShrink: 0 }, children: "Correlation" }), _jsx("span", { style: { width: 48, flexShrink: 0, textAlign: "right" }, children: "Events" }), _jsx("span", { style: { width: 80, flexShrink: 0, textAlign: "right" }, children: "Duration" }), _jsx("span", { style: { flex: 1 }, children: "Last Activity" })] }), loading && correlations.length === 0 ? (_jsx("div", { className: "ci-pulse", style: { padding: 12 }, children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "ci-row", style: { gap: 8, padding: "10px 0" }, children: [_jsx("div", { className: "ci-skeleton", style: { height: 12, width: 112 } }), _jsx("div", { className: "ci-skeleton", style: { height: 12, width: 96 } }), _jsx("div", { className: "ci-skeleton", style: { height: 12, width: 48 } })] }, i))) })) : correlations.length === 0 ? (_jsx("div", { className: "ci-empty", style: { height: 128 }, children: "No correlations found" })) : (_jsx("div", { className: "ci-scroll", children: correlations.map((corr) => (_jsxs("button", { onClick: () => handleRowClick(corr.correlationId), className: "ci-list-row ci-row", style: { width: "100%", textAlign: "left", gap: 8, padding: "10px 12px", borderBottom: "1px solid var(--ci-border)", background: "none", border: "none", borderBottomStyle: "solid", cursor: "pointer" }, children: [_jsx("span", { className: "ci-mono ci-truncate", style: {
|
|
43
|
+
fontSize: 10,
|
|
44
|
+
flexShrink: 0,
|
|
45
|
+
width: 112,
|
|
46
|
+
padding: "2px 6px",
|
|
47
|
+
borderRadius: "var(--ci-radius-sm)",
|
|
43
48
|
color: eventTextColor(corr.rootEventType),
|
|
44
49
|
background: eventBg(corr.rootEventType),
|
|
45
|
-
}, title: corr.rootEventType, children: corr.rootEventType }), _jsx("span", { className: "
|
|
50
|
+
}, title: corr.rootEventType, children: corr.rootEventType }), _jsx("span", { className: "ci-mono ci-truncate", style: { fontSize: 10, color: "var(--ci-purple-dim)", width: 96, flexShrink: 0, cursor: "pointer", transition: "color 150ms" }, title: `Click to copy: ${corr.correlationId}`, onClick: (e) => { e.stopPropagation(); handleCopy(corr.correlationId); }, children: corr.correlationId.slice(0, 8) }), _jsx("span", { className: "ci-mono ci-tabular", style: { fontSize: 11, color: "var(--ci-text)", opacity: 0.7, width: 48, flexShrink: 0, textAlign: "right" }, children: corr.eventCount }), _jsx("span", { className: "ci-mono ci-tabular", style: { fontSize: 10, color: "var(--ci-text-muted)", opacity: 0.5, width: 80, flexShrink: 0, textAlign: "right" }, children: _jsx(RelativeDuration, { firstTs: corr.firstTs, lastTs: corr.lastTs }) }), _jsx("span", { className: "ci-tabular ci-truncate", style: { fontSize: 10, color: "var(--ci-text-dimmer)", flex: 1 }, children: formatTs(corr.lastTs) }), corr.hasErrors && (_jsx("span", { className: "ci-error-dot", title: "This correlation has errors" }))] }, corr.correlationId))) }))] }));
|
|
46
51
|
}
|
package/dist/panes/LogsPane.js
CHANGED
|
@@ -9,8 +9,8 @@ import { Search, ChevronRight, ChevronDown } from "lucide-react";
|
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
function LogRow({ log, showReactor }) {
|
|
11
11
|
const [expanded, setExpanded] = useState(false);
|
|
12
|
-
const levelColor = LOG_LEVEL_COLORS[log.level] ?? "
|
|
13
|
-
return (_jsxs("div", {
|
|
12
|
+
const levelColor = LOG_LEVEL_COLORS[log.level] ?? "ci-log-debug";
|
|
13
|
+
return (_jsxs("div", { style: { padding: "6px 8px", borderRadius: "var(--ci-radius-md)", transition: "background 100ms" }, className: "ci-list-row", children: [_jsxs("div", { className: "ci-row", style: { gap: 8, minWidth: 0 }, children: [_jsx("span", { className: levelColor, style: { padding: "2px 6px", borderRadius: "var(--ci-radius-sm)", fontSize: 9, fontWeight: 600, textTransform: "uppercase", flexShrink: 0 }, children: log.level }), _jsx("span", { className: "ci-tabular", style: { fontSize: 10, color: "var(--ci-text-muted)", opacity: 0.5, flexShrink: 0 }, children: formatTs(log.loggedAt) }), showReactor && (_jsx("span", { className: "ci-mono", style: { fontSize: 10, color: "var(--ci-text-dimmer)", flexShrink: 0 }, children: log.reactorId })), _jsx("span", { className: "ci-truncate", style: { fontSize: 11, color: "var(--ci-text)", opacity: 0.8 }, children: log.message }), log.data != null && (_jsx("button", { onClick: () => setExpanded((v) => !v), className: "ci-btn", style: { marginLeft: "auto", flexShrink: 0, fontSize: 10 }, children: expanded ? _jsx(ChevronDown, { size: 10 }) : _jsx(ChevronRight, { size: 10 }) }))] }), expanded && log.data != null && (_jsx("pre", { className: "ci-mono", style: { marginTop: 6, marginLeft: 16, fontSize: 10, color: "var(--ci-text-muted)", opacity: 0.7, background: "rgba(255, 255, 255, 0.02)", borderRadius: "var(--ci-radius-md)", padding: 10, maxHeight: 128, overflow: "auto", whiteSpace: "pre-wrap", border: "1px solid var(--ci-border)" }, children: typeof log.data === "string" ? log.data : JSON.stringify(log.data, null, 2) }))] }));
|
|
14
14
|
}
|
|
15
15
|
export function LogsPane({ onInvestigate } = {}) {
|
|
16
16
|
const logs = useSelector((s) => s.logs);
|
|
@@ -18,7 +18,7 @@ export function LogsPane({ onInvestigate } = {}) {
|
|
|
18
18
|
const flowData = useSelector((s) => s.flowData);
|
|
19
19
|
const scrubberStart = useSelector((s) => s.scrubberStart);
|
|
20
20
|
const scrubberEnd = useSelector((s) => s.scrubberEnd);
|
|
21
|
-
const [levelFilter, setLevelFilter] = useState(new Set(["debug", "info", "warn"]));
|
|
21
|
+
const [levelFilter, setLevelFilter] = useState(new Set(["debug", "info", "warn", "error"]));
|
|
22
22
|
const [searchText, setSearchText] = useState("");
|
|
23
23
|
const isCorrelationScope = logsFilter.scope === "correlation" && logsFilter.correlationId != null;
|
|
24
24
|
const hasFilter = logsFilter.reactorId != null || isCorrelationScope;
|
|
@@ -47,7 +47,7 @@ export function LogsPane({ onInvestigate } = {}) {
|
|
|
47
47
|
return filtered;
|
|
48
48
|
}, [logs, logsFilter, levelFilter, searchText, visibleEventIds]);
|
|
49
49
|
if (!hasFilter) {
|
|
50
|
-
return (_jsx("div", { className: "
|
|
50
|
+
return (_jsx("div", { className: "ci-empty", children: "Click a reactor node in the causal tree to view logs" }));
|
|
51
51
|
}
|
|
52
52
|
const toggleLevel = (level) => {
|
|
53
53
|
setLevelFilter((prev) => {
|
|
@@ -59,7 +59,16 @@ export function LogsPane({ onInvestigate } = {}) {
|
|
|
59
59
|
return next;
|
|
60
60
|
});
|
|
61
61
|
};
|
|
62
|
-
return (_jsxs("div", { className: "
|
|
63
|
-
|
|
64
|
-
: "
|
|
62
|
+
return (_jsxs("div", { className: "ci-col", children: [_jsxs("div", { className: "ci-row ci-surface", style: { padding: "8px 12px", borderBottom: "1px solid var(--ci-border)", gap: 12, flexWrap: "wrap" }, children: [_jsx("div", { className: "ci-row", style: { gap: 4, fontSize: 10 }, children: ["debug", "info", "warn", "error"].map((level) => (_jsx("button", { onClick: () => toggleLevel(level), className: levelFilter.has(level) ? LOG_LEVEL_COLORS[level] : "", style: {
|
|
63
|
+
padding: "2px 8px",
|
|
64
|
+
borderRadius: "var(--ci-radius-md)",
|
|
65
|
+
textTransform: "uppercase",
|
|
66
|
+
fontWeight: 600,
|
|
67
|
+
border: "none",
|
|
68
|
+
cursor: "pointer",
|
|
69
|
+
background: levelFilter.has(level) ? undefined : "transparent",
|
|
70
|
+
color: levelFilter.has(level) ? undefined : "var(--ci-text-dimmer)",
|
|
71
|
+
textDecoration: levelFilter.has(level) ? undefined : "line-through",
|
|
72
|
+
transition: "all 150ms",
|
|
73
|
+
}, children: level }, level))) }), _jsxs("div", { className: "ci-row", style: { position: "relative", marginLeft: "auto" }, children: [_jsx(Search, { size: 10, style: { position: "absolute", left: 10, color: "var(--ci-text-dimmer)", pointerEvents: "none" } }), _jsx("input", { type: "text", placeholder: "Search logs...", value: searchText, onChange: (e) => setSearchText(e.target.value), className: "ci-input", style: { paddingLeft: 28, paddingRight: 8, fontSize: 11, paddingTop: 4, paddingBottom: 4, width: 176 } })] }), onInvestigate && (_jsx("button", { onClick: () => onInvestigate(logsFilter), className: "ci-btn ci-row", style: { gap: 4, fontSize: 11 }, title: "Investigate logs", children: _jsx(Search, { size: 12 }) }))] }), _jsxs("div", { style: { padding: "6px 12px", fontSize: 10, color: "var(--ci-text-muted)", opacity: 0.5 }, children: [logsFilter.reactorId ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "ci-mono", style: { color: "var(--ci-text)", opacity: 0.6 }, children: logsFilter.reactorId }), isCorrelationScope && _jsx("span", { style: { marginLeft: 4 }, children: "(all reactors in correlation)" })] })) : (_jsx("span", { children: "All reactors in correlation" })), _jsxs("span", { className: "ci-tabular", style: { marginLeft: 8 }, children: [filteredLogs.length, " logs"] })] }), _jsxs("div", { className: "ci-scroll", style: { padding: "0 4px" }, children: [filteredLogs.length === 0 && (_jsx("div", { style: { padding: 12, fontSize: 11, color: "var(--ci-text-dimmer)" }, children: "No logs match filters" })), filteredLogs.map((log, i) => (_jsx(LogRow, { log: log, showReactor: isCorrelationScope }, i)))] })] }));
|
|
65
74
|
}
|