HEX
Server: Apache/2
System: Linux nexus-01 4.18.0-553.146.1.el8_10.x86_64 #1 SMP Tue Jul 21 03:06:01 EDT 2026 x86_64
User: aglcoke (1118)
PHP: 8.2.32
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/cwd/usr/share/rspamd/www/js/app/symbols.js
/*
 * Copyright (C) 2017 Vsevolod Stakhov <vsevolod@highsecure.ru>
 */

define(["app/common", "app/tab-utils", "tabulator"],
    (common, tabUtils, Tabulator) => {
        "use strict";
        const ui = {};
        let altered = {};
        let groupSelectEl = null;

        function setSymbolsControlsDisabled(disabled) {
            document.querySelectorAll("#refresh, #updateSymbols").forEach((el) => {
                el.disabled = disabled;
            });
        }
        function setSaveButtonsDisabled(disabled) {
            document.querySelectorAll("#save-alert button").forEach((el) => {
                el.disabled = disabled;
            });
        }

        function clear_altered() {
            document.getElementById("save-alert").classList.add("d-none");
            altered = {};
        }

        function saveSymbols(server) {
            setSaveButtonsDisabled(true);

            const values = [];
            Object.entries(altered).forEach(([key, value]) => values.push({name: key, value: value}));

            common.query("./savesymbols", {
                success: function () {
                    clear_altered();
                    common.alertMessage("alert-modal alert-success", "Symbols successfully saved");
                },
                complete: () => setSaveButtonsDisabled(false),
                errorMessage: "Save symbols error",
                method: "POST",
                params: {
                    data: JSON.stringify(values),
                    dataType: "json",
                },
                server: server
            });
        }

        function process_symbols_data(data) {
            const items = [];
            const lookup = {};
            const freqs = [];
            const stddevs = [];
            const distinct_groups = [];

            data.forEach((group) => {
                group.rules.forEach((item) => {
                    const formatter = new Intl.NumberFormat("en", {
                        minimumFractionDigits: 2,
                        maximumFractionDigits: 6,
                        useGrouping: false
                    });
                    item.group = group.group;
                    let label_class = "";
                    if (item.weight < 0) {
                        label_class = "scorebar-ham";
                    } else if (item.weight > 0) {
                        label_class = "scorebar-spam";
                    }
                    item.weight = '<input class="form-control input-sm mb-disabled scorebar ' + label_class +
                        '" autocomplete="off" type="number" step="0.01" tabindex="1" ' +
                        'value="' + formatter.format(item.weight) + '" id="_sym_' + item.symbol + '"></input>';
                    if (!item.time) {
                        item.time = 0;
                    }
                    item.time = Number(item.time).toFixed(2);

                    // Normalize frequency values for scaling
                    ["frequency", "frequency_stddev"].forEach((p) => (item[p] = Number(item[p] || 0)));

                    freqs.push(item.frequency);
                    stddevs.push(item.frequency_stddev);
                    if (!(item.group in lookup)) {
                        lookup[item.group] = 1;
                        distinct_groups.push(item.group);
                    }
                    items.push(item);
                });
            });

            // For better mean calculations - use only non-zero values
            const nonzero_freqs = freqs.filter((f) => Number(f) > 0.0);
            const avg_freq = nonzero_freqs.length > 0
                ? nonzero_freqs.reduce((acc, f) => acc + Number(f), 0.0) / nonzero_freqs.length
                : 0.0;
            let mult = 1.0;
            let exp = 0.0;

            if (avg_freq > 0.0) {
                while (mult * avg_freq < 1.0) {
                    mult *= 10;
                    exp++;
                }
            }

            return [items, distinct_groups, {mult, exp}];
        }
        // Populate a native <select> with distinct sorted groups from the
        // table data, plus an empty option to clear the filter.
        function formatFreq(value, params) {
            return (value * params.mult).toFixed(2) + ((params.exp > 0) ? "e-" + params.exp : "");
        }

        function populateGroupSelect(select, rows) {
            const current = select.value;
            select.innerHTML = "";
            select.appendChild(new Option("Any group", ""));
            const data = rows || (common.tables.symbols && common.tables.symbols.getData()) || [];
            [...new Set(data.map((r) => r.group))]
                .sort((a, b) => a.localeCompare(b))
                .forEach((g) => select.appendChild(new Option(g, g)));
            select.value = current;
        }

        // @get symbols into modal form
        ui.getSymbols = function () {
            setSymbolsControlsDisabled(true);
            clear_altered();
            common.query("symbols", {
                success: function (json) {
                    const [{data}] = json;
                    const [rows, , freqParams] = process_symbols_data(data);

                    common.tables.symbols = new Tabulator("#symbolsTable", {
                        layout: "fitColumns",
                        responsiveLayout: "collapse",
                        responsiveLayoutCollapseStartOpen: false,
                        selectable: false,
                        pagination: "local",
                        paginationSize: 25,
                        paginationButtonCount: 5,
                        data: rows,
                        initialSort: [{column: "group", dir: "asc"}],
                        columns: [
                            {
                                // Toggle to expand collapsed (responsive) columns
                                formatter: "responsiveCollapse",
                                width: 36,
                                minWidth: 36,
                                responsive: 0,
                                hozAlign: "center",
                                resizable: false,
                                headerSort: false,
                            },
                            {
                                title: "Group",
                                field: "group",
                                headerFilter: (cell, onRendered, success) => {
                                    const select = document.createElement("select");
                                    select.className = "form-select";
                                    populateGroupSelect(select, rows);
                                    select.refreshGroups = () => populateGroupSelect(select);
                                    select.addEventListener("change", () => success(select.value));
                                    groupSelectEl = select;
                                    return select;
                                },
                                headerFilterFunc: (filterVal, cellVal) => !filterVal || filterVal === cellVal,
                                minWidth: 110,
                                width: 205,
                                responsive: 0,
                            },
                            {
                                title: "Symbol",
                                field: "symbol",
                                headerFilter: "input",
                                minWidth: 270,
                                responsive: 0,
                                widthGrow: 2,
                            },
                            {
                                title: "Description",
                                field: "description",
                                headerFilter: "input",
                                minWidth: 105,
                                responsive: 2,
                                widthGrow: 4,
                            },
                            {
                                title: "Score",
                                field: "weight",
                                formatter: "html",
                                minWidth: 75,
                                width: 90,
                                responsive: 0,
                            },
                            {
                                title: "Frequency, <nobr>hits/s</nobr>",
                                field: "frequency",
                                sorter: "number",
                                formatter: (cell, params) => formatFreq(cell.getValue(), params),
                                formatterParams: freqParams,
                                minWidth: 140,
                            },
                            {
                                title: "Stddev, <nobr>hits/s</nobr>",
                                field: "frequency_stddev",
                                sorter: "number",
                                formatter: (cell, params) => formatFreq(cell.getValue(), params),
                                formatterParams: freqParams,
                                minWidth: 120,
                                responsive: 3,
                            },
                            {title: "Avg. time, s", field: "time", sorter: "number", minWidth: 110},
                        ],
                    });

                    tabUtils.hideFooterOnSinglePage("symbols");
                    tabUtils.stripTableholderTabindex("symbols");
                    tabUtils.bindRowClickToggle("symbols");
                    tabUtils.patchScrollIntoViewOnce();
                    tabUtils.installScrollPreservation("symbols", {
                        armTriggers: [document.getElementById("updateSymbols")].filter(Boolean),
                    });

                    common.tables.symbols.on("tableBuilt", () => {
                        if (common.read_only) {
                            document.querySelectorAll(".mb-disabled").forEach((el) => {
                                el.disabled = true;
                            });
                        }
                        setSymbolsControlsDisabled(false);
                    });
                    common.tables.symbols.on("renderComplete", () => {
                        setSymbolsControlsDisabled(false);
                    });
                },
                error: () => setSymbolsControlsDisabled(false),
                server: common.getServer()
            });
        };


        document.getElementById("updateSymbols").addEventListener("click", (e) => {
            e.preventDefault();
            setSymbolsControlsDisabled(true);
            clear_altered();
            common.query("symbols", {
                success: function (data) {
                    const [rows, , freqParams] = process_symbols_data(data[0].data);

                    common.tables.symbols.setData(rows);
                    if (groupSelectEl) populateGroupSelect(groupSelectEl);
                    common.tables.symbols.updateColumnDefinition("frequency", {formatterParams: freqParams});
                    common.tables.symbols.updateColumnDefinition("frequency_stddev", {formatterParams: freqParams});
                },
                error: () => setSymbolsControlsDisabled(false),
                server: common.getServer()
            });
        });

        common.delegate("#symbolsTable", "input", ".scorebar", (event, target) => {
            target.classList.remove("scorebar-ham", "scorebar-spam");
            if (target.value < 0) {
                target.classList.add("scorebar-ham");
            } else if (target.value > 0) {
                target.classList.add("scorebar-spam");
            }
        });
        common.delegate("#symbolsTable", "change", ".scorebar", (event, target) => {
            altered[target.id.substring(5)] = parseFloat(target.value);
            document.getElementById("save-alert").classList.remove("d-none");
        });

        document.querySelectorAll("#save-alert button").forEach((el) => {
            el.addEventListener("click", () => saveSymbols(el.dataset.save));
        });

        return ui;
    });