" +
41 | "
" +
42 | "
" + alertText + " ");
43 | $(".notification-area").append(a);
44 |
45 | setTimeout(() => {
46 | $(a).fadeTo(500, 0).slideUp(500, function () {
47 | $(this).alert("close");
48 | });
49 | }, 5000);
50 | }
51 |
52 | function queryServer(neighbours_status, ind, req_url, o) {
53 | neighbours_status[ind].checked = false;
54 | neighbours_status[ind].data = {};
55 | neighbours_status[ind].status = false;
56 | const req_params = {
57 | jsonp: false,
58 | data: o.data,
59 | headers: $.extend({Password: getPassword()}, o.headers),
60 | url: neighbours_status[ind].url + req_url,
61 | xhr: function () {
62 | const xhr = $.ajaxSettings.xhr();
63 | // Download progress
64 | if (req_url !== "neighbours") {
65 | xhr.addEventListener("progress", (e) => {
66 | if (e.lengthComputable) {
67 | neighbours_status[ind].percentComplete = e.loaded / e.total;
68 | const percentComplete = neighbours_status
69 | .reduce((prev, curr) => (curr.percentComplete ? curr.percentComplete + prev : prev), 0);
70 | NProgress.set(percentComplete / neighbours_status.length);
71 | }
72 | }, false);
73 | }
74 | return xhr;
75 | },
76 | success: function (json) {
77 | neighbours_status[ind].checked = true;
78 | neighbours_status[ind].status = true;
79 | neighbours_status[ind].data = json;
80 | },
81 | error: function (jqXHR, textStatus, errorThrown) {
82 | neighbours_status[ind].checked = true;
83 | function errorMessage() {
84 | alertMessage("alert-error", neighbours_status[ind].name + " > " +
85 | (o.errorMessage ? o.errorMessage : "Request failed") +
86 | (errorThrown ? ": " + errorThrown : ""));
87 | }
88 | if (o.error) {
89 | o.error(neighbours_status[ind],
90 | jqXHR, textStatus, errorThrown);
91 | } else if (o.errorOnceId) {
92 | const alert_status = o.errorOnceId + neighbours_status[ind].name;
93 | if (!(alert_status in sessionStorage)) {
94 | sessionStorage.setItem(alert_status, true);
95 | errorMessage();
96 | }
97 | } else {
98 | errorMessage();
99 | }
100 | },
101 | complete: function (jqXHR) {
102 | if (neighbours_status.every((elt) => elt.checked)) {
103 | if (neighbours_status.some((elt) => elt.status)) {
104 | if (o.success) {
105 | o.success(neighbours_status, jqXHR);
106 | } else {
107 | alertMessage("alert-success", "Request completed");
108 | }
109 | } else {
110 | alertMessage("alert-error", "Request failed");
111 | }
112 | if (o.complete) o.complete();
113 | NProgress.done();
114 | }
115 | },
116 | statusCode: o.statusCode
117 | };
118 | if (o.method) {
119 | req_params.method = o.method;
120 | }
121 | if (o.params) {
122 | $.each(o.params, (k, v) => {
123 | req_params[k] = v;
124 | });
125 | }
126 | $.ajax(req_params);
127 | }
128 |
129 |
130 | // Public functions
131 |
132 | ui.alertMessage = alertMessage;
133 | ui.getPassword = getPassword;
134 |
135 | // Get selectors' current state
136 | ui.getSelector = function (id) {
137 | const e = document.getElementById(id);
138 | return e.options[e.selectedIndex].value;
139 | };
140 |
141 | ui.getServer = function () {
142 | const checked_server = ui.getSelector("selSrv");
143 | return (checked_server === "All SERVERS") ? "local" : checked_server;
144 | };
145 |
146 | /**
147 | * @param {string} url - A string containing the URL to which the request is sent
148 | * @param {Object} [options] - A set of key/value pairs that configure the Ajax request. All settings are optional.
149 | *
150 | * @param {Function} [options.complete] - A function to be called when the requests to all neighbours complete.
151 | * @param {Object|string|Array} [options.data] - Data to be sent to the server.
152 | * @param {Function} [options.error] - A function to be called if the request fails.
153 | * @param {string} [options.errorMessage] - Text to display in the alert message if the request fails.
154 | * @param {string} [options.errorOnceId] - A prefix of the alert ID to be added to the session storage. If the
155 | * parameter is set, the error for each server will be displayed only once per session.
156 | * @param {Object} [options.headers] - An object of additional header key/value pairs to send along with requests
157 | * using the XMLHttpRequest transport.
158 | * @param {string} [options.method] - The HTTP method to use for the request.
159 | * @param {Object} [options.params] - An object of additional jQuery.ajax() settings key/value pairs.
160 | * @param {string} [options.server] - A server to which send the request.
161 | * @param {Function} [options.success] - A function to be called if the request succeeds.
162 | *
163 | * @returns {undefined}
164 | */
165 | ui.query = function (url, options) {
166 | // Force options to be an object
167 | const o = options || {};
168 | Object.keys(o).forEach((option) => {
169 | if (["complete", "data", "error", "errorMessage", "errorOnceId", "headers", "method", "params", "server",
170 | "statusCode", "success"]
171 | .indexOf(option) < 0) {
172 | throw new Error("Unknown option: " + option);
173 | }
174 | });
175 |
176 | let neighbours_status = [{
177 | name: "local",
178 | host: "local",
179 | url: "",
180 | }];
181 | o.server = o.server || ui.getSelector("selSrv");
182 | if (o.server === "All SERVERS") {
183 | queryServer(neighbours_status, 0, "neighbours", {
184 | success: function (json) {
185 | const [{data}] = json;
186 | if (jQuery.isEmptyObject(data)) {
187 | ui.neighbours = {
188 | local: {
189 | host: window.location.host,
190 | url: window.location.origin + window.location.pathname
191 | }
192 | };
193 | } else {
194 | ui.neighbours = data;
195 | }
196 | neighbours_status = [];
197 | $.each(ui.neighbours, (ind) => {
198 | neighbours_status.push({
199 | name: ind,
200 | host: ui.neighbours[ind].host,
201 | url: ui.neighbours[ind].url,
202 | });
203 | });
204 | $.each(neighbours_status, (ind) => {
205 | queryServer(neighbours_status, ind, url, o);
206 | });
207 | },
208 | errorMessage: "Cannot receive neighbours data"
209 | });
210 | } else {
211 | if (o.server !== "local") {
212 | neighbours_status = [{
213 | name: o.server,
214 | host: ui.neighbours[o.server].host,
215 | url: ui.neighbours[o.server].url,
216 | }];
217 | }
218 | queryServer(neighbours_status, 0, url, o);
219 | }
220 | };
221 |
222 | ui.escapeHTML = function (string) {
223 | const htmlEscaper = /[&<>"'/`=]/g;
224 | const htmlEscapes = {
225 | "&": "&",
226 | "<": "<",
227 | ">": ">",
228 | "\"": """,
229 | "'": "'",
230 | "/": "/",
231 | "`": "`",
232 | "=": "="
233 | };
234 | return String(string).replace(htmlEscaper, (match) => htmlEscapes[match]);
235 | };
236 |
237 | return ui;
238 | });
239 |
--------------------------------------------------------------------------------
/www/js/app/config.js:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (C) 2017 Vsevolod Stakhov
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 | */
24 |
25 | /* global require */
26 |
27 | define(["jquery", "app/common"],
28 | ($, common) => {
29 | "use strict";
30 | const ui = {};
31 |
32 | ui.getActions = function getActions() {
33 | common.query("actions", {
34 | success: function (data) {
35 | $("#actionsFormField").empty();
36 | const items = [];
37 | $.each(data[0].data, (i, item) => {
38 | const actionsOrder = ["greylist", "add header", "rewrite subject", "reject"];
39 | const idx = actionsOrder.indexOf(item.action);
40 | if (idx >= 0) {
41 | items.push({
42 | idx: idx,
43 | html:
44 | '"
51 | });
52 | }
53 | });
54 |
55 | items.sort((a, b) => a.idx - b.idx);
56 |
57 | $("#actionsFormField").html(
58 | items.map((e) => e.html).join(""));
59 | },
60 | server: common.getServer()
61 | });
62 | };
63 |
64 | ui.saveActions = function (server) {
65 | function descending(arr) {
66 | let desc = true;
67 | const filtered = arr.filter((el) => el !== null);
68 | for (let i = 0; i < filtered.length - 1; i++) {
69 | if (filtered[i + 1] >= filtered[i]) {
70 | desc = false;
71 | break;
72 | }
73 | }
74 | return desc;
75 | }
76 |
77 | const elts = (function () {
78 | const values = [];
79 | const inputs = $("#actionsForm :input[data-id=\"action\"]");
80 | // Rspamd order: [spam, rewrite_subject, probable_spam, greylist]
81 | values[0] = parseFloat(inputs[3].value);
82 | values[1] = parseFloat(inputs[2].value);
83 | values[2] = parseFloat(inputs[1].value);
84 | values[3] = parseFloat(inputs[0].value);
85 |
86 | return JSON.stringify(values);
87 | }());
88 | // String to array for comparison
89 | const eltsArray = JSON.parse(elts);
90 | if (eltsArray[0] < 0) {
91 | common.alertMessage("alert-modal alert-error", "Spam can not be negative");
92 | } else if (eltsArray[1] < 0) {
93 | common.alertMessage("alert-modal alert-error", "Rewrite subject can not be negative");
94 | } else if (eltsArray[2] < 0) {
95 | common.alertMessage("alert-modal alert-error", "Probable spam can not be negative");
96 | } else if (eltsArray[3] < 0) {
97 | common.alertMessage("alert-modal alert-error", "Greylist can not be negative");
98 | } else if (descending(eltsArray)) {
99 | common.query("saveactions", {
100 | method: "POST",
101 | params: {
102 | data: elts,
103 | dataType: "json"
104 | },
105 | server: server
106 | });
107 | } else {
108 | common.alertMessage("alert-modal alert-error", "Incorrect order of actions thresholds");
109 | }
110 | };
111 |
112 | ui.getMaps = function () {
113 | const $listmaps = $("#listMaps");
114 | $listmaps.closest(".card").hide();
115 | common.query("maps", {
116 | success: function (json) {
117 | const [{data}] = json;
118 | $listmaps.empty();
119 | $("#modalBody").empty();
120 | const $tbody = $("");
121 |
122 | $.each(data, (i, item) => {
123 | let $td = 'Read ';
124 | if (!(item.editable === false || common.read_only)) {
125 | $td = $($td).append(' Write ');
126 | }
127 | const $tr = $("").append($td);
128 |
129 | const $span = $('' +
130 | item.uri + " ").data("item", item);
131 | $span.wrap("").parent().appendTo($tr);
132 | $(" " + item.description + " ").appendTo($tr);
133 | $tr.appendTo($tbody);
134 | });
135 | $tbody.appendTo($listmaps);
136 | $listmaps.closest(".card").show();
137 | },
138 | server: common.getServer()
139 | });
140 | };
141 |
142 |
143 | let jar = {};
144 | const editor = {
145 | advanced: {
146 | codejar: true,
147 | elt: "div",
148 | class: "editor language-clike",
149 | readonly_attr: {contenteditable: false},
150 | },
151 | basic: {
152 | elt: "textarea",
153 | class: "form-control map-textarea",
154 | readonly_attr: {readonly: true},
155 | }
156 | };
157 | let mode = "advanced";
158 |
159 | // Modal form for maps
160 | $(document).on("click", "[data-bs-toggle=\"modal\"]", function () {
161 | const item = $(this).data("item");
162 | common.query("getmap", {
163 | headers: {
164 | Map: item.map
165 | },
166 | success: function (data) {
167 | // Highlighting a large amount of text is unresponsive
168 | mode = (new Blob([data[0].data]).size > 5120) ? "basic" : $("input[name=editorMode]:checked").val();
169 |
170 | $("<" + editor[mode].elt + ' id="editor" class="' + editor[mode].class + '" data-id="' + item.map +
171 | '">' + editor[mode].elt + ">").appendTo("#modalBody");
172 |
173 | if (editor[mode].codejar) {
174 | require(["codejar", "linenumbers", "prism"], (CodeJar, withLineNumbers, Prism) => {
175 | jar = new CodeJar(
176 | document.querySelector("#editor"),
177 | withLineNumbers((el) => Prism.highlightElement(el))
178 | );
179 | jar.updateCode(data[0].data);
180 | });
181 | } else {
182 | document.querySelector("#editor").innerHTML = common.escapeHTML(data[0].data);
183 | }
184 |
185 | let icon = "fa-edit";
186 | if (item.editable === false || common.read_only) {
187 | $("#editor").attr(editor[mode].readonly_attr);
188 | icon = "fa-eye";
189 | $("#modalSaveGroup").hide();
190 | } else {
191 | $("#modalSaveGroup").show();
192 | }
193 | $("#modalDialog .modal-header").find("[data-fa-i2svg]").addClass(icon);
194 | $("#modalTitle").html(item.uri);
195 |
196 | $("#modalDialog").modal("show");
197 | },
198 | errorMessage: "Cannot receive maps data",
199 | server: common.getServer()
200 | });
201 | return false;
202 | });
203 | $("#modalDialog").on("hidden.bs.modal", () => {
204 | if (editor[mode].codejar) {
205 | jar.destroy();
206 | $(".codejar-wrap").remove();
207 | } else {
208 | $("#editor").remove();
209 | }
210 | });
211 |
212 | $("#saveActionsBtn").on("click", () => {
213 | ui.saveActions();
214 | });
215 | $("#saveActionsClusterBtn").on("click", () => {
216 | ui.saveActions("All SERVERS");
217 | });
218 |
219 | function saveMap(server) {
220 | common.query("savemap", {
221 | success: function () {
222 | common.alertMessage("alert-success", "Map data successfully saved");
223 | $("#modalDialog").modal("hide");
224 | },
225 | errorMessage: "Save map error",
226 | method: "POST",
227 | headers: {
228 | Map: $("#editor").data("id"),
229 | },
230 | params: {
231 | data: editor[mode].codejar ? jar.toString() : $("#editor").val(),
232 | dataType: "text",
233 | },
234 | server: server
235 | });
236 | }
237 | $("#modalSave").on("click", () => {
238 | saveMap();
239 | });
240 | $("#modalSaveAll").on("click", () => {
241 | saveMap("All SERVERS");
242 | });
243 |
244 | return ui;
245 | });
246 |
--------------------------------------------------------------------------------
/www/js/app/graph.js:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (C) 2017 Vsevolod Stakhov
5 | Copyright (C) 2017 Alexander Moisseev
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in
15 | all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | THE SOFTWARE.
24 | */
25 |
26 | /* global FooTable */
27 |
28 | define(["jquery", "app/common", "d3evolution", "d3pie", "d3", "footable"],
29 | ($, common, D3Evolution, D3Pie, d3) => {
30 | "use strict";
31 |
32 | const rrd_pie_config = {
33 | cornerRadius: 2,
34 | size: {
35 | canvasWidth: 400,
36 | canvasHeight: 180,
37 | pieInnerRadius: "50%",
38 | pieOuterRadius: "80%"
39 | },
40 | labels: {
41 | outer: {
42 | format: "none"
43 | },
44 | inner: {
45 | hideWhenLessThanPercentage: 8,
46 | offset: 0
47 | },
48 | },
49 | padAngle: 0.02,
50 | pieCenterOffset: {
51 | x: -120,
52 | y: 10,
53 | },
54 | total: {
55 | enabled: true
56 | },
57 | };
58 |
59 | const ui = {};
60 | let prevUnit = "msg/s";
61 |
62 | ui.draw = function (graphs, neighbours, checked_server, type) {
63 | const graph_options = {
64 | title: "Rspamd throughput",
65 | width: 1060,
66 | height: 370,
67 | yAxisLabel: "Message rate, msg/s",
68 |
69 | legend: {
70 | space: 140,
71 | entries: common.chartLegend
72 | }
73 | };
74 |
75 | function initGraph() {
76 | const graph = new D3Evolution("graph", $.extend({}, graph_options, {
77 | yScale: common.getSelector("selYScale"),
78 | type: common.getSelector("selType"),
79 | interpolate: common.getSelector("selInterpolate"),
80 | convert: common.getSelector("selConvert"),
81 | }));
82 | $("#selYScale").change(function () {
83 | graph.yScale(this.value);
84 | });
85 | $("#selConvert").change(function () {
86 | graph.convert(this.value);
87 | });
88 | $("#selType").change(function () {
89 | graph.type(this.value);
90 | });
91 | $("#selInterpolate").change(function () {
92 | graph.interpolate(this.value);
93 | });
94 |
95 | return graph;
96 | }
97 |
98 | function getRrdSummary(json, scaleFactor) {
99 | const xExtents = d3.extent(d3.merge(json), (d) => d.x);
100 | const timeInterval = xExtents[1] - xExtents[0];
101 |
102 | let total = 0;
103 | const rows = json.map((curr, i) => {
104 | // Time intervals that don't have data are excluded from average calculation as d3.mean()ignores nulls
105 | const avg = d3.mean(curr, (d) => d.y);
106 | // To find an integral on the whole time interval we need to convert nulls to zeroes
107 | // eslint-disable-next-line no-bitwise
108 | const value = d3.mean(curr, (d) => Number(d.y)) * timeInterval / scaleFactor ^ 0;
109 | const yExtents = d3.extent(curr, (d) => d.y);
110 |
111 | total += value;
112 | return {
113 | label: graph_options.legend.entries[i].label,
114 | value: value,
115 | min: Number(yExtents[0].toFixed(6)),
116 | avg: Number(avg.toFixed(6)),
117 | max: Number(yExtents[1].toFixed(6)),
118 | last: Number(curr[curr.length - 1].y.toFixed(6)),
119 | color: graph_options.legend.entries[i].color,
120 | };
121 | }, []);
122 |
123 | return {
124 | rows: rows,
125 | total: total
126 | };
127 | }
128 |
129 | function initSummaryTable(rows, unit) {
130 | common.tables.rrd_summary = FooTable.init("#rrd-table", {
131 | sorting: {
132 | enabled: true
133 | },
134 | columns: [
135 | {name: "label", title: "Action"},
136 | {name: "value", title: "Messages", defaultContent: ""},
137 | {name: "min", title: "Minimum, " + unit + " ", defaultContent: ""},
138 | {name: "avg", title: "Average, " + unit + " ", defaultContent: ""},
139 | {name: "max", title: "Maximum, " + unit + " ", defaultContent: ""},
140 | {name: "last", title: "Last, " + unit},
141 | ],
142 | rows: rows.map((curr, i) => ({
143 | options: {
144 | style: {
145 | color: graph_options.legend.entries[i].color
146 | }
147 | },
148 | value: curr
149 | }), [])
150 | });
151 | }
152 |
153 | function drawRrdTable(rows, unit) {
154 | if (Object.prototype.hasOwnProperty.call(common.tables, "rrd_summary")) {
155 | $.each(common.tables.rrd_summary.rows.all, (i, row) => {
156 | row.val(rows[i], false, true);
157 | });
158 | } else {
159 | initSummaryTable(rows, unit);
160 | }
161 | }
162 |
163 | function updateWidgets(data) {
164 | let rrd_summary = {rows: []};
165 | let unit = "msg/s";
166 |
167 | if (data) {
168 | // Autoranging
169 | let scaleFactor = 1;
170 | const yMax = d3.max(d3.merge(data), (d) => d.y);
171 | if (yMax < 1) {
172 | scaleFactor = 60;
173 | unit = "msg/min";
174 | data.forEach((s) => {
175 | s.forEach((d) => {
176 | if (d.y !== null) { d.y *= scaleFactor; }
177 | });
178 | });
179 | }
180 |
181 | rrd_summary = getRrdSummary(data, scaleFactor);
182 | }
183 |
184 | if (!graphs.rrd_pie) graphs.rrd_pie = new D3Pie("rrd-pie", rrd_pie_config);
185 | graphs.rrd_pie.data(rrd_summary.rows);
186 |
187 | graphs.graph.data(data);
188 | if (unit !== prevUnit) {
189 | graphs.graph.yAxisLabel("Message rate, " + unit);
190 | $(".unit").text(unit);
191 | prevUnit = unit;
192 | }
193 | drawRrdTable(rrd_summary.rows, unit);
194 | document.getElementById("rrd-total-value").innerHTML = rrd_summary.total;
195 | }
196 |
197 | if (!graphs.graph) {
198 | graphs.graph = initGraph();
199 | }
200 |
201 |
202 | common.query("graph", {
203 | success: function (req_data) {
204 | let data = null;
205 | const neighbours_data = req_data
206 | .filter((d) => d.status) // filter out unavailable neighbours
207 | .map((d) => d.data);
208 |
209 | if (neighbours_data.length === 1) {
210 | [data] = neighbours_data;
211 | } else {
212 | let time_match = true;
213 | neighbours_data.reduce((res, curr, _, arr) => {
214 | if ((curr[0][0].x !== res[0][0].x) ||
215 | (curr[0][curr[0].length - 1].x !== res[0][res[0].length - 1].x)) {
216 | time_match = false;
217 | common.alertMessage("alert-error",
218 | "Neighbours time extents do not match. Check if time is synchronized on all servers.");
219 | arr.splice(1); // Break out of .reduce() by mutating the source array
220 | }
221 | return curr;
222 | });
223 |
224 | if (time_match) {
225 | data = neighbours_data.reduce((res, curr) => curr.map((action, j) => action.map((d, i) => ({
226 | x: d.x,
227 | y: (res[j][i].y === null) ? d.y : res[j][i].y + d.y
228 | }))));
229 | }
230 | }
231 | updateWidgets(data);
232 | },
233 | complete: function () { $("#refresh").removeAttr("disabled").removeClass("disabled"); },
234 | errorMessage: "Cannot receive throughput data",
235 | errorOnceId: "alerted_graph_",
236 | data: {type: type}
237 | });
238 | };
239 |
240 |
241 | // Handling mouse events on overlapping elements
242 | $("#rrd-pie").mouseover(() => {
243 | $("#rrd-pie,#rrd-pie-tooltip").css("z-index", "200");
244 | $("#rrd-table_toggle").css("z-index", "300");
245 | });
246 | $("#rrd-table_toggle").mouseover(() => {
247 | $("#rrd-pie,#rrd-pie-tooltip").css("z-index", "0");
248 | $("#rrd-table_toggle").css("z-index", "0");
249 | });
250 |
251 | return ui;
252 | });
253 |
--------------------------------------------------------------------------------
/www/js/app/history.js:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (C) 2017 Vsevolod Stakhov
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 | */
24 |
25 | /* global FooTable */
26 |
27 | define(["jquery", "app/common", "app/libft", "footable"],
28 | ($, common, libft) => {
29 | "use strict";
30 | const ui = {};
31 | let prevVersion = null;
32 |
33 | function process_history_legacy(data) {
34 | const items = [];
35 |
36 | function compare(e1, e2) { return e1.name.localeCompare(e2.name); }
37 |
38 | $("#selSymOrder_history, label[for='selSymOrder_history']").hide();
39 |
40 | $.each(data, (i, item) => {
41 | item.time = libft.unix_time_format(item.unix_time);
42 | libft.preprocess_item(item);
43 | item.symbols = Object.keys(item.symbols)
44 | .map((key) => item.symbols[key])
45 | .sort(compare)
46 | .map((e) => e.name)
47 | .join(", ");
48 | item.time = {
49 | value: libft.unix_time_format(item.unix_time),
50 | options: {
51 | sortValue: item.unix_time
52 | }
53 | };
54 |
55 | items.push(item);
56 | });
57 |
58 | return {items: items};
59 | }
60 |
61 | function columns_legacy() {
62 | return [{
63 | name: "id",
64 | title: "ID",
65 | style: {
66 | width: 300,
67 | maxWidth: 300,
68 | overflow: "hidden",
69 | textOverflow: "ellipsis",
70 | wordBreak: "keep-all",
71 | whiteSpace: "nowrap"
72 | }
73 | }, {
74 | name: "ip",
75 | title: "IP address",
76 | breakpoints: "xs sm",
77 | style: {width: 150, maxWidth: 150}
78 | }, {
79 | name: "action",
80 | title: "Action",
81 | style: {width: 110, maxWidth: 110}
82 | }, {
83 | name: "score",
84 | title: "Score",
85 | style: {maxWidth: 110},
86 | sortValue: function (val) { return Number(val.options.sortValue); }
87 | }, {
88 | name: "symbols",
89 | title: "Symbols",
90 | breakpoints: "all",
91 | style: {width: 550, maxWidth: 550}
92 | }, {
93 | name: "size",
94 | title: "Message size",
95 | breakpoints: "xs sm",
96 | style: {width: 120, maxWidth: 120},
97 | formatter: libft.formatBytesIEC
98 | }, {
99 | name: "scan_time",
100 | title: "Scan time",
101 | breakpoints: "xs sm",
102 | style: {maxWidth: 80},
103 | sortValue: function (val) { return Number(val); }
104 | }, {
105 | sorted: true,
106 | direction: "DESC",
107 | name: "time",
108 | title: "Time",
109 | sortValue: function (val) { return Number(val.options.sortValue); }
110 | }, {
111 | name: "user",
112 | title: "Authenticated user",
113 | breakpoints: "xs sm",
114 | style: {width: 200, maxWidth: 200}
115 | }];
116 | }
117 |
118 | const columns = {
119 | 2: libft.columns_v2("history"),
120 | legacy: columns_legacy()
121 | };
122 |
123 | function process_history_data(data) {
124 | const process_functions = {
125 | 2: libft.process_history_v2,
126 | legacy: process_history_legacy
127 | };
128 | let pf = process_functions.legacy;
129 |
130 | if (data.version) {
131 | const strkey = data.version.toString();
132 | if (process_functions[strkey]) {
133 | pf = process_functions[strkey];
134 | }
135 | }
136 |
137 | return pf(data, "history");
138 | }
139 |
140 | function get_history_columns(data) {
141 | let func = columns.legacy;
142 |
143 | if (data.version) {
144 | const strkey = data.version.toString();
145 | if (columns[strkey]) {
146 | func = columns[strkey];
147 | }
148 | }
149 |
150 | return func;
151 | }
152 |
153 | ui.getHistory = function () {
154 | $("#refresh, #updateHistory").attr("disabled", true);
155 | common.query("history", {
156 | success: function (req_data) {
157 | function differentVersions(neighbours_data) {
158 | const dv = neighbours_data.some((e) => e.version !== neighbours_data[0].version);
159 | if (dv) {
160 | common.alertMessage("alert-error",
161 | "Neighbours history backend versions do not match. Cannot display history.");
162 | return true;
163 | }
164 | return false;
165 | }
166 |
167 | const neighbours_data = req_data
168 | .filter((d) => d.status) // filter out unavailable neighbours
169 | .map((d) => d.data);
170 | if (neighbours_data.length && !differentVersions(neighbours_data)) {
171 | let data = {};
172 | const [{version}] = neighbours_data;
173 | if (version) {
174 | data.rows = [].concat.apply([], neighbours_data
175 | .map((e) => e.rows));
176 | data.version = version;
177 | $("#legacy-history-badge").hide();
178 | } else {
179 | // Legacy version
180 | data = [].concat.apply([], neighbours_data);
181 | $("#legacy-history-badge").show();
182 | }
183 | const o = process_history_data(data);
184 | const {items} = o;
185 | common.symbols.history = o.symbols;
186 |
187 | if (Object.prototype.hasOwnProperty.call(common.tables, "history") &&
188 | version === prevVersion) {
189 | common.tables.history.rows.load(items);
190 | } else {
191 | libft.destroyTable("history");
192 | // Is there a way to get an event when the table is destroyed?
193 | setTimeout(() => {
194 | libft.initHistoryTable(data, items, "history", get_history_columns(data), false,
195 | () => $("#refresh, #updateHistory").removeAttr("disabled"));
196 | }, 200);
197 | }
198 | prevVersion = version;
199 | } else {
200 | libft.destroyTable("history");
201 | }
202 | },
203 | error: () => $("#refresh, #updateHistory").removeAttr("disabled"),
204 | errorMessage: "Cannot receive history",
205 | });
206 | };
207 |
208 | function initErrorsTable(rows) {
209 | common.tables.errors = FooTable.init("#errorsLog", {
210 | columns: [
211 | {sorted: true,
212 | direction: "DESC",
213 | name: "ts",
214 | title: "Time",
215 | style: {width: 300, maxWidth: 300},
216 | sortValue: function (val) { return Number(val.options.sortValue); }},
217 | {name: "type",
218 | title: "Worker type",
219 | breakpoints: "xs sm",
220 | style: {width: 150, maxWidth: 150}},
221 | {name: "pid",
222 | title: "PID",
223 | breakpoints: "xs sm",
224 | style: {width: 110, maxWidth: 110}},
225 | {name: "module", title: "Module"},
226 | {name: "id", title: "Internal ID"},
227 | {name: "message", title: "Message", breakpoints: "xs sm"},
228 | ],
229 | rows: rows,
230 | paging: {
231 | enabled: true,
232 | limit: 5,
233 | size: common.page_size.errors
234 | },
235 | filtering: {
236 | enabled: true,
237 | position: "left",
238 | connectors: false
239 | },
240 | sorting: {
241 | enabled: true
242 | }
243 | });
244 | }
245 |
246 | ui.getErrors = function () {
247 | if (common.read_only) return;
248 |
249 | common.query("errors", {
250 | success: function (data) {
251 | const neighbours_data = data
252 | .filter((d) => d.status) // filter out unavailable neighbours
253 | .map((d) => d.data);
254 | const rows = [].concat.apply([], neighbours_data);
255 | $.each(rows, (i, item) => {
256 | item.ts = {
257 | value: libft.unix_time_format(item.ts),
258 | options: {
259 | sortValue: item.ts
260 | }
261 | };
262 | });
263 | if (Object.prototype.hasOwnProperty.call(common.tables, "errors")) {
264 | common.tables.errors.rows.load(rows);
265 | } else {
266 | initErrorsTable(rows);
267 | }
268 | }
269 | });
270 |
271 | $("#updateErrors").off("click");
272 | $("#updateErrors").on("click", (e) => {
273 | e.preventDefault();
274 | ui.getErrors();
275 | });
276 | };
277 |
278 |
279 | libft.set_page_size("history", $("#history_page_size").val());
280 | libft.bindHistoryTableEventHandlers("history", 8);
281 |
282 | $("#updateHistory").off("click");
283 | $("#updateHistory").on("click", (e) => {
284 | e.preventDefault();
285 | ui.getHistory();
286 | });
287 |
288 | // @reset history log
289 | $("#resetHistory").off("click");
290 | $("#resetHistory").on("click", (e) => {
291 | e.preventDefault();
292 | if (!confirm("Are you sure you want to reset history log?")) { // eslint-disable-line no-alert
293 | return;
294 | }
295 | libft.destroyTable("history");
296 | libft.destroyTable("errors");
297 |
298 | common.query("historyreset", {
299 | success: function () {
300 | ui.getHistory();
301 | ui.getErrors();
302 | },
303 | errorMessage: "Cannot reset history log"
304 | });
305 | });
306 |
307 | return ui;
308 | });
309 |
--------------------------------------------------------------------------------
/www/js/app/selectors.js:
--------------------------------------------------------------------------------
1 | define(["jquery", "app/common"],
2 | ($, common) => {
3 | "use strict";
4 | const ui = {};
5 |
6 | function enable_disable_check_btn() {
7 | $("#selectorsChkMsgBtn").prop("disabled", (
8 | $.trim($("#selectorsMsgArea").val()).length === 0 ||
9 | !$("#selectorsSelArea").hasClass("is-valid")
10 | ));
11 | }
12 |
13 | function checkMsg(data) {
14 | const selector = $("#selectorsSelArea").val();
15 | common.query("plugins/selectors/check_message?selector=" + encodeURIComponent(selector), {
16 | data: data,
17 | method: "POST",
18 | success: function (neighbours_status) {
19 | const json = neighbours_status[0].data;
20 | if (json.success) {
21 | common.alertMessage("alert-success", "Message successfully processed");
22 | $("#selectorsResArea")
23 | .val(Object.prototype.hasOwnProperty.call(json, "data") ? json.data.toString() : "");
24 | } else {
25 | common.alertMessage("alert-error", "Unexpected error processing message");
26 | }
27 | },
28 | server: common.getServer()
29 | });
30 | }
31 |
32 | function checkSelectors() {
33 | function toggle_form_group_class(remove, add) {
34 | $("#selectorsSelArea").removeClass("is-" + remove).addClass("is-" + add);
35 | enable_disable_check_btn();
36 | }
37 | const selector = $("#selectorsSelArea").val();
38 | if (selector.length && !common.read_only) {
39 | common.query("plugins/selectors/check_selector?selector=" + encodeURIComponent(selector), {
40 | method: "GET",
41 | success: function (json) {
42 | if (json[0].data.success) {
43 | toggle_form_group_class("invalid", "valid");
44 | } else {
45 | toggle_form_group_class("valid", "invalid");
46 | }
47 | },
48 | server: common.getServer()
49 | });
50 | } else {
51 | $("#selectorsSelArea").removeClass("is-valid is-invalid");
52 | enable_disable_check_btn();
53 | }
54 | }
55 |
56 | function buildLists() {
57 | function build_table_from_json(json, table_id) {
58 | Object.keys(json).forEach((key) => {
59 | const td = $(" ");
60 | const tr = $(" ")
61 | .append(td.clone().html("" + key + "
"))
62 | .append(td.clone().html(json[key].description));
63 | $(table_id + " tbody").append(tr);
64 | });
65 | }
66 |
67 | function getList(list) {
68 | common.query("plugins/selectors/list_" + list, {
69 | method: "GET",
70 | success: function (neighbours_status) {
71 | const json = neighbours_status[0].data;
72 | build_table_from_json(json, "#selectorsTable-" + list);
73 | },
74 | server: common.getServer()
75 | });
76 | }
77 |
78 | getList("extractors");
79 | getList("transforms");
80 | }
81 |
82 | ui.displayUI = function () {
83 | if (!common.read_only &&
84 | !$("#selectorsTable-extractors>tbody>tr").length &&
85 | !$("#selectorsTable-transforms>tbody>tr").length) buildLists();
86 | if (!$("#selectorsSelArea").is(".is-valid, .is-invalid")) checkSelectors();
87 | };
88 |
89 |
90 | function toggleSidebar(side) {
91 | $("#sidebar-" + side).toggleClass("collapsed");
92 | let contentClass = "col-lg-6";
93 | const openSidebarsCount = $("#sidebar-left").hasClass("collapsed") +
94 | $("#sidebar-right").hasClass("collapsed");
95 | switch (openSidebarsCount) {
96 | case 1:
97 | contentClass = "col-lg-9";
98 | break;
99 | case 2:
100 | contentClass = "col-lg-12";
101 | break;
102 | default:
103 | }
104 | $("#content").removeClass("col-lg-12 col-lg-9 col-lg-6")
105 | .addClass(contentClass);
106 | }
107 | $("#sidebar-tab-left>a").click(() => {
108 | toggleSidebar("left");
109 | return false;
110 | });
111 | $("#sidebar-tab-right>a").click(() => {
112 | toggleSidebar("right");
113 | return false;
114 | });
115 |
116 | $("#selectorsMsgClean").on("click", () => {
117 | $("#selectorsChkMsgBtn").attr("disabled", true);
118 | $("#selectorsMsgArea").val("");
119 | return false;
120 | });
121 | $("#selectorsClean").on("click", () => {
122 | $("#selectorsSelArea").val("");
123 | checkSelectors();
124 | return false;
125 | });
126 | $("#selectorsChkMsgBtn").on("click", () => {
127 | $("#selectorsResArea").val("");
128 | checkMsg($("#selectorsMsgArea").val());
129 | return false;
130 | });
131 |
132 | $("#selectorsMsgArea").on("input", () => {
133 | enable_disable_check_btn();
134 | });
135 | $("#selectorsSelArea").on("input", () => {
136 | checkSelectors();
137 | });
138 |
139 | return ui;
140 | });
141 |
--------------------------------------------------------------------------------
/www/js/app/symbols.js:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (C) 2017 Vsevolod Stakhov
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 | */
24 |
25 | /* global FooTable */
26 |
27 | define(["jquery", "app/common", "footable"],
28 | ($, common) => {
29 | "use strict";
30 | const ui = {};
31 | let altered = {};
32 |
33 | function clear_altered() {
34 | $("#save-alert").addClass("d-none");
35 | altered = {};
36 | }
37 |
38 | function saveSymbols(server) {
39 | $("#save-alert button").attr("disabled", true);
40 |
41 | const values = [];
42 | Object.entries(altered).forEach(([key, value]) => values.push({name: key, value: value}));
43 |
44 | common.query("./savesymbols", {
45 | success: function () {
46 | clear_altered();
47 | common.alertMessage("alert-modal alert-success", "Symbols successfully saved");
48 | },
49 | complete: () => $("#save-alert button").removeAttr("disabled"),
50 | errorMessage: "Save symbols error",
51 | method: "POST",
52 | params: {
53 | data: JSON.stringify(values),
54 | dataType: "json",
55 | },
56 | server: server
57 | });
58 | }
59 |
60 | function process_symbols_data(data) {
61 | const items = [];
62 | const lookup = {};
63 | const freqs = [];
64 | const distinct_groups = [];
65 |
66 | data.forEach((group) => {
67 | group.rules.forEach((item) => {
68 | const formatter = new Intl.NumberFormat("en", {
69 | minimumFractionDigits: 2,
70 | maximumFractionDigits: 6,
71 | useGrouping: false
72 | });
73 | item.group = group.group;
74 | let label_class = "";
75 | if (item.weight < 0) {
76 | label_class = "scorebar-ham";
77 | } else if (item.weight > 0) {
78 | label_class = "scorebar-spam";
79 | }
80 | item.weight = ' ';
83 | if (!item.time) {
84 | item.time = 0;
85 | }
86 | item.time = Number(item.time).toFixed(2) + "s";
87 | if (!item.frequency) {
88 | item.frequency = 0;
89 | }
90 | freqs.push(item.frequency);
91 | item.frequency = Number(item.frequency).toFixed(2);
92 | if (!(item.group in lookup)) {
93 | lookup[item.group] = 1;
94 | distinct_groups.push(item.group);
95 | }
96 | items.push(item);
97 | });
98 | });
99 |
100 | // For better mean calculations
101 | const avg_freq = freqs
102 | .sort((a, b) => Number(a) < Number(b))
103 | .reduce((f1, acc) => f1 + acc) / (freqs.length !== 0 ? freqs.length : 1.0);
104 | let mult = 1.0;
105 | let exp = 0.0;
106 |
107 | if (avg_freq > 0.0) {
108 | while (mult * avg_freq < 1.0) {
109 | mult *= 10;
110 | exp++;
111 | }
112 | }
113 | $.each(items, (i, item) => {
114 | item.frequency = Number(item.frequency) * mult;
115 |
116 | if (exp > 0) {
117 | item.frequency = item.frequency.toFixed(2) + "e-" + exp;
118 | } else {
119 | item.frequency = item.frequency.toFixed(2);
120 | }
121 | });
122 | return [items, distinct_groups];
123 | }
124 | // @get symbols into modal form
125 | ui.getSymbols = function () {
126 | $("#refresh, #updateSymbols").attr("disabled", true);
127 | clear_altered();
128 | common.query("symbols", {
129 | success: function (json) {
130 | const [{data}] = json;
131 | const items = process_symbols_data(data);
132 |
133 | /* eslint-disable consistent-this, no-underscore-dangle, one-var-declaration-per-line */
134 | FooTable.groupFilter = FooTable.Filtering.extend({
135 | construct: function (instance) {
136 | this._super(instance);
137 | [,this.groups] = items;
138 | this.def = "Any group";
139 | this.$group = null;
140 | },
141 | $create: function () {
142 | this._super();
143 | const self = this;
144 | const $form_grp = $("
", {
145 | class: "form-group"
146 | }).append($(" ", {
147 | class: "sr-only",
148 | text: "Group"
149 | })).prependTo(self.$form);
150 |
151 | self.$group = $(" ", {
152 | class: "form-select"
153 | }).on("change", {
154 | self: self
155 | }, self._onStatusDropdownChanged).append(
156 | $(" ", {
157 | text: self.def
158 | })).appendTo($form_grp);
159 |
160 | $.each(self.groups, (i, group) => {
161 | self.$group.append($(" ").text(group));
162 | });
163 | },
164 | _onStatusDropdownChanged: function (e) {
165 | const {self} = e.data;
166 | const selected = $(this).val();
167 | if (selected !== self.def) {
168 | self.addFilter("group", selected, ["group"]);
169 | } else {
170 | self.removeFilter("group");
171 | }
172 | self.filter();
173 | },
174 | draw: function () {
175 | this._super();
176 | const group = this.find("group");
177 | if (group instanceof FooTable.Filter) {
178 | this.$group.val(group.query.val());
179 | } else {
180 | this.$group.val(this.def);
181 | }
182 | }
183 | });
184 | /* eslint-enable consistent-this, no-underscore-dangle, one-var-declaration-per-line */
185 |
186 | common.tables.symbols = FooTable.init("#symbolsTable", {
187 | columns: [
188 | {sorted: true, direction: "ASC", name: "group", title: "Group"},
189 | {name: "symbol", title: "Symbol"},
190 | {name: "description", title: "Description", breakpoints: "xs sm"},
191 | {name: "weight", title: "Score"},
192 | {name: "frequency",
193 | title: "Frequency",
194 | breakpoints: "xs sm",
195 | sortValue: function (value) { return Number(value).toFixed(2); }},
196 | {name: "time", title: "Avg. time", breakpoints: "xs sm"},
197 | ],
198 | rows: items[0],
199 | paging: {
200 | enabled: true,
201 | limit: 5,
202 | size: 25
203 | },
204 | filtering: {
205 | enabled: true,
206 | position: "left",
207 | connectors: false
208 | },
209 | sorting: {
210 | enabled: true
211 | },
212 | components: {
213 | filtering: FooTable.groupFilter
214 | },
215 | on: {
216 | "ready.ft.table": function () {
217 | if (common.read_only) {
218 | $(".mb-disabled").attr("disabled", true);
219 | }
220 | },
221 | "postdraw.ft.table":
222 | () => $("#refresh, #updateSymbols").removeAttr("disabled")
223 | }
224 | });
225 | },
226 | error: () => $("#refresh, #updateSymbols").removeAttr("disabled"),
227 | server: common.getServer()
228 | });
229 | };
230 |
231 |
232 | $("#updateSymbols").on("click", (e) => {
233 | e.preventDefault();
234 | $("#refresh, #updateSymbols").attr("disabled", true);
235 | clear_altered();
236 | common.query("symbols", {
237 | success: function (data) {
238 | const [items] = process_symbols_data(data[0].data);
239 | common.tables.symbols.rows.load(items);
240 | },
241 | error: () => $("#refresh, #updateSymbols").removeAttr("disabled"),
242 | server: common.getServer()
243 | });
244 | });
245 |
246 | $("#symbolsTable")
247 | .on("input", ".scorebar", ({target}) => {
248 | const t = $(target);
249 | t.removeClass("scorebar-ham scorebar-spam");
250 | if (target.value < 0) {
251 | t.addClass("scorebar-ham");
252 | } else if (target.value > 0) {
253 | t.addClass("scorebar-spam");
254 | }
255 | })
256 | .on("change", ".scorebar", ({target}) => {
257 | altered[$(target).attr("id").substring(5)] = parseFloat(target.value);
258 | $("#save-alert").removeClass("d-none");
259 | });
260 |
261 | $("#save-alert button")
262 | .on("click", ({target}) => saveSymbols($(target).data("save")));
263 |
264 | return ui;
265 | });
266 |
--------------------------------------------------------------------------------
/www/js/app/upload.js:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (C) 2017 Vsevolod Stakhov
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 | */
24 |
25 | /* global require */
26 |
27 | define(["jquery", "app/common", "app/libft"],
28 | ($, common, libft) => {
29 | "use strict";
30 | const ui = {};
31 | let files = null;
32 | let filesIdx = null;
33 | let scanTextHeaders = {};
34 |
35 | function cleanTextUpload(source) {
36 | $("#" + source + "TextSource").val("");
37 | }
38 |
39 | function uploadText(data, source, headers) {
40 | let url = null;
41 | if (source === "spam") {
42 | url = "learnspam";
43 | } else if (source === "ham") {
44 | url = "learnham";
45 | } else if (source === "fuzzy") {
46 | url = "fuzzyadd";
47 | } else if (source === "scan") {
48 | url = "checkv2";
49 | }
50 |
51 | function server() {
52 | if (common.getSelector("selSrv") === "All SERVERS" &&
53 | common.getSelector("selLearnServers") === "random") {
54 | const servers = $("#selSrv option").slice(1).map((_, o) => o.value);
55 | return servers[Math.floor(Math.random() * servers.length)];
56 | }
57 | return null;
58 | }
59 |
60 | common.query(url, {
61 | data: data,
62 | params: {
63 | processData: false,
64 | },
65 | method: "POST",
66 | headers: headers,
67 | success: function (json, jqXHR) {
68 | cleanTextUpload(source);
69 | common.alertMessage("alert-success", "Data successfully uploaded");
70 | if (jqXHR.status !== 200) {
71 | common.alertMessage("alert-info", jqXHR.statusText);
72 | }
73 | },
74 | server: server()
75 | });
76 | }
77 |
78 | function enable_disable_scan_btn(disable) {
79 | $("#scan button:not(#cleanScanHistory, #scanOptionsToggle)")
80 | .prop("disabled", (disable || $.trim($("textarea").val()).length === 0));
81 | }
82 |
83 | function setFileInputFiles(i) {
84 | const dt = new DataTransfer();
85 | if (arguments.length) dt.items.add(files[i]);
86 | $("#formFile").prop("files", dt.files);
87 | }
88 |
89 | function readFile(callback, i) {
90 | const reader = new FileReader();
91 | reader.readAsText(files[(arguments.length === 1) ? 0 : i]);
92 | reader.onload = () => callback(reader.result);
93 | }
94 |
95 | function scanText(data) {
96 | enable_disable_scan_btn(true);
97 | common.query("checkv2", {
98 | data: data,
99 | params: {
100 | processData: false,
101 | },
102 | method: "POST",
103 | headers: scanTextHeaders,
104 | success: function (neighbours_status) {
105 | const json = neighbours_status[0].data;
106 | if (json.action) {
107 | common.alertMessage("alert-success", "Data successfully scanned");
108 |
109 | const o = libft.process_history_v2({rows: [json]}, "scan");
110 | const {items} = o;
111 | common.symbols.scan.push(o.symbols[0]);
112 |
113 | if (files) items[0].file = files[filesIdx].name;
114 |
115 | if (Object.prototype.hasOwnProperty.call(common.tables, "scan")) {
116 | common.tables.scan.rows.load(items, true);
117 | } else {
118 | require(["footable"], () => {
119 | libft.initHistoryTable(data, items, "scan", libft.columns_v2("scan"), true,
120 | () => {
121 | if (files && filesIdx < files.length - 1) {
122 | readFile((result) => {
123 | if (filesIdx === files.length - 1) {
124 | $("#scanMsgSource").val(result);
125 | setFileInputFiles(filesIdx);
126 | }
127 | scanText(result);
128 | }, ++filesIdx);
129 | } else {
130 | enable_disable_scan_btn();
131 | $("#cleanScanHistory").removeAttr("disabled");
132 | $("html, body").animate({
133 | scrollTop: $("#scanResult").offset().top
134 | }, 1000);
135 | }
136 | });
137 | });
138 | }
139 | } else {
140 | common.alertMessage("alert-error", "Cannot scan data");
141 | }
142 | },
143 | error: enable_disable_scan_btn,
144 | errorMessage: "Cannot upload data",
145 | statusCode: {
146 | 404: function () {
147 | common.alertMessage("alert-error", "Cannot upload data, no server found");
148 | },
149 | 500: function () {
150 | common.alertMessage("alert-error", "Cannot tokenize message: no text data");
151 | },
152 | 503: function () {
153 | common.alertMessage("alert-error", "Cannot tokenize message: no text data");
154 | }
155 | },
156 | server: common.getServer()
157 | });
158 | }
159 |
160 | function getFuzzyHashes(data) {
161 | function fillHashTable(rules) {
162 | $("#hashTable tbody").empty();
163 | for (const [rule, hashes] of Object.entries(rules)) {
164 | hashes.forEach((hash, i) => {
165 | $("#hashTable tbody").append("" +
166 | (i === 0 ? '' + rule + " " : "") +
167 | "" + hash + " ");
168 | });
169 | }
170 | $("#hash-card").slideDown();
171 | }
172 |
173 | common.query("plugins/fuzzy/hashes?flag=" + $("#fuzzy-flag").val(), {
174 | data: data,
175 | params: {
176 | processData: false,
177 | },
178 | method: "POST",
179 | success: function (neighbours_status) {
180 | const json = neighbours_status[0].data;
181 | if (json.success) {
182 | common.alertMessage("alert-success", "Message successfully processed");
183 | fillHashTable(json.hashes);
184 | } else {
185 | common.alertMessage("alert-error", "Unexpected error processing message");
186 | }
187 | },
188 | server: common.getServer()
189 | });
190 | }
191 |
192 |
193 | libft.set_page_size("scan", $("#scan_page_size").val());
194 | libft.bindHistoryTableEventHandlers("scan", 3);
195 |
196 | $("#cleanScanHistory").off("click");
197 | $("#cleanScanHistory").on("click", (e) => {
198 | e.preventDefault();
199 | if (!confirm("Are you sure you want to clean scan history?")) { // eslint-disable-line no-alert
200 | return;
201 | }
202 | libft.destroyTable("scan");
203 | common.symbols.scan.length = 0;
204 | $("#cleanScanHistory").attr("disabled", true);
205 | });
206 |
207 | enable_disable_scan_btn();
208 | $("textarea").on("input", () => {
209 | enable_disable_scan_btn();
210 | if (files) {
211 | files = null;
212 | setFileInputFiles();
213 | }
214 | });
215 |
216 | $("#scanClean").on("click", () => {
217 | enable_disable_scan_btn(true);
218 | $("#scanForm")[0].reset();
219 | $("html, body").animate({scrollTop: 0}, 1000);
220 | return false;
221 | });
222 |
223 | $(".card-close-btn").on("click", function () {
224 | $(this).closest(".card").slideUp();
225 | });
226 |
227 | function getScanTextHeaders() {
228 | scanTextHeaders = ["IP", "User", "From", "Rcpt", "Helo", "Hostname"].reduce((o, header) => {
229 | const value = $("#scan-opt-" + header.toLowerCase()).val();
230 | if (value !== "") o[header] = value;
231 | return o;
232 | }, {});
233 | if ($("#scan-opt-pass-all").prop("checked")) scanTextHeaders.Pass = "all";
234 | }
235 |
236 | $("[data-upload]").on("click", function () {
237 | const source = $(this).data("upload");
238 | const data = $("#scanMsgSource").val();
239 | if ($.trim(data).length > 0) {
240 | if (source === "scan") {
241 | getScanTextHeaders();
242 | scanText(data);
243 | } else if (source === "compute-fuzzy") {
244 | getFuzzyHashes(data);
245 | } else {
246 | let headers = {};
247 | if (source === "fuzzy") {
248 | headers = {
249 | flag: $("#fuzzyFlagText").val(),
250 | weight: $("#fuzzyWeightText").val()
251 | };
252 | }
253 | uploadText(data, source, headers);
254 | }
255 | } else {
256 | common.alertMessage("alert-error", "Message source field cannot be blank");
257 | }
258 | return false;
259 | });
260 |
261 | const dragoverClassList = "outline-dashed-primary bg-primary-subtle";
262 | $("#scanMsgSource")
263 | .on("dragenter dragover dragleave drop", (e) => {
264 | e.preventDefault();
265 | e.stopPropagation();
266 | })
267 | .on("dragenter dragover", () => {
268 | $("#scanMsgSource").addClass(dragoverClassList);
269 | })
270 | .on("dragleave drop", () => {
271 | $("#scanMsgSource").removeClass(dragoverClassList);
272 | })
273 | .on("drop", (e) => {
274 | ({files} = e.originalEvent.dataTransfer);
275 | filesIdx = 0;
276 |
277 | if (files.length === 1) {
278 | setFileInputFiles(0);
279 | enable_disable_scan_btn();
280 | readFile((result) => {
281 | $("#scanMsgSource").val(result);
282 | enable_disable_scan_btn();
283 | });
284 | // eslint-disable-next-line no-alert
285 | } else if (files.length < 10 || confirm("Are you sure you want to scan " + files.length + " files?")) {
286 | getScanTextHeaders();
287 | readFile((result) => scanText(result));
288 | }
289 | });
290 |
291 | return ui;
292 | });
293 |
--------------------------------------------------------------------------------
/www/js/lib/codejar-linenumbers.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * codejar-linenumbers v1.0.1 (https://github.com/julianpoemp/codejar-linenumbers)
3 | * Copyright (c) 2023, Julian Poemp, MIT
4 | */
5 | function withLineNumbers(r,e={}){const d={class:"codejar-linenumbers",wrapClass:"codejar-wrap",width:"35px",backgroundColor:"rgba(128, 128, 128, 0.15)",color:"",...e};let l;return function(e){r(e),l||(l=init(e,d),e.addEventListener("scroll",()=>l.style.top=`-${e.scrollTop}px`));var t=(e.textContent||"").replace(/\n$/g,"").split("\n").length;let o="";for(let e=0;e{},h;l.setAttribute("contenteditable","plaintext-only"),l.setAttribute("spellcheck",c.spellcheck?"true":"false"),l.style.outline="none",l.style.overflowWrap="break-word",l.style.overflowY="auto",l.style.whiteSpace="pre-wrap";const d=(e,t)=>{n(e,t)};let g=!1;(g="plaintext-only"!==l.contentEditable?!0:g)&&l.setAttribute("contenteditable","true");const t=S(()=>{var e=v();d(l,e),T(e)},30);let y=!1;const N=e=>!O(e)&&!M(e)&&"Meta"!==e.key&&"Control"!==e.key&&"Alt"!==e.key&&!e.key.startsWith("Arrow"),s=S(e=>{N(e)&&(x(),y=!1)},300);e=(e,t)=>{r.push([e,t]),l.addEventListener(e,t)};function v(){var e=H();const t={start:0,end:0,dir:void 0};let{anchorNode:n,anchorOffset:r,focusNode:o,focusOffset:a}=e;if(n&&o)return n===l&&o===l?(t.start=0=r?"->":"<-"):(n.nodeType===Node.ELEMENT_NODE&&(e=f.createTextNode(""),n.insertBefore(e,n.childNodes[r]),n=e,r=0),o.nodeType===Node.ELEMENT_NODE&&(e=f.createTextNode(""),o.insertBefore(e,o.childNodes[a]),o=e,a=0),w(l,e=>{if(e===n&&e===o)return t.start+=r,t.end+=a,t.dir=r<=a?"->":"<-","stop";if(e===n){if(t.start+=r,t.dir)return"stop";t.dir="->"}else if(e===o){if(t.end+=a,t.dir)return"stop";t.dir="<-"}e.nodeType===Node.TEXT_NODE&&("->"!=t.dir&&(t.start+=e.nodeValue.length),"<-"!=t.dir)&&(t.end+=e.nodeValue.length)}),l.normalize()),t;throw"error1"}function T(n){var e=H();let r,o=0,a,i=0,d=(n.dir||(n.dir="->"),n.start<0&&(n.start=0),n.end<0&&(n.end=0),"<-"==n.dir&&({start:t,end:s}=n,n.start=s,n.end=t),0);w(l,e=>{var t;if(e.nodeType===Node.TEXT_NODE)return t=(e.nodeValue||"").length,d+t>n.start&&(r||(r=e,o=n.start-d),d+t>n.end)?(a=e,i=n.end-d,"stop"):void(d+=t)}),r||(r=l,o=l.childNodes.length),a||(a=l,i=l.childNodes.length),"<-"==n.dir&&([r,o,a,i]=[a,i,r,o]);var t,s=C(r),s=(s&&(t=f.createTextNode(""),s.parentNode?.insertBefore(t,s),r=t,o=0),C(a));s&&(t=f.createTextNode(""),s.parentNode?.insertBefore(t,s),a=t,i=0),e.setBaseAndExtent(r,o,a,i),l.normalize()}function C(e){for(;e&&e!==l;){if(e.nodeType===Node.ELEMENT_NODE){var t=e;if("false"==t.getAttribute("contenteditable"))return t}e=e.parentNode}}function E(){var e=H().getRangeAt(0),t=f.createRange();return t.selectNodeContents(l),t.setEnd(e.startContainer,e.startOffset),t.toString()}function m(){var e=H().getRangeAt(0),t=f.createRange();return t.selectNodeContents(l),t.setStart(e.endContainer,e.endOffset),t.toString()}function b(e){g&&"Enter"===e.key&&(B(e),e.stopPropagation(),""==m()?(L("\n "),(e=v()).start=--e.end,T(e)):L("\n"))}function x(){var e,t,n;a&&(e=l.innerHTML,t=v(),(n=u[p])&&n.html===e&&n.pos.start===t.start&&n.pos.end===t.end||(p++,u[p]={html:e,pos:t},u.splice(p+1),300
/g,">").replace(/"/g,""").replace(/'/g,"'"),f.execCommand("insertHTML",!1,e)}function S(t,n){let r=0;return(...e)=>{clearTimeout(r),r=o.setTimeout(()=>t(...e),n)}}function A(e){let t=e.length-1;for(;0<=t&&"\n"!==e[t];)t--;let n=++t;for(;n{if(!e.defaultPrevented){if(h=_(),c.preserveIdent){var t=e;if("Enter"===t.key){var n=E(),r=m(),[o]=A(n);let e=o;c.indentOn.test(n)&&(e+=c.tab),0=u.length)&&p--,N(e))&&!y&&(x(),y=!0),g&&(!k(i=e)||"C"!==D(i))&&T(v())}}),e("keyup",e=>{e.defaultPrevented||e.isComposing||(h!==_()&&t(),s(e),i(_()))}),e("focus",e=>{a=!0}),e("blur",e=>{a=!1}),e("paste",e=>{var t;x(),(e=e).defaultPrevented||(B(e),e=(e.originalEvent??e).clipboardData.getData("text/plain").replace(/\r\n?/g,"\n"),t=v(),L(e),d(l),T({start:Math.min(t.start,t.end)+e.length,end:Math.min(t.start,t.end)+e.length,dir:"<-"})),x(),i(_())}),e("cut",e=>{var t,n;x(),e=e,t=v(),n=H(),(e.originalEvent??e).clipboardData.setData("text/plain",n.toString()),f.execCommand("delete"),d(l),T({start:Math.min(t.start,t.end),end:Math.min(t.start,t.end),dir:"<-"}),B(e),x(),i(_())}),{updateOptions(e){Object.assign(c,e)},updateCode(e){l.textContent=e,d(l),i(e)},onUpdate(e){i=e},toString:_,save:v,restore:T,recordHistory:x,destroy(){for(var[e,t]of r)l.removeEventListener(e,t)}}}
--------------------------------------------------------------------------------
/www/js/lib/d3evolution.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * D3Evolution 2.0.1 (https://github.com/moisseev/D3Evolution)
3 | * Copyright (c) 2016-2017, Alexander Moisseev, BSD 2-Clause
4 | */
5 | function D3Evolution(t,e){"use strict";var l=$.extend(!0,{title:"",width:800,height:400,margin:{top:80,right:60,bottom:40,left:60},yAxisLabel:"",type:"line",yScale:"lin",duration:1250,interpolate:"curveLinear",legend:{buttonRadius:7,space:130,entries:[]}},e);this.destroy=function(){d3.selectAll("#"+t+" svg").remove()},this.destroy();const n={curveLinear:d3.curveLinear,curveStep:d3.curveStep,curveStepBefore:d3.curveStepBefore,curveStepAfter:d3.curveStepAfter,curveMonotoneX:d3.curveMonotoneX,curveBasis:d3.curveBasis,curveBasisOpen:d3.curveBasisOpen,curveBundle:d3.curveBundle,curveCardinal:d3.curveCardinal,curveCardinalOpen:d3.curveCardinalOpen,curveCatmullRom:d3.curveCatmullRom,curveCatmullRomOpen:d3.curveCatmullRomOpen,curveNatural:d3.curveNatural};var o=null,i=null,c=null,u=l.width-l.margin.left-l.margin.right,r=l.height-l.margin.top-l.margin.bottom,s=d3.scaleTime().range([0,u]),d=null,p=null;function a(){p="log"===l.yScale?(d=d3.scaleLog().clamp(!0).range([r,0]),d3.scaleLog().range([r-30,0])):(d=d3.scaleLinear().range([r,0])).copy()}a();function y(t){return void 0!==l.legend.entries[t]&&void 0!==l.legend.entries[t].color?l.legend.entries[t].color:H(t)}function f(t){return void 0!==l.legend.entries[t]&&void 0!==l.legend.entries[t].label?l.legend.entries[t].label:"path_"+t}var g=d3.axisBottom().scale(s),h=d3.axisLeft().scale(p).ticks(5),m=d3.axisBottom().tickFormat("").scale(s).tickSize(-r,0),v=d3.axisLeft().tickFormat("").scale(p).tickSize(-u,0),D=d3.scaleQuantize().range([r,0]),T=d3.area().x(function(t){return s(t.x)}).y0(function(){return r}).y1(function(t){return D(null===t.y)}).curve(d3.curveStep),x=d3.line().defined(function(t){return null!==t.y}).x(function(t){return s(t.x)}).y(function(t){return d(t.y)}).curve(n[l.interpolate]),A=d3.area().defined(function(t){return null!==t.y}).x(function(t){return s(t.x)}).y0(function(t){return d(t.y0)}).y1(function(t){return d(t.y0+t.y)}).curve(n[l.interpolate]),X=function(t){t.reduce(function(n,t){return t.forEach(function(t,e){t.y0=n.length?n[e].y+n[e].y0:0}),t},[])},H=d3.scaleOrdinal(d3.schemeCategory10),Q=function(t){var n=t.reduce(function(n,t){return t.map(function(t,e){return t.y+(n[e]||0)})},[]),t=$.extend(!0,[],t);return t.forEach(function(t){t.forEach(function(t,e){n[e]&&(t.y/=n[e])})}),t};function k(){if("log"===l.yScale){const e=d.invert(r);o.forEach(function(t){t.forEach(function(t){return 0===t.y?t.y:e})})}}var e=d3.select("#"+t).append("svg").classed("d3evolution",!0).attr("width",l.width).attr("height",l.height),b=e.append("g").attr("class","legend"),S=e.append("g").attr("width",u).attr("height",r).attr("transform","translate("+l.margin.left+", "+l.margin.top+")"),L=(S.append("g").attr("class","x grid").attr("transform","translate(0,"+r+")").call(m),S.append("g").attr("class","y grid").attr("transform","translate(0,0)").call(v),S.append("g").attr("class","x axis").attr("transform","translate(0,"+r+")").call(g),S.append("g").attr("class","y axis").attr("transform","translate(0,0)").call(h),d3.scaleOrdinal().domain([0]).range([r])),w=d3.axisLeft().scale(L),R=(S.append("g").attr("class","y-zero axis").call(w),S.append("text").attr("class","y label").attr("x",20-l.margin.left).attr("y",-20).style("opacity","percentage"===l.convert?0:1).text(l.yAxisLabel)),B=e.append("svg:text").attr("x",l.width/2).attr("y",l.margin.top/3).attr("text-anchor","middle"),L=(B.append("tspan").attr("class","chart-title").text(l.title+" "),B.timeRange=B.append("tspan"),e.append("svg:text").attr("x",l.width-20).attr("y",l.margin.top/3).attr("text-anchor","end")),V=L.append("tspan").attr("class","cursor-time");L.append("svg:title").text("Current cursor position");const C=function(t){return d3.timeFormat("%Y-%m-%d %H:%M:%S")(new Date(t))};function M(e){var n=o.map(function(t){return t[e]}),t=n[0].x;return V.text(C(t)),b.selectAll("text.value").text(function(t,e){return null===n[e].y?null:d3.format("percentage"===l.convert?".2~%":".6~")(n[e].y)}),n}var O=null,E=null;function Y(t){var e=d3.bisector(function(t){return t.x}).left,t=s.invert(d3.pointer(t)[0]),n=M(e(o[0],t)-1);O.selectAll(".x,.cursor circle").attr("transform","translate("+s(n[0].x)+",0)"),O.selectAll(".y").attr("transform",function(t,e){e=n[e];return"translate(0,"+(("area"===l.type?d(e.y0+e.y):d(e.y))||0)+")"}).style("display",function(t,e){return n[e].y?null:"none"})}function j(){O.style("display","none"),M(E)}function q(){O.style("display",null)}S.append("rect").style("fill","none").style("pointer-events","all").attr("width",u).attr("height",r).on("mousemove",Y).on("mouseout",j).on("mouseover",q);function G(){o="percentage"===l.convert?(R.transition().duration(l.duration).style("opacity",0),Q(i)):(R.transition().duration(l.duration).style("opacity",1),i),E=i[0].length-1,z()}var F=S.append("g"),_=S.append("g"),z=((O=S.append("g").attr("class","cursor").style("pointer-events","none").style("display","none")).append("line").attr("class","x background").attr("y1",0).attr("y2",r),O.append("line").attr("class","x foreground").attr("y1",0).attr("y2",r),function(){var t=[];if(t="area"===l.type?(X(o),"log"===l.yScale?d3.extent(d3.merge(o),function(t){return t.y0+t.y===0?null:t.y0+t.y}):d3.extent(d3.merge(o),function(t){return t.y0+t.y})):"log"===l.yScale?d3.extent(d3.merge(o),function(t){return 0===t.y?null:t.y}):d3.extent(d3.merge(o),function(t){return t.y}),"log"===l.yScale?(void 0===t[0]?t=[.0095,.0105]:t[0]===t[1]&&(t[0]*=.9),p.domain([t[0],t[1]]),e=p.invert(r),d.domain([e,t[1]])):(d.domain([0{e.on("click",t=>{t=e.nodes().indexOf(t.currentTarget);N[t]=0===N[t]?1:0,d3.select("#circle_"+t).transition().duration(l.duration).style("fill-opacity",N[t]+.2),d3.select("#path_"+t).transition().duration(l.duration).style("opacity",N[t])})};function r(e,n){function r(t){return!1===n?N[t]:t===e?1:0===N[t]?0:.4}d3.select("#circle_"+e).attr("r",l.legend.buttonRadius*(!1===n?1:1.3)),_.selectAll("path.path").style("opacity",function(t,e){return r(e)}).style("fill-opacity",function(t,e){return r(e)})}i=$.extend(!0,[],t),c=l.width-l.margin.right-l.legend.space*i.length,i.forEach(function(t){t.forEach(function(t){t.x*=1e3})});var t=d3.extent(d3.merge(i),function(t){return t.x}),t=(s.domain([t[0],t[1]]),B.timeRange.text("[ "+C(t[0])+" / "+C(t[1])+" ]"),F.selectAll("path.path-null").data(i)),t=(t.enter().append("path").attr("class","path-null"),F.selectAll("path.path-null").transition().duration(l.duration/2).style("opacity",0).on("end",function(){F.selectAll("path.path-null").attr("d",T).transition().duration(l.duration/2).style("opacity",1)}),t.exit().remove(),G(),k(),_.selectAll("path.path").data(o)),t=(e(t.enter().append("path").merge(t).attr("class","path").attr("id",function(t,e){return"path_"+e}).on("mousemove",Y).on("mouseover",function(t,e,n){r(n),q()}).on("mouseout",function(t,e,n){r(n,!1),j()})),t.exit().remove(),t=_.selectAll("path.path"),"area"===l.type?t.style("fill",function(t,e){return y(e)}).style("stroke","none").style("fill-opacity",function(t,e){return N[e]}):t.style("fill","none").style("stroke",function(t,e){return y(e)}).style("opacity",function(t,e){return N[e]}),t.transition().duration(l.duration).attr("d","area"===l.type?A:x),d3.transition().duration(l.duration)),t=(S.select(".x.grid").transition(t).call(m.scale(s)),S.select(".x.axis").transition(t).call(g.scale(s)),O.selectAll(".y").data(o)),n=t.enter().append("g").attr("class","y").style("stroke",function(t,e){return y(e)}),n=(n.append("circle").attr("class","background"),n.append("circle").attr("class","foreground"),n.selectAll("circle").attr("r",7).style("fill","none"),n.append("line").attr("class","background"),n.append("line").attr("class","foreground"),n.selectAll("line").attr("x1",0).attr("x2",u),t.exit().remove(),b.selectAll("circle").data(o)),t=(e(n.enter().append("circle").attr("id",function(t,e){return"circle_"+e}).attr("cy",2*l.margin.top/3).attr("r",l.legend.buttonRadius).style("fill",function(t,e){return y(e)}).style("stroke",function(t,e){return y(e)}).style("fill-opacity",function(t,e){return N[e]+.2}).on("mouseover",function(t,e,n){r(n)}).on("mouseout",function(t,e,n){r(n,!1)})),n.exit().remove(),b.selectAll("circle").transition().duration(l.duration).attr("cx",function(t,e){return c+l.legend.space*e}),b.selectAll("g").data(o)),n=t.enter().append("g"),a=(e(n.append("text").attr("class","name").attr("dy","0.3em").text(function(t,e){return f(e)}).on("mouseover",function(t,e,n){r(n)}).on("mouseout",function(t,e,n){r(n,!1)})),n.append("text").attr("class","value").attr("dy","20"),t.exit().remove(),I(),b.selectAll("text.value"));return a.transition("opacity").duration(l.duration/2).style("opacity",0).on("end",function(){M(E),a.transition("opacity").duration(l.duration/2).style("opacity",1)}),this},this.legend=function(t){return $.extend(!0,l.legend,t),b.selectAll("circle").transition().duration(l.duration).attr("cx",function(t,e){return c+l.legend.space*e}).attr("r",l.legend.buttonRadius).style("fill",function(t,e){return y(e)}).style("stroke",function(t,e){return y(e)}),b.selectAll("text.name").text(function(t,e){return f(e)}),I(),_.selectAll("path.path").transition().duration(l.duration).style("fill","area"===l.type?function(t,e){return y(e)}:"none").style("stroke","area"!==l.type?function(t,e){return y(e)}:"none"),O.selectAll(".y").style("stroke",function(t,e){return y(e)}),this},this.convert=function(t){return l.convert=t,G(),M(E),_.selectAll("path.path").data(o).transition().duration(l.duration).attr("d","area"===l.type?A:x),this},this.interpolate=function(t){return l.interpolate=t,A.curve(n[l.interpolate]),x.curve(n[l.interpolate]),_.selectAll("path.path").attr("d","area"===l.type?A:x),this},this.type=function(t){return l.type=t,z(),_.selectAll("path.path").style("stroke","area"!==l.type?function(t,e){return y(e)}:"none").style("fill","area"===l.type?function(t,e){return y(e)}:"none").transition().duration(l.duration).attr("d","area"===l.type?A:x),this},this.yAxisLabel=function(t){return l.yAxisLabel=t,R.transition().duration(l.duration/2).style("opacity",0).on("end",function(){R.text(l.yAxisLabel).transition().duration(l.duration/2).style("opacity",1)}),this},this.yScale=function(t){return l.yScale=t,a(),k(),z(),_.selectAll("path.path").transition().duration(l.duration).attr("d","area"===l.type?A:x),this}}
--------------------------------------------------------------------------------
/www/js/lib/d3pie.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * rspamd-D3Pie 1.1.0 (https://github.com/moisseev/rspamd-D3Pie)
3 | * Copyright (c) 2022, Alexander Moisseev, BSD 2-Clause
4 | */
5 | function D3Pie(v,t){"use strict";const A=$.extend(!0,{canvasPadding:5,cornerRadius:3,duration:1250,gradient:{enabled:!0,percentage:100},labels:{inner:{hideWhenLessThanPercentage:4,offset:.15},outer:{collideHeight:13,format:"label",pieDistance:30}},padAngle:.01,pieCenterOffset:{x:0,y:0},size:{canvasHeight:400,canvasWidth:600,pieInnerRadius:"20%",pieOuterRadius:"85%"},title:"",total:{enabled:!1}},t),e=(this.destroy=function(){d3.selectAll("#"+v+" svg, #"+v+"-tooltip").remove()},this.destroy(),d3.select("#"+v).append("svg").attr("class","d3pie").attr("width",A.size.canvasWidth).attr("height",A.size.canvasHeight));let l=0;if(""!==A.title){const a=e.append("svg:text").attr("class","chart-title").attr("x",A.size.canvasWidth/2);a.append("tspan").text(A.title+" "),l=a.node().getBBox().height,a.attr("y",l+A.canvasPadding)}const x=e.append("g").attr("transform","translate("+(A.size.canvasWidth/2+A.pieCenterOffset.x)+","+(A.size.canvasHeight/2+l/2+A.pieCenterOffset.y)+")"),y={},m={},{outerRadius:M,innerRadius:R}=function(){function t(t,e){var a;return/%/u.test(t)?(a=Math.max(0,Math.min(99,parseInt(t.replace(/[\D]/u,""),10)))/100,Math.floor(e*a)):parseInt(t,10)}var e=A.size.canvasWidth-2*A.canvasPadding,a=A.size.canvasHeight-2*A.canvasPadding-l;let n=Math.min(e,a)/2;"none"!==A.labels.outer.format&&(e=parseInt(A.labels.outer.pieDistance,10),n>e&&(n-=e));a=t(A.size.pieOuterRadius,n);return{outerRadius:a,innerRadius:t(A.size.pieInnerRadius,a)}}(),w=M+A.labels.outer.pieDistance,z=d3.line().curve(d3.curveCatmullRomOpen),P=d3.select("body").append("div").attr("id",v+"-tooltip").attr("class","d3pie-tooltip"),r=P.append("span").attr("id",v+"-tooltip-text");function I(t){t.on("mouseover",function(t,e){var a=P.datum().total,n=a?Math.round(100*e.data.value/a):NaN;e.data.value?(P.transition().duration(300).style("opacity",1),r.text(e.data.label+(a?": "+e.data.value+" ("+n+"%)":""))):P.transition().duration(300).style("opacity",0),P.each(function(t){t.height=this.getBoundingClientRect().height})}).on("mouseout",function(){P.transition().duration(300).style("opacity",0)}).on("mousemove",t=>{const{pageX:e,pageY:a}=t;P.style("left",e+"px").style("top",function(t){return a-t.height-2+"px"})})}const H=x.append("g");if(I(H),H.append("circle").attr("r",R).style("opacity",0),A.total.enabled){const n=H.append("text").attr("class","total-text");n.append("tspan").attr("class","total-value").style("font-size",.6*R+"px"),n.append("tspan").attr("x","0").attr("dy",.5*R).text(void 0!==A.total.label?A.total.label:"Total")}const O=e.append("defs");this.data=function(t){let l=$.extend(!0,[],t);const d=[],e=l.reduce(function(t,e){return t+(e.value||0)},0),a=(P.datum({total:e}),H.datum({data:{label:void 0!==A.total.label?A.total.label:"Total",value:e}}),A.total.enabled&&H.select(".total-value").text(d3.format(".3~s")(e)),l.unshift({label:"undefined",color:A.gradient.enabled?"steelblue":"#ecf1f5",value:0===e?1:0}),d3.scaleOrdinal(d3.schemeSet1));function n(t,e){return void 0!==t&&void 0!==t.color?t.color:a(e)}function s(t,e,a=e){return d3.arc().innerRadius(e).outerRadius(a).centroid(t)}function c(t){return d3.interpolate(y[t.data.label],t)}function u(t){var e=w-.1;return Math.max(-e,Math.min(e,t))}function r(r,i){m[r.data.label].newAngle=function(){var t=u(d[i].y);let e=Math.sqrt(Math.pow(w,2)-Math.pow(t,2)),a=((r.endAngle+r.startAngle)/2>Math.PI&&(e*=-1),Math.PI/2-Math.atan2(-t,e));return a<0&&(a+=2*Math.PI),{startAngle:a,endAngle:a}}();const o=d3.interpolate(m[r.data.label].currentAngle,m[r.data.label].newAngle);return function(t){var e=s(o(t),w),[a,n]=e,l=0
5 | * @version 1.2.1
6 | * @see https://github.com/aidanlister/jquery-stickytabs
7 | * @license MIT
8 | */
9 | !function(c){c.fn.stickyTabs=function(o){function t(){var o="href"==n.selectorAttribute?window.location.hash:window.location.hash.substring(1),t=o?"a["+n.selectorAttribute+'="'+o+'"]':n.initialTab;c(t,i).tab("show"),setTimeout(l,1)}var i=this,n=c.extend({getHashCallback:function(o,t){return o},selectorAttribute:"href",backToTop:!1,initialTab:c("li.active > a",i)},o),l=function(){!0===n.backToTop&&window.scrollTo(0,0)};return t(),c(window).on("hashchange",t),c("a",i).on("click",function(o){var t=this.href.split("#")[1];!function(o){history&&history.pushState?history.pushState(null,null,window.location.pathname+window.location.search+"#"+o):(scrollV=document.body.scrollTop,scrollH=document.body.scrollLeft,window.location.hash=o,document.body.scrollTop=scrollV,document.body.scrollLeft=scrollH)}(n.getHashCallback(t,this)),setTimeout(l,1)}),this}}(jQuery);
--------------------------------------------------------------------------------
/www/js/lib/nprogress.min.js:
--------------------------------------------------------------------------------
1 | /* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress
2 | * @version 0.2.0
3 | * @license MIT */
4 | !function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():e.NProgress=n()}(this,function(){var n,t,o={version:"0.2.0"},a=o.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:''};function u(e,n,t){return e=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""+i.tag+">"},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
4 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
5 | !function(){if("undefined"!=typeof Prism){var r={tab:/\t/,crlf:/\r\n/,lf:/\n/,cr:/\r/,space:/ /};Prism.hooks.add("before-highlight",(function(r){i(r.grammar)}))}function e(r,a){var n=r[a];switch(Prism.util.type(n)){case"RegExp":var t={};r[a]={pattern:n,inside:t},i(t);break;case"Array":for(var f=0,s=n.length;f { // eslint-disable-line strict
45 | d3 = d3global;
46 | });
47 |
48 | // Notify user on module loading failure
49 | requirejs.onError = function (e) {
50 | "use strict";
51 | document.getElementById("loading").classList.add("d-none");
52 | document.getElementsByClassName("notification-area")[0].innerHTML =
53 | " " +
54 | "Module loading error: " + e.requireType + ", module: " + e.requireModules + " " +
55 | "" +
58 | " Reload" +
59 | " " +
60 | "
";
61 | throw e;
62 | };
63 |
64 | // Load main UI
65 | require(["app/rspamd"], (rspamd) => {
66 | "use strict";
67 | rspamd.connect();
68 | });
69 |
--------------------------------------------------------------------------------
/www/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/poralix/rspamd/fb3a969e51c69ddf52143348e3b3978b52cc2aa4/www/mstile-150x150.png
--------------------------------------------------------------------------------
/www/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------