]> source.dussan.org Git - rspamd.git/commitdiff
[Minor] Move common stuff to separate files
authormoisseev <moiseev@mezonplus.ru>
Wed, 27 Dec 2023 12:22:23 +0000 (15:22 +0300)
committermoisseev <moiseev@mezonplus.ru>
Wed, 27 Dec 2023 12:24:31 +0000 (15:24 +0300)
interface/js/app/common.js [new file with mode: 0644]
interface/js/app/config.js
interface/js/app/graph.js
interface/js/app/history.js
interface/js/app/libft.js [new file with mode: 0644]
interface/js/app/rspamd.js
interface/js/app/selectors.js
interface/js/app/stats.js
interface/js/app/symbols.js
interface/js/app/upload.js

diff --git a/interface/js/app/common.js b/interface/js/app/common.js
new file mode 100644 (file)
index 0000000..ea6102f
--- /dev/null
@@ -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;
+    });
index 1aaf71289e190b053bc2da7392c53e7c52efa37a..6be1075558bf9aa1d6501f26cf5a77b5d1e99b40 100644 (file)
 
 /* 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",
index 2fc00a45758eed1e5a87f1aae697dd66daa3d0a3..71306f457d6dba89bc2ec777ccfba0526cd49f1f 100644 (file)
@@ -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
                             }
index a4d027da0f025c117333df93651d5e85d0adad88..58d835fba339421e65cc4b54fe74d5e4fc641a67 100644 (file)
@@ -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();
diff --git a/interface/js/app/libft.js b/interface/js/app/libft.js
new file mode 100644 (file)
index 0000000..58262d2
--- /dev/null
@@ -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;
+    });
index b1d71a5c941aa4ce8cc5925343e779ed880f4a9a..938f048e7b36ecca3e76e0cdcec687ed254669d5 100644 (file)
  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()
             );
         }
index 2a0097e79c1b30b21f4acd5b9ce148d77cb98355..53240d83847736b9e0d7c96cf0dbb6780529c60e 100644 (file)
@@ -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();
index ddf641d69ad6325f5741bcb1caec893a2254be6b..04b4a75c5b1fb5285a4ec2b227dd93354b4bbfa3 100644 (file)
@@ -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);
index b00fc990fe3b04ee6b4c1e8fccc18b094479a964..1e3fb5de726a9a095af13c1da3694f4555fc73d9 100644 (file)
@@ -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
             });
index c474396aed5e61331a2565f4555229f10e97ded6..086c6025e9327472f5e367f14596f6eddb31c2eb 100644 (file)
@@ -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;
         });