@prsm/devtools 1.1.0 → 1.3.0
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/client/assets/index-BEgM9Z3B.css +1 -0
- package/dist/client/assets/index-DYVuye_Q.js +178 -0
- package/dist/client/index.html +9 -3
- package/package.json +9 -4
- package/src/index.js +172 -4
- package/dist/client/assets/index-Dj0vLuDk.css +0 -1
- package/dist/client/assets/index-LvnrgtvZ.js +0 -176
package/dist/client/index.html
CHANGED
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>prsm devtools</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
|
+
<link
|
|
10
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Space+Mono:wght@400;700&display=swap"
|
|
11
|
+
rel="stylesheet"
|
|
12
|
+
/>
|
|
7
13
|
<style>
|
|
8
|
-
body { margin: 0; background: #
|
|
14
|
+
body { margin: 0; background: #ffffff; }
|
|
9
15
|
</style>
|
|
10
|
-
<script type="module" crossorigin src="./assets/index-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
16
|
+
<script type="module" crossorigin src="./assets/index-DYVuye_Q.js"></script>
|
|
17
|
+
<link rel="stylesheet" crossorigin href="./assets/index-BEgM9Z3B.css">
|
|
12
18
|
</head>
|
|
13
19
|
<body>
|
|
14
20
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prsm/devtools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Read-only Express middleware dashboard for observing @prsm infrastructure at runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -31,12 +31,17 @@
|
|
|
31
31
|
"express": "^4.21.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
+
"@prsm/cache": "^1.0.2",
|
|
34
35
|
"@prsm/cells": "^1.2.0",
|
|
35
|
-
"@prsm/cron": "^1.0
|
|
36
|
-
"@prsm/limit": "^1.
|
|
36
|
+
"@prsm/cron": "^1.1.0",
|
|
37
|
+
"@prsm/limit": "^1.2.0",
|
|
38
|
+
"@prsm/lock": "^1.1.0",
|
|
37
39
|
"@prsm/queue": "^3.0.8",
|
|
40
|
+
"@prsm/realtime": "^1.0.5",
|
|
41
|
+
"@prsm/workflow": "^3.1.0",
|
|
38
42
|
"cors": "^2.8.6",
|
|
39
|
-
"dotenv": "^16.4.7"
|
|
43
|
+
"dotenv": "^16.4.7",
|
|
44
|
+
"pg": "^8.13.1"
|
|
40
45
|
},
|
|
41
46
|
"engines": {
|
|
42
47
|
"node": ">=18"
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Router, static as serveStatic } from 'express'
|
|
1
|
+
import { Router, static as serveStatic, json } from 'express'
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { resolve, dirname } from 'node:path'
|
|
4
4
|
import { existsSync, readFileSync } from 'node:fs'
|
|
@@ -29,9 +29,23 @@ function patternToString(p) {
|
|
|
29
29
|
*/
|
|
30
30
|
export function prsmDevtools(options = {}) {
|
|
31
31
|
const router = Router()
|
|
32
|
-
const { queue, cron, limit, workflow, realtime } = options
|
|
32
|
+
const { queue, cron, limit, workflow, realtime, lock, cache } = options
|
|
33
|
+
const connectionDisplay = typeof options.connectionDisplay === 'function' ? options.connectionDisplay : null
|
|
34
|
+
|
|
35
|
+
function displayFor(metadata) {
|
|
36
|
+
if (!connectionDisplay) return { label: null, sublabel: null }
|
|
37
|
+
try {
|
|
38
|
+
const out = connectionDisplay(metadata) ?? {}
|
|
39
|
+
const label = typeof out.label === 'string' && out.label.length ? out.label : null
|
|
40
|
+
const sublabel = typeof out.sublabel === 'string' && out.sublabel.length ? out.sublabel : null
|
|
41
|
+
return { label, sublabel }
|
|
42
|
+
} catch {
|
|
43
|
+
return { label: null, sublabel: null }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
33
46
|
const cellGraphs = normalizeCellGraphs(options.cells)
|
|
34
47
|
const sseClients = new Set()
|
|
48
|
+
const jsonBody = json()
|
|
35
49
|
|
|
36
50
|
function broadcast(event, data) {
|
|
37
51
|
const msg = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`
|
|
@@ -76,6 +90,15 @@ export function prsmDevtools(options = {}) {
|
|
|
76
90
|
}
|
|
77
91
|
}
|
|
78
92
|
|
|
93
|
+
if (cache) {
|
|
94
|
+
for (const [name, c] of Object.entries(cache)) {
|
|
95
|
+
const events = ['hit', 'miss', 'set', 'del', 'invalidate', 'refresh', 'stampede:lead', 'stampede:wait', 'stampede:result', 'stampede:timeout', 'error']
|
|
96
|
+
for (const ev of events) {
|
|
97
|
+
c.on(ev, (data) => broadcast(`cache:${ev}`, { cache: name, ...(data ?? {}) }))
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
79
102
|
router.get('/api/config', (_req, res) => {
|
|
80
103
|
res.json({
|
|
81
104
|
queue: !!queue,
|
|
@@ -84,9 +107,27 @@ export function prsmDevtools(options = {}) {
|
|
|
84
107
|
workflow: !!workflow,
|
|
85
108
|
realtime: !!realtime,
|
|
86
109
|
cells: cellGraphs ? Object.keys(cellGraphs) : [],
|
|
110
|
+
lock: lock ? Object.keys(lock) : [],
|
|
111
|
+
cache: cache ? Object.keys(cache) : [],
|
|
87
112
|
})
|
|
88
113
|
})
|
|
89
114
|
|
|
115
|
+
if (cache) {
|
|
116
|
+
router.get('/api/cache', (_req, res) => {
|
|
117
|
+
const out = {}
|
|
118
|
+
for (const [name, c] of Object.entries(cache)) {
|
|
119
|
+
out[name] = c.stats()
|
|
120
|
+
}
|
|
121
|
+
res.json(out)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
router.get('/api/cache/:name', (req, res) => {
|
|
125
|
+
const c = cache[req.params.name]
|
|
126
|
+
if (!c) return res.status(404).json({ error: 'cache not found' })
|
|
127
|
+
res.json({ name: req.params.name, stats: c.stats() })
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
90
131
|
router.get('/api/events', (req, res) => {
|
|
91
132
|
res.setHeader('Content-Type', 'text/event-stream')
|
|
92
133
|
res.setHeader('Cache-Control', 'no-cache')
|
|
@@ -111,6 +152,18 @@ export function prsmDevtools(options = {}) {
|
|
|
111
152
|
}))
|
|
112
153
|
res.json({ jobs })
|
|
113
154
|
})
|
|
155
|
+
|
|
156
|
+
router.post('/api/cron/:name/run', async (req, res) => {
|
|
157
|
+
if (typeof cron.run !== 'function') {
|
|
158
|
+
return res.status(400).json({ error: 'This @prsm/cron version does not support manual runs' })
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const result = await cron.run(req.params.name)
|
|
162
|
+
res.json(result)
|
|
163
|
+
} catch (err) {
|
|
164
|
+
res.status(400).json({ error: err.message })
|
|
165
|
+
}
|
|
166
|
+
})
|
|
114
167
|
}
|
|
115
168
|
|
|
116
169
|
if (limit) {
|
|
@@ -118,13 +171,45 @@ export function prsmDevtools(options = {}) {
|
|
|
118
171
|
res.json({ limiters: Object.keys(limit) })
|
|
119
172
|
})
|
|
120
173
|
|
|
174
|
+
router.get('/api/limits/:name/keys', async (req, res) => {
|
|
175
|
+
const limiter = limit[req.params.name]
|
|
176
|
+
if (!limiter) return res.status(404).json({ error: 'Limiter not found' })
|
|
177
|
+
if (!limiter.keys) {
|
|
178
|
+
return res.status(400).json({ error: 'This @prsm/limit version does not support key listing' })
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const n = req.query.limit ? Number(req.query.limit) : undefined
|
|
182
|
+
const keys = await limiter.keys(n ? { limit: n } : undefined)
|
|
183
|
+
res.json({ keys })
|
|
184
|
+
} catch (err) {
|
|
185
|
+
res.status(500).json({ error: err.message })
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
121
189
|
router.get('/api/limits/:name/peek/:key', async (req, res) => {
|
|
122
190
|
const limiter = limit[req.params.name]
|
|
123
191
|
if (!limiter) return res.status(404).json({ error: 'Limiter not found' })
|
|
124
192
|
if (!limiter.peek) return res.status(400).json({ error: 'Limiter does not support peek' })
|
|
125
193
|
|
|
126
|
-
|
|
127
|
-
|
|
194
|
+
try {
|
|
195
|
+
const result = await limiter.peek(req.params.key)
|
|
196
|
+
res.json(result)
|
|
197
|
+
} catch (err) {
|
|
198
|
+
res.status(500).json({ error: err.message })
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
router.post('/api/limits/:name/reset/:key', async (req, res) => {
|
|
203
|
+
const limiter = limit[req.params.name]
|
|
204
|
+
if (!limiter) return res.status(404).json({ error: 'Limiter not found' })
|
|
205
|
+
if (!limiter.reset) return res.status(400).json({ error: 'Limiter does not support reset' })
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
await limiter.reset(req.params.key)
|
|
209
|
+
res.json({ ok: true })
|
|
210
|
+
} catch (err) {
|
|
211
|
+
res.status(500).json({ error: err.message })
|
|
212
|
+
}
|
|
128
213
|
})
|
|
129
214
|
}
|
|
130
215
|
|
|
@@ -168,6 +253,47 @@ export function prsmDevtools(options = {}) {
|
|
|
168
253
|
if (!execution) return res.status(404).json({ error: 'Execution not found' })
|
|
169
254
|
res.json({ execution })
|
|
170
255
|
})
|
|
256
|
+
|
|
257
|
+
router.post('/api/workflow/start', jsonBody, async (req, res) => {
|
|
258
|
+
const { name, version, input } = req.body || {}
|
|
259
|
+
if (!name || typeof name !== 'string') {
|
|
260
|
+
return res.status(400).json({ error: 'name is required' })
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const execution = await workflow.start(name, input ?? {}, version ? { version } : undefined)
|
|
264
|
+
res.json({ id: execution.id })
|
|
265
|
+
} catch (err) {
|
|
266
|
+
res.status(400).json({ error: err.message })
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
router.post('/api/workflow/executions/:id/signal', jsonBody, async (req, res) => {
|
|
271
|
+
try {
|
|
272
|
+
const execution = await workflow.signal(req.params.id, req.body?.payload ?? {})
|
|
273
|
+
res.json({ execution })
|
|
274
|
+
} catch (err) {
|
|
275
|
+
const status = err.name === 'AlreadySignaledError' ? 409 : 400
|
|
276
|
+
res.status(status).json({ error: err.message })
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
router.post('/api/workflow/executions/:id/cancel', jsonBody, async (req, res) => {
|
|
281
|
+
try {
|
|
282
|
+
const execution = await workflow.cancel(req.params.id, req.body?.reason)
|
|
283
|
+
res.json({ execution })
|
|
284
|
+
} catch (err) {
|
|
285
|
+
res.status(400).json({ error: err.message })
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
router.post('/api/workflow/executions/:id/resume', async (req, res) => {
|
|
290
|
+
try {
|
|
291
|
+
const execution = await workflow.resume(req.params.id)
|
|
292
|
+
res.json({ execution })
|
|
293
|
+
} catch (err) {
|
|
294
|
+
res.status(400).json({ error: err.message })
|
|
295
|
+
}
|
|
296
|
+
})
|
|
171
297
|
}
|
|
172
298
|
|
|
173
299
|
if (realtime) {
|
|
@@ -194,6 +320,7 @@ export function prsmDevtools(options = {}) {
|
|
|
194
320
|
return {
|
|
195
321
|
id,
|
|
196
322
|
metadata,
|
|
323
|
+
...displayFor(metadata),
|
|
197
324
|
local: !!local,
|
|
198
325
|
latency: local?.latency?.ms ?? null,
|
|
199
326
|
alive: local?.alive ?? null,
|
|
@@ -283,6 +410,7 @@ export function prsmDevtools(options = {}) {
|
|
|
283
410
|
res.json({
|
|
284
411
|
id,
|
|
285
412
|
metadata,
|
|
413
|
+
...displayFor(metadata),
|
|
286
414
|
rooms,
|
|
287
415
|
presence,
|
|
288
416
|
channels,
|
|
@@ -374,6 +502,46 @@ export function prsmDevtools(options = {}) {
|
|
|
374
502
|
})
|
|
375
503
|
}
|
|
376
504
|
|
|
505
|
+
if (lock) {
|
|
506
|
+
router.get('/api/locks', async (_req, res) => {
|
|
507
|
+
const managers = []
|
|
508
|
+
for (const [name, manager] of Object.entries(lock)) {
|
|
509
|
+
const kind = typeof manager.renew === 'function' ? 'semaphore' : 'mutex'
|
|
510
|
+
try {
|
|
511
|
+
managers.push({ name, kind, locks: await manager.list() })
|
|
512
|
+
} catch (err) {
|
|
513
|
+
managers.push({ name, kind, locks: [], error: err.message })
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
res.json({ managers })
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
router.get('/api/locks/:name', async (req, res) => {
|
|
520
|
+
const manager = lock[req.params.name]
|
|
521
|
+
if (!manager) return res.status(404).json({ error: 'Lock manager not found' })
|
|
522
|
+
try {
|
|
523
|
+
const kind = typeof manager.renew === 'function' ? 'semaphore' : 'mutex'
|
|
524
|
+
const locks = await manager.list()
|
|
525
|
+
res.json({ name: req.params.name, kind, locks })
|
|
526
|
+
} catch (err) {
|
|
527
|
+
res.status(500).json({ error: err.message })
|
|
528
|
+
}
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
router.post('/api/locks/:name/release', jsonBody, async (req, res) => {
|
|
532
|
+
const manager = lock[req.params.name]
|
|
533
|
+
if (!manager) return res.status(404).json({ error: 'Lock manager not found' })
|
|
534
|
+
const { key, id } = req.body || {}
|
|
535
|
+
if (!key || !id) return res.status(400).json({ error: 'key and id are required' })
|
|
536
|
+
try {
|
|
537
|
+
const released = await manager.release(key, id)
|
|
538
|
+
res.json({ released })
|
|
539
|
+
} catch (err) {
|
|
540
|
+
res.status(500).json({ error: err.message })
|
|
541
|
+
}
|
|
542
|
+
})
|
|
543
|
+
}
|
|
544
|
+
|
|
377
545
|
if (existsSync(clientDir)) {
|
|
378
546
|
const indexPath = resolve(clientDir, 'index.html')
|
|
379
547
|
const indexHtml = readFileSync(indexPath, 'utf8')
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--bg: #0a0a0a;--bg-surface: #111111;--bg-raised: #1a1a1a;--bg-hover: #222222;--bg-active: #2a2a2a;--border: #2a2a2a;--border-subtle: #1e1e1e;--text: #d4d4d4;--text-bright: #e8e8e8;--text-muted: #555555;--accent: #34d399;--accent-dim: rgba(52, 211, 153, .12);--accent-text: #2dd4a2;--color-blue: #6ad;--color-red: #c55;--color-yellow: #b93;--color-green: #5a9;--syn-string: #a5d6a7;--syn-number: #4dd0e1;--syn-boolean: #ce93d8;--syn-null: #666666;--syn-key: #b0b0b0;--syn-bracket: #555555}*{box-sizing:border-box;margin:0;padding:0}body{font-family:SF Mono,Fira Code,JetBrains Mono,Cascadia Code,monospace;font-size:12px;line-height:1.5;color:var(--text);background:var(--bg);-webkit-font-smoothing:antialiased}::selection{background:var(--accent-dim);color:var(--accent-text)}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#3a3a3a}.app{display:flex;flex-direction:column;height:100vh;overflow:hidden}.top-bar{display:flex;align-items:center;justify-content:space-between;padding:0 16px;height:40px;border-bottom:1px solid var(--border);background:var(--bg-surface);flex-shrink:0}.top-bar-left{display:flex;align-items:center;gap:12px}.logo{font-size:11px;font-weight:600;letter-spacing:.5px;text-transform:uppercase;color:var(--text-muted)}.logo span{color:var(--accent)}.top-bar-right{display:flex;align-items:center;gap:12px}.nav-bar{display:flex;align-items:center;gap:0;border-bottom:1px solid var(--border);background:var(--bg-surface);flex-shrink:0;padding:0 16px}.nav-bar a{padding:8px 16px;font-size:11px;color:var(--text-muted);cursor:pointer;border-bottom:2px solid transparent;-webkit-user-select:none;user-select:none;text-decoration:none}.nav-bar a:hover{color:var(--text)}.nav-bar a.active{color:var(--accent-text);border-bottom-color:var(--accent)}.page-scroll{flex:1;overflow-y:auto}.page-content{max-width:1100px;padding:20px 24px 40px}.page-content-wide{padding:20px 24px 40px}.tab-bar{display:flex;align-items:center;gap:0;border-bottom:1px solid var(--border);background:var(--bg-surface);flex-shrink:0;padding:0 16px}.tab{padding:8px 16px;font-size:11px;color:var(--text-muted);cursor:pointer;border-bottom:2px solid transparent;-webkit-user-select:none;user-select:none}.tab:hover{color:var(--text)}.tab.active{color:var(--accent-text);border-bottom-color:var(--accent)}.tab .count{margin-left:6px;font-size:10px;color:var(--text-muted)}.tab.active .count{color:var(--accent)}.sidebar{width:280px;min-width:280px;border-right:1px solid var(--border);overflow-y:auto;background:var(--bg-surface)}.content{flex:1;overflow-y:auto;padding:16px}.sidebar-section{border-bottom:1px solid var(--border-subtle)}.sidebar-header{padding:8px 12px;font-size:10px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-muted);background:var(--bg)}.sidebar-item{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;cursor:pointer;border-left:2px solid transparent}.sidebar-item:hover{background:var(--bg-hover)}.sidebar-item.active{background:var(--accent-dim);border-left-color:var(--accent)}.sidebar-item .label{font-size:11px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidebar-item.active .label{color:var(--accent-text)}.sidebar-item .meta{font-size:10px;color:var(--text-muted);flex-shrink:0;margin-left:8px}.badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;font-size:10px;border-radius:9px;background:#1f1f1f;color:#999}.badge.accent{background:var(--accent-dim);color:var(--accent)}.section{margin-bottom:20px}.section-title{font-size:10px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-muted);margin-bottom:8px}.card{background:var(--bg-surface);border:1px solid var(--border);border-radius:4px;overflow:hidden}.card+.card{margin-top:8px}.card-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--bg);border-bottom:1px solid var(--border-subtle);font-size:11px}.card-header .name{color:var(--text-bright)}.card-body{padding:8px 12px}.kv-row{display:flex;align-items:baseline;padding:2px 0;font-size:11px}.kv-key{color:var(--text-muted);min-width:100px;flex-shrink:0}.kv-value{color:var(--text);word-break:break-all}.member-row{display:flex;align-items:center;justify-content:space-between;padding:4px 12px;font-size:11px;border-bottom:1px solid var(--border-subtle)}.member-row:last-child{border-bottom:none}.member-id{color:var(--text);font-size:11px}.member-presence{font-size:10px;color:var(--accent)}.tag{display:inline-block;padding:1px 6px;font-size:10px;border-radius:3px;background:#1f1f1f;color:#999;margin:1px 2px}.tag.accent{background:var(--accent-dim);color:var(--accent)}.empty{padding:24px;text-align:center;color:var(--text-muted);font-size:11px}.view-hint{font-size:11px;color:var(--text-muted);margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid var(--border-subtle)}.no-presence{font-size:10px;color:#333}.pattern-list{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}.json-view{font-size:11px;line-height:1.6;white-space:pre-wrap;word-break:break-all}.json-view .shiki{background:transparent!important;padding:0;margin:0}.json-view .shiki code{font-family:inherit;font-size:inherit}.select{background:var(--bg-raised);border:1px solid var(--border);color:var(--text);font-family:inherit;font-size:11px;padding:4px 8px;border-radius:3px;outline:none;cursor:pointer}.select:focus{border-color:var(--accent)}.conn-link{cursor:pointer;text-decoration:underline;text-decoration-color:var(--border);text-underline-offset:2px}.conn-link:hover{color:var(--accent-text);text-decoration-color:var(--accent)}.exposed-row{display:flex;align-items:center;flex-wrap:wrap;gap:3px;padding:4px 12px;border-bottom:1px solid var(--border-subtle)}.exposed-row:last-child{border-bottom:none}.exposed-label{font-size:10px;color:var(--text-muted);min-width:70px;flex-shrink:0}.pulse{width:6px;height:6px;border-radius:50%;background:var(--accent)}.pulse.disconnected{background:#ef4444}.instance-id{font-size:10px;color:var(--text-muted)}section[data-v-5ee7e46a]{margin-top:28px}.subsystems[data-v-5ee7e46a]{display:flex;gap:8px;margin-bottom:20px}.chip[data-v-5ee7e46a]{padding:5px 14px;font-size:11px;font-weight:500;color:var(--text-muted);background:var(--bg-surface);border:1px solid var(--border);border-radius:4px;font-family:inherit;cursor:pointer}.chip.on[data-v-5ee7e46a]{color:var(--text);border-color:#333}.chip.off[data-v-5ee7e46a]{color:var(--text-muted);border-color:var(--border-subtle);opacity:.55}.chip[data-v-5ee7e46a]:disabled{cursor:default;opacity:.35}.stream[data-v-5ee7e46a]{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:4px;max-height:520px;overflow-y:auto}.event-row[data-v-5ee7e46a]{display:flex;gap:0;align-items:center;padding:0;border-bottom:1px solid var(--bg-raised);font-size:11px}.event-row[data-v-5ee7e46a]:last-child{border-bottom:none}.event-source[data-v-5ee7e46a]{width:72px;padding:6px 8px;color:var(--text-muted);text-align:right;border-right:1px solid var(--bg-raised);flex-shrink:0}.event-action[data-v-5ee7e46a]{width:152px;padding:6px 8px;color:var(--text-muted);flex-shrink:0}.event-action.complete[data-v-5ee7e46a],.event-action.fire[data-v-5ee7e46a],.event-action.execution-succeeded[data-v-5ee7e46a],.event-action.step-succeeded[data-v-5ee7e46a]{color:var(--color-green)}.event-action.step-routed[data-v-5ee7e46a]{color:var(--color-blue)}.event-action.failed[data-v-5ee7e46a],.event-action.error[data-v-5ee7e46a],.event-action.execution-failed[data-v-5ee7e46a],.event-action.step-failed[data-v-5ee7e46a],.event-action.execution-lease-lost[data-v-5ee7e46a]{color:var(--color-red)}.event-action.retry[data-v-5ee7e46a],.event-action.step-retry[data-v-5ee7e46a]{color:var(--color-yellow)}.event-data[data-v-5ee7e46a]{flex:1;min-width:0;padding:6px 8px;color:#666;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.event-time[data-v-5ee7e46a]{width:80px;padding:6px 8px;color:var(--text-muted);text-align:right;flex-shrink:0}.gauges[data-v-74a2dfa0]{display:flex;gap:12px;margin-bottom:8px}.gauge[data-v-74a2dfa0]{flex:1;padding:16px;background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:4px}.gauge-value[data-v-74a2dfa0]{display:block;font-size:28px;font-weight:600;color:var(--text-muted)}.gauge-value.lit[data-v-74a2dfa0]{color:var(--color-blue)}.gauge-value.warn[data-v-74a2dfa0]{color:var(--color-red)}.gauge-label[data-v-74a2dfa0]{display:block;font-size:10px;color:var(--text-muted);margin-top:4px;letter-spacing:.3px;text-transform:uppercase}.session-note[data-v-74a2dfa0]{font-size:10px;color:#333;margin-bottom:24px}section[data-v-74a2dfa0]{margin-top:24px}.stream[data-v-74a2dfa0]{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:4px;max-height:400px;overflow-y:auto}.event-row[data-v-74a2dfa0]{display:flex;gap:0;align-items:center;border-bottom:1px solid var(--bg-raised);font-size:11px}.event-row[data-v-74a2dfa0]:last-child{border-bottom:none}.event-action[data-v-74a2dfa0]{width:72px;padding:6px 10px;color:var(--text-muted);flex-shrink:0}.event-action.complete[data-v-74a2dfa0]{color:var(--color-green)}.event-action.failed[data-v-74a2dfa0]{color:var(--color-red)}.event-action.retry[data-v-74a2dfa0]{color:var(--color-yellow)}.event-action.new[data-v-74a2dfa0]{color:var(--text-muted)}.event-action.drain[data-v-74a2dfa0]{color:#666}.event-data[data-v-74a2dfa0]{flex:1;padding:6px 8px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.event-time[data-v-74a2dfa0]{width:80px;padding:6px 8px;color:#333;text-align:right;flex-shrink:0}.job-row[data-v-6d545d29]{display:flex;align-items:center;padding:0;border-bottom:1px solid var(--border-subtle);font-size:11px}.job-row[data-v-6d545d29]:last-child{border-bottom:none}.job-row.header[data-v-6d545d29]{font-size:10px;color:var(--text-muted);letter-spacing:.3px;text-transform:uppercase;border-bottom:1px solid var(--border)}.job-row.header span[data-v-6d545d29]{padding:8px 12px}.col-name[data-v-6d545d29]{flex:1;padding:10px 12px}.col-next[data-v-6d545d29]{width:100px;padding:10px 12px;color:#666;text-align:right}.col-in[data-v-6d545d29]{width:60px;padding:10px 12px;color:var(--text-muted);text-align:right}.col-in.soon[data-v-6d545d29]{color:var(--color-blue)}.stream[data-v-6d545d29]{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:4px;max-height:400px;overflow-y:auto}.event-row[data-v-6d545d29]{display:flex;align-items:center;border-bottom:1px solid var(--bg-raised);font-size:11px}.event-row[data-v-6d545d29]:last-child{border-bottom:none}.event-action[data-v-6d545d29]{width:52px;padding:6px 10px;color:var(--text-muted);flex-shrink:0}.event-action.fire[data-v-6d545d29]{color:var(--color-green)}.event-action.error[data-v-6d545d29]{color:var(--color-red)}.event-name[data-v-6d545d29]{flex:1;padding:6px 8px;color:#666}.event-time[data-v-6d545d29]{width:80px;padding:6px 8px;color:#333;text-align:right;flex-shrink:0}.text-view[data-v-5a4270db]{font:12px SF Mono,monospace;color:var(--text);white-space:pre-wrap;word-break:break-word;margin:0}.limiter-list[data-v-22c855ec]{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:24px}section[data-v-22c855ec]{margin-top:24px}.desc[data-v-22c855ec]{font-size:11px;color:var(--text-muted);margin-bottom:12px}.peek-form[data-v-22c855ec]{display:flex;gap:8px}.input[data-v-22c855ec]{padding:6px 10px;background:var(--bg-surface);border:1px solid var(--border);border-radius:4px;font-size:11px;color:var(--text);flex:1;font-family:inherit;outline:none}.input[data-v-22c855ec]::placeholder{color:#333}.input[data-v-22c855ec]:focus{border-color:var(--accent)}.btn[data-v-22c855ec]{padding:6px 16px;background:var(--bg-raised);color:#999;border:1px solid var(--border);border-radius:4px;cursor:pointer;font-size:11px;font-family:inherit}.btn[data-v-22c855ec]:hover{color:var(--text)}.btn[data-v-22c855ec]:disabled{opacity:.3;cursor:default}.error[data-v-22c855ec]{margin-top:10px;color:var(--color-red);font-size:11px}.graph-shell[data-v-6098c89d]{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:4px;overflow:hidden;position:relative;cursor:grab;touch-action:none}.graph-shell[data-v-6098c89d]:active{cursor:grabbing}.graph-shell svg[data-v-6098c89d]{user-select:none;-webkit-user-select:none}.graph-shell.fullscreen[data-v-6098c89d]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;border-radius:0;border:none;background:var(--bg)}.graph-controls[data-v-6098c89d]{position:absolute;top:10px;right:10px;z-index:2;display:flex;gap:4px}.graph-controls button[data-v-6098c89d]{padding:4px 10px;background:var(--bg-raised);border:1px solid var(--border);border-radius:4px;color:#888;font-size:11px;font-family:inherit;cursor:pointer}.graph-controls button[data-v-6098c89d]:hover{background:var(--bg-hover);color:#ccc}.graph[data-v-6098c89d]{width:100%;min-height:420px;display:block}.fullscreen .graph[data-v-6098c89d]{min-height:100vh}.edge[data-v-6098c89d]{fill:none;stroke:#232323;stroke-width:3}.edge-seen[data-v-6098c89d]{stroke:#3b3b3b}.edge-active[data-v-6098c89d]{stroke:#8fb3ff}.edge-label[data-v-6098c89d]{fill:#4a4a4a;font-size:11px;font-family:inherit}.node[data-v-6098c89d]{cursor:pointer}.node rect[data-v-6098c89d]{fill:#151515;stroke:#262626;stroke-width:1.5}.node.selected rect[data-v-6098c89d]{stroke:#6b7280}.node.current rect[data-v-6098c89d],.node.running rect[data-v-6098c89d]{stroke:#5b8cff}.node.succeeded rect[data-v-6098c89d],.node.decision-success rect[data-v-6098c89d]{stroke:#3d8f63}.node.failed rect[data-v-6098c89d]{stroke:#a14a4a}.node.activity .node-type[data-v-6098c89d]{fill:#7aa2f7}.node.decision .node-type[data-v-6098c89d]{fill:#e0af68}.node.succeed .node-type[data-v-6098c89d]{fill:#73daca}.node.fail .node-type[data-v-6098c89d]{fill:#f7768e}.node-name[data-v-6098c89d]{fill:var(--text-bright);font-size:13px;font-weight:600}.node-type[data-v-6098c89d],.node-meta[data-v-6098c89d]{font-family:inherit;font-size:11px}.node-meta[data-v-6098c89d]{fill:#5f5f5f}.workflow-layout[data-v-124a0c49]{display:grid;grid-template-columns:280px 1fr;gap:16px}.wf-sidebar[data-v-124a0c49]{display:flex;flex-direction:column;gap:8px}.wf-sidebar .card.active[data-v-124a0c49]{border-color:var(--accent)}.detail[data-v-124a0c49]{display:flex;flex-direction:column;gap:16px}.headline[data-v-124a0c49]{display:flex;justify-content:space-between;align-items:flex-end}.headline-name[data-v-124a0c49]{font-size:14px;font-weight:600;color:var(--text-bright)}.headline-sub[data-v-124a0c49]{margin-top:2px;color:var(--text-muted);font-size:11px}.headline-stats[data-v-124a0c49]{display:flex;gap:10px;color:var(--text-muted);font-size:11px}.filters[data-v-24631062]{display:flex;gap:8px;margin-bottom:16px}.execution-layout[data-v-24631062]{display:grid;grid-template-columns:240px minmax(0,1fr) 340px;gap:14px;align-items:start}.list[data-v-24631062]{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:4px;overflow:hidden;max-height:calc(100vh - 240px);overflow-y:auto}.execution-row[data-v-24631062]{padding:10px 12px;border-bottom:1px solid var(--bg-raised);cursor:pointer}.execution-row[data-v-24631062]:last-child{border-bottom:none}.execution-row.active[data-v-24631062]{background:var(--bg-raised)}.execution-main[data-v-24631062]{display:flex;justify-content:space-between;gap:8px}.execution-workflow[data-v-24631062]{color:var(--text-bright);font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.execution-id[data-v-24631062]{color:#666;font-size:11px;flex-shrink:0}.execution-meta[data-v-24631062]{display:flex;justify-content:space-between;gap:6px;margin-top:4px;color:var(--text-muted);font-size:10px}.execution-row.running .execution-workflow[data-v-24631062]{color:#8fb3ff}.execution-row.failed .execution-workflow[data-v-24631062]{color:#d87a7a}.execution-row.succeeded .execution-workflow[data-v-24631062]{color:#7ec49b}.headline[data-v-24631062]{margin-bottom:14px}.headline-name[data-v-24631062]{font-size:14px;font-weight:600;color:var(--text-bright)}.headline-sub[data-v-24631062]{margin-top:2px;color:var(--text-muted);font-size:11px}.center[data-v-24631062]{display:flex;flex-direction:column;gap:12px;min-width:0}.inspector[data-v-24631062]{display:flex;flex-direction:column;gap:10px;max-height:calc(100vh - 240px);overflow-y:auto}.step-hint[data-v-24631062]{color:#383838;font-size:10px;margin-bottom:8px;font-style:italic}.timeline[data-v-24631062]{max-height:420px;overflow-y:auto}.timeline-row[data-v-24631062]{display:flex;gap:6px;align-items:baseline;border-bottom:1px solid var(--border-subtle);padding:6px 12px;font-size:10px;white-space:nowrap}.timeline-row[data-v-24631062]:last-child{border-bottom:none}.timeline-type[data-v-24631062]{color:#b1b1b1;flex-shrink:0}.timeline-step[data-v-24631062]{color:#666;flex:1;overflow:hidden;text-overflow:ellipsis}.timeline-time[data-v-24631062]{color:var(--text-muted);flex-shrink:0}.realtime-shell[data-v-d6b8bed2]{display:flex;flex-direction:column;flex:1;overflow:hidden}.rt-main[data-v-d6b8bed2]{display:flex;flex:1;overflow:hidden}.tab-bar-right[data-v-d6b8bed2]{margin-left:auto;display:flex;align-items:center;gap:8px}.tab-bar a[data-v-d6b8bed2]{text-decoration:none}.cells-page[data-v-183abe97]{display:flex;flex-direction:column;gap:12px}.cells-toolbar[data-v-183abe97]{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.left-group[data-v-183abe97]{display:flex;align-items:center;gap:12px}.graph-tabs[data-v-183abe97]{display:flex;border:1px solid var(--border);border-radius:4px;overflow:hidden}.graph-tabs button[data-v-183abe97]{padding:6px 14px;background:var(--bg-surface);color:var(--text-muted);border:none;font:inherit;font-size:11px;cursor:pointer;border-right:1px solid var(--border)}.graph-tabs button[data-v-183abe97]:last-child{border-right:none}.graph-tabs button.active[data-v-183abe97]{background:var(--accent-dim);color:var(--accent-text)}.graph-tabs button[data-v-183abe97]:hover:not(.active){background:var(--bg-hover);color:var(--text)}.view-toggle[data-v-183abe97]{display:flex;border:1px solid var(--border);border-radius:4px;overflow:hidden}.view-toggle button[data-v-183abe97]{padding:6px 14px;background:var(--bg-surface);color:var(--text-muted);border:none;font:inherit;font-size:11px;cursor:pointer;border-right:1px solid var(--border)}.view-toggle button[data-v-183abe97]:last-child{border-right:none}.view-toggle button.active[data-v-183abe97]{background:var(--accent-dim);color:var(--accent-text)}.view-toggle button[data-v-183abe97]:hover:not(.active){background:var(--bg-hover);color:var(--text)}.stats[data-v-183abe97]{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--text-muted)}.dot[data-v-183abe97]{width:3px;height:3px;background:var(--text-muted);border-radius:50%}.cells-layout[data-v-183abe97]{display:flex;flex-direction:column;gap:12px;min-height:0}.cells-main[data-v-183abe97]{min-width:0;overflow:auto}.cells-detail[data-v-183abe97]{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:4px;padding:12px}.detail-grid[data-v-183abe97]{display:grid;gap:10px;grid-template-columns:minmax(0,1fr) 240px}.detail-main[data-v-183abe97]{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));min-width:0}.detail-side[data-v-183abe97]{display:flex;flex-direction:column;gap:10px}.detail-card[data-v-183abe97]{background:var(--bg);border:1px solid var(--border-subtle);border-radius:3px;padding:10px;min-width:0;display:flex;flex-direction:column;gap:6px}.detail-card.span-2[data-v-183abe97]{grid-column:span 2}@media(max-width:900px){.detail-grid[data-v-183abe97]{grid-template-columns:minmax(0,1fr)}.detail-card.span-2[data-v-183abe97]{grid-column:span 1}}.scroll-y[data-v-183abe97]{max-height:220px;overflow-y:auto}.kv-list[data-v-183abe97]{display:flex;flex-direction:column;gap:8px}.kv-list-row[data-v-183abe97]{display:flex;flex-direction:column;gap:2px;min-width:0}.kv-list-key[data-v-183abe97]{font-size:9px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px}.kv-list-val[data-v-183abe97]{font-size:11px;color:var(--text);word-break:break-word;font-family:SF Mono,monospace}.cell-row[data-v-183abe97]{display:flex;align-items:center;border-bottom:1px solid var(--border-subtle);font-size:11px;cursor:pointer}.cell-row[data-v-183abe97]:last-child{border-bottom:none}.cell-row[data-v-183abe97]:hover{background:var(--bg-hover)}.cell-row.selected[data-v-183abe97]{background:var(--accent-dim)}.cell-row.recent[data-v-183abe97]{animation:flash-183abe97 .8s ease-out}.cell-row.header[data-v-183abe97]{font-size:10px;color:var(--text-muted);letter-spacing:.3px;text-transform:uppercase;cursor:default;border-bottom:1px solid var(--border)}.cell-row.header[data-v-183abe97]:hover{background:transparent}@keyframes flash-183abe97{0%{background:var(--accent-dim)}to{background:transparent}}.col-name[data-v-183abe97]{flex:0 0 200px;padding:8px 12px;color:var(--text-bright)}.col-value[data-v-183abe97]{flex:1;padding:8px 12px;color:var(--text);font-family:SF Mono,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.col-deps[data-v-183abe97]{flex:0 0 200px;padding:8px 12px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.col-status[data-v-183abe97]{flex:0 0 80px;padding:8px 12px}.col-updated[data-v-183abe97]{flex:0 0 100px;padding:8px 12px;color:var(--text-muted);text-align:right}.graph-wrap[data-v-183abe97]{background:var(--bg-surface);border:1px solid var(--border-subtle);border-radius:4px;padding:8px;overflow:auto;width:100%;box-sizing:border-box}.graph-wrap svg[data-v-183abe97]{display:block}.node-group[data-v-183abe97]{cursor:pointer}.node-group rect[data-v-183abe97]{fill:var(--bg-raised);stroke:var(--border);stroke-width:1}.node-group:hover rect[data-v-183abe97]{stroke:var(--text-muted)}.node-group.selected rect[data-v-183abe97]{stroke:var(--accent);stroke-width:1.5}.node-group.recent rect[data-v-183abe97]{animation:pulse-rect-183abe97 .8s ease-out}@keyframes pulse-rect-183abe97{0%{fill:var(--accent-dim);stroke:var(--accent)}to{fill:var(--bg-raised);stroke:var(--border)}}.edge-line[data-v-183abe97]{stroke:var(--border)}.edge-line.recent[data-v-183abe97]{animation:pulse-edge-183abe97 .8s ease-out}@keyframes pulse-edge-183abe97{0%{stroke:var(--accent)}to{stroke:var(--border)}}.node-name[data-v-183abe97]{fill:var(--text-bright);font:600 11px SF Mono,monospace}.node-value[data-v-183abe97]{fill:var(--text);font:10px SF Mono,monospace}.detail-header[data-v-183abe97]{display:flex;align-items:center;gap:8px;padding-bottom:10px;margin-bottom:12px;border-bottom:1px solid var(--border)}.detail-name[data-v-183abe97]{flex:1;font-size:12px;color:var(--text-bright);font-weight:600}.detail-status[data-v-183abe97]{font-size:9px;text-transform:uppercase;letter-spacing:.5px}.detail-spacer[data-v-183abe97]{flex:1}.detail-close[data-v-183abe97]{cursor:pointer;color:var(--text-muted);font-size:16px;padding:0 4px}.detail-close[data-v-183abe97]:hover{color:var(--text-bright)}.detail-section[data-v-183abe97]{margin-bottom:14px}.detail-label[data-v-183abe97]{font-size:9px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px}.detail-content[data-v-183abe97]{font-size:11px;color:var(--text);word-break:break-word}.detail-pre[data-v-183abe97]{font:11px SF Mono,monospace;color:var(--text);background:var(--bg);border:1px solid var(--border-subtle);border-radius:3px;padding:8px;white-space:pre-wrap;overflow-x:auto;max-height:220px}.detail-label[data-v-183abe97]{display:flex;align-items:center;gap:8px}.detail-label-meta[data-v-183abe97]{font-size:9px;color:var(--text);text-transform:none;letter-spacing:0;font-family:SF Mono,monospace}.history-disabled[data-v-183abe97]{font-size:10px;color:var(--text-muted);line-height:1.5}.history-disabled code[data-v-183abe97]{background:var(--bg-raised);color:var(--text);padding:1px 4px;border-radius:2px;font-size:10px}.history-list[data-v-183abe97]{display:flex;flex-direction:column;gap:2px;max-height:240px;overflow-y:auto}.history-entry[data-v-183abe97]{display:flex;gap:8px;align-items:center;font-size:10px;padding:3px 6px;background:var(--bg);border-radius:2px}.history-time[data-v-183abe97]{flex:0 0 70px;color:var(--text-muted)}.history-value[data-v-183abe97]{flex:1;color:var(--text);font-family:SF Mono,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.empty[data-v-183abe97]{padding:20px;text-align:center;color:var(--text-muted);font-size:11px}.empty-small[data-v-183abe97]{padding:6px;text-align:center;color:var(--text-muted);font-size:10px}
|