neoagent 2.4.1-beta.46 → 2.4.1-beta.48
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/package.json +1 -1
- package/server/admin/admin.js +1 -1
- package/server/admin/index.html +22 -0
- package/server/admin/login.html +46 -7
- package/server/http/static.js +20 -6
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +4 -4
- package/server/routes/admin.js +26 -12
package/package.json
CHANGED
package/server/admin/admin.js
CHANGED
|
@@ -37,7 +37,7 @@ async function api(path, opts = {}) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function esc(str) {
|
|
40
|
-
return String(str ?? '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
40
|
+
return String(str ?? '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function fmtTime(iso) {
|
package/server/admin/index.html
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>NeoAgent Admin</title>
|
|
7
|
+
<link rel="icon" href="/admin/logo.svg" type="image/svg+xml">
|
|
7
8
|
<link rel="stylesheet" href="/admin/admin.css">
|
|
8
9
|
<style>
|
|
9
10
|
/* ── Layout ────────────────────────────────────────────────────── */
|
|
@@ -82,6 +83,22 @@
|
|
|
82
83
|
border-top: 1px solid var(--border);
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
.sidebar-legal {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
gap: 6px;
|
|
90
|
+
padding: 4px 8px 8px;
|
|
91
|
+
font-size: 11px;
|
|
92
|
+
color: var(--text-muted, #666);
|
|
93
|
+
}
|
|
94
|
+
.sidebar-legal a {
|
|
95
|
+
color: var(--text-muted, #666);
|
|
96
|
+
text-decoration: none;
|
|
97
|
+
transition: color 0.15s;
|
|
98
|
+
}
|
|
99
|
+
.sidebar-legal a:hover { color: var(--text, #ccc); }
|
|
100
|
+
.sidebar-legal span { opacity: 0.4; }
|
|
101
|
+
|
|
85
102
|
.main {
|
|
86
103
|
margin-left: var(--sidebar-w);
|
|
87
104
|
flex: 1;
|
|
@@ -811,6 +828,11 @@
|
|
|
811
828
|
</nav>
|
|
812
829
|
|
|
813
830
|
<div class="sidebar-footer">
|
|
831
|
+
<div class="sidebar-legal">
|
|
832
|
+
<a href="/legal.html#overview" target="_blank" rel="noopener">Privacy Policy</a>
|
|
833
|
+
<span>·</span>
|
|
834
|
+
<a href="/legal.html#legal-notice" target="_blank" rel="noopener">Legal Notice</a>
|
|
835
|
+
</div>
|
|
814
836
|
<button class="nav-item btn-danger" onclick="signOut()">
|
|
815
837
|
<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
816
838
|
<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"/>
|
package/server/admin/login.html
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>NeoAgent Admin</title>
|
|
7
|
+
<link rel="icon" href="/admin/logo.svg" type="image/svg+xml">
|
|
7
8
|
<link rel="stylesheet" href="/admin/admin.css">
|
|
8
9
|
<style>
|
|
9
10
|
body {
|
|
@@ -70,18 +71,56 @@
|
|
|
70
71
|
font-size: 14px;
|
|
71
72
|
margin-top: 4px;
|
|
72
73
|
}
|
|
74
|
+
|
|
75
|
+
/* ── Admin hero mark ────────────────────────────── */
|
|
76
|
+
.admin-hero {
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
align-items: center;
|
|
80
|
+
margin-bottom: 28px;
|
|
81
|
+
gap: 14px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.admin-a-mark {
|
|
85
|
+
width: 80px;
|
|
86
|
+
height: 80px;
|
|
87
|
+
border-radius: 22px;
|
|
88
|
+
background: var(--bg-secondary);
|
|
89
|
+
border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
|
|
90
|
+
box-shadow:
|
|
91
|
+
0 0 0 4px color-mix(in srgb, var(--accent) 8%, transparent),
|
|
92
|
+
0 12px 32px rgba(0,0,0,0.45);
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
font-size: 42px;
|
|
97
|
+
font-weight: 800;
|
|
98
|
+
color: var(--accent);
|
|
99
|
+
letter-spacing: -2px;
|
|
100
|
+
font-family: var(--font-sans);
|
|
101
|
+
line-height: 1;
|
|
102
|
+
user-select: none;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.admin-hero .brand {
|
|
106
|
+
margin-bottom: 0;
|
|
107
|
+
justify-content: center;
|
|
108
|
+
}
|
|
73
109
|
</style>
|
|
74
110
|
</head>
|
|
75
111
|
<body>
|
|
76
112
|
<div class="login-wrap">
|
|
77
113
|
<div class="login-card">
|
|
78
|
-
<div class="
|
|
79
|
-
<div class="
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<div
|
|
114
|
+
<div class="admin-hero">
|
|
115
|
+
<div class="admin-a-mark" aria-hidden="true">A</div>
|
|
116
|
+
<div class="brand">
|
|
117
|
+
<div class="brand-mark">
|
|
118
|
+
<img src="/admin/logo.svg" width="32" height="32" alt="">
|
|
119
|
+
</div>
|
|
120
|
+
<div>
|
|
121
|
+
<div class="brand-name">NeoAgent</div>
|
|
122
|
+
<div class="brand-sub">Admin</div>
|
|
123
|
+
</div>
|
|
85
124
|
</div>
|
|
86
125
|
</div>
|
|
87
126
|
|
package/server/http/static.js
CHANGED
|
@@ -7,6 +7,7 @@ const { DATA_DIR } = require('../../runtime/paths');
|
|
|
7
7
|
const { requireAuth } = require('../middleware/auth');
|
|
8
8
|
|
|
9
9
|
const FLUTTER_WEB_DIR = path.join(__dirname, '..', 'public');
|
|
10
|
+
const LANDING_DIR = path.join(__dirname, '..', '..', 'landing');
|
|
10
11
|
const BUILD_ID_PATH = path.join(FLUTTER_WEB_DIR, '.last_build_id');
|
|
11
12
|
|
|
12
13
|
function setNoStoreHeaders(res) {
|
|
@@ -66,11 +67,20 @@ function registerStaticRoutes(app) {
|
|
|
66
67
|
const adminRouter = require('../routes/admin');
|
|
67
68
|
app.use('/admin', adminRouter);
|
|
68
69
|
|
|
69
|
-
app
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
// Flutter app at /app
|
|
71
|
+
app.use(
|
|
72
|
+
'/app',
|
|
73
|
+
express.static(FLUTTER_WEB_DIR, {
|
|
74
|
+
index: false,
|
|
75
|
+
setHeaders: setFlutterStaticHeaders,
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
app.get(/^\/app(\/.*)?$/, serveFlutterApp);
|
|
79
|
+
|
|
80
|
+
// Landing page at /
|
|
81
|
+
app.use(express.static(LANDING_DIR));
|
|
82
|
+
app.get('/', (req, res) => res.sendFile(path.join(LANDING_DIR, 'index.html')));
|
|
83
|
+
app.get('/legal.html', (req, res) => res.sendFile(path.join(LANDING_DIR, 'legal.html')));
|
|
74
84
|
}
|
|
75
85
|
|
|
76
86
|
function serveFlutterApp(req, res) {
|
|
@@ -83,11 +93,15 @@ function serveFlutterApp(req, res) {
|
|
|
83
93
|
);
|
|
84
94
|
}
|
|
85
95
|
setNoStoreHeaders(res);
|
|
86
|
-
|
|
96
|
+
// Rewrite <base href="/"> to <base href="/app/"> so Flutter asset paths resolve correctly
|
|
97
|
+
const html = fs.readFileSync(entry, 'utf8').replace('<base href="/">', '<base href="/app/">');
|
|
98
|
+
res.set('Content-Type', 'text/html');
|
|
99
|
+
return res.send(html);
|
|
87
100
|
}
|
|
88
101
|
|
|
89
102
|
module.exports = {
|
|
90
103
|
FLUTTER_WEB_DIR,
|
|
104
|
+
LANDING_DIR,
|
|
91
105
|
registerStaticRoutes,
|
|
92
106
|
serveFlutterApp
|
|
93
107
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
ce60a1618c141f32f9c6c152f08d1f52
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"c416acfeb8126e097f758c664aaa3da929e27d
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "2964141920" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|
|
@@ -133824,7 +133824,7 @@ r===$&&A.b()
|
|
|
133824
133824
|
o.push(A.j4(p,A.j8(!1,new A.a3(B.up,A.dl(new A.cv(B.hr,new A.a7p(r,p),p),p,p),p),!1,B.I,!0),p,p,0,0,0,p))}r=!1
|
|
133825
133825
|
if(!s.ay)if(!s.ch){r=s.e
|
|
133826
133826
|
r===$&&A.b()
|
|
133827
|
-
r=B.b.t("
|
|
133827
|
+
r=B.b.t("mq74d0iw-7a6b040").length!==0&&r.b}if(r){r=s.d
|
|
133828
133828
|
r===$&&A.b()
|
|
133829
133829
|
r=r.au&&!r.aQ?84:0
|
|
133830
133830
|
q=s.e
|
|
@@ -139266,7 +139266,7 @@ $S:0}
|
|
|
139266
139266
|
A.ZM.prototype={}
|
|
139267
139267
|
A.Sw.prototype={
|
|
139268
139268
|
n9(a){var s=this
|
|
139269
|
-
if(B.b.t("
|
|
139269
|
+
if(B.b.t("mq74d0iw-7a6b040").length===0||s.a!=null)return
|
|
139270
139270
|
s.AN()
|
|
139271
139271
|
s.a=A.qh(B.Rb,new A.bas(s))},
|
|
139272
139272
|
AN(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
|
|
@@ -139284,7 +139284,7 @@ if(!t.f.b(k)){s=1
|
|
|
139284
139284
|
break}i=J.a_(k,"buildId")
|
|
139285
139285
|
h=i==null?null:B.b.t(J.p(i))
|
|
139286
139286
|
j=h==null?"":h
|
|
139287
|
-
if(J.bf(j)===0||J.d(j,"
|
|
139287
|
+
if(J.bf(j)===0||J.d(j,"mq74d0iw-7a6b040")){s=1
|
|
139288
139288
|
break}n.b=!0
|
|
139289
139289
|
n.E()
|
|
139290
139290
|
p=2
|
|
@@ -139301,7 +139301,7 @@ case 2:return A.i(o.at(-1),r)}})
|
|
|
139301
139301
|
return A.k($async$AN,r)},
|
|
139302
139302
|
vw(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
|
|
139303
139303
|
var $async$vw=A.h(function(a2,a3){if(a2===1){o.push(a3)
|
|
139304
|
-
s=p}for(;;)switch(s){case 0:if(B.b.t("
|
|
139304
|
+
s=p}for(;;)switch(s){case 0:if(B.b.t("mq74d0iw-7a6b040").length===0||n.c){s=1
|
|
139305
139305
|
break}n.c=!0
|
|
139306
139306
|
n.E()
|
|
139307
139307
|
p=4
|
package/server/routes/admin.js
CHANGED
|
@@ -435,13 +435,21 @@ router.get('/api/analytics', requireAdminAuth, (req, res) => {
|
|
|
435
435
|
};
|
|
436
436
|
const topUsers = db.prepare(`
|
|
437
437
|
SELECT u.id, u.username, u.display_name,
|
|
438
|
-
|
|
439
|
-
COALESCE(
|
|
440
|
-
COALESCE(
|
|
438
|
+
COALESCE(r.runs, 0) AS runs,
|
|
439
|
+
COALESCE(r.tokens, 0) AS tokens,
|
|
440
|
+
COALESCE(a.storage, 0) AS storage
|
|
441
441
|
FROM users u
|
|
442
|
-
LEFT JOIN
|
|
443
|
-
|
|
444
|
-
|
|
442
|
+
LEFT JOIN (
|
|
443
|
+
SELECT user_id,
|
|
444
|
+
COUNT(*) AS runs,
|
|
445
|
+
COALESCE(SUM(total_tokens),0) AS tokens
|
|
446
|
+
FROM agent_runs GROUP BY user_id
|
|
447
|
+
) r ON r.user_id = u.id
|
|
448
|
+
LEFT JOIN (
|
|
449
|
+
SELECT user_id, COALESCE(SUM(byte_size),0) AS storage
|
|
450
|
+
FROM artifacts GROUP BY user_id
|
|
451
|
+
) a ON a.user_id = u.id
|
|
452
|
+
ORDER BY runs DESC LIMIT 8
|
|
445
453
|
`).all();
|
|
446
454
|
const recentRuns = db.prepare(`
|
|
447
455
|
SELECT r.id, u.username, r.title, r.status, r.total_tokens,
|
|
@@ -465,13 +473,19 @@ router.get('/api/users', requireAdminAuth, (req, res) => {
|
|
|
465
473
|
const sql = `
|
|
466
474
|
SELECT u.id, u.username, u.display_name, u.email, u.email_verified_at,
|
|
467
475
|
u.created_at, u.last_login,
|
|
468
|
-
|
|
469
|
-
COALESCE(
|
|
476
|
+
COALESCE(r.run_count, 0) AS run_count,
|
|
477
|
+
COALESCE(a.storage_bytes,0) AS storage_bytes
|
|
470
478
|
FROM users u
|
|
471
|
-
LEFT JOIN
|
|
472
|
-
|
|
479
|
+
LEFT JOIN (
|
|
480
|
+
SELECT user_id, COUNT(*) AS run_count
|
|
481
|
+
FROM agent_runs GROUP BY user_id
|
|
482
|
+
) r ON r.user_id = u.id
|
|
483
|
+
LEFT JOIN (
|
|
484
|
+
SELECT user_id, COALESCE(SUM(byte_size),0) AS storage_bytes
|
|
485
|
+
FROM artifacts GROUP BY user_id
|
|
486
|
+
) a ON a.user_id = u.id
|
|
473
487
|
${q ? 'WHERE u.username LIKE ? OR u.email LIKE ?' : ''}
|
|
474
|
-
|
|
488
|
+
ORDER BY u.created_at DESC LIMIT 200`;
|
|
475
489
|
const users = q ? db.prepare(sql).all(q, q) : db.prepare(sql).all();
|
|
476
490
|
res.json({ users });
|
|
477
491
|
} catch (err) {
|
|
@@ -597,7 +611,7 @@ router.put('/api/providers', requireAdminAuth, express.json(), (req, res) => {
|
|
|
597
611
|
if (!ALLOWED_PROVIDER_KEYS.has(key)) {
|
|
598
612
|
return res.status(400).json({ error: 'Unknown provider key' });
|
|
599
613
|
}
|
|
600
|
-
const trimmed = String(value || '').trim();
|
|
614
|
+
const trimmed = String(value || '').trim().replace(/[\r\n]/g, '');
|
|
601
615
|
upsertEnvValue(ENV_FILE, key, trimmed);
|
|
602
616
|
if (trimmed) {
|
|
603
617
|
process.env[key] = trimmed;
|