/* * User administration view. * * UserAdmin owns the user list, its editable controls and the new-user form. * The application remains responsible for permissions, routing, breadcrumbs * and making the surrounding view visible. */ class UserAdmin { /** * goal : Connect the user administration controls to the wiki API. * pre : api performs authenticated requests; translate resolves UI keys; * the user administration form and list exist in the document. * post : Submitting the new-user form creates a user and reloads the list. * result : A directly usable controller for the user administration view. */ constructor(api, translate) { this.api = api; this.translate = translate; this.form = document.getElementById("new-user-form"); this.list = document.getElementById("user-list"); this.form.addEventListener("submit", (event) => { this.#createUser(event).catch((error) => console.error(error)); }); } /** * goal : Refresh the editable user list from the server. * pre : The current session has administrator permission. * post : Every current user has editable identity, role and password fields. * result : A promise that resolves when all rows have been rendered. * internals: The API supplies public user records. #renderUser creates each * row and connects its save and delete operations to that record. */ async load() { const result = await this.api("/api/admin/users"); this.list.replaceChildren(); for (const user of result.users) { this.list.append(this.#renderUser(user)); } } /** Create a user from the dedicated form and reload the canonical list. */ async #createUser(event) { event.preventDefault(); const username = document.getElementById("new-username").value; await this.api("/api/admin/users", { method: "POST", body: JSON.stringify({ username, displayName: document.getElementById("new-display-name").value || username, email: document.getElementById("new-email").value, password: document.getElementById("new-password").value, role: document.getElementById("new-role").value, enabled: true }) }); this.form.reset(); await this.load(); } /** Build one editable user row and connect its actions. */ #renderUser(user) { const row = document.createElement("div"); row.className = "user-row"; const username = document.createElement("strong"); username.textContent = user.username; const displayName = document.createElement("input"); displayName.value = user.displayName; const email = document.createElement("input"); email.type = "email"; email.value = user.email || ""; email.placeholder = this.translate("email-address", "Email address"); const role = this.#roleSelect(user.role); const enabled = document.createElement("input"); enabled.type = "checkbox"; enabled.checked = user.enabled; const password = document.createElement("input"); password.type = "password"; password.placeholder = this.translate("new-password", "New password"); password.autocomplete = "new-password"; const save = document.createElement("button"); save.textContent = this.translate("save", "Save"); save.addEventListener("click", () => { this.#updateUser(user, displayName, email, role, enabled, password) .catch((error) => console.error(error)); }); const remove = document.createElement("button"); remove.textContent = this.translate("delete", "Delete"); remove.className = "danger"; remove.addEventListener("click", () => { this.#deleteUser(user).catch((error) => console.error(error)); }); const actions = document.createElement("div"); actions.append(save, remove); row.append(username, displayName, email, role, enabled, password, actions); return row; } /** Build the translated role selector for one user row. */ #roleSelect(selectedRole) { const select = document.createElement("select"); for (const roleName of ["reader", "editor", "admin"]) { const option = document.createElement("option"); option.value = roleName; option.textContent = this.translate(`role-${roleName}`, roleName); option.selected = roleName === selectedRole; select.append(option); } return select; } /** Store the current controls of one user row. */ async #updateUser(user, displayName, email, role, enabled, password) { await this.api(`/api/admin/users/${user.id}`, { method: "PUT", body: JSON.stringify({ displayName: displayName.value, email: email.value, role: role.value, enabled: enabled.checked, password: password.value || undefined }) }); password.value = ""; } /** Confirm and remove one user, then reload the canonical list. */ async #deleteUser(user) { const question = this.translate("delete-user-confirm", "Delete user {username}?") .replace("{username}", user.username); if (!window.confirm(question)) return; await this.api(`/api/admin/users/${user.id}`, { method: "DELETE" }); await this.load(); } } export { UserAdmin };