@agenticmail/enterprise 0.5.294 → 0.5.296
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-3YLLWCUC.js +1519 -0
- package/dist/chunk-HGSWCMB7.js +3952 -0
- package/dist/chunk-KWW53O2B.js +48 -0
- package/dist/cli-agent-BCISHZTV.js +1778 -0
- package/dist/cli-recover-PMJRFJNY.js +487 -0
- package/dist/cli-serve-TTN3XR4Q.js +143 -0
- package/dist/cli-verify-HYUKQELV.js +149 -0
- package/dist/cli.js +5 -5
- package/dist/dashboard/app.js +64 -1
- package/dist/dashboard/pages/login.js +148 -1
- package/dist/dashboard/pages/users.js +124 -8
- package/dist/factory-NTLTU26R.js +9 -0
- package/dist/index.js +3 -3
- package/dist/postgres-PO2XULHX.js +768 -0
- package/dist/server-ADSJRMMF.js +15 -0
- package/dist/setup-2VN7D4OT.js +20 -0
- package/dist/sqlite-C5PV4SCD.js +499 -0
- package/package.json +1 -1
- package/src/admin/routes.ts +29 -0
- package/src/auth/routes.ts +105 -2
- package/src/dashboard/app.js +64 -1
- package/src/dashboard/pages/login.js +148 -1
- package/src/dashboard/pages/users.js +124 -8
- package/src/db/adapter.ts +1 -0
- package/src/db/postgres.ts +2 -0
- package/src/db/sqlite.ts +2 -3
|
@@ -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-NTLTU26R.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-NTLTU26R.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-PMJRFJNY.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-HYUKQELV.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-TTN3XR4Q.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-BCISHZTV.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-2VN7D4OT.js").then((m) => m.runSetupWizard()).catch(fatal);
|
|
68
68
|
break;
|
|
69
69
|
}
|
|
70
70
|
function fatal(err) {
|
package/dist/dashboard/app.js
CHANGED
|
@@ -94,6 +94,12 @@ function App() {
|
|
|
94
94
|
const [user, setUser] = useState(null);
|
|
95
95
|
const [pendingCount, setPendingCount] = useState(0);
|
|
96
96
|
const [permissions, setPermissions] = useState('*'); // '*' = full access, or { pageId: true | ['tab1','tab2'] }
|
|
97
|
+
const [mustResetPassword, setMustResetPassword] = useState(false);
|
|
98
|
+
const [show2faReminder, setShow2faReminder] = useState(false);
|
|
99
|
+
const [forceResetPw, setForceResetPw] = useState('');
|
|
100
|
+
const [forceResetPw2, setForceResetPw2] = useState('');
|
|
101
|
+
const [forceResetLoading, setForceResetLoading] = useState(false);
|
|
102
|
+
const [forceResetError, setForceResetError] = useState('');
|
|
97
103
|
const [needsSetup, setNeedsSetup] = useState(null);
|
|
98
104
|
const [sidebarPinned, setSidebarPinned] = useState(() => localStorage.getItem('em_sidebar_pinned') === 'true');
|
|
99
105
|
const [sidebarHovered, setSidebarHovered] = useState(false);
|
|
@@ -150,7 +156,54 @@ function App() {
|
|
|
150
156
|
|
|
151
157
|
if (!authChecked) return h('div', { style: { minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--bg-primary)', color: 'var(--text-muted)' } }, 'Loading...');
|
|
152
158
|
if (needsSetup === true && !authed) return h(OnboardingWizard, { onComplete: () => { setNeedsSetup(false); setAuthed(true); authCall('/me').then(d => { setUser(d.user || d); }).catch(() => {}); } });
|
|
153
|
-
if (!authed) return h(LoginPage, { onLogin: (d) => {
|
|
159
|
+
if (!authed) return h(LoginPage, { onLogin: (d) => {
|
|
160
|
+
setAuthed(true);
|
|
161
|
+
if (d?.user) { setUser(d.user); if (!d.user.totpEnabled) setShow2faReminder(true); }
|
|
162
|
+
if (d?.mustResetPassword) setMustResetPassword(true);
|
|
163
|
+
} });
|
|
164
|
+
|
|
165
|
+
// Force password reset modal
|
|
166
|
+
const doForceReset = async () => {
|
|
167
|
+
if (forceResetPw !== forceResetPw2) { setForceResetError('Passwords do not match'); return; }
|
|
168
|
+
if (forceResetPw.length < 8) { setForceResetError('Password must be at least 8 characters'); return; }
|
|
169
|
+
setForceResetLoading(true); setForceResetError('');
|
|
170
|
+
try {
|
|
171
|
+
await authCall('/force-reset-password', { method: 'POST', body: JSON.stringify({ newPassword: forceResetPw }) });
|
|
172
|
+
setMustResetPassword(false);
|
|
173
|
+
toast('Password updated successfully', 'success');
|
|
174
|
+
} catch (e) { setForceResetError(e.message); }
|
|
175
|
+
setForceResetLoading(false);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
if (mustResetPassword) {
|
|
179
|
+
return h('div', { style: { minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--bg-primary)', padding: 20 } },
|
|
180
|
+
h('div', { style: { maxWidth: 420, width: '100%', background: 'var(--bg-secondary)', borderRadius: 12, padding: 32, border: '1px solid var(--border)' } },
|
|
181
|
+
h('div', { style: { textAlign: 'center', marginBottom: 24 } },
|
|
182
|
+
h('div', { style: { width: 48, height: 48, borderRadius: '50%', background: 'var(--warning-soft, rgba(245,158,11,0.1))', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 12px' } },
|
|
183
|
+
h('svg', { width: 24, height: 24, viewBox: '0 0 24 24', fill: 'none', stroke: 'var(--warning, #f59e0b)', strokeWidth: 2, strokeLinecap: 'round' },
|
|
184
|
+
h('path', { d: 'M12 9v4m0 4h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z' })
|
|
185
|
+
)
|
|
186
|
+
),
|
|
187
|
+
h('h2', { style: { fontSize: 18, fontWeight: 700 } }, 'Password Reset Required'),
|
|
188
|
+
h('p', { style: { color: 'var(--text-muted)', fontSize: 13, marginTop: 4 } }, 'Your administrator created this account with a temporary password. Please set a new password to continue.')
|
|
189
|
+
),
|
|
190
|
+
h('div', { style: { display: 'flex', flexDirection: 'column', gap: 12 } },
|
|
191
|
+
h('div', null,
|
|
192
|
+
h('label', { style: { fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)', display: 'block', marginBottom: 4 } }, 'New Password'),
|
|
193
|
+
h('input', { className: 'input', type: 'password', value: forceResetPw, onChange: (e) => setForceResetPw(e.target.value), placeholder: 'Min 8 characters', autoFocus: true })
|
|
194
|
+
),
|
|
195
|
+
h('div', null,
|
|
196
|
+
h('label', { style: { fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)', display: 'block', marginBottom: 4 } }, 'Confirm Password'),
|
|
197
|
+
h('input', { className: 'input', type: 'password', value: forceResetPw2, onChange: (e) => setForceResetPw2(e.target.value), placeholder: 'Confirm new password', onKeyDown: (e) => { if (e.key === 'Enter') doForceReset(); } })
|
|
198
|
+
),
|
|
199
|
+
forceResetError && h('div', { style: { color: 'var(--danger)', fontSize: 12 } }, forceResetError),
|
|
200
|
+
h('button', { className: 'btn btn-primary', onClick: doForceReset, disabled: forceResetLoading || !forceResetPw || !forceResetPw2, style: { width: '100%', justifyContent: 'center', marginTop: 4 } },
|
|
201
|
+
forceResetLoading ? 'Updating...' : 'Set New Password'
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
);
|
|
206
|
+
}
|
|
154
207
|
|
|
155
208
|
const nav = [
|
|
156
209
|
{ section: 'Overview', items: [{ id: 'dashboard', icon: I.dashboard, label: 'Dashboard' }] },
|
|
@@ -279,6 +332,16 @@ function App() {
|
|
|
279
332
|
)
|
|
280
333
|
),
|
|
281
334
|
h('div', { className: 'page-content' },
|
|
335
|
+
// 2FA recommendation banner
|
|
336
|
+
show2faReminder && h('div', { style: { display: 'flex', alignItems: 'center', gap: 12, padding: '10px 16px', margin: '0 0 16px', background: 'var(--warning-soft, rgba(245,158,11,0.1))', border: '1px solid var(--warning, #f59e0b)', borderRadius: 8, fontSize: 13 } },
|
|
337
|
+
I.shield(),
|
|
338
|
+
h('div', { style: { flex: 1 } },
|
|
339
|
+
h('strong', null, 'Enable Two-Factor Authentication'),
|
|
340
|
+
h('span', { style: { color: 'var(--text-secondary)', marginLeft: 6 } }, 'Protect your account and enable self-service password reset.')
|
|
341
|
+
),
|
|
342
|
+
h('button', { className: 'btn btn-warning btn-sm', onClick: () => { setPage('settings'); setShow2faReminder(false); history.pushState(null, '', '/dashboard/settings'); } }, 'Set Up 2FA'),
|
|
343
|
+
h('button', { className: 'btn btn-ghost btn-sm', onClick: () => setShow2faReminder(false), style: { padding: '2px 6px', minWidth: 0 } }, '\u00d7')
|
|
344
|
+
),
|
|
282
345
|
selectedAgentId
|
|
283
346
|
? h(AgentDetailPage, { agentId: selectedAgentId, onBack: () => { _setSelectedAgentId(null); _setPage('agents'); history.pushState(null, '', '/dashboard/agents'); } })
|
|
284
347
|
: page === 'agents'
|
|
@@ -20,6 +20,16 @@ export function LoginPage({ onLogin }) {
|
|
|
20
20
|
var [challengeToken, setChallengeToken] = useState('');
|
|
21
21
|
var [totpCode, setTotpCode] = useState('');
|
|
22
22
|
|
|
23
|
+
// Forgot password state
|
|
24
|
+
var [forgotMode, setForgotMode] = useState(false); // show forgot password form
|
|
25
|
+
var [forgotEmail, setForgotEmail] = useState('');
|
|
26
|
+
var [forgotCode, setForgotCode] = useState('');
|
|
27
|
+
var [forgotNewPw, setForgotNewPw] = useState('');
|
|
28
|
+
var [forgotNewPw2, setForgotNewPw2] = useState('');
|
|
29
|
+
var [forgotStep, setForgotStep] = useState('email'); // 'email' | 'code' | 'no2fa' | 'done'
|
|
30
|
+
var [forgotLoading, setForgotLoading] = useState(false);
|
|
31
|
+
var [forgotError, setForgotError] = useState('');
|
|
32
|
+
|
|
23
33
|
useEffect(function() {
|
|
24
34
|
fetch('/auth/sso/providers').then(function(r) { return r.ok ? r.json() : null; }).then(function(d) {
|
|
25
35
|
if (d && d.providers && d.providers.length > 0) setSsoProviders(d.providers);
|
|
@@ -59,6 +69,43 @@ export function LoginPage({ onLogin }) {
|
|
|
59
69
|
setLoading(false);
|
|
60
70
|
};
|
|
61
71
|
|
|
72
|
+
var submitForgotEmail = async function() {
|
|
73
|
+
setForgotLoading(true); setForgotError('');
|
|
74
|
+
try {
|
|
75
|
+
// Check if user has 2FA by attempting reset without code
|
|
76
|
+
var d = await authCall('/reset-password-self', { method: 'POST', body: JSON.stringify({ email: forgotEmail, newPassword: 'check__only__12', totpCode: '' }) });
|
|
77
|
+
if (d.has2fa) { setForgotStep('code'); }
|
|
78
|
+
else if (d.no2fa) { setForgotStep('no2fa'); }
|
|
79
|
+
else { setForgotStep('code'); }
|
|
80
|
+
} catch (err) {
|
|
81
|
+
var msg = err.message || '';
|
|
82
|
+
if (msg.indexOf('not enabled') >= 0 || msg.indexOf('administrator') >= 0) {
|
|
83
|
+
setForgotStep('no2fa');
|
|
84
|
+
} else {
|
|
85
|
+
setForgotStep('code');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
setForgotLoading(false);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
var submitForgotReset = async function() {
|
|
92
|
+
if (forgotNewPw !== forgotNewPw2) { setForgotError('Passwords do not match'); return; }
|
|
93
|
+
if (forgotNewPw.length < 8) { setForgotError('Password must be at least 8 characters'); return; }
|
|
94
|
+
setForgotLoading(true); setForgotError('');
|
|
95
|
+
try {
|
|
96
|
+
var d = await authCall('/reset-password-self', { method: 'POST', body: JSON.stringify({ email: forgotEmail, totpCode: forgotCode, newPassword: forgotNewPw }) });
|
|
97
|
+
if (d.ok) { setForgotStep('done'); }
|
|
98
|
+
else if (d.no2fa) { setForgotStep('no2fa'); setForgotError(d.error); }
|
|
99
|
+
else if (d.error) { setForgotError(d.error); }
|
|
100
|
+
} catch (err) { setForgotError(err.message); }
|
|
101
|
+
setForgotLoading(false);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
var cancelForgot = function() {
|
|
105
|
+
setForgotMode(false); setForgotStep('email'); setForgotEmail(''); setForgotCode('');
|
|
106
|
+
setForgotNewPw(''); setForgotNewPw2(''); setForgotError('');
|
|
107
|
+
};
|
|
108
|
+
|
|
62
109
|
var cancel2fa = function() {
|
|
63
110
|
setNeeds2fa(false);
|
|
64
111
|
setChallengeToken('');
|
|
@@ -110,6 +157,103 @@ export function LoginPage({ onLogin }) {
|
|
|
110
157
|
);
|
|
111
158
|
}
|
|
112
159
|
|
|
160
|
+
// ─── Forgot Password Screen ──────────────────────────
|
|
161
|
+
|
|
162
|
+
if (forgotMode) {
|
|
163
|
+
return h('div', { className: 'login-page', style: _brandBg ? { backgroundImage: 'url(' + _brandBg + ')', backgroundSize: 'cover', backgroundPosition: 'center' } : {} },
|
|
164
|
+
h('div', { className: 'login-card' },
|
|
165
|
+
h('div', { className: 'login-logo' },
|
|
166
|
+
h('img', { src: _brandLogo, alt: 'AgenticMail', style: { width: 48, height: 48, objectFit: 'contain' } }),
|
|
167
|
+
h('h1', null, 'Reset Password'),
|
|
168
|
+
h('p', null, forgotStep === 'email' ? 'Enter your email address' : forgotStep === 'code' ? 'Verify with your authenticator app' : forgotStep === 'done' ? 'Password updated' : 'Contact your administrator')
|
|
169
|
+
),
|
|
170
|
+
|
|
171
|
+
// Step: enter email
|
|
172
|
+
forgotStep === 'email' && h('div', null,
|
|
173
|
+
h('div', { className: 'form-group' },
|
|
174
|
+
h('label', { className: 'form-label' }, 'Email Address'),
|
|
175
|
+
h('input', { className: 'input', type: 'email', value: forgotEmail, onChange: function(e) { setForgotEmail(e.target.value); }, placeholder: 'you@company.com', autoFocus: true })
|
|
176
|
+
),
|
|
177
|
+
forgotError && h('div', { style: { color: 'var(--danger)', fontSize: 13, marginBottom: 12 } }, forgotError),
|
|
178
|
+
h('button', { className: 'btn btn-primary', onClick: submitForgotEmail, disabled: forgotLoading || !forgotEmail, style: { width: '100%', justifyContent: 'center', padding: '8px' } }, forgotLoading ? 'Checking...' : 'Continue'),
|
|
179
|
+
h('div', { style: { textAlign: 'center', marginTop: 16 } },
|
|
180
|
+
h('button', { type: 'button', className: 'btn btn-ghost btn-sm', onClick: cancelForgot }, 'Back to login')
|
|
181
|
+
)
|
|
182
|
+
),
|
|
183
|
+
|
|
184
|
+
// Step: enter 2FA code + new password
|
|
185
|
+
forgotStep === 'code' && h('div', null,
|
|
186
|
+
h('div', { style: { background: 'var(--info-soft, rgba(59,130,246,0.1))', borderRadius: 8, padding: 12, marginBottom: 16, fontSize: 12, color: 'var(--text-secondary)' } },
|
|
187
|
+
'Enter the 6-digit code from your authenticator app (or a backup code) along with your new password.'
|
|
188
|
+
),
|
|
189
|
+
h('div', { className: 'form-group' },
|
|
190
|
+
h('label', { className: 'form-label' }, '2FA Code'),
|
|
191
|
+
h('input', {
|
|
192
|
+
className: 'input', type: 'text', inputMode: 'numeric', autoComplete: 'one-time-code',
|
|
193
|
+
value: forgotCode, onChange: function(e) { setForgotCode(e.target.value.replace(/[^0-9A-Za-z]/g, '').slice(0, 8)); },
|
|
194
|
+
placeholder: '000000', autoFocus: true, maxLength: 8,
|
|
195
|
+
style: { textAlign: 'center', fontSize: 20, letterSpacing: '0.2em', fontFamily: 'var(--font-mono)' }
|
|
196
|
+
})
|
|
197
|
+
),
|
|
198
|
+
h('div', { className: 'form-group' },
|
|
199
|
+
h('label', { className: 'form-label' }, 'New Password'),
|
|
200
|
+
h('input', { className: 'input', type: 'password', value: forgotNewPw, onChange: function(e) { setForgotNewPw(e.target.value); }, placeholder: 'Min 8 characters' })
|
|
201
|
+
),
|
|
202
|
+
h('div', { className: 'form-group' },
|
|
203
|
+
h('label', { className: 'form-label' }, 'Confirm Password'),
|
|
204
|
+
h('input', { className: 'input', type: 'password', value: forgotNewPw2, onChange: function(e) { setForgotNewPw2(e.target.value); }, placeholder: 'Confirm new password' })
|
|
205
|
+
),
|
|
206
|
+
forgotError && h('div', { style: { color: 'var(--danger)', fontSize: 13, marginBottom: 12 } }, forgotError),
|
|
207
|
+
h('button', { className: 'btn btn-primary', onClick: submitForgotReset, disabled: forgotLoading || !forgotCode || !forgotNewPw || !forgotNewPw2, style: { width: '100%', justifyContent: 'center', padding: '8px' } }, forgotLoading ? 'Resetting...' : 'Reset Password'),
|
|
208
|
+
h('div', { style: { textAlign: 'center', marginTop: 16 } },
|
|
209
|
+
h('button', { type: 'button', className: 'btn btn-ghost btn-sm', onClick: cancelForgot }, 'Back to login')
|
|
210
|
+
)
|
|
211
|
+
),
|
|
212
|
+
|
|
213
|
+
// Step: no 2FA — contact admin
|
|
214
|
+
forgotStep === 'no2fa' && h('div', null,
|
|
215
|
+
h('div', { style: { textAlign: 'center', padding: '12px 0' } },
|
|
216
|
+
h('div', { style: { width: 48, height: 48, borderRadius: '50%', background: 'var(--danger-soft, rgba(220,38,38,0.1))', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 12px' } },
|
|
217
|
+
h('svg', { width: 24, height: 24, viewBox: '0 0 24 24', fill: 'none', stroke: 'var(--danger, #dc2626)', strokeWidth: 2, strokeLinecap: 'round' },
|
|
218
|
+
h('path', { d: 'M12 9v4m0 4h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z' })
|
|
219
|
+
)
|
|
220
|
+
),
|
|
221
|
+
h('h3', { style: { fontSize: 16, fontWeight: 600, marginBottom: 8 } }, 'Cannot Reset Password'),
|
|
222
|
+
h('p', { style: { fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.6, maxWidth: 320, margin: '0 auto' } },
|
|
223
|
+
'Two-factor authentication is not enabled on this account. Without 2FA, you cannot reset your password yourself.'
|
|
224
|
+
),
|
|
225
|
+
h('div', { style: { marginTop: 16, padding: 12, background: 'var(--bg-tertiary)', borderRadius: 8, fontSize: 13 } },
|
|
226
|
+
h('strong', null, 'What to do:'), h('br', null),
|
|
227
|
+
'Contact your organization administrator and ask them to reset your password from the Users page.'
|
|
228
|
+
),
|
|
229
|
+
h('div', { style: { marginTop: 16, padding: 10, background: 'var(--warning-soft, rgba(245,158,11,0.08))', borderRadius: 8, fontSize: 12, color: 'var(--text-secondary)' } },
|
|
230
|
+
'Tip: Once you regain access, enable 2FA immediately so you can reset your own password in the future.'
|
|
231
|
+
)
|
|
232
|
+
),
|
|
233
|
+
h('div', { style: { textAlign: 'center', marginTop: 16 } },
|
|
234
|
+
h('button', { type: 'button', className: 'btn btn-primary', onClick: cancelForgot, style: { width: '100%', justifyContent: 'center' } }, 'Back to Login')
|
|
235
|
+
)
|
|
236
|
+
),
|
|
237
|
+
|
|
238
|
+
// Step: done
|
|
239
|
+
forgotStep === 'done' && h('div', null,
|
|
240
|
+
h('div', { style: { textAlign: 'center', padding: '12px 0' } },
|
|
241
|
+
h('div', { style: { width: 48, height: 48, borderRadius: '50%', background: 'var(--success-soft, rgba(21,128,61,0.1))', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 12px' } },
|
|
242
|
+
h('svg', { width: 24, height: 24, viewBox: '0 0 24 24', fill: 'none', stroke: 'var(--success, #15803d)', strokeWidth: 2, strokeLinecap: 'round' },
|
|
243
|
+
h('path', { d: 'M20 6L9 17l-5-5' })
|
|
244
|
+
)
|
|
245
|
+
),
|
|
246
|
+
h('h3', { style: { fontSize: 16, fontWeight: 600, marginBottom: 8 } }, 'Password Reset Successfully'),
|
|
247
|
+
h('p', { style: { fontSize: 13, color: 'var(--text-muted)' } }, 'You can now sign in with your new password.')
|
|
248
|
+
),
|
|
249
|
+
h('div', { style: { textAlign: 'center', marginTop: 16 } },
|
|
250
|
+
h('button', { type: 'button', className: 'btn btn-primary', onClick: cancelForgot, style: { width: '100%', justifyContent: 'center' } }, 'Sign In')
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
113
257
|
// ─── Main Login Screen ────────────────────────────────
|
|
114
258
|
|
|
115
259
|
return h('div', { className: 'login-page', style: _brandBg ? { backgroundImage: 'url(' + _brandBg + ')', backgroundSize: 'cover', backgroundPosition: 'center' } : {} },
|
|
@@ -138,7 +282,10 @@ export function LoginPage({ onLogin }) {
|
|
|
138
282
|
h('input', { className: 'input', type: 'password', value: password, onChange: function(e) { setPassword(e.target.value); }, placeholder: 'Enter password', required: true })
|
|
139
283
|
),
|
|
140
284
|
error && h('div', { style: { color: 'var(--danger)', fontSize: 13, marginBottom: 16 } }, error),
|
|
141
|
-
h('button', { className: 'btn btn-primary', type: 'submit', disabled: loading, style: { width: '100%', justifyContent: 'center', padding: '8px' } }, loading ? 'Signing in...' : 'Sign In')
|
|
285
|
+
h('button', { className: 'btn btn-primary', type: 'submit', disabled: loading, style: { width: '100%', justifyContent: 'center', padding: '8px' } }, loading ? 'Signing in...' : 'Sign In'),
|
|
286
|
+
h('div', { style: { textAlign: 'center', marginTop: 12 } },
|
|
287
|
+
h('button', { type: 'button', className: 'btn btn-ghost btn-sm', onClick: function() { setForgotMode(true); setForgotEmail(email); setError(''); }, style: { fontSize: 12, color: 'var(--text-muted)' } }, 'Forgot Password?')
|
|
288
|
+
)
|
|
142
289
|
),
|
|
143
290
|
|
|
144
291
|
// ── API Key Tab ─────────────────────────────────
|
|
@@ -210,13 +210,95 @@ 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
298
|
var { toast } = useApp();
|
|
217
299
|
var [users, setUsers] = useState([]);
|
|
218
300
|
var [creating, setCreating] = useState(false);
|
|
219
|
-
var [form, setForm] = useState({ email: '', password: '', name: '', role: 'viewer' });
|
|
301
|
+
var [form, setForm] = useState({ email: '', password: '', name: '', role: 'viewer', permissions: '*' });
|
|
220
302
|
var [resetTarget, setResetTarget] = useState(null);
|
|
221
303
|
var [newPassword, setNewPassword] = useState('');
|
|
222
304
|
var [resetting, setResetting] = useState(false);
|
|
@@ -230,10 +312,22 @@ export function UsersPage() {
|
|
|
230
312
|
apiCall('/page-registry').then(function(d) { setPageRegistry(d); }).catch(function() {});
|
|
231
313
|
}, []);
|
|
232
314
|
|
|
315
|
+
var generateCreatePassword = function() {
|
|
316
|
+
var chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%';
|
|
317
|
+
var pw = '';
|
|
318
|
+
for (var i = 0; i < 16; i++) pw += chars[Math.floor(Math.random() * chars.length)];
|
|
319
|
+
setForm(function(f) { return Object.assign({}, f, { password: pw }); });
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
var [showCreatePerms, setShowCreatePerms] = useState(false);
|
|
323
|
+
|
|
233
324
|
var create = async function() {
|
|
234
325
|
try {
|
|
235
|
-
|
|
236
|
-
|
|
326
|
+
var body = { email: form.email, password: form.password, name: form.name, role: form.role };
|
|
327
|
+
if (form.permissions !== '*') body.permissions = form.permissions;
|
|
328
|
+
await apiCall('/users', { method: 'POST', body: JSON.stringify(body) });
|
|
329
|
+
toast('User created. They will be prompted to set a new password on first login.', 'success');
|
|
330
|
+
setCreating(false); setForm({ email: '', password: '', name: '', role: 'viewer', permissions: '*' }); setShowCreatePerms(false); load();
|
|
237
331
|
} catch (e) { toast(e.message, 'error'); }
|
|
238
332
|
};
|
|
239
333
|
|
|
@@ -324,13 +418,35 @@ export function UsersPage() {
|
|
|
324
418
|
),
|
|
325
419
|
|
|
326
420
|
// Create user modal
|
|
327
|
-
creating && h(Modal, { title: 'Add User', onClose: function() { setCreating(false); }, footer: h(Fragment, null, h('button', { className: 'btn btn-secondary', onClick: function() { setCreating(false); } }, 'Cancel'), h('button', { className: 'btn btn-primary', onClick: create, disabled: !form.email || !form.password }, 'Create')) },
|
|
328
|
-
h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Name'), h('input', { className: 'input', value: form.name, onChange: function(e) { setForm(function(f) { return Object.assign({}, f, { name: e.target.value }); }); } })),
|
|
421
|
+
creating && h(Modal, { title: 'Add User', onClose: function() { setCreating(false); setShowCreatePerms(false); }, width: 520, footer: h(Fragment, null, h('button', { className: 'btn btn-secondary', onClick: function() { setCreating(false); setShowCreatePerms(false); } }, 'Cancel'), h('button', { className: 'btn btn-primary', onClick: create, disabled: !form.email || !form.password }, 'Create User')) },
|
|
422
|
+
h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Name'), h('input', { className: 'input', value: form.name, onChange: function(e) { setForm(function(f) { return Object.assign({}, f, { name: e.target.value }); }); }, autoFocus: true })),
|
|
329
423
|
h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Email *'), h('input', { className: 'input', type: 'email', value: form.email, onChange: function(e) { setForm(function(f) { return Object.assign({}, f, { email: e.target.value }); }); } })),
|
|
330
|
-
h('div', { className: 'form-group' },
|
|
424
|
+
h('div', { className: 'form-group' },
|
|
425
|
+
h('label', { className: 'form-label' }, 'Initial Password *'),
|
|
426
|
+
h('div', { style: { display: 'flex', gap: 8 } },
|
|
427
|
+
h('input', { className: 'input', type: 'text', value: form.password, onChange: function(e) { setForm(function(f) { return Object.assign({}, f, { password: e.target.value }); }); }, placeholder: 'Min 8 characters', style: { flex: 1, fontFamily: 'var(--font-mono)', fontSize: 13 } }),
|
|
428
|
+
h('button', { type: 'button', className: 'btn btn-secondary btn-sm', onClick: generateCreatePassword, title: 'Generate random password', style: { whiteSpace: 'nowrap' } }, I.refresh(), ' Generate')
|
|
429
|
+
),
|
|
430
|
+
form.password && h('div', { style: { marginTop: 6, padding: 8, background: 'var(--warning-soft, rgba(245,158,11,0.08))', borderRadius: 6, fontSize: 11, color: 'var(--text-secondary)' } },
|
|
431
|
+
'The user will be required to change this password on their first login. Share it securely.'
|
|
432
|
+
)
|
|
433
|
+
),
|
|
331
434
|
h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Role'), h('select', { className: 'input', value: form.role, onChange: function(e) { setForm(function(f) { return Object.assign({}, f, { role: e.target.value }); }); } }, h('option', { value: 'viewer' }, 'Viewer'), h('option', { value: 'member' }, 'Member'), h('option', { value: 'admin' }, 'Admin'), h('option', { value: 'owner' }, 'Owner'))),
|
|
332
|
-
|
|
333
|
-
|
|
435
|
+
// Inline permissions for member/viewer
|
|
436
|
+
(form.role === 'member' || form.role === 'viewer') && h('div', { style: { marginTop: 4 } },
|
|
437
|
+
h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' } },
|
|
438
|
+
h('label', { className: 'form-label', style: { marginBottom: 0 } }, 'Page Permissions'),
|
|
439
|
+
h('button', { type: 'button', className: 'btn btn-ghost btn-sm', onClick: function() { setShowCreatePerms(!showCreatePerms); }, style: { fontSize: 11 } }, showCreatePerms ? 'Hide' : 'Customize')
|
|
440
|
+
),
|
|
441
|
+
!showCreatePerms && h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginTop: 4 } }, 'Full access (default). Click "Customize" to restrict.'),
|
|
442
|
+
showCreatePerms && pageRegistry && h(InlinePermissionPicker, {
|
|
443
|
+
permissions: form.permissions,
|
|
444
|
+
pageRegistry: pageRegistry,
|
|
445
|
+
onChange: function(p) { setForm(function(f) { return Object.assign({}, f, { permissions: p }); }); }
|
|
446
|
+
})
|
|
447
|
+
),
|
|
448
|
+
(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)' } },
|
|
449
|
+
'Owner and Admin roles always have full access to all pages.'
|
|
334
450
|
)
|
|
335
451
|
),
|
|
336
452
|
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
provision,
|
|
3
3
|
runSetupWizard
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3YLLWCUC.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-HGSWCMB7.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-KWW53O2B.js";
|
|
117
117
|
import {
|
|
118
118
|
AGENTICMAIL_TOOLS,
|
|
119
119
|
ALL_TOOLS,
|