commit def746a: [WebUI] Rework scan results display
moisseev
moiseev at mezonplus.ru
Wed Aug 21 07:35:05 UTC 2019
Author: moisseev
Date: 2019-08-20 14:36:19 +0300
URL: https://github.com/rspamd/rspamd/commit/def746a5dda265e113940ee13329ad3b45efd187
[WebUI] Rework scan results display
---
.eslintrc.json | 2 +-
interface/css/rspamd.css | 12 +-
interface/index.html | 45 +++--
interface/js/app/history.js | 433 +++-----------------------------------------
interface/js/app/rspamd.js | 404 ++++++++++++++++++++++++++++++++++++++++-
interface/js/app/upload.js | 161 ++++++++++------
6 files changed, 566 insertions(+), 491 deletions(-)
diff --git a/.eslintrc.json b/.eslintrc.json
index c9fa15153..bfc3fd6a5 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -24,7 +24,7 @@
"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"],
diff --git a/interface/css/rspamd.css b/interface/css/rspamd.css
index 9a0438d78..0e7c877d9 100644
--- a/interface/css/rspamd.css
+++ b/interface/css/rspamd.css
@@ -502,17 +502,17 @@ td.maps-cell {
}
/* 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;
diff --git a/interface/index.html b/interface/index.html
index cb22f3064..84aa211cd 100644
--- a/interface/index.html
+++ b/interface/index.html
@@ -219,20 +219,6 @@
</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;">
@@ -274,6 +260,31 @@
</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">
@@ -281,8 +292,8 @@
<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>
@@ -301,7 +312,7 @@
</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>
diff --git a/interface/js/app/history.js b/interface/js/app/history.js
index 49e228e3a..a7d656f27 100644
--- a/interface/js/app/history.js
+++ b/interface/js/app/history.js
@@ -27,245 +27,21 @@
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];
@@ -274,7 +50,7 @@ define(["jquery", "footable", "humanize"],
.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
}
@@ -362,10 +138,10 @@ define(["jquery", "footable", "humanize"],
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: {
@@ -500,17 +276,16 @@ define(["jquery", "footable", "humanize"],
}];
}
- 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) {
@@ -520,7 +295,7 @@ define(["jquery", "footable", "humanize"],
}
}
- return pf(data);
+ return pf(rspamd, data, "history");
}
function get_history_columns(data) {
@@ -536,136 +311,7 @@ define(["jquery", "footable", "humanize"],
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) {
@@ -696,29 +342,29 @@ define(["jquery", "footable", "humanize"],
// 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",
@@ -726,33 +372,14 @@ define(["jquery", "footable", "humanize"],
};
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");
@@ -761,8 +388,8 @@ define(["jquery", "footable", "humanize"],
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 () {
@@ -774,7 +401,7 @@ define(["jquery", "footable", "humanize"],
});
};
- 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}},
@@ -788,7 +415,7 @@ define(["jquery", "footable", "humanize"],
paging: {
enabled: true,
limit: 5,
- size: page_size.errors
+ size: rspamd.page_size.errors
},
filtering: {
enabled: true,
@@ -816,7 +443,7 @@ define(["jquery", "footable", "humanize"],
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
}
@@ -825,7 +452,7 @@ define(["jquery", "footable", "humanize"],
if (Object.prototype.hasOwnProperty.call(tables, "errors")) {
tables.errors.rows.load(rows);
} else {
- initErrorsTable(tables, rows);
+ initErrorsTable(rspamd, tables, rows);
}
}
});
*** OUTPUT TRUNCATED, 662 LINES SKIPPED ***
More information about the Commits
mailing list