Bladeren bron

[Minor] Move common stuff to separate files

tags/3.8.0
moisseev 4 maanden geleden
bovenliggende
commit
01a0df9f25

+ 233
- 0
interface/js/app/common.js Bestand weergeven

@@ -0,0 +1,233 @@
/* 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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
"\"": "&quot;",
"'": "&#39;",
"/": "&#x2F;",
"`": "&#x60;",
"=": "&#x3D;"
};
return String(string).replace(htmlEscaper, (match) => htmlEscapes[match]);
};

return ui;
});

+ 17
- 17
interface/js/app/config.js Bestand weergeven

@@ -24,13 +24,13 @@

/* 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 = [];
@@ -88,15 +88,15 @@ define(["jquery", "app/rspamd"],
// 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,
@@ -105,14 +105,14 @@ define(["jquery", "app/rspamd"],
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();
@@ -121,7 +121,7 @@ define(["jquery", "app/rspamd"],

$.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('&nbsp;<span class="badge text-bg-success">Write</span>');
}
const $tr = $("<tr>").append($td);
@@ -158,9 +158,9 @@ define(["jquery", "app/rspamd"],

// 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
},
@@ -180,11 +180,11 @@ define(["jquery", "app/rspamd"],
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();
@@ -218,9 +218,9 @@ define(["jquery", "app/rspamd"],
});

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",

+ 12
- 12
interface/js/app/graph.js Bestand weergeven

@@ -25,8 +25,8 @@

/* 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 = {
@@ -68,16 +68,16 @@ define(["jquery", "app/rspamd", "d3evolution", "d3pie", "d3", "footable"],

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);
@@ -127,7 +127,7 @@ define(["jquery", "app/rspamd", "d3evolution", "d3pie", "d3", "footable"],
}

function initSummaryTable(rows, unit) {
rspamd.tables.rrd_summary = FooTable.init("#rrd-table", {
common.tables.rrd_summary = FooTable.init("#rrd-table", {
sorting: {
enabled: true
},
@@ -151,8 +151,8 @@ define(["jquery", "app/rspamd", "d3evolution", "d3pie", "d3", "footable"],
}

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 {
@@ -199,7 +199,7 @@ define(["jquery", "app/rspamd", "d3evolution", "d3pie", "d3", "footable"],
}


rspamd.query("graph", {
common.query("graph", {
success: function (req_data) {
let data = null;
const neighbours_data = req_data
@@ -214,7 +214,7 @@ define(["jquery", "app/rspamd", "d3evolution", "d3pie", "d3", "footable"],
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
}

+ 26
- 26
interface/js/app/history.js Bestand weergeven

@@ -24,8 +24,8 @@

/* 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;
@@ -38,15 +38,15 @@ define(["jquery", "app/rspamd", "d3", "footable"],
$("#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
}
@@ -234,7 +234,7 @@ define(["jquery", "app/rspamd", "d3", "footable"],

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;
@@ -263,12 +263,12 @@ define(["jquery", "app/rspamd", "d3", "footable"],
}

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;
}
@@ -293,21 +293,21 @@ define(["jquery", "app/rspamd", "d3", "footable"],
}
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"); },
@@ -316,7 +316,7 @@ define(["jquery", "app/rspamd", "d3", "footable"],
};

function initErrorsTable(rows) {
rspamd.tables.errors = FooTable.init("#errorsLog", {
common.tables.errors = FooTable.init("#errorsLog", {
columns: [
{sorted: true,
direction: "DESC",
@@ -340,7 +340,7 @@ define(["jquery", "app/rspamd", "d3", "footable"],
paging: {
enabled: true,
limit: 5,
size: rspamd.page_size.errors
size: common.page_size.errors
},
filtering: {
enabled: true,
@@ -354,9 +354,9 @@ define(["jquery", "app/rspamd", "d3", "footable"],
}

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
@@ -364,14 +364,14 @@ define(["jquery", "app/rspamd", "d3", "footable"],
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);
}
@@ -386,8 +386,8 @@ define(["jquery", "app/rspamd", "d3", "footable"],
};


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) => {
@@ -402,10 +402,10 @@ define(["jquery", "app/rspamd", "d3", "footable"],
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();

+ 380
- 0
interface/js/app/libft.js Bestand weergeven

@@ -0,0 +1,380 @@
/* 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(",&#8203;") + more("rcpt_smtp") + "]";
if (mime) {
full += " ";
shrt += " ";
}
}
if (mime) {
full += item.rcpt_mime.join(", ");
shrt += item.rcpt_mime.slice(0, rcpt_lim).join(",&#8203;") + 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;
});

+ 18
- 612
interface/js/app/rspamd.js Bestand weergeven

@@ -23,48 +23,20 @@
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;
@@ -92,7 +64,7 @@ define(["jquery", "nprogress", "stickytabs", "visibility",
}

function disconnect() {
[graphs, tables].forEach((o) => {
[graphs, common.tables].forEach((o) => {
Object.keys(o).forEach((key) => {
o[key].destroy();
delete o[key];
@@ -108,12 +80,6 @@ define(["jquery", "nprogress", "stickytabs", "visibility",
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;
@@ -180,7 +146,7 @@ define(["jquery", "nprogress", "stickytabs", "visibility",
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
@@ -192,8 +158,8 @@ define(["jquery", "nprogress", "stickytabs", "visibility",
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();
@@ -245,72 +211,16 @@ define(["jquery", "nprogress", "stickytabs", "visibility",
}, (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>'));
@@ -326,7 +236,7 @@ define(["jquery", "nprogress", "stickytabs", "visibility",
complete: function () {
ajaxSetup(localStorage.getItem("ajax_timeout"));

if (ui.read_only) {
if (common.read_only) {
$(".ro-disable").attr("disabled", true);
$(".ro-hide").hide();
} else {
@@ -343,98 +253,8 @@ define(["jquery", "nprogress", "stickytabs", "visibility",
});
}

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.
@@ -484,7 +304,7 @@ define(["jquery", "nprogress", "stickytabs", "visibility",
return;
}

ui.query("auth", {
common.query("auth", {
headers: {
Password: password
},
@@ -503,7 +323,7 @@ define(["jquery", "nprogress", "stickytabs", "visibility",
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();
@@ -518,420 +338,6 @@ define(["jquery", "nprogress", "stickytabs", "visibility",
});
};

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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
"\"": "&quot;",
"'": "&#39;",
"/": "&#x2F;",
"`": "&#x60;",
"=": "&#x3D;"
};
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(",&#8203;") + more("rcpt_smtp") + "]";
if (mime) {
full += " ";
shrt += " ";
}
}
if (mime) {
full += item.rcpt_mime.join(", ");
shrt += item.rcpt_mime.slice(0, rcpt_lim).join(",&#8203;") + 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;
@@ -950,22 +356,22 @@ define(["jquery", "nprogress", "stickytabs", "visibility",
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()
);
}

+ 10
- 10
interface/js/app/selectors.js Bestand weergeven

@@ -1,5 +1,5 @@
define(["jquery", "app/rspamd"],
($, rspamd) => {
define(["jquery", "app/common"],
($, common) => {
"use strict";
const ui = {};

@@ -11,23 +11,23 @@ define(["jquery", "app/rspamd"],
}

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()
@@ -40,8 +40,8 @@ define(["jquery", "app/rspamd"],
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) {
@@ -70,7 +70,7 @@ define(["jquery", "app/rspamd"],
}

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;
@@ -85,7 +85,7 @@ define(["jquery", "app/rspamd"],
}

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();

+ 6
- 6
interface/js/app/stats.js Bestand weergeven

@@ -22,8 +22,8 @@
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) {
@@ -254,7 +254,7 @@ define(["jquery", "app/rspamd", "d3pie", "d3"],
["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]
});
@@ -266,7 +266,7 @@ define(["jquery", "app/rspamd", "d3pie", "d3"],
// 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,
@@ -315,7 +315,7 @@ define(["jquery", "app/rspamd", "d3pie", "d3"],
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) => {
@@ -326,7 +326,7 @@ define(["jquery", "app/rspamd", "d3pie", "d3"],
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);

+ 10
- 10
interface/js/app/symbols.js Bestand weergeven

@@ -24,8 +24,8 @@

/* global FooTable */

