--- /dev/null
+/* global jQuery */
+
+define(["jquery", "nprogress"],
+ ($, NProgress) => {
+ "use strict";
+ const ui = {
+ chartLegend: [
+ {label: "reject", color: "#FF0000"},
+ {label: "soft reject", color: "#BF8040"},
+ {label: "rewrite subject", color: "#FF6600"},
+ {label: "add header", color: "#FFAD00"},
+ {label: "greylist", color: "#436EEE"},
+ {label: "no action", color: "#66CC00"}
+ ],
+ locale: (localStorage.getItem("selected_locale") === "custom") ? localStorage.getItem("custom_locale") : null,
+ neighbours: [],
+ page_size: {
+ scan: 25,
+ errors: 25,
+ history: 25
+ },
+ symbols: {
+ scan: [],
+ history: []
+ },
+ tables: {}
+ };
+
+
+ NProgress.configure({
+ minimum: 0.01,
+ showSpinner: false,
+ });
+
+ function getPassword() {
+ return sessionStorage.getItem("Password");
+ }
+
+ function alertMessage(alertClass, alertText) {
+ const a = $("<div class=\"alert " + alertClass + " alert-dismissible fade in show\">" +
+ "<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" title=\"Dismiss\"></button>" +
+ "<strong>" + alertText + "</strong>");
+ $(".notification-area").append(a);
+
+ setTimeout(() => {
+ $(a).fadeTo(500, 0).slideUp(500, function () {
+ $(this).alert("close");
+ });
+ }, 5000);
+ }
+
+ function queryServer(neighbours_status, ind, req_url, o) {
+ neighbours_status[ind].checked = false;
+ neighbours_status[ind].data = {};
+ neighbours_status[ind].status = false;
+ const req_params = {
+ jsonp: false,
+ data: o.data,
+ headers: $.extend({Password: getPassword()}, o.headers),
+ url: neighbours_status[ind].url + req_url,
+ xhr: function () {
+ const xhr = $.ajaxSettings.xhr();
+ // Download progress
+ if (req_url !== "neighbours") {
+ xhr.addEventListener("progress", (e) => {
+ if (e.lengthComputable) {
+ neighbours_status[ind].percentComplete = e.loaded / e.total;
+ const percentComplete = neighbours_status
+ .reduce((prev, curr) => (curr.percentComplete ? curr.percentComplete + prev : prev), 0);
+ NProgress.set(percentComplete / neighbours_status.length);
+ }
+ }, false);
+ }
+ return xhr;
+ },
+ success: function (json) {
+ neighbours_status[ind].checked = true;
+ neighbours_status[ind].status = true;
+ neighbours_status[ind].data = json;
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ neighbours_status[ind].checked = true;
+ function errorMessage() {
+ alertMessage("alert-error", neighbours_status[ind].name + " > " +
+ (o.errorMessage ? o.errorMessage : "Request failed") +
+ (errorThrown ? ": " + errorThrown : ""));
+ }
+ if (o.error) {
+ o.error(neighbours_status[ind],
+ jqXHR, textStatus, errorThrown);
+ } else if (o.errorOnceId) {
+ const alert_status = o.errorOnceId + neighbours_status[ind].name;
+ if (!(alert_status in sessionStorage)) {
+ sessionStorage.setItem(alert_status, true);
+ errorMessage();
+ }
+ } else {
+ errorMessage();
+ }
+ },
+ complete: function (jqXHR) {
+ if (neighbours_status.every((elt) => elt.checked)) {
+ if (neighbours_status.some((elt) => elt.status)) {
+ if (o.success) {
+ o.success(neighbours_status, jqXHR);
+ } else {
+ alertMessage("alert-success", "Request completed");
+ }
+ } else {
+ alertMessage("alert-error", "Request failed");
+ }
+ if (o.complete) o.complete();
+ NProgress.done();
+ }
+ },
+ statusCode: o.statusCode
+ };
+ if (o.method) {
+ req_params.method = o.method;
+ }
+ if (o.params) {
+ $.each(o.params, (k, v) => {
+ req_params[k] = v;
+ });
+ }
+ $.ajax(req_params);
+ }
+
+
+ // Public functions
+
+ ui.alertMessage = alertMessage;
+ ui.getPassword = getPassword;
+
+ // Get selectors' current state
+ ui.getSelector = function (id) {
+ const e = document.getElementById(id);
+ return e.options[e.selectedIndex].value;
+ };
+
+ /**
+ * @param {string} url - A string containing the URL to which the request is sent
+ * @param {Object} [options] - A set of key/value pairs that configure the Ajax request. All settings are optional.
+ *
+ * @param {Function} [options.complete] - A function to be called when the requests to all neighbours complete.
+ * @param {Object|string|Array} [options.data] - Data to be sent to the server.
+ * @param {Function} [options.error] - A function to be called if the request fails.
+ * @param {string} [options.errorMessage] - Text to display in the alert message if the request fails.
+ * @param {string} [options.errorOnceId] - A prefix of the alert ID to be added to the session storage. If the
+ * parameter is set, the error for each server will be displayed only once per session.
+ * @param {Object} [options.headers] - An object of additional header key/value pairs to send along with requests
+ * using the XMLHttpRequest transport.
+ * @param {string} [options.method] - The HTTP method to use for the request.
+ * @param {Object} [options.params] - An object of additional jQuery.ajax() settings key/value pairs.
+ * @param {string} [options.server] - A server to which send the request.
+ * @param {Function} [options.success] - A function to be called if the request succeeds.
+ *
+ * @returns {undefined}
+ */
+ ui.query = function (url, options) {
+ // Force options to be an object
+ const o = options || {};
+ Object.keys(o).forEach((option) => {
+ if (["complete", "data", "error", "errorMessage", "errorOnceId", "headers", "method", "params", "server",
+ "statusCode", "success"]
+ .indexOf(option) < 0) {
+ throw new Error("Unknown option: " + option);
+ }
+ });
+
+ let neighbours_status = [{
+ name: "local",
+ host: "local",
+ url: "",
+ }];
+ o.server = o.server || ui.getSelector("selSrv");
+ if (o.server === "All SERVERS") {
+ queryServer(neighbours_status, 0, "neighbours", {
+ success: function (json) {
+ const [{data}] = json;
+ if (jQuery.isEmptyObject(data)) {
+ ui.neighbours = {
+ local: {
+ host: window.location.host,
+ url: window.location.origin + window.location.pathname
+ }
+ };
+ } else {
+ ui.neighbours = data;
+ }
+ neighbours_status = [];
+ $.each(ui.neighbours, (ind) => {
+ neighbours_status.push({
+ name: ind,
+ host: ui.neighbours[ind].host,
+ url: ui.neighbours[ind].url,
+ });
+ });
+ $.each(neighbours_status, (ind) => {
+ queryServer(neighbours_status, ind, url, o);
+ });
+ },
+ errorMessage: "Cannot receive neighbours data"
+ });
+ } else {
+ if (o.server !== "local") {
+ neighbours_status = [{
+ name: o.server,
+ host: ui.neighbours[o.server].host,
+ url: ui.neighbours[o.server].url,
+ }];
+ }
+ queryServer(neighbours_status, 0, url, o);
+ }
+ };
+
+ ui.escapeHTML = function (string) {
+ const htmlEscaper = /[&<>"'/`=]/g;
+ const htmlEscapes = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ "\"": """,
+ "'": "'",
+ "/": "/",
+ "`": "`",
+ "=": "="
+ };
+ return String(string).replace(htmlEscaper, (match) => htmlEscapes[match]);
+ };
+
+ return ui;
+ });
/* global require */
-define(["jquery", "app/rspamd"],
- ($, rspamd) => {
+define(["jquery", "app/common"],
+ ($, common) => {
"use strict";
const ui = {};
ui.getActions = function getActions(checked_server) {
- rspamd.query("actions", {
+ common.query("actions", {
success: function (data) {
$("#actionsFormField").empty();
const items = [];
// String to array for comparison
const eltsArray = JSON.parse(elts);
if (eltsArray[0] < 0) {
- rspamd.alertMessage("alert-modal alert-error", "Spam can not be negative");
+ common.alertMessage("alert-modal alert-error", "Spam can not be negative");
} else if (eltsArray[1] < 0) {
- rspamd.alertMessage("alert-modal alert-error", "Rewrite subject can not be negative");
+ common.alertMessage("alert-modal alert-error", "Rewrite subject can not be negative");
} else if (eltsArray[2] < 0) {
- rspamd.alertMessage("alert-modal alert-error", "Probable spam can not be negative");
+ common.alertMessage("alert-modal alert-error", "Probable spam can not be negative");
} else if (eltsArray[3] < 0) {
- rspamd.alertMessage("alert-modal alert-error", "Greylist can not be negative");
+ common.alertMessage("alert-modal alert-error", "Greylist can not be negative");
} else if (descending(eltsArray)) {
- rspamd.query("saveactions", {
+ common.query("saveactions", {
method: "POST",
params: {
data: elts,
server: server
});
} else {
- rspamd.alertMessage("alert-modal alert-error", "Incorrect order of actions thresholds");
+ common.alertMessage("alert-modal alert-error", "Incorrect order of actions thresholds");
}
};
ui.getMaps = function (checked_server) {
const $listmaps = $("#listMaps");
$listmaps.closest(".card").hide();
- rspamd.query("maps", {
+ common.query("maps", {
success: function (json) {
const [{data}] = json;
$listmaps.empty();
$.each(data, (i, item) => {
let $td = '<td><span class="badge text-bg-secondary">Read</span></td>';
- if (!(item.editable === false || rspamd.read_only)) {
+ if (!(item.editable === false || common.read_only)) {
$td = $($td).append(' <span class="badge text-bg-success">Write</span>');
}
const $tr = $("<tr>").append($td);
// Modal form for maps
$(document).on("click", "[data-bs-toggle=\"modal\"]", function () {
- const checked_server = rspamd.getSelector("selSrv");
+ const checked_server = common.getSelector("selSrv");
const item = $(this).data("item");
- rspamd.query("getmap", {
+ common.query("getmap", {
headers: {
Map: item.map
},
jar.updateCode(data[0].data);
});
} else {
- document.querySelector("#editor").innerHTML = rspamd.escapeHTML(data[0].data);
+ document.querySelector("#editor").innerHTML = common.escapeHTML(data[0].data);
}
let icon = "fa-edit";
- if (item.editable === false || rspamd.read_only) {
+ if (item.editable === false || common.read_only) {
$("#editor").attr(editor[mode].readonly_attr);
icon = "fa-eye";
$("#modalSaveGroup").hide();
});
function saveMap(server) {
- rspamd.query("savemap", {
+ common.query("savemap", {
success: function () {
- rspamd.alertMessage("alert-success", "Map data successfully saved");
+ common.alertMessage("alert-success", "Map data successfully saved");
$("#modalDialog").modal("hide");
},
errorMessage: "Save map error",
/* global FooTable */
-define(["jquery", "app/rspamd", "d3evolution", "d3pie", "d3", "footable"],
- ($, rspamd, D3Evolution, D3Pie, d3) => {
+define(["jquery", "app/common", "d3evolution", "d3pie", "d3", "footable"],
+ ($, common, D3Evolution, D3Pie, d3) => {
"use strict";
const rrd_pie_config = {
legend: {
space: 140,
- entries: rspamd.chartLegend
+ entries: common.chartLegend
}
};
function initGraph() {
const graph = new D3Evolution("graph", $.extend({}, graph_options, {
- yScale: rspamd.getSelector("selYScale"),
- type: rspamd.getSelector("selType"),
- interpolate: rspamd.getSelector("selInterpolate"),
- convert: rspamd.getSelector("selConvert"),
+ yScale: common.getSelector("selYScale"),
+ type: common.getSelector("selType"),
+ interpolate: common.getSelector("selInterpolate"),
+ convert: common.getSelector("selConvert"),
}));
$("#selYScale").change(function () {
graph.yScale(this.value);
}
function initSummaryTable(rows, unit) {
- rspamd.tables.rrd_summary = FooTable.init("#rrd-table", {
+ common.tables.rrd_summary = FooTable.init("#rrd-table", {
sorting: {
enabled: true
},
}
function drawRrdTable(rows, unit) {
- if (Object.prototype.hasOwnProperty.call(rspamd.tables, "rrd_summary")) {
- $.each(rspamd.tables.rrd_summary.rows.all, (i, row) => {
+ if (Object.prototype.hasOwnProperty.call(common.tables, "rrd_summary")) {
+ $.each(common.tables.rrd_summary.rows.all, (i, row) => {
row.val(rows[i], false, true);
});
} else {
}
- rspamd.query("graph", {
+ common.query("graph", {
success: function (req_data) {
let data = null;
const neighbours_data = req_data
if ((curr[0][0].x !== res[0][0].x) ||
(curr[0][curr[0].length - 1].x !== res[0][res[0].length - 1].x)) {
time_match = false;
- rspamd.alertMessage("alert-error",
+ common.alertMessage("alert-error",
"Neighbours time extents do not match. Check if time is synchronized on all servers.");
arr.splice(1); // Break out of .reduce() by mutating the source array
}
/* global FooTable */
-define(["jquery", "app/rspamd", "d3", "footable"],
- ($, rspamd, d3) => {
+define(["jquery", "app/common", "app/libft", "d3", "footable"],
+ ($, common, libft, d3) => {
"use strict";
const ui = {};
let prevVersion = null;
$("#selSymOrder_history, label[for='selSymOrder_history']").hide();
$.each(data, (i, item) => {
- item.time = rspamd.unix_time_format(item.unix_time);
- rspamd.preprocess_item(item);
+ item.time = libft.unix_time_format(item.unix_time);
+ libft.preprocess_item(item);
item.symbols = Object.keys(item.symbols)
.map((key) => item.symbols[key])
.sort(compare)
.map((e) => e.name)
.join(", ");
item.time = {
- value: rspamd.unix_time_format(item.unix_time),
+ value: libft.unix_time_format(item.unix_time),
options: {
sortValue: item.unix_time
}
function process_history_data(data) {
const process_functions = {
- 2: rspamd.process_history_v2,
+ 2: libft.process_history_v2,
legacy: process_history_legacy
};
let pf = process_functions.legacy;
}
ui.getHistory = function () {
- rspamd.query("history", {
+ common.query("history", {
success: function (req_data) {
function differentVersions(neighbours_data) {
const dv = neighbours_data.some((e) => e.version !== neighbours_data[0].version);
if (dv) {
- rspamd.alertMessage("alert-error",
+ common.alertMessage("alert-error",
"Neighbours history backend versions do not match. Cannot display history.");
return true;
}
}
const o = process_history_data(data);
const {items} = o;
- rspamd.symbols.history = o.symbols;
+ common.symbols.history = o.symbols;
- if (Object.prototype.hasOwnProperty.call(rspamd.tables, "history") &&
+ if (Object.prototype.hasOwnProperty.call(common.tables, "history") &&
version === prevVersion) {
- rspamd.tables.history.rows.load(items);
+ common.tables.history.rows.load(items);
} else {
- rspamd.destroyTable("history");
+ libft.destroyTable("history");
// Is there a way to get an event when the table is destroyed?
setTimeout(() => {
- rspamd.initHistoryTable(data, items, "history", get_history_columns(data), false);
+ libft.initHistoryTable(data, items, "history", get_history_columns(data), false);
}, 200);
}
prevVersion = version;
} else {
- rspamd.destroyTable("history");
+ libft.destroyTable("history");
}
},
complete: function () { $("#refresh").removeAttr("disabled").removeClass("disabled"); },
};
function initErrorsTable(rows) {
- rspamd.tables.errors = FooTable.init("#errorsLog", {
+ common.tables.errors = FooTable.init("#errorsLog", {
columns: [
{sorted: true,
direction: "DESC",
paging: {
enabled: true,
limit: 5,
- size: rspamd.page_size.errors
+ size: common.page_size.errors
},
filtering: {
enabled: true,
}
ui.getErrors = function () {
- if (rspamd.read_only) return;
+ if (common.read_only) return;
- rspamd.query("errors", {
+ common.query("errors", {
success: function (data) {
const neighbours_data = data
.filter((d) => d.status) // filter out unavailable neighbours
const rows = [].concat.apply([], neighbours_data);
$.each(rows, (i, item) => {
item.ts = {
- value: rspamd.unix_time_format(item.ts),
+ value: libft.unix_time_format(item.ts),
options: {
sortValue: item.ts
}
};
});
- if (Object.prototype.hasOwnProperty.call(rspamd.tables, "errors")) {
- rspamd.tables.errors.rows.load(rows);
+ if (Object.prototype.hasOwnProperty.call(common.tables, "errors")) {
+ common.tables.errors.rows.load(rows);
} else {
initErrorsTable(rows);
}
};
- rspamd.set_page_size("history", $("#history_page_size").val());
- rspamd.bindHistoryTableEventHandlers("history", 8);
+ libft.set_page_size("history", $("#history_page_size").val());
+ libft.bindHistoryTableEventHandlers("history", 8);
$("#updateHistory").off("click");
$("#updateHistory").on("click", (e) => {
if (!confirm("Are you sure you want to reset history log?")) { // eslint-disable-line no-alert
return;
}
- rspamd.destroyTable("history");
- rspamd.destroyTable("errors");
+ libft.destroyTable("history");
+ libft.destroyTable("errors");
- rspamd.query("historyreset", {
+ common.query("historyreset", {
success: function () {
ui.getHistory();
ui.getErrors();
--- /dev/null
+/* global FooTable */
+
+define(["jquery", "app/common", "footable"],
+ ($, common) => {
+ "use strict";
+ const ui = {};
+
+ let pageSizeTimerId = null;
+ let pageSizeInvocationCounter = 0;
+
+ function get_compare_function(table) {
+ const compare_functions = {
+ magnitude: function (e1, e2) {
+ return Math.abs(e2.score) - Math.abs(e1.score);
+ },
+ name: function (e1, e2) {
+ return e1.name.localeCompare(e2.name);
+ },
+ score: function (e1, e2) {
+ return e2.score - e1.score;
+ }
+ };
+
+ return compare_functions[common.getSelector("selSymOrder_" + table)];
+ }
+
+ function sort_symbols(o, compare_function) {
+ return Object.keys(o)
+ .map((key) => o[key])
+ .sort(compare_function)
+ .map((e) => e.str)
+ .join("<br>\n");
+ }
+
+
+ // Public functions
+
+ ui.set_page_size = function (table, page_size, changeTablePageSize) {
+ const n = parseInt(page_size, 10); // HTML Input elements return string representing a number
+ if (n > 0) {
+ common.page_size[table] = n;
+
+ if (changeTablePageSize &&
+ $("#historyTable_" + table + " tbody").is(":parent")) { // Table is not empty
+ clearTimeout(pageSizeTimerId);
+ const t = FooTable.get("#historyTable_" + table);
+ if (t) {
+ pageSizeInvocationCounter = 0;
+ // Wait for input finish
+ pageSizeTimerId = setTimeout(() => t.pageSize(n), 1000);
+ } else if (++pageSizeInvocationCounter < 10) {
+ // Wait for FooTable instance ready
+ pageSizeTimerId = setTimeout(() => ui.set_page_size(table, n, true), 1000);
+ }
+ }
+ }
+ };
+
+ ui.bindHistoryTableEventHandlers = function (table, symbolsCol) {
+ function change_symbols_order(order) {
+ $(".btn-sym-" + table + "-" + order).addClass("active").siblings().removeClass("active");
+ const compare_function = get_compare_function(table);
+ $.each(common.tables[table].rows.all, (i, row) => {
+ const cell_val = sort_symbols(common.symbols[table][i], compare_function);
+ row.cells[symbolsCol].val(cell_val, false, true);
+ });
+ }
+
+ $("#selSymOrder_" + table).unbind().change(function () {
+ const order = this.value;
+ change_symbols_order(order);
+ });
+ $("#" + table + "_page_size").change((e) => ui.set_page_size(table, e.target.value, true));
+ $(document).on("click", ".btn-sym-order-" + table + " input", function () {
+ const order = this.value;
+ $("#selSymOrder_" + table).val(order);
+ change_symbols_order(order);
+ });
+ };
+
+ ui.destroyTable = function (table) {
+ if (common.tables[table]) {
+ common.tables[table].destroy();
+ delete common.tables[table];
+ }
+ };
+
+ ui.initHistoryTable = function (data, items, table, columns, expandFirst) {
+ /* eslint-disable no-underscore-dangle */
+ FooTable.Cell.extend("collapse", function () {
+ // call the original method
+ this._super();
+ // Copy cell classes to detail row tr element
+ this._setClasses(this.$detail);
+ });
+ /* eslint-enable no-underscore-dangle */
+
+ /* eslint-disable consistent-this, no-underscore-dangle, one-var-declaration-per-line */
+ FooTable.actionFilter = FooTable.Filtering.extend({
+ construct: function (instance) {
+ this._super(instance);
+ this.actions = ["reject", "add header", "greylist",
+ "no action", "soft reject", "rewrite subject"];
+ this.def = "Any action";
+ this.$action = null;
+ },
+ $create: function () {
+ this._super();
+ const self = this;
+ const $form_grp = $("<div/>", {
+ class: "form-group d-inline-flex align-items-center"
+ }).append($("<label/>", {
+ class: "sr-only",
+ text: "Action"
+ })).prependTo(self.$form);
+
+ $("<div/>", {
+ class: "form-check form-check-inline",
+ title: "Invert action match."
+ }).append(
+ self.$not = $("<input/>", {
+ type: "checkbox",
+ class: "form-check-input",
+ id: "not_" + table
+ }).on("change", {self: self}, self._onStatusDropdownChanged),
+ $("<label/>", {
+ class: "form-check-label",
+ for: "not_" + table,
+ text: "not"
+ })
+ ).appendTo($form_grp);
+
+ self.$action = $("<select/>", {
+ class: "form-select"
+ }).on("change", {
+ self: self
+ }, self._onStatusDropdownChanged).append(
+ $("<option/>", {
+ text: self.def
+ })).appendTo($form_grp);
+
+ $.each(self.actions, (i, action) => {
+ self.$action.append($("<option/>").text(action));
+ });
+ },
+ _onStatusDropdownChanged: function (e) {
+ const {self} = e.data;
+ const selected = self.$action.val();
+ if (selected !== self.def) {
+ const not = self.$not.is(":checked");
+ let query = null;
+
+ if (selected === "reject") {
+ query = not ? "-reject OR soft" : "reject -soft";
+ } else {
+ query = not ? selected.replace(/(\b\w+\b)/g, "-$1") : selected;
+ }
+
+ self.addFilter("action", query, ["action"]);
+ } else {
+ self.removeFilter("action");
+ }
+ self.filter();
+ }
+ });
+ /* eslint-enable consistent-this, no-underscore-dangle, one-var-declaration-per-line */
+
+ common.tables[table] = FooTable.init("#historyTable_" + table, {
+ columns: columns,
+ rows: items,
+ expandFirst: expandFirst,
+ paging: {
+ enabled: true,
+ limit: 5,
+ size: common.page_size[table]
+ },
+ filtering: {
+ enabled: true,
+ position: "left",
+ connectors: false
+ },
+ sorting: {
+ enabled: true
+ },
+ components: {
+ filtering: FooTable.actionFilter
+ },
+ on: {
+ "expand.ft.row": function (e, ft, row) {
+ setTimeout(() => {
+ const detail_row = row.$el.next();
+ const order = common.getSelector("selSymOrder_" + table);
+ detail_row.find(".btn-sym-" + table + "-" + order)
+ .addClass("active").siblings().removeClass("active");
+ }, 5);
+ }
+ }
+ });
+ };
+
+ ui.preprocess_item = function (item) {
+ function escape_HTML_array(arr) {
+ arr.forEach((d, i) => { arr[i] = common.escapeHTML(d); });
+ }
+
+ for (const prop in item) {
+ if (!{}.hasOwnProperty.call(item, prop)) continue;
+ switch (prop) {
+ case "rcpt_mime":
+ case "rcpt_smtp":
+ escape_HTML_array(item[prop]);
+ break;
+ case "symbols":
+ Object.keys(item.symbols).forEach((key) => {
+ const sym = item.symbols[key];
+ if (!sym.name) {
+ sym.name = key;
+ }
+ sym.name = common.escapeHTML(sym.name);
+ if (sym.description) {
+ sym.description = common.escapeHTML(sym.description);
+ }
+
+ if (sym.options) {
+ escape_HTML_array(sym.options);
+ }
+ });
+ break;
+ default:
+ if (typeof item[prop] === "string") {
+ item[prop] = common.escapeHTML(item[prop]);
+ }
+ }
+ }
+
+ if (item.action === "clean" || item.action === "no action") {
+ item.action = "<div style='font-size:11px' class='badge text-bg-success'>" + item.action + "</div>";
+ } else if (item.action === "rewrite subject" || item.action === "add header" || item.action === "probable spam") {
+ item.action = "<div style='font-size:11px' class='badge text-bg-warning'>" + item.action + "</div>";
+ } else if (item.action === "spam" || item.action === "reject") {
+ item.action = "<div style='font-size:11px' class='badge text-bg-danger'>" + item.action + "</div>";
+ } else {
+ item.action = "<div style='font-size:11px' class='badge text-bg-info'>" + item.action + "</div>";
+ }
+
+ const score_content = (item.score < item.required_score)
+ ? "<span class='text-success'>" + item.score.toFixed(2) + " / " + item.required_score + "</span>"
+ : "<span class='text-danger'>" + item.score.toFixed(2) + " / " + item.required_score + "</span>";
+
+ item.score = {
+ options: {
+ sortValue: item.score
+ },
+ value: score_content
+ };
+ };
+
+ ui.unix_time_format = function (tm) {
+ const date = new Date(tm ? tm * 1000 : 0);
+ return (common.locale)
+ ? date.toLocaleString(common.locale)
+ : date.toLocaleString();
+ };
+
+ ui.process_history_v2 = function (data, table) {
+ // Display no more than rcpt_lim recipients
+ const rcpt_lim = 3;
+ const items = [];
+ const unsorted_symbols = [];
+ const compare_function = get_compare_function(table);
+
+ $("#selSymOrder_" + table + ", label[for='selSymOrder_" + table + "']").show();
+
+ $.each(data.rows,
+ (i, item) => {
+ function more(p) {
+ const l = item[p].length;
+ return (l > rcpt_lim) ? " … (" + l + ")" : "";
+ }
+ function format_rcpt(smtp, mime) {
+ let full = "";
+ let shrt = "";
+ if (smtp) {
+ full = "[" + item.rcpt_smtp.join(", ") + "] ";
+ shrt = "[" + item.rcpt_smtp.slice(0, rcpt_lim).join(",​") + more("rcpt_smtp") + "]";
+ if (mime) {
+ full += " ";
+ shrt += " ";
+ }
+ }
+ if (mime) {
+ full += item.rcpt_mime.join(", ");
+ shrt += item.rcpt_mime.slice(0, rcpt_lim).join(",​") + more("rcpt_mime");
+ }
+ return {full: full, shrt: shrt};
+ }
+
+ function get_symbol_class(name, score) {
+ if (name.match(/^GREYLIST$/)) {
+ return "symbol-special";
+ }
+
+ if (score < 0) {
+ return "symbol-negative";
+ } else if (score > 0) {
+ return "symbol-positive";
+ }
+ return null;
+ }
+
+ ui.preprocess_item(item);
+ Object.values(item.symbols).forEach((sym) => {
+ sym.str = '<span class="symbol-default ' + get_symbol_class(sym.name, sym.score) + '"><strong>';
+
+ if (sym.description) {
+ sym.str += '<abbr title="' + sym.description + '">' + sym.name + "</abbr>";
+ } else {
+ sym.str += sym.name;
+ }
+ sym.str += "</strong> (" + sym.score + ")</span>";
+
+ if (sym.options) {
+ sym.str += " [" + sym.options.join(",") + "]";
+ }
+ });
+ unsorted_symbols.push(item.symbols);
+ item.symbols = sort_symbols(item.symbols, compare_function);
+ if (table === "scan") {
+ item.unix_time = (new Date()).getTime() / 1000;
+ }
+ item.time = {
+ value: ui.unix_time_format(item.unix_time),
+ options: {
+ sortValue: item.unix_time
+ }
+ };
+ item.time_real = item.time_real.toFixed(3);
+ item.id = item["message-id"];
+
+ if (table === "history") {
+ let rcpt = {};
+ if (!item.rcpt_mime.length) {
+ rcpt = format_rcpt(true, false);
+ } else if (
+ $(item.rcpt_mime).not(item.rcpt_smtp).length !== 0 ||
+ $(item.rcpt_smtp).not(item.rcpt_mime).length !== 0
+ ) {
+ rcpt = format_rcpt(true, true);
+ } else {
+ rcpt = format_rcpt(false, true);
+ }
+ item.rcpt_mime_short = rcpt.shrt;
+ item.rcpt_mime = rcpt.full;
+
+ if (item.sender_mime !== item.sender_smtp) {
+ item.sender_mime = "[" + item.sender_smtp + "] " + item.sender_mime;
+ }
+ }
+ items.push(item);
+ });
+
+ return {items: items, symbols: unsorted_symbols};
+ };
+
+ ui.waitForRowsDisplayed = function (table, rows_total, callback, iteration) {
+ let i = (typeof iteration === "undefined") ? 10 : iteration;
+ const num_rows = $("#historyTable_" + table + " > tbody > tr:not(.footable-detail-row)").length;
+ if (num_rows === common.page_size[table] ||
+ num_rows === rows_total) {
+ return callback();
+ } else if (--i) {
+ setTimeout(() => {
+ ui.waitForRowsDisplayed(table, rows_total, callback, i);
+ }, 500);
+ }
+ return null;
+ };
+
+ return ui;
+ });
THE SOFTWARE.
*/
-/* global jQuery, FooTable, require, Visibility */
+/* global require, Visibility */
-define(["jquery", "nprogress", "stickytabs", "visibility",
+define(["jquery", "app/common", "stickytabs", "visibility",
"bootstrap", "fontawesome"],
-($, NProgress) => {
+($, common) => {
"use strict";
- const ui = {
- chartLegend: [
- {label: "reject", color: "#FF0000"},
- {label: "soft reject", color: "#BF8040"},
- {label: "rewrite subject", color: "#FF6600"},
- {label: "add header", color: "#FFAD00"},
- {label: "greylist", color: "#436EEE"},
- {label: "no action", color: "#66CC00"}
- ],
- page_size: {
- scan: 25,
- errors: 25,
- history: 25
- },
- symbols: {
- scan: [],
- history: []
- }
- };
+ const ui = {};
const defaultAjaxTimeout = 20000;
const ajaxTimeoutBox = ".popover #settings-popover #ajax-timeout";
const graphs = {};
- const tables = {};
- let neighbours = []; // list of clusters
let checked_server = "All SERVERS";
const timer_id = [];
- let pageSizeTimerId = null;
- let pageSizeInvocationCounter = 0;
- let locale = (localStorage.getItem("selected_locale") === "custom") ? localStorage.getItem("custom_locale") : null;
-
- NProgress.configure({
- minimum: 0.01,
- showSpinner: false,
- });
function ajaxSetup(ajax_timeout, setFieldValue, saveToLocalStorage) {
const timeout = (ajax_timeout && ajax_timeout >= 0) ? ajax_timeout : defaultAjaxTimeout;
}
function disconnect() {
- [graphs, tables].forEach((o) => {
+ [graphs, common.tables].forEach((o) => {
Object.keys(o).forEach((key) => {
o[key].destroy();
delete o[key];
ui.connect();
}
- // Get selectors' current state
- function getSelector(id) {
- const e = document.getElementById(id);
- return e.options[e.selectedIndex].value;
- }
-
function tabClick(id) {
let tab_id = id;
if ($(id).attr("disabled")) return;
break;
case "#throughput_nav":
require(["app/graph"], (module) => {
- const selData = getSelector("selData"); // Graph's dataset selector state
+ const selData = common.getSelector("selData"); // Graph's dataset selector state
const step = {
day: 60000,
week: 300000
refreshInterval = null;
}
setAutoRefresh(refreshInterval, "throughput",
- () => module.draw(graphs, neighbours, checked_server, selData));
- if (id !== "#autoRefresh") module.draw(graphs, neighbours, checked_server, selData);
+ () => module.draw(graphs, common.neighbours, checked_server, selData));
+ if (id !== "#autoRefresh") module.draw(graphs, common.neighbours, checked_server, selData);
$(".preset").hide();
$(".history").hide();
}, (id === "#autoRefresh") ? 0 : 1000);
}
- function getPassword() {
- return sessionStorage.getItem("Password");
- }
-
- function get_compare_function(table) {
- const compare_functions = {
- magnitude: function (e1, e2) {
- return Math.abs(e2.score) - Math.abs(e1.score);
- },
- name: function (e1, e2) {
- return e1.name.localeCompare(e2.name);
- },
- score: function (e1, e2) {
- return e2.score - e1.score;
- }
- };
-
- return compare_functions[getSelector("selSymOrder_" + table)];
- }
-
function saveCredentials(password) {
sessionStorage.setItem("Password", password);
}
- function set_page_size(table, page_size, changeTablePageSize) {
- const n = parseInt(page_size, 10); // HTML Input elements return string representing a number
- if (n > 0) {
- ui.page_size[table] = n;
-
- if (changeTablePageSize &&
- $("#historyTable_" + table + " tbody").is(":parent")) { // Table is not empty
- clearTimeout(pageSizeTimerId);
- const t = FooTable.get("#historyTable_" + table);
- if (t) {
- pageSizeInvocationCounter = 0;
- // Wait for input finish
- pageSizeTimerId = setTimeout(() => t.pageSize(n), 1000);
- } else if (++pageSizeInvocationCounter < 10) {
- // Wait for FooTable instance ready
- pageSizeTimerId = setTimeout(() => set_page_size(table, n, true), 1000);
- }
- }
- }
- }
-
- function sort_symbols(o, compare_function) {
- return Object.keys(o)
- .map((key) => o[key])
- .sort(compare_function)
- .map((e) => e.str)
- .join("<br>\n");
- }
-
- function unix_time_format(tm) {
- const date = new Date(tm ? tm * 1000 : 0);
- return (locale)
- ? date.toLocaleString(locale)
- : date.toLocaleString();
- }
-
function displayUI() {
// In many browsers local storage can only store string.
// So when we store the boolean true or false, it actually stores the strings "true" or "false".
- ui.read_only = sessionStorage.getItem("read_only") === "true";
+ common.read_only = sessionStorage.getItem("read_only") === "true";
- ui.query("auth", {
+ common.query("auth", {
success: function (neighbours_status) {
$("#selSrv").empty();
$("#selSrv").append($('<option value="All SERVERS">All SERVERS</option>'));
complete: function () {
ajaxSetup(localStorage.getItem("ajax_timeout"));
- if (ui.read_only) {
+ if (common.read_only) {
$(".ro-disable").attr("disabled", true);
$(".ro-hide").hide();
} else {
});
}
- function alertMessage(alertClass, alertText) {
- const a = $("<div class=\"alert " + alertClass + " alert-dismissible fade in show\">" +
- "<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" title=\"Dismiss\"></button>" +
- "<strong>" + alertText + "</strong>");
- $(".notification-area").append(a);
-
- setTimeout(() => {
- $(a).fadeTo(500, 0).slideUp(500, function () {
- $(this).alert("close");
- });
- }, 5000);
- }
-
- function queryServer(neighbours_status, ind, req_url, o) {
- neighbours_status[ind].checked = false;
- neighbours_status[ind].data = {};
- neighbours_status[ind].status = false;
- const req_params = {
- jsonp: false,
- data: o.data,
- headers: $.extend({Password: getPassword()}, o.headers),
- url: neighbours_status[ind].url + req_url,
- xhr: function () {
- const xhr = $.ajaxSettings.xhr();
- // Download progress
- if (req_url !== "neighbours") {
- xhr.addEventListener("progress", (e) => {
- if (e.lengthComputable) {
- neighbours_status[ind].percentComplete = e.loaded / e.total;
- const percentComplete = neighbours_status
- .reduce((prev, curr) => (curr.percentComplete ? curr.percentComplete + prev : prev), 0);
- NProgress.set(percentComplete / neighbours_status.length);
- }
- }, false);
- }
- return xhr;
- },
- success: function (json) {
- neighbours_status[ind].checked = true;
- neighbours_status[ind].status = true;
- neighbours_status[ind].data = json;
- },
- error: function (jqXHR, textStatus, errorThrown) {
- neighbours_status[ind].checked = true;
- function errorMessage() {
- alertMessage("alert-error", neighbours_status[ind].name + " > " +
- (o.errorMessage ? o.errorMessage : "Request failed") +
- (errorThrown ? ": " + errorThrown : ""));
- }
- if (o.error) {
- o.error(neighbours_status[ind],
- jqXHR, textStatus, errorThrown);
- } else if (o.errorOnceId) {
- const alert_status = o.errorOnceId + neighbours_status[ind].name;
- if (!(alert_status in sessionStorage)) {
- sessionStorage.setItem(alert_status, true);
- errorMessage();
- }
- } else {
- errorMessage();
- }
- },
- complete: function (jqXHR) {
- if (neighbours_status.every((elt) => elt.checked)) {
- if (neighbours_status.some((elt) => elt.status)) {
- if (o.success) {
- o.success(neighbours_status, jqXHR);
- } else {
- alertMessage("alert-success", "Request completed");
- }
- } else {
- alertMessage("alert-error", "Request failed");
- }
- if (o.complete) o.complete();
- NProgress.done();
- }
- },
- statusCode: o.statusCode
- };
- if (o.method) {
- req_params.method = o.method;
- }
- if (o.params) {
- $.each(o.params, (k, v) => {
- req_params[k] = v;
- });
- }
- $.ajax(req_params);
- }
// Public functions
- ui.alertMessage = alertMessage;
ui.connect = function () {
// Prevent locking out of the WebUI if timeout is too low.
return;
}
- ui.query("auth", {
+ common.query("auth", {
headers: {
Password: password
},
if (textStatus.statusText === "Unauthorized") {
invalidFeedback("#authUnauthorizedFeedback");
} else {
- ui.alertMessage("alert-modal alert-error", textStatus.statusText);
+ common.alertMessage("alert-modal alert-error", textStatus.statusText);
}
$("#connectPassword").val("");
$("#connectPassword").focus();
});
};
- ui.getPassword = getPassword;
- ui.getSelector = getSelector;
-
- /**
- * @param {string} url - A string containing the URL to which the request is sent
- * @param {Object} [options] - A set of key/value pairs that configure the Ajax request. All settings are optional.
- *
- * @param {Function} [options.complete] - A function to be called when the requests to all neighbours complete.
- * @param {Object|string|Array} [options.data] - Data to be sent to the server.
- * @param {Function} [options.error] - A function to be called if the request fails.
- * @param {string} [options.errorMessage] - Text to display in the alert message if the request fails.
- * @param {string} [options.errorOnceId] - A prefix of the alert ID to be added to the session storage. If the
- * parameter is set, the error for each server will be displayed only once per session.
- * @param {Object} [options.headers] - An object of additional header key/value pairs to send along with requests
- * using the XMLHttpRequest transport.
- * @param {string} [options.method] - The HTTP method to use for the request.
- * @param {Object} [options.params] - An object of additional jQuery.ajax() settings key/value pairs.
- * @param {string} [options.server] - A server to which send the request.
- * @param {Function} [options.success] - A function to be called if the request succeeds.
- *
- * @returns {undefined}
- */
- ui.query = function (url, options) {
- // Force options to be an object
- const o = options || {};
- Object.keys(o).forEach((option) => {
- if (["complete", "data", "error", "errorMessage", "errorOnceId", "headers", "method", "params", "server",
- "statusCode", "success"]
- .indexOf(option) < 0) {
- throw new Error("Unknown option: " + option);
- }
- });
-
- let neighbours_status = [{
- name: "local",
- host: "local",
- url: "",
- }];
- o.server = o.server || checked_server;
- if (o.server === "All SERVERS") {
- queryServer(neighbours_status, 0, "neighbours", {
- success: function (json) {
- const [{data}] = json;
- if (jQuery.isEmptyObject(data)) {
- neighbours = {
- local: {
- host: window.location.host,
- url: window.location.origin + window.location.pathname
- }
- };
- } else {
- neighbours = data;
- }
- neighbours_status = [];
- $.each(neighbours, (ind) => {
- neighbours_status.push({
- name: ind,
- host: neighbours[ind].host,
- url: neighbours[ind].url,
- });
- });
- $.each(neighbours_status, (ind) => {
- queryServer(neighbours_status, ind, url, o);
- });
- },
- errorMessage: "Cannot receive neighbours data"
- });
- } else {
- if (o.server !== "local") {
- neighbours_status = [{
- name: o.server,
- host: neighbours[o.server].host,
- url: neighbours[o.server].url,
- }];
- }
- queryServer(neighbours_status, 0, url, o);
- }
- };
-
- // Scan and History shared functions
-
- ui.tables = tables;
- ui.unix_time_format = unix_time_format;
- ui.set_page_size = set_page_size;
-
- ui.bindHistoryTableEventHandlers = function (table, symbolsCol) {
- function change_symbols_order(order) {
- $(".btn-sym-" + table + "-" + order).addClass("active").siblings().removeClass("active");
- const compare_function = get_compare_function(table);
- $.each(tables[table].rows.all, (i, row) => {
- const cell_val = sort_symbols(ui.symbols[table][i], compare_function);
- row.cells[symbolsCol].val(cell_val, false, true);
- });
- }
-
- $("#selSymOrder_" + table).unbind().change(function () {
- const order = this.value;
- change_symbols_order(order);
- });
- $("#" + table + "_page_size").change((e) => set_page_size(table, e.target.value, true));
- $(document).on("click", ".btn-sym-order-" + table + " input", function () {
- const order = this.value;
- $("#selSymOrder_" + table).val(order);
- change_symbols_order(order);
- });
- };
-
- ui.destroyTable = function (table) {
- if (tables[table]) {
- tables[table].destroy();
- delete tables[table];
- }
- };
-
-
- ui.initHistoryTable = function (data, items, table, columns, expandFirst) {
- /* eslint-disable no-underscore-dangle */
- FooTable.Cell.extend("collapse", function () {
- // call the original method
- this._super();
- // Copy cell classes to detail row tr element
- this._setClasses(this.$detail);
- });
- /* eslint-enable no-underscore-dangle */
-
- /* eslint-disable consistent-this, no-underscore-dangle, one-var-declaration-per-line */
- FooTable.actionFilter = FooTable.Filtering.extend({
- construct: function (instance) {
- this._super(instance);
- this.actions = ["reject", "add header", "greylist",
- "no action", "soft reject", "rewrite subject"];
- this.def = "Any action";
- this.$action = null;
- },
- $create: function () {
- this._super();
- const self = this;
- const $form_grp = $("<div/>", {
- class: "form-group d-inline-flex align-items-center"
- }).append($("<label/>", {
- class: "sr-only",
- text: "Action"
- })).prependTo(self.$form);
-
- $("<div/>", {
- class: "form-check form-check-inline",
- title: "Invert action match."
- }).append(
- self.$not = $("<input/>", {
- type: "checkbox",
- class: "form-check-input",
- id: "not_" + table
- }).on("change", {self: self}, self._onStatusDropdownChanged),
- $("<label/>", {
- class: "form-check-label",
- for: "not_" + table,
- text: "not"
- })
- ).appendTo($form_grp);
-
- self.$action = $("<select/>", {
- class: "form-select"
- }).on("change", {
- self: self
- }, self._onStatusDropdownChanged).append(
- $("<option/>", {
- text: self.def
- })).appendTo($form_grp);
-
- $.each(self.actions, (i, action) => {
- self.$action.append($("<option/>").text(action));
- });
- },
- _onStatusDropdownChanged: function (e) {
- const {self} = e.data;
- const selected = self.$action.val();
- if (selected !== self.def) {
- const not = self.$not.is(":checked");
- let query = null;
-
- if (selected === "reject") {
- query = not ? "-reject OR soft" : "reject -soft";
- } else {
- query = not ? selected.replace(/(\b\w+\b)/g, "-$1") : selected;
- }
-
- self.addFilter("action", query, ["action"]);
- } else {
- self.removeFilter("action");
- }
- self.filter();
- }
- });
- /* eslint-enable consistent-this, no-underscore-dangle, one-var-declaration-per-line */
-
- tables[table] = FooTable.init("#historyTable_" + table, {
- columns: columns,
- rows: items,
- expandFirst: expandFirst,
- paging: {
- enabled: true,
- limit: 5,
- size: ui.page_size[table]
- },
- filtering: {
- enabled: true,
- position: "left",
- connectors: false
- },
- sorting: {
- enabled: true
- },
- components: {
- filtering: FooTable.actionFilter
- },
- on: {
- "expand.ft.row": function (e, ft, row) {
- setTimeout(() => {
- const detail_row = row.$el.next();
- const order = getSelector("selSymOrder_" + table);
- detail_row.find(".btn-sym-" + table + "-" + order)
- .addClass("active").siblings().removeClass("active");
- }, 5);
- }
- }
- });
- };
-
- ui.escapeHTML = function (string) {
- const htmlEscaper = /[&<>"'/`=]/g;
- const htmlEscapes = {
- "&": "&",
- "<": "<",
- ">": ">",
- "\"": """,
- "'": "'",
- "/": "/",
- "`": "`",
- "=": "="
- };
- return String(string).replace(htmlEscaper, (match) => htmlEscapes[match]);
- };
-
- ui.preprocess_item = function (item) {
- function escape_HTML_array(arr) {
- arr.forEach((d, i) => { arr[i] = ui.escapeHTML(d); });
- }
-
- for (const prop in item) {
- if (!{}.hasOwnProperty.call(item, prop)) continue;
- switch (prop) {
- case "rcpt_mime":
- case "rcpt_smtp":
- escape_HTML_array(item[prop]);
- break;
- case "symbols":
- Object.keys(item.symbols).forEach((key) => {
- const sym = item.symbols[key];
- if (!sym.name) {
- sym.name = key;
- }
- sym.name = ui.escapeHTML(sym.name);
- if (sym.description) {
- sym.description = ui.escapeHTML(sym.description);
- }
-
- if (sym.options) {
- escape_HTML_array(sym.options);
- }
- });
- break;
- default:
- if (typeof item[prop] === "string") {
- item[prop] = ui.escapeHTML(item[prop]);
- }
- }
- }
-
- if (item.action === "clean" || item.action === "no action") {
- item.action = "<div style='font-size:11px' class='badge text-bg-success'>" + item.action + "</div>";
- } else if (item.action === "rewrite subject" || item.action === "add header" || item.action === "probable spam") {
- item.action = "<div style='font-size:11px' class='badge text-bg-warning'>" + item.action + "</div>";
- } else if (item.action === "spam" || item.action === "reject") {
- item.action = "<div style='font-size:11px' class='badge text-bg-danger'>" + item.action + "</div>";
- } else {
- item.action = "<div style='font-size:11px' class='badge text-bg-info'>" + item.action + "</div>";
- }
-
- const score_content = (item.score < item.required_score)
- ? "<span class='text-success'>" + item.score.toFixed(2) + " / " + item.required_score + "</span>"
- : "<span class='text-danger'>" + item.score.toFixed(2) + " / " + item.required_score + "</span>";
-
- item.score = {
- options: {
- sortValue: item.score
- },
- value: score_content
- };
- };
-
- ui.process_history_v2 = function (data, table) {
- // Display no more than rcpt_lim recipients
- const rcpt_lim = 3;
- const items = [];
- const unsorted_symbols = [];
- const compare_function = get_compare_function(table);
-
- $("#selSymOrder_" + table + ", label[for='selSymOrder_" + table + "']").show();
-
- $.each(data.rows,
- (i, item) => {
- function more(p) {
- const l = item[p].length;
- return (l > rcpt_lim) ? " … (" + l + ")" : "";
- }
- function format_rcpt(smtp, mime) {
- let full = "";
- let shrt = "";
- if (smtp) {
- full = "[" + item.rcpt_smtp.join(", ") + "] ";
- shrt = "[" + item.rcpt_smtp.slice(0, rcpt_lim).join(",​") + more("rcpt_smtp") + "]";
- if (mime) {
- full += " ";
- shrt += " ";
- }
- }
- if (mime) {
- full += item.rcpt_mime.join(", ");
- shrt += item.rcpt_mime.slice(0, rcpt_lim).join(",​") + more("rcpt_mime");
- }
- return {full: full, shrt: shrt};
- }
-
- function get_symbol_class(name, score) {
- if (name.match(/^GREYLIST$/)) {
- return "symbol-special";
- }
-
- if (score < 0) {
- return "symbol-negative";
- } else if (score > 0) {
- return "symbol-positive";
- }
- return null;
- }
-
- ui.preprocess_item(item);
- Object.values(item.symbols).forEach((sym) => {
- sym.str = '<span class="symbol-default ' + get_symbol_class(sym.name, sym.score) + '"><strong>';
-
- if (sym.description) {
- sym.str += '<abbr title="' + sym.description + '">' + sym.name + "</abbr>";
- } else {
- sym.str += sym.name;
- }
- sym.str += "</strong> (" + sym.score + ")</span>";
-
- if (sym.options) {
- sym.str += " [" + sym.options.join(",") + "]";
- }
- });
- unsorted_symbols.push(item.symbols);
- item.symbols = sort_symbols(item.symbols, compare_function);
- if (table === "scan") {
- item.unix_time = (new Date()).getTime() / 1000;
- }
- item.time = {
- value: unix_time_format(item.unix_time),
- options: {
- sortValue: item.unix_time
- }
- };
- item.time_real = item.time_real.toFixed(3);
- item.id = item["message-id"];
-
- if (table === "history") {
- let rcpt = {};
- if (!item.rcpt_mime.length) {
- rcpt = format_rcpt(true, false);
- } else if (
- $(item.rcpt_mime).not(item.rcpt_smtp).length !== 0 ||
- $(item.rcpt_smtp).not(item.rcpt_mime).length !== 0
- ) {
- rcpt = format_rcpt(true, true);
- } else {
- rcpt = format_rcpt(false, true);
- }
- item.rcpt_mime_short = rcpt.shrt;
- item.rcpt_mime = rcpt.full;
-
- if (item.sender_mime !== item.sender_smtp) {
- item.sender_mime = "[" + item.sender_smtp + "] " + item.sender_mime;
- }
- }
- items.push(item);
- });
-
- return {items: items, symbols: unsorted_symbols};
- };
-
- ui.waitForRowsDisplayed = function (table, rows_total, callback, iteration) {
- let i = (typeof iteration === "undefined") ? 10 : iteration;
- const num_rows = $("#historyTable_" + table + " > tbody > tr:not(.footable-detail-row)").length;
- if (num_rows === ui.page_size[table] ||
- num_rows === rows_total) {
- return callback();
- } else if (--i) {
- setTimeout(() => {
- ui.waitForRowsDisplayed(table, rows_total, callback, i);
- }, 500);
- }
- return null;
- };
-
(function initSettings() {
let selected_locale = null;
now.toLocaleString(custom_locale);
if (saveToLocalStorage) localStorage.setItem("custom_locale", custom_locale);
- locale = (selected_locale === "custom") ? custom_locale : null;
+ common.locale = (selected_locale === "custom") ? custom_locale : null;
toggle_form_group_class("invalid", "valid");
} catch (err) {
- locale = null;
+ common.locale = null;
toggle_form_group_class("valid", "invalid");
}
} else {
if (saveToLocalStorage) localStorage.setItem("custom_locale", null);
- locale = null;
+ common.locale = null;
$(localeTextbox).removeClass("is-valid is-invalid");
}
// Display date example
$(".popover #settings-popover #date-example").text(
- (locale)
- ? now.toLocaleString(locale)
+ (common.locale)
+ ? now.toLocaleString(common.locale)
: now.toLocaleString()
);
}
-define(["jquery", "app/rspamd"],
- ($, rspamd) => {
+define(["jquery", "app/common"],
+ ($, common) => {
"use strict";
const ui = {};
}
function get_server() {
- const checked_server = rspamd.getSelector("selSrv");
+ const checked_server = common.getSelector("selSrv");
return (checked_server === "All SERVERS") ? "local" : checked_server;
}
function checkMsg(data) {
const selector = $("#selectorsSelArea").val();
- rspamd.query("plugins/selectors/check_message?selector=" + encodeURIComponent(selector), {
+ common.query("plugins/selectors/check_message?selector=" + encodeURIComponent(selector), {
data: data,
method: "POST",
success: function (neighbours_status) {
const json = neighbours_status[0].data;
if (json.success) {
- rspamd.alertMessage("alert-success", "Message successfully processed");
+ common.alertMessage("alert-success", "Message successfully processed");
$("#selectorsResArea")
.val(Object.prototype.hasOwnProperty.call(json, "data") ? json.data.toString() : "");
} else {
- rspamd.alertMessage("alert-error", "Unexpected error processing message");
+ common.alertMessage("alert-error", "Unexpected error processing message");
}
},
server: get_server()
enable_disable_check_btn();
}
const selector = $("#selectorsSelArea").val();
- if (selector.length && !rspamd.read_only) {
- rspamd.query("plugins/selectors/check_selector?selector=" + encodeURIComponent(selector), {
+ if (selector.length && !common.read_only) {
+ common.query("plugins/selectors/check_selector?selector=" + encodeURIComponent(selector), {
method: "GET",
success: function (json) {
if (json[0].data.success) {
}
function getList(list) {
- rspamd.query("plugins/selectors/list_" + list, {
+ common.query("plugins/selectors/list_" + list, {
method: "GET",
success: function (neighbours_status) {
const json = neighbours_status[0].data;
}
ui.displayUI = function () {
- if (!rspamd.read_only &&
+ if (!common.read_only &&
!$("#selectorsTable-extractors>tbody>tr").length &&
!$("#selectorsTable-transforms>tbody>tr").length) buildLists();
if (!$("#selectorsSelArea").is(".is-valid, .is-invalid")) checkSelectors();
THE SOFTWARE.
*/
-define(["jquery", "app/rspamd", "d3pie", "d3"],
- ($, rspamd, D3Pie, d3) => {
+define(["jquery", "app/common", "d3pie", "d3"],
+ ($, common, D3Pie, d3) => {
"use strict";
// @ ms to date
function msToTime(seconds) {
["no action", "soft reject", "add header", "rewrite subject", "greylist", "reject"]
.forEach((action) => {
data.push({
- color: rspamd.chartLegend.find((item) => item.label === action).color,
+ color: common.chartLegend.find((item) => item.label === action).color,
label: action,
value: actions[action]
});
// Public API
const ui = {
statWidgets: function (graphs, checked_server) {
- rspamd.query("stat", {
+ common.query("stat", {
success: function (neighbours_status) {
const neighbours_sum = {
version: neighbours_status[0].data.version,
const alerted = "alerted_stats_legacy_" + neighbours_status[e].name;
promises.push($.ajax({
url: neighbours_status[e].url + "auth",
- headers: {Password: rspamd.getPassword()},
+ headers: {Password: common.getPassword()},
success: function (data) {
sessionStorage.removeItem(alerted);
["config_id", "version", "uptime"].forEach((p) => {
error: function (jqXHR, textStatus, errorThrown) {
if (!(alerted in sessionStorage)) {
sessionStorage.setItem(alerted, true);
- rspamd.alertMessage("alert-error", neighbours_status[e].name + " > " +
+ common.alertMessage("alert-error", neighbours_status[e].name + " > " +
"Cannot receive legacy stats data" + (errorThrown ? ": " + errorThrown : ""));
}
process_node_stat(e);
/* global FooTable */
-define(["jquery", "app/rspamd", "footable"],
- ($, rspamd) => {
+define(["jquery", "app/common", "footable"],
+ ($, common) => {
"use strict";
const ui = {};
let altered = {};
const values = [];
Object.entries(altered).forEach(([key, value]) => values.push({name: key, value: value}));
- rspamd.query("./savesymbols", {
+ common.query("./savesymbols", {
success: function () {
clear_altered();
- rspamd.alertMessage("alert-modal alert-success", "Symbols successfully saved");
+ common.alertMessage("alert-modal alert-success", "Symbols successfully saved");
},
complete: () => $("#save-alert button").removeAttr("disabled", true),
errorMessage: "Save symbols error",
// @get symbols into modal form
ui.getSymbols = function (checked_server) {
clear_altered();
- rspamd.query("symbols", {
+ common.query("symbols", {
success: function (json) {
const [{data}] = json;
const items = process_symbols_data(data);
});
/* eslint-enable consistent-this, no-underscore-dangle, one-var-declaration-per-line */
- rspamd.tables.symbols = FooTable.init("#symbolsTable", {
+ common.tables.symbols = FooTable.init("#symbolsTable", {
columns: [
{sorted: true, direction: "ASC", name: "group", title: "Group"},
{name: "symbol", title: "Symbol"},
},
on: {
"ready.ft.table": function () {
- if (rspamd.read_only) {
+ if (common.read_only) {
$(".mb-disabled").attr("disabled", true);
}
}
$("#updateSymbols").on("click", (e) => {
e.preventDefault();
clear_altered();
- const checked_server = rspamd.getSelector("selSrv");
- rspamd.query("symbols", {
+ const checked_server = common.getSelector("selSrv");
+ common.query("symbols", {
success: function (data) {
const [items] = process_symbols_data(data[0].data);
- rspamd.tables.symbols.rows.load(items);
+ common.tables.symbols.rows.load(items);
},
server: (checked_server === "All SERVERS") ? "local" : checked_server
});
/* global require */
-define(["jquery", "app/rspamd"],
- ($, rspamd) => {
+define(["jquery", "app/common", "app/libft"],
+ ($, common, libft) => {
"use strict";
const ui = {};
}
function server() {
- if (rspamd.getSelector("selSrv") === "All SERVERS" &&
- rspamd.getSelector("selLearnServers") === "random") {
+ if (common.getSelector("selSrv") === "All SERVERS" &&
+ common.getSelector("selLearnServers") === "random") {
const servers = $("#selSrv option").slice(1).map((_, o) => o.value);
return servers[Math.floor(Math.random() * servers.length)];
}
return null;
}
- rspamd.query(url, {
+ common.query(url, {
data: data,
params: {
processData: false,
headers: headers,
success: function (json, jqXHR) {
cleanTextUpload(source);
- rspamd.alertMessage("alert-success", "Data successfully uploaded");
+ common.alertMessage("alert-success", "Data successfully uploaded");
if (jqXHR.status !== 200) {
- rspamd.alertMessage("alert-info", jqXHR.statusText);
+ common.alertMessage("alert-info", jqXHR.statusText);
}
},
server: server()
}
function get_server() {
- const checked_server = rspamd.getSelector("selSrv");
+ const checked_server = common.getSelector("selSrv");
return (checked_server === "All SERVERS") ? "local" : checked_server;
}
// @upload text
function scanText(data, headers) {
- rspamd.query("checkv2", {
+ common.query("checkv2", {
data: data,
params: {
processData: false,
success: function (neighbours_status) {
function scrollTop(rows_total) {
// Is there a way to get an event when all rows are loaded?
- rspamd.waitForRowsDisplayed("scan", rows_total, () => {
+ libft.waitForRowsDisplayed("scan", rows_total, () => {
$("#cleanScanHistory").removeAttr("disabled", true);
$("html, body").animate({
scrollTop: $("#scanResult").offset().top
const json = neighbours_status[0].data;
if (json.action) {
- rspamd.alertMessage("alert-success", "Data successfully scanned");
+ common.alertMessage("alert-success", "Data successfully scanned");
const rows_total = $("#historyTable_scan > tbody > tr:not(.footable-detail-row)").length + 1;
- const o = rspamd.process_history_v2({rows: [json]}, "scan");
+ const o = libft.process_history_v2({rows: [json]}, "scan");
const {items} = o;
- rspamd.symbols.scan.push(o.symbols[0]);
+ common.symbols.scan.push(o.symbols[0]);
- if (Object.prototype.hasOwnProperty.call(rspamd.tables, "scan")) {
- rspamd.tables.scan.rows.load(items, true);
+ if (Object.prototype.hasOwnProperty.call(common.tables, "scan")) {
+ common.tables.scan.rows.load(items, true);
scrollTop(rows_total);
} else {
- rspamd.destroyTable("scan");
+ libft.destroyTable("scan");
require(["footable"], () => {
// Is there a way to get an event when the table is destroyed?
setTimeout(() => {
- rspamd.initHistoryTable(data, items, "scan", columns_v2(), true);
+ libft.initHistoryTable(data, items, "scan", columns_v2(), true);
scrollTop(rows_total);
}, 200);
});
}
} else {
- rspamd.alertMessage("alert-error", "Cannot scan data");
+ common.alertMessage("alert-error", "Cannot scan data");
}
},
errorMessage: "Cannot upload data",
statusCode: {
404: function () {
- rspamd.alertMessage("alert-error", "Cannot upload data, no server found");
+ common.alertMessage("alert-error", "Cannot upload data, no server found");
},
500: function () {
- rspamd.alertMessage("alert-error", "Cannot tokenize message: no text data");
+ common.alertMessage("alert-error", "Cannot tokenize message: no text data");
},
503: function () {
- rspamd.alertMessage("alert-error", "Cannot tokenize message: no text data");
+ common.alertMessage("alert-error", "Cannot tokenize message: no text data");
}
},
server: get_server()
$("#hash-card").slideDown();
}
- rspamd.query("plugins/fuzzy/hashes?flag=" + $("#fuzzy-flag").val(), {
+ common.query("plugins/fuzzy/hashes?flag=" + $("#fuzzy-flag").val(), {
data: data,
params: {
processData: false,
success: function (neighbours_status) {
const json = neighbours_status[0].data;
if (json.success) {
- rspamd.alertMessage("alert-success", "Message successfully processed");
+ common.alertMessage("alert-success", "Message successfully processed");
fillHashTable(json.hashes);
} else {
- rspamd.alertMessage("alert-error", "Unexpected error processing message");
+ common.alertMessage("alert-error", "Unexpected error processing message");
}
},
server: get_server()
}
- rspamd.set_page_size("scan", $("#scan_page_size").val());
- rspamd.bindHistoryTableEventHandlers("scan", 3);
+ libft.set_page_size("scan", $("#scan_page_size").val());
+ libft.bindHistoryTableEventHandlers("scan", 3);
$("#cleanScanHistory").off("click");
$("#cleanScanHistory").on("click", (e) => {
if (!confirm("Are you sure you want to clean scan history?")) { // eslint-disable-line no-alert
return;
}
- rspamd.destroyTable("scan");
- rspamd.symbols.scan.length = 0;
+ libft.destroyTable("scan");
+ common.symbols.scan.length = 0;
$("#cleanScanHistory").attr("disabled", true);
});
uploadText(data, source, headers);
}
} else {
- rspamd.alertMessage("alert-error", "Message source field cannot be blank");
+ common.alertMessage("alert-error", "Message source field cannot be blank");
}
return false;
});