var chartOptions = { colors:['#008ffb', '#f08681'], series: [ { name: 'Ping', data: [null] }, { name: 'Pakke Tap', type: 'area', data: [null] } ], chart: { height: '100%', id: 'realtime', type: 'area', animations: { enabled: false, }, toolbar: { show: false }, zoom: { enabled: false }, }, dataLabels: { enabled: false, }, stroke: { curve: 'straight' }, markers: { size: 0 }, xaxis: { type: 'datetime', labels: { datetimeUTC: false, }, }, yaxis: [ { seriesName: 'Ping', labels: { formatter: function (val) { if (val == null) { return 0; } return val.toFixed(1); }, } }, { seriesName: 'Pakke Tap', min:0, max:100, opposite: true, labels: { formatter: function (val) { if (val == null) { return 0 + '%'; } return val.toFixed(0) + "%"; }, } } ], legend: { show: false }, }; var timeRangeInSeconds = 0; var chartLatest; var chartHasFetchedTimeRange = false; var chartElement; var chartPingSeries = []; var chartPacketLossSeries = []; var scanId; const queryParam = new Proxy(new URLSearchParams(window.location.search), { get: (searchParams, prop) => searchParams.get(prop), }); document.body.onload = async function() { chartElement = new ApexCharts(document.querySelector("#liveChart"), chartOptions); chartElement.render(); timeRangeInSeconds = timeRangeSelector[timeRangeSelector.selectedIndex].attributes.value.nodeValue; await update_summary(); update_chart(); window.setInterval(async function() { update_summary(); update_chart(); }, 5000) }; timeRangeSelector.onchange = function(event) { timeRangeInSeconds = event.srcElement[event.srcElement.selectedIndex].attributes.value.nodeValue; if (timeRangeInSeconds == 'Details') { if (queryParam.ref_uuid !== null) { window.location.href = './?p=scan_details&ref_uuid=' + queryParam.ref_uuid + '&scan_id=' + scanId; } else { window.location.href = './?p=scan_details&host=' + queryParam.host + '&scan_id=' + scanId; } } // TODO: a very rare race condition may occur when switching too fast between time ranges can cause the chart to show wrong data // cancel the update interval and start it again // reset chart chartHasFetchedTimeRange = false; chartPingSeries = []; chartPacketLossSeries = []; chartElement.updateSeries([ { data: chartPingSeries }, { data: chartPacketLossSeries } ]); update_chart(); update_summary(); }; async function update_summary() { if (queryParam.ref_uuid !== null) { var response = await fetch('./?p=api_stats&ref_uuid=' + queryParam.ref_uuid + '&since=' + (Math.floor(Date.now() / 1000) - timeRangeInSeconds)); } else { var response = await fetch('./?p=api_stats&host=' + queryParam.host + '&since=' + (Math.floor(Date.now() / 1000) - timeRangeInSeconds)); } if (response.status == 400) { setTimeout(() => { window.location.reload(); }, 1000); return; } if (response.status !== 200) { return; } let jsonResponse = await response.json(); scanId = jsonResponse.scan_id; let resp_ping = jsonResponse.ping_latest; let resp_packet_loss = jsonResponse.packet_loss_percentage; let resp_uptime = jsonResponse.uptime_percentage; let resp_connection_status = jsonResponse.connection_status; if (resp_ping == null) { ping.innerHTML = 'Feilet'; } else { ping.innerHTML = resp_ping + ' ms'; } packet_loss.innerHTML = resp_packet_loss + ' %'; uptime.innerHTML = resp_uptime + ' %'; if (resp_connection_status == 'ONLINE') { connection_status.className = 'sign-text alert-success' connection_status.innerHTML = 'Online'; } if (resp_connection_status == 'UNSTABLE') { connection_status.className = 'sign-text alert-warning' connection_status.innerHTML = 'Ustabilt'; } if (resp_connection_status == 'OFFLINE') { connection_status.className = 'sign-text alert-danger' connection_status.innerHTML = 'Offline'; } } async function update_chart() { if (!chartHasFetchedTimeRange) { var response = await fetch('./?p=api_chart&limit_length=1000&scan_id=' + scanId + '&since=' + (Math.floor(Date.now() / 1000) - timeRangeInSeconds)); chartHasFetchedTimeRange = true; } else { var response = await fetch('./?p=api_chart&scan_id=' + scanId + '&since=' + chartLatest); } if (response.status === 204) { return; } let jsonResponse = await response.json(); chartLatest = jsonResponse.latest; jsonResponse.pings.forEach(ping => { let newDate = new Date(ping.date_added + ' UTC'); chartPingSeries.push({ x: newDate.toString(), y: ping.ping_avg}); }); // remove old pings for (let i = 0; i < chartPingSeries.length; i++) { let pingEpoch = Math.floor((new Date(chartPingSeries[i].x)).getTime() / 1000); if (pingEpoch < chartLatest - timeRangeInSeconds) { chartPingSeries.splice(i, 1); } } jsonResponse.pings.forEach(ping => { let newDate = new Date(ping.date_added + ' UTC'); if (ping.pkt_transmitted != ping.pkt_received) { let pktLossPercentage = ping.pkt_received / ping.pkt_transmitted * 100; pktLossPercentage = 100 - pktLossPercentage; chartPacketLossSeries.push({ x: newDate.toString(), y: pktLossPercentage}); } else { chartPacketLossSeries.push({ x: newDate.toString(), y: 0}); } }); for (let i = 0; i < chartPacketLossSeries.length; i++) { let pingEpoch = Math.floor((new Date(chartPacketLossSeries[i].x)).getTime() / 1000); if (pingEpoch < chartLatest - timeRangeInSeconds) { chartPacketLossSeries.splice(i, 1); } } chartElement.updateSeries([ { name: 'Ping', data: chartPingSeries }, { name: 'Pakke Tap', data: chartPacketLossSeries } ]); }