diff --git a/README.md b/README.md
index 078bf56..aba6b1a 100644
--- a/README.md
+++ b/README.md
@@ -10,17 +10,18 @@ The canonical deployment of this app can be found at https://lmc.laboon.io, howe
## Development Progress
This project is currently a work in progress. The following list provides a high-level overview which features have been implemented and which features are planned for the future:
- - [x] ~~OAuth Authentication/Login/Logout~~
- - [x] ~~Gravatar~~
- - [x] ~~Linodes (including the dashboard and all related subpages, but not including graphs)~~
- - [x] ~~Block Storage (Volumes)~~
- - [x] ~~Images~~
- - [x] ~~DNS~~
+ - [x] OAuth Authentication/Login/Logout
+ - [x] Gravatar
+ - [x] Linodes (including the dashboard and all related subpages, but not including graphs)
+ - [x] Block Storage (Volumes)
+ - [x] Images
+ - [x] DNS
+ - [x] Account Details (exluding PayPal support)
+ - [ ] PayPal payments
- [ ] Graphs
- [ ] StackScripts
- [ ] NodeBalancers
- [ ] Longview
- - [ ] Account Details
- [ ] User Profile Settings
- [ ] Support Tickets
diff --git a/account/account.css b/account/account.css
new file mode 100644
index 0000000..4ea2f6a
--- /dev/null
+++ b/account/account.css
@@ -0,0 +1,91 @@
+/*
+ * 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 url('/global.css');
+
+#account {
+ padding: 15px 15px 15px;
+}
+
+#balance {
+ font-weight: bold;
+}
+
+.balance-negative {
+ color: #F00;
+}
+
+.balance-positive {
+ color: #008000;
+}
+
+#billing-activity tr td:first-of-type {
+ font-weight: normal;
+ text-align: left;
+}
+
+#billing-activity tr td:last-of-type {
+ text-align: right;
+}
+
+#billing-link {
+ text-align: right;
+}
+
+#billing-row {
+ border: none;
+}
+
+#cancel {
+ font-size: 12px;
+}
+
+.gdpr {
+ display: none;
+}
+
+#learn-more {
+ font-size: 11px;
+}
+
+#managed {
+ display: none;
+ margin-top: 50px;
+}
+
+p {
+ margin-top: 50px;
+ text-align: center;
+}
+
+#pay {
+ display: none;
+}
+
+.promotions {
+ display: none;
+}
+
+tbody:not(.lmc-tbody-head) tr td:first-of-type {
+ font-weight: bold;
+ text-align: right;
+ white-space: nowrap;
+}
+
+#uninvoiced {
+ display: none;
+}
diff --git a/account/account.js b/account/account.js
new file mode 100644
index 0000000..8188a77
--- /dev/null
+++ b/account/account.js
@@ -0,0 +1,387 @@
+/*
+ * 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, apiGet, apiPost, parseParams, setupHeader, timeString } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.active = "active";
+ elements.address = "address";
+ elements.balance = "balance";
+ elements.balanceNegative = "balance-negative";
+ elements.balancePositive = "balance-positive";
+ elements.balanceStatus = "balance-status";
+ elements.billingActivity = "billing-activity";
+ elements.ccNumber = "cc-number";
+ elements.ccDuration = "cc-duration";
+ elements.ccExpire = "cc-expire";
+ elements.current = "current";
+ elements.email = "email";
+ elements.gdpr = "gdpr";
+ elements.gdprDate = "gdpr-date";
+ elements.info = "info";
+ elements.lmcRow = "lmc-tr1";
+ elements.lmcRowAlt = "lmc-tr2";
+ elements.lmcRowStandard = "lmc-tr3";
+ elements.managed = "managed";
+ elements.managedButton = "managed-button";
+ elements.pay = "pay";
+ elements.promotions = "promotions";
+ elements.promotionsTable = "promotions-table";
+ elements.uninvoiced = "uninvoiced";
+ elements.uninvoicedBalance = "uninvoiced-balance";
+
+ // Data received from API calls
+ var data = {};
+ data.account = {};
+ data.invoices = [];
+ data.linodes = [];
+ data.payments = [];
+
+ // Static references to UI elements
+ var ui = {};
+ ui.active = {};
+ ui.address = {};
+ ui.balance = {};
+ ui.balanceStatus = {};
+ ui.billingActivity = {};
+ ui.ccNumber = {};
+ ui.ccDuration = {};
+ ui.ccExpire = {};
+ ui.current = {};
+ ui.email = {};
+ ui.gdpr = [];
+ ui.gdprDate = {};
+ ui.managed = {};
+ ui.managedButton = {};
+ ui.pay = {};
+ ui.promotions = [];
+ ui.promotionsTable = {};
+ ui.uninvoiced = {};
+ ui.uninvoicedBalance = {};
+
+ // Temporary state
+ var state = {};
+ state.haveInvoices = false;
+ state.havePayments = false;
+
+ // Creates a row for the promotion table
+ var createPromoRow = function(promo)
+ {
+ var row = document.createElement("tr");
+ row.className = elements.lmcRowStandard;
+
+ var name = document.createElement("td");
+ name.innerHTML = promo.summary;
+ row.appendChild(name);
+
+ var details = document.createElement("td");
+ var line1 = document.createElement("span");
+ var expire = new Date(promo.expire_dt + "Z");
+ line1.innerHTML = "$" + promo.credit_remaining + " remaining. Exp: " + expire.toLocaleDateString();
+ var br = document.createElement("br");
+ var line2 = document.createElement("span");
+ line2.innerHTML = "($" + promo.this_month_credit_remaining + " remaining this month. Monthly cap: $" + promo.credit_monthly_cap + ")";
+ details.appendChild(line1);
+ details.appendChild(br);
+ details.appendChild(line2);
+ row.appendChild(details);
+
+ var description = document.createElement("td");
+ description.className = elements.info;
+ description.innerHTML = promo.description;
+ row.appendChild(description);
+
+ return row;
+ };
+
+ // Creates a row for the recent activity table
+ var createRecentRow = function(recent, alt)
+ {
+ var row = document.createElement("tr");
+ if (alt)
+ row.className = elements.lmcRowAlt;
+ else
+ row.className = elements.lmcRow;
+
+ var dateCell = document.createElement("td");
+ var date = document.createElement("span");
+ var recentDate = new Date(recent.date + "Z");
+ var now = new Date();
+ date.innerHTML = recentDate.toLocaleDateString();
+ var br = document.createElement("br");
+ var ago = document.createElement("span");
+ ago.className = elements.info;
+ ago.innerHTML = timeString(now - recentDate, true);
+ dateCell.appendChild(date);
+ dateCell.appendChild(br);
+ dateCell.appendChild(ago);
+ row.appendChild(dateCell);
+
+ if (recent.label) {
+ // Invoice stuff
+ var linkCell = document.createElement("td");
+ var link = document.createElement("a");
+ link.href = "/account/invoice?iid=" + recent.id;
+ link.innerHTML = recent.label;
+ linkCell.appendChild(link);
+ row.appendChild(linkCell);
+
+ var total = document.createElement("td");
+ if (recent.total < 0)
+ total.innerHTML = "($" + (-recent.total).toFixed(2) + ")";
+ else
+ total.innerHTML = "$" + recent.total.toFixed(2);
+ row.appendChild(total);
+ } else {
+ // Payment stuff
+ var thanks = document.createElement("td");
+ thanks.innerHTML = "Payment. Thank you";
+ row.appendChild(thanks);
+
+ var total = document.createElement("td");
+ if (total > 0)
+ total.innerHTML = "($" + recent.total.toFixed(2) + ")";
+ else
+ total.innerHTML = "$" + (-recent.total).toFixed(2);
+ row.appendChild(total);
+ }
+
+ return row;
+ };
+
+ // Callback for account details API call
+ var displayAccount = function(response)
+ {
+ data.account = response;
+
+ // Contact info
+ var br = document.createElement("br");
+ if (data.account.company.length) {
+ var company = document.createElement("span");
+ company.innerHTML = data.account.company;
+ ui.address.appendChild(company);
+ ui.address.appendChild(br);
+ }
+ var name = document.createElement("span");
+ name.innerHTML = data.account.first_name + " " + data.account.last_name;
+ ui.address.appendChild(name);
+ ui.address.appendChild(br.cloneNode(true));
+ var address1 = document.createElement("span");
+ address1.innerHTML = data.account.address_1;
+ ui.address.appendChild(address1);
+ ui.address.appendChild(br.cloneNode(true));
+ if (data.account.address_2.length) {
+ var address2 = document.createElement("span");
+ address2.innerHTML = data.account.address_2;
+ ui.address.appendChild(address2);
+ ui.address.appendChild(br.cloneNode(true));
+ }
+ var address3 = document.createElement("span");
+ address3.innerHTML = data.account.city + ", " + data.account.state + " " + data.account.zip;
+ ui.address.appendChild(address3);
+
+ // Email
+ ui.email.innerHTML = data.account.email;
+
+ // CC info
+ ui.ccNumber.innerHTML = data.account.credit_card.last_four;
+ var expired = document.createElement("span");
+ var strong = document.createElement("strong");
+ strong.innerHTML = "Expired!";
+ var dash = document.createElement("span");
+ dash.innerHTML = " - ";
+ var updateLink = document.createElement("a");
+ updateLink.href = "/account/creditcard";
+ updateLink.innerHTML = "update credit card";
+ expired.appendChild(strong);
+ expired.appendChild(dash);
+ expired.appendChild(updateLink);
+ if (data.account.credit_card.expiry) {
+ ui.ccExpire.innerHTML = data.account.credit_card.expiry;
+ var monthYear = data.account.credit_card.expiry.split("/");
+ var expireDate = new Date(parseInt(monthYear[1]), parseInt(monthYear[0]), 0);
+ var now = new Date();
+ if (expireDate - now > 0)
+ ui.ccDuration.innerHTML = "Expires " + timeString(now - expireDate, true);
+ else
+ ui.ccDuration.appendChild(expired);
+ }
+
+ // Account balance
+ if (data.account.balance == 0) {
+ ui.balance.className = elements.balancePositive;
+ ui.current.innerHTML = "Your account is current.";
+ } else if (data.account.balance < 0) {
+ ui.balance.className = elements.balancePositive;
+ ui.balanceStatus.innerHTML = " credit";
+ ui.current.innerHTML = "This will be applied towards future invoices.";
+ data.account.balance = -data.account.balance;
+ } else {
+ ui.balance.className = elements.balanceNegative;
+ ui.balanceStatus.innerHTML = " due ";
+ ui.pay.href += "?amount=" + (-data.account.balance);
+ ui.pay.style.display = "initial";
+ ui.current.innerHTML = "Please pay now to avoid any service interruptions.";
+ }
+ ui.balance.innerHTML = "$" + data.account.balance.toFixed(2);
+ ui.uninvoicedBalance.innerHTML = "$" + data.account.balance_uninvoiced.toFixed(2);
+ if (data.account.balance_uninvoiced > 0)
+ ui.uninvoiced.style.display = "table-row";
+
+ // Promotions
+ if (data.account.active_promotions.length) {
+ for (var i = 0; i < ui.promotions.length; i++)
+ ui.promotions[i].style.display = "table-row-group";
+ }
+ for (var i = 0; i < data.account.active_promotions.length; i++)
+ ui.promotionsTable.appendChild(createPromoRow(data.account.active_promotions[i]));
+
+ var active = new Date(data.account.active_since + "Z");
+ ui.active.innerHTML = active.toLocaleDateString();
+ };
+
+ // Callback for invoices API call
+ var displayInvoices = function(response)
+ {
+ data.invoices = data.invoices.concat(response.data);
+
+ // Request the next page if there are more
+ if (response.page != response.pages) {
+ apiGet("/account/invoices?page=" + (response.page + 1), displayInvoices, null);
+ return;
+ }
+
+ state.haveInvoices = true;
+ if (state.havePayments)
+ displayRecent();
+ };
+
+ // Callback for linodes API call
+ var displayLinodes = function(response)
+ {
+ data.linodes = data.linodes.concat(response.data);
+
+ // Request the next page if there are more
+ if (response.page != response.pages) {
+ apiGet("/linode/instances?page=" + (response.page + 1), displayLinodes, null);
+ return;
+ }
+
+ ui.managedButton.disabled = false;
+ };
+
+ // Callback for payments API call
+ var displayPayments = function(response)
+ {
+ data.payments = data.payments.concat(response.data);
+
+ // Request the next page if there are more
+ if (response.page != response.pages) {
+ apiGet("/account/payments?page=" + (response.page + 1), displayPayments, null);
+ return;
+ }
+
+ state.havePayments = true;
+ if (state.haveInvoices)
+ displayRecent();
+ };
+
+ // Populates table with recent invoices and payments
+ var displayRecent = function()
+ {
+ // Combine to single array and sort by date
+ var recent = data.invoices.concat(data.payments);
+ recent.sort(function(a, b)
+ {
+ var aDate = new Date(a.date + "Z");
+ var bDate = new Date(b.date + "Z");
+ return aDate - bDate;
+ });
+
+ // Insert items into table
+ for (var i = recent.length - 1, count = 0; i >= 0, count < 4; i--, count++)
+ ui.billingActivity.appendChild(createRecentRow(recent[i], ui.billingActivity.children.length % 2));
+ };
+
+ // Callback for account settings API call
+ var displaySettings = function(response)
+ {
+ // Show managed signup if not managed
+ if (!response.managed)
+ ui.managed.style.display = "table";
+ };
+
+ // Click handler for managed button
+ var handleManaged = function(event)
+ {
+ if (event.currentTarget.disabled)
+ return;
+
+ if (!confirm("Linode Managed costs an additional $100/mo per Linode. This will increase your projected monthly bill by $" + (data.linodes.length * 100) + ". Are you sure?"))
+ return;
+
+ apiPost("/account/settings/managed-enable", {}, function(response)
+ {
+ location.reload();
+ });
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ setupHeader();
+
+ // Get element references
+ ui.active = document.getElementById(elements.active);
+ ui.address = document.getElementById(elements.address);
+ ui.balance = document.getElementById(elements.balance);
+ ui.balanceStatus = document.getElementById(elements.balanceStatus);
+ ui.billingActivity = document.getElementById(elements.billingActivity);
+ ui.ccNumber = document.getElementById(elements.ccNumber);
+ ui.ccDuration = document.getElementById(elements.ccDuration);
+ ui.ccExpire = document.getElementById(elements.ccExpire);
+ ui.current = document.getElementById(elements.current);
+ ui.email = document.getElementById(elements.email);
+ ui.gdpr = document.getElementsByClassName(elements.gdpr);
+ ui.gdprDate = document.getElementById(elements.gdprDate);
+ ui.managed = document.getElementById(elements.managed);
+ ui.managedButton = document.getElementById(elements.managedButton);
+ ui.pay = document.getElementById(elements.pay);
+ ui.promotions = document.getElementsByClassName(elements.promotions);
+ ui.promotionsTable = document.getElementById(elements.promotionsTable);
+ ui.uninvoiced = document.getElementById(elements.uninvoiced);
+ ui.uninvoicedBalance = document.getElementById(elements.uninvoicedBalance);
+
+ // Register event handlers
+ ui.managedButton.addEventListener("click", handleManaged);
+
+ // Get data from API
+ apiGet("/account", displayAccount, null);
+ apiGet("/account/settings", displaySettings, null);
+ apiGet("/account/invoices", displayInvoices, null);
+ apiGet("/account/payments", displayPayments, null);
+ apiGet("/linode/instances", displayLinodes, null);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/account/backups_enable_all/backups_enable_all.css b/account/backups_enable_all/backups_enable_all.css
new file mode 100644
index 0000000..08f2606
--- /dev/null
+++ b/account/backups_enable_all/backups_enable_all.css
@@ -0,0 +1,30 @@
+/*
+ * 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 url('/global.css');
+
+#backups-enable-all {
+ padding: 15px 15px 15px;
+}
+
+h2 {
+ font-size: 18px;
+}
+
+.info {
+ font-size: 12px;
+}
diff --git a/account/backups_enable_all/backups_enable_all.js b/account/backups_enable_all/backups_enable_all.js
new file mode 100644
index 0000000..ff8fc2e
--- /dev/null
+++ b/account/backups_enable_all/backups_enable_all.js
@@ -0,0 +1,179 @@
+/*
+ * 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, apiGet, apiPost, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.cost = "cost";
+ elements.doIt = "doit";
+ elements.linodeCount = "linode-count";
+ elements.subnav = "subnav-link";
+ elements.subnavActive = "subnav-link-active";
+
+ // Data received from API calls
+ var data = {};
+ data.linodes = [];
+ data.plans = {};
+
+ // Static references to UI elements
+ var ui = {};
+ ui.cost = {};
+ ui.dotIt = {};
+ ui.linodeCount = [];
+
+ // Temporary state
+ var state = {};
+ state.haveLinodes = false;
+ state.havePlans = false;
+ state.numResponses = 0;
+ state.numRequests = 0;
+
+ // Computes and displays the total monthly cost
+ var displayCost = function()
+ {
+ var cost = 0;
+ var haveAll = true;
+ for (var i = 0; i < data.linodes.length; i++) {
+ if (data.linodes[i].backups.enabled)
+ continue;
+
+ if (!data.plans[data.linodes[i].type]) {
+ haveAll = false;
+ apiGet("/linode/types/" + data.linodes[i].type, function(response)
+ {
+ data.plans[response.id] = response;
+ displayCost();
+ }, null);
+ continue;
+ }
+
+ cost += data.plans[data.linodes[i].type].addons.backups.price.monthly;
+ }
+
+ if (haveAll) {
+ ui.cost.innerHTML = cost.toFixed(2);
+ ui.doIt.disabled = false;
+ }
+ };
+
+ // Callback for linodes API call
+ var displayLinodes = function(response)
+ {
+ data.linodes = data.linodes.concat(response.data);
+
+ // Request the next page if there are more
+ if (response.page != response.pages) {
+ apiGet("/linode/instances?page=" + (response.page + 1), displayLinodes, null);
+ return;
+ }
+
+ state.haveLinodes = true;
+
+ // Display count of linodes without backups
+ var count = 0;
+ for (var i = 0; i < data.linodes.length; i++) {
+ if (!data.linodes[i].backups.enabled)
+ count++;
+ }
+
+ // If there are no linodes without backups, go back
+ if (!count)
+ location.href = "/account/settings";
+
+ for (var i = 0; i < ui.linodeCount.length; i++)
+ ui.linodeCount[i].innerHTML = count;
+
+ if (state.havePlans)
+ displayCost();
+ };
+
+ // Callback for plans API call
+ var displayPlans = function(response)
+ {
+ for (var i = 0; i < response.data.length; i++)
+ data.plans[response.data[i].id] = response.data[i];
+
+ // Request the next page if there are more
+ if (response.page != response.pages) {
+ apiGet("/linode/types?page=" + (response.page + 1), displayPlans, null);
+ return;
+ }
+
+ state.havePlans = true;
+
+ if (state.haveLinode)
+ displayCost();
+ };
+
+ // Click handler for enable button
+ var doIt = function(event)
+ {
+ if (event.currentTarget.disabled)
+ return;
+
+ // Enable backups for each linode without backups
+ for (var i = 0; i < data.linodes.length; i++) {
+ if (data.linodes[i].backups.enabled)
+ continue;
+
+ state.numRequests++;
+ apiPost("/linode/instances/" + data.linodes[i].id + "/backups/enable", {}, function(response)
+ {
+ state.numResponses++;
+ if (state.numResponses >= state.numRequests)
+ location.href = "/account/settings";
+ });
+ }
+
+ ui.doIt.disabled = true;
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ setupHeader();
+
+ // Highlight the account settings subnav link
+ var subnavLinks = document.getElementsByClassName(elements.subnav);
+ for (var i = 0; i < subnavLinks.length; i++) {
+ if (subnavLinks[i].pathname == "/account/settings")
+ subnavLinks[i].className = elements.subnav + " " + elements.subnavActive;
+ else
+ subnavLinks[i].className = elements.subnav;
+ }
+
+ // Get element references
+ ui.cost = document.getElementById(elements.cost);
+ ui.doIt = document.getElementById(elements.doIt);
+ ui.linodeCount = document.getElementsByClassName(elements.linodeCount);
+
+ // Attach event handlers
+ ui.doIt.addEventListener("click", doIt);
+
+ // Get data from the API
+ apiGet("/linode/instances", displayLinodes, null);
+ apiGet("/linode/types", displayPlans, null);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/account/backups_enable_all/index.shtml b/account/backups_enable_all/index.shtml
new file mode 100644
index 0000000..0431082
--- /dev/null
+++ b/account/backups_enable_all/index.shtml
@@ -0,0 +1,39 @@
+
+
+
+
+
+ LMC - Account // Enable Backups for All Linodes
+
+
+
+
+
+
+
+
+
+
Backup Enrollment
+
This will enable the Linode Backup Service for Linodes, for a total additional cost of $/month.
+
Are you sure you want to do this?
+
The cost of the Linode Backup Service for each Linode plan is listed on the Backups info page.
+
+
+
+
+
diff --git a/account/billing_history/billing_history.css b/account/billing_history/billing_history.css
new file mode 100644
index 0000000..d224c14
--- /dev/null
+++ b/account/billing_history/billing_history.css
@@ -0,0 +1,44 @@
+/*
+ * 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 url('/global.css');
+
+.balance-negative {
+ color: #F00;
+}
+
+.balance-positive {
+ color: #008000;
+}
+
+#billing-history {
+ padding: 15px 15px 15px;
+}
+
+#current {
+ font-size: 13px;
+ font-weight: bold;
+ text-align: right;
+}
+
+td:nth-of-type(3) {
+ text-align: right;
+}
+
+tr {
+ border-bottom: 1px solid #E8E8E8;
+}
diff --git a/account/billing_history/billing_history.js b/account/billing_history/billing_history.js
new file mode 100644
index 0000000..b9e001f
--- /dev/null
+++ b/account/billing_history/billing_history.js
@@ -0,0 +1,210 @@
+/*
+ * 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, apiGet, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.balance = "balance";
+ elements.balanceNegative = "balance-negative";
+ elements.billingTable = "billing-table";
+ elements.lmcRow = "lmc-tr1";
+ elements.lmcRowAlt = "lmc-tr2";
+ elements.loading = "loading";
+
+ // Data received from API calls
+ var data = {};
+ data.invoices = [];
+ data.payments = [];
+
+ // Static references to UI elements
+ var ui = {};
+ ui.balance = {};
+ ui.billingTable = {};
+ ui.loading = {};
+
+ // Temporary state
+ var state = {};
+ state.haveInvoices = false;
+ state.havePayments = false;
+
+ // Creates a row for the billing table
+ var createBillingRow = function(item, alt)
+ {
+ var row = document.createElement("tr");
+ if (alt)
+ row.className = elements.lmcRowAlt;
+ else
+ row.className = elements.lmcRow;
+
+ var dateCell = document.createElement("td");
+ var date = new Date(item.date + "Z");
+ dateCell.innerHTML = date.toLocaleDateString();
+ row.appendChild(dateCell);
+
+ var description = document.createElement("td");
+ var amount = document.createElement("td");
+
+ if (item.year) {
+ // Yearly payment report
+ var span1 = document.createElement("span");
+ span1.innerHTML = "Yearly Payments Report (";
+ var link = document.createElement("a");
+ link.href = "/account/paymentreceiptyear?year=" + item.year;
+ link.innerHTML = item.year;
+ var span2 = document.createElement("span");
+ span2.innerHTML = ")";
+ description.appendChild(span1);
+ description.appendChild(link);
+ description.appendChild(span2);
+ } else if (item.label) {
+ // Invoice
+ var link = document.createElement("a");
+ link.href = "/account/invoice?iid=" + item.id;
+ link.innerHTML = item.label;
+ description.appendChild(link);
+
+ if (item.total < 0)
+ amount.innerHTML = "($" + (-item.total).toFixed(2) + ")";
+ else
+ amount.innerHTML = "$" + item.total.toFixed(2);
+ } else {
+ // Payment
+ var link = document.createElement("a");
+ link.href = "/account/paymentreceipt?pid=" + item.id;
+ link.innerHTML = "Payment";
+ var thanks = document.createElement("span");
+ thanks.innerHTML = " - Thank you";
+ description.appendChild(link);
+ description.appendChild(thanks);
+
+ if (item.usd > 0)
+ amount.innerHTML = "($" + item.usd.toFixed(2) + ")";
+ else
+ amount.innerHTML = "$" + (-item.usd).toFixed(2);
+ }
+
+ row.appendChild(description);
+ row.appendChild(amount);
+
+ return row;
+ };
+
+ // Populate billing table with invoices and payments
+ var displayAll = function()
+ {
+ // Remove loading row
+ ui.loading.remove();
+
+ // Combine to single array
+ var combined = data.invoices.concat(data.payments);
+
+ // Insert yearly payment reports
+ var now = new Date();
+ var lowestYear = now.getFullYear();
+ for (var i = 0; i < combined.length; i++) {
+ var date = new Date(combined[i].date + "Z");
+ if (date.getFullYear() < lowestYear)
+ lowestYear = date.getFullYear();
+ }
+ for (var i = now.getFullYear(); i >= lowestYear; i--) {
+ combined.push({
+ "date": i + "-01-01T12:00:00",
+ "year": i
+ });
+ }
+
+ // Sort
+ combined.sort(function(a, b)
+ {
+ var aDate = new Date(a.date + "Z");
+ var bDate = new Date(b.date + "Z");
+ return bDate - aDate;
+ });
+
+ // Insert
+ for (var i = 0; i < combined.length; i++)
+ ui.billingTable.appendChild(createBillingRow(combined[i], ui.billingTable.children.length % 2));
+ };
+
+ // Callback for account details API call
+ var displayBalance = function(response)
+ {
+ ui.balance.innerHTML = "$" + response.balance.toFixed(2);
+ if (response.balance < 0) {
+ ui.balance.innerHTML += " credit";
+ } else if (response.balance > 0) {
+ ui.balance.innerHTML += " outstanding";
+ ui.balance.className = elements.balanceNegative;
+ }
+ };
+
+ // Callback for invoices API call
+ var displayInvoices = function(response)
+ {
+ data.invoices = data.invoices.concat(response.data);
+
+ // Request the next page if there are more
+ if (response.page != response.pages) {
+ apiGet("/account/invoices?page=" + (response.page + 1), displayInvoices, null);
+ return;
+ }
+
+ state.haveInvoices = true;
+ if (state.havePayments)
+ displayAll();
+ };
+
+ // Callback for payments API call
+ var displayPayments = function(response)
+ {
+ data.payments = data.payments.concat(response.data);
+
+ // Request the next page if there are more
+ if (response.page != response.pages) {
+ apiGet("/account/payments?page=" + (response.page + 1), displayPayments, null);
+ return;
+ }
+
+ state.havePayments = true;
+ if (state.haveInvoices)
+ displayAll();
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ setupHeader();
+
+ // Get element references
+ ui.balance = document.getElementById(elements.balance);
+ ui.billingTable = document.getElementById(elements.billingTable);
+ ui.loading = document.getElementById(elements.loading);
+
+ // Get data from API
+ apiGet("/account", displayBalance, null);
+ apiGet("/account/invoices", displayInvoices, null);
+ apiGet("/account/payments", displayPayments, null);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/account/billing_history/index.shtml b/account/billing_history/index.shtml
new file mode 100644
index 0000000..96822ce
--- /dev/null
+++ b/account/billing_history/index.shtml
@@ -0,0 +1,52 @@
+
+
+
+
+
+ LMC - Account // Billing History
+
+
+
+
+
+
+
+
+
+
+
+
+
Billing History
+
+
+
Date
+
Description
+
Amount
+
+
+
+
+
Loading...
+
+
+
+
Current Balance
+
+
+
+
diff --git a/account/cancel/cancel.css b/account/cancel/cancel.css
new file mode 100644
index 0000000..42a8a42
--- /dev/null
+++ b/account/cancel/cancel.css
@@ -0,0 +1,34 @@
+/*
+ * 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 url('/global.css');
+
+#cancel {
+ padding: 15px 15px 15px;
+}
+
+#cancel-note {
+ background-color: #8BCBEA;
+ margin: 15px auto 0;
+ padding: 10px;
+ width: 51%;
+}
+
+tbody tr td:first-of-type {
+ font-weight: bold;
+ text-align: right;
+}
diff --git a/account/cancel/cancel.js b/account/cancel/cancel.js
new file mode 100644
index 0000000..033d826
--- /dev/null
+++ b/account/cancel/cancel.js
@@ -0,0 +1,74 @@
+/*
+ * 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, apiPost, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.cancelButton = "cancel-button";
+ elements.confirm = "confirm";
+ elements.reason = "reason";
+
+ // Data received from API calls
+ var data = {};
+
+ // Static references to UI elements
+ var ui = {};
+ ui.cancelButton = {};
+ ui.confirm = {};
+ ui.reason = {};
+
+ // Click handler for cancel button
+ var handleCancel = function(event)
+ {
+ if (!ui.confirm.checked)
+ return;
+
+ var req = {
+ "comments": ui.reason.value
+ };
+
+ apiPost("/account/cancel", req, function(response)
+ {
+ if (response.survey_link)
+ location.href = response.survey_link;
+ else
+ location.href = "/logout";
+ });
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ setupHeader();
+
+ // Get element references
+ ui.cancelButton = document.getElementById(elements.cancelButton);
+ ui.confirm = document.getElementById(elements.confirm);
+ ui.reason = document.getElementById(elements.reason);
+
+ // Attach event handlers
+ ui.cancelButton.addEventListener("click", handleCancel);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/account/cancel/index.shtml b/account/cancel/index.shtml
new file mode 100644
index 0000000..9fd25f6
--- /dev/null
+++ b/account/cancel/index.shtml
@@ -0,0 +1,62 @@
+
+
+
+
+
+ LMC - Account // Cancel
+
+
+
+
+
+
+
+
+
+
+
+
+
Cancel Account
+
+
+
Sorry to see you go!
+
+
+
+
+
Reason
+
+ Please share why you're cancelling your account:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
NOTE: This will immediately shut down and cancel all of your Linodes, DNS hosting, and inactivate all Users. You will not be able to log into your account after cancelling.
+
+
diff --git a/account/creditcard/creditcard.css b/account/creditcard/creditcard.css
new file mode 100644
index 0000000..49d2a4c
--- /dev/null
+++ b/account/creditcard/creditcard.css
@@ -0,0 +1,27 @@
+/*
+ * 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 url('/global.css');
+
+#creditcard {
+ padding: 15px 15px 15px;
+}
+
+tbody:not(.lmc-tbody-head) tr td:first-of-type {
+ font-weight: bold;
+ text-align: right;
+}
diff --git a/account/creditcard/creditcard.js b/account/creditcard/creditcard.js
new file mode 100644
index 0000000..c76af3d
--- /dev/null
+++ b/account/creditcard/creditcard.js
@@ -0,0 +1,105 @@
+/*
+ * 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, apiGet, apiPost, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.ccCurrent = "cc-current";
+ elements.ccNew = "cc-new";
+ elements.expiryCurrent = "expiry-current";
+ elements.expiryMonth = "expiry-month";
+ elements.expiryYear = "expiry-year";
+ elements.updateButton = "update-button";
+
+ // Data received from API calls
+ var data = {};
+
+ // Static references to UI elements
+ var ui = {};
+ ui.ccCurrent = {};
+ ui.ccNew = {};
+ ui.expiryCurrent = {};
+ ui.expiryMonth = {};
+ ui.expiryYear = {};
+ ui.updateButton = {};
+
+ // Callback for account details API call
+ var displayCC = function(response)
+ {
+ if (!response.credit_card)
+ return;
+
+ ui.ccCurrent.innerHTML = response.credit_card.last_four;
+ ui.expiryCurrent.innerHTML = response.credit_card.expiry;
+ };
+
+ // Click handler for update button
+ var handleUpdate = function(event)
+ {
+ if (event.currentTarget.disabled)
+ return;
+
+ var req = {
+ "card_number": ui.ccNew.value,
+ "expiry_month": parseInt(ui.expiryMonth.value),
+ "expiry_year": parseInt(ui.expiryYear.value)
+ };
+
+ apiPost("/account/credit-card", req, function(response)
+ {
+ location.href = "/account";
+ });
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ setupHeader();
+
+ // Get element references
+ ui.ccCurrent = document.getElementById(elements.ccCurrent);
+ ui.ccNew = document.getElementById(elements.ccNew);
+ ui.expiryCurrent = document.getElementById(elements.expiryCurrent);
+ ui.expiryMonth = document.getElementById(elements.expiryMonth);
+ ui.expiryYear = document.getElementById(elements.expiryYear);
+ ui.updateButton = document.getElementById(elements.updateButton);
+
+ // Populate expiry selectors
+ var now = new Date();
+ ui.expiryMonth.value = now.getMonth() + 1;
+ for (var i = 0; i < 25; i++) {
+ var year = document.createElement("option");
+ year.value = now.getFullYear() + i;
+ year.innerHTML = now.getFullYear() + i;
+ ui.expiryYear.appendChild(year);
+ }
+
+ // Register event handlers
+ ui.updateButton.addEventListener("click", handleUpdate);
+
+ // Get data from API
+ apiGet("/account", displayCC, null);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/account/creditcard/index.shtml b/account/creditcard/index.shtml
new file mode 100644
index 0000000..7d55e55
--- /dev/null
+++ b/account/creditcard/index.shtml
@@ -0,0 +1,91 @@
+
+
+
+
+
+ LMC - Account // Update Credit Card
+
+
+
+
+
+
+
+
+
+
+
+
+
Update Credit Card
+
+
+
Current Card
+
+
+
+
+
Current Card
+
xxxxxxxxxxxx Exp:
+
+
+
+
+
+
+
+
+
Update Card
+
+
+
+
+
New Card Number
+
+
Linode accepts Visa, MasterCard, American Express, and Discover
+ Let Linode worry about your infrastructure, so you can get back to worrying about your business. Linode Managed helps keep your systems up and running with a team of experts responding to monitoring events, so you can sleep well.
+
+ Linode Managed includes 24/7 monitoring and incident responses, backups, and Longview Pro, +$100/mo. per Linode.
+
+
+
diff --git a/account/invoice/invoice.css b/account/invoice/invoice.css
new file mode 100644
index 0000000..29a85c7
--- /dev/null
+++ b/account/invoice/invoice.css
@@ -0,0 +1,59 @@
+/*
+ * 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 url('/global.css');
+
+#invoice {
+ padding: 15px 15px 15px;
+}
+
+#invoice-table td:nth-of-type(4) {
+ text-align: center;
+}
+
+#invoice-table td:nth-of-type(5) {
+ text-align: center;
+}
+
+#invoice-table td:nth-of-type(6) {
+ text-align: right;
+}
+
+#invoice-table td:nth-of-type(7) {
+ text-align: right;
+}
+
+#invoice-table td:nth-of-type(8) {
+ text-align: right;
+}
+
+.lmc-table tbody:not(.lmc-tbody-head) tr:last-of-type {
+ border-bottom: 1px solid #E8E8E8;
+}
+
+#totals {
+ margin: 20px 0 0 auto;
+ text-align: right;
+}
+
+#totals td {
+ padding: 5px;
+}
+
+#totals tr:last-of-type {
+ font-weight: bold;
+}
diff --git a/account/invoice/invoice.js b/account/invoice/invoice.js
new file mode 100644
index 0000000..762b1bd
--- /dev/null
+++ b/account/invoice/invoice.js
@@ -0,0 +1,151 @@
+/*
+ * 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, apiGet, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.invoiceBody = "invoice-body";
+ elements.invoiceID = "invoice-id";
+ elements.lmcRow = "lmc-tr1";
+ elements.lmcRowAlt = "lmc-tr2";
+ elements.subnav = "subnav-link";
+ elements.subnavActive = "subnav-link-active";
+ elements.subtotal = "subtotal";
+ elements.tax = "tax";
+ elements.total = "total";
+
+ // Data received from API calls
+ var data = {};
+
+ // Static references to UI elements
+ var ui = {};
+ ui.invoiceBody = {};
+ ui.invoiceID = {};
+ ui.subtotal = {};
+ ui.tax = {};
+ ui.total = {};
+
+ // Generates an invoice item table row
+ var createItemRow = function(item, alt)
+ {
+ var row = document.createElement("tr");
+ if (alt)
+ row.className = elements.lmcRowAlt;
+ else
+ row.className = elements.lmcRow;
+
+ var description = document.createElement("td");
+ description.innerHTML = item.label;
+ row.appendChild(description);
+
+ var from = document.createElement("td");
+ var fromDate = new Date(item.from + "Z");
+ from.innerHTML = fromDate.toLocaleString();
+ row.appendChild(from);
+
+ var to = document.createElement("td");
+ var toDate = new Date(item.to + "Z");
+ to.innerHTML = toDate.toLocaleString();
+ row.appendChild(to);
+
+ var quantity = document.createElement("td");
+ quantity.innerHTML = item.quantity;
+ row.appendChild(quantity);
+
+ var unitPrice = document.createElement("td");
+ unitPrice.innerHTML = "$" + parseFloat(item.unit_price).toFixed(4);
+ row.appendChild(unitPrice);
+
+ var subtotal = document.createElement("td");
+ subtotal.innerHTML = "$" + item.amount.toFixed(2);
+ row.appendChild(subtotal);
+
+ var tax = document.createElement("td");
+ tax.innerHTML = "$" + item.tax.toFixed(2);
+ row.appendChild(tax);
+
+ var total = document.createElement("td");
+ total.innerHTML = "$" + item.total.toFixed(2);
+ row.appendChild(total);
+
+ return row;
+ };
+
+ // Callback for invoice API call
+ var displayInvoice = function(response)
+ {
+ ui.subtotal.innerHTML = "$" + response.subtotal.toFixed(2);
+ ui.tax.innerHTML = "$" + response.tax.toFixed(2);
+ ui.total.innerHTML = "$" + response.total.toFixed(2);
+ };
+
+ // Callback for invoice items API call
+ var displayItems = function(response)
+ {
+ // Insert invoice items into table
+ for (var i = 0; i < response.data.length; i++)
+ ui.invoiceBody.appendChild(createItemRow(response.data[i], ui.invoiceBody.children.length % 2));
+
+ // Request the next page if there are more
+ if (response.page != response.pages)
+ apiGet("/account/invoices/" + data.params.iid + "/items?page=" + (response.page + 1), displayItems, null);
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ // We need an invoice ID, so die if we don't have it
+ if (!data.params.iid) {
+ alert("No invoice ID supplied!");
+ return;
+ }
+
+ setupHeader();
+
+ // Highlight the remote access subnav link
+ var subnavLinks = document.getElementsByClassName(elements.subnav);
+ for (var i = 0; i < subnavLinks.length; i++) {
+ if (subnavLinks[i].pathname == "/account/billing_history")
+ subnavLinks[i].className = elements.subnav + " " + elements.subnavActive;
+ else
+ subnavLinks[i].className = elements.subnav;
+ }
+
+ // Get element references
+ ui.invoiceBody = document.getElementById(elements.invoiceBody);
+ ui.invoiceID = document.getElementById(elements.invoiceID);
+ ui.subtotal = document.getElementById(elements.subtotal);
+ ui.tax = document.getElementById(elements.tax);
+ ui.total = document.getElementById(elements.total);
+
+ // Set title and table header
+ document.title += " " + data.params.iid;
+ ui.invoiceID.innerHTML = data.params.iid;
+
+ // Get data from API
+ apiGet("/account/invoices/" + data.params.iid, displayInvoice, null);
+ apiGet("/account/invoices/" + data.params.iid + "/items", displayItems, null);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/account/make_a_payment/index.shtml b/account/make_a_payment/index.shtml
new file mode 100644
index 0000000..6880c0b
--- /dev/null
+++ b/account/make_a_payment/index.shtml
@@ -0,0 +1,96 @@
+
+
+
+
+
+ LMC - Account // Make a Payment
+
+
+
+
+
+
+
+
+
+
diff --git a/account/make_a_payment/make_a_payment.css b/account/make_a_payment/make_a_payment.css
new file mode 100644
index 0000000..9b34dd5
--- /dev/null
+++ b/account/make_a_payment/make_a_payment.css
@@ -0,0 +1,43 @@
+/*
+ * 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 url('/global.css');
+
+#balance {
+ font-weight: bold;
+}
+
+.balance-negative {
+ color: #F00;
+}
+
+.balance-positive {
+ color: #008000;
+}
+
+input[type="number"] {
+ width: 80px;
+}
+
+#make-a-payment {
+ padding: 15px 15px 15px;
+}
+
+tbody:not(.lmc-tbody-head) tr td:first-of-type {
+ font-weight: bold;
+ text-align: right;
+}
diff --git a/account/make_a_payment/make_a_payment.js b/account/make_a_payment/make_a_payment.js
new file mode 100644
index 0000000..afcdc25
--- /dev/null
+++ b/account/make_a_payment/make_a_payment.js
@@ -0,0 +1,125 @@
+/*
+ * 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, apiGet, apiPost, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.balance = "balance";
+ elements.balanceNegative = "balance-negative";
+ elements.ccAmount = "cc-amount";
+ elements.ccCharge = "cc-charge";
+ elements.ccExp = "cc-exp";
+ elements.ccNumber = "cc-number";
+ elements.cvv = "cvv";
+ elements.paypalAmount = "paypal-amount";
+ elements.paypalCharge = "paypal-charge";
+
+ // Data received from API calls
+ var data = {};
+
+ // Static references to UI elements
+ var ui = {};
+ ui.balance = {};
+ ui.ccAmount = {};
+ ui.ccCharge = {};
+ ui.ccExp = {};
+ ui.ccNumber = {};
+ ui.cvv = {};
+ ui.paypalAmount = {};
+ ui.paypalCharge = {};
+
+ // Callback for account details API call
+ var displayAccount = function(response)
+ {
+ ui.balance.innerHTML = "$";
+ if (response.balance < 0) {
+ ui.balance.innerHTML += (-response.balance).toFixed(2) + " credit";
+ } else if (response.balance > 0) {
+ ui.balance.innerHTML += response.balance.toFixed(2) + " outstanding";
+ ui.balance.className = elements.balanceNegative;
+ } else {
+ ui.balance.innerHTML += response.balance.toFixed(2);
+ }
+
+ if (response.credit_card) {
+ ui.ccNumber.innerHTML = response.credit_card.last_four;
+ ui.ccExp.innerHTML = response.credit_card.expiry;
+ }
+
+ ui.ccCharge.disabled = false;
+ //ui.paypalCharge.disabled = false;
+ };
+
+ // Click handler for CC charge button
+ var handleCharge = function(event)
+ {
+ if (event.currentTarget.disabled)
+ return;
+
+ if (!confirm("Charge $" + parseInt(ui.ccAmount.value).toFixed(2) + " against your credit card?"))
+ return;
+
+ var req = {
+ "usd": ui.ccAmount.value
+ };
+ if (ui.cvv.value.length)
+ req.cvv = ui.cvv.value;
+
+ apiPost("/account/payments", req, function(response)
+ {
+ location.href = "/account";
+ });
+ };
+
+ // Click handler for PayPal button
+ var handlePayPal = function(event)
+ {
+ if (event.currentTarget.disabled)
+ return;
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ setupHeader();
+
+ // Get element references
+ ui.balance = document.getElementById(elements.balance);
+ ui.ccAmount = document.getElementById(elements.ccAmount);
+ ui.ccCharge = document.getElementById(elements.ccCharge);
+ ui.ccExp = document.getElementById(elements.ccExp);
+ ui.ccNumber = document.getElementById(elements.ccNumber);
+ ui.cvv = document.getElementById(elements.cvv);
+ ui.paypalAmount = document.getElementById(elements.paypalAmount);
+ ui.paypalCharge = document.getElementById(elements.paypalCharge);
+
+ // Register event handlers
+ ui.ccCharge.addEventListener("click", handleCharge);
+ ui.paypalCharge.addEventListener("click", handlePayPal);
+
+ // Get data from API
+ apiGet("/account", displayAccount, null);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/account/paymentreceipt/index.shtml b/account/paymentreceipt/index.shtml
new file mode 100644
index 0000000..f9b37db
--- /dev/null
+++ b/account/paymentreceipt/index.shtml
@@ -0,0 +1,54 @@
+
+
+
+
+
+ LMC - Account // View Payment
+
+
+
+
+
+
+
+
+
+
+
+
+
Payment
+
+
+
Description
+
Date
+
Amount
+
+
+
+
+
Payment #. Thank you.
+
+
+
+
+
+
Payment Total:
+
+
+
+
diff --git a/account/paymentreceipt/paymentreceipt.css b/account/paymentreceipt/paymentreceipt.css
new file mode 100644
index 0000000..a7dcc62
--- /dev/null
+++ b/account/paymentreceipt/paymentreceipt.css
@@ -0,0 +1,36 @@
+/*
+ * 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 url('/global.css');
+
+.lmc-table tbody:not(.lmc-tbody-head) tr:last-of-type {
+ border-bottom: 1px solid #E8E8E8;
+}
+
+#paymentreceipt {
+ padding: 15px 15px 15px;
+}
+
+#paymentreceipt p {
+ font-size: 13px;
+ font-weight: bold;
+ text-align: right;
+}
+
+table td:nth-of-type(3) {
+ text-align: right;
+}
diff --git a/account/paymentreceipt/paymentreceipt.js b/account/paymentreceipt/paymentreceipt.js
new file mode 100644
index 0000000..2cc6d6b
--- /dev/null
+++ b/account/paymentreceipt/paymentreceipt.js
@@ -0,0 +1,87 @@
+/*
+ * 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, apiGet, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.amount = "amount";
+ elements.date = "date";
+ elements.paymentID = "payment-id";
+ elements.subnav = "subnav-link";
+ elements.subnavActive = "subnav-link-active";
+ elements.total = "total";
+
+ // Data received from API calls
+ var data = {};
+
+ // Static references to UI elements
+ var ui = {};
+ ui.amount = {};
+ ui.date = {};
+ ui.paymentID = {};
+ ui.total = {};
+
+ // Callback for payment details API call
+ var displayPayment = function(response)
+ {
+ var paymentDate = new Date(response.date + "Z");
+ ui.date.innerHTML = paymentDate.toLocaleDateString();
+ ui.amount.innerHTML = "$" + response.usd.toFixed(2);
+ ui.total.innerHTML = "$" + response.usd.toFixed(2);
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ // We need a payment ID, so die if we don't have it
+ if (!data.params.pid) {
+ alert("No payment ID supplied!");
+ return;
+ }
+
+ setupHeader();
+
+ // Highlight the billing history subnav link
+ var subnavLinks = document.getElementsByClassName(elements.subnav);
+ for (var i = 0; i < subnavLinks.length; i++) {
+ if (subnavLinks[i].pathname == "/account/billing_history")
+ subnavLinks[i].className = elements.subnav + " " + elements.subnavActive;
+ else
+ subnavLinks[i].className = elements.subnav;
+ }
+
+ // Get element references
+ ui.amount = document.getElementById(elements.amount);
+ ui.date = document.getElementById(elements.date);
+ ui.paymentID = document.getElementById(elements.paymentID);
+ ui.total = document.getElementById(elements.total);
+
+ // Set payment ID
+ ui.paymentID.innerHTML = data.params.pid;
+
+ // Get data from API
+ apiGet("/account/payments/" + data.params.pid, displayPayment, null);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/account/paymentreceiptyear/index.shtml b/account/paymentreceiptyear/index.shtml
new file mode 100644
index 0000000..27260c9
--- /dev/null
+++ b/account/paymentreceiptyear/index.shtml
@@ -0,0 +1,55 @@
+
+
+
+
+
+ LMC - Account // View Payments in
+
+
+
+
+
+
+
+
+
+
+
+
+
Payment(s) in
+
+
+
Description
+
Date
+
Amount
+
+
+
+
+
Loading...
+
+
+
No payments to display.
+
+
+
+
Payment Total:
+
+
+
+
diff --git a/account/paymentreceiptyear/paymentreceiptyear.css b/account/paymentreceiptyear/paymentreceiptyear.css
new file mode 100644
index 0000000..7f6da71
--- /dev/null
+++ b/account/paymentreceiptyear/paymentreceiptyear.css
@@ -0,0 +1,40 @@
+/*
+ * 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 url('/global.css');
+
+.lmc-table tbody:not(.lmc-tbody-head) tr:last-of-type {
+ border-bottom: 1px solid #E8E8E8;
+}
+
+#no-payments {
+ display: none;
+}
+
+#paymentreceiptyear {
+ padding: 15px 15px 15px;
+}
+
+#paymentreceiptyear p {
+ font-size: 13px;
+ font-weight: bold;
+ text-align: right;
+}
+
+table td:nth-of-type(3) {
+ text-align: right;
+}
diff --git a/account/paymentreceiptyear/paymentreceiptyear.js b/account/paymentreceiptyear/paymentreceiptyear.js
new file mode 100644
index 0000000..a800dd1
--- /dev/null
+++ b/account/paymentreceiptyear/paymentreceiptyear.js
@@ -0,0 +1,146 @@
+/*
+ * 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, apiGet, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.lmcRow = "lmc-tr1";
+ elements.lmcRowAlt = "lmc-tr2";
+ elements.loading = "loading";
+ elements.noPayments = "no-payments";
+ elements.paymentsBody = "payments-body";
+ elements.subnav = "subnav-link";
+ elements.subnavActive = "subnav-link-active";
+ elements.total = "total";
+ elements.year = "year";
+
+ // Data received from API calls
+ var data = {};
+ data.payments = [];
+
+ // Static references to UI elements
+ var ui = {};
+ ui.loading = {};
+ ui.noPayments = {};
+ ui.paymentsBody = {};
+ ui.total = {};
+ ui.year = {};
+
+ // Generate a payment table row
+ var createPaymentRow = function(payment, alt)
+ {
+ var row = document.createElement("tr");
+ if (alt)
+ row.className = elements.lmcRowAlt;
+ else
+ row.className = elements.lmcRow;
+
+ var description = document.createElement("td");
+ description.innerHTML = "Payment. Thank you.";
+ row.appendChild(description);
+
+ var date = document.createElement("td");
+ var paymentDate = new Date(payment.date + "Z");
+ date.innerHTML = paymentDate.toLocaleDateString();
+ row.appendChild(date);
+
+ var amount = document.createElement("td");
+ amount.innerHTML = "$" + payment.usd.toFixed(2);
+ row.appendChild(amount);
+
+ return row;
+ };
+
+ // Callback for payments API call
+ var displayPayments = function(response)
+ {
+ // Only add payments from the specified year
+ var year = parseInt(data.params.year);
+ for (var i = 0; i < response.data.length; i++) {
+ var paymentDate = new Date(response.data[i].date + "Z");
+ if (paymentDate.getFullYear() == year)
+ data.payments.push(response.data[i]);
+ }
+
+ // Request the next page if there are more
+ if (response.page != response.pages) {
+ apiGet("/account/payments?page=" + (response.page + 1), displayPayments, null);
+ return;
+ }
+
+ ui.loading.remove();
+ if (!data.payments.length) {
+ ui.noPayments.style.display = "table-row";
+ return;
+ }
+
+ // Insert payments into table
+ var total = 0.0;
+ for (var i = 0; i < data.payments.length; i++) {
+ total += data.payments[i].usd;
+ ui.paymentsBody.appendChild(createPaymentRow(data.payments[i], i % 2));
+ }
+
+ ui.total.innerHTML = "$" + total.toFixed(2);
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ // We need a year, so die if we don't have it
+ if (!data.params.year) {
+ alert("No payment ID supplied!");
+ return;
+ } else if (!parseInt(data.params.year)) {
+ alert("Supplied year is invalid!");
+ return;
+ }
+
+ setupHeader();
+
+ // Highlight the remote access subnav link
+ var subnavLinks = document.getElementsByClassName(elements.subnav);
+ for (var i = 0; i < subnavLinks.length; i++) {
+ if (subnavLinks[i].pathname == "/account/billing_history")
+ subnavLinks[i].className = elements.subnav + " " + elements.subnavActive;
+ else
+ subnavLinks[i].className = elements.subnav;
+ }
+
+ // Get element references
+ ui.loading = document.getElementById(elements.loading);
+ ui.noPayments = document.getElementById(elements.noPayments);
+ ui.paymentsBody = document.getElementById(elements.paymentsBody);
+ ui.total = document.getElementById(elements.total);
+ ui.year = document.getElementById(elements.year);
+
+ // Set year
+ var year = parseInt(data.params.year);
+ ui.year.innerHTML = year;
+
+ // Get data from API
+ apiGet("/account/payments", displayPayments, null);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/account/settings/index.shtml b/account/settings/index.shtml
new file mode 100644
index 0000000..ec885ed
--- /dev/null
+++ b/account/settings/index.shtml
@@ -0,0 +1,83 @@
+
+
+
+
+
+ LMC - Account // Settings
+
+
+
+
+
+
+
+
+
+
Settings saved!
+
+
+
+
Account Settings
+
+
+
Network Helper - Default Setting
+
+
+
+
+
Default Behavior
+
+ This controls the default setting for the Network Helper on newly created Configuration Profiles.
+
+
+
+
+
+
+
+
+
+
+
+
Linode Backup Enrollment
+
+
+
+
+
Default Behavior
+
+ This controls whether Linode Backups are enabled, by default, for Linodes when they are initially created.
+
+
+
+
+ Enable backups for all existing Linodes
+
+ For each Linode with Backups enabled, your account will be billed the additional hourly rate noted on the Backups info page.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/account/settings/settings.css b/account/settings/settings.css
new file mode 100644
index 0000000..658b5f2
--- /dev/null
+++ b/account/settings/settings.css
@@ -0,0 +1,35 @@
+/*
+ * 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 url('/global.css');
+
+#saved {
+ background-color: #ADD370;
+ display: none;
+ font-size: 16px;
+ margin-top: 0;
+ padding: 7px;
+}
+
+#settings {
+ padding: 15px 15px 15px;
+}
+
+tbody:not(.lmc-tbody-head) tr td:first-of-type {
+ font-weight: bold;
+ text-align: right;
+}
diff --git a/account/settings/settings.js b/account/settings/settings.js
new file mode 100644
index 0000000..1572026
--- /dev/null
+++ b/account/settings/settings.js
@@ -0,0 +1,100 @@
+/*
+ * 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, apiGet, apiPut, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.backupsOff = "backups-off";
+ elements.backupsOn = "backups-on";
+ elements.nhOff = "nh-off";
+ elements.nhOn = "nh-on";
+ elements.saveButton = "save-button";
+ elements.saved = "saved";
+
+ // Data received from API calls
+ var data = {};
+
+ // Static references to UI elements
+ var ui = {};
+ ui.backupsOff = {};
+ ui.backupsOn = {};
+ ui.nhOff = {};
+ ui.nhOn = {};
+ ui.saveButton = {};
+ ui.saved = {};
+
+ // Callback for account settings API call
+ var displaySettings = function(response)
+ {
+ if (response.backups_enabled)
+ ui.backupsOn.checked = true;
+ else
+ ui.backupsOff.checked = true;
+
+ if (response.network_helper)
+ ui.nhOn.checked = true;
+ else
+ ui.nhOff.checked = true;
+
+ ui.saveButton.disabled = false;
+ };
+
+ // Click handler for save button
+ var handleSave = function(event)
+ {
+ if (event.currentTarget.disabled)
+ return;
+
+ var req = {
+ "backups_enabled": ui.backupsOn.checked,
+ "network_helper": ui.nhOn.checked
+ };
+
+ apiPut("/account/settings", req, function(response)
+ {
+ ui.saved.style.display = "block";
+ });
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ setupHeader();
+
+ // Get element references
+ ui.backupsOff = document.getElementById(elements.backupsOff);
+ ui.backupsOn = document.getElementById(elements.backupsOn);
+ ui.nhOff = document.getElementById(elements.nhOff);
+ ui.nhOn = document.getElementById(elements.nhOn);
+ ui.saveButton = document.getElementById(elements.saveButton);
+ ui.saved = document.getElementById(elements.saved);
+
+ // Attach event handlers
+ ui.saveButton.addEventListener("click", handleSave);
+
+ // Get data from API
+ apiGet("/account/settings", displaySettings, null);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/dns/domain_add/domain_add.css b/dns/domain_add/domain_add.css
index 2d7800d..4f3033e 100644
--- a/dns/domain_add/domain_add.css
+++ b/dns/domain_add/domain_add.css
@@ -29,10 +29,6 @@
display: none;
}
-tbody:not(.lmc-tbody-head) tr:last-of-type {
- border: none;
-}
-
tbody:not(.lmc-tbody-head) tr td:first-of-type {
font-weight: bold;
text-align: right;
diff --git a/dns/domain_clone/domain_clone.css b/dns/domain_clone/domain_clone.css
index 642469b..4176595 100644
--- a/dns/domain_clone/domain_clone.css
+++ b/dns/domain_clone/domain_clone.css
@@ -21,10 +21,6 @@
padding: 0px 15px 15px;
}
-tbody:not(.lmc-tbody-head) tr:last-of-type {
- border: none;
-}
-
tbody:not(.lmc-tbody-head) tr td:first-of-type {
font-weight: bold;
text-align: right;
diff --git a/dns/domain_import/domain_import.css b/dns/domain_import/domain_import.css
index 997b6f8..ac64f03 100644
--- a/dns/domain_import/domain_import.css
+++ b/dns/domain_import/domain_import.css
@@ -26,10 +26,6 @@
padding: 8px;
}
-tbody:not(.lmc-tbody-head) tr:last-of-type {
- border: none;
-}
-
tbody:not(.lmc-tbody-head) tr td:first-of-type {
font-weight: bold;
text-align: right;
diff --git a/dns/domain_soa/domain_soa.css b/dns/domain_soa/domain_soa.css
index c99a7f1..ae48807 100644
--- a/dns/domain_soa/domain_soa.css
+++ b/dns/domain_soa/domain_soa.css
@@ -26,7 +26,3 @@ tbody:not(.lmc-tbody-head) tr td:first-of-type {
text-align: right;
white-space: nowrap;
}
-
-tbody:not(.lmc-tbody-head) tr:last-of-type {
- border: none;
-}
diff --git a/dns/resource/resource.css b/dns/resource/resource.css
index 82ab969..59954a5 100644
--- a/dns/resource/resource.css
+++ b/dns/resource/resource.css
@@ -30,7 +30,3 @@ tbody:not(.lmc-tbody-head) tr td:first-of-type {
text-align: right;
white-space: nowrap;
}
-
-tbody:not(.lmc-tbody-head):not(.resource-section) tr:last-of-type {
- border: none;
-}
diff --git a/global.css b/global.css
index c159c60..42e4d89 100644
--- a/global.css
+++ b/global.css
@@ -86,6 +86,14 @@ header {
font-size: 13.3px;
}
+.lmc-table tbody:not(.lmc-tbody-head) tr {
+ border-bottom: 1px solid #E8E8E8;
+}
+
+.lmc-table tbody:not(.lmc-tbody-head) tr:last-of-type {
+ border: none;
+}
+
.lmc-table tbody.lmc-tbody-head tr:first-of-type {
font-weight: bold;
}
@@ -118,7 +126,6 @@ header {
.lmc-tr3 {
background-color: #FFF;
- border-bottom: 1px solid #E8E8E8;
}
#logout-link {
diff --git a/global.js b/global.js
index d08414d..4eefab0 100644
--- a/global.js
+++ b/global.js
@@ -50,7 +50,8 @@ var regionNames = {
"ca-central": "Toronto, ON, CA",
"ap-west": "Mumbai, IN",
"ap-southeast": "Sydney, AU",
- "philadelphia": "Philadelphia, PA, USA"
+ "philadelphia": "Philadelphia, PA, USA",
+ "absecon": "Absecon, NJ, USA"
};
// Human-readable event titles
@@ -163,7 +164,7 @@ function apiDelete(endpoint, callback)
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("DELETE", settings.apiURL + endpoint, true);
- xmlhttp.setRequestHeader("Authorization", "Bearer " + localStorage.apiKey);
+ xmlhttp.setRequestHeader("Authorization", localStorage.apiKey);
xmlhttp.onreadystatechange = function()
{
@@ -220,7 +221,7 @@ function apiGet(endpoint, callback, filters)
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", settings.apiURL + endpoint, true);
- xmlhttp.setRequestHeader("Authorization", "Bearer " + localStorage.apiKey);
+ xmlhttp.setRequestHeader("Authorization", localStorage.apiKey);
if (filters)
xmlhttp.setRequestHeader("X-Filter", JSON.stringify(filters));
@@ -279,7 +280,7 @@ function apiPost(endpoint, data, callback)
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", settings.apiURL + endpoint, true);
- xmlhttp.setRequestHeader("Authorization", "Bearer " + localStorage.apiKey);
+ xmlhttp.setRequestHeader("Authorization", localStorage.apiKey);
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.onreadystatechange = function()
@@ -337,7 +338,7 @@ function apiPut(endpoint, data, callback)
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("PUT", settings.apiURL + endpoint, true);
- xmlhttp.setRequestHeader("Authorization", "Bearer " + localStorage.apiKey);
+ xmlhttp.setRequestHeader("Authorization", localStorage.apiKey);
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.onreadystatechange = function()
@@ -609,9 +610,11 @@ function setupHeader()
// Highlight the current page in the subnav
var subnavLinks = document.getElementsByClassName(elements.subnavLink);
- for (var i = 0; i < subnavLinks.length; i++) {
- if ((location.origin + location.pathname).startsWith(subnavLinks[i].href.split("?")[0]))
+ 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
@@ -695,4 +698,4 @@ function translateKernel(slug, element)
apiGet("/linode/kernels/" + slug, callback, null);
}
-export { settings, elements, regionNames, apiDelete, apiGet, apiPost, apiPut, migrateETA, oauthPost, parseParams, setupHeader, eventTitles, timeString, translateKernel };
+export { settings, elements, regionNames, apiDelete, apiGet, apiPost, apiPut, md5, migrateETA, oauthPost, parseParams, setupHeader, eventTitles, timeString, translateKernel };
diff --git a/images/create/create.css b/images/create/create.css
index 90808bc..0ab6088 100644
--- a/images/create/create.css
+++ b/images/create/create.css
@@ -21,10 +21,6 @@
padding: 0px 15px 15px;
}
-tbody tr:last-of-type {
- border: none;
-}
-
tbody tr td:first-of-type {
font-weight: bold;
text-align: right;
diff --git a/images/edit/edit.css b/images/edit/edit.css
index bfb799b..d8e4f4b 100644
--- a/images/edit/edit.css
+++ b/images/edit/edit.css
@@ -25,7 +25,3 @@ table:first-of-type tbody tr td:first-of-type {
font-weight: bold;
text-align: right;
}
-
-tbody tr:last-of-type {
- border: none;
-}
diff --git a/include/account_subnav.html b/include/account_subnav.html
new file mode 100644
index 0000000..ff9fc05
--- /dev/null
+++ b/include/account_subnav.html
@@ -0,0 +1,11 @@
+
+
+
diff --git a/linodes/backup_details/backup_details.css b/linodes/backup_details/backup_details.css
index 457116c..829d793 100644
--- a/linodes/backup_details/backup_details.css
+++ b/linodes/backup_details/backup_details.css
@@ -34,10 +34,6 @@ table:first-of-type tbody tr td:first-of-type {
text-align: right;
}
-tbody tr:last-of-type {
- border: none;
-}
-
td {
white-space: nowrap;
}
diff --git a/linodes/backups/backups.css b/linodes/backups/backups.css
index 0ac2570..678aab4 100644
--- a/linodes/backups/backups.css
+++ b/linodes/backups/backups.css
@@ -45,7 +45,3 @@ table:first-of-type tbody:not(.lmc-tbody-head) tr td:first-of-type {
font-weight: bold;
text-align: right;
}
-
-tbody:not(.lmc-tbody-head) tr:last-of-type {
- border: none;
-}
diff --git a/linodes/clone/clone.css b/linodes/clone/clone.css
index ecd40d3..be0134e 100644
--- a/linodes/clone/clone.css
+++ b/linodes/clone/clone.css
@@ -53,10 +53,6 @@
text-align: right;
}
-#dest-table tbody:not(.lmc-tbody-head) tr:last-of-type {
- border: none;
-}
-
#dest-table td:last-of-type {
width: 100%;
}
diff --git a/linodes/config/config.css b/linodes/config/config.css
index 06aecf7..c123286 100644
--- a/linodes/config/config.css
+++ b/linodes/config/config.css
@@ -21,10 +21,6 @@
padding: 0px 15px 15px;
}
-tbody:not(.lmc-tbody-head) tr:last-of-type {
- border: none;
-}
-
tbody:not(.lmc-tbody-head) tr td:first-of-type {
font-weight: bold;
text-align: right;
diff --git a/linodes/dashboard/dashboard.css b/linodes/dashboard/dashboard.css
index 4118ba4..8655485 100644
--- a/linodes/dashboard/dashboard.css
+++ b/linodes/dashboard/dashboard.css
@@ -25,11 +25,19 @@
margin-top: 5px;
}
+#config-table tr:last-of-type {
+ border-bottom: 1px solid #E8E8E8;
+}
+
.disk-icon {
height: 24px;
width: 26px;
}
+#disk-table tr:last-of-type {
+ border-bottom: 1px solid #E8E8E8;
+}
+
.extra-event {
display: none;
}
@@ -209,3 +217,7 @@ h3 {
#uptime {
display: none;
}
+
+#volume-table tr:last-of-type {
+ border-bottom: 1px solid #E8E8E8;
+}
diff --git a/linodes/deploy/deploy.css b/linodes/deploy/deploy.css
index beb6d03..d8a0eef 100644
--- a/linodes/deploy/deploy.css
+++ b/linodes/deploy/deploy.css
@@ -25,10 +25,6 @@
width: 80px;
}
-tbody tr:last-of-type {
- border: none;
-}
-
tbody tr td:first-of-type {
font-weight: bold;
text-align: right;
diff --git a/linodes/disk/disk.css b/linodes/disk/disk.css
index e59b8c3..1314171 100644
--- a/linodes/disk/disk.css
+++ b/linodes/disk/disk.css
@@ -53,10 +53,6 @@ h3 {
font-weight: bold;
}
-tbody tr:last-of-type {
- border: none;
-}
-
tbody tr td:first-of-type {
font-weight: bold;
text-align: right;
diff --git a/linodes/ip_failover/ip_failover.css b/linodes/ip_failover/ip_failover.css
index b9afe63..a3c4a20 100644
--- a/linodes/ip_failover/ip_failover.css
+++ b/linodes/ip_failover/ip_failover.css
@@ -21,10 +21,6 @@
padding: 0px 15px 15px;
}
-tbody tr:last-of-type {
- border: none;
-}
-
tbody tr td:first-of-type {
font-weight: bold;
text-align: right;
diff --git a/linodes/ip_remove/ip_remove.css b/linodes/ip_remove/ip_remove.css
index 929a509..2304154 100644
--- a/linodes/ip_remove/ip_remove.css
+++ b/linodes/ip_remove/ip_remove.css
@@ -38,3 +38,7 @@ td:nth-of-type(2) {
text-align: center;
white-space: nowrap;
}
+
+.lmc-table tbody:not(.lmc-tbody-head) tr:last-of-type {
+ border-bottom: 1px solid #E8E8E8;
+}
diff --git a/linodes/ip_swap/ip_swap.css b/linodes/ip_swap/ip_swap.css
index 899c4e2..a05b2c6 100644
--- a/linodes/ip_swap/ip_swap.css
+++ b/linodes/ip_swap/ip_swap.css
@@ -85,7 +85,3 @@ p {
table {
margin-bottom: 20px;
}
-
-tbody:not(.lmc-tbody-head) tr:last-of-type {
- border: none;
-}
diff --git a/linodes/mutate/mutate.css b/linodes/mutate/mutate.css
index 324aa99..067d3ba 100644
--- a/linodes/mutate/mutate.css
+++ b/linodes/mutate/mutate.css
@@ -30,10 +30,6 @@ h2 {
margin-top: 0;
}
-.lmc-tr3 {
- border-bottom: none;
-}
-
#mutate {
padding: 0px 15px 15px;
}
diff --git a/linodes/rdns/rdns.css b/linodes/rdns/rdns.css
index c02c2fc..8d4e99a 100644
--- a/linodes/rdns/rdns.css
+++ b/linodes/rdns/rdns.css
@@ -28,7 +28,3 @@ p {
tbody tr td:first-of-type {
font-weight: bold;
}
-
-tbody tr:last-of-type {
- border: none;
-}
diff --git a/linodes/rebuild/rebuild.css b/linodes/rebuild/rebuild.css
index 94bc4c4..e38d22e 100644
--- a/linodes/rebuild/rebuild.css
+++ b/linodes/rebuild/rebuild.css
@@ -21,10 +21,6 @@
padding: 0px 15px 15px;
}
-tbody tr:last-of-type {
- border: none;
-}
-
tbody tr td:first-of-type {
font-weight: bold;
text-align: right;
diff --git a/linodes/remote_access/remote_access.css b/linodes/remote_access/remote_access.css
index bb2c859..210d55c 100644
--- a/linodes/remote_access/remote_access.css
+++ b/linodes/remote_access/remote_access.css
@@ -37,10 +37,6 @@ table:first-of-type {
margin-bottom: 30px;
}
-tbody:not(.lmc-tbody-head) tr:last-of-type {
- border: none;
-}
-
tbody:not(.lmc-tbody-head) tr td:first-of-type {
font-weight: bold;
text-align: right;
diff --git a/linodes/rescue/rescue.css b/linodes/rescue/rescue.css
index b9b4fc8..40e67e4 100644
--- a/linodes/rescue/rescue.css
+++ b/linodes/rescue/rescue.css
@@ -25,10 +25,6 @@
margin-bottom: 30px;
}
-tbody:not(.lmc-tbody-head) tr:last-of-type {
- border: none;
-}
-
tbody:not(.lmc-tbody-head) tr td:first-of-type {
font-weight: bold;
text-align: right;
diff --git a/linodes/resize/resize.css b/linodes/resize/resize.css
index 639fbc8..52f911c 100644
--- a/linodes/resize/resize.css
+++ b/linodes/resize/resize.css
@@ -46,6 +46,10 @@ li {
margin-bottom: 15px;
}
+.lmc-table tbody:not(.lmc-tbody-head) tr {
+ border-bottom: none;
+}
+
.lmc-table td {
vertical-align: top;
width: 25%;
diff --git a/linodes/settings/settings.css b/linodes/settings/settings.css
index 3adb308..4c38e61 100644
--- a/linodes/settings/settings.css
+++ b/linodes/settings/settings.css
@@ -38,7 +38,3 @@ tbody:not(.lmc-tbody-head) tr td:first-of-type {
text-align: right;
white-space: nowrap;
}
-
-tbody:not(.lmc-tbody-head) tr:last-of-type {
- border: none;
-}
diff --git a/login.js b/login.js
index b2c99de..85b3a33 100644
--- a/login.js
+++ b/login.js
@@ -63,10 +63,11 @@ import { clientID } from "/clientID.js";
data.params = parseParams();
// If we're being given an access token, store it and redirect
- if (data.params.access_token && data.params.expires_in && data.params.state) {
+ if (data.params.access_token && data.params.expires_in && data.params.token_type && data.params.state) {
if (localStorage.state && localStorage.state == data.params.state) {
localStorage.removeItem("state");
- localStorage.apiKey = data.params.access_token;
+ var type = data.params.token_type.charAt(0).toUpperCase() + data.params.token_type.slice(1);
+ localStorage.apiKey = type + " " + data.params.access_token;
localStorage.apiExpire = Date.now() + (data.params.expires_in * 1000);
if (localStorage.redirectTo)
location.href = localStorage.redirectTo;
diff --git a/user/add/add.css b/user/add/add.css
new file mode 100644
index 0000000..fa9baec
--- /dev/null
+++ b/user/add/add.css
@@ -0,0 +1,27 @@
+/*
+ * 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 url('/global.css');
+
+#add {
+ padding: 15px 15px 15px;
+}
+
+tbody:not(.lmc-tbody-head) tr td:first-of-type {
+ font-weight: bold;
+ text-align: right;
+}
diff --git a/user/add/add.js b/user/add/add.js
new file mode 100644
index 0000000..da2a044
--- /dev/null
+++ b/user/add/add.js
@@ -0,0 +1,80 @@
+/*
+ * 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, apiPost, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.addButton = "add-button";
+ elements.email = "email";
+ elements.restrictedYes = "restricted-yes";
+ elements.user = "user";
+
+ // Data received from API calls
+ var data = {};
+
+ // Static references to UI elements
+ var ui = {};
+ ui.addButton = {};
+ ui.email = {};
+ ui.restrictedYes = {};
+ ui.user = {};
+
+ // Click handler for add button
+ var handleAdd = function(event)
+ {
+ var req = {
+ "username": ui.user.value,
+ "email": ui.email.value,
+ "restricted": ui.restrictedYes.checked
+ };
+
+ apiPost("/account/users", req, function(response)
+ {
+ location.href = "/user";
+ });
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ setupHeader();
+
+ // Highlight the account nav link
+ var navlinks = document.getElementsByClassName(elements.navlink);
+ for (var i = 0; i < navlinks.length; i++) {
+ if (navlinks[i].pathname == "/account/")
+ navlinks[i].className = " " + elements.navlinkActive;
+ }
+
+ // Get element references
+ ui.addButton = document.getElementById(elements.addButton);
+ ui.email = document.getElementById(elements.email);
+ ui.restrictedYes = document.getElementById(elements.restrictedYes);
+ ui.user = document.getElementById(elements.user);
+
+ // Attach event handlers
+ ui.addButton.addEventListener("click", handleAdd);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/user/add/index.shtml b/user/add/index.shtml
new file mode 100644
index 0000000..3992118
--- /dev/null
+++ b/user/add/index.shtml
@@ -0,0 +1,66 @@
+
+
+
+
+
+ LMC - Account // Add User
+
+
+
+
+
+
+
+
+
+
+
+
+
Add User
+
+
+
+
+
Username
+
+
+
+
Password
+
A password creation link will be emailed to the user
+
+
+
Email
+
+
+
+
Restricted User
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/user/edit/edit.css b/user/edit/edit.css
new file mode 100644
index 0000000..3b1c6fa
--- /dev/null
+++ b/user/edit/edit.css
@@ -0,0 +1,27 @@
+/*
+ * 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 url('/global.css');
+
+#edit {
+ padding: 15px 15px 15px;
+}
+
+tbody:not(.lmc-tbody-head) tr td:first-of-type {
+ font-weight: bold;
+ text-align: right;
+}
diff --git a/user/edit/edit.js b/user/edit/edit.js
new file mode 100644
index 0000000..27c629d
--- /dev/null
+++ b/user/edit/edit.js
@@ -0,0 +1,118 @@
+/*
+ * 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, apiGet, apiPut, md5, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.email = "email";
+ elements.pwReset = "pw-reset";
+ elements.restrictedNo = "restricted-no";
+ elements.restrictedYes = "restricted-yes";
+ elements.saveButton = "save-button";
+ elements.user = "user";
+ elements.userLabel = "user-label";
+
+ // Data received from API calls
+ var data = {};
+
+ // Static references to UI elements
+ var ui = {};
+ ui.email = {};
+ ui.pwReset = {};
+ ui.restrictedNo = {};
+ ui.restrictedYes = {};
+ ui.saveButton = {};
+ ui.user = {};
+ ui.userLabel = {};
+
+ // Callback for user details API call
+ var displayUser = function(response)
+ {
+ // Fill in form
+ ui.user.value = response.username;
+ ui.email.value = response.email;
+ if (response.restricted)
+ ui.restrictedYes.checked = true;
+ else
+ ui.restrictedNo.checked = true;
+ ui.saveButton.disabled = false;
+ };
+
+ // Click handler for save button
+ var handleSave = function(event)
+ {
+ if (event.currentTarget.disabled)
+ return;
+
+ var req = {
+ "username": ui.user.value,
+ "restricted": ui.restrictedYes.checked
+ };
+
+ apiPut("/account/users/" + data.params.user, req, function(response)
+ {
+ location.href = "/user";
+ });
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ // We need a username, so die if we don't have it
+ if (!data.params.user) {
+ alert("No username supplied!");
+ return;
+ }
+
+ setupHeader();
+
+ // Highlight the account nav link
+ var navlinks = document.getElementsByClassName(elements.navlink);
+ for (var i = 0; i < navlinks.length; i++) {
+ if (navlinks[i].pathname == "/account/")
+ navlinks[i].className = " " + elements.navlinkActive;
+ }
+
+ // Get element references
+ ui.email = document.getElementById(elements.email);
+ ui.pwReset = document.getElementById(elements.pwReset);
+ ui.restrictedNo = document.getElementById(elements.restrictedNo);
+ ui.restrictedYes = document.getElementById(elements.restrictedYes);
+ ui.saveButton = document.getElementById(elements.saveButton);
+ ui.user = document.getElementById(elements.user);
+ ui.userLabel = document.getElementById(elements.userLabel);
+
+ // Populate username where we need it
+ ui.userLabel.innerHTML = data.params.user;
+ ui.pwReset.href += data.params.user;
+ document.title += " - " + data.params.user;
+
+ // Attach event handlers
+ ui.saveButton.addEventListener("click", handleSave);
+
+ // Get data from API
+ apiGet("/account/users/" + data.params.user, displayUser, null);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/user/edit/index.shtml b/user/edit/index.shtml
new file mode 100644
index 0000000..f6fdc08
--- /dev/null
+++ b/user/edit/index.shtml
@@ -0,0 +1,67 @@
+
+
+
+
+
+ LMC - Account // Edit User
+
+
+
+
+
+
+
+
+
+
diff --git a/user/remove/remove.css b/user/remove/remove.css
new file mode 100644
index 0000000..b8f1a06
--- /dev/null
+++ b/user/remove/remove.css
@@ -0,0 +1,22 @@
+/*
+ * 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 url('/global.css');
+
+#remove {
+ padding: 15px 15px 15px;
+}
diff --git a/user/remove/remove.js b/user/remove/remove.js
new file mode 100644
index 0000000..e1b9159
--- /dev/null
+++ b/user/remove/remove.js
@@ -0,0 +1,84 @@
+/*
+ * 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, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.cancelButton = "cancel-button";
+ elements.deleteButton = "delete-button";
+ elements.userLabel = "user-label";
+
+ // Data received from API calls
+ var data = {};
+
+ // Static references to UI elements
+ var ui = {};
+ ui.cancelButton = {};
+ ui.deleteButton = {};
+ ui.userLabel = {};
+
+ // Click handler for delete button
+ var handleDelete = function(event)
+ {
+ apiDelete("/account/users/" + data.params.user, function(response)
+ {
+ location.href = "/user";
+ });
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ // We need a username, so die if we don't have it
+ if (!data.params.user) {
+ alert("No username supplied!");
+ return;
+ }
+
+ setupHeader();
+
+ // Highlight the account nav link
+ var navlinks = document.getElementsByClassName(elements.navlink);
+ for (var i = 0; i < navlinks.length; i++) {
+ if (navlinks[i].pathname == "/account/")
+ navlinks[i].className = " " + elements.navlinkActive;
+ }
+
+ // Get element references
+ ui.cancelButton = document.getElementById(elements.cancelButton);
+ ui.deleteButton = document.getElementById(elements.deleteButton);
+ ui.userLabel = document.getElementById(elements.userLabel);
+
+ // Populate username where we need it
+ ui.userLabel.innerHTML = data.params.user;
+
+ // Attach event handlers
+ ui.cancelButton.addEventListener("click", function(event)
+ {
+ location.href = "/user";
+ });
+ ui.deleteButton.addEventListener("click", handleDelete);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/user/user.css b/user/user.css
new file mode 100644
index 0000000..fe0ffcc
--- /dev/null
+++ b/user/user.css
@@ -0,0 +1,32 @@
+/*
+ * 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 url('/global.css');
+
+.profile-img {
+ height: 36px;
+ vertical-align: middle;
+ width: 36px;
+}
+
+td:nth-of-type(4) {
+ text-align: center;
+}
+
+#user {
+ padding: 15px 15px 15px;
+}
diff --git a/user/user.js b/user/user.js
new file mode 100644
index 0000000..008985c
--- /dev/null
+++ b/user/user.js
@@ -0,0 +1,148 @@
+/*
+ * 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, apiGet, md5, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.info = "info";
+ elements.lmcRow = "lmc-tr1";
+ elements.lmcRowAlt = "lmc-tr2";
+ elements.loading = "loading";
+ elements.navlinkActive = "navlink-active";
+ elements.profileImg = "profile-img";
+ elements.userTable = "user-table";
+
+ // Data received from API calls
+ var data = {};
+ data.users = [];
+
+ // Static references to UI elements
+ var ui = {};
+ ui.loading = {};
+ ui.userTable = {};
+
+ // Generates a user table row
+ var createUserRow = function(user, alt)
+ {
+ var row = document.createElement("tr");
+ if (alt)
+ row.className = elements.lmcRowAlt;
+ else
+ row.className = elements.lmcRow;
+
+ var nameCell = document.createElement("td");
+ var imgLink = document.createElement("a");
+ imgLink.href = "/user/edit?user=" + user.username;
+ var img = document.createElement("img");
+ img.className = elements.profileImg;
+ img.src = "https://www.gravatar.com/avatar/" + md5(user.email);
+ img.alt = user.username;
+ imgLink.appendChild(img);
+ var separator = document.createElement("span");
+ separator.innerHTML = " ";
+ var nameLink = document.createElement("a");
+ nameLink.href = "/user/edit?user=" + user.username;
+ nameLink.innerHTML = user.username;
+ nameCell.appendChild(imgLink);
+ nameCell.appendChild(separator);
+ nameCell.appendChild(nameLink);
+ row.appendChild(nameCell);
+
+ var email = document.createElement("td");
+ email.innerHTML = user.email;
+ row.appendChild(email);
+
+ var restricted = document.createElement("td");
+ if (user.restricted) {
+ var yes = document.createElement("span");
+ yes.innerHTML = "Yes (";
+ var permsLink = document.createElement("a");
+ permsLink.href = "/user/grants?user=" + user.username;
+ permsLink.innerHTML = "edit permissions";
+ var close = document.createElement("span");
+ close.innerHTML = ")";
+ restricted.appendChild(yes);
+ restricted.appendChild(permsLink);
+ restricted.appendChild(close);
+ } else {
+ restricted.innerHTML = "No";
+ }
+ row.appendChild(restricted);
+
+ var options = document.createElement("td");
+ var editLink = document.createElement("a");
+ editLink.href = "/user/edit?user=" + user.username;
+ editLink.innerHTML = "Edit";
+ var pipe = document.createElement("span");
+ pipe.innerHTML = " | ";
+ var removeLink = document.createElement("a");
+ removeLink.href = "/user/remove?user=" + user.username;
+ removeLink.innerHTML = "Remove";
+ options.appendChild(editLink);
+ options.appendChild(pipe);
+ options.appendChild(removeLink);
+ row.appendChild(options);
+
+ return row;
+ };
+
+ // Callback for users API call
+ var displayUsers = function(response)
+ {
+ data.users = data.users.concat(response.data);
+
+ // Request the next page if there are more
+ if (response.page != response.pages) {
+ apiGet("/account/users?page=" + (response.page + 1), displayUsers, null);
+ return;
+ }
+
+ ui.loading.remove();
+
+ // Add users to table
+ for (var i = 0; i < data.users.length; i++)
+ ui.userTable.appendChild(createUserRow(data.users[i], ui.userTable.children.length % 2));
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ setupHeader();
+
+ // Highlight the account nav link
+ var navlinks = document.getElementsByClassName(elements.navlink);
+ for (var i = 0; i < navlinks.length; i++) {
+ if (navlinks[i].pathname == "/account/")
+ navlinks[i].className = " " + elements.navlinkActive;
+ }
+
+ // Get element references
+ ui.loading = document.getElementById(elements.loading);
+ ui.userTable = document.getElementById(elements.userTable);
+
+ // Get data from API
+ apiGet("/account/users", displayUsers, null);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();
diff --git a/volumes/add/add.css b/volumes/add/add.css
index 1a489d6..e58ee40 100644
--- a/volumes/add/add.css
+++ b/volumes/add/add.css
@@ -25,7 +25,3 @@ tbody tr td:first-of-type {
font-weight: bold;
text-align: right;
}
-
-tbody tr:last-of-type {
- border: none;
-}
diff --git a/volumes/attach/attach.css b/volumes/attach/attach.css
index 9ccde82..f336a71 100644
--- a/volumes/attach/attach.css
+++ b/volumes/attach/attach.css
@@ -25,7 +25,3 @@ tbody tr td:first-of-type {
font-weight: bold;
text-align: right;
}
-
-tbody tr:last-of-type {
- border: none;
-}
diff --git a/volumes/clone/clone.css b/volumes/clone/clone.css
index db6d72a..8420f60 100644
--- a/volumes/clone/clone.css
+++ b/volumes/clone/clone.css
@@ -25,7 +25,3 @@ tbody tr td:first-of-type {
font-weight: bold;
text-align: right;
}
-
-tbody tr:last-of-type {
- border: none;
-}