diff options
-rw-r--r-- | interface/css/rspamd.css | 2 | ||||
-rw-r--r-- | interface/index.html | 8 | ||||
-rw-r--r-- | interface/js/app/libft.js | 7 | ||||
-rw-r--r-- | 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 @@ -63,6 +63,11 @@ define(["jquery", "app/common", "footable"], whiteSpace: "normal" } }, { + name: "file", + title: "File name", + breakpoints: "xs", + sortValue: (val) => ((typeof val === "undefined") ? "" : val) + }, { name: "ip", title: "IP address", breakpoints: "xs sm md", @@ -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; }); |