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

962 lines
23 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 ITable {
constructor(params) {
this._table = null;
this._class = null;
this._header = null;
this._status = null;
this._frames = [];
this._columns = [];
this._body = null;
this._onsort = null;
this._onclick = null;
this._onfocus = null;
if (params) {
this._class = params.class || null;
this._onsort = params.onsort || null;
this._onclick = params.onclick || null;
this._onfocus = params.onfocus || null;
this._nodata_text = params.nodata_text || null;
}
this._focused = false;
this._focused_row = null;
this._selected_rows = [];
}
element() {
if (!this._table) {
let that = this;
this._table = document.createElement("div");
if (this._class)
this._table.setAttribute("class", this._class);
this._table.classList.add("table");
this._table.setAttribute("tabindex", -1);
this._table.addEventListener("focus", function(event) {
that._focused = true;
that._update_focus();
}, true);
this._table.addEventListener("blur", function(event) {
that._focused = false;
that._update_focus();
}, true);
let th = document.createElement("div");
th.setAttribute("class", "table-header");
this._table.appendChild(th);
this._header = document.createElement("div");
this._header.setAttribute("class", "table-row");
this._header.addEventListener("click", function(event) {
let col = that.get_column_by_element(event.target);
if (col && col.is_sortable()) {
if (that._onsort) {
that._onsort(col);
}
}
});
th.appendChild(this._header);
this._fill_columns();
this._body = document.createElement("div");
this._body.setAttribute("class", "table-body");
this._body.addEventListener("click", function(event) {
let row = that._get_row_by_element(event.target);
if (row) {
that._set_selected_rows([ row ]);
if (that._onclick)
that._onclick(row);
}
});
this._body.addEventListener("focus", function(event) {
let row = that._get_row_by_element(event.target);
if (row) {
that._update_focused_row(row);
if (that._onfocus)
that._onfocus(row.element());
}
}, true);
this._body.addEventListener("blur", function(event) {
let row = that._get_row_by_element(event.target);
if (row) {
row.onfocus(false);
}
}, true);
this._body.addEventListener("keydown", function(event) {
let row = null;
switch (event.code) {
case "ArrowDown":
row = that._get_row(that._focused_row !== null && (that._focused_row.id() + 1) || 0);
break;
case "ArrowUp":
if (that._focused_row) {
let id = that._focused_row.id();
if (id >= 0)
row = that._get_row(id - 1);
}
else {
row = that._get_row(0);
}
break;
case "PageUp":
if (that._focused_row && that._frames.length > 0) {
let c_id = that._focused_row.id();
let f_fr = that._frames[0];
let f_id = f_fr.first_index();
if (c_id == f_id)
break;
let s_el = that._get_scroll_element();
if (s_el) {
let r_ht = that._focused_row.element().getBoundingClientRect().height;
let s_ht = s_el.getBoundingClientRect().height;
let n_id = Math.max(c_id - Math.floor(s_ht / r_ht) - 1, f_id);
row = that._get_row(n_id);
}
else {
row = f_fr.row(f_id);
}
}
break;
case "PageDown":
if (that._focused_row && that._frames.length > 0) {
let c_id = that._focused_row.id();
let l_fr = that._frames[that._frames.length - 1];
let l_id = l_fr.last_index();
if (c_id == l_id)
break;
let s_el = that._get_scroll_element();
if (s_el) {
let r_ht = that._focused_row.element().getBoundingClientRect().height;
let s_ht = s_el.getBoundingClientRect().height;
let n_id = Math.min(c_id + Math.floor(s_ht / r_ht) - 1, l_id);
row = that._get_row(n_id);
}
else {
row = l_fr.row(l_id);
}
}
break;
case "Home":
if (that._frames.length > 0) {
let first_frame = that._frames[0];
row = first_frame.row(first_frame.first_index());
}
break;
case "End":
if (that._frames.length > 0) {
let last_frame = that._frames[that._frames.length - 1];
row = last_frame.row(last_frame.last_index());
}
break;
case "Enter":
case "NumpadEnter":
if (that._onclick && that._focused_row)
that._onclick(that._focused_row);
event.preventDefault();
return;
}
if (row) {
row.element().focus();
that._set_selected_rows([ row ]);
event.preventDefault();
}
});
this._fill_frames();
this._table.appendChild(this._body);
}
return this._table;
}
more() {
return this._frames.length > 0 && this._frames[this._frames.length - 1].more();
}
frames_count() {
return this._frames.length;
}
add_column(data) {
let col = new ITableColumn(data.content, {
name: data.name,
class: data.class,
sortable: data.sortable,
sorted: data.sorted
});
this._columns.push(col);
if (this._header)
this._header.appendChild(col.element());
return col;
}
get_column_by_element(el) {
el = el && el.closest("div.table-cell");
if (el) {
for (let i = 0; i < this._columns.length; ++i) {
let col = this._columns[i];
if (el === col.element())
return col;
}
}
}
display_status(status, text) {
if (this._status && !status) {
this._status.remove();
this._status = null;
return;
}
this.element();
this._status = document.createElement("div");
this._status.setAttribute("class", "table-row colspanned");
let el = document.createElement("div");
el.setAttribute("class", "table-cell");
this._status.appendChild(el);
let el2 = document.createElement("div");
el2.setAttribute("class", "table-cell");
el2.appendChild(document.createTextNode("\u00A0")); // Non breaking space
this._status.appendChild(el2);
if (status === "wait") {
set_wait_status(el);
}
else {
remove_all_children(this._body);
if (status === "nodata") {
el.classList.add("nodata");
el.appendChild(document.createTextNode(text || "No data"));
}
else {
set_error_status(el, text);
}
}
this._body.appendChild(this._status);
}
last_row_index() {
let idx = -1;
if (this._frames.length > 0) {
idx = this._frames[this._frames.length - 1].last_index();
}
return idx;
}
add_frame(frame) {
if (frame.count() === 0) {
if (this._frames.length === 0)
this.display_status("nodata", this._nodata_text);
return
}
if (this._frames.length > 0 && this._frames[0].first_index() > frame.last_index()) {
this._frames.unshift(frame);
if (this._body)
this._body.insertBefore(frame.element(), this._body.firstChild);
}
else {
this._frames.push(frame);
if (this._body)
this._body.appendChild(frame.element());
}
}
clear() {
this._frames = [];
if (this._body)
remove_all_children(this._body);
this._focused_row = null;
this._selected_rows = [];
}
focus() {
if (!this._focused_row) {
if (this._frames.length > 0) {
let fr = this._frames[0];
this._focused_row = fr.row(fr.first_index());
}
}
if (this._focused_row)
this._focused_row.element().focus();
}
sort(col_name, direction) {
if (this._frames.length == 1) {
for (let i = 0; i < this._columns.length; ++i) {
let col = this._columns[i];
if (col.is_sortable() && col.name() === col_name) {
let fr = this._frames[0];
fr.sort(i, direction);
if (this._body) {
remove_all_children(this._body);
this._body.appendChild(fr.element());
}
return;
}
}
}
}
set_sorted(col_name, direction) {
this._columns.forEach(function(col) {
if (col.is_sortable()) {
if (col.name() !== col_name) {
col.sort(null);
}
else {
if (direction === "toggle") {
direction = null;
if (col.sorted() === "ascent") {
direction = "descent";
}
else if (col.sorted() === "descent") {
direction = "ascent";
}
}
col.sort(direction);
}
}
});
}
_fill_columns() {
this._columns.forEach(function(col) {
this._header.appendChild(col.element());
}, this);
}
_fill_frames() {
this._frames.forEach(function(fr) {
this._body.appendChild(fr.element());
}, this);
}
_get_row(row_id) {
for (let i = 0; i < this._frames.length; ++i) {
let fr = this._frames[i];
if (fr.last_index() >= row_id) {
if (fr.first_index() <= row_id)
return fr.row(row_id);
}
}
return null;
}
_get_row_by_element(el) {
let row = null;
if (el) {
el = el.closest("div.table-row");
if (el) {
let id = parseInt(el.getAttribute("data-id"));
if (id !== NaN)
row = this._get_row(id);
}
}
return row;
}
_update_focus() {
if (this._focused)
this._table.classList.add("focused");
else
this._table.classList.remove("focused");
}
_update_focused_row(row) {
if (this._focused_row && row !== this._focused_row) {
this._focused_row.tabindex(-1);
}
this._focused_row = row;
this._focused_row.tabindex(0);
this._focused_row.onfocus(true);
}
_set_selected_rows(rows) {
this._selected_rows.forEach(function(row) {
row.select(false);
});
rows.forEach(function(row) {
row.select(true);
});
this._selected_rows = rows;
}
_get_scroll_element() {
let t_rect = this._table.getBoundingClientRect();
let p_elem = this._table.parentElement;
while (p_elem) {
let p_rect = p_elem.getBoundingClientRect();
if (t_rect.top < p_rect.top || t_rect.bottom > p_rect.bottom) {
return p_elem;
}
p_elem = p_elem.paretnElement;
}
}
}
class ITableFrame {
constructor(data, pos) {
this._pos = pos;
this._more = data.more && true || false;
let id = pos;
this._rows = data.rows.map(function(rd) {
if (!(rd instanceof ITableRow)) {
rd = new ITableRow(rd);
}
rd.id(id++);
return rd;
});
}
count() {
return this._rows.length;
}
first_index() {
return this._pos;
}
last_index() {
let cnt = this._rows.length;
if (cnt > 0) {
return this._pos + cnt - 1;
}
return null;
}
row(id) {
let idx = id - this._pos;
if (idx >= 0 && idx < this._rows.length) {
return this._rows[idx];
}
return null;
}
more() {
return this._more;
}
element() {
let fr = document.createDocumentFragment();
this._rows.forEach(function(row) {
fr.appendChild(row.element());
});
return fr;
}
sort(col_idx, direction) {
let dir = (direction === "ascent" && 1) || (direction === "descent" && 2) || 0;
if (dir) {
let that = this;
this._rows.sort(function(a, b) {
let c1 = a.cell(col_idx);
let c2 = b.cell(col_idx);
if (dir === 1) {
return that._compare_cells(c2, c1);
}
return that._compare_cells(c1, c2);
});
let id = this._pos;
this._rows.forEach(function(row) {
row.id(id++);
});
}
}
_compare_cells(c1, c2) {
return c1.value("sort") < c2.value("sort");
}
}
class ITableRow {
constructor(data) {
this._id = -1;
this._focused = false;
this._tabindex = -1;
this._selected = false;
this._element = null;
this._class = data.class || null;
this._userdata = data.userdata || null;
this._cells = data.cells.map(function(col) {
if (col instanceof ITableCell) {
return col;
}
let props = null;
if (col.title || col.class || col.label) {
props = {
title: col.title || null,
class: col.class || null,
label: col.label || null
};
}
return new ITableCell(col.content, props);
});
}
userdata() {
return this._userdata;
}
element() {
if (!this._element) {
this._element = document.createElement("div");
this._element.setAttribute("data-id", this._id);
if (this._class)
this._element.setAttribute("class", this._class);
this._element.classList.add("table-row");
this._cells.forEach(function(col) {
this._element.appendChild(col.element());
}, this);
this._update_focus();
this._update_tabindex();
this._update_select();
}
return this._element;
}
onfocus(flag) {
this._focused = flag;
if (this._element)
this._update_focus();
}
tabindex(index) {
if (this._tabindex !== index) {
this._tabindex = index;
this._update_tabindex();
}
}
select(flag) {
this._selected = flag;
if (this._element)
this._update_select();
}
id(new_id) {
if (new_id !== undefined && new_id !== this._id) {
this._id = new_id;
if (this._element) {
this._element.setAttribute("data-id", this._id);
}
}
return this._id;
}
cell(index) {
return this._cells[index] || null;
}
_update_focus() {
if (this._focused)
this._element.classList.add("focused");
else
this._element.classList.remove("focused");
}
_update_tabindex() {
this._element.setAttribute("tabindex", this._tabindex);
}
_update_select() {
if (this._selected) {
this._element.classList.add("selected");
}
else {
this._element.classList.remove("selected");
}
}
}
class ITableCell {
constructor(content, props) {
this._element = null;
this._content = content;
if (props) {
this._title = props.title || null;
this._class = props.class || null;
this._label = props.label || null;
}
}
element() {
if (!this._element) {
this._element = document.createElement("div");
if (this._title) {
this._element.setAttribute("title", this._title);
}
if (this._class) {
this._element.setAttribute("class", this._class);
}
if (this._label) {
this._element.setAttribute("data-label", this._label);
}
this._element.classList.add("table-cell");
let content = this.value("dom");
if (content !== null) {
if (typeof(content) === "object") {
this._element.appendChild(content)
}
else {
this._element.appendChild(document.createTextNode(content));
}
}
}
return this._element;
}
value(target) {
if (target === "dom" || typeof(this._content) !== "object") {
return this._content;
}
return null;
}
}
class ITableColumn extends ITableCell {
constructor(content, props) {
super(content, props);
this._name = props.name;
this._sortable = !!props.sortable;
this._sorted = props.sorted || null;
}
element() {
if (this._element !== super.element()) {
this._update_sorted();
}
return this._element;
}
is_sortable() {
return this._sortable;
}
sort(dir) {
if (this._sorted !== dir) {
this._sorted = dir || null;
if (this._element) {
this._update_sorted();
}
}
}
sorted() {
return this._sorted;
}
name() {
return this._name;
}
_update_sorted() {
if (this._sortable) {
this._element.classList.add("sortable");
let c_act = {
asc: "remove",
des: "remove"
};
if (this._sorted) {
this._element.classList.add("arrows");
if (this._sorted === "ascent") {
c_act["asc"] = "add";
}
else if (this._sorted === "descent") {
c_act["des"] = "add";
}
}
else {
this._element.classList.remove("arrows");
}
for (let key in c_act) {
this._element.classList[c_act[key]]("sorted-" + key);
}
}
}
}
class ModalDialog {
constructor(params) {
this._params = params;
this._element = null;
this._title = null;
this._buttons = [];
this._content = null;
this._first = null;
this._last = null;
this._result = null;
this._callback = null;
}
element() {
if (!this._element) {
let ovl = document.createElement("div");
ovl.setAttribute("class", "dialog-overlay hidden");
let dlg = document.createElement("div");
dlg.setAttribute("class", "dialog");
let con = document.createElement("div");
con.setAttribute("class", "container");
this._title = document.createElement("div");
this._title.setAttribute("class", "title");
{
let tt = document.createElement("div");
tt.setAttribute("class", "title-text");
tt.appendChild(document.createTextNode(this._params.title || ""));
this._title.appendChild(tt);
}
let that = this;
{
let cbt = document.createElement("button");
cbt.setAttribute("type", "button");
cbt.setAttribute("class", "close-btn");
cbt.appendChild(document.createTextNode("x"));
this._title.appendChild(cbt);
this._buttons = [ cbt ];
cbt.addEventListener("click", function(event) {
that.hide();
});
}
con.appendChild(this._title);
let frm = document.createElement("form");
this._content = document.createElement("div");
frm.appendChild(this._content);
let bdv = document.createElement("div");
bdv.setAttribute("class", "dialog-buttons");
this._add_buttons(bdv);
frm.appendChild(bdv);
con.appendChild(frm);
dlg.appendChild(con);
ovl.appendChild(dlg);
this._element = ovl;
this._gen_content();
this._update_first_last();
this._element.addEventListener("click", function(event) {
if (event.target === this && that._params.overlay_click !== "ignore") {
that.hide();
}
});
frm.addEventListener("keydown", function(event) {
if (event.key == "Tab") {
if (!event.shiftKey) {
if (event.target == that._last) {
that._first.focus();
event.preventDefault();
}
}
else {
if (event.target == that._first) {
that._last.focus();
event.preventDefault();
}
}
}
});
frm.addEventListener("submit", function(event) {
event.preventDefault();
that._submit();
});
frm.addEventListener("reset", function(event) {
this._reset();
}.bind(this));
}
return this._element;
}
show() {
this.element();
this._result = null;
this._title.querySelector("button.close-btn").classList.add("active");
this._element.classList.remove("hidden");
if (this._first) {
this._first.focus();
}
let that = this;
return new Promise(function(resolve, reject) {
that._callback = resolve;
});
}
hide() {
if (this._element) {
this._title.querySelector("button.close-btn").classList.remove("active");
this._element.classList.add("hidden");
}
this._callback && this._callback(this._result);
}
_add_buttons(container) {
let bl = this._params.buttons || [];
bl.forEach(function(bt) {
let name = null;
let type = null;
if (bt == "ok") {
name = "Ok";
type = "submit";
}
else if (bt == "apply") {
name = "Apply";
type = "submit";
}
else if (bt == "reset") {
name = "Reset";
type = "reset";
}
else if (bt == "login") {
name = "Log in";
type = "submit";
}
else if (bt == "cancel") {
name = "Cancel";
type = "close";
}
else if (bt == "close") {
name = "Close";
type = "close";
}
else {
name = bt;
type = bt;
}
this._add_button(container, name, type);
}, this);
}
_add_button(container, text, type) {
let btn = document.createElement("button");
if (type == "close") {
btn.setAttribute("type", "button");
btn.addEventListener("click", this.hide.bind(this));
}
else {
btn.setAttribute("type", type);
}
btn.appendChild(document.createTextNode(text));
container.appendChild(btn);
this._buttons.push(btn);
}
_gen_content() {
}
_update_first_last() {
this._first = null;
this._last = null;
let list = this._element.querySelector("form").elements;
for (let i = 0; i < list.length; ++i) {
let el = list[i];
if (!el.elements && !el.disabled) {
if (!this._first)
this._first = el;
this._last = el;
}
}
}
_submit() {
}
_reset() {
}
}
class AboutDialog extends ModalDialog {
constructor(params) {
super({
title: "About",
buttons: [ "ok" ]
});
this._authors = params.authors;
this._documentation = params.documentation;
this._source_code = params.source_code;
}
element() {
if (!this._element) {
super.element();
this._element.children[0].classList.add("about");
this._content.classList.add("vertical-content");
this._content.parentElement.classList.add("vertical-content");
}
return this._element;
}
_gen_content() {
let header = document.createElement("h2");
header.appendChild(document.createTextNode(Router.app_name(true)));
this._content.appendChild(header);
let cblock = document.createElement("div");
this._authors.forEach(function(author) {
let ablock = document.createElement("div");
ablock.appendChild(document.createTextNode("Copyright © " + author.years + ", "));
cblock.appendChild(ablock);
let alink = document.createElement("a");
alink.setAttribute("href", author.url);
alink.setAttribute("title", "The author's page");
alink.setAttribute("target", "_blank");
alink.appendChild(document.createTextNode(author.name));
ablock.appendChild(alink);
});
this._content.appendChild(cblock);
let oblock = document.createElement("div");
oblock.setAttribute("class", "left-titled");
let add_row = function(title, value) {
let t_el = document.createElement("span");
t_el.appendChild(document.createTextNode(title + ": "));
oblock.appendChild(t_el);
let v_el = document.createElement("div");
value.forEach(function(v) {
if (v_el.children.length > 0) {
v_el.appendChild(document.createTextNode(", "));
}
let a_el = document.createElement("a");
a_el.setAttribute("href", v.url);
a_el.setAttribute("title", v.title || v.ancor);
a_el.setAttribute("target", "_blank");
a_el.appendChild(document.createTextNode(v.ancor));
v_el.appendChild(a_el);
});
oblock.appendChild(v_el);
};
this._content.appendChild(oblock);
add_row("Documentation", this._documentation);
add_row("Source code", this._source_code);
{
let tl = document.createElement("span");
tl.appendChild(document.createTextNode("PHP version: "));
oblock.appendChild(tl);
let vl = document.createElement("span");
vl.appendChild(document.createTextNode(Router.php_version || "n/a"));
oblock.appendChild(vl);
}
let lblock = document.createElement("div");
lblock.appendChild(document.createTextNode(
"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._content.appendChild(lblock);
}
_submit() {
this.hide();
}
}