Basic graph implementation

This commit is contained in:
L. Bradley LaBoon 2023-04-03 20:01:50 -04:00
parent 148c9cd0db
commit 99e0e497c2
9 changed files with 566 additions and 178 deletions

View File

@ -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%;

View File

@ -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 };

View File

@ -6,7 +6,6 @@
<a class="subnav-link" href="/linodes/rescue?lid=0">Rescue</a>
<a class="subnav-link" href="/linodes/resize?lid=0">Resize</a>
<a class="subnav-link" href="/linodes/clone?lid=0">Clone</a>
<a class="subnav-link" href="/linodes/graphs?lid=0">Graphs</a>
<a class="subnav-link" href="/linodes/backups?lid=0">Backups</a>
<a class="subnav-link" href="/linodes/settings?lid=0">Settings</a>
</nav>

View File

@ -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;

View File

@ -15,7 +15,7 @@
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
*/
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);
})();

View File

@ -109,6 +109,145 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
</tbody>
</table>
<h3>Graphs</h3>
<select disabled id="graph-range">
<option selected value="">Last 24 Hours</option>
</select>
<div class="lmc-graph">
<h4>CPU (%)</h4>
<canvas id="cpu-graph"></canvas>
<table>
<thead>
<tr>
<td></td>
<td>Max</td>
<td>Avg</td>
<td>Last</td>
</tr>
</thead>
<tbody>
<tr>
<td><div id="cpu-color" class="lmc-graph-color"></div> CPU %</td>
<td id="cpu-max"></td>
<td id="cpu-avg"></td>
<td id="cpu-last"></td>
</tr>
</tbody>
</table>
<h4>Disk I/O (blocks/s)</h4>
<canvas id="io-graph"></canvas>
<table>
<thead>
<tr>
<td></td>
<td>Max</td>
<td>Avg</td>
<td>Last</td>
</tr>
</thead>
<tbody>
<tr>
<td><div id="io-rate-color" class="lmc-graph-color"></div> I/O Rate</td>
<td id="io-rate-max"></td>
<td id="io-rate-avg"></td>
<td id="io-rate-last"></td>
</tr>
<tr>
<td><div id="swap-rate-color" class="lmc-graph-color"></div> Swap Rate</td>
<td id="swap-rate-max"></td>
<td id="swap-rate-avg"></td>
<td id="swap-rate-last"></td>
</tr>
</tbody>
</table>
<h4>Network - IPv4 (bits/s)</h4>
<canvas id="ipv4-graph"></canvas>
<table>
<thead>
<tr>
<td></td>
<td>Max</td>
<td>Avg</td>
<td>Last</td>
</tr>
</thead>
<tbody>
<tr>
<td><div id="ipv4-privout-color" class="lmc-graph-color"></div> Private Out</td>
<td id="ipv4-privout-max"></td>
<td id="ipv4-privout-avg"></td>
<td id="ipv4-privout-last"></td>
</tr>
<tr>
<td><div id="ipv4-privin-color" class="lmc-graph-color"></div> Private In</td>
<td id="ipv4-privin-max"></td>
<td id="ipv4-privin-avg"></td>
<td id="ipv4-privin-last"></td>
</tr>
<tr>
<td><div id="ipv4-out-color" class="lmc-graph-color"></div> Public Out</td>
<td id="ipv4-out-max"></td>
<td id="ipv4-out-avg"</td>
<td id="ipv4-out-last"></td>
</tr>
<tr>
<td><div id="ipv4-in-color" class="lmc-graph-color"></div> Public In</td>
<td id="ipv4-in-max"></td>
<td id="ipv4-in-avg"></td>
<td id="ipv4-in-last"></td>
</tr>
<tr>
<td>Total Traffic</td>
<td>In: <span id="ipv4-total-in"></span></td>
<td>Out: <span id="ipv4-total-out"></span></td>
<td>Combined: <span id="ipv4-total"></span></td>
</tr>
</tbody>
</table>
<h4>Network - IPv6 (bits/s)</h4>
<canvas id="ipv6-graph"></canvas>
<table>
<thead>
<tr>
<td></td>
<td>Max</td>
<td>Avg</td>
<td>Last</td>
</tr>
</thead>
<tbody>
<tr>
<td><div id="ipv6-privout-color" class="lmc-graph-color"></div> Private Out</td>
<td id="ipv6-privout-max"></td>
<td id="ipv6-privout-avg"></td>
<td id="ipv6-privout-last"></td>
</tr>
<tr>
<td><div id="ipv6-privin-color" class="lmc-graph-color"></div> Private In</td>
<td id="ipv6-privin-max"></td>
<td id="ipv6-privin-avg"></td>
<td id="ipv6-privin-last"></td>
</tr>
<tr>
<td><div id="ipv6-out-color" class="lmc-graph-color"></div> Public Out</td>
<td id="ipv6-out-max"></td>
<td id="ipv6-out-avg"</td>
<td id="ipv6-out-last"></td>
</tr>
<tr>
<td><div id="ipv6-in-color" class="lmc-graph-color"></div> Public In</td>
<td id="ipv6-in-max"></td>
<td id="ipv6-in-avg"></td>
<td id="ipv6-in-last"></td>
</tr>
<tr>
<td>Total Traffic</td>
<td>In: <span id="ipv6-total-in"></span></td>
<td>Out: <span id="ipv6-total-out"></span></td>
<td>Combined: <span id="ipv6-total"></span></td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="linode-sidebar">
<div class="sidebar-box">

