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
DateDescriptionAmount
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/contact/contact.css b/account/contact/contact.css new file mode 100644 index 0000000..43fdbf6 --- /dev/null +++ b/account/contact/contact.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'); + +#contact { + padding: 15px 15px 15px; +} + +tbody tr td:first-of-type { + font-weight: bold; + text-align: right; +} diff --git a/account/contact/contact.js b/account/contact/contact.js new file mode 100644 index 0000000..857d73f --- /dev/null +++ b/account/contact/contact.js @@ -0,0 +1,143 @@ +/* + * 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"; +import { countries } from "/account/contact/countries.js"; + +(function() +{ + // Element names specific to this page + elements.address1 = "address1"; + elements.address2 = "address2"; + elements.city = "city"; + elements.company = "company"; + elements.country = "country"; + elements.email = "email"; + elements.firstName = "first-name"; + elements.lastName = "last-name"; + elements.phone = "phone"; + elements.saveButton = "save-button"; + elements.state = "state"; + elements.tax = "tax"; + elements.zip = "zip"; + + // Data received from API calls + var data = {}; + + // Static references to UI elements + var ui = {}; + ui.address1 = {}; + ui.address2 = {}; + ui.city = {}; + ui.company = {}; + ui.country = {}; + ui.email = {}; + ui.firstName = {}; + ui.lastName = {}; + ui.phone = {}; + ui.saveButton = {}; + ui.state = {}; + ui.tax = {}; + ui.zip = {}; + + // Callback for account details API call + var displayAccount = function(response) + { + ui.company.value = response.company; + ui.email.value = response.email; + ui.firstName.value = response.first_name; + ui.lastName.value = response.last_name; + ui.address1.value = response.address_1; + ui.address2.value = response.address_2; + ui.city.value = response.city; + ui.state.value = response.state; + ui.zip.value = response.zip; + ui.country.value = response.country; + ui.tax.value = response.tax_id; + ui.phone.value = response.phone; + + ui.saveButton.disabled = false; + }; + + // Click handler for save button + var handleSave = function(event) + { + if (event.currentTarget.disabled) + return; + + var req = { + "company": ui.company.value, + "email": ui.email.value, + "first_name": ui.firstName.value, + "last_name": ui.lastName.value, + "address_1": ui.address1.value, + "address_2": ui.address2.value, + "city": ui.city.value, + "state": ui.state.value, + "zip": ui.zip.value, + "country": ui.country.value, + "tax": ui.tax.value, + "phone": ui.phone.value + }; + + apiPut("/account", req, function(response) + { + location.href = "/account"; + }); + }; + + // Initial setup + var setup = function() + { + // Parse URL parameters + data.params = parseParams(); + + setupHeader(); + + // Get element references + ui.address1 = document.getElementById(elements.address1); + ui.address2 = document.getElementById(elements.address2); + ui.city = document.getElementById(elements.city); + ui.company = document.getElementById(elements.company); + ui.country = document.getElementById(elements.country); + ui.email = document.getElementById(elements.email); + ui.firstName = document.getElementById(elements.firstName); + ui.lastName = document.getElementById(elements.lastName); + ui.phone = document.getElementById(elements.phone); + ui.saveButton = document.getElementById(elements.saveButton); + ui.state = document.getElementById(elements.state); + ui.tax = document.getElementById(elements.tax); + ui.zip = document.getElementById(elements.zip); + + // Populate country selector + for (var i = 0; i < countries.length; i++) { + var option = document.createElement("option"); + option.value = countries[i].code; + option.innerHTML = countries[i]["name"]; + ui.country.appendChild(option); + } + + // Register event handlers + ui.saveButton.addEventListener("click", handleSave); + + // Get data from API + apiGet("/account", displayAccount, null); + }; + + // Attach onload handler + window.addEventListener("load", setup); +})(); diff --git a/account/contact/countries.js b/account/contact/countries.js new file mode 100644 index 0000000..6c9a404 --- /dev/null +++ b/account/contact/countries.js @@ -0,0 +1,1013 @@ +/* + * 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 . + */ + +var countries = [ + { + "name": "Afghanistan", + "code": "AF" + }, + { + "name": "Åland Islands", + "code": "AX" + }, + { + "name": "Albania", + "code": "AL" + }, + { + "name": "Algeria", + "code": "DZ" + }, + { + "name": "American Samoa", + "code": "AS" + }, + { + "name": "Andorra", + "code": "AD" + }, + { + "name": "Angola", + "code": "AO" + }, + { + "name": "Anguilla", + "code": "AI" + }, + { + "name": "Antarctica", + "code": "AQ" + }, + { + "name": "Antigua and Barbuda", + "code": "AG" + }, + { + "name": "Argentina", + "code": "AR" + }, + { + "name": "Armenia", + "code": "AM" + }, + { + "name": "Aruba", + "code": "AW" + }, + { + "name": "Australia", + "code": "AU" + }, + { + "name": "Austria", + "code": "AT" + }, + { + "name": "Azerbaijan", + "code": "AZ" + }, + { + "name": "Bahamas", + "code": "BS" + }, + { + "name": "Bahrain", + "code": "BH" + }, + { + "name": "Bangladesh", + "code": "BD" + }, + { + "name": "Barbados", + "code": "BB" + }, + { + "name": "Belarus", + "code": "BY" + }, + { + "name": "Belgium", + "code": "BE" + }, + { + "name": "Belize", + "code": "BZ" + }, + { + "name": "Benin", + "code": "BJ" + }, + { + "name": "Bermuda", + "code": "BM" + }, + { + "name": "Bhutan", + "code": "BT" + }, + { + "name": "Bolivia", + "code": "BO" + }, + { + "name": "Bonaire, Sint Eustatius and Saba", + "code": "BQ" + }, + { + "name": "Bosnia and Herzegovina", + "code": "BA" + }, + { + "name": "Botswana", + "code": "BW" + }, + { + "name": "Bouvet Island", + "code": "BV" + }, + { + "name": "Brazil", + "code": "BR" + }, + { + "name": "British Indian Ocean Territory", + "code": "IO" + }, + { + "name": "Brunei Darussalam", + "code": "BN" + }, + { + "name": "Bulgaria", + "code": "BG" + }, + { + "name": "Burkina Faso", + "code": "BF" + }, + { + "name": "Burundi", + "code": "BI" + }, + { + "name": "Cambodia", + "code": "KH" + }, + { + "name": "Cameroon", + "code": "CM" + }, + { + "name": "Canada", + "code": "CA" + }, + { + "name": "Cape Verde", + "code": "CV" + }, + { + "name": "Cayman Islands", + "code": "KY" + }, + { + "name": "Central African Republic", + "code": "CF" + }, + { + "name": "Chad", + "code": "TD" + }, + { + "name": "Chile", + "code": "CL" + }, + { + "name": "China", + "code": "CN" + }, + { + "name": "Christmas Island", + "code": "CX" + }, + { + "name": "Cocos (Keeling) Islands", + "code": "CC" + }, + { + "name": "Colombia", + "code": "CO" + }, + { + "name": "Comoros", + "code": "KM" + }, + { + "name": "Congo, Republic of the (Brazzaville)", + "code": "CG" + }, + { + "name": "Congo, the Democratic Republic of the (Kinshasa)", + "code": "CD" + }, + { + "name": "Cook Islands", + "code": "CK" + }, + { + "name": "Costa Rica", + "code": "CR" + }, + { + "name": "Côte d'Ivoire, Republic of", + "code": "CI" + }, + { + "name": "Croatia", + "code": "HR" + }, + { + "name": "Cuba", + "code": "CU" + }, + { + "name": "Curaçao", + "code": "CW" + }, + { + "name": "Cyprus", + "code": "CY" + }, + { + "name": "Czech Republic", + "code": "CZ" + }, + { + "name": "Denmark", + "code": "DK" + }, + { + "name": "Djibouti", + "code": "DJ" + }, + { + "name": "Dominica", + "code": "DM" + }, + { + "name": "Dominican Republic", + "code": "DO" + }, + { + "name": "Ecuador", + "code": "EC" + }, + { + "name": "Egypt", + "code": "EG" + }, + { + "name": "El Salvador", + "code": "SV" + }, + { + "name": "Equatorial Guinea", + "code": "GQ" + }, + { + "name": "Eritrea", + "code": "ER" + }, + { + "name": "Estonia", + "code": "EE" + }, + { + "name": "Ethiopia", + "code": "ET" + }, + { + "name": "Falkland Islands (Islas Malvinas)", + "code": "FK" + }, + { + "name": "Faroe Islands", + "code": "FO" + }, + { + "name": "Fiji", + "code": "FJ" + }, + { + "name": "Finland", + "code": "FI" + }, + { + "name": "France", + "code": "FR" + }, + { + "name": "French Guiana", + "code": "GF" + }, + { + "name": "French Polynesia", + "code": "PF" + }, + { + "name": "French Southern and Antarctic Lands", + "code": "TF" + }, + { + "name": "Gabon", + "code": "GA" + }, + { + "name": "Gambia, The", + "code": "GM" + }, + { + "name": "Georgia", + "code": "GE" + }, + { + "name": "Germany", + "code": "DE" + }, + { + "name": "Ghana", + "code": "GH" + }, + { + "name": "Gibraltar", + "code": "GI" + }, + { + "name": "Greece", + "code": "GR" + }, + { + "name": "Greenland", + "code": "GL" + }, + { + "name": "Grenada", + "code": "GD" + }, + { + "name": "Guadeloupe", + "code": "GP" + }, + { + "name": "Guam", + "code": "GU" + }, + { + "name": "Guatemala", + "code": "GT" + }, + { + "name": "Guernsey", + "code": "GG" + }, + { + "name": "Guinea", + "code": "GN" + }, + { + "name": "Guinea-Bissau", + "code": "GW" + }, + { + "name": "Guyana", + "code": "GY" + }, + { + "name": "Haiti", + "code": "HT" + }, + { + "name": "Heard Island and McDonald Islands", + "code": "HM" + }, + { + "name": "Holy See (Vatican City)", + "code": "VA" + }, + { + "name": "Honduras", + "code": "HN" + }, + { + "name": "Hong Kong", + "code": "HK" + }, + { + "name": "Hungary", + "code": "HU" + }, + { + "name": "Iceland", + "code": "IS" + }, + { + "name": "India", + "code": "IN" + }, + { + "name": "Indonesia", + "code": "ID" + }, + { + "name": "Iran, Islamic Republic of", + "code": "IR" + }, + { + "name": "Iraq", + "code": "IQ" + }, + { + "name": "Ireland", + "code": "IE" + }, + { + "name": "Isle of Man", + "code": "IM" + }, + { + "name": "Israel", + "code": "IL" + }, + { + "name": "Italy", + "code": "IT" + }, + { + "name": "Jamaica", + "code": "JM" + }, + { + "name": "Japan", + "code": "JP" + }, + { + "name": "Jersey", + "code": "JE" + }, + { + "name": "Jordan", + "code": "JO" + }, + { + "name": "Kazakhstan", + "code": "KZ" + }, + { + "name": "Kenya", + "code": "KE" + }, + { + "name": "Kiribati", + "code": "KI" + }, + { + "name": "Korea, Democratic People's Republic of", + "code": "KP" + }, + { + "name": "Korea, Republic of", + "code": "KR" + }, + { + "name": "Kuwait", + "code": "KW" + }, + { + "name": "Kyrgyzstan", + "code": "KG" + }, + { + "name": "Laos", + "code": "LA" + }, + { + "name": "Latvia", + "code": "LV" + }, + { + "name": "Lebanon", + "code": "LB" + }, + { + "name": "Lesotho", + "code": "LS" + }, + { + "name": "Liberia", + "code": "LR" + }, + { + "name": "Libya", + "code": "LY" + }, + { + "name": "Liechtenstein", + "code": "LI" + }, + { + "name": "Lithuania", + "code": "LT" + }, + { + "name": "Luxembourg", + "code": "LU" + }, + { + "name": "Macao", + "code": "MO" + }, + { + "name": "Macedonia, Republic of", + "code": "MK" + }, + { + "name": "Madagascar", + "code": "MG" + }, + { + "name": "Malawi", + "code": "MW" + }, + { + "name": "Malaysia", + "code": "MY" + }, + { + "name": "Maldives", + "code": "MV" + }, + { + "name": "Mali", + "code": "ML" + }, + { + "name": "Malta", + "code": "MT" + }, + { + "name": "Marshall Islands", + "code": "MH" + }, + { + "name": "Martinique", + "code": "MQ" + }, + { + "name": "Mauritania", + "code": "MR" + }, + { + "name": "Mauritius", + "code": "MU" + }, + { + "name": "Mayotte", + "code": "YT" + }, + { + "name": "Mexico", + "code": "MX" + }, + { + "name": "Micronesia, Federated States of", + "code": "FM" + }, + { + "name": "Moldova", + "code": "MD" + }, + { + "name": "Monaco", + "code": "MC" + }, + { + "name": "Mongolia", + "code": "MN" + }, + { + "name": "Montenegro", + "code": "ME" + }, + { + "name": "Montserrat", + "code": "MS" + }, + { + "name": "Morocco", + "code": "MA" + }, + { + "name": "Mozambique", + "code": "MZ" + }, + { + "name": "Myanmar", + "code": "MM" + }, + { + "name": "Namibia", + "code": "NA" + }, + { + "name": "Nauru", + "code": "NR" + }, + { + "name": "Nepal", + "code": "NP" + }, + { + "name": "Netherlands", + "code": "NL" + }, + { + "name": "New Caledonia", + "code": "NC" + }, + { + "name": "New Zealand", + "code": "NZ" + }, + { + "name": "Nicaragua", + "code": "NI" + }, + { + "name": "Niger", + "code": "NE" + }, + { + "name": "Nigeria", + "code": "NG" + }, + { + "name": "Niue", + "code": "NU" + }, + { + "name": "Norfolk Island", + "code": "NF" + }, + { + "name": "Northern Mariana Islands", + "code": "MP" + }, + { + "name": "Norway", + "code": "NO" + }, + { + "name": "Oman", + "code": "OM" + }, + { + "name": "Pakistan", + "code": "PK" + }, + { + "name": "Palau", + "code": "PW" + }, + { + "name": "Palestine, State of", + "code": "PS" + }, + { + "name": "Panama", + "code": "PA" + }, + { + "name": "Papua New Guinea", + "code": "PG" + }, + { + "name": "Paraguay", + "code": "PY" + }, + { + "name": "Peru", + "code": "PE" + }, + { + "name": "Philippines", + "code": "PH" + }, + { + "name": "Pitcairn", + "code": "PN" + }, + { + "name": "Poland", + "code": "PL" + }, + { + "name": "Portugal", + "code": "PT" + }, + { + "name": "Puerto Rico", + "code": "PR" + }, + { + "name": "Qatar", + "code": "QA" + }, + { + "name": "Réunion", + "code": "RE" + }, + { + "name": "Romania", + "code": "RO" + }, + { + "name": "Russian Federation", + "code": "RU" + }, + { + "name": "Rwanda", + "code": "RW" + }, + { + "name": "Saint Barthélemy", + "code": "BL" + }, + { + "name": "Saint Helena, Ascension and Tristan da Cunha", + "code": "SH" + }, + { + "name": "Saint Kitts and Nevis", + "code": "KN" + }, + { + "name": "Saint Lucia", + "code": "LC" + }, + { + "name": "Saint Martin", + "code": "MF" + }, + { + "name": "Saint Pierre and Miquelon", + "code": "PM" + }, + { + "name": "Saint Vincent and the Grenadines", + "code": "VC" + }, + { + "name": "Samoa", + "code": "WS" + }, + { + "name": "San Marino", + "code": "SM" + }, + { + "name": "Sao Tome and Principe", + "code": "ST" + }, + { + "name": "Saudi Arabia", + "code": "SA" + }, + { + "name": "Senegal", + "code": "SN" + }, + { + "name": "Serbia", + "code": "RS" + }, + { + "name": "Seychelles", + "code": "SC" + }, + { + "name": "Sierra Leone", + "code": "SL" + }, + { + "name": "Singapore", + "code": "SG" + }, + { + "name": "Sint Maarten (Dutch part)", + "code": "SX" + }, + { + "name": "Slovakia", + "code": "SK" + }, + { + "name": "Slovenia", + "code": "SI" + }, + { + "name": "Solomon Islands", + "code": "SB" + }, + { + "name": "Somalia", + "code": "SO" + }, + { + "name": "South Africa", + "code": "ZA" + }, + { + "name": "South Georgia and South Sandwich Islands", + "code": "GS" + }, + { + "name": "South Sudan", + "code": "SS" + }, + { + "name": "Spain", + "code": "ES" + }, + { + "name": "Sri Lanka", + "code": "LK" + }, + { + "name": "Sudan", + "code": "SD" + }, + { + "name": "Suriname", + "code": "SR" + }, + { + "name": "Swaziland", + "code": "SZ" + }, + { + "name": "Sweden", + "code": "SE" + }, + { + "name": "Switzerland", + "code": "CH" + }, + { + "name": "Syrian Arab Republic", + "code": "SY" + }, + { + "name": "Taiwan", + "code": "TW" + }, + { + "name": "Tajikistan", + "code": "TJ" + }, + { + "name": "Tanzania, United Republic of", + "code": "TZ" + }, + { + "name": "Thailand", + "code": "TH" + }, + { + "name": "Timor-Leste", + "code": "TL" + }, + { + "name": "Togo", + "code": "TG" + }, + { + "name": "Tokelau", + "code": "TK" + }, + { + "name": "Tonga", + "code": "TO" + }, + { + "name": "Trinidad and Tobago", + "code": "TT" + }, + { + "name": "Tunisia", + "code": "TN" + }, + { + "name": "Turkey", + "code": "TR" + }, + { + "name": "Turkmenistan", + "code": "TM" + }, + { + "name": "Turks and Caicos Islands", + "code": "TC" + }, + { + "name": "Tuvalu", + "code": "TV" + }, + { + "name": "Uganda", + "code": "UG" + }, + { + "name": "Ukraine", + "code": "UA" + }, + { + "name": "United Arab Emirates", + "code": "AE" + }, + { + "name": "United Kingdom", + "code": "GB" + }, + { + "name": "United States", + "code": "US" + }, + { + "name": "United States Minor Outlying Islands", + "code": "UM" + }, + { + "name": "Uruguay", + "code": "UY" + }, + { + "name": "Uzbekistan", + "code": "UZ" + }, + { + "name": "Vanuatu", + "code": "VU" + }, + { + "name": "Venezuela, Bolivarian Republic of", + "code": "VE" + }, + { + "name": "Vietnam", + "code": "VN" + }, + { + "name": "Virgin Islands, British", + "code": "VG" + }, + { + "name": "Virgin Islands, U.S.", + "code": "VI" + }, + { + "name": "Wallis and Futuna", + "code": "WF" + }, + { + "name": "Western Sahara", + "code": "EH" + }, + { + "name": "Yemen", + "code": "YE" + }, + { + "name": "Zambia", + "code": "ZM" + }, + { + "name": "Zimbabwe", + "code": "ZW" + } +]; + +export { countries }; diff --git a/account/contact/index.shtml b/account/contact/index.shtml new file mode 100644 index 0000000..3c705bd --- /dev/null +++ b/account/contact/index.shtml @@ -0,0 +1,107 @@ + + + + + + LMC - Account // Contact Info + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Contact Information
Company Name
EmailMain point of contact and receives all emails.
First Name
Last Name
Address1
Address2
City
State
Zip
Country
Tax IDVAT, GST, etc. identification number
Phone1
+
+
+ + 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 Cardxxxxxxxxxxxx Exp:
Update Card
New Card NumberLinode accepts Visa, MasterCard, American Express, and Discover
Expires + + +
+
+
+
+ + diff --git a/account/index.shtml b/account/index.shtml new file mode 100644 index 0000000..87e4666 --- /dev/null +++ b/account/index.shtml @@ -0,0 +1,165 @@ + + + + + + LMC - Account + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Account Information
Contact
Address
Email
Credit Card
Credit Card + xxxxxxxxxxxx Exp:
+ +
Recent Billing Activity
Recent Activity + + +
+
Account Balance
Account Balance(make a payment)
Uninvoiced Balance and countingThis will appear on your next invoice.
Promotions
EU Model Contract
Signed (view)
+ + + + + + + + + + + + + + + + + + + +
Upgrade to Managed
 
