poc2_worked
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
(() => {
|
||||
'use strict';
|
||||
|
||||
const COLORS = {
|
||||
grid:'#202126', text:'#777780', strong:'#d4d4d8', green:'#3ecf8e', blue:'#60a5fa', red:'#f87171', amber:'#fbbf24',
|
||||
palette:['#3ecf8e','#60a5fa','#fbbf24','#a78bfa','#f87171','#22d3ee','#fb7185','#94a3b8']
|
||||
};
|
||||
|
||||
function setup(canvas) {
|
||||
if (!canvas || !canvas.isConnected || canvas.offsetParent === null) return null;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
if (rect.width < 20) return null;
|
||||
const DPR = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));
|
||||
const width = Math.max(1, Math.floor(rect.width));
|
||||
const cssHeight = Number.parseFloat(getComputedStyle(canvas).height) || 0;
|
||||
const height = Math.max(170, Math.floor(cssHeight || Number(canvas.getAttribute('height')) || 220));
|
||||
canvas.width = Math.floor(width * DPR);
|
||||
canvas.height = Math.floor(height * DPR);
|
||||
canvas.style.height = `${height}px`;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
|
||||
return {ctx, width, height};
|
||||
}
|
||||
|
||||
function seriesMax(rows, key) {
|
||||
return Math.max(1, ...rows.map(row => Number(row?.[key] || 0)));
|
||||
}
|
||||
|
||||
function formatTick(ts) {
|
||||
const d = new Date(Number(ts || 0));
|
||||
return d.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'});
|
||||
}
|
||||
|
||||
function compactNumber(value) {
|
||||
const n = Number(value || 0);
|
||||
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
||||
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
|
||||
return String(Math.round(n));
|
||||
}
|
||||
|
||||
function formatRate(value) {
|
||||
let n = Math.max(0, Number(value || 0));
|
||||
const units = ['bps','Kbps','Mbps','Gbps','Tbps'];
|
||||
let i = 0;
|
||||
while (n >= 1000 && i < units.length - 1) { n /= 1000; i++; }
|
||||
return `${n < 10 && i ? n.toFixed(1) : Math.round(n)} ${units[i]}`;
|
||||
}
|
||||
|
||||
function drawGrid(ctx, width, height, pad, rows) {
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = COLORS.grid;
|
||||
ctx.fillStyle = COLORS.text;
|
||||
ctx.font = '10px ui-sans-serif, system-ui';
|
||||
for (let i = 0; i <= 4; i++) {
|
||||
const y = pad.t + ((height - pad.t - pad.b) / 4) * i;
|
||||
ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(width - pad.r, y); ctx.stroke();
|
||||
}
|
||||
const labelIndexes = [0, Math.floor((rows.length - 1) / 2), rows.length - 1];
|
||||
ctx.textBaseline = 'bottom';
|
||||
labelIndexes.forEach((idx, i) => {
|
||||
if (!rows[idx]) return;
|
||||
const x = pad.l + ((width - pad.l - pad.r) * idx / Math.max(rows.length - 1, 1));
|
||||
ctx.textAlign = i === 0 ? 'left' : (i === labelIndexes.length - 1 ? 'right' : 'center');
|
||||
ctx.fillText(formatTick(rows[idx].ts_ms), x, height - 2);
|
||||
});
|
||||
}
|
||||
|
||||
function drawLine(ctx, rows, key, max, width, height, pad, color, fillAlpha=0) {
|
||||
if (!rows.length || !(Number(max) > 0)) return;
|
||||
const iw = width - pad.l - pad.r;
|
||||
const ih = height - pad.t - pad.b;
|
||||
const points = rows.map((row, idx) => ({
|
||||
x: pad.l + iw * idx / Math.max(rows.length - 1, 1),
|
||||
y: pad.t + ih - (Number(row[key] || 0) / Number(max)) * ih,
|
||||
}));
|
||||
if (fillAlpha) {
|
||||
const grad = ctx.createLinearGradient(0, pad.t, 0, height - pad.b);
|
||||
grad.addColorStop(0, hexToRgba(color, fillAlpha)); grad.addColorStop(1, hexToRgba(color, 0));
|
||||
ctx.fillStyle = grad; ctx.beginPath(); ctx.moveTo(points[0].x, height - pad.b);
|
||||
points.forEach(p => ctx.lineTo(p.x, p.y)); ctx.lineTo(points.at(-1).x, height - pad.b); ctx.closePath(); ctx.fill();
|
||||
}
|
||||
ctx.strokeStyle = color; ctx.lineWidth = 1.6; ctx.lineJoin = 'round'; ctx.lineCap = 'round';
|
||||
ctx.beginPath(); points.forEach((p, idx) => idx ? ctx.lineTo(p.x, p.y) : ctx.moveTo(p.x, p.y)); ctx.stroke();
|
||||
}
|
||||
|
||||
function hexToRgba(hex, alpha) {
|
||||
const value = hex.replace('#','');
|
||||
const n = parseInt(value, 16);
|
||||
return `rgba(${(n>>16)&255},${(n>>8)&255},${n&255},${alpha})`;
|
||||
}
|
||||
|
||||
function normalizedDonutRows(rows) {
|
||||
const positive = (rows || []).map(row => ({
|
||||
name:String(row?.name || 'unknown'), count:Math.max(0, Number(row?.count || 0))
|
||||
})).filter(row => row.count > 0);
|
||||
if (positive.length <= 6) return positive;
|
||||
const head = positive.slice(0, 5);
|
||||
const other = positive.slice(5).reduce((sum, row) => sum + row.count, 0);
|
||||
if (other) head.push({name:'other', count:other});
|
||||
return head;
|
||||
}
|
||||
|
||||
function drawDonut(canvas, rows) {
|
||||
if (!canvas) return;
|
||||
const prepared = setup(canvas); if (!prepared) return;
|
||||
const {ctx,width,height} = prepared;
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
const items = normalizedDonutRows(rows);
|
||||
const total = items.reduce((sum, row) => sum + row.count, 0);
|
||||
if (!total) {
|
||||
ctx.fillStyle = COLORS.text; ctx.font = '11px ui-sans-serif, system-ui'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
||||
ctx.fillText('No data in this window', width / 2, height / 2);
|
||||
return;
|
||||
}
|
||||
|
||||
const wide = width >= 360;
|
||||
const cx = wide ? Math.min(width * .34, 135) : width / 2;
|
||||
const cy = wide ? height / 2 : Math.min(88, height * .42);
|
||||
const radius = Math.min(70, Math.max(48, Math.min(width * .22, height * .32)));
|
||||
const inner = radius * .64;
|
||||
let angle = -Math.PI / 2;
|
||||
items.forEach((row, idx) => {
|
||||
const portion = row.count / total;
|
||||
const end = angle + portion * Math.PI * 2;
|
||||
ctx.beginPath(); ctx.arc(cx, cy, radius, angle, end); ctx.arc(cx, cy, inner, end, angle, true); ctx.closePath();
|
||||
ctx.fillStyle = COLORS.palette[idx % COLORS.palette.length]; ctx.fill();
|
||||
angle = end;
|
||||
});
|
||||
|
||||
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = COLORS.strong; ctx.font = '600 18px ui-sans-serif, system-ui'; ctx.fillText(compactNumber(total), cx, cy - 5);
|
||||
ctx.fillStyle = COLORS.text; ctx.font = '9px ui-sans-serif, system-ui'; ctx.fillText('events', cx, cy + 14);
|
||||
|
||||
const legendX = wide ? Math.min(width * .60, cx + radius + 35) : 14;
|
||||
const legendY = wide ? Math.max(18, cy - Math.min(items.length * 16, 84) / 2) : Math.min(height - 74, cy + radius + 16);
|
||||
const legendWidth = wide ? Math.max(90, width - legendX - 12) : width - 28;
|
||||
items.forEach((row, idx) => {
|
||||
const y = legendY + idx * 18;
|
||||
ctx.fillStyle = COLORS.palette[idx % COLORS.palette.length]; ctx.fillRect(legendX, y + 3, 7, 7);
|
||||
ctx.fillStyle = COLORS.text; ctx.font = '10px ui-sans-serif, system-ui'; ctx.textAlign = 'left'; ctx.textBaseline = 'top';
|
||||
const label = row.name.length > 18 ? `${row.name.slice(0,17)}…` : row.name;
|
||||
ctx.fillText(label, legendX + 13, y, Math.max(40, legendWidth - 45));
|
||||
ctx.textAlign = 'right';
|
||||
ctx.fillText(`${Math.round(row.count / total * 100)}%`, legendX + legendWidth, y);
|
||||
});
|
||||
}
|
||||
|
||||
function drawLoading(canvas, label='Building selected range…') {
|
||||
if (!canvas) return;
|
||||
const prepared = setup(canvas); if (!prepared) return;
|
||||
const {ctx,width,height} = prepared;
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.strokeStyle = COLORS.grid; ctx.lineWidth = 1;
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const y = height * i / 4;
|
||||
ctx.beginPath(); ctx.moveTo(12, y); ctx.lineTo(width - 12, y); ctx.stroke();
|
||||
}
|
||||
ctx.fillStyle = COLORS.text; ctx.font = '11px ui-sans-serif, system-ui'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
||||
ctx.fillText(label, width / 2, height / 2);
|
||||
}
|
||||
|
||||
function drawTraffic(canvas, rows) {
|
||||
if (!canvas) return;
|
||||
const prepared = setup(canvas); if (!prepared) return;
|
||||
const {ctx,width,height} = prepared; const pad={l:8,r:8,t:12,b:22};
|
||||
drawGrid(ctx,width,height,pad,rows);
|
||||
drawLine(ctx, rows, 'bytes', seriesMax(rows,'bytes'), width,height,pad,COLORS.blue,.10);
|
||||
drawLine(ctx, rows, 'events', seriesMax(rows,'events'), width,height,pad,COLORS.green,.12);
|
||||
}
|
||||
|
||||
function drawThroughput(canvas, rows) {
|
||||
if (!canvas) return;
|
||||
const prepared = setup(canvas); if (!prepared) return;
|
||||
const {ctx,width,height} = prepared; const pad={l:56,r:10,t:12,b:22};
|
||||
drawGrid(ctx,width,height,pad,rows);
|
||||
const max = Math.max(seriesMax(rows,'bps'), seriesMax(rows,'in_bps'), seriesMax(rows,'out_bps'));
|
||||
ctx.fillStyle = COLORS.text; ctx.font = '10px ui-sans-serif, system-ui'; ctx.textAlign = 'right'; ctx.textBaseline = 'middle';
|
||||
if (!(max > 0)) {
|
||||
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
||||
ctx.fillText('No TZSP throughput samples in this window', width / 2, height / 2);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i <= 4; i++) {
|
||||
const value = max * (4 - i) / 4;
|
||||
const y = pad.t + ((height - pad.t - pad.b) / 4) * i;
|
||||
ctx.fillText(formatRate(value), pad.l - 7, y);
|
||||
}
|
||||
// Total is always available from raw TZSP byte counters, even when LAN
|
||||
// direction classification is not configured correctly.
|
||||
drawLine(ctx, rows, 'bps', max, width,height,pad,COLORS.amber,.04);
|
||||
drawLine(ctx, rows, 'in_bps', max, width,height,pad,COLORS.blue,.05);
|
||||
drawLine(ctx, rows, 'out_bps', max, width,height,pad,COLORS.green,.04);
|
||||
}
|
||||
|
||||
function drawEvents(canvas, rows) {
|
||||
if (!canvas) return;
|
||||
const prepared = setup(canvas); if (!prepared) return;
|
||||
const {ctx,width,height} = prepared; const pad={l:8,r:8,t:12,b:22};
|
||||
drawGrid(ctx,width,height,pad,rows);
|
||||
const max = Math.max(seriesMax(rows,'events'), seriesMax(rows,'alerts'));
|
||||
drawLine(ctx, rows, 'events', max, width,height,pad,COLORS.green,.10);
|
||||
drawLine(ctx, rows, 'alerts', max, width,height,pad,COLORS.red,0);
|
||||
}
|
||||
|
||||
window.MikroSuricataCharts = {drawTraffic, drawThroughput, drawEvents, drawDonut, drawLoading};
|
||||
})();
|
||||
Reference in New Issue
Block a user