commit 62b136a: [WebUI] Add file input and drop area to scan tab

moisseev moiseev at mezonplus.ru
Mon Jul 29 17:53:15 UTC 2024


Author: moisseev
Date: 2024-03-01 17:33:07 +0300
URL: https://github.com/rspamd/rspamd/commit/62b136a336e589efadc513418641ff526f5bb996 (refs/pull/4849/head)

[WebUI] Add file input and drop area to scan tab

---
 interface/css/rspamd.css   |  2 +
 interface/index.html       |  8 +++-
 interface/js/app/libft.js  |  7 +++-
 interface/js/app/upload.js | 95 ++++++++++++++++++++++++++++++++++++++--------
 4 files changed, 94 insertions(+), 18 deletions(-)

diff --git a/interface/css/rspamd.css b/interface/css/rspamd.css
index 566e1db71..2959e6b1d 100644
--- a/interface/css/rspamd.css
+++ b/interface/css/rspamd.css
@@ -394,6 +394,8 @@ table#symbolsTable input[type="number"] {
     text-align: center;
 }
 
+.outline-dashed-primary { outline: 2px dashed var(--bs-primary); }
+
 .scorebar-spam {
     background-color: rgba(240 0 0 / 0.1) !important;
 }
diff --git a/interface/index.html b/interface/index.html
index 2607348bd..86fdbdb31 100644
--- a/interface/index.html
+++ b/interface/index.html
@@ -378,16 +378,20 @@
 
 			<div class="tab-pane" id="scan">
 				<div class="card bg-light shadow my-3">
-					<div class="card-header text-secondary py-2">
+					<div class="card-header text-secondary py-1 d-flex align-items-center">
 						<span class="icon me-3"><i class="fas fa-envelope"></i></span>
 						<span class="h6 fw-bolder my-2">Scan suspected message</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="formFile" type="file">
+						</div>
 					</div>
 					<div class="card-body">
 						<div class="row">
 							<form class="col-lg-12" id="scanForm">
 								<div class="mb-0">
 									<label class="form-label" for="scanMsgSource">Message source:</label>
-									<textarea class="form-control" id="scanMsgSource" rows="10" placeholder="Paste raw message source"></textarea>
+									<textarea class="form-control" id="scanMsgSource" rows="10" placeholder='Paste raw message source, drag and drop files here or use "Browse..." button.'></textarea>
 								</div>
 								<div class="collapse row mt-3" id="scanOptions">
 									<div class="col-lg-6">
diff --git a/interface/js/app/libft.js b/interface/js/app/libft.js
index 1e9cbf9a1..b8febbc1c 100644
--- a/interface/js/app/libft.js
+++ b/interface/js/app/libft.js
@@ -62,6 +62,11 @@ define(["jquery", "app/common", "footable"],
                     wordBreak: "break-all",
                     whiteSpace: "normal"
                 }