Linode Managed + 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. +
or learn more.
+

+ Your account has been active since
+
+ Cancel this Account +

+
+
+ + diff --git a/account/invoice/index.shtml b/account/invoice/index.shtml new file mode 100644 index 0000000..17b7997 --- /dev/null +++ b/account/invoice/index.shtml @@ -0,0 +1,68 @@ + + + + + + LMC - Account // View Invoice + + + + + + + +
+
+ + + + + + + + + + + + + + + + + +
Invoice #
DescriptionFromToQuantityUnit PriceSubtotalTaxTotal
+ + + + + + + + + + + + + + + +
Subtotal:
Tax:
Invoice Total:
+
+
+ + 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 + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Make a Payment
Current Balance
Make a Payment - via Credit Card
Current Cardxxxxxxxxxxxx Exp: (update credit card)
Amount to Charge (USD)
CVV (optional)
Make a Payment - via PayPal
Amount to Pay (USD)
+
+
+ + 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
DescriptionDateAmount
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
DescriptionDateAmount
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
PasswordA 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 + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Edit User:
Username
Reset PasswordClick here to reset this user's password
EmailYou can change your email on the profile page.
Restricted User +
+ +
+
+
+
+ + diff --git a/user/grants/grants.css b/user/grants/grants.css new file mode 100644 index 0000000..485b1b5 --- /dev/null +++ b/user/grants/grants.css @@ -0,0 +1,56 @@ +/* + * 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'); + +#grants { + padding: 15px 15px 15px; +} + +h2 { + font-size: 18px; +} + +table { + border-bottom: 1px solid #4C4C4C; + border-collapse: separate; + border-left: 1px solid #B2B2B2; + border-right: 1px solid #4C4C4C; + border-spacing: 2px; + border-top: 1px solid #B2B2B2; + margin-bottom: 30px; +} + +table label { + display: block; +} + +table tbody td { + text-align: center; +} + +table tbody td:first-of-type { + text-align: left; +} + +table td { + border-bottom: 1px solid #B2B2B2; + border-left: 1px solid #4C4C4C; + border-right: 1px solid #B2B2B2; + border-top: 1px solid #4C4C4C; + padding: 2px; +} diff --git a/user/grants/grants.js b/user/grants/grants.js new file mode 100644 index 0000000..7615327 --- /dev/null +++ b/user/grants/grants.js @@ -0,0 +1,271 @@ +/* + * 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.billingNone = "billing-none"; + elements.billingRO = "billing-ro"; + elements.billingRW = "billing-rw"; + elements.createDomain = "create-domain"; + elements.createImage = "create-image"; + elements.createLinode = "create-linode"; + elements.createLV = "create-lv"; + elements.createNB = "create-nb"; + elements.createSS = "create-ss"; + elements.createVolume = "create-volume"; + elements.grantBox = "grant-box"; + elements.grantTables = {}; + elements.grantTables.domain = "dns-grants"; + elements.grantTables.image = "image-grants"; + elements.grantTables.linode = "linode-grants"; + elements.grantTables.longview = "lv-grants"; + elements.grantTables.nodebalancer = "nb-grants"; + elements.grantTables.stackscript = "ss-grants"; + elements.grantTables.volume = "volume-grants"; + elements.modifyLV = "modify-lv"; + elements.updateButton = "update-button"; + + // Data received from API calls + var data = {}; + + // Static references to UI elements + var ui = {}; + ui.billingNone = {}; + ui.billingRO = {}; + ui.billingRW = {}; + ui.createDomain = {}; + ui.createImage = {}; + ui.createLinode = {}; + ui.createLV = {}; + ui.createNB = {}; + ui.createSS = {}; + ui.createVolume = {}; + ui.grantTables = {}; + ui.grantTables.domain = {}; + ui.grantTables.image = {}; + ui.grantTables.linode = {}; + ui.grantTables.longview = {}; + ui.grantTables.nodebalancer = {}; + ui.grantTables.stackscript = {}; + ui.grantTables.volume = {}; + ui.modifyLV = {}; + ui.updateButton = {}; + + // Creates a row for a grant table + var createGrantRow = function(type, perm) + { + var row = document.createElement("tr"); + + var label = document.createElement("td"); + label.innerHTML = perm.label; + row.appendChild(label); + + var none = document.createElement("td"); + var noneLabel = document.createElement("label"); + noneLabel.for = type + "-none-" + perm.id; + var noneBox = document.createElement("input"); + noneBox.id = type + "-none-" + perm.id; + noneBox.className = elements.grantBox; + noneBox.type = "radio"; + noneBox.name = type + "-" + perm.id; + noneLabel.appendChild(noneBox); + none.appendChild(noneLabel); + row.appendChild(none); + + var ro = document.createElement("td"); + var roLabel = document.createElement("label"); + roLabel.for = type + "-ro-" + perm.id; + var roBox = document.createElement("input"); + roBox.id = type + "-ro-" + perm.id; + roBox.className = elements.grantBox; + roBox.type = "radio"; + roBox.name = type + "-" + perm.id; + roLabel.appendChild(roBox); + ro.appendChild(roLabel); + row.appendChild(ro); + + var rw = document.createElement("td"); + var rwLabel = document.createElement("label"); + rwLabel.for = type + "-rw-" + perm.id; + var rwBox = document.createElement("input"); + rwBox.id = type + "-rw-" + perm.id; + rwBox.className = elements.grantBox; + rwBox.type = "radio"; + rwBox.name = type + "-" + perm.id; + rwLabel.appendChild(rwBox); + rw.appendChild(rwLabel); + row.appendChild(rw); + + switch (perm.permissions) { + case "read_only": + roBox.checked = true; + break; + case "read_write": + rwBox.checked = true; + break; + default: + noneBox.checked = true; + } + + return row; + }; + + // Callback for user grants API call + var displayGrants = function(response) + { + // Set global grants + ui.createLinode.checked = response.global.add_linodes; + ui.createNB.checked = response.global.add_nodebalancers; + ui.createLV.checked = response.global.add_longview; + ui.modifyLV.checked = response.global.longview_subscription; + ui.createDomain.checked = response.global.add_domains; + ui.createSS.checked = response.global.add_stackscripts; + ui.createImage.checked = response.global.add_images; + ui.createVolume.checked = response.global.add_volumes; + + // Billing grants + switch (response.global.account_access) { + case "read_only": + ui.billingRO.checked = true; + break; + case "read_write": + ui.billingRW.checked = true; + break; + default: + ui.billingNone.checked = true; + } + + // Specific grants + for (var i in ui.grantTables) { + for (var j = 0; j < response[i].length; j++) + ui.grantTables[i].appendChild(createGrantRow(i, response[i][j])); + } + + ui.updateButton.disabled = false; + }; + + // Click handler for update button + var handleUpdate = function(event) + { + if (event.currentTarget.disabled) + return; + + var req = {}; + + // Add in global permissions + req.global = { + "add_linodes": ui.createLinode.checked, + "add_nodebalancers": ui.createNB.checked, + "add_longview": ui.createLV.checked, + "longview_subscription": ui.modifyLV.checked, + "add_domains": ui.createDomain.checked, + "add_stackscripts": ui.createSS.checked, + "add_images": ui.createImage.checked, + "add_volumes": ui.createVolume.checked + }; + + // Add in billing permissions + if (ui.billingRO.checked) + req.global.account_access = "read_only"; + else if (ui.billingRW.checked) + req.global.account_access = "read_write"; + else + req.global.account_access = null; + + // Add in specific persmissions + for (var i in ui.grantTables) + req[i] = []; + + var grantBoxes = document.getElementsByClassName(elements.grantBox); + for (var i = 0; i < grantBoxes.length; i++) { + if (!grantBoxes[i].checked) + continue; + + // This will give us an array tuple of [type, permission, id] + var id = grantBoxes[i].id.split("-"); + + var perm = {}; + perm.id = parseInt(id[2]); + if (id[1] == "ro") + perm.permissions = "read_only"; + else if (id[1] == "rw") + perm.permissions = "read_write"; + else + perm.permissions = null; + + req[id[0]].push(perm); + } + + apiPut("/account/users/" + data.params.user + "/grants", 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.billingNone = document.getElementById(elements.billingNone); + ui.billingRO = document.getElementById(elements.billingRO); + ui.billingRW = document.getElementById(elements.billingRW); + ui.createDomain = document.getElementById(elements.createDomain); + ui.createImage = document.getElementById(elements.createImage); + ui.createLinode = document.getElementById(elements.createLinode); + ui.createLV = document.getElementById(elements.createLV); + ui.createNB = document.getElementById(elements.createNB); + ui.createSS = document.getElementById(elements.createSS); + ui.createVolume = document.getElementById(elements.createVolume); + for (var i in ui.grantTables) + ui.grantTables[i] = document.getElementById(elements.grantTables[i]); + ui.modifyLV = document.getElementById(elements.modifyLV); + ui.updateButton = document.getElementById(elements.updateButton); + + // Populate username where we need it + document.title += " - " + data.params.user; + + // Attach event handlers + ui.updateButton.addEventListener("click", handleUpdate); + + // Get data from API + apiGet("/account/users/" + data.params.user + "/grants", displayGrants, null); + }; + + // Attach onload handler + window.addEventListener("load", setup); +})(); diff --git a/user/grants/index.shtml b/user/grants/index.shtml new file mode 100644 index 0000000..c689ef3 --- /dev/null +++ b/user/grants/index.shtml @@ -0,0 +1,132 @@ + + + + + + LMC - Account // User Grants + + + + + + + +
+
+

Global Grants

+
+
+
+
+
+
+
+ +

Billing Access

+
+
+ +

Linode Grants

+ + + + + + + + + + +
LinodeNoneRead OnlyRead-Write
+

NodeBalancer Grants

+ + + + + + + + + + +
NodeBalancerNoneRead OnlyRead-Write
+

Longview Grants

+ + + + + + + + + + +
ServerNoneRead OnlyRead-Write
+

DNS Zone Grants

+ + + + + + + + + + +
ZoneNoneRead OnlyRead-Write
+

StackScript Grants

+ + + + + + + + + + +
StackScriptNoneRead OnlyRead-Write
+

Image Grants

+ + + + + + + + + + +
ImageNoneRead OnlyRead-Write
+

Volume Grants

+ + + + + + + + + + +
VolumeNoneRead OnlyRead-Write
+ +
+
+ + diff --git a/user/index.shtml b/user/index.shtml new file mode 100644 index 0000000..ff94a61 --- /dev/null +++ b/user/index.shtml @@ -0,0 +1,53 @@ + + + + + + LMC - Account // User Manager + + + + + + + +
+
+ + + + + + + + + + + + + + + + + +
User Manager
UsernameEmailRestrictedOptions
Loading...
+ +
+
+ + diff --git a/user/remove/index.shtml b/user/remove/index.shtml new file mode 100644 index 0000000..ee58612 --- /dev/null +++ b/user/remove/index.shtml @@ -0,0 +1,36 @@ + + + + + + LMC - Account // Remove User + + + + + + + +
+
+

Are you sure you want to delete the 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; -}