]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[WebUI] Add history load range controls 5550/head
authorAlexander Moisseev <moiseev@mezonplus.ru>
Tue, 22 Jul 2025 16:25:05 +0000 (19:25 +0300)
committerAlexander Moisseev <moiseev@mezonplus.ru>
Tue, 22 Jul 2025 16:25:05 +0000 (19:25 +0300)
- Add 'Offset' and 'Count' inputs to History tab for selecting
  the range of history rows to load
- Add corresponding default setting in Settings tab

interface/css/rspamd.css
interface/index.html
interface/js/app/history.js
interface/js/app/rspamd.js

index 9f97a668b28b44a8aaba79507d5d6753b8437b50..54310049b0f661870ef7b1f1cebc66f881a00a6e 100644 (file)
@@ -420,8 +420,10 @@ table#symbolsTable input[type="number"] {
     display: none;
 }
 
+#history-from,
+#history-count,
 #history_page_size {
-    width: 6em !important;
+    width: 6em;
     text-align: center;
 }
 
index 30181e788645598831b27beb811986387c877941..165eae200b2967a49c13be83c60e7138dbae676b 100644 (file)
                                                                </div>
                                                        </div>
                                                </div>
+
+                                               <div class="card mt-1">
+                                                       <div class="card-body">
+                                                               <h6 class="card-title fw-bolder">History rows per load</h6>
+                                                               <div class="input-group input-group-sm was-validated">
+                                                                       <input type="number" id="settings-history-count" class="form-control" min="1" step="1" placeholder="1000">
+                                                                       <button id="settings-history-count-restore" class="btn btn-secondary">Restore default</button>
+                                                               </div>
+                                                       </div>
+                                               </div>
                                        </div>
                                </div>
                        </form>
                                                                <option value="score">Score value</option>
                                                                <option value="name">Name</option>
                                                        </select>
-                                                       <label for="history_page_size" class="ms-2">Rows per page:</label>
+                                                       <label for="history-from" class="ms-3" title="Start from this row number">Offset:</label>
+                                                       <input type="number" id="history-from" class="form-control ms-1" value="0" min="0" step="1" title="Start from this row number">
+                                                       <label for="history-count" class="ms-2" title="Number of rows to load">Count:</label>
+                                                       <input type="number" id="history-count" class="form-control ms-1" value="1000" min="1" step="1" title="Number of rows to load">
+                                                       <label for="history_page_size" class="ms-2">Rows/page:</label>
                                                        <input id="history_page_size" class="form-control ms-1" value="25" min="1" type="number">
-                                                       <button class="btn btn-outline-secondary btn-sm ms-2 d-flex align-items-center dropdown-toggle ft-columns-btn" type="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false" disabled>
+                                                       <button class="btn btn-outline-secondary btn-sm ms-3 d-flex align-items-center dropdown-toggle ft-columns-btn" type="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false" disabled>
                                                                <i class="fas fa-columns me-1"></i>Columns
                                                        </button>
                                                        <div class="dropdown-menu ft-columns-dropdown p-2"></div>
