@agenticmail/enterprise 0.2.1
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/ARCHITECTURE.md +183 -0
- package/agenticmail-enterprise.db +0 -0
- package/dashboards/README.md +120 -0
- package/dashboards/dotnet/Program.cs +261 -0
- package/dashboards/express/app.js +146 -0
- package/dashboards/go/main.go +513 -0
- package/dashboards/html/index.html +535 -0
- package/dashboards/java/AgenticMailDashboard.java +376 -0
- package/dashboards/php/index.php +414 -0
- package/dashboards/python/app.py +273 -0
- package/dashboards/ruby/app.rb +195 -0
- package/dist/chunk-77IDQJL3.js +7 -0
- package/dist/chunk-7RGCCHIT.js +115 -0
- package/dist/chunk-DXNKR3TG.js +1355 -0
- package/dist/chunk-IQWA44WT.js +970 -0
- package/dist/chunk-LCUZGIDH.js +965 -0
- package/dist/chunk-N2JVTNNJ.js +2553 -0
- package/dist/chunk-O462UJBH.js +363 -0
- package/dist/chunk-PNKVD2UK.js +26 -0
- package/dist/cli.js +218 -0
- package/dist/dashboard/index.html +558 -0
- package/dist/db-adapter-DEWEFNIV.js +7 -0
- package/dist/dynamodb-CCGL2E77.js +426 -0
- package/dist/engine/index.js +1261 -0
- package/dist/index.js +522 -0
- package/dist/mongodb-ODTXIVPV.js +319 -0
- package/dist/mysql-RM3S2FV5.js +521 -0
- package/dist/postgres-LN7A6MGQ.js +518 -0
- package/dist/routes-2JEPIIKC.js +441 -0
- package/dist/routes-74ZLKJKP.js +399 -0
- package/dist/server.js +7 -0
- package/dist/sqlite-3K5YOZ4K.js +439 -0
- package/dist/turso-LDWODSDI.js +442 -0
- package/package.json +49 -0
- package/src/admin/routes.ts +331 -0
- package/src/auth/routes.ts +130 -0
- package/src/cli.ts +260 -0
- package/src/dashboard/index.html +558 -0
- package/src/db/adapter.ts +230 -0
- package/src/db/dynamodb.ts +456 -0
- package/src/db/factory.ts +51 -0
- package/src/db/mongodb.ts +360 -0
- package/src/db/mysql.ts +472 -0
- package/src/db/postgres.ts +479 -0
- package/src/db/sql-schema.ts +123 -0
- package/src/db/sqlite.ts +391 -0
- package/src/db/turso.ts +411 -0
- package/src/deploy/fly.ts +368 -0
- package/src/deploy/managed.ts +213 -0
- package/src/engine/activity.ts +474 -0
- package/src/engine/agent-config.ts +429 -0
- package/src/engine/agenticmail-bridge.ts +296 -0
- package/src/engine/approvals.ts +278 -0
- package/src/engine/db-adapter.ts +682 -0
- package/src/engine/db-schema.ts +335 -0
- package/src/engine/deployer.ts +595 -0
- package/src/engine/index.ts +134 -0
- package/src/engine/knowledge.ts +486 -0
- package/src/engine/lifecycle.ts +635 -0
- package/src/engine/openclaw-hook.ts +371 -0
- package/src/engine/routes.ts +528 -0
- package/src/engine/skills.ts +473 -0
- package/src/engine/tenant.ts +345 -0
- package/src/engine/tool-catalog.ts +189 -0
- package/src/index.ts +64 -0
- package/src/lib/resilience.ts +326 -0
- package/src/middleware/index.ts +286 -0
- package/src/server.ts +310 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* AgenticMail Enterprise Dashboard — PHP Edition
|
|
4
|
+
*
|
|
5
|
+
* ZERO dependencies. No Composer, no Laravel, no framework.
|
|
6
|
+
* Just PHP 7.4+ and a web server.
|
|
7
|
+
*
|
|
8
|
+
* Setup:
|
|
9
|
+
* 1. Edit $API_URL below
|
|
10
|
+
* 2. Drop this file on any PHP web server (Apache, Nginx, XAMPP, MAMP)
|
|
11
|
+
* 3. Open in browser
|
|
12
|
+
*
|
|
13
|
+
* Or run locally:
|
|
14
|
+
* php -S localhost:8080 index.php
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
$API_URL = getenv('AGENTICMAIL_URL') ?: 'http://localhost:3000';
|
|
18
|
+
|
|
19
|
+
// ─── Session & Auth ─────────────────────────────────────
|
|
20
|
+
session_start();
|
|
21
|
+
$token = $_SESSION['am_token'] ?? null;
|
|
22
|
+
$user = $_SESSION['am_user'] ?? null;
|
|
23
|
+
$error = '';
|
|
24
|
+
$success = '';
|
|
25
|
+
|
|
26
|
+
// ─── API Helper ─────────────────────────────────────────
|
|
27
|
+
function am_api(string $path, string $method = 'GET', ?array $body = null): array {
|
|
28
|
+
global $API_URL, $token;
|
|
29
|
+
$opts = [
|
|
30
|
+
'http' => [
|
|
31
|
+
'method' => $method,
|
|
32
|
+
'header' => "Content-Type: application/json\r\n" .
|
|
33
|
+
($token ? "Authorization: Bearer $token\r\n" : ''),
|
|
34
|
+
'timeout' => 10,
|
|
35
|
+
'ignore_errors' => true,
|
|
36
|
+
],
|
|
37
|
+
];
|
|
38
|
+
if ($body !== null) {
|
|
39
|
+
$opts['http']['content'] = json_encode($body);
|
|
40
|
+
}
|
|
41
|
+
$ctx = stream_context_create($opts);
|
|
42
|
+
$response = @file_get_contents($API_URL . $path, false, $ctx);
|
|
43
|
+
if ($response === false) return ['error' => 'Could not connect to AgenticMail server'];
|
|
44
|
+
return json_decode($response, true) ?: ['error' => 'Invalid response'];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── Handle Actions ─────────────────────────────────────
|
|
48
|
+
$action = $_POST['action'] ?? $_GET['action'] ?? '';
|
|
49
|
+
|
|
50
|
+
if ($action === 'login') {
|
|
51
|
+
$data = am_api('/auth/login', 'POST', [
|
|
52
|
+
'email' => $_POST['email'] ?? '',
|
|
53
|
+
'password' => $_POST['password'] ?? '',
|
|
54
|
+
]);
|
|
55
|
+
if (isset($data['token'])) {
|
|
56
|
+
$_SESSION['am_token'] = $data['token'];
|
|
57
|
+
$_SESSION['am_user'] = $data['user'];
|
|
58
|
+
$token = $data['token'];
|
|
59
|
+
$user = $data['user'];
|
|
60
|
+
} else {
|
|
61
|
+
$error = $data['error'] ?? 'Login failed';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if ($action === 'logout') {
|
|
66
|
+
session_destroy();
|
|
67
|
+
header('Location: ?');
|
|
68
|
+
exit;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if ($action === 'create_agent' && $token) {
|
|
72
|
+
$body = ['name' => $_POST['name'] ?? '', 'role' => $_POST['role'] ?? 'assistant'];
|
|
73
|
+
if (!empty($_POST['email'])) $body['email'] = $_POST['email'];
|
|
74
|
+
$result = am_api('/api/agents', 'POST', $body);
|
|
75
|
+
$success = isset($result['id']) ? "Agent '{$body['name']}' created!" : ($result['error'] ?? 'Failed');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if ($action === 'archive_agent' && $token) {
|
|
79
|
+
$id = $_GET['id'] ?? '';
|
|
80
|
+
$result = am_api("/api/agents/$id/archive", 'POST');
|
|
81
|
+
$success = ($result['ok'] ?? false) ? 'Agent archived' : ($result['error'] ?? 'Failed');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if ($action === 'create_user' && $token) {
|
|
85
|
+
$result = am_api('/api/users', 'POST', [
|
|
86
|
+
'name' => $_POST['name'] ?? '',
|
|
87
|
+
'email' => $_POST['email'] ?? '',
|
|
88
|
+
'role' => $_POST['role'] ?? 'member',
|
|
89
|
+
'password' => $_POST['password'] ?? '',
|
|
90
|
+
]);
|
|
91
|
+
$success = isset($result['id']) ? "User created!" : ($result['error'] ?? 'Failed');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if ($action === 'create_key' && $token) {
|
|
95
|
+
$result = am_api('/api/api-keys', 'POST', ['name' => $_POST['name'] ?? '']);
|
|
96
|
+
if (isset($result['plaintext'])) {
|
|
97
|
+
$success = "Key created: " . $result['plaintext'] . " (SAVE THIS NOW)";
|
|
98
|
+
} else {
|
|
99
|
+
$error = $result['error'] ?? 'Failed';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if ($action === 'revoke_key' && $token) {
|
|
104
|
+
$id = $_GET['id'] ?? '';
|
|
105
|
+
am_api("/api/api-keys/$id", 'DELETE');
|
|
106
|
+
$success = 'Key revoked';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if ($action === 'save_settings' && $token) {
|
|
110
|
+
$result = am_api('/api/settings', 'PATCH', [
|
|
111
|
+
'name' => $_POST['name'] ?? '',
|
|
112
|
+
'domain' => $_POST['domain'] ?? '',
|
|
113
|
+
'primaryColor' => $_POST['primaryColor'] ?? '#6366f1',
|
|
114
|
+
]);
|
|
115
|
+
$success = isset($result['error']) ? $result['error'] : 'Settings saved!';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─── Load Data ──────────────────────────────────────────
|
|
119
|
+
$page = $_GET['page'] ?? 'dashboard';
|
|
120
|
+
$stats = $agents = $users = $keys = $audit = $settings = $retention = null;
|
|
121
|
+
|
|
122
|
+
if ($token) {
|
|
123
|
+
if ($page === 'dashboard') {
|
|
124
|
+
$stats = am_api('/api/stats');
|
|
125
|
+
$audit = am_api('/api/audit?limit=8');
|
|
126
|
+
} elseif ($page === 'agents') {
|
|
127
|
+
$agents = am_api('/api/agents');
|
|
128
|
+
} elseif ($page === 'users') {
|
|
129
|
+
$users = am_api('/api/users');
|
|
130
|
+
} elseif ($page === 'api-keys') {
|
|
131
|
+
$keys = am_api('/api/api-keys');
|
|
132
|
+
} elseif ($page === 'audit') {
|
|
133
|
+
$p = max(0, (int)($_GET['p'] ?? 0));
|
|
134
|
+
$audit = am_api("/api/audit?limit=25&offset=" . ($p * 25));
|
|
135
|
+
} elseif ($page === 'settings') {
|
|
136
|
+
$settings = am_api('/api/settings');
|
|
137
|
+
$retention = am_api('/api/retention');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function e(string $s): string { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); }
|
|
142
|
+
function badge(string $status): string {
|
|
143
|
+
$colors = ['active'=>'#22c55e','archived'=>'#888','suspended'=>'#ef4444','owner'=>'#f59e0b','admin'=>'#6366f1','member'=>'#888','viewer'=>'#555'];
|
|
144
|
+
$c = $colors[$status] ?? '#888';
|
|
145
|
+
return "<span style='display:inline-block;padding:2px 10px;border-radius:999px;font-size:11px;font-weight:600;background:{$c}20;color:$c'>$status</span>";
|
|
146
|
+
}
|
|
147
|
+
?>
|
|
148
|
+
<!DOCTYPE html>
|
|
149
|
+
<html lang="en">
|
|
150
|
+
<head>
|
|
151
|
+
<meta charset="UTF-8">
|
|
152
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
153
|
+
<title>AgenticMail Enterprise — PHP Dashboard</title>
|
|
154
|
+
<style>
|
|
155
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
156
|
+
:root { --bg:#0a0a0f; --surface:#12121a; --border:#1e1e2e; --text:#e4e4ef; --dim:#8888a0; --muted:#55556a; --primary:#6366f1; --success:#22c55e; --danger:#ef4444; --warning:#f59e0b; --r:8px; }
|
|
157
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; }
|
|
158
|
+
.layout { display: flex; min-height: 100vh; }
|
|
159
|
+
.sidebar { width: 240px; background: var(--surface); border-right: 1px solid var(--border); position: fixed; top: 0; left: 0; bottom: 0; display: flex; flex-direction: column; }
|
|
160
|
+
.sidebar-header { padding: 20px; border-bottom: 1px solid var(--border); }
|
|
161
|
+
.sidebar-header h2 { font-size: 16px; } .sidebar-header h2 em { font-style:normal; color: var(--primary); }
|
|
162
|
+
.sidebar-header small { font-size: 11px; color: var(--muted); display: block; margin-top: 2px; }
|
|
163
|
+
.nav { flex: 1; padding: 8px 0; }
|
|
164
|
+
.nav-sec { font-size:10px; text-transform:uppercase; letter-spacing:0.08em; color:var(--muted); padding:12px 20px 4px; }
|
|
165
|
+
.nav a { display:flex; align-items:center; gap:10px; padding:10px 20px; color:var(--dim); text-decoration:none; font-size:13px; }
|
|
166
|
+
.nav a:hover { color:var(--text); background:rgba(255,255,255,0.03); }
|
|
167
|
+
.nav a.active { color:var(--primary); background:rgba(99,102,241,0.12); border-right:2px solid var(--primary); }
|
|
168
|
+
.sidebar-footer { padding:16px 20px; border-top:1px solid var(--border); font-size:12px; }
|
|
169
|
+
.content { flex:1; margin-left:240px; padding:32px; max-width:1100px; }
|
|
170
|
+
h2.title { font-size:22px; font-weight:700; margin-bottom:4px; }
|
|
171
|
+
.desc { font-size:13px; color:var(--dim); margin-bottom:24px; }
|
|
172
|
+
.stats { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:16px; margin-bottom:24px; }
|
|
173
|
+
.stat { background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:20px; }
|
|
174
|
+
.stat .l { font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:0.06em; }
|
|
175
|
+
.stat .v { font-size:30px; font-weight:700; margin-top:4px; }
|
|
176
|
+
.card { background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:20px; margin-bottom:16px; }
|
|
177
|
+
.card-t { font-size:13px; color:var(--dim); text-transform:uppercase; letter-spacing:0.05em; font-weight:600; margin-bottom:12px; }
|
|
178
|
+
table { width:100%; border-collapse:collapse; font-size:13px; }
|
|
179
|
+
th { text-align:left; padding:10px 12px; color:var(--muted); font-size:11px; text-transform:uppercase; letter-spacing:0.05em; border-bottom:1px solid var(--border); font-weight:600; }
|
|
180
|
+
td { padding:12px; border-bottom:1px solid var(--border); }
|
|
181
|
+
tr:hover td { background:rgba(255,255,255,0.015); }
|
|
182
|
+
.btn { display:inline-flex; align-items:center; padding:8px 16px; border-radius:var(--r); font-size:13px; font-weight:600; cursor:pointer; border:1px solid var(--border); background:var(--surface); color:var(--text); text-decoration:none; }
|
|
183
|
+
.btn:hover { background:rgba(255,255,255,0.05); }
|
|
184
|
+
.btn-p { background:var(--primary); border-color:var(--primary); color:#fff; }
|
|
185
|
+
.btn-p:hover { background:#818cf8; }
|
|
186
|
+
.btn-d { color:var(--danger); border-color:var(--danger); }
|
|
187
|
+
.btn-sm { padding:4px 10px; font-size:12px; }
|
|
188
|
+
.input { width:100%; padding:10px 14px; background:var(--bg); border:1px solid var(--border); border-radius:var(--r); color:var(--text); font-size:14px; }
|
|
189
|
+
.input:focus { outline:none; border-color:var(--primary); }
|
|
190
|
+
.fg { margin-bottom:14px; }
|
|
191
|
+
.fl { display:block; font-size:12px; color:var(--dim); margin-bottom:4px; font-weight:500; }
|
|
192
|
+
.alert { padding:12px 16px; border-radius:var(--r); margin-bottom:16px; font-size:13px; }
|
|
193
|
+
.alert-e { background:rgba(239,68,68,0.1); border:1px solid var(--danger); color:var(--danger); }
|
|
194
|
+
.alert-s { background:rgba(34,197,94,0.1); border:1px solid var(--success); color:var(--success); }
|
|
195
|
+
.empty { text-align:center; padding:48px 20px; color:var(--muted); }
|
|
196
|
+
.empty-i { font-size:36px; margin-bottom:10px; }
|
|
197
|
+
.login-wrap { display:flex; align-items:center; justify-content:center; min-height:100vh; }
|
|
198
|
+
.login-box { width:380px; max-width:90vw; }
|
|
199
|
+
.login-box h1 { text-align:center; font-size:22px; margin-bottom:4px; }
|
|
200
|
+
.login-box h1 em { font-style:normal; color:var(--primary); }
|
|
201
|
+
.login-box .sub { text-align:center; color:var(--dim); font-size:13px; margin-bottom:32px; }
|
|
202
|
+
select.input { appearance:auto; }
|
|
203
|
+
@media(max-width:768px) { .sidebar{width:56px;} .sidebar-header h2,.sidebar-header small,.nav a span,.nav-sec,.sidebar-footer{display:none;} .nav a{justify-content:center;padding:14px 0;font-size:18px;} .content{margin-left:56px;padding:16px;} }
|
|
204
|
+
</style>
|
|
205
|
+
</head>
|
|
206
|
+
<body>
|
|
207
|
+
|
|
208
|
+
<?php if (!$token): ?>
|
|
209
|
+
<!-- ═══ Login ═══ -->
|
|
210
|
+
<div class="login-wrap">
|
|
211
|
+
<div class="login-box">
|
|
212
|
+
<h1>🏢 <em>AgenticMail</em> Enterprise</h1>
|
|
213
|
+
<p class="sub">Sign in to your dashboard</p>
|
|
214
|
+
<?php if ($error): ?><div class="alert alert-e"><?= e($error) ?></div><?php endif; ?>
|
|
215
|
+
<form method="POST">
|
|
216
|
+
<input type="hidden" name="action" value="login">
|
|
217
|
+
<div class="fg"><label class="fl">Email</label><input class="input" type="email" name="email" required autofocus></div>
|
|
218
|
+
<div class="fg"><label class="fl">Password</label><input class="input" type="password" name="password" required></div>
|
|
219
|
+
<button class="btn btn-p" style="width:100%;justify-content:center" type="submit">Sign In</button>
|
|
220
|
+
</form>
|
|
221
|
+
<p style="text-align:center;margin-top:16px;font-size:11px;color:var(--muted)">Connected to: <?= e($API_URL) ?></p>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<?php else: ?>
|
|
226
|
+
<!-- ═══ App ═══ -->
|
|
227
|
+
<div class="layout">
|
|
228
|
+
<div class="sidebar">
|
|
229
|
+
<div class="sidebar-header">
|
|
230
|
+
<h2>🏢 <em>Agentic</em>Mail</h2>
|
|
231
|
+
<small>Enterprise · PHP</small>
|
|
232
|
+
</div>
|
|
233
|
+
<div class="nav">
|
|
234
|
+
<div class="nav-sec">Overview</div>
|
|
235
|
+
<a href="?page=dashboard" class="<?= $page === 'dashboard' ? 'active' : '' ?>">📊 <span>Dashboard</span></a>
|
|
236
|
+
<div class="nav-sec">Manage</div>
|
|
237
|
+
<a href="?page=agents" class="<?= $page === 'agents' ? 'active' : '' ?>">🤖 <span>Agents</span></a>
|
|
238
|
+
<a href="?page=users" class="<?= $page === 'users' ? 'active' : '' ?>">👥 <span>Users</span></a>
|
|
239
|
+
<a href="?page=api-keys" class="<?= $page === 'api-keys' ? 'active' : '' ?>">🔑 <span>API Keys</span></a>
|
|
240
|
+
<div class="nav-sec">System</div>
|
|
241
|
+
<a href="?page=audit" class="<?= $page === 'audit' ? 'active' : '' ?>">📋 <span>Audit Log</span></a>
|
|
242
|
+
<a href="?page=settings" class="<?= $page === 'settings' ? 'active' : '' ?>">⚙️ <span>Settings</span></a>
|
|
243
|
+
</div>
|
|
244
|
+
<div class="sidebar-footer">
|
|
245
|
+
<div style="color:var(--dim)"><?= e($user['name'] ?? '') ?></div>
|
|
246
|
+
<div style="color:var(--muted);font-size:11px"><?= e($user['email'] ?? '') ?></div>
|
|
247
|
+
<a href="?action=logout" style="color:var(--muted);font-size:11px;margin-top:6px;display:inline-block">Sign out</a>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<div class="content">
|
|
252
|
+
<?php if ($error): ?><div class="alert alert-e"><?= e($error) ?></div><?php endif; ?>
|
|
253
|
+
<?php if ($success): ?><div class="alert alert-s"><?= e($success) ?></div><?php endif; ?>
|
|
254
|
+
|
|
255
|
+
<?php if ($page === 'dashboard' && $stats): ?>
|
|
256
|
+
<h2 class="title">Dashboard</h2>
|
|
257
|
+
<p class="desc">Overview of your AgenticMail instance</p>
|
|
258
|
+
<div class="stats">
|
|
259
|
+
<div class="stat"><div class="l">Total Agents</div><div class="v" style="color:var(--primary)"><?= (int)($stats['totalAgents'] ?? 0) ?></div></div>
|
|
260
|
+
<div class="stat"><div class="l">Active Agents</div><div class="v" style="color:var(--success)"><?= (int)($stats['activeAgents'] ?? 0) ?></div></div>
|
|
261
|
+
<div class="stat"><div class="l">Users</div><div class="v"><?= (int)($stats['totalUsers'] ?? 0) ?></div></div>
|
|
262
|
+
<div class="stat"><div class="l">Audit Events</div><div class="v"><?= (int)($stats['totalAuditEvents'] ?? 0) ?></div></div>
|
|
263
|
+
</div>
|
|
264
|
+
<div class="card">
|
|
265
|
+
<div class="card-t">Recent Activity</div>
|
|
266
|
+
<?php $events = $audit['events'] ?? []; if (empty($events)): ?>
|
|
267
|
+
<div class="empty"><div class="empty-i">📋</div>No activity yet</div>
|
|
268
|
+
<?php else: foreach ($events as $ev): ?>
|
|
269
|
+
<div style="padding:10px 0;border-bottom:1px solid var(--border);font-size:13px">
|
|
270
|
+
<span style="color:var(--primary);font-weight:500"><?= e($ev['action']) ?></span> on <?= e($ev['resource']) ?>
|
|
271
|
+
<div style="font-size:11px;color:var(--muted)"><?= date('M j, Y g:i A', strtotime($ev['timestamp'])) ?><?= $ev['ip'] ? " · {$ev['ip']}" : '' ?></div>
|
|
272
|
+
</div>
|
|
273
|
+
<?php endforeach; endif; ?>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<?php elseif ($page === 'agents'): ?>
|
|
277
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:24px">
|
|
278
|
+
<div><h2 class="title">Agents</h2><p class="desc" style="margin:0">Manage AI agent identities</p></div>
|
|
279
|
+
<button class="btn btn-p" onclick="document.getElementById('modal-agent').style.display='flex'">+ New Agent</button>
|
|
280
|
+
</div>
|
|
281
|
+
<div class="card">
|
|
282
|
+
<?php $list = $agents['agents'] ?? []; if (empty($list)): ?>
|
|
283
|
+
<div class="empty"><div class="empty-i">🤖</div>No agents yet</div>
|
|
284
|
+
<?php else: ?>
|
|
285
|
+
<table><thead><tr><th>Name</th><th>Email</th><th>Role</th><th>Status</th><th>Created</th><th></th></tr></thead><tbody>
|
|
286
|
+
<?php foreach ($list as $a): ?>
|
|
287
|
+
<tr><td style="font-weight:600"><?= e($a['name']) ?></td><td style="color:var(--dim)"><?= e($a['email']) ?></td><td><?= e($a['role']) ?></td><td><?= badge($a['status']) ?></td><td style="color:var(--muted);font-size:12px"><?= date('M j, Y', strtotime($a['createdAt'])) ?></td><td><?php if ($a['status'] === 'active'): ?><a class="btn btn-sm btn-d" href="?page=agents&action=archive_agent&id=<?= e($a['id']) ?>">Archive</a><?php endif; ?></td></tr>
|
|
288
|
+
<?php endforeach; ?>
|
|
289
|
+
</tbody></table>
|
|
290
|
+
<?php endif; ?>
|
|
291
|
+
</div>
|
|
292
|
+
<!-- Modal -->
|
|
293
|
+
<div id="modal-agent" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);align-items:center;justify-content:center;z-index:100">
|
|
294
|
+
<div class="card" style="width:440px;max-width:90vw">
|
|
295
|
+
<h3 style="margin-bottom:16px">Create Agent</h3>
|
|
296
|
+
<form method="POST"><input type="hidden" name="action" value="create_agent">
|
|
297
|
+
<div class="fg"><label class="fl">Name</label><input class="input" name="name" required placeholder="e.g. researcher"></div>
|
|
298
|
+
<div class="fg"><label class="fl">Email (optional)</label><input class="input" name="email" placeholder="auto-generated"></div>
|
|
299
|
+
<div class="fg"><label class="fl">Role</label><select class="input" name="role"><option>assistant</option><option>secretary</option><option>researcher</option><option>writer</option><option>custom</option></select></div>
|
|
300
|
+
<div style="display:flex;gap:8px;justify-content:flex-end"><button class="btn" type="button" onclick="this.closest('[id]').style.display='none'">Cancel</button><button class="btn btn-p" type="submit">Create</button></div>
|
|
301
|
+
</form>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
<?php elseif ($page === 'users'): ?>
|
|
306
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:24px">
|
|
307
|
+
<div><h2 class="title">Users</h2><p class="desc" style="margin:0">Manage team members</p></div>
|
|
308
|
+
<button class="btn btn-p" onclick="document.getElementById('modal-user').style.display='flex'">+ New User</button>
|
|
309
|
+
</div>
|
|
310
|
+
<div class="card">
|
|
311
|
+
<?php $list = $users['users'] ?? []; if (empty($list)): ?>
|
|
312
|
+
<div class="empty"><div class="empty-i">👥</div>No users yet</div>
|
|
313
|
+
<?php else: ?>
|
|
314
|
+
<table><thead><tr><th>Name</th><th>Email</th><th>Role</th><th>Last Login</th></tr></thead><tbody>
|
|
315
|
+
<?php foreach ($list as $u2): ?>
|
|
316
|
+
<tr><td style="font-weight:600"><?= e($u2['name']) ?></td><td style="color:var(--dim)"><?= e($u2['email']) ?></td><td><?= badge($u2['role']) ?></td><td style="color:var(--muted);font-size:12px"><?= isset($u2['lastLoginAt']) ? date('M j, Y g:i A', strtotime($u2['lastLoginAt'])) : 'Never' ?></td></tr>
|
|
317
|
+
<?php endforeach; ?>
|
|
318
|
+
</tbody></table>
|
|
319
|
+
<?php endif; ?>
|
|
320
|
+
</div>
|
|
321
|
+
<div id="modal-user" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);align-items:center;justify-content:center;z-index:100">
|
|
322
|
+
<div class="card" style="width:440px;max-width:90vw">
|
|
323
|
+
<h3 style="margin-bottom:16px">Create User</h3>
|
|
324
|
+
<form method="POST"><input type="hidden" name="action" value="create_user">
|
|
325
|
+
<div class="fg"><label class="fl">Name</label><input class="input" name="name" required></div>
|
|
326
|
+
<div class="fg"><label class="fl">Email</label><input class="input" type="email" name="email" required></div>
|
|
327
|
+
<div class="fg"><label class="fl">Role</label><select class="input" name="role"><option>member</option><option>admin</option><option>owner</option><option>viewer</option></select></div>
|
|
328
|
+
<div class="fg"><label class="fl">Password</label><input class="input" type="password" name="password" required minlength="8"></div>
|
|
329
|
+
<div style="display:flex;gap:8px;justify-content:flex-end"><button class="btn" type="button" onclick="this.closest('[id]').style.display='none'">Cancel</button><button class="btn btn-p" type="submit">Create</button></div>
|
|
330
|
+
</form>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
<?php elseif ($page === 'api-keys'): ?>
|
|
335
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:24px">
|
|
336
|
+
<div><h2 class="title">API Keys</h2><p class="desc" style="margin:0">Manage programmatic access</p></div>
|
|
337
|
+
<button class="btn btn-p" onclick="document.getElementById('modal-key').style.display='flex'">+ New Key</button>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="card">
|
|
340
|
+
<?php $list = $keys['keys'] ?? []; if (empty($list)): ?>
|
|
341
|
+
<div class="empty"><div class="empty-i">🔑</div>No API keys</div>
|
|
342
|
+
<?php else: ?>
|
|
343
|
+
<table><thead><tr><th>Name</th><th>Key</th><th>Last Used</th><th>Status</th><th></th></tr></thead><tbody>
|
|
344
|
+
<?php foreach ($list as $k): ?>
|
|
345
|
+
<tr><td style="font-weight:600"><?= e($k['name']) ?></td><td><code style="font-size:12px"><?= e($k['keyPrefix']) ?>...</code></td><td style="color:var(--muted);font-size:12px"><?= isset($k['lastUsedAt']) ? date('M j g:i A', strtotime($k['lastUsedAt'])) : 'Never' ?></td><td><?= badge($k['revoked'] ? 'archived' : 'active') ?></td><td><?php if (!($k['revoked'] ?? false)): ?><a class="btn btn-sm btn-d" href="?page=api-keys&action=revoke_key&id=<?= e($k['id']) ?>">Revoke</a><?php endif; ?></td></tr>
|
|
346
|
+
<?php endforeach; ?>
|
|
347
|
+
</tbody></table>
|
|
348
|
+
<?php endif; ?>
|
|
349
|
+
</div>
|
|
350
|
+
<div id="modal-key" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);align-items:center;justify-content:center;z-index:100">
|
|
351
|
+
<div class="card" style="width:440px;max-width:90vw">
|
|
352
|
+
<h3 style="margin-bottom:16px">Create API Key</h3>
|
|
353
|
+
<form method="POST"><input type="hidden" name="action" value="create_key">
|
|
354
|
+
<div class="fg"><label class="fl">Key Name</label><input class="input" name="name" required placeholder="e.g. CI/CD pipeline"></div>
|
|
355
|
+
<div style="display:flex;gap:8px;justify-content:flex-end"><button class="btn" type="button" onclick="this.closest('[id]').style.display='none'">Cancel</button><button class="btn btn-p" type="submit">Create</button></div>
|
|
356
|
+
</form>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
|
|
360
|
+
<?php elseif ($page === 'audit'): ?>
|
|
361
|
+
<?php $p = max(0, (int)($_GET['p'] ?? 0)); $total = $audit['total'] ?? 0; $pages = max(1, ceil($total / 25)); ?>
|
|
362
|
+
<h2 class="title">Audit Log</h2>
|
|
363
|
+
<p class="desc"><?= $total ?> total events</p>
|
|
364
|
+
<div class="card">
|
|
365
|
+
<?php $events = $audit['events'] ?? []; if (empty($events)): ?>
|
|
366
|
+
<div class="empty"><div class="empty-i">📋</div>No audit events yet</div>
|
|
367
|
+
<?php else: ?>
|
|
368
|
+
<table><thead><tr><th>Time</th><th>Actor</th><th>Action</th><th>Resource</th><th>IP</th></tr></thead><tbody>
|
|
369
|
+
<?php foreach ($events as $ev): ?>
|
|
370
|
+
<tr><td style="font-size:12px;color:var(--muted);white-space:nowrap"><?= date('M j g:i A', strtotime($ev['timestamp'])) ?></td><td><?= e($ev['actor']) ?></td><td style="color:var(--primary);font-weight:500"><?= e($ev['action']) ?></td><td style="font-size:12px"><?= e($ev['resource']) ?></td><td style="font-size:12px;color:var(--muted)"><?= $ev['ip'] ?: '-' ?></td></tr>
|
|
371
|
+
<?php endforeach; ?>
|
|
372
|
+
</tbody></table>
|
|
373
|
+
<div style="display:flex;gap:8px;justify-content:center;margin-top:16px">
|
|
374
|
+
<?php if ($p > 0): ?><a class="btn btn-sm" href="?page=audit&p=<?= $p - 1 ?>">← Prev</a><?php endif; ?>
|
|
375
|
+
<span style="padding:6px 12px;font-size:12px;color:var(--muted)">Page <?= $p + 1 ?> of <?= $pages ?></span>
|
|
376
|
+
<?php if (($p + 1) * 25 < $total): ?><a class="btn btn-sm" href="?page=audit&p=<?= $p + 1 ?>">Next →</a><?php endif; ?>
|
|
377
|
+
</div>
|
|
378
|
+
<?php endif; ?>
|
|
379
|
+
</div>
|
|
380
|
+
|
|
381
|
+
<?php elseif ($page === 'settings' && $settings): ?>
|
|
382
|
+
<h2 class="title">Settings</h2>
|
|
383
|
+
<p class="desc">Configure your organization</p>
|
|
384
|
+
<div class="card">
|
|
385
|
+
<div class="card-t">General</div>
|
|
386
|
+
<form method="POST" style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
|
|
387
|
+
<input type="hidden" name="action" value="save_settings">
|
|
388
|
+
<div class="fg"><label class="fl">Organization Name</label><input class="input" name="name" value="<?= e($settings['name'] ?? '') ?>"></div>
|
|
389
|
+
<div class="fg"><label class="fl">Domain</label><input class="input" name="domain" value="<?= e($settings['domain'] ?? '') ?>" placeholder="agents.acme.com"></div>
|
|
390
|
+
<div class="fg"><label class="fl">Primary Color</label><input class="input" type="color" name="primaryColor" value="<?= e($settings['primaryColor'] ?? '#6366f1') ?>" style="height:38px;padding:4px"></div>
|
|
391
|
+
<div></div>
|
|
392
|
+
<div><button class="btn btn-p" type="submit">Save Settings</button></div>
|
|
393
|
+
</form>
|
|
394
|
+
</div>
|
|
395
|
+
<div class="card">
|
|
396
|
+
<div class="card-t">Plan</div>
|
|
397
|
+
<?= badge(strtoupper($settings['plan'] ?? 'free')) ?>
|
|
398
|
+
<span style="font-size:13px;color:var(--dim);margin-left:12px">Subdomain: <?= e($settings['subdomain'] ?? 'not set') ?>.agenticmail.cloud</span>
|
|
399
|
+
</div>
|
|
400
|
+
<?php if ($retention): ?>
|
|
401
|
+
<div class="card">
|
|
402
|
+
<div class="card-t">Data Retention</div>
|
|
403
|
+
<div style="font-size:13px">
|
|
404
|
+
Status: <span style="color:<?= ($retention['enabled'] ?? false) ? 'var(--success)' : 'var(--muted)' ?>"><?= ($retention['enabled'] ?? false) ? 'Enabled' : 'Disabled' ?></span><br>
|
|
405
|
+
<span style="color:var(--dim)">Retain emails for <?= (int)($retention['retainDays'] ?? 365) ?> days<?= ($retention['archiveFirst'] ?? true) ? ' (archive before delete)' : '' ?></span>
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
<?php endif; ?>
|
|
409
|
+
<?php endif; ?>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
<?php endif; ?>
|
|
413
|
+
</body>
|
|
414
|
+
</html>
|