+            }, {
+                name: "file",
+                title: "File name",
+                breakpoints: "xs",
+                sortValue: (val) => ((typeof val === "undefined") ? "" : val)
             }, {
                 name: "ip",
                 title: "IP address",
@@ -171,7 +176,7 @@ define(["jquery", "app/common", "footable"],
             }].filter((col) => {
                 switch (table) {
                     case "history":
-                        return true;
+                        return (col.name !== "file");
                     case "scan":
                         return ["ip", "sender_mime", "rcpt_mime_short", "rcpt_mime", "subject", "size", "user"]
                             .every((name) => col.name !== name);
diff --git a/interface/js/app/upload.js b/interface/js/app/upload.js
index a27dc8a88..c464ef30f 100644
--- a/interface/js/app/upload.js
+++ b/interface/js/app/upload.js
@@ -28,6 +28,9 @@ define(["jquery", "app/common", "app/libft"],
     ($, common, libft) => {
         "use strict";
         const ui = {};
+        let files = null;
+        let filesIdx = null;
+        let scanTextHeaders = {};
 
         function cleanTextUpload(source) {
             $("#" + source + "TextSource").val("");
@@ -77,7 +80,19 @@ define(["jquery", "app/common", "app/libft"],
                 .prop("disabled", (disable || $.trim($("textarea").val()).length === 0));
         }
 
-        function scanText(data, headers) {
+        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);
+        }
+
+        function scanText(data) {
             enable_disable_scan_btn(true);
             common.query("checkv2", {
                 data: data,
@@ -85,7 +100,7 @@ define(["jquery", "app/common", "app/libft"],
                     processData: false,
                 },
                 method: "POST",
-                headers: headers,
+                headers: scanTextHeaders,
                 success: function (neighbours_status) {
                     const json = neighbours_status[0].data;
                     if (json.action) {
@@ -95,17 +110,29 @@ 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 (Object.prototype.hasOwnProperty.call(common.tables, "scan")) {
                             common.tables.scan.rows.load(items, true);
                         } else {
                             require(["footable"], () => {
                                 libft.initHistoryTable(data, items, "scan", libft.columns_v2("scan"), true,
                                     () => {
-                                        enable_disable_scan_btn();
-                                        $("#cleanScanHistory").removeAttr("disabled");
-                                        $("html, body").animate({
-                                            scrollTop: $("#scanResult").offset().top
-                                        }, 1000);
+                                        if (files && filesIdx < files.length - 1) {
+                                            readFile((result) => {
+                                                if (filesIdx === files.length - 1) {
+                                                    $("#scanMsgSource").val(result);
+                                                    setFileInputFiles(filesIdx);
+                                                }
+                                                scanText(result);
+                                            }, ++filesIdx);
+                                        } else {
+                                            enable_disable_scan_btn();
+                                            $("#cleanScanHistory").removeAttr("disabled");
+                                            $("html, body").animate({
+                                                scrollTop: $("#scanResult").offset().top
+                                            }, 1000);
+                                        }
                                     });
                             });
                         }
@@ -180,6 +207,10 @@ 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", () => {
@@ -193,22 +224,26 @@ define(["jquery", "app/common", "app/libft"],
             $(this).closest(".card").slideUp();
         });
 
+        function getScanTextHeaders() {
+            scanTextHeaders = ["IP", "User", "From", "Rcpt", "Helo", "Hostname"].reduce((o, header) => {
+                const value = $("#scan-opt-" + header.toLowerCase()).val();
+                if (value !== "") o[header] = value;
+                return o;
+            }, {});
+            if ($("#scan-opt-pass-all").prop("checked")) scanTextHeaders.Pass = "all";
+        }
+
         $("[data-upload]").on("click", function () {
             const source = $(this).data("upload");
             const data = $("#scanMsgSource").val();
-            let headers = {};
             if ($.trim(data).length > 0) {
                 if (source === "scan") {
-                    headers = ["IP", "User", "From", "Rcpt", "Helo", "Hostname"].reduce((o, header) => {
-                        const value = $("#scan-opt-" + header.toLowerCase()).val();
-                        if (value !== "") o[header] = value;
-                        return o;
-                    }, {});
-                    if ($("#scan-opt-pass-all").prop("checked")) headers.Pass = "all";
-                    scanText(data, headers);
+                    getScanTextHeaders();
+                    scanText(data);
                 } else if (source === "compute-fuzzy") {
                     getFuzzyHashes(data);
                 } else {
+                    let headers = {};
                     if (source === "fuzzy") {
                         headers = {
                             flag: $("#fuzzyFlagText").val(),
@@ -223,5 +258,35 @@ define(["jquery", "app/common", "app/libft"],
             return false;
         });
 
+        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) => {
+                ({files} = e.originalEvent.dataTransfer);
+                filesIdx = 0;
+
+                if (files.length === 1) {
+                    setFileInputFiles(0);
+                    enable_disable_scan_btn();
+                    readFile((result) => {
+                        $("#scanMsgSource").val(result);
+                        enable_disable_scan_btn();
+                    });
+                // eslint-disable-next-line no-alert
+                } else if (files.length < 10 || confirm("Are you sure you want to scan " + files.length + " files?")) {
+                    getScanTextHeaders();
+                    readFile((result) => scanText(result));
+                }
+            });
+
         return ui;
     });


More information about the Commits mailing list