diff options
author | Vsevolod Stakhov <vsevolod@rspamd.com> | 2025-06-01 17:53:32 +0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-06-01 17:53:32 +0600 |
commit | c93385967c2410fe62ef8d34ab7ddeea23a9f843 (patch) | |
tree | ff494da5f924390b66919ab6ef21c78f3f3d2f0a | |
parent | e91a7e22a6ff197679821383a540f16047b5d5b0 (diff) | |
parent | 9b41324db146f07ab7e4da2f8dcd4c123319bdf8 (diff) | |
download | rspamd-master.tar.gz rspamd-master.zip |
[WebUI] Add file upload to `Test selectors`
-rw-r--r-- | interface/css/rspamd.css | 9 | ||||
-rw-r--r-- | interface/index.html | 6 | ||||
-rw-r--r-- | interface/js/app/common.js | 58 | ||||
-rw-r--r-- | interface/js/app/selectors.js | 10 | ||||
-rw-r--r-- | interface/js/app/upload.js | 70 |
5 files changed, 91 insertions, 62 deletions
diff --git a/interface/css/rspamd.css b/interface/css/rspamd.css index 896f92008..a79fed28b 100644 --- a/interface/css/rspamd.css +++ b/interface/css/rspamd.css @@ -418,13 +418,18 @@ table#symbolsTable input[type="number"] { .outline-dashed-primary { outline: 2px dashed var(--bs-primary); } -#scanMsgSource:placeholder-shown { +#scanMsgSource:placeholder-shown, +#selectorsMsgArea:placeholder-shown { background-image: url("../img/drop-area.svg"); background-repeat: no-repeat; background-position: center; opacity: 0.8; } -#scanMsgSource:not(:placeholder-shown) { background-image: none;} + +#scanMsgSource:not(:placeholder-shown), +#selectorsMsgArea:not(:placeholder-shown) { + background-image: none; +} .scorebar-spam { background-color: rgba(240 0 0 / 0.1) !important; diff --git a/interface/index.html b/interface/index.html index cf2187601..b176d6527 100644 --- a/interface/index.html +++ b/interface/index.html @@ -580,6 +580,10 @@ <div class="card-header text-secondary py-2 d-flex align-items-center"> <span class="icon me-3"><i class="fas fa-envelope"></i></span> <span class="h6 fw-bolder my-auto">Test Rspamd selectors</span> + <div class="d-flex input-group-sm align-items-center ms-auto"> + <label for="formFile" class="col-auto col-form-label-sm me-1">Choose a file:</label> + <input class="form-control form-control-sm btn btn-secondary" id="selectorsFile" type="file"> + </div> </div> <div class="card-body p-0"> <div class="row h-100 m-0" id="row-main"> @@ -608,7 +612,7 @@ <div class="col"> <div class="form-group"> <label class="form-label" for="selectorsMsgArea">Message source:</label> - <textarea class="form-control" id="selectorsMsgArea" rows="9" placeholder="Paste raw message source"></textarea> + <textarea class="form-control" id="selectorsMsgArea" rows="9" placeholder='Paste raw message source, drag and drop files here or use "Browse..." button.'></textarea> </div> <button class="btn btn-secondary d-flex align-items-center float-end" id="selectorsMsgClean"><i class="fas fa-trash-alt me-2"></i>Clean form</button> </div> diff --git a/interface/js/app/common.js b/interface/js/app/common.js index ace4bbba1..44e0dcf77 100644 --- a/interface/js/app/common.js +++ b/interface/js/app/common.js @@ -261,5 +261,63 @@ define(["jquery", "nprogress"], ).appendTo(ftFilter.$dropdown); }; + ui.fileUtils = { + readFile(files, callback, index = 0) { + const file = files[index]; + const reader = new FileReader(); + reader.onerror = () => alertMessage("alert-error", `Error reading file: ${file.name}`); + reader.onloadend = () => callback(reader.result); + reader.readAsText(file); + }, + + setFileInputFiles(fileInput, files, i) { + const dt = new DataTransfer(); + if (arguments.length > 2) dt.items.add(files[i]); + $(fileInput).prop("files", dt.files); + }, + + setupFileHandling(textArea, fileInput, fileSet, enable_btn_cb, multiple_files_cb) { + const dragoverClassList = "outline-dashed-primary bg-primary-subtle"; + const {readFile, setFileInputFiles} = ui.fileUtils; + + function handleFileInput(fileSource) { + fileSet.files = fileSource.files; + fileSet.index = 0; + const {files} = fileSet; + + if (files.length === 1) { + setFileInputFiles(fileInput, files, 0); + enable_btn_cb(); + readFile(files, (result) => { + $(textArea).val(result); + enable_btn_cb(); + }); + } else if (multiple_files_cb) { + multiple_files_cb(files); + } else { + alertMessage("alert-warning", "Multiple files processing is not supported."); + } + } + + $(textArea) + .on("dragenter dragover dragleave drop", (e) => { + e.preventDefault(); + e.stopPropagation(); + }) + .on("dragenter dragover", () => $(textArea).addClass(dragoverClassList)) + .on("dragleave drop", () => $(textArea).removeClass(dragoverClassList)) + .on("drop", (e) => handleFileInput(e.originalEvent.dataTransfer)) + .on("input", () => { + enable_btn_cb(); + if (fileSet.files) { + fileSet.files = null; + setFileInputFiles(fileInput, fileSet.files); + } + }); + + $(fileInput).on("change", (e) => handleFileInput(e.target)); + } + }; + return ui; }); diff --git a/interface/js/app/selectors.js b/interface/js/app/selectors.js index c2b8b27e5..4a1c6d0d0 100644 --- a/interface/js/app/selectors.js +++ b/interface/js/app/selectors.js @@ -2,6 +2,7 @@ define(["jquery", "app/common"], ($, common) => { "use strict"; const ui = {}; + const fileSet = {files: null, index: null}; function enable_disable_check_btn() { $("#selectorsChkMsgBtn").prop("disabled", ( @@ -129,12 +130,15 @@ define(["jquery", "app/common"], return false; }); - $("#selectorsMsgArea").on("input", () => { - enable_disable_check_btn(); - }); $("#selectorsSelArea").on("input", () => { checkSelectors(); }); + $("#selectorsMsgClean").on("click", () => { + $("#selectorsMsgArea").val(""); + $("#selectorsFile").val(""); + }); + + common.fileUtils.setupFileHandling("#selectorsMsgArea", "#selectorsFile", fileSet, enable_disable_check_btn); return ui; }); diff --git a/interface/js/app/upload.js b/interface/js/app/upload.js index d5283cdb1..a5d30b59e 100644 --- a/interface/js/app/upload.js +++ b/interface/js/app/upload.js @@ -28,8 +28,7 @@ define(["jquery", "app/common", "app/libft"], ($, common, libft) => { "use strict"; const ui = {}; - let files = null; - let filesIdx = null; + const fileSet = {files: null, index: null}; let scanTextHeaders = {}; function uploadText(data, url, headers, method = "POST") { @@ -67,19 +66,7 @@ define(["jquery", "app/common", "app/libft"], function enable_disable_scan_btn(disable) { $("#scan button:not(#cleanScanHistory, #deleteHashesBtn, #scanOptionsToggle, .ft-columns-btn)") - .prop("disabled", (disable || $.trim($("textarea").val()).length === 0)); - } - - function setFileInputFiles(i) { - const dt = new DataTransfer(); - if (arguments.length) dt.items.add(files[i]); - $("#formFile").prop("files", dt.files); - } - - function readFile(callback, i) { - const reader = new FileReader(); - reader.readAsText(files[(arguments.length === 1) ? 0 : i]); - reader.onload = () => callback(reader.result); + .prop("disabled", (disable || $.trim($("#scanMsgSource").val()).length === 0)); } function scanText(data) { @@ -100,7 +87,7 @@ define(["jquery", "app/common", "app/libft"], const {items} = o; common.symbols.scan.push(o.symbols[0]); - if (files) items[0].file = files[filesIdx].name; + if (fileSet.files) items[0].file = fileSet.files[fileSet.index].name; if (Object.prototype.hasOwnProperty.call(common.tables, "scan")) { common.tables.scan.rows.load(items, true); @@ -108,14 +95,16 @@ define(["jquery", "app/common", "app/libft"], require(["footable"], () => { libft.initHistoryTable(data, items, "scan", libft.columns_v2("scan"), true, () => { - if (files && filesIdx < files.length - 1) { - readFile((result) => { - if (filesIdx === files.length - 1) { + const {files} = fileSet; + if (files && fileSet.index < files.length - 1) { + common.fileUtils.readFile(files, (result) => { + const {index} = fileSet; + if (index === files.length - 1) { $("#scanMsgSource").val(result); - setFileInputFiles(filesIdx); + common.fileUtils.setFileInputFiles("#formFile", files, index); } scanText(result); - }, ++filesIdx); + }, ++fileSet.index); } else { enable_disable_scan_btn(); $("#cleanScanHistory, #scan .ft-columns-dropdown .btn-dropdown-apply") @@ -196,13 +185,6 @@ define(["jquery", "app/common", "app/libft"], }); enable_disable_scan_btn(); - $("textarea").on("input", () => { - enable_disable_scan_btn(); - if (files) { - files = null; - setFileInputFiles(); - } - }); $("#scanClean").on("click", () => { enable_disable_scan_btn(true); @@ -304,39 +286,15 @@ define(["jquery", "app/common", "app/libft"], }); - function fileInputHandler(obj) { - ({files} = obj); - filesIdx = 0; - - if (files.length === 1) { - setFileInputFiles(0); - enable_disable_scan_btn(); - readFile((result) => { - $("#scanMsgSource").val(result); - enable_disable_scan_btn(); - }); + function multiple_files_cb(files) { // eslint-disable-next-line no-alert - } else if (files.length < 10 || confirm("Are you sure you want to scan " + files.length + " files?")) { + if (files.length < 10 || confirm("Are you sure you want to scan " + files.length + " files?")) { getScanTextHeaders(); - readFile((result) => scanText(result)); + common.fileUtils.readFile(files, (result) => scanText(result)); } } - const dragoverClassList = "outline-dashed-primary bg-primary-subtle"; - $("#scanMsgSource") - .on("dragenter dragover dragleave drop", (e) => { - e.preventDefault(); - e.stopPropagation(); - }) - .on("dragenter dragover", () => { - $("#scanMsgSource").addClass(dragoverClassList); - }) - .on("dragleave drop", () => { - $("#scanMsgSource").removeClass(dragoverClassList); - }) - .on("drop", (e) => fileInputHandler(e.originalEvent.dataTransfer)); - - $("#formFile").on("change", (e) => fileInputHandler(e.target)); + common.fileUtils.setupFileHandling("#scanMsgSource", "#formFile", fileSet, enable_disable_scan_btn, multiple_files_cb); return ui; }); |