"singleLine": { "afterColon": false }
}],
"max-params": ["warn", 6],
- "max-statements": ["warn", 33],
+ "max-statements": ["warn", 44],
"max-statements-per-line": ["error", { "max": 2 }],
"multiline-comment-style": "off",
"multiline-ternary": ["error", "always-multiline"],
}
/* history table */
-#historyTable > tbody > tr > td,
-#historyTable > thead > tr > th {
+#historyTable_scan > tbody > tr > td,
+#historyTable_scan > thead > tr > th,
+#historyTable_history > tbody > tr > td,
+#historyTable_history > thead > tr > th {
padding: 4px;
}
-#historyTable > thead > tr > th {
+#historyTable_scan > thead > tr > th,
+#historyTable_history > thead > tr > th {
padding-right: 20px;
}
-#selSymOrder {
- height: auto;
-}
.widget-title-form label {
font-size: 12px;
font-weight: normal;
</div>
</form>
</div>
- <div id="scanResult" style="display: none;">
- <h4>Scan results:</h4>
- <div class="well nomargin nopadding">
- <table class="table table-log table-hover" id="scanOutput">
- <thead>
- <tr>
- <th class="col4" title="Action">Action</th>
- <th class="col5" title="Score / Req. score">Score / Req. score</th>
- <th class="col6" title="Symbols">Symbols</th>
- </tr>
- </thead>
- </table>
- </div>
- </div>
</div>
</div>
<div class="widget-box learn" style="display: none;">
</div>
</div>
</div>
+
+ <div class="widget-box">
+ <div class="widget-title">
+ <div id="scanResult" class="form-inline widget-title-form input-group-sm pull-right buttons">
+ <label for="selSymOrder_scan">Symbols order:</label>
+ <select id="selSymOrder_scan" class="form-control">
+ <option value="magnitude" selected>Score magnitude</option>
+ <option value="score">Score value</option>
+ <option value="name">Name</option>
+ </select>
+ <label for="scan_page_size">Rows per page:</label>
+ <input id="scan_page_size" class="form-control" value="25" min="1" type="number">
+ <button class="btn btn-default btn-sm" id="cleanScanHistory">
+ <i class="glyphicon glyphicon-trash"></i> Clean history
+ </button>
+ </div>
+ <span class="icon"><i class="glyphicon glyphicon-eye-open"></i></span>
+ <h5>Scan results history</h5>
+ </div>
+ <div class="widget-content nopadding">
+ <div id="scanLog">
+ <table class="table" id="historyTable_scan"></table>
+ </div>
+ </div>
+ </div>
</div>
<div class="tab-pane" id="history">
<div class="widget-box">
<div class="widget-title">
<div class="form-inline widget-title-form input-group-sm pull-right buttons">
- <label for="selSymOrder">Symbols order:</label>
- <select id="selSymOrder" class="form-control">
+ <label for="selSymOrder_history">Symbols order:</label>
+ <select id="selSymOrder_history" class="form-control">
<option value="magnitude" selected>Score magnitude</option>
<option value="score">Score value</option>
<option value="name">Name</option>
</div>
<div class="widget-content nopadding">
<div id="historyLog">
- <table class="table" id="historyTable"></table>
+ <table class="table" id="historyTable_history"></table>
</div>
</div>
</div>
define(["jquery", "footable", "humanize"],
function ($, _, Humanize) {
"use strict";
- var page_size = {
- errors: 25,
- history: 25
- };
-
- function set_page_size(n, callback) {
- if (n !== page_size.history && n > 0) {
- page_size.history = n;
- if (callback) {
- return callback(n);
- }
- }
- return null;
- }
-
- set_page_size($("#history_page_size").val());
-
var ui = {};
var prevVersion = null;
- var htmlEscapes = {
- "&": "&",
- "<": "<",
- ">": ">",
- "\"": """,
- "'": "'",
- "/": "/",
- "`": "`",
- "=": "="
- };
- var htmlEscaper = /[&<>"'/`=]/g;
- var symbols = [];
- var symbolDescriptions = {};
-
- var escapeHTML = function (string) {
- return String(string).replace(htmlEscaper, function (match) {
- return htmlEscapes[match];
- });
- };
-
- var escape_HTML_array = function (arr) {
- arr.forEach(function (d, i) { arr[i] = escapeHTML(d); });
- };
-
- function unix_time_format(tm) {
- var date = new Date(tm ? tm * 1000 : 0);
- return date.toLocaleString();
- }
-
- function preprocess_item(item) {
- for (var 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(function (key) {
- var sym = item.symbols[key];
- if (!sym.name) {
- sym.name = key;
- }
- sym.name = escapeHTML(sym.name);
- if (sym.description) {
- sym.description = escapeHTML(sym.description);
- }
-
- if (sym.options) {
- escape_HTML_array(sym.options);
- }
- });
- break;
- default:
- if (typeof item[prop] === "string") {
- item[prop] = escapeHTML(item[prop]);
- }
- }
- }
-
- if (item.action === "clean" || item.action === "no action") {
- item.action = "<div style='font-size:11px' class='label label-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='label label-warning'>" + item.action + "</div>";
- } else if (item.action === "spam" || item.action === "reject") {
- item.action = "<div style='font-size:11px' class='label label-danger'>" + item.action + "</div>";
- } else {
- item.action = "<div style='font-size:11px' class='label label-info'>" + item.action + "</div>";
- }
-
- var 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
- };
- }
-
- function getSelector(id) {
- var e = document.getElementById(id);
- return e.options[e.selectedIndex].value;
- }
-
- function get_compare_function() {
- var 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")];
- }
-
- function sort_symbols(o, compare_function) {
- return Object.keys(o)
- .map(function (key) {
- return o[key];
- })
- .sort(compare_function)
- .map(function (e) { return e.str; })
- .join("<br>\n");
- }
-
- function process_history_v2(data) {
- // Display no more than rcpt_lim recipients
- var rcpt_lim = 3;
- var items = [];
- var unsorted_symbols = [];
- var compare_function = get_compare_function();
-
- $("#selSymOrder, label[for='selSymOrder']").show();
-
- $.each(data.rows,
- function (i, item) {
- function more(p) {
- var l = item[p].length;
- return (l > rcpt_lim) ? " … (" + l + ")" : "";
- }
- function format_rcpt(smtp, mime) {
- var full = "";
- var 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;
- }
-
- preprocess_item(item);
- Object.keys(item.symbols).forEach(function (key) {
- var sym = item.symbols[key];
- sym.str = '<span class="symbol-default ' + get_symbol_class(sym.name, sym.score) + '"><strong>';
-
- if (sym.description) {
- sym.str += '<abbr data-sym-key="' + key + '">' +
- sym.name + "</abbr></strong> (" + sym.score + ")</span>";
- // Store description for tooltip
- symbolDescriptions[key] = sym.description;
- } else {
- sym.str += sym.name + "</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);
- 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"];
-
- var 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};
- }
-
- function process_history_legacy(data) {
+ function process_history_legacy(rspamd, data) {
var items = [];
var compare = function (e1, e2) {
return e1.name.localeCompare(e2.name);
};
- $("#selSymOrder, label[for='selSymOrder']").hide();
+ $("#selSymOrder_history, label[for='selSymOrder_history']").hide();
$.each(data, function (i, item) {
- item.time = unix_time_format(item.unix_time);
- preprocess_item(item);
+ item.time = rspamd.unix_time_format(item.unix_time);
+ rspamd.preprocess_item(rspamd, item);
item.symbols = Object.keys(item.symbols)
.map(function (key) {
return item.symbols[key];
.map(function (e) { return e.name; })
.join(", ");
item.time = {
- value: unix_time_format(item.unix_time),
+ value: rspamd.unix_time_format(item.unix_time),
options: {
sortValue: item.unix_time
}
name: "symbols",
title: "Symbols<br /><br />" +
'<span style="font-weight:normal;">Sort by:</span><br />' +
- '<div class="btn-group btn-group-xs btn-sym-order" data-toggle="buttons">' +
- '<button type="button" class="btn btn-default btn-sym-magnitude" value="magnitude">Magnitude</button>' +
- '<button type="button" class="btn btn-default btn-sym-score" value="score">Value</button>' +
- '<button type="button" class="btn btn-default btn-sym-name" value="name">Name</button>' +
+ '<div class="btn-group btn-group-xs btn-sym-order-history" data-toggle="buttons">' +
+ '<button type="button" class="btn btn-default btn-sym-history-magnitude" value="magnitude">Magnitude</button>' +
+ '<button type="button" class="btn btn-default btn-sym-history-score" value="score">Value</button>' +
+ '<button type="button" class="btn btn-default btn-sym-history-name" value="name">Name</button>' +
"</div>",
breakpoints: "all",
style: {
}];
}
- var process_functions = {
- 2: process_history_v2,
- legacy: process_history_legacy
- };
-
var columns = {
2: columns_v2,
legacy: columns_legacy
};
- function process_history_data(data) {
+ function process_history_data(rspamd, data) {
+ var process_functions = {
+ 2: rspamd.process_history_v2,
+ legacy: process_history_legacy
+ };
var pf = process_functions.legacy;
if (data.version) {
}
}
- return pf(data);
+ return pf(rspamd, data, "history");
}
function get_history_columns(data) {
return func();
}
- function drawTooltips() {
- // Update symbol description tooltips
- $.each(symbolDescriptions, function (key, description) {
- $("abbr[data-sym-key=" + key + "]").tooltip({
- placement: "bottom",
- html: true,
- title: description
- });
- });
- }
-
- function initHistoryTable(rspamd, tables, data, items) {
- /* 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();
- var self = this, $form_grp = $("<div/>", {
- class: "form-group"
- }).append($("<label/>", {
- class: "sr-only",
- text: "Action"
- })).prependTo(self.$form);
-
- self.$action = $("<select/>", {
- class: "form-control"
- }).on("change", {
- self: self
- }, self._onStatusDropdownChanged).append(
- $("<option/>", {
- text: self.def
- })).appendTo($form_grp);
-
- $.each(self.actions, function (i, action) {
- self.$action.append($("<option/>").text(action));
- });
- },
- _onStatusDropdownChanged: function (e) {
- var self = e.data.self, selected = $(this).val();
- if (selected !== self.def) {
- if (selected === "reject") {
- self.addFilter("action", "reject -soft", ["action"]);
- } else {
- self.addFilter("action", selected, ["action"]);
- }
- } else {
- self.removeFilter("action");
- }
- self.filter();
- },
- draw: function () {
- this._super();
- var action = this.find("action");
- if (action instanceof FooTable.Filter) {
- if (action.query.val() === "reject -soft") {
- this.$action.val("reject");
- } else {
- this.$action.val(action.query.val());
- }
- } else {
- this.$action.val(this.def);
- }
- }
- });
- /* eslint-enable consistent-this, no-underscore-dangle, one-var-declaration-per-line */
-
- tables.history = FooTable.init("#historyTable", {
- columns: get_history_columns(data),
- rows: items,
- paging: {
- enabled: true,
- limit: 5,
- size: page_size.history
- },
- filtering: {
- enabled: true,
- position: "left",
- connectors: false
- },
- sorting: {
- enabled: true
- },
- components: {
- filtering: FooTable.actionFilter
- },
- on: {
- "ready.ft.table": drawTooltips,
- "after.ft.sorting": drawTooltips,
- "after.ft.paging": drawTooltips,
- "after.ft.filtering": drawTooltips,
- "expand.ft.row": function (e, ft, row) {
- setTimeout(function () {
- var detail_row = row.$el.next();
- var order = getSelector("selSymOrder");
- detail_row.find(".btn-sym-" + order)
- .addClass("active").siblings().removeClass("active");
- }, 5);
- }
- }
- });
- }
-
- function destroyTable(tables, table) {
- if (tables[table]) {
- tables[table].destroy();
- delete tables[table];
- }
- }
-
ui.getHistory = function (rspamd, tables) {
- function waitForRowsDisplayed(rows_total, callback, iteration) {
- var i = (typeof iteration === "undefined") ? 10 : iteration;
- var num_rows = $("#historyTable > tbody > tr").length;
- if (num_rows === page_size.history ||
- num_rows === rows_total) {
- return callback();
- } else if (--i) {
- setTimeout(function () {
- waitForRowsDisplayed(rows_total, callback, i);
- }, 500);
- }
- return null;
- }
-
rspamd.query("history", {
success: function (req_data) {
function differentVersions(neighbours_data) {
// Legacy version
data = [].concat.apply([], neighbours_data);
}
- var o = process_history_data(data);
+ var o = process_history_data(rspamd, data);
var items = o.items;
- symbols = o.symbols;
+ rspamd.symbols.history = o.symbols;
if (Object.prototype.hasOwnProperty.call(tables, "history") &&
version === prevVersion) {
tables.history.rows.load(items);
if (version) { // Non-legacy
// Is there a way to get an event when all rows are loaded?
- waitForRowsDisplayed(items.length, function () {
- drawTooltips();
+ rspamd.waitForRowsDisplayed("history", items.length, function () {
+ rspamd.drawTooltips();
});
}
} else {
- destroyTable(tables, "history");
+ rspamd.destroyTable("history");
// Is there a way to get an event when the table is destroyed?
setTimeout(function () {
- initHistoryTable(rspamd, tables, data, items);
+ rspamd.initHistoryTable(rspamd, data, items, "history", get_history_columns(data), false);
}, 200);
}
prevVersion = version;
} else {
- destroyTable(tables, "history");
+ rspamd.destroyTable("history");
}
},
errorMessage: "Cannot receive history",
};
ui.setup = function (rspamd, tables) {
- function change_symbols_order(order) {
- $(".btn-sym-" + order).addClass("active").siblings().removeClass("active");
- var compare_function = get_compare_function();
- $.each(tables.history.rows.all, function (i, row) {
- var cell_val = sort_symbols(symbols[i], compare_function);
- row.cells[8].val(cell_val, false, true);
- });
- drawTooltips();
- }
+ rspamd.set_page_size("history", $("#history_page_size").val());
+ rspamd.bindHistoryTableEventHandlers("history", 8);
$("#updateHistory").off("click");
$("#updateHistory").on("click", function (e) {
e.preventDefault();
ui.getHistory(rspamd, tables);
});
- $("#selSymOrder").unbind().change(function () {
- var order = this.value;
- change_symbols_order(order);
- });
- $("#history_page_size").change(function () {
- set_page_size(this.value, function (n) { tables.history.pageSize(n); });
- });
- $(document).on("click", ".btn-sym-order button", function () {
- var order = this.value;
- $("#selSymOrder").val(order);
- change_symbols_order(order);
- });
// @reset history log
$("#resetHistory").off("click");
if (!confirm("Are you sure you want to reset history log?")) { // eslint-disable-line no-alert
return;
}
- destroyTable(tables, "history");
- destroyTable(tables, "errors");
+ rspamd.destroyTable("history");
+ rspamd.destroyTable("errors");
rspamd.query("historyreset", {
success: function () {
});
};
- function initErrorsTable(tables, rows) {
+ function initErrorsTable(rspamd, tables, rows) {
tables.errors = FooTable.init("#errorsLog", {
columns: [
{sorted:true, direction:"DESC", name:"ts", title:"Time", style:{"font-size":"11px", "width":300, "maxWidth":300}},
paging: {
enabled: true,
limit: 5,
- size: page_size.errors
+ size: rspamd.page_size.errors
},
filtering: {
enabled: true,
var rows = [].concat.apply([], neighbours_data);
$.each(rows, function (i, item) {
item.ts = {
- value: unix_time_format(item.ts),
+ value: rspamd.unix_time_format(item.ts),
options: {
sortValue: item.ts
}
if (Object.prototype.hasOwnProperty.call(tables, "errors")) {
tables.errors.rows.load(rows);
} else {
- initErrorsTable(tables, rows);
+ initErrorsTable(rspamd, tables, rows);
}
}
});
THE SOFTWARE.
*/
-/* global jQuery:false, Visibility:false */
+/* global jQuery:false, FooTable:false, Visibility:false */
define(["jquery", "d3pie", "visibility", "nprogress", "stickytabs", "app/stats", "app/graph", "app/config",
"app/symbols", "app/history", "app/upload"],
function ($, D3pie, visibility, NProgress, stickyTabs, tab_stat, tab_graph, tab_config,
tab_symbols, tab_history, tab_upload) {
"use strict";
- // begin
+ var ui = {
+ page_size: {
+ scan: 25,
+ errors: 25,
+ history: 25
+ },
+ symbols: {
+ scan: [],
+ history: []
+ }
+ };
+
var graphs = {};
var tables = {};
var neighbours = []; // list of clusters
var checked_server = "All SERVERS";
- var ui = {};
var timer_id = [];
var selData = null; // Graph's dataset selector state
+ var symbolDescriptions = {};
NProgress.configure({
minimum: 0.01,
}, 1000);
}
- // @return password
+ function drawTooltips() {
+ // Update symbol description tooltips
+ $.each(symbolDescriptions, function (key, description) {
+ $("abbr[data-sym-key=" + key + "]").tooltip({
+ placement: "bottom",
+ html: true,
+ title: description
+ });
+ });
+ }
+
function getPassword() {
return sessionStorage.getItem("Password");
}
- // @save credentials
+ function getSelector(id) {
+ var e = document.getElementById(id);
+ return e.options[e.selectedIndex].value;
+ }
+
+ function get_compare_function(table) {
+ var 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, callback) {
+ var n = parseInt(page_size, 10); // HTML Input elements return string representing a number
+ if (n !== ui.page_size[table] && n > 0) {
+ ui.page_size[table] = n;
+ if (callback) {
+ return callback(n);
+ }
+ }
+ return null;
+ }
+
+ function sort_symbols(o, compare_function) {
+ return Object.keys(o)
+ .map(function (key) {
+ return o[key];
+ })
+ .sort(compare_function)
+ .map(function (e) { return e.str; })
+ .join("<br>\n");
+ }
+
+ function unix_time_format(tm) {
+ var date = new Date(tm ? tm * 1000 : 0);
+ return date.toLocaleString();
+ }
+
function displayUI() {
ui.query("auth", {
success: function (neighbours_status) {
tab_config.setup(ui);
tab_history.setup(ui, tables);
tab_symbols.setup(ui, tables);
- tab_upload.setup(ui);
+ tab_upload.setup(ui, tables);
selData = tab_graph.setup();
};
};
ui.getPassword = getPassword;
+ ui.getSelector = getSelector;
/**
* @param {string} url - A string containing the URL to which the request is sent
}
};
+ // Scan and History shared functions
+
+ ui.drawTooltips = drawTooltips;
+ 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");
+ var compare_function = get_compare_function(table);
+ $.each(tables[table].rows.all, function (i, row) {
+ var cell_val = sort_symbols(ui.symbols[table][i], compare_function);
+ row.cells[symbolsCol].val(cell_val, false, true);
+ });
+ drawTooltips();
+ }
+
+ $("#selSymOrder_" + table).unbind().change(function () {
+ var order = this.value;
+ change_symbols_order(order);
+ });
+ $("#" + table + "_page_size").change(function () {
+ set_page_size(table, this.value, function (n) { tables[table].pageSize(n); });
+ });
+ $(document).on("click", ".btn-sym-order-" + table + " button", function () {
+ var 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 (rspamd, data, items, table, columns, expandFirst) {
+ /* 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();
+ var self = this, $form_grp = $("<div/>", {
+ class: "form-group"
+ }).append($("<label/>", {
+ class: "sr-only",
+ text: "Action"
+ })).prependTo(self.$form);
+
+ self.$action = $("<select/>", {
+ class: "form-control"
+ }).on("change", {
+ self: self
+ }, self._onStatusDropdownChanged).append(
+ $("<option/>", {
+ text: self.def
+ })).appendTo($form_grp);
+
+ $.each(self.actions, function (i, action) {
+ self.$action.append($("<option/>").text(action));
+ });
+ },
+ _onStatusDropdownChanged: function (e) {
+ var self = e.data.self, selected = $(this).val();
+ if (selected !== self.def) {
+ if (selected === "reject") {
+ self.addFilter("action", "reject -soft", ["action"]);
+ } else {
+ self.addFilter("action", selected, ["action"]);
+ }
+ } else {
+ self.removeFilter("action");
+ }
+ self.filter();
+ },
+ draw: function () {
+ this._super();
+ var action = this.find("action");
+ if (action instanceof FooTable.Filter) {
+ if (action.query.val() === "reject -soft") {
+ this.$action.val("reject");
+ } else {
+ this.$action.val(action.query.val());
+ }
+ } else {
+ this.$action.val(this.def);
+ }
+ }
+ });
+ /* 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: {
+ "ready.ft.table": drawTooltips,
+ "after.ft.sorting": drawTooltips,
+ "after.ft.paging": drawTooltips,
+ "after.ft.filtering": drawTooltips,
+ "expand.ft.row": function (e, ft, row) {
+ setTimeout(function () {
+ var detail_row = row.$el.next();
+ var order = getSelector("selSymOrder_" + table);
+ detail_row.find(".btn-sym-" + table + "-" + order)
+ .addClass("active").siblings().removeClass("active");
+ }, 5);
+ }
+ }
+ });
+ };
+
+ ui.preprocess_item = function (rspamd, item) {
+ function escapeHTML(string) {
+ var htmlEscaper = /[&<>"'/`=]/g;
+ var htmlEscapes = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ "\"": """,
+ "'": "'",
+ "/": "/",
+ "`": "`",
+ "=": "="
+ };
+ return String(string).replace(htmlEscaper, function (match) {
+ return htmlEscapes[match];
+ });
+ }
+ function escape_HTML_array(arr) {
+ arr.forEach(function (d, i) { arr[i] = escapeHTML(d); });
+ }
+
+ for (var 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(function (key) {
+ var sym = item.symbols[key];
+ if (!sym.name) {
+ sym.name = key;
+ }
+ sym.name = escapeHTML(sym.name);
+ if (sym.description) {
+ sym.description = escapeHTML(sym.description);
+ }
+
+ if (sym.options) {
+ escape_HTML_array(sym.options);
+ }
+ });
+ break;
+ default:
+ if (typeof item[prop] === "string") {
+ item[prop] = escapeHTML(item[prop]);
+ }
+ }
+ }
+
+ if (item.action === "clean" || item.action === "no action") {
+ item.action = "<div style='font-size:11px' class='label label-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='label label-warning'>" + item.action + "</div>";
+ } else if (item.action === "spam" || item.action === "reject") {
+ item.action = "<div style='font-size:11px' class='label label-danger'>" + item.action + "</div>";
+ } else {
+ item.action = "<div style='font-size:11px' class='label label-info'>" + item.action + "</div>";
+ }
+
+ var 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 (rspamd, data, table) {
+ // Display no more than rcpt_lim recipients
+ var rcpt_lim = 3;
+ var items = [];
+ var unsorted_symbols = [];
+ var compare_function = get_compare_function(table);
+
+ $("#selSymOrder_" + table + ", label[for='selSymOrder_" + table + "']").show();
+
+ $.each(data.rows,
+ function (i, item) {
+ function more(p) {
+ var l = item[p].length;
+ return (l > rcpt_lim) ? " … (" + l + ")" : "";
+ }
+ function format_rcpt(smtp, mime) {
+ var full = "";
+ var 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;
+ }
+
+ rspamd.preprocess_item(rspamd, item);
+ Object.keys(item.symbols).forEach(function (key) {
+ var sym = item.symbols[key];
+ sym.str = '<span class="symbol-default ' + get_symbol_class(sym.name, sym.score) + '"><strong>';
+
+ if (sym.description) {
+ sym.str += '<abbr data-sym-key="' + key + '">' +
+ sym.name + "</abbr></strong> (" + sym.score + ")</span>";
+ // Store description for tooltip
+ symbolDescriptions[key] = sym.description;
+ } else {
+ sym.str += sym.name + "</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") {
+ var 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) {
+ var i = (typeof iteration === "undefined") ? 10 : iteration;
+ var 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(function () {
+ ui.waitForRowsDisplayed(table, rows_total, callback, i);
+ }, 500);
+ }
+ return null;
+ };
+
return ui;
});
}
});
}
+
+ function columns_v2() {
+ return [{
+ name: "id",
+ title: "ID",
+ style: {
+ "font-size": "11px",
+ "minWidth": 130,
+ "overflow": "hidden",
+ "textOverflow": "ellipsis",
+ "wordBreak": "break-all",
+ "whiteSpace": "normal"
+ }
+ }, {
+ name: "action",
+ title: "Action",
+ style: {
+ "font-size": "11px",
+ "minwidth": 82
+ }
+ }, {
+ name: "score",
+ title: "Score",
+ style: {
+ "font-size": "11px",
+ "maxWidth": 110
+ },
+ sortValue: function (val) { return Number(val.options.sortValue); }
+ }, {
+ name: "symbols",
+ title: "Symbols<br /><br />" +
+ '<span style="font-weight:normal;">Sort by:</span><br />' +
+ '<div class="btn-group btn-group-xs btn-sym-order-scan" data-toggle="buttons">' +
+ '<button type="button" class="btn btn-default btn-sym-scan-magnitude" value="magnitude">Magnitude</button>' +
+ '<button type="button" class="btn btn-default btn-sym-scan-score" value="score">Value</button>' +
+ '<button type="button" class="btn btn-default btn-sym-scan-name" value="name">Name</button>' +
+ "</div>",
+ breakpoints: "all",
+ style: {
+ "font-size": "11px",
+ "width": 550,
+ "maxWidth": 550
+ }
+ }, {
+ name: "time_real",
+ title: "Scan time",
+ breakpoints: "xs sm md",
+ style: {
+ "font-size": "11px",
+ "maxWidth": 72
+ },
+ sortValue: function (val) { return Number(val); }
+ }, {
+ sorted: true,
+ direction: "DESC",
+ name: "time",
+ title: "Time",
+ style: {
+ "font-size": "11px"
+ },
+ sortValue: function (val) { return Number(val.options.sortValue); }
+ }];
+ }
+
// @upload text
- function scanText(rspamd, data, server) {
- var items = [];
+ function scanText(rspamd, tables, data, server) {
rspamd.query("checkv2", {
data: data,
params: {
var json = neighbours_status[0].data;
if (json.action) {
rspamd.alertMessage("alert-success", "Data successfully scanned");
- var action = "";
-
- if (json.action === "clean" || json.action === "no action") {
- action = "label-success";
- } else if (json.action === "rewrite subject" || json.action === "add header" || json.action === "probable spam") {
- action = "label-warning";
- } else if (json.action === "spam") {
- action = "label-danger";
- }
- var score = "";
- if (json.score <= json.required_score) {
- score = "label-success";
- } else if (json.score >= json.required_score) {
- score = "label-danger";
- }
- $("<tbody id=\"tmpBody\"><tr>" +
- "<td><span class=\"label " + action + "\">" + json.action + "</span></td>" +
- "<td><span class=\"label " + score + "\">" + json.score.toFixed(2) + "/" + json.required_score.toFixed(2) + "</span></td>" +
- "</tr></tbody>")
- .insertAfter("#scanOutput thead");
- var sym_desc = {};
- var nsym = 0;
+ var rows_total = $("#historyTable_scan > tbody > tr:not(.footable-detail-row)").length + 1;
+ var o = rspamd.process_history_v2(rspamd, {rows:[json]}, "scan");
+ var items = o.items;
+ rspamd.symbols.scan.push(o.symbols[0]);
- $.each(json.symbols, function (i, item) {
- if (typeof item === "object") {
- var sym_id = "sym_" + nsym;
- if (item.description) {
- sym_desc[sym_id] = item.description;
- }
- items.push("<div class=\"cell-overflow\" tabindex=\"1\"><abbr id=\"" + sym_id +
- "\">" + item.name + "</abbr>: " + item.score.toFixed(2) + "</div>");
- nsym++;
- }
- });
- $("<td/>", {
- id: "tmpSymbols",
- html: items.join("")
- }).appendTo("#scanResult");
- $("#tmpSymbols").insertAfter("#tmpBody td:last").removeAttr("id");
- $("#tmpBody").removeAttr("id");
- $("#scanResult").show();
- // Show tooltips
- $.each(sym_desc, function (k, v) {
- $("#" + k).tooltip({
- placement: "bottom",
- title: v
+ if (Object.prototype.hasOwnProperty.call(tables, "scan")) {
+ tables.scan.rows.load(items, true);
+ // Is there a way to get an event when all rows are loaded?
+ rspamd.waitForRowsDisplayed("scan", rows_total, function () {
+ rspamd.drawTooltips();
+ $("html, body").animate({
+ scrollTop: $("#scanResult").offset().top
+ }, 1000);
});
- });
- $("html, body").animate({
- scrollTop: $("#scanResult").offset().top
- }, 1000);
+ } else {
+ rspamd.destroyTable("scan");
+ // Is there a way to get an event when the table is destroyed?
+ setTimeout(function () {
+ rspamd.initHistoryTable(rspamd, data, items, "scan", columns_v2(), true);
+ rspamd.waitForRowsDisplayed("scan", rows_total, function () {
+ $("html, body").animate({
+ scrollTop: $("#scanResult").offset().top
+ }, 1000);
+ });
+ }, 200);
+ }
} else {
rspamd.alertMessage("alert-error", "Cannot scan data");
}
});
}
- ui.setup = function (rspamd) {
- function getSelector(id) {
- var e = document.getElementById(id);
- return e.options[e.selectedIndex].value;
- }
+ ui.setup = function (rspamd, tables) {
+ rspamd.set_page_size("scan", $("#scan_page_size").val());
+ rspamd.bindHistoryTableEventHandlers("scan", 3);
+ $("#cleanScanHistory").off("click");
+ $("#cleanScanHistory").on("click", function (e) {
+ e.preventDefault();
+ 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;
+ });
$("#scan button").attr("disabled", true);
$("textarea").on("input", function () {
var $this = $(this);
: {};
if ($.trim(data).length > 0) {
if (source === "scan") {
- var checked_server = getSelector("selSrv");
+ var checked_server = rspamd.getSelector("selSrv");
var server = (checked_server === "All SERVERS") ? "local" : checked_server;
- scanText(rspamd, data, server);
+ scanText(rspamd, tables, data, server);
} else {
uploadText(rspamd, data, source, headers);
}