feat(cloudron): add tirreno package artifacts
- Add CloudronStack/output/CloudronPackages-Artifacts/tirreno/ directory and its contents - Includes package manifest, Dockerfile, source code, documentation, and build artifacts - Add tirreno-1761840148.tar.gz as a build artifact - Add tirreno-cloudron-package-1761841304.tar.gz as the Cloudron package - Include all necessary files for the tirreno Cloudron package This adds the complete tirreno Cloudron package artifacts to the repository.
This commit is contained in:
		@@ -0,0 +1,210 @@
 | 
			
		||||
import {BaseChart}  from './BaseChart.js?v=2';
 | 
			
		||||
import {COLOR_MAP} from '../utils/Constants.js?v=2';
 | 
			
		||||
import {formatIntTimeUtc} from '../utils/Date.js?v=2';
 | 
			
		||||
import {renderChartTooltipPart} from '../DataRenderers.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class BaseBarChart extends BaseChart {
 | 
			
		||||
    getSeries() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.getDaySeries(),
 | 
			
		||||
            {
 | 
			
		||||
                width:      -1,
 | 
			
		||||
                paths:      uPlot.paths.bars({size: [0.6, 100]}),
 | 
			
		||||
                points:     {show: false},
 | 
			
		||||
            },
 | 
			
		||||
            this.getSingleSeries('Regular events', 'green'),
 | 
			
		||||
            this.getSingleSeries('Warning events', 'yellow'),
 | 
			
		||||
            this.getSingleSeries('Alert events', 'red'),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getSingleSeries(label, color) {
 | 
			
		||||
        return {
 | 
			
		||||
            label:      label,
 | 
			
		||||
            width:      -1,
 | 
			
		||||
            drawStyle:  1,
 | 
			
		||||
            fill: 	    COLOR_MAP[color].main,
 | 
			
		||||
            stroke:     COLOR_MAP[color].main,
 | 
			
		||||
            paths:      uPlot.paths.bars({size: [0.6, 100]}),
 | 
			
		||||
            points:     {show: false},
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // dataset adaption for bands instead of regular bars
 | 
			
		||||
    getData(data) {
 | 
			
		||||
        let stacked = [data[0]];
 | 
			
		||||
        let sums = new Array(data[0].length).fill(0);
 | 
			
		||||
 | 
			
		||||
        stacked.push(new Array(data[0].length).fill(0));
 | 
			
		||||
 | 
			
		||||
        let maxLvl = data.length - 1;
 | 
			
		||||
 | 
			
		||||
        for (let i = 1; i < data.length; i++) {
 | 
			
		||||
            let series = [];
 | 
			
		||||
            for (let j = 0; j < data[0].length; j++) {
 | 
			
		||||
                sums[j] += +data[i][j];
 | 
			
		||||
                series.push(sums[j]);
 | 
			
		||||
            }
 | 
			
		||||
            stacked.push(series);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        maxLvl = data.length;
 | 
			
		||||
 | 
			
		||||
        for (let i = 0; i < data[0].length; i++) {
 | 
			
		||||
            let topMet = false;
 | 
			
		||||
            for (let j = maxLvl; j > 1; j--) {
 | 
			
		||||
                if (stacked[j][i] <= stacked[j-1][i] && !topMet) {
 | 
			
		||||
                    stacked[j][i] = null;
 | 
			
		||||
                } else {
 | 
			
		||||
                    topMet = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.data = stacked;
 | 
			
		||||
 | 
			
		||||
        return stacked;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    stack(data, omit) {
 | 
			
		||||
        let data2 = [];
 | 
			
		||||
        let bands = [];
 | 
			
		||||
        let d0Len = data ? data[0].length : 0;
 | 
			
		||||
        let accum = Array(d0Len);
 | 
			
		||||
 | 
			
		||||
        let i;
 | 
			
		||||
 | 
			
		||||
        for (i = 0; i < d0Len; i++) {
 | 
			
		||||
            accum[i] = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let el;
 | 
			
		||||
 | 
			
		||||
        for (i = 1; i < data.length; i++) {
 | 
			
		||||
            el = data[i];
 | 
			
		||||
 | 
			
		||||
            if (!omit(i)) {
 | 
			
		||||
                el = el.map((v, j) => {
 | 
			
		||||
                    let val = accum[j] + +v;
 | 
			
		||||
                    accum[j] = val;
 | 
			
		||||
 | 
			
		||||
                    return val;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            data2.push(el);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (i = 1; i < data.length; i++) {
 | 
			
		||||
            !omit(i) && bands.push({
 | 
			
		||||
                series: [
 | 
			
		||||
                    data.findIndex((s, j) => j > i && !omit(j)),
 | 
			
		||||
                    i,
 | 
			
		||||
                ],
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bands = bands.filter(b => b.series[1] > -1);
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            data: [data[0]].concat(data2),
 | 
			
		||||
            bands,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getOptions(resolution = 'day', nullChar = '0') {
 | 
			
		||||
        const opts = super.getOptions(resolution, nullChar);
 | 
			
		||||
 | 
			
		||||
        let stacked = this.stack(this.data, i => false);
 | 
			
		||||
 | 
			
		||||
        opts.bands = stacked.bands;
 | 
			
		||||
 | 
			
		||||
        opts.series.forEach((s, sIdx) => {
 | 
			
		||||
            if (s) {
 | 
			
		||||
                s.value = (u, v, si, i) => u.data[sIdx][i];
 | 
			
		||||
 | 
			
		||||
                s.points = s.points || {};
 | 
			
		||||
                s.points.filter = (u, seriesIdx, show, gaps) => {
 | 
			
		||||
                    if (show) {
 | 
			
		||||
                        let pts = [];
 | 
			
		||||
                        u.data[seriesIdx].forEach((v, i) => {
 | 
			
		||||
                            v != null && pts.push(i);
 | 
			
		||||
                        });
 | 
			
		||||
                        return pts;
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return opts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tooltipCursor(u, seriestt, opts, resolution, defaultVal) {
 | 
			
		||||
        const left = u.cursor.left;
 | 
			
		||||
        const idx  = u.cursor.idx;
 | 
			
		||||
        const col  = [];
 | 
			
		||||
 | 
			
		||||
        if (opts && opts.cursorMemo) {
 | 
			
		||||
            opts.cursorMemo.set(left, top);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        seriestt.style.display = 'none';
 | 
			
		||||
 | 
			
		||||
        if (left >= 0 && u.data) {
 | 
			
		||||
            let xVal = u.data[0][idx];
 | 
			
		||||
 | 
			
		||||
            let maxLvl = u.data.length - 1;
 | 
			
		||||
 | 
			
		||||
            for (let i = 0; i <= maxLvl; i++) {
 | 
			
		||||
                col.push(u.data[i][idx]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const vtp = (resolution === 'day') ? 'DAY' : ((resolution === 'hour') ? 'HOUR' : 'MINUTE');
 | 
			
		||||
            let ts = '';
 | 
			
		||||
 | 
			
		||||
            if (Number.isInteger(xVal)) {
 | 
			
		||||
                const useTime = resolution === 'hour' || resolution === 'minute';
 | 
			
		||||
                ts = formatIntTimeUtc(xVal * 1000, useTime);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let frag = document.createDocumentFragment();
 | 
			
		||||
            frag.appendChild(document.createTextNode(ts.replace(/\./g, '/')));
 | 
			
		||||
 | 
			
		||||
            let prev = null;
 | 
			
		||||
 | 
			
		||||
            let maxVal = 0;
 | 
			
		||||
            let maxIdx = 0;
 | 
			
		||||
 | 
			
		||||
            for (let i = maxLvl; i >= 1; i--) {
 | 
			
		||||
                if (col[i] === null) {
 | 
			
		||||
                    col[i] = 0;
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (maxVal < col[i]) {
 | 
			
		||||
                        maxVal = col[i];
 | 
			
		||||
                        maxIdx = i;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (prev !== null) {
 | 
			
		||||
                        col[prev] -= col[i];
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    prev = i;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (let i = 2; i <= maxLvl; i++) {
 | 
			
		||||
                frag = this.extendTooltipFragment(i, null, col, defaultVal, u, frag);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (frag.children.length > 1) {
 | 
			
		||||
                seriestt.replaceChildren(frag);
 | 
			
		||||
 | 
			
		||||
                seriestt.style.top = Math.round(u.valToPos(maxVal, u.series[maxIdx].scale)) + 'px';
 | 
			
		||||
                seriestt.style.left = Math.round(u.valToPos(xVal, vtp)) + 'px';
 | 
			
		||||
                seriestt.style.display = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [seriestt, opts];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,380 @@
 | 
			
		||||
import {Loader} from '../Loader.js?v=2';
 | 
			
		||||
import {getQueryParams}  from '../utils/DataSource.js?v=2';
 | 
			
		||||
import {handleAjaxError} from '../utils/ErrorHandler.js?v=2';
 | 
			
		||||
import {formatIntTimeUtc} from '../utils/Date.js?v=2';
 | 
			
		||||
import {fireEvent} from '../utils/Event.js?v=2';
 | 
			
		||||
import {renderChartTooltipPart} from '../DataRenderers.js?v=2';
 | 
			
		||||
import {
 | 
			
		||||
    MAX_HOURS_CHART,
 | 
			
		||||
    MIN_HOURS_CHART,
 | 
			
		||||
    X_AXIS_SERIFS,
 | 
			
		||||
} from '../utils/Constants.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class BaseChart {
 | 
			
		||||
    constructor(chartParams) {
 | 
			
		||||
        this.config = chartParams;
 | 
			
		||||
 | 
			
		||||
        this.cursLeft = -10;
 | 
			
		||||
        this.cursTop = -10;
 | 
			
		||||
        this.cursorMemo = {
 | 
			
		||||
            set: (left, top) => {
 | 
			
		||||
                this.cursLeft = left;
 | 
			
		||||
                this.cursTop = top;
 | 
			
		||||
            },
 | 
			
		||||
            get: () => ({
 | 
			
		||||
                left: this.cursLeft,
 | 
			
		||||
                top: this.cursTop,
 | 
			
		||||
                y: false,
 | 
			
		||||
                drag: {
 | 
			
		||||
                    x: false,
 | 
			
		||||
                    y: false
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.timeLabelColor = '#d7e6e1';
 | 
			
		||||
 | 
			
		||||
        this.loader = new Loader();
 | 
			
		||||
 | 
			
		||||
        const loaderDiv = document.createElement('div');
 | 
			
		||||
        loaderDiv.id = 'loader';
 | 
			
		||||
        this.chartBlock.appendChild(loaderDiv);
 | 
			
		||||
 | 
			
		||||
        this.chart = null;
 | 
			
		||||
        if (!this.config.sequential) {
 | 
			
		||||
            this.loadData();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const onDateFilterChanged = this.onDateFilterChanged.bind(this);
 | 
			
		||||
        window.addEventListener('dateFilterChanged', onDateFilterChanged, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onDateFilterChanged() {
 | 
			
		||||
        this.loadData();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    stopAnimation() {
 | 
			
		||||
        this.loaderBlock.classList.add('is-hidden');
 | 
			
		||||
        this.loader.stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    startLoader() {
 | 
			
		||||
        const el = document.createElement('p');
 | 
			
		||||
        el.className = 'text-loader';
 | 
			
		||||
 | 
			
		||||
        this.loaderBlock.classList.remove('is-hidden');
 | 
			
		||||
        this.loaderBlock.replaceChildren(el);
 | 
			
		||||
 | 
			
		||||
        const p = this.loaderBlock.querySelector('p');
 | 
			
		||||
 | 
			
		||||
        this.loader.start(p);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    loadData() {
 | 
			
		||||
        if (!this.config.sequential) {
 | 
			
		||||
            this.startLoader();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const token  = document.head.querySelector('[name=\'csrf-token\'][content]').content;
 | 
			
		||||
        const params = this.config.getParams();
 | 
			
		||||
        const data   = getQueryParams(params);
 | 
			
		||||
 | 
			
		||||
        data['mode']        = params.mode;
 | 
			
		||||
        data['token']       = token;
 | 
			
		||||
        data['resolution']  = 'day';
 | 
			
		||||
        if (data['dateFrom']) {
 | 
			
		||||
            const diff = new Date(data['dateTo']) - new Date(data['dateFrom']);
 | 
			
		||||
            const hours = diff/(60 * 60 * 1000);
 | 
			
		||||
            if (hours <= MAX_HOURS_CHART && hours > MIN_HOURS_CHART) {
 | 
			
		||||
                data['resolution'] = 'hour';
 | 
			
		||||
            } else if (hours <= MIN_HOURS_CHART) {
 | 
			
		||||
                data['resolution'] = 'minute';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fireEvent('dateFilterChangedCaught');
 | 
			
		||||
 | 
			
		||||
        $.ajax({
 | 
			
		||||
            url: '/admin/loadChart',
 | 
			
		||||
            type: 'get',
 | 
			
		||||
            data: data,
 | 
			
		||||
            success: (responseData, status) => this.onChartLoaded(responseData, status, data['resolution']),
 | 
			
		||||
            error: handleAjaxError,
 | 
			
		||||
            complete: function() {
 | 
			
		||||
                fireEvent('dateFilterChangedCompleted');
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onChartLoaded(data, status, resolution) {
 | 
			
		||||
        if ('success' == status) {
 | 
			
		||||
            if (this.chart) {
 | 
			
		||||
                this.chart.destroy();
 | 
			
		||||
            }
 | 
			
		||||
            const prepData = this.getData(data);
 | 
			
		||||
            this.chart = new uPlot(this.getOptions(resolution), prepData, this.chartBlock);
 | 
			
		||||
 | 
			
		||||
            this.stopAnimation();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    seriesResolutionShift(series, resolution) {
 | 
			
		||||
        if (resolution === 'hour') {
 | 
			
		||||
            series[0].label = 'Hour';
 | 
			
		||||
            series[0].scale = 'HOUR';
 | 
			
		||||
            series[0].value = '{YYYY}-{MM}-{DD} {HH}:{mm}';
 | 
			
		||||
        } else if (resolution === 'minute') {
 | 
			
		||||
            series[0].label = 'Minute';
 | 
			
		||||
            series[0].scale = 'MINUTE';
 | 
			
		||||
            series[0].value = '{YYYY}-{MM}-{DD} {HH}:{mm}';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return series;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getDaySeries() {
 | 
			
		||||
        return {
 | 
			
		||||
            label: 'Day',
 | 
			
		||||
            scale: 'DAY',
 | 
			
		||||
            value: '{YYYY}-{MM}-{DD}'
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getAxisConfig() {
 | 
			
		||||
        const xAxis = {
 | 
			
		||||
            scale: 'DAY',
 | 
			
		||||
            stroke: '#8180a0',
 | 
			
		||||
            grid: {
 | 
			
		||||
                width: 1 / devicePixelRatio,
 | 
			
		||||
                stroke: '#2b2a3d',
 | 
			
		||||
            },
 | 
			
		||||
            ticks: {
 | 
			
		||||
                width: 1 / devicePixelRatio,
 | 
			
		||||
                stroke: '#2b2a3d',
 | 
			
		||||
            },
 | 
			
		||||
            values: [
 | 
			
		||||
                //Copied from https://github.com/leeoniya/uPlot/tree/master/docs#axis--grid-opts
 | 
			
		||||
                // tick incr     default          year        month    day        hour     min        sec       mode
 | 
			
		||||
                [3600 * 24,     '{DD}/{MM}',  '\n{YYYY}',     null,    null,      null,    null,      null,        1],
 | 
			
		||||
            ],
 | 
			
		||||
        };
 | 
			
		||||
        const yAxis = {
 | 
			
		||||
            stroke: '#8180a0',
 | 
			
		||||
            values: (u, vals, space) => vals.map(v => this.formatKiloValue(u, v)),
 | 
			
		||||
            grid: {
 | 
			
		||||
                width: 1 / devicePixelRatio,
 | 
			
		||||
                stroke: '#2b2a3d',
 | 
			
		||||
            },
 | 
			
		||||
            ticks: {
 | 
			
		||||
                width: 1 / devicePixelRatio,
 | 
			
		||||
                stroke: '#2b2a3d',
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            x: xAxis,
 | 
			
		||||
            y: yAxis,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getData(data) {
 | 
			
		||||
        return data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getOptions(resolution = 'day', nullChar = '0') {
 | 
			
		||||
        const tooltipsPlugin = this.tooltipsPlugin({cursorMemo: this.cursorMemo}, resolution, nullChar);
 | 
			
		||||
        const axes = this.getAxisConfig();
 | 
			
		||||
        const series = this.seriesResolutionShift(this.getSeries(), resolution);
 | 
			
		||||
        const xAxis = this.xAxisResolutionShift(axes.x, resolution);
 | 
			
		||||
        const yAxis = axes.y;
 | 
			
		||||
 | 
			
		||||
        const opts = {
 | 
			
		||||
            width: 995,
 | 
			
		||||
            height: 200,
 | 
			
		||||
 | 
			
		||||
            tzDate: ts => uPlot.tzDate(new Date(ts * 1000), 'Etc/UTC'),
 | 
			
		||||
            series: series,
 | 
			
		||||
 | 
			
		||||
            legend: {
 | 
			
		||||
                show: false
 | 
			
		||||
            },
 | 
			
		||||
            cursor: this.cursorMemo.get(),
 | 
			
		||||
            plugins: [tooltipsPlugin],
 | 
			
		||||
            scales: {
 | 
			
		||||
                x: {time: false},
 | 
			
		||||
            },
 | 
			
		||||
            axes: [
 | 
			
		||||
                xAxis,
 | 
			
		||||
                yAxis,
 | 
			
		||||
            ]
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return opts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    xAxisResolutionShift(xAxis, resolution) {
 | 
			
		||||
        if (resolution === 'hour') {
 | 
			
		||||
            xAxis.scale = 'HOUR';
 | 
			
		||||
            xAxis.values = [
 | 
			
		||||
                // tick incr default                year         month         day         hour     min     sec     mode
 | 
			
		||||
                [3600,      '{HH}:{mm}', '\n{DD}/{MM}/{YYYY}',   null,    '\n{DD}/{MM}',   null,    null,   null,   1]
 | 
			
		||||
            ];
 | 
			
		||||
            xAxis.space = function(self, axisIdx, scaleMin, scaleMax, plotDim) {
 | 
			
		||||
                let rangeHours   = (scaleMax - scaleMin) / 3600;
 | 
			
		||||
                if (rangeHours > X_AXIS_SERIFS) rangeHours = X_AXIS_SERIFS;
 | 
			
		||||
                const pxPerHour = plotDim / rangeHours;
 | 
			
		||||
 | 
			
		||||
                return pxPerHour;
 | 
			
		||||
            };
 | 
			
		||||
        } else if (resolution === 'minute') {
 | 
			
		||||
            xAxis.scale = 'MINUTE';
 | 
			
		||||
            xAxis.values = [
 | 
			
		||||
                // tick incr default              year           month         day         hour     min     sec     mode
 | 
			
		||||
                [60,        '{HH}:{mm}', '\n{DD}/{MM}/{YYYY}',   null,    '\n{DD}/{MM}',   null,    null,   null,   1]
 | 
			
		||||
            ];
 | 
			
		||||
            xAxis.space = function(self, axisIdx, scaleMin, scaleMax, plotDim) {
 | 
			
		||||
                let rangeMinutes   = (scaleMax - scaleMin) / 60;
 | 
			
		||||
                if (rangeMinutes > X_AXIS_SERIFS) rangeMinutes = X_AXIS_SERIFS;
 | 
			
		||||
                const pxPerMinute = plotDim / rangeMinutes;
 | 
			
		||||
 | 
			
		||||
                return pxPerMinute;
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return xAxis;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    formatKiloValue(u, value) {
 | 
			
		||||
        if (value === 0) {
 | 
			
		||||
            return value;
 | 
			
		||||
        }
 | 
			
		||||
        if (value % 1000000 === 0) {
 | 
			
		||||
            return Math.round(value / 1000000) + 'M';
 | 
			
		||||
        }
 | 
			
		||||
        if (value % 1000 === 0) {
 | 
			
		||||
            return Math.round(value / 1000) + 'k';
 | 
			
		||||
        }
 | 
			
		||||
        return value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get loaderBlock() {
 | 
			
		||||
        return document.getElementById('loader');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get chartBlock() {
 | 
			
		||||
        return document.querySelector('.stat-chart:not(#session-stat)');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tooltipsPlugin(opts, resolution = 'day', defaultVal = '0') {
 | 
			
		||||
        let self = this;
 | 
			
		||||
        let seriestt;
 | 
			
		||||
 | 
			
		||||
        function init(u, options, data) {
 | 
			
		||||
            seriestt = self.tooltipInit(u, options, data);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function setCursor(u) {
 | 
			
		||||
            [seriestt, opts] = self.tooltipCursor(u, seriestt, opts, resolution, defaultVal);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            hooks: {
 | 
			
		||||
                init,
 | 
			
		||||
                setCursor,
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tooltipCursor(u, seriestt, opts, resolution, defaultVal) {
 | 
			
		||||
        const left = u.cursor.left;
 | 
			
		||||
        const idx  = u.cursor.idx;
 | 
			
		||||
 | 
			
		||||
        if (opts && opts.cursorMemo) {
 | 
			
		||||
            opts.cursorMemo.set(left, top);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        seriestt.style.display = 'none';
 | 
			
		||||
 | 
			
		||||
        if (left >= 0) {
 | 
			
		||||
            let xVal = u.data[0][idx];
 | 
			
		||||
 | 
			
		||||
            const vtp = (resolution === 'day') ? 'DAY' : ((resolution === 'hour') ? 'HOUR' : 'MINUTE');
 | 
			
		||||
            let ts = '';
 | 
			
		||||
 | 
			
		||||
            if (Number.isInteger(xVal)) {
 | 
			
		||||
                const useTime = resolution === 'hour' || resolution === 'minute';
 | 
			
		||||
                ts = formatIntTimeUtc(xVal * 1000, useTime);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let frag = document.createDocumentFragment();
 | 
			
		||||
            frag.appendChild(renderChartTooltipPart(this.timeLabelColor, null, ts.replace(/\./g, '/')));
 | 
			
		||||
 | 
			
		||||
            for (let i = 1; i <= 12; i++) {
 | 
			
		||||
                frag = this.extendTooltipFragment(i, idx, u.data, defaultVal, u, frag);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (frag.children.length > 1) {
 | 
			
		||||
                seriestt.replaceChildren(frag);
 | 
			
		||||
 | 
			
		||||
                let val = null;
 | 
			
		||||
                let lvl = 1;
 | 
			
		||||
 | 
			
		||||
                const lim = Math.min(u.data.length - 1, 12);
 | 
			
		||||
 | 
			
		||||
                for (let i = 1; i <= lim; i++) {
 | 
			
		||||
                    if (u.data[i][idx] > val) {
 | 
			
		||||
                        val = u.data[i][idx];
 | 
			
		||||
                        lvl = i;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                val = (val !== null && val != undefined) ? val : defaultVal;
 | 
			
		||||
 | 
			
		||||
                seriestt.style.top = Math.round(u.valToPos(val, u.series[lvl].scale)) + 'px';
 | 
			
		||||
                seriestt.style.left = Math.round(u.valToPos(xVal, vtp)) + 'px';
 | 
			
		||||
                seriestt.style.display = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [seriestt, opts];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tooltipInit(u, options, data) {
 | 
			
		||||
        let over = u.over;
 | 
			
		||||
 | 
			
		||||
        let tt = document.createElement('div');
 | 
			
		||||
        tt.className = 'tooltipline';
 | 
			
		||||
        tt.textContent = '';
 | 
			
		||||
        tt.style.pointerEvents = 'none';
 | 
			
		||||
        tt.style.position = 'absolute';
 | 
			
		||||
        tt.style.background = 'rgba(0,0,0,1)';
 | 
			
		||||
        over.appendChild(tt);
 | 
			
		||||
 | 
			
		||||
        over.addEventListener('mouseleave', () => {
 | 
			
		||||
            if (!u.cursor._lock) {
 | 
			
		||||
                tt.style.display = 'none';
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        over.addEventListener('mouseenter', () => {
 | 
			
		||||
            tt.style.display = u.data.length > 1 ? null : 'none';
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        tt.style.display = (u.cursor.left < 0) ? 'none' : null;
 | 
			
		||||
 | 
			
		||||
        return tt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    extendTooltipFragment(lvl, idx, data, defaultVal, u, frag) {
 | 
			
		||||
        if (data.length > lvl) {
 | 
			
		||||
            let series = u.series[lvl];
 | 
			
		||||
            let val = (idx !== null) ? data[lvl][idx] : data[lvl];
 | 
			
		||||
            val = (val !== null && val != undefined) ? val : defaultVal;
 | 
			
		||||
 | 
			
		||||
            frag.appendChild(document.createElement('br'));
 | 
			
		||||
            frag.appendChild(renderChartTooltipPart(series.stroke(), series.label, val));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return frag;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,74 @@
 | 
			
		||||
import {BaseChart}  from './BaseChart.js?v=2';
 | 
			
		||||
import {
 | 
			
		||||
    COLOR_MAP,
 | 
			
		||||
    X_AXIS_SERIFS,
 | 
			
		||||
} from '../utils/Constants.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class BaseLineChart extends BaseChart {
 | 
			
		||||
    getSeries() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.getDaySeries(),
 | 
			
		||||
            this.getSingleSeries('Total events', 'green'),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getSingleSeries(label, color) {
 | 
			
		||||
        return {
 | 
			
		||||
            label:  label,
 | 
			
		||||
            scale:  'EVENTS',
 | 
			
		||||
            value:  (u, v) => Number(v.toFixed(0)).toLocaleString(),
 | 
			
		||||
            points: {
 | 
			
		||||
                space: 0,
 | 
			
		||||
                fill: COLOR_MAP[color].main,
 | 
			
		||||
            },
 | 
			
		||||
            stroke: COLOR_MAP[color].main,
 | 
			
		||||
            fill:   COLOR_MAP[color].light,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getAxisConfig() {
 | 
			
		||||
        const axes = super.getAxisConfig();
 | 
			
		||||
 | 
			
		||||
        axes.x.space = function(self, axisIdx, scaleMin, scaleMax, plotDim) {
 | 
			
		||||
            let rangeDays   = (scaleMax - scaleMin) / 86400;
 | 
			
		||||
            if (rangeDays > X_AXIS_SERIFS) rangeDays = X_AXIS_SERIFS;
 | 
			
		||||
            const pxPerDay = plotDim / rangeDays;
 | 
			
		||||
 | 
			
		||||
            return pxPerDay;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        axes.y.scale    = 'EVENTS';
 | 
			
		||||
        axes.y.side     = 3;
 | 
			
		||||
        axes.y.split    = u => [
 | 
			
		||||
            u.series[1].min,
 | 
			
		||||
            u.series[1].max,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        return axes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getOptions(resolution = 'day') {
 | 
			
		||||
        return super.getOptions(resolution, '—');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // invert lines order to keep originally first line on top layer
 | 
			
		||||
    seriesResolutionShift(series, resolution) {
 | 
			
		||||
        if (resolution === 'hour') {
 | 
			
		||||
            series[0].label = 'Hour';
 | 
			
		||||
            series[0].scale = 'HOUR';
 | 
			
		||||
            series[0].value = '{YYYY}-{MM}-{DD} {HH}:{mm}';
 | 
			
		||||
        } else if (resolution === 'minute') {
 | 
			
		||||
            series[0].label = 'Minute';
 | 
			
		||||
            series[0].scale = 'MINUTE';
 | 
			
		||||
            series[0].value = '{YYYY}-{MM}-{DD} {HH}:{mm}';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const inverted = [series[0]].concat(series.slice(1).reverse());
 | 
			
		||||
 | 
			
		||||
        return inverted;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getData(data) {
 | 
			
		||||
        return [data[0]].concat(data.slice(1).reverse());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,137 @@
 | 
			
		||||
import {Loader} from '../Loader.js?v=2';
 | 
			
		||||
import {BaseChart}  from './BaseChart.js?v=2';
 | 
			
		||||
import {
 | 
			
		||||
    COLOR_LIGHT_GREEN,
 | 
			
		||||
    COLOR_GREEN,
 | 
			
		||||
    X_AXIS_SERIFS,
 | 
			
		||||
} from '../utils/Constants.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class BaseSparklineChart extends BaseChart {
 | 
			
		||||
    constructor(chartParams) {
 | 
			
		||||
        super(chartParams);
 | 
			
		||||
 | 
			
		||||
        this.charts = null;
 | 
			
		||||
 | 
			
		||||
        if (!this.loaders) {
 | 
			
		||||
            this.loaders = [];
 | 
			
		||||
            this.elems.forEach(el => {this.loaders[el] = new Loader();});
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getOptions() {
 | 
			
		||||
        const tooltipsPlugin = this.tooltipsPlugin({cursorMemo: this.cursorMemo}, 'day', '0');
 | 
			
		||||
        return {
 | 
			
		||||
            width: 200,
 | 
			
		||||
            height: 30,
 | 
			
		||||
            pxAlign: false,
 | 
			
		||||
            cursor: {
 | 
			
		||||
                show: false
 | 
			
		||||
            },
 | 
			
		||||
            select: {
 | 
			
		||||
                show: false,
 | 
			
		||||
            },
 | 
			
		||||
            legend: {
 | 
			
		||||
                show: false,
 | 
			
		||||
            },
 | 
			
		||||
            scales: {
 | 
			
		||||
                x: {time: false},
 | 
			
		||||
            },
 | 
			
		||||
            axes: [
 | 
			
		||||
                {show: false},
 | 
			
		||||
                {show: false}
 | 
			
		||||
            ],
 | 
			
		||||
            cursor: this.cursorMemo.get(),
 | 
			
		||||
            plugins: [tooltipsPlugin],
 | 
			
		||||
            series: [
 | 
			
		||||
                {
 | 
			
		||||
                    label: 'Day',
 | 
			
		||||
                    scale: 'DAY',
 | 
			
		||||
                    value: '{YYYY}-{MM}-{DD}',
 | 
			
		||||
                    stroke: '#8180a0',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    label: 'This week',
 | 
			
		||||
                    stroke: COLOR_GREEN,
 | 
			
		||||
                    fill: COLOR_LIGHT_GREEN,
 | 
			
		||||
                    points: {show: false}
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    label: 'Previous week',
 | 
			
		||||
                    stroke: 'rgba(129,128,160,0.7)',
 | 
			
		||||
                    fill: 'rgba(129,128,160,0.03)',
 | 
			
		||||
                    points: {show: false}
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onChartLoaded(data, status, resolution) {
 | 
			
		||||
        if ('success' == status) {
 | 
			
		||||
            data = this.getData(data);
 | 
			
		||||
 | 
			
		||||
            this.stopLoader();
 | 
			
		||||
 | 
			
		||||
            this.charts = [];
 | 
			
		||||
 | 
			
		||||
            this.elems.forEach(el => {
 | 
			
		||||
                const lines = [data.time, data[el], data[el + 'Prev']];
 | 
			
		||||
                this.charts.push(new uPlot(this.getOptions(), lines, this.getChartBlock(el)));
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    startLoader() {
 | 
			
		||||
        if (!this.loaders) {
 | 
			
		||||
            this.loaders = [];
 | 
			
		||||
            this.elems.forEach(el => this.loaders[el] = new Loader());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.elems.forEach(name => {
 | 
			
		||||
            const el = document.createElement('p');
 | 
			
		||||
            const block = this.getChartBlock(name);
 | 
			
		||||
 | 
			
		||||
            block.classList.remove('is-hidden');
 | 
			
		||||
            block.replaceChildren(el);
 | 
			
		||||
 | 
			
		||||
            const p = block.querySelector('p');
 | 
			
		||||
 | 
			
		||||
            this.loaders[name].start(p);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    stopLoader() {
 | 
			
		||||
        this.elems.forEach(el => {
 | 
			
		||||
            this.loaders[el].stop();
 | 
			
		||||
            this.getChartBlock(el).querySelector('p').classList.add('is-hidden');
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getData(data) {
 | 
			
		||||
        return {
 | 
			
		||||
            'time':                 data[0],
 | 
			
		||||
            'totalDevices':         data[1],
 | 
			
		||||
            'totalIps':             data[2],
 | 
			
		||||
            'totalSessions':        data[3],
 | 
			
		||||
            'totalEvents':          data[4],
 | 
			
		||||
            'totalDevicesPrev':     data[5],
 | 
			
		||||
            'totalIpsPrev':         data[6],
 | 
			
		||||
            'totalSessionsPrev':    data[7],
 | 
			
		||||
            'totalEventsPrev':      data[8],
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getChartBlock(cls) {
 | 
			
		||||
        return document.querySelector(`td.${cls} p.session-stat`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get chartBlocks() {
 | 
			
		||||
        const result = {};
 | 
			
		||||
        this.elems.forEach(el => {result[el] = this.getChartBlock(el);});
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get elems() {
 | 
			
		||||
        return ['totalDevices', 'totalIps', 'totalSessions', 'totalEvents'];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
import {BaseLineChart} from './BaseLine.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class BlacklistChart extends BaseLineChart {
 | 
			
		||||
    getSeries() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.getDaySeries(),
 | 
			
		||||
            this.getSingleSeries('Blacklisted identities', 'red'),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
import {BaseLineChart} from './BaseLine.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class BotsChart extends BaseLineChart {
 | 
			
		||||
    getSeries() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.getDaySeries(),
 | 
			
		||||
            this.getSingleSeries('Bots', 'red'),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
import {BaseLineChart} from './BaseLine.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class DomainsChart extends BaseLineChart {
 | 
			
		||||
    getSeries() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.getDaySeries(),
 | 
			
		||||
            this.getSingleSeries('Total domains', 'green'),
 | 
			
		||||
            this.getSingleSeries('New domains', 'yellow'),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
import {BaseLineChart} from './BaseLine.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class EventsChart extends BaseLineChart {
 | 
			
		||||
    getSeries() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.getDaySeries(),
 | 
			
		||||
            this.getSingleSeries('Regular events', 'green'),
 | 
			
		||||
            this.getSingleSeries('Warning events', 'yellow'),
 | 
			
		||||
            this.getSingleSeries('Alert events', 'red'),
 | 
			
		||||
            this.getSingleSeries('Unauthenticated events', 'purple'),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
import {BaseLineChart} from './BaseLine.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class IpsChart extends BaseLineChart {
 | 
			
		||||
    getSeries() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.getDaySeries(),
 | 
			
		||||
            this.getSingleSeries('Residential', 'green'),
 | 
			
		||||
            this.getSingleSeries('Privacy', 'yellow'),
 | 
			
		||||
            this.getSingleSeries('Suspicious', 'red'),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
import {BaseLineChart} from './BaseLine.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class IspsChart extends BaseLineChart {
 | 
			
		||||
    getSeries() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.getDaySeries(),
 | 
			
		||||
            this.getSingleSeries('Total ISPs', 'green'),
 | 
			
		||||
            this.getSingleSeries('New ISPs', 'yellow'),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
import {BaseLineChart} from './BaseLine.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class LogbookChart extends BaseLineChart {
 | 
			
		||||
    getSeries() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.getDaySeries(),
 | 
			
		||||
            this.getSingleSeries('Success', 'green'),
 | 
			
		||||
            this.getSingleSeries('Validation issues', 'yellow'),
 | 
			
		||||
            this.getSingleSeries('Failed', 'red'),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
import {BaseLineChart} from './BaseLine.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class ResourcesChart extends BaseLineChart {
 | 
			
		||||
    getSeries() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.getDaySeries(),
 | 
			
		||||
            this.getSingleSeries('200', 'green'),
 | 
			
		||||
            this.getSingleSeries('404', 'yellow'),
 | 
			
		||||
            this.getSingleSeries('403 & 500', 'red'),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
import {BaseLineChart} from './BaseLine.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class ReviewQueueChart extends BaseLineChart {
 | 
			
		||||
    getSeries() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.getDaySeries(),
 | 
			
		||||
            this.getSingleSeries('Whitelisted', 'green'),
 | 
			
		||||
            this.getSingleSeries('In review', 'yellow'),
 | 
			
		||||
            this.getSingleSeries('Blacklisted', 'red'),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
import {BaseLineChart} from './BaseLine.js?v=2';
 | 
			
		||||
 | 
			
		||||
export class UsersChart extends BaseLineChart {
 | 
			
		||||
    getSeries() {
 | 
			
		||||
        return [
 | 
			
		||||
            this.getDaySeries(),
 | 
			
		||||
            this.getSingleSeries('High trust', 'green'),
 | 
			
		||||
            this.getSingleSeries('Average trust', 'yellow'),
 | 
			
		||||
            this.getSingleSeries('In review', 'red'),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user