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;
});