const config = { api: { history: '/api/history?owner=' } }; let currentChart = null; async function renderChart(owner) { const root = document.getElementById('modal-stats-root'); if (!root) return; try { const response = await fetch(`${config.api.history}${encodeURIComponent(owner)}`); if (!response.ok) throw new Error('Network error'); const historyData = await response.json(); if (!historyData || historyData.length === 0) { root.innerHTML = '
No history available.
'; return; } const parseUploadValue = (value) => { if (typeof value !== 'string') return 0; const num = parseFloat(value.replace(/,/g, '').replace(/TiB|GiB|MiB/i, '').trim()); if (isNaN(num)) return 0; const lowerVal = value.toLowerCase(); if (lowerVal.includes('tib')) return num; if (lowerVal.includes('gib')) return num / 1024; if (lowerVal.includes('mib')) return num / 1024 / 1024; return num; }; const series = [ { name: 'Upload (TiB)', data: historyData.map(r => ({ x: new Date(r.timestamp).getTime(), y: parseUploadValue(r.upload) })) }, { name: 'Rank', data: historyData.map(r => ({ x: new Date(r.timestamp).getTime(), y: r.rank })) }, { name: 'Points', data: historyData.map(r => ({ x: new Date(r.timestamp).getTime(), y: r.points })) }, { name: 'Seeding', data: historyData.map(r => ({ x: new Date(r.timestamp).getTime(), y: r.seeding_count })) } ]; root.innerHTML = ''; const options = { series: series, chart: { type: 'line', height: '100%', width: '100%', background: 'transparent', foreColor: '#71717a', fontFamily: 'Inter, system-ui, sans-serif', toolbar: { show: false }, animations: { enabled: true, easing: 'easeinout', speed: 800 } }, theme: { mode: 'dark' }, colors: ['#FFFFFF', '#10b981', '#3b82f6', '#f43f5e'], stroke: { curve: 'smooth', width: [3, 2, 2, 2], dashArray: 0 }, grid: { borderColor: '#27272a', strokeDashArray: 4, padding: { top: 20, bottom: 0, left: 20, right: 20 }, xaxis: { lines: { show: true } }, yaxis: { lines: { show: true } } }, xaxis: { type: 'datetime', labels: { style: { fontSize: '10px', fontWeight: 600 }, datetimeUTC: false }, axisBorder: { show: false }, axisTicks: { show: false } }, yaxis: [ { seriesName: 'Upload (TiB)', labels: { style: { colors: '#FFF', fontSize: '11px', fontWeight: 600 }, formatter: (v) => v != null ? v.toFixed(2) : '' } }, { seriesName: 'Rank', opposite: true, reversed: true, labels: { style: { colors: '#10b981', fontSize: '11px', fontWeight: 600 }, formatter: (v) => v != null ? '#' + Math.round(v) : '' } }, { seriesName: 'Points', show: false }, { seriesName: 'Seeding', show: false } ], legend: { position: 'bottom', fontSize: '12px', fontWeight: 600, markers: { width: 8, height: 8, radius: 10 }, itemMargin: { horizontal: 15, vertical: 10 } }, tooltip: { shared: true, theme: 'dark', x: { format: 'dd MMM yyyy HH:mm' }, y: { formatter: (val, { seriesIndex }) => { if (val == null) return '--'; switch (seriesIndex) { case 0: return val.toFixed(3) + ' TiB'; case 1: return '#' + Math.round(val); default: return Math.round(val).toLocaleString(); } } } }, markers: { size: 0, hover: { size: 5 } } }; if (currentChart) currentChart.destroy(); currentChart = new ApexCharts(document.getElementById('chart-mount'), options); currentChart.render(); } catch (e) { root.innerHTML = `Error: ${e.message}