commit 01a0df9: [Minor] Move common stuff to separate files

moisseev moiseev at mezonplus.ru
Mon Jul 29 17:50:52 UTC 2024


Author: moisseev
Date: 2023-12-27 15:22:23 +0300
URL: https://github.com/rspamd/rspamd/commit/01a0df9f25cee9e56294aea179e6a4e5f1b9c09a

[Minor] Move common stuff to separate files

---
 interface/js/app/common.js    | 233 ++++++++++++++++
 interface/js/app/config.js    |  34 +--
 interface/js/app/graph.js     |  24 +-
 interface/js/app/history.js   |  52 ++--
 interface/js/app/libft.js     | 380 +++++++++++++++++++++++++
 interface/js/app/rspamd.js    | 630 ++----------------------------------------
 interface/js/app/selectors.js |  20 +-
 interface/js/app/stats.js     |  12 +-
 interface/js/app/symbols.js   |  20 +-
 interface/js/app/upload.js    |  58 ++--
 10 files changed, 741 insertions(+), 722 deletions(-)

diff --git a/interface/js/app/common.js b/interface/js/app/common.js
new file mode 100644
index 000000000..ea6102f60
--- /dev/null
+++ b/interface/js/app/common.js
@@ -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 = {
+                "&": "&",
+                "<": "<",
+                ">": ">",
+                "\"": """,
+                "'": "'",
+                "/": "&#x2F;",
+                "`": "&#x60;",
+                "=": "&#x3D;"
+            };
+            return String(string).replace(htmlEscaper, (match) => htmlEscapes[match]);
+        };
+
+        return ui;
+    });
diff --git a/interface/js/app/config.js b/interface/js/app/config.js
index 1aaf71289..6be107555 100644
--- a/interface/js/app/config.js
+++ b/interface/js/app/config.js
@@ -24,13 +24,13 @@
 
 /* global require */
 
-define(["jquery", "app/rspamd"],
-    ($, rspamd) => {
+define(["jquery", "app/common"],
+    ($, common) => {
         "use strict";
         const ui = {};
 
         ui.getActions = function getActions(checked_server) {
-            rspamd.query("actions", {
+            common.query("actions", {
                 success: function (data) {
                     $("#actionsFormField").empty();
                     const items = [];
@@ -88,15 +88,15 @@ define(["jquery", "app/rspamd"],
             // String to array for comparison
             const eltsArray = JSON.parse(elts);
             if (eltsArray[0] < 0) {
-                rspamd.alertMessage("alert-modal alert-error", "Spam can not be negative");
+                common.alertMessage("alert-modal alert-error", "Spam can not be negative");
             } else if (eltsArray[1] < 0) {
-                rspamd.alertMessage("alert-modal alert-error", "Rewrite subject can not be negative");
+                common.alertMessage("alert-modal alert-error", "Rewrite subject can not be negative");
             } else if (eltsArray[2] < 0) {
-                rspamd.alertMessage("alert-modal alert-error", "Probable spam can not be negative");
+                common.alertMessage("alert-modal alert-error", "Probable spam can not be negative");
             } else if (eltsArray[3] < 0) {
-                rspamd.alertMessage("alert-modal alert-error", "Greylist can not be negative");
+                common.alertMessage("alert-modal alert-error", "Greylist can not be negative");
             } else if (descending(eltsArray)) {
-                rspamd.query("saveactions", {
+                common.query("saveactions", {
                     method: "POST",
                     params: {
                         data: elts,
@@ -105,14 +105,14 @@ define(["jquery", "app/rspamd"],
                     server: server
                 });
             } else {
-                rspamd.alertMessage("alert-modal alert-error", "Incorrect order of actions thresholds");
+                common.alertMessage("alert-modal alert-error", "Incorrect order of actions thresholds");
             }
         };
 
         ui.getMaps = function (checked_server) {
             const $listmaps = $("#listMaps");
             $listmaps.closest(".card").hide();
-            rspamd.query("maps", {
+            common.query("maps", {
                 success: function (json) {
                     const [{data}] = json;
                     $listmaps.empty();
@@ -121,7 +121,7 @@ define(["jquery", "app/rspamd"],
 
                     $.each(data, (i, item) => {
                         let $td = '<td><span class="badge text-bg-secondary">Read</span></td>';
-                        if (!(item.editable === false || rspamd.read_only)) {
+                        if (!(item.editable === false || common.read_only)) {
                             $td = $($td).append(' <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",
diff --git a/interface/js/app/graph.js b/interface/js/app/graph.js
index 2fc00a457..71306f457 100644
--- a/interface/js/app/graph.js
+++ b/interface/js/app/graph.js
@@ -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
                             }
diff --git a/interface/js/app/history.js b/interface/js/app/history.js
index a4d027da0..58d835fba 100644
--- a/interface/js/app/history.js
+++ b/interface/js/app/history.js
@@ -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
index 000000000..58262d22f
--- /dev/null
+++ b/interface/js/app/libft.js
@@ -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 */
*** OUTPUT TRUNCATED, 1399 LINES SKIPPED ***


More information about the Commits mailing list