lmc/global.js

868 lines
24 KiB
JavaScript

/*
* 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/>.
*/
// Global default settings
var settings = {
"apiURL": "https://api.linode.com/v4",
"loginURL": "https://login.linode.com",
"oauthURL": "https://login.linode.com/oauth",
"preferredDistro": "linode/debian10",
"preferredKernel": "linode/grub2",
"refreshRate": 2000
};
// Strings containing the names of various elements and CSS selectors
var elements = {
"navlink": "navlink",
"navlinkActive": "navlink-active",
"profilePic": "profile-pic",
"subnavLink": "subnav-link",
"subnavLinkActive": "subnav-link-active",
"username": "username"
};
// Region names for legacy DCs no longer in the API
var regionNames = {
"us-east-1b": "Newark 2, NJ",
"philadelphia": "Philadelphia, PA",
"absecon": "Absecon, NJ"
};
// Group contries into regions for easier selection
var countryContinents = {
"us": "na",
"gb": "eu",
"jp": "ap",
"sg": "ap",
"de": "eu",
"in": "ap",
"ca": "na",
"au": "ap",
"fr": "eu",
"br": "sa",
"nl": "eu",
"se": "eu",
"es": "eu",
"it": "eu",
"id": "ap",
"nz": "ap",
"pl": "eu",
"za": "af",
"my": "ap",
"hk": "ap",
"co": "sa",
"mx": "na",
"cl": "sa"
};
// Human-readable event titles
var eventTitles = {
"account_update": "Update Account",
"account_settings_update": "Update Account Settings",
"backups_enable": "Backups Enabled",
"backups_cancel": "Backups Cancelled",
"backups_restore": "Restore From Backup",
"community_question_reply": "Community Question Reply",
"community_like": "Community Like",
"credit_card_updated": "Update Credit Card",
"disk_create": "Create Disk",
"disk_delete": "Delete Disk",
"disk_update": "Update Disk",
"disk_duplicate": "Clone Disk",
"disk_imagize": "Create Image From Disk",
"disk_resize": "Resize Disk",
"dns_record_create": "Create DNS Record",
"dns_record_delete": "Delete DNS Record",
"dns_record_update": "Update DNS Record",
"dns_zone_create": "Create DNS Zone",
"dns_zone_delete": "Delete DNS Zone",
"dns_zone_import": "Import DNS Zone",
"dns_zone_update": "Update DNS Zone",
"firewall_create": "Create Firewall",
"firewall_delete": "Delete Firewall",
"firewall_disable": "Disable Firewall",
"firewall_enable": "Enable Firewall",
"firewall_update": "Update Firewall",
"firewall_device_add": "Add Firewall Device",
"firewall_device_remove": "Remove Firewall Device",
"host_reboot": "Host Initiated Restart",
"image_delete": "Delete Image",
"image_update": "Update Image",
"ipaddress_update": "Update IP Address",
"lassie_reboot": "Lassie Initiated Boot",
"lish_boot": "LISH Initiated Boot",
"linode_addip": "Add IP Address",
"linode_boot": "System Boot",
"linode_clone": "Clone Linode",
"linode_create": "Linode Initial Configuration",
"linode_delete": "Inactivate Linode",
"linode_update": "Update Linode",
"linode_deleteip": "Remove IP Address",
"linode_migrate": "Migrate Linode",
"linode_migrate_datacenter": "Migrate Linode",
"linode_migrate_datacenter_create": "Migrate Linode",
"linode_mutate": "Upgrade Linode",
"linode_mutate_create": "Upgrade Linode",
"linode_reboot": "System Reboot",
"linode_rebuild": "Rebuild Linode",
"linode_resize": "Resize Linode",
"linode_resize_create": "Resize Linode",
"linode_shutdown": "System Shutdown",
"linode_snapshot": "Linode Snapshot",
"linode_config_create": "Create Configuration Profile",
"linode_config_delete": "Delete Configuration Profile",
"linode_config_update": "Update Configuration Profile",
"lke_node_create": "Create LKE Node",
"longviewclient_create": "Create Longview Client",
"longviewclient_delete": "Delete Longview Client",
"longviewclient_update": "Update Longview Client",
"managed_disabled": "Managed Service Disabled",
"managed_enabled": "Managed Service Enabled",
"managed_service_create": "Create Managed Service",
"managed_service_delete": "Delete Managed Service",
"nodebalancer_create": "NodeBalancer Initial Configuration",
"nodebalancer_delete": "Inactivate NodeBalancer",
"nodebalancer_update": "Update NodeBalancer",
"nodebalancer_config_create": "Create NodeBalancer Configuration",
"nodebalancer_config_delete": "Delete NodeBalancer Configuration",
"nodebalancer_config_update": "Update NodeBalancer Configuration",
"nodebalancer_node_create": "Create NodeBalancer Node",
"nodebalancer_node_delete": "Delete NodeBalancer Node",
"nodebalancer_node_update": "Update NodeBalancer Node",
"oauth_client_create": "Create OAuth Client",
"oauth_client_delete": "Delete OAuth Client",
"oauth_client_secret_reset": "Reset OAuth Client Secret",
"oauth_client_update": "Update OAuth Client",
"password_reset": "Change Root Password",
"payment_submitted": "Payment Submitted",
"profile_update": "Update Profile",
"stackscript_create": "Create StackScript",
"stackscript_delete": "Delete StackScript",
"stackscript_update": "Update StackScript",
"stackscript_publicize": "Publish StackScript",
"stackscript_revise": "Revise StackScript",
"tag_create": "Create Tag",
"tag_delete": "Delete Tag",
"tfa_disabled": "2FA Disabled",
"tfs_enabled": "2FA Enabled",
"ticket_attachment_upload": "Uploaded Ticket Attachment",
"ticket_create": "Create Ticket",
"ticket_update": "Update Ticket",
"token_create": "Create API Token",
"token_delete": "Delete API Token",
"token_update": "Update API Token",
"user_create": "Create User",
"user_delete": "Delete User",
"user_update": "Update User",
"user_ssh_key_add": "Add SSH Key",
"user_ssh_key_delete": "Delete SSH Key",
"user_ssh_key_update": "Update SSH Key",
"vlan_attach": "Attach VLAN",
"vlan_detach": "Detach VLAN",
"volume_attach": "Attach Volume",
"volume_clone": "Clone Volume",
"volume_create": "Create Volume",
"volume_delete": "Delete Volume",
"volume_update": "Update Volume",
"volume_detach": "Detach Volume",
"volume_resize": "Resize Volume"
};
// A list of OAuth scopes
var oauthScopes = {
"account": "Account",
"databases": "Databases",
"domains": "Domains",
"events": "Events",
"firewall": "Firewalls",
"images": "Images",
"ips": "IPs",
"linodes": "Linodes",
"lke": "Kubernetes",
"longview": "Longview",
"nodebalancers": "NodeBalancers",
"object_storage": "Object Storage",
"stackscripts": "StackScripts",
"volumes": "Volumes"
};
// Make an HTTP DELETE request to the Linode API
function apiDelete(endpoint, callback)
{
// Redirect to login if there is no API key or it's expired
if (localStorage.apiKey && localStorage.apiExpire) {
var now = new Date();
var expires = new Date(parseInt(localStorage.apiExpire));
if (expires <= now) {
alert("Your session has expired.");
localStorage.removeItem("apiKey");
localStorage.removeItem("apiExpire");
location.href = "/?skip=1&redirectTo=" + encodeURIComponent(location.href);
return;
}
} else {
location.href = "/";
return;
}
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("DELETE", settings.apiURL + endpoint, true);
xmlhttp.setRequestHeader("Authorization", localStorage.apiKey);
xmlhttp.onreadystatechange = function()
{
if (xmlhttp.readyState != 4)
return;
var response = JSON.parse(xmlhttp.responseText);
if (xmlhttp.status >= 400) {
console.log("Error " + xmlhttp.status);
console.log("DELETE " + settings.apiURL + endpoint);
if (response.errors) {
for (var i = 0; i < response.errors.length; i++) {
if (response.errors[i].field)
alert(response.errors[i].field + " " + response.errors[i].reason);
else
alert(response.errors[i].reason);
}
}
// Redirect to login if invalid API key
if (xmlhttp.status == 401) {
localStorage.removeItem("apiKey");
location.href = "/";
}
return;
}
response['_endpoint'] = endpoint;
callback(response);
};
xmlhttp.send();
}
// Make an HTTP GET request to the Linode API
function apiGet(endpoint, callback, filters)
{
// Redirect to login if there is no API key or it's expired
if (localStorage.apiKey && localStorage.apiExpire) {
var now = new Date();
var expires = new Date(parseInt(localStorage.apiExpire));
if (expires <= now) {
alert("Your session has expired.");
localStorage.removeItem("apiKey");
localStorage.removeItem("apiExpire");
location.href = "/?skip=1&redirectTo=" + encodeURIComponent(location.href);
return;
}
} else {
location.href = "/";
return;
}
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", settings.apiURL + endpoint, true);
xmlhttp.setRequestHeader("Authorization", localStorage.apiKey);
if (filters)
xmlhttp.setRequestHeader("X-Filter", JSON.stringify(filters));
xmlhttp.onreadystatechange = function()
{
if (xmlhttp.readyState != 4)
return;
var response = JSON.parse(xmlhttp.responseText);
if (xmlhttp.status >= 400) {
console.log("Error " + xmlhttp.status);
console.log("GET " + settings.apiURL + endpoint);
if (response.errors) {
for (var i = 0; i < response.errors.length; i++) {
if (response.errors[i].field)
alert(response.errors[i].field + " " + response.errors[i].reason);
else
alert(response.errors[i].reason);
}
}
// Redirect to login if invalid API key
if (xmlhttp.status == 401) {
localStorage.removeItem("apiKey");
location.href = "/";
}
// Redirect to linodes page if unauthorized
if (xmlhttp.status == 403)
location.href = "/linodes";
return;
}
response['_endpoint'] = endpoint;
callback(response);
};
xmlhttp.send();
}
// Make an HTTP POST request to the Linode API
function apiPost(endpoint, data, callback)
{
// Redirect to login if there is no API key or it's expired
if (localStorage.apiKey && localStorage.apiExpire) {
var now = new Date();
var expires = new Date(parseInt(localStorage.apiExpire));
if (expires <= now) {
alert("Your session has expired.");
localStorage.removeItem("apiKey");
localStorage.removeItem("apiExpire");
location.href = "/?skip=1&redirectTo=" + encodeURIComponent(location.href);
return;
}
} else {
location.href = "/";
return;
}
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", settings.apiURL + endpoint, true);
xmlhttp.setRequestHeader("Authorization", localStorage.apiKey);
if (data instanceof File)
xmlhttp.setRequestHeader("Content-Type", data.type);
else
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.onreadystatechange = function()
{
if (xmlhttp.readyState != 4)
return;
var response = JSON.parse(xmlhttp.responseText);
if (xmlhttp.status >= 400) {
console.log("Error " + xmlhttp.status);
console.log("POST " + settings.apiURL + endpoint);
if (response.errors) {
for (var i = 0; i < response.errors.length; i++) {
if (response.errors[i].field)
alert(response.errors[i].field + " " + response.errors[i].reason);
else
alert(response.errors[i].reason);
}
}
// Redirect to login if invalid token
if (xmlhttp.status == 401) {
localStorage.removeItem("apiKey");
location.href = "/";
}
return;
}
response['_endpoint'] = endpoint;
callback(response);
};
if (data instanceof File)
xmlhttp.send(data);
else
xmlhttp.send(JSON.stringify(data));
}
// Make an HTTP PUT request to the Linode API
function apiPut(endpoint, data, callback)
{
// Redirect to login if there is no API key or it's expired
if (localStorage.apiKey && localStorage.apiExpire) {
var now = new Date();
var expires = new Date(parseInt(localStorage.apiExpire));
if (expires <= now) {
alert("Your session has expired.");
localStorage.removeItem("apiKey");
localStorage.removeItem("apiExpire");
location.href = "/?skip=1&redirectTo=" + encodeURIComponent(location.href);
return;
}
} else {
location.href = "/";
return;
}
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("PUT", settings.apiURL + endpoint, true);
xmlhttp.setRequestHeader("Authorization", localStorage.apiKey);
if (data instanceof File)
xmlhttp.setRequestHeader("Content-Type", data.type);
else
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.onreadystatechange = function()
{
if (xmlhttp.readyState != 4)
return;
var response = JSON.parse(xmlhttp.responseText);
if (xmlhttp.status >= 400) {
console.log("Error " + xmlhttp.status);
console.log("PUT " + settings.apiURL + endpoint);
if (response.errors) {
for (var i = 0; i < response.errors.length; i++) {
if (response.errors[i].field)
alert(response.errors[i].field + " " + response.errors[i].reason);
else
alert(response.errors[i].reason);
}
}
// Redirect to login if invalid token
if (xmlhttp.status == 401) {
localStorage.removeItem("apiKey");
location.href = "/";
}
return;
}
response['_endpoint'] = endpoint;
callback(response);
};
if (data instanceof File)
xmlhttp.send(data);
else
xmlhttp.send(JSON.stringify(data));
}
// Callback for user info API call
function displayUser(response)
{
if (!response) {
console.log("Error getting profile data");
return;
}
// Display username
var usernameTag = document.getElementById(elements.username);
usernameTag.innerHTML = response.username;
// Display profile pic
var profilePic = document.getElementById(elements.profilePic);
profilePic.src = "https://www.gravatar.com/avatar/" + md5(response.email, false);
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)
{
// Convert string to bytes
var data;
if (binary) {
var strBytes = [];
for (var i = 0; i < str.length; i++)
strBytes[i] = str.charCodeAt(i);
data = new Uint8Array(strBytes);
} else {
data = new TextEncoder("utf-8").encode(str);
}
// Constants
var s = new Uint32Array([
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
]);
var K = new Uint32Array([
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
]);
// Initialize hash
var hash = new Uint32Array([0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]);
// Apply padding
var padLength = data.length + 1;
while (padLength % 64 != 56)
padLength++;
padLength += 8;
var padData = new Uint8Array(padLength);
for (var i = 0; i < data.length; i++)
padData[i] = data[i];
for (var i = data.length; i < padLength; i++)
padData[i] = 0x0;
// Append 1 bit
padData[data.length] = 0x80;
// Append original length
var bits = data.length * 8;
var upperLen = parseInt(bits.toString(16).slice(0, -8), 16);
if (isNaN(upperLen))
upperLen = 0;
padData[padLength-8] = bits;
padData[padLength-7] = bits >>> 8;
padData[padLength-6] = bits >>> 16;
padData[padLength-5] = bits >>> 24;
padData[padLength-4] = upperLen;
padData[padLength-3] = upperLen >>> 8;
padData[padLength-2] = upperLen >>> 16;
padData[padLength-1] = upperLen >>> 24;
// Process the data in 64-byte chunks
for (var i = 0; i < padLength; i += 64) {
var M = new Uint32Array(16);
for (var j = 0, offset = i; j < 16; j++, offset += 4)
M[j] = padData[offset] + (padData[offset+1] << 8) + (padData[offset+2] << 16) + (padData[offset+3] << 24);
// Main loop
var chunk = new Uint32Array(hash);
for (var j = 0; j < 64; j++) {
var locals = new Uint32Array(2);
if (j <= 15) {
locals[0] = (chunk[1] & chunk[2]) | ((~chunk[1]) & chunk[3]);
locals[1] = j;
} else if (j <= 31) {
locals[0] = (chunk[3] & chunk[1]) | ((~chunk[3]) & chunk[2]);
locals[1] = (5 * j + 1) % 16;
} else if (j <= 47) {
locals[0] = chunk[1] ^ chunk[2] ^ chunk[3];
locals[1] = (3 * j + 5) % 16;
} else {
locals[0] = chunk[2] ^ (chunk[1] | (~chunk[3]));
locals[1] = (7 * j) % 16;
}
locals[0] += chunk[0] + K[j] + M[locals[1]];
chunk[0] = chunk[3];
chunk[3] = chunk[2];
chunk[2] = chunk[1];
chunk[1] += rotl(locals[0], s[j]);
}
// Add this chunk's hash to the main hash
hash[0] += chunk[0];
hash[1] += chunk[1];
hash[2] += chunk[2];
hash[3] += chunk[3];
}
// Convert to string
var md5Str = "";
for (var i = 0; i < 4; i++) {
var bytes = new Uint8Array([hash[i], hash[i] >> 8, hash[i] >> 16, hash[i] >> 24]);
for (var j = 0; j < 4; j++) {
if (bytes[j] < 0x10)
md5Str += "0";
md5Str += bytes[j].toString(16);
if (binary && (i != 3 || j != 3))
md5Str += ":";
}
}
return md5Str;
}
// Returns the ETA for a migration given the disk size (MB) and destination
function migrateETA(size, local)
{
var str = "";
// Calculate minutes
var minutes = 0;
if (local)
minutes = (0.75 * size / 1024).toFixed(0);
else
minutes = (10 * size / 1024).toFixed(0);
// Divide into hours
if (minutes >= 60) {
var hours = Math.floor(minutes / 60);
minutes = minutes % 60;
if (hours == 1)
hours = hours + " hour and ";
else
hours = hours + " hours and ";
str += hours;
}
if (minutes == 1)
minutes = minutes + " minute";
else
minutes = minutes + " minutes";
str += minutes;
return str;
}
// Make an OAuth HTTP POST request
function oauthPost(endpoint, data, callback)
{
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", settings.oauthURL + endpoint, true);
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlhttp.onreadystatechange = function()
{
if (xmlhttp.readyState != 4)
return;
var response = JSON.parse(xmlhttp.responseText);
if (xmlhttp.status >= 400) {
console.log("Error " + xmlhttp.status);
console.log("POST " + settings.oauthURL + endpoint);
if (response.errors) {
for (var i = 0; i < response.errors.length; i++) {
if (response.errors[i].field)
alert(response.errors[i].field + " " + response.errors[i].reason);
else
alert(response.errors[i].reason);
}
}
return;
}
response['_endpoint'] = endpoint;
callback(response);
};
xmlhttp.send(data.toString());
}
// Make an object storage HTTP PUT request
function objPut(url, data, progress, callback)
{
var xmlhttp = new XMLHttpRequest();
xmlhttp.upload.addEventListener("progress", progress);
xmlhttp.open("PUT", url, true);
xmlhttp.setRequestHeader("Content-Type", "application/octet-stream");
xmlhttp.onreadystatechange = function()
{
if (xmlhttp.readyState != 4)
return;
if (xmlhttp.status >= 400) {
console.log("Error " + xmlhttp.status);
console.log("PUT " + url);
alert("An error occurred during file upload!");
return;
}
callback();
};
xmlhttp.send(data);
}
// Parse URL parameters
function parseParams()
{
var parsed = {};
if (location.search.length > 1)
var params = new URLSearchParams(location.search.slice(1));
else
var params = new URLSearchParams(location.hash.slice(1));
for (var pair of params.entries())
parsed[pair[0]] = pair[1];
return parsed;
}
// Bitwise left-rotate
function rotl(val, sft)
{
return (val << sft) | (val >>> (32 - sft));
}
// Setup the page header
function setupHeader()
{
// Highlight the current page in the navbar
var navlinks = document.getElementsByClassName(elements.navlink);
for (var i = 0; i < navlinks.length; i++) {
if ((location.origin + location.pathname).startsWith(navlinks[i].href))
navlinks[i].className = navlinks[i].className.replace(elements.navlink, elements.navlinkActive);
}
// Highlight the current page in the subnav
var subnavLinks = document.getElementsByClassName(elements.subnavLink);
for (var i = subnavLinks.length - 1; i >= 0; i--) {
if ((location.origin + location.pathname).startsWith(subnavLinks[i].href.split("?")[0])) {
subnavLinks[i].className += " " + elements.subnavLinkActive;
break;
}
}
// Get user info
apiGet("/profile", displayUser, null);
}
// Convert a millisecond count into a "friendly" time string
function timeString(ms, includeSuffix)
{
// Future or past?
var suffix = "";
if (includeSuffix) {
if (ms < 0) {
suffix = " from now";
ms = -ms;
} else {
suffix = " ago";
}
}
// Break into years, days, hours, minutes, seconds, and ms
var units = [0, 0, 0, 0, 0, ms.toFixed(0)];
var unitNames = ["year", "day", "hour", "minute", "second", "millisecond"];
while (units[5] >= 31536000000) {
units[5] -= 31536000000;
units[0]++;
}
while (units[5] >= 86400000) {
units[5] -= 86400000;
units[1]++;
}
while (units[5] >= 3600000) {
units[5] -= 3600000;
units[2]++;
}
while (units[5] >= 60000) {
units[5] -= 60000;
units[3]++;
}
while (units[5] >= 1000) {
units[5] -= 1000;
units[4]++;
}
// Grab the first two non-zero units
var first = "";
var second = "";
for (var i = 0; i < units.length; i++) {
if (!units[i])
continue;
if (!first.length) {
first = units[i] + " " + unitNames[i];
if (units[i] > 1)
first += "s";
continue;
}
if (!second.length) {
second = " " + units[i] + " " + unitNames[i];
if (units[i] > 1)
second += "s";
break;
}
}
if (!first.length)
return "0 milliseconds" + suffix;
else
return first + second + suffix;
}
// Translates a kernel slug into a label and sets the contents of an element
function translateKernel(slug, element)
{
var callback = function(response)
{
element.innerHTML = response.label;
};
apiGet("/linode/kernels/" + slug, callback, null);
}
export { settings, elements, regionNames, countryContinents, apiDelete, apiGet, apiPost, apiPut, drawSeries, md5, migrateETA, oauthPost, oauthScopes, objPut, parseParams, setupHeader, eventTitles, timeString, translateKernel };