%PDF- %PDF-
Direktori : /www/varak.net/dmarc.varak.net/public/js/ |
Current File : //www/varak.net/dmarc.varak.net/public/js/users.js |
/** * dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports. * Copyright (C) 2023-2025 Aleksey Andreev (liuch) * * Available at: * https://github.com/liuch/dmarc-srg * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ const User = { name: null, level: null, auth_type: null }; class UserList { constructor() { this._page = null; this._table = null; this._scroll = null; this._element = document.getElementById("main-block"); this._sort = { column: "name", direction: "ascent" }; } display() { this._make_page_container(); this._make_scroll_container(); this._make_table(); this._scroll.append(this._table.element()); this._page.append(this._scroll); this._element.appendChild(this._page); this._table.focus(); } update() { this._fetch_list(); } title() { return "User List"; } _fetch_list() { this._table.display_status("wait"); return window.fetch("users.php", { method: "GET", cache: "no-store", headers: HTTP_HEADERS, credentials: "same-origin" }).then(resp => { if (!resp.ok) throw new Error("Failed to fetch the user list"); return resp.json(); }).then(data => { this._table.display_status(null); Common.checkResult(data); const d = { more: data.more }; d.rows = data.users.map(it => this._make_row_data(it)); d.rows.push(new NewUserRow(5)); const fr = new UserFrame(d, this._table.last_row_index() + 1); this._table.clear(); this._table.add_frame(fr); if (this._sort.column) this._table.sort(this._sort.column, this._sort.direction); this._table.focus(); }).catch(err => { Common.displayError(err); this._table.display_status("error", err.message || null); }); } _make_page_container() { this._page = document.createElement("div"); this._page.classList.add("page-container"); } _make_scroll_container() { this._scroll = document.createElement("div"); this._scroll.classList.add("table-wrapper"); } _make_table() { this._table = new ITable({ class: "main-table users", onclick: row => { const data = row.userdata(); if (data) this._display_edit_dialog(data); }, onsort: col => { const dir = col.sorted() && "toggle" || "ascent"; this._table.set_sorted(col.name(), dir); this._table.sort(col.name(), col.sorted()); this._sort.column = col.name(); this._sort.direction = col.sorted(); this._table.focus(); }, onfocus: el => scroll_to_element(el, this._scroll) }); [ { content: "", sortable: true, name: "status", class: "cell-status" }, { content: "Name", sortable: true, name: "name" }, { content: "Access level", sortable: false, name: "level" }, { content: "Domains", sortable: false, name: "domains" }, { content: "Updated", sortable: true, name: "date" }, ].forEach(col => { const c = this._table.add_column(col); if (c.name() === this._sort.column) c.sort(this._sort.direction); }); } _make_row_data(d) { const rd = { cells: [], userdata: d.name }; rd.cells.push(new UserStatusCell(d.enabled)); rd.cells.push({ content: d.name, class: "user-name" }); rd.cells.push({ content: d.level, class: "user-level" }); rd.cells.push({ content: d.domains }); rd.cells.push(new UserTimeCell(new Date(d.updated_time))); return rd; } _display_edit_dialog(username) { const dlg_par = {}; if (username === "*new") { dlg_par["new"] = true; } else { dlg_par.name = username; dlg_par.minimal = User.level !== "admin"; } const dlg = new UserEditDialog(dlg_par); this._element.appendChild(dlg.element()); dlg.show().then(d => { if (d) this.update(); }).finally(() => { dlg.element().remove(); this._table.focus(); }); } } class UserStatusCell extends ITableCell { constructor(is_enabled, props) { props ||= {}; const ca = (props.class || "").split(" "); ca.push(is_enabled && "state-green" || "state-gray"); props.class = ca.filter(s => s.length > 0).join(" "); super(is_enabled, props); } value(target) { if (target === "dom") { const div = document.createElement("div"); div.classList.add("state-background", "status-indicator"); if (!this._title) div.title = this._content && "enabled" || "disabled"; return div; } return this._content; } } class UserTimeCell extends ITableCell { value(target) { if (target === "dom") return this._content && this._content.toUIString() || ""; if (target === "sort") return this._content && this._content.valueOf() || 0; super.value(target); } } class NewUserRow extends ITableRow { constructor(col_cnt) { super({ userdata: "*new", cells: [] }); this._col_cnt = col_cnt; } element() { if (!this._element) { super.element(); this._element.classList.add("colspanned", "virtual-item"); for (let i = 0; i < this._col_cnt; ++i) { const cell = document.createElement("div"); cell.classList.add("table-cell"); cell.textContent = !i && "New user" || "\u00A0"; this._element.appendChild(cell); } } return this._element; } } class UserFrame extends ITableFrame { sort(col_idx, direction) { this._sort_dir = (direction === "ascent" && 1) || (direction === "descent" && 2) || 0; super.sort(col_idx, direction); } _compare_cells(c1, c2) { if (!c1) { return this._sort_dir === 2; } if (!c2) { return this._sort_dir === 1; } return super._compare_cells(c1, c2); } } class UserEditDialog extends VerticalDialog { constructor(params) { params ||= {} const ba = []; if (!params.minimal) ba.push("save"); ba.push("close"); let title = null; if (!params["new"]) { title = "User settings"; if (!params.minimal) ba.splice(1, 0, "delete"); } else { title = "New user"; } super({ title: title, buttons: ba }); this._data = params; this._name_el = null; this._alvl_el = null; this._actv_el = null; this._doms_el = null; this._pasw_el = null; this._c_tm_el = null; this._u_tm_el = null; this._domains = new Set(); this._fetched = false; } _gen_content() { const min = this._data.minimal; // Name const nm = document.createElement("input"); nm.type = "text"; if (!this._data["new"]) { nm.value = this._data.name; nm.disabled = true; } nm.required = true; nm.maxLength = 32; this._insert_input_row("Name", nm); this._name_el = nm; // Level const lv = document.createElement("select"); [ "Manager", "User" ].forEach(val => { const op = document.createElement("option"); op.value = val.toLowerCase(); op.textContent = val; lv.appendChild(op); }); lv.required = true; lv.value = "user"; if (min) lv.disabled = true; this._insert_input_row("Level", lv); this._alvl_el = lv; if (!min) { // Enabled const en = document.createElement("select"); [ "Yes", "No" ].forEach(val => { const op = document.createElement("option"); op.value = val.toLowerCase(); op.textContent = val; en.appendChild(op); }); en.required = true; this._insert_input_row("Enabled", en); this._actv_el = en; // Domains const dm = document.createElement("multi-select"); dm.setAttribute("placeholder", "No domains"); dm.setLabel("Domains"); this._insert_input_row("Domains", dm); this._doms_el = dm; } // Password const sp = document.createElement("span"); sp.classList.add("value"); sp.textContent = "None "; if (!this._data["new"]) { const pw = document.createElement("a"); pw.href = ""; pw.textContent = "[ Change ]"; sp.appendChild(pw); pw.addEventListener("click", event => { event.preventDefault(); this._display_password_dialog(pw); }); } this._insert_input_row("Password", sp); this._pasw_el = sp; // Created const ct = document.createElement("input"); ct.type = "text"; ct.value = "n/a"; ct.disabled = true; this._insert_input_row("Created", ct); this._c_tm_el = ct; // Updated const ut = document.createElement("input"); ut.type = "text"; ut.value = "n/a"; ut.disabled = true; this._insert_input_row("Updated", ut); this._u_tm_el = ut; this._inputs.addEventListener("input", event => this._input_handler(event)); this._doms_el && this._doms_el.addEventListener("change", event => this._input_handler(event)); if (!this._fetched) this._fetch_data(); } _input_handler(event) { if (!this._fetched) return; let dis = true; if (this._name_el.value.trim() !== "" && this._alvl_el.value !== "") { if (this._alvl_el.dataset.server !== this._alvl_el.value) { dis = false; } else if (this._actv_el && this._actv_el.dataset.server !== this._actv_el.value) { dis = false; } else if (this._doms_el) { const dlist = this._doms_el.getValues(); if (dlist.length != this._domains.size || !dlist.every(d => this._domains.has(d))) { dis = false; } } } this._buttons[1].disabled = dis; } _add_button(container, text, type) { let dsbl = false; let lstn = null; switch (type) { case "save": text = "Save"; dsbl = true; lstn = this._save.bind(this); break; case "delete": text = "Delete"; lstn = this._confirm_delete.bind(this); break; default: super._add_button(container, text, type); return; } const btn = document.createElement("button"); btn.type = "button"; btn.disabled = dsbl; btn.textContent = text; btn.addEventListener("click", lstn); container.appendChild(btn); this._buttons.push(btn); } _fetch_data() { this._enable_ui(false); this.display_status("wait", "Getting data..."); const url = new URL("users.php", document.location); url.searchParams.set("user", this._data.name || ""); return window.fetch(url, { method: "GET", cache: "no-store", headers: HTTP_HEADERS, credentials: "same-origin" }).then(resp => { if (!resp.ok) throw new Error("Failed to fetch data"); return resp.json(); }).then(data => { this._fetched = true; Common.checkResult(data); [ "created_time", "updated_time" ].forEach(it => { data[it] && (data[it] = new Date(data[it])) }); this._update_ui(data); this._enable_ui(true); }).catch(err => { Common.displayError(err); this.display_status("error", err.message); }).finally(() => { this.display_status("wait", null); }); } _enable_ui(en) { this._name_el.disabled = !en || !this._data["new"]; this._alvl_el.disabled = !en || this._data.minimal; if (this._actv_el) this._actv_el.disabled = !en; if (this._doms_el) this._doms_el.disabled = !en; for (let i = 2; i < this._buttons.length - 1; ++i) { this._buttons[i].disabled = !en; } this.focus(); } _update_ui(data) { if (data.level) this._set_option_value(this._alvl_el, data.level); if (this._actv_el) this._set_option_value(this._actv_el, (data.enabled || this._data["new"]) ? "yes" : "no"); if (this._doms_el) { this._domains = new Set(data.domains.assigned); data.domains.available.forEach(d => this._doms_el.appendItem(d)); if (data.domains.assigned) this._doms_el.setValues(data.domains.assigned); } if (data.password) this._set_password_yes(); this._c_tm_el.value = data.created_time && data.created_time.toUIString() || "n/a"; this._u_tm_el.value = data.updated_time && data.updated_time.toUIString() || "n/a"; } _set_password_yes() { this._pasw_el.childNodes[0].textContent = "Yes "; } _set_option_value(el, data) { let val = ""; for (let i = 0; i < el.options.length; ++i) { const op = el.options[i]; if (data === op.value) { op.setAttribute("selected", ""); val = op.value; } else { op.removeAttribute("selected"); } } el.value = val; el.dataset.server = val; } _save() { this._enable_ui(false); this.display_status("wait", "Sending data to the server..."); const body = {}; body.name = this._data["new"] && this._name_el.value || this._data.name; body.level = this._alvl_el.value; body.enabled = this._actv_el && this._actv_el.value === "yes"; body.action = this._data["new"] && "add" || "update"; if (this._doms_el) { const dlist = this._doms_el.getValues(); if (this._domains.size != dlist.length || dlist.some(d => !this._domains.has(d))) { body.domains = dlist; } } window.fetch("users.php", { method: "POST", cache: "no-store", headers: Object.assign(HTTP_HEADERS, HTTP_HEADERS_POST), credentials: "same-origin", body: JSON.stringify(body) }).then(resp => { if (!resp.ok) throw new Error("Failed to " + (body.new && "add" || "update") + " the user data"); return resp.json(); }).then(data => { Common.checkResult(data); this._result = body; this.hide(); Notification.add({ text: `The user ${body.name} successfully ` + (body.action === "add" && "added" || "updated") }); }).catch(err => { Common.displayError(err); this.display_status("error", err.message); }).finally(() => { this.display_status("wait", null); this._enable_ui(true); }); } _confirm_delete() { if (confirm("Are sure you want to delete this user?")) this._delete(); } _delete() { this._enable_ui(false); this.display_status("wait", "Sending a request to the server..."); const body = { name: this._data.name, action: "delete" }; window.fetch("users.php", { method: "POST", cache: "no-store", headers: Object.assign(HTTP_HEADERS, HTTP_HEADERS_POST), credentials: "same-origin", body: JSON.stringify(body) }).then(resp => { if (!resp.ok) throw new Error("Failed to delete the user"); return resp.json(); }).then(data => { Common.checkResult(data); this._result = data; this.hide(); Notification.add({ text: `The user ${body.name} successfully removed` }); }).catch(err => { Common.displayError(err); this.display_status("error", err.message); }).finally(() => { this.display_status("wait", null); this._enable_ui(true); }); } _display_password_dialog(source_el) { const dlg = new PasswordDialog({ username: this._data.name, current_password: this._data.name === User.name }); this._element.classList.add("hidden"); document.getElementById("main-block").appendChild(dlg.element()); dlg.show().then(d => { if (d) this._set_password_yes(); }).finally(() => { dlg.element().remove(); this._element.classList.remove("hidden"); source_el.focus(); }); } } class PasswordDialog extends VerticalDialog { constructor(params) { super({ title: `Password change [${params.username}]`, buttons: [ "apply", "cancel" ] }); this._data = params; this._apl_bt = null; this._pw_cur = null; this._pw_nw1 = null; this._pw_nw2 = null; } _gen_content() { if (this._data.current_password) { const pw_cur = document.createElement("input"); pw_cur.type = "password"; pw_cur.required = true; this._insert_input_row("Current password", pw_cur); this._pw_cur = pw_cur; } const pw_nw1 = document.createElement("input"); pw_nw1.type = "password"; pw_nw1.required = true; this._insert_input_row("New password", pw_nw1); this._pw_nw1 = pw_nw1; const pw_nw2 = document.createElement("input"); pw_nw2.type = "password"; pw_nw2.required = true; this._insert_input_row("Confirm password", pw_nw2); this._pw_nw2 = pw_nw2; this._inputs.addEventListener("input", event => { this._apl_bt.disabled = [ this._pw_cur, this._pw_nw1, this._pw_nw2 ].some(el => { return el && el.value === ""; }); }); } _add_button(c, t, type) { super._add_button(c, t, type); if (type === "submit") { this._apl_bt = this._buttons[this._buttons.length - 1]; this._apl_bt.disabled = true; } } _submit() { if (this._pw_nw1.value !== this._pw_nw2.value) { this.display_status("error", "New password and confirm password don't match"); this._pw_nw1.focus(); return; } this.display_status("wait", "Updating the password..."); this._enable_ui(false); const body = { name: this._data.username, action: "set_password", new_password: this._pw_nw1.value }; if (this._pw_cur) body.password = this._pw_cur.value; window.fetch("users.php", { method: "POST", cache: "no-store", headers: Object.assign(HTTP_HEADERS, HTTP_HEADERS_POST), credentials: "same-origin", body: JSON.stringify(body) }).then(resp => { if (!resp.ok) throw new Error("Failed to " + (body.new && "add" || "update") + " the user data"); return resp.json(); }).then(data => { Common.checkResult(data); this._result = true; this.hide(); Notification.add({ text: "The password has been successfully updated" }); }).catch(err => { Common.displayError(err); this.display_status("error", err.message); }).finally(() => { this.display_status("wait", null); this._enable_ui(true); }); } _enable_ui(en) { if (this._pw_cur) this._pw_cur.disabled = !en; this._pw_nw1.disabled = !en; this._pw_nw2.disabled = !en; this.focus(); } }