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

679 lines
17 KiB
JavaScript

/**
* 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 Admin {
constructor(id) {
this._state = null;
this._element = null;
this._sources = null;
this._database = null;
}
display() {
let cn = document.getElementById("main-block");
if (!this._element) {
this._element = document.createElement("div");
this._element.setAttribute("class", "panel-container round-border");
this._element.appendChild(this._get_database_elements());
this._element.appendChild(this._get_sources_elements());
}
cn.appendChild(this._element);
}
update() {
this._get_admin_state();
}
title() {
return "Admin Panel";
}
_get_database_elements() {
let fr = document.createDocumentFragment();
let h = document.createElement("h4");
h.appendChild(document.createTextNode("Database"));
fr.appendChild(h);
if (!this._database) {
this._database = new DatabaseListBox(this._create_db_item_menu_element());
}
fr.appendChild(this._database.element());
return fr;
}
_get_sources_elements() {
let fr = document.createDocumentFragment();
let h = document.createElement("h4");
h.appendChild(document.createTextNode("Report sources"));
fr.appendChild(h);
if (!this._sources) {
this._sources = new SourceListBox();
}
fr.appendChild(this._sources.element());
return fr;
}
_get_admin_state() {
[ this._database, this._sources ].forEach(function(c) {
c.set_status("wait");
});
let t = this;
window.fetch("admin.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 admin data");
return resp.json();
}).then(function(data) {
Common.checkResult(data);
t._state = data;
t._fill_data();
}).catch(function(err) {
Common.displayError(err);
t._fill_data(err.message);
});
}
_send_command(cmd) {
let t = this;
return window.fetch("admin.php", {
method: "POST",
cache: "no-store",
headers: Object.assign(HTTP_HEADERS, HTTP_HEADERS_POST),
credentials: "same-origin",
body: JSON.stringify(cmd)
}).then(function(resp) {
if (!resp.ok)
throw new Error("Failed");
return resp.json();
}).finally(function() {
t._get_admin_state();
Status.instance().update().catch(function(){});
});
}
_fill_data(err_msg) {
if (!err_msg) {
let d = this._state.database || [];
this._database.set_data({
root: {
name: d.name || "-",
type: d.type || "-",
correct: d.correct,
message: d.message || "-",
location: d.location || "-"
},
groups: [
{ name: "Tables", items: this._state.database.tables || [] }
]
});
this._sources.set_data({
groups: [
{ name: "Mailboxes", type: "mailbox", items: this._state.mailboxes || [] },
{ name: "Directories", type: "directory", items: this._state.directories || [] }
]
});
}
else {
this._database.set_status("error", err_msg);
this._sources.set_status("error", err_msg);
}
if (this._state && this._state.database && this._state.database.needs_upgrade) {
document.querySelector(".db-menu-button li[data-action=upgradedb]").classList.remove("hidden");
}
}
_create_db_item_menu_element() {
let el = document.createElement("div");
let span = document.createElement("span");
span.setAttribute("role", "button");
span.appendChild(document.createTextNode("..."));
el.appendChild(span);
//
let mn = document.createElement("div");
mn.setAttribute("class", "db-item-menu popup-menu round-border hidden");
let ul = document.createElement("ul");
Admin.db_actions.forEach(function(it) {
let li = document.createElement("li");
li.setAttribute("data-action", it.action);
li.setAttribute("data-title", it.title);
li.setAttribute("title", it.long_title);
let sp = document.createElement("span");
sp.appendChild(document.createTextNode(it.name));
li.appendChild(sp);
ul.appendChild(li);
if (it.action === "upgradedb")
li.classList.add("hidden");
}, this);
mn.appendChild(ul);
el.appendChild(mn);
let t = this;
el.addEventListener("click", function(event) {
let it = event.target.closest("li");
if (it || event.target.parentNode === this) {
event.stopPropagation();
this.querySelector(".popup-menu").classList.toggle("hidden");
}
if (it) {
let action = it.getAttribute("data-action");
let title = it.getAttribute("data-title");
t._do_db_action_password(action, title);
}
});
return el;
}
_do_db_action_password(action, title) {
let ld = new LoginDialog({
nofetch: true,
nousername: true
});
document.getElementById("main-block").appendChild(ld.element());
let that = this;
ld.show().then(function(d) {
if (d) {
that._do_db_action(action, title, { password: d.password });
}
}).catch(function(err) {
Common.displayError(err);
}).finally(function() {
ld.remove();
});
}
_do_db_action(action, title, data) {
let d = { cmd: action };
if (data) {
d = Object.assign(d, data);
}
this._send_command(d).then(function(data) {
Common.checkResult(data);
Notification.add({ text: title + ": " + (data.message || "Completed successfully!"), type: "info" });
}).catch(function(err) {
Common.displayError(err);
Notification.add({ text: title + ": " + err.message, type: "error", delay: 10000 });
});
}
}
Admin.db_actions = [
{
name: "Initiate",
action: "initdb",
title: "Initiate DB",
long_title: "Create all needed tables and indexes in the database"
},
{
name: "Drop",
action: "cleandb",
title: "Drop tables",
long_title: "Drop all the tables from the database"
},
{
name: "Upgrade",
action: "upgradedb",
title: "Upgrade DB",
long_title: "Update the structure of the database"
}
];
class DropdownListBox {
constructor() {
this._item_groups = [];
this._element = null;
this._root_item = null
this._list_element = null;
}
element() {
if (!this._element) {
let el = document.createElement("div");
el.setAttribute("class", "round-border");
let that = this;
el.addEventListener("click", function(event) {
if (event.target.closest(".root-list-block")) {
if (that._item_groups.length > 0) {
that._list_element.classList.toggle("hidden");
that._root_item.element().classList.toggle("bottom-border");
}
}
});
this._element = el;
this._update_element();
}
return this._element;
}
set_status(type, message) {
if (type === "wait") {
set_wait_status(this.element());
}
else if (type === "error") {
set_error_status(this.element(), message);
}
}
set_data(data) {
this._root_item = new ListBoxItem();
this._make_group_list(data);
this._make_root_columns(data);
if (this._element) {
this._update_element();
}
}
_update_element() {
if (this._element.children.length != 2) {
remove_all_children(this._element);
this._element.appendChild(this._content_container());
this._element.appendChild(this._list_container());
}
}
_content_container() {
if (!this._root_item) {
this._root_item = new ListBoxItem();
}
let c = this._root_item.element();
let cl = [];
for (let i = 0; i < c.classList.length; ++i) {
if (c.classList[i].startsWith("state-"))
cl.push(c.classList[i]);
}
c.setAttribute("class", "root-list-block" + (cl.length >0 && (" " + cl.join(" ")) || ""));
return c;
}
_list_container() {
let c = document.createElement("div");
c.setAttribute("class", "list-container hidden");
c.appendChild(this._make_list_item_elements());
this._list_element = c;
return c;
}
_make_root_columns(data) {
}
_make_group_list(data) {
this._item_groups = data.groups.map(function(gd) {
return this._make_group_item(gd);
}, this);
}
_make_group_item(gr_data) {
return new ListBoxItemGroup(gr_data);
}
_make_list_item_elements() {
let fr = document.createDocumentFragment();
this._item_groups.forEach(function(ig) {
fr.appendChild(ig.element());
});
return fr;
}
}
class DatabaseListBox extends DropdownListBox {
constructor(menu) {
super();
this._menu = menu;
this._name = null;
this._type = null;
this._correct = false;
this._message = null;
this._location = null;
}
set_data(data) {
this._name = data.root.name;
this._type = data.root.type;
this._correct = data.root.correct;
this._message = data.root.message;
this._location = data.root.location;
super.set_data(data);
}
_make_root_columns(data) {
this._root_item.state(this._correct && "green" || "red");
this._root_item.add_column(new StatusIndicator(this._name, this._message, "title-item-wrap"));
this._root_item.add_column(new ListBoxColumn(this._message, null, "message-item state-text"));
this._root_item.add_column(new ListBoxColumn(this._type, null, "db-type"));
this._root_item.add_column(new ListBoxColumn(this._location, null, "db-location"));
if (this._menu)
this._root_item.add_column(new ListBoxColumn(this._menu, null, "db-menu-button"));
}
_make_group_item(gr_data) {
return new DatabaseItemGroup(gr_data);
}
}
class SourceListBox extends DropdownListBox {
element() {
let _new = !this._element && true || false;
super.element();
if (_new) {
let that = this;
this._element.addEventListener("click", function(event) {
if (event.target.tagName == "BUTTON") {
let p = event.target.closest("div[data-id]")
if (p) {
let id = parseInt(p.getAttribute("data-id"));
let type = p.getAttribute("data-type");
that._check_button_clicked(id, type, event.target);
}
}
});
}
return this._element;
}
_make_root_columns(data) {
let count = this._item_groups.reduce(function(cnt, gr) {
return cnt + gr.count();
}, 0);
let enabled = (count > 0);
this._root_item.state(enabled && "green" || "gray");
this._root_item.add_column(new StatusIndicator("Total sources: " + count, enabled && "Enabled" || "Disabled"));
}
_make_group_item(gr_data) {
return new SourceItemGroup(gr_data);
}
_check_button_clicked(id, type, btn) {
let that = this;
let state = "yellow";
let btn_text = btn.textContent;
btn.textContent = "Checking...";
btn.disabled = true;
window.fetch("admin.php", {
method: "POST",
cache: "no-store",
headers: Object.assign(HTTP_HEADERS, HTTP_HEADERS_POST),
credentials: "same-origin",
body: JSON.stringify({ cmd: "checksource", id: id, type: type })
}).then(function(resp) {
if (!resp.ok)
throw new Error("Failed");
return resp.json();
}).then(function(data) {
Common.checkResult(data);
let msg = [ data.message ];
if (data.status) {
if (type === "mailbox") {
msg.push("Messages: " + data.status.messages);
msg.push("Unseen: " + data.status.unseen);
}
else if (type === "directory") {
msg.push("Files: " + data.status.files);
}
}
Notification.add({ text: msg, type: "info" });
state = "green";
}).catch(function(err) {
Common.displayError(err);
Notification.add({ text: err.message, type: "error" });
}).finally(function() {
btn.textContent = btn_text;
btn.disabled = false;
that._set_state(state, id, type);
});
}
_set_state(state, id, type) {
let flag = 0;
let gstate = "green";
for (let i = 0; flag !== 3 && i < this._item_groups.length; ++i) {
let gr = this._item_groups[i];
if (!(flag & 1) && gr.type() === type) {
gr.state(state, id);
flag |= 1;
}
if (!(flag & 2)) {
let s = gr.state();
if (s !== "green") {
gstate = s;
flag |= 2;
}
}
}
this._root_item.state(gstate);
}
}
class ListBoxItem {
constructor() {
this._state = null;
this._element = null;
this._columns = [];
}
add_column(col) {
this._columns.push(col);
}
element() {
if (!this._element) {
this._element = document.createElement("div");
let extra_class = "";
if (this._state) {
extra_class = " state-" + this._state;
}
this._element.setAttribute("class", "block-list-item round-border" + extra_class);
this._insert_column_elements();
}
return this._element;
}
state(state) {
if (!state) {
return this._state;
}
if (this._element) {
if (this._state) {
this._element.classList.remove("state-" + this._state);
}
this._element.classList.add("state-" + state);
}
this._state = state;
}
_insert_column_elements() {
this._columns.forEach(function(c) {
this._element.appendChild(c.element());
}, this);
}
}
class SourceListItem extends ListBoxItem {
constructor(data) {
super();
this._id = data.id;
this._type = data.type;
}
id() {
return this._id;
}
type() {
return this._type;
}
element() {
let el = super.element();
el.setAttribute("data-id", this._id);
el.setAttribute("data-type", this._type);
return el;
}
}
class ListBoxItemGroup {
constructor(data) {
this._name = data.name;
this._type = data.type;
this._element = null;
this._items = data.items.map(function(it) {
return this._make_item(it);
}, this);
}
type() {
return this._type;
}
count() {
return this._items.length;
}
element() {
if (!this._element) {
let fr = document.createDocumentFragment();
let h = document.createElement("h5");
h.appendChild(document.createTextNode(this._name + " (" + this._items.length + ")"));
fr.appendChild(h);
this._items.forEach(function(it) {
fr.appendChild(it.element());
});
this._element = fr;
}
return this._element;
}
_make_item(d) {
return new ListBoxItem();
}
}
class DatabaseItemGroup extends ListBoxItemGroup {
_make_item(d) {
let it = super._make_item(d);
let state = d.error_code && "red" || (d.message === "Ok" && "green" || "yellow");
it.state(state);
it.add_column(new StatusIndicator(d.name, d.message, "title-item-wrap"));
it.add_column(new ListBoxColumn(d.engine || d.message, null, "message-item state-text"));
it.add_column(new ListBoxColumn(d.rows || 0, "Records", "dbtable-records"));
it.add_column(new ListBoxColumn((d.data_length || 0) + (d.index_length || 0), "Size", "dbtable-size"));
return it;
}
}
class SourceItemGroup extends ListBoxItemGroup {
state(new_state, item_id) {
if (item_id !== undefined) {
this._items.find(function(item) {
if (item.id() == item_id) {
item.state(new_state);
return true;
}
return false;
});
return;
}
let gstate = "green";
for (let i = 0; i < this._items.length; ++i) {
let state = this._items[i].state();
if (state !== gstate) {
return state;
}
}
return gstate;
}
_make_item(d) {
let it = new SourceListItem({ id: d.id, type: this._type });
it.state("green");
it.add_column(new StatusIndicator(d.name, null, "title-item-wrap"));
if (this._type === "mailbox") {
it.add_column(new ListBoxColumn(d.mailbox, null, "mailbox-location"));
it.add_column(new ListBoxColumn(d.host, "Host", "mailbox-host"));
}
else {
it.add_column(new ListBoxColumn(d.location, null, "directory-location"));
}
it.add_column(new ListBoxColumn(this._make_check_button(), null, "source-check-button"));
return it;
}
_make_check_button() {
let btn = document.createElement("button");
btn.appendChild(document.createTextNode("Check accessibility"));
return btn;
}
}
class ListBoxColumn {
constructor(value, title, class_string) {
this._value = value;
this._title = title;
this._class = class_string;
this._element = null;
}
element() {
if (!this._element) {
this._element = document.createElement("div");
this._element.setAttribute("class", "block-item-column" + (this._class && (" " + this._class) || ""));
this._add_children();
}
return this._element;
}
_add_children() {
let val_el = this._element;
if (this._title) {
let sp = document.createElement("span");
sp.appendChild(document.createTextNode(this._title + ":"));
this._element.appendChild(sp);
val_el = document.createElement("span");
val_el.setAttribute("class", "value");
this._element.appendChild(val_el);
}
if (typeof(this._value) != "object")
val_el.appendChild(document.createTextNode(this._value));
else
val_el.appendChild(this._value);
}
}
class StatusIndicator extends ListBoxColumn {
_add_children() {
let div = document.createElement("div");
div.setAttribute("class", "state-background status-indicator");
if (this._title) {
div.setAttribute("title", this._title);
}
this._element.appendChild(div);
if (this._value) {
div = document.createElement("div");
div.appendChild(document.createTextNode(this._value));
this._element.appendChild(div);
}
}
}