neoagent 2.4.1-beta.44 → 2.4.1-beta.45
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/docs/configuration.md +6 -0
- package/flutter_app/lib/main_app_shell.dart +0 -2
- package/flutter_app/lib/main_chat.dart +1 -0
- package/flutter_app/lib/main_controller.dart +14 -2
- package/flutter_app/lib/main_models.dart +36 -0
- package/flutter_app/lib/main_navigation.dart +1 -9
- package/flutter_app/lib/main_settings.dart +4 -265
- package/flutter_app/lib/main_shared.dart +138 -3
- package/flutter_app/lib/main_unified.dart +5 -100
- package/lib/manager.js +32 -0
- package/package.json +1 -1
- package/runtime/paths.js +3 -1
- package/server/admin/admin.css +268 -0
- package/server/admin/admin.js +348 -0
- package/server/admin/index.html +532 -0
- package/server/admin/login.html +164 -0
- package/server/admin/logo.svg +43 -0
- package/server/http/static.js +4 -1
- package/server/index.js +1 -1
- package/server/middleware/adminAuth.js +11 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +70538 -71095
- package/server/routes/admin.js +321 -0
- package/server/routes/settings.js +10 -2
- package/server/services/widgets/service.js +17 -1
- package/server/utils/logger.js +44 -6
|
@@ -0,0 +1,532 @@
|
|
|
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>NeoAgent Admin</title>
|
|
7
|
+
<link rel="stylesheet" href="/admin/admin.css">
|
|
8
|
+
<style>
|
|
9
|
+
/* ── Layout ────────────────────────────────────────────────────── */
|
|
10
|
+
body {
|
|
11
|
+
display: flex;
|
|
12
|
+
min-height: 100vh;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.sidebar {
|
|
16
|
+
width: var(--sidebar-w);
|
|
17
|
+
background: var(--bg-sidebar);
|
|
18
|
+
border-right: 1px solid var(--border);
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
position: fixed;
|
|
22
|
+
inset: 0 auto 0 0;
|
|
23
|
+
z-index: 10;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.sidebar-header {
|
|
27
|
+
padding: 20px 18px 16px;
|
|
28
|
+
border-bottom: 1px solid var(--border);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
nav {
|
|
32
|
+
flex: 1;
|
|
33
|
+
padding: 10px 10px;
|
|
34
|
+
overflow-y: auto;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.nav-section-label {
|
|
38
|
+
font-size: 10px;
|
|
39
|
+
font-weight: 600;
|
|
40
|
+
text-transform: uppercase;
|
|
41
|
+
letter-spacing: 0.10em;
|
|
42
|
+
color: var(--text-muted);
|
|
43
|
+
padding: 10px 10px 4px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.nav-item {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: 9px;
|
|
50
|
+
padding: 8px 10px;
|
|
51
|
+
border-radius: var(--radius-sm);
|
|
52
|
+
color: var(--text-secondary);
|
|
53
|
+
font-size: 13px;
|
|
54
|
+
font-weight: 500;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
transition: background 0.12s, color 0.12s;
|
|
57
|
+
border: none;
|
|
58
|
+
background: none;
|
|
59
|
+
width: 100%;
|
|
60
|
+
text-align: left;
|
|
61
|
+
text-decoration: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.nav-item:hover { background: rgba(224,240,224,0.05); color: var(--text); }
|
|
65
|
+
|
|
66
|
+
.nav-item.active {
|
|
67
|
+
background: rgba(225, 176, 82, 0.12);
|
|
68
|
+
color: var(--accent);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.nav-item svg {
|
|
72
|
+
width: 15px;
|
|
73
|
+
height: 15px;
|
|
74
|
+
flex-shrink: 0;
|
|
75
|
+
opacity: 0.75;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.nav-item.active svg { opacity: 1; }
|
|
79
|
+
|
|
80
|
+
.sidebar-footer {
|
|
81
|
+
padding: 10px;
|
|
82
|
+
border-top: 1px solid var(--border);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.main {
|
|
86
|
+
margin-left: var(--sidebar-w);
|
|
87
|
+
flex: 1;
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
min-height: 100vh;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* ── Page header ───────────────────────────────────────────────── */
|
|
94
|
+
.page-header {
|
|
95
|
+
padding: 28px 28px 0;
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: flex-start;
|
|
98
|
+
justify-content: space-between;
|
|
99
|
+
gap: 16px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.page-title {
|
|
103
|
+
font-size: 20px;
|
|
104
|
+
font-weight: 700;
|
|
105
|
+
letter-spacing: -0.3px;
|
|
106
|
+
color: var(--text);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.page-subtitle {
|
|
110
|
+
color: var(--text-muted);
|
|
111
|
+
font-size: 13px;
|
|
112
|
+
margin-top: 3px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.refresh-hint {
|
|
116
|
+
font-size: 12px;
|
|
117
|
+
color: var(--text-muted);
|
|
118
|
+
white-space: nowrap;
|
|
119
|
+
padding-top: 6px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.content {
|
|
123
|
+
padding: 20px 28px 48px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* ── Pages ─────────────────────────────────────────────────────── */
|
|
127
|
+
.page { display: none; }
|
|
128
|
+
.page.active { display: block; }
|
|
129
|
+
|
|
130
|
+
/* ── Status grid ───────────────────────────────────────────────── */
|
|
131
|
+
.status-grid {
|
|
132
|
+
display: grid;
|
|
133
|
+
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
134
|
+
gap: 10px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.status-tile {
|
|
138
|
+
background: var(--bg-secondary);
|
|
139
|
+
border: 1px solid var(--border);
|
|
140
|
+
border-radius: 8px;
|
|
141
|
+
padding: 14px 16px;
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: flex-start;
|
|
144
|
+
gap: 12px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.status-dot {
|
|
148
|
+
width: 8px;
|
|
149
|
+
height: 8px;
|
|
150
|
+
border-radius: 50%;
|
|
151
|
+
margin-top: 5px;
|
|
152
|
+
flex-shrink: 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.status-dot.ok { background: var(--success); box-shadow: 0 0 6px rgba(116,192,124,0.5); }
|
|
156
|
+
.status-dot.fail { background: var(--danger); box-shadow: 0 0 6px rgba(222,138,120,0.5); }
|
|
157
|
+
|
|
158
|
+
.status-label { font-size: 13px; font-weight: 600; color: var(--text); }
|
|
159
|
+
.status-detail { font-size: 12px; color: var(--text-muted); margin-top: 3px; line-height: 1.4; }
|
|
160
|
+
|
|
161
|
+
/* ── Logs ──────────────────────────────────────────────────────── */
|
|
162
|
+
.toolbar {
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
gap: 8px;
|
|
166
|
+
margin-bottom: 12px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.toolbar-count {
|
|
170
|
+
font-size: 13px;
|
|
171
|
+
color: var(--text-muted);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.spacer { flex: 1; }
|
|
175
|
+
|
|
176
|
+
.log-table {
|
|
177
|
+
background: var(--bg-sidebar);
|
|
178
|
+
border: 1px solid var(--border);
|
|
179
|
+
border-radius: 8px;
|
|
180
|
+
overflow: hidden;
|
|
181
|
+
max-height: 540px;
|
|
182
|
+
overflow-y: auto;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.log-row {
|
|
186
|
+
display: grid;
|
|
187
|
+
grid-template-columns: 80px 46px 1fr;
|
|
188
|
+
border-bottom: 1px solid rgba(224,240,224,0.04);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.log-row:last-child { border-bottom: none; }
|
|
192
|
+
|
|
193
|
+
.log-row:hover { background: rgba(224,240,224,0.03); }
|
|
194
|
+
|
|
195
|
+
.log-cell {
|
|
196
|
+
padding: 6px 10px;
|
|
197
|
+
font-family: var(--font-mono);
|
|
198
|
+
font-size: 11.5px;
|
|
199
|
+
overflow: hidden;
|
|
200
|
+
text-overflow: ellipsis;
|
|
201
|
+
white-space: nowrap;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.log-cell.msg {
|
|
205
|
+
white-space: pre-wrap;
|
|
206
|
+
word-break: break-all;
|
|
207
|
+
color: var(--text-secondary);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.log-ts { color: var(--text-muted); }
|
|
211
|
+
|
|
212
|
+
.log-level-log { color: var(--text-muted); }
|
|
213
|
+
.log-level-info { color: var(--info); }
|
|
214
|
+
.log-level-warn { color: var(--warning); }
|
|
215
|
+
.log-level-error { color: var(--danger); }
|
|
216
|
+
|
|
217
|
+
.log-msg-warn { color: #e4ca8a; }
|
|
218
|
+
.log-msg-error { color: var(--danger); }
|
|
219
|
+
|
|
220
|
+
/* ── Version / updates ─────────────────────────────────────────── */
|
|
221
|
+
.kv-row {
|
|
222
|
+
display: flex;
|
|
223
|
+
align-items: baseline;
|
|
224
|
+
justify-content: space-between;
|
|
225
|
+
gap: 12px;
|
|
226
|
+
padding: 9px 0;
|
|
227
|
+
border-bottom: 1px solid var(--border);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.kv-row:last-child { border-bottom: none; }
|
|
231
|
+
|
|
232
|
+
.kv-key {
|
|
233
|
+
font-size: 13px;
|
|
234
|
+
color: var(--text-muted);
|
|
235
|
+
flex-shrink: 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.kv-val {
|
|
239
|
+
font-size: 13px;
|
|
240
|
+
font-family: var(--font-mono);
|
|
241
|
+
color: var(--text);
|
|
242
|
+
text-align: right;
|
|
243
|
+
word-break: break-all;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.progress-track {
|
|
247
|
+
height: 5px;
|
|
248
|
+
background: var(--bg-secondary);
|
|
249
|
+
border-radius: 999px;
|
|
250
|
+
overflow: hidden;
|
|
251
|
+
margin: 8px 0 4px;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.progress-fill {
|
|
255
|
+
height: 100%;
|
|
256
|
+
background: var(--accent);
|
|
257
|
+
border-radius: 999px;
|
|
258
|
+
transition: width 0.5s ease;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.update-controls {
|
|
262
|
+
display: flex;
|
|
263
|
+
align-items: flex-end;
|
|
264
|
+
gap: 14px;
|
|
265
|
+
flex-wrap: wrap;
|
|
266
|
+
margin-top: 16px;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.update-controls .field {
|
|
270
|
+
margin-bottom: 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.update-controls label {
|
|
274
|
+
text-transform: uppercase;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* ── Config table ──────────────────────────────────────────────── */
|
|
278
|
+
.config-table {
|
|
279
|
+
width: 100%;
|
|
280
|
+
border-collapse: collapse;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.config-table tr {
|
|
284
|
+
border-bottom: 1px solid var(--border);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.config-table tr:last-child { border-bottom: none; }
|
|
288
|
+
|
|
289
|
+
.config-table td {
|
|
290
|
+
padding: 9px 4px;
|
|
291
|
+
font-size: 13px;
|
|
292
|
+
vertical-align: baseline;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.config-table td:first-child {
|
|
296
|
+
font-family: var(--font-mono);
|
|
297
|
+
color: var(--text-muted);
|
|
298
|
+
width: 220px;
|
|
299
|
+
padding-right: 16px;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.config-table td:last-child {
|
|
303
|
+
font-family: var(--font-mono);
|
|
304
|
+
color: var(--text);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.config-empty {
|
|
308
|
+
font-style: italic;
|
|
309
|
+
color: var(--text-muted) !important;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* ── Provider rows ─────────────────────────────────────────── */
|
|
313
|
+
.provider-row {
|
|
314
|
+
display: grid;
|
|
315
|
+
grid-template-columns: 1fr auto;
|
|
316
|
+
align-items: center;
|
|
317
|
+
gap: 16px;
|
|
318
|
+
padding: 12px 0;
|
|
319
|
+
border-bottom: 1px solid var(--border);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.provider-row:last-child { border-bottom: none; }
|
|
323
|
+
|
|
324
|
+
.provider-meta {
|
|
325
|
+
display: flex;
|
|
326
|
+
align-items: center;
|
|
327
|
+
gap: 10px;
|
|
328
|
+
min-width: 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.provider-name {
|
|
332
|
+
font-size: 13px;
|
|
333
|
+
font-weight: 600;
|
|
334
|
+
color: var(--text);
|
|
335
|
+
flex-shrink: 0;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.provider-hint {
|
|
339
|
+
font-family: var(--font-mono);
|
|
340
|
+
font-size: 11px;
|
|
341
|
+
color: var(--text-muted);
|
|
342
|
+
overflow: hidden;
|
|
343
|
+
text-overflow: ellipsis;
|
|
344
|
+
white-space: nowrap;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.provider-controls {
|
|
348
|
+
display: flex;
|
|
349
|
+
align-items: center;
|
|
350
|
+
gap: 8px;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.provider-controls input {
|
|
354
|
+
width: 220px;
|
|
355
|
+
padding: 7px 12px;
|
|
356
|
+
font-size: 13px;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.provider-save-btn {
|
|
360
|
+
padding: 7px 14px;
|
|
361
|
+
font-size: 12px;
|
|
362
|
+
flex-shrink: 0;
|
|
363
|
+
}
|
|
364
|
+
</style>
|
|
365
|
+
</head>
|
|
366
|
+
<body>
|
|
367
|
+
<!-- ── Sidebar ─────────────────────────────────────────────────────── -->
|
|
368
|
+
<aside class="sidebar">
|
|
369
|
+
<div class="sidebar-header">
|
|
370
|
+
<a class="brand" href="/admin">
|
|
371
|
+
<div class="brand-mark">
|
|
372
|
+
<img src="/admin/logo.svg" width="28" height="28" alt="">
|
|
373
|
+
</div>
|
|
374
|
+
<div>
|
|
375
|
+
<div class="brand-name">NeoAgent</div>
|
|
376
|
+
<div class="brand-sub">Admin</div>
|
|
377
|
+
</div>
|
|
378
|
+
</a>
|
|
379
|
+
</div>
|
|
380
|
+
|
|
381
|
+
<nav aria-label="Admin navigation">
|
|
382
|
+
<div class="nav-section-label">Dashboard</div>
|
|
383
|
+
|
|
384
|
+
<button class="nav-item active" data-page="overview" onclick="showPage('overview',this)">
|
|
385
|
+
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
386
|
+
<path d="M3 4a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H4a1 1 0 01-1-1V4zm0 8a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H4a1 1 0 01-1-1v-4zm8-8a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V4zm0 8a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z"/>
|
|
387
|
+
</svg>
|
|
388
|
+
Overview
|
|
389
|
+
</button>
|
|
390
|
+
|
|
391
|
+
<button class="nav-item" data-page="logs" onclick="showPage('logs',this)">
|
|
392
|
+
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
393
|
+
<path fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 5a1 1 0 011-1h6a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"/>
|
|
394
|
+
</svg>
|
|
395
|
+
Logs
|
|
396
|
+
</button>
|
|
397
|
+
|
|
398
|
+
<button class="nav-item" data-page="updates" onclick="showPage('updates',this)">
|
|
399
|
+
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
400
|
+
<path fill-rule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clip-rule="evenodd"/>
|
|
401
|
+
</svg>
|
|
402
|
+
Updates
|
|
403
|
+
</button>
|
|
404
|
+
|
|
405
|
+
<button class="nav-item" data-page="providers" onclick="showPage('providers',this)">
|
|
406
|
+
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
407
|
+
<path fill-rule="evenodd" d="M18 8a6 6 0 01-7.743 5.743L10 14l-1 1-1 1H6v2H2v-4l4.257-4.257A6 6 0 1118 8zm-6-4a1 1 0 100 2 2 2 0 012 2 1 1 0 102 0 4 4 0 00-4-4z" clip-rule="evenodd"/>
|
|
408
|
+
</svg>
|
|
409
|
+
Providers
|
|
410
|
+
</button>
|
|
411
|
+
|
|
412
|
+
<button class="nav-item" data-page="config" onclick="showPage('config',this)">
|
|
413
|
+
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
414
|
+
<path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd"/>
|
|
415
|
+
</svg>
|
|
416
|
+
Configuration
|
|
417
|
+
</button>
|
|
418
|
+
</nav>
|
|
419
|
+
|
|
420
|
+
<div class="sidebar-footer">
|
|
421
|
+
<button class="nav-item btn-danger" onclick="signOut()">
|
|
422
|
+
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
423
|
+
<path fill-rule="evenodd" d="M3 3a1 1 0 00-1 1v12a1 1 0 102 0V4a1 1 0 00-1-1zm10.293 9.293a1 1 0 001.414 1.414l3-3a1 1 0 000-1.414l-3-3a1 1 0 10-1.414 1.414L14.586 9H7a1 1 0 100 2h7.586l-1.293 1.293z" clip-rule="evenodd"/>
|
|
424
|
+
</svg>
|
|
425
|
+
Sign out
|
|
426
|
+
</button>
|
|
427
|
+
</div>
|
|
428
|
+
</aside>
|
|
429
|
+
|
|
430
|
+
<!-- ── Main ──────────────────────────────────────────────────────────── -->
|
|
431
|
+
<main class="main">
|
|
432
|
+
|
|
433
|
+
<!-- Overview -->
|
|
434
|
+
<section id="page-overview" class="page active" aria-label="Overview">
|
|
435
|
+
<div class="page-header">
|
|
436
|
+
<div>
|
|
437
|
+
<h1 class="page-title">Overview</h1>
|
|
438
|
+
<p class="page-subtitle">Connected services and system status</p>
|
|
439
|
+
</div>
|
|
440
|
+
<span class="refresh-hint" id="overview-ts" aria-live="polite"></span>
|
|
441
|
+
</div>
|
|
442
|
+
<div class="content">
|
|
443
|
+
<div class="card">
|
|
444
|
+
<div class="card-title">Service Health</div>
|
|
445
|
+
<div class="status-grid" id="health-grid">
|
|
446
|
+
<div class="empty"><span class="spinner"></span></div>
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
</section>
|
|
451
|
+
|
|
452
|
+
<!-- Logs -->
|
|
453
|
+
<section id="page-logs" class="page" aria-label="Logs">
|
|
454
|
+
<div class="page-header">
|
|
455
|
+
<div>
|
|
456
|
+
<h1 class="page-title">Server Logs</h1>
|
|
457
|
+
<p class="page-subtitle">Live server log stream — last 1,000 entries, persisted across restarts</p>
|
|
458
|
+
</div>
|
|
459
|
+
<span class="refresh-hint" id="logs-ts" aria-live="polite"></span>
|
|
460
|
+
</div>
|
|
461
|
+
<div class="content">
|
|
462
|
+
<div class="card">
|
|
463
|
+
<div class="toolbar">
|
|
464
|
+
<span class="toolbar-count" id="log-count" aria-live="polite">0 entries</span>
|
|
465
|
+
<span class="spacer"></span>
|
|
466
|
+
<button class="btn btn-ghost" onclick="copyLogs()">Copy all</button>
|
|
467
|
+
<button class="btn btn-ghost" onclick="clearLogs()">Clear</button>
|
|
468
|
+
</div>
|
|
469
|
+
<div class="log-table" id="log-table" role="log" aria-label="Server logs">
|
|
470
|
+
<div class="empty">No log entries yet</div>
|
|
471
|
+
</div>
|
|
472
|
+
</div>
|
|
473
|
+
</div>
|
|
474
|
+
</section>
|
|
475
|
+
|
|
476
|
+
<!-- Updates -->
|
|
477
|
+
<section id="page-updates" class="page" aria-label="Updates">
|
|
478
|
+
<div class="page-header">
|
|
479
|
+
<div>
|
|
480
|
+
<h1 class="page-title">Updates</h1>
|
|
481
|
+
<p class="page-subtitle">Server version and runtime update controls</p>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
<div class="content">
|
|
485
|
+
<div class="card">
|
|
486
|
+
<div class="card-title">Version Info</div>
|
|
487
|
+
<div id="version-content"><div class="empty"><span class="spinner"></span></div></div>
|
|
488
|
+
</div>
|
|
489
|
+
<div class="card">
|
|
490
|
+
<div class="card-title">Runtime Update</div>
|
|
491
|
+
<div id="update-content"><div class="empty"><span class="spinner"></span></div></div>
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
</section>
|
|
495
|
+
|
|
496
|
+
<!-- Providers -->
|
|
497
|
+
<section id="page-providers" class="page" aria-label="Providers">
|
|
498
|
+
<div class="page-header">
|
|
499
|
+
<div>
|
|
500
|
+
<h1 class="page-title">AI Providers</h1>
|
|
501
|
+
<p class="page-subtitle">API keys for LLM and voice providers — saved to .env, applied immediately</p>
|
|
502
|
+
</div>
|
|
503
|
+
</div>
|
|
504
|
+
<div class="content">
|
|
505
|
+
<div class="card">
|
|
506
|
+
<div class="card-title">Provider Keys</div>
|
|
507
|
+
<div id="providers-content"><div class="empty"><span class="spinner"></span></div></div>
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
</section>
|
|
511
|
+
|
|
512
|
+
<!-- Configuration -->
|
|
513
|
+
<section id="page-config" class="page" aria-label="Configuration">
|
|
514
|
+
<div class="page-header">
|
|
515
|
+
<div>
|
|
516
|
+
<h1 class="page-title">Configuration</h1>
|
|
517
|
+
<p class="page-subtitle">Current environment settings (non-secret values only)</p>
|
|
518
|
+
</div>
|
|
519
|
+
</div>
|
|
520
|
+
<div class="content">
|
|
521
|
+
<div class="card">
|
|
522
|
+
<div class="card-title">Environment</div>
|
|
523
|
+
<div id="config-content"><div class="empty"><span class="spinner"></span></div></div>
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
</section>
|
|
527
|
+
|
|
528
|
+
</main>
|
|
529
|
+
|
|
530
|
+
<script src="/admin/admin.js"></script>
|
|
531
|
+
</body>
|
|
532
|
+
</html>
|
|
@@ -0,0 +1,164 @@
|
|
|
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>NeoAgent Admin</title>
|
|
7
|
+
<link rel="stylesheet" href="/admin/admin.css">
|
|
8
|
+
<style>
|
|
9
|
+
body {
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
min-height: 100vh;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.login-wrap {
|
|
17
|
+
width: 100%;
|
|
18
|
+
max-width: 380px;
|
|
19
|
+
padding: 20px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.login-card {
|
|
23
|
+
background: var(--bg-card);
|
|
24
|
+
border: 1px solid var(--border);
|
|
25
|
+
border-radius: var(--radius);
|
|
26
|
+
padding: 36px 32px 32px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.brand {
|
|
30
|
+
margin-bottom: 32px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.brand-mark {
|
|
34
|
+
width: 32px;
|
|
35
|
+
height: 32px;
|
|
36
|
+
border-radius: 9px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.brand-mark img {
|
|
40
|
+
width: 32px;
|
|
41
|
+
height: 32px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.login-heading {
|
|
45
|
+
font-size: 20px;
|
|
46
|
+
font-weight: 700;
|
|
47
|
+
color: var(--text);
|
|
48
|
+
letter-spacing: -0.3px;
|
|
49
|
+
margin-bottom: 4px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.login-sub {
|
|
53
|
+
font-size: 13px;
|
|
54
|
+
color: var(--text-muted);
|
|
55
|
+
margin-bottom: 28px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.login-sub code {
|
|
59
|
+
font-family: var(--font-mono);
|
|
60
|
+
font-size: 12px;
|
|
61
|
+
background: var(--bg-secondary);
|
|
62
|
+
padding: 1px 5px;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
color: var(--accent);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.submit-btn {
|
|
68
|
+
width: 100%;
|
|
69
|
+
padding: 11px;
|
|
70
|
+
font-size: 14px;
|
|
71
|
+
margin-top: 4px;
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
74
|
+
</head>
|
|
75
|
+
<body>
|
|
76
|
+
<div class="login-wrap">
|
|
77
|
+
<div class="login-card">
|
|
78
|
+
<div class="brand">
|
|
79
|
+
<div class="brand-mark">
|
|
80
|
+
<img src="/admin/logo.svg" width="32" height="32" alt="">
|
|
81
|
+
</div>
|
|
82
|
+
<div>
|
|
83
|
+
<div class="brand-name">NeoAgent</div>
|
|
84
|
+
<div class="brand-sub">Admin</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<h1 class="login-heading">Sign in</h1>
|
|
89
|
+
<p class="login-sub">
|
|
90
|
+
Credentials are in your <code>.env</code> or via <code>neoagent admin</code>
|
|
91
|
+
</p>
|
|
92
|
+
|
|
93
|
+
<div class="alert alert-error" id="error" role="alert"></div>
|
|
94
|
+
|
|
95
|
+
<form id="form" novalidate>
|
|
96
|
+
<div class="field">
|
|
97
|
+
<label for="username">Username</label>
|
|
98
|
+
<input
|
|
99
|
+
id="username"
|
|
100
|
+
type="text"
|
|
101
|
+
autocomplete="username"
|
|
102
|
+
autofocus
|
|
103
|
+
required
|
|
104
|
+
spellcheck="false"
|
|
105
|
+
autocorrect="off"
|
|
106
|
+
autocapitalize="none"
|
|
107
|
+
>
|
|
108
|
+
</div>
|
|
109
|
+
<div class="field">
|
|
110
|
+
<label for="password">Password</label>
|
|
111
|
+
<input
|
|
112
|
+
id="password"
|
|
113
|
+
type="password"
|
|
114
|
+
autocomplete="current-password"
|
|
115
|
+
required
|
|
116
|
+
>
|
|
117
|
+
</div>
|
|
118
|
+
<button type="submit" class="btn btn-primary submit-btn" id="submit-btn">
|
|
119
|
+
Sign in
|
|
120
|
+
</button>
|
|
121
|
+
</form>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<script>
|
|
126
|
+
const form = document.getElementById('form');
|
|
127
|
+
const submitBtn = document.getElementById('submit-btn');
|
|
128
|
+
const errorEl = document.getElementById('error');
|
|
129
|
+
|
|
130
|
+
form.addEventListener('submit', async (e) => {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
errorEl.classList.remove('visible');
|
|
133
|
+
submitBtn.disabled = true;
|
|
134
|
+
submitBtn.textContent = 'Signing in…';
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const res = await fetch('/admin/api/login', {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: { 'Content-Type': 'application/json' },
|
|
140
|
+
body: JSON.stringify({
|
|
141
|
+
username: document.getElementById('username').value.trim(),
|
|
142
|
+
password: document.getElementById('password').value,
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (res.ok) {
|
|
147
|
+
window.location.replace('/admin');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const body = await res.json().catch(() => ({}));
|
|
152
|
+
errorEl.textContent = body.error || 'Invalid credentials';
|
|
153
|
+
errorEl.classList.add('visible');
|
|
154
|
+
} catch {
|
|
155
|
+
errorEl.textContent = 'Network error — is the server running?';
|
|
156
|
+
errorEl.classList.add('visible');
|
|
157
|
+
} finally {
|
|
158
|
+
submitBtn.disabled = false;
|
|
159
|
+
submitBtn.textContent = 'Sign in';
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
</script>
|
|
163
|
+
</body>
|
|
164
|
+
</html>
|