import { html, nothing } from 'lit'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { LightElement } from '../lib/base.js'; import { t } from '../lib/i18n.js'; export class UsersPage extends LightElement { static get properties() { return { _open: { state: true }, _users: { state: true }, _roles: { state: true }, _error: { state: true }, _modal: { state: true }, // null | { mode: 'create'|'edit'|'password', user?, form } }; } constructor() { super(); this._open = false; this._users = null; this._roles = null; this._error = null; this._modal = null; } connectedCallback() { super.connectedCallback(); this.__onLocaleChanged = () => this.requestUpdate(); window.addEventListener('locale-changed', this.__onLocaleChanged); window.addEventListener('llm-page-change', (e) => { this._open = e.detail.page === 'users'; this.style.display = this._open ? 'flex' : 'none'; if (this._open) this._load(); }); } disconnectedCallback() { window.removeEventListener('locale-changed', this.__onLocaleChanged); super.disconnectedCallback(); } async _load() { this._error = null; try { const [uRes, rRes] = await Promise.all([ fetch('/api/users'), fetch('/api/roles'), ]); if (!uRes.ok) throw new Error(`Users: HTTP ${uRes.status}`); if (!rRes.ok) throw new Error(`Roles: HTTP ${rRes.status}`); this._users = await uRes.json(); this._roles = await rRes.json(); } catch (e) { this._error = e.message; } } // ── Modal helpers ──────────────────────────────────────────────────────────── _openCreate() { this._modal = { mode: 'create', form: { username: '', display_name: '', role_id: this._roles?.[0]?.id ?? '', password: '', encrypted: false, birthdate: '', sex: '', notes: '' }, }; } _openEdit(user) { this._modal = { mode: 'edit', user, form: { username: user.username, display_name: user.display_name ?? '', role_id: user.role_id, active: user.active, birthdate: user.birthdate ?? '', sex: user.sex ?? '', notes: user.notes ?? '' }, }; } _openPassword(user) { this._modal = { mode: 'password', user, form: { password: '' } }; } _closeModal() { this._modal = null; this._error = null; } _patch(field, value) { this._modal = { ...this._modal, form: { ...this._modal.form, [field]: value } }; } // ── API actions ────────────────────────────────────────────────────────────── async _save() { const { mode, form } = this._modal; this._error = null; if (mode === 'create') { if (!form.username.trim() || !form.password) { this._error = t('users.error.required_username_pw'); return; } try { const res = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: form.username.trim(), display_name: form.display_name.trim() || null, role_id: form.role_id, password: form.password, encrypted: form.encrypted, birthdate: form.birthdate || null, sex: form.sex.trim() || null, notes: form.notes.trim() || null, }), }); if (!res.ok) throw new Error(await res.text()); this._closeModal(); await this._load(); } catch (e) { this._error = e.message; } } else if (mode === 'edit') { const { user } = this._modal; if (!form.username.trim()) { this._error = t('users.error.required_username'); return; } try { const res = await fetch(`/api/users/${user.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: form.username.trim(), display_name: form.display_name.trim() || null, role_id: form.role_id, active: form.active, birthdate: form.birthdate || null, sex: form.sex.trim() || null, notes: form.notes.trim() || null, }), }); if (!res.ok) throw new Error(await res.text()); this._closeModal(); await this._load(); } catch (e) { this._error = e.message; } } else if (mode === 'password') { const { user } = this._modal; if (!form.password) { this._error = t('users.error.password_empty'); return; } try { const res = await fetch(`/api/users/${user.id}/password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: form.password }), }); if (!res.ok) throw new Error(await res.text()); this._closeModal(); } catch (e) { this._error = e.message; } } } async _delete(user) { if (!confirm(t('users.confirm.delete', { username: user.username }))) return; try { const res = await fetch(`/api/users/${user.id}`, { method: 'DELETE' }); if (!res.ok) throw new Error(await res.text()); await this._load(); } catch (e) { this._error = e.message; } } // ── Render ────────────────────────────────────────────────────────────────── _roleLabel(roleId) { return this._roles?.find(r => r.id === roleId)?.label ?? roleId; } _profileFields(form) { return html`
${t('users.empty')}
| ${t('users.table.username')} | ${t('users.table.display_name')} | ${t('users.table.role')} | ${t('users.table.db')} | ${t('users.table.status')} | |
|---|---|---|---|---|---|
| ${u.username} | ${u.display_name ?? '—'} | ${this._roleLabel(u.role_id)} | ${u.encrypted ? html`${t('users.badge.encrypted')}` : html`${t('users.badge.cleartext')}`} | ${u.active ? html`${t('users.badge.active')}` : html`${t('users.badge.inactive')}`} |
|