HEX
Server: Apache/2
System: Linux nexus-01 4.18.0-553.120.1.el8_10.x86_64 #1 SMP Mon Apr 20 18:04:27 EDT 2026 x86_64
User: aglcoke (1118)
PHP: 8.2.31
Disabled: mail,exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Upload Files
File: //proc/1/task/1/root/proc/1/root/usr/share/rspamd/www/js/app/graph.js
/*
 * Copyright (C) 2017 Vsevolod Stakhov <vsevolod@highsecure.ru>
 * Copyright (C) 2017 Alexander Moisseev
 */

/* global FooTable */

define(["jquery", "app/common", "d3evolution", "d3pie", "d3", "footable"],
    ($, common, D3Evolution, D3Pie, d3) => {
        "use strict";

        const rrd_pie_config = {
            cornerRadius: 2,
            size: {
                canvasWidth: 400,
                canvasHeight: 180,
                pieInnerRadius: "50%",
                pieOuterRadius: "80%"
            },
            labels: {
                outer: {
                    format: "none"
                },
                inner: {
                    hideWhenLessThanPercentage: 8,
                    offset: 0
                },
            },
            padAngle: 0.02,
            pieCenterOffset: {
                x: -120,
                y: 10,
            },
            total: {
                enabled: true
            },
        };

        const ui = {};
        let prevUnit = "msg/s";

        ui.draw = function (graphs, neighbours, checked_server, type) {
            const graph_options = {
                title: "Rspamd throughput",
                width: 1060,
                height: 370,
                yAxisLabel: "Message rate, msg/s",

                legend: {
                    space: 140,
                    entries: common.chartLegend
                }
            };

            function initGraph() {
                const graph = new D3Evolution("graph", $.extend({}, graph_options, {
                    yScale: common.getSelector("selYScale"),
                    type: common.getSelector("selType"),
                    interpolate: common.getSelector("selInterpolate"),
                    convert: common.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) {
                const xExtents = d3.extent(d3.merge(json), (d) => d.x);
                const timeInterval = xExtents[1] - xExtents[0];

                let total = 0;
                const rows = json.map((curr, i) => {
                    // Time intervals that don't have data are excluded from average calculation as d3.mean()ignores nulls
                    const avg = d3.mean(curr, (d) => d.y);
                    // To find an integral on the whole time interval we need to convert nulls to zeroes
                    // eslint-disable-next-line no-bitwise
                    const value = d3.mean(curr, (d) => Number(d.y)) * timeInterval / scaleFactor ^ 0;
                    const yExtents = d3.extent(curr, (d) => d.y);

                    total += value;
                    return {
                        label: graph_options.legend.entries[i].label,
                        value: value,
                        min: Number((yExtents[0] ?? 0).toFixed(6)),
                        avg: Number((avg ?? 0).toFixed(6)),
                        max: Number((yExtents[1] ?? 0).toFixed(6)),
                        last: Number((curr[curr.length - 1]?.y ?? 0).toFixed(6)),
                        color: graph_options.legend.entries[i].color,
                    };
                }, []);

                return {
                    rows: rows,
                    total: total
                };
            }

            function initSummaryTable(rows, unit) {
                common.tables.rrd_summary = FooTable.init("#rrd-table", {
                    breakpoints: common.breakpoints,
                    cascade: true,
                    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((curr, i) => ({
                        options: {
                            style: {
                                color: graph_options.legend.entries[i].color
                            }
                        },
                        value: curr
                    }), [])
                });
            }

            function drawRrdTable(rows, unit) {
                if (Object.prototype.hasOwnProperty.call(common.tables, "rrd_summary")) {
                    $.each(common.tables.rrd_summary.rows.all, (i, row) => {
                        row.val(rows[i], false, true);
                    });
                } else {
                    initSummaryTable(rows, unit);
                }
            }

            function updateWidgets(data) {
                let rrd_summary = {rows: []};
                let unit = "msg/s";

                if (data) {
                    // Autoranging
                    let scaleFactor = 1;
                    const yMax = d3.max(d3.merge(data), (d) => d.y);
                    if (yMax < 1) {
                        scaleFactor = 60;
                        unit = "msg/min";
                        data.forEach((s) => {
                            s.forEach((d) => {
                                if (d.y !== null) { d.y *= scaleFactor; }
                            });
                        });
                    }

                    rrd_summary = getRrdSummary(data, scaleFactor);
                }

                if (!graphs.rrd_pie) graphs.rrd_pie = new D3Pie("rrd-pie", rrd_pie_config);
                graphs.rrd_pie.data(rrd_summary.rows);

                graphs.graph.data(data);
                if (unit !== prevUnit) {
                    graphs.graph.yAxisLabel("Message rate, " + unit);
                    $(".unit").text(unit);
                    prevUnit = unit;
                }
                drawRrdTable(rrd_summary.rows, unit);
                document.getElementById("rrd-total-value").innerHTML = rrd_summary.total;
            }

            if (!graphs.graph) {
                graphs.graph = initGraph();
            }


            common.query("graph", {
                success: function (req_data) {
                    let data = null;
                    const neighbours_data = req_data
                        .filter((d) => d.status) // filter out unavailable neighbours
                        .map((d) => d.data);

                    if (neighbours_data.length === 1) {
                        [data] = neighbours_data;
                    } else {
                        let time_match = true;
                        neighbours_data.reduce((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;
                                common.logError({
                                    server: "Multi-server",
                                    endpoint: "graph",
                                    message: "Neighbours time extents do not match. " +
                                        "Check if time is synchronized on all servers.",
                                    errorType: "data_inconsistency"
                                });
                                arr.splice(1); // Break out of .reduce() by mutating the source array
                            }
                            return curr;
                        });

                        if (time_match) {
                            data = neighbours_data.reduce((res, curr) => curr.map((action, j) => action.map((d, i) => ({
                                x: d.x,
                                y: (res[j][i].y === null) ? d.y : res[j][i].y + d.y
                            }))));
                        }
                    }
                    updateWidgets(data);
                },
                complete: function () { $("#refresh").removeAttr("disabled").removeClass("disabled"); },
                errorMessage: "Cannot receive throughput data",
                errorOnceId: "alerted_graph_",
                data: {type: type}
            });
        };


        // Handling mouse events on overlapping elements
        $("#rrd-pie").mouseover(() => {
            $("#rrd-pie,#rrd-pie-tooltip").css("z-index", "200");
            $("#rrd-table_toggle").css("z-index", "300");
        });
        $("#rrd-table_toggle").mouseover(() => {
            $("#rrd-pie,#rrd-pie-tooltip").css("z-index", "0");
            $("#rrd-table_toggle").css("z-index", "0");
        });

        return ui;
    });