From ce2dba2f58b8dacbf0569d31d40e2c001c1cc0ab Mon Sep 17 00:00:00 2001 From: Alexander Moisseev Date: Wed, 25 Jun 2025 17:20:19 +0300 Subject: [PATCH] [WebUI] Add fuzzy flag selectors to Scan/Learn tab MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit - Populate selectors with symbols and flags from writable fuzzy storages. - Cache each server’s config_id to avoid redundant `/plugins/fuzzy/storages` calls. - Dynamically show, hide, and disable controls based on fuzzy_check module availability and storage writability. --- eslint.config.mjs | 3 ++ interface/css/rspamd.css | 9 ++++ interface/index.html | 4 +- interface/js/app/rspamd.js | 2 +- interface/js/app/upload.js | 94 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 2 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 2f256afcbe..bdd6ede481 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -13,6 +13,9 @@ export default [ ...globals.browser, define: false, }, + parserOptions: { + ecmaVersion: 2020, + }, sourceType: "script", }, plugins: { diff --git a/interface/css/rspamd.css b/interface/css/rspamd.css index a79fed28b0..a4335c07b9 100644 --- a/interface/css/rspamd.css +++ b/interface/css/rspamd.css @@ -95,6 +95,15 @@ fieldset[disabled] .btn { pointer-events: auto; cursor: not-allowed; } +.card.disabled, +.input-group.disabled { + cursor: not-allowed; + opacity: 0.65; +} +.card.disabled *, +.input-group.disabled * { + pointer-events: none; +} .w-1 { width: 1%; } diff --git a/interface/index.html b/interface/index.html index 61487ce295..30181e7886 100644 --- a/interface/index.html +++ b/interface/index.html @@ -453,7 +453,8 @@
- + +
@@ -495,6 +496,7 @@
+
diff --git a/interface/js/app/rspamd.js b/interface/js/app/rspamd.js index da4e9bccfe..ceba6864bf 100644 --- a/interface/js/app/rspamd.js +++ b/interface/js/app/rspamd.js @@ -176,7 +176,7 @@ define(["jquery", "app/common", "stickytabs", "visibility", require(["app/symbols"], (module) => module.getSymbols()); break; case "#scan_nav": - require(["app/upload"]); + require(["app/upload"], (module) => module.getFuzzyStorages()); break; case "#selectors_nav": require(["app/selectors"], (module) => module.displayUI()); diff --git a/interface/js/app/upload.js b/interface/js/app/upload.js index 0960ebf25b..c82bb23069 100644 --- a/interface/js/app/upload.js +++ b/interface/js/app/upload.js @@ -312,5 +312,99 @@ define(["jquery", "app/common", "app/libft"], }; ui.getClassifiers(); + + const fuzzyWidgets = [ + { + picker: "#fuzzy-flag-picker", + input: "#fuzzy-flag", + container: ($picker) => $picker.parent() + }, + { + picker: "#fuzzyFlagText-picker", + input: "#fuzzyFlagText", + container: ($picker) => $picker.closest("div.card") + } + ]; + + function toggleWidgets(showPicker, showInput) { + fuzzyWidgets.forEach(({picker, input}) => { + $(picker)[showPicker ? "show" : "hide"](); + $(input)[showInput ? "show" : "hide"](); + }); + } + + function setWidgetsDisabled(disable) { + fuzzyWidgets.forEach(({picker, container}) => { + container($(picker))[disable ? "addClass" : "removeClass"]("disabled"); + }); + } + + let lastFuzzyStoragesReq = {config_id: null, server: null}; + + ui.getFuzzyStorages = function () { + const server = common.getServer(); + + const servers = JSON.parse(sessionStorage.getItem("Credentials") || "{}"); + const config_id = servers[server]?.data?.config_id; + + if ((config_id && config_id === lastFuzzyStoragesReq.config_id) || + (!config_id && server === lastFuzzyStoragesReq.server)) { + return; + } + lastFuzzyStoragesReq = {config_id: config_id, server: server}; + + fuzzyWidgets.forEach(({picker, container}) => container($(picker)).removeAttr("title")); + + common.query("plugins/fuzzy/storages", { + success: function (data) { + const storages = data[0].data.storages || {}; + const hasWritableStorages = Object.keys(storages).some((name) => !storages[name].read_only); + + toggleWidgets(true, false); + setWidgetsDisabled(!hasWritableStorages); + + fuzzyWidgets.forEach(({picker, input}) => { + const $sel = $(picker); + + $sel.empty(); + + if (hasWritableStorages) { + Object.entries(storages).forEach(([name, info]) => { + if (!info.read_only) { + Object.entries(info.flags).forEach(([symbol, val]) => { + $sel.append($("