@workflow/web-shared 4.1.1 → 4.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/event-list-view.d.ts +3 -1
- package/dist/components/event-list-view.d.ts.map +1 -1
- package/dist/components/event-list-view.js +53 -53
- package/dist/components/event-list-view.js.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/sidebar/attribute-panel.d.ts +5 -1
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +35 -25
- package/dist/components/sidebar/attribute-panel.js.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +17 -15
- package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
- package/dist/components/ui/data-inspector.d.ts +14 -1
- package/dist/components/ui/data-inspector.d.ts.map +1 -1
- package/dist/components/ui/data-inspector.js +32 -11
- package/dist/components/ui/data-inspector.js.map +1 -1
- package/dist/components/workflow-trace-view.d.ts +3 -1
- package/dist/components/workflow-trace-view.d.ts.map +1 -1
- package/dist/components/workflow-trace-view.js +2 -2
- package/dist/components/workflow-trace-view.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/hydration.d.ts +7 -0
- package/dist/lib/hydration.d.ts.map +1 -1
- package/dist/lib/hydration.js +25 -0
- package/dist/lib/hydration.js.map +1 -1
- package/package.json +2 -2
- package/src/components/event-list-view.tsx +181 -173
- package/src/components/index.ts +6 -1
- package/src/components/sidebar/attribute-panel.tsx +145 -106
- package/src/components/sidebar/entity-detail-panel.tsx +106 -95
- package/src/components/ui/data-inspector.tsx +84 -19
- package/src/components/workflow-trace-view.tsx +4 -0
- package/src/index.ts +1 -0
- package/src/lib/hydration.ts +26 -0
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { Lock } from 'lucide-react';
|
|
12
12
|
import { createContext, useContext, useEffect, useRef, useState } from 'react';
|
|
13
|
+
import { Spinner } from './spinner';
|
|
13
14
|
import {
|
|
14
15
|
ObjectInspector,
|
|
15
16
|
ObjectLabel,
|
|
@@ -75,6 +76,68 @@ export const StreamClickContext = createContext<
|
|
|
75
76
|
((streamId: string) => void) | undefined
|
|
76
77
|
>(undefined);
|
|
77
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Context for passing a decrypt handler down to DataInspector instances.
|
|
81
|
+
* When provided, encrypted markers become clickable buttons that trigger decryption.
|
|
82
|
+
*/
|
|
83
|
+
export type DecryptClickContextValue = {
|
|
84
|
+
onDecrypt: () => void;
|
|
85
|
+
isDecrypting: boolean;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const DecryptClickContext = createContext<
|
|
89
|
+
DecryptClickContextValue | undefined
|
|
90
|
+
>(undefined);
|
|
91
|
+
|
|
92
|
+
function EncryptedInlineLabel() {
|
|
93
|
+
const ctx = useContext(DecryptClickContext);
|
|
94
|
+
if (ctx) {
|
|
95
|
+
return (
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] cursor-pointer"
|
|
99
|
+
style={{
|
|
100
|
+
backgroundColor: 'var(--ds-gray-100)',
|
|
101
|
+
color: 'var(--ds-gray-700)',
|
|
102
|
+
border: '1px solid var(--ds-gray-400)',
|
|
103
|
+
fontStyle: 'italic',
|
|
104
|
+
opacity: ctx.isDecrypting ? 0.6 : 1,
|
|
105
|
+
}}
|
|
106
|
+
disabled={ctx.isDecrypting}
|
|
107
|
+
onClick={(e) => {
|
|
108
|
+
e.stopPropagation();
|
|
109
|
+
ctx.onDecrypt();
|
|
110
|
+
}}
|
|
111
|
+
title="Click to decrypt"
|
|
112
|
+
>
|
|
113
|
+
{ctx.isDecrypting ? (
|
|
114
|
+
<Spinner size={12} />
|
|
115
|
+
) : (
|
|
116
|
+
<Lock
|
|
117
|
+
className="h-3 w-3"
|
|
118
|
+
style={{ display: 'inline', flexShrink: 0 }}
|
|
119
|
+
/>
|
|
120
|
+
)}
|
|
121
|
+
<span>{ctx.isDecrypting ? 'Decrypting…' : 'Decrypt'}</span>
|
|
122
|
+
</button>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return (
|
|
126
|
+
<span style={{ color: 'var(--ds-gray-600)', fontStyle: 'italic' }}>
|
|
127
|
+
<Lock
|
|
128
|
+
className="h-3 w-3"
|
|
129
|
+
style={{
|
|
130
|
+
display: 'inline',
|
|
131
|
+
verticalAlign: 'middle',
|
|
132
|
+
marginRight: '3px',
|
|
133
|
+
marginTop: '-1px',
|
|
134
|
+
}}
|
|
135
|
+
/>
|
|
136
|
+
Encrypted
|
|
137
|
+
</span>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
78
141
|
function StreamRefInline({ streamRef }: { streamRef: StreamRef }) {
|
|
79
142
|
const onStreamClick = useContext(StreamClickContext);
|
|
80
143
|
return (
|
|
@@ -132,26 +195,13 @@ function NodeRenderer({
|
|
|
132
195
|
}) {
|
|
133
196
|
const extendedTheme = useContext(ExtendedThemeContext);
|
|
134
197
|
|
|
135
|
-
// Encrypted marker → flat label with Lock icon,
|
|
198
|
+
// Encrypted marker → flat label with Lock icon, clickable when onDecrypt is available
|
|
136
199
|
if (
|
|
137
200
|
data !== null &&
|
|
138
201
|
typeof data === 'object' &&
|
|
139
202
|
data.constructor?.name === ENCRYPTED_DISPLAY_NAME
|
|
140
203
|
) {
|
|
141
|
-
const label =
|
|
142
|
-
<span style={{ color: 'var(--ds-gray-600)', fontStyle: 'italic' }}>
|
|
143
|
-
<Lock
|
|
144
|
-
className="h-3 w-3"
|
|
145
|
-
style={{
|
|
146
|
-
display: 'inline',
|
|
147
|
-
verticalAlign: 'middle',
|
|
148
|
-
marginRight: '3px',
|
|
149
|
-
marginTop: '-1px',
|
|
150
|
-
}}
|
|
151
|
-
/>
|
|
152
|
-
Encrypted
|
|
153
|
-
</span>
|
|
154
|
-
);
|
|
204
|
+
const label = <EncryptedInlineLabel />;
|
|
155
205
|
if (depth === 0) {
|
|
156
206
|
return label;
|
|
157
207
|
}
|
|
@@ -234,6 +284,10 @@ export interface DataInspectorProps {
|
|
|
234
284
|
name?: string;
|
|
235
285
|
/** Callback when a stream reference is clicked */
|
|
236
286
|
onStreamClick?: (streamId: string) => void;
|
|
287
|
+
/** Callback when an encrypted marker is clicked (triggers decryption) */
|
|
288
|
+
onDecrypt?: () => void;
|
|
289
|
+
/** Whether decryption is currently in progress */
|
|
290
|
+
isDecrypting?: boolean;
|
|
237
291
|
}
|
|
238
292
|
|
|
239
293
|
export function DataInspector({
|
|
@@ -241,6 +295,8 @@ export function DataInspector({
|
|
|
241
295
|
expandLevel = 2,
|
|
242
296
|
name,
|
|
243
297
|
onStreamClick,
|
|
298
|
+
onDecrypt,
|
|
299
|
+
isDecrypting = false,
|
|
244
300
|
}: DataInspectorProps) {
|
|
245
301
|
const stableData = useStableInspectorData(data);
|
|
246
302
|
const [initialExpandLevel, setInitialExpandLevel] = useState(expandLevel);
|
|
@@ -269,16 +325,25 @@ export function DataInspector({
|
|
|
269
325
|
</ExtendedThemeContext.Provider>
|
|
270
326
|
);
|
|
271
327
|
|
|
272
|
-
|
|
328
|
+
let wrapped = content;
|
|
329
|
+
|
|
273
330
|
if (onStreamClick) {
|
|
274
|
-
|
|
331
|
+
wrapped = (
|
|
275
332
|
<StreamClickContext.Provider value={onStreamClick}>
|
|
276
|
-
{
|
|
333
|
+
{wrapped}
|
|
277
334
|
</StreamClickContext.Provider>
|
|
278
335
|
);
|
|
279
336
|
}
|
|
280
337
|
|
|
281
|
-
|
|
338
|
+
if (onDecrypt) {
|
|
339
|
+
wrapped = (
|
|
340
|
+
<DecryptClickContext.Provider value={{ onDecrypt, isDecrypting }}>
|
|
341
|
+
{wrapped}
|
|
342
|
+
</DecryptClickContext.Provider>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return wrapped;
|
|
282
347
|
}
|
|
283
348
|
|
|
284
349
|
function useStableInspectorData<T>(next: T): T {
|
|
@@ -775,6 +775,7 @@ export const WorkflowTraceViewer = ({
|
|
|
775
775
|
encryptionKey,
|
|
776
776
|
onDecrypt,
|
|
777
777
|
isDecrypting = false,
|
|
778
|
+
hasEncryptedData = false,
|
|
778
779
|
}: {
|
|
779
780
|
run: WorkflowRun;
|
|
780
781
|
events: Event[];
|
|
@@ -815,6 +816,8 @@ export const WorkflowTraceViewer = ({
|
|
|
815
816
|
onDecrypt?: () => void;
|
|
816
817
|
/** Whether the encryption key is currently being fetched */
|
|
817
818
|
isDecrypting?: boolean;
|
|
819
|
+
/** Run-level hint: the run contains encrypted data (from probe). */
|
|
820
|
+
hasEncryptedData?: boolean;
|
|
818
821
|
}) => {
|
|
819
822
|
const toast = useToast();
|
|
820
823
|
const [selectedSpan, setSelectedSpan] = useState<SelectedSpanInfo | null>(
|
|
@@ -1151,6 +1154,7 @@ export const WorkflowTraceViewer = ({
|
|
|
1151
1154
|
onDecrypt={onDecrypt}
|
|
1152
1155
|
isDecrypting={isDecrypting}
|
|
1153
1156
|
selectedSpan={selectedSpan}
|
|
1157
|
+
hasEncryptedData={hasEncryptedData}
|
|
1154
1158
|
/>
|
|
1155
1159
|
</ErrorBoundary>
|
|
1156
1160
|
</div>
|
package/src/index.ts
CHANGED
package/src/lib/hydration.ts
CHANGED
|
@@ -359,3 +359,29 @@ export async function hydrateResourceIOWithKey<T>(
|
|
|
359
359
|
|
|
360
360
|
return result as T;
|
|
361
361
|
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Check whether a hydrated resource (event, step, run, etc.) contains any
|
|
365
|
+
* encrypted display markers. Inspects the standard top-level fields
|
|
366
|
+
* (`input`, `output`, `error`, `metadata`) as well as the event-type-specific
|
|
367
|
+
* `eventData` ref fields.
|
|
368
|
+
*/
|
|
369
|
+
export function hasEncryptedFields(resource: unknown): boolean {
|
|
370
|
+
if (!resource || typeof resource !== 'object') return false;
|
|
371
|
+
const r = resource as Record<string, unknown>;
|
|
372
|
+
|
|
373
|
+
for (const key of ['input', 'output', 'metadata', 'error']) {
|
|
374
|
+
if (isEncryptedMarker(r[key])) return true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (r.eventData && typeof r.eventData === 'object') {
|
|
378
|
+
const eventType = typeof r.eventType === 'string' ? r.eventType : '';
|
|
379
|
+
const refKeys = EVENT_DATA_REF_FIELDS[eventType] ?? [];
|
|
380
|
+
const ed = r.eventData as Record<string, unknown>;
|
|
381
|
+
for (const key of refKeys) {
|
|
382
|
+
if (key in ed && isEncryptedMarker(ed[key])) return true;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return false;
|
|
387
|
+
}
|