aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@rspamd.com>2025-06-01 17:53:32 +0600
committerGitHub <noreply@github.com>2025-06-01 17:53:32 +0600
commitc93385967c2410fe62ef8d34ab7ddeea23a9f843 (patch)
treeff494da5f924390b66919ab6ef21c78f3f3d2f0a
parente91a7e22a6ff197679821383a540f16047b5d5b0 (diff)
parent9b41324db146f07ab7e4da2f8dcd4c123319bdf8 (diff)
downloadrspamd-master.tar.gz
rspamd-master.zip
Merge pull request #5483 from moisseev/selector-file-uploadHEADmaster
[WebUI] Add file upload to `Test selectors`
-rw-r--r--interface/css/rspamd.css9
-rw-r--r--interface/index.html6
-rw-r--r--interface/js/app/common.js58
-rw-r--r--interface/js/app/selectors.js10
-rw-r--r--interface/js/app/upload.js70
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;
});