@prsm/devtools 1.1.0 → 1.2.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-B7_KPm6_.css +1 -0
- package/dist/client/assets/index-DWT8hGf_.js +178 -0
- package/dist/client/index.html +9 -3
- package/package.json +7 -4
- package/src/index.js +131 -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-DWT8hGf_.js"></script>
|
|
17
|
+
<link rel="stylesheet" crossorigin href="./assets/index-B7_KPm6_.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.2.0",
|
|
4
4
|
"description": "Read-only Express middleware dashboard for observing @prsm infrastructure at runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -32,11 +32,14 @@
|
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@prsm/cells": "^1.2.0",
|
|
35
|
-
"@prsm/cron": "^1.0
|
|
36
|
-
"@prsm/limit": "^1.
|
|
35
|
+
"@prsm/cron": "^1.1.0",
|
|
36
|
+
"@prsm/limit": "^1.2.0",
|
|
37
|
+
"@prsm/lock": "^1.1.0",
|
|
37
38
|
"@prsm/queue": "^3.0.8",
|
|
39
|
+
"@prsm/workflow": "^3.1.0",
|
|
38
40
|
"cors": "^2.8.6",
|
|
39
|
-
"dotenv": "^16.4.7"
|
|
41
|
+
"dotenv": "^16.4.7",
|
|
42
|
+
"pg": "^8.13.1"
|
|
40
43
|
},
|
|
41
44
|
"engines": {
|
|
42
45
|
"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,10 @@ 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 } = options
|
|
33
33
|
const cellGraphs = normalizeCellGraphs(options.cells)
|
|
34
34
|
const sseClients = new Set()
|
|
35
|
+
const jsonBody = json()
|
|
35
36
|
|
|
36
37
|
function broadcast(event, data) {
|
|
37
38
|
const msg = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`
|
|
@@ -84,6 +85,7 @@ export function prsmDevtools(options = {}) {
|
|
|
84
85
|
workflow: !!workflow,
|
|
85
86
|
realtime: !!realtime,
|
|
86
87
|
cells: cellGraphs ? Object.keys(cellGraphs) : [],
|
|
88
|
+
lock: lock ? Object.keys(lock) : [],
|
|
87
89
|
})
|
|
88
90
|
})
|
|
89
91
|
|
|
@@ -111,6 +113,18 @@ export function prsmDevtools(options = {}) {
|
|
|
111
113
|
}))
|
|
112
114
|
res.json({ jobs })
|
|
113
115
|
})
|
|
116
|
+
|
|
117
|
+
router.post('/api/cron/:name/run', async (req, res) => {
|
|
118
|
+
if (typeof cron.run !== 'function') {
|
|
119
|
+
return res.status(400).json({ error: 'This @prsm/cron version does not support manual runs' })
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const result = await cron.run(req.params.name)
|
|
123
|
+
res.json(result)
|
|
124
|
+
} catch (err) {
|
|
125
|
+
res.status(400).json({ error: err.message })
|
|
126
|
+
}
|
|
127
|
+
})
|
|
114
128
|
}
|
|
115
129
|
|
|
116
130
|
if (limit) {
|
|
@@ -118,13 +132,45 @@ export function prsmDevtools(options = {}) {
|
|
|
118
132
|
res.json({ limiters: Object.keys(limit) })
|
|
119
133
|
})
|
|
120
134
|
|
|
135
|
+
router.get('/api/limits/:name/keys', async (req, res) => {
|
|
136
|
+
const limiter = limit[req.params.name]
|
|
137
|
+
if (!limiter) return res.status(404).json({ error: 'Limiter not found' })
|
|
138
|
+
if (!limiter.keys) {
|
|
139
|
+
return res.status(400).json({ error: 'This @prsm/limit version does not support key listing' })
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const n = req.query.limit ? Number(req.query.limit) : undefined
|
|
143
|
+
const keys = await limiter.keys(n ? { limit: n } : undefined)
|
|
144
|
+
res.json({ keys })
|
|
145
|
+
} catch (err) {
|
|
146
|
+
res.status(500).json({ error: err.message })
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
121
150
|
router.get('/api/limits/:name/peek/:key', async (req, res) => {
|
|
122
151
|
const limiter = limit[req.params.name]
|
|
123
152
|
if (!limiter) return res.status(404).json({ error: 'Limiter not found' })
|
|
124
153
|
if (!limiter.peek) return res.status(400).json({ error: 'Limiter does not support peek' })
|
|
125
154
|
|
|
126
|
-
|
|
127
|
-
|
|
155
|
+
try {
|
|
156
|
+
const result = await limiter.peek(req.params.key)
|
|
157
|
+
res.json(result)
|
|
158
|
+
} catch (err) {
|
|
159
|
+
res.status(500).json({ error: err.message })
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
router.post('/api/limits/:name/reset/:key', async (req, res) => {
|
|
164
|
+
const limiter = limit[req.params.name]
|
|
165
|
+
if (!limiter) return res.status(404).json({ error: 'Limiter not found' })
|
|
166
|
+
if (!limiter.reset) return res.status(400).json({ error: 'Limiter does not support reset' })
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
await limiter.reset(req.params.key)
|
|
170
|
+
res.json({ ok: true })
|
|
171
|
+
} catch (err) {
|
|
172
|
+
res.status(500).json({ error: err.message })
|
|
173
|
+
}
|
|
128
174
|
})
|
|
129
175
|
}
|
|
130
176
|
|
|
@@ -168,6 +214,47 @@ export function prsmDevtools(options = {}) {
|
|
|
168
214
|
if (!execution) return res.status(404).json({ error: 'Execution not found' })
|
|
169
215
|
res.json({ execution })
|
|
170
216
|
})
|
|
217
|
+
|
|
218
|
+
router.post('/api/workflow/start', jsonBody, async (req, res) => {
|
|
219
|
+
const { name, version, input } = req.body || {}
|
|
220
|
+
if (!name || typeof name !== 'string') {
|
|
221
|
+
return res.status(400).json({ error: 'name is required' })
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const execution = await workflow.start(name, input ?? {}, version ? { version } : undefined)
|
|
225
|
+
res.json({ id: execution.id })
|
|
226
|
+
} catch (err) {
|
|
227
|
+
res.status(400).json({ error: err.message })
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
router.post('/api/workflow/executions/:id/signal', jsonBody, async (req, res) => {
|
|
232
|
+
try {
|
|
233
|
+
const execution = await workflow.signal(req.params.id, req.body?.payload ?? {})
|
|
234
|
+
res.json({ execution })
|
|
235
|
+
} catch (err) {
|
|
236
|
+
const status = err.name === 'AlreadySignaledError' ? 409 : 400
|
|
237
|
+
res.status(status).json({ error: err.message })
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
router.post('/api/workflow/executions/:id/cancel', jsonBody, async (req, res) => {
|
|
242
|
+
try {
|
|
243
|
+
const execution = await workflow.cancel(req.params.id, req.body?.reason)
|
|
244
|
+
res.json({ execution })
|
|
245
|
+
} catch (err) {
|
|
246
|
+
res.status(400).json({ error: err.message })
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
router.post('/api/workflow/executions/:id/resume', async (req, res) => {
|
|
251
|
+
try {
|
|
252
|
+
const execution = await workflow.resume(req.params.id)
|
|
253
|
+
res.json({ execution })
|
|
254
|
+
} catch (err) {
|
|
255
|
+
res.status(400).json({ error: err.message })
|
|
256
|
+
}
|
|
257
|
+
})
|
|
171
258
|
}
|
|
172
259
|
|
|
173
260
|
if (realtime) {
|
|
@@ -374,6 +461,46 @@ export function prsmDevtools(options = {}) {
|
|
|
374
461
|
})
|
|
375
462
|
}
|
|
376
463
|
|
|
464
|
+
if (lock) {
|
|
465
|
+
router.get('/api/locks', async (_req, res) => {
|
|
466
|
+
const managers = []
|
|
467
|
+
for (const [name, manager] of Object.entries(lock)) {
|
|
468
|
+
const kind = typeof manager.renew === 'function' ? 'semaphore' : 'mutex'
|
|
469
|
+
try {
|
|
470
|
+
managers.push({ name, kind, locks: await manager.list() })
|
|
471
|
+
} catch (err) {
|
|
472
|
+
managers.push({ name, kind, locks: [], error: err.message })
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
res.json({ managers })
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
router.get('/api/locks/:name', async (req, res) => {
|
|
479
|
+
const manager = lock[req.params.name]
|
|
480
|
+
if (!manager) return res.status(404).json({ error: 'Lock manager not found' })
|
|
481
|
+
try {
|
|
482
|
+
const kind = typeof manager.renew === 'function' ? 'semaphore' : 'mutex'
|
|
483
|
+
const locks = await manager.list()
|
|
484
|
+
res.json({ name: req.params.name, kind, locks })
|
|
485
|
+
} catch (err) {
|
|
486
|
+
res.status(500).json({ error: err.message })
|
|
487
|
+
}
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
router.post('/api/locks/:name/release', jsonBody, async (req, res) => {
|
|
491
|
+
const manager = lock[req.params.name]
|
|
492
|
+
if (!manager) return res.status(404).json({ error: 'Lock manager not found' })
|
|
493
|
+
const { key, id } = req.body || {}
|
|
494
|
+
if (!key || !id) return res.status(400).json({ error: 'key and id are required' })
|
|
495
|
+
try {
|
|
496
|
+
const released = await manager.release(key, id)
|
|
497
|
+
res.json({ released })
|
|
498
|
+
} catch (err) {
|
|
499
|
+
res.status(500).json({ error: err.message })
|
|
500
|
+
}
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
|
|
377
504
|
if (existsSync(clientDir)) {
|
|
378
505
|
const indexPath = resolve(clientDir, 'index.html')
|
|
379
506
|
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}
|