@agenticmail/enterprise 0.5.295 → 0.5.297
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/dist/chunk-A5LURBEY.js +1519 -0
- package/dist/chunk-WQRGOMQJ.js +4008 -0
- package/dist/chunk-Y3WLLVOK.js +48 -0
- package/dist/cli-agent-WSCDFYMU.js +1778 -0
- package/dist/cli-recover-NXARYVSH.js +487 -0
- package/dist/cli-serve-GTTDOLB7.js +143 -0
- package/dist/cli-verify-RKB6KQN4.js +149 -0
- package/dist/cli.js +5 -5
- package/dist/dashboard/pages/users.js +207 -30
- package/dist/factory-M6E7YAKW.js +9 -0
- package/dist/index.js +3 -3
- package/dist/postgres-LJSV5YUF.js +771 -0
- package/dist/server-VTMQY7VU.js +15 -0
- package/dist/setup-HK4WDXUL.js +20 -0
- package/dist/sqlite-E5KKAJ24.js +503 -0
- package/package.json +1 -1
- package/src/admin/routes.ts +59 -3
- package/src/auth/routes.ts +5 -0
- package/src/dashboard/pages/users.js +207 -30
- package/src/db/adapter.ts +1 -0
- package/src/db/postgres.ts +2 -0
- package/src/db/sqlite.ts +1 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DomainLock
|
|
3
|
+
} from "./chunk-UPU23ZRG.js";
|
|
4
|
+
import "./chunk-KFQGP6VL.js";
|
|
5
|
+
|
|
6
|
+
// src/domain-lock/cli-verify.ts
|
|
7
|
+
function getFlag(args, name) {
|
|
8
|
+
const idx = args.indexOf(name);
|
|
9
|
+
if (idx !== -1 && args[idx + 1]) return args[idx + 1];
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
function hasFlag(args, name) {
|
|
13
|
+
return args.includes(name);
|
|
14
|
+
}
|
|
15
|
+
function detectDbType(url) {
|
|
16
|
+
const u = url.toLowerCase().trim();
|
|
17
|
+
if (u.startsWith("postgres") || u.startsWith("pg:")) return "postgres";
|
|
18
|
+
if (u.startsWith("mysql")) return "mysql";
|
|
19
|
+
if (u.startsWith("mongodb")) return "mongodb";
|
|
20
|
+
if (u.startsWith("libsql") || u.includes(".turso.io")) return "turso";
|
|
21
|
+
if (u.endsWith(".db") || u.endsWith(".sqlite") || u.endsWith(".sqlite3") || u.startsWith("file:")) return "sqlite";
|
|
22
|
+
return "postgres";
|
|
23
|
+
}
|
|
24
|
+
async function runVerifyDomain(args) {
|
|
25
|
+
const { default: chalk } = await import("chalk");
|
|
26
|
+
const { default: ora } = await import("ora");
|
|
27
|
+
console.log("");
|
|
28
|
+
console.log(chalk.bold(" AgenticMail Enterprise \u2014 Domain Verification"));
|
|
29
|
+
console.log("");
|
|
30
|
+
let domain = getFlag(args, "--domain");
|
|
31
|
+
let dnsChallenge;
|
|
32
|
+
let db = null;
|
|
33
|
+
let dbConnected = false;
|
|
34
|
+
const envDbUrl = process.env.DATABASE_URL;
|
|
35
|
+
if (envDbUrl) {
|
|
36
|
+
const dbType = detectDbType(envDbUrl);
|
|
37
|
+
const spinner = ora(`Connecting to database (${dbType})...`).start();
|
|
38
|
+
try {
|
|
39
|
+
const { createAdapter } = await import("./factory-M6E7YAKW.js");
|
|
40
|
+
db = await createAdapter({ type: dbType, connectionString: envDbUrl });
|
|
41
|
+
await db.migrate();
|
|
42
|
+
const settings = await db.getSettings();
|
|
43
|
+
if (!domain && settings?.domain) domain = settings.domain;
|
|
44
|
+
if (settings?.domainDnsChallenge) dnsChallenge = settings.domainDnsChallenge;
|
|
45
|
+
dbConnected = true;
|
|
46
|
+
spinner.succeed(`Connected to ${dbType} database` + (domain ? ` (domain: ${domain})` : ""));
|
|
47
|
+
} catch (err) {
|
|
48
|
+
spinner.warn(`Could not connect via DATABASE_URL: ${err.message}`);
|
|
49
|
+
db = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!dbConnected) {
|
|
53
|
+
const dbPath = getFlag(args, "--db");
|
|
54
|
+
const dbType = getFlag(args, "--db-type") || "sqlite";
|
|
55
|
+
if (dbPath) {
|
|
56
|
+
const spinner = ora(`Connecting to ${dbType} database...`).start();
|
|
57
|
+
try {
|
|
58
|
+
const { createAdapter } = await import("./factory-M6E7YAKW.js");
|
|
59
|
+
db = await createAdapter({ type: dbType, connectionString: dbPath });
|
|
60
|
+
await db.migrate();
|
|
61
|
+
const settings = await db.getSettings();
|
|
62
|
+
if (!domain && settings?.domain) domain = settings.domain;
|
|
63
|
+
if (settings?.domainDnsChallenge) dnsChallenge = settings.domainDnsChallenge;
|
|
64
|
+
dbConnected = true;
|
|
65
|
+
spinner.succeed(`Connected to ${dbType} database`);
|
|
66
|
+
} catch {
|
|
67
|
+
spinner.warn("Could not read local database");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!domain) {
|
|
72
|
+
const { default: inquirer } = await import("inquirer");
|
|
73
|
+
const answer = await inquirer.prompt([{
|
|
74
|
+
type: "input",
|
|
75
|
+
name: "domain",
|
|
76
|
+
message: "Domain to verify:",
|
|
77
|
+
suffix: chalk.dim(" (e.g. agents.yourcompany.com)"),
|
|
78
|
+
validate: (v) => v.includes(".") || "Enter a valid domain",
|
|
79
|
+
filter: (v) => v.trim().toLowerCase()
|
|
80
|
+
}]);
|
|
81
|
+
domain = answer.domain;
|
|
82
|
+
}
|
|
83
|
+
const lock = new DomainLock();
|
|
84
|
+
const maxAttempts = hasFlag(args, "--poll") ? 5 : 1;
|
|
85
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
86
|
+
const spinner = ora(
|
|
87
|
+
maxAttempts > 1 ? `Checking DNS verification (attempt ${attempt}/${maxAttempts})...` : "Checking DNS verification..."
|
|
88
|
+
).start();
|
|
89
|
+
try {
|
|
90
|
+
const result = await lock.checkVerification(domain);
|
|
91
|
+
if (!result.success) {
|
|
92
|
+
spinner.fail("Verification check failed");
|
|
93
|
+
console.log("");
|
|
94
|
+
console.error(chalk.red(` ${result.error}`));
|
|
95
|
+
console.log("");
|
|
96
|
+
if (db) await db.disconnect();
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
if (result.verified) {
|
|
100
|
+
spinner.succeed("Domain verified!");
|
|
101
|
+
if (dbConnected && db) {
|
|
102
|
+
try {
|
|
103
|
+
await db.updateSettings({
|
|
104
|
+
domainStatus: "verified",
|
|
105
|
+
domainVerifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
106
|
+
});
|
|
107
|
+
console.log(chalk.dim(" Database updated with verified status."));
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
console.log("");
|
|
112
|
+
console.log(chalk.green.bold(` \u2713 ${domain} is verified and protected.`));
|
|
113
|
+
console.log(chalk.dim(" Your deployment domain is locked. No other instance can claim it."));
|
|
114
|
+
console.log(chalk.dim(" The system now operates 100% offline \u2014 no outbound calls are made."));
|
|
115
|
+
console.log("");
|
|
116
|
+
if (db) await db.disconnect();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
spinner.warn(`Check failed: ${err.message}`);
|
|
121
|
+
}
|
|
122
|
+
if (attempt < maxAttempts) {
|
|
123
|
+
const waitSpinner = ora(`DNS record not found yet. Retrying in 10 seconds...`).start();
|
|
124
|
+
await new Promise((r) => setTimeout(r, 1e4));
|
|
125
|
+
waitSpinner.stop();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
console.log("");
|
|
129
|
+
console.log(chalk.yellow(" DNS record not detected yet."));
|
|
130
|
+
console.log("");
|
|
131
|
+
console.log(chalk.bold(" Make sure this TXT record exists at your DNS provider:"));
|
|
132
|
+
console.log("");
|
|
133
|
+
console.log(` ${chalk.bold("Host:")} ${chalk.cyan(`_agenticmail-verify.${domain}`)}`);
|
|
134
|
+
console.log(` ${chalk.bold("Type:")} ${chalk.cyan("TXT")}`);
|
|
135
|
+
if (dnsChallenge) {
|
|
136
|
+
console.log(` ${chalk.bold("Value:")} ${chalk.cyan(dnsChallenge)}`);
|
|
137
|
+
} else {
|
|
138
|
+
console.log(` ${chalk.bold("Value:")} ${chalk.dim("(check your dashboard or setup output)")}`);
|
|
139
|
+
}
|
|
140
|
+
console.log("");
|
|
141
|
+
console.log(chalk.dim(" DNS propagation can take up to 48 hours."));
|
|
142
|
+
console.log(chalk.dim(" Run with --poll to retry automatically:"));
|
|
143
|
+
console.log(chalk.dim(` npx @agenticmail/enterprise verify-domain --domain ${domain} --poll`));
|
|
144
|
+
console.log("");
|
|
145
|
+
if (db) await db.disconnect();
|
|
146
|
+
}
|
|
147
|
+
export {
|
|
148
|
+
runVerifyDomain
|
|
149
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -14,10 +14,10 @@ switch (command) {
|
|
|
14
14
|
import("./cli-submit-skill-LDFJGSKO.js").then((m) => m.runSubmitSkill(args.slice(1))).catch(fatal);
|
|
15
15
|
break;
|
|
16
16
|
case "recover":
|
|
17
|
-
import("./cli-recover-
|
|
17
|
+
import("./cli-recover-NXARYVSH.js").then((m) => m.runRecover(args.slice(1))).catch(fatal);
|
|
18
18
|
break;
|
|
19
19
|
case "verify-domain":
|
|
20
|
-
import("./cli-verify-
|
|
20
|
+
import("./cli-verify-RKB6KQN4.js").then((m) => m.runVerifyDomain(args.slice(1))).catch(fatal);
|
|
21
21
|
break;
|
|
22
22
|
case "reset-password":
|
|
23
23
|
import("./cli-reset-password-SO5Y6MW7.js").then((m) => m.runResetPassword(args.slice(1))).catch(fatal);
|
|
@@ -57,14 +57,14 @@ Skill Development:
|
|
|
57
57
|
break;
|
|
58
58
|
case "serve":
|
|
59
59
|
case "start":
|
|
60
|
-
import("./cli-serve-
|
|
60
|
+
import("./cli-serve-GTTDOLB7.js").then((m) => m.runServe(args.slice(1))).catch(fatal);
|
|
61
61
|
break;
|
|
62
62
|
case "agent":
|
|
63
|
-
import("./cli-agent-
|
|
63
|
+
import("./cli-agent-WSCDFYMU.js").then((m) => m.runAgent(args.slice(1))).catch(fatal);
|
|
64
64
|
break;
|
|
65
65
|
case "setup":
|
|
66
66
|
default:
|
|
67
|
-
import("./setup-
|
|
67
|
+
import("./setup-HK4WDXUL.js").then((m) => m.runSetupWizard()).catch(fatal);
|
|
68
68
|
break;
|
|
69
69
|
}
|
|
70
70
|
function fatal(err) {
|
|
@@ -210,10 +210,93 @@ function PermissionEditor({ userId, userName, currentPerms, pageRegistry, onSave
|
|
|
210
210
|
);
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
// ─── Inline Permission Picker (for create modal) ──
|
|
214
|
+
|
|
215
|
+
function InlinePermissionPicker({ permissions, pageRegistry, onChange }) {
|
|
216
|
+
var [expandedPage, setExpandedPage] = useState(null);
|
|
217
|
+
|
|
218
|
+
// Resolve grants from permissions
|
|
219
|
+
var grants = permissions === '*' ? (function() { var a = {}; Object.keys(pageRegistry).forEach(function(p) { a[p] = true; }); return a; })() : (permissions || {});
|
|
220
|
+
|
|
221
|
+
var togglePage = function(pid) {
|
|
222
|
+
var next = Object.assign({}, grants);
|
|
223
|
+
if (next[pid]) { delete next[pid]; if (expandedPage === pid) setExpandedPage(null); }
|
|
224
|
+
else { next[pid] = true; }
|
|
225
|
+
onChange(Object.keys(next).length === Object.keys(pageRegistry).length ? '*' : next);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
var toggleTab = function(pid, tabId) {
|
|
229
|
+
var next = Object.assign({}, grants);
|
|
230
|
+
var current = next[pid];
|
|
231
|
+
var allTabs = Object.keys(pageRegistry[pid].tabs || {});
|
|
232
|
+
if (current === true) {
|
|
233
|
+
var remaining = allTabs.filter(function(t) { return t !== tabId; });
|
|
234
|
+
next[pid] = remaining.length > 0 ? remaining : true;
|
|
235
|
+
} else if (Array.isArray(current)) {
|
|
236
|
+
var idx = current.indexOf(tabId);
|
|
237
|
+
if (idx >= 0) {
|
|
238
|
+
var arr = current.filter(function(t) { return t !== tabId; });
|
|
239
|
+
if (arr.length === 0) delete next[pid];
|
|
240
|
+
else next[pid] = arr;
|
|
241
|
+
} else {
|
|
242
|
+
var newArr = current.concat([tabId]);
|
|
243
|
+
next[pid] = newArr.length === allTabs.length ? true : newArr;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
onChange(Object.keys(next).length === Object.keys(pageRegistry).length && Object.values(next).every(function(v) { return v === true; }) ? '*' : next);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
var isTabChecked = function(pid, tabId) {
|
|
250
|
+
var g = grants[pid];
|
|
251
|
+
if (!g) return false;
|
|
252
|
+
if (g === true) return true;
|
|
253
|
+
return Array.isArray(g) && g.indexOf(tabId) >= 0;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
var sections = { overview: 'Overview', management: 'Management', administration: 'Administration' };
|
|
257
|
+
var grouped = {};
|
|
258
|
+
Object.keys(pageRegistry).forEach(function(pid) { var s = pageRegistry[pid].section; if (!grouped[s]) grouped[s] = []; grouped[s].push(pid); });
|
|
259
|
+
|
|
260
|
+
return h('div', { style: { maxHeight: 250, overflowY: 'auto', border: '1px solid var(--border)', borderRadius: 6, marginTop: 8 } },
|
|
261
|
+
Object.keys(sections).map(function(sKey) {
|
|
262
|
+
var pids = grouped[sKey] || [];
|
|
263
|
+
if (!pids.length) return null;
|
|
264
|
+
return h(Fragment, { key: sKey },
|
|
265
|
+
h('div', { style: { fontSize: 10, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--text-muted)', padding: '8px 10px 2px' } }, sections[sKey]),
|
|
266
|
+
pids.map(function(pid) {
|
|
267
|
+
var page = pageRegistry[pid];
|
|
268
|
+
var checked = !!grants[pid];
|
|
269
|
+
var hasTabs = page.tabs && Object.keys(page.tabs).length > 0;
|
|
270
|
+
var isExpanded = expandedPage === pid;
|
|
271
|
+
return h(Fragment, { key: pid },
|
|
272
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 10px', fontSize: 12, cursor: 'pointer', background: checked ? 'var(--bg-tertiary)' : 'transparent' }, onClick: function(e) {
|
|
273
|
+
if (e.target.closest && e.target.closest('[data-expand]')) return;
|
|
274
|
+
togglePage(pid);
|
|
275
|
+
} },
|
|
276
|
+
h('input', { type: 'checkbox', checked: checked, readOnly: true, style: { width: 14, height: 14, accentColor: 'var(--primary)', cursor: 'pointer' } }),
|
|
277
|
+
h('span', { style: { flex: 1 } }, page.label),
|
|
278
|
+
hasTabs && checked && h('button', { 'data-expand': true, className: 'btn btn-ghost', style: { padding: '0 4px', minWidth: 0, fontSize: 10, lineHeight: 1 }, onClick: function(e) { e.stopPropagation(); setExpandedPage(isExpanded ? null : pid); } },
|
|
279
|
+
isExpanded ? I.chevronDown() : I.chevronRight()
|
|
280
|
+
)
|
|
281
|
+
),
|
|
282
|
+
hasTabs && isExpanded && checked && Object.keys(page.tabs).map(function(tabId) {
|
|
283
|
+
return h('div', { key: tabId, style: { display: 'flex', alignItems: 'center', gap: 8, padding: '3px 10px 3px 34px', fontSize: 11, color: 'var(--text-secondary)', cursor: 'pointer' }, onClick: function() { toggleTab(pid, tabId); } },
|
|
284
|
+
h('input', { type: 'checkbox', checked: isTabChecked(pid, tabId), readOnly: true, style: { width: 13, height: 13, accentColor: 'var(--primary)', cursor: 'pointer' } }),
|
|
285
|
+
h('span', null, page.tabs[tabId])
|
|
286
|
+
);
|
|
287
|
+
})
|
|
288
|
+
);
|
|
289
|
+
})
|
|
290
|
+
);
|
|
291
|
+
})
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
213
295
|
// ─── Users Page ────────────────────────────────────
|
|
214
296
|
|
|
215
297
|
export function UsersPage() {
|
|
216
|
-
var
|
|
298
|
+
var app = useApp();
|
|
299
|
+
var toast = app.toast;
|
|
217
300
|
var [users, setUsers] = useState([]);
|
|
218
301
|
var [creating, setCreating] = useState(false);
|
|
219
302
|
var [form, setForm] = useState({ email: '', password: '', name: '', role: 'viewer', permissions: '*' });
|
|
@@ -261,22 +344,39 @@ export function UsersPage() {
|
|
|
261
344
|
setResetting(false);
|
|
262
345
|
};
|
|
263
346
|
|
|
264
|
-
var
|
|
347
|
+
var toggleActive = async function(user) {
|
|
348
|
+
var action = user.isActive === false ? 'reactivate' : 'deactivate';
|
|
265
349
|
var ok = await showConfirm({
|
|
266
|
-
title: '
|
|
267
|
-
message:
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
350
|
+
title: action === 'deactivate' ? 'Deactivate User' : 'Reactivate User',
|
|
351
|
+
message: action === 'deactivate'
|
|
352
|
+
? 'Deactivate "' + (user.name || user.email) + '"? They will be unable to log in and will see a message to contact their organization.'
|
|
353
|
+
: 'Reactivate "' + (user.name || user.email) + '"? They will be able to log in again.',
|
|
354
|
+
danger: action === 'deactivate',
|
|
355
|
+
confirmText: action === 'deactivate' ? 'Deactivate' : 'Reactivate'
|
|
271
356
|
});
|
|
272
357
|
if (!ok) return;
|
|
273
358
|
try {
|
|
274
|
-
await apiCall('/users/' + user.id, { method: '
|
|
275
|
-
toast('User
|
|
359
|
+
await apiCall('/users/' + user.id + '/' + action, { method: 'POST' });
|
|
360
|
+
toast('User ' + action + 'd', 'success');
|
|
276
361
|
load();
|
|
277
362
|
} catch (e) { toast(e.message, 'error'); }
|
|
278
363
|
};
|
|
279
364
|
|
|
365
|
+
var [deleteStep, setDeleteStep] = useState(0);
|
|
366
|
+
var [deleteTarget, setDeleteTarget] = useState(null);
|
|
367
|
+
var [deleteTyped, setDeleteTyped] = useState('');
|
|
368
|
+
|
|
369
|
+
var startDelete = function(user) { setDeleteTarget(user); setDeleteStep(1); setDeleteTyped(''); };
|
|
370
|
+
var cancelDelete = function() { setDeleteTarget(null); setDeleteStep(0); setDeleteTyped(''); };
|
|
371
|
+
|
|
372
|
+
var confirmDelete = async function() {
|
|
373
|
+
try {
|
|
374
|
+
await apiCall('/users/' + deleteTarget.id, { method: 'DELETE', body: JSON.stringify({ confirmationToken: 'DELETE_USER_' + deleteTarget.email }) });
|
|
375
|
+
toast('User permanently deleted', 'success');
|
|
376
|
+
cancelDelete(); load();
|
|
377
|
+
} catch (e) { toast(e.message, 'error'); }
|
|
378
|
+
};
|
|
379
|
+
|
|
280
380
|
var openPermissions = async function(user) {
|
|
281
381
|
try {
|
|
282
382
|
var d = await apiCall('/users/' + user.id + '/permissions');
|
|
@@ -357,24 +457,11 @@ export function UsersPage() {
|
|
|
357
457
|
h('button', { type: 'button', className: 'btn btn-ghost btn-sm', onClick: function() { setShowCreatePerms(!showCreatePerms); }, style: { fontSize: 11 } }, showCreatePerms ? 'Hide' : 'Customize')
|
|
358
458
|
),
|
|
359
459
|
!showCreatePerms && h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginTop: 4 } }, 'Full access (default). Click "Customize" to restrict.'),
|
|
360
|
-
showCreatePerms && pageRegistry && h(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return h('div', { key: pid, style: { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 10px', fontSize: 12, cursor: 'pointer' }, onClick: function() {
|
|
366
|
-
setForm(function(f) {
|
|
367
|
-
var current = f.permissions === '*' ? (function() { var a = {}; Object.keys(pageRegistry).forEach(function(p) { a[p] = true; }); return a; })() : Object.assign({}, f.permissions);
|
|
368
|
-
if (current[pid]) { delete current[pid]; } else { current[pid] = true; }
|
|
369
|
-
if (Object.keys(current).length === Object.keys(pageRegistry).length) return Object.assign({}, f, { permissions: '*' });
|
|
370
|
-
return Object.assign({}, f, { permissions: current });
|
|
371
|
-
});
|
|
372
|
-
} },
|
|
373
|
-
h('input', { type: 'checkbox', checked: checked, readOnly: true, style: { width: 14, height: 14, accentColor: 'var(--primary)' } }),
|
|
374
|
-
h('span', null, page.label)
|
|
375
|
-
);
|
|
376
|
-
})
|
|
377
|
-
)
|
|
460
|
+
showCreatePerms && pageRegistry && h(InlinePermissionPicker, {
|
|
461
|
+
permissions: form.permissions,
|
|
462
|
+
pageRegistry: pageRegistry,
|
|
463
|
+
onChange: function(p) { setForm(function(f) { return Object.assign({}, f, { permissions: p }); }); }
|
|
464
|
+
})
|
|
378
465
|
),
|
|
379
466
|
(form.role === 'owner' || form.role === 'admin') && h('div', { style: { marginTop: 8, padding: 8, background: 'var(--info-soft)', borderRadius: 'var(--radius)', fontSize: 11, color: 'var(--info)' } },
|
|
380
467
|
'Owner and Admin roles always have full access to all pages.'
|
|
@@ -416,18 +503,100 @@ export function UsersPage() {
|
|
|
416
503
|
onClose: function() { setPermTarget(null); }
|
|
417
504
|
}),
|
|
418
505
|
|
|
506
|
+
// 5-step delete confirmation modal
|
|
507
|
+
deleteTarget && h(Modal, {
|
|
508
|
+
title: 'Delete User — Step ' + deleteStep + ' of 5',
|
|
509
|
+
onClose: cancelDelete,
|
|
510
|
+
width: 480,
|
|
511
|
+
footer: h(Fragment, null,
|
|
512
|
+
h('button', { className: 'btn btn-secondary', onClick: deleteStep === 1 ? cancelDelete : function() { setDeleteStep(deleteStep - 1); } }, deleteStep === 1 ? 'Cancel' : 'Back'),
|
|
513
|
+
deleteStep < 5
|
|
514
|
+
? h('button', { className: 'btn btn-' + (deleteStep >= 3 ? 'danger' : 'primary'), onClick: function() { setDeleteStep(deleteStep + 1); } }, 'Continue')
|
|
515
|
+
: h('button', { className: 'btn btn-danger', onClick: confirmDelete, disabled: deleteTyped !== deleteTarget.email }, 'Permanently Delete')
|
|
516
|
+
)
|
|
517
|
+
},
|
|
518
|
+
// Step 1: Warning
|
|
519
|
+
deleteStep === 1 && h('div', null,
|
|
520
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 12, padding: 16, background: 'var(--danger-soft, rgba(220,38,38,0.08))', borderRadius: 8, marginBottom: 16 } },
|
|
521
|
+
h('svg', { width: 24, height: 24, viewBox: '0 0 24 24', fill: 'none', stroke: 'var(--danger)', strokeWidth: 2 }, h('path', { d: 'M12 9v4m0 4h.01M10.29 3.86l-8.6 14.86A2 2 0 0 0 3.4 21h17.2a2 2 0 0 0 1.71-2.98L13.71 3.86a2 2 0 0 0-3.42 0z' })),
|
|
522
|
+
h('div', null,
|
|
523
|
+
h('strong', null, 'Permanent Deletion'),
|
|
524
|
+
h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginTop: 2 } }, 'This action cannot be undone.')
|
|
525
|
+
)
|
|
526
|
+
),
|
|
527
|
+
h('p', { style: { fontSize: 13 } }, 'You are about to permanently delete the user account for:'),
|
|
528
|
+
h('div', { style: { padding: 12, background: 'var(--bg-tertiary)', borderRadius: 8, marginTop: 8 } },
|
|
529
|
+
h('strong', null, deleteTarget.name || 'Unnamed'), h('br'),
|
|
530
|
+
h('span', { style: { fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' } }, deleteTarget.email)
|
|
531
|
+
),
|
|
532
|
+
h('p', { style: { fontSize: 12, color: 'var(--text-muted)', marginTop: 12 } }, 'Consider deactivating instead — deactivated users can be reactivated later.')
|
|
533
|
+
),
|
|
534
|
+
// Step 2: Data loss
|
|
535
|
+
deleteStep === 2 && h('div', null,
|
|
536
|
+
h('h4', { style: { marginBottom: 12 } }, 'Data That Will Be Lost'),
|
|
537
|
+
h('ul', { style: { paddingLeft: 20, fontSize: 13, lineHeight: 1.8 } },
|
|
538
|
+
h('li', null, 'All login sessions will be terminated immediately'),
|
|
539
|
+
h('li', null, 'Audit log entries will be orphaned (no user reference)'),
|
|
540
|
+
h('li', null, 'Any API keys created by this user will be revoked'),
|
|
541
|
+
h('li', null, 'Permission grants and role assignments will be removed'),
|
|
542
|
+
h('li', null, '2FA configuration and backup codes will be destroyed')
|
|
543
|
+
)
|
|
544
|
+
),
|
|
545
|
+
// Step 3: Impact
|
|
546
|
+
deleteStep === 3 && h('div', null,
|
|
547
|
+
h('h4', { style: { marginBottom: 12 } }, 'Impact Assessment'),
|
|
548
|
+
h('div', { style: { padding: 12, background: 'var(--warning-soft, rgba(245,158,11,0.08))', borderRadius: 8, fontSize: 13, lineHeight: 1.6 } },
|
|
549
|
+
h('p', null, 'If this user manages or supervises any agents, those agents will lose their manager assignment.'),
|
|
550
|
+
h('p', { style: { marginTop: 8 } }, 'If this user created approval workflows, pending approvals may become orphaned.'),
|
|
551
|
+
h('p', { style: { marginTop: 8 } }, 'Any scheduled tasks or cron jobs created by this user will continue to run but cannot be modified.')
|
|
552
|
+
)
|
|
553
|
+
),
|
|
554
|
+
// Step 4: Alternative
|
|
555
|
+
deleteStep === 4 && h('div', null,
|
|
556
|
+
h('h4', { style: { marginBottom: 12 } }, 'Are You Sure?'),
|
|
557
|
+
h('div', { style: { padding: 16, background: 'var(--success-soft, rgba(21,128,61,0.08))', borderRadius: 8, marginBottom: 16 } },
|
|
558
|
+
h('strong', null, 'Recommended alternative: Deactivate'),
|
|
559
|
+
h('p', { style: { fontSize: 12, color: 'var(--text-secondary)', marginTop: 4 } }, 'Deactivating blocks login while preserving all data. The user can be reactivated at any time. This is the safe option.')
|
|
560
|
+
),
|
|
561
|
+
h('div', { style: { padding: 16, background: 'var(--danger-soft, rgba(220,38,38,0.08))', borderRadius: 8 } },
|
|
562
|
+
h('strong', null, 'Permanent deletion'),
|
|
563
|
+
h('p', { style: { fontSize: 12, color: 'var(--text-secondary)', marginTop: 4 } }, 'Removes the user and all associated data forever. There is no recovery.')
|
|
564
|
+
)
|
|
565
|
+
),
|
|
566
|
+
// Step 5: Type email to confirm
|
|
567
|
+
deleteStep === 5 && h('div', null,
|
|
568
|
+
h('h4', { style: { marginBottom: 12, color: 'var(--danger)' } }, 'Final Confirmation'),
|
|
569
|
+
h('p', { style: { fontSize: 13, marginBottom: 12 } }, 'Type the user\'s email address to confirm permanent deletion:'),
|
|
570
|
+
h('div', { style: { padding: 8, background: 'var(--bg-tertiary)', borderRadius: 6, fontFamily: 'var(--font-mono)', fontSize: 13, textAlign: 'center', marginBottom: 12 } }, deleteTarget.email),
|
|
571
|
+
h('input', {
|
|
572
|
+
className: 'input', type: 'text', value: deleteTyped,
|
|
573
|
+
onChange: function(e) { setDeleteTyped(e.target.value); },
|
|
574
|
+
placeholder: 'Type email to confirm',
|
|
575
|
+
autoFocus: true,
|
|
576
|
+
style: { fontFamily: 'var(--font-mono)', fontSize: 13, borderColor: deleteTyped === deleteTarget.email ? 'var(--danger)' : 'var(--border)' }
|
|
577
|
+
}),
|
|
578
|
+
deleteTyped && deleteTyped !== deleteTarget.email && h('div', { style: { fontSize: 11, color: 'var(--danger)', marginTop: 4 } }, 'Email does not match')
|
|
579
|
+
)
|
|
580
|
+
),
|
|
581
|
+
|
|
419
582
|
// Users table
|
|
420
583
|
h('div', { className: 'card' },
|
|
421
584
|
h('div', { className: 'card-body-flush' },
|
|
422
585
|
users.length === 0 ? h('div', { style: { padding: 24, textAlign: 'center', color: 'var(--text-muted)' } }, 'No users')
|
|
423
586
|
: h('table', null,
|
|
424
|
-
h('thead', null, h('tr', null, h('th', null, 'Name'), h('th', null, 'Email'), h('th', null, 'Role'), h('th', null, 'Access'), h('th', null, '2FA'), h('th', null, 'Created'), h('th', { style: { width:
|
|
587
|
+
h('thead', null, h('tr', null, h('th', null, 'Name'), h('th', null, 'Email'), h('th', null, 'Role'), h('th', null, 'Status'), h('th', null, 'Access'), h('th', null, '2FA'), h('th', null, 'Created'), h('th', { style: { width: 200 } }, 'Actions'))),
|
|
425
588
|
h('tbody', null, users.map(function(u) {
|
|
426
589
|
var isRestricted = u.role === 'member' || u.role === 'viewer';
|
|
427
|
-
|
|
590
|
+
var isDeactivated = u.isActive === false;
|
|
591
|
+
var isSelf = u.id === ((app || {}).user || {}).id;
|
|
592
|
+
return h('tr', { key: u.id, style: isDeactivated ? { opacity: 0.6 } : {} },
|
|
428
593
|
h('td', null, h('strong', null, u.name || '-')),
|
|
429
594
|
h('td', null, h('span', { style: { fontFamily: 'var(--font-mono)', fontSize: 12 } }, u.email)),
|
|
430
595
|
h('td', null, h('span', { className: 'badge badge-' + (u.role === 'owner' ? 'warning' : u.role === 'admin' ? 'primary' : 'neutral') }, u.role)),
|
|
596
|
+
h('td', null, isDeactivated
|
|
597
|
+
? h('span', { className: 'badge badge-danger', style: { fontSize: 10 } }, 'Deactivated')
|
|
598
|
+
: h('span', { className: 'badge badge-success', style: { fontSize: 10 } }, 'Active')
|
|
599
|
+
),
|
|
431
600
|
h('td', null, permBadge(u)),
|
|
432
601
|
h('td', null, u.totpEnabled ? h('span', { className: 'badge badge-success' }, 'On') : h('span', { className: 'badge badge-neutral' }, 'Off')),
|
|
433
602
|
h('td', { style: { fontSize: 12, color: 'var(--text-muted)' } }, u.createdAt ? new Date(u.createdAt).toLocaleDateString() : '-'),
|
|
@@ -440,7 +609,15 @@ export function UsersPage() {
|
|
|
440
609
|
style: !isRestricted ? { opacity: 0.4 } : {}
|
|
441
610
|
}, I.shield()),
|
|
442
611
|
h('button', { className: 'btn btn-ghost btn-sm', title: 'Reset Password', onClick: function() { setResetTarget(u); setNewPassword(''); } }, I.lock()),
|
|
443
|
-
|
|
612
|
+
// Deactivate / Reactivate
|
|
613
|
+
!isSelf && h('button', {
|
|
614
|
+
className: 'btn btn-ghost btn-sm',
|
|
615
|
+
title: isDeactivated ? 'Reactivate User' : 'Deactivate User',
|
|
616
|
+
onClick: function() { toggleActive(u); },
|
|
617
|
+
style: { color: isDeactivated ? 'var(--success, #15803d)' : 'var(--warning, #f59e0b)' }
|
|
618
|
+
}, isDeactivated ? I.check() : I.pause()),
|
|
619
|
+
// Delete (owner only)
|
|
620
|
+
!isSelf && h('button', { className: 'btn btn-ghost btn-sm', title: 'Delete User Permanently', onClick: function() { startDelete(u); }, style: { color: 'var(--danger)' } }, I.trash())
|
|
444
621
|
)
|
|
445
622
|
)
|
|
446
623
|
);
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
provision,
|
|
3
3
|
runSetupWizard
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-A5LURBEY.js";
|
|
5
5
|
import {
|
|
6
6
|
AgenticMailManager,
|
|
7
7
|
GoogleEmailProvider,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
requireRole,
|
|
43
43
|
securityHeaders,
|
|
44
44
|
validate
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-WQRGOMQJ.js";
|
|
46
46
|
import "./chunk-OF4MUWWS.js";
|
|
47
47
|
import {
|
|
48
48
|
PROVIDER_REGISTRY,
|
|
@@ -113,7 +113,7 @@ import {
|
|
|
113
113
|
import {
|
|
114
114
|
createAdapter,
|
|
115
115
|
getSupportedDatabases
|
|
116
|
-
} from "./chunk-
|
|
116
|
+
} from "./chunk-Y3WLLVOK.js";
|
|
117
117
|
import {
|
|
118
118
|
AGENTICMAIL_TOOLS,
|
|
119
119
|
ALL_TOOLS,
|