Co-authored-by: gobha <jasafpro@gmail.com> Co-committed-by: gobha <jasafpro@gmail.com>
172 lines
8.3 KiB
HTML
172 lines
8.3 KiB
HTML
{{/*
|
|
Admin surface — v0.37.6: Full Preact component tree.
|
|
Replaces server-rendered DOM with a mount point.
|
|
All 24 sections are native Preact components.
|
|
*/}}
|
|
|
|
{{define "surface-admin"}}
|
|
<div id="admin-mount" class="surface-admin" style="flex-direction:column;">
|
|
{{/* Preact renders into this div. Show a loading state until it mounts. */}}
|
|
<div class="settings-placeholder" style="padding:40px;text-align:center;">Loading admin…</div>
|
|
</div>
|
|
|
|
{{/* Modals — kept outside mount for legacy compat during transition. */}}
|
|
{{/* These will be removed once all modal usage is confirmed replaced by Dialog primitives. */}}
|
|
|
|
{{/* Create User modal */}}
|
|
<div id="createUserModal" class="modal-overlay">
|
|
<div class="modal" style="max-width:440px;">
|
|
<div class="modal-header">
|
|
<h2>Create User</h2>
|
|
<button class="modal-close" onclick="sb.call('closeModal','createUserModal')">✕</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-group"><label>Username</label><input type="text" id="adminNewUsername" placeholder="username" autocomplete="off"></div>
|
|
<div class="form-group"><label>Email</label><input type="text" id="adminNewEmail" placeholder="email@example.com" autocomplete="off"></div>
|
|
<div class="form-group"><label>Password</label><input type="password" id="adminNewPassword" placeholder="min 8 chars" autocomplete="new-password"></div>
|
|
<div class="form-group"><label>Role</label><select id="adminNewRole"><option value="user">User</option><option value="admin">Admin</option></select></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn-md btn-ghost" onclick="sb.call('closeModal','createUserModal')">Cancel</button>
|
|
<button class="btn-md btn-primary" id="adminCreateUserBtn">Create User</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{/* Reset Password modal */}}
|
|
<div id="resetPwModal" class="modal-overlay">
|
|
<div class="modal" style="max-width:440px;">
|
|
<div class="modal-header">
|
|
<h2>Reset Password</h2>
|
|
<button class="modal-close" onclick="sb.call('closeModal','resetPwModal')">✕</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p id="resetPwUser" style="font-weight:600;margin-bottom:8px;"></p>
|
|
<div class="admin-warning" style="background:var(--danger-dim);border:1px solid var(--danger);border-radius:var(--radius);padding:10px 12px;font-size:12px;margin-bottom:12px;color:var(--danger);">
|
|
<strong>Warning:</strong> This will destroy the user's personal vault. All personal API keys (BYOK) will be permanently deleted.
|
|
</div>
|
|
<div class="form-group"><label>New Password</label><input type="password" id="resetPwInput" placeholder="min 8 chars" autocomplete="new-password"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn-md btn-ghost" onclick="sb.call('closeModal','resetPwModal')">Cancel</button>
|
|
<button class="btn-md btn-danger" id="resetPwConfirmBtn">Reset Password & Destroy Vault</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{/* Approve User modal */}}
|
|
<div id="approveUserModal" class="modal-overlay">
|
|
<div class="modal" style="max-width:440px;">
|
|
<div class="modal-header">
|
|
<h2 id="approveUserTitle">Approve User</h2>
|
|
<button class="modal-close" onclick="sb.call('closeModal','approveUserModal')">✕</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<input type="hidden" id="approveUserId">
|
|
<div id="approveTeamsList"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn-md btn-ghost" onclick="sb.call('closeModal','approveUserModal')">Cancel</button>
|
|
<button class="btn-md btn-primary" id="approveUserSubmitBtn">Activate User</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{/* Create Team modal */}}
|
|
<div id="createTeamModal" class="modal-overlay">
|
|
<div class="modal" style="max-width:440px;">
|
|
<div class="modal-header">
|
|
<h2>Create Team</h2>
|
|
<button class="modal-close" onclick="sb.call('closeModal','createTeamModal')">✕</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-group"><label>Name</label><input type="text" id="adminTeamName" placeholder="Team name"></div>
|
|
<div class="form-group"><label>Description</label><input type="text" id="adminTeamDesc" placeholder="Optional description"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn-md btn-ghost" onclick="sb.call('closeModal','createTeamModal')">Cancel</button>
|
|
<button class="btn-md btn-primary" id="adminCreateTeamBtn">Create Team</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{/* Create Group modal */}}
|
|
<div id="createGroupModal" class="modal-overlay">
|
|
<div class="modal" style="max-width:440px;">
|
|
<div class="modal-header">
|
|
<h2>Create Group</h2>
|
|
<button class="modal-close" onclick="sb.call('closeModal','createGroupModal')">✕</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-group"><label>Name</label><input type="text" id="adminGroupName" placeholder="Group name"></div>
|
|
<div class="form-group"><label>Description</label><input type="text" id="adminGroupDesc" placeholder="Optional description"></div>
|
|
<div style="display:flex;gap:12px;">
|
|
<div class="form-group" style="flex:1"><label>Scope</label><select id="adminGroupScope"><option value="global">Global</option><option value="team">Team</option></select></div>
|
|
<div class="form-group" id="adminGroupTeamRow" style="flex:1;display:none"><label>Team</label><select id="adminGroupTeam"></select></div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn-md btn-ghost" onclick="sb.call('closeModal','createGroupModal')">Cancel</button>
|
|
<button class="btn-md btn-primary" id="adminCreateGroupBtn">Create Group</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{/* Provider form modal */}}
|
|
<div id="providerFormModal" class="modal-overlay">
|
|
<div class="modal" style="max-width:520px;">
|
|
<div class="modal-header">
|
|
<h2 id="providerFormTitle">Add Provider</h2>
|
|
<button class="modal-close" onclick="sb.call('closeModal','providerFormModal')">✕</button>
|
|
</div>
|
|
<div class="modal-body" id="adminAddProviderForm"></div>
|
|
<div class="modal-footer">
|
|
<button class="btn-md btn-ghost" onclick="sb.call('closeModal','providerFormModal')">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{/* Persona form modal */}}
|
|
<div id="personaFormModal" class="modal-overlay">
|
|
<div class="modal" style="max-width:520px;">
|
|
<div class="modal-header">
|
|
<h2 id="personaFormTitle">Create Persona</h2>
|
|
<button class="modal-close" onclick="sb.call('closeModal','personaFormModal')">✕</button>
|
|
</div>
|
|
<div class="modal-body" id="adminAddPersonaForm"></div>
|
|
<div class="modal-footer">
|
|
<button class="btn-md btn-ghost" onclick="sb.call('closeModal','personaFormModal')">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{/* CSS: loaded via base.html (variables, layout, primitives, modals, chat, panels, surfaces, splash) */}}
|
|
{{define "css-admin"}}{{end}}
|
|
|
|
{{/* Scripts */}}
|
|
{{define "scripts-admin"}}
|
|
{{/* Inject section name for Preact routing */}}
|
|
<script nonce="{{.CSPNonce}}">
|
|
window.__SECTION__ = '{{.Section}}';
|
|
</script>
|
|
|
|
{{/* Vendor: Preact + htm → globals, then SDK boot, then admin surface */}}
|
|
<script type="module" nonce="{{.CSPNonce}}">
|
|
const { h, render } = await import('{{.BasePath}}/js/sw/vendor/preact.module.js');
|
|
const hooksModule = await import('{{.BasePath}}/js/sw/vendor/hooks.module.js');
|
|
const { default: htm } = await import('{{.BasePath}}/js/sw/vendor/htm.module.js');
|
|
const html = htm.bind(h);
|
|
window.preact = window.preact || { h, render };
|
|
window.hooks = window.hooks || hooksModule;
|
|
window.html = window.html || html;
|
|
|
|
// Boot SDK (idempotent)
|
|
const { boot } = await import('{{.BasePath}}/js/sw/sdk/index.js?v={{.Version}}');
|
|
await boot();
|
|
|
|
// Mount admin surface
|
|
await import('{{.BasePath}}/js/sw/surfaces/admin/index.js?v={{.Version}}');
|
|
</script>
|
|
{{end}}
|