123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- /*
- The MIT License (MIT)
-
- Copyright (C) 2017 Vsevolod Stakhov <vsevolod@highsecure.ru>
- Copyright (C) 2017 Alexander Moisseev
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- */
-
- /* global d3:false, FooTable:false */
-
- define(["jquery", "d3evolution", "footable"],
- function ($, D3Evolution) {
- "use strict";
-
- var rrd_pie_config = {
- header: {},
- size: {
- canvasWidth: 400,
- canvasHeight: 180,
- pieInnerRadius: "20%",
- pieOuterRadius: "80%"
- },
- labels: {
- outer: {
- format: "none"
- },
- inner: {
- hideWhenLessThanPercentage: 8
- },
- },
- misc: {
- pieCenterOffset: {
- x: -120,
- y: 10,
- },
- gradient: {
- enabled: true,
- },
- },
- };
-
- var graph_options = {
- title: "Rspamd throughput",
- width: 1060,
- height: 370,
- yAxisLabel: "Message rate, msg/s",
-
- legend: {
- space: 140,
- entries: [{
- label: "Rejected",
- color: "#FF0000"
- }, {
- label: "Temporarily rejected",
- color: "#BF8040"
- }, {
- label: "Subject rewritten",
- color: "#FF6600"
- }, {
- label: "Probable spam",
- color: "#FFAD00"
- }, {
- label: "Greylisted",
- color: "#436EEE"
- }, {
- label: "Clean",
- color: "#66CC00"
- }]
- }
- };
-
- // Get selectors' current state
- function getSelector(id) {
- var e = document.getElementById(id);
- return e.options[e.selectedIndex].value;
- }
-
- function initGraph() {
- var graph = new D3Evolution("graph", $.extend({}, graph_options, {
- yScale: getSelector("selYScale"),
- type: getSelector("selType"),
- interpolate: getSelector("selInterpolate"),
- convert: getSelector("selConvert"),
- }));
- $("#selYScale").change(function () {
- graph.yScale(this.value);
- });
- $("#selConvert").change(function () {
- graph.convert(this.value);
- });
- $("#selType").change(function () {
- graph.type(this.value);
- });
- $("#selInterpolate").change(function () {
- graph.interpolate(this.value);
- });
-
- return graph;
- }
-
- function getRrdSummary(json, scaleFactor) {
- var xExtents = d3.extent(d3.merge(json), function (d) { return d.x; });
- var timeInterval = xExtents[1] - xExtents[0];
-
- var total = 0;
- var rows = json.map(function (curr, i) {
- // Time intervals that don't have data are excluded from average calculation as d3.mean()ignores nulls
- var avg = d3.mean(curr, function (d) { return d.y; });
- // To find an integral on the whole time interval we need to convert nulls to zeroes
- var value = d3.mean(curr, function (d) { return Number(d.y); }) * timeInterval / scaleFactor ^ 0; // eslint-disable-line no-bitwise
- var yExtents = d3.extent(curr, function (d) { return d.y; });
-
- total += value;
- return {
- label: graph_options.legend.entries[i].label,
- value: value,
- min: Number(yExtents[0].toFixed(6)),
- avg: Number(avg.toFixed(6)),
- max: Number(yExtents[1].toFixed(6)),
- last: Number(curr[curr.length - 1].y.toFixed(6)),
- color: graph_options.legend.entries[i].color,
- };
- }, []);
-
- return {
- rows: rows,
- total: total
- };
- }
-
- function initSummaryTable(tables, rows, unit) {
- tables.rrd_summary = FooTable.init("#rrd-table", {
- sorting: {
- enabled: true
- },
- columns: [
- {name:"label", title:"Action"},
- {name:"value", title:"Messages", defaultContent:""},
- {name:"min", title:"Minimum, <span class=\"unit\">" + unit + "</span>", defaultContent:""},
- {name:"avg", title:"Average, <span class=\"unit\">" + unit + "</span>", defaultContent:""},
- {name:"max", title:"Maximum, <span class=\"unit\">" + unit + "</span>", defaultContent:""},
- {name:"last", title:"Last, " + unit},
- ],
- rows: rows.map(function (curr, i) {
- return {
- options: {
- style: {
- color: graph_options.legend.entries[i].color
- }
- },
- value: curr
- };
- }, [])
- });
- }
-
- function drawRrdTable(tables, rows, unit) {
- if (Object.prototype.hasOwnProperty.call(tables, "rrd_summary")) {
- $.each(tables.rrd_summary.rows.all, function (i, row) {
- row.val(rows[i], false, true);
- });
- } else {
- initSummaryTable(tables, rows, unit);
- }
- }
-
- var ui = {};
- var prevUnit = "msg/s";
-
- ui.draw = function (rspamd, graphs, tables, neighbours, checked_server, type) {
- function updateWidgets(data) {
- var rrd_summary = {rows:[]};
- var unit = "msg/s";
-
- if (data) {
- // Autoranging
- var scaleFactor = 1;
- var yMax = d3.max(d3.merge(data), function (d) { return d.y; });
- if (yMax < 1) {
- scaleFactor = 60;
- unit = "msg/min";
- data.forEach(function (s) {
- s.forEach(function (d) {
- if (d.y !== null) { d.y *= scaleFactor; }
- });
- });
- }
-
- rrd_summary = getRrdSummary(data, scaleFactor);
- }
-
- if (graphs.rrd_pie) {
- graphs.rrd_pie.destroy();
- delete graphs.rrd_pie;
- }
- if (rrd_summary.total) {
- graphs.rrd_pie = rspamd.drawPie(graphs.rrd_pie,
- "rrd-pie",
- rrd_summary.rows,
- rrd_pie_config);
- } else {
- // Show grayed out pie as percentage is undefined
- graphs.rrd_pie = rspamd.drawPie(graphs.rrd_pie,
- "rrd-pie",
- [{
- value: 1,
- color: "#FFFFFF",
- }],
- $.extend({}, rrd_pie_config, {
- labels: {
- outer: {
- format: "none"
- },
- inner: {
- format: "none"
- },
- },
- tooltips: {
- enabled: true,
- string: "Undefined"
- },
- })
- );
- }
-
- graphs.graph.data(data);
- if (unit !== prevUnit) {
- graphs.graph.yAxisLabel("Message rate, " + unit);
- $(".unit").text(unit);
- prevUnit = unit;
- }
- drawRrdTable(tables, rrd_summary.rows, unit);
- document.getElementById("rrd-total-value").innerHTML = rrd_summary.total;
- }
-
- if (!graphs.graph) {
- graphs.graph = initGraph();
- }
-
- rspamd.query("graph", {
- success: function (req_data) {
- var data = null;
- var neighbours_data = req_data
- .filter(function (d) { return d.status; }) // filter out unavailable neighbours
- .map(function (d) { return d.data; });
-
- if (neighbours_data.length === 1) {
- data = neighbours_data[0];
- } else {
- var time_match = true;
- neighbours_data.reduce(function (res, curr, _, arr) {
- if ((curr[0][0].x !== res[0][0].x) ||
- (curr[0][curr[0].length - 1].x !== res[0][res[0].length - 1].x)) {
- time_match = false;
- rspamd.alertMessage("alert-error",
- "Neighbours time extents do not match. Check if time is synchronized on all servers.");
- arr.splice(1); // Break out of .reduce() by mutating the source array
- }
- return curr;
- });
-
- if (time_match) {
- data = neighbours_data.reduce(function (res, curr) {
- return curr.map(function (action, j) {
- return action.map(function (d, i) {
- return {
- x: d.x,
- y: (res[j][i].y === null) ? d.y : res[j][i].y + d.y
- };
- });
- });
- });
- }
- }
- updateWidgets(data);
- },
- errorMessage: "Cannot receive throughput data",
- errorOnceId: "alerted_graph_",
- data: {type:type}
- });
- };
-
- ui.setup = function () {
- // Handling mouse events on overlapping elements
- $("#rrd-pie").mouseover(function () {
- $("#rrd-pie").css("z-index", "200");
- $("#rrd-table_toggle").css("z-index", "300");
- });
- $("#rrd-table_toggle").mouseover(function () {
- $("#rrd-pie").css("z-index", "0");
- $("#rrd-table_toggle").css("z-index", "0");
- });
-
- return getSelector("selData");
- };
-
- return ui;
- });
|