agentdynamics 0.4.0__py3-none-any.whl
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.
- agentdynamics/__init__.py +12 -0
- agentdynamics/__main__.py +322 -0
- agentdynamics/analysis.py +755 -0
- agentdynamics/autotrace.py +697 -0
- agentdynamics/bootstrap/sitecustomize.py +27 -0
- agentdynamics/collectors/__init__.py +0 -0
- agentdynamics/collectors/aegis_audit.py +72 -0
- agentdynamics/collectors/claude_code.py +257 -0
- agentdynamics/collectors/generic.py +103 -0
- agentdynamics/collectors/inbox.py +95 -0
- agentdynamics/collectors/langfuse.py +98 -0
- agentdynamics/collectors/langsmith.py +258 -0
- agentdynamics/collectors/otlp.py +349 -0
- agentdynamics/collectors/spans.py +202 -0
- agentdynamics/config.py +139 -0
- agentdynamics/engine.py +376 -0
- agentdynamics/flows.py +142 -0
- agentdynamics/govern.py +231 -0
- agentdynamics/integrations/__init__.py +1 -0
- agentdynamics/integrations/aegis.py +352 -0
- agentdynamics/phases.py +82 -0
- agentdynamics/pricing.py +69 -0
- agentdynamics/privacy.py +41 -0
- agentdynamics/sdk.py +105 -0
- agentdynamics/server.py +949 -0
- agentdynamics/slo.py +84 -0
- agentdynamics/store.py +228 -0
- agentdynamics/web/app.js +1069 -0
- agentdynamics/web/charts.js +185 -0
- agentdynamics/web/index.html +40 -0
- agentdynamics/web/style.css +244 -0
- agentdynamics-0.4.0.dist-info/METADATA +195 -0
- agentdynamics-0.4.0.dist-info/RECORD +37 -0
- agentdynamics-0.4.0.dist-info/WHEEL +5 -0
- agentdynamics-0.4.0.dist-info/entry_points.txt +2 -0
- agentdynamics-0.4.0.dist-info/licenses/LICENSE +202 -0
- agentdynamics-0.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/* Minimal SVG chart kit: columns (stacked), line/area, hbars, 100% stack, sparkline. */
|
|
2
|
+
const C = (() => {
|
|
3
|
+
const NS = "http://www.w3.org/2000/svg";
|
|
4
|
+
const css = (v) => getComputedStyle(document.documentElement).getPropertyValue(v).trim();
|
|
5
|
+
const SERIES = ["--s1", "--s2", "--s3", "--s4", "--s5", "--s6", "--s7", "--s8"];
|
|
6
|
+
// Phase colors follow the entity, never rank.
|
|
7
|
+
const PHASE = { explore: "--s1", edit: "--s2", verify: "--s3", execute: "--s4", plan: "--s7", delegate: "--s5",
|
|
8
|
+
vcs: "--s6", communicate: "--s8", respond: "--neutral", other: "--neutral" };
|
|
9
|
+
const color = (i) => `var(${SERIES[i % SERIES.length]})`;
|
|
10
|
+
const phaseColor = (p) => `var(${PHASE[p] || "--neutral"})`;
|
|
11
|
+
const tipEl = () => document.getElementById("tip");
|
|
12
|
+
|
|
13
|
+
function tip(html, ev) {
|
|
14
|
+
const t = tipEl();
|
|
15
|
+
if (!html) { t.style.display = "none"; return; }
|
|
16
|
+
t.innerHTML = html;
|
|
17
|
+
t.style.display = "block";
|
|
18
|
+
const w = t.offsetWidth, h = t.offsetHeight;
|
|
19
|
+
let x = ev.clientX + 14, y = ev.clientY + 14;
|
|
20
|
+
if (x + w > innerWidth - 8) x = ev.clientX - w - 14;
|
|
21
|
+
if (y + h > innerHeight - 8) y = ev.clientY - h - 14;
|
|
22
|
+
t.style.left = x + "px"; t.style.top = y + "px";
|
|
23
|
+
}
|
|
24
|
+
const hideTip = () => tip(null);
|
|
25
|
+
|
|
26
|
+
function el(tag, attrs = {}, parent) {
|
|
27
|
+
const e = document.createElementNS(NS, tag);
|
|
28
|
+
for (const k in attrs) e.setAttribute(k, attrs[k]);
|
|
29
|
+
if (parent) parent.appendChild(e);
|
|
30
|
+
return e;
|
|
31
|
+
}
|
|
32
|
+
function niceMax(v) {
|
|
33
|
+
if (!v || v <= 0) return 1;
|
|
34
|
+
const p = Math.pow(10, Math.floor(Math.log10(v)));
|
|
35
|
+
for (const m of [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10]) if (m * p >= v) return m * p;
|
|
36
|
+
return 10 * p;
|
|
37
|
+
}
|
|
38
|
+
// top-rounded rect path anchored to baseline
|
|
39
|
+
function colPath(x, y, w, h, r) {
|
|
40
|
+
r = Math.min(r, w / 2, h);
|
|
41
|
+
if (h <= 0) return "";
|
|
42
|
+
return `M${x},${y + h}V${y + r}Q${x},${y} ${x + r},${y}H${x + w - r}Q${x + w},${y} ${x + w},${y + r}V${y + h}Z`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** columns(el, rows, {x, keys:[{key,label,color}], fmt, height, xfmt}) — stacked when several keys */
|
|
46
|
+
function columns(host, rows, o) {
|
|
47
|
+
host.innerHTML = "";
|
|
48
|
+
host.classList.add("chart");
|
|
49
|
+
const H = o.height || 200, W = host.clientWidth || 600;
|
|
50
|
+
const m = { l: 48, r: 8, t: 8, b: 24 };
|
|
51
|
+
const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, height: H }, host);
|
|
52
|
+
const keys = o.keys;
|
|
53
|
+
const totals = rows.map((r) => keys.reduce((s, k) => s + (+r[k.key] || 0), 0));
|
|
54
|
+
const max = niceMax(Math.max(...totals, 0));
|
|
55
|
+
const iw = W - m.l - m.r, ih = H - m.t - m.b;
|
|
56
|
+
for (let i = 0; i <= 4; i++) {
|
|
57
|
+
const y = m.t + ih - (ih * i) / 4;
|
|
58
|
+
el("line", { x1: m.l, x2: W - m.r, y1: y, y2: y, class: "gridline" }, svg);
|
|
59
|
+
el("text", { x: m.l - 6, y: y + 4, "text-anchor": "end" }, svg).textContent = o.fmt((max * i) / 4);
|
|
60
|
+
}
|
|
61
|
+
if (!rows.length) return;
|
|
62
|
+
const bw = iw / rows.length, gap = Math.max(1, Math.min(6, bw * 0.25)), w = Math.max(1, bw - gap);
|
|
63
|
+
const every = Math.ceil(rows.length / Math.max(1, Math.floor(iw / 70)));
|
|
64
|
+
rows.forEach((r, i) => {
|
|
65
|
+
const x = m.l + i * bw + gap / 2;
|
|
66
|
+
let base = m.t + ih;
|
|
67
|
+
keys.forEach((k, ki) => {
|
|
68
|
+
const v = +r[k.key] || 0;
|
|
69
|
+
const h = (v / max) * ih;
|
|
70
|
+
if (h <= 0) return;
|
|
71
|
+
const top = ki === keys.length - 1 || keys.slice(ki + 1).every((kk) => !(+r[kk.key]));
|
|
72
|
+
const hh = Math.max(0, h - (ki ? 2 : 0));
|
|
73
|
+
const p = top ? colPath(x, base - h, w, hh, 3) : `M${x},${base - h}h${w}v${hh}h${-w}Z`;
|
|
74
|
+
el("path", { d: p, fill: k.color }, svg);
|
|
75
|
+
base -= h;
|
|
76
|
+
});
|
|
77
|
+
if (i % every === 0) el("text", { x: x + w / 2, y: H - 6, "text-anchor": "middle" }, svg).textContent = (o.xfmt || ((d) => d))(r[o.x]);
|
|
78
|
+
const hit = el("rect", { x: m.l + i * bw, y: m.t, width: bw, height: ih, fill: "transparent" }, svg);
|
|
79
|
+
hit.addEventListener("mousemove", (ev) => {
|
|
80
|
+
let h = `<b>${(o.xfmt || ((d) => d))(r[o.x])}</b>`;
|
|
81
|
+
[...keys].reverse().forEach((k) => {
|
|
82
|
+
if (keys.length > 1 && !(+r[k.key])) return;
|
|
83
|
+
h += `<div class="tr"><span><i style="background:${k.color}"></i>${k.label}</span><b>${o.fmt(+r[k.key] || 0)}</b></div>`;
|
|
84
|
+
});
|
|
85
|
+
if (keys.length > 1) h += `<div class="tr"><span>Total</span><b>${o.fmt(totals[i])}</b></div>`;
|
|
86
|
+
tip(h, ev);
|
|
87
|
+
});
|
|
88
|
+
hit.addEventListener("mouseleave", hideTip);
|
|
89
|
+
if (o.onClick) { hit.style.cursor = "pointer"; hit.addEventListener("click", () => o.onClick(r)); }
|
|
90
|
+
});
|
|
91
|
+
if (keys.length > 1) legend(host, keys);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function legend(host, keys) {
|
|
95
|
+
const lg = document.createElement("div");
|
|
96
|
+
lg.className = "legend";
|
|
97
|
+
lg.innerHTML = keys.map((k) => `<span><i style="background:${k.color}"></i>${k.label}</span>`).join("");
|
|
98
|
+
host.appendChild(lg);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** line(el, pts:[{x,y,...}], {fmt, xfmt, height, area, color, label, tipExtra, refLine}) */
|
|
102
|
+
function line(host, pts, o) {
|
|
103
|
+
host.innerHTML = "";
|
|
104
|
+
host.classList.add("chart");
|
|
105
|
+
const H = o.height || 180, W = host.clientWidth || 600;
|
|
106
|
+
const m = { l: 52, r: 10, t: 10, b: 24 };
|
|
107
|
+
const svg = el("svg", { viewBox: `0 0 ${W} ${H}`, height: H }, host);
|
|
108
|
+
const iw = W - m.l - m.r, ih = H - m.t - m.b;
|
|
109
|
+
const max = niceMax(Math.max(...pts.map((p) => p.y), o.refLine || 0, 0));
|
|
110
|
+
const xs = pts.map((p) => p.x);
|
|
111
|
+
const x0 = Math.min(...xs), x1 = Math.max(...xs);
|
|
112
|
+
const sx = (v) => m.l + (x1 === x0 ? iw / 2 : ((v - x0) / (x1 - x0)) * iw);
|
|
113
|
+
const sy = (v) => m.t + ih - (v / max) * ih;
|
|
114
|
+
for (let i = 0; i <= 4; i++) {
|
|
115
|
+
const y = m.t + ih - (ih * i) / 4;
|
|
116
|
+
el("line", { x1: m.l, x2: W - m.r, y1: y, y2: y, class: "gridline" }, svg);
|
|
117
|
+
el("text", { x: m.l - 6, y: y + 4, "text-anchor": "end" }, svg).textContent = o.fmt((max * i) / 4);
|
|
118
|
+
}
|
|
119
|
+
if (!pts.length) return;
|
|
120
|
+
const col = o.color || "var(--s1)";
|
|
121
|
+
const d = pts.map((p, i) => `${i ? "L" : "M"}${sx(p.x)},${sy(p.y)}`).join("");
|
|
122
|
+
if (o.area) el("path", { d: `${d}L${sx(pts.at(-1).x)},${m.t + ih}L${sx(pts[0].x)},${m.t + ih}Z`, fill: col, opacity: 0.12 }, svg);
|
|
123
|
+
if (o.refLine) {
|
|
124
|
+
el("line", { x1: m.l, x2: W - m.r, y1: sy(o.refLine), y2: sy(o.refLine), stroke: "var(--critical)", "stroke-dasharray": "4 4", "stroke-width": 1 }, svg);
|
|
125
|
+
el("text", { x: W - m.r, y: sy(o.refLine) - 4, "text-anchor": "end" }, svg).textContent = o.refLabel || "";
|
|
126
|
+
}
|
|
127
|
+
el("path", { d, fill: "none", stroke: col, "stroke-width": 2, "stroke-linejoin": "round" }, svg);
|
|
128
|
+
const ticks = Math.min(6, pts.length);
|
|
129
|
+
for (let i = 0; i < ticks; i++) {
|
|
130
|
+
const p = pts[Math.round((i * (pts.length - 1)) / Math.max(1, ticks - 1))];
|
|
131
|
+
el("text", { x: sx(p.x), y: H - 6, "text-anchor": "middle" }, svg).textContent = (o.xfmt || ((v) => v))(p.x);
|
|
132
|
+
}
|
|
133
|
+
const cross = el("line", { y1: m.t, y2: m.t + ih, stroke: "var(--text-3)", "stroke-width": 1, opacity: 0 }, svg);
|
|
134
|
+
const dot = el("circle", { r: 4, fill: col, stroke: "var(--surface)", "stroke-width": 2, opacity: 0 }, svg);
|
|
135
|
+
const hit = el("rect", { x: m.l, y: m.t, width: iw, height: ih, fill: "transparent" }, svg);
|
|
136
|
+
hit.addEventListener("mousemove", (ev) => {
|
|
137
|
+
const r = svg.getBoundingClientRect();
|
|
138
|
+
const mx = ((ev.clientX - r.left) / r.width) * W;
|
|
139
|
+
let best = pts[0];
|
|
140
|
+
for (const p of pts) if (Math.abs(sx(p.x) - mx) < Math.abs(sx(best.x) - mx)) best = p;
|
|
141
|
+
cross.setAttribute("x1", sx(best.x)); cross.setAttribute("x2", sx(best.x)); cross.setAttribute("opacity", 0.5);
|
|
142
|
+
dot.setAttribute("cx", sx(best.x)); dot.setAttribute("cy", sy(best.y)); dot.setAttribute("opacity", 1);
|
|
143
|
+
tip(`<b>${(o.xfmt || ((v) => v))(best.x)}</b><div class="tr"><span>${o.label || "value"}</span><b>${o.fmt(best.y)}</b></div>${o.tipExtra ? o.tipExtra(best) : ""}`, ev);
|
|
144
|
+
});
|
|
145
|
+
hit.addEventListener("mouseleave", () => { hideTip(); cross.setAttribute("opacity", 0); dot.setAttribute("opacity", 0); });
|
|
146
|
+
if (o.onClick) hit.addEventListener("click", (ev) => {
|
|
147
|
+
const r = svg.getBoundingClientRect(); const mx = ((ev.clientX - r.left) / r.width) * W;
|
|
148
|
+
let best = pts[0]; for (const p of pts) if (Math.abs(sx(p.x) - mx) < Math.abs(sx(best.x) - mx)) best = p;
|
|
149
|
+
o.onClick(best);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** hbars(el, items:[{label, value, color?, sub?, href?}], {fmt}) */
|
|
154
|
+
function hbars(host, items, o = {}) {
|
|
155
|
+
const max = Math.max(...items.map((i) => i.value), 0) || 1;
|
|
156
|
+
host.innerHTML = items.map((i) => `
|
|
157
|
+
<div class="hbar" ${i.title ? `title="${i.title}"` : ""}>
|
|
158
|
+
<div class="k">${i.href ? `<a href="${i.href}">${i.label}</a>` : i.label}</div>
|
|
159
|
+
<div class="track"><div class="fill" style="width:${(100 * i.value) / max}%;background:${i.color || "var(--s1)"}"></div></div>
|
|
160
|
+
<div class="v">${(o.fmt || ((v) => v))(i.value)}</div>
|
|
161
|
+
</div>`).join("") || `<div class="empty">No data</div>`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** stack100(el, parts:[{key,label,value,color}]) with legend */
|
|
165
|
+
function stack100(host, parts, o = {}) {
|
|
166
|
+
const tot = parts.reduce((s, p) => s + p.value, 0) || 1;
|
|
167
|
+
const bar = parts.filter((p) => p.value > 0).map((p) =>
|
|
168
|
+
`<div style="width:${(100 * p.value) / tot}%;background:${p.color}" data-l="${p.label}" data-v="${p.value}"></div>`).join("");
|
|
169
|
+
host.innerHTML = `<div class="stack100">${bar}</div>` + (o.legend === false ? "" :
|
|
170
|
+
`<div class="legend">${parts.filter((p) => p.value > 0).map((p) => `<span><i style="background:${p.color}"></i>${p.label} ${Math.round((100 * p.value) / tot)}%</span>`).join("")}</div>`);
|
|
171
|
+
host.querySelectorAll(".stack100 > div").forEach((d) => {
|
|
172
|
+
d.addEventListener("mousemove", (ev) => tip(`<b>${d.dataset.l}</b><div class="tr"><span>share</span><b>${Math.round((100 * d.dataset.v) / tot)}%</b></div>${o.fmt ? `<div class="tr"><span>value</span><b>${o.fmt(+d.dataset.v)}</b></div>` : ""}`, ev));
|
|
173
|
+
d.addEventListener("mouseleave", hideTip);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function sparkline(values, w = 90, h = 22, col = "var(--s1)") {
|
|
178
|
+
if (!values.length) return "";
|
|
179
|
+
const max = Math.max(...values) || 1;
|
|
180
|
+
const pts = values.map((v, i) => `${values.length === 1 ? w / 2 : (i * w) / (values.length - 1)},${h - 2 - (v / max) * (h - 4)}`).join(" ");
|
|
181
|
+
return `<svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}"><polyline points="${pts}" fill="none" stroke="${col}" stroke-width="1.5" stroke-linejoin="round"/></svg>`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { columns, line, hbars, stack100, sparkline, color, phaseColor, tip, hideTip, css, PHASE };
|
|
185
|
+
})();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>AgentDynamics</title>
|
|
7
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M2 13h4l3-8 4 14 3-9 2 3h4' fill='none' stroke='%232a78d6' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E">
|
|
8
|
+
<link rel="stylesheet" href="/style.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div class="app">
|
|
12
|
+
<aside class="side">
|
|
13
|
+
<div class="brand">
|
|
14
|
+
<svg width="22" height="22" viewBox="0 0 24 24"><path d="M2 13h4l3-8 4 14 3-9 2 3h4" fill="none" stroke="var(--accent)" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
15
|
+
AgentDynamics
|
|
16
|
+
</div>
|
|
17
|
+
<nav id="nav"></nav>
|
|
18
|
+
</aside>
|
|
19
|
+
<div class="main">
|
|
20
|
+
<div class="topbar">
|
|
21
|
+
<label>Project <select id="f-project"><option value="">All projects</option></select></label>
|
|
22
|
+
<label id="f-env-wrap" hidden>Env <select id="f-env"></select></label>
|
|
23
|
+
<div class="seg" id="f-days">
|
|
24
|
+
<button data-v="1">24h</button><button data-v="7">7d</button><button data-v="30">30d</button><button data-v="">All</button>
|
|
25
|
+
</div>
|
|
26
|
+
<label title="Include delegated subagent runs as separate tasks"><input type="checkbox" id="f-sub"> Subagents as tasks</label>
|
|
27
|
+
<div class="spacer"></div>
|
|
28
|
+
<span class="small muted" id="refreshed"></span>
|
|
29
|
+
<button id="btn-refresh" title="Re-scan sources now">Refresh</button>
|
|
30
|
+
<button id="btn-theme" title="Toggle theme" aria-label="Toggle theme">◐</button>
|
|
31
|
+
</div>
|
|
32
|
+
<div id="page" class="page"><div class="loading">Loading…</div></div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="tip" id="tip"></div>
|
|
36
|
+
<div class="toast" id="toast"></div>
|
|
37
|
+
<script src="/charts.js"></script>
|
|
38
|
+
<script src="/app.js"></script>
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light;
|
|
3
|
+
--bg: #f4f4f2;
|
|
4
|
+
--surface: #fcfcfb;
|
|
5
|
+
--surface-2: #f0efec;
|
|
6
|
+
--border: #e2e1dc;
|
|
7
|
+
--text: #0b0b0b;
|
|
8
|
+
--text-2: #52514e;
|
|
9
|
+
--text-3: #7d7c77;
|
|
10
|
+
--accent: #2a78d6;
|
|
11
|
+
--accent-soft: #e3eefb;
|
|
12
|
+
--good: #0ca30c; --warning: #fab219; --serious: #ec835a; --critical: #d03b3b;
|
|
13
|
+
--good-text: #006300; --warning-text: #8a5a00; --critical-text: #b02a2a;
|
|
14
|
+
--s1: #2a78d6; --s2: #eb6834; --s3: #1baf7a; --s4: #eda100; --s5: #e87ba4; --s6: #008300; --s7: #4a3aa7; --s8: #e34948;
|
|
15
|
+
--neutral: #b4b2ab;
|
|
16
|
+
--grid: #ebeae6;
|
|
17
|
+
--shadow: 0 1px 2px rgba(0,0,0,.05), 0 2px 8px rgba(0,0,0,.04);
|
|
18
|
+
--mono: ui-monospace, "Cascadia Code", Consolas, monospace;
|
|
19
|
+
}
|
|
20
|
+
@media (prefers-color-scheme: dark) {
|
|
21
|
+
:root:not([data-theme="light"]) {
|
|
22
|
+
color-scheme: dark;
|
|
23
|
+
--bg: #121211; --surface: #1a1a19; --surface-2: #232321; --border: #2f2f2c;
|
|
24
|
+
--text: #ffffff; --text-2: #c3c2b7; --text-3: #8d8c85;
|
|
25
|
+
--accent: #3987e5; --accent-soft: #1b2b40;
|
|
26
|
+
--good-text: #4cc94c; --warning-text: #fab219; --critical-text: #ef7070;
|
|
27
|
+
--s1: #3987e5; --s2: #d95926; --s3: #199e70; --s4: #c98500; --s5: #d55181; --s6: #008300; --s7: #9085e9; --s8: #e66767;
|
|
28
|
+
--neutral: #5d5c57; --grid: #2a2a28;
|
|
29
|
+
--shadow: none;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
:root[data-theme="dark"] {
|
|
33
|
+
color-scheme: dark;
|
|
34
|
+
--bg: #121211; --surface: #1a1a19; --surface-2: #232321; --border: #2f2f2c;
|
|
35
|
+
--text: #ffffff; --text-2: #c3c2b7; --text-3: #8d8c85;
|
|
36
|
+
--accent: #3987e5; --accent-soft: #1b2b40;
|
|
37
|
+
--good-text: #4cc94c; --warning-text: #fab219; --critical-text: #ef7070;
|
|
38
|
+
--s1: #3987e5; --s2: #d95926; --s3: #199e70; --s4: #c98500; --s5: #d55181; --s6: #008300; --s7: #9085e9; --s8: #e66767;
|
|
39
|
+
--neutral: #5d5c57; --grid: #2a2a28;
|
|
40
|
+
--shadow: none;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
* { box-sizing: border-box; }
|
|
44
|
+
html, body { margin: 0; height: 100%; }
|
|
45
|
+
body {
|
|
46
|
+
background: var(--bg); color: var(--text);
|
|
47
|
+
font: 14px/1.45 "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
48
|
+
font-variant-numeric: tabular-nums;
|
|
49
|
+
}
|
|
50
|
+
a { color: var(--accent); text-decoration: none; }
|
|
51
|
+
a:hover { text-decoration: underline; }
|
|
52
|
+
h1 { font-size: 20px; margin: 0; font-weight: 650; letter-spacing: -.01em; }
|
|
53
|
+
h2 { font-size: 14px; margin: 0 0 10px; font-weight: 600; }
|
|
54
|
+
h3 { font-size: 13px; margin: 0 0 6px; font-weight: 600; color: var(--text-2); }
|
|
55
|
+
code, .mono { font-family: var(--mono); font-size: 12px; }
|
|
56
|
+
|
|
57
|
+
.app { display: grid; grid-template-columns: 220px 1fr; min-height: 100vh; }
|
|
58
|
+
.side {
|
|
59
|
+
background: var(--surface); border-right: 1px solid var(--border); padding: 16px 10px;
|
|
60
|
+
position: sticky; top: 0; height: 100vh; overflow-y: auto;
|
|
61
|
+
}
|
|
62
|
+
.brand { display: flex; align-items: center; gap: 8px; padding: 0 8px 16px; font-weight: 700; font-size: 15px; }
|
|
63
|
+
.brand svg { flex: none; }
|
|
64
|
+
.nav-group { margin-bottom: 14px; }
|
|
65
|
+
.nav-group > div { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-3); padding: 4px 10px; }
|
|
66
|
+
.nav-group a {
|
|
67
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
68
|
+
padding: 6px 10px; border-radius: 6px; color: var(--text-2); font-size: 13.5px;
|
|
69
|
+
}
|
|
70
|
+
.nav-group a:hover { background: var(--surface-2); text-decoration: none; color: var(--text); }
|
|
71
|
+
.nav-group a.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
|
|
72
|
+
.nav-group a small { color: var(--text-3); font-size: 11px; font-weight: 400; }
|
|
73
|
+
|
|
74
|
+
.main { min-width: 0; }
|
|
75
|
+
.topbar {
|
|
76
|
+
display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
|
|
77
|
+
padding: 10px 24px; background: var(--surface); border-bottom: 1px solid var(--border);
|
|
78
|
+
position: sticky; top: 0; z-index: 5;
|
|
79
|
+
}
|
|
80
|
+
.topbar .spacer { flex: 1; }
|
|
81
|
+
.topbar label { font-size: 12px; color: var(--text-3); display: flex; gap: 6px; align-items: center; }
|
|
82
|
+
select, input[type=text], input[type=number], textarea {
|
|
83
|
+
background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: 6px;
|
|
84
|
+
padding: 5px 8px; font: inherit; font-size: 13px;
|
|
85
|
+
}
|
|
86
|
+
button, .btn {
|
|
87
|
+
background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: 6px;
|
|
88
|
+
padding: 5px 12px; font: inherit; font-size: 13px; cursor: pointer;
|
|
89
|
+
}
|
|
90
|
+
button:hover { background: var(--surface-2); }
|
|
91
|
+
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
92
|
+
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
|
|
93
|
+
.seg button { border: 0; border-radius: 0; padding: 5px 10px; }
|
|
94
|
+
.seg button.on { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
|
|
95
|
+
.status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; background: var(--good); }
|
|
96
|
+
|
|
97
|
+
.page { padding: 20px 24px 60px; max-width: 1500px; }
|
|
98
|
+
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
|
|
99
|
+
.page-head p { margin: 4px 0 0; color: var(--text-2); font-size: 13px; max-width: 780px; }
|
|
100
|
+
|
|
101
|
+
.grid { display: grid; gap: 14px; }
|
|
102
|
+
.g2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
103
|
+
.g3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
104
|
+
.g-3-2 { grid-template-columns: 3fr 2fr; }
|
|
105
|
+
.g-2-1 { grid-template-columns: 2fr 1fr; }
|
|
106
|
+
.span2 { grid-column: span 2; }
|
|
107
|
+
@media (max-width: 1100px) { .g2, .g3, .g-3-2, .g-2-1 { grid-template-columns: 1fr; } .span2 { grid-column: auto; } }
|
|
108
|
+
|
|
109
|
+
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; box-shadow: var(--shadow); min-width: 0; }
|
|
110
|
+
.card-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; margin-bottom: 8px; }
|
|
111
|
+
.card-head h2 { margin: 0; }
|
|
112
|
+
.card-head .sub { color: var(--text-3); font-size: 12px; }
|
|
113
|
+
|
|
114
|
+
.kpis { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; margin-bottom: 14px; }
|
|
115
|
+
.kpi { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; box-shadow: var(--shadow); }
|
|
116
|
+
.kpi .label { font-size: 12px; color: var(--text-2); display: flex; align-items: center; gap: 6px; }
|
|
117
|
+
.kpi .value { font-size: 24px; font-weight: 650; margin-top: 2px; letter-spacing: -.02em; }
|
|
118
|
+
.kpi .hint { font-size: 11.5px; color: var(--text-3); margin-top: 2px; }
|
|
119
|
+
|
|
120
|
+
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
121
|
+
th { text-align: left; font-weight: 600; color: var(--text-3); font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;
|
|
122
|
+
padding: 6px 8px; border-bottom: 1px solid var(--border); white-space: nowrap; cursor: default; }
|
|
123
|
+
th.sortable { cursor: pointer; }
|
|
124
|
+
td { padding: 7px 8px; border-bottom: 1px solid var(--grid); vertical-align: top; }
|
|
125
|
+
tr.click { cursor: pointer; }
|
|
126
|
+
tr.click:hover td { background: var(--surface-2); }
|
|
127
|
+
td.num, th.num { text-align: right; white-space: nowrap; }
|
|
128
|
+
.truncate { max-width: 460px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
129
|
+
.table-wrap { overflow-x: auto; }
|
|
130
|
+
|
|
131
|
+
.pill { display: inline-flex; align-items: center; gap: 5px; padding: 1px 8px; border-radius: 999px; font-size: 11.5px;
|
|
132
|
+
font-weight: 600; background: var(--surface-2); color: var(--text-2); white-space: nowrap; }
|
|
133
|
+
.pill::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--neutral); }
|
|
134
|
+
.pill.normal::before, .pill.satisfied::before, .pill.completed::before, .pill.ok::before { background: var(--good); }
|
|
135
|
+
.pill.warning::before, .pill.tolerating::before, .pill.rework::before, .pill.info::before { background: var(--warning); }
|
|
136
|
+
.pill.serious::before { background: var(--serious); }
|
|
137
|
+
.pill.critical::before, .pill.frustrated::before, .pill.interrupted::before, .pill.failed::before { background: var(--critical); }
|
|
138
|
+
.pill.in-progress::before { background: var(--accent); }
|
|
139
|
+
.pill.info::before { background: var(--accent); }
|
|
140
|
+
.tag { display: inline-block; font-size: 11px; padding: 0 6px; border-radius: 4px; background: var(--surface-2); color: var(--text-2); margin-right: 4px; border: 1px solid var(--border); }
|
|
141
|
+
.tag.bad { color: var(--critical-text); border-color: color-mix(in srgb, var(--critical) 40%, transparent); }
|
|
142
|
+
.tag.warn { color: var(--warning-text); border-color: color-mix(in srgb, var(--warning) 50%, transparent); }
|
|
143
|
+
|
|
144
|
+
.muted { color: var(--text-3); }
|
|
145
|
+
.small { font-size: 12px; }
|
|
146
|
+
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
|
147
|
+
.between { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
|
|
148
|
+
.empty { color: var(--text-3); padding: 24px; text-align: center; }
|
|
149
|
+
.loading { color: var(--text-3); padding: 40px; text-align: center; }
|
|
150
|
+
|
|
151
|
+
/* charts */
|
|
152
|
+
.chart { position: relative; width: 100%; }
|
|
153
|
+
.chart svg { display: block; width: 100%; overflow: visible; }
|
|
154
|
+
.chart text { fill: var(--text-3); font-size: 11px; }
|
|
155
|
+
.chart .gridline { stroke: var(--grid); stroke-width: 1; }
|
|
156
|
+
.chart .axis { stroke: var(--border); }
|
|
157
|
+
.legend { display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 12px; color: var(--text-2); margin-top: 6px; }
|
|
158
|
+
.legend span { display: inline-flex; align-items: center; gap: 6px; }
|
|
159
|
+
.legend i { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
|
|
160
|
+
.tip {
|
|
161
|
+
position: fixed; z-index: 50; pointer-events: none; background: var(--surface); color: var(--text);
|
|
162
|
+
border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; font-size: 12px; box-shadow: 0 4px 16px rgba(0,0,0,.14);
|
|
163
|
+
max-width: 360px; display: none;
|
|
164
|
+
}
|
|
165
|
+
.tip b { font-weight: 600; }
|
|
166
|
+
.tip .tr { display: flex; justify-content: space-between; gap: 16px; }
|
|
167
|
+
.tip .tr i { width: 8px; height: 8px; border-radius: 2px; display: inline-block; margin-right: 6px; }
|
|
168
|
+
|
|
169
|
+
.hbar { display: grid; grid-template-columns: minmax(90px, 160px) 1fr 70px; gap: 8px; align-items: center; font-size: 12.5px; margin: 5px 0; }
|
|
170
|
+
.hbar .track { height: 10px; background: var(--surface-2); border-radius: 4px; overflow: hidden; }
|
|
171
|
+
.hbar .fill { height: 100%; border-radius: 0 4px 4px 0; background: var(--s1); }
|
|
172
|
+
.hbar .v { text-align: right; color: var(--text-2); }
|
|
173
|
+
.hbar .k { color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
174
|
+
|
|
175
|
+
.stack100 { display: flex; height: 14px; border-radius: 4px; overflow: hidden; gap: 2px; background: var(--surface); }
|
|
176
|
+
.stack100 > div { height: 100%; }
|
|
177
|
+
|
|
178
|
+
.score-row { display: grid; grid-template-columns: 110px 1fr 40px; gap: 8px; align-items: center; margin: 6px 0; font-size: 13px; }
|
|
179
|
+
.score-row .track { height: 8px; background: var(--surface-2); border-radius: 4px; overflow: hidden; }
|
|
180
|
+
.score-row .fill { height: 100%; border-radius: 4px; }
|
|
181
|
+
.big-score { font-size: 40px; font-weight: 700; letter-spacing: -.03em; line-height: 1; }
|
|
182
|
+
|
|
183
|
+
/* waterfall */
|
|
184
|
+
.wf { font-size: 12px; }
|
|
185
|
+
.wf-row { display: grid; grid-template-columns: 34px 170px 1fr 80px 70px; gap: 8px; align-items: center; padding: 3px 0; border-bottom: 1px solid var(--grid); cursor: pointer; }
|
|
186
|
+
.wf-row:hover { background: var(--surface-2); }
|
|
187
|
+
.wf-row.sel { background: var(--accent-soft); }
|
|
188
|
+
.wf-track { position: relative; height: 14px; }
|
|
189
|
+
.wf-bar { position: absolute; top: 2px; height: 10px; border-radius: 3px; min-width: 2px; }
|
|
190
|
+
.wf-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
191
|
+
.wf-detail { background: var(--surface-2); border-radius: 8px; padding: 10px 12px; margin: 4px 0 8px; font-size: 12px; white-space: pre-wrap; word-break: break-word; font-family: var(--mono); max-height: 320px; overflow: auto; }
|
|
192
|
+
|
|
193
|
+
.insight { border-left: 3px solid var(--neutral); padding: 10px 14px; background: var(--surface); border-radius: 0 10px 10px 0; border-top: 1px solid var(--border); border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); }
|
|
194
|
+
.insight.warning { border-left-color: var(--warning); }
|
|
195
|
+
.insight.ok { border-left-color: var(--good); }
|
|
196
|
+
.insight.info { border-left-color: var(--accent); }
|
|
197
|
+
.insight .metric { font-size: 18px; font-weight: 650; margin: 2px 0 4px; }
|
|
198
|
+
.insight .advice { font-size: 12.5px; color: var(--text-2); margin-top: 6px; }
|
|
199
|
+
|
|
200
|
+
.flow-wrap { display: grid; grid-template-columns: 1fr 300px; gap: 14px; }
|
|
201
|
+
@media (max-width: 1100px) { .flow-wrap { grid-template-columns: 1fr; } }
|
|
202
|
+
.flow svg { width: 100%; display: block; }
|
|
203
|
+
.flow .node rect { fill: var(--surface); stroke: var(--border); stroke-width: 1.5; }
|
|
204
|
+
.flow .node:hover rect { stroke: var(--accent); }
|
|
205
|
+
.flow .node.sel rect { stroke: var(--accent); stroke-width: 2.5; }
|
|
206
|
+
.flow .node text { fill: var(--text); font-size: 12px; }
|
|
207
|
+
.flow .node text.sub { fill: var(--text-3); font-size: 10.5px; }
|
|
208
|
+
.flow .edge { fill: none; stroke: var(--neutral); opacity: .7; }
|
|
209
|
+
.flow .edge.err { stroke: var(--critical); }
|
|
210
|
+
.flow .elabel { fill: var(--text-3); font-size: 10px; }
|
|
211
|
+
.flow .col-title { fill: var(--text-3); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }
|
|
212
|
+
|
|
213
|
+
.prompt-box { background: var(--surface-2); border-radius: 8px; padding: 10px 12px; white-space: pre-wrap; word-break: break-word; max-height: 180px; overflow: auto; font-size: 13px; }
|
|
214
|
+
details summary { cursor: pointer; color: var(--text-2); font-size: 12.5px; }
|
|
215
|
+
.code-block { background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 12px; overflow-x: auto; font-family: var(--mono); font-size: 12px; white-space: pre; }
|
|
216
|
+
.rule-row input[type=number] { width: 90px; }
|
|
217
|
+
.toast { position: fixed; bottom: 18px; right: 18px; background: var(--text); color: var(--bg); padding: 8px 14px; border-radius: 8px; font-size: 13px; opacity: 0; transition: opacity .2s; z-index: 60; }
|
|
218
|
+
.toast.show { opacity: 1; }
|
|
219
|
+
|
|
220
|
+
@media (max-width: 760px) {
|
|
221
|
+
.app { grid-template-columns: 1fr; }
|
|
222
|
+
.side { position: static; height: auto; display: flex; overflow-x: auto; gap: 4px; padding: 8px; }
|
|
223
|
+
.brand { display: none; }
|
|
224
|
+
.side nav { display: flex; gap: 2px; }
|
|
225
|
+
.nav-group { display: flex; margin: 0; }
|
|
226
|
+
.nav-group > div { display: none; }
|
|
227
|
+
.nav-group a { white-space: nowrap; }
|
|
228
|
+
.page { padding: 16px; }
|
|
229
|
+
.topbar { padding: 8px 16px; }
|
|
230
|
+
.wf-row { grid-template-columns: 28px 110px 1fr 60px; }
|
|
231
|
+
.wf-row > :last-child { display: none; }
|
|
232
|
+
}
|
|
233
|
+
.spacer { flex: 1; }
|
|
234
|
+
|
|
235
|
+
.stack-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 10px; }
|
|
236
|
+
.stack { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; text-align: left; padding: 10px 12px; border-radius: 8px; }
|
|
237
|
+
.stack span { font-size: 12px; color: var(--text-3); }
|
|
238
|
+
.stack.on { border-color: var(--accent); background: var(--accent-soft); }
|
|
239
|
+
.stack.on b { color: var(--accent); }
|
|
240
|
+
.live { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-radius: 8px; background: var(--surface-2); font-size: 14px; }
|
|
241
|
+
.live.ok { background: color-mix(in srgb, var(--good) 14%, var(--surface)); }
|
|
242
|
+
.spinner { width: 14px; height: 14px; border-radius: 50%; border: 2px solid var(--border); border-top-color: var(--accent); animation: spin 0.9s linear infinite; }
|
|
243
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
244
|
+
@media (prefers-reduced-motion: reduce) { .spinner { animation: none; } }
|