149 lines
5.2 KiB
JavaScript
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 };
|