diff --git a/README.md b/README.md
index 7f062c0..37cd307 100644
--- a/README.md
+++ b/README.md
@@ -19,9 +19,9 @@ This project is currently a work in progress. The following list provides a high
- [x] Account Details
- [x] User Profile Settings
- [x] Graphs
+ - [x] NodeBalancers
- [ ] Adding PayPal/GPay payment methods
- [ ] StackScripts
- - [ ] NodeBalancers
- [ ] Longview
- [ ] Support Tickets
@@ -34,4 +34,4 @@ Before reporting an issue, please search the issue tracker to see if the issue h
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. 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. Optionally, you can configure your web server to use the [404.html](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.
diff --git a/global.js b/global.js
index 58f9404..997beee 100644
--- a/global.js
+++ b/global.js
@@ -448,6 +448,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)
{
@@ -864,4 +882,4 @@ function translateKernel(slug, element)
apiGet("/linode/kernels/" + slug, callback, null);
}
-export { settings, elements, regionNames, countryContinents, apiDelete, apiGet, apiPost, apiPut, drawSeries, md5, migrateETA, oauthPost, oauthScopes, objPut, parseParams, setupHeader, eventTitles, timeString, translateKernel };
+export { settings, elements, regionNames, countryContinents, apiDelete, apiGet, apiPost, apiPut, countSI, drawSeries, md5, migrateETA, oauthPost, oauthScopes, objPut, parseParams, setupHeader, eventTitles, timeString, translateKernel };
diff --git a/linodes/dashboard/dashboard.js b/linodes/dashboard/dashboard.js
index 806ae56..13f6a96 100644
--- a/linodes/dashboard/dashboard.js
+++ b/linodes/dashboard/dashboard.js
@@ -15,7 +15,7 @@
* along with Linode Manager Classic. If not, see .
*/
-import { settings, elements, apiDelete, apiGet, apiPost, drawSeries, 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()
{
@@ -233,24 +233,6 @@ import { settings, elements, apiDelete, apiGet, apiPost, drawSeries, eventTitles
apiPost("/linode/instances/" + data.params.lid + "/boot", request, callback);
};
- // Convert an unqualified count into a string with an SI prefix (i.e. bytes to MB/GB/etc)
- var countSI = 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;
- };
-
// Generate a config profile table row
var createConfigRow = function(config)
{
diff --git a/login.js b/login.js
index 85b3a33..14e8117 100644
--- a/login.js
+++ b/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
diff --git a/nodebalancers/add/add.css b/nodebalancers/add/add.css
new file mode 100644
index 0000000..c7e052f
--- /dev/null
+++ b/nodebalancers/add/add.css
@@ -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 .
+ */
+
+@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);
+}
diff --git a/nodebalancers/add/add.js b/nodebalancers/add/add.js
new file mode 100644
index 0000000..f94e96f
--- /dev/null
+++ b/nodebalancers/add/add.js
@@ -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 .
+ */
+
+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);
+})();
diff --git a/nodebalancers/add/index.shtml b/nodebalancers/add/index.shtml
new file mode 100644
index 0000000..efae29d
--- /dev/null
+++ b/nodebalancers/add/index.shtml
@@ -0,0 +1,66 @@
+
+
+
+
+
+ LMC - Add a NodeBalancer
+
+
+
+
+
+
+
+
+
+
+
+
Location
+
+
+
+
+
+
+
+
+
+
+ Add this NodeBalancer!
+
+
+
+
+
diff --git a/nodebalancers/balancer/balancer.css b/nodebalancers/balancer/balancer.css
new file mode 100644
index 0000000..70b313e
--- /dev/null
+++ b/nodebalancers/balancer/balancer.css
@@ -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 .
+ */
+
+@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;
+}
diff --git a/nodebalancers/balancer/balancer.js b/nodebalancers/balancer/balancer.js
new file mode 100644
index 0000000..f6f3597
--- /dev/null
+++ b/nodebalancers/balancer/balancer.js
@@ -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 .
+ */
+
+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);
+})();
diff --git a/nodebalancers/balancer/index.shtml b/nodebalancers/balancer/index.shtml
new file mode 100644
index 0000000..5acaa0c
--- /dev/null
+++ b/nodebalancers/balancer/index.shtml
@@ -0,0 +1,162 @@
+
+
+
+
+
+ LMC - NodeBalancers
+
+
+
+
+
+
+
+
+
+
+
+
+ Configurations
+
+
+ Port
+ Protocol
+ Algorithm
+ Session Stickiness
+ Health Check Method
+ Node Status
+ Options
+
+
+
+
+ Loading configurations...
+
+
+
+
Create Configuration
+
+
Graphs
+
+
Connections (CXN/s) - Last 24 Hours
+
+
+
+
+
+ Max
+ Avg
+ Last
+
+
+
+
+
Connections
+
+
+
+
+
+
+
Traffic (bits/s) - Last 24 Hours
+
+
+
+
+
+ Max
+ Avg
+ Last
+
+
+
+
+
Outgoing
+
+
+
+
+
+
Incoming
+
+
+
+
+
+
+
+
+
+
+
diff --git a/nodebalancers/config/config.css b/nodebalancers/config/config.css
new file mode 100644
index 0000000..d97f786
--- /dev/null
+++ b/nodebalancers/config/config.css
@@ -0,0 +1,43 @@
+/*
+ * This file is part of Linode Manager Classic.
+ *
+ * Linode Manager Classic is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Linode Manager Classic is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Linode Manager Classic. If not, see .
+ */
+
+@import url('/global.css');
+
+.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;
+}
diff --git a/nodebalancers/config/config.js b/nodebalancers/config/config.js
new file mode 100644
index 0000000..bc2a502
--- /dev/null
+++ b/nodebalancers/config/config.js
@@ -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 .
+ */
+
+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("") == -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);
+})();
diff --git a/nodebalancers/config/index.shtml b/nodebalancers/config/index.shtml
new file mode 100644
index 0000000..280ae51
--- /dev/null
+++ b/nodebalancers/config/index.shtml
@@ -0,0 +1,222 @@
+
+
+
+
+
+ LMC - NodeBalancers
+
+
+
+
+
+
+
+
+
+
+
+
+ Nodes
+
+
+ Label
+ Address
+ Port
+ Weight
+ Mode
+ Status
+ Options
+
+
+
+
+
Add Node
+
+
+
+
+
diff --git a/nodebalancers/index.shtml b/nodebalancers/index.shtml
new file mode 100644
index 0000000..37c3f57
--- /dev/null
+++ b/nodebalancers/index.shtml
@@ -0,0 +1,41 @@
+
+
+
+
+
+ LMC - NodeBalancers
+
+
+
+
+
+
+
+
+ Loading...
+
+
+
+
+
Used, Remaining, Quota
+
+
+
+
diff --git a/nodebalancers/node/index.shtml b/nodebalancers/node/index.shtml
new file mode 100644
index 0000000..ab10ccc
--- /dev/null
+++ b/nodebalancers/node/index.shtml
@@ -0,0 +1,87 @@
+
+
+
+
+
+ LMC - NodeBalancers
+
+
+
+
+
+
+
+
+
diff --git a/nodebalancers/node/node.css b/nodebalancers/node/node.css
new file mode 100644
index 0000000..e30f31e
--- /dev/null
+++ b/nodebalancers/node/node.css
@@ -0,0 +1,27 @@
+/*
+ * This file is part of Linode Manager Classic.
+ *
+ * Linode Manager Classic is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Linode Manager Classic is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Linode Manager Classic. If not, see .
+ */
+
+@import url('/global.css');
+
+#node {
+ padding: 0px 15px 15px;
+}
+
+tbody tr td:first-of-type {
+ font-weight: bold;
+ text-align: right;
+}
diff --git a/nodebalancers/node/node.js b/nodebalancers/node/node.js
new file mode 100644
index 0000000..e4e2a8e
--- /dev/null
+++ b/nodebalancers/node/node.js
@@ -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 .
+ */
+
+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);
+})();
diff --git a/nodebalancers/nodebalancers.css b/nodebalancers/nodebalancers.css
new file mode 100644
index 0000000..7364ab1
--- /dev/null
+++ b/nodebalancers/nodebalancers.css
@@ -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 .
+ */
+
+@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;
+}
diff --git a/nodebalancers/nodebalancers.js b/nodebalancers/nodebalancers.js
new file mode 100644
index 0000000..12c2f27
--- /dev/null
+++ b/nodebalancers/nodebalancers.js
@@ -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 .
+ */
+
+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);
+})();
diff --git a/nodebalancers/remove/index.shtml b/nodebalancers/remove/index.shtml
new file mode 100644
index 0000000..7af7bab
--- /dev/null
+++ b/nodebalancers/remove/index.shtml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ LMC - Remove NodeBalancer
+
+
+
+
+
+
+
+
+
+
Are you sure you want to delete the " " NodeBalancer?
+
Yes, delete this sucker
+
+
+
+
diff --git a/nodebalancers/remove/remove.css b/nodebalancers/remove/remove.css
new file mode 100644
index 0000000..c66c72a
--- /dev/null
+++ b/nodebalancers/remove/remove.css
@@ -0,0 +1,22 @@
+/*
+ * This file is part of Linode Manager Classic.
+ *
+ * Linode Manager Classic is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Linode Manager Classic is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Linode Manager Classic. If not, see .
+ */
+
+@import url('/global.css');
+
+#remove {
+ padding: 0px 15px 15px;
+}
diff --git a/nodebalancers/remove/remove.js b/nodebalancers/remove/remove.js
new file mode 100644
index 0000000..51ff8c9
--- /dev/null
+++ b/nodebalancers/remove/remove.js
@@ -0,0 +1,85 @@
+/*
+ * This file is part of Linode Manager Classic.
+ *
+ * Linode Manager Classic is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Linode Manager Classic is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Linode Manager Classic. If not, see .
+ */
+
+import { settings, elements, apiGet, apiDelete, parseParams, setupHeader } from "/global.js";
+
+(function()
+{
+ // Element names specific to this page
+ elements.deleteButton = "delete-button";
+ elements.label = "label";
+ elements.toplinkLabel = "toplink-label";
+
+ // Data recieved from API calls
+ var data = {};
+
+ // Static references to UI elements
+ var ui = {};
+ ui.deleteButton = {};
+ ui.label = {};
+ ui.toplinkLabel = {};
+
+ // Callback for image API call
+ var displayNodebalancer = function(response)
+ {
+ ui.toplinkLabel.innerHTML = response.label;
+ ui.label.innerHTML = response.label;
+ };
+
+ // Handler for delete button
+ var handleDelete = function(event)
+ {
+ apiDelete("/nodebalancers/" + data.params.nbid, function(response)
+ {
+ location.href = "/nodebalancers";
+ });
+ };
+
+ // Initial setup
+ var setup = function()
+ {
+ // Parse URL parameters
+ data.params = parseParams();
+
+ // 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 volume 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.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("/nodebalancers/" + data.params.nbid, displayNodebalancer, null);
+ };
+
+ // Attach onload handler
+ window.addEventListener("load", setup);
+})();