engine.tpl 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. {{ define "engine" }}
  2. {{ template "header" .}}
  3. <div id="wrapper">
  4. {{ template "navigation" .}}
  5. <!-- Page Content -->
  6. <div id="page-wrapper">
  7. <div class="container-fluid">
  8. <div class="row">
  9. <h4 class="page-header">{{.Title}}</h4>
  10. <div class="panel panel-default">
  11. <!--<div class="panel-heading">Send manual commands to engine</div>-->
  12. <div class="panel-body">
  13. <div class="col-lg-9">
  14. <div class="panel-group" id="accordion">
  15. <div class="panel panel-default">
  16. <div class="panel-heading">
  17. <h4 class="panel-title">
  18. <a data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="false" class="collapsed">Send manual commands to engine</a>
  19. </h4>
  20. </div> <!-- ./panel-heading -->
  21. <div id="collapseOne" class="panel-collapse collapse" aria-expanded="false" style="height: 0px;">
  22. <div class="panel-body">
  23. <form role="form" id="formEngine" action="javascript:void(0);" onsubmit="formEngineSubmit()">
  24. <fieldset id="formEngineFieldSet">
  25. <div class="col-lg-9">
  26. <div class="form-group">
  27. <label>Destination</label>
  28. <select class="form-control" name="Destination" id="Destination" size="1">
  29. <option value="00000000-0000-0000-0000-000000000000" selected="selected" disabled="disabled">Please choose a destination cube</option>
  30. {{.DestinationOptions}}
  31. </select>
  32. <label>Agent</label>
  33. <select class="form-control" name="Agent" id="Agent" size="1">
  34. <option value="00000000-0000-0000-0000-000000000000" selected="selected" disabled="disabled">Please choose an agent</option>
  35. {{.AgentOptions}}
  36. </select>
  37. </div> <!-- /.form-group -->
  38. </div> <!-- ./col-lg-9 -->
  39. <div class="col-lg-3">
  40. <button type="submit" class="btn btn-outline btn-success"><i class="fa fa-check"></i>&nbsp;Submit</button>
  41. <button type="reset" class="btn btn-outline btn-warning"><i class="fa fa-trash-o"></i>&nbsp;Reset</button>
  42. </div> <!-- ./col-lg-3 -->
  43. </fieldset>
  44. </form>
  45. </div> <!-- ./panel-body -->
  46. </div> <!-- ./collapseOne -->
  47. </div> <!-- ./panel-default -->
  48. </div> <!-- ./panel-group accordion -->
  49. </div> <!-- ./col-lg-9 -->
  50. <div class="col-lg-3">
  51. <div class="panel panel-default">
  52. <div class="panel-heading">Engine master control</div>
  53. <div class="panel-body">
  54. <button type="button" id="clearLog" class="btn btn-default btn-circle"><i class="fa fa-trash-o" onclick="clearLog()"></i></button>
  55. <button type="button" id="startEngine" class="btn btn-success btn-circle"><i class="fa fa-check-circle" onclick="startEngine()"></i></button>
  56. <button type="button" id="oneStep" class="btn btn-warning btn-circle"><i class="fa fa-step-forward" onclick="oneStep()"></i></button>
  57. <button type="button" id="stopEngine" class="btn btn-danger btn-circle"><i class="fa fa-times-circle" onclick="stopEngine()"></i></button>
  58. </div> <!-- ./panel-body -->
  59. </div> <!-- ./panel -->
  60. </div> <!-- ./col-lg-3 -->
  61. </div> <!-- ./panel-body -->
  62. </div> <!-- ./panel -->
  63. </div> <!-- ./row -->
  64. <div id="alertMessage" class="alert alert-warning alert-dismissable" hidden style="display: none;">
  65. <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
  66. <p id="message">No valid active agents or destination cubes found.</p>
  67. </div>
  68. <div class="row">
  69. <div class="col-lg-12">
  70. <div id="engineResponse" name="engineResponse" contenteditable="false"></div>
  71. <!-- websockets will fill this in -->
  72. <script type="text/javascript">
  73. const wsuri = "ws://{{.Host}}{{.ServerPort}}{{.URLPathPrefix}}/wsEngine/";
  74. var conn = null; // WebSocket connection, must be sort-of-global-y (20170703)
  75. function formEngineConfig(enableStatus) {
  76. document.getElementById("formEngine").disabled = enableStatus;
  77. document.getElementById("formEngineFieldSet").disabled = enableStatus;
  78. }
  79. /*
  80. * WebSocket handler, called only when the window has loaded
  81. * and every time we suspect that the connection was closed (20170703)
  82. */
  83. function start() {
  84. if (window["WebSocket"]) {
  85. // start by preparing the scrollable response element
  86. var log = document.getElementById("engineResponse");
  87. log.height = 400;
  88. log.scrollTop = log.scrollHeight; // scroll to bottom - http://web.archive.org/web/20080821211053/http://radio.javaranch.com/pascarello/2005/12/14/1134573598403.html
  89. // now deal with the WebSocket
  90. conn = new WebSocket(wsuri);
  91. console.log("Attempting to connect to WebSocket on " + wsuri);
  92. conn.binaryType = "arraybuffer";
  93. conn.onopen = function() {
  94. console.log("Now connected to " + wsuri);
  95. formEngineConfig(false); // enable form, we can accept ws connections
  96. // when this is started, send a message to the server to tell that
  97. // we are ready to receive messages:
  98. var msg = {
  99. type: "status",
  100. subtype: "ready",
  101. text: "Client is ready now",
  102. id: ""
  103. };
  104. // Send the msg object as a JSON-formatted string.
  105. conn.send(JSON.stringify(msg));
  106. setInterval(check, 5000); // check if connection is active every five seconds
  107. }
  108. conn.onclose = function(evt) {
  109. //var msg = JSON.parse(evt.data);
  110. console.log("Connection closed; data received: " + evt.data);
  111. log.innerHTML += 'Connection closed - trying to reconnect<br />';
  112. log.scrollTop = log.scrollHeight;
  113. check(); // this should disable form as well
  114. }
  115. // Handler to deal with messages coming from the server (20170702)
  116. conn.onmessage = function(evt) {
  117. // see https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications
  118. var msg;
  119. if (typeof evt.data == "string") {
  120. msg = JSON.parse(evt.data);
  121. } else if (evt.data instanceof ArrayBuffer) {
  122. // Note: sometimes the websocket data comes as an ArrayBuffer, when the data is structured; sometimes it doesn't. To figure out both cases, we force the WebSocket to return an ArrayBuffer and extract the relevant JSON strings from there: https://developers.google.com/web/updates/2014/08/Easier-ArrayBuffer-String-conversion-with-the-Encoding-API (20170702)
  123. // The decode() method takes a DataView as a parameter, which is a wrapper on top of the ArrayBuffer.
  124. var dataView = new DataView(evt.data);
  125. // The TextDecoder interface is documented at http://encoding.spec.whatwg.org/#interface-textdecoder
  126. var decoder = new TextDecoder("utf-8");
  127. var decodedString = decoder.decode(dataView);
  128. // Now we should have a string in JSON
  129. msg = JSON.parse(decodedString);
  130. } else {
  131. // I have no idea how to decode any other type
  132. console.log("Unexpected type of event data, attempt to create a message out of it, even if it may fail.");
  133. msg = evt.data;
  134. }
  135. var logTxt = "";
  136. // check for message type from the server, right now it may be:
  137. // status: just add that message to the scrollable element
  138. // htmlControl: to turn on/off certain elements of the form
  139. // (to disallow submission of new data while the engine
  140. // has been manually ran for one Agent/Destination pair)
  141. switch (msg.type) {
  142. case "status":
  143. switch (msg.subtype) {
  144. case "info":
  145. logTxt = "<div class='alert alert-info'>" + msg.text + "</div>";
  146. break;
  147. case "success":
  148. logTxt = "<div class='alert alert-success'>" + msg.text + "</div>";
  149. break;
  150. case "warning":
  151. logTxt = "<div class='alert alert-warning'>" + msg.text + "</p>";
  152. break;
  153. case "error":
  154. case "danger":
  155. logTxt = "<div class='alert alert-danger'>" + msg.text + "</p>";
  156. break;
  157. default:
  158. logTxt = msg.text;
  159. break;
  160. };
  161. break;
  162. case "htmlControl":
  163. switch (msg.subtype) {
  164. case "disable":
  165. document.getElementById(msg.id).disabled = true;
  166. logTxt = "<em>Element " + msg.id + " disabled</em><br />";
  167. break;
  168. case "enable":
  169. document.getElementById(msg.id).disabled = false;
  170. logTxt = "<em>Element " + msg.id + " enabled</em><br />";
  171. break;
  172. default:
  173. logTxt = "Unknown subtype '" + msg.subtype + "'<br />";
  174. break;
  175. };
  176. break;
  177. default:
  178. logTxt = "Unknown type '" + msg.type + "' with text '" +
  179. msg.text + "'<br />";
  180. break;
  181. };
  182. // console.log('Received from server: (' + msg.type + '): "' +
  183. // msg.text + '"; writing on log: "' + logTxt + '"');
  184. log.innerHTML += logTxt;
  185. log.scrollTop = log.scrollHeight;
  186. }
  187. conn.onerror = function(err) {
  188. console.log("Error from WebSocket: " + err.data);
  189. log.innerHTML += "Error from WebSocket: " + err.data + "<br />";
  190. log.scrollTop = log.scrollHeight;
  191. check();
  192. }
  193. } else {
  194. log.innerHTML += "<b>Your browser does not support WebSockets.</b><br />";
  195. /*log.scrollTop = log.scrollHeight;*/
  196. }
  197. }
  198. /*
  199. * Main handling starts here, when we know that everything has been loaded
  200. */
  201. window.onload = function () {
  202. // Disable form if we have no destination cubes or active agents
  203. if ("{{ .DestinationOptions }}" == "" || "{{ .AgentOptions }}" == "") {
  204. formEngineConfig(true); // inthis context, disabled = true
  205. // we might give an explanation here, e.g. enable a hidden field
  206. // and put an answer there
  207. document.getElementById("alertMessage").style.display = 'block';
  208. }
  209. if (!conn || conn.readyState === WebSocket.CLOSED) {
  210. // if we have no valid connection yet, do not allow the form to be submitted (20170703)
  211. formEngineConfig(true);
  212. }
  213. start();
  214. };
  215. window.onunload = function () {
  216. if (conn.readyState === WebSocket.OPEN) {
  217. console.log("Cool, we are able to send a message informing the server that we are gone!");
  218. var msg = {
  219. type: "status",
  220. subtype: "gone",
  221. text: "",
  222. id: ""
  223. };
  224. conn.send(JSON.stringify(msg));
  225. }
  226. }
  227. // see https://stackoverflow.com/questions/3780511/reconnection-of-client-when-server-reboots-in-websocket
  228. function check() {
  229. // check if connection is ready; if not, try to start it again
  230. if(!conn || conn.readyState === WebSocket.CLOSED) {
  231. formEngineConfig(true); // disable form for now
  232. start();
  233. }
  234. }
  235. function formEngineSubmit() {
  236. // make sure we have a valid connection to the server
  237. console.log("formEngine was submitted!");
  238. if (!conn || conn.readyState === WebSocket.CLOSED) {
  239. console.log("Connection closed while sending formEngine data; trying to restart...");
  240. formEngineConfig(true); // to be sure the form remains disabled
  241. start(); // we could call check() here... (20170703)
  242. return false;
  243. } else if (conn.readyState === WebSocket.OPEN) {
  244. console.log("Connection OK while sending formEngine data - sending...");
  245. var msg = {
  246. type: "formSubmit",
  247. subtype: "",
  248. text: document.forms["formEngine"]["Destination"].value + '|' +
  249. document.forms["formEngine"]["Agent"].value,
  250. id: ""
  251. };
  252. conn.send(JSON.stringify(msg));
  253. return true;
  254. } else {
  255. console.log("Connection either starting or closing, not ready yet for sending — what to do?");
  256. // probably needs to be addressed somehow (20170703)
  257. return false;
  258. }
  259. }
  260. // This is just to get us an empty log again
  261. function clearLog() {
  262. var log = document.getElementById("engineResponse");
  263. log.innerHTML = "<p class='text-muted'><small><i>(log cleared on request)</i></small></p>";
  264. log.scrollTop = log.scrollHeight;
  265. window.setTimeout(function () {
  266. //log.innerHTML = "";
  267. log.scrollTop = log.scrollHeight;
  268. },5000);
  269. }
  270. function startEngine() {
  271. if (conn.readyState === WebSocket.OPEN) {
  272. var msg = {
  273. type: "engineControl",
  274. subtype: "start",
  275. text: "Start Engine NOW!",
  276. id: "startEngine"
  277. };
  278. conn.send(JSON.stringify(msg));
  279. return true;
  280. }
  281. document.getElementById("message").innerHTML = "WebSocket not connected!";
  282. document.getElementById("alertMessage").style.display = 'block';
  283. return false;
  284. }
  285. function oneStep() {
  286. if (conn.readyState === WebSocket.OPEN) {
  287. var msg = {
  288. type: "engineControl",
  289. subtype: "one-step",
  290. text: "Run Engine once, then stop",
  291. id: "oneStep"
  292. };
  293. conn.send(JSON.stringify(msg));
  294. return true;
  295. }
  296. document.getElementById("message").innerHTML = "WebSocket not connected!";
  297. document.getElementById("alertMessage").style.display = 'block';
  298. return false;
  299. }
  300. function stopEngine() {
  301. if (conn.readyState === WebSocket.OPEN) {
  302. var msg = {
  303. type: "engineControl",
  304. subtype: "stop",
  305. text: "Stop Engine NOW!",
  306. id: "stopEngine"
  307. };
  308. conn.send(JSON.stringify(msg));
  309. return true;
  310. }
  311. document.getElementById("message").innerHTML = "WebSocket not connected!";
  312. document.getElementById("alertMessage").style.display = 'block';
  313. return false;
  314. }
  315. </script>
  316. <noscript>Look, if you don't even bother to turn on JavaScript, you will get nothing.</noscript>
  317. {{ if .Content }}
  318. {{ .Content }}
  319. {{ end }}
  320. {{ if .ButtonText }}
  321. <a href="{{.URLPathPrefix}}{{ .ButtonURL }}">
  322. <button type="button" class="btn btn-outline btn-primary btn-lg">{{ .ButtonText }}</button>
  323. </a>
  324. {{ end }}
  325. </div>
  326. <!-- /.col-lg-12 -->
  327. </div>
  328. <!-- /.row -->
  329. </div>
  330. <!-- /.container-fluid -->
  331. </div>
  332. <!-- /#page-wrapper -->
  333. </div>
  334. <!-- /#wrapper -->
  335. {{ template "footer" .}}
  336. {{ end }}