123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284 |
- /*
- * EliasDB
- *
- * Copyright 2016 Matthias Ladkau. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
- package server
- /*
- LoginSRC is a simple login page.
- */
- const LoginSRC = `
- <!DOCTYPE html>
- <html>
- <head>
- <title>EliasDB Login</title>
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <style>
-
- body, html {
- height: 100%;
- }
- #wrapper {
- position:absolute;
- /* Uncomment this if you have a fancy background image
- background-image: url("/img/bg/bg.jpg");
- background-position: center;
- background-repeat: no-repeat;
- background-size: cover;
- */
- height: 100%;
- width:100%;
- }
- #login-panel {
- text-align: center;
- padding : 2em;
- max-width : 40em;
- width : 35%;
- min-width : 20em;
- margin: 15% auto;
- background: #ffffff;
- border-radius: 5px;
- }
- #login-panel .form-input, #login-panel h4 {
- margin-bottom: 1em;
- }
- #login-panel .input-icon {
- position: absolute;
- margin: 0.8em;
- display: inline-block;
- width: 1.2em;
- z-index: 1;
- }
- #login-panel .form-input {
- padding-left: 2.5em;
- display: inline-block;
- }
- #login-panel .btn {
- width: 100%;
- }
- </style>
- </head>
- <body>
- <div id="wrapper">
- <div id="login-panel">
- <h4>Login</h4>
- <form action="/api/login/" method="post">
- <input class="form-input input-lg" name="user" placeholder="Enter Login" autofocus><br>
- <input class="form-input input-lg" name="pass" type="password" placeholder="Enter Password"><br>
- <input class="btn btn-primary" type="submit" value="Login">
- </form>
- </div>
- </div>
- </body>
- </html>
- `
- /*
- TermSRC is the terminal HTML as a text blob.
- */
- const TermSRC = `
- <!doctype html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>Terminal</title>
- <style>
- body {
- background: #fff;
- font-family: 'verdana';
- font-size: 11px;
- margin: 0;
- min-width: 320px;
- }
- .t-header {
- background: linear-gradient(#000, #444);
- color: #fff;
- font-weight: bold;
- padding: 0 1em;
- margin: 0 0 1em 0;
- box-shadow: 3px 3px 3px rgba(50, 50, 50, 0.25);
- }
- .t-header h1 {
- display: inline-block;
- font-size: 18px;
- margin: 3px 0;
- }
- .t-terms {
- padding: 0 10%;
- display: inline-block;
- width: 80%
- }
- .t-terms .t-term {
- background: #EEEEEE;
- padding: 10px;
- border: #000000 2px solid;
- border-radius: 10px;
- margin: 3em 0 0 0;
- }
- .t-terms .t-term.t-input-term:hover {
- border-color: #888888;
- }
- .t-terms .t-term.t-output-term {
- white-space: pre-wrap;
- font-family: "Lucida Console", "Courier";
- font-size: 11px;
- }
- .t-terms .t-term.t-output-term.t-error {
- background: #FFBBBB;
- }
- .t-terms .t-term.t-output-term.t-normal {
- background: #B3D9FF;
- }
- .t-terms .t-term .t-result-table {
- width: 100%;
- border-spacing: 0;
- border-collapse: collapse;
- }
- .t-terms .t-term .t-result-table th {
- text-align: left;
- border-style: none none solid none;
- }
- .t-terms .t-term .t-result-table td {
- padding: 5px;
- }
- .t-terms .t-term .t-prompt {
- float: left;
- width: 15px;
- font-weight: bold;
- }
- .t-terms .t-term .t-input {
- display: inline-block;
- width: calc(100% - 15px);
- height: 10px;
- outline: none;
- }
- .t-terms .t-term .t-button {
- background: #EEEEEE;
- float: right;
- border: #000000 2px solid;
- margin: 2px;
- border-radius: 10px;
- font-weight: bold;
- }
- .t-terms .t-term .t-button:hover {
- color: #888888;
- border-color: #888888;
- }
- </style>
- </head>
- <body onload="t.main.init()">
- <div class="t-header"><h1 id="name"></h1> <h1 id="version"></h1></div>
- <div id="terms" class="t-terms"></div>
- <script>
- // Utility functions
- // =================
- if (t === undefined) {
- var t = {};
- }
- t.$ = function(id) { "use strict"; return document.getElementById(id); };
- t.copyObject = function (o1, o2) { "use strict"; for (var attr in o1) { o2[attr] = o1[attr]; } };
- t.mergeObject = function (o1, o2) { "use strict"; for (var attr in o1) { if(o2[attr] === undefined) { o2[attr] = o1[attr]; } } };
- t.cloneObject = function (o) { "use strict"; var r = {}; ge.copyObject(o, r); return r; };
- t.esc = function (str) { "use strict"; return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>' ); };
- t.unesc = function (str) { "use strict"; return str.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); };
- t.insert = function(element, child) { "use strict"; element.appendChild(child); return element; };
- t.insertBefore = function(element, child) { "use strict"; element.parentNode.insertBefore(child, element); };
- t.insertAfter = function(element, child) { "use strict"; element.parentNode.insertBefore(child, element.nextSibling); };
- t.create = function(tag, attrs) {
- "use strict";
- var element = document.createElement(tag);
- if (attrs !== undefined) {
- t.getObjectKeys(attrs).forEach(function (v) {
- element.setAttribute(v, attrs[v]);
- });
- }
- return element;
- };
- t.getObjectKeys = function (obj) {
- "use strict";
- var key, keys = [];
- for(key in obj) {
- if (obj.hasOwnProperty(key)) {
- keys.push(key);
- }
- }
- return keys;
- };
- t.getObjectValues = function (obj) {
- "use strict";
- var key, values = [];
- for(key in obj) {
- if (obj.hasOwnProperty(key)) {
- values.push(obj[key]);
- }
- }
- return values;
- };
- t.addEvent = function (element, eventName, func) {
- "use strict";
- if (element.addEventListener) {
- element.addEventListener(eventName, func, false);
- } else if (element.attachEvent) {
- element.attachEvent('on' + eventName, func);
- }
- };
- t.stopBubbleEvent = function (e) {
- "use strict";
- e = e ? e:window.event;
- if (e.stopPropagation) {
- e.stopPropagation();
- }
- if (e.cancelBubble !== null) {
- e.cancelBubble = true;
- }
- if (e.preventDefault) {
- e.preventDefault();
- }
- };
- t.bind = function () {
- "use strict";
- var f = arguments[0], t = Array.prototype.slice.call(arguments, 1);
- var a = t.splice(1);
- return function() {
- "use strict";
- return f.apply(t[0],
- a.concat(Array.prototype.slice.call(arguments, 0)));
- }
- };
- t.ajax = function (url, method, body, callbackOK, callbackError) {
- "use strict";
- var http = new XMLHttpRequest(),
- start;
- if (method === undefined) {
- method = "GET"
- }
- http.open(method, url, true);
- http.setRequestHeader("content-type", "application/json");
- http.setRequestHeader("accept", "application/json");
- http.onload = function () {
- var rtime = Date.now() - start;
- document.title = "Terminal (Last response time: "+ rtime +"ms)";
- console.log("Response time:", rtime);
- try {
- if (http.status === 200) {
- if (callbackOK) {
- if (http.response !== "") {
- callbackOK(JSON.parse(http.response));
- } else {
- callbackOK();
- }
- }
- } else {
- if (callbackError) {
- callbackError(http.response);
- } else {
- console.log(http.response);
- }
- }
- } catch(e) {
- console.log("Ajax call failed - exception:", e);
- }
- };
- start = Date.now();
- if (body !== undefined) {
- http.send(JSON.stringify(body));
- } else {
- http.send();
- }
- };
- // Global variables
- // ================
- t.ajaxPrefix = "/db";
- t.partition = "main";
- // Console
- // =======
- t.main = {
- init : function() {
- "use strict";
- console.log("Init console")
- t.ajax(t.ajaxPrefix + "/about/", "GET", undefined, function (r) {
- t.$("name").innerHTML = t.esc(r.product);
- t.$("version").innerHTML = t.esc(r.version);
- t.main.addPrompt();
- });
- },
- // Add a new prompt element.
- //
- addPrompt : function () {
- "use strict";
- var input = t.create("div", {
- "contenteditable" : "true",
- "class" : "t-input"
- }),
- term = t.create("div", {
- "class" : "t-term t-input-term"
- }),
- prompt = t.create("div", {
- "class" : "t-prompt"
- }),
- buttonOK = t.create("button", {
- "class" : "t-button",
- }),
- buttonClear = t.create("button", {
- "class" : "t-button",
- }),
- stripText = function (t) {
- // Strip out unwanted html tags
- t = t.replace(/<(?:.|\n)*?>/g, '');
-
- // Convert sansitised html to text
-
- t = t.replace(/>/g, '>');
- t = t.replace(/</g, '<');
- t = t.replace(/"/g, '"');
- t = t.replace(/'/g, "'");
- t = t.replace(/&/g, '&');
- t = t.replace(/ /g, ' ');
- return t
- };
- buttonOK.innerHTML = "Ok";
- buttonClear.innerHTML = "Clear";
- prompt.innerHTML = ">"
- t.insert(term, prompt)
- t.insert(term, input);
- t.insert(term, buttonOK);
- t.insert(term, buttonClear);
- t.insert(t.$("terms"), term);
- input.focus();
- t.addEvent(input, "keydown", function (e) {
- // Shift+Return pressed
- if (e.shiftKey && e.keyCode === 13) {
- t.stopBubbleEvent(e);
- t.main.exec(input, stripText(input.innerHTML));
- }
- });
- t.addEvent(term, "click", function (e) {
- input.focus();
- });
- t.addEvent(term, "drop", function (e) {
- e.stopPropagation();
- e.preventDefault();
- var files = e.dataTransfer.files;
- for (var i = 0, f; f = files[i]; i++) {
- var reader = new FileReader();
- reader.onload = function(content) {
- input.innerHTML += t.esc(content.target.result);
- }
- reader.readAsText(f);
- }
- });
- t.addEvent(term, "dragover", function (e) {
- e.stopPropagation();
- e.preventDefault();
- e.dataTransfer.dropEffect = 'copy';
- });
- t.addEvent(buttonOK, "click", function (e) {
- t.main.exec(input, stripText(input.innerHTML));
- });
- t.addEvent(buttonClear, "click", function (e) {
- input.innerHTML = "";
- });
- },
- // Execute a command.
- //
- exec : function (element, text) {
- "use strict";
- text = text.trim();
- text = text.replace( /\n/g, " ");
- var sp = text.split(" ", 1),
- cmd = sp[0].toLowerCase(),
- rest = text.substring(sp[0].length);
- console.log("Execute:", cmd, rest);
- // After the cmd has been executed
- var cmdFunc = t.cmds[cmd];
- if (cmdFunc !== undefined) {
- cmdFunc(element, rest);
- } else {
- t.main.addError(element, "Unknown command: '" + cmd + "'");
- }
- },
- // Add an error message. Returns true if a new element was added.
- //
- addError : function (element, text) {
- "use strict";
- var ret = t.main._addTextTerm(element, text, "t-term t-output-term t-error");
- if (ret) {
- // A new output was added we need a new prompt below it
- t.main.addPrompt();
- }
- element.focus();
- },
- // Add a normal message. Returns true if a new element was added.
- //
- addOutput : function (element, text) {
- "use strict";
- var ret = t.main._addTextTerm(element, text, "t-term t-output-term t-normal");
- if (ret) {
- // A new output was added we need a new prompt below it
- t.main.addPrompt();
- }
- },
- // Add a new element which should just display text.
- //
- _addTextTerm : function (element, text, classes) {
- "use strict";
- var term = element._term,
- addedNewElement = false;
- if (term === undefined) {
- term = t.create("div", {
- "class" : classes
- });
- element._term = term;
- t.insert(t.$("terms"), term);
- addedNewElement = true;
- } else {
- term.setAttribute("class", classes);
- }
- t.main._showColorEffect(term);
- term.innerHTML = t.esc(text);
- return addedNewElement;
- },
- // Output a result table.
- //
- addTableOutput : function (element, tableObj) {
- "use strict";
- // Clear the output term
- t.main.addOutput(element, "");
- // Build up the table
- var term = element._term,
- table = t.create("table", {
- "class" : "t-result-table"
- }),
- tableHeader = t.create("tr");
- // Build up the header
- tableObj.header.labels.forEach(function (l) {
- var cell = t.create("th");
- cell.innerHTML = l;
- t.insert(tableHeader, cell);
- });
- t.insert(table, tableHeader);
- // Build up the rows
- tableObj.rows.forEach(function (r) {
- var row = t.create("tr");
- t.insert(table, row);
- r.forEach(function (c) {
- var cell = t.create("td");
- // Make sure nested structures are printed
- // in a human readable fashion
- if (typeof c === 'object') {
- c = JSON.stringify(c);
- }
-
- cell.innerHTML = c;
- t.insert(row, cell);
- });
- });
- t.insert(term, table);
- },
- // Show a color effect on an element.
- //
- _showColorEffect : function (elem) {
- "use strict";
- var count = parseInt("AAAAAA", 16),
- loop = function () {
- elem.style.backgroundColor = "#" + count.toString(16);
- count += parseInt("080808", 16);
- if (count < parseInt("DDDDDD", 16)) {
- window.setTimeout(loop, 30);
- } else {
- elem.style.backgroundColor = "";
- }
- };
- loop();
- },
- };
- t.cmds = {
- // Get the help text
- //
- "help" : function (element, data) {
- "use strict";
- data = data.replace(/^\s+|\s+$/gm, '');
- if (data === "") {
- t.main.addOutput(element, "Available commands:\n\n" +
- "ver - Returns product information\n" +
- "info - Returns general datastore information\n" +
- "part - Display / change the partition which is queried\n" +
- "get/lookup - Run a YQL query\n" +
- "find - Do a fulltext search\n");
- return;
- }
- else if (data === "get") {
- t.main.addOutput(element, "Run a YQL query.\n\n" +
- "A query can have the form: get <node kind> where <condition>\n");
- return;
- }
- else if (data === "lookup") {
- t.main.addOutput(element, "Run a YQL query.\n\n" +
- 'A query can have the form: lookup <node kind> "<node id>",... where <condition>\n');
- return;
- }
- else if (data === "find") {
- t.main.addOutput(element, "Do a fulltext search.\n\n" +
- 'Find a word or phrase in the datastore. This will query all node kinds in all visible partitions (partitions which don\'t start with a _ character');
- return;
- }
- t.main.addOutput(element, "Unknown help topic: " + data);
- },
- // Get the "about" JSON
- //
- "ver" : function (element) {
- "use strict";
- t.ajax(t.ajaxPrefix + "/about/", "GET", undefined, function (r) {
- t.main.addOutput(element, JSON.stringify(r, undefined, 4));
- });
- },
- // Get the "info" JSON
- //
- "info" : function (element) {
- "use strict";
- t.ajax(t.ajaxPrefix + "/v1/info/", "GET", undefined, function (r) {
- t.main.addOutput(element, JSON.stringify(r, undefined, 4));
- });
- },
- // Get data in the datastore.
- //
- "get" : function (element, data) {
- "use strict";
- t.ajax(t.ajaxPrefix + "/v1/query/" + t.partition + "?q=get" + encodeURIComponent(data), "GET", undefined,
- function (r) {
- t.main.addTableOutput(element, r);
- },
- function (r) {
- t.main.addError(element, r);
- });
- },
- // Lookup data in the datastore.
- //
- "lookup" : function (element, data) {
- "use strict";
- t.ajax(t.ajaxPrefix + "/v1/query/" + t.partition + "?q=lookup" + encodeURIComponent(data), "GET", undefined,
- function (r) {
- t.main.addTableOutput(element, r);
- },
- function (r) {
- t.main.addError(element, r);
- });
- },
- // Lookup data in the datastore.
- //
- "find" : function (element, data) {
- "use strict";
- data = data.replace(/^\s+|\s+$/gm, ''); // Remove whitespace from beginning and end
- var phrase = data;
- console.log("p:", phrase);
- // Check all arguments were given
-
- if (!phrase) {
- t.main.addError(element, "Find requires a search word / phrase");
- return;
- }
- // Do the index lookup for nodes. Could also do edge
- // index lookup using /e/ but I am too lazy in the moment ...
- t.ajax(t.ajaxPrefix + "/v1/find/?lookup=1&text=" + encodeURIComponent(phrase), "GET", undefined,
- function (r) {
- t.main.addOutput(element, JSON.stringify(r, undefined, 4));
- },
- function (r) {
- t.main.addError(element, r);
- });
- },
- // Change partition.
- //
- "part" : function (element, data) {
- "use strict";
- data = data.replace(/^\s+|\s+$/gm, '');
- if (data !== "") {
- t.partition = data;
- }
- t.main.addOutput(element, "Partition to query is: " + t.partition);
- }
- };
- </script>
- </body>
- </html>
- `
- /*
- ClusterTermSRC is the cluster terminal HTML as a text blob.
- */
- const ClusterTermSRC = `
- <!doctype html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>Cluster Terminal</title>
- <style>
- body {
- background: #fff;
- font-family: 'verdana';
- font-size: 11px;
- margin: 0;
- min-width: 320px;
- }
-
- .c-toggle{
- float:right;
- }
- /* Header */
-
- .c-header {
- background: linear-gradient(#000, #444);
- color: #fff;
- font-weight: bold;
- padding: 0 1em;
- margin: 0 0 1em 0;
- box-shadow: 3px 3px 3px rgba(50, 50, 50, 0.25);
- }
- .c-header h1 {
- display: inline-block;
- font-size: 18px;
- margin: 3px 0;
- }
- /* Info */
-
- table.c-info {
- margin: auto;
- min-width: 40%;
- }
-
- table.c-info td {
- background: #FFFEEF;
- width:50%;
- padding: 10px;
- border: #000000 2px solid;
- border-radius: 10px;
- margin: 3em 0 0 0;
- }
- table.c-info td:nth-child(odd) {
- text-align: right;
- }
- table.c-info td:nth-child(even) {
- background: #EEEEEE;
- }
- /* Terminal */
- .c-term {
- display: inline-block;
- background: #FFFEEF;
- padding: 10px;
- border: #000000 2px solid;
- border-radius: 10px;
- margin: 3em 0 0 0;
- }
- .c-term h2 {
- padding: 0;
- margin: 0;
- }
- .c-term table {
- padding: 5px;
- width:100%;
- }
- .c-term input {
- padding: 2px;
- width:100%;
- }
- .c-term .term_result {
- padding: 20px;
- font-weight: bold;
- white-space: pre-wrap;
- }
- /* Status */
- .c-status {
- float: left;
- margin: 0 0 0 50%;
- min-height: 100%;
- padding: 0;
- width: 50%;
- }
- .c-status dl {
- margin: 0;
- }
-
- .c-status dd {
- padding: 0 0 5px 0;
- }
- .c-log {
- background: #EEEEEE;
- padding: 10px;
- border: #000000 2px solid;
- border-radius: 10px;
- margin: 3em 0 0 0;
- }
- .c-log pre {
- white-space: pre-wrap;
- word-wrap: break-word;
- }
- .c-members {
- float: left;
- margin-left: -100%;
- text-align: left;
- width: 100%;
- }
- .c-members .c-member {
- background: #B3D9FF;
- display: inline-block;
- padding: 10px;
- border: #000000 2px solid;
- border-radius: 10px;
- margin: 3em 0 0 0;
- max-width: 80%;
- word-wrap: break-word;
- }
- .c-members .c-member c-error {
- background: #FFBBBB;
- }
- </style>
- </head>
- <body onload="c.main.init()">
- <div class="c-header"><h1 id="name"></h1> <h1 id="version"></h1></div>
- <!-- Info section - information on this cluster member are displayed here -->
-
- <table class="c-info">
- <tr>
- <td>Member:</td>
- <td id="mname"></td>
- </tr>
- <tr>
- <td>Network Interface:</td>
- <td id="mnetaddr"></td>
- </tr>
- </table>
- <!-- Terminal section - commands can be send to the cluster here -->
-
- <div id="term-toggle" class="c-term">
- <a href="javascript:void(0)" onclick="c.toggle('term', 'term-toggle')">Show Config Panel</a>
- </div>
-
- <div id="term" class="c-term" style="display:none">
- <div class="c-toggle"><a href="javascript:void(0)" onclick="c.toggle('term-toggle', 'term')">Hide Config Panel</a></div>
- <h2>Ping other instance:</h2>
-
- <table>
- <tr>
- <td>Member name:</td>
- <td><input id="ping_name" placeholder="e.g. member1"></td>
- </tr>
- <tr>
- <td>Network Interface:</td>
- <td><input id="ping_interface" placeholder="e.g. localhost:9030"></td>
- </tr>
- </table>
- <button onclick="c.main.PingInstance(c.$('ping_name').value, c.$('ping_interface').value)">Ping Instance</button>
- <pre id="ping_message" class="term_result"></pre>
- <br><br>
- <h2>Join a cluster:</h2>
-
- <table>
- <tr>
- <td>Member name:</td>
- <td><input id="join_name" placeholder="e.g. member1"></td>
- </tr>
- <tr>
- <td>Network Interface:</td>
- <td><input id="join_interface" placeholder="e.g. localhost:9030"></td>
- </tr>
- </table>
- <button onclick="c.main.JoinCluster(c.$('join_name').value, c.$('join_interface').value)">Join Cluster</button>
- <pre id="join_message" class="term_result"></pre>
- <br><br>
- <h2>Eject a member from this cluster:</h2>
- <table>
- <tr>
- <td>Member name:</td>
- <td><input id="eject_name" placeholder="e.g. member1"></td>
- </tr>
- </table>
- <button onclick="c.main.EjectMember(c.$('eject_name').value)">Eject Member</button>
- <pre id="eject_message" class="term_result"></pre>
- </div>
-
- <!-- Status section - the cluster status is displayed here -->
-
- <div class="c-status">
- <div id="members" class="c-members">
- </div>
- <div class="c-log">
- Member Log: <a title="Clear all log messages" href="javascript:void(0)" onclick="c.main.ClearLog()">[Clear]</a>
- <div id="logOn" class="c-toggle"><a title="Switch live log off" href="javascript:void(0)" onclick="c.toggle('logOff', 'logOn');c.pollLog=false">Live Log: On</a></div>
- <div id="logOff" class="c-toggle" style="display:none"><a title="Switch live log on" href="javascript:void(0)" onclick="c.toggle('logOn', 'logOff');c.pollLog=true;c.main.RunLogMonitor()">Live Log: Off</a></div>
- <pre id="log"></pre>
- </div>
- </div>
- <script>
- // Utility functions
- // =================
- if (c === undefined) {
- var c = {};
- }
- c.$ = function(id) { "use strict"; return document.getElementById(id); };
- c.toggle = function (show, hide) { "use strict"; c.$(hide).style = "display:none;"; c.$(show).style = "display:auto;"; };
- c.copyObject = function (o1, o2) { "use strict"; for (var attr in o1) { o2[attr] = o1[attr]; } };
- c.mergeObject = function (o1, o2) { "use strict"; for (var attr in o1) { if(o2[attr] === undefined) { o2[attr] = o1[attr]; } } };
- c.cloneObject = function (o) { "use strict"; var r = {}; ge.copyObject(o, r); return r; };
- c.esc = function (str) { "use strict"; return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>' ); };
- c.unesc = function (str) { "use strict"; return str.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); };
- c.insert = function(element, child) { "use strict"; if (typeof child === "string") { element.innerHTML = c.esc(child); } else { element.appendChild(child); } return element; };
- c.insertBefore = function(element, child) { "use strict"; element.parentNode.insertBefore(child, element); };
- c.insertAfter = function(element, child) { "use strict"; element.parentNode.insertBefore(child, element.nextSibling); };
- c.create = function(tag, attrs) {
- "use strict";
- var element = document.createElement(tag);
- if (attrs !== undefined) {
- c.getObjectKeys(attrs).forEach(function (v) {
- element.setAttribute(v, attrs[v]);
- });
- }
- return element;
- };
- c.getObjectKeys = function (obj) {
- "use strict";
- var key, keys = [];
- for(key in obj) {
- if (obj.hasOwnProperty(key)) {
- keys.push(key);
- }
- }
- keys.sort();
- return keys;
- };
- c.getObjectValues = function (obj) {
- "use strict";
- var key, values = [];
- for(key in obj) {
- if (obj.hasOwnProperty(key)) {
- values.push(obj[key]);
- }
- }
- return values;
- };
- c.addEvent = function (element, eventName, func) {
- "use strict";
- if (element.addEventListener) {
- element.addEventListener(eventName, func, false);
- } else if (element.attachEvent) {
- element.attachEvent('on' + eventName, func);
- }
- };
- c.stopBubbleEvent = function (e) {
- "use strict";
- e = e ? e:window.event;
- if (e.stopPropagation) {
- e.stopPropagation();
- }
- if (e.cancelBubble !== null) {
- e.cancelBubble = true;
- }
- if (e.preventDefault) {
- e.preventDefault();
- }
- };
- c.bind = function () {
- "use strict";
- var f = arguments[0], t = Array.prototype.slice.call(arguments, 1);
- var a = t.splice(1);
- return function() {
- "use strict";
- return f.apply(t[0],
- a.concat(Array.prototype.slice.call(arguments, 0)));
- }
- };
- c.ajax = function (url, method, body, callbackOK, callbackError) {
- "use strict";
- var http = new XMLHttpRequest(),
- start;
- if (method === undefined) {
- method = "GET"
- }
- http.open(method, url, true);
- http.setRequestHeader("content-type", "application/json");
- http.setRequestHeader("accept", "application/json");
- http.onload = function () {
- var rtime = Date.now() - start;
- document.title = "Terminal (Last response time: "+ rtime +"ms)";
- console.log("Response time:", rtime);
- try {
- if (http.status === 200) {
- if (callbackOK) {
- if (http.response !== "") {
- callbackOK(JSON.parse(http.response));
- } else {
- callbackOK();
- }
- }
- } else {
- if (callbackError) {
- callbackError(http.response);
- } else {
- console.log(http.response);
- }
- }
- } catch(e) {
- console.log("Ajax call failed - exception:", e);
- throw e;
- }
- };
- if (body !== undefined) {
- http.send(JSON.stringify(body));
- } else {
- start = Date.now()
- http.send();
- }
- };
- // Global variables
- // ================
- c.ajaxPrefix = "/db";
- c.pollStatus = true;
- c.pollLog = true;
- c.memberInfos = {};
- c.pollInterval = 1000;
- // Console
- // =======
- c.main = {
- init : function() {
- "use strict";
- console.log("Init console")
- c.ajax(c.ajaxPrefix + "/about/", "GET", undefined, function (r) {
- c.$("name").innerHTML = c.esc(r.product + " Cluster Terminal");
- c.$("version").innerHTML = c.esc(r.version);
- });
- c.main.PopulateMemberInfo();
- c.main.PopulateMembers();
- c.main.RunLogMonitor();
- },
- // Ping another cluster member instance.
- //
- PingInstance : function(name, netaddr) {
- "use strict";
- c.ajax(c.ajaxPrefix + "/v1/cluster/ping", "PUT", {
- "name" : name,
- "netaddr" : netaddr
- }, function (r) {
- c.$("ping_message").innerHTML = c.esc(r.join(", "));
- }, function(e) {
- c.$("ping_message").innerHTML = c.esc(e);
- });
- },
- // Join another cluster.
- //
- JoinCluster : function(name, netaddr) {
- "use strict";
- c.ajax(c.ajaxPrefix + "/v1/cluster/join", "PUT", {
- "name" : name,
- "netaddr" : netaddr
- }, function (r) {
- console.log(r);
- c.$("join_message").innerHTML = c.esc("This instance joined the cluster of " + name);
- }, function(e) {
- c.$("join_message").innerHTML = c.esc(e);
- });
- },
- // Eject a member from the cluster.
- //
- EjectMember : function(name) {
- "use strict";
- c.ajax(c.ajaxPrefix + "/v1/cluster/eject", "PUT", {
- "name" : name
- }, function (r) {
- console.log(r);
- c.$("eject_message").innerHTML = c.esc("Member " + name + " was ejected from the cluster");
- }, function(e) {
- c.$("eject_message").innerHTML = c.esc(e);
- });
- },
- // Clear the member's cluster log.
- //
- ClearLog : function(name) {
- "use strict";
- c.ajax(c.ajaxPrefix + "/v1/cluster/log", "DELETE", {
- "name" : name
- }, function (r) {
- c.$("log").innerHTML = "";
- });
- },
- // Populate the member infos for the cluster.
- // NOTE: This is relative expensive as all members need to be contacted!
- //
- PopulateMemberInfo : function () {
- "use strict";
- c.ajax(c.ajaxPrefix + "/v1/cluster/memberinfos", "GET", undefined, function (r) {
- c.memberInfos = r;
- });
- },
- // Populate the cluster members.
- //
- PopulateMembers : function () {
- "use strict";
-
- var membersElement = c.$("members"),
- refreshMemberInfos = false;
-
- function insertMember(name, netaddr) {
- var memberElement = c.create("div", {
- "class" : "c-member"
- }),
- dl = c.create("dl"),
- memberInfo = c.memberInfos[name];
- c.insert(dl, c.insert(c.create("dt"), "Member"));
- c.insert(dl, c.insert(c.create("dd"), name));
- c.insert(dl, c.insert(c.create("dt"), "Network Interface"));
- c.insert(dl, c.insert(c.create("dd"), netaddr));
- c.insert(memberElement, dl);
- c.insert(membersElement, memberElement);
- if (memberInfo !== undefined) {
- if (memberInfo.termurl !== undefined) {
- c.insert(dl, c.insert(c.create("dt"), "Term URL"));
- c.insert(dl, c.insert(c.create("dd"), c.insert(c.create("a", {
- "href" : memberInfo.termurl
- }), memberInfo.termurl)));
- }
- } else {
- console.log("Member info of", name, "not found - refresh member infos");
- refreshMemberInfos = true;
- }
- }
- c.ajax(c.ajaxPrefix + "/v1/cluster/", "GET", undefined, function (r) {
- "use strict";
- var members = c.main._listToObject(r.members),
- failed = c.main._listToObject(r.failed);
- // Populate info section
-
- c.$("mname").innerHTML = c.esc(r.members[0]);
- c.$("mnetaddr").innerHTML = c.esc(r.members[1]);
- // Populate status section
- c.$("members").innerHTML = "";
- c.getObjectKeys(members).forEach(function (m) {
- insertMember(m, members[m]);
- });
- if (refreshMemberInfos) {
- c.main.PopulateMemberInfo();
- }
-
- if (c.pollStatus) {
- window.setTimeout(c.main.PopulateMembers, c.pollInterval);
- }
- });
- },
- // Convert a stateinfo list into an object.
- //
- _listToObject : function (l) {
- "use strict";
- var o = {};
- if (l) {
- for (var i=0;i<l.length;i+=2) {
- o[l[i]] = l[i+1];
- }
- }
- return o;
- },
- // Update the cluster log.
- //
- RunLogMonitor : function () {
- "use strict";
-
- c.ajax(c.ajaxPrefix + "/v1/cluster/log", "GET", undefined, function (r) {
- c.$("log").innerHTML = c.esc(r.join("\n"));
- if (c.pollLog) {
- window.setTimeout(c.main.RunLogMonitor, c.pollInterval);
- }
- });
- }
- };
- </script>
- </body>
- </html>
- `
|