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/symbols.js
/*
 * Copyright (C) 2017 Vsevolod Stakhov <vsevolod@highsecure.ru>
 */

/* global FooTable */

define(["jquery", "app/common", "footable"],
    ($, common) => {
        "use strict";
        const ui = {};
        let altered = {};

        function clear_altered() {
            $("#save-alert").addClass("d-none");
            altered = {};
        }

        function saveSymbols(server) {
            $("#save-alert button").attr("disabled", 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: () => $("#save-alert button").removeAttr("disabled"),
                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++;
                }
            }

            function formatFrequency(value) {
                return {
                    value: (value * mult).toFixed(2) + ((exp > 0) ? "e-" + exp : ""),
                    options: {sortValue: value}
                };
            }
            $.each(items, (i, item) => {
                item.frequency = formatFrequency(item.frequency);
                item.frequency_stddev = formatFrequency(item.frequency_stddev);
            });
            return [items, distinct_groups];
        }
        // @get symbols into modal form
        ui.getSymbols = function () {
            $("#refresh, #updateSymbols").attr("disabled", true);
            clear_altered();
            common.query("symbols", {
                success: function (json) {
                    const [{data}] = json;
                    const items = process_symbols_data(data);

                    /* eslint-disable consistent-this, no-underscore-dangle */
                    FooTable.groupFilter = FooTable.Filtering.extend({
                        construct: function (instance) {
                            this._super(instance);
                            [,this.groups] = items;
                            this.groups.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
                            this.def = "Any group";
                            this.$group = null;
                        },
                        $create: function () {
                            this._super();
                            const self = this;
                            const $form_grp = $("<div/>", {
                                class: "form-group"
                            }).append($("<label/>", {
                                class: "sr-only",
                                text: "Group"
                            })).prependTo(self.$form);

                            self.$group = $("<select/>", {
                                class: "form-select"
                            }).on("change", {
                                self: self
                            }, self._onStatusDropdownChanged).append(
                                $("<option/>", {
                                    text: self.def
                                })).appendTo($form_grp);

                            $.each(self.groups, (i, group) => {
                                self.$group.append($("<option/>").text(group));
                            });

                            common.appendButtonsToFtFilterDropdown(self);
                        },
                        _onStatusDropdownChanged: function (e) {
                            const {self} = e.data;
                            const selected = $(this).val();
                            if (selected !== self.def) {
                                self.addFilter("group", selected, ["group"]);
                            } else {
                                self.removeFilter("group");
                            }
                            self.filter();
                        },
                        draw: function () {
                            this._super();
                            const group = this.find("group");
                            if (group instanceof FooTable.Filter) {
                                this.$group.val(group.query.val());
                            } else {
                                this.$group.val(this.def);
                            }
                        }
                    });
                    /* eslint-enable consistent-this, no-underscore-dangle */

                    common.tables.symbols = FooTable.init("#symbolsTable", {
                        breakpoints: common.breakpoints,
                        cascade: true,
                        columns: [
                            {sorted: true, direction: "ASC", name: "group", title: "Group"},
                            {name: "symbol", title: "Symbol"},
                            {name: "description", title: "Description", breakpoints: "md"},
                            {name: "weight", title: "Score"},
                            {name: "frequency",
                                title: "Frequency, <nobr>hits/s</nobr>",
                                breakpoints: "md",
                                sortValue: (val) => val.options.sortValue},
                            {name: "frequency_stddev",
                                title: "Stddev, <nobr>hits/s</nobr>",
                                breakpoints: "lg",
                                sortValue: (val) => val.options.sortValue},
                            {name: "time",
                                title: "Avg. time, s",
                                breakpoints: "md",
                                sortValue: (val) => parseFloat(val)},
                        ],
                        rows: items[0],
                        paging: {
                            enabled: true,
                            limit: 5,
                            size: 25
                        },
                        filtering: {
                            enabled: true,
                            position: "left",
                            connectors: false
                        },
                        sorting: {
                            enabled: true
                        },
                        components: {
                            filtering: FooTable.groupFilter
                        },
                        on: {
                            "ready.ft.table": function () {
                                if (common.read_only) {
                                    $(".mb-disabled").attr("disabled", true);
                                }
                            },
                            "postdraw.ft.table":
                                () => $("#refresh, #updateSymbols").removeAttr("disabled")
                        }
                    });
                },
                error: () => $("#refresh, #updateSymbols").removeAttr("disabled"),
                server: common.getServer()
            });
        };


        $("#updateSymbols").on("click", (e) => {
            e.preventDefault();
            $("#refresh, #updateSymbols").attr("disabled", true);
            clear_altered();
            common.query("symbols", {
                success: function (data) {
                    const [items] = process_symbols_data(data[0].data);
                    common.tables.symbols.rows.load(items);
                },
                error: () => $("#refresh, #updateSymbols").removeAttr("disabled"),
                server: common.getServer()
            });
        });

        $("#symbolsTable")
            .on("input", ".scorebar", ({target}) => {
                const t = $(target);
                t.removeClass("scorebar-ham scorebar-spam");
                if (target.value < 0) {
                    t.addClass("scorebar-ham");
                } else if (target.value > 0) {
                    t.addClass("scorebar-spam");
                }
            })
            .on("change", ".scorebar", ({target}) => {
                altered[$(target).attr("id").substring(5)] = parseFloat(target.value);
                $("#save-alert").removeClass("d-none");
            });

        $("#save-alert button")
            .on("click", ({target}) => saveSymbols($(target).data("save")));

        return ui;
    });