define(["jquery", "app/rspamd", "footable"],
($, rspamd) => {
define(["jquery", "app/common", "footable"],
($, common) => {
"use strict";
const ui = {};
let altered = {};
@@ -41,10 +41,10 @@ define(["jquery", "app/rspamd", "footable"],
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",
@@ -124,7 +124,7 @@ define(["jquery", "app/rspamd", "footable"],
// @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);
@@ -182,7 +182,7 @@ define(["jquery", "app/rspamd", "footable"],
});
/* 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"},
@@ -213,7 +213,7 @@ define(["jquery", "app/rspamd", "footable"],
},
on: {
"ready.ft.table": function () {
if (rspamd.read_only) {
if (common.read_only) {
$(".mb-disabled").attr("disabled", true);
}
}
@@ -228,11 +228,11 @@ define(["jquery", "app/rspamd", "footable"],
$("#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
});

+ 29
- 29
interface/js/app/upload.js Bestand weergeven

@@ -24,8 +24,8 @@

/* global require */

define(["jquery", "app/rspamd"],
($, rspamd) => {
define(["jquery", "app/common", "app/libft"],
($, common, libft) => {
"use strict";
const ui = {};

@@ -47,15 +47,15 @@ define(["jquery", "app/rspamd"],
}

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,
@@ -64,9 +64,9 @@ define(["jquery", "app/rspamd"],
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()
@@ -128,13 +128,13 @@ define(["jquery", "app/rspamd"],
}

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,
@@ -144,7 +144,7 @@ define(["jquery", "app/rspamd"],
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
@@ -154,40 +154,40 @@ define(["jquery", "app/rspamd"],

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()
@@ -207,7 +207,7 @@ define(["jquery", "app/rspamd"],
$("#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,
@@ -216,10 +216,10 @@ define(["jquery", "app/rspamd"],
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()
@@ -227,8 +227,8 @@ define(["jquery", "app/rspamd"],
}


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) => {
@@ -236,8 +236,8 @@ define(["jquery", "app/rspamd"],
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);
});

@@ -288,7 +288,7 @@ define(["jquery", "app/rspamd"],
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;
});

Laden…
Annuleren
Opslaan