procdeck 1.1.1 → 2.0.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/CHANGELOG.md +277 -0
- package/bin/procdeck.js +50 -0
- package/package.json +13 -46
- package/readme.md +40 -25
- package/schema.json +38 -73
- package/dist/chunk-SIAA4J6H.js +0 -21
- package/dist/cli.js +0 -48488
- package/dist/public.d.ts +0 -82
- package/dist/public.js +0 -7
- package/ui/dist/apple-touch-icon.png +0 -0
- package/ui/dist/assets/index-BkSDVPyT.css +0 -32
- package/ui/dist/assets/index-Co81BEum.js +0 -95
- package/ui/dist/icon-192.png +0 -0
- package/ui/dist/icon-512.png +0 -0
- package/ui/dist/icon-alert.svg +0 -25
- package/ui/dist/icon.svg +0 -22
- package/ui/dist/index.html +0 -33
package/dist/public.d.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The package's public entry point — everything a `procdeck.config.ts` needs.
|
|
3
|
-
*
|
|
4
|
-
* The types are written out structurally instead of being derived from the
|
|
5
|
-
* Effect schema in `config.ts`, because a derived type makes the emitted `.d.ts`
|
|
6
|
-
* import `effect`, and effect is bundled into the CLI rather than installed:
|
|
7
|
-
* consumers would be typechecking against a package they don't have.
|
|
8
|
-
*
|
|
9
|
-
* `tests/public.test.ts` asserts at the type level that this mirrors the schema
|
|
10
|
-
* exactly, so the two cannot drift apart silently.
|
|
11
|
-
*/
|
|
12
|
-
/** A check that must pass before the proc spawns; failure parks it in `blocked`. */
|
|
13
|
-
export type Preflight = {
|
|
14
|
-
/** Shell command; exit 0 lets the proc spawn. */
|
|
15
|
-
readonly shell: string;
|
|
16
|
-
/** Regex the check's output must also match, for checks that exit 0 either way. */
|
|
17
|
-
readonly expect?: string;
|
|
18
|
-
/** What the human should do when the check fails. */
|
|
19
|
-
readonly hint?: string;
|
|
20
|
-
};
|
|
21
|
-
/** A regex watched in the pane output; a match raises a badge in the UI. */
|
|
22
|
-
export type Alert = {
|
|
23
|
-
readonly pattern: string;
|
|
24
|
-
/** Badge text. */
|
|
25
|
-
readonly label: string;
|
|
26
|
-
};
|
|
27
|
-
/** A single supervised process. Exactly one of `shell` / `cmd` is used. */
|
|
28
|
-
export type ProcSpec = {
|
|
29
|
-
/** Pane label and proxy subdomain (`api` → http://api.localhost:4820). */
|
|
30
|
-
readonly id: string;
|
|
31
|
-
/** Command line handed to `$SHELL -c`. */
|
|
32
|
-
readonly shell?: string;
|
|
33
|
-
/** argv, executed without a shell. */
|
|
34
|
-
readonly cmd?: ReadonlyArray<string>;
|
|
35
|
-
/** Working directory; defaults to the config file's directory. */
|
|
36
|
-
readonly cwd?: string;
|
|
37
|
-
/** Extra environment; values may use `${port}` / `${port:other}`. */
|
|
38
|
-
readonly env?: {
|
|
39
|
-
readonly [key: string]: string;
|
|
40
|
-
};
|
|
41
|
-
/** Spawn on startup. Defaults to true. */
|
|
42
|
-
readonly autostart?: boolean;
|
|
43
|
-
/** URL this process serves, shown in the UI as a link. */
|
|
44
|
-
readonly url?: string;
|
|
45
|
-
/**
|
|
46
|
-
* Pin this proc's assigned `${port}` to a fixed public number instead of a
|
|
47
|
-
* random free one — dependents outside the deck keep their hardcoded
|
|
48
|
-
* addresses, and an observed proc still binds a hidden internal port.
|
|
49
|
-
* Requires `${port}` somewhere in the spec.
|
|
50
|
-
*/
|
|
51
|
-
readonly port?: number;
|
|
52
|
-
/** Ids of procs that must be ready before this one spawns. */
|
|
53
|
-
readonly needs?: ReadonlyArray<string>;
|
|
54
|
-
/** What "ready" means for dependents. Defaults to "listening". */
|
|
55
|
-
readonly readyWhen?: "listening" | "started";
|
|
56
|
-
/**
|
|
57
|
-
* Route this proc's assigned `${port}` through procdeck's HTTP observer
|
|
58
|
-
* (`procdeck http` captures requests/responses). Defaults to true for
|
|
59
|
-
* procs that use `${port}`; false hands the proc the public port directly.
|
|
60
|
-
*/
|
|
61
|
-
readonly observe?: boolean;
|
|
62
|
-
readonly preflight?: Preflight;
|
|
63
|
-
readonly alerts?: ReadonlyArray<Alert>;
|
|
64
|
-
};
|
|
65
|
-
/** A procdeck deck: the web UI's settings and the processes it supervises. */
|
|
66
|
-
export type ProcdeckConfig = {
|
|
67
|
-
/** Only meaningful in a JSON config; accepted here for symmetry. */
|
|
68
|
-
readonly $schema?: string;
|
|
69
|
-
/** Deck name; defaults to the config directory's basename. */
|
|
70
|
-
readonly name?: string;
|
|
71
|
-
/** Port for the web UI and the `*.localhost` proxy. Defaults to 4820. */
|
|
72
|
-
readonly port?: number;
|
|
73
|
-
/**
|
|
74
|
-
* Interface the UI and proxy bind to. Defaults to "127.0.0.1" (loopback
|
|
75
|
-
* only — the UI types into real terminals). "0.0.0.0" to reach the deck
|
|
76
|
-
* from another machine or from a container's host.
|
|
77
|
-
*/
|
|
78
|
-
readonly host?: string;
|
|
79
|
-
readonly procs: ReadonlyArray<ProcSpec>;
|
|
80
|
-
};
|
|
81
|
-
/** Identity helper that gives config files their types. */
|
|
82
|
-
export declare const defineConfig: (config: ProcdeckConfig) => ProcdeckConfig;
|
package/dist/public.js
DELETED
|
Binary file
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
3
|
-
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
|
4
|
-
* https://github.com/chjj/term.js
|
|
5
|
-
* @license MIT
|
|
6
|
-
*
|
|
7
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
-
* in the Software without restriction, including without limitation the rights
|
|
10
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
-
* furnished to do so, subject to the following conditions:
|
|
13
|
-
*
|
|
14
|
-
* The above copyright notice and this permission notice shall be included in
|
|
15
|
-
* all copies or substantial portions of the Software.
|
|
16
|
-
*
|
|
17
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
-
* THE SOFTWARE.
|
|
24
|
-
*
|
|
25
|
-
* Originally forked from (with the author's permission):
|
|
26
|
-
* Fabrice Bellard's javascript vt100 for jslinux:
|
|
27
|
-
* http://bellard.org/jslinux/
|
|
28
|
-
* Copyright (c) 2011 Fabrice Bellard
|
|
29
|
-
* The original design remains. The terminal itself
|
|
30
|
-
* has been extended to include xterm CSI codes, among
|
|
31
|
-
* other features.
|
|
32
|
-
*/.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{font-family:monospace;-webkit-user-select:text;user-select:text;white-space:pre}.xterm .xterm-accessibility-tree>div{transform-origin:left;width:fit-content}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}.xterm .xterm-scrollable-element>.scrollbar{cursor:default}.xterm .xterm-scrollable-element>.scrollbar>.scra{cursor:pointer;font-size:11px!important}.xterm .xterm-scrollable-element>.visible{opacity:1;background:#0000;transition:opacity .1s linear;z-index:11}.xterm .xterm-scrollable-element>.invisible{opacity:0;pointer-events:none}.xterm .xterm-scrollable-element>.invisible.fade{transition:opacity .8s linear}.xterm .xterm-scrollable-element>.shadow{position:absolute;display:none}.xterm .xterm-scrollable-element>.shadow.top{display:block;top:0;left:3px;height:3px;width:100%;box-shadow:var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.left{display:block;top:3px;left:0;height:100%;width:3px;box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.top-left-corner{display:block;top:0;left:0;height:3px;width:3px}.xterm .xterm-scrollable-element>.shadow.top.left{box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}:root{--bg: #0e1116;--panel: #151a21;--hover: #1b212a;--control: #1f2630;--border: #232a34;--text: #d8dee9;--muted: #7b8794;--accent: #4c9aff;--accent-bg: #16202e;--accent-border: #2a3a52;--ok: #3fb950;--warn: #d29922;--on-warn: #1a1005;--bad: #f85149;--overlay: rgba(31, 38, 48, .85);color-scheme:dark}@media(prefers-color-scheme:light){:root:not([data-theme=dark]){--bg: #ffffff;--panel: #f6f8fa;--hover: #eaeef2;--control: #f6f8fa;--border: #d0d7de;--text: #1f2328;--muted: #656d76;--accent: #0969da;--accent-bg: #ddf4ff;--accent-border: #b6e3ff;--ok: #1a7f37;--warn: #bf8700;--on-warn: #ffffff;--bad: #cf222e;--overlay: rgba(246, 248, 250, .9);color-scheme:light}}:root[data-theme=light]{--bg: #ffffff;--panel: #f6f8fa;--hover: #eaeef2;--control: #f6f8fa;--border: #d0d7de;--text: #1f2328;--muted: #656d76;--accent: #0969da;--accent-bg: #ddf4ff;--accent-border: #b6e3ff;--ok: #1a7f37;--warn: #bf8700;--on-warn: #ffffff;--bad: #cf222e;--overlay: rgba(246, 248, 250, .9);color-scheme:light}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:13px/1.45 ui-sans-serif,system-ui,sans-serif}.layout{height:100vh;display:grid;grid-template-columns:260px 1fr;grid-template-rows:auto minmax(0,1fr)}.layout.grid{grid-template-columns:1fr}.layout.grid aside{display:none}.layout aside{grid-area:2 / 1}.layout main{grid-area:2 / 2}.layout.grid main{grid-area:2 / 1}.banner{grid-area:2 / 1 / 3 / -1;align-self:start;z-index:2;padding:4px 14px;font-size:12px;color:var(--on-warn);background:var(--warn);animation:banner-pulse 1.6s ease-in-out infinite}.layout.down aside,.layout.down main{opacity:.55;transition:opacity .4s}.banner.down{color:var(--text);background:var(--control);border-bottom:1px solid var(--border);animation:none}@keyframes banner-pulse{50%{opacity:.75}}header{grid-column:1 / -1;display:flex;gap:8px;align-items:center;padding:8px 14px;border-bottom:1px solid var(--border);background:var(--panel)}header h1{margin:0 8px 0 0;font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}header .meta{margin-left:0}header .meta.error{color:var(--bad)}.deck{position:relative;margin-right:6px}button.deck-name{color:var(--text);font-size:12px;font-weight:600;background:none;border-color:transparent;padding:3px 6px}button.deck-name .caret{margin-left:5px;font-size:10px;color:var(--muted)}.deck.open button.deck-name{background:var(--control);border-color:var(--border)}.switcher{position:absolute;top:calc(100% + 4px);left:0;z-index:5;min-width:220px;max-width:360px;padding:4px;display:flex;flex-direction:column;gap:2px;background:var(--panel);border:1px solid var(--border);border-radius:6px;box-shadow:0 8px 24px #00000059;font-size:12px}.switcher>.meta{padding:6px 8px;white-space:normal;line-height:1.4}.switcher>.version{margin-top:2px;border-top:1px solid var(--border);font-size:11px}.switcher a{display:flex;gap:8px;align-items:baseline;padding:6px 8px;border-radius:4px;color:var(--text);text-decoration:none}.switcher a:hover{background:var(--hover)}.switcher a .name{font-weight:600}.switcher a .meta{margin-left:auto}.deck-actions{margin-left:auto;display:inline-flex;gap:6px}.deck-actions .bell{padding:4px 8px;filter:grayscale(1);opacity:.5}.deck-actions .bell.on{filter:none;opacity:1;border-color:var(--accent)}.deck-actions .bell.denied{filter:none;opacity:.8;border-color:var(--warn)}.deck-actions .install{color:var(--accent);margin-right:8px}.deck-actions .shutdown{margin-left:6px;color:var(--muted)}.deck-actions .shutdown:hover:not(:disabled){color:var(--bad);border-color:var(--bad)}aside{background:var(--panel);border-right:1px solid var(--border);overflow-y:auto}.proc{padding:10px 16px;border-bottom:1px solid var(--border);cursor:pointer;display:grid;grid-template-columns:8px 1fr;gap:10px;align-items:baseline}.proc:hover{background:var(--hover)}.proc.active{background:var(--hover);box-shadow:inset 2px 0 0 var(--accent)}.dot{width:8px;height:8px;border-radius:50%;background:var(--muted);align-self:center}.dot.running{background:var(--ok)}.dot.starting{background:var(--warn)}.dot.waiting{background:var(--accent)}.dot.exited{background:var(--bad)}.dot.blocked{background:transparent;border:2px solid var(--bad)}.badge{margin-left:6px;padding:1px 5px;border-radius:8px;font-size:10px;font-weight:600;color:var(--on-warn);background:var(--warn);vertical-align:1px}.badge.err{color:#fff;background:var(--bad);cursor:help}.proc .name{font-weight:600}.proc-body{min-width:0}.sub{display:flex;align-items:center;gap:5px;margin-top:4px}.up{margin-left:auto;color:var(--muted);font-size:10px;font-family:ui-monospace,SFMono-Regular,monospace;font-variant-numeric:tabular-nums}.up.exit{color:var(--bad);opacity:.85}.proc .cmd{color:var(--muted);font-size:11px;font-family:ui-monospace,SFMono-Regular,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}main{display:grid;grid-template-rows:minmax(0,1fr) auto;min-width:0;min-height:0}.meta{color:var(--muted);font-family:ui-monospace,SFMono-Regular,monospace;font-size:11px;margin-left:auto}button{font:inherit;color:var(--text);background:var(--control);border:1px solid var(--border);border-radius:6px;padding:4px 12px;cursor:pointer}button:hover:not(:disabled){border-color:var(--accent)}button:disabled{opacity:.35;cursor:default}a{color:var(--accent)}.links{display:flex;align-items:center;gap:6px;min-width:0;overflow:hidden}a.port{font-family:ui-monospace,SFMono-Regular,monospace;font-size:11px;text-decoration:none;color:var(--accent);background:var(--accent-bg);border:1px solid var(--accent-border);border-radius:5px;padding:1px 6px;white-space:nowrap}a.port:hover{border-color:var(--accent)}.links-more{color:var(--muted);font-size:11px;white-space:nowrap;cursor:help}input.search{font-family:ui-monospace,SFMono-Regular,monospace;font-size:12px;color:var(--text);background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:4px 9px;width:200px}input.search:focus{outline:none;border-color:var(--accent)}.hints{padding:10px 16px;color:var(--muted);font-size:10px;line-height:1.7}.to-bottom{position:absolute;right:18px;bottom:14px;z-index:5;font-size:11px;padding:3px 12px;border-radius:12px;background:var(--overlay)}.to-bottom[hidden]{display:none}.segmented{display:inline-flex;margin-left:4px}.segmented button{padding:4px 9px;border-radius:0;font-size:13px;line-height:1.2}.segmented button:first-child{border-radius:6px 0 0 6px}.segmented button+button{margin-left:-1px}.segmented button:last-child{border-radius:0 6px 6px 0}.segmented.theme{margin-right:8px}.segmented button.on{color:var(--accent);border-color:var(--accent);background:var(--accent-bg);position:relative;z-index:1}.terminals{position:relative;min-height:0;background:var(--bg)}.tile{display:flex;flex-direction:column;min-height:0;min-width:0}.tile[hidden]{display:none}.term{position:relative;flex:1;min-height:0}.terminals.single .tile{position:absolute;top:0;right:0;bottom:0;left:0}.terminals.single .term{margin:8px 4px 4px 10px}.terminals.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-rows:minmax(180px,1fr);gap:6px;padding:6px;overflow-y:auto}.terminals.grid.cols-1{grid-template-columns:minmax(0,1fr)}.terminals.grid.cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.terminals.grid.cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}@media(max-width:1100px){.terminals.grid.cols-3,.terminals.grid.cols-4{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:700px){.terminals.grid{grid-template-columns:minmax(0,1fr)}}.tray{display:flex;flex-wrap:wrap;align-items:center;gap:4px 10px;padding:4px 10px;border-top:1px solid var(--border);background:var(--panel);font-size:11px}.tray-item{display:inline-flex;align-items:center}.tray .peek{display:inline-flex;align-items:center;gap:6px;padding:2px 6px;background:transparent;border-color:transparent}.tray .peek:hover{background:var(--hover);border-color:var(--border)}.tray .peek.active{border-color:var(--accent)}.tray .peek .name{font-weight:600}.tray .pin{opacity:0}.tray-item:hover .pin{opacity:.6}button.pin{padding:0 4px;font-size:11px;line-height:1.4;background:transparent;border-color:transparent;filter:grayscale(1);opacity:.3}button.pin:hover{opacity:.8;border-color:var(--accent)}button.pin.on{opacity:1;filter:none}.proc .name button.pin{margin-left:4px;vertical-align:1px;opacity:0}.proc:hover .name button.pin,.proc .name button.pin.on{opacity:.3}.proc .name button.pin.on{opacity:1}.terminals.grid .tile{border:1px solid var(--border);border-radius:6px;overflow:hidden}.terminals.grid .tile.active{border-color:var(--accent)}.terminals.grid .term{margin:4px 2px 2px 6px}.tile-head{display:flex;align-items:center;gap:8px;padding:4px 8px;font-size:11px;background:var(--panel);border-bottom:1px solid var(--border);-webkit-user-select:none;user-select:none;min-width:0}.terminals.grid .tile-head{cursor:pointer}.tile-head .name{font-weight:600;white-space:nowrap}.tile-head .links{flex-shrink:0;overflow:visible}.tile-head .meta{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:1;min-width:3ch}.pane-actions{display:inline-flex;gap:2px;flex-shrink:0;opacity:.4;transition:opacity .12s}.tile:hover .pane-actions,.tile.active .pane-actions{opacity:1}.pane-actions button{padding:1px 7px;font-size:12px;line-height:1.3;background:transparent;border-color:transparent}.pane-actions button:hover:not(:disabled){background:var(--control);border-color:var(--accent)}.traffic{display:grid;grid-template-rows:auto minmax(0,1fr);min-height:0;height:100%}.traffic-bar{display:flex;align-items:center;flex-wrap:wrap;gap:6px;padding:8px 12px;border-bottom:1px solid var(--border)}.traffic-bar>button{padding:4px 9px;font-size:13px}.traffic-bar button.on{color:var(--accent);border-color:var(--accent);background:var(--accent-bg)}.traffic-spacer{flex:1}.traffic-list{overflow:auto;font:12px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace}.traffic-empty{padding:24px;color:var(--muted);font:13px/1.6 ui-sans-serif,system-ui,sans-serif;max-width:60ch}.traffic-row{display:grid;grid-template-columns:92px 8ch 5ch minmax(0,1fr) auto 6ch 7ch;gap:10px;align-items:baseline;padding:2px 12px;border-bottom:1px solid var(--border);cursor:pointer;white-space:nowrap}.traffic-row:hover{background:var(--hover)}.traffic-row.open{background:var(--accent-bg)}.traffic-row .proc-tag{color:var(--accent);overflow:hidden;text-overflow:ellipsis}.traffic-row .method{font-weight:600}.traffic-row .method.ws{color:var(--accent)}.traffic-row .path{overflow:hidden;text-overflow:ellipsis}.traffic-row .status.ok{color:var(--ok)}.traffic-row .status.warn{color:var(--warn)}.traffic-row .status.bad{color:var(--bad)}.traffic-row .status.ws{color:var(--muted)}.traffic-row .meta:last-child,.traffic-row .meta:nth-last-child(2){text-align:right}.traffic-detail{padding:6px 12px 12px 114px;border-bottom:1px solid var(--border);background:var(--panel)}.traffic-detail .detail-label{color:var(--muted);font:11px/2 ui-sans-serif,system-ui,sans-serif;text-transform:uppercase;letter-spacing:.04em}.traffic-detail pre{margin:0 0 8px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:pre-wrap;word-break:break-word;max-height:40vh;overflow:auto}
|