index.html 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>MiLight Hub</title>
  6. <meta name="description" content="The HTML5 Herald">
  7. <meta name="author" content="SitePoint">
  8. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"/>
  9. <link href="https://gitcdn.github.io/bootstrap-toggle/2.2.2/css/bootstrap-toggle.min.css" rel="stylesheet"/>
  10. <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/9.7.0/css/bootstrap-slider.min.css" rel="stylesheet"/>
  11. <link href="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.4/css/selectize.bootstrap3.min.css" rel="stylesheet"/>
  12. <!--[if lt IE 9]>
  13. <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
  14. <![endif]-->
  15. <style>
  16. .header-row { border-bottom: 1px solid #ccc; }
  17. label { display: block; }
  18. .radio-option { padding: 0 5px; cursor: pointer; }
  19. .command-buttons { list-style: none; margin: 0; padding: 0; }
  20. .command-buttons li { display: inline-block; margin-right: 1em; }
  21. .form-entry { margin: 0 0 20px 0; }
  22. .form-entry .form-control { width: 20em; }
  23. .form-entry label { display: inline-block; }
  24. label:not(.error) .error-info { display: none; }
  25. .error-info { color: #f00; font-size: 0.7em; }
  26. .error-info:before { content: '('; }
  27. .error-info:after { content: ')'; }
  28. .header-btn { margin: 20px; }
  29. .btn-secondary {
  30. background-color: #fff;
  31. border: 1px solid #ccc;
  32. }
  33. .inline { display: inline-block; }
  34. .hue-picker {
  35. height: 2em;
  36. width: 100%;
  37. display: block;
  38. }
  39. .hue-picker-inner {
  40. height: 2em;
  41. width: calc(100% - 3em);
  42. display: inline-block;
  43. cursor: pointer;
  44. background: linear-gradient(to right,
  45. rgb(255, 0, 0) 0%,
  46. rgb(255, 255, 0) 16.6667%,
  47. rgb(0, 255, 0) 33.3333%,
  48. rgb(0, 255, 255) 50%,
  49. rgb(0, 0, 255) 66.6667%,
  50. rgb(255, 0, 255) 83.3333%,
  51. rgb(255, 0, 0) 100%
  52. );
  53. }
  54. .hue-value-display {
  55. border: 1px solid #000;
  56. margin-left: 0.5em;
  57. width: 2em;
  58. height: 2em;
  59. display: inline-block;
  60. }
  61. .plus-minus-group {
  62. overflow: auto;
  63. width: 100%;
  64. clear: both;
  65. display: block;
  66. }
  67. .plus-minus-group button:first-of-type {
  68. border-bottom-right-radius: 0;
  69. border-top-right-radius: 0;
  70. float: left;
  71. display: block;
  72. }
  73. .plus-minus-group button:last-of-type {
  74. border-bottom-left-radius: 0;
  75. border-top-left-radius: 0;
  76. display: block;
  77. }
  78. .plus-minus-group .title {
  79. border-width: 1px 0;
  80. border-color: #ccc;
  81. border-style: solid;
  82. padding: 5px 5px 5px 7px;
  83. margin: 0;
  84. height: 34px;
  85. line-height: 1.49;
  86. float: left;
  87. display: block;
  88. }
  89. .field-help {
  90. display: inline-block;
  91. font-size: 1.25em;
  92. margin-left: 0.5em;
  93. }
  94. </style>
  95. </head>
  96. <body>
  97. <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
  98. <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
  99. <script src="https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js"></script>
  100. <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
  101. <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/9.7.0/bootstrap-slider.min.js"></script>
  102. <script src="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.4/js/standalone/selectize.min.js"></script>
  103. <script lang="text/javascript">
  104. var FORM_SETTINGS = [
  105. "admin_username", "admin_password", "ce_pin", "csn_pin", "packet_repeats",
  106. "http_repeat_factor", "auto_restart_period"
  107. ];
  108. var FORM_SETTINGS_HELP = {
  109. packet_repeats : "The number of times to repeat RF packets sent to bulbs",
  110. http_repeat_factor : "Multiplicative factor on packet_repeats for " +
  111. "requests initiated by the HTTP API. UDP API typically receives " +
  112. "duplicate packets, so more repeats should be used for HTTP.",
  113. auto_restart_period : "Automatically restart the device every number of " +
  114. "minutes specified. Use 0 for disabled."
  115. }
  116. var UDP_PROTOCOL_VERSIONS = [ 5, 6 ];
  117. var DEFAULT_UDP_PROTOCL_VERSION = 5;
  118. var selectize;
  119. var toHex = function(v) {
  120. return "0x" + (v).toString(16).toUpperCase();
  121. }
  122. var activeUrl = function() {
  123. var deviceId = $('#deviceId option:selected').val()
  124. , groupId = $('#groupId input:checked').data('value')
  125. , mode = getCurrentMode();
  126. if (deviceId == "") {
  127. alert("Please enter a device ID.");
  128. throw "Must enter device ID";
  129. }
  130. if (! $('#group-option').data('for').split(',').includes(mode)) {
  131. groupId = 0;
  132. }
  133. return "/gateways/" + deviceId + "/" + mode + "/" + groupId;
  134. }
  135. var getCurrentMode = function() {
  136. return $('input[name="mode"]:checked').data('value');
  137. };
  138. var updateGroup = _.throttle(
  139. function(params) {
  140. $.ajax(
  141. activeUrl(),
  142. {
  143. method: 'PUT',
  144. data: JSON.stringify(params),
  145. contentType: 'application/json'
  146. }
  147. );
  148. },
  149. 1000
  150. );
  151. var sendCommand = _.throttle(
  152. function(params) {
  153. $.ajax(
  154. '/system',
  155. {
  156. method: 'POST',
  157. data: JSON.stringify(params),
  158. contentType: 'application/json'
  159. }
  160. );
  161. },
  162. 1000
  163. )
  164. var sniffRequest;
  165. var sniffing = false;
  166. var getTraffic = function() {
  167. var sniffType = $('#sniff-type input:checked').data('value');
  168. sniffRequest = $.get('/gateway_traffic/' + sniffType, function(data) {
  169. $('#sniffed-traffic').html(data + $('#sniffed-traffic').html());
  170. getTraffic();
  171. });
  172. };
  173. var gatewayServerRow = function(deviceId, port, version) {
  174. var elmt = '<tr>';
  175. elmt += '<td>';
  176. elmt += '<input name="deviceIds[]" class="form-control" value="' + deviceId + '"/>';
  177. elmt += '</td>';
  178. elmt += '<td>'
  179. elmt += '<input name="ports[]" class="form-control" value="' + port + '"/>';;
  180. elmt += '</td>';
  181. elmt += '<td>';
  182. elmt += '<div class="btn-group" data-toggle="buttons">';
  183. for (var i = 0; i < UDP_PROTOCOL_VERSIONS.length; i++) {
  184. var val = UDP_PROTOCOL_VERSIONS[i]
  185. , selected = (version == val || (val == DEFAULT_UDP_PROTOCL_VERSION && !UDP_PROTOCOL_VERSIONS.includes(version)));
  186. elmt += '<label class="btn btn-secondary' + (selected ? ' active' : '') + '">';
  187. elmt += '<input type="radio" name="versions[]" autocomplete="off" data-value="' + val + '" '
  188. + (selected ? 'checked' : '') +'> ' + val;
  189. elmt += '</label>';
  190. }
  191. elmt += '</div></td>';
  192. elmt += '<td>';
  193. elmt += '<button class="btn btn-danger remove-gateway-server">';
  194. elmt += '<i class="glyphicon glyphicon-remove"></i>';
  195. elmt += '</button>';
  196. elmt += '</td>';
  197. elmt += '</tr>';
  198. return elmt;
  199. }
  200. var loadSettings = function() {
  201. $.getJSON('/settings', function(val) {
  202. Object.keys(val).forEach(function(k) {
  203. var field = $('#settings input[name="' + k + '"]');
  204. if (field.length > 0) {
  205. field.val(val[k]);
  206. }
  207. });
  208. if (val.device_ids) {
  209. selectize.clearOptions();
  210. val.device_ids.forEach(function(v) {
  211. selectize.addOption({text: toHex(v), value: v});
  212. });
  213. selectize.refreshOptions();
  214. }
  215. var gatewayForm = $('#gateway-server-configs').html('');
  216. if (val.gateway_configs) {
  217. val.gateway_configs.forEach(function(v) {
  218. gatewayForm.append(gatewayServerRow(toHex(v[0]), v[1], v[2]));
  219. });
  220. }
  221. });
  222. };
  223. var saveGatewayConfigs = function() {
  224. var form = $('#gateway-server-form')
  225. , errors = false;
  226. $('input', form).removeClass('error');
  227. var deviceIds = $('input[name="deviceIds[]"]', form).map(function(i, v) {
  228. var val = $(v).val();
  229. if (isNaN(val)) {
  230. errors = true;
  231. $(v).addClass('error');
  232. return null;
  233. } else {
  234. return val;
  235. }
  236. });
  237. var ports = $('input[name="ports[]"]', form).map(function(i, v) {
  238. var val = $(v).val();
  239. if (isNaN(val)) {
  240. errors = true;
  241. $(v).addClass('error');
  242. return null;
  243. } else {
  244. return val;
  245. }
  246. });
  247. var versions = $('.active input[name="versions[]"]', form).map(function(i, v) {
  248. return $(v).data('value');
  249. });
  250. if (!errors) {
  251. var data = [];
  252. for (var i = 0; i < deviceIds.length; i++) {
  253. data[i] = [deviceIds[i], ports[i], versions[i]];
  254. }
  255. $.ajax(
  256. '/settings',
  257. {
  258. method: 'put',
  259. contentType: 'application/json',
  260. data: JSON.stringify({gateway_configs: data})
  261. }
  262. )
  263. }
  264. };
  265. var deviceIdError = function(v) {
  266. if (!v) {
  267. $('#device-id-label').removeClass('error');
  268. } else {
  269. $('#device-id-label').addClass('error');
  270. $('#device-id-label .error-info').html(v);
  271. }
  272. };
  273. var updateModeOptions = function() {
  274. var currentMode = getCurrentMode();
  275. $('.mode-option').map(function() {
  276. if ($(this).data('for').split(',').includes(currentMode)) {
  277. $(this).show();
  278. } else {
  279. $(this).hide();
  280. }
  281. });
  282. };
  283. $(function() {
  284. $('.radio-option').click(function() {
  285. $(this).prev().prop('checked', true);
  286. });
  287. var hueDragging = false;
  288. var colorUpdated = function(e) {
  289. var x = e.pageX - $(this).offset().left
  290. , pct = x/(1.0*$(this).width())
  291. , hue = Math.round(360*pct)
  292. ;
  293. $('.hue-value-display').css({
  294. backgroundColor: "hsl(" + hue + ",100%,50%)"
  295. });
  296. updateGroup({hue: hue});
  297. };
  298. $('.hue-picker-inner')
  299. .mousedown(function(e) {
  300. hueDragging = true;
  301. colorUpdated.call(this, e);
  302. })
  303. .mouseup(function(e) {
  304. hueDragging = false;
  305. })
  306. .mouseout(function(e) {
  307. hueDragging = false;
  308. })
  309. .mousemove(function(e) {
  310. if (hueDragging) {
  311. colorUpdated.call(this, e);
  312. }
  313. });
  314. $('.slider').slider();
  315. $('.raw-update').change(function() {
  316. var data = {}
  317. , val = $(this).attr('type') == 'checkbox' ? $(this).is(':checked') : $(this).val()
  318. ;
  319. data[$(this).attr('name')] = val;
  320. updateGroup(data);
  321. });
  322. $('.command-btn').click(function() {
  323. updateGroup({command: $(this).data('command')});
  324. });
  325. $('.system-btn').click(function() {
  326. sendCommand({command: $(this).data('command')});
  327. });
  328. $('#sniff').click(function() {
  329. if (sniffing) {
  330. sniffRequest.abort();
  331. sniffing = false;
  332. $(this).html('Start Sniffing');
  333. } else {
  334. sniffing = true;
  335. getTraffic();
  336. $(this).html('Stop Sniffing');
  337. }
  338. });
  339. $('#add-server-btn').click(function() {
  340. $('#gateway-server-configs').append(gatewayServerRow('', ''));
  341. });
  342. $('#mode').change(updateModeOptions);
  343. $('body').on('click', '.remove-gateway-server', function() {
  344. $(this).closest('tr').remove();
  345. });
  346. selectize = $('#deviceId').selectize({
  347. create: true,
  348. sortField: 'text',
  349. onOptionAdd: function(v, item) {
  350. item.value = parseInt(item.value);
  351. },
  352. createFilter: function(v) {
  353. if (! v.match(/^(0x[a-fA-F0-9]{1,4}|[0-9]{1,5})$/)) {
  354. deviceIdError("Must be an integer between 0x0000 and 0xFFFF");
  355. return false;
  356. }
  357. var value = parseInt(v);
  358. if (! (0 <= v && v <= 0xFFFF)) {
  359. deviceIdError("Must be an integer between 0x0000 and 0xFFFF");
  360. return false;
  361. }
  362. deviceIdError(false);
  363. return true;
  364. }
  365. });
  366. selectize = selectize[0].selectize;
  367. var settings = "";
  368. FORM_SETTINGS.forEach(function(k) {
  369. var elmt = '<div class="form-entry">';
  370. elmt += '<label for="' + k + '">' + k + '</label>';
  371. if (FORM_SETTINGS_HELP[k]) {
  372. elmt += '<div class="field-help" data-help-text="' + FORM_SETTINGS_HELP[k] + '"></div>';
  373. }
  374. elmt += '<input type="text" class="form-control" name="' + k + '"/>';
  375. elmt += '</div>';
  376. settings += elmt;
  377. });
  378. $('#settings').prepend(settings);
  379. $('#settings').submit(function(e) {
  380. var obj = {};
  381. FORM_SETTINGS.forEach(function(k) {
  382. obj[k] = $('#settings input[name="' + k + '"]').val();
  383. });
  384. // pretty hacky. whatever.
  385. obj.device_ids = _.map(
  386. $('.selectize-control .option'),
  387. function(x) {
  388. return $(x).data('value')
  389. }
  390. );
  391. $.ajax(
  392. "/settings",
  393. {
  394. method: 'put',
  395. contentType: 'application/json',
  396. data: JSON.stringify(obj)
  397. }
  398. );
  399. e.preventDefault();
  400. return false;
  401. });
  402. $('#gateway-server-form').submit(function(e) {
  403. saveGatewayConfigs();
  404. e.preventDefault();
  405. return false;
  406. });
  407. $('.field-help').each(function() {
  408. var elmt = $('<i></i>')
  409. .addClass('glyphicon glyphicon-question-sign')
  410. .tooltip({
  411. placement: 'top',
  412. title: $(this).data('help-text'),
  413. container: 'body'
  414. });
  415. $(this).append(elmt);
  416. });
  417. loadSettings();
  418. updateModeOptions();
  419. });
  420. </script>
  421. <div class="container">
  422. <div class="row header-row">
  423. <div class="col-sm-12">
  424. <h1>
  425. Control Lights
  426. </h1>
  427. </div>
  428. </div>
  429. <div>&nbsp;</div>
  430. <div class="row">
  431. <div class="col-sm-4">
  432. <label for="deviceId" id="device-id-label">
  433. Device Id
  434. <span class="error-info"></span>
  435. </label>
  436. <select id="deviceId" placeholder="Enter hub ID">
  437. </select>
  438. </div>
  439. <div class="col-sm-3">
  440. <div class="mode-option" id="group-option" data-for="cct,rgbw,rgb_cct">
  441. <label for="groupId">Group</label>
  442. <div class="btn-group" id="groupId" data-toggle="buttons">
  443. <label class="btn btn-secondary active">
  444. <input type="radio" name="options" autocomplete="off" data-value="1" checked> 1
  445. </label>
  446. <label class="btn btn-secondary">
  447. <input type="radio" name="options" autocomplete="off" data-value="2"> 2
  448. </label>
  449. <label class="btn btn-secondary">
  450. <input type="radio" name="options" autocomplete="off" data-value="3"> 3
  451. </label>
  452. <label class="btn btn-secondary">
  453. <input type="radio" name="options" autocomplete="off" data-value="4"> 4
  454. </label>
  455. <label class="btn btn-secondary">
  456. <input type="radio" name="options" autocomplete="off" data-value="0"> All
  457. </label>
  458. </div>
  459. </div>
  460. </div>
  461. <div class="col-sm-4">
  462. <label for="groupId">Mode</label>
  463. <div class="btn-group" id="mode" data-toggle="buttons">
  464. <label class="btn btn-secondary active">
  465. <input type="radio" name="mode" autocomplete="off" data-value="rgbw" checked> RGBW
  466. </label>
  467. <label class="btn btn-secondary">
  468. <input type="radio" name="mode" autocomplete="off" data-value="cct"> CCT
  469. </label>
  470. <label class="btn btn-secondary">
  471. <input type="radio" name="mode" autocomplete="off" data-value="rgb_cct"> RGB+CCT
  472. </label>
  473. <label class="btn btn-secondary">
  474. <input type="radio" name="mode" autocomplete="off" data-value="rgb"> RGB
  475. </label>
  476. </div>
  477. </div>
  478. </div>
  479. <div class="row"><div class="col-sm-12">
  480. <div class="mode-option" data-for="rgbw,rgb_cct,rgb">
  481. <div class="row">
  482. <div class="col-sm-12">
  483. <h5>Hue</h5>
  484. </div>
  485. </div>
  486. <div class="row">
  487. <div class="col-sm-6">
  488. <span class="hue-picker">
  489. <span class="hue-picker-inner"></span>
  490. <span class="hue-value-display"></span>
  491. </span>
  492. </div>
  493. </div>
  494. </div>
  495. </div></div>
  496. <div class="mode-option" data-for="rgb_cct">
  497. <div class="row">
  498. <div class="col-sm-12">
  499. <h5>Saturation</h5>
  500. </div>
  501. </div>
  502. <div class="row">
  503. <div class="col-sm-6">
  504. <input class="slider raw-update" name="saturation"
  505. data-slider-min="0"
  506. data-slider-max="100"
  507. data-slider-value="100"
  508. />
  509. </div>
  510. </div>
  511. </div>
  512. <div class="mode-option" data-for="cct,rgb_cct">
  513. <div class="row">
  514. <div class="col-sm-12">
  515. <h5>Color Temperature</h5>
  516. </div>
  517. </div>
  518. <div class="row">
  519. <div class="col-sm-6">
  520. <input class="slider raw-update" name="temperature"
  521. data-slider-min="0"
  522. data-slider-max="100"
  523. data-slider-value="100"
  524. />
  525. </div>
  526. </div>
  527. </div>
  528. <div class="row">
  529. <div class="col-sm-12">
  530. <h5>Brightness</h5>
  531. </div>
  532. </div>
  533. <div class="row">
  534. <div class="col-sm-12">
  535. <input class="slider raw-update" name="level"
  536. data-slider-min="0"
  537. data-slider-max="100"
  538. data-slider-value="100"
  539. />
  540. </div>
  541. </div>
  542. <div class="row">
  543. <div class="col-sm-12">
  544. <h5>Commands</h5>
  545. </div>
  546. </div>
  547. <div class="row">
  548. <div class="col-sm-12">
  549. <ul class="command-buttons">
  550. <li>
  551. <input type="checkbox" name="status" class="raw-update" data-toggle="toggle" checked/>
  552. </li>
  553. <div class="mode-option inline" data-for="rgbw,rgb_cct">
  554. <li>
  555. <button type="button" class="btn btn-secondary command-btn" data-command="set_white">White</button>
  556. </li>
  557. </div>
  558. <li>
  559. <button type="button" class="btn btn-success command-btn" data-command="pair">
  560. <i class="glyphicon glyphicon-plus"></i>
  561. Pair
  562. </button>
  563. </li>
  564. <li>
  565. <button type="button" class="btn btn-danger command-btn" data-command="unpair">
  566. <i class="glyphicon glyphicon-remove"></i>
  567. Unpair
  568. </button>
  569. </li>
  570. </ul>
  571. <p></p>
  572. <ul class="command-buttons">
  573. <div class="mode-option inline" data-for="rgb,rgbw,rgb_cct">
  574. <li>
  575. <div class="plus-minus-group">
  576. <button type="button" class="btn btn-default btn-number command-btn" data-command="previous_mode">
  577. <span class="glyphicon glyphicon-minus"></span>
  578. </button>
  579. <span class="title">Mode</span>
  580. <button type="button" class="btn btn-default btn-number command-btn clearfix" data-command="next_mode">
  581. <span class="glyphicon glyphicon-plus"></span>
  582. </button>
  583. <div class="clearfix"></div>
  584. </div>
  585. </li>
  586. </div>
  587. <div class="mode-option inline" data-for="rgb,rgbw,rgb_cct">
  588. <li>
  589. <div class="plus-minus-group">
  590. <button type="button" class="btn btn-default btn-number command-btn" data-command="mode_speed_down">
  591. <span class="glyphicon glyphicon-minus"></span>
  592. </button>
  593. <span class="title">Speed</span>
  594. <button type="button" class="btn btn-default btn-number command-btn" data-command="mode_speed_up">
  595. <span class="glyphicon glyphicon-plus"></span>
  596. </button>
  597. <div class="clearfix"></div>
  598. </div>
  599. </li>
  600. </div>
  601. </ul>
  602. </div>
  603. </div>
  604. <div class="row header-row">
  605. <div class="col col-sm-10">
  606. <h1>Gateway Servers</h1>
  607. </div>
  608. <div class="col col-sm-2">
  609. <button class="btn btn-success header-btn" id="add-server-btn">
  610. <i class="glyphicon glyphicon-plus"></i>
  611. Add Server
  612. </button>
  613. </div>
  614. </div>
  615. <div class="row">
  616. <div class="col col-sm-12">
  617. <form id="gateway-server-form">
  618. <table class="table">
  619. <thead>
  620. <tr>
  621. <th>Device ID</th>
  622. <th>UDP Port</th>
  623. <th>Protocol Version</th>
  624. </tr>
  625. </thead>
  626. <tbody id="gateway-server-configs">
  627. </tbody>
  628. </table>
  629. <input type="submit" class="btn btn-success" value="Save" />
  630. </form>
  631. </div>
  632. </div>
  633. <div>&nbsp;</div>
  634. <div class="row header-row">
  635. <div class="col-sm-12">
  636. <h1>Settings</h1>
  637. </div>
  638. </div>
  639. <div>&nbsp;</div>
  640. <div class="row">
  641. <div class="col-sm-12">
  642. <form action="#" id="settings">
  643. <input type="submit" class="btn btn-success" value="Submit" />
  644. </form>
  645. </div>
  646. </div>
  647. <div class="row header-row">
  648. <div class="col-sm-12">
  649. <h1>Sniff Traffic</h1>
  650. </div>
  651. </div>
  652. <div>&nbsp;</div>
  653. <div class="row">
  654. <div class="col-sm-12">
  655. <button type="button" id="sniff" class="btn btn-primary">Start Sniffing</button>
  656. <div class="btn-group" id="sniff-type" data-toggle="buttons">
  657. <label class="btn btn-secondary active">
  658. <input type="radio" name="options" autocomplete="off" data-value="rgbw" checked> RGBW
  659. </label>
  660. <label class="btn btn-secondary">
  661. <input type="radio" name="options" autocomplete="off" data-value="cct"> CCT
  662. </label>
  663. <label class="btn btn-secondary">
  664. <input type="radio" name="options" autocomplete="off" data-value="rgb_cct"> RGB+CCT
  665. </label>
  666. <label class="btn btn-secondary">
  667. <input type="radio" name="options" autocomplete="off" data-value="rgb"> RGB
  668. </label>
  669. </div>
  670. <div> &nbsp; </div>
  671. <pre id="sniffed-traffic"></pre>
  672. </div>
  673. </div>
  674. <div class="row header-row">
  675. <div class="col-sm-12">
  676. <h1>Admin</h1>
  677. </div>
  678. </div>
  679. <div>&nbsp;</div>
  680. <div class="row">
  681. <div class="col-sm-12">
  682. <button type="button" class="btn btn-danger system-btn" data-command="restart">
  683. Restart
  684. </button>
  685. </div>
  686. </div>
  687. </div>
  688. </body>
  689. </html>