/* * 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 . */ import { settings, elements, apiDelete, apiGet, apiPost, drawSeries, eventTitles, parseParams, setupHeader, timeString, translateKernel } from "/global.js"; (function() { // Element names specific to this page elements.backups = "backups"; elements.backupsEnabled = "backups-enabled"; elements.boot = "boot"; 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-"; elements.diskTable = "disk-table"; elements.diskUsage = "disk-usage"; 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"; elements.jobProgress = "job-progress"; elements.jobProgressRow = "job-progress-row"; elements.jobRunning = "job-running"; elements.jobSuccess = "job-success"; elements.jobType = "job-type"; elements.lastBackup = "last-backup"; elements.lastBackupTime = "last-backup-time"; elements.linodeLabel = "linode-label"; elements.linodeTag = "linode-tag"; elements.linodeTagLink = "linode-tag-link"; elements.lmcRow = "lmc-tr3"; elements.loadingConfigs = "loading-configs"; elements.loadingDisks = "loading-disks"; elements.loadingJobs = "loading-jobs"; elements.loadingVolumes = "loading-volumes"; elements.moreJobs = "moar-jobs"; elements.netUsage = "net-usage"; elements.notification = "notification"; elements.notifications = "notifications"; elements.reboot = "reboot"; elements.serverStatus = "server-status"; elements.shutDown = "shut-down"; elements.spinnerImg = "/img/spinner-trans.gif"; 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"; elements.upgrade = "upgrade"; elements.volumeTable = "volume-table"; // Data recieved from API calls var data = {}; data.params = {}; data.configs = []; data.disks = []; data.events = []; data.linode = {}; data.linodeTransfer = {}; data.notifications = []; data.plan = {}; data.stats = {}; data.volumes = []; // Static references to UI elements var ui = {}; 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 = {}; ui.lastBackupTime = {}; ui.linodeLabel = {}; ui.linodeTag = {}; ui.linodeTagLink = {}; ui.loadingConfigs = {}; ui.loadingDisks = {}; ui.loadingJobs = {}; ui.loadingVolumes = {}; ui.moreJobs = {}; ui.netUsage = {}; ui.notifications = {}; ui.reboot = {}; ui.serverStatus = {}; ui.shutDown = {}; ui.storageFree = {}; ui.storageTotal = {}; ui.storageUsed = {}; ui.swapRateAvg = {}; ui.swapRateLast = {}; ui.swapRateMax = {}; ui.transferMonthly = {}; ui.transferOverage = {}; ui.transferUsed = {}; ui.upgrade = {}; ui.volumeTable = {}; // Temporary state var state = {}; state.diskRefresh = false; state.eventsComplete = 0; state.haveRanges = false; state.linodeRefresh = false; state.showExtraEvents = false; // Button handler for boot button var bootLinode = function(event) { if (!confirm("Boot this Linode?")) return; var config = getSelectedConfig(); var request = {}; if (config) request.config_id = config; // Reload the page on successful API return var callback = function() { location.reload(); }; apiPost("/linode/instances/" + data.params.lid + "/boot", request, callback); }; // 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 = ""; for (var i = 0; i < prefix.length; i++) { if (count >= 1024) { count /= 1024; unit = prefix.charAt(i); } else { break; } } return count.toFixed(2) + " " + unit; }; // Generate a config profile table row var createConfigRow = function(config) { var row = document.createElement("tr"); row.className = elements.lmcRow; // Radio button selector var radioCell = document.createElement("td"); var radioInput = document.createElement("input"); radioInput.name = elements.configRadioName; radioInput.className = elements.configRadioName; radioInput.id = elements.configRadioName + "-" + config.id; radioInput.type = "radio"; radioCell.appendChild(radioInput); row.appendChild(radioCell); // Config profile name and kernel var nameCell = document.createElement("td"); var nameLink = document.createElement("a"); nameLink.href = "/linodes/config?cid=" + config.id + "&lid=" + data.params.lid; if (config.label.length > 41) nameLink.innerHTML = config.label.substring(0, 41) + "..."; else nameLink.innerHTML = config.label; nameCell.appendChild(nameLink); var prefix = document.createElement("span"); prefix.className = elements.info; prefix.innerHTML = " ("; nameCell.appendChild(prefix); var kernel = document.createElement("span"); kernel.className = elements.info; translateKernel(config.kernel, kernel); nameCell.appendChild(kernel); var suffix = document.createElement("span"); suffix.className = elements.info; suffix.innerHTML = ")"; nameCell.appendChild(suffix); row.appendChild(nameCell); // Option links var options = document.createElement("td"); var editLink = document.createElement("a"); editLink.href = "/linodes/config?cid=" + config.id + "&lid=" + data.params.lid; editLink.innerHTML = "Edit"; var separator = document.createElement("span"); separator.innerHTML = " | "; var removeLink = document.createElement("a"); removeLink.href = "#"; removeLink.id = elements.configRemovePrefix + config.id; removeLink.innerHTML = "Remove"; removeLink.addEventListener("click", deleteConfig); options.appendChild(editLink); options.appendChild(separator); options.appendChild(removeLink); row.appendChild(options); return row; }; // Generate a disk table row var createDiskRow = function(disk) { var row = document.createElement("tr"); row.className = elements.lmcRow; // Disk icon var iconCell = document.createElement("td"); var diskIcon = document.createElement("img"); diskIcon.className = elements.diskIcon; diskIcon.src = elements.diskIconImg; iconCell.appendChild(diskIcon); row.appendChild(iconCell); // Disk name and size var nameCell = document.createElement("td"); var nameLink = document.createElement("a"); nameLink.href = "/linodes/disk?did=" + disk.id + "&lid=" + data.params.lid; nameLink.innerHTML = disk.label; var size = document.createElement("span"); size.className = elements.info; size.innerHTML = " (" + disk.size + " MB, " + disk.filesystem + ")"; nameCell.appendChild(nameLink); nameCell.appendChild(size); row.appendChild(nameCell); // Options links var options = document.createElement("td"); var editLink = document.createElement("a"); editLink.href = "/linodes/disk?did=" + disk.id + "&lid=" + data.params.lid; editLink.innerHTML = "Edit"; var separator = document.createElement("span"); separator.innerHTML = " | "; var removeLink = document.createElement("a"); removeLink.href = "#"; removeLink.id = elements.diskRemovePrefix + disk.id; removeLink.innerHTML = "Remove"; removeLink.addEventListener("click", deleteDisk); options.appendChild(editLink); options.appendChild(separator); options.appendChild(removeLink); row.appendChild(options); return row; }; // Generate an event table row var createEventRow = function(event, extra) { var row = document.createElement("tr"); row.id = elements.eventRowPrefix + event.id; row.className = elements.lmcRow; if (extra) row.className += " " + elements.extraEvent; // Status cell var statusCell = document.createElement("td"); if (event.status.match(/scheduled|started/)) { statusCell.className = elements.jobRunning; var spinner = document.createElement("img"); spinner.src = elements.spinnerImg; statusCell.appendChild(spinner); } else if (event.status == "failed") { var failed = document.createElement("span"); failed.className = elements.jobFailed; failed.innerHTML = "Failed"; statusCell.appendChild(failed); } else if (event.status == "notification") { var notice = document.createElement("span"); notice.className = elements.jobNotice; notice.innerHTML = "Notice"; statusCell.appendChild(notice); } else if (event.status == "finished") { var success = document.createElement("span"); success.className = elements.jobSuccess; success.innerHTML = "Success"; statusCell.appendChild(success); } row.appendChild(statusCell); // Details cell var detailsCell = document.createElement("td"); var jobType = document.createElement("span"); jobType.className = elements.jobType; if (eventTitles[event.action]) jobType.innerHTML = eventTitles[event.action]; else jobType.innerHTML = event.action; var jobDetails = document.createElement("span"); if (event.secondary_entity) jobDetails.innerHTML = " - " + event.secondary_entity.label; var br = document.createElement("br"); var jobInfo = document.createElement("span"); jobInfo.className = elements.jobInfo; var eventDate = new Date(event.created + "Z"); var curDate = new Date(); jobInfo.innerHTML = "Entered: " + timeString(curDate - eventDate, true); if (event.duration) jobInfo.innerHTML += " - Took: " + timeString(event.duration * 1000, false); else if (event.status != "notification") jobInfo.innerHTML += " - Waiting..."; detailsCell.appendChild(jobType); detailsCell.appendChild(jobDetails); detailsCell.appendChild(br); detailsCell.appendChild(jobInfo); row.appendChild(detailsCell); // Progress cell var progressCell = document.createElement("td"); if (event.percent_complete && event.percent_complete != 100) progressCell.innerHTML = event.percent_complete + "%"; if (event.time_remaining) { if (progressCell.innerHTML.length) progressCell.innerHTML += ", "; var times = event.time_remaining.split(":"); var ms = 0; ms += parseInt(times[0]) * 3600000; ms += parseInt(times[1]) * 60000; ms += parseInt(times[2]) * 1000; progressCell.innerHTML += timeString(ms, false) + " to go"; } if (event.rate) { if (progressCell.innerHTML.length) progressCell.innerHTML += ", "; progressCell.innerHTML += event.rate; } row.appendChild(progressCell); return row; }; // Generate a volume table row var createVolumeRow = function(volume) { var row = document.createElement("tr"); row.className = elements.lmcRow; // Disk icon var iconCell = document.createElement("td"); var diskIcon = document.createElement("img"); diskIcon.className = elements.diskIcon; diskIcon.src = elements.diskIconImg; iconCell.appendChild(diskIcon); row.appendChild(iconCell); // Volume name and size var nameCell = document.createElement("td"); var nameLink = document.createElement("a"); nameLink.href = "/volumes/settings?vid=" + volume.id; nameLink.innerHTML = volume.label; var size = document.createElement("span"); size.className = elements.info; size.innerHTML = " (" + volume.size + " GiB)"; nameCell.appendChild(nameLink); nameCell.appendChild(size); row.appendChild(nameCell); // Options links var options = document.createElement("td"); var editLink = document.createElement("a"); editLink.href = "/volumes/settings?vid=" + volume.id; editLink.innerHTML = "Edit"; var separator = document.createElement("span"); separator.innerHTML = " | "; var cloneLink = document.createElement("a"); cloneLink.href = "/volumes/clone?vid=" + volume.id; cloneLink.innerHTML = "Clone"; var detachLink = document.createElement("a"); detachLink.href = "/volumes/detach?vid=" + volume.id; detachLink.innerHTML = "Detach"; var removeLink = document.createElement("a"); removeLink.href = "/volumes/remove?vid=" + volume.id; removeLink.innerHTML = "Remove"; options.appendChild(editLink); options.appendChild(separator); options.appendChild(cloneLink); options.appendChild(separator.cloneNode(true)); options.appendChild(detachLink); options.appendChild(separator.cloneNode(true)); options.appendChild(removeLink); row.appendChild(options); return row; }; // Handler for config delete var deleteConfig = function(event) { if (!confirm("Delete this Configuration Profile?")) return; var cid = event.currentTarget.id.substring(elements.configRemovePrefix.length); apiDelete("/linode/instances/" + data.params.lid + "/configs/" + cid, function() { location.reload(); }); }; // Handler for disk delete var deleteDisk = function(event) { var did = parseInt(event.currentTarget.id.substring(elements.diskRemovePrefix.length)); var disk = null; for (var i = 0; i < data.disks.length; i++) { if (data.disks[i].id == did) { disk = data.disks[i]; break; } } if (!disk) return; if (!confirm("Delete " + disk.label + "?")) return; apiDelete("/linode/instances/" + data.params.lid + "/disks/" + did, function() { location.reload(); }); }; // Callback for config profile API call var displayConfigs = function(response) { // Add configs to array data.configs = data.configs.concat(response.data); // Request the next page if there are more if (response.page != response.pages) { apiGet("/linode/instances/" + data.params.lid + "/configs?page=" + (response.page + 1), displayConfigs, null); return; } // Remove loading row ui.loadingConfigs.remove(); // Insert config profile rows into table for (var i = 0; i < data.configs.length; i++) ui.configTable.appendChild(createConfigRow(data.configs[i])); }; // Callback for linode details API call var displayDetails = function(response) { data.linode = response; // Set page title and header stuff if (document.title.indexOf("//") == -1) 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"; } else { ui.linodeTag.style.display = "none"; } // Set running status ui.serverStatus.innerHTML = data.linode.status.charAt(0).toUpperCase() + data.linode.status.slice(1).replace(/_/g, " "); if (data.linode.status == "running") { ui.shutDown.disabled = false; ui.shutDown.style.display = "block"; } else { ui.shutDown.style.display = "none"; } if (data.linode.status.match(/offline|provisioning|migrating|rebuilding|restoring|shutting_down|resizing/)) { ui.reboot.style.display = "none"; ui.boot.disabled = false; ui.boot.style.display = "inline"; } else if (data.linode.status != "cloning") { ui.boot.style.display = "none"; ui.reboot.disabled = false; ui.reboot.style.display = "inline"; } // Update storage info displayStorage(); // Set backup status if (data.linode.backups.enabled) { ui.backups.innerHTML = "Enabled!"; ui.backups.className += " " + elements.backupsEnabled; if (data.linode.backups.last_successful) { var backupDate = new Date(data.linode.backups.last_successful + "Z"); var now = new Date(); ui.lastBackupTime.innerHTML = timeString(now - backupDate, true); ui.lastBackup.style.display = "block"; } } else { ui.backups.innerHTML = ""; var text = document.createElement("span"); text.innerHTML = "No - "; var backupLink = document.createElement("a"); backupLink.href = "/linodes/backups_enable?lid=" + data.params.lid; backupLink.innerHTML = "Enable"; backups.appendChild(text); backups.appendChild(backupLink); } // Get plan info 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; }; // Callback for disks API call var displayDisks = function(response) { // Add disks to array data.disks = data.disks.concat(response.data); // Request the next page if there are more if (response.page != response.pages) { apiGet("/linode/instances/" + data.params.lid + "/disks?page=" + (response.page + 1), displayDisks, null); return; } // Remove loading row ui.loadingDisks.remove(); // Update storage info displayStorage(); // Insert disk rows into table ui.diskTable.innerHTML = ""; for (var i = 0; i < data.disks.length; i++) ui.diskTable.appendChild(createDiskRow(data.disks[i])); state.diskRefresh = false; }; // Callback for events API call var displayEvents = function(response) { // Only collect a max of 8 finished jobs for (var i = 0; i < response.data.length && state.eventsComplete < 8; i++) { if (response.data[i].status.match(/failed|finished|notification/)) state.eventsComplete++; data.events.push(response.data[i]); } // Request the next page if there are more if (state.eventsComplete < 8 && response.page != response.pages) { var filter = { "entity.type": "linode", "entity.id": parseInt(data.params.lid) }; apiGet("/account/events?page=" + (response.page + 1), displayEvents, filter); return; } // Remove loading row ui.loadingJobs.remove(); // Insert events into table and compute total progress var progress = 0; var totalProgress = 0; for (var i = 0; i < data.events.length; i++) { var extra = (data.events[i].status.match(/failed|finished|notification/) && i >= 4); ui.eventTable.appendChild(createEventRow(data.events[i], extra)); if (!data.events[i].status.match(/failed|finished|notification/)) { totalProgress += 100; progress += data.events[i].percent_complete; // Set refresh timer for event window.setTimeout(getEvent, settings.refreshRate, data.events[i].id); } } // If there are ongoing events, show the progress bar if (totalProgress) { ui.jobProgress.max = totalProgress; ui.jobProgress.value = progress; ui.jobProgress.innerHTML = (progress / totalProgress * 100).toFixed(0) + "%"; ui.jobProgress.title = ui.jobProgress.innerHTML; ui.jobProgressRow.style.display = "table-row"; } }; // Callback for notifications API call var displayNotifications = function(response) { // Add notifications to array data.notifications = data.notifications.concat(response.data); // Request the next page if there are more pages if (response.page != response.pages) { apiGet("/account/notifications?page=" + (response.page + 1), displayNotifications, null); return; } // Display notifications for (var i = 0; i < data.notifications.length; i++) { if (!data.notifications[i].entity || data.notifications[i].entity.type != "linode" || data.notifications[i].entity.id != data.params.lid) continue; if (data.notifications[i].type == "maintenance") { var now = new Date(); var maintStart = new Date(data.notifications[i].when + "Z"); data.notifications[i].label = "Maintenance Scheduled"; data.notifications[i].message = "This Linode's physical host will be undergoing maintenance on " + maintStart.toLocaleString() + " (in " + timeString(maintStart - now, false) + ")."; data.notifications[i].message += " During this time, your Linode will be shut down and remain offline, then returned to its last state (running or powered off)."; data.notifications[i].message += " For more information, please see your open support tickets."; } if (data.notifications[i].type == "migration_scheduled") { var now = new Date(); var migrateStart = new Date(data.notifications[i].when + "Z"); data.notifications[i].message += " If no action is taken, the migration will start automatically on " + migrateStart.toLocaleString() + " (in " + timeString(migrateStart - now, false) + ")."; } var notification = document.createElement("div"); notification.className = elements.notification; var header = document.createElement("h1"); header.innerHTML = data.notifications[i].label; notification.appendChild(header); if (data.notifications[i].type == "migration_pending" || data.notifications[i].type == "migration_scheduled") { var migrateButton = document.createElement("button"); migrateButton.type = "button"; migrateButton.innerHTML = "Migrate " + data.notifications[i].entity.label + " NOW"; migrateButton.addEventListener("click", initiateMigration); notification.appendChild(migrateButton); } var body = document.createElement("p"); body.innerHTML = data.notifications[i].message; notification.appendChild(body); ui.notifications.appendChild(notification); } }; // Callback for linode plan API call var displayPlan = function(response) { data.plan = response; // Display the upgrade banner if one is available if (data.plan.successor) 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() { if (!data.linode.specs) return; ui.storageTotal.innerHTML = data.linode.specs.disk + " MB"; var storageUsed = 0; for (var i = 0; i < data.disks.length; i++) storageUsed += data.disks[i].size; ui.storageUsed.innerHTML = storageUsed + " MB"; ui.storageFree.innerHTML = (data.linode.specs.disk - storageUsed) + " MB"; ui.diskUsage.value = (storageUsed / data.linode.specs.disk * 100).toFixed(0); ui.diskUsage.innerHTML = ui.diskUsage.value + "%"; ui.diskUsage.title = ui.diskUsage.innerHTML; }; // Callback for network transfer API call var displayTransfer = function(response) { data.linodeTransfer = response; // Display network transfer info ui.transferMonthly.innerHTML = data.linodeTransfer.quota + " GB"; 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 + "%"; ui.netUsage.title = ui.netUsage.innerHTML; }; // Callback for volumes API call var displayVolumes = function(response) { // Add volumes to array data.volumes = data.volumes.concat(response.data); // Request the next page if there are more if (response.page != response.pages) { apiGet("/linode/instances/" + data.params.lid + "/volumes?page=" + (response.page + 1), displayVolumes, null); return; } // Remove loading row ui.loadingVolumes.remove(); // Insert volume rows into table for (var i = 0; i < data.volumes.length; i++) ui.volumeTable.appendChild(createVolumeRow(data.volumes[i])); }; // Retrieve the given event from the API var getEvent = function(eventID) { apiGet("/account/events/" + eventID, updateEvent, null); }; // Get selected config profile var getSelectedConfig = function() { var configs = document.getElementsByClassName(elements.configRadioName); for (var i = 0; i < configs.length; i++) { if (configs[i].checked) return parseInt(configs[i].id.replace(elements.configRadioName + "-", "")); } return 0; }; // Button handler to initiate migration var initiateMigration = function(event) { if (event.currentTarget.disabled) return; var req = { "upgrade": false }; apiPost("/linode/instances/" + data.params.lid + "/migrate", req, function(response) { location.reload(); }); }; // Button handler for reboot button var rebootLinode = function(event) { if (!confirm("Are you sure you want to issue a Reboot?")) return; var config = getSelectedConfig(); var request = {}; if (config) request.config_id = config; // Reload the page on successful API return var callback = function() { location.reload(); }; apiPost("/linode/instances/" + data.params.lid + "/reboot", request, callback); }; // 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 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); apiGet("/linode/instances/" + data.params.lid + "/configs", displayConfigs, null); apiGet("/linode/instances/" + data.params.lid + "/disks", displayDisks, null); apiGet("/linode/instances/" + data.params.lid + "/volumes", displayVolumes, null); apiGet("/linode/instances/" + data.params.lid + "/transfer", displayTransfer, null); var filter = { "entity.type": "linode", "entity.id": parseInt(data.params.lid) }; apiGet("/account/events", displayEvents, filter); apiGet("/account/notifications", displayNotifications, null); apiGet("/linode/instances/" + data.params.lid + "/stats", displayStats, null); }; // Button handler for shutdown button var shutDownLinode = function(event) { if (!confirm("Are you sure you want to issue a Shutdown?")) return; // Reload the page on successful API return var callback = function() { location.reload(); }; apiPost("/linode/instances/" + data.params.lid + "/shutdown", {}, callback); }; // Toggle visibility of "extra" events var toggleEvents = function(event) { // Flip state.showExtraEvents = !state.showExtraEvents; var display = "none"; if (state.showExtraEvents) display = "table-row"; // Get extra events and show/hide them var extras = document.getElementsByClassName(elements.extraEvent); for (var i = 0; i < extras.length; i++) extras[i].style.display = display; }; // Update an existing event var updateEvent = function(event) { // Update the local copy of the event var progress = 0; for (var i = 0; i < data.events.length; i++) { if (data.events[i].id == event.id) { progress = event.percent_complete - data.events[i].percent_complete; // Refresh details if event status changed if (event.status != data.events[i].status && !state.linodeRefresh && !state.diskRefresh) { state.linodeRefresh = true; state.diskRefresh = true; data.linode = {}; data.disks = []; apiGet("/linode/instances/" + data.params.lid, displayDetails, null); apiGet("/linode/instances/" + data.params.lid + "/disks", displayDisks, null); } data.events[i] = event; continue; } } // Generate a new event row and replace the existing one var eventRow = document.getElementById(elements.eventRowPrefix + event.id); var extra = (eventRow.className.indexOf(elements.extraEvent) != -1); eventRow.replaceWith(createEventRow(event, extra)); // Update progress bar ui.jobProgress.value += progress; ui.jobProgress.innerHTML = (ui.jobProgress.value / ui.jobProgress.max * 100).toFixed(0) + "%"; ui.jobProgress.title = ui.jobProgress.innerHTML; if (ui.jobProgress.value == ui.jobProgress.max) ui.jobProgressRow.style.display = "none"; // Refresh again if not complete if (!event.status.match(/failed|finished|notification/)) 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); })();