- 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.
		
			
				
	
	
		
			381 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			381 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
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;
 | 
						|
    }
 | 
						|
}
 |