View File

@ -1,22 +0,0 @@
/*
* This file is part of Linode Manager Classic.
*
* Linode Manager Classic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Linode Manager Classic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
*/
@import url('/global.css');
#graphs {
padding: 0px 15px 15px;
}

View File

@ -1,82 +0,0 @@
/*
* This file is part of Linode Manager Classic.
*
* Linode Manager Classic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Linode Manager Classic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
*/
import { settings, elements, apiGet, parseParams, setupHeader } from "/global.js";
(function()
{
// Element names specific to this page
elements.linodeLabel = "linode-label";
elements.linodeTag = "linode-tag";
elements.linodeTagLink = "linode-tag-link";
// Data recieved from API calls
var data = {};
data.linode = {};
// Static references to UI elements
var ui = {};
ui.linodeLabel = {};
ui.linodeTag = {};
ui.linodeTagLink = {};
// Callback for linode details API call
var displayDetails = function(response)
{
data.linode = response;
// Set page title and header stuff
document.title += " // " + data.linode.label;
ui.linodeLabel.innerHTML = data.linode.label;
if (data.linode.tags.length == 1) {
ui.linodeTagLink.href = "/linodes?tag=" + data.linode.tags[0];
ui.linodeTagLink.innerHTML = "(" + data.linode.tags[0] + ")";
ui.linodeTag.style.display = "inline";
}
};
// Initial setup
var setup = function()
{
// Parse URL parameters
data.params = parseParams();
// We need a Linode ID, so die if we don't have it
if (!data.params.lid) {
alert("No Linode ID supplied!");
return;
}
setupHeader();
// Update links on page to include proper Linode ID
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++)
anchors[i].href = anchors[i].href.replace("lid=0", "lid=" + data.params.lid);
// Get element references
ui.linodeLabel = document.getElementById(elements.linodeLabel);
ui.linodeTag = document.getElementById(elements.linodeTag);
ui.linodeTagLink = document.getElementById(elements.linodeTagLink);
// Get data from API
apiGet("/linode/instances/" + data.params.lid, displayDetails, null);
};
// Attach onload handler
window.addEventListener("load", setup);
})();

View File

@ -1,35 +0,0 @@
<!--
This file is part of Linode Manager Classic.
Linode Manager Classic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Linode Manager Classic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
-->
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>LMC - Graphs</title>
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
<link rel="stylesheet" type="text/css" href="graphs.css" />
<script src="graphs.js" type="module"></script>
</head>
<body>
<!--#include virtual="/include/header.html"-->
<!--#include virtual="/include/linode_subnav.html"-->
<div id="main-content" class="wrapper">
<div id="top-links"><a href="/linodes">Linodes</a> » <span id="linode-tag"><a id="linode-tag-link" href=""></a> » </span><a id="linode-label" href="/linodes/dashboard?lid=0"></a> » <span class="top-links-title">Graphs</span></div>
<div id="graphs">
</div>
</div>
</body>
</html>