2020-01-10 00:24:59 -05:00
|
|
|
/*
|
|
|
|
* 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"
|
|
|
|
};
|
|
|
|
|
|
|
|
// Regions (Linode doesn't provide "friendly" names via the API)
|
|
|
|
var regionNames = {
|
|
|
|
"us-central": "Dallas, TX, USA",
|
|
|
|
"us-west": "Fremont, CA, USA",
|
|
|
|
"us-southeast": "Atlanta, GA, USA",
|
|
|
|
"us-east": "Newark, NJ, USA",
|
|
|
|
"us-east-1b": "Newark 2, NJ, USA",
|
|
|
|
"eu-west": "London, England, UK",
|
|
|
|
"ap-south": "Singapore, SG",
|
|
|
|
"eu-central": "Frankfurt, DE",
|
|
|
|
"ap-northeast": "Tokyo, JP",
|
|
|
|
"ap-northeast-1a": "Tokyo 2, JP",
|
|
|
|
"ca-central": "Toronto, ON, CA",
|
|
|
|
"ap-west": "Mumbai, IN",
|
|
|
|
"ap-southeast": "Sydney, AU",
|
2020-03-13 23:01:39 -04:00
|
|
|
"philadelphia": "Philadelphia, PA, USA",
|
|
|
|
"absecon": "Absecon, NJ, USA"
|
2020-01-10 00:24:59 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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",
|
2021-03-11 10:37:07 -05:00
|
|
|
"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",
|
2020-01-10 00:24:59 -05:00
|
|
|
"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",
|
2021-03-11 10:37:07 -05:00
|
|
|
"lke_node_create": "Create LKE Node",
|
|
|
|
"longviewclient_create": "Create Longview Client",
|
|
|
|
"longviewclient_delete": "Delete Longview Client",
|
|
|
|
"longviewclient_update": "Update Longview Client",
|
2020-01-10 00:24:59 -05:00
|
|
|
"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",
|
2021-03-11 10:37:07 -05:00
|
|
|
"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",
|
2020-01-10 00:24:59 -05:00
|
|
|
"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",
|
2021-03-11 10:37:07 -05:00
|
|
|
"tag_create": "Create Tag",
|
|
|
|
"tag_delete": "Delete Tag",
|
2020-01-10 00:24:59 -05:00
|
|
|
"tfa_disabled": "2FA Disabled",
|
|
|
|
"tfs_enabled": "2FA Enabled",
|
|
|
|
"ticket_attachment_upload": "Uploaded Ticket Attachment",
|
|
|
|
"ticket_create": "Create Ticket",
|
|
|
|
"ticket_update": "Update Ticket",
|
2021-03-11 10:37:07 -05:00
|
|
|
"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",
|
2020-01-10 00:24:59 -05:00
|
|
|
"user_ssh_key_add": "Add SSH Key",
|
2021-03-11 10:37:07 -05:00
|
|
|
"user_ssh_key_delete": "Delete SSH Key",
|
2020-01-10 00:24:59 -05:00
|
|
|
"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"
|
|
|
|
};
|
|
|
|
|
2021-03-11 10:37:07 -05:00
|
|
|
// 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"
|
|
|
|
};
|
|
|
|
|
2020-01-10 00:24:59 -05:00
|
|
|
// 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);
|
2020-03-13 23:01:39 -04:00
|
|
|
xmlhttp.setRequestHeader("Authorization", localStorage.apiKey);
|
2020-01-10 00:24:59 -05:00
|
|
|
|
|
|
|
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);
|
2020-03-13 23:01:39 -04:00
|
|
|
xmlhttp.setRequestHeader("Authorization", localStorage.apiKey);
|
2020-01-10 00:24:59 -05:00
|
|
|
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 = "/";
|
|
|
|
}
|
|
|
|
|
2021-03-11 10:37:07 -05:00
|
|
|
// Redirect to linodes page if unauthorized
|
|
|
|
if (xmlhttp.status == 403)
|
|
|
|
location.href = "/linodes";
|
|
|
|
|
2020-01-10 00:24:59 -05:00
|
|
|
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);
|
2020-03-13 23:01:39 -04:00
|
|
|
xmlhttp.setRequestHeader("Authorization", localStorage.apiKey);
|
2021-03-11 10:37:07 -05:00
|
|
|
if (data instanceof File)
|
|
|
|
xmlhttp.setRequestHeader("Content-Type", data.type);
|
|
|
|
else
|
|
|
|
xmlhttp.setRequestHeader("Content-Type", "application/json");
|
2020-01-10 00:24:59 -05:00
|
|
|
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
2021-03-11 10:37:07 -05:00
|
|
|
if (data instanceof File)
|
|
|
|
xmlhttp.send(data);
|
|
|
|
else
|
|
|
|
xmlhttp.send(JSON.stringify(data));
|
2020-01-10 00:24:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
2020-03-13 23:01:39 -04:00
|
|
|
xmlhttp.setRequestHeader("Authorization", localStorage.apiKey);
|
2021-03-11 10:37:07 -05:00
|
|
|
if (data instanceof File)
|
|
|
|
xmlhttp.setRequestHeader("Content-Type", data.type);
|
|
|
|
else
|
|
|
|
xmlhttp.setRequestHeader("Content-Type", "application/json");
|
2020-01-10 00:24:59 -05:00
|
|
|
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
2021-03-11 10:37:07 -05:00
|
|
|
if (data instanceof File)
|
|
|
|
xmlhttp.send(data);
|
|
|
|
else
|
|
|
|
xmlhttp.send(JSON.stringify(data));
|
2020-01-10 00:24:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
2021-03-11 10:37:07 -05:00
|
|
|
profilePic.src = "https://www.gravatar.com/avatar/" + md5(response.email, false);
|
2020-01-10 00:24:59 -05:00
|
|
|
profilePic.style = "display: initial;";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return an MD5 hash of the given string
|
2021-03-11 10:37:07 -05:00
|
|
|
function md5(str, binary)
|
2020-01-10 00:24:59 -05:00
|
|
|
{
|
|
|
|
// Convert string to bytes
|
2021-03-11 10:37:07 -05:00
|
|
|
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);
|
|
|
|
}
|
2020-01-10 00:24:59 -05:00
|
|
|
|
|
|
|
// 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);
|
2021-03-11 10:37:07 -05:00
|
|
|
if (binary && (i != 3 || j != 3))
|
|
|
|
md5Str += ":";
|
2020-01-10 00:24:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2021-05-26 19:44:30 -04:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2020-01-10 00:24:59 -05:00
|
|
|
// 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);
|
2020-03-13 23:01:39 -04:00
|
|
|
for (var i = subnavLinks.length - 1; i >= 0; i--) {
|
|
|
|
if ((location.origin + location.pathname).startsWith(subnavLinks[i].href.split("?")[0])) {
|
2020-01-10 00:24:59 -05:00
|
|
|
subnavLinks[i].className += " " + elements.subnavLinkActive;
|
2020-03-13 23:01:39 -04:00
|
|
|
break;
|
|
|
|
}
|
2020-01-10 00:24:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2020-01-22 23:03:30 -05:00
|
|
|
suffix = " from now";
|
2020-01-10 00:24:59 -05:00
|
|
|
ms = -ms;
|
|
|
|
} else {
|
2020-01-22 23:03:30 -05:00
|
|
|
suffix = " ago";
|
2020-01-10 00:24:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-22 23:03:30 -05:00
|
|
|
// 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]++;
|
2020-01-10 00:24:59 -05:00
|
|
|
}
|
2020-01-22 23:03:30 -05:00
|
|
|
while (units[5] >= 86400000) {
|
|
|
|
units[5] -= 86400000;
|
|
|
|
units[1]++;
|
2020-01-10 00:24:59 -05:00
|
|
|
}
|
2020-01-22 23:03:30 -05:00
|
|
|
while (units[5] >= 3600000) {
|
|
|
|
units[5] -= 3600000;
|
|
|
|
units[2]++;
|
2020-01-10 00:24:59 -05:00
|
|
|
}
|
2020-01-22 23:03:30 -05:00
|
|
|
while (units[5] >= 60000) {
|
|
|
|
units[5] -= 60000;
|
|
|
|
units[3]++;
|
2020-01-10 00:24:59 -05:00
|
|
|
}
|
2020-01-22 23:03:30 -05:00
|
|
|
while (units[5] >= 1000) {
|
|
|
|
units[5] -= 1000;
|
|
|
|
units[4]++;
|
2020-01-10 00:24:59 -05:00
|
|
|
}
|
|
|
|
|
2020-01-22 23:03:30 -05:00
|
|
|
// 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;
|
2020-01-10 00:24:59 -05:00
|
|
|
else
|
2020-01-22 23:03:30 -05:00
|
|
|
return first + second + suffix;
|
2020-01-10 00:24:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2021-05-26 19:44:30 -04:00
|
|
|
export { settings, elements, regionNames, apiDelete, apiGet, apiPost, apiPut, md5, migrateETA, oauthPost, oauthScopes, objPut, parseParams, setupHeader, eventTitles, timeString, translateKernel };
|