smeserver-dmarc-srg/root/opt/dmarc-srg/js/domains.js

487 lines
13 KiB
JavaScript
Raw Normal View History

2023-06-21 15:19:40 +02:00
/**
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
* Copyright (C) 2020 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/>.
*/
class DomainList {
constructor() {
this._table = null;
this._scroll = null;
this._element = document.getElementById("main-block");
this._sort = { column: "fqdn", direction: "ascent" };
}
display() {
this._make_scroll_container();
this._make_table();
this._scroll.appendChild(this._table.element());
this._element.appendChild(this._scroll);
this._table.focus();
}
update() {
this._fetch_list();
}
title() {
return "Domain List";
}
_fetch_list() {
this._table.display_status("wait");
let that = this;
return window.fetch("domains.php", {
method: "GET",
cache: "no-store",
headers: HTTP_HEADERS,
credentials: "same-origin"
}).then(function(resp) {
if (!resp.ok)
throw new Error("Failed to fetch the domain list");
return resp.json();
}).then(function(data) {
that._table.display_status(null);
Common.checkResult(data);
let d = { more: data.more };
d.rows = data.domains.map(function(it) {
return that._make_row_data(it);
});
d.rows.push(new NewDomainRow(4));
let fr = new DomainFrame(d, that._table.last_row_index() + 1);
that._table.clear();
that._table.add_frame(fr);
if (that._sort.column) {
that._table.sort(that._sort.column, that._sort.direction);
}
that._table.focus();
}).catch(function(err) {
Common.displayError(err);
that._table.display_status("error");
});
}
_make_scroll_container() {
this._scroll = document.createElement("div");
this._scroll.setAttribute("class", "main-table-container");
}
_make_table() {
this._table = new ITable({
class: "main-table domains",
onclick: function(row) {
let data = row.userdata();
if (data) {
this._display_edit_dialog(data);
}
}.bind(this),
onsort: function(col) {
let 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();
}.bind(this),
onfocus: function(el) {
scroll_to_element(el, this._scroll);
}.bind(this)
});
[
{ content: "", sortable: true, name: "status", class: "cell-status" },
{ content: "FQDN", sortable: true, name: "fqdn" },
{ content: "Updated", sortable: true, name: "date" },
{ content: "Description", class: "descr" }
].forEach(function(col) {
let c = this._table.add_column(col);
if (c.name() === this._sort.column) {
c.sort(this._sort.direction);
}
}, this);
}
_make_row_data(d) {
let rd = { cells: [], userdata: d.fqdn };
rd.cells.push(new DomainStatusCell(d.active));
rd.cells.push({ content: d.fqdn, class: "fqdn" });
rd.cells.push(new DomainTimeCell(new Date(d.updated_time)));
rd.cells.push({ content: d.description || "", class: "descr" });
return rd;
}
_display_edit_dialog(fqdn) {
let dlg = new DomainEditDialog(fqdn === "*new" && { "new": true } || { fqdn: fqdn });
this._element.appendChild(dlg.element());
let that = this;
dlg.show().then(function(d) {
if (d) {
that.update();
}
}).finally(function() {
dlg.element().remove();
that._table.focus();
});
}
}
class DomainStatusCell extends ITableCell {
constructor(is_active, props) {
props = props || {};
let ca = (props.class || "").split(" ");
ca.push(is_active && "state-green" || "state-gray");
props.class = ca.filter(function(s) { return s.length > 0; }).join(" ");
super(is_active, props);
}
value(target) {
if (target === "dom") {
let div = document.createElement("div");
div.setAttribute("class", "state-background status-indicator");
if (!this._title) {
div.setAttribute("title", this._content && "active" || "inactive");
}
return div;
}
return this._content;
}
}
class DomainTimeCell extends ITableCell {
value(target) {
if (target === "dom") {
return this._content && this._content.toUIString() || "";
}
if (target === "sort") {
return this._content && this._content.valueOf() || "";
}
super.value(target);
}
}
class NewDomainRow 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) {
let cell = document.createElement("div");
cell.setAttribute("class", "table-cell");
cell.appendChild(document.createTextNode(!i && "New domain" || "\u00A0"));
this._element.appendChild(cell);
}
}
return this._element;
}
}
class DomainFrame 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 DomainEditDialog extends ModalDialog {
constructor(params) {
let tl = null;
let ba = [ "save", "close" ];
if (!params["new"]) {
tl = "Domain settings";
ba.splice(1, 0, "delete");
}
else {
tl = "New domain";
}
super({ title: tl, buttons: ba });
this._data = params || {};
this._content = null;
this._inputs = null;
this._fqdn_el = null;
this._actv_el = null;
this._desc_el = null;
this._c_tm_el = null;
this._u_tm_el = null;
this._fetched = false;
}
_gen_content() {
this._inputs = document.createElement("div");
this._inputs.setAttribute("class", "titled-input");
this._content.appendChild(this._inputs);
this._content.classList.add("vertical-content");
let fq = document.createElement("input");
fq.setAttribute("type", "text");
if (!this._data["new"]) {
fq.setAttribute("value", this._data.fqdn);
fq.disabled = true;
}
fq.required = true;
this._insert_row("FQDN", fq);
this._fqdn_el = fq;
{
let en = document.createElement("select");
let op1 = document.createElement("option");
op1.setAttribute("value", "yes");
op1.appendChild(document.createTextNode("Yes"));
en.appendChild(op1);
let op2 = document.createElement("option");
op2.setAttribute("value", "no");
op2.appendChild(document.createTextNode("No"));
en.appendChild(op2);
en.required = true;
this._insert_row("Active", en);
this._actv_el = en;
}
let tx = document.createElement("textarea");
this._insert_row("Description", tx).classList.add("description");
this._desc_el = tx;
let ct = document.createElement("input");
ct.setAttribute("type", "text");
ct.disabled = true;
ct.setAttribute("value","n/a");
this._insert_row("Created", ct);
this._c_tm_el = ct;
let ut = document.createElement("input");
ut.setAttribute("type", "text");
ut.setAttribute("value","n/a");
ut.disabled = true;
this._insert_row("Updated", ut);
this._u_tm_el = ut;
this._inputs.addEventListener("input", function(event) {
if (this._fetched || this._data["new"]) {
this._buttons[1].disabled = (
this._actv_el.dataset.server === this._actv_el.value &&
this._desc_el.defaultValue === this._desc_el.value &&
this._fqdn_el.defaultValue === this._fqdn_el.value
);
}
}.bind(this));
if (!this._data["new"] && !this._fetched) {
this._fetch_data();
}
}
_insert_row(text, v_el) {
let l_el = document.createElement("label");
let t_el = document.createElement("span");
t_el.appendChild(document.createTextNode(text + ": "));
l_el.appendChild(t_el);
l_el.appendChild(v_el);
this._inputs.appendChild(l_el);
return v_el;
}
_fetch_data() {
this._enable_ui(false);
this._content.appendChild(set_wait_status());
let uparams = new URLSearchParams();
uparams.set("domain", this._data.fqdn);
let that = this;
window.fetch("domains.php?" + uparams.toString(), {
method: "GET",
cache: "no-store",
headers: HTTP_HEADERS,
credentials: "same-origin"
}).then(function(resp) {
if (!resp.ok)
throw new Error("Failed to fetch the domain data");
return resp.json();
}).then(function(data) {
that._fetched = true;
Common.checkResult(data);
data.created_time = new Date(data.created_time);
data.updated_time = new Date(data.updated_time);
that._update_ui(data);
that._enable_ui(true);
}).catch(function(err) {
Common.displayError(err);
that._content.appendChild(set_error_status(null, err.message));
}).finally(function() {
that._content.querySelector(".wait-message").remove();
});
}
_update_ui(data) {
let val = "";
for (let i = 0; i < this._actv_el.options.length; ++i) {
let op = this._actv_el.options[i];
let ee = op.value === "yes";
if ((data.active && ee) || (!data.active && !ee)) {
op.setAttribute("selected", "selected");
val = op.value;
}
else {
op.removeAttribute("selected");
}
}
this._actv_el.value = val;
this._actv_el.dataset.server = val;
this._desc_el.appendChild(document.createTextNode(data.description || ""));
this._c_tm_el.setAttribute("value", data.created_time && data.created_time.toUIString() || "n/a");
this._u_tm_el.setAttribute("value", data.updated_time && data.updated_time.toUIString() || "n/a");
}
_add_button(container, text, type) {
let btn = null;
if (type === "save") {
text = "Save";
btn = document.createElement("button");
btn.disabled = true;
btn.addEventListener("click", this._save.bind(this));
}
else if (type === "delete") {
text = "Delete";
btn = document.createElement("button");
btn.addEventListener("click", this._confirm_delete.bind(this));
}
else {
super._add_button(container, text, type);
return;
}
btn.setAttribute("type", "button");
btn.appendChild(document.createTextNode(text));
container.appendChild(btn);
this._buttons.push(btn);
}
_enable_ui(en) {
this._fqdn_el.disabled = !en || !this._data["new"];
this._actv_el.disabled = !en;
this._desc_el.disabled = !en;
for (let i = 2; i < this._buttons.length - 1; ++i) {
this._buttons[i].disabled = !en;
}
this._update_first_last();
if (this._first) {
this._first.focus();
}
}
_save() {
this._enable_ui(false);
let em = this._content.querySelector(".error-message");
if (em) {
em.remove();
}
this._content.appendChild(set_wait_status());
let body = {};
body.fqdn = this._data["new"] && this._fqdn_el.value || this._data.fqdn;
body.action = this._data["new"] && "add" || "update";
body.active = this._actv_el.value === "yes";
body.description = this._desc_el.value;
let that = this;
window.fetch("domains.php", {
method: "POST",
cache: "no-store",
headers: Object.assign(HTTP_HEADERS, HTTP_HEADERS_POST),
credentials: "same-origin",
body: JSON.stringify(body)
}).then(function(resp) {
if (!resp.ok)
throw new Error("Failed to " + (body.new && "add" || "update") + " the domain data");
return resp.json();
}).then(function(data) {
Common.checkResult(data);
that._result = body;
that.hide();
Notification.add({
text: "The domain " + body.fqdn + " was " + (body.action === "add" && "added" || "updated")
});
}).catch(function(err) {
Common.displayError(err);
that._content.appendChild(set_error_status(null, err.message));
}).finally(function() {
that._content.querySelector(".wait-message").remove();
that._enable_ui(true);
});
}
_confirm_delete() {
if (confirm("Are sure you want to delete this domain?")) {
this._delete();
}
}
_delete() {
this._enable_ui(false);
let em = this._content.querySelector(".error-message");
if (em) {
em.remove();
}
this._content.appendChild(set_wait_status());
let body = {};
body.fqdn = this._data.fqdn;
body.action = "delete";
let that = this;
window.fetch("domains.php", {
method: "POST",
cache: "no-store",
headers: Object.assign(HTTP_HEADERS, HTTP_HEADERS_POST),
credentials: "same-origin",
body: JSON.stringify(body)
}).then(function(resp) {
if (!resp.ok)
throw new Error("Failed to delete the domain");
return resp.json();
}).then(function(data) {
Common.checkResult(data);
that._result = data;
that.hide();
Notification.add({ text: "The domain " + body.fqdn + " was removed" });
}).catch(function(err) {
Common.displayError(err);
that._content.appendChild(set_error_status(null, err.message));
}).finally(function() {
that._content.querySelector(".wait-message").remove();
that._enable_ui(true);
});
}
}