riva 0.1.1__tar.gz → 0.1.2__tar.gz
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.
- {riva-0.1.1/src/riva.egg-info → riva-0.1.2}/PKG-INFO +1 -1
- {riva-0.1.1 → riva-0.1.2}/pyproject.toml +4 -1
- riva-0.1.2/src/riva/web/static/css/style.css +270 -0
- riva-0.1.2/src/riva/web/static/index.html +125 -0
- riva-0.1.2/src/riva/web/static/js/app.js +139 -0
- riva-0.1.2/src/riva/web/static/js/render.js +249 -0
- riva-0.1.2/src/riva/web/static/js/utils.js +65 -0
- {riva-0.1.1 → riva-0.1.2/src/riva.egg-info}/PKG-INFO +1 -1
- {riva-0.1.1 → riva-0.1.2}/src/riva.egg-info/SOURCES.txt +5 -0
- {riva-0.1.1 → riva-0.1.2}/LICENSE +0 -0
- {riva-0.1.1 → riva-0.1.2}/README.md +0 -0
- {riva-0.1.1 → riva-0.1.2}/setup.cfg +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/__init__.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/__main__.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/__init__.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/autogen.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/base.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/claude_code.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/cline.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/codex_cli.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/continue_dev.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/crewai.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/cursor.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/gemini_cli.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/github_copilot.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/langgraph.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/openclaw.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/registry.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/agents/windsurf.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/cli.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/core/__init__.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/core/audit.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/core/env_scanner.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/core/monitor.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/core/network.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/core/scanner.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/core/storage.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/core/usage_stats.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/tui/__init__.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/tui/components.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/tui/dashboard.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/utils/__init__.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/utils/formatting.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/utils/jsonl.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/web/__init__.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/web/daemon.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva/web/server.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva.egg-info/dependency_links.txt +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva.egg-info/entry_points.txt +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva.egg-info/requires.txt +0 -0
- {riva-0.1.1 → riva-0.1.2}/src/riva.egg-info/top_level.txt +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_audit.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_base.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_cli.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_components.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_detectors.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_env_scanner.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_formatting.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_ide_detectors.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_jsonl.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_monitor.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_network.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_registry.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_scanner.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_storage.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_usage_stats.py +0 -0
- {riva-0.1.1 → riva-0.1.2}/tests/test_web.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "riva"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "AI Agent Task Manager - discover and monitor AI coding agents running on your machine"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -52,6 +52,9 @@ riva = "riva.cli:cli"
|
|
|
52
52
|
[tool.setuptools.packages.find]
|
|
53
53
|
where = ["src"]
|
|
54
54
|
|
|
55
|
+
[tool.setuptools.package-data]
|
|
56
|
+
riva = ["web/static/**/*"]
|
|
57
|
+
|
|
55
58
|
[tool.pytest.ini_options]
|
|
56
59
|
testpaths = ["tests"]
|
|
57
60
|
addopts = "-v --tb=short"
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/* RIVA Dark Theme */
|
|
2
|
+
:root {
|
|
3
|
+
--bg: #0d1117;
|
|
4
|
+
--bg-card: #161b22;
|
|
5
|
+
--bg-hover: #1c2129;
|
|
6
|
+
--border: #30363d;
|
|
7
|
+
--text: #c9d1d9;
|
|
8
|
+
--text-dim: #8b949e;
|
|
9
|
+
--accent: #58a6ff;
|
|
10
|
+
--green: #3fb950;
|
|
11
|
+
--yellow: #d29922;
|
|
12
|
+
--red: #f85149;
|
|
13
|
+
--purple: #bc8cff;
|
|
14
|
+
--orange: #d18616;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
18
|
+
|
|
19
|
+
body {
|
|
20
|
+
background: var(--bg);
|
|
21
|
+
color: var(--text);
|
|
22
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji';
|
|
23
|
+
font-size: 14px;
|
|
24
|
+
line-height: 1.5;
|
|
25
|
+
padding: 20px;
|
|
26
|
+
max-width: 1400px;
|
|
27
|
+
margin: 0 auto;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Header */
|
|
31
|
+
header {
|
|
32
|
+
display: flex;
|
|
33
|
+
justify-content: space-between;
|
|
34
|
+
align-items: center;
|
|
35
|
+
padding-bottom: 16px;
|
|
36
|
+
border-bottom: 1px solid var(--border);
|
|
37
|
+
margin-bottom: 0;
|
|
38
|
+
}
|
|
39
|
+
header h1 { font-size: 20px; font-weight: 600; color: var(--accent); }
|
|
40
|
+
header h1 span { color: var(--text-dim); font-weight: 400; font-size: 14px; }
|
|
41
|
+
|
|
42
|
+
.connection-status { display: flex; align-items: center; color: var(--text-dim); font-size: 13px; }
|
|
43
|
+
.status-dot {
|
|
44
|
+
display: inline-block;
|
|
45
|
+
width: 8px; height: 8px;
|
|
46
|
+
border-radius: 50%;
|
|
47
|
+
margin-right: 6px;
|
|
48
|
+
}
|
|
49
|
+
.status-dot.live { background: var(--green); }
|
|
50
|
+
.status-dot.offline { background: var(--red); }
|
|
51
|
+
|
|
52
|
+
/* Tab Navigation */
|
|
53
|
+
.tab-nav {
|
|
54
|
+
display: flex;
|
|
55
|
+
gap: 0;
|
|
56
|
+
border-bottom: 1px solid var(--border);
|
|
57
|
+
margin-bottom: 24px;
|
|
58
|
+
overflow-x: auto;
|
|
59
|
+
}
|
|
60
|
+
.tab-btn {
|
|
61
|
+
padding: 10px 20px;
|
|
62
|
+
background: none;
|
|
63
|
+
border: none;
|
|
64
|
+
border-bottom: 2px solid transparent;
|
|
65
|
+
color: var(--text-dim);
|
|
66
|
+
font-size: 13px;
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
white-space: nowrap;
|
|
70
|
+
transition: color 0.15s, border-color 0.15s;
|
|
71
|
+
}
|
|
72
|
+
.tab-btn:hover { color: var(--text); }
|
|
73
|
+
.tab-btn.active {
|
|
74
|
+
color: var(--accent);
|
|
75
|
+
border-bottom-color: var(--accent);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.tab-panel { display: none; }
|
|
79
|
+
.tab-panel.active { display: block; }
|
|
80
|
+
|
|
81
|
+
/* Sections */
|
|
82
|
+
section { margin-bottom: 32px; }
|
|
83
|
+
section h2 {
|
|
84
|
+
font-size: 16px;
|
|
85
|
+
font-weight: 600;
|
|
86
|
+
color: var(--accent);
|
|
87
|
+
margin-bottom: 12px;
|
|
88
|
+
padding-bottom: 6px;
|
|
89
|
+
border-bottom: 1px solid var(--border);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Tables */
|
|
93
|
+
table {
|
|
94
|
+
width: 100%;
|
|
95
|
+
border-collapse: collapse;
|
|
96
|
+
background: var(--bg-card);
|
|
97
|
+
border: 1px solid var(--border);
|
|
98
|
+
border-radius: 6px;
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
}
|
|
101
|
+
th {
|
|
102
|
+
text-align: left;
|
|
103
|
+
padding: 10px 12px;
|
|
104
|
+
background: var(--bg);
|
|
105
|
+
color: var(--text-dim);
|
|
106
|
+
font-weight: 600;
|
|
107
|
+
font-size: 12px;
|
|
108
|
+
text-transform: uppercase;
|
|
109
|
+
letter-spacing: 0.5px;
|
|
110
|
+
border-bottom: 1px solid var(--border);
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
user-select: none;
|
|
113
|
+
}
|
|
114
|
+
th:hover { color: var(--text); }
|
|
115
|
+
td {
|
|
116
|
+
padding: 8px 12px;
|
|
117
|
+
border-bottom: 1px solid var(--border);
|
|
118
|
+
font-size: 13px;
|
|
119
|
+
}
|
|
120
|
+
tr:last-child td { border-bottom: none; }
|
|
121
|
+
tr:hover td { background: var(--bg-hover); }
|
|
122
|
+
|
|
123
|
+
/* Badges */
|
|
124
|
+
.badge {
|
|
125
|
+
display: inline-block;
|
|
126
|
+
padding: 2px 8px;
|
|
127
|
+
border-radius: 12px;
|
|
128
|
+
font-size: 11px;
|
|
129
|
+
font-weight: 600;
|
|
130
|
+
}
|
|
131
|
+
.badge-running { background: rgba(63,185,80,0.15); color: var(--green); }
|
|
132
|
+
.badge-installed { background: rgba(88,166,255,0.15); color: var(--accent); }
|
|
133
|
+
.badge-not_found { background: rgba(139,148,158,0.15); color: var(--text-dim); }
|
|
134
|
+
|
|
135
|
+
/* Severity badges */
|
|
136
|
+
.severity-critical { background: rgba(248,81,73,0.2); color: var(--red); font-weight: 700; }
|
|
137
|
+
.severity-high { background: rgba(248,81,73,0.15); color: var(--red); }
|
|
138
|
+
.severity-medium { background: rgba(210,153,34,0.15); color: var(--yellow); }
|
|
139
|
+
.severity-low { background: rgba(88,166,255,0.15); color: var(--accent); }
|
|
140
|
+
.severity-info { background: rgba(139,148,158,0.1); color: var(--text-dim); }
|
|
141
|
+
|
|
142
|
+
/* Status colors for network */
|
|
143
|
+
.conn-established { color: var(--green); }
|
|
144
|
+
.conn-close-wait { color: var(--yellow); }
|
|
145
|
+
.conn-time-wait { color: var(--red); }
|
|
146
|
+
|
|
147
|
+
/* Cards */
|
|
148
|
+
.card-grid {
|
|
149
|
+
display: grid;
|
|
150
|
+
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
|
151
|
+
gap: 16px;
|
|
152
|
+
}
|
|
153
|
+
.card {
|
|
154
|
+
background: var(--bg-card);
|
|
155
|
+
border: 1px solid var(--border);
|
|
156
|
+
border-radius: 8px;
|
|
157
|
+
padding: 16px;
|
|
158
|
+
}
|
|
159
|
+
.card h3 {
|
|
160
|
+
font-size: 14px;
|
|
161
|
+
font-weight: 600;
|
|
162
|
+
margin-bottom: 10px;
|
|
163
|
+
display: flex;
|
|
164
|
+
justify-content: space-between;
|
|
165
|
+
align-items: center;
|
|
166
|
+
}
|
|
167
|
+
.card-row {
|
|
168
|
+
display: flex;
|
|
169
|
+
justify-content: space-between;
|
|
170
|
+
padding: 3px 0;
|
|
171
|
+
font-size: 13px;
|
|
172
|
+
}
|
|
173
|
+
.card-row .label { color: var(--text-dim); }
|
|
174
|
+
.card-row .value { color: var(--text); font-family: monospace; }
|
|
175
|
+
|
|
176
|
+
.sparkline-container { margin-top: 10px; }
|
|
177
|
+
.sparkline-label { font-size: 11px; color: var(--text-dim); margin-bottom: 2px; }
|
|
178
|
+
svg.sparkline { display: block; }
|
|
179
|
+
|
|
180
|
+
/* Bar chart */
|
|
181
|
+
.bar-chart { margin-top: 8px; }
|
|
182
|
+
.bar-row {
|
|
183
|
+
display: flex;
|
|
184
|
+
align-items: center;
|
|
185
|
+
margin-bottom: 4px;
|
|
186
|
+
font-size: 12px;
|
|
187
|
+
}
|
|
188
|
+
.bar-label { width: 120px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
189
|
+
.bar-track { flex: 1; height: 16px; background: var(--bg); border-radius: 3px; margin: 0 8px; overflow: hidden; }
|
|
190
|
+
.bar-fill { height: 100%; border-radius: 3px; background: var(--accent); transition: width 0.3s ease; }
|
|
191
|
+
.bar-count { width: 40px; text-align: right; color: var(--text-dim); font-family: monospace; }
|
|
192
|
+
|
|
193
|
+
/* Accordion */
|
|
194
|
+
.accordion { border: 1px solid var(--border); border-radius: 6px; overflow: hidden; background: var(--bg-card); }
|
|
195
|
+
.accordion + .accordion { margin-top: 8px; }
|
|
196
|
+
.accordion-header {
|
|
197
|
+
padding: 10px 14px;
|
|
198
|
+
cursor: pointer;
|
|
199
|
+
display: flex;
|
|
200
|
+
justify-content: space-between;
|
|
201
|
+
align-items: center;
|
|
202
|
+
font-weight: 600;
|
|
203
|
+
font-size: 13px;
|
|
204
|
+
user-select: none;
|
|
205
|
+
}
|
|
206
|
+
.accordion-header:hover { background: var(--bg-hover); }
|
|
207
|
+
.accordion-arrow { transition: transform 0.2s; font-size: 12px; color: var(--text-dim); }
|
|
208
|
+
.accordion.open .accordion-arrow { transform: rotate(90deg); }
|
|
209
|
+
.accordion-body { display: none; padding: 0 14px 14px; }
|
|
210
|
+
.accordion.open .accordion-body { display: block; }
|
|
211
|
+
|
|
212
|
+
/* Buttons */
|
|
213
|
+
.btn {
|
|
214
|
+
display: inline-flex;
|
|
215
|
+
align-items: center;
|
|
216
|
+
gap: 6px;
|
|
217
|
+
padding: 6px 14px;
|
|
218
|
+
border: 1px solid var(--border);
|
|
219
|
+
border-radius: 6px;
|
|
220
|
+
background: var(--bg-card);
|
|
221
|
+
color: var(--text);
|
|
222
|
+
font-size: 13px;
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
transition: background 0.15s, border-color 0.15s;
|
|
225
|
+
}
|
|
226
|
+
.btn:hover { background: var(--bg-hover); border-color: var(--text-dim); }
|
|
227
|
+
.btn-primary { background: rgba(88,166,255,0.15); border-color: var(--accent); color: var(--accent); }
|
|
228
|
+
.btn-primary:hover { background: rgba(88,166,255,0.25); }
|
|
229
|
+
|
|
230
|
+
/* Empty state */
|
|
231
|
+
.empty-msg { color: var(--text-dim); font-style: italic; padding: 20px; text-align: center; }
|
|
232
|
+
|
|
233
|
+
/* Stat summary cards */
|
|
234
|
+
.stat-row {
|
|
235
|
+
display: flex;
|
|
236
|
+
gap: 12px;
|
|
237
|
+
margin-bottom: 16px;
|
|
238
|
+
flex-wrap: wrap;
|
|
239
|
+
}
|
|
240
|
+
.stat-card {
|
|
241
|
+
flex: 1;
|
|
242
|
+
min-width: 150px;
|
|
243
|
+
background: var(--bg-card);
|
|
244
|
+
border: 1px solid var(--border);
|
|
245
|
+
border-radius: 8px;
|
|
246
|
+
padding: 12px 16px;
|
|
247
|
+
text-align: center;
|
|
248
|
+
}
|
|
249
|
+
.stat-card .stat-value { font-size: 24px; font-weight: 700; color: var(--accent); }
|
|
250
|
+
.stat-card .stat-label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.5px; margin-top: 2px; }
|
|
251
|
+
|
|
252
|
+
/* Footer */
|
|
253
|
+
footer {
|
|
254
|
+
margin-top: 32px;
|
|
255
|
+
padding-top: 16px;
|
|
256
|
+
border-top: 1px solid var(--border);
|
|
257
|
+
display: flex;
|
|
258
|
+
justify-content: space-between;
|
|
259
|
+
align-items: center;
|
|
260
|
+
color: var(--text-dim);
|
|
261
|
+
font-size: 12px;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Responsive */
|
|
265
|
+
@media (max-width: 768px) {
|
|
266
|
+
body { padding: 12px; }
|
|
267
|
+
.card-grid { grid-template-columns: 1fr; }
|
|
268
|
+
.stat-row { flex-direction: column; }
|
|
269
|
+
.tab-btn { padding: 8px 12px; font-size: 12px; }
|
|
270
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
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.0">
|
|
6
|
+
<title>RIVA — AI Agent Task Manager</title>
|
|
7
|
+
<link rel="stylesheet" href="/static/css/style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
|
|
11
|
+
<header>
|
|
12
|
+
<h1>RIVA <span>AI Agent Task Manager</span></h1>
|
|
13
|
+
<div class="connection-status" id="conn-header">
|
|
14
|
+
<span class="status-dot live" id="conn-dot"></span>
|
|
15
|
+
<span id="conn-text">Live</span>
|
|
16
|
+
</div>
|
|
17
|
+
</header>
|
|
18
|
+
|
|
19
|
+
<!-- Tab Navigation -->
|
|
20
|
+
<nav class="tab-nav">
|
|
21
|
+
<button class="tab-btn active" data-tab="overview">Overview</button>
|
|
22
|
+
<button class="tab-btn" data-tab="network">Network</button>
|
|
23
|
+
<button class="tab-btn" data-tab="security">Security</button>
|
|
24
|
+
<button class="tab-btn" data-tab="usage">Usage</button>
|
|
25
|
+
<button class="tab-btn" data-tab="config">Config</button>
|
|
26
|
+
</nav>
|
|
27
|
+
|
|
28
|
+
<!-- ============================================================ -->
|
|
29
|
+
<!-- Overview Tab -->
|
|
30
|
+
<!-- ============================================================ -->
|
|
31
|
+
<div class="tab-panel active" id="tab-overview">
|
|
32
|
+
<section id="sec-agents">
|
|
33
|
+
<h2>Agent Overview</h2>
|
|
34
|
+
<div id="agent-table-wrap"><p class="empty-msg">Loading...</p></div>
|
|
35
|
+
</section>
|
|
36
|
+
|
|
37
|
+
<section id="sec-agent-cards">
|
|
38
|
+
<h2>Agent Details</h2>
|
|
39
|
+
<div class="card-grid" id="agent-cards"></div>
|
|
40
|
+
</section>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<!-- ============================================================ -->
|
|
44
|
+
<!-- Network Tab -->
|
|
45
|
+
<!-- ============================================================ -->
|
|
46
|
+
<div class="tab-panel" id="tab-network">
|
|
47
|
+
<section>
|
|
48
|
+
<h2>Network Connections</h2>
|
|
49
|
+
<p style="color:var(--text-dim);font-size:13px;margin-bottom:16px">
|
|
50
|
+
Active TCP connections from running AI agents. Color coding:
|
|
51
|
+
<span class="conn-established">ESTABLISHED</span> /
|
|
52
|
+
<span class="conn-close-wait">CLOSE_WAIT</span> /
|
|
53
|
+
<span class="conn-time-wait">TIME_WAIT</span>
|
|
54
|
+
</p>
|
|
55
|
+
<div id="network-table-wrap"><p class="empty-msg">Loading...</p></div>
|
|
56
|
+
</section>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<!-- ============================================================ -->
|
|
60
|
+
<!-- Security Tab -->
|
|
61
|
+
<!-- ============================================================ -->
|
|
62
|
+
<div class="tab-panel" id="tab-security">
|
|
63
|
+
<section>
|
|
64
|
+
<h2>Security Audit</h2>
|
|
65
|
+
<div style="margin-bottom:16px;display:flex;gap:8px">
|
|
66
|
+
<button class="btn btn-primary" id="btn-run-audit" onclick="runAudit(false)">Run Audit</button>
|
|
67
|
+
<button class="btn" onclick="runAudit(true)">Run Audit (+ Network)</button>
|
|
68
|
+
</div>
|
|
69
|
+
<div id="audit-results-wrap"><p class="empty-msg">Click "Run Audit" to scan for security issues.</p></div>
|
|
70
|
+
</section>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<!-- ============================================================ -->
|
|
74
|
+
<!-- Usage Tab -->
|
|
75
|
+
<!-- ============================================================ -->
|
|
76
|
+
<div class="tab-panel" id="tab-usage">
|
|
77
|
+
<section id="sec-stats">
|
|
78
|
+
<h2>Usage Statistics</h2>
|
|
79
|
+
<div id="stats-table-wrap"><p class="empty-msg">Loading...</p></div>
|
|
80
|
+
</section>
|
|
81
|
+
|
|
82
|
+
<section id="sec-stats-cards">
|
|
83
|
+
<h2>Usage Details</h2>
|
|
84
|
+
<div class="card-grid" id="stats-cards"></div>
|
|
85
|
+
</section>
|
|
86
|
+
|
|
87
|
+
<section>
|
|
88
|
+
<h2>Historical Resource Usage</h2>
|
|
89
|
+
<div id="history-chart-wrap"><p class="empty-msg">Loading...</p></div>
|
|
90
|
+
</section>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<!-- ============================================================ -->
|
|
94
|
+
<!-- Config Tab -->
|
|
95
|
+
<!-- ============================================================ -->
|
|
96
|
+
<div class="tab-panel" id="tab-config">
|
|
97
|
+
<section id="sec-registry">
|
|
98
|
+
<h2>Agent Registry</h2>
|
|
99
|
+
<div id="registry-table-wrap"><p class="empty-msg">Loading...</p></div>
|
|
100
|
+
</section>
|
|
101
|
+
|
|
102
|
+
<section id="sec-env">
|
|
103
|
+
<h2>Environment Variables</h2>
|
|
104
|
+
<div id="env-table-wrap"><p class="empty-msg">Loading...</p></div>
|
|
105
|
+
</section>
|
|
106
|
+
|
|
107
|
+
<section id="sec-config">
|
|
108
|
+
<h2>Agent Configurations</h2>
|
|
109
|
+
<div id="config-wrap"><p class="empty-msg">Loading...</p></div>
|
|
110
|
+
</section>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<footer>
|
|
114
|
+
<div class="connection-status">
|
|
115
|
+
<span class="status-dot live" id="conn-dot-footer"></span>
|
|
116
|
+
<span id="conn-text-footer">Connected</span>
|
|
117
|
+
</div>
|
|
118
|
+
<div id="last-updated">Last updated: —</div>
|
|
119
|
+
</footer>
|
|
120
|
+
|
|
121
|
+
<script src="/static/js/utils.js"></script>
|
|
122
|
+
<script src="/static/js/render.js"></script>
|
|
123
|
+
<script src="/static/js/app.js"></script>
|
|
124
|
+
</body>
|
|
125
|
+
</html>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/* RIVA Dashboard — Tab Router & Polling Orchestration */
|
|
2
|
+
|
|
3
|
+
(function() {
|
|
4
|
+
var connected = true;
|
|
5
|
+
var lastHistories = {};
|
|
6
|
+
var currentTab = 'overview';
|
|
7
|
+
|
|
8
|
+
/* --- Tab Router --- */
|
|
9
|
+
function initTabs() {
|
|
10
|
+
var buttons = document.querySelectorAll('.tab-btn');
|
|
11
|
+
buttons.forEach(function(btn) {
|
|
12
|
+
btn.addEventListener('click', function() {
|
|
13
|
+
var target = this.dataset.tab;
|
|
14
|
+
switchTab(target);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function switchTab(tabName) {
|
|
20
|
+
currentTab = tabName;
|
|
21
|
+
document.querySelectorAll('.tab-btn').forEach(function(btn) {
|
|
22
|
+
btn.classList.toggle('active', btn.dataset.tab === tabName);
|
|
23
|
+
});
|
|
24
|
+
document.querySelectorAll('.tab-panel').forEach(function(panel) {
|
|
25
|
+
panel.classList.toggle('active', panel.id === 'tab-' + tabName);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* --- Connection Status --- */
|
|
30
|
+
function setConnection(ok) {
|
|
31
|
+
connected = ok;
|
|
32
|
+
var cls = ok ? 'live' : 'offline';
|
|
33
|
+
var txt = ok ? 'Live' : 'Offline';
|
|
34
|
+
document.getElementById('conn-dot').className = 'status-dot ' + cls;
|
|
35
|
+
document.getElementById('conn-text').textContent = txt;
|
|
36
|
+
document.getElementById('conn-dot-footer').className = 'status-dot ' + cls;
|
|
37
|
+
document.getElementById('conn-text-footer').textContent = ok ? 'Connected' : 'Disconnected';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function updateTimestamp() {
|
|
41
|
+
document.getElementById('last-updated').textContent = 'Last updated: ' + new Date().toLocaleTimeString();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* --- Polling --- */
|
|
45
|
+
async function pollFast() {
|
|
46
|
+
try {
|
|
47
|
+
var fetches = [
|
|
48
|
+
fetch('/api/agents'),
|
|
49
|
+
fetch('/api/agents/history')
|
|
50
|
+
];
|
|
51
|
+
// Also fetch network if on network tab
|
|
52
|
+
if (currentTab === 'network') {
|
|
53
|
+
fetches.push(fetch('/api/network'));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
var responses = await Promise.all(fetches);
|
|
57
|
+
if (!responses[0].ok || !responses[1].ok) throw new Error('fetch failed');
|
|
58
|
+
|
|
59
|
+
var agentsData = await responses[0].json();
|
|
60
|
+
var histData = await responses[1].json();
|
|
61
|
+
lastHistories = histData.histories || {};
|
|
62
|
+
|
|
63
|
+
renderAgentTable(agentsData.agents || []);
|
|
64
|
+
renderAgentCards(agentsData.agents || [], lastHistories);
|
|
65
|
+
|
|
66
|
+
if (currentTab === 'network' && responses[2] && responses[2].ok) {
|
|
67
|
+
var netData = await responses[2].json();
|
|
68
|
+
renderNetworkTable(netData.network || []);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setConnection(true);
|
|
72
|
+
updateTimestamp();
|
|
73
|
+
} catch (e) {
|
|
74
|
+
setConnection(false);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function pollSlow() {
|
|
79
|
+
try {
|
|
80
|
+
var fetches = [
|
|
81
|
+
fetch('/api/stats'),
|
|
82
|
+
fetch('/api/env'),
|
|
83
|
+
fetch('/api/registry'),
|
|
84
|
+
fetch('/api/config')
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
var responses = await Promise.all(fetches);
|
|
88
|
+
|
|
89
|
+
if (responses[0].ok) { var d = await responses[0].json(); renderStatsTable(d.stats || []); renderStatsCards(d.stats || []); }
|
|
90
|
+
if (responses[1].ok) { var d = await responses[1].json(); renderEnvTable(d.env_vars || []); }
|
|
91
|
+
if (responses[2].ok) { var d = await responses[2].json(); renderRegistryTable(d.agents || []); }
|
|
92
|
+
if (responses[3].ok) { var d = await responses[3].json(); renderConfigs(d.configs || []); }
|
|
93
|
+
|
|
94
|
+
// Fetch historical data for usage tab
|
|
95
|
+
if (currentTab === 'usage') {
|
|
96
|
+
try {
|
|
97
|
+
var histRes = await fetch('/api/history?hours=1');
|
|
98
|
+
if (histRes.ok) {
|
|
99
|
+
var histData = await histRes.json();
|
|
100
|
+
renderHistoricalChart(histData.snapshots || []);
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
setConnection(true);
|
|
106
|
+
updateTimestamp();
|
|
107
|
+
} catch (e) {
|
|
108
|
+
setConnection(false);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* --- Audit Button --- */
|
|
113
|
+
window.runAudit = async function(includeNetwork) {
|
|
114
|
+
var url = '/api/audit';
|
|
115
|
+
if (includeNetwork) url += '?network=true';
|
|
116
|
+
try {
|
|
117
|
+
var btn = document.getElementById('btn-run-audit');
|
|
118
|
+
if (btn) btn.disabled = true;
|
|
119
|
+
var res = await fetch(url);
|
|
120
|
+
if (res.ok) {
|
|
121
|
+
var data = await res.json();
|
|
122
|
+
renderAuditDashboard(data.audit || []);
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {
|
|
125
|
+
// ignore
|
|
126
|
+
} finally {
|
|
127
|
+
var btn = document.getElementById('btn-run-audit');
|
|
128
|
+
if (btn) btn.disabled = false;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/* --- Init --- */
|
|
133
|
+
initTabs();
|
|
134
|
+
pollFast();
|
|
135
|
+
pollSlow();
|
|
136
|
+
|
|
137
|
+
setInterval(pollFast, 2000);
|
|
138
|
+
setInterval(pollSlow, 30000);
|
|
139
|
+
})();
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/* RIVA UI Render Functions */
|
|
2
|
+
|
|
3
|
+
function renderToolBars(tools, maxCount) {
|
|
4
|
+
if (!tools || !tools.length) return '<p class="empty-msg">No tool data</p>';
|
|
5
|
+
var mc = maxCount || Math.max.apply(null, tools.map(function(t) { return t.call_count; }).concat([1]));
|
|
6
|
+
return '<div class="bar-chart">' + tools.slice(0, 10).map(function(t) {
|
|
7
|
+
var pct = (t.call_count / mc * 100).toFixed(1);
|
|
8
|
+
return '<div class="bar-row">' +
|
|
9
|
+
'<span class="bar-label" title="' + esc(t.tool_name) + '">' + esc(t.tool_name) + '</span>' +
|
|
10
|
+
'<div class="bar-track"><div class="bar-fill" style="width:' + pct + '%"></div></div>' +
|
|
11
|
+
'<span class="bar-count">' + t.call_count + '</span></div>';
|
|
12
|
+
}).join('') + '</div>';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function renderDailyChart(daily, width, height) {
|
|
16
|
+
if (!daily || daily.length < 2) return '';
|
|
17
|
+
var maxT = Math.max.apply(null, daily.map(function(d) { return d.total_tokens; }).concat([1]));
|
|
18
|
+
var step = width / (daily.length - 1);
|
|
19
|
+
var points = daily.map(function(d, i) {
|
|
20
|
+
var x = (i * step).toFixed(1);
|
|
21
|
+
var y = (height - (d.total_tokens / maxT) * (height - 4) - 2).toFixed(1);
|
|
22
|
+
return x + ',' + y;
|
|
23
|
+
}).join(' ');
|
|
24
|
+
return '<div class="sparkline-container">' +
|
|
25
|
+
'<div class="sparkline-label">Daily Tokens</div>' +
|
|
26
|
+
'<svg class="sparkline" width="' + width + '" height="' + height + '" viewBox="0 0 ' + width + ' ' + height + '">' +
|
|
27
|
+
'<polyline fill="none" stroke="' + getColor('purple') + '" stroke-width="1.5" points="' + points + '"/>' +
|
|
28
|
+
'</svg></div>';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* --- Overview Tab --- */
|
|
32
|
+
function renderAgentTable(agents) {
|
|
33
|
+
var wrap = document.getElementById('agent-table-wrap');
|
|
34
|
+
if (!agents.length) { wrap.innerHTML = '<p class="empty-msg">No agents detected</p>'; return; }
|
|
35
|
+
var h = '<table id="tbl-agents"><thead><tr><th>Agent</th><th>Status</th><th>PID</th><th>CPU %</th><th>Memory</th><th>Uptime</th><th>Working Dir</th></tr></thead><tbody>';
|
|
36
|
+
agents.forEach(function(a) {
|
|
37
|
+
h += '<tr><td>' + esc(a.name) + '</td><td>' + statusBadge(a.status) + '</td><td>' + esc(a.pid) + '</td>' +
|
|
38
|
+
'<td>' + a.cpu_percent.toFixed(1) + '</td><td>' + esc(a.memory_formatted) + '</td>' +
|
|
39
|
+
'<td>' + esc(a.uptime_formatted) + '</td><td style="max-width:250px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + esc(a.working_directory) + '">' + esc(a.working_directory) + '</td></tr>';
|
|
40
|
+
});
|
|
41
|
+
h += '</tbody></table>';
|
|
42
|
+
wrap.innerHTML = h;
|
|
43
|
+
makeSortable(document.getElementById('tbl-agents'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function renderAgentCards(agents, histories) {
|
|
47
|
+
var el = document.getElementById('agent-cards');
|
|
48
|
+
var running = agents.filter(function(a) { return a.status === 'running'; });
|
|
49
|
+
if (!running.length) { el.innerHTML = '<p class="empty-msg">No running agents</p>'; return; }
|
|
50
|
+
el.innerHTML = running.map(function(a) {
|
|
51
|
+
var key = a.name + ':' + a.pid;
|
|
52
|
+
var hist = (histories || {})[key];
|
|
53
|
+
var sparkHtml = '';
|
|
54
|
+
if (hist) {
|
|
55
|
+
sparkHtml =
|
|
56
|
+
'<div class="sparkline-container"><div class="sparkline-label">CPU %</div>' + renderSparklineSVG(hist.cpu_history, getColor('green'), 340, 40) + '</div>' +
|
|
57
|
+
'<div class="sparkline-container"><div class="sparkline-label">Memory MB</div>' + renderSparklineSVG(hist.memory_history, getColor('accent'), 340, 40) + '</div>';
|
|
58
|
+
}
|
|
59
|
+
return '<div class="card"><h3>' + esc(a.name) + ' ' + statusBadge(a.status) + '</h3>' +
|
|
60
|
+
'<div class="card-row"><span class="label">PID</span><span class="value">' + esc(a.pid) + '</span></div>' +
|
|
61
|
+
'<div class="card-row"><span class="label">Binary</span><span class="value">' + esc(a.binary_path) + '</span></div>' +
|
|
62
|
+
'<div class="card-row"><span class="label">API</span><span class="value">' + esc(a.api_domain) + '</span></div>' +
|
|
63
|
+
'<div class="card-row"><span class="label">CPU</span><span class="value">' + a.cpu_percent.toFixed(1) + '%</span></div>' +
|
|
64
|
+
'<div class="card-row"><span class="label">Memory</span><span class="value">' + esc(a.memory_formatted) + '</span></div>' +
|
|
65
|
+
'<div class="card-row"><span class="label">Uptime</span><span class="value">' + esc(a.uptime_formatted) + '</span></div>' +
|
|
66
|
+
sparkHtml + '</div>';
|
|
67
|
+
}).join('');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* --- Network Tab --- */
|
|
71
|
+
function renderNetworkTable(networkData) {
|
|
72
|
+
var wrap = document.getElementById('network-table-wrap');
|
|
73
|
+
if (!networkData || !networkData.length) {
|
|
74
|
+
wrap.innerHTML = '<p class="empty-msg">No running agents with network connections</p>';
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
var h = '';
|
|
79
|
+
networkData.forEach(function(snap) {
|
|
80
|
+
h += '<h3 style="margin:16px 0 8px;font-size:14px;color:var(--accent)">' + esc(snap.agent) + ' (PID ' + snap.pid + ') \u2014 ' + snap.connection_count + ' connections</h3>';
|
|
81
|
+
if (!snap.connections.length) {
|
|
82
|
+
h += '<p class="empty-msg">No connections</p>';
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
h += '<table><thead><tr><th>Local</th><th>Remote</th><th>Status</th><th>Hostname</th><th>Service</th><th>TLS</th></tr></thead><tbody>';
|
|
86
|
+
snap.connections.forEach(function(c) {
|
|
87
|
+
var statusCls = c.status === 'ESTABLISHED' ? 'conn-established' : c.status === 'CLOSE_WAIT' ? 'conn-close-wait' : c.status === 'TIME_WAIT' ? 'conn-time-wait' : '';
|
|
88
|
+
var tls = c.is_tls ? '<span style="color:var(--green)">Yes</span>' : '<span style="color:var(--text-dim)">No</span>';
|
|
89
|
+
h += '<tr><td>' + esc(c.local_addr + ':' + c.local_port) + '</td>' +
|
|
90
|
+
'<td>' + esc(c.remote_addr + ':' + c.remote_port) + '</td>' +
|
|
91
|
+
'<td class="' + statusCls + '">' + esc(c.status) + '</td>' +
|
|
92
|
+
'<td>' + esc(c.hostname) + '</td>' +
|
|
93
|
+
'<td>' + esc(c.known_service) + '</td>' +
|
|
94
|
+
'<td>' + tls + '</td></tr>';
|
|
95
|
+
});
|
|
96
|
+
h += '</tbody></table>';
|
|
97
|
+
});
|
|
98
|
+
wrap.innerHTML = h;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* --- Security Tab --- */
|
|
102
|
+
function renderAuditDashboard(auditData) {
|
|
103
|
+
var wrap = document.getElementById('audit-results-wrap');
|
|
104
|
+
if (!auditData || !auditData.length) {
|
|
105
|
+
wrap.innerHTML = '<p class="empty-msg">No audit results yet. Click "Run Audit" above.</p>';
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Summary cards
|
|
110
|
+
var pass_count = auditData.filter(function(r) { return r.status === 'pass'; }).length;
|
|
111
|
+
var warn_count = auditData.filter(function(r) { return r.status === 'warn'; }).length;
|
|
112
|
+
var fail_count = auditData.filter(function(r) { return r.status === 'fail'; }).length;
|
|
113
|
+
|
|
114
|
+
var h = '<div class="stat-row">' +
|
|
115
|
+
'<div class="stat-card"><div class="stat-value" style="color:var(--green)">' + pass_count + '</div><div class="stat-label">Passed</div></div>' +
|
|
116
|
+
'<div class="stat-card"><div class="stat-value" style="color:var(--yellow)">' + warn_count + '</div><div class="stat-label">Warnings</div></div>' +
|
|
117
|
+
'<div class="stat-card"><div class="stat-value" style="color:var(--red)">' + fail_count + '</div><div class="stat-label">Failed</div></div>' +
|
|
118
|
+
'</div>';
|
|
119
|
+
|
|
120
|
+
h += '<table id="tbl-audit"><thead><tr><th>Check</th><th>Status</th><th>Severity</th><th>Category</th><th>Detail</th></tr></thead><tbody>';
|
|
121
|
+
auditData.forEach(function(r) {
|
|
122
|
+
var statusCls = r.status === 'pass' ? 'badge-running' : r.status === 'warn' ? 'badge-installed' : 'severity-high';
|
|
123
|
+
h += '<tr><td>' + esc(r.check) + '</td>' +
|
|
124
|
+
'<td><span class="badge ' + statusCls + '">' + esc(r.status) + '</span></td>' +
|
|
125
|
+
'<td>' + severityBadge(r.severity) + '</td>' +
|
|
126
|
+
'<td>' + esc(r.category) + '</td>' +
|
|
127
|
+
'<td>' + esc(r.detail) + '</td></tr>';
|
|
128
|
+
});
|
|
129
|
+
h += '</tbody></table>';
|
|
130
|
+
wrap.innerHTML = h;
|
|
131
|
+
makeSortable(document.getElementById('tbl-audit'));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* --- Usage Tab --- */
|
|
135
|
+
function renderStatsTable(stats) {
|
|
136
|
+
var wrap = document.getElementById('stats-table-wrap');
|
|
137
|
+
if (!stats.length) { wrap.innerHTML = '<p class="empty-msg">No usage data</p>'; return; }
|
|
138
|
+
var h = '<table id="tbl-stats"><thead><tr><th>Agent</th><th>Status</th><th>Total Tokens</th><th>Sessions</th><th>Messages</th><th>Tool Calls</th><th>Period</th></tr></thead><tbody>';
|
|
139
|
+
stats.forEach(function(s) {
|
|
140
|
+
var period = (s.time_range_start && s.time_range_end) ? esc(s.time_range_start) + ' \u2014 ' + esc(s.time_range_end) : '\u2014';
|
|
141
|
+
h += '<tr><td>' + esc(s.name) + '</td><td>' + statusBadge(s.status) + '</td>' +
|
|
142
|
+
'<td>' + esc(s.total_tokens_formatted) + '</td><td>' + s.total_sessions + '</td>' +
|
|
143
|
+
'<td>' + s.total_messages + '</td><td>' + s.total_tool_calls + '</td><td>' + period + '</td></tr>';
|
|
144
|
+
});
|
|
145
|
+
h += '</tbody></table>';
|
|
146
|
+
wrap.innerHTML = h;
|
|
147
|
+
makeSortable(document.getElementById('tbl-stats'));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function renderStatsCards(stats) {
|
|
151
|
+
var el = document.getElementById('stats-cards');
|
|
152
|
+
var withData = stats.filter(function(s) { return s.total_tokens > 0; });
|
|
153
|
+
if (!withData.length) { el.innerHTML = '<p class="empty-msg">No detailed usage data</p>'; return; }
|
|
154
|
+
el.innerHTML = withData.map(function(s) {
|
|
155
|
+
var modelsHtml = '';
|
|
156
|
+
var modelKeys = Object.keys(s.models || {});
|
|
157
|
+
if (modelKeys.length) {
|
|
158
|
+
modelsHtml = '<div style="margin-top:10px"><div class="sparkline-label">Model Token Breakdown</div><table style="font-size:12px"><thead><tr><th>Model</th><th>Input</th><th>Output</th><th>Cache Read</th><th>Cache Create</th><th>Total</th></tr></thead><tbody>';
|
|
159
|
+
modelKeys.forEach(function(mid) {
|
|
160
|
+
var m = s.models[mid];
|
|
161
|
+
modelsHtml += '<tr><td>' + esc(mid) + '</td><td>' + m.input_tokens + '</td><td>' + m.output_tokens + '</td><td>' + m.cache_read_input_tokens + '</td><td>' + m.cache_creation_input_tokens + '</td><td>' + m.total_tokens + '</td></tr>';
|
|
162
|
+
});
|
|
163
|
+
modelsHtml += '</tbody></table></div>';
|
|
164
|
+
}
|
|
165
|
+
var toolMax = s.top_tools.length ? Math.max.apply(null, s.top_tools.map(function(t) { return t.call_count; })) : 1;
|
|
166
|
+
return '<div class="card"><h3>' + esc(s.name) + '</h3>' +
|
|
167
|
+
'<div class="card-row"><span class="label">Tokens</span><span class="value">' + esc(s.total_tokens_formatted) + '</span></div>' +
|
|
168
|
+
'<div class="card-row"><span class="label">Sessions</span><span class="value">' + s.total_sessions + '</span></div>' +
|
|
169
|
+
'<div class="card-row"><span class="label">Messages</span><span class="value">' + s.total_messages + '</span></div>' +
|
|
170
|
+
modelsHtml +
|
|
171
|
+
'<div style="margin-top:10px"><div class="sparkline-label">Top Tools</div>' + renderToolBars(s.top_tools, toolMax) + '</div>' +
|
|
172
|
+
renderDailyChart(s.daily_activity, 340, 50) +
|
|
173
|
+
'</div>';
|
|
174
|
+
}).join('');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function renderHistoricalChart(snapshots) {
|
|
178
|
+
var wrap = document.getElementById('history-chart-wrap');
|
|
179
|
+
if (!snapshots || !snapshots.length) {
|
|
180
|
+
wrap.innerHTML = '<p class="empty-msg">No historical data. Start the web server to begin recording.</p>';
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Group by agent
|
|
185
|
+
var agents = {};
|
|
186
|
+
snapshots.forEach(function(s) {
|
|
187
|
+
var name = s.agent_name || 'Unknown';
|
|
188
|
+
if (!agents[name]) agents[name] = [];
|
|
189
|
+
agents[name].push(s);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
var h = '';
|
|
193
|
+
Object.keys(agents).forEach(function(name) {
|
|
194
|
+
var data = agents[name].reverse();
|
|
195
|
+
var cpuVals = data.map(function(s) { return s.cpu_percent || 0; });
|
|
196
|
+
var memVals = data.map(function(s) { return s.memory_mb || 0; });
|
|
197
|
+
|
|
198
|
+
h += '<div class="card" style="margin-bottom:16px"><h3>' + esc(name) + '</h3>';
|
|
199
|
+
h += '<div class="sparkline-container"><div class="sparkline-label">CPU % (historical)</div>' +
|
|
200
|
+
renderSparklineSVG(cpuVals, getColor('green'), 600, 50) + '</div>';
|
|
201
|
+
h += '<div class="sparkline-container"><div class="sparkline-label">Memory MB (historical)</div>' +
|
|
202
|
+
renderSparklineSVG(memVals, getColor('accent'), 600, 50) + '</div>';
|
|
203
|
+
h += '</div>';
|
|
204
|
+
});
|
|
205
|
+
wrap.innerHTML = h;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* --- Config Tab --- */
|
|
209
|
+
function renderEnvTable(envVars) {
|
|
210
|
+
var wrap = document.getElementById('env-table-wrap');
|
|
211
|
+
if (!envVars.length) { wrap.innerHTML = '<p class="empty-msg">No AI environment variables found</p>'; return; }
|
|
212
|
+
var h = '<table><thead><tr><th>Variable</th><th>Value</th><th>Length</th></tr></thead><tbody>';
|
|
213
|
+
envVars.forEach(function(e) {
|
|
214
|
+
h += '<tr><td>' + esc(e.name) + '</td><td style="font-family:monospace">' + esc(e.value) + '</td><td>' + esc(e.raw_length) + '</td></tr>';
|
|
215
|
+
});
|
|
216
|
+
h += '</tbody></table>';
|
|
217
|
+
wrap.innerHTML = h;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function renderRegistryTable(agents) {
|
|
221
|
+
var wrap = document.getElementById('registry-table-wrap');
|
|
222
|
+
if (!agents.length) { wrap.innerHTML = '<p class="empty-msg">No agents in registry</p>'; return; }
|
|
223
|
+
var h = '<table><thead><tr><th>Agent</th><th>Binaries</th><th>Config Dir</th><th>API Domain</th><th>Installed</th></tr></thead><tbody>';
|
|
224
|
+
agents.forEach(function(a) {
|
|
225
|
+
var inst = a.installed ? '<span class="badge badge-running">yes</span>' : '<span class="badge badge-not_found">no</span>';
|
|
226
|
+
h += '<tr><td>' + esc(a.name) + '</td><td>' + esc((a.binaries||[]).join(', ')) + '</td><td>' + esc(a.config_dir) + '</td><td>' + esc(a.api_domain) + '</td><td>' + inst + '</td></tr>';
|
|
227
|
+
});
|
|
228
|
+
h += '</tbody></table>';
|
|
229
|
+
wrap.innerHTML = h;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function renderConfigs(configs) {
|
|
233
|
+
var wrap = document.getElementById('config-wrap');
|
|
234
|
+
if (!configs.length) { wrap.innerHTML = '<p class="empty-msg">No installed agent configurations</p>'; return; }
|
|
235
|
+
wrap.innerHTML = configs.map(function(c, i) {
|
|
236
|
+
var keys = Object.keys(c.config || {}).sort();
|
|
237
|
+
var tbody = '';
|
|
238
|
+
keys.forEach(function(k) {
|
|
239
|
+
var v = c.config[k];
|
|
240
|
+
if (typeof v === 'object') v = JSON.stringify(v, null, 2);
|
|
241
|
+
if (String(v).length > 200) v = String(v).substring(0, 200) + '...';
|
|
242
|
+
tbody += '<tr><td style="font-weight:600;white-space:nowrap">' + esc(k) + '</td><td style="font-family:monospace;white-space:pre-wrap;word-break:break-all">' + esc(v) + '</td></tr>';
|
|
243
|
+
});
|
|
244
|
+
return '<div class="accordion" id="acc-' + i + '">' +
|
|
245
|
+
'<div class="accordion-header" onclick="this.parentElement.classList.toggle(\'open\')">' +
|
|
246
|
+
'<span>' + esc(c.name) + '</span><span class="accordion-arrow">▶</span></div>' +
|
|
247
|
+
'<div class="accordion-body"><table><thead><tr><th>Key</th><th>Value</th></tr></thead><tbody>' + tbody + '</tbody></table></div></div>';
|
|
248
|
+
}).join('');
|
|
249
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/* RIVA UI Utility Functions */
|
|
2
|
+
|
|
3
|
+
function esc(s) {
|
|
4
|
+
if (s == null) return '\u2014';
|
|
5
|
+
const d = document.createElement('div');
|
|
6
|
+
d.textContent = String(s);
|
|
7
|
+
return d.innerHTML;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function statusBadge(status) {
|
|
11
|
+
const cls = 'badge badge-' + status;
|
|
12
|
+
const label = status.replace('_', ' ');
|
|
13
|
+
return '<span class="' + cls + '">' + label + '</span>';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function severityBadge(severity) {
|
|
17
|
+
const cls = 'badge severity-' + severity;
|
|
18
|
+
return '<span class="' + cls + '">' + esc(severity) + '</span>';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function renderSparklineSVG(values, color, width, height) {
|
|
22
|
+
if (!values || values.length < 2) return '';
|
|
23
|
+
const max = Math.max(...values, 0.1);
|
|
24
|
+
const step = width / (values.length - 1);
|
|
25
|
+
const points = values.map(function(v, i) {
|
|
26
|
+
const x = (i * step).toFixed(1);
|
|
27
|
+
const y = (height - (v / max) * (height - 4) - 2).toFixed(1);
|
|
28
|
+
return x + ',' + y;
|
|
29
|
+
}).join(' ');
|
|
30
|
+
return '<svg class="sparkline" width="' + width + '" height="' + height + '" viewBox="0 0 ' + width + ' ' + height + '">' +
|
|
31
|
+
'<polyline fill="none" stroke="' + color + '" stroke-width="1.5" points="' + points + '"/>' +
|
|
32
|
+
'</svg>';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getColor(name) {
|
|
36
|
+
return getComputedStyle(document.documentElement).getPropertyValue('--' + name).trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Table sorting */
|
|
40
|
+
function makeSortable(table) {
|
|
41
|
+
if (!table) return;
|
|
42
|
+
var headers = table.querySelectorAll('th');
|
|
43
|
+
headers.forEach(function(th, colIdx) {
|
|
44
|
+
th.addEventListener('click', function() {
|
|
45
|
+
var tbody = table.querySelector('tbody');
|
|
46
|
+
if (!tbody) return;
|
|
47
|
+
var rows = Array.from(tbody.querySelectorAll('tr'));
|
|
48
|
+
var asc = th.dataset.sortDir !== 'asc';
|
|
49
|
+
th.dataset.sortDir = asc ? 'asc' : 'desc';
|
|
50
|
+
|
|
51
|
+
rows.sort(function(a, b) {
|
|
52
|
+
var aVal = a.cells[colIdx] ? a.cells[colIdx].textContent.trim() : '';
|
|
53
|
+
var bVal = b.cells[colIdx] ? b.cells[colIdx].textContent.trim() : '';
|
|
54
|
+
var aNum = parseFloat(aVal);
|
|
55
|
+
var bNum = parseFloat(bVal);
|
|
56
|
+
if (!isNaN(aNum) && !isNaN(bNum)) {
|
|
57
|
+
return asc ? aNum - bNum : bNum - aNum;
|
|
58
|
+
}
|
|
59
|
+
return asc ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
rows.forEach(function(row) { tbody.appendChild(row); });
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -42,6 +42,11 @@ src/riva/utils/jsonl.py
|
|
|
42
42
|
src/riva/web/__init__.py
|
|
43
43
|
src/riva/web/daemon.py
|
|
44
44
|
src/riva/web/server.py
|
|
45
|
+
src/riva/web/static/index.html
|
|
46
|
+
src/riva/web/static/css/style.css
|
|
47
|
+
src/riva/web/static/js/app.js
|
|
48
|
+
src/riva/web/static/js/render.js
|
|
49
|
+
src/riva/web/static/js/utils.js
|
|
45
50
|
tests/test_audit.py
|
|
46
51
|
tests/test_base.py
|
|
47
52
|
tests/test_cli.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|