From 99e0e497c2306cd40126c4896432604e933fc7e5 Mon Sep 17 00:00:00 2001 From: "L. Bradley LaBoon" Date: Mon, 3 Apr 2023 20:01:50 -0400 Subject: [PATCH] Basic graph implementation --- global.css | 18 ++ global.js | 59 +++++- include/linode_subnav.html | 1 - linodes/dashboard/dashboard.css | 53 ++++- linodes/dashboard/dashboard.js | 335 ++++++++++++++++++++++++++++---- linodes/dashboard/index.shtml | 139 +++++++++++++ linodes/graphs/graphs.css | 22 --- linodes/graphs/graphs.js | 82 -------- linodes/graphs/index.shtml | 35 ---- 9 files changed, 566 insertions(+), 178 deletions(-) delete mode 100644 linodes/graphs/graphs.css delete mode 100644 linodes/graphs/graphs.js delete mode 100644 linodes/graphs/index.shtml diff --git a/global.css b/global.css index 72837d3..e1a9e70 100644 --- a/global.css +++ b/global.css @@ -77,6 +77,24 @@ header { font-weight: bold; } +.lmc-graph canvas { + width: 100%; +} + +.lmc-graph h4 { + text-align: center; +} + +.lmc-graph table { + width: 100%; +} + +.lmc-graph-color { + height: 15px; + display: inline-block; + width: 15px; +} + .lmc-table { background-color: #CECECE; width: 100%; diff --git a/global.js b/global.js index 378a3aa..d895aec 100644 --- a/global.js +++ b/global.js @@ -452,6 +452,63 @@ function displayUser(response) profilePic.style = "display: initial;"; } +// Draw timeseries data with the given canvas in the given color and fill +// series is an array of objects, with each object containing the color/fill settings and an array of data points +function drawSeries(series, canvas) +{ + // Compute scale and totals + var xMin = series[0].points[0][0]; + var xMax = series[0].points[series[0].points.length - 1][0]; + var yMax = 0; + for (var i = 0; i < series.length; i++) { + xMin = Math.min(xMin, series[i].points[0][0]); + xMax = Math.max(xMax, series[i].points[series[i].points.length - 1][0]); + series[i].max = 0, series[i].avg = 0; + for (var j = 0; j < series[i].points.length; j++) { + series[i].max = Math.max(series[i].max, series[i].points[j][1]); + series[i].avg += series[i].points[j][1]; + } + series[i].avg /= series[i].points.length; + yMax = Math.max(yMax, series[i].max); + } + xMax -= xMin; + + // Setup drawing context + var ctx = canvas.getContext("2d"); + ctx.lineWidth = 1; + + // Clear the canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + + for (var i = 0; i < series.length; i++) { + ctx.fillStyle = series[i].color; + ctx.strokeStyle = series[i].color; + + // Draw data + ctx.beginPath(); + ctx.moveTo((series[i].points[0][0] - xMin) / xMax * canvas.width, canvas.height - (series[i].points[0][1] / yMax * canvas.height)); + for (var j = 1; j < series[i].points.length; j++) + ctx.lineTo((series[i].points[j][0] - xMin) / xMax * canvas.width, canvas.height - (series[i].points[j][1] / yMax * canvas.height)); + if (series[i].fill) { + ctx.lineTo((series[i].points[series[i].points.length-1][0] - xMin) / xMax * canvas.width, canvas.height); + ctx.lineTo((series[i].points[0][0] - xMin) / xMax * canvas.width, canvas.height); + ctx.closePath(); + ctx.fill(); + } else { + ctx.stroke(); + } + } + + // Draw axis lines + ctx.strokeStyle = "black"; + ctx.lineWidth = 2.5; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(0, canvas.height); + ctx.lineTo(canvas.width, canvas.height); + ctx.stroke(); +} + // Return an MD5 hash of the given string function md5(str, binary) { @@ -793,4 +850,4 @@ function translateKernel(slug, element) apiGet("/linode/kernels/" + slug, callback, null); } -export { settings, elements, regionNames, apiDelete, apiGet, apiPost, apiPut, md5, migrateETA, oauthPost, oauthScopes, objPut, parseParams, setupHeader, eventTitles, timeString, translateKernel }; +export { settings, elements, regionNames, apiDelete, apiGet, apiPost, apiPut, drawSeries, md5, migrateETA, oauthPost, oauthScopes, objPut, parseParams, setupHeader, eventTitles, timeString, translateKernel }; diff --git a/include/linode_subnav.html b/include/linode_subnav.html index ab72d4f..8f41a98 100644 --- a/include/linode_subnav.html +++ b/include/linode_subnav.html @@ -6,7 +6,6 @@ Rescue Resize Clone - Graphs Backups Settings diff --git a/linodes/dashboard/dashboard.css b/linodes/dashboard/dashboard.css index 8655485..01af940 100644 --- a/linodes/dashboard/dashboard.css +++ b/linodes/dashboard/dashboard.css @@ -25,10 +25,18 @@ margin-top: 5px; } +canvas { + height: 250px; +} + #config-table tr:last-of-type { border-bottom: 1px solid #E8E8E8; } +#cpu-color { + background-color: #03C; +} + .disk-icon { height: 24px; width: 26px; @@ -42,12 +50,51 @@ display: none; } +#graph-range { + font-size: 18px; +} + h3 { border-bottom: 1px solid #E8E8E8; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: lighter; - margin-bottom: 50px; +} + +#io-rate-color { + background-color: #FFD04B; +} + +#ipv4-in-color { + background-color: #03C; +} + +#ipv4-out-color { + background-color: #32CD32; +} + +#ipv4-privin-color { + background-color: #C09; +} + +#ipv4-privout-color { + background-color: #FF9; +} + +#ipv6-in-color { + background-color: #03C; +} + +#ipv6-out-color { + background-color: #32CD32; +} + +#ipv6-privin-color { + background-color: #C09; +} + +#ipv6-privout-color { + background-color: #FF9; } .job-failed { @@ -202,6 +249,10 @@ h3 { float: right; } +#swap-rate-color { + background-color: #FA373E; +} + #upgrade { background-color: #F0F0F0; display: none; diff --git a/linodes/dashboard/dashboard.js b/linodes/dashboard/dashboard.js index 7655746..806ae56 100644 --- a/linodes/dashboard/dashboard.js +++ b/linodes/dashboard/dashboard.js @@ -15,7 +15,7 @@ * along with Linode Manager Classic. If not, see . */ -import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParams, setupHeader, timeString, translateKernel } from "/global.js"; +import { settings, elements, apiDelete, apiGet, apiPost, drawSeries, eventTitles, parseParams, setupHeader, timeString, translateKernel } from "/global.js"; (function() { @@ -26,6 +26,10 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam elements.configRadioName = "config-radio"; elements.configRemovePrefix = "config-remove-"; elements.configTable = "config-table"; + elements.cpuAvg = "cpu-avg"; + elements.cpuGraph = "cpu-graph"; + elements.cpuLast = "cpu-last"; + elements.cpuMax = "cpu-max"; elements.diskIcon = "disk-icon"; elements.diskIconImg = "/img/disk.gif"; elements.diskRemovePrefix = "disk-remove-"; @@ -34,7 +38,44 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam elements.eventRowPrefix = "event-row-"; elements.eventTable = "event-table"; elements.extraEvent = "extra-event"; + elements.graphRange = "graph-range"; elements.info = "info"; + elements.ioGraph = "io-graph"; + elements.ioRateAvg = "io-rate-avg"; + elements.ioRateLast = "io-rate-last"; + elements.ioRateMax = "io-rate-max"; + elements.ipv4Graph = "ipv4-graph"; + elements.ipv4InAvg = "ipv4-in-avg"; + elements.ipv4InLast = "ipv4-in-last"; + elements.ipv4InMax = "ipv4-in-max"; + elements.ipv4OutAvg = "ipv4-out-avg"; + elements.ipv4OutLast = "ipv4-out-last"; + elements.ipv4OutMax = "ipv4-out-max"; + elements.ipv4PrivInAvg = "ipv4-privin-avg"; + elements.ipv4PrivInLast = "ipv4-privin-last"; + elements.ipv4PrivInMax = "ipv4-privin-max"; + elements.ipv4PrivOutAvg = "ipv4-privout-avg"; + elements.ipv4PrivOutLast = "ipv4-privout-last"; + elements.ipv4PrivOutMax = "ipv4-privout-max"; + elements.ipv4Total = "ipv4-total"; + elements.ipv4TotalIn = "ipv4-total-in"; + elements.ipv4TotalOut = "ipv4-total-out"; + elements.ipv6Graph = "ipv6-graph"; + elements.ipv6InAvg = "ipv6-in-avg"; + elements.ipv6InLast = "ipv6-in-last"; + elements.ipv6InMax = "ipv6-in-max"; + elements.ipv6OutAvg = "ipv6-out-avg"; + elements.ipv6OutLast = "ipv6-out-last"; + elements.ipv6OutMax = "ipv6-out-max"; + elements.ipv6PrivInAvg = "ipv6-privin-avg"; + elements.ipv6PrivInLast = "ipv6-privin-last"; + elements.ipv6PrivInMax = "ipv6-privin-max"; + elements.ipv6PrivOutAvg = "ipv6-privout-avg"; + elements.ipv6PrivOutLast = "ipv6-privout-last"; + elements.ipv6PrivOutMax = "ipv6-privout-max"; + elements.ipv6Total = "ipv6-total"; + elements.ipv6TotalIn = "ipv6-total-in"; + elements.ipv6TotalOut = "ipv6-total-out"; elements.jobFailed = "job-failed"; elements.jobInfo = "job-info"; elements.jobNotice = "job-notice"; @@ -64,6 +105,9 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam elements.storageFree = "storage-free"; elements.storageTotal = "storage-total"; elements.storageUsed = "storage-used"; + elements.swapRateAvg = "swap-rate-avg"; + elements.swapRateLast = "swap-rate-last"; + elements.swapRateMax = "swap-rate-max"; elements.transferMonthly = "transfer-monthly"; elements.transferOverage = "transfer-overage"; elements.transferUsed = "transfer-used"; @@ -80,6 +124,7 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam data.linodeTransfer = {}; data.notifications = []; data.plan = {}; + data.stats = {}; data.volumes = []; // Static references to UI elements @@ -87,9 +132,50 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam ui.backups = {}; ui.boot = {}; ui.configTable = {}; + ui.cpuAvg = {}; + ui.cpuGraph = {}; + ui.cpuLast = {}; + ui.cpuMax = {}; ui.diskTable = {}; ui.diskUsage = {}; ui.eventTable = {}; + ui.graphRange = {}; + ui.ioGraph = {}; + ui.ioRateAvg = {}; + ui.ioRateLast = {}; + ui.ioRateMax = {}; + ui.ipv4Graph = {}; + ui.ipv4InAvg = {}; + ui.ipv4InLast = {}; + ui.ipv4InMax = {}; + ui.ipv4OutAvg = {}; + ui.ipv4OutLast = {}; + ui.ipv4OutMax = {}; + ui.ipv4PrivInAvg = {}; + ui.ipv4PrivInLast = {}; + ui.ipv4PrivInMax = {}; + ui.ipv4PrivOutAvg = {}; + ui.ipv4PrivOutLast = {}; + ui.ipv4PrivOutMax = {}; + ui.ipv4Total = {}; + ui.ipv4TotalIn = {}; + ui.ipv4TotalOut = {}; + ui.ipv6Graph = {}; + ui.ipv6InAvg = {}; + ui.ipv6InLast = {}; + ui.ipv6InMax = {}; + ui.ipv6OutAvg = {}; + ui.ipv6OutLast = {}; + ui.ipv6OutMax = {}; + ui.ipv6PrivInAvg = {}; + ui.ipv6PrivInLast = {}; + ui.ipv6PrivInMax = {}; + ui.ipv6PrivOutAvg = {}; + ui.ipv6PrivOutLast = {}; + ui.ipv6PrivOutMax = {}; + ui.ipv6Total = {}; + ui.ipv6TotalIn = {}; + ui.ipv6TotalOut = {}; ui.jobProgress = {}; ui.jobProgressRow = {}; ui.lastBackup = {}; @@ -110,6 +196,9 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam ui.storageFree = {}; ui.storageTotal = {}; ui.storageUsed = {}; + ui.swapRateAvg = {}; + ui.swapRateLast = {}; + ui.swapRateMax = {}; ui.transferMonthly = {}; ui.transferOverage = {}; ui.transferUsed = {}; @@ -120,6 +209,7 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam var state = {}; state.diskRefresh = false; state.eventsComplete = 0; + state.haveRanges = false; state.linodeRefresh = false; state.showExtraEvents = false; @@ -143,8 +233,8 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam apiPost("/linode/instances/" + data.params.lid + "/boot", request, callback); }; - // Convert a byte count into a "friendly" byte string (KB, MB, GB, etc) - var byteString = function(count) + // Convert an unqualified count into a string with an SI prefix (i.e. bytes to MB/GB/etc) + var countSI = function(count) { var prefix = "KMGTPEZY"; var unit = ""; @@ -158,7 +248,7 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam } } - return count.toFixed(2) + " " + unit + "B"; + return count.toFixed(2) + " " + unit; }; // Generate a config profile table row @@ -525,6 +615,33 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam if (data.linode.type && !data.plan.id) apiGet("/linode/types/" + data.linode.type, displayPlan, null); + // Populate graph range picker + if (!state.haveRanges) { + var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + var created = new Date(data.linode.created + "Z"); + var now = new Date(); + var last30 = document.createElement("option"); + last30.value = "/" + now.getFullYear() + "/" + (now.getMonth() + 1).toString().padStart(2, "0"); + last30.innerHTML = "Last 30 Days"; + ui.graphRange.appendChild(last30); + + while (!(now.getFullYear() == created.getFullYear() && now.getMonth() == created.getMonth())) { + if (now.getMonth() == 0) { + now.setMonth(11); + now.setFullYear(now.getFullYear() - 1); + } else { + now.setMonth(now.getMonth() - 1); + } + + var yearMonth = document.createElement("option"); + yearMonth.value = "/" + now.getFullYear() + "/" + (now.getMonth() + 1).toString().padStart(2, "0"); + yearMonth.innerHTML = months[now.getMonth()] + " " + now.getFullYear(); + ui.graphRange.appendChild(yearMonth); + } + + state.haveRanges = true; + } + state.linodeRefresh = false; }; @@ -662,6 +779,159 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam ui.upgrade.style.display = "block"; }; + // Show stats graphs + var displayStats = function(response) + { + // Insert dummy points in case of blank data + if (!response.data.cpu.length) + response.data.cpu = [[0,0]]; + if (!response.data.io.io.length) + response.data.io.io = [[0,0]]; + if (!response.data.io.swap.length) + response.data.io.swap = [[0,0]]; + if (!response.data.netv4.private_out.length) + response.data.netv4.private_out = [[0,0]]; + if (!response.data.netv4.private_in.length) + response.data.netv4.private_in = [[0,0]]; + if (!response.data.netv4.out.length) + response.data.netv4.out = [[0,0]]; + if (!response.data.netv4.in.length) + response.data.netv4.in = [[0,0]]; + if (!response.data.netv6.private_out.length) + response.data.netv6.private_out = [[0,0]]; + if (!response.data.netv6.private_in.length) + response.data.netv6.private_in = [[0,0]]; + if (!response.data.netv6.out.length) + response.data.netv6.out = [[0,0]]; + if (!response.data.netv6.in.length) + response.data.netv6.in = [[0,0]]; + + data.stats.cpu = [{ + "color": "#03C", + "fill": false, + "points": response.data.cpu + }]; + data.stats.io = [ + { + "color": "#FFD04B", + "fill": true, + "points": response.data.io.io + }, + { + "color": "#FA373E", + "fill": false, + "points": response.data.io.swap + } + ]; + data.stats.netv4 = [ + { + "color": "#FF9", + "fill": true, + "points": response.data.netv4.private_out + }, + { + "color": "#C09", + "fill": false, + "points": response.data.netv4.private_in + }, + { + "color": "#32CD32", + "fill": true, + "points": response.data.netv4.out + }, + { + "color": "#03C", + "fill": false, + "points": response.data.netv4.in + } + ]; + data.stats.netv6 = [ + { + "color": "#FF9", + "fill": true, + "points": response.data.netv6.private_out + }, + { + "color": "#C09", + "fill": false, + "points": response.data.netv6.private_in + }, + { + "color": "#32CD32", + "fill": true, + "points": response.data.netv6.out + }, + { + "color": "#03C", + "fill": false, + "points": response.data.netv6.in + } + ]; + + // Draw graphs + drawSeries(data.stats.cpu, ui.cpuGraph); + drawSeries(data.stats.io, ui.ioGraph); + drawSeries(data.stats.netv4, ui.ipv4Graph); + drawSeries(data.stats.netv6, ui.ipv6Graph); + + // Compute traffic totals + var ipv4Out = data.stats.netv4[0].avg * (data.stats.netv4[0].points[data.stats.netv4[0].points.length-1][0] - data.stats.netv4[0].points[0][0]) / 1000; + ipv4Out += data.stats.netv4[2].avg * (data.stats.netv4[2].points[data.stats.netv4[2].points.length-1][0] - data.stats.netv4[2].points[0][0]) / 1000; + ipv4Out /= 8; + var ipv4In = data.stats.netv4[1].avg * (data.stats.netv4[1].points[data.stats.netv4[1].points.length-1][0] - data.stats.netv4[1].points[0][0]) / 1000; + ipv4In += data.stats.netv4[3].avg * (data.stats.netv4[3].points[data.stats.netv4[3].points.length-1][0] - data.stats.netv4[3].points[0][0]) / 1000; + ipv4In /= 8; + var ipv6Out = data.stats.netv6[0].avg * (data.stats.netv6[0].points[data.stats.netv6[0].points.length-1][0] - data.stats.netv6[0].points[0][0]) / 1000; + ipv6Out += data.stats.netv6[2].avg * (data.stats.netv6[2].points[data.stats.netv6[2].points.length-1][0] - data.stats.netv6[2].points[0][0]) / 1000; + ipv6Out /= 8; + var ipv6In = data.stats.netv6[1].avg * (data.stats.netv6[1].points[data.stats.netv6[1].points.length-1][0] - data.stats.netv6[1].points[0][0]) / 1000; + ipv6In += data.stats.netv6[3].avg * (data.stats.netv6[3].points[data.stats.netv6[3].points.length-1][0] - data.stats.netv6[3].points[0][0]) / 1000; + ipv6In /= 8; + + // Update tables + ui.cpuMax.innerHTML = data.stats.cpu[0].max + "%"; + ui.cpuAvg.innerHTML = data.stats.cpu[0].avg.toFixed(2) + "%"; + ui.cpuLast.innerHTML = data.stats.cpu[0].points[data.stats.cpu[0].points.length - 1][1] + "%"; + ui.ioRateMax.innerHTML = data.stats.io[0].max; + ui.ioRateAvg.innerHTML = data.stats.io[0].avg.toFixed(2); + ui.ioRateLast.innerHTML = data.stats.io[0].points[data.stats.io[0].points.length - 1][1]; + ui.swapRateMax.innerHTML = data.stats.io[1].max; + ui.swapRateAvg.innerHTML = data.stats.io[1].avg.toFixed(2); + ui.swapRateLast.innerHTML = data.stats.io[1].points[data.stats.io[1].points.length - 1][1]; + ui.ipv4PrivOutMax.innerHTML = countSI(data.stats.netv4[0].max) + "b/s"; + ui.ipv4PrivOutAvg.innerHTML = countSI(data.stats.netv4[0].avg) + "b/s"; + ui.ipv4PrivOutLast.innerHTML = countSI(data.stats.netv4[0].points[data.stats.netv4[0].points.length - 1][1]) + "b/s"; + ui.ipv4PrivInMax.innerHTML = countSI(data.stats.netv4[1].max) + "b/s"; + ui.ipv4PrivInAvg.innerHTML = countSI(data.stats.netv4[1].avg) + "b/s"; + ui.ipv4PrivInLast.innerHTML = countSI(data.stats.netv4[1].points[data.stats.netv4[1].points.length - 1][1]) + "b/s"; + ui.ipv4OutMax.innerHTML = countSI(data.stats.netv4[2].max) + "b/s"; + ui.ipv4OutAvg.innerHTML = countSI(data.stats.netv4[2].avg) + "b/s"; + ui.ipv4OutLast.innerHTML = countSI(data.stats.netv4[2].points[data.stats.netv4[2].points.length - 1][1]) + "b/s"; + ui.ipv4InMax.innerHTML = countSI(data.stats.netv4[3].max) + "b/s"; + ui.ipv4InAvg.innerHTML = countSI(data.stats.netv4[3].avg) + "b/s"; + ui.ipv4InLast.innerHTML = countSI(data.stats.netv4[3].points[data.stats.netv4[3].points.length - 1][1]) + "b/s"; + ui.ipv4TotalIn.innerHTML = countSI(ipv4In) + "B"; + ui.ipv4TotalOut.innerHTML = countSI(ipv4Out) + "B"; + ui.ipv4Total.innerHTML = countSI(ipv4In + ipv4Out) + "B"; + ui.ipv6PrivOutMax.innerHTML = countSI(data.stats.netv6[0].max) + "b/s"; + ui.ipv6PrivOutAvg.innerHTML = countSI(data.stats.netv6[0].avg) + "b/s"; + ui.ipv6PrivOutLast.innerHTML = countSI(data.stats.netv6[0].points[data.stats.netv6[0].points.length - 1][1]) + "b/s"; + ui.ipv6PrivInMax.innerHTML = countSI(data.stats.netv6[1].max) + "b/s"; + ui.ipv6PrivInAvg.innerHTML = countSI(data.stats.netv6[1].avg) + "b/s"; + ui.ipv6PrivInLast.innerHTML = countSI(data.stats.netv6[1].points[data.stats.netv6[1].points.length - 1][1]) + "b/s"; + ui.ipv6OutMax.innerHTML = countSI(data.stats.netv6[2].max) + "b/s"; + ui.ipv6OutAvg.innerHTML = countSI(data.stats.netv6[2].avg) + "b/s"; + ui.ipv6OutLast.innerHTML = countSI(data.stats.netv6[2].points[data.stats.netv6[2].points.length - 1][1]) + "b/s"; + ui.ipv6InMax.innerHTML = countSI(data.stats.netv6[3].max) + "b/s"; + ui.ipv6InAvg.innerHTML = countSI(data.stats.netv6[3].avg) + "b/s"; + ui.ipv6InLast.innerHTML = countSI(data.stats.netv6[3].points[data.stats.netv6[3].points.length - 1][1]) + "b/s"; + ui.ipv6TotalIn.innerHTML = countSI(ipv6In) + "B"; + ui.ipv6TotalOut.innerHTML = countSI(ipv6Out) + "B"; + ui.ipv6Total.innerHTML = countSI(ipv6In + ipv6Out) + "B"; + + ui.graphRange.disabled = false; + }; + // Show storage totals var displayStorage = function() { @@ -686,7 +956,7 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam // Display network transfer info ui.transferMonthly.innerHTML = data.linodeTransfer.quota + " GB"; - ui.transferUsed.innerHTML = byteString(data.linodeTransfer.used); + ui.transferUsed.innerHTML = countSI(data.linodeTransfer.used) + "B"; ui.transferOverage.innerHTML = data.linodeTransfer.billable + " GB"; ui.netUsage.value = ((data.linodeTransfer.used / 1024 / 1024 / 1024) / data.linodeTransfer.quota * 100).toFixed(0); ui.netUsage.innerHTML = ui.netUsage.value + "%"; @@ -786,43 +1056,25 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam anchors[i].href = anchors[i].href.replace("lid=0", "lid=" + data.params.lid); // Get element references - ui.backups = document.getElementById(elements.backups); - ui.boot = document.getElementById(elements.boot); - ui.configTable = document.getElementById(elements.configTable); - ui.diskTable = document.getElementById(elements.diskTable); - ui.diskUsage = document.getElementById(elements.diskUsage); - ui.eventTable = document.getElementById(elements.eventTable); - ui.jobProgress = document.getElementById(elements.jobProgress); - ui.jobProgressRow = document.getElementById(elements.jobProgressRow); - ui.lastBackup = document.getElementById(elements.lastBackup); - ui.lastBackupTime = document.getElementById(elements.lastBackupTime); - ui.linodeLabel = document.getElementById(elements.linodeLabel); - ui.linodeTag = document.getElementById(elements.linodeTag); - ui.linodeTagLink = document.getElementById(elements.linodeTagLink); - ui.loadingConfigs = document.getElementById(elements.loadingConfigs); - ui.loadingDisks = document.getElementById(elements.loadingDisks); - ui.loadingJobs = document.getElementById(elements.loadingJobs); - ui.loadingVolumes = document.getElementById(elements.loadingVolumes); - ui.moreJobs = document.getElementById(elements.moreJobs); - ui.netUsage = document.getElementById(elements.netUsage); - ui.notifications = document.getElementById(elements.notifications); - ui.reboot = document.getElementById(elements.reboot); - ui.serverStatus = document.getElementById(elements.serverStatus); - ui.shutDown = document.getElementById(elements.shutDown); - ui.storageFree = document.getElementById(elements.storageFree); - ui.storageTotal = document.getElementById(elements.storageTotal); - ui.storageUsed = document.getElementById(elements.storageUsed); - ui.transferMonthly = document.getElementById(elements.transferMonthly); - ui.transferOverage = document.getElementById(elements.transferOverage); - ui.transferUsed = document.getElementById(elements.transferUsed); - ui.upgrade = document.getElementById(elements.upgrade); - ui.volumeTable = document.getElementById(elements.volumeTable); + for (var i in ui) + ui[i] = document.getElementById(elements[i]); // Attach button handlers ui.boot.addEventListener("click", bootLinode); ui.moreJobs.addEventListener("click", toggleEvents); ui.reboot.addEventListener("click", rebootLinode); ui.shutDown.addEventListener("click", shutDownLinode); + ui.graphRange.addEventListener("input", updateGraphs); + + // Set graph resolutions + ui.cpuGraph.height = ui.cpuGraph.clientHeight; + ui.cpuGraph.width = ui.cpuGraph.clientWidth; + ui.ioGraph.height = ui.ioGraph.clientHeight; + ui.ioGraph.width = ui.ioGraph.clientWidth; + ui.ipv4Graph.height = ui.ipv4Graph.clientHeight; + ui.ipv4Graph.width = ui.ipv4Graph.clientWidth; + ui.ipv6Graph.height = ui.ipv6Graph.clientHeight; + ui.ipv6Graph.width = ui.ipv6Graph.clientWidth; // Get data from the API apiGet("/linode/instances/" + data.params.lid, displayDetails, null); @@ -836,6 +1088,7 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam }; apiGet("/account/events", displayEvents, filter); apiGet("/account/notifications", displayNotifications, null); + apiGet("/linode/instances/" + data.params.lid + "/stats", displayStats, null); }; // Button handler for shutdown button @@ -908,6 +1161,16 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam window.setTimeout(getEvent, settings.refreshRate, event.id); }; + // Re-populate graphs with selected range + var updateGraphs = function(event) + { + if (event.currentTarget.disabled) + return; + + apiGet("/linode/instances/" + data.params.lid + "/stats" + ui.graphRange.value, displayStats, null); + ui.graphRange.disabled = true; + }; + // Attach onload handler window.addEventListener("load", setup); })(); diff --git a/linodes/dashboard/index.shtml b/linodes/dashboard/index.shtml index 073a3fd..7caf205 100644 --- a/linodes/dashboard/index.shtml +++ b/linodes/dashboard/index.shtml @@ -109,6 +109,145 @@ along with Linode Manager Classic. If not, see .

Graphs

+ +
+

CPU (%)

+ + + + + + + + + + + + + + + + + + +
MaxAvgLast
CPU %
+

Disk I/O (blocks/s)

+ + + + + + + + + + + + + + + + + + + + + + + + +
MaxAvgLast
I/O Rate
Swap Rate
+

Network - IPv4 (bits/s)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MaxAvgLast
Private Out
Private In
Public Out
+
Public In
Total TrafficIn: Out: Combined:
+

Network - IPv6 (bits/s)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MaxAvgLast
Private Out
Private In
Public Out
+
Public In
Total TrafficIn: Out: Combined:
+