Files
racket-wiki/static/js/wiki/admin/user-admin.js
T
2026-09-02 08:38:03 +02:00

149 lines
5.2 KiB
JavaScript

/*
* 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 };