Compare commits
26 Commits
148c9cd0db
...
main
Author | SHA1 | Date | |
---|---|---|---|
0231a7478a
|
|||
ad202847c4
|
|||
709a220e1b
|
|||
35e9d259f5
|
|||
4c9e0f6bbe
|
|||
59889d6910
|
|||
5f89bdcb1a
|
|||
c4864dd6c0
|
|||
c06bd1dc75
|
|||
b1f190d65a
|
|||
8ea657c757
|
|||
4c4db6a734
|
|||
dae18dd844
|
|||
3ce7fc6304
|
|||
f1b59439eb | |||
555a2e8969 | |||
88b8cf319c | |||
19e234d02f | |||
d447511557 | |||
a82a658922 | |||
166295e046 | |||
ced38b68e9 | |||
bd9a85a068 | |||
ac999c9704 | |||
5eb587aba5 | |||
99e0e497c2 |
2
404.html
2
404.html
@ -28,7 +28,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<h2>We couldn't find that page :(</h2>
|
||||
<h3 class="info">
|
||||
It's possible this page hasn't been implemented yet.<br />
|
||||
You can check the development status <a target="_blank" href="https://git.bradleylaboon.com/lb.laboon/lmc">here</a>.
|
||||
You can check the development status <a target="_blank" href="https://laboon.dev/brad/lmc">here</a>.
|
||||
</h3>
|
||||
</fieldset>
|
||||
</body>
|
||||
|
16
README.md
16
README.md
@ -1,4 +1,4 @@
|
||||
Full disclosure: I am an employee of Linode, however this project is being developed independently in my own free time and is not associated in any way with Linode, LLC. Only publicly-available documentation and information is being used in the development of this project.
|
||||
Full disclosure: I am an employee of Linode/Akamai, however this project is being developed independently in my own free time and is not associated in any way with Linode, LLC or Akamai Technologies, Inc. Only publicly-available documentation and information is being used in the development of this project.
|
||||
|
||||
# Linode Manager Classic
|
||||
LMC is a modern recreation of Linode's original manager interface that many people know and love. It is implemented as a client-side browser app using just vanilla HTML5, CSS3, and JavaScript (no 3rd-party libraries or other external dependencies). It uses Linode's OAuth provider for authentication and interfaces with APIv4.
|
||||
@ -12,26 +12,26 @@ This project is currently a work in progress. The following list provides a high
|
||||
|
||||
- [x] OAuth Authentication/Login/Logout
|
||||
- [x] Gravatar
|
||||
- [x] Linodes (including the dashboard and all related subpages, but not including graphs)
|
||||
- [x] Linodes
|
||||
- [x] Block Storage (Volumes)
|
||||
- [x] Images
|
||||
- [x] DNS
|
||||
- [x] Account Details (excluding PayPal support)
|
||||
- [x] Account Details
|
||||
- [x] User Profile Settings
|
||||
- [ ] PayPal payments
|
||||
- [ ] Graphs
|
||||
- [x] Graphs
|
||||
- [x] NodeBalancers
|
||||
- [ ] Adding PayPal/GPay payment methods
|
||||
- [ ] StackScripts
|
||||
- [ ] NodeBalancers
|
||||
- [ ] Longview
|
||||
- [ ] Support Tickets
|
||||
|
||||
Eventually I plan to also implement features which were never available in the original manager, including Object Storage, One-Click Apps, and LKE.
|
||||
|
||||
## Reporting Issues
|
||||
Before reporting an issue, please search the issue tracker to see if the issue has already reported. The canonical source of this repository is located at https://git.bradleylaboon.com/lb.laboon/lmc. Any forks on any other websites (including GitHub) are unofficial. The issue tracker located at that address should be used for all bug reports. In some cases, the root cause of an issue might be due to features which are currently missing from Linode's APIv4 (for example, APIv4 does not expose which physical host a given Linode instance is running on, so it is not possible to display this information like the original manager did). In these cases I may close the issue and redirect you upstream to Linode.
|
||||
Before reporting an issue, please search the issue tracker to see if the issue has already reported. The canonical source of this repository is located at https://laboon.dev/brad/lmc. Any forks on any other websites (including GitHub) are unofficial. The issue tracker located at that address should be used for all bug reports. In some cases, the root cause of an issue might be due to features which are currently missing from Linode's APIv4 (for example, APIv4 does not expose which physical host a given Linode instance is running on, so it is not possible to display this information like the original manager did). In these cases I may close the issue and redirect you upstream to Linode.
|
||||
|
||||
## Contributing
|
||||
The best way to contribute is by opening issues on the issue tracker. For simple fixes or adjustments I might accept a PR, but for the time being I would prefer to maintain this project as a solo effort. I may change my mind in the future.
|
||||
|
||||
## Self-hosting
|
||||
In order to self-host this application, you must first create your own OAuth client using your existing Linode account (instructions for doing this in Linode's new manager can be found [here](https://github.com/linode/manager/blob/develop/CREATE_CLIENT.md)). When creating the OAuth client, use the URL where you will be hosting the app as the Callback URL. Then you can clone this repository into your webroot and copy the `clientID.js.example` file to `clientID.js` and fill in the empty string with your Client ID. You must ensure that Server Side Includes (SSI) is enabled in your web server. Optionally, you can configure your web server to use the `404.html` file as a custom error document.
|
||||
In order to self-host this application, you must first create your own OAuth client using your existing Linode account. When creating the OAuth client, use the URL where you will be hosting the app as the Callback URL. Then you can clone this repository into your webroot and copy the [clientID.js.example](clientID.js.example) file to `clientID.js` and fill in the empty string with your Client ID. You must ensure that Server Side Includes (SSI) is enabled in your web server for .shtml documents. Optionally, you can configure your web server to use the [404.html](404.html) file as a custom error document.
|
||||
|
@ -15,7 +15,7 @@
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeString } from "/global.js";
|
||||
import { settings, elements, apiDelete, apiGet, apiPost, parseParams, setupHeader, timeString } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
@ -27,10 +27,9 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri
|
||||
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.defaultPrefix = "default-payment-";
|
||||
elements.deletePrefix = "delete-payment-";
|
||||
elements.email = "email";
|
||||
elements.gdpr = "gdpr";
|
||||
elements.gdprDate = "gdpr-date";
|
||||
@ -41,6 +40,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri
|
||||
elements.managed = "managed";
|
||||
elements.managedButton = "managed-button";
|
||||
elements.pay = "pay";
|
||||
elements.paymentMethods = "payment-methods";
|
||||
elements.promotions = "promotions";
|
||||
elements.promotionsTable = "promotions-table";
|
||||
elements.uninvoiced = "uninvoiced";
|
||||
@ -50,7 +50,8 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri
|
||||
var data = {};
|
||||
data.account = {};
|
||||
data.invoices = [];
|
||||
data.linodes = [];
|
||||
data.numLinodes = 0;
|
||||
data.paymentMethods = [];
|
||||
data.payments = [];
|
||||
|
||||
// Static references to UI elements
|
||||
@ -60,9 +61,6 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri
|
||||
ui.balance = {};
|
||||
ui.balanceStatus = {};
|
||||
ui.billingActivity = {};
|
||||
ui.ccNumber = {};
|
||||
ui.ccDuration = {};
|
||||
ui.ccExpire = {};
|
||||
ui.current = {};
|
||||
ui.email = {};
|
||||
ui.gdpr = [];
|
||||
@ -70,6 +68,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri
|
||||
ui.managed = {};
|
||||
ui.managedButton = {};
|
||||
ui.pay = {};
|
||||
ui.paymentMethods = {};
|
||||
ui.promotions = [];
|
||||
ui.promotionsTable = {};
|
||||
ui.uninvoiced = {};
|
||||
@ -80,6 +79,78 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri
|
||||
state.haveInvoices = false;
|
||||
state.havePayments = false;
|
||||
|
||||
// Creates a row for the payment methods table
|
||||
var createMethodRow = function(method)
|
||||
{
|
||||
var typeNames = {
|
||||
"credit_card": "Credit Card",
|
||||
"google_pay": "Google Pay",
|
||||
"paypal": "PayPal"
|
||||
};
|
||||
|
||||
var row = document.createElement("tr");
|
||||
row.className = elements.lmcRowStandard;
|
||||
|
||||
var type = document.createElement("td");
|
||||
if (typeNames[method.type])
|
||||
type.innerHTML = typeNames[method.type];
|
||||
else
|
||||
type.innerHTML = method.type;
|
||||
row.appendChild(type);
|
||||
|
||||
var details = document.createElement("td");
|
||||
if (method.type == "credit_card" || method.type == "google_pay") {
|
||||
var ccInfo = document.createElement("span");
|
||||
ccInfo.innerHTML = method.data.card_type + " xxxxxxxxxxxx" + method.data.last_four + " Exp: " + method.data.expiry;
|
||||
var br = document.createElement("br");
|
||||
details.appendChild(ccInfo);
|
||||
details.appendChild(br);
|
||||
var monthYear = method.data.expiry.split("/");
|
||||
var expireDate = new Date(parseInt(monthYear[1]), parseInt(monthYear[0]), 0);
|
||||
var now = new Date();
|
||||
if (expireDate - now > 0) {
|
||||
var duration = document.createElement("span");
|
||||
duration.innerHTML = "Expires " + timeString(now - expireDate, true);
|
||||
details.appendChild(duration);
|
||||
} else {
|
||||
var expired = document.createElement("strong");
|
||||
expired.innerHTML = "Expired!";
|
||||
details.appendChild(expired);
|
||||
}
|
||||
} else if (method.type == "paypal") {
|
||||
var email = document.createElement("span");
|
||||
email.innerHTML = method.data.email;
|
||||
details.appendChild(email);
|
||||
}
|
||||
row.appendChild(details);
|
||||
|
||||
var options = document.createElement("td");
|
||||
if (method.is_default) {
|
||||
var defaultMsg = document.createElement("strong");
|
||||
defaultMsg.innerHTML = "This is the default payment method";
|
||||
options.appendChild(defaultMsg);
|
||||
} else {
|
||||
var defaultLink = document.createElement("a");
|
||||
defaultLink.id = elements.defaultPrefix + method.id;
|
||||
defaultLink.href = "#";
|
||||
defaultLink.innerHTML = "Make default";
|
||||
defaultLink.addEventListener("click", defaultMethod);
|
||||
var separator = document.createElement("span");
|
||||
separator.innerHTML = " | ";
|
||||
var deleteLink = document.createElement("a");
|
||||
deleteLink.id = elements.deletePrefix + method.id;
|
||||
deleteLink.href = "#";
|
||||
deleteLink.innerHTML = "Delete";
|
||||
deleteLink.addEventListener("click", deleteMethod);
|
||||
options.appendChild(defaultLink);
|
||||
options.appendChild(separator);
|
||||
options.appendChild(deleteLink);
|
||||
}
|
||||
row.appendChild(options);
|
||||
|
||||
return row;
|
||||
};
|
||||
|
||||
// Creates a row for the promotion table
|
||||
var createPromoRow = function(promo)
|
||||
{
|
||||
@ -165,6 +236,32 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri
|
||||
return row;
|
||||
};
|
||||
|
||||
// Handler for making a payment method default
|
||||
var defaultMethod = function(event)
|
||||
{
|
||||
var id = event.currentTarget.id.slice(elements.defaultPrefix.length);
|
||||
if (!confirm("Make this the default payment method?"))
|
||||
return;
|
||||
|
||||
apiPost("/account/payment-methods/" + id + "/make-default", {}, function(response)
|
||||
{
|
||||
location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
// Handler for deleting a payment method
|
||||
var deleteMethod = function(event)
|
||||
{
|
||||
var id = event.currentTarget.id.slice(elements.deletePrefix.length);
|
||||
if (!confirm("Delete this payment method?"))
|
||||
return;
|
||||
|
||||
apiDelete("/account/payment-methods/" + id, function(response)
|
||||
{
|
||||
location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
// Callback for account details API call
|
||||
var displayAccount = function(response)
|
||||
{
|
||||
@ -199,30 +296,6 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri
|
||||
// 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;
|
||||
@ -275,15 +348,23 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri
|
||||
// Callback for linodes API call
|
||||
var displayLinodes = function(response)
|
||||
{
|
||||
data.linodes = data.linodes.concat(response.data);
|
||||
data.numLinodes = response.results;
|
||||
ui.managedButton.disabled = false;
|
||||
};
|
||||
|
||||
// Callback for payment methods API call
|
||||
var displayMethods = function(response)
|
||||
{
|
||||
data.paymentMethods = data.paymentMethods.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);
|
||||
apiGet("/account/payment-methods?page=" + (response.page + 1), displayMethods, null);
|
||||
return;
|
||||
}
|
||||
|
||||
ui.managedButton.disabled = false;
|
||||
for (var i = 0; i < data.paymentMethods.length; i++)
|
||||
ui.paymentMethods.appendChild(createMethodRow(data.paymentMethods[i]));
|
||||
};
|
||||
|
||||
// Callback for payments API call
|
||||
@ -333,7 +414,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri
|
||||
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?"))
|
||||
if (!confirm("Linode Managed costs an additional $100/mo per Linode. This will increase your projected monthly bill by $" + (data.numLinodes * 100) + ". Are you sure?"))
|
||||
return;
|
||||
|
||||
apiPost("/account/settings/managed-enable", {}, function(response)
|
||||
@ -356,9 +437,6 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri
|
||||
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);
|
||||
@ -366,6 +444,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri
|
||||
ui.managed = document.getElementById(elements.managed);
|
||||
ui.managedButton = document.getElementById(elements.managedButton);
|
||||
ui.pay = document.getElementById(elements.pay);
|
||||
ui.paymentMethods = document.getElementById(elements.paymentMethods);
|
||||
ui.promotions = document.getElementsByClassName(elements.promotions);
|
||||
ui.promotionsTable = document.getElementById(elements.promotionsTable);
|
||||
ui.uninvoiced = document.getElementById(elements.uninvoiced);
|
||||
@ -379,6 +458,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeStri
|
||||
apiGet("/account/settings", displaySettings, null);
|
||||
apiGet("/account/invoices", displayInvoices, null);
|
||||
apiGet("/account/payments", displayPayments, null);
|
||||
apiGet("/account/payment-methods", displayMethods, null);
|
||||
apiGet("/linode/instances", displayLinodes, null);
|
||||
};
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#backups-enable-all {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
|
@ -117,7 +117,7 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/
|
||||
|
||||
state.havePlans = true;
|
||||
|
||||
if (state.haveLinode)
|
||||
if (state.haveLinodes)
|
||||
displayCost();
|
||||
};
|
||||
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <a href="/account/settings">Settings</a> » <span class="top-links-title">Backup Enrollment</span></div>
|
||||
<div id="backups-enable-all">
|
||||
<h2>Backup Enrollment</h2>
|
||||
<p>This will enable the Linode Backup Service for <strong><span class="linode-count"></span></strong> Linodes, for a total additional cost of <strong>$<span id="cost"></span></strong>/month.</p>
|
||||
|
@ -26,7 +26,7 @@
|
||||
}
|
||||
|
||||
#billing-history {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
#current {
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <span class="top-links-title">Billing History</span></div>
|
||||
<div id="billing-history">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#cancel {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
#cancel-note {
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <span class="top-links-title">Cancel Account</span></div>
|
||||
<div id="cancel">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#contact {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
tbody tr td:first-of-type {
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <span class="top-links-title">Contact Info</span></div>
|
||||
<div id="contact">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#creditcard {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
tbody:not(.lmc-tbody-head) tr td:first-of-type {
|
||||
|
@ -15,15 +15,14 @@
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/global.js";
|
||||
import { settings, elements, apiPost, parseParams, setupHeader } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
// Element names specific to this page
|
||||
elements.ccCurrent = "cc-current";
|
||||
elements.ccNew = "cc-new";
|
||||
elements.cvv = "cvv";
|
||||
elements.expiryCurrent = "expiry-current";
|
||||
elements.default = "default";
|
||||
elements.expiryMonth = "expiry-month";
|
||||
elements.expiryYear = "expiry-year";
|
||||
elements.updateButton = "update-button";
|
||||
@ -33,24 +32,13 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/
|
||||
|
||||
// Static references to UI elements
|
||||
var ui = {};
|
||||
ui.ccCurrent = {};
|
||||
ui.ccNew = {};
|
||||
ui.cvv = {};
|
||||
ui.expiryCurrent = {};
|
||||
ui.default = {};
|
||||
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)
|
||||
{
|
||||
@ -58,13 +46,17 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/
|
||||
return;
|
||||
|
||||
var req = {
|
||||
"card_number": ui.ccNew.value,
|
||||
"cvv": ui.cvv.value,
|
||||
"expiry_month": parseInt(ui.expiryMonth.value),
|
||||
"expiry_year": parseInt(ui.expiryYear.value)
|
||||
"data": {
|
||||
"card_number": ui.ccNew.value.replaceAll("-", "").replaceAll(" ", ""),
|
||||
"cvv": ui.cvv.value,
|
||||
"expiry_month": parseInt(ui.expiryMonth.value),
|
||||
"expiry_year": parseInt(ui.expiryYear.value)
|
||||
},
|
||||
"is_default": ui.default.checked,
|
||||
"type": "credit_card"
|
||||
};
|
||||
|
||||
apiPost("/account/credit-card", req, function(response)
|
||||
apiPost("/account/payment-methods", req, function(response)
|
||||
{
|
||||
location.href = "/account";
|
||||
});
|
||||
@ -79,10 +71,9 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/
|
||||
setupHeader();
|
||||
|
||||
// Get element references
|
||||
ui.ccCurrent = document.getElementById(elements.ccCurrent);
|
||||
ui.ccNew = document.getElementById(elements.ccNew);
|
||||
ui.cvv = document.getElementById(elements.cvv);
|
||||
ui.expiryCurrent = document.getElementById(elements.expiryCurrent);
|
||||
ui.default = document.getElementById(elements.default);
|
||||
ui.expiryMonth = document.getElementById(elements.expiryMonth);
|
||||
ui.expiryYear = document.getElementById(elements.expiryYear);
|
||||
ui.updateButton = document.getElementById(elements.updateButton);
|
||||
@ -99,9 +90,6 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/
|
||||
|
||||
// Register event handlers
|
||||
ui.updateButton.addEventListener("click", handleUpdate);
|
||||
|
||||
// Get data from API
|
||||
apiGet("/account", displayCC, null);
|
||||
};
|
||||
|
||||
// Attach onload handler
|
||||
|
@ -18,7 +18,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>LMC - Account // Update Credit Card</title>
|
||||
<title>LMC - Account // Add Credit Card</title>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" type="text/css" href="creditcard.css" />
|
||||
<script src="creditcard.js" type="module"></script>
|
||||
@ -27,34 +27,17 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <span class="top-links-title">Add Credit Card</span></div>
|
||||
<div id="creditcard">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td colspan="3">Update Credit Card</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">Current Card</td>
|
||||
<td colspan="3">Add Credit Card</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Current Card</td>
|
||||
<td>xxxxxxxxxxxx<span id="cc-current"></span> Exp: <span id="expiry-current"></span></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody class="lmc-tbody-head">
|
||||
<tr class="noshow">
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">Update Card</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr class="lmc-tr3">
|
||||
<td>New Card Number</td>
|
||||
<td>Card Number</td>
|
||||
<td><input id="cc-new" type="text" /></td>
|
||||
<td class="info">Linode accepts Visa, MasterCard, American Express, and Discover</td>
|
||||
</tr>
|
||||
@ -84,9 +67,14 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Make Default</td>
|
||||
<td><input id="default" type="checkbox" /></td>
|
||||
<td class="info">This card will become the new default payment method</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td></td>
|
||||
<td colspan="2"><button id="update-button" type="button">Update Credit Card</button>
|
||||
<td colspan="2"><button id="update-button" type="button">Add Credit Card</button>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -52,18 +52,10 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">Credit Card</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Credit Card</td>
|
||||
<td colspan="2">
|
||||
xxxxxxxxxxxx<span id="cc-number"></span> Exp: <span id="cc-expire"></span><br />
|
||||
<span id="cc-duration"></span>
|
||||
</td>
|
||||
<td colspan="3">Payment Methods</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody id="payment-methods"></tbody>
|
||||
<tbody class="lmc-tbody-head">
|
||||
<tr class="noshow">
|
||||
<td colspan="3"></td>
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <a href="/account/billing_history">Billing History</a> » <span class="top-links-title">Invoice</span></div>
|
||||
<div id="invoice">
|
||||
<table id="invoice-table" class="lmc-table">
|
||||
<thead>
|
||||
@ -53,7 +54,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<td id="subtotal"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tax:</td>
|
||||
<td>Tax Subtotal:</td>
|
||||
<td id="tax"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#invoice {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
#invoice-table td:nth-of-type(4) {
|
||||
|
@ -91,6 +91,16 @@ import { settings, elements, apiGet, parseParams, setupHeader } from "/global.js
|
||||
var displayInvoice = function(response)
|
||||
{
|
||||
ui.subtotal.innerHTML = "$" + response.subtotal.toFixed(2);
|
||||
for (var i = 0; i < response.tax_summary.length; i++) {
|
||||
var taxRow = document.createElement("tr");
|
||||
var taxText = document.createElement("td");
|
||||
taxText.innerHTML = response.tax_summary[i].name + ":";
|
||||
var tax = document.createElement("td");
|
||||
tax.innerHTML = "$" + response.tax_summary[i].tax.toFixed(2);
|
||||
taxRow.appendChild(taxText);
|
||||
taxRow.appendChild(tax);
|
||||
ui.tax.parentNode.parentNode.insertBefore(taxRow, ui.tax.parentNode);
|
||||
}
|
||||
ui.tax.innerHTML = "$" + response.tax.toFixed(2);
|
||||
ui.total.innerHTML = "$" + response.total.toFixed(2);
|
||||
};
|
||||
|
@ -27,66 +27,30 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <span class="top-links-title">Make a Payment</span></div>
|
||||
<div id="make-a-payment">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td colspan="3">Make a Payment</td>
|
||||
<td colspan="2">Make a Payment</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Current Balance</td>
|
||||
<td id="balance" class="balance-positive"></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody class="lmc-tbody-head">
|
||||
<tr class="noshow">
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">Make a Payment - via Credit Card</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Current Card</td>
|
||||
<td>xxxxxxxxxxxx<span id="cc-number"></span> Exp: <span id="cc-exp"></span></td>
|
||||
<td class="info"><a href="/account/creditcard">(update credit card)</a></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Amount to Charge</td>
|
||||
<td><input id="cc-amount" type="number" min="0" max="50000" step="0.01" value="0.00" /> <span class="info">(USD)</span></td>
|
||||
<td></td>
|
||||
<td><input id="cc-amount" type="number" min="5" max="2000" step="0.01" value="5.00" /> <span class="info">(USD)</span></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>CVV</td>
|
||||
<td><input id="cvv" type="text" size="8" /> <span class="info">(optional)</span></td>
|
||||
<td></td>
|
||||
<td>Payment Method</td>
|
||||
<td><select id="method"></select></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td></td>
|
||||
<td colspan="2"><button disabled id="cc-charge" type="button">Charge Credit Card</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody class="lmc-tbody-head">
|
||||
<tr class="noshow">
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">Make a Payment - via PayPal</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Amount to Pay</td>
|
||||
<td><input id="paypal-amount" type="number" min="0" max="10000" step="0.01" value="0.00" /> <span class="info">(USD)</span></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td></td>
|
||||
<td colspan="2"><button disabled id="paypal-charge" type="button">Continue...</button></td>
|
||||
<td><button disabled id="cc-charge" type="button">Make Payment</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -34,7 +34,7 @@ input[type="number"] {
|
||||
}
|
||||
|
||||
#make-a-payment {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
tbody:not(.lmc-tbody-head) tr td:first-of-type {
|
||||
|
@ -24,25 +24,18 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/
|
||||
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";
|
||||
elements.method = "method";
|
||||
|
||||
// Data received from API calls
|
||||
var data = {};
|
||||
data.methods = [];
|
||||
|
||||
// Static references to UI elements
|
||||
var ui = {};
|
||||
ui.balance = {};
|
||||
ui.ccAmount = {};
|
||||
ui.ccCharge = {};
|
||||
ui.ccExp = {};
|
||||
ui.ccNumber = {};
|
||||
ui.cvv = {};
|
||||
ui.paypalAmount = {};
|
||||
ui.paypalCharge = {};
|
||||
ui.method = {};
|
||||
|
||||
// Callback for account details API call
|
||||
var displayAccount = function(response)
|
||||
@ -53,17 +46,55 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/
|
||||
} else if (response.balance > 0) {
|
||||
ui.balance.innerHTML += response.balance.toFixed(2) + " outstanding";
|
||||
ui.balance.className = elements.balanceNegative;
|
||||
if (response.balance < 5) {
|
||||
ui.ccAmount.min = response.balance.toFixed(2);
|
||||
ui.ccAmount.value = response.balance.toFixed(2);
|
||||
} else if (response.balance > 2000) {
|
||||
ui.ccAmount.max = Math.min(50000, response.balance.toFixed(2));
|
||||
}
|
||||
} 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;
|
||||
};
|
||||
|
||||
// Callback for payment methods API call
|
||||
var displayMethods = function(response)
|
||||
{
|
||||
data.methods = data.methods.concat(response.data);
|
||||
|
||||
// Request the next page if there are more
|
||||
if (response.page != response.pages) {
|
||||
apiGet("/account/payment-methods?page=" + (response.page + 1), displayMethods, null);
|
||||
return;
|
||||
}
|
||||
|
||||
var typeNames = {
|
||||
"credit_card": "Credit Card",
|
||||
"google_pay": "Google Pay",
|
||||
"paypal": "PayPal"
|
||||
};
|
||||
|
||||
for (var i = 0; i < data.methods.length; i++) {
|
||||
var option = document.createElement("option");
|
||||
option.value = data.methods[i].id;
|
||||
if (typeNames[data.methods[i].type])
|
||||
option.innerHTML = typeNames[data.methods[i].type];
|
||||
else
|
||||
option.innerHTML = data.methods[i].type;
|
||||
if (data.methods[i].type == "credit_card" || data.methods[i].type == "google_pay")
|
||||
option.innerHTML += ": " + data.methods[i].data.card_type + " ****" + data.methods[i].data.last_four;
|
||||
else if (data.methods[i].type == "paypal")
|
||||
option.innerHTML += ": " + data.methods[i].data.email;
|
||||
ui.method.appendChild(option);
|
||||
if (data.methods[i].is_default) {
|
||||
option.innerHTML += " (default)";
|
||||
ui.method.value = data.methods[i].id;
|
||||
}
|
||||
}
|
||||
|
||||
ui.ccCharge.disabled = false;
|
||||
//ui.paypalCharge.disabled = false;
|
||||
};
|
||||
|
||||
// Click handler for CC charge button
|
||||
@ -76,10 +107,9 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/
|
||||
return;
|
||||
|
||||
var req = {
|
||||
"payment_method_id": parseInt(ui.method.value),
|
||||
"usd": ui.ccAmount.value
|
||||
};
|
||||
if (ui.cvv.value.length)
|
||||
req.cvv = ui.cvv.value;
|
||||
|
||||
apiPost("/account/payments", req, function(response)
|
||||
{
|
||||
@ -87,13 +117,6 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/
|
||||
});
|
||||
};
|
||||
|
||||
// Click handler for PayPal button
|
||||
var handlePayPal = function(event)
|
||||
{
|
||||
if (event.currentTarget.disabled)
|
||||
return;
|
||||
};
|
||||
|
||||
// Initial setup
|
||||
var setup = function()
|
||||
{
|
||||
@ -106,18 +129,14 @@ import { settings, elements, apiGet, apiPost, parseParams, setupHeader } from "/
|
||||
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);
|
||||
ui.method = document.getElementById(elements.method);
|
||||
|
||||
// Register event handlers
|
||||
ui.ccCharge.addEventListener("click", handleCharge);
|
||||
ui.paypalCharge.addEventListener("click", handlePayPal);
|
||||
|
||||
// Get data from API
|
||||
apiGet("/account", displayAccount, null);
|
||||
apiGet("/account/payment-methods", displayMethods, null);
|
||||
};
|
||||
|
||||
// Attach onload handler
|
||||
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#app {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
tbody:not(.lmc-tbody-head) tr td:first-of-type {
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <a href="/account/oauth_apps">OAuth Apps</a> » <span class="top-links-title">Add/Edit OAuth App</span></div>
|
||||
<div id="app">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <span class="top-links-title">OAuth Apps</span></div>
|
||||
<div id="oauth_apps">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -24,7 +24,7 @@
|
||||
}
|
||||
|
||||
#oauth_apps {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
td:nth-of-type(6) {
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <a href="/account/billing_history">Billing History</a> » <span class="top-links-title">Payment</span></div>
|
||||
<div id="paymentreceipt">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
|
||||
#paymentreceipt {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
#paymentreceipt p {
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <a href="/account/billing_history">Billing History</a> » <span class="top-links-title">Yearly Payments</span></div>
|
||||
<div id="paymentreceiptyear">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -26,7 +26,7 @@
|
||||
}
|
||||
|
||||
#paymentreceiptyear {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
#paymentreceiptyear p {
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <span class="top-links-title">Settings</span></div>
|
||||
<div id="settings">
|
||||
<p id="saved">Settings saved!</p>
|
||||
<table class="lmc-table">
|
||||
|
@ -26,7 +26,7 @@
|
||||
}
|
||||
|
||||
#settings {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
tbody:not(.lmc-tbody-head) tr td:first-of-type {
|
||||
|
18
global.css
18
global.css
@ -77,6 +77,24 @@ header {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.lmc-graph canvas {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lmc-graph h4 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.lmc-graph table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lmc-graph-color {
|
||||
height: 15px;
|
||||
display: inline-block;
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
.lmc-table {
|
||||
background-color: #CECECE;
|
||||
width: 100%;
|
||||
|
128
global.js
128
global.js
@ -35,24 +35,38 @@ var elements = {
|
||||
"username": "username"
|
||||
};
|
||||
|
||||
// Regions (Linode doesn't provide "friendly" names via the API)
|
||||
// Region names for legacy DCs no longer in the API
|
||||
var regionNames = {
|
||||
"us-central": "Dallas, TX, USA",
|
||||
"us-west": "Fremont, CA, USA",
|
||||
"us-southeast": "Atlanta, GA, USA",
|
||||
"us-east": "Newark, NJ, USA",
|
||||
"us-east-1b": "Newark 2, NJ, USA",
|
||||
"eu-west": "London, England, UK",
|
||||
"ap-south": "Singapore, SG",
|
||||
"eu-central": "Frankfurt, DE",
|
||||
"ap-northeast": "Tokyo, JP",
|
||||
"ap-northeast-1a": "Tokyo 2, JP",
|
||||
"ca-central": "Toronto, ON, CA",
|
||||
"ap-west": "Mumbai, IN",
|
||||
"ap-southeast": "Sydney, NSW, AU",
|
||||
"philadelphia": "Philadelphia, PA, USA",
|
||||
"absecon": "Absecon, NJ, USA",
|
||||
"us-iad": "Washington DC, USA"
|
||||
"us-east-1b": "Newark 2, NJ",
|
||||
"philadelphia": "Philadelphia, PA",
|
||||
"absecon": "Absecon, NJ"
|
||||
};
|
||||
|
||||
// Group contries into regions for easier selection
|
||||
var countryContinents = {
|
||||
"us": "na",
|
||||
"gb": "eu",
|
||||
"jp": "ap",
|
||||
"sg": "ap",
|
||||
"de": "eu",
|
||||
"in": "ap",
|
||||
"ca": "na",
|
||||
"au": "ap",
|
||||
"fr": "eu",
|
||||
"br": "sa",
|
||||
"nl": "eu",
|
||||
"se": "eu",
|
||||
"es": "eu",
|
||||
"it": "eu",
|
||||
"id": "ap",
|
||||
"nz": "ap",
|
||||
"pl": "eu",
|
||||
"za": "af",
|
||||
"my": "ap",
|
||||
"hk": "ap",
|
||||
"co": "sa",
|
||||
"mx": "na",
|
||||
"cl": "sa"
|
||||
};
|
||||
|
||||
// Human-readable event titles
|
||||
@ -183,7 +197,8 @@ var oauthScopes = {
|
||||
"nodebalancers": "NodeBalancers",
|
||||
"object_storage": "Object Storage",
|
||||
"stackscripts": "StackScripts",
|
||||
"volumes": "Volumes"
|
||||
"volumes": "Volumes",
|
||||
"vpc": "VPCs"
|
||||
};
|
||||
|
||||
// Make an HTTP DELETE request to the Linode API
|
||||
@ -434,6 +449,24 @@ function apiPut(endpoint, data, callback)
|
||||
xmlhttp.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
// Convert an unqualified count into a string with an SI prefix (i.e. bytes to MB/GB/etc)
|
||||
function countSI(count)
|
||||
{
|
||||
var prefix = "KMGTPEZY";
|
||||
var unit = "";
|
||||
|
||||
for (var i = 0; i < prefix.length; i++) {
|
||||
if (count >= 1024) {
|
||||
count /= 1024;
|
||||
unit = prefix.charAt(i);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return count.toFixed(2) + " " + unit;
|
||||
}
|
||||
|
||||
// Callback for user info API call
|
||||
function displayUser(response)
|
||||
{
|
||||
@ -452,6 +485,63 @@ function displayUser(response)
|
||||
profilePic.style = "display: initial;";
|
||||
}
|
||||
|
||||
// Draw timeseries data with the given canvas in the given color and fill
|
||||
// series is an array of objects, with each object containing the color/fill settings and an array of data points
|
||||
function drawSeries(series, canvas)
|
||||
{
|
||||
// Compute scale and totals
|
||||
var xMin = series[0].points[0][0];
|
||||
var xMax = series[0].points[series[0].points.length - 1][0];
|
||||
var yMax = 0;
|
||||
for (var i = 0; i < series.length; i++) {
|
||||
xMin = Math.min(xMin, series[i].points[0][0]);
|
||||
xMax = Math.max(xMax, series[i].points[series[i].points.length - 1][0]);
|
||||
series[i].max = 0, series[i].avg = 0;
|
||||
for (var j = 0; j < series[i].points.length; j++) {
|
||||
series[i].max = Math.max(series[i].max, series[i].points[j][1]);
|
||||
series[i].avg += series[i].points[j][1];
|
||||
}
|
||||
series[i].avg /= series[i].points.length;
|
||||
yMax = Math.max(yMax, series[i].max);
|
||||
}
|
||||
xMax -= xMin;
|
||||
|
||||
// Setup drawing context
|
||||
var ctx = canvas.getContext("2d");
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
// Clear the canvas
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
for (var i = 0; i < series.length; i++) {
|
||||
ctx.fillStyle = series[i].color;
|
||||
ctx.strokeStyle = series[i].color;
|
||||
|
||||
// Draw data
|
||||
ctx.beginPath();
|
||||
ctx.moveTo((series[i].points[0][0] - xMin) / xMax * canvas.width, canvas.height - (series[i].points[0][1] / yMax * canvas.height));
|
||||
for (var j = 1; j < series[i].points.length; j++)
|
||||
ctx.lineTo((series[i].points[j][0] - xMin) / xMax * canvas.width, canvas.height - (series[i].points[j][1] / yMax * canvas.height));
|
||||
if (series[i].fill) {
|
||||
ctx.lineTo((series[i].points[series[i].points.length-1][0] - xMin) / xMax * canvas.width, canvas.height);
|
||||
ctx.lineTo((series[i].points[0][0] - xMin) / xMax * canvas.width, canvas.height);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
} else {
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
// Draw axis lines
|
||||
ctx.strokeStyle = "black";
|
||||
ctx.lineWidth = 2.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.lineTo(0, canvas.height);
|
||||
ctx.lineTo(canvas.width, canvas.height);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Return an MD5 hash of the given string
|
||||
function md5(str, binary)
|
||||
{
|
||||
@ -793,4 +883,4 @@ function translateKernel(slug, element)
|
||||
apiGet("/linode/kernels/" + slug, callback, null);
|
||||
}
|
||||
|
||||
export { settings, elements, regionNames, apiDelete, apiGet, apiPost, apiPut, md5, migrateETA, oauthPost, oauthScopes, objPut, parseParams, setupHeader, eventTitles, timeString, translateKernel };
|
||||
export { settings, elements, regionNames, countryContinents, apiDelete, apiGet, apiPost, apiPut, countSI, drawSeries, md5, migrateETA, oauthPost, oauthScopes, objPut, parseParams, setupHeader, eventTitles, timeString, translateKernel };
|
||||
|
@ -15,7 +15,7 @@
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, regionNames, apiGet, apiPost, parseParams, setupHeader, timeString } from "/global.js";
|
||||
import { settings, elements, apiGet, apiPost, parseParams, setupHeader, timeString } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
|
@ -15,7 +15,7 @@
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, regionNames, apiGet, apiDelete, parseParams, setupHeader, timeString } from "/global.js";
|
||||
import { settings, elements, apiGet, apiDelete, parseParams, setupHeader, timeString } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
|
@ -15,7 +15,7 @@
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, regionNames, apiGet, apiPut, parseParams, setupHeader, timeString } from "/global.js";
|
||||
import { settings, elements, apiGet, apiPut, parseParams, setupHeader, timeString } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
|
@ -15,7 +15,7 @@
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, regionNames, apiGet, parseParams, setupHeader, timeString } from "/global.js";
|
||||
import { settings, elements, apiGet, parseParams, setupHeader, timeString } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
|
@ -46,7 +46,14 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Region</td>
|
||||
<td><select id="region"></select></td>
|
||||
<td><select id="region">
|
||||
<optgroup id="na" label="North America"></optgroup>
|
||||
<optgroup id="eu" label="Europe"></optgroup>
|
||||
<optgroup id="ap" label="Asia/Pacific"></optgroup>
|
||||
<optgroup id="sa" label="South America"></optgroup>
|
||||
<optgroup id="af" label="Africa"></optgroup>
|
||||
<optgroup id="dc-other" label="Other"></optgroup>
|
||||
</select></td>
|
||||
<td class="info">
|
||||
For fastest initial upload, select the region that is geographically closest to you.<br />
|
||||
Once uploaded you will be able to deploy the image to other regions.
|
||||
|
@ -17,6 +17,10 @@
|
||||
|
||||
@import url('/global.css');
|
||||
|
||||
optgroup {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.step-2 {
|
||||
display: none;
|
||||
}
|
||||
|
@ -15,11 +15,12 @@
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, regionNames, apiGet, apiPost, objPut, parseParams, setupHeader } from "/global.js";
|
||||
import { settings, elements, regionNames, countryContinents, apiGet, apiPost, objPut, parseParams, setupHeader } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
// Element names specific to this page
|
||||
elements.dcOther = "dc-other";
|
||||
elements.description = "description";
|
||||
elements.imageFile = "image-file";
|
||||
elements.label = "label";
|
||||
@ -35,6 +36,7 @@ import { settings, elements, regionNames, apiGet, apiPost, objPut, parseParams,
|
||||
|
||||
// Static references to UI elements
|
||||
var ui = {};
|
||||
ui.dcOther = {};
|
||||
ui.description = {};
|
||||
ui.imageFile = {};
|
||||
ui.label = {};
|
||||
@ -49,11 +51,19 @@ import { settings, elements, regionNames, apiGet, apiPost, objPut, parseParams,
|
||||
for (var i = 0; i < response.data.length; i++) {
|
||||
var dc = document.createElement("option");
|
||||
dc.value = response.data[i].id;
|
||||
if (regionNames[response.data[i].id])
|
||||
if (response.data[i].label && response.data[i].label.length)
|
||||
dc.innerHTML = response.data[i].label;
|
||||
else if (regionNames[response.data[i].id])
|
||||
dc.innerHTML = regionNames[response.data[i].id];
|
||||
else
|
||||
dc.innerHTML = response.data[i].id;
|
||||
ui.region.appendChild(dc);
|
||||
var optgroup = null;
|
||||
if (countryContinents[response.data[i].country])
|
||||
optgroup = document.getElementById(countryContinents[response.data[i].country]);
|
||||
if (!optgroup)
|
||||
optgroup = ui.dcOther;
|
||||
optgroup.style.display = "initial";
|
||||
optgroup.appendChild(dc);
|
||||
}
|
||||
|
||||
ui.nextButton.disabled = false;
|
||||
@ -126,6 +136,7 @@ import { settings, elements, regionNames, apiGet, apiPost, objPut, parseParams,
|
||||
}
|
||||
|
||||
// Get element references
|
||||
ui.dcOther = document.getElementById(elements.dcOther);
|
||||
ui.description = document.getElementById(elements.description);
|
||||
ui.imageFile = document.getElementById(elements.imageFile);
|
||||
ui.label = document.getElementById(elements.label);
|
||||
|
@ -2,7 +2,7 @@
|
||||
<nav id="subnav" class="wrapper">
|
||||
<a class="subnav-link" href="/account">Account</a>
|
||||
<a class="subnav-link" href="/account/contact">Contact Info</a>
|
||||
<a class="subnav-link" href="/account/creditcard">Update Credit Card</a>
|
||||
<a class="subnav-link" href="/account/creditcard">Add Credit Card</a>
|
||||
<a class="subnav-link" href="/account/make_a_payment">Make A Payment</a>
|
||||
<a class="subnav-link" href="/account/billing_history">Billing History</a>
|
||||
<a class="subnav-link" href="/user">Users</a>
|
||||
|
@ -6,7 +6,6 @@
|
||||
<a class="subnav-link" href="/linodes/rescue?lid=0">Rescue</a>
|
||||
<a class="subnav-link" href="/linodes/resize?lid=0">Resize</a>
|
||||
<a class="subnav-link" href="/linodes/clone?lid=0">Clone</a>
|
||||
<a class="subnav-link" href="/linodes/graphs?lid=0">Graphs</a>
|
||||
<a class="subnav-link" href="/linodes/backups?lid=0">Backups</a>
|
||||
<a class="subnav-link" href="/linodes/settings?lid=0">Settings</a>
|
||||
</nav>
|
||||
|
@ -57,7 +57,7 @@ h3 {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
width: 135px;
|
||||
width: 145px;
|
||||
}
|
||||
|
||||
.instance-type h3 {
|
||||
@ -89,6 +89,10 @@ h3 {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
optgroup {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#right-links {
|
||||
float: right;
|
||||
}
|
||||
|
@ -15,13 +15,14 @@
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, regionNames, apiGet, apiPost, parseParams, setupHeader } from "/global.js";
|
||||
import { settings, elements, regionNames, countryContinents, apiGet, apiPost, parseParams, setupHeader } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
// Element names specific to this page
|
||||
elements.addButton = "add-button";
|
||||
elements.datacenters = "datacenters";
|
||||
elements.dcOther = "dc-other";
|
||||
elements.instanceType = "instance-type";
|
||||
elements.instanceTypeActive = "instance-type-active";
|
||||
|
||||
@ -33,6 +34,7 @@ import { settings, elements, regionNames, apiGet, apiPost, parseParams, setupHea
|
||||
// Static references to UI elements
|
||||
var ui = {};
|
||||
ui.datacenters = {};
|
||||
ui.dcOther = {};
|
||||
|
||||
var createLinodeTypeButton = function(type)
|
||||
{
|
||||
@ -67,16 +69,26 @@ import { settings, elements, regionNames, apiGet, apiPost, parseParams, setupHea
|
||||
for (var i = 0; i < response.data.length; i++) {
|
||||
var dc = document.createElement("option");
|
||||
dc.value = response.data[i].id;
|
||||
if (regionNames[response.data[i].id])
|
||||
if (response.data[i].label && response.data[i].label.length)
|
||||
dc.innerHTML = response.data[i].label;
|
||||
else if (regionNames[response.data[i].id])
|
||||
dc.innerHTML = regionNames[response.data[i].id];
|
||||
else
|
||||
dc.innerHTML = response.data[i].id;
|
||||
ui.datacenters.appendChild(dc);
|
||||
var optgroup = null;
|
||||
if (countryContinents[response.data[i].country])
|
||||
optgroup = document.getElementById(countryContinents[response.data[i].country]);
|
||||
if (!optgroup)
|
||||
optgroup = ui.dcOther;
|
||||
optgroup.style.display = "initial";
|
||||
optgroup.appendChild(dc);
|
||||
}
|
||||
|
||||
// Request the next page if there are more pages
|
||||
if (response.page != response.pages)
|
||||
if (response.page != response.pages) {
|
||||
apiGet("/regions?page=" + (response.page + 1), displayRegions, null);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
var displayTypes = function(response)
|
||||
@ -135,6 +147,7 @@ import { settings, elements, regionNames, apiGet, apiPost, parseParams, setupHea
|
||||
data.params = parseParams();
|
||||
|
||||
ui.datacenters = document.getElementById(elements.datacenters);
|
||||
ui.dcOther = document.getElementById(elements.dcOther);
|
||||
|
||||
// Register add button handler
|
||||
document.getElementById(elements.addButton).addEventListener("click", handleAddLinode);
|
||||
|
@ -49,7 +49,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
</div>
|
||||
<div class="instance-category">
|
||||
<h3>High Memory Instances</h3>
|
||||
<p>High Memory instances favor RAM over other resources, and can be good for memory hungry use cases like caching and in-memory databases.</p>
|
||||
<p>High Memory instances favor RAM over other resources, and can be good for memory hungry use cases like caching and in-memory databases. All High Memory plans use dedicated CPU cores.</p>
|
||||
<div id="highmem"></div>
|
||||
</div>
|
||||
<div class="instance-category">
|
||||
@ -57,10 +57,22 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<p>Linodes with dedicated GPUs accelerate highly specialized applications such as machine learning, AI, and video transcoding.</p>
|
||||
<div id="gpu"></div>
|
||||
</div>
|
||||
<div class="instance-category">
|
||||
<h3>Premium Instances</h3>
|
||||
<p>Premium CPU instances guarantee a minimum processor generation of AMD EPYC™ Milan or newer to ensure consistent high performance for more demanding workloads.</p>
|
||||
<div id="premium"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="location">
|
||||
<h2>Location</h2>
|
||||
<select id="datacenters"></select>
|
||||
<select id="datacenters">
|
||||
<optgroup id="na" label="North America"></optgroup>
|
||||
<optgroup id="eu" label="Europe"></optgroup>
|
||||
<optgroup id="ap" label="Asia/Pacific"></optgroup>
|
||||
<optgroup id="sa" label="South America"></optgroup>
|
||||
<optgroup id="af" label="Africa"></optgroup>
|
||||
<optgroup id="dc-other" label="Other"></optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div id="submit">
|
||||
<button id="add-button" type="button">Add this Linode!</button>
|
||||
|
@ -50,6 +50,7 @@ import { settings, elements, apiGet, apiPost, parseParams, regionNames, setupHea
|
||||
data.backup = {};
|
||||
data.linode = {};
|
||||
data.linodes = [];
|
||||
data.region = {};
|
||||
data.types = [];
|
||||
|
||||
// Static references to UI elements
|
||||
@ -89,6 +90,7 @@ import { settings, elements, apiGet, apiPost, parseParams, regionNames, setupHea
|
||||
|
||||
var plan = document.createElement("td");
|
||||
if (linode.type) {
|
||||
plan.innerHTML = linode.type;
|
||||
for (var i = 0; i < data.types.length; i++) {
|
||||
if (data.types[i].id == linode.type) {
|
||||
plan.innerHTML = data.types[i].label;
|
||||
@ -103,7 +105,9 @@ import { settings, elements, apiGet, apiPost, parseParams, regionNames, setupHea
|
||||
row.appendChild(plan);
|
||||
|
||||
var location = document.createElement("td");
|
||||
if (regionNames[linode.region])
|
||||
if (data.region.label && data.region.label.length)
|
||||
location.innerHTML = data.region.label;
|
||||
else if (regionNames[linode.region])
|
||||
location.innerHTML = regionNames[linode.region];
|
||||
else
|
||||
location.innerHTML = linode.region;
|
||||
@ -136,6 +140,15 @@ import { settings, elements, apiGet, apiPost, parseParams, regionNames, setupHea
|
||||
|
||||
ui.backupType.innerHTML = data.backup.type;
|
||||
|
||||
if (regionNames[data.backup.region]) {
|
||||
ui.backupLocation.innerHTML = regionNames[data.backup.region];
|
||||
ui.destLocation.innerHTML = regionNames[data.backup.region];
|
||||
} else {
|
||||
ui.backupLocation.innerHTML = data.backup.region;
|
||||
ui.destLocation.innerHTML = data.backup.region;
|
||||
}
|
||||
apiGet("/regions/" + data.backup.region, displayRegion, null);
|
||||
|
||||
for (var i = 0; i < data.backup.configs.length; i++) {
|
||||
var li = document.createElement("li");
|
||||
li.innerHTML = data.backup.configs[i];
|
||||
@ -152,7 +165,7 @@ import { settings, elements, apiGet, apiPost, parseParams, regionNames, setupHea
|
||||
|
||||
ui.backupSize.innerHTML = data.backup.totalSize + " MB";
|
||||
|
||||
if (state.haveTypes && data.linode.id)
|
||||
if (state.haveTypes)
|
||||
insertTypes();
|
||||
};
|
||||
|
||||
@ -169,18 +182,6 @@ import { settings, elements, apiGet, apiPost, parseParams, regionNames, setupHea
|
||||
ui.linodeTagLink.innerHTML = "(" + data.linode.tags[0] + ")";
|
||||
ui.linodeTag.style.display = "inline";
|
||||
}
|
||||
|
||||
// Display location
|
||||
if (regionNames[data.linode.region]) {
|
||||
ui.backupLocation.innerHTML = regionNames[data.linode.region];
|
||||
ui.destLocation.innerHTML = regionNames[data.linode.region];
|
||||
} else {
|
||||
ui.backupLocation.innerHTML = data.linode.region;
|
||||
ui.destLocation.innerHTML = data.linode.region;
|
||||
}
|
||||
|
||||
if (state.haveTypes && data.backup.id)
|
||||
insertTypes();
|
||||
};
|
||||
|
||||
// Callback for linode disks API call
|
||||
@ -238,7 +239,7 @@ import { settings, elements, apiGet, apiPost, parseParams, regionNames, setupHea
|
||||
// Request the next page if there are more pages
|
||||
if (response.page != response.pages) {
|
||||
var filter = {
|
||||
"region": data.linode.region
|
||||
"region": data.backup.region
|
||||
};
|
||||
apiGet("/linode/instances?page=" + (response.page + 1), displayLinodes, filter);
|
||||
return;
|
||||
@ -254,6 +255,17 @@ import { settings, elements, apiGet, apiPost, parseParams, regionNames, setupHea
|
||||
}
|
||||
};
|
||||
|
||||
// Callback for region API call
|
||||
var displayRegion = function(response)
|
||||
{
|
||||
data.region = response;
|
||||
|
||||
if (data.region.label && data.region.label.length) {
|
||||
ui.backupLocation.innerHTML = response.label;
|
||||
ui.destLocation.innerHTML = response.label;
|
||||
}
|
||||
};
|
||||
|
||||
// Callback for linode types API call
|
||||
var displayTypes = function(response)
|
||||
{
|
||||
@ -267,7 +279,7 @@ import { settings, elements, apiGet, apiPost, parseParams, regionNames, setupHea
|
||||
}
|
||||
|
||||
state.haveTypes = true;
|
||||
if (data.backup.id && data.linode.id)
|
||||
if (data.backup.id)
|
||||
insertTypes();
|
||||
};
|
||||
|
||||
@ -295,7 +307,7 @@ import { settings, elements, apiGet, apiPost, parseParams, regionNames, setupHea
|
||||
var req = {
|
||||
"label": ui.destLabel.value,
|
||||
"type": ui.destPlan.value,
|
||||
"region": data.linode.region,
|
||||
"region": data.backup.region,
|
||||
"backup_id": data.backup.id
|
||||
};
|
||||
var callback = function(response)
|
||||
@ -348,11 +360,11 @@ import { settings, elements, apiGet, apiPost, parseParams, regionNames, setupHea
|
||||
ui.destPlan.appendChild(option);
|
||||
}
|
||||
|
||||
updatePrice(null);
|
||||
updateSpace(null);
|
||||
ui.newLinode.disabled = false;
|
||||
|
||||
var filter = {
|
||||
"region": data.linode.region
|
||||
"region": data.backup.region
|
||||
};
|
||||
apiGet("/linode/instances", displayLinodes, filter);
|
||||
};
|
||||
@ -410,7 +422,7 @@ import { settings, elements, apiGet, apiPost, parseParams, regionNames, setupHea
|
||||
ui.newLinode = document.getElementById(elements.newLinode);
|
||||
|
||||
// Register event handlers
|
||||
ui.destPlan.addEventListener("input", updatePrice);
|
||||
ui.destPlan.addEventListener("input", updateSpace);
|
||||
ui.newLinode.addEventListener("click", handleCreate);
|
||||
|
||||
// Get data from API
|
||||
@ -431,7 +443,7 @@ import { settings, elements, apiGet, apiPost, parseParams, regionNames, setupHea
|
||||
};
|
||||
|
||||
// Update the price display
|
||||
var updatePrice = function(event)
|
||||
var updateSpace = function(event)
|
||||
{
|
||||
// Find the selected type
|
||||
var type = null;
|
||||
|
@ -15,7 +15,7 @@
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, apiGet, apiPost, migrateETA, parseParams, regionNames, setupHeader, translateKernel } from "/global.js";
|
||||
import { settings, elements, apiGet, apiPost, countryContinents, migrateETA, parseParams, regionNames, setupHeader, translateKernel } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
@ -25,6 +25,7 @@ import { settings, elements, apiGet, apiPost, migrateETA, parseParams, regionNam
|
||||
elements.configDiskRow = "config-disk-row";
|
||||
elements.configsNone = "configs-none";
|
||||
elements.configTable = "config-table-body";
|
||||
elements.dcOther = "dc-other";
|
||||
elements.destBackups = "dest-backups";
|
||||
elements.destBackupsPrice = "dest-backups-price";
|
||||
elements.destLabel = "dest-label";
|
||||
@ -54,6 +55,7 @@ import { settings, elements, apiGet, apiPost, migrateETA, parseParams, regionNam
|
||||
data.configs = [];
|
||||
data.disks = [];
|
||||
data.linode = {};
|
||||
data.regions = [];
|
||||
data.types = [];
|
||||
|
||||
// Static references to UI elements
|
||||
@ -62,6 +64,7 @@ import { settings, elements, apiGet, apiPost, migrateETA, parseParams, regionNam
|
||||
ui.configCloneTable = {};
|
||||
ui.configsNone = {};
|
||||
ui.configTable = {};
|
||||
ui.dcOther = {};
|
||||
ui.destBackups = {};
|
||||
ui.destBackupsPrice = {};
|
||||
ui.destLabel = {};
|
||||
@ -241,7 +244,17 @@ import { settings, elements, apiGet, apiPost, migrateETA, parseParams, regionNam
|
||||
}
|
||||
|
||||
// Display the source location
|
||||
if (regionNames[data.linode.region])
|
||||
var region = null;
|
||||
for (var i = 0; i < data.regions.length; i++) {
|
||||
if (data.regions[i].id == data.linode.region) {
|
||||
region = data.regions[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (region && region.label && region.label.length)
|
||||
ui.sourceLocation.innerHTML = region.label;
|
||||
else if (regionNames[data.linode.region])
|
||||
ui.sourceLocation.innerHTML = regionNames[data.linode.region];
|
||||
else
|
||||
ui.sourceLocation.innerHTML = data.linode.region;
|
||||
@ -290,25 +303,8 @@ import { settings, elements, apiGet, apiPost, migrateETA, parseParams, regionNam
|
||||
// Callback for regions API call
|
||||
var displayRegions = function(response)
|
||||
{
|
||||
for (var i = 0; i < response.data.length; i++) {
|
||||
// Add regions to selector
|
||||
var dc = document.createElement("option");
|
||||
dc.value = response.data[i].id;
|
||||
if (regionNames[response.data[i].id])
|
||||
dc.innerHTML = regionNames[response.data[i].id];
|
||||
else
|
||||
dc.innerHTML = response.data[i].id;
|
||||
ui.destLocation.appendChild(dc);
|
||||
|
||||
// Add optgroups to linode selector
|
||||
var optgroup = document.createElement("optgroup");
|
||||
optgroup.id = response.data[i].id;
|
||||
if (regionNames[response.data[i].id])
|
||||
optgroup.label = regionNames[response.data[i].id];
|
||||
else
|
||||
optgroup.label = response.data[i].label;
|
||||
ui.destLinode.appendChild(optgroup);
|
||||
}
|
||||
// Add regions to array
|
||||
data.regions = data.regions.concat(response.data);
|
||||
|
||||
// Request the next page if there are more pages
|
||||
if (response.page != response.pages) {
|
||||
@ -316,6 +312,37 @@ import { settings, elements, apiGet, apiPost, migrateETA, parseParams, regionNam
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < data.regions.length; i++) {
|
||||
// Add regions to selector
|
||||
var dc = document.createElement("option");
|
||||
dc.value = data.regions[i].id;
|
||||
if (data.regions[i].label && data.regions[i].label.length)
|
||||
dc.innerHTML = data.regions[i].label;
|
||||
else if (regionNames[data.regions[i].id])
|
||||
dc.innerHTML = regionNames[data.regions[i].id];
|
||||
else
|
||||
dc.innerHTML = data.regions[i].id;
|
||||
var group = null;
|
||||
if (countryContinents[data.regions[i].country])
|
||||
group = document.getElementById(countryContinents[data.regions[i].country]);
|
||||
if (!group)
|
||||
group = ui.dcOther;
|
||||
group.style.display = "initial";
|
||||
group.appendChild(dc);
|
||||
|
||||
// Add optgroups to linode selector
|
||||
var optgroup = document.createElement("optgroup");
|
||||
optgroup.id = data.regions[i].id;
|
||||
if (data.regions[i].label && data.regions[i].label.length)
|
||||
optgroup.label = data.regions[i].label;
|
||||
else if (regionNames[data.regions[i].id])
|
||||
optgroup.label = regionNames[data.regions[i].id];
|
||||
else
|
||||
optgroup.label = data.regions[i].label;
|
||||
ui.destLinode.appendChild(optgroup);
|
||||
}
|
||||
|
||||
apiGet("/linode/instances/" + data.params.lid, displayDetails, null);
|
||||
apiGet("/linode/instances", displayLinodes, null);
|
||||
};
|
||||
|
||||
@ -415,6 +442,7 @@ import { settings, elements, apiGet, apiPost, migrateETA, parseParams, regionNam
|
||||
ui.configCloneTable = document.getElementById(elements.configCloneTable);
|
||||
ui.configsNone = document.getElementById(elements.configsNone);
|
||||
ui.configTable = document.getElementById(elements.configTable);
|
||||
ui.dcOther = document.getElementById(elements.dcOther);
|
||||
ui.destBackups = document.getElementById(elements.destBackups);
|
||||
ui.destBackupsPrice = document.getElementById(elements.destBackupsPrice);
|
||||
ui.destLabel = document.getElementById(elements.destLabel);
|
||||
@ -442,7 +470,6 @@ import { settings, elements, apiGet, apiPost, migrateETA, parseParams, regionNam
|
||||
ui.destPlan.addEventListener("input", updatePrices);
|
||||
|
||||
// Get data from API
|
||||
apiGet("/linode/instances/" + data.params.lid, displayDetails, null);
|
||||
apiGet("/linode/instances/" + data.params.lid + "/configs", displayConfigs, null);
|
||||
apiGet("/linode/instances/" + data.params.lid + "/disks", displayDisks, null);
|
||||
apiGet("/linode/types", displayTypes, null);
|
||||
|
@ -142,7 +142,14 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
</tr>
|
||||
<tr class="lmc-tr3 new-linode">
|
||||
<td>Location</td>
|
||||
<td><select id="dest-location"></select></td>
|
||||
<td><select id="dest-location">
|
||||
<optgroup id="na" label="North America"></optgroup>
|
||||
<optgroup id="eu" label="Europe"></optgroup>
|
||||
<optgroup id="ap" label="Asia/Pacific"></optgroup>
|
||||
<optgroup id="sa" label="South America"></optgroup>
|
||||
<optgroup id="af" label="Africa"></optgroup>
|
||||
<optgroup id="dc-other" label="Other"></optgroup>
|
||||
</select></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3 new-linode">
|
||||
<td>Plan</td>
|
||||
|
@ -25,10 +25,18 @@
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
canvas {
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
#config-table tr:last-of-type {
|
||||
border-bottom: 1px solid #E8E8E8;
|
||||
}
|
||||
|
||||
#cpu-color {
|
||||
background-color: #03C;
|
||||
}
|
||||
|
||||
.disk-icon {
|
||||
height: 24px;
|
||||
width: 26px;
|
||||
@ -42,12 +50,51 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
#graph-range {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
border-bottom: 1px solid #E8E8E8;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: lighter;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
#io-rate-color {
|
||||
background-color: #FFD04B;
|
||||
}
|
||||
|
||||
#ipv4-in-color {
|
||||
background-color: #03C;
|
||||
}
|
||||
|
||||
#ipv4-out-color {
|
||||
background-color: #32CD32;
|
||||
}
|
||||
|
||||
#ipv4-privin-color {
|
||||
background-color: #C09;
|
||||
}
|
||||
|
||||
#ipv4-privout-color {
|
||||
background-color: #FF9;
|
||||
}
|
||||
|
||||
#ipv6-in-color {
|
||||
background-color: #03C;
|
||||
}
|
||||
|
||||
#ipv6-out-color {
|
||||
background-color: #32CD32;
|
||||
}
|
||||
|
||||
#ipv6-privin-color {
|
||||
background-color: #C09;
|
||||
}
|
||||
|
||||
#ipv6-privout-color {
|
||||
background-color: #FF9;
|
||||
}
|
||||
|
||||
.job-failed {
|
||||
@ -202,6 +249,10 @@ h3 {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#swap-rate-color {
|
||||
background-color: #FA373E;
|
||||
}
|
||||
|
||||
#upgrade {
|
||||
background-color: #F0F0F0;
|
||||
display: none;
|
||||
|
@ -15,7 +15,7 @@
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParams, setupHeader, timeString, translateKernel } from "/global.js";
|
||||
import { settings, elements, apiDelete, apiGet, apiPost, countSI, drawSeries, eventTitles, parseParams, setupHeader, timeString, translateKernel } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
@ -26,6 +26,10 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
elements.configRadioName = "config-radio";
|
||||
elements.configRemovePrefix = "config-remove-";
|
||||
elements.configTable = "config-table";
|
||||
elements.cpuAvg = "cpu-avg";
|
||||
elements.cpuGraph = "cpu-graph";
|
||||
elements.cpuLast = "cpu-last";
|
||||
elements.cpuMax = "cpu-max";
|
||||
elements.diskIcon = "disk-icon";
|
||||
elements.diskIconImg = "/img/disk.gif";
|
||||
elements.diskRemovePrefix = "disk-remove-";
|
||||
@ -34,7 +38,44 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
elements.eventRowPrefix = "event-row-";
|
||||
elements.eventTable = "event-table";
|
||||
elements.extraEvent = "extra-event";
|
||||
elements.graphRange = "graph-range";
|
||||
elements.info = "info";
|
||||
elements.ioGraph = "io-graph";
|
||||
elements.ioRateAvg = "io-rate-avg";
|
||||
elements.ioRateLast = "io-rate-last";
|
||||
elements.ioRateMax = "io-rate-max";
|
||||
elements.ipv4Graph = "ipv4-graph";
|
||||
elements.ipv4InAvg = "ipv4-in-avg";
|
||||
elements.ipv4InLast = "ipv4-in-last";
|
||||
elements.ipv4InMax = "ipv4-in-max";
|
||||
elements.ipv4OutAvg = "ipv4-out-avg";
|
||||
elements.ipv4OutLast = "ipv4-out-last";
|
||||
elements.ipv4OutMax = "ipv4-out-max";
|
||||
elements.ipv4PrivInAvg = "ipv4-privin-avg";
|
||||
elements.ipv4PrivInLast = "ipv4-privin-last";
|
||||
elements.ipv4PrivInMax = "ipv4-privin-max";
|
||||
elements.ipv4PrivOutAvg = "ipv4-privout-avg";
|
||||
elements.ipv4PrivOutLast = "ipv4-privout-last";
|
||||
elements.ipv4PrivOutMax = "ipv4-privout-max";
|
||||
elements.ipv4Total = "ipv4-total";
|
||||
elements.ipv4TotalIn = "ipv4-total-in";
|
||||
elements.ipv4TotalOut = "ipv4-total-out";
|
||||
elements.ipv6Graph = "ipv6-graph";
|
||||
elements.ipv6InAvg = "ipv6-in-avg";
|
||||
elements.ipv6InLast = "ipv6-in-last";
|
||||
elements.ipv6InMax = "ipv6-in-max";
|
||||
elements.ipv6OutAvg = "ipv6-out-avg";
|
||||
elements.ipv6OutLast = "ipv6-out-last";
|
||||
elements.ipv6OutMax = "ipv6-out-max";
|
||||
elements.ipv6PrivInAvg = "ipv6-privin-avg";
|
||||
elements.ipv6PrivInLast = "ipv6-privin-last";
|
||||
elements.ipv6PrivInMax = "ipv6-privin-max";
|
||||
elements.ipv6PrivOutAvg = "ipv6-privout-avg";
|
||||
elements.ipv6PrivOutLast = "ipv6-privout-last";
|
||||
elements.ipv6PrivOutMax = "ipv6-privout-max";
|
||||
elements.ipv6Total = "ipv6-total";
|
||||
elements.ipv6TotalIn = "ipv6-total-in";
|
||||
elements.ipv6TotalOut = "ipv6-total-out";
|
||||
elements.jobFailed = "job-failed";
|
||||
elements.jobInfo = "job-info";
|
||||
elements.jobNotice = "job-notice";
|
||||
@ -64,6 +105,9 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
elements.storageFree = "storage-free";
|
||||
elements.storageTotal = "storage-total";
|
||||
elements.storageUsed = "storage-used";
|
||||
elements.swapRateAvg = "swap-rate-avg";
|
||||
elements.swapRateLast = "swap-rate-last";
|
||||
elements.swapRateMax = "swap-rate-max";
|
||||
elements.transferMonthly = "transfer-monthly";
|
||||
elements.transferOverage = "transfer-overage";
|
||||
elements.transferUsed = "transfer-used";
|
||||
@ -80,6 +124,7 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
data.linodeTransfer = {};
|
||||
data.notifications = [];
|
||||
data.plan = {};
|
||||
data.stats = {};
|
||||
data.volumes = [];
|
||||
|
||||
// Static references to UI elements
|
||||
@ -87,9 +132,50 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
ui.backups = {};
|
||||
ui.boot = {};
|
||||
ui.configTable = {};
|
||||
ui.cpuAvg = {};
|
||||
ui.cpuGraph = {};
|
||||
ui.cpuLast = {};
|
||||
ui.cpuMax = {};
|
||||
ui.diskTable = {};
|
||||
ui.diskUsage = {};
|
||||
ui.eventTable = {};
|
||||
ui.graphRange = {};
|
||||
ui.ioGraph = {};
|
||||
ui.ioRateAvg = {};
|
||||
ui.ioRateLast = {};
|
||||
ui.ioRateMax = {};
|
||||
ui.ipv4Graph = {};
|
||||
ui.ipv4InAvg = {};
|
||||
ui.ipv4InLast = {};
|
||||
ui.ipv4InMax = {};
|
||||
ui.ipv4OutAvg = {};
|
||||
ui.ipv4OutLast = {};
|
||||
ui.ipv4OutMax = {};
|
||||
ui.ipv4PrivInAvg = {};
|
||||
ui.ipv4PrivInLast = {};
|
||||
ui.ipv4PrivInMax = {};
|
||||
ui.ipv4PrivOutAvg = {};
|
||||
ui.ipv4PrivOutLast = {};
|
||||
ui.ipv4PrivOutMax = {};
|
||||
ui.ipv4Total = {};
|
||||
ui.ipv4TotalIn = {};
|
||||
ui.ipv4TotalOut = {};
|
||||
ui.ipv6Graph = {};
|
||||
ui.ipv6InAvg = {};
|
||||
ui.ipv6InLast = {};
|
||||
ui.ipv6InMax = {};
|
||||
ui.ipv6OutAvg = {};
|
||||
ui.ipv6OutLast = {};
|
||||
ui.ipv6OutMax = {};
|
||||
ui.ipv6PrivInAvg = {};
|
||||
ui.ipv6PrivInLast = {};
|
||||
ui.ipv6PrivInMax = {};
|
||||
ui.ipv6PrivOutAvg = {};
|
||||
ui.ipv6PrivOutLast = {};
|
||||
ui.ipv6PrivOutMax = {};
|
||||
ui.ipv6Total = {};
|
||||
ui.ipv6TotalIn = {};
|
||||
ui.ipv6TotalOut = {};
|
||||
ui.jobProgress = {};
|
||||
ui.jobProgressRow = {};
|
||||
ui.lastBackup = {};
|
||||
@ -110,6 +196,9 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
ui.storageFree = {};
|
||||
ui.storageTotal = {};
|
||||
ui.storageUsed = {};
|
||||
ui.swapRateAvg = {};
|
||||
ui.swapRateLast = {};
|
||||
ui.swapRateMax = {};
|
||||
ui.transferMonthly = {};
|
||||
ui.transferOverage = {};
|
||||
ui.transferUsed = {};
|
||||
@ -120,6 +209,7 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
var state = {};
|
||||
state.diskRefresh = false;
|
||||
state.eventsComplete = 0;
|
||||
state.haveRanges = false;
|
||||
state.linodeRefresh = false;
|
||||
state.showExtraEvents = false;
|
||||
|
||||
@ -143,24 +233,6 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
apiPost("/linode/instances/" + data.params.lid + "/boot", request, callback);
|
||||
};
|
||||
|
||||
// Convert a byte count into a "friendly" byte string (KB, MB, GB, etc)
|
||||
var byteString = function(count)
|
||||
{
|
||||
var prefix = "KMGTPEZY";
|
||||
var unit = "";
|
||||
|
||||
for (var i = 0; i < prefix.length; i++) {
|
||||
if (count >= 1024) {
|
||||
count /= 1024;
|
||||
unit = prefix.charAt(i);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return count.toFixed(2) + " " + unit + "B";
|
||||
};
|
||||
|
||||
// Generate a config profile table row
|
||||
var createConfigRow = function(config)
|
||||
{
|
||||
@ -525,6 +597,33 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
if (data.linode.type && !data.plan.id)
|
||||
apiGet("/linode/types/" + data.linode.type, displayPlan, null);
|
||||
|
||||
// Populate graph range picker
|
||||
if (!state.haveRanges) {
|
||||
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
var created = new Date(data.linode.created + "Z");
|
||||
var now = new Date();
|
||||
var last30 = document.createElement("option");
|
||||
last30.value = "/" + now.getFullYear() + "/" + (now.getMonth() + 1).toString().padStart(2, "0");
|
||||
last30.innerHTML = "Last 30 Days";
|
||||
ui.graphRange.appendChild(last30);
|
||||
|
||||
while (!(now.getFullYear() == created.getFullYear() && now.getMonth() == created.getMonth())) {
|
||||
if (now.getMonth() == 0) {
|
||||
now.setMonth(11);
|
||||
now.setFullYear(now.getFullYear() - 1);
|
||||
} else {
|
||||
now.setMonth(now.getMonth() - 1);
|
||||
}
|
||||
|
||||
var yearMonth = document.createElement("option");
|
||||
yearMonth.value = "/" + now.getFullYear() + "/" + (now.getMonth() + 1).toString().padStart(2, "0");
|
||||
yearMonth.innerHTML = months[now.getMonth()] + " " + now.getFullYear();
|
||||
ui.graphRange.appendChild(yearMonth);
|
||||
}
|
||||
|
||||
state.haveRanges = true;
|
||||
}
|
||||
|
||||
state.linodeRefresh = false;
|
||||
};
|
||||
|
||||
@ -662,6 +761,159 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
ui.upgrade.style.display = "block";
|
||||
};
|
||||
|
||||
// Show stats graphs
|
||||
var displayStats = function(response)
|
||||
{
|
||||
// Insert dummy points in case of blank data
|
||||
if (!response.data.cpu.length)
|
||||
response.data.cpu = [[0,0]];
|
||||
if (!response.data.io.io.length)
|
||||
response.data.io.io = [[0,0]];
|
||||
if (!response.data.io.swap.length)
|
||||
response.data.io.swap = [[0,0]];
|
||||
if (!response.data.netv4.private_out.length)
|
||||
response.data.netv4.private_out = [[0,0]];
|
||||
if (!response.data.netv4.private_in.length)
|
||||
response.data.netv4.private_in = [[0,0]];
|
||||
if (!response.data.netv4.out.length)
|
||||
response.data.netv4.out = [[0,0]];
|
||||
if (!response.data.netv4.in.length)
|
||||
response.data.netv4.in = [[0,0]];
|
||||
if (!response.data.netv6.private_out.length)
|
||||
response.data.netv6.private_out = [[0,0]];
|
||||
if (!response.data.netv6.private_in.length)
|
||||
response.data.netv6.private_in = [[0,0]];
|
||||
if (!response.data.netv6.out.length)
|
||||
response.data.netv6.out = [[0,0]];
|
||||
if (!response.data.netv6.in.length)
|
||||
response.data.netv6.in = [[0,0]];
|
||||
|
||||
data.stats.cpu = [{
|
||||
"color": "#03C",
|
||||
"fill": false,
|
||||
"points": response.data.cpu
|
||||
}];
|
||||
data.stats.io = [
|
||||
{
|
||||
"color": "#FFD04B",
|
||||
"fill": true,
|
||||
"points": response.data.io.io
|
||||
},
|
||||
{
|
||||
"color": "#FA373E",
|
||||
"fill": false,
|
||||
"points": response.data.io.swap
|
||||
}
|
||||
];
|
||||
data.stats.netv4 = [
|
||||
{
|
||||
"color": "#FF9",
|
||||
"fill": true,
|
||||
"points": response.data.netv4.private_out
|
||||
},
|
||||
{
|
||||
"color": "#C09",
|
||||
"fill": false,
|
||||
"points": response.data.netv4.private_in
|
||||
},
|
||||
{
|
||||
"color": "#32CD32",
|
||||
"fill": true,
|
||||
"points": response.data.netv4.out
|
||||
},
|
||||
{
|
||||
"color": "#03C",
|
||||
"fill": false,
|
||||
"points": response.data.netv4.in
|
||||
}
|
||||
];
|
||||
data.stats.netv6 = [
|
||||
{
|
||||
"color": "#FF9",
|
||||
"fill": true,
|
||||
"points": response.data.netv6.private_out
|
||||
},
|
||||
{
|
||||
"color": "#C09",
|
||||
"fill": false,
|
||||
"points": response.data.netv6.private_in
|
||||
},
|
||||
{
|
||||
"color": "#32CD32",
|
||||
"fill": true,
|
||||
"points": response.data.netv6.out
|
||||
},
|
||||
{
|
||||
"color": "#03C",
|
||||
"fill": false,
|
||||
"points": response.data.netv6.in
|
||||
}
|
||||
];
|
||||
|
||||
// Draw graphs
|
||||
drawSeries(data.stats.cpu, ui.cpuGraph);
|
||||
drawSeries(data.stats.io, ui.ioGraph);
|
||||
drawSeries(data.stats.netv4, ui.ipv4Graph);
|
||||
drawSeries(data.stats.netv6, ui.ipv6Graph);
|
||||
|
||||
// Compute traffic totals
|
||||
var ipv4Out = data.stats.netv4[0].avg * (data.stats.netv4[0].points[data.stats.netv4[0].points.length-1][0] - data.stats.netv4[0].points[0][0]) / 1000;
|
||||
ipv4Out += data.stats.netv4[2].avg * (data.stats.netv4[2].points[data.stats.netv4[2].points.length-1][0] - data.stats.netv4[2].points[0][0]) / 1000;
|
||||
ipv4Out /= 8;
|
||||
var ipv4In = data.stats.netv4[1].avg * (data.stats.netv4[1].points[data.stats.netv4[1].points.length-1][0] - data.stats.netv4[1].points[0][0]) / 1000;
|
||||
ipv4In += data.stats.netv4[3].avg * (data.stats.netv4[3].points[data.stats.netv4[3].points.length-1][0] - data.stats.netv4[3].points[0][0]) / 1000;
|
||||
ipv4In /= 8;
|
||||
var ipv6Out = data.stats.netv6[0].avg * (data.stats.netv6[0].points[data.stats.netv6[0].points.length-1][0] - data.stats.netv6[0].points[0][0]) / 1000;
|
||||
ipv6Out += data.stats.netv6[2].avg * (data.stats.netv6[2].points[data.stats.netv6[2].points.length-1][0] - data.stats.netv6[2].points[0][0]) / 1000;
|
||||
ipv6Out /= 8;
|
||||
var ipv6In = data.stats.netv6[1].avg * (data.stats.netv6[1].points[data.stats.netv6[1].points.length-1][0] - data.stats.netv6[1].points[0][0]) / 1000;
|
||||
ipv6In += data.stats.netv6[3].avg * (data.stats.netv6[3].points[data.stats.netv6[3].points.length-1][0] - data.stats.netv6[3].points[0][0]) / 1000;
|
||||
ipv6In /= 8;
|
||||
|
||||
// Update tables
|
||||
ui.cpuMax.innerHTML = data.stats.cpu[0].max + "%";
|
||||
ui.cpuAvg.innerHTML = data.stats.cpu[0].avg.toFixed(2) + "%";
|
||||
ui.cpuLast.innerHTML = data.stats.cpu[0].points[data.stats.cpu[0].points.length - 1][1] + "%";
|
||||
ui.ioRateMax.innerHTML = data.stats.io[0].max;
|
||||
ui.ioRateAvg.innerHTML = data.stats.io[0].avg.toFixed(2);
|
||||
ui.ioRateLast.innerHTML = data.stats.io[0].points[data.stats.io[0].points.length - 1][1];
|
||||
ui.swapRateMax.innerHTML = data.stats.io[1].max;
|
||||
ui.swapRateAvg.innerHTML = data.stats.io[1].avg.toFixed(2);
|
||||
ui.swapRateLast.innerHTML = data.stats.io[1].points[data.stats.io[1].points.length - 1][1];
|
||||
ui.ipv4PrivOutMax.innerHTML = countSI(data.stats.netv4[0].max) + "b/s";
|
||||
ui.ipv4PrivOutAvg.innerHTML = countSI(data.stats.netv4[0].avg) + "b/s";
|
||||
ui.ipv4PrivOutLast.innerHTML = countSI(data.stats.netv4[0].points[data.stats.netv4[0].points.length - 1][1]) + "b/s";
|
||||
ui.ipv4PrivInMax.innerHTML = countSI(data.stats.netv4[1].max) + "b/s";
|
||||
ui.ipv4PrivInAvg.innerHTML = countSI(data.stats.netv4[1].avg) + "b/s";
|
||||
ui.ipv4PrivInLast.innerHTML = countSI(data.stats.netv4[1].points[data.stats.netv4[1].points.length - 1][1]) + "b/s";
|
||||
ui.ipv4OutMax.innerHTML = countSI(data.stats.netv4[2].max) + "b/s";
|
||||
ui.ipv4OutAvg.innerHTML = countSI(data.stats.netv4[2].avg) + "b/s";
|
||||
ui.ipv4OutLast.innerHTML = countSI(data.stats.netv4[2].points[data.stats.netv4[2].points.length - 1][1]) + "b/s";
|
||||
ui.ipv4InMax.innerHTML = countSI(data.stats.netv4[3].max) + "b/s";
|
||||
ui.ipv4InAvg.innerHTML = countSI(data.stats.netv4[3].avg) + "b/s";
|
||||
ui.ipv4InLast.innerHTML = countSI(data.stats.netv4[3].points[data.stats.netv4[3].points.length - 1][1]) + "b/s";
|
||||
ui.ipv4TotalIn.innerHTML = countSI(ipv4In) + "B";
|
||||
ui.ipv4TotalOut.innerHTML = countSI(ipv4Out) + "B";
|
||||
ui.ipv4Total.innerHTML = countSI(ipv4In + ipv4Out) + "B";
|
||||
ui.ipv6PrivOutMax.innerHTML = countSI(data.stats.netv6[0].max) + "b/s";
|
||||
ui.ipv6PrivOutAvg.innerHTML = countSI(data.stats.netv6[0].avg) + "b/s";
|
||||
ui.ipv6PrivOutLast.innerHTML = countSI(data.stats.netv6[0].points[data.stats.netv6[0].points.length - 1][1]) + "b/s";
|
||||
ui.ipv6PrivInMax.innerHTML = countSI(data.stats.netv6[1].max) + "b/s";
|
||||
ui.ipv6PrivInAvg.innerHTML = countSI(data.stats.netv6[1].avg) + "b/s";
|
||||
ui.ipv6PrivInLast.innerHTML = countSI(data.stats.netv6[1].points[data.stats.netv6[1].points.length - 1][1]) + "b/s";
|
||||
ui.ipv6OutMax.innerHTML = countSI(data.stats.netv6[2].max) + "b/s";
|
||||
ui.ipv6OutAvg.innerHTML = countSI(data.stats.netv6[2].avg) + "b/s";
|
||||
ui.ipv6OutLast.innerHTML = countSI(data.stats.netv6[2].points[data.stats.netv6[2].points.length - 1][1]) + "b/s";
|
||||
ui.ipv6InMax.innerHTML = countSI(data.stats.netv6[3].max) + "b/s";
|
||||
ui.ipv6InAvg.innerHTML = countSI(data.stats.netv6[3].avg) + "b/s";
|
||||
ui.ipv6InLast.innerHTML = countSI(data.stats.netv6[3].points[data.stats.netv6[3].points.length - 1][1]) + "b/s";
|
||||
ui.ipv6TotalIn.innerHTML = countSI(ipv6In) + "B";
|
||||
ui.ipv6TotalOut.innerHTML = countSI(ipv6Out) + "B";
|
||||
ui.ipv6Total.innerHTML = countSI(ipv6In + ipv6Out) + "B";
|
||||
|
||||
ui.graphRange.disabled = false;
|
||||
};
|
||||
|
||||
// Show storage totals
|
||||
var displayStorage = function()
|
||||
{
|
||||
@ -686,7 +938,7 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
|
||||
// Display network transfer info
|
||||
ui.transferMonthly.innerHTML = data.linodeTransfer.quota + " GB";
|
||||
ui.transferUsed.innerHTML = byteString(data.linodeTransfer.used);
|
||||
ui.transferUsed.innerHTML = countSI(data.linodeTransfer.used) + "B";
|
||||
ui.transferOverage.innerHTML = data.linodeTransfer.billable + " GB";
|
||||
ui.netUsage.value = ((data.linodeTransfer.used / 1024 / 1024 / 1024) / data.linodeTransfer.quota * 100).toFixed(0);
|
||||
ui.netUsage.innerHTML = ui.netUsage.value + "%";
|
||||
@ -786,43 +1038,25 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
anchors[i].href = anchors[i].href.replace("lid=0", "lid=" + data.params.lid);
|
||||
|
||||
// Get element references
|
||||
ui.backups = document.getElementById(elements.backups);
|
||||
ui.boot = document.getElementById(elements.boot);
|
||||
ui.configTable = document.getElementById(elements.configTable);
|
||||
ui.diskTable = document.getElementById(elements.diskTable);
|
||||
ui.diskUsage = document.getElementById(elements.diskUsage);
|
||||
ui.eventTable = document.getElementById(elements.eventTable);
|
||||
ui.jobProgress = document.getElementById(elements.jobProgress);
|
||||
ui.jobProgressRow = document.getElementById(elements.jobProgressRow);
|
||||
ui.lastBackup = document.getElementById(elements.lastBackup);
|
||||
ui.lastBackupTime = document.getElementById(elements.lastBackupTime);
|
||||
ui.linodeLabel = document.getElementById(elements.linodeLabel);
|
||||
ui.linodeTag = document.getElementById(elements.linodeTag);
|
||||
ui.linodeTagLink = document.getElementById(elements.linodeTagLink);
|
||||
ui.loadingConfigs = document.getElementById(elements.loadingConfigs);
|
||||
ui.loadingDisks = document.getElementById(elements.loadingDisks);
|
||||
ui.loadingJobs = document.getElementById(elements.loadingJobs);
|
||||
ui.loadingVolumes = document.getElementById(elements.loadingVolumes);
|
||||
ui.moreJobs = document.getElementById(elements.moreJobs);
|
||||
ui.netUsage = document.getElementById(elements.netUsage);
|
||||
ui.notifications = document.getElementById(elements.notifications);
|
||||
ui.reboot = document.getElementById(elements.reboot);
|
||||
ui.serverStatus = document.getElementById(elements.serverStatus);
|
||||
ui.shutDown = document.getElementById(elements.shutDown);
|
||||
ui.storageFree = document.getElementById(elements.storageFree);
|
||||
ui.storageTotal = document.getElementById(elements.storageTotal);
|
||||
ui.storageUsed = document.getElementById(elements.storageUsed);
|
||||
ui.transferMonthly = document.getElementById(elements.transferMonthly);
|
||||
ui.transferOverage = document.getElementById(elements.transferOverage);
|
||||
ui.transferUsed = document.getElementById(elements.transferUsed);
|
||||
ui.upgrade = document.getElementById(elements.upgrade);
|
||||
ui.volumeTable = document.getElementById(elements.volumeTable);
|
||||
for (var i in ui)
|
||||
ui[i] = document.getElementById(elements[i]);
|
||||
|
||||
// Attach button handlers
|
||||
ui.boot.addEventListener("click", bootLinode);
|
||||
ui.moreJobs.addEventListener("click", toggleEvents);
|
||||
ui.reboot.addEventListener("click", rebootLinode);
|
||||
ui.shutDown.addEventListener("click", shutDownLinode);
|
||||
ui.graphRange.addEventListener("input", updateGraphs);
|
||||
|
||||
// Set graph resolutions
|
||||
ui.cpuGraph.height = ui.cpuGraph.clientHeight;
|
||||
ui.cpuGraph.width = ui.cpuGraph.clientWidth;
|
||||
ui.ioGraph.height = ui.ioGraph.clientHeight;
|
||||
ui.ioGraph.width = ui.ioGraph.clientWidth;
|
||||
ui.ipv4Graph.height = ui.ipv4Graph.clientHeight;
|
||||
ui.ipv4Graph.width = ui.ipv4Graph.clientWidth;
|
||||
ui.ipv6Graph.height = ui.ipv6Graph.clientHeight;
|
||||
ui.ipv6Graph.width = ui.ipv6Graph.clientWidth;
|
||||
|
||||
// Get data from the API
|
||||
apiGet("/linode/instances/" + data.params.lid, displayDetails, null);
|
||||
@ -836,6 +1070,7 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
};
|
||||
apiGet("/account/events", displayEvents, filter);
|
||||
apiGet("/account/notifications", displayNotifications, null);
|
||||
apiGet("/linode/instances/" + data.params.lid + "/stats", displayStats, null);
|
||||
};
|
||||
|
||||
// Button handler for shutdown button
|
||||
@ -908,6 +1143,16 @@ import { settings, elements, apiDelete, apiGet, apiPost, eventTitles, parseParam
|
||||
window.setTimeout(getEvent, settings.refreshRate, event.id);
|
||||
};
|
||||
|
||||
// Re-populate graphs with selected range
|
||||
var updateGraphs = function(event)
|
||||
{
|
||||
if (event.currentTarget.disabled)
|
||||
return;
|
||||
|
||||
apiGet("/linode/instances/" + data.params.lid + "/stats" + ui.graphRange.value, displayStats, null);
|
||||
ui.graphRange.disabled = true;
|
||||
};
|
||||
|
||||
// Attach onload handler
|
||||
window.addEventListener("load", setup);
|
||||
})();
|
||||
|
@ -109,6 +109,145 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Graphs</h3>
|
||||
<select disabled id="graph-range">
|
||||
<option selected value="">Last 24 Hours</option>
|
||||
</select>
|
||||
<div class="lmc-graph">
|
||||
<h4>CPU (%)</h4>
|
||||
<canvas id="cpu-graph"></canvas>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Max</td>
|
||||
<td>Avg</td>
|
||||
<td>Last</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><div id="cpu-color" class="lmc-graph-color"></div> CPU %</td>
|
||||
<td id="cpu-max"></td>
|
||||
<td id="cpu-avg"></td>
|
||||
<td id="cpu-last"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Disk I/O (blocks/s)</h4>
|
||||
<canvas id="io-graph"></canvas>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Max</td>
|
||||
<td>Avg</td>
|
||||
<td>Last</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><div id="io-rate-color" class="lmc-graph-color"></div> I/O Rate</td>
|
||||
<td id="io-rate-max"></td>
|
||||
<td id="io-rate-avg"></td>
|
||||
<td id="io-rate-last"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div id="swap-rate-color" class="lmc-graph-color"></div> Swap Rate</td>
|
||||
<td id="swap-rate-max"></td>
|
||||
<td id="swap-rate-avg"></td>
|
||||
<td id="swap-rate-last"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Network - IPv4 (bits/s)</h4>
|
||||
<canvas id="ipv4-graph"></canvas>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Max</td>
|
||||
<td>Avg</td>
|
||||
<td>Last</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><div id="ipv4-privout-color" class="lmc-graph-color"></div> Private Out</td>
|
||||
<td id="ipv4-privout-max"></td>
|
||||
<td id="ipv4-privout-avg"></td>
|
||||
<td id="ipv4-privout-last"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div id="ipv4-privin-color" class="lmc-graph-color"></div> Private In</td>
|
||||
<td id="ipv4-privin-max"></td>
|
||||
<td id="ipv4-privin-avg"></td>
|
||||
<td id="ipv4-privin-last"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div id="ipv4-out-color" class="lmc-graph-color"></div> Public Out</td>
|
||||
<td id="ipv4-out-max"></td>
|
||||
<td id="ipv4-out-avg"</td>
|
||||
<td id="ipv4-out-last"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div id="ipv4-in-color" class="lmc-graph-color"></div> Public In</td>
|
||||
<td id="ipv4-in-max"></td>
|
||||
<td id="ipv4-in-avg"></td>
|
||||
<td id="ipv4-in-last"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Traffic</td>
|
||||
<td>In: <span id="ipv4-total-in"></span></td>
|
||||
<td>Out: <span id="ipv4-total-out"></span></td>
|
||||
<td>Combined: <span id="ipv4-total"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Network - IPv6 (bits/s)</h4>
|
||||
<canvas id="ipv6-graph"></canvas>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Max</td>
|
||||
<td>Avg</td>
|
||||
<td>Last</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><div id="ipv6-privout-color" class="lmc-graph-color"></div> Private Out</td>
|
||||
<td id="ipv6-privout-max"></td>
|
||||
<td id="ipv6-privout-avg"></td>
|
||||
<td id="ipv6-privout-last"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div id="ipv6-privin-color" class="lmc-graph-color"></div> Private In</td>
|
||||
<td id="ipv6-privin-max"></td>
|
||||
<td id="ipv6-privin-avg"></td>
|
||||
<td id="ipv6-privin-last"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div id="ipv6-out-color" class="lmc-graph-color"></div> Public Out</td>
|
||||
<td id="ipv6-out-max"></td>
|
||||
<td id="ipv6-out-avg"</td>
|
||||
<td id="ipv6-out-last"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div id="ipv6-in-color" class="lmc-graph-color"></div> Public In</td>
|
||||
<td id="ipv6-in-max"></td>
|
||||
<td id="ipv6-in-avg"></td>
|
||||
<td id="ipv6-in-last"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Traffic</td>
|
||||
<td>In: <span id="ipv6-total-in"></span></td>
|
||||
<td>Out: <span id="ipv6-total-out"></span></td>
|
||||
<td>Combined: <span id="ipv6-total"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="linode-sidebar">
|
||||
<div class="sidebar-box">
|
||||
|
@ -48,6 +48,7 @@ import { settings, elements, regionNames, apiGet, parseParams, setupHeader } fro
|
||||
data.noTag = false;
|
||||
data.notifications = [];
|
||||
data.plans = [];
|
||||
data.regions = [];
|
||||
|
||||
// Static references to UI elements
|
||||
var ui = {};
|
||||
@ -60,6 +61,8 @@ import { settings, elements, regionNames, apiGet, parseParams, setupHeader } fro
|
||||
var state = {};
|
||||
state.haveLinodes = false;
|
||||
state.haveNotifications = false;
|
||||
state.haveRegions = false;
|
||||
state.haveTypes = false;
|
||||
|
||||
var createLinodeRow = function(linode, alt)
|
||||
{
|
||||
@ -92,8 +95,10 @@ import { settings, elements, regionNames, apiGet, parseParams, setupHeader } fro
|
||||
plan.innerHTML = getPlanLabel(linode.type);
|
||||
else
|
||||
plan.innerHTML = "Unknown";
|
||||
if (plan.innerHTML == "")
|
||||
if (plan.innerHTML == "") {
|
||||
plan.innerHTML = linode.type;
|
||||
translatePlan(linode.type, plan);
|
||||
}
|
||||
var ip = document.createElement("td");
|
||||
ip.innerHTML = linode.ipv4[0];
|
||||
var ipCount = 0;
|
||||
@ -110,8 +115,17 @@ import { settings, elements, regionNames, apiGet, parseParams, setupHeader } fro
|
||||
plus.innerHTML = " (+" + ipCount + ")";
|
||||
ip.appendChild(plus);
|
||||
}
|
||||
var regionData = null;
|
||||
for (var i = 0; i < data.regions.length; i++) {
|
||||
if (data.regions[i].id == linode.region) {
|
||||
regionData = data.regions[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
var region = document.createElement("td");
|
||||
if (regionNames[linode.region])
|
||||
if (regionData && regionData.label && regionData.label.length)
|
||||
region.innerHTML = regionData.label;
|
||||
else if (regionNames[linode.region])
|
||||
region.innerHTML = regionNames[linode.region];
|
||||
else
|
||||
region.innerHTML = linode.region;
|
||||
@ -312,8 +326,19 @@ import { settings, elements, regionNames, apiGet, parseParams, setupHeader } fro
|
||||
body.innerHTML = data.notifications[i].message;
|
||||
}
|
||||
// Replace "this facility" with actual location for outages
|
||||
if (data.notifications[i].type == "outage" && data.notifications[i].entity && data.notifications[i].entity.type == "region" && regionNames[data.notifications[i].entity.id])
|
||||
header.innerHTML = header.innerHTML.replace("this facility", regionNames[data.notifications[i].entity.id]);
|
||||
if (data.notifications[i].type == "outage" && data.notifications[i].entity && data.notifications[i].entity.type == "region") {
|
||||
var region = null;
|
||||
for (var j = 0; j < data.regions.length; j++) {
|
||||
if (data.regions[j].id == data.notifications[i].entity.id) {
|
||||
region = data.regions[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (region && region.label && region.label.length)
|
||||
header.innerHTML = header.innerHTML.replace("this facility", region.label);
|
||||
else if (regionNames[data.notifications[i].entity.id])
|
||||
header.innerHTML = header.innerHTML.replace("this facility", regionNames[data.notifications[i].entity.id]);
|
||||
}
|
||||
notification.appendChild(header);
|
||||
notification.appendChild(body);
|
||||
ui.notifications.appendChild(notification);
|
||||
@ -379,13 +404,42 @@ import { settings, elements, regionNames, apiGet, parseParams, setupHeader } fro
|
||||
return;
|
||||
}
|
||||
|
||||
// Get linodes
|
||||
var filters = null;
|
||||
if (data.params.tag)
|
||||
filters = {
|
||||
"tags": data.params.tag
|
||||
};
|
||||
apiGet("/linode/instances", displayLinodes, filters);
|
||||
state.haveTypes = true;
|
||||
if (state.haveRegions) {
|
||||
// Get linodes
|
||||
var filters = null;
|
||||
if (data.params.tag)
|
||||
filters = {
|
||||
"tags": data.params.tag
|
||||
};
|
||||
apiGet("/linode/instances", displayLinodes, filters);
|
||||
}
|
||||
};
|
||||
|
||||
var getRegions = function(response)
|
||||
{
|
||||
// Add regions to array
|
||||
data.regions = data.regions.concat(response.data);
|
||||
|
||||
// Request the next page if there are more pages
|
||||
if (response.page != response.pages) {
|
||||
apiGet("/regions?page=" + (response.page + 1), getRegions, null);
|
||||
return;
|
||||
}
|
||||
|
||||
state.haveRegions = true;
|
||||
if (state.haveTypes) {
|
||||
// Get linodes
|
||||
var filters = null;
|
||||
if (data.params.tag)
|
||||
filters = {
|
||||
"tags": data.params.tag
|
||||
};
|
||||
apiGet("/linode/instances", displayLinodes, filters);
|
||||
}
|
||||
|
||||
// Get notifications
|
||||
apiGet("/account/notifications", displayNotifications, null);
|
||||
};
|
||||
|
||||
var insertLinodes = function()
|
||||
@ -425,9 +479,9 @@ import { settings, elements, regionNames, apiGet, parseParams, setupHeader } fro
|
||||
setupHeader();
|
||||
|
||||
// Get linode and transfer info
|
||||
apiGet("/regions", getRegions, null);
|
||||
apiGet("/linode/types", getPlans, null);
|
||||
apiGet("/account/transfer", displayTransfer, null);
|
||||
apiGet("/account/notifications", displayNotifications, null);
|
||||
};
|
||||
|
||||
var translatePlan = function(name, cell)
|
||||
|
@ -44,24 +44,6 @@ import { settings, elements, apiGet, parseParams, setupHeader } from "/global.js
|
||||
data.linode = {};
|
||||
data.netInfo = {};
|
||||
|
||||
// LISH uses different DC names than the API uses, so we need to translate them
|
||||
data.lishDCs = {
|
||||
"us-central": "dallas",
|
||||
"us-west": "fremont",
|
||||
"us-southeast": "atlanta",
|
||||
"us-east": "newark",
|
||||
"us-east-1b": "cjj2",
|
||||
"eu-west": "london",
|
||||
"ap-south": "singapore",
|
||||
"eu-central": "frankfurt",
|
||||
"ap-northeast": "shg1",
|
||||
"ap-northeast-1a": "shg1",
|
||||
"ca-central": "tor1",
|
||||
"ap-west": "mum1",
|
||||
"ap-southeast": "syd1",
|
||||
"philadelphia": "phl1"
|
||||
};
|
||||
|
||||
// Static references to UI elements
|
||||
var ui = {};
|
||||
ui.ipFailover = {};
|
||||
@ -113,8 +95,8 @@ import { settings, elements, apiGet, parseParams, setupHeader } from "/global.js
|
||||
apiGet("/regions/" + data.linode.region, displayResolvers, null);
|
||||
|
||||
// Update the LISH link
|
||||
ui.lishLink.href = ui.lishLink.href.replace("lish-", "lish-" + data.lishDCs[data.linode.region]);
|
||||
ui.lishLink.innerHTML = ui.lishLink.innerHTML.replace("lish-", "lish-" + data.lishDCs[data.linode.region]);
|
||||
ui.lishLink.href = ui.lishLink.href.replace("lish-", "lish-" + data.linode.region);
|
||||
ui.lishLink.innerHTML = ui.lishLink.innerHTML.replace("lish-", "lish-" + data.linode.region);
|
||||
ui.lishLink.innerHTML += " " + data.linode.label;
|
||||
};
|
||||
|
||||
|
@ -46,17 +46,19 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<br />
|
||||
<h3>Standard</h3>
|
||||
<div id="standard"></div>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<br />
|
||||
<h3>Dedicated CPU</h3>
|
||||
<div id="dedicated"></div>
|
||||
<br />
|
||||
</td>
|
||||
<td>
|
||||
<h3>High Memory</h3>
|
||||
<div id="highmem"></div>
|
||||
<br />
|
||||
<h3>Dedicated GPU</h3>
|
||||
<div id="gpu"></div>
|
||||
<br />
|
||||
<h3>Premium</h3>
|
||||
<div id="premium"></div>
|
||||
</td>
|
||||
<td>
|
||||
<h2>How it works</h2>
|
||||
|
@ -33,6 +33,7 @@ import { settings, elements, apiGet, apiPost, migrateETA, parseParams, setupHead
|
||||
elements.linodeTag = "linode-tag";
|
||||
elements.linodeTagLink = "linode-tag-link";
|
||||
elements.nanode = "nanode";
|
||||
elements.premium = "premium";
|
||||
elements.resizeButton = "resize-button";
|
||||
elements.standard = "standard";
|
||||
|
||||
@ -55,6 +56,7 @@ import { settings, elements, apiGet, apiPost, migrateETA, parseParams, setupHead
|
||||
ui.linodeTag = {};
|
||||
ui.linodeTagLink = {};
|
||||
ui.nanode = {};
|
||||
ui.premium = {};
|
||||
ui.resizeButton = {};
|
||||
ui.standard = {};
|
||||
|
||||
@ -234,6 +236,7 @@ import { settings, elements, apiGet, apiPost, migrateETA, parseParams, setupHead
|
||||
ui.linodeTag = document.getElementById(elements.linodeTag);
|
||||
ui.linodeTagLink = document.getElementById(elements.linodeTagLink);
|
||||
ui.nanode = document.getElementById(elements.nanode);
|
||||
ui.premium = document.getElementById(elements.premium);
|
||||
ui.resizeButton = document.getElementById(elements.resizeButton);
|
||||
ui.standard = document.getElementById(elements.standard);
|
||||
|
||||
|
2
login.js
2
login.js
@ -53,7 +53,7 @@ import { clientID } from "/clientID.js";
|
||||
params.set("response_type", "token");
|
||||
params.set("state", localStorage.state);
|
||||
|
||||
location.href = settings.oauthURL + "/authorize" + "?" + params.toString();
|
||||
location.href = settings.oauthURL + "/authorize?" + params.toString();
|
||||
};
|
||||
|
||||
// Initial setup
|
||||
|
72
nodebalancers/add/add.css
Normal file
72
nodebalancers/add/add.css
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is part of Linode Manager Classic.
|
||||
*
|
||||
* Linode Manager Classic is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Linode Manager Classic is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@import url('/global.css');
|
||||
|
||||
#add {
|
||||
padding: 0px 15px 15px;
|
||||
}
|
||||
|
||||
#add-button {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin: 0 auto;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#datacenters {
|
||||
font-size: 18px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#location {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
optgroup {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#submit {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
tbody td {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
tbody td:nth-of-type(2) {
|
||||
color: green;
|
||||
}
|
||||
|
||||
td {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
td:first-of-type {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
thead {
|
||||
background: linear-gradient(#00EE00, #00BF00);
|
||||
}
|
147
nodebalancers/add/add.js
Normal file
147
nodebalancers/add/add.js
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* This file is part of Linode Manager Classic.
|
||||
*
|
||||
* Linode Manager Classic is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Linode Manager Classic is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, apiGet, apiPost, countryContinents, parseParams, regionNames, setupHeader } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
// Element names specific to this page
|
||||
elements.addButton = "add-button";
|
||||
elements.datacenters = "datacenters";
|
||||
elements.dcOther = "dc-other";
|
||||
elements.hourly = "hourly";
|
||||
elements.monthly = "monthly";
|
||||
|
||||
// Data recieved from API calls
|
||||
var data = {};
|
||||
data.linodes = [];
|
||||
data.regions = [];
|
||||
|
||||
// Static references to UI elements
|
||||
var ui = {};
|
||||
ui.addButton = {};
|
||||
ui.datacenters = {};
|
||||
ui.dcOther = {};
|
||||
ui.hourly = {};
|
||||
ui.monthly = {};
|
||||
|
||||
// Callback for regions API call
|
||||
var displayRegions = function(response)
|
||||
{
|
||||
// Add regions that support block storage to array
|
||||
for (var i = 0; i < response.data.length; i++) {
|
||||
if (response.data[i].capabilities.includes("NodeBalancers"))
|
||||
data.regions.push(response.data[i]);
|
||||
}
|
||||
|
||||
// Request the next page if there are more pages
|
||||
if (response.page != response.pages) {
|
||||
apiGet("/regions?page=" + (response.page + 1), displayRegions, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add regions to selector
|
||||
for (var i = 0; i < data.regions.length; i++) {
|
||||
// Regional pricing
|
||||
if (data.regions[i].id == "id-cgk") {
|
||||
data.regions[i].hourly = "$0.018/hr";
|
||||
data.regions[i].monthly = "$12.00/mo";
|
||||
} else if (data.regions[i].id == "br-gru") {
|
||||
data.regions[i].hourly = "$0.021/hr";
|
||||
data.regions[i].monthly = "$14.00/mo";
|
||||
} else {
|
||||
data.regions[i].hourly = "$0.015/hr";
|
||||
data.regions[i].monthly = "$10.00/mo";
|
||||
}
|
||||
|
||||
var loc = document.createElement("option");
|
||||
loc.value = data.regions[i].id;
|
||||
if (data.regions[i].label && data.regions[i].label.length)
|
||||
loc.innerHTML = data.regions[i].label;
|
||||
else if (regionNames[data.regions[i].id])
|
||||
loc.innerHTML = regionNames[data.regions[i].id];
|
||||
else
|
||||
loc.innerHTML = data.regions[i].id;
|
||||
var optgroup = null;
|
||||
if (countryContinents[data.regions[i].country])
|
||||
optgroup = document.getElementById(countryContinents[data.regions[i].country]);
|
||||
if (!optgroup)
|
||||
optgroup = ui.dcOther;
|
||||
optgroup.style.display = "initial";
|
||||
optgroup.appendChild(loc);
|
||||
}
|
||||
|
||||
ui.addButton.disabled = false;
|
||||
};
|
||||
|
||||
// Click handler for add button
|
||||
var handleAdd = function(event)
|
||||
{
|
||||
if (event.currentTarget.disabled)
|
||||
return;
|
||||
|
||||
var req = {
|
||||
"region": ui.datacenters.value
|
||||
};
|
||||
if (data.params.tag)
|
||||
req.tags = [data.params.tag];
|
||||
|
||||
apiPost("/nodebalancers", req, function(response)
|
||||
{
|
||||
location.href = "/nodebalancers/balancer?nbid=" + response.id;
|
||||
});
|
||||
};
|
||||
|
||||
// Location select handler
|
||||
var handleLocation = function(event)
|
||||
{
|
||||
// Update prices based on location
|
||||
for (var i = 0; i < data.regions.length; i++) {
|
||||
if (data.regions[i].id == ui.datacenters.value) {
|
||||
ui.hourly.innerHTML = data.regions[i].hourly;
|
||||
ui.monthly.innerHTML = data.regions[i].monthly;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initial setup
|
||||
var setup = function()
|
||||
{
|
||||
// Parse URL parameters
|
||||
data.params = parseParams();
|
||||
|
||||
setupHeader();
|
||||
|
||||
// Get element references
|
||||
ui.addButton = document.getElementById(elements.addButton);
|
||||
ui.datacenters = document.getElementById(elements.datacenters);
|
||||
ui.dcOther = document.getElementById(elements.dcOther);
|
||||
ui.hourly = document.getElementById(elements.hourly);
|
||||
ui.monthly = document.getElementById(elements.monthly);
|
||||
|
||||
// Attach event handlers
|
||||
ui.addButton.addEventListener("click", handleAdd);
|
||||
ui.datacenters.addEventListener("input", handleLocation);
|
||||
|
||||
// Get data from API
|
||||
apiGet("/regions", displayRegions, null);
|
||||
};
|
||||
|
||||
// Attach onload handler
|
||||
window.addEventListener("load", setup);
|
||||
})();
|
66
nodebalancers/add/index.shtml
Normal file
66
nodebalancers/add/index.shtml
Normal file
@ -0,0 +1,66 @@
|
||||
<!--
|
||||
This file is part of Linode Manager Classic.
|
||||
|
||||
Linode Manager Classic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Linode Manager Classic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>LMC - Add a NodeBalancer</title>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" type="text/css" href="add.css" />
|
||||
<script src="add.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/nodebalancers">NodeBalancers</a> » <span class="top-links-title">Add a NodeBalancer</span></div>
|
||||
<div id="add">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Plan</td>
|
||||
<td>Hourly</td>
|
||||
<td>Monthly</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="lmc-tr3">
|
||||
<td><input checked type="radio" name="plan" /></td>
|
||||
<td>NodeBalancer</td>
|
||||
<td id="hourly">$0.015/hr</td>
|
||||
<td id="monthly">$10.00/mo</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="location">
|
||||
<h2>Location</h2>
|
||||
<select id="datacenters">
|
||||
<optgroup id="na" label="North America"></optgroup>
|
||||
<optgroup id="eu" label="Europe"></optgroup>
|
||||
<optgroup id="ap" label="Asia/Pacific"></optgroup>
|
||||
<optgroup id="sa" label="South America"></optgroup>
|
||||
<optgroup id="af" label="Africa"></optgroup>
|
||||
<optgroup id="dc-other" label="Other"></optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div id="submit">
|
||||
<button disabled id="add-button" type="button">Add this NodeBalancer!</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
62
nodebalancers/balancer/balancer.css
Normal file
62
nodebalancers/balancer/balancer.css
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* This file is part of Linode Manager Classic.
|
||||
*
|
||||
* Linode Manager Classic is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Linode Manager Classic is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@import url('/global.css');
|
||||
|
||||
canvas {
|
||||
height: 323px;
|
||||
}
|
||||
|
||||
.center-cell {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#cxn-color {
|
||||
background-color: #906;
|
||||
}
|
||||
|
||||
#graph-range {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
border-bottom: 1px solid #E8E8E8;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
#nodebalancer-details {
|
||||
padding: 0px 15px 20px;
|
||||
}
|
||||
|
||||
#settings-table tr td:first-of-type {
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#settings-table tr td:not(:last-of-type) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#traffic-in-color {
|
||||
background-color: #03C;
|
||||
}
|
||||
|
||||
#traffic-out-color {
|
||||
background-color: #32CD32;
|
||||
}
|
411
nodebalancers/balancer/balancer.js
Normal file
411
nodebalancers/balancer/balancer.js
Normal file
@ -0,0 +1,411 @@
|
||||
/*
|
||||
* This file is part of Linode Manager Classic.
|
||||
*
|
||||
* Linode Manager Classic is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Linode Manager Classic is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, apiDelete, apiGet, apiPut, countSI, drawSeries, parseParams, regionNames, setupHeader } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
// Element names specific to this page
|
||||
elements.centerCell = "center-cell";
|
||||
elements.configTable = "config-table";
|
||||
elements.cxnAvg = "cxn-avg";
|
||||
elements.cxnGraph = "cxn-graph";
|
||||
elements.cxnLast = "cxn-last";
|
||||
elements.cxnMax = "cxn-max";
|
||||
elements.hostname = "hostname";
|
||||
elements.inputLabel = "input-label";
|
||||
elements.ipv4 = "ipv4";
|
||||
elements.ipv6 = "ipv6";
|
||||
elements.lmcRow = "lmc-tr1";
|
||||
elements.lmcRowAlt = "lmc-tr2";
|
||||
elements.loadingConfigs = "loading-configs";
|
||||
elements.location = "location";
|
||||
elements.nodebalancerLabel = "nodebalancer-label";
|
||||
elements.nodebalancerTag = "nodebalancer-tag";
|
||||
elements.nodebalancerTagLink = "nodebalancer-tag-link";
|
||||
elements.removePrefix = "remove-config-";
|
||||
elements.saveButton = "save-button";
|
||||
elements.tableLabel = "table-label";
|
||||
elements.tags = "tags";
|
||||
elements.throttle = "throttle";
|
||||
elements.trafficGraph = "traffic-graph";
|
||||
elements.trafficInAvg = "traffic-in-avg";
|
||||
elements.trafficInLast = "traffic-in-last";
|
||||
elements.trafficInMax = "traffic-in-max";
|
||||
elements.trafficOutAvg = "traffic-out-avg";
|
||||
elements.trafficOutLast = "traffic-out-last";
|
||||
elements.trafficOutMax = "traffic-out-max";
|
||||
elements.transferred = "transferred";
|
||||
|
||||
// Data recieved from API calls
|
||||
var data = {};
|
||||
data.configs = [];
|
||||
data.nodebalancer = {};
|
||||
data.params = {};
|
||||
data.regions = [];
|
||||
data.stats = {};
|
||||
|
||||
// Static references to UI elements
|
||||
var ui = {};
|
||||
ui.configTable = {};
|
||||
ui.cxnAvg = {};
|
||||
ui.cxnGraph = {};
|
||||
ui.cxnLast = {};
|
||||
ui.cxnMax = {};
|
||||
ui.hostname = {};
|
||||
ui.inputLabel = {};
|
||||
ui.ipv4 = {};
|
||||
ui.ipv6 = {};
|
||||
ui.loadingConfigs = {};
|
||||
ui.location = {};
|
||||
ui.nodebalancerLabel = {};
|
||||
ui.nodebalancerTag = {};
|
||||
ui.nodebalancerTagLink = {};
|
||||
ui.saveButton = {};
|
||||
ui.tableLabel = {};
|
||||
ui.tags = {};
|
||||
ui.throttle = {};
|
||||
ui.trafficGraph = {};
|
||||
ui.trafficInAvg = {};
|
||||
ui.trafficInLast = {};
|
||||
ui.trafficInMax = {};
|
||||
ui.trafficOutAvg = {};
|
||||
ui.trafficOutLast = {};
|
||||
ui.trafficOutMax = {};
|
||||
ui.transferred = {};
|
||||
|
||||
// Temporary state
|
||||
var state = {};
|
||||
state.haveNodebalancer = false;
|
||||
state.haveRegions = false;
|
||||
|
||||
// Generate a configuration table row
|
||||
var createConfigRow = function(config, alt)
|
||||
{
|
||||
var row = document.createElement("tr");
|
||||
if (alt)
|
||||
row.className = elements.lmcRowAlt;
|
||||
else
|
||||
row.className = elements.lmcRow;
|
||||
|
||||
var port = document.createElement("td");
|
||||
var portLink = document.createElement("a");
|
||||
portLink.href = "/nodebalancers/config?nbid=" + data.params.nbid + "&nbcid=" + config.id;
|
||||
portLink.innerHTML = "Port " + config.port;
|
||||
port.appendChild(portLink);
|
||||
row.appendChild(port);
|
||||
|
||||
var protocol = document.createElement("td");
|
||||
protocol.innerHTML = config.protocol.toUpperCase();
|
||||
row.appendChild(protocol);
|
||||
|
||||
var algorithmStrings = {
|
||||
"roundrobin": "Round Robin",
|
||||
"leastconn": "Least Connections",
|
||||
"source": "Source IP"
|
||||
};
|
||||
var algorithm = document.createElement("td");
|
||||
if (algorithmStrings[config.algorithm])
|
||||
algorithm.innerHTML = algorithmStrings[config.algorithm];
|
||||
else
|
||||
algorithm.innerHTML = config.algorithm;
|
||||
row.appendChild(algorithm);
|
||||
|
||||
var stickinessStrings = {
|
||||
"none": "None",
|
||||
"table": "Table",
|
||||
"http_cookie": "HTTP Cookie"
|
||||
};
|
||||
var stickiness = document.createElement("td");
|
||||
if (stickinessStrings[config.stickiness])
|
||||
stickiness.innerHTML = stickinessStrings[config.stickiness];
|
||||
else
|
||||
stickiness.innerHTML = config.stickiness;
|
||||
row.appendChild(stickiness);
|
||||
|
||||
var healthStrings = {
|
||||
"none": "None",
|
||||
"connection": "TCP Connection",
|
||||
"http": "HTTP Valid Status",
|
||||
"http_body": "HTTP Body Regex"
|
||||
};
|
||||
var healthCheck = document.createElement("td");
|
||||
if (healthStrings[config.check])
|
||||
healthCheck.innerHTML = healthStrings[config.check];
|
||||
else
|
||||
healthCheck.innerHTML = config.check;
|
||||
row.appendChild(healthCheck);
|
||||
|
||||
var status = document.createElement("td");
|
||||
status.innerHTML = config.nodes_status.up + " up, " + config.nodes_status.down + " down";
|
||||
row.appendChild(status);
|
||||
|
||||
var options = document.createElement("td");
|
||||
options.className = elements.centerCell;
|
||||
var editLink = document.createElement("a");
|
||||
editLink.href = "/nodebalancers/config?nbid=" + data.params.nbid + "&nbcid=" + config.id;
|
||||
editLink.innerHTML = "Edit";
|
||||
var separator = document.createElement("span");
|
||||
separator.innerHTML = " | ";
|
||||
var removeLink = document.createElement("a");
|
||||
removeLink.href = "#";
|
||||
removeLink.id = elements.removePrefix + config.id;
|
||||
removeLink.innerHTML = "Remove";
|
||||
removeLink.addEventListener("click", handleConfigRemove);
|
||||
options.appendChild(editLink);
|
||||
options.appendChild(separator);
|
||||
options.appendChild(removeLink);
|
||||
row.appendChild(options);
|
||||
|
||||
return row;
|
||||
};
|
||||
|
||||
// Callback for nodebalancer configs API call
|
||||
var displayConfigs = function(response)
|
||||
{
|
||||
// Add configs to array
|
||||
data.configs = data.configs.concat(response.data);
|
||||
|
||||
// Request the next page if there are more pages
|
||||
if (response.page != response.pages) {
|
||||
apiGet("/nodebalancers/" + data.params.nbid + "/configs?page=" + (response.page + 1), displayConfigs, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove loading row
|
||||
ui.loadingConfigs.remove();
|
||||
|
||||
// Insert configuration rows into table
|
||||
for (var i = 0; i < data.configs.length; i++)
|
||||
ui.configTable.appendChild(createConfigRow(data.configs[i], i % 2));
|
||||
};
|
||||
|
||||
// Callback for nodebalancer API call
|
||||
var displayNodebalancer = function(response)
|
||||
{
|
||||
data.nodebalancer = response;
|
||||
|
||||
// Set page title and header stuff
|
||||
if (document.title.indexOf("//") == -1)
|
||||
document.title += " // Edit " + data.nodebalancer.label;
|
||||
ui.nodebalancerLabel.innerHTML = data.nodebalancer.label;
|
||||
if (data.nodebalancer.tags.length == 1) {
|
||||
ui.nodebalancerTagLink.href = "/nodebalancers?tag=" + data.nodebalancer.tags[0];
|
||||
ui.nodebalancerTagLink.innerHTML = "(" + data.nodebalancer.tags[0] + ")";
|
||||
ui.nodebalancerTag.style.display = "inline";
|
||||
} else {
|
||||
ui.nodebalancerTag.style.display = "none";
|
||||
}
|
||||
|
||||
// Populate info
|
||||
ui.tableLabel.innerHTML = data.nodebalancer.label;
|
||||
ui.hostname.innerHTML = data.nodebalancer.hostname;
|
||||
ui.ipv4.innerHTML = data.nodebalancer.ipv4;
|
||||
ui.ipv6.innerHTML = data.nodebalancer.ipv6;
|
||||
ui.transferred.innerHTML = countSI(data.nodebalancer.transfer.in * 1048576) + "B in - " + countSI(data.nodebalancer.transfer.out * 1048576) + "B out (" + countSI(data.nodebalancer.transfer.total * 1048576) + "B total)";
|
||||
ui.inputLabel.value = data.nodebalancer.label;
|
||||
ui.throttle.value = data.nodebalancer.client_conn_throttle;
|
||||
ui.tags.value = data.nodebalancer.tags.join(",");
|
||||
|
||||
ui.saveButton.disabled = false;
|
||||
|
||||
state.haveNodebalancer = true;
|
||||
if (state.haveRegions)
|
||||
insertRegion();
|
||||
};
|
||||
|
||||
// Callback for regions API call
|
||||
var displayRegions = function(response)
|
||||
{
|
||||
// Add regions to array
|
||||
data.regions = data.regions.concat(response.data);
|
||||
|
||||
// Request the next page if there are more pages
|
||||
if (response.page != response.pages) {
|
||||
apiGet("/regions?page=" + (response.page + 1), getRegions, null);
|
||||
return;
|
||||
}
|
||||
|
||||
state.haveRegions = true;
|
||||
if (state.haveNodebalancer)
|
||||
insertRegion();
|
||||
};
|
||||
|
||||
// Callback for nodebalancer stats API call
|
||||
var displayStats = function(response)
|
||||
{
|
||||
// Insert dummy points in case of blank data
|
||||
if (!response.data.connections.length)
|
||||
response.data.connections = [[0,0]];
|
||||
if (!response.data.traffic.in.length)
|
||||
response.data.traffic.in = [[0,0]];
|
||||
if (!response.data.traffic.out.length)
|
||||
reponse.data.traffic.out = [[0,0]];
|
||||
|
||||
data.stats.cxn = [{
|
||||
"color": "#906",
|
||||
"fill": true,
|
||||
"points": response.data.connections
|
||||
}];
|
||||
data.stats.traffic = [
|
||||
{
|
||||
"color": "#32CD32",
|
||||
"fill": true,
|
||||
"points": response.data.traffic.out
|
||||
},
|
||||
{
|
||||
"color": "#03C",
|
||||
"fill": false,
|
||||
"points": response.data.traffic.in
|
||||
}
|
||||
];
|
||||
|
||||
// Draw graphs
|
||||
drawSeries(data.stats.cxn, ui.cxnGraph);
|
||||
drawSeries(data.stats.traffic, ui.trafficGraph);
|
||||
|
||||
// Update tables
|
||||
ui.cxnMax.innerHTML = data.stats.cxn[0].max;
|
||||
ui.cxnAvg.innerHTML = data.stats.cxn[0].avg.toFixed(2);
|
||||
ui.cxnLast.innerHTML = data.stats.cxn[0].points[data.stats.cxn[0].points.length - 1][1];
|
||||
ui.trafficOutMax.innerHTML = countSI(data.stats.traffic[1].max) + "b/s";
|
||||
ui.trafficOutAvg.innerHTML = countSI(data.stats.traffic[1].avg) + "b/s";
|
||||
ui.trafficOutLast.innerHTML = countSI(data.stats.traffic[1].points[data.stats.traffic[1].points.length - 1][1]) + "b/s";
|
||||
ui.trafficInMax.innerHTML = countSI(data.stats.traffic[0].max) + "b/s";
|
||||
ui.trafficInAvg.innerHTML = countSI(data.stats.traffic[0].avg) + "b/s";
|
||||
ui.trafficInLast.innerHTML = countSI(data.stats.traffic[0].points[data.stats.traffic[0].points.length - 1][1]) + "b/s";
|
||||
};
|
||||
|
||||
// Click handler for config remove link
|
||||
var handleConfigRemove = function(event)
|
||||
{
|
||||
if (!confirm("Are you sure you want to delete this config?"))
|
||||
return;
|
||||
|
||||
var nbcid = event.currentTarget.id.substring(elements.removePrefix.length);
|
||||
apiDelete("/nodebalancers/" + data.params.nbid + "/configs/" + nbcid, function()
|
||||
{
|
||||
location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
// Click handler for save button
|
||||
var handleSave = function(event)
|
||||
{
|
||||
if (event.currentTarget.disabled)
|
||||
return;
|
||||
|
||||
var req = {
|
||||
"label": ui.inputLabel.value,
|
||||
"client_conn_throttle": parseInt(ui.throttle.value),
|
||||
"tags": []
|
||||
};
|
||||
|
||||
if (ui.tags.value.length)
|
||||
req.tags = ui.tags.value.split(",");
|
||||
|
||||
apiPut("/nodebalancers/" + data.params.nbid, req, function(response)
|
||||
{
|
||||
location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
// Display region info
|
||||
var insertRegion = function()
|
||||
{
|
||||
var regionData = null;
|
||||
for (var i = 0; i < data.regions.length; i++) {
|
||||
if (data.regions[i].id == data.nodebalancer.region) {
|
||||
regionData = data.regions[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (regionData && regionData.label && regionData.label.length)
|
||||
ui.location.innerHTML = regionData.label;
|
||||
else if (regionNames[data.nodebalancer.region])
|
||||
ui.location.innerHTML = regionNames[data.nodebalancer.region];
|
||||
else
|
||||
ui.location.innerHTML = data.nodebalancer.region;
|
||||
};
|
||||
|
||||
// Initial setup
|
||||
var setup = function()
|
||||
{
|
||||
// Parse URL parameters
|
||||
data.params = parseParams();
|
||||
|
||||
// We need a NodeBalancer ID, so die if we don't have it
|
||||
if (!data.params.nbid) {
|
||||
alert("No NodeBalancer ID supplied!");
|
||||
return;
|
||||
}
|
||||
|
||||
setupHeader();
|
||||
|
||||
// Update links on page to include proper Linode ID
|
||||
var anchors = document.getElementsByTagName("a");
|
||||
for (var i = 0; i < anchors.length; i++)
|
||||
anchors[i].href = anchors[i].href.replace("nbid=0", "nbid=" + data.params.nbid);
|
||||
|
||||
// Get element references
|
||||
ui.configTable = document.getElementById(elements.configTable);
|
||||
ui.cxnAvg = document.getElementById(elements.cxnAvg);
|
||||
ui.cxnGraph = document.getElementById(elements.cxnGraph);
|
||||
ui.cxnLast = document.getElementById(elements.cxnLast);
|
||||
ui.cxnMax = document.getElementById(elements.cxnMax);
|
||||
ui.hostname = document.getElementById(elements.hostname);
|
||||
ui.inputLabel = document.getElementById(elements.inputLabel);
|
||||
ui.ipv4 = document.getElementById(elements.ipv4);
|
||||
ui.ipv6 = document.getElementById(elements.ipv6);
|
||||
ui.loadingConfigs = document.getElementById(elements.loadingConfigs);
|
||||
ui.location = document.getElementById(elements.location);
|
||||
ui.nodebalancerLabel = document.getElementById(elements.nodebalancerLabel);
|
||||
ui.nodebalancerTag = document.getElementById(elements.nodebalancerTag);
|
||||
ui.nodebalancerTagLink = document.getElementById(elements.nodebalancerTagLink);
|
||||
ui.saveButton = document.getElementById(elements.saveButton);
|
||||
ui.tableLabel = document.getElementById(elements.tableLabel);
|
||||
ui.tags = document.getElementById(elements.tags);
|
||||
ui.throttle = document.getElementById(elements.throttle);
|
||||
ui.trafficGraph = document.getElementById(elements.trafficGraph);
|
||||
ui.trafficInAvg = document.getElementById(elements.trafficInAvg);
|
||||
ui.trafficInLast = document.getElementById(elements.trafficInLast);
|
||||
ui.trafficInMax = document.getElementById(elements.trafficInMax);
|
||||
ui.trafficOutAvg = document.getElementById(elements.trafficOutAvg);
|
||||
ui.trafficOutLast = document.getElementById(elements.trafficOutLast);
|
||||
ui.trafficOutMax = document.getElementById(elements.trafficOutMax);
|
||||
ui.transferred = document.getElementById(elements.transferred);
|
||||
|
||||
// Attach event handlers
|
||||
ui.saveButton.addEventListener("click", handleSave);
|
||||
|
||||
// Set graph resolutions
|
||||
ui.cxnGraph.height = ui.cxnGraph.clientHeight;
|
||||
ui.cxnGraph.width = ui.cxnGraph.clientWidth;
|
||||
ui.trafficGraph.height = ui.trafficGraph.clientHeight;
|
||||
ui.trafficGraph.width = ui.trafficGraph.clientWidth;
|
||||
|
||||
// Get data from the API
|
||||
apiGet("/nodebalancers/" + data.params.nbid, displayNodebalancer, null);
|
||||
apiGet("/regions", displayRegions, null);
|
||||
apiGet("/nodebalancers/" + data.params.nbid + "/configs", displayConfigs, null);
|
||||
apiGet("/nodebalancers/" + data.params.nbid + "/stats", displayStats, null);
|
||||
};
|
||||
|
||||
// Attach onload handler
|
||||
window.addEventListener("load", setup);
|
||||
})();
|
162
nodebalancers/balancer/index.shtml
Normal file
162
nodebalancers/balancer/index.shtml
Normal file
@ -0,0 +1,162 @@
|
||||
<!--
|
||||
This file is part of Linode Manager Classic.
|
||||
|
||||
Linode Manager Classic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Linode Manager Classic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>LMC - NodeBalancers</title>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" type="text/css" href="balancer.css" />
|
||||
<script src="balancer.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/nodebalancers">NodeBalancers</a> » <span id="nodebalancer-tag"><a id="nodebalancer-tag-link" href=""></a> » </span><span id="nodebalancer-label" class="top-links-title"></span></div>
|
||||
<div id="nodebalancer-details">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td colspan="7">Configurations</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Port</td>
|
||||
<td>Protocol</td>
|
||||
<td>Algorithm</td>
|
||||
<td>Session Stickiness</td>
|
||||
<td>Health Check Method</td>
|
||||
<td>Node Status</td>
|
||||
<td class="center-cell">Options</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="config-table">
|
||||
<tr id="loading-configs" class="lmc-tr3">
|
||||
<td colspan="7">Loading configurations...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="sub-links"><a href="/nodebalancers/config?nbid=0&nbcid=0">Create Configuration</a></p>
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td id="table-label" colspan="3"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">NodeBalancer Settings</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="settings-table">
|
||||
<tr class="lmc-tr3">
|
||||
<td>Hostname</td>
|
||||
<td id="hostname"></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>IPv4 Address</td>
|
||||
<td id="ipv4"></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>IPv6 Address</td>
|
||||
<td id="ipv6"></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Location</td>
|
||||
<td id="location"></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Transferred this Month</td>
|
||||
<td id="transferred"></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>NodeBalancer Label</td>
|
||||
<td><input id="input-label" type="text" value="" size="24" /></td>
|
||||
<td class="info">Rename your NodeBalancer</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Client Connection Throttle</td>
|
||||
<td><input id="throttle" type="number" min="0" max="20" value="0" size="4" /></td>
|
||||
<td class="info">To help mitigate abuse, throttle connections from a single client IP to this number per second. 0 to disable.</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Tags</td>
|
||||
<td><input id="tags" type="text" size="24" /> (comma-separated)</td>
|
||||
<td class="info">Group NodeBalancers together on the NodeBalancers tab using tags!</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td></td>
|
||||
<td><button disabled id="save-button" type="button">Save Changes</button></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Graphs</h3>
|
||||
<div class="lmc-graph">
|
||||
<h4>Connections (CXN/s) - Last 24 Hours</h4>
|
||||
<canvas id="cxn-graph"></canvas>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Max</td>
|
||||
<td>Avg</td>
|
||||
<td>Last</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><div id="cxn-color" class="lmc-graph-color"></div> Connections</td>
|
||||
<td id="cxn-max"></td>
|
||||
<td id="cxn-avg"></td>
|
||||
<td id="cxn-last"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Traffic (bits/s) - Last 24 Hours</h4>
|
||||
<canvas id="traffic-graph"></canvas>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Max</td>
|
||||
<td>Avg</td>
|
||||
<td>Last</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><div id="traffic-out-color" class="lmc-graph-color"></div> Outgoing</td>
|
||||
<td id="traffic-out-max"></td>
|
||||
<td id="traffic-out-avg"></td>
|
||||
<td id="traffic-out-last"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div id="traffic-in-color" class="lmc-graph-color"></div> Incoming</td>
|
||||
<td id="traffic-in-max"></td>
|
||||
<td id="traffic-in-avg"></td>
|
||||
<td id="traffic-in-last"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
43
nodebalancers/config/config.css
Normal file
43
nodebalancers/config/config.css
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@import url('/global.css');
|
||||
|
||||
.center-cell {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.check-show {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#config {
|
||||
padding: 0px 15px 15px;
|
||||
}
|
||||
|
||||
#config-table tbody:not(.lmc-tbody-head) tr td:first-of-type {
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#config-table tbody:not(.lmc-tbody-head) tr td:not(:last-of-type) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.protocol-show {
|
||||
display: none;
|
||||
}
|
394
nodebalancers/config/config.js
Normal file
394
nodebalancers/config/config.js
Normal file
@ -0,0 +1,394 @@
|
||||
/*
|
||||
* This file is part of Linode Manager Classic.
|
||||
*
|
||||
* Linode Manager Classic is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Linode Manager Classic is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, apiDelete, apiGet, apiPost, apiPut, parseParams, setupHeader } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
// Element names specific to this page
|
||||
elements.algorithm = "algorithm";
|
||||
elements.centerCell = "center-cell";
|
||||
elements.checkAttempts = "check-attempts";
|
||||
elements.checkBody = "check-body";
|
||||
elements.checkInterval = "check-interval";
|
||||
elements.checkPath = "check-path";
|
||||
elements.checkShow = "check-show";
|
||||
elements.checkTimeout = "check-timeout";
|
||||
elements.checkType = "check-type";
|
||||
elements.cipherSuite = "cipher-suite";
|
||||
elements.commonName = "common-name";
|
||||
elements.configLabel = "config-label";
|
||||
elements.fingerprint = "fingerprint";
|
||||
elements.lmcRow = "lmc-tr1";
|
||||
elements.lmcRowAlt = "lmc-tr2";
|
||||
elements.nodebalancerLabel = "nodebalancer-label";
|
||||
elements.nodebalancerTag = "nodebalancer-tag";
|
||||
elements.nodebalancerTagLink = "nodebalancer-tag-link";
|
||||
elements.nodesTable = "nodes-table";
|
||||
elements.nonzero = "nonzero";
|
||||
elements.passive = "passive";
|
||||
elements.port = "port";
|
||||
elements.protocol = "protocol";
|
||||
elements.protocolShow = "protocol-show";
|
||||
elements.proxyProtocol = "proxy-protocol";
|
||||
elements.removePrefix = "remove-node-";
|
||||
elements.replaceCert = "replace-cert";
|
||||
elements.replaceCertOnly = "replace-cert-only";
|
||||
elements.replaceLink = "replace-link";
|
||||
elements.saveButton = "save-button";
|
||||
elements.sslCert = "ssl-cert";
|
||||
elements.sslKey = "ssl-key";
|
||||
elements.stickiness = "stickiness";
|
||||
|
||||
// Data recieved from API calls
|
||||
var data = {};
|
||||
data.config = {};
|
||||
data.nodebalancer = {};
|
||||
data.nodes = [];
|
||||
|
||||
// Static references to UI elements
|
||||
var ui = {};
|
||||
ui.algorithm = {};
|
||||
ui.checkAttempts = {};
|
||||
ui.checkBody = {};
|
||||
ui.checkInterval = {};
|
||||
ui.checkPath = {};
|
||||
ui.checkShow = [];
|
||||
ui.checkTimeout = {};
|
||||
ui.checkType = {};
|
||||
ui.cipherSuite = {};
|
||||
ui.commonName = {};
|
||||
ui.configLabel = {};
|
||||
ui.fingerprint = {};
|
||||
ui.nodebalancerLabel = {};
|
||||
ui.nodebalancerTag = {};
|
||||
ui.nodebalancerTagLink = {};
|
||||
ui.nodesTable = {};
|
||||
ui.passive = {};
|
||||
ui.port = {};
|
||||
ui.protocol = {};
|
||||
ui.protocolShow = [];
|
||||
ui.proxyProtocol = {};
|
||||
ui.saveButton = {};
|
||||
ui.replaceLink = {};
|
||||
ui.sslCert = {};
|
||||
ui.sslKey = {};
|
||||
ui.stickiness = {};
|
||||
|
||||
// Temporary State
|
||||
var state = {};
|
||||
state.replaceCert = true;
|
||||
|
||||
// Create a row for the nodes table
|
||||
var createNodeRow = function(node, alt)
|
||||
{
|
||||
var row = document.createElement("tr");
|
||||
if (alt)
|
||||
row.className = elements.lmcRowAlt;
|
||||
else
|
||||
row.className = elements.lmcRow;
|
||||
|
||||
var label = document.createElement("td");
|
||||
label.innerHTML = node.label;
|
||||
row.appendChild(label);
|
||||
|
||||
var address = document.createElement("td");
|
||||
var lastColon = node.address.lastIndexOf(":");
|
||||
address.innerHTML = node.address.slice(0, lastColon);
|
||||
row.appendChild(address);
|
||||
|
||||
var port = document.createElement("td");
|
||||
port.innerHTML = node.address.slice(lastColon + 1);
|
||||
row.appendChild(port);
|
||||
|
||||
var weight = document.createElement("td");
|
||||
weight.innerHTML = node.weight;
|
||||
row.appendChild(weight);
|
||||
|
||||
var mode = document.createElement("td");
|
||||
mode.innerHTML = node.mode.charAt(0).toUpperCase() + node.mode.slice(1);
|
||||
row.appendChild(mode);
|
||||
|
||||
var status = document.createElement("td");
|
||||
status.innerHTML = node.status;
|
||||
row.appendChild(status);
|
||||
|
||||
var options = document.createElement("td");
|
||||
options.className = elements.centerCell;
|
||||
var editLink = document.createElement("a");
|
||||
editLink.href = "/nodebalancers/node?nbid=" + data.params.nbid + "&nbcid=" + data.params.nbcid + "&nbnid=" + node.id;
|
||||
editLink.innerHTML = "Edit";
|
||||
var separator = document.createElement("span");
|
||||
separator.innerHTML = " | ";
|
||||
var removeLink = document.createElement("a");
|
||||
removeLink.id = elements.removePrefix + node.id;
|
||||
removeLink.href = "#";
|
||||
removeLink.innerHTML = "Remove";
|
||||
removeLink.addEventListener("click", handleRemoveNode);
|
||||
options.appendChild(editLink);
|
||||
options.appendChild(separator);
|
||||
options.appendChild(removeLink);
|
||||
row.appendChild(options);
|
||||
|
||||
return row;
|
||||
};
|
||||
|
||||
// Callback for config API call
|
||||
var displayConfig = function(response)
|
||||
{
|
||||
data.config = response;
|
||||
|
||||
ui.configLabel.innerHTML = "Port " + data.config.port;
|
||||
ui.port.value = data.config.port;
|
||||
ui.protocol.value = data.config.protocol;
|
||||
ui.proxyProtocol.value = data.config.proxy_protocol;
|
||||
ui.algorithm.value = data.config.algorithm;
|
||||
ui.stickiness.value = data.config.stickiness;
|
||||
ui.commonName.innerHTML = data.config.ssl_commonname;
|
||||
ui.fingerprint.innerHTML = data.config.ssl_fingerprint;
|
||||
ui.cipherSuite.value = data.config.cipher_suite;
|
||||
ui.checkType.value = data.config.check;
|
||||
ui.checkInterval.value = data.config.check_interval;
|
||||
ui.checkTimeout.value = data.config.check_timeout;
|
||||
ui.checkAttempts.value = data.config.check_attempts;
|
||||
ui.checkPath.value = data.config.check_path;
|
||||
ui.checkBody.value = data.config.check_body;
|
||||
ui.passive.checked = data.config.check_passive;
|
||||
state.replaceCert = (!data.config.ssl_key || data.config.ssl_key.indexOf("<REDACTED>") == -1);
|
||||
|
||||
ui.saveButton.disabled = false;
|
||||
|
||||
showHideChecks();
|
||||
showHideProtocol();
|
||||
};
|
||||
|
||||
// Callback for nodebalancer API call
|
||||
var displayNodebalancer = function(response)
|
||||
{
|
||||
data.nodebalancer = response;
|
||||
|
||||
// Set page title and header stuff
|
||||
if (document.title.indexOf("//") == -1)
|
||||
document.title += " // Edit " + data.nodebalancer.label;
|
||||
ui.nodebalancerLabel.innerHTML = data.nodebalancer.label;
|
||||
if (data.nodebalancer.tags.length == 1) {
|
||||
ui.nodebalancerTagLink.href = "/nodebalancers?tag=" + data.nodebalancer.tags[0];
|
||||
ui.nodebalancerTagLink.innerHTML = "(" + data.nodebalancer.tags[0] + ")";
|
||||
ui.nodebalancerTag.style.display = "inline";
|
||||
} else {
|
||||
ui.nodebalancerTag.style.display = "none";
|
||||
}
|
||||
};
|
||||
|
||||
// Callback for nodes API call
|
||||
var displayNodes = function(response)
|
||||
{
|
||||
data.nodes = data.nodes.concat(response.data);
|
||||
|
||||
// Request the next page if there are more pages
|
||||
if (response.page != response.pages) {
|
||||
apiGet("/nodebalancers/" + data.params.nbid + "/configs/" + data.params.nbcid + "/nodes?page=" + (response.page + 1), displayNodes, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert nodes into table
|
||||
for (var i = 0; i < data.nodes.length; i++)
|
||||
ui.nodesTable.appendChild(createNodeRow(data.nodes[i], i % 2));
|
||||
};
|
||||
|
||||
// Remove node handler
|
||||
var handleRemoveNode = function(event)
|
||||
{
|
||||
if (!confirm("Are you sure you want to remove this node?"))
|
||||
return;
|
||||
|
||||
var nbnid = event.currentTarget.id.substring(elements.removePrefix.length);
|
||||
apiDelete("/nodebalancers/" + data.params.nbid + "/configs/" + data.params.nbcid + "/nodes/" + nbnid, function()
|
||||
{
|
||||
location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
// Replace link handler
|
||||
var handleReplace = function(event)
|
||||
{
|
||||
state.replaceCert = true;
|
||||
showHideProtocol();
|
||||
};
|
||||
|
||||
// Save button handler
|
||||
var handleSave = function(event)
|
||||
{
|
||||
if (event.currentTarget.disabled)
|
||||
return;
|
||||
|
||||
var req = {
|
||||
"port": parseInt(ui.port.value),
|
||||
"protocol": ui.protocol.value,
|
||||
"algorithm": ui.algorithm.value,
|
||||
"stickiness": ui.stickiness.value,
|
||||
"check": ui.checkType.value,
|
||||
"check_passive": ui.passive.checked
|
||||
};
|
||||
|
||||
if (ui.protocol.value == "tcp") {
|
||||
req.proxy_protocol = ui.proxyProtocol.value;
|
||||
} else if (ui.protocol.value == "https") {
|
||||
req.cipher_suite = ui.cipherSuite.value;
|
||||
if (state.replaceCert) {
|
||||
req.ssl_cert = ui.sslCert.value;
|
||||
req.ssl_key = ui.sslKey.value;
|
||||
}
|
||||
}
|
||||
|
||||
switch (ui.checkType.value) {
|
||||
case "http_body":
|
||||
req.check_body = ui.checkBody.value;
|
||||
case "http":
|
||||
req.check_path = ui.checkPath.value;
|
||||
case "connection":
|
||||
req.check_interval = parseInt(ui.checkInterval.value);
|
||||
req.check_timeout = parseInt(ui.checkTimeout.value);
|
||||
req.check_attempts = parseInt(ui.checkAttempts.value);
|
||||
}
|
||||
|
||||
if (data.params.nbcid == 0) {
|
||||
apiPost("/nodebalancers/" + data.params.nbid + "/configs", req, function(response)
|
||||
{
|
||||
location.href = "/nodebalancers/config?nbid=" + data.params.nbid + "&nbcid=" + response.id;
|
||||
});
|
||||
} else {
|
||||
apiPut("/nodebalancers/" + data.params.nbid + "/configs/" + data.params.nbcid, req, function(response)
|
||||
{
|
||||
location.href = "/nodebalancers/balancer?nbid=" + data.params.nbid;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Initial setup
|
||||
var setup = function()
|
||||
{
|
||||
// Parse URL parameters
|
||||
data.params = parseParams();
|
||||
|
||||
// We need a NodeBalancer ID, so die if we don't have it
|
||||
if (!data.params.nbid) {
|
||||
alert("No NodeBalancer ID supplied!");
|
||||
return;
|
||||
}
|
||||
|
||||
// We also need a config ID
|
||||
if (!data.params.nbcid) {
|
||||
alert("No config ID supplied!");
|
||||
return;
|
||||
}
|
||||
|
||||
setupHeader();
|
||||
|
||||
// Update links on page to include proper Linode ID
|
||||
var anchors = document.getElementsByTagName("a");
|
||||
for (var i = 0; i < anchors.length; i++) {
|
||||
anchors[i].href = anchors[i].href.replace("nbid=0", "nbid=" + data.params.nbid);
|
||||
anchors[i].href = anchors[i].href.replace("nbcid=0", "nbcid=" + data.params.nbcid);
|
||||
}
|
||||
|
||||
// Get element references
|
||||
ui.algorithm = document.getElementById(elements.algorithm);
|
||||
ui.checkAttempts = document.getElementById(elements.checkAttempts);
|
||||
ui.checkBody = document.getElementById(elements.checkBody);
|
||||
ui.checkInterval = document.getElementById(elements.checkInterval);
|
||||
ui.checkPath = document.getElementById(elements.checkPath);
|
||||
ui.checkShow = document.getElementsByClassName(elements.checkShow);
|
||||
ui.checkTimeout = document.getElementById(elements.checkTimeout);
|
||||
ui.checkType = document.getElementById(elements.checkType);
|
||||
ui.cipherSuite = document.getElementById(elements.cipherSuite);
|
||||
ui.commonName = document.getElementById(elements.commonName);
|
||||
ui.configLabel = document.getElementById(elements.configLabel);
|
||||
ui.fingerprint = document.getElementById(elements.fingerprint);
|
||||
ui.nodebalancerLabel = document.getElementById(elements.nodebalancerLabel);
|
||||
ui.nodebalancerTag = document.getElementById(elements.nodebalancerTag);
|
||||
ui.nodebalancerTagLink = document.getElementById(elements.nodebalancerTagLink);
|
||||
ui.nodesTable = document.getElementById(elements.nodesTable);
|
||||
ui.passive = document.getElementById(elements.passive);
|
||||
ui.port = document.getElementById(elements.port);
|
||||
ui.protocol = document.getElementById(elements.protocol);
|
||||
ui.protocolShow = document.getElementsByClassName(elements.protocolShow);
|
||||
ui.proxyProtocol = document.getElementById(elements.proxyProtocol);
|
||||
ui.replaceLink = document.getElementById(elements.replaceLink);
|
||||
ui.saveButton = document.getElementById(elements.saveButton);
|
||||
ui.sslCert = document.getElementById(elements.sslCert);
|
||||
ui.sslKey = document.getElementById(elements.sslKey);
|
||||
ui.stickiness = document.getElementById(elements.stickiness);
|
||||
|
||||
// Register event handlers
|
||||
ui.checkType.addEventListener("input", showHideChecks);
|
||||
ui.protocol.addEventListener("input", showHideProtocol);
|
||||
ui.replaceLink.addEventListener("click", handleReplace);
|
||||
ui.saveButton.addEventListener("click", handleSave);
|
||||
|
||||
// Get data from API
|
||||
apiGet("/nodebalancers/" + data.params.nbid, displayNodebalancer, null);
|
||||
if (parseInt(data.params.nbcid)) {
|
||||
apiGet("/nodebalancers/" + data.params.nbid + "/configs/" + data.params.nbcid, displayConfig, null);
|
||||
apiGet("/nodebalancers/" + data.params.nbid + "/configs/" + data.params.nbcid + "/nodes", displayNodes, null);
|
||||
} else {
|
||||
ui.configLabel.innerHTML = "Create Configuration";
|
||||
ui.saveButton.disabled = false;
|
||||
var hideElements = document.getElementsByClassName(elements.nonzero);
|
||||
for (var i = 0; i < hideElements.length; i++)
|
||||
hideElements[i].style.display = "none";
|
||||
}
|
||||
};
|
||||
|
||||
// Show/hide check rows
|
||||
var showHideChecks = function(event)
|
||||
{
|
||||
for (var i = 0; i < ui.checkShow.length; i++) {
|
||||
if (ui.checkShow[i].classList.contains(elements.checkShow + "-" + ui.checkType.value))
|
||||
ui.checkShow[i].style.display = "table-row";
|
||||
else
|
||||
ui.checkShow[i].style.display = "none";
|
||||
}
|
||||
};
|
||||
|
||||
// Show/hide protocol stuff
|
||||
var showHideProtocol = function(event)
|
||||
{
|
||||
for (var i = 0; i < ui.protocolShow.length; i++) {
|
||||
if (ui.protocolShow[i].classList.contains(elements.protocolShow + "-" + ui.protocol.value)) {
|
||||
if (ui.protocolShow[i].classList.contains(elements.replaceCert)) {
|
||||
if (ui.protocolShow[i].classList.contains(elements.replaceCertOnly) == state.replaceCert)
|
||||
ui.protocolShow[i].style.display = "table-row";
|
||||
else
|
||||
ui.protocolShow[i].style.display = "none";
|
||||
} else {
|
||||
ui.protocolShow[i].style.display = "table-row";
|
||||
}
|
||||
} else {
|
||||
ui.protocolShow[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
if (data.params.nbcid == 0 && ui.protocol.value == "http")
|
||||
ui.port.value = 80;
|
||||
if (data.params.nbcid == 0 && ui.protocol.value == "https")
|
||||
ui.port.value = 443;
|
||||
};
|
||||
|
||||
// Attach onload handler
|
||||
window.addEventListener("load", setup);
|
||||
})();
|
222
nodebalancers/config/index.shtml
Normal file
222
nodebalancers/config/index.shtml
Normal file
@ -0,0 +1,222 @@
|
||||
<!--
|
||||
This file is part of Linode Manager Classic.
|
||||
|
||||
Linode Manager Classic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Linode Manager Classic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>LMC - NodeBalancers</title>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" type="text/css" href="config.css" />
|
||||
<script src="config.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/nodebalancers">NodeBalancers</a> » <span id="nodebalancer-tag"><a id="nodebalancer-tag-link" href=""></a> » </span><a id="nodebalancer-label" href="/nodebalancers/balancer?nbid=0"></a> » <span id="config-label" class="top-links-title"></span></div>
|
||||
<div id="config">
|
||||
<table class="lmc-table nonzero">
|
||||
<thead>
|
||||
<tr>
|
||||
<td colspan="7">Nodes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Label</td>
|
||||
<td>Address</td>
|
||||
<td>Port</td>
|
||||
<td>Weight</td>
|
||||
<td>Mode</td>
|
||||
<td>Status</td>
|
||||
<td class="center-cell">Options</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="nodes-table"></tbody>
|
||||
</table>
|
||||
<p class="sub-links nonzero"><a href="/nodebalancers/node?nbid=0&nbcid=0&nbnid=0">Add Node</a></p>
|
||||
<table id="config-table" class="lmc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td colspan="3">Edit Configuration</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">Configuration Settings</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Port</td>
|
||||
<td><input id="port" type="number" min="1" max="65535" value="80" size="4" /></td>
|
||||
<td class="info">Listen on this port</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Protocol</td>
|
||||
<td>
|
||||
<select id="protocol">
|
||||
<option value="tcp">TCP</option>
|
||||
<option selected value="http">HTTP</option>
|
||||
<option value="https">HTTPS</option>
|
||||
</select>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3 protocol-show protocol-show-tcp">
|
||||
<td>Proxy Protocol</td>
|
||||
<td>
|
||||
<select id="proxy-protocol">
|
||||
<option selected value="none">None</option>
|
||||
<option value="v1">v1</option>
|
||||
<option value="v2">v2</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="info">Proxy protocol preserves initial TCP connection information. Consult the <a target="_blank" href="https://www.linode.com/docs/products/networking/nodebalancers/guides/proxy-protocol/">Proxy Protocol guide</a> for information on the differences between each option.</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Algorithm</td>
|
||||
<td>
|
||||
<select id="algorithm">
|
||||
<option selected value="roundrobin">Round Robin</option>
|
||||
<option value="leastconn">Least Connections</option>
|
||||
<option value="source">Source IP</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="info">Roundrobin assigns connections to each backend sequentially. Least connections choose the backend with the least number of current connections. Source uses the client's IPv4 address</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Session Stickiness</td>
|
||||
<td>
|
||||
<select id="stickiness">
|
||||
<option value="none">None</option>
|
||||
<option selected value="table">Table</option>
|
||||
<option value="http_cookie">HTTP Cookie</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="info">Route subsequent requests from a client to the same backend</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody class="lmc-tbody-head">
|
||||
<tr class="noshow">
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
<tr class="protocol-show protocol-show-https">
|
||||
<td colspan="3">SSL Settings</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr class="lmc-tr3 protocol-show protocol-show-https replace-cert">
|
||||
<td>Common Name</td>
|
||||
<td id="common-name"></td>
|
||||
<td class="info">Need help with SSL? - <a target="_blank" href="https://www.linode.com/docs/products/networking/nodebalancers/guides/configure/">NodeBalancer Reference Guide</a></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3 protocol-show protocol-show-https replace-cert">
|
||||
<td>Fingerprint</td>
|
||||
<td id="fingerprint" colspan="2"></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3 protocol-show protocol-show-https">
|
||||
<td>SSL Cipher Suite</td>
|
||||
<td>
|
||||
<select id="cipher-suite">
|
||||
<option selected value="recommended">Recommended</option>
|
||||
<option value="legacy">Legacy</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="info">Select 'recommended' to use only strong encryption. Select 'legacy' to enable weak encryption that supports older browsers. You really want to select 'recommended'. <a target="_blank" href="https://www.linode.com/docs/products/networking/nodebalancers/guides/configure/">NodeBalancer Reference Guide</a></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3 protocol-show protocol-show-https replace-cert">
|
||||
<td></td>
|
||||
<td><a id="replace-link" href="#">Replace Certificate</a></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3 protocol-show protocol-show-https replace-cert replace-cert-only">
|
||||
<td>SSL Certificate</td>
|
||||
<td colspan="2"><textarea id="ssl-cert" rows="6" cols="64" placeholder="Please provide your SSL certificate (including chained intermediate certificates if needed)"></textarea></td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3 protocol-show protocol-show-https replace-cert replace-cert-only">
|
||||
<td>Private Key</td>
|
||||
<td colspan="2"><textarea id="ssl-key" rows="6" cols="64" placeholder="Please provide your unpassphrased SSL private key"></textarea></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody class="lmc-tbody-head">
|
||||
<tr class="noshow">
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">Active Health Check</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Health Check Type</td>
|
||||
<td>
|
||||
<select id="check-type">
|
||||
<option selected value="none">None</option>
|
||||
<option value="connection">TCP Connection</option>
|
||||
<option value="http">HTTP Valid Status</option>
|
||||
<option value="http_body">HTTP Body Regex</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="info">Active health checks proactively check the health of the backend nodes. 'TCP Connection' requires a successful TCP handshake. 'HTTP Valid Status' requires a 2xx or 3xx response from the backend node. 'HTTP Body Regex' uses a regex to match against an expected result body.</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3 check-show check-show-connection check-show-http check-show-http_body">
|
||||
<td>Check Interval</td>
|
||||
<td><input id="check-interval" type="number" min="1" value="5" size="4" /></td>
|
||||
<td class="info">Seconds between health check probes</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3 check-show check-show-connection check-show-http check-show-http_body">
|
||||
<td>Check Timeout</td>
|
||||
<td><input id="check-timeout" type="number" min="1" max="30" value="3" size="4" /></td>
|
||||
<td class="info">Seconds to wait before considering the probe a failure. 1-30. Must be less than check interval.</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3 check-show check-show-connection check-show-http check-show-http_body">
|
||||
<td>Check Attempts</td>
|
||||
<td><input id="check-attempts" type="number" min="1" max="30" value="2" size="4" /></td>
|
||||
<td class="info">Number of failed probes before taking a node out of rotation. 1-30</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3 check-show check-show-http check-show-http_body">
|
||||
<td>Check HTTP Path</td>
|
||||
<td><input id="check-path" type="text" value="/" size="24" /></td>
|
||||
<td class="info">When check=http, the path to request</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3 check-show check-show-http_body">
|
||||
<td>Expected HTTP Body</td>
|
||||
<td><textarea id="check-body" rows="4" cols="40"></textarea></td>
|
||||
<td class="info">When check=http, a regex to match within the first 16KB of the response body</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody class="lmc-tbody-head">
|
||||
<tr class="noshow">
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">Passive Checks</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Enabled</td>
|
||||
<td><input checked id="passive" type="checkbox" /></td>
|
||||
<td class="info">Enable passive checks based on observing communication with backend nodes.</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td></td>
|
||||
<td><button disabled id="save-button" type="button">Save Changes</button></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
41
nodebalancers/index.shtml
Normal file
41
nodebalancers/index.shtml
Normal file
@ -0,0 +1,41 @@
|
||||
<!--
|
||||
This file is part of Linode Manager Classic.
|
||||
|
||||
Linode Manager Classic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Linode Manager Classic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>LMC - NodeBalancers</title>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" type="text/css" href="nodebalancers.css" />
|
||||
<script src="nodebalancers.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="nodebalancers">
|
||||
<span id="loading">Loading...</span>
|
||||
</div>
|
||||
<div id="transfer-pool">
|
||||
<p id="transfer-header">This Month's Network Transfer Pool</p>
|
||||
<div id="transfer-bar">
|
||||
<div id="bar-used"></div><div id="bar-remaining"></div>
|
||||
</div>
|
||||
<p id="transfer-details"><span id="transfer-used"></span> Used, <span id="transfer-remaining"></span> Remaining, <span id="transfer-quota"></span> Quota</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
87
nodebalancers/node/index.shtml
Normal file
87
nodebalancers/node/index.shtml
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>LMC - NodeBalancers</title>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" type="text/css" href="node.css" />
|
||||
<script src="node.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/nodebalancers">NodeBalancers</a> » <span id="nodebalancer-tag"><a id="nodebalancer-tag-link" href=""></a> » </span><a id="nodebalancer-label" href="/nodebalancers/balancer?nbid=0"></a> » <a id="config-label" href="/nodebalancers/config?nbid=0&nbcid=0">Port</a> » <span class="top-links-title">Edit Node</span></div>
|
||||
<div id="node">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td colspan="3">Edit Node</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">Node Settings</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Label</td>
|
||||
<td><input id="label" type="text" size="30" /></td>
|
||||
<td class="info">This backend node's label</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Address</td>
|
||||
<td>
|
||||
<select id="address">
|
||||
<option selected disabled value="0">Select A Linode</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="info">The private IP address of the backend Linode instance</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Port</td>
|
||||
<td><input id="port" type="number" min="1" max="65535" size="4" /></td>
|
||||
<td class="info">The backend port for this node</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Weight</td>
|
||||
<td><input id="weight" type="number" min="1" max="255" value="100" size="4" /></td>
|
||||
<td class="info">Load balancing weight, 1-255. Higher means more connections.</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td>Mode</td>
|
||||
<td>
|
||||
<select id="mode">
|
||||
<option value="accept">Accept</option>
|
||||
<option value="reject">Reject</option>
|
||||
<option value="backup">Backup</option>
|
||||
<option value="drain">Drain</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="info">The connections mode for this node</td>
|
||||
</tr>
|
||||
<tr class="lmc-tr3">
|
||||
<td></td>
|
||||
<td><button disabled id="save-button" type="button">Save Changes</button></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
27
nodebalancers/node/node.css
Normal file
27
nodebalancers/node/node.css
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@import url('/global.css');
|
||||
|
||||
#node {
|
||||
padding: 0px 15px 15px;
|
||||
}
|
||||
|
||||
tbody tr td:first-of-type {
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
255
nodebalancers/node/node.js
Normal file
255
nodebalancers/node/node.js
Normal file
@ -0,0 +1,255 @@
|
||||
/*
|
||||
* This file is part of Linode Manager Classic.
|
||||
*
|
||||
* Linode Manager Classic is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Linode Manager Classic is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, apiGet, apiPost, apiPut, parseParams, setupHeader } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
// Element names specific to this page
|
||||
elements.address = "address";
|
||||
elements.configLabel = "config-label";
|
||||
elements.label = "label";
|
||||
elements.mode = "mode";
|
||||
elements.nodebalancerLabel = "nodebalancer-label";
|
||||
elements.nodebalancerTag = "nodebalancer-tag";
|
||||
elements.nodebalancerTagLink = "nodebalancer-tag-link";
|
||||
elements.port = "port";
|
||||
elements.saveButton = "save-button";
|
||||
elements.weight = "weight";
|
||||
|
||||
// Data recieved from API calls
|
||||
var data = {};
|
||||
data.config = {};
|
||||
data.linodeFilters = {
|
||||
"ipv4": {
|
||||
"+contains": "192.168"
|
||||
}
|
||||
};
|
||||
data.linodes = [];
|
||||
data.nodebalancer = {};
|
||||
data.node = {};
|
||||
|
||||
// Static references to UI elements
|
||||
var ui = {};
|
||||
ui.address = {};
|
||||
ui.configLabel = {};
|
||||
ui.label = {};
|
||||
ui.mode = {};
|
||||
ui.nodebalancerLabel = {};
|
||||
ui.nodebalancerTag = {};
|
||||
ui.nodebalancerTagLink = {};
|
||||
ui.port = {};
|
||||
ui.saveButton = {};
|
||||
ui.weight = {};
|
||||
|
||||
// Callback for config API call
|
||||
var displayConfig = function(response)
|
||||
{
|
||||
data.config = response;
|
||||
|
||||
ui.configLabel.innerHTML = "Port " + data.config.port;
|
||||
if (!parseInt(data.params.nbnid))
|
||||
ui.port.value = data.config.port;
|
||||
};
|
||||
|
||||
// Callback for linodes API call
|
||||
var displayLinodes = function(response)
|
||||
{
|
||||
data.linodes = data.linodes.concat(response.data);
|
||||
|
||||
// Request the next page if there are more pages
|
||||
if (response.page != response.pages) {
|
||||
apiGet("/linode/instances?page=" + (response.page + 1), displayLinodes, data.linodeFilters);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add linodes to address selector
|
||||
for (var i = 0; i < data.linodes.length; i++) {
|
||||
var ipIndex = -1;
|
||||
for (var j = 0; j < data.linodes[i].ipv4.length; j++) {
|
||||
if (data.linodes[i].ipv4[j].indexOf("192.168.") == 0) {
|
||||
ipIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ipIndex == -1)
|
||||
continue;
|
||||
|
||||
var option = document.createElement("option");
|
||||
option.value = data.linodes[i].ipv4[j];
|
||||
option.innerHTML = data.linodes[i].ipv4[j] + " (" + data.linodes[i].label + ")";
|
||||
ui.address.appendChild(option);
|
||||
}
|
||||
|
||||
if (parseInt(data.params.nbnid))
|
||||
apiGet("/nodebalancers/" + data.params.nbid + "/configs/" + data.params.nbcid + "/nodes/" + data.params.nbnid, displayNode, null);
|
||||
else
|
||||
ui.saveButton.disabled = false;
|
||||
};
|
||||
|
||||
// Callback for nodebalancer API call
|
||||
var displayNodebalancer = function(response)
|
||||
{
|
||||
data.nodebalancer = response;
|
||||
|
||||
// Set page title and header stuff
|
||||
if (document.title.indexOf("//") == -1)
|
||||
document.title += " // Edit " + data.nodebalancer.label;
|
||||
ui.nodebalancerLabel.innerHTML = data.nodebalancer.label;
|
||||
if (data.nodebalancer.tags.length == 1) {
|
||||
ui.nodebalancerTagLink.href = "/nodebalancers?tag=" + data.nodebalancer.tags[0];
|
||||
ui.nodebalancerTagLink.innerHTML = "(" + data.nodebalancer.tags[0] + ")";
|
||||
ui.nodebalancerTag.style.display = "inline";
|
||||
} else {
|
||||
ui.nodebalancerTag.style.display = "none";
|
||||
}
|
||||
|
||||
// Get linodes
|
||||
data.linodeFilters.region = data.nodebalancer.region;
|
||||
apiGet("/linode/instances", displayLinodes, data.linodeFilters);
|
||||
};
|
||||
|
||||
// Callback for node API call
|
||||
var displayNode = function(response)
|
||||
{
|
||||
data.node = response;
|
||||
|
||||
ui.label.value = data.node.label;
|
||||
var lastColon = data.node.address.lastIndexOf(":");
|
||||
ui.address.value = data.node.address.slice(0, lastColon);
|
||||
ui.port.value = data.node.address.slice(lastColon + 1);
|
||||
ui.weight.value = data.node.weight;
|
||||
ui.mode.value = data.node.mode;
|
||||
|
||||
ui.saveButton.disabled = false;
|
||||
};
|
||||
|
||||
// Save button handler
|
||||
var handleSave = function(event)
|
||||
{
|
||||
if (event.currentTarget.disabled)
|
||||
return;
|
||||
|
||||
if (ui.address.value == "0")
|
||||
return;
|
||||
|
||||
var req = {
|
||||
"label": ui.label.value,
|
||||
"address": ui.address.value + ":" + ui.port.value,
|
||||
"weight": parseInt(ui.weight.value),
|
||||
"mode": ui.mode.value
|
||||
};
|
||||
|
||||
if (data.params.nbnid == 0) {
|
||||
apiPost("/nodebalancers/" + data.params.nbid + "/configs/" + data.params.nbcid + "/nodes", req, function(response)
|
||||
{
|
||||
location.href = "/nodebalancers/config?nbid=" + data.params.nbid + "&nbcid=" + data.params.nbcid;
|
||||
});
|
||||
} else {
|
||||
apiPut("/nodebalancers/" + data.params.nbid + "/configs/" + data.params.nbcid + "/nodes/" + data.params.nbnid, req, function(response)
|
||||
{
|
||||
location.href = "/nodebalancers/config?nbid=" + data.params.nbid + "&nbcid=" + data.params.nbcid;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Initial setup
|
||||
var setup = function()
|
||||
{
|
||||
// Parse URL parameters
|
||||
data.params = parseParams();
|
||||
|
||||
// We need a NodeBalancer ID, so die if we don't have it
|
||||
if (!data.params.nbid) {
|
||||
alert("No NodeBalancer ID supplied!");
|
||||
return;
|
||||
}
|
||||
|
||||
// We also need a config ID
|
||||
if (!data.params.nbcid) {
|
||||
alert("No config ID supplied!");
|
||||
return;
|
||||
}
|
||||
|
||||
// We also need a node ID
|
||||
if (!data.params.nbnid) {
|
||||
alert("No node ID supplied!");
|
||||
return;
|
||||
}
|
||||
|
||||
setupHeader();
|
||||
|
||||
// Update links on page to include proper Linode ID
|
||||
var anchors = document.getElementsByTagName("a");
|
||||
for (var i = 0; i < anchors.length; i++) {
|
||||
anchors[i].href = anchors[i].href.replace("nbid=0", "nbid=" + data.params.nbid);
|
||||
anchors[i].href = anchors[i].href.replace("nbcid=0", "nbcid=" + data.params.nbcid);
|
||||
}
|
||||
|
||||
// Get element references
|
||||
ui.address = document.getElementById(elements.address);
|
||||
ui.configLabel = document.getElementById(elements.configLabel);
|
||||
ui.label = document.getElementById(elements.label);
|
||||
ui.mode = document.getElementById(elements.mode);
|
||||
ui.nodebalancerLabel = document.getElementById(elements.nodebalancerLabel);
|
||||
ui.nodebalancerTag = document.getElementById(elements.nodebalancerTag);
|
||||
ui.nodebalancerTagLink = document.getElementById(elements.nodebalancerTagLink);
|
||||
ui.port = document.getElementById(elements.port);
|
||||
ui.saveButton = document.getElementById(elements.saveButton);
|
||||
ui.weight = document.getElementById(elements.weight);
|
||||
|
||||
// Register event handlers
|
||||
ui.saveButton.addEventListener("click", handleSave);
|
||||
|
||||
// Get data from API
|
||||
apiGet("/nodebalancers/" + data.params.nbid, displayNodebalancer, null);
|
||||
apiGet("/nodebalancers/" + data.params.nbid + "/configs/" + data.params.nbcid, displayConfig, null);
|
||||
};
|
||||
|
||||
// Show/hide check rows
|
||||
var showHideChecks = function(event)
|
||||
{
|
||||
for (var i = 0; i < ui.checkShow.length; i++) {
|
||||
if (ui.checkShow[i].classList.contains(elements.checkShow + "-" + ui.checkType.value))
|
||||
ui.checkShow[i].style.display = "table-row";
|
||||
else
|
||||
ui.checkShow[i].style.display = "none";
|
||||
}
|
||||
};
|
||||
|
||||
// Show/hide protocol stuff
|
||||
var showHideProtocol = function(event)
|
||||
{
|
||||
for (var i = 0; i < ui.protocolShow.length; i++) {
|
||||
if (ui.protocolShow[i].classList.contains(elements.protocolShow + "-" + ui.protocol.value)) {
|
||||
if (ui.protocolShow[i].classList.contains(elements.replaceCert)) {
|
||||
if (ui.protocolShow[i].classList.contains(elements.replaceCertOnly) == state.replaceCert)
|
||||
ui.protocolShow[i].style.display = "table-row";
|
||||
else
|
||||
ui.protocolShow[i].style.display = "none";
|
||||
} else {
|
||||
ui.protocolShow[i].style.display = "table-row";
|
||||
}
|
||||
} else {
|
||||
ui.protocolShow[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Attach onload handler
|
||||
window.addEventListener("load", setup);
|
||||
})();
|
64
nodebalancers/nodebalancers.css
Normal file
64
nodebalancers/nodebalancers.css
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* This file is part of Linode Manager Classic.
|
||||
*
|
||||
* Linode Manager Classic is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Linode Manager Classic is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@import url('/global.css');
|
||||
|
||||
#transfer-bar {
|
||||
border: 3px solid #999;
|
||||
margin: 0 auto;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
#bar-remaining {
|
||||
background-color: #EFEFEF;
|
||||
border: 2px solid #EFEFEF;
|
||||
display: none;
|
||||
font-size: 16px;
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#bar-used {
|
||||
background-color: #ADD370;
|
||||
border: 2px dashed #008000;
|
||||
display: none;
|
||||
font-size: 16px;
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.center-cell {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#nodebalancers {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#transfer-details {
|
||||
margin: 5px 0px;
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#transfer-header {
|
||||
font-weight: bold;
|
||||
margin: 50px 0px 5px;
|
||||
text-align: center;
|
||||
}
|
366
nodebalancers/nodebalancers.js
Normal file
366
nodebalancers/nodebalancers.js
Normal file
@ -0,0 +1,366 @@
|
||||
/*
|
||||
* This file is part of Linode Manager Classic.
|
||||
*
|
||||
* Linode Manager Classic is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Linode Manager Classic is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, regionNames, apiGet, countSI, parseParams, setupHeader } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
// Element names specific to this page
|
||||
elements.barRemaining = "bar-remaining";
|
||||
elements.barUsed = "bar-used";
|
||||
elements.centerCell = "center-cell";
|
||||
elements.info = "info";
|
||||
elements.lmcRow = "lmc-tr1";
|
||||
elements.lmcRowAlt = "lmc-tr2";
|
||||
elements.lmcTable = "lmc-table";
|
||||
elements.loading = "loading";
|
||||
elements.nodebalancerPortsPrefix = "nodebalancer-ports-";
|
||||
elements.nodebalancers = "nodebalancers";
|
||||
elements.nodebalancerStatusPrefix = "nodebalancer-nodestatus-";
|
||||
elements.nodebalancerTagPrefix = "nodebalancer-tag-";
|
||||
elements.subLinks = "sub-links";
|
||||
elements.transferQuota = "transfer-quota";
|
||||
elements.transferRemaining = "transfer-remaining";
|
||||
elements.transferUsed = "transfer-used";
|
||||
|
||||
// Data recieved from API calls
|
||||
var data = {};
|
||||
data.params = {};
|
||||
data.nodebalancers = [];
|
||||
data.nodebalancerTags = [];
|
||||
data.noTag = false;
|
||||
data.regions = [];
|
||||
|
||||
// Static references to UI elements
|
||||
var ui = {};
|
||||
ui.barRemaining = {};
|
||||
ui.barUsed = {};
|
||||
ui.loading = {};
|
||||
ui.nodebalancers = {};
|
||||
ui.nodebalancerTables = {};
|
||||
ui.transferQuota = {};
|
||||
ui.transferRemaining = {};
|
||||
ui.transferUsed = {};
|
||||
|
||||
// Temporary state
|
||||
var state = {};
|
||||
state.haveNodebalancers = false;
|
||||
state.haveRegions = false;
|
||||
|
||||
var createNodebalancerRow = function(nodebalancer, alt)
|
||||
{
|
||||
var row = document.createElement("tr");
|
||||
if (alt)
|
||||
row.className = elements.lmcRowAlt;
|
||||
else
|
||||
row.className = elements.lmcRow;
|
||||
var name = document.createElement("td");
|
||||
var nameLink = document.createElement("a");
|
||||
nameLink.href = "/nodebalancers/balancer?nbid=" + nodebalancer.id;
|
||||
nameLink.innerHTML = nodebalancer.label;
|
||||
name.appendChild(nameLink);
|
||||
var regionData = null;
|
||||
for (var i = 0; i < data.regions.length; i++) {
|
||||
if (data.regions[i].id == nodebalancer.region) {
|
||||
regionData = data.regions[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
var region = document.createElement("td");
|
||||
if (regionData && regionData.label && regionData.label.length)
|
||||
region.innerHTML = regionData.label;
|
||||
else if (regionNames[nodebalancer.region])
|
||||
region.innerHTML = regionNames[nodebalancer.region];
|
||||
else
|
||||
region.innerHTML = nodebalancer.region;
|
||||
var ip = document.createElement("td");
|
||||
ip.innerHTML = nodebalancer.ipv4;
|
||||
var ports = document.createElement("td");
|
||||
ports.id = elements.nodebalancerPortsPrefix + nodebalancer.id;
|
||||
var nodeStatus = document.createElement("td");
|
||||
nodeStatus.id = elements.nodebalancerStatusPrefix + nodebalancer.id;
|
||||
var transferred = document.createElement("td");
|
||||
transferred.innerHTML = countSI(nodebalancer.transfer.total * 1048576) + "B";
|
||||
var options = document.createElement("td");
|
||||
options.className = elements.centerCell;
|
||||
var editLink = document.createElement("a");
|
||||
editLink.href = "/nodebalancers/balancer?nbid=" + nodebalancer.id;
|
||||
editLink.innerHTML = "Edit";
|
||||
var optionsSeparator = document.createElement("span");
|
||||
optionsSeparator.innerHTML = " | ";
|
||||
var removeLink = document.createElement("a");
|
||||
removeLink.href = "/nodebalancers/remove?nbid=" + nodebalancer.id;
|
||||
removeLink.innerHTML = "Remove";
|
||||
options.appendChild(editLink);
|
||||
options.appendChild(optionsSeparator);
|
||||
options.appendChild(removeLink);
|
||||
row.appendChild(name);
|
||||
row.appendChild(region);
|
||||
row.appendChild(ip);
|
||||
row.appendChild(ports);
|
||||
row.appendChild(nodeStatus);
|
||||
row.appendChild(transferred);
|
||||
row.appendChild(options);
|
||||
|
||||
return row;
|
||||
};
|
||||
|
||||
var createNodebalancerTable = function(tag)
|
||||
{
|
||||
var table = document.createElement("table");
|
||||
table.id = elements.nodebalancerTagPrefix + tag;
|
||||
table.className = elements.lmcTable;
|
||||
var thead = document.createElement("thead");
|
||||
var headRow1 = document.createElement("tr");
|
||||
var title = document.createElement("td");
|
||||
if (tag.length == 0)
|
||||
title.innerHTML = "NodeBalancers";
|
||||
else
|
||||
title.innerHTML = tag;
|
||||
headRow1.appendChild(title);
|
||||
var headRow2 = document.createElement("tr");
|
||||
var cells = ["Label", "Location", "IP", "Ports", "Node Status", "Transferred", "Options"];
|
||||
title.colSpan = cells.length;
|
||||
for (var i = 0; i < cells.length; i++) {
|
||||
var cell = document.createElement("td");
|
||||
if (cells[i] == "Options")
|
||||
cell.className = elements.centerCell;
|
||||
cell.innerHTML = cells[i];
|
||||
headRow2.appendChild(cell);
|
||||
}
|
||||
thead.appendChild(headRow1);
|
||||
thead.appendChild(headRow2);
|
||||
var tbody = document.createElement("tbody");
|
||||
table.appendChild(thead);
|
||||
table.appendChild(tbody);
|
||||
ui.nodebalancerTables[tag] = tbody;
|
||||
|
||||
var subLinks = document.createElement("p");
|
||||
subLinks.className = elements.subLinks;
|
||||
var addNodebalancer = document.createElement("a");
|
||||
addNodebalancer.href = "/nodebalancers/add";
|
||||
if (tag.length > 0)
|
||||
addNodebalancer.href += "?tag=" + tag;
|
||||
addNodebalancer.innerHTML = "Add a NodeBalancer";
|
||||
subLinks.appendChild(addNodebalancer);
|
||||
|
||||
ui.nodebalancers.appendChild(table);
|
||||
ui.nodebalancers.appendChild(subLinks);
|
||||
};
|
||||
|
||||
// Callback for NB configs API call
|
||||
var displayConfigs = function(response)
|
||||
{
|
||||
// Find the index of this NB in the array
|
||||
var nbid = parseInt(response['_endpoint'].split("/")[2]);
|
||||
var nbindex = -1;
|
||||
for (var i = 0; i < data.nodebalancers.length; i++) {
|
||||
if (data.nodebalancers[i].id == nbid) {
|
||||
nbindex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nbindex == -1)
|
||||
return;
|
||||
|
||||
// Add configs to object
|
||||
data.nodebalancers[nbindex].configs = data.nodebalancers[nbindex].configs.concat(response.data);
|
||||
|
||||
// Request the next page if there are more pages
|
||||
if (response.page != response.pages) {
|
||||
apiGet("/nodebalancers/" + nbid + "/configs?page=" + (response.page + 1), displayConfigs, null);
|
||||
return;
|
||||
}
|
||||
|
||||
var ports = document.getElementById(elements.nodebalancerPortsPrefix + nbid);
|
||||
var status = document.getElementById(elements.nodebalancerStatusPrefix + nbid);
|
||||
var upTotal = 0;
|
||||
var downTotal = 0;
|
||||
|
||||
// Count the backend totals and insert port/config links
|
||||
if (!data.nodebalancers[nbindex].configs.length) {
|
||||
var addLink = document.createElement("a");
|
||||
addLink.href = "/nodebalancers/config?nbid=" + nbid + "&nbcid=0";
|
||||
addLink.innerHTML = "Add...";
|
||||
ports.appendChild(addLink);
|
||||
}
|
||||
|
||||
for (var i = 0; i < data.nodebalancers[nbindex].configs.length; i++) {
|
||||
upTotal += data.nodebalancers[nbindex].configs[i].nodes_status.up;
|
||||
downTotal += data.nodebalancers[nbindex].configs[i].nodes_status.down;
|
||||
|
||||
if (i > 0) {
|
||||
var separator = document.createElement("span");
|
||||
separator.innerHTML = ", ";
|
||||
ports.appendChild(separator);
|
||||
}
|
||||
var port = document.createElement("a");
|
||||
port.href = "/nodebalancers/config?nbid=" + nbid + "&nbcid=" + data.nodebalancers[nbindex].configs[i].id;
|
||||
port.innerHTML = data.nodebalancers[nbindex].configs[i].port;
|
||||
ports.appendChild(port);
|
||||
}
|
||||
|
||||
status.innerHTML = upTotal + " up, " + downTotal + " down";
|
||||
};
|
||||
|
||||
var displayNodebalancers = function(response)
|
||||
{
|
||||
// Add linodes to array
|
||||
data.nodebalancers = data.nodebalancers.concat(response.data);
|
||||
|
||||
// Add new tags to array
|
||||
for (var i = 0; i < response.data.length; i++) {
|
||||
if (response.data[i].tags.length == 0)
|
||||
data.noTag = true;
|
||||
|
||||
for (var j = 0; j < response.data[i].tags.length; j++) {
|
||||
if (!data.nodebalancerTags.includes(response.data[i].tags[j]))
|
||||
data.nodebalancerTags.push(response.data[i].tags[j]);
|
||||
}
|
||||
}
|
||||
|
||||
// Request the next page if there are more pages
|
||||
if (response.page != response.pages) {
|
||||
var progress = (response.page / response.pages) * 100;
|
||||
progress = progress.toFixed(0);
|
||||
ui.loading.innerHTML = "Loading " + progress + "%...";
|
||||
var filters = null;
|
||||
if (data.params.tag)
|
||||
filters = {
|
||||
"tags": data.params.tag
|
||||
};
|
||||
apiGet("/nodebalancers?page=" + (response.page + 1), displayNodebalancers, filters);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove tag filter if there are no results, otherwise redirect to add page
|
||||
if (!data.nodebalancers.length) {
|
||||
if (data.params.tag)
|
||||
location.href = "/nodebalancers";
|
||||
else
|
||||
location.href = "/nodebalancers/add";
|
||||
}
|
||||
|
||||
// Sort
|
||||
data.nodebalancerTags.sort();
|
||||
data.nodebalancers.sort(function(a, b)
|
||||
{
|
||||
return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
|
||||
});
|
||||
|
||||
// Create tables
|
||||
ui.loading.remove();
|
||||
if (data.noTag)
|
||||
createNodebalancerTable("");
|
||||
for (var i = 0; i < data.nodebalancerTags.length; i++)
|
||||
createNodebalancerTable(data.nodebalancerTags[i]);
|
||||
|
||||
state.haveNodebalancers = true;
|
||||
|
||||
// Insert linodes
|
||||
if (state.haveRegions)
|
||||
insertNodebalancers();
|
||||
};
|
||||
|
||||
var displayRegions = function(response)
|
||||
{
|
||||
// Add regions to array
|
||||
data.regions = data.regions.concat(response.data);
|
||||
|
||||
// Request the next page if there are more pages
|
||||
if (response.page != response.pages) {
|
||||
apiGet("/regions?page=" + (response.page + 1), getRegions, null);
|
||||
return;
|
||||
}
|
||||
|
||||
state.haveRegions = true;
|
||||
if (state.haveNodebalancers)
|
||||
insertNodebalancers();
|
||||
};
|
||||
|
||||
var displayTransfer = function(response)
|
||||
{
|
||||
// Get border width of bar segments from CSS sheet
|
||||
var remainingBorderWidth = 0;
|
||||
var usedBorderWidth = 0;
|
||||
for (var i = 0; i < document.styleSheets[0].cssRules.length; i++) {
|
||||
if (document.styleSheets[0].cssRules[i].selectorText == "#" + elements.barRemaining)
|
||||
remainingBorderWidth = Number.parseInt(document.styleSheets[0].cssRules[i].style.borderWidth) * 2;
|
||||
else if (document.styleSheets[0].cssRules[i].selectorText == "#" + elements.barUsed)
|
||||
usedBorderWidth = Number.parseInt(document.styleSheets[0].cssRules[i].style.borderWidth) * 2;
|
||||
}
|
||||
|
||||
var usage = (response.used / response.quota) * 100;
|
||||
usage = usage.toFixed(0);
|
||||
|
||||
if (usage != 0) {
|
||||
ui.barUsed.style = "display: inline-block; width: calc(" + usage + "% - " + usedBorderWidth + "px);";
|
||||
ui.barUsed.innerHTML = usage + "% Used";
|
||||
}
|
||||
if (usage != 100) {
|
||||
ui.barRemaining.style = "display: inline-block; width: calc(" + (100 - usage) + "% - " + remainingBorderWidth + "px);";
|
||||
ui.barRemaining.innerHTML = (100 - usage) + "% Remaining";
|
||||
}
|
||||
|
||||
ui.transferUsed.innerHTML = response.used + "GB";
|
||||
ui.transferRemaining.innerHTML = (response.quota - response.used) + "GB";
|
||||
ui.transferQuota.innerHTML = response.quota + "GB";
|
||||
};
|
||||
|
||||
var insertNodebalancers = function()
|
||||
{
|
||||
// Insert linodes into tables
|
||||
for (var i = 0; i < data.nodebalancers.length; i++) {
|
||||
if (data.nodebalancers[i].tags.length == 0)
|
||||
ui.nodebalancerTables[""].appendChild(createNodebalancerRow(data.nodebalancers[i], ui.nodebalancerTables[""].children.length % 2));
|
||||
for (var j = 0; j < data.nodebalancers[i].tags.length; j++)
|
||||
ui.nodebalancerTables[data.nodebalancers[i].tags[j]].appendChild(createNodebalancerRow(data.nodebalancers[i], ui.nodebalancerTables[data.nodebalancers[i].tags[j]].children.length % 2));
|
||||
|
||||
data.nodebalancers[i].configs = [];
|
||||
apiGet("/nodebalancers/" + data.nodebalancers[i].id + "/configs", displayConfigs, null);
|
||||
}
|
||||
};
|
||||
|
||||
var setup = function()
|
||||
{
|
||||
// Parse URL parameters
|
||||
data.params = parseParams();
|
||||
|
||||
ui.barRemaining = document.getElementById(elements.barRemaining);
|
||||
ui.barUsed = document.getElementById(elements.barUsed);
|
||||
ui.loading = document.getElementById(elements.loading);
|
||||
ui.nodebalancers = document.getElementById(elements.nodebalancers);
|
||||
ui.transferQuota = document.getElementById(elements.transferQuota);
|
||||
ui.transferRemaining = document.getElementById(elements.transferRemaining);
|
||||
ui.transferUsed = document.getElementById(elements.transferUsed);
|
||||
|
||||
setupHeader();
|
||||
|
||||
// Get linode and transfer info
|
||||
apiGet("/regions", displayRegions, null);
|
||||
apiGet("/account/transfer", displayTransfer, null);
|
||||
var filters = null;
|
||||
if (data.params.tag)
|
||||
filters = {
|
||||
"tags": data.params.tag
|
||||
};
|
||||
apiGet("/nodebalancers", displayNodebalancers, filters);
|
||||
};
|
||||
|
||||
// Attach onload handler
|
||||
window.addEventListener("load", setup);
|
||||
})();
|
@ -18,17 +18,18 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>LMC - Graphs</title>
|
||||
<title>LMC - Remove NodeBalancer</title>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" type="text/css" href="graphs.css" />
|
||||
<script src="graphs.js" type="module"></script>
|
||||
<link rel="stylesheet" type="text/css" href="remove.css" />
|
||||
<script src="remove.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/linode_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/linodes">Linodes</a> » <span id="linode-tag"><a id="linode-tag-link" href=""></a> » </span><a id="linode-label" href="/linodes/dashboard?lid=0"></a> » <span class="top-links-title">Graphs</span></div>
|
||||
<div id="graphs">
|
||||
<div id="top-links"><a href="/nodebalancers">NodeBalancers</a> » <a id="toplink-label" href="/nodebalancers/balancer?nbid=0"></a> » <span class="top-links-title">Remove</span></div>
|
||||
<div id="remove">
|
||||
<p>Are you sure you want to delete the "<span id="label"></span>" NodeBalancer?</p>
|
||||
<button id="delete-button" type="button">Yes, delete this sucker</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
@ -17,6 +17,6 @@
|
||||
|
||||
@import url('/global.css');
|
||||
|
||||
#graphs {
|
||||
#remove {
|
||||
padding: 0px 15px 15px;
|
||||
}
|
@ -15,38 +15,38 @@
|
||||
* along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { settings, elements, apiGet, parseParams, setupHeader } from "/global.js";
|
||||
import { settings, elements, apiGet, apiDelete, parseParams, setupHeader } from "/global.js";
|
||||
|
||||
(function()
|
||||
{
|
||||
// Element names specific to this page
|
||||
elements.linodeLabel = "linode-label";
|
||||
elements.linodeTag = "linode-tag";
|
||||
elements.linodeTagLink = "linode-tag-link";
|
||||
elements.deleteButton = "delete-button";
|
||||
elements.label = "label";
|
||||
elements.toplinkLabel = "toplink-label";
|
||||
|
||||
// Data recieved from API calls
|
||||
var data = {};
|
||||
data.linode = {};
|
||||
|
||||
// Static references to UI elements
|
||||
var ui = {};
|
||||
ui.linodeLabel = {};
|
||||
ui.linodeTag = {};
|
||||
ui.linodeTagLink = {};
|
||||
ui.deleteButton = {};
|
||||
ui.label = {};
|
||||
ui.toplinkLabel = {};
|
||||
|
||||
// Callback for linode details API call
|
||||
var displayDetails = function(response)
|
||||
// Callback for image API call
|
||||
var displayNodebalancer = function(response)
|
||||
{
|
||||
data.linode = response;
|
||||
ui.toplinkLabel.innerHTML = response.label;
|
||||
ui.label.innerHTML = response.label;
|
||||
};
|
||||
|
||||
// Set page title and header stuff
|
||||
document.title += " // " + data.linode.label;
|
||||
ui.linodeLabel.innerHTML = data.linode.label;
|
||||
if (data.linode.tags.length == 1) {
|
||||
ui.linodeTagLink.href = "/linodes?tag=" + data.linode.tags[0];
|
||||
ui.linodeTagLink.innerHTML = "(" + data.linode.tags[0] + ")";
|
||||
ui.linodeTag.style.display = "inline";
|
||||
}
|
||||
// Handler for delete button
|
||||
var handleDelete = function(event)
|
||||
{
|
||||
apiDelete("/nodebalancers/" + data.params.nbid, function(response)
|
||||
{
|
||||
location.href = "/nodebalancers";
|
||||
});
|
||||
};
|
||||
|
||||
// Initial setup
|
||||
@ -55,26 +55,29 @@ import { settings, elements, apiGet, parseParams, setupHeader } from "/global.js
|
||||
// Parse URL parameters
|
||||
data.params = parseParams();
|
||||
|
||||
// We need a Linode ID, so die if we don't have it
|
||||
if (!data.params.lid) {
|
||||
alert("No Linode ID supplied!");
|
||||
// We need an image ID, so die if we don't have it
|
||||
if (!data.params.nbid) {
|
||||
alert("No NodeBalancer ID supplied!");
|
||||
return;
|
||||
}
|
||||
|
||||
setupHeader();
|
||||
|
||||
// Update links on page to include proper Linode ID
|
||||
// Update links on page to include proper volume ID
|
||||
var anchors = document.getElementsByTagName("a");
|
||||
for (var i = 0; i < anchors.length; i++)
|
||||
anchors[i].href = anchors[i].href.replace("lid=0", "lid=" + data.params.lid);
|
||||
anchors[i].href = anchors[i].href.replace("nbid=0", "nbid=" + data.params.nbid);
|
||||
|
||||
// Get element references
|
||||
ui.linodeLabel = document.getElementById(elements.linodeLabel);
|
||||
ui.linodeTag = document.getElementById(elements.linodeTag);
|
||||
ui.linodeTagLink = document.getElementById(elements.linodeTagLink);
|
||||
ui.deleteButton = document.getElementById(elements.deleteButton);
|
||||
ui.label = document.getElementById(elements.label);
|
||||
ui.toplinkLabel = document.getElementById(elements.toplinkLabel);
|
||||
|
||||
// Attach event handlers
|
||||
ui.deleteButton.addEventListener("click", handleDelete);
|
||||
|
||||
// Get data from API
|
||||
apiGet("/linode/instances/" + data.params.lid, displayDetails, null);
|
||||
apiGet("/nodebalancers/" + data.params.nbid, displayNodebalancer, null);
|
||||
};
|
||||
|
||||
// Attach onload handler
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#add {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
.lmc-table>tbody:not(.lmc-tbody-head)>tr>td:first-of-type {
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/profile_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/profile">My Profile</a> » <a href="/profile/api">API Keys</a> » <span class="top-links-title">Add</span></div>
|
||||
<div id="add">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#api {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
.app-img {
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/profile_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/profile">My Profile</a> » <span class="top-links-title">API Keys</span></div>
|
||||
<div id="api">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#auth {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
tbody:not(.lmc-tbody-head) tr td:first-of-type {
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/profile_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/profile">My Profile</a> » <span class="top-links-title">Password & Authentication</span></div>
|
||||
<div id="auth">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/profile_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/profile">My Profile</a> » <span class="top-links-title">Lish Settings</span></div>
|
||||
<div id="lish">
|
||||
<p id="settings-saved">Settings saved.</p>
|
||||
<table class="lmc-table">
|
||||
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#lish {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
#keys {
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/profile_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/profile">My Profile</a> » <span class="top-links-title">Referrals</span></div>
|
||||
<div id="referrals">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
|
||||
#referrals {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
tbody:not(.lmc-tbody-head) tr td:first-of-type {
|
||||
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#add {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
tbody:not(.lmc-tbody-head) tr td:first-of-type {
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/profile_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/profile">My Profile</a> » <a href="/profile/ssh">SSH Keys</a> » <span class="top-links-title">Add</span></div>
|
||||
<div id="add">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/profile_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/profile">My Profile</a> » <span class="top-links-title">SSH Keys</span></div>
|
||||
<div id="ssh">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/profile_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/profile">My Profile</a> » <a href="/profile/ssh">SSH Keys</a> » <span class="top-links-title">Remove</span></div>
|
||||
<div id="remove">
|
||||
<p>Are you sure you want to delete the key <strong id="label"></strong>?</p>
|
||||
<button disabled id="remove-button" type="button">Yes, delete this sucker</button>
|
||||
|
@ -18,5 +18,5 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#remove {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
}
|
||||
|
||||
#ssh {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
td:nth-of-type(4) {
|
||||
|
@ -48,5 +48,5 @@
|
||||
}
|
||||
|
||||
#twofactor {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#add {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
tbody:not(.lmc-tbody-head) tr td:first-of-type {
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <a href="/user">Users</a> » <span class="top-links-title">Add</span></div>
|
||||
<div id="add">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#edit {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
tbody:not(.lmc-tbody-head) tr td:first-of-type {
|
||||
|
@ -25,6 +25,7 @@ import { settings, elements, apiGet, apiPut, md5, parseParams, setupHeader } fro
|
||||
elements.restrictedNo = "restricted-no";
|
||||
elements.restrictedYes = "restricted-yes";
|
||||
elements.saveButton = "save-button";
|
||||
elements.topUserLabel = "top-user-label";
|
||||
elements.user = "user";
|
||||
elements.userLabel = "user-label";
|
||||
|
||||
@ -38,6 +39,7 @@ import { settings, elements, apiGet, apiPut, md5, parseParams, setupHeader } fro
|
||||
ui.restrictedNo = {};
|
||||
ui.restrictedYes = {};
|
||||
ui.saveButton = {};
|
||||
ui.topUserLabel = {};
|
||||
ui.user = {};
|
||||
ui.userLabel = {};
|
||||
|
||||
@ -98,10 +100,12 @@ import { settings, elements, apiGet, apiPut, md5, parseParams, setupHeader } fro
|
||||
ui.restrictedNo = document.getElementById(elements.restrictedNo);
|
||||
ui.restrictedYes = document.getElementById(elements.restrictedYes);
|
||||
ui.saveButton = document.getElementById(elements.saveButton);
|
||||
ui.topUserLabel = document.getElementById(elements.topUserLabel);
|
||||
ui.user = document.getElementById(elements.user);
|
||||
ui.userLabel = document.getElementById(elements.userLabel);
|
||||
|
||||
// Populate username where we need it
|
||||
ui.topUserLabel.innerHTML = data.params.user;
|
||||
ui.userLabel.innerHTML = data.params.user;
|
||||
ui.pwReset.href += data.params.user;
|
||||
document.title += " - " + data.params.user;
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <a href="/user">Users</a> » <span id="top-user-label" class="top-links-title"></span></div>
|
||||
<div id="edit">
|
||||
<table class="lmc-table">
|
||||
<thead>
|
||||
|
@ -18,7 +18,7 @@
|
||||
@import url('/global.css');
|
||||
|
||||
#grants {
|
||||
padding: 15px 15px 15px;
|
||||
padding: 0 15px 15px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
|
@ -40,6 +40,7 @@ import { settings, elements, apiGet, apiPut, parseParams, setupHeader } from "/g
|
||||
elements.grantTables.stackscript = "ss-grants";
|
||||
elements.grantTables.volume = "volume-grants";
|
||||
elements.modifyLV = "modify-lv";
|
||||
elements.topUserLink = "top-user-link";
|
||||
elements.updateButton = "update-button";
|
||||
|
||||
// Data received from API calls
|
||||
@ -66,6 +67,7 @@ import { settings, elements, apiGet, apiPut, parseParams, setupHeader } from "/g
|
||||
ui.grantTables.stackscript = {};
|
||||
ui.grantTables.volume = {};
|
||||
ui.modifyLV = {};
|
||||
ui.topUserLink = {};
|
||||
ui.updateButton = {};
|
||||
|
||||
// Creates a row for a grant table
|
||||
@ -254,10 +256,13 @@ import { settings, elements, apiGet, apiPut, parseParams, setupHeader } from "/g
|
||||
for (var i in ui.grantTables)
|
||||
ui.grantTables[i] = document.getElementById(elements.grantTables[i]);
|
||||
ui.modifyLV = document.getElementById(elements.modifyLV);
|
||||
ui.topUserLink = document.getElementById(elements.topUserLink);
|
||||
ui.updateButton = document.getElementById(elements.updateButton);
|
||||
|
||||
// Populate username where we need it
|
||||
document.title += " - " + data.params.user;
|
||||
ui.topUserLink.href = "/user/edit?user=" + data.params.user;
|
||||
ui.topUserLink.innerHTML = data.params.user;
|
||||
|
||||
// Attach event handlers
|
||||
ui.updateButton.addEventListener("click", handleUpdate);
|
||||
|
@ -27,6 +27,7 @@ along with Linode Manager Classic. If not, see <https://www.gnu.org/licenses/>.
|
||||
<!--#include virtual="/include/header.html"-->
|
||||
<!--#include virtual="/include/account_subnav.html"-->
|
||||
<div id="main-content" class="wrapper">
|
||||
<div id="top-links"><a href="/account">Account</a> » <a href="/user">Users</a> » <a id="top-user-link"></a> » <span class="top-links-title">Grants</span></div>
|
||||
<div id="grants">
|
||||
<h2>Global Grants</h2>
|
||||
<input id="create-linode" type="checkbox" /> <label for="create-linode">Can add Linodes to this account ($)</label><br />
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user