pages.go 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284
  1. /*
  2. * EliasDB
  3. *
  4. * Copyright 2016 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the Mozilla Public
  7. * License, v. 2.0. If a copy of the MPL was not distributed with this
  8. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  9. */
  10. package server
  11. /*
  12. LoginSRC is a simple login page.
  13. */
  14. const LoginSRC = `
  15. <!DOCTYPE html>
  16. <html>
  17. <head>
  18. <title>EliasDB Login</title>
  19. <meta name="viewport" content="width=device-width, initial-scale=1">
  20. <style>
  21. body, html {
  22. height: 100%;
  23. }
  24. #wrapper {
  25. position:absolute;
  26. /* Uncomment this if you have a fancy background image
  27. background-image: url("/img/bg/bg.jpg");
  28. background-position: center;
  29. background-repeat: no-repeat;
  30. background-size: cover;
  31. */
  32. height: 100%;
  33. width:100%;
  34. }
  35. #login-panel {
  36. text-align: center;
  37. padding : 2em;
  38. max-width : 40em;
  39. width : 35%;
  40. min-width : 20em;
  41. margin: 15% auto;
  42. background: #ffffff;
  43. border-radius: 5px;
  44. }
  45. #login-panel .form-input, #login-panel h4 {
  46. margin-bottom: 1em;
  47. }
  48. #login-panel .input-icon {
  49. position: absolute;
  50. margin: 0.8em;
  51. display: inline-block;
  52. width: 1.2em;
  53. z-index: 1;
  54. }
  55. #login-panel .form-input {
  56. padding-left: 2.5em;
  57. display: inline-block;
  58. }
  59. #login-panel .btn {
  60. width: 30%;
  61. }
  62. </style>
  63. </head>
  64. <body>
  65. <div id="wrapper">
  66. <div id="login-panel">
  67. <h4>Login</h4>
  68. <form action="/db/login/" method="post">
  69. <input class="form-input input-lg" name="user" placeholder="Enter Login" autofocus><br>
  70. <input class="form-input input-lg" name="pass" type="password" placeholder="Enter Password"><br>
  71. <input class="btn btn-primary" type="submit" value="Login">
  72. </form>
  73. </div>
  74. </div>
  75. </body>
  76. </html>
  77. `
  78. /*
  79. TermSRC is the terminal HTML as a text blob.
  80. */
  81. const TermSRC = `
  82. <!doctype html>
  83. <html>
  84. <head>
  85. <meta charset="utf-8">
  86. <title>Terminal</title>
  87. <style>
  88. body {
  89. background: #fff;
  90. font-family: 'verdana';
  91. font-size: 11px;
  92. margin: 0;
  93. min-width: 320px;
  94. }
  95. .t-header {
  96. background: linear-gradient(#000, #444);
  97. color: #fff;
  98. font-weight: bold;
  99. padding: 0 1em;
  100. margin: 0 0 1em 0;
  101. box-shadow: 3px 3px 3px rgba(50, 50, 50, 0.25);
  102. }
  103. .t-header h1 {
  104. display: inline-block;
  105. font-size: 18px;
  106. margin: 3px 0;
  107. }
  108. .t-terms {
  109. padding: 0 10%;
  110. display: inline-block;
  111. width: 80%
  112. }
  113. .t-terms .t-term {
  114. background: #EEEEEE;
  115. padding: 10px;
  116. border: #000000 2px solid;
  117. border-radius: 10px;
  118. margin: 3em 0 0 0;
  119. }
  120. .t-terms .t-term.t-input-term:hover {
  121. border-color: #888888;
  122. }
  123. .t-terms .t-term.t-output-term {
  124. white-space: pre-wrap;
  125. font-family: "Lucida Console", "Courier";
  126. font-size: 11px;
  127. }
  128. .t-terms .t-term.t-output-term.t-error {
  129. background: #FFBBBB;
  130. }
  131. .t-terms .t-term.t-output-term.t-normal {
  132. background: #B3D9FF;
  133. }
  134. .t-terms .t-term .t-result-table {
  135. width: 100%;
  136. border-spacing: 0;
  137. border-collapse: collapse;
  138. }
  139. .t-terms .t-term .t-result-table th {
  140. text-align: left;
  141. border-style: none none solid none;
  142. }
  143. .t-terms .t-term .t-result-table td {
  144. padding: 5px;
  145. }
  146. .t-terms .t-term .t-prompt {
  147. float: left;
  148. width: 15px;
  149. font-weight: bold;
  150. }
  151. .t-terms .t-term .t-input {
  152. display: inline-block;
  153. width: calc(100% - 15px);
  154. height: 10px;
  155. outline: none;
  156. }
  157. .t-terms .t-term .t-button {
  158. background: #EEEEEE;
  159. float: right;
  160. border: #000000 2px solid;
  161. margin: 2px;
  162. border-radius: 10px;
  163. font-weight: bold;
  164. }
  165. .t-terms .t-term .t-button:hover {
  166. color: #888888;
  167. border-color: #888888;
  168. }
  169. </style>
  170. </head>
  171. <body onload="t.main.init()">
  172. <div class="t-header"><h1 id="name"></h1> <h1 id="version"></h1></div>
  173. <div id="terms" class="t-terms"></div>
  174. <script>
  175. // Utility functions
  176. // =================
  177. if (t === undefined) {
  178. var t = {};
  179. }
  180. t.$ = function(id) { "use strict"; return document.getElementById(id); };
  181. t.copyObject = function (o1, o2) { "use strict"; for (var attr in o1) { o2[attr] = o1[attr]; } };
  182. t.mergeObject = function (o1, o2) { "use strict"; for (var attr in o1) { if(o2[attr] === undefined) { o2[attr] = o1[attr]; } } };
  183. t.cloneObject = function (o) { "use strict"; var r = {}; ge.copyObject(o, r); return r; };
  184. t.esc = function (str) { "use strict"; return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;' ); };
  185. t.unesc = function (str) { "use strict"; return str.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&'); };
  186. t.insert = function(element, child) { "use strict"; element.appendChild(child); return element; };
  187. t.insertBefore = function(element, child) { "use strict"; element.parentNode.insertBefore(child, element); };
  188. t.insertAfter = function(element, child) { "use strict"; element.parentNode.insertBefore(child, element.nextSibling); };
  189. t.create = function(tag, attrs) {
  190. "use strict";
  191. var element = document.createElement(tag);
  192. if (attrs !== undefined) {
  193. t.getObjectKeys(attrs).forEach(function (v) {
  194. element.setAttribute(v, attrs[v]);
  195. });
  196. }
  197. return element;
  198. };
  199. t.getObjectKeys = function (obj) {
  200. "use strict";
  201. var key, keys = [];
  202. for(key in obj) {
  203. if (obj.hasOwnProperty(key)) {
  204. keys.push(key);
  205. }
  206. }
  207. return keys;
  208. };
  209. t.getObjectValues = function (obj) {
  210. "use strict";
  211. var key, values = [];
  212. for(key in obj) {
  213. if (obj.hasOwnProperty(key)) {
  214. values.push(obj[key]);
  215. }
  216. }
  217. return values;
  218. };
  219. t.addEvent = function (element, eventName, func) {
  220. "use strict";
  221. if (element.addEventListener) {
  222. element.addEventListener(eventName, func, false);
  223. } else if (element.attachEvent) {
  224. element.attachEvent('on' + eventName, func);
  225. }
  226. };
  227. t.stopBubbleEvent = function (e) {
  228. "use strict";
  229. e = e ? e:window.event;
  230. if (e.stopPropagation) {
  231. e.stopPropagation();
  232. }
  233. if (e.cancelBubble !== null) {
  234. e.cancelBubble = true;
  235. }
  236. if (e.preventDefault) {
  237. e.preventDefault();
  238. }
  239. };
  240. t.bind = function () {
  241. "use strict";
  242. var f = arguments[0], t = Array.prototype.slice.call(arguments, 1);
  243. var a = t.splice(1);
  244. return function() {
  245. "use strict";
  246. return f.apply(t[0],
  247. a.concat(Array.prototype.slice.call(arguments, 0)));
  248. }
  249. };
  250. t.ajax = function (url, method, body, callbackOK, callbackError) {
  251. "use strict";
  252. var http = new XMLHttpRequest(),
  253. start;
  254. if (method === undefined) {
  255. method = "GET"
  256. }
  257. http.open(method, url, true);
  258. http.setRequestHeader("content-type", "application/json");
  259. http.setRequestHeader("accept", "application/json");
  260. http.onload = function () {
  261. var rtime = Date.now() - start;
  262. document.title = "Terminal (Last response time: "+ rtime +"ms)";
  263. console.log("Response time:", rtime);
  264. try {
  265. if (http.status === 200) {
  266. if (callbackOK) {
  267. if (http.response !== "") {
  268. callbackOK(JSON.parse(http.response));
  269. } else {
  270. callbackOK();
  271. }
  272. }
  273. } else {
  274. if (callbackError) {
  275. callbackError(http.response);
  276. } else {
  277. console.log(http.response);
  278. }
  279. }
  280. } catch(e) {
  281. console.log("Ajax call failed - exception:", e);
  282. }
  283. };
  284. start = Date.now();
  285. if (body !== undefined) {
  286. http.send(JSON.stringify(body));
  287. } else {
  288. http.send();
  289. }
  290. };
  291. // Global variables
  292. // ================
  293. t.ajaxPrefix = "/db";
  294. t.partition = "main";
  295. // Console
  296. // =======
  297. t.main = {
  298. init : function() {
  299. "use strict";
  300. console.log("Init console")
  301. t.ajax(t.ajaxPrefix + "/about/", "GET", undefined, function (r) {
  302. t.$("name").innerHTML = t.esc(r.product);
  303. t.$("version").innerHTML = t.esc(r.version);
  304. t.main.addPrompt();
  305. });
  306. },
  307. // Add a new prompt element.
  308. //
  309. addPrompt : function () {
  310. "use strict";
  311. var input = t.create("div", {
  312. "contenteditable" : "true",
  313. "class" : "t-input"
  314. }),
  315. term = t.create("div", {
  316. "class" : "t-term t-input-term"
  317. }),
  318. prompt = t.create("div", {
  319. "class" : "t-prompt"
  320. }),
  321. buttonOK = t.create("button", {
  322. "class" : "t-button",
  323. }),
  324. buttonClear = t.create("button", {
  325. "class" : "t-button",
  326. }),
  327. stripText = function (t) {
  328. // Strip out unwanted html tags
  329. t = t.replace(/<(?:.|\n)*?>/g, '');
  330. // Convert sansitised html to text
  331. t = t.replace(/&gt;/g, '>');
  332. t = t.replace(/&lt;/g, '<');
  333. t = t.replace(/&quot;/g, '"');
  334. t = t.replace(/&apos;/g, "'");
  335. t = t.replace(/&amp;/g, '&');
  336. t = t.replace(/&nbsp;/g, ' ');
  337. return t
  338. };
  339. buttonOK.innerHTML = "Ok";
  340. buttonClear.innerHTML = "Clear";
  341. prompt.innerHTML = "&gt;"
  342. t.insert(term, prompt)
  343. t.insert(term, input);
  344. t.insert(term, buttonOK);
  345. t.insert(term, buttonClear);
  346. t.insert(t.$("terms"), term);
  347. input.focus();
  348. t.addEvent(input, "keydown", function (e) {
  349. // Shift+Return pressed
  350. if (e.shiftKey && e.keyCode === 13) {
  351. t.stopBubbleEvent(e);
  352. t.main.exec(input, stripText(input.innerHTML));
  353. }
  354. });
  355. t.addEvent(term, "click", function (e) {
  356. input.focus();
  357. });
  358. t.addEvent(term, "drop", function (e) {
  359. e.stopPropagation();
  360. e.preventDefault();
  361. var files = e.dataTransfer.files;
  362. for (var i = 0, f; f = files[i]; i++) {
  363. var reader = new FileReader();
  364. reader.onload = function(content) {
  365. input.innerHTML += t.esc(content.target.result);
  366. }
  367. reader.readAsText(f);
  368. }
  369. });
  370. t.addEvent(term, "dragover", function (e) {
  371. e.stopPropagation();
  372. e.preventDefault();
  373. e.dataTransfer.dropEffect = 'copy';
  374. });
  375. t.addEvent(buttonOK, "click", function (e) {
  376. t.main.exec(input, stripText(input.innerHTML));
  377. });
  378. t.addEvent(buttonClear, "click", function (e) {
  379. input.innerHTML = "";
  380. });
  381. },
  382. // Execute a command.
  383. //
  384. exec : function (element, text) {
  385. "use strict";
  386. text = text.trim();
  387. text = text.replace( /\n/g, " ");
  388. var sp = text.split(" ", 1),
  389. cmd = sp[0].toLowerCase(),
  390. rest = text.substring(sp[0].length);
  391. console.log("Execute:", cmd, rest);
  392. // After the cmd has been executed
  393. var cmdFunc = t.cmds[cmd];
  394. if (cmdFunc !== undefined) {
  395. cmdFunc(element, rest);
  396. } else {
  397. t.main.addError(element, "Unknown command: '" + cmd + "'");
  398. }
  399. },
  400. // Add an error message. Returns true if a new element was added.
  401. //
  402. addError : function (element, text) {
  403. "use strict";
  404. var ret = t.main._addTextTerm(element, text, "t-term t-output-term t-error");
  405. if (ret) {
  406. // A new output was added we need a new prompt below it
  407. t.main.addPrompt();
  408. }
  409. element.focus();
  410. },
  411. // Add a normal message. Returns true if a new element was added.
  412. //
  413. addOutput : function (element, text) {
  414. "use strict";
  415. var ret = t.main._addTextTerm(element, text, "t-term t-output-term t-normal");
  416. if (ret) {
  417. // A new output was added we need a new prompt below it
  418. t.main.addPrompt();
  419. }
  420. },
  421. // Add a new element which should just display text.
  422. //
  423. _addTextTerm : function (element, text, classes) {
  424. "use strict";
  425. var term = element._term,
  426. addedNewElement = false;
  427. if (term === undefined) {
  428. term = t.create("div", {
  429. "class" : classes
  430. });
  431. element._term = term;
  432. t.insert(t.$("terms"), term);
  433. addedNewElement = true;
  434. } else {
  435. term.setAttribute("class", classes);
  436. }
  437. t.main._showColorEffect(term);
  438. term.innerHTML = t.esc(text);
  439. return addedNewElement;
  440. },
  441. // Output a result table.
  442. //
  443. addTableOutput : function (element, tableObj) {
  444. "use strict";
  445. // Clear the output term
  446. t.main.addOutput(element, "");
  447. // Build up the table
  448. var term = element._term,
  449. table = t.create("table", {
  450. "class" : "t-result-table"
  451. }),
  452. tableHeader = t.create("tr");
  453. // Build up the header
  454. tableObj.header.labels.forEach(function (l) {
  455. var cell = t.create("th");
  456. cell.innerHTML = l;
  457. t.insert(tableHeader, cell);
  458. });
  459. t.insert(table, tableHeader);
  460. // Build up the rows
  461. tableObj.rows.forEach(function (r) {
  462. var row = t.create("tr");
  463. t.insert(table, row);
  464. r.forEach(function (c) {
  465. var cell = t.create("td");
  466. // Make sure nested structures are printed
  467. // in a human readable fashion
  468. if (typeof c === 'object') {
  469. c = JSON.stringify(c);
  470. }
  471. cell.innerHTML = c;
  472. t.insert(row, cell);
  473. });
  474. });
  475. t.insert(term, table);
  476. },
  477. // Show a color effect on an element.
  478. //
  479. _showColorEffect : function (elem) {
  480. "use strict";
  481. var count = parseInt("AAAAAA", 16),
  482. loop = function () {
  483. elem.style.backgroundColor = "#" + count.toString(16);
  484. count += parseInt("080808", 16);
  485. if (count < parseInt("DDDDDD", 16)) {
  486. window.setTimeout(loop, 30);
  487. } else {
  488. elem.style.backgroundColor = "";
  489. }
  490. };
  491. loop();
  492. },
  493. };
  494. t.cmds = {
  495. // Get the help text
  496. //
  497. "help" : function (element, data) {
  498. "use strict";
  499. data = data.replace(/^\s+|\s+$/gm, '');
  500. if (data === "") {
  501. t.main.addOutput(element, "Available commands:\n\n" +
  502. "ver - Returns product information\n" +
  503. "info - Returns general datastore information\n" +
  504. "part - Display / change the partition which is queried\n" +
  505. "get/lookup - Run a YQL query\n" +
  506. "find - Do a fulltext search\n");
  507. return;
  508. }
  509. else if (data === "get") {
  510. t.main.addOutput(element, "Run a YQL query.\n\n" +
  511. "A query can have the form: get <node kind> where <condition>\n");
  512. return;
  513. }
  514. else if (data === "lookup") {
  515. t.main.addOutput(element, "Run a YQL query.\n\n" +
  516. 'A query can have the form: lookup <node kind> "<node id>",... where <condition>\n');
  517. return;
  518. }
  519. else if (data === "find") {
  520. t.main.addOutput(element, "Do a fulltext search.\n\n" +
  521. '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');
  522. return;
  523. }
  524. t.main.addOutput(element, "Unknown help topic: " + data);
  525. },
  526. // Get the "about" JSON
  527. //
  528. "ver" : function (element) {
  529. "use strict";
  530. t.ajax(t.ajaxPrefix + "/about/", "GET", undefined, function (r) {
  531. t.main.addOutput(element, JSON.stringify(r, undefined, 4));
  532. });
  533. },
  534. // Get the "info" JSON
  535. //
  536. "info" : function (element) {
  537. "use strict";
  538. t.ajax(t.ajaxPrefix + "/v1/info/", "GET", undefined, function (r) {
  539. t.main.addOutput(element, JSON.stringify(r, undefined, 4));
  540. });
  541. },
  542. // Get data in the datastore.
  543. //
  544. "get" : function (element, data) {
  545. "use strict";
  546. t.ajax(t.ajaxPrefix + "/v1/query/" + t.partition + "?q=get" + encodeURIComponent(data), "GET", undefined,
  547. function (r) {
  548. t.main.addTableOutput(element, r);
  549. },
  550. function (r) {
  551. t.main.addError(element, r);
  552. });
  553. },
  554. // Lookup data in the datastore.
  555. //
  556. "lookup" : function (element, data) {
  557. "use strict";
  558. t.ajax(t.ajaxPrefix + "/v1/query/" + t.partition + "?q=lookup" + encodeURIComponent(data), "GET", undefined,
  559. function (r) {
  560. t.main.addTableOutput(element, r);
  561. },
  562. function (r) {
  563. t.main.addError(element, r);
  564. });
  565. },
  566. // Lookup data in the datastore.
  567. //
  568. "find" : function (element, data) {
  569. "use strict";
  570. data = data.replace(/^\s+|\s+$/gm, ''); // Remove whitespace from beginning and end
  571. var phrase = data;
  572. console.log("p:", phrase);
  573. // Check all arguments were given
  574. if (!phrase) {
  575. t.main.addError(element, "Find requires a search word / phrase");
  576. return;
  577. }
  578. // Do the index lookup for nodes. Could also do edge
  579. // index lookup using /e/ but I am too lazy in the moment ...
  580. t.ajax(t.ajaxPrefix + "/v1/find/?lookup=1&text=" + encodeURIComponent(phrase), "GET", undefined,
  581. function (r) {
  582. t.main.addOutput(element, JSON.stringify(r, undefined, 4));
  583. },
  584. function (r) {
  585. t.main.addError(element, r);
  586. });
  587. },
  588. // Change partition.
  589. //
  590. "part" : function (element, data) {
  591. "use strict";
  592. data = data.replace(/^\s+|\s+$/gm, '');
  593. if (data !== "") {
  594. t.partition = data;
  595. }
  596. t.main.addOutput(element, "Partition to query is: " + t.partition);
  597. }
  598. };
  599. </script>
  600. </body>
  601. </html>
  602. `
  603. /*
  604. ClusterTermSRC is the cluster terminal HTML as a text blob.
  605. */
  606. const ClusterTermSRC = `
  607. <!doctype html>
  608. <html>
  609. <head>
  610. <meta charset="utf-8">
  611. <title>Cluster Terminal</title>
  612. <style>
  613. body {
  614. background: #fff;
  615. font-family: 'verdana';
  616. font-size: 11px;
  617. margin: 0;
  618. min-width: 320px;
  619. }
  620. .c-toggle{
  621. float:right;
  622. }
  623. /* Header */
  624. .c-header {
  625. background: linear-gradient(#000, #444);
  626. color: #fff;
  627. font-weight: bold;
  628. padding: 0 1em;
  629. margin: 0 0 1em 0;
  630. box-shadow: 3px 3px 3px rgba(50, 50, 50, 0.25);
  631. }
  632. .c-header h1 {
  633. display: inline-block;
  634. font-size: 18px;
  635. margin: 3px 0;
  636. }
  637. /* Info */
  638. table.c-info {
  639. margin: auto;
  640. min-width: 40%;
  641. }
  642. table.c-info td {
  643. background: #FFFEEF;
  644. width:50%;
  645. padding: 10px;
  646. border: #000000 2px solid;
  647. border-radius: 10px;
  648. margin: 3em 0 0 0;
  649. }
  650. table.c-info td:nth-child(odd) {
  651. text-align: right;
  652. }
  653. table.c-info td:nth-child(even) {
  654. background: #EEEEEE;
  655. }
  656. /* Terminal */
  657. .c-term {
  658. display: inline-block;
  659. background: #FFFEEF;
  660. padding: 10px;
  661. border: #000000 2px solid;
  662. border-radius: 10px;
  663. margin: 3em 0 0 1em;
  664. }
  665. .c-term h2 {
  666. padding: 0;
  667. margin: 0;
  668. }
  669. .c-term table {
  670. padding: 5px;
  671. width:100%;
  672. }
  673. .c-term input {
  674. padding: 2px;
  675. width:95%;
  676. }
  677. .c-term .term_result {
  678. padding: 20px;
  679. font-weight: bold;
  680. white-space: pre-wrap;
  681. }
  682. /* Status */
  683. .c-status {
  684. float: left;
  685. margin: 0 0 0 50%;
  686. min-height: 100%;
  687. padding: 0;
  688. width: 50%;
  689. }
  690. .c-status dl {
  691. margin: 0;
  692. }
  693. .c-status dd {
  694. padding: 0 0 5px 0;
  695. }
  696. .c-log {
  697. background: #EEEEEE;
  698. padding: 10px;
  699. border: #000000 2px solid;
  700. border-radius: 10px;
  701. margin: 3em 1em 0 0;
  702. }
  703. .c-log pre {
  704. white-space: pre-wrap;
  705. word-wrap: break-word;
  706. }
  707. .c-members {
  708. float: left;
  709. margin-left: -100%;
  710. text-align: left;
  711. width: 100%;
  712. }
  713. .c-members .c-member {
  714. background: #B3D9FF;
  715. display: inline-block;
  716. padding: 10px;
  717. border: #000000 2px solid;
  718. border-radius: 10px;
  719. margin: 3em 0 0 1em;
  720. max-width: 80%;
  721. word-wrap: break-word;
  722. }
  723. .c-members .c-member c-error {
  724. background: #FFBBBB;
  725. }
  726. </style>
  727. </head>
  728. <body onload="c.main.init()">
  729. <div class="c-header"><h1 id="name"></h1> <h1 id="version"></h1></div>
  730. <!-- Info section - information on this cluster member are displayed here -->
  731. <table class="c-info">
  732. <tr>
  733. <td>Member:</td>
  734. <td id="mname"></td>
  735. </tr>
  736. <tr>
  737. <td>Network Interface:</td>
  738. <td id="mnetaddr"></td>
  739. </tr>
  740. </table>
  741. <!-- Terminal section - commands can be send to the cluster here -->
  742. <div id="term-toggle" class="c-term">
  743. <a href="javascript:void(0)" onclick="c.toggle('term', 'term-toggle')">Show Config Panel</a>
  744. </div>
  745. <div id="term" class="c-term" style="display:none">
  746. <div class="c-toggle"><a href="javascript:void(0)" onclick="c.toggle('term-toggle', 'term')">Hide Config Panel</a></div>
  747. <h2>Ping other instance:</h2>
  748. <table>
  749. <tr>
  750. <td>Member name:</td>
  751. <td><input id="ping_name" placeholder="e.g. member1"></td>
  752. </tr>
  753. <tr>
  754. <td>Network Interface:</td>
  755. <td><input id="ping_interface" placeholder="e.g. localhost:9030"></td>
  756. </tr>
  757. </table>
  758. <button onclick="c.main.PingInstance(c.$('ping_name').value, c.$('ping_interface').value)">Ping Instance</button>
  759. <pre id="ping_message" class="term_result"></pre>
  760. <br><br>
  761. <h2>Join a cluster:</h2>
  762. <table>
  763. <tr>
  764. <td>Member name:</td>
  765. <td><input id="join_name" placeholder="e.g. member1"></td>
  766. </tr>
  767. <tr>
  768. <td>Network Interface:</td>
  769. <td><input id="join_interface" placeholder="e.g. localhost:9030"></td>
  770. </tr>
  771. </table>
  772. <button onclick="c.main.JoinCluster(c.$('join_name').value, c.$('join_interface').value)">Join Cluster</button>
  773. <pre id="join_message" class="term_result"></pre>
  774. <br><br>
  775. <h2>Eject a member from this cluster:</h2>
  776. <table>
  777. <tr>
  778. <td>Member name:</td>
  779. <td><input id="eject_name" placeholder="e.g. member1"></td>
  780. </tr>
  781. </table>
  782. <button onclick="c.main.EjectMember(c.$('eject_name').value)">Eject Member</button>
  783. <pre id="eject_message" class="term_result"></pre>
  784. </div>
  785. <!-- Status section - the cluster status is displayed here -->
  786. <div class="c-status">
  787. <div id="members" class="c-members">
  788. </div>
  789. <div class="c-log">
  790. Member Log: <a title="Clear all log messages" href="javascript:void(0)" onclick="c.main.ClearLog()">[Clear]</a>
  791. <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>
  792. <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>
  793. <pre id="log"></pre>
  794. </div>
  795. </div>
  796. <script>
  797. // Utility functions
  798. // =================
  799. if (c === undefined) {
  800. var c = {};
  801. }
  802. c.$ = function(id) { "use strict"; return document.getElementById(id); };
  803. c.toggle = function (show, hide) { "use strict"; c.$(hide).style = "display:none;"; c.$(show).style = "display:auto;"; };
  804. c.copyObject = function (o1, o2) { "use strict"; for (var attr in o1) { o2[attr] = o1[attr]; } };
  805. c.mergeObject = function (o1, o2) { "use strict"; for (var attr in o1) { if(o2[attr] === undefined) { o2[attr] = o1[attr]; } } };
  806. c.cloneObject = function (o) { "use strict"; var r = {}; ge.copyObject(o, r); return r; };
  807. c.esc = function (str) { "use strict"; return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;' ); };
  808. c.unesc = function (str) { "use strict"; return str.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&'); };
  809. c.insert = function(element, child) { "use strict"; if (typeof child === "string") { element.innerHTML = c.esc(child); } else { element.appendChild(child); } return element; };
  810. c.insertBefore = function(element, child) { "use strict"; element.parentNode.insertBefore(child, element); };
  811. c.insertAfter = function(element, child) { "use strict"; element.parentNode.insertBefore(child, element.nextSibling); };
  812. c.create = function(tag, attrs) {
  813. "use strict";
  814. var element = document.createElement(tag);
  815. if (attrs !== undefined) {
  816. c.getObjectKeys(attrs).forEach(function (v) {
  817. element.setAttribute(v, attrs[v]);
  818. });
  819. }
  820. return element;
  821. };
  822. c.getObjectKeys = function (obj) {
  823. "use strict";
  824. var key, keys = [];
  825. for(key in obj) {
  826. if (obj.hasOwnProperty(key)) {
  827. keys.push(key);
  828. }
  829. }
  830. keys.sort();
  831. return keys;
  832. };
  833. c.getObjectValues = function (obj) {
  834. "use strict";
  835. var key, values = [];
  836. for(key in obj) {
  837. if (obj.hasOwnProperty(key)) {
  838. values.push(obj[key]);
  839. }
  840. }
  841. return values;
  842. };
  843. c.addEvent = function (element, eventName, func) {
  844. "use strict";
  845. if (element.addEventListener) {
  846. element.addEventListener(eventName, func, false);
  847. } else if (element.attachEvent) {
  848. element.attachEvent('on' + eventName, func);
  849. }
  850. };
  851. c.stopBubbleEvent = function (e) {
  852. "use strict";
  853. e = e ? e:window.event;
  854. if (e.stopPropagation) {
  855. e.stopPropagation();
  856. }
  857. if (e.cancelBubble !== null) {
  858. e.cancelBubble = true;
  859. }
  860. if (e.preventDefault) {
  861. e.preventDefault();
  862. }
  863. };
  864. c.bind = function () {
  865. "use strict";
  866. var f = arguments[0], t = Array.prototype.slice.call(arguments, 1);
  867. var a = t.splice(1);
  868. return function() {
  869. "use strict";
  870. return f.apply(t[0],
  871. a.concat(Array.prototype.slice.call(arguments, 0)));
  872. }
  873. };
  874. c.ajax = function (url, method, body, callbackOK, callbackError) {
  875. "use strict";
  876. var http = new XMLHttpRequest(),
  877. start;
  878. if (method === undefined) {
  879. method = "GET"
  880. }
  881. http.open(method, url, true);
  882. http.setRequestHeader("content-type", "application/json");
  883. http.setRequestHeader("accept", "application/json");
  884. http.onload = function () {
  885. var rtime = Date.now() - start;
  886. document.title = "Terminal (Last response time: "+ rtime +"ms)";
  887. console.log("Response time:", rtime);
  888. try {
  889. if (http.status === 200) {
  890. if (callbackOK) {
  891. if (http.response !== "") {
  892. callbackOK(JSON.parse(http.response));
  893. } else {
  894. callbackOK();
  895. }
  896. }
  897. } else {
  898. if (callbackError) {
  899. callbackError(http.response);
  900. } else {
  901. console.log(http.response);
  902. }
  903. }
  904. } catch(e) {
  905. console.log("Ajax call failed - exception:", e);
  906. throw e;
  907. }
  908. };
  909. if (body !== undefined) {
  910. http.send(JSON.stringify(body));
  911. } else {
  912. start = Date.now()
  913. http.send();
  914. }
  915. };
  916. // Global variables
  917. // ================
  918. c.ajaxPrefix = "/db";
  919. c.pollStatus = true;
  920. c.pollLog = true;
  921. c.memberInfos = {};
  922. c.pollInterval = 1000;
  923. // Console
  924. // =======
  925. c.main = {
  926. init : function() {
  927. "use strict";
  928. console.log("Init console")
  929. c.ajax(c.ajaxPrefix + "/about/", "GET", undefined, function (r) {
  930. c.$("name").innerHTML = c.esc(r.product + " Cluster Terminal");
  931. c.$("version").innerHTML = c.esc(r.version);
  932. });
  933. c.main.PopulateMemberInfo();
  934. c.main.PopulateMembers();
  935. c.main.RunLogMonitor();
  936. },
  937. // Ping another cluster member instance.
  938. //
  939. PingInstance : function(name, netaddr) {
  940. "use strict";
  941. c.ajax(c.ajaxPrefix + "/v1/cluster/ping", "PUT", {
  942. "name" : name,
  943. "netaddr" : netaddr
  944. }, function (r) {
  945. c.$("ping_message").innerHTML = c.esc(r.join(", "));
  946. }, function(e) {
  947. c.$("ping_message").innerHTML = c.esc(e);
  948. });
  949. },
  950. // Join another cluster.
  951. //
  952. JoinCluster : function(name, netaddr) {
  953. "use strict";
  954. c.ajax(c.ajaxPrefix + "/v1/cluster/join", "PUT", {
  955. "name" : name,
  956. "netaddr" : netaddr
  957. }, function (r) {
  958. console.log(r);
  959. c.$("join_message").innerHTML = c.esc("This instance joined the cluster of " + name);
  960. }, function(e) {
  961. c.$("join_message").innerHTML = c.esc(e);
  962. });
  963. },
  964. // Eject a member from the cluster.
  965. //
  966. EjectMember : function(name) {
  967. "use strict";
  968. c.ajax(c.ajaxPrefix + "/v1/cluster/eject", "PUT", {
  969. "name" : name
  970. }, function (r) {
  971. console.log(r);
  972. c.$("eject_message").innerHTML = c.esc("Member " + name + " was ejected from the cluster");
  973. }, function(e) {
  974. c.$("eject_message").innerHTML = c.esc(e);
  975. });
  976. },
  977. // Clear the member's cluster log.
  978. //
  979. ClearLog : function(name) {
  980. "use strict";
  981. c.ajax(c.ajaxPrefix + "/v1/cluster/log", "DELETE", {
  982. "name" : name
  983. }, function (r) {
  984. c.$("log").innerHTML = "";
  985. });
  986. },
  987. // Populate the member infos for the cluster.
  988. // NOTE: This is relative expensive as all members need to be contacted!
  989. //
  990. PopulateMemberInfo : function () {
  991. "use strict";
  992. c.ajax(c.ajaxPrefix + "/v1/cluster/memberinfos", "GET", undefined, function (r) {
  993. c.memberInfos = r;
  994. });
  995. },
  996. // Populate the cluster members.
  997. //
  998. PopulateMembers : function () {
  999. "use strict";
  1000. var membersElement = c.$("members"),
  1001. refreshMemberInfos = false;
  1002. function insertMember(name, netaddr) {
  1003. var memberElement = c.create("div", {
  1004. "class" : "c-member"
  1005. }),
  1006. dl = c.create("dl"),
  1007. memberInfo = c.memberInfos[name];
  1008. c.insert(dl, c.insert(c.create("dt"), "Member"));
  1009. c.insert(dl, c.insert(c.create("dd"), name));
  1010. c.insert(dl, c.insert(c.create("dt"), "Network Interface"));
  1011. c.insert(dl, c.insert(c.create("dd"), netaddr));
  1012. c.insert(memberElement, dl);
  1013. c.insert(membersElement, memberElement);
  1014. if (memberInfo !== undefined) {
  1015. if (memberInfo.termurl !== undefined) {
  1016. c.insert(dl, c.insert(c.create("dt"), "Term URL"));
  1017. c.insert(dl, c.insert(c.create("dd"), c.insert(c.create("a", {
  1018. "href" : memberInfo.termurl
  1019. }), memberInfo.termurl)));
  1020. }
  1021. } else {
  1022. console.log("Member info of", name, "not found - refresh member infos");
  1023. refreshMemberInfos = true;
  1024. }
  1025. }
  1026. c.ajax(c.ajaxPrefix + "/v1/cluster/", "GET", undefined, function (r) {
  1027. "use strict";
  1028. var members = c.main._listToObject(r.members),
  1029. failed = c.main._listToObject(r.failed);
  1030. // Populate info section
  1031. c.$("mname").innerHTML = c.esc(r.members[0]);
  1032. c.$("mnetaddr").innerHTML = c.esc(r.members[1]);
  1033. // Populate status section
  1034. c.$("members").innerHTML = "";
  1035. c.getObjectKeys(members).forEach(function (m) {
  1036. insertMember(m, members[m]);
  1037. });
  1038. if (refreshMemberInfos) {
  1039. c.main.PopulateMemberInfo();
  1040. }
  1041. if (c.pollStatus) {
  1042. window.setTimeout(c.main.PopulateMembers, c.pollInterval);
  1043. }
  1044. });
  1045. },
  1046. // Convert a stateinfo list into an object.
  1047. //
  1048. _listToObject : function (l) {
  1049. "use strict";
  1050. var o = {};
  1051. if (l) {
  1052. for (var i=0;i<l.length;i+=2) {
  1053. o[l[i]] = l[i+1];
  1054. }
  1055. }
  1056. return o;
  1057. },
  1058. // Update the cluster log.
  1059. //
  1060. RunLogMonitor : function () {
  1061. "use strict";
  1062. c.ajax(c.ajaxPrefix + "/v1/cluster/log", "GET", undefined, function (r) {
  1063. c.$("log").innerHTML = c.esc(r.join("\n"));
  1064. if (c.pollLog) {
  1065. window.setTimeout(c.main.RunLogMonitor, c.pollInterval);
  1066. }
  1067. });
  1068. }
  1069. };
  1070. </script>
  1071. </body>
  1072. </html>
  1073. `