index 18592208733d90cec9aa2be863cedb00f633503b..bf1dbae534f283d879054062d6c18c6e44eb4df7 100644 (file)
@@ -30,6 +30,12 @@ define(["jquery", "app/common", "app/libft", "footable"],
         const ui = {};
         let prevVersion = null;
 
+        // History range: offset and count
+        const histFromDef = 0;
+        const historyCountDef = 1000;
+        let histFrom = histFromDef;
+        let histCount = parseInt(localStorage.getItem("historyCount"), 10) || historyCountDef;
+
         function process_history_legacy(data) {
             const items = [];
 
@@ -152,7 +158,8 @@ define(["jquery", "app/common", "app/libft", "footable"],
 
         ui.getHistory = function () {
             $("#refresh, #updateHistory").attr("disabled", true);
-            common.query("history", {
+            const histTo = histFrom - 1 + histCount;
+            common.query(`history?from=${histFrom}&to=${histTo}`, {
                 success: function (req_data) {
                     function differentVersions(neighbours_data) {
                         const dv = neighbours_data.some((e) => e.version !== neighbours_data[0].version);
@@ -192,8 +199,10 @@ define(["jquery", "app/common", "app/libft", "footable"],
                             // Is there a way to get an event when the table is destroyed?
                             setTimeout(() => {
                                 libft.initHistoryTable(data, items, "history", get_history_columns(data), false,
-                                    () => $("#refresh, #updateHistory, #history .ft-columns-dropdown .btn-dropdown-apply")
-                                        .removeAttr("disabled"));
+                                    () => {
+                                        $("#history .ft-columns-dropdown .btn-dropdown-apply").removeAttr("disabled");
+                                        ui.updateHistoryControlsState();
+                                    });
                             }, 200);
                         }
                         prevVersion = version;
@@ -201,7 +210,7 @@ define(["jquery", "app/common", "app/libft", "footable"],
                         libft.destroyTable("history");
                     }
                 },
-                error: () => $("#refresh, #updateHistory").removeAttr("disabled"),
+                error: () => ui.updateHistoryControlsState(),
                 errorMessage: "Cannot receive history",
             });
         };
@@ -282,6 +291,46 @@ define(["jquery", "app/common", "app/libft", "footable"],
             });
         };
 
+        ui.updateHistoryControlsState = function () {
+            const from = parseInt($("#history-from").val(), 10);
+            const count = parseInt($("#history-count").val(), 10);
+            const valid = !(isNaN(from) || from < 0 || isNaN(count) || count < 1);
+
+            if (valid) {
+                $("#refresh, #updateHistory").removeAttr("disabled").removeClass("disabled");
+            } else {
+                $("#refresh, #updateHistory").attr("disabled", true).addClass("disabled");
+            }
+        };
+
+        function validateAndClampInput(el) {
+            const min = el.id === "history-from" ? 0 : 1;
+            let v = parseInt(el.value, 10);
+            if (isNaN(v) || v < min) {
+                v = min;
+                $(el).addClass("is-invalid");
+            } else {
+                $(el).removeClass("is-invalid");
+            }
+            return v;
+        }
+
+        $("#history-from").val(histFrom);
+        $("#history-count").val(histCount);
+        $("#history-from, #history-count").on("input", (e) => {
+            validateAndClampInput(e.currentTarget);
+            ui.updateHistoryControlsState();
+        });
+        $("#history-from, #history-count").on("blur", (e) => {
+            const el = e.currentTarget;
+            const v = validateAndClampInput(el);
+            $(el).val(v).removeClass("is-invalid");
+            ui.updateHistoryControlsState();
+        });
+        $("#history-from,#history-count").on("change", () => {
+            histFrom = parseInt($("#history-from").val(), 10) || histFromDef;
+            histCount = parseInt($("#history-count").val(), 10) || historyCountDef;
+        });
 
         libft.set_page_size("history", $("#history_page_size").val());
         libft.bindHistoryTableEventHandlers("history", 8);
index cb7fb8acefcbfe0addca5edbf8129ef0d22e593f..4b154c2ae471daea38fa94c448ba5615d2e83e9f 100644 (file)
@@ -198,6 +198,8 @@ define(["jquery", "app/common", "stickytabs", "visibility",
                     $(".preset").hide();
                     $(".history").show();
                     $(".dynamic").hide();
+
+                    module.updateHistoryControlsState();
                 });
                 break;
             case "#disconnect":
@@ -348,6 +350,8 @@ define(["jquery", "app/common", "stickytabs", "visibility",
         let selected_locale = null;
         let custom_locale = null;
         const localeTextbox = ".popover #settings-popover #locale";
+        const historyCountDef = 1000;
+        const historyCountSelector = ".popover #settings-popover #settings-history-count";
 
         function validateLocale(saveToLocalStorage) {
             function toggle_form_group_class(remove, add) {
@@ -406,6 +410,8 @@ define(["jquery", "app/common", "stickytabs", "visibility",
             $(localeTextbox).val(custom_locale);
 
             ajaxSetup(localStorage.getItem("ajax_timeout"), true);
+
+            $(historyCountSelector).val(parseInt(localStorage.getItem("historyCount"), 10) || historyCountDef);
         });
         $(document).on("change", '.popover #settings-popover input:radio[name="locale"]', function () {
             selected_locale = this.value;
@@ -423,6 +429,21 @@ define(["jquery", "app/common", "stickytabs", "visibility",
             ajaxSetup(null, true, true);
         });
 
+        $(document).on("input", historyCountSelector, (e) => {
+            const v = parseInt($(e.currentTarget).val(), 10);
+            if (v > 0) {
+                localStorage.setItem("historyCount", v);
+                $(e.currentTarget).removeClass("is-invalid");
+                $("#history-count").val(v).trigger("change");
+            } else {
+                $(e.currentTarget).addClass("is-invalid");
+            }
+        });
+        $(document).on("click", ".popover #settings-popover #settings-history-count-restore", () => {
+            localStorage.removeItem("historyCount");
+            $(historyCountSelector).val(historyCountDef);
+        });
+
         // Dismiss Bootstrap popover by clicking outside
         $("body").on("click", (e) => {
             $(".popover").each(function () {