Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4823 lines
277 KiB

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>ESPurna 0.0.0</title>
  5. <meta charset="utf-8">
  6. <link rel="shortcut icon" href="data:image/png;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABMLAAATCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJP/0AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACT/9H8k//QfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJP/0DAAAAAAAAAAAAAAAAAC6/zYj/vT/JP/0bwAAAAAAAAAAAAAAACT/9FYAAAAAAAAAAAAAAAAAAAAAAAAAACT/9JEk//SWJP/0JAAAAAAE0f6mGvH3/yX/9MsAAAAAANL/cxvy9/8k//QmAAAAAAAAAAAAAAAAAAAAAAAAAAAAkf8EHvX2+SX/9P8j8fXNBMv++A/l+/8V6/n/AsT+3Aau/v8m//TKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADJ/3MN3/v/E+v5/wBl//8Af///GJf0/wFb//8U0vn/JP/0WQAAAAAAAAAAAAAAAAAAAAAk//QIJP/0NST/9Ggm//OGC9z8/QXZ/v9QeNn/czjG/1pB0/8AZv//DJj8/x7b9v8l//TTJP70aQAAAAAAAAAAJP/0RiT/9KYk//T4Jf/0/xDi+v8Fs/7/Qmvf/7gbpP+xH6f/NFHm/wBm//8AXP//Eqv6/yX/9P8k//TzJP/0UwAAAAAAAAAAAAAAABXZ9ZQFtP3/A6v//zyO4v8NXvn/bznI/wBm//8Ij/3/EuL61xHn+ngp//MdAAAAAAAAAAAAAAAAAAAAAAy69BsLuvX/EeX6/w7g+/8Hzf3/AF7//wqN+/8Bcv//CoD8/yj/82MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAk/ISJf/0syb/9P8R5Pr/Brn9/wSi/v8K3vz/B9r9/w/S+v8j+PT7I//0BQAAAAAAAAAAAAAAAAAAAAAAAAAAJP/0OCT/9P8l//T/Dd/76QTN/u8Z8/j/Jv/z4ADL/0sN3/vhJf/0/yT/9IwAAAAAAAAAAAAAAAAAAAAAAAAAACT/9Lwk//R5AAAAAADM/6kI1Pw+JP/0/yT/9IsAAAAAAAAAACT/9CAk//TYJP/0HwAAAAAAAAAAAAAAACT/9AIAAAAAAAAAAAAAAAAAzf8WAAAAACT/9OYk//Q3AAAAAAAAAAAAAAAAAAAAACT/9BMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAk//SDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJP/0AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v8AAP5/AADcdwAAxEcAAMAPAADgDwAAAAMAAAAAAADgAwAAwA8AAMAHAADABwAAyGMAALp7AAD+/wAA/v8AAA==">
  7. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  8. <!-- build:css style.css -->
  9. <style>
  10. html {
  11. -ms-text-size-adjust: 100%;
  12. -webkit-text-size-adjust: 100%;
  13. font-family: sans-serif;
  14. }
  15. body {
  16. margin: 0;
  17. }
  18. article,
  19. aside,
  20. details,
  21. figcaption,
  22. figure,
  23. footer,
  24. header,
  25. hgroup,
  26. main,
  27. menu,
  28. nav,
  29. section,
  30. summary {
  31. display: block;
  32. }
  33. audio, canvas, progress, video {
  34. display: inline-block;
  35. vertical-align: baseline;
  36. }
  37. audio:not([controls]) {
  38. display: none;
  39. height: 0;
  40. }
  41. [hidden], template {
  42. display: none;
  43. }
  44. a {
  45. background-color: transparent;
  46. }
  47. a:active, a:hover {
  48. outline: 0;
  49. }
  50. abbr[title] {
  51. border-bottom: 1px dotted;
  52. }
  53. b, strong {
  54. font-weight: 700;
  55. }
  56. dfn {
  57. font-style: italic;
  58. }
  59. h1 {
  60. font-size: 2em;
  61. margin: 0.67em 0;
  62. }
  63. mark {
  64. background: #ff0;
  65. color: #000;
  66. }
  67. small {
  68. font-size: 80%;
  69. }
  70. sub, sup {
  71. font-size: 75%;
  72. line-height: 0;
  73. position: relative;
  74. vertical-align: baseline;
  75. }
  76. sup {
  77. top: -0.5em;
  78. }
  79. sub {
  80. bottom: -0.25em;
  81. }
  82. img {
  83. border: 0;
  84. }
  85. svg:not(:root) {
  86. overflow: hidden;
  87. }
  88. figure {
  89. margin: 1em 40px;
  90. }
  91. hr {
  92. -webkit-box-sizing: content-box;
  93. box-sizing: content-box;
  94. height: 0;
  95. }
  96. pre {
  97. overflow: auto;
  98. }
  99. code, kbd, pre, samp {
  100. font-family: monospace, monospace;
  101. font-size: 1em;
  102. }
  103. button, input, optgroup, select, textarea {
  104. color: inherit;
  105. font: inherit;
  106. margin: 0;
  107. }
  108. button {
  109. overflow: visible;
  110. }
  111. button, select {
  112. text-transform: none;
  113. }
  114. button, html input[type=button], input[type=reset], input[type=submit] {
  115. -webkit-appearance: button;
  116. cursor: pointer;
  117. }
  118. button[disabled], html input[disabled] {
  119. cursor: default;
  120. }
  121. button::-moz-focus-inner, input::-moz-focus-inner {
  122. border: 0;
  123. padding: 0;
  124. }
  125. input {
  126. line-height: normal;
  127. }
  128. input[type=checkbox], input[type=radio] {
  129. -webkit-box-sizing: border-box;
  130. box-sizing: border-box;
  131. padding: 0;
  132. }
  133. input[type=number]::-webkit-inner-spin-button,
  134. input[type=number]::-webkit-outer-spin-button {
  135. height: auto;
  136. }
  137. input[type=search] {
  138. -webkit-appearance: textfield;
  139. -webkit-box-sizing: content-box;
  140. box-sizing: content-box;
  141. }
  142. input[type=search]::-webkit-search-cancel-button,
  143. input[type=search]::-webkit-search-decoration {
  144. -webkit-appearance: none;
  145. }
  146. fieldset {
  147. border: 1px solid silver;
  148. margin: 0 2px;
  149. padding: 0.35em 0.625em 0.75em;
  150. }
  151. legend {
  152. border: 0;
  153. padding: 0;
  154. }
  155. textarea {
  156. overflow: auto;
  157. }
  158. optgroup {
  159. font-weight: 700;
  160. }
  161. table {
  162. border-collapse: collapse;
  163. border-spacing: 0;
  164. }
  165. td, th {
  166. padding: 0;
  167. }
  168. .hidden, [hidden] {
  169. display: none !important;
  170. }
  171. .pure-img {
  172. display: block;
  173. height: auto;
  174. max-width: 100%;
  175. }
  176. .pure-g {
  177. -ms-flex-flow: row wrap;
  178. -ms-flex-line-pack: start;
  179. -webkit-align-content: flex-start;
  180. -webkit-box-direction: normal;
  181. -webkit-box-orient: horizontal;
  182. -webkit-flex-flow: row wrap;
  183. align-content: flex-start;
  184. display: flex;
  185. flex-flow: row wrap;
  186. font-family: FreeSans, Arimo, Droid Sans, Helvetica, Arial, sans-serif;
  187. letter-spacing: -0.31em;
  188. text-rendering: optimizespeed;
  189. }
  190. @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
  191. table .pure-g {
  192. display: block;
  193. }
  194. }
  195. .opera-only :-o-prefocus, .pure-g {
  196. word-spacing: -0.43em;
  197. }
  198. .pure-u {
  199. display: inline-block;
  200. letter-spacing: normal;
  201. text-rendering: auto;
  202. vertical-align: top;
  203. word-spacing: normal;
  204. zoom: 1;
  205. }
  206. .pure-g [class*=pure-u] {
  207. font-family: sans-serif;
  208. }
  209. .pure-u-1,
  210. .pure-u-1-1,
  211. .pure-u-1-12,
  212. .pure-u-1-2,
  213. .pure-u-1-24,
  214. .pure-u-1-3,
  215. .pure-u-1-4,
  216. .pure-u-1-5,
  217. .pure-u-1-6,
  218. .pure-u-1-8,
  219. .pure-u-10-24,
  220. .pure-u-11-12,
  221. .pure-u-11-24,
  222. .pure-u-12-24,
  223. .pure-u-13-24,
  224. .pure-u-14-24,
  225. .pure-u-15-24,
  226. .pure-u-16-24,
  227. .pure-u-17-24,
  228. .pure-u-18-24,
  229. .pure-u-19-24,
  230. .pure-u-2-24,
  231. .pure-u-2-3,
  232. .pure-u-2-5,
  233. .pure-u-20-24,
  234. .pure-u-21-24,
  235. .pure-u-22-24,
  236. .pure-u-23-24,
  237. .pure-u-24-24,
  238. .pure-u-3-24,
  239. .pure-u-3-4,
  240. .pure-u-3-5,
  241. .pure-u-3-8,
  242. .pure-u-4-24,
  243. .pure-u-4-5,
  244. .pure-u-5-12,
  245. .pure-u-5-24,
  246. .pure-u-5-5,
  247. .pure-u-5-6,
  248. .pure-u-5-8,
  249. .pure-u-6-24,
  250. .pure-u-7-12,
  251. .pure-u-7-24,
  252. .pure-u-7-8,
  253. .pure-u-8-24,
  254. .pure-u-9-24 {
  255. display: inline-block;
  256. letter-spacing: normal;
  257. text-rendering: auto;
  258. vertical-align: top;
  259. word-spacing: normal;
  260. zoom: 1;
  261. }
  262. .pure-u-1-24 {
  263. width: 4.1667%;
  264. }
  265. .pure-u-1-12, .pure-u-2-24 {
  266. width: 8.3333%;
  267. }
  268. .pure-u-1-8, .pure-u-3-24 {
  269. width: 12.5%;
  270. }
  271. .pure-u-1-6, .pure-u-4-24 {
  272. width: 16.6667%;
  273. }
  274. .pure-u-1-5 {
  275. width: 20%;
  276. }
  277. .pure-u-5-24 {
  278. width: 20.8333%;
  279. }
  280. .pure-u-1-4, .pure-u-6-24 {
  281. width: 25%;
  282. }
  283. .pure-u-7-24 {
  284. width: 29.1667%;
  285. }
  286. .pure-u-1-3, .pure-u-8-24 {
  287. width: 33.3333%;
  288. }
  289. .pure-u-3-8, .pure-u-9-24 {
  290. width: 37.5%;
  291. }
  292. .pure-u-2-5 {
  293. width: 40%;
  294. }
  295. .pure-u-10-24, .pure-u-5-12 {
  296. width: 41.6667%;
  297. }
  298. .pure-u-11-24 {
  299. width: 45.8333%;
  300. }
  301. .pure-u-1-2, .pure-u-12-24 {
  302. width: 50%;
  303. }
  304. .pure-u-13-24 {
  305. width: 54.1667%;
  306. }
  307. .pure-u-14-24, .pure-u-7-12 {
  308. width: 58.3333%;
  309. }
  310. .pure-u-3-5 {
  311. width: 60%;
  312. }
  313. .pure-u-15-24, .pure-u-5-8 {
  314. width: 62.5%;
  315. }
  316. .pure-u-16-24, .pure-u-2-3 {
  317. width: 66.6667%;
  318. }
  319. .pure-u-17-24 {
  320. width: 70.8333%;
  321. }
  322. .pure-u-18-24, .pure-u-3-4 {
  323. width: 75%;
  324. }
  325. .pure-u-19-24 {
  326. width: 79.1667%;
  327. }
  328. .pure-u-4-5 {
  329. width: 80%;
  330. }
  331. .pure-u-20-24, .pure-u-5-6 {
  332. width: 83.3333%;
  333. }
  334. .pure-u-21-24, .pure-u-7-8 {
  335. width: 87.5%;
  336. }
  337. .pure-u-11-12, .pure-u-22-24 {
  338. width: 91.6667%;
  339. }
  340. .pure-u-23-24 {
  341. width: 95.8333%;
  342. }
  343. .pure-u-1, .pure-u-1-1, .pure-u-24-24, .pure-u-5-5 {
  344. width: 100%;
  345. }
  346. .pure-button {
  347. -moz-user-select: none;
  348. -ms-user-select: none;
  349. -webkit-box-sizing: border-box;
  350. -webkit-user-drag: none;
  351. -webkit-user-select: none;
  352. box-sizing: border-box;
  353. cursor: pointer;
  354. display: inline-block;
  355. line-height: normal;
  356. text-align: center;
  357. user-select: none;
  358. vertical-align: middle;
  359. white-space: nowrap;
  360. zoom: 1;
  361. }
  362. .pure-button::-moz-focus-inner {
  363. border: 0;
  364. padding: 0;
  365. }
  366. .pure-button-group {
  367. letter-spacing: -0.31em;
  368. text-rendering: optimizespeed;
  369. }
  370. .opera-only :-o-prefocus, .pure-button-group {
  371. word-spacing: -0.43em;
  372. }
  373. .pure-button-group .pure-button {
  374. letter-spacing: normal;
  375. text-rendering: auto;
  376. vertical-align: top;
  377. word-spacing: normal;
  378. }
  379. .pure-button {
  380. background-color: #e6e6e6;
  381. border: none transparent;
  382. border-radius: 2px;
  383. color: rgba(0, 0, 0, 0.8);
  384. font-family: inherit;
  385. font-size: 100%;
  386. padding: 0.5em 1em;
  387. text-decoration: none;
  388. }
  389. .pure-button-hover, .pure-button:focus, .pure-button:hover {
  390. background-image: linear-gradient(transparent, rgba(0, 0, 0, 0.05) 40%, rgba(0, 0, 0, 0.1));
  391. }
  392. .pure-button:focus {
  393. outline: 0;
  394. }
  395. .pure-button-active, .pure-button:active {
  396. -webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15) inset, 0 0 6px rgba(0, 0, 0, 0.2) inset;
  397. box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15) inset, 0 0 6px rgba(0, 0, 0, 0.2) inset;
  398. }
  399. .pure-button-disabled,
  400. .pure-button-disabled:active,
  401. .pure-button-disabled:focus,
  402. .pure-button-disabled:hover,
  403. .pure-button[disabled] {
  404. -webkit-box-shadow: none;
  405. background-image: none;
  406. border: 0;
  407. box-shadow: none;
  408. cursor: not-allowed;
  409. opacity: 0.4;
  410. pointer-events: none;
  411. }
  412. .pure-button-hidden {
  413. display: none;
  414. }
  415. .pure-button-primary,
  416. .pure-button-selected,
  417. a.pure-button-primary,
  418. a.pure-button-selected {
  419. background-color: #0078e7;
  420. color: #fff;
  421. }
  422. .pure-button-group .pure-button {
  423. border-radius: 0;
  424. border-right: 1px solid rgba(0, 0, 0, 0.2);
  425. margin: 0;
  426. }
  427. .pure-button-group .pure-button:first-child {
  428. border-bottom-left-radius: 2px;
  429. border-top-left-radius: 2px;
  430. }
  431. .pure-button-group .pure-button:last-child {
  432. border-bottom-right-radius: 2px;
  433. border-right: 0;
  434. border-top-right-radius: 2px;
  435. }
  436. .pure-form input[type=color],
  437. .pure-form input[type=date],
  438. .pure-form input[type=datetime-local],
  439. .pure-form input[type=datetime],
  440. .pure-form input[type=email],
  441. .pure-form input[type=month],
  442. .pure-form input[type=number],
  443. .pure-form input[type=password],
  444. .pure-form input[type=search],
  445. .pure-form input[type=tel],
  446. .pure-form input[type=text],
  447. .pure-form input[type=time],
  448. .pure-form input[type=url],
  449. .pure-form input[type=week],
  450. .pure-form select,
  451. .pure-form textarea {
  452. -webkit-box-shadow: inset 0 1px 3px #ddd;
  453. -webkit-box-sizing: border-box;
  454. border: 1px solid #ccc;
  455. border-radius: 4px;
  456. box-shadow: inset 0 1px 3px #ddd;
  457. box-sizing: border-box;
  458. display: inline-block;
  459. padding: 0.5em 0.6em;
  460. vertical-align: middle;
  461. }
  462. .pure-form input:not([type]) {
  463. -webkit-box-shadow: inset 0 1px 3px #ddd;
  464. -webkit-box-sizing: border-box;
  465. border: 1px solid #ccc;
  466. border-radius: 4px;
  467. box-shadow: inset 0 1px 3px #ddd;
  468. box-sizing: border-box;
  469. display: inline-block;
  470. padding: 0.5em 0.6em;
  471. }
  472. .pure-form input[type=color] {
  473. padding: 0.2em 0.5em;
  474. }
  475. .pure-form input:not([type]):focus,
  476. .pure-form input[type=color]:focus,
  477. .pure-form input[type=date]:focus,
  478. .pure-form input[type=datetime-local]:focus,
  479. .pure-form input[type=datetime]:focus,
  480. .pure-form input[type=email]:focus,
  481. .pure-form input[type=month]:focus,
  482. .pure-form input[type=number]:focus,
  483. .pure-form input[type=password]:focus,
  484. .pure-form input[type=search]:focus,
  485. .pure-form input[type=tel]:focus,
  486. .pure-form input[type=text]:focus,
  487. .pure-form input[type=time]:focus,
  488. .pure-form input[type=url]:focus,
  489. .pure-form input[type=week]:focus,
  490. .pure-form select:focus,
  491. .pure-form textarea:focus {
  492. border-color: #129fea;
  493. outline: 0;
  494. }
  495. .pure-form input[type=checkbox]:focus,
  496. .pure-form input[type=file]:focus,
  497. .pure-form input[type=radio]:focus {
  498. outline: 1px auto #129fea;
  499. }
  500. .pure-form .pure-checkbox, .pure-form .pure-radio {
  501. display: block;
  502. margin: 0.5em 0;
  503. }
  504. .pure-form input:not([type])[disabled],
  505. .pure-form input[type=color][disabled],
  506. .pure-form input[type=date][disabled],
  507. .pure-form input[type=datetime-local][disabled],
  508. .pure-form input[type=datetime][disabled],
  509. .pure-form input[type=email][disabled],
  510. .pure-form input[type=month][disabled],
  511. .pure-form input[type=number][disabled],
  512. .pure-form input[type=password][disabled],
  513. .pure-form input[type=search][disabled],
  514. .pure-form input[type=tel][disabled],
  515. .pure-form input[type=text][disabled],
  516. .pure-form input[type=time][disabled],
  517. .pure-form input[type=url][disabled],
  518. .pure-form input[type=week][disabled],
  519. .pure-form select[disabled],
  520. .pure-form textarea[disabled] {
  521. background-color: #eaeded;
  522. color: #cad2d3;
  523. cursor: not-allowed;
  524. }
  525. .pure-form input[readonly],
  526. .pure-form select[readonly],
  527. .pure-form textarea[readonly] {
  528. background-color: #eee;
  529. border-color: #ccc;
  530. color: #777;
  531. }
  532. .pure-form input:focus:invalid,
  533. .pure-form select:focus:invalid,
  534. .pure-form textarea:focus:invalid {
  535. border-color: #e9322d;
  536. color: #b94a48;
  537. }
  538. .pure-form input[type=checkbox]:focus:invalid,
  539. .pure-form input[type=file]:focus:invalid,
  540. .pure-form input[type=radio]:focus:invalid {
  541. outline-color: #e9322d;
  542. }
  543. .pure-form select {
  544. background-color: #fff;
  545. border: 1px solid #ccc;
  546. height: 2.25em;
  547. }
  548. .pure-form select[multiple] {
  549. height: auto;
  550. }
  551. .pure-form label {
  552. margin: 0.5em 0 0.2em;
  553. }
  554. .pure-form fieldset {
  555. border: 0;
  556. margin: 0;
  557. padding: 0.35em 0 0.75em;
  558. }
  559. .pure-form legend {
  560. border-bottom: 1px solid #e5e5e5;
  561. color: #333;
  562. display: block;
  563. margin-bottom: 0.3em;
  564. padding: 0.3em 0;
  565. width: 100%;
  566. }
  567. .pure-form-stacked input:not([type]),
  568. .pure-form-stacked input[type=color],
  569. .pure-form-stacked input[type=date],
  570. .pure-form-stacked input[type=datetime-local],
  571. .pure-form-stacked input[type=datetime],
  572. .pure-form-stacked input[type=email],
  573. .pure-form-stacked input[type=file],
  574. .pure-form-stacked input[type=month],
  575. .pure-form-stacked input[type=number],
  576. .pure-form-stacked input[type=password],
  577. .pure-form-stacked input[type=search],
  578. .pure-form-stacked input[type=tel],
  579. .pure-form-stacked input[type=text],
  580. .pure-form-stacked input[type=time],
  581. .pure-form-stacked input[type=url],
  582. .pure-form-stacked input[type=week],
  583. .pure-form-stacked label,
  584. .pure-form-stacked select,
  585. .pure-form-stacked textarea {
  586. display: block;
  587. margin: 0.25em 0;
  588. }
  589. .pure-form-aligned .pure-help-inline,
  590. .pure-form-aligned input,
  591. .pure-form-aligned select,
  592. .pure-form-aligned textarea,
  593. .pure-form-message-inline {
  594. display: inline-block;
  595. vertical-align: middle;
  596. }
  597. .pure-form-aligned textarea {
  598. vertical-align: top;
  599. }
  600. .pure-form-aligned .pure-control-group {
  601. margin-bottom: 0.5em;
  602. }
  603. .pure-form-aligned .pure-control-group label {
  604. display: inline-block;
  605. margin: 0 1em 0 0;
  606. text-align: right;
  607. vertical-align: middle;
  608. width: 10em;
  609. }
  610. .pure-form-aligned .pure-controls {
  611. margin: 1.5em 0 0 11em;
  612. }
  613. .pure-form .pure-input-rounded, .pure-form input.pure-input-rounded {
  614. border-radius: 2em;
  615. padding: 0.5em 1em;
  616. }
  617. .pure-form .pure-group fieldset {
  618. margin-bottom: 10px;
  619. }
  620. .pure-form .pure-group input, .pure-form .pure-group textarea {
  621. border-radius: 0;
  622. display: block;
  623. margin: 0 0 -1px;
  624. padding: 10px;
  625. position: relative;
  626. top: -1px;
  627. }
  628. .pure-form .pure-group input:focus, .pure-form .pure-group textarea:focus {
  629. z-index: 3;
  630. }
  631. .pure-form .pure-group input:first-child,
  632. .pure-form .pure-group textarea:first-child {
  633. border-radius: 4px 4px 0 0;
  634. margin: 0;
  635. top: 1px;
  636. }
  637. .pure-form .pure-group input:first-child:last-child,
  638. .pure-form .pure-group textarea:first-child:last-child {
  639. border-radius: 4px;
  640. margin: 0;
  641. top: 1px;
  642. }
  643. .pure-form .pure-group input:last-child,
  644. .pure-form .pure-group textarea:last-child {
  645. border-radius: 0 0 4px 4px;
  646. margin: 0;
  647. top: -2px;
  648. }
  649. .pure-form .pure-group button {
  650. margin: 0.35em 0;
  651. }
  652. .pure-form .pure-input-1 {
  653. width: 100%;
  654. }
  655. .pure-form .pure-input-3-4 {
  656. width: 75%;
  657. }
  658. .pure-form .pure-input-2-3 {
  659. width: 66%;
  660. }
  661. .pure-form .pure-input-1-2 {
  662. width: 50%;
  663. }
  664. .pure-form .pure-input-1-3 {
  665. width: 33%;
  666. }
  667. .pure-form .pure-input-1-4 {
  668. width: 25%;
  669. }
  670. .pure-form .pure-help-inline, .pure-form-message-inline {
  671. color: #666;
  672. display: inline-block;
  673. font-size: 0.875em;
  674. padding-left: 0.3em;
  675. vertical-align: middle;
  676. }
  677. .pure-form-message {
  678. color: #666;
  679. display: block;
  680. font-size: 0.875em;
  681. }
  682. @media only screen and (max-width: 5in) {
  683. .pure-form button[type=submit] {
  684. margin: 0.7em 0 0;
  685. }
  686. .pure-form input:not([type]),
  687. .pure-form input[type=color],
  688. .pure-form input[type=date],
  689. .pure-form input[type=datetime-local],
  690. .pure-form input[type=datetime],
  691. .pure-form input[type=email],
  692. .pure-form input[type=month],
  693. .pure-form input[type=number],
  694. .pure-form input[type=password],
  695. .pure-form input[type=search],
  696. .pure-form input[type=tel],
  697. .pure-form input[type=text],
  698. .pure-form input[type=time],
  699. .pure-form input[type=url],
  700. .pure-form input[type=week],
  701. .pure-form label {
  702. display: block;
  703. margin-bottom: 0.3em;
  704. }
  705. .pure-group input:not([type]),
  706. .pure-group input[type=color],
  707. .pure-group input[type=date],
  708. .pure-group input[type=datetime-local],
  709. .pure-group input[type=datetime],
  710. .pure-group input[type=email],
  711. .pure-group input[type=month],
  712. .pure-group input[type=number],
  713. .pure-group input[type=password],
  714. .pure-group input[type=search],
  715. .pure-group input[type=tel],
  716. .pure-group input[type=text],
  717. .pure-group input[type=time],
  718. .pure-group input[type=url],
  719. .pure-group input[type=week] {
  720. margin-bottom: 0;
  721. }
  722. .pure-form-aligned .pure-control-group label {
  723. display: block;
  724. margin-bottom: 0.3em;
  725. text-align: left;
  726. width: 100%;
  727. }
  728. .pure-form-aligned .pure-controls {
  729. margin: 1.5em 0 0;
  730. }
  731. .pure-form .pure-help-inline, .pure-form-message, .pure-form-message-inline {
  732. display: block;
  733. font-size: 0.75em;
  734. padding: 0.2em 0 0.8em;
  735. }
  736. }
  737. .pure-menu {
  738. -webkit-box-sizing: border-box;
  739. box-sizing: border-box;
  740. }
  741. .pure-menu-fixed {
  742. left: 0;
  743. position: fixed;
  744. top: 0;
  745. z-index: 3;
  746. }
  747. .pure-menu-item, .pure-menu-list {
  748. position: relative;
  749. }
  750. .pure-menu-list {
  751. list-style: none;
  752. margin: 0;
  753. padding: 0;
  754. }
  755. .pure-menu-item {
  756. height: 100%;
  757. margin: 0;
  758. padding: 0;
  759. }
  760. .pure-menu-heading, .pure-menu-link {
  761. display: block;
  762. text-decoration: none;
  763. white-space: nowrap;
  764. }
  765. .pure-menu-horizontal {
  766. white-space: nowrap;
  767. width: 100%;
  768. }
  769. .pure-menu-horizontal .pure-menu-list {
  770. display: inline-block;
  771. }
  772. .pure-menu-horizontal .pure-menu-heading,
  773. .pure-menu-horizontal .pure-menu-item,
  774. .pure-menu-horizontal .pure-menu-separator {
  775. display: inline-block;
  776. vertical-align: middle;
  777. zoom: 1;
  778. }
  779. .pure-menu-item .pure-menu-item {
  780. display: block;
  781. }
  782. .pure-menu-children {
  783. display: none;
  784. left: 100%;
  785. margin: 0;
  786. padding: 0;
  787. position: absolute;
  788. top: 0;
  789. z-index: 3;
  790. }
  791. .pure-menu-horizontal .pure-menu-children {
  792. left: 0;
  793. top: auto;
  794. width: inherit;
  795. }
  796. .pure-menu-active > .pure-menu-children,
  797. .pure-menu-allow-hover:hover > .pure-menu-children {
  798. display: block;
  799. position: absolute;
  800. }
  801. .pure-menu-has-children > .pure-menu-link:after {
  802. content: "\25B8";
  803. font-size: small;
  804. padding-left: 0.5em;
  805. }
  806. .pure-menu-horizontal .pure-menu-has-children > .pure-menu-link:after {
  807. content: "\25BE";
  808. }
  809. .pure-menu-scrollable {
  810. overflow-x: hidden;
  811. overflow-y: scroll;
  812. }
  813. .pure-menu-scrollable .pure-menu-list {
  814. display: block;
  815. }
  816. .pure-menu-horizontal.pure-menu-scrollable .pure-menu-list {
  817. display: inline-block;
  818. }
  819. .pure-menu-horizontal.pure-menu-scrollable {
  820. -ms-overflow-style: none;
  821. -webkit-overflow-scrolling: touch;
  822. overflow-x: auto;
  823. overflow-y: hidden;
  824. padding: 0.5em 0;
  825. white-space: nowrap;
  826. }
  827. .pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar {
  828. display: none;
  829. }
  830. .pure-menu-horizontal .pure-menu-children .pure-menu-separator,
  831. .pure-menu-separator {
  832. background-color: #ccc;
  833. height: 1px;
  834. margin: 0.3em 0;
  835. }
  836. .pure-menu-horizontal .pure-menu-separator {
  837. height: 1.3em;
  838. margin: 0 0.3em;
  839. width: 1px;
  840. }
  841. .pure-menu-horizontal .pure-menu-children .pure-menu-separator {
  842. display: block;
  843. width: auto;
  844. }
  845. .pure-menu-heading {
  846. color: #565d64;
  847. text-transform: uppercase;
  848. }
  849. .pure-menu-link {
  850. color: #777;
  851. }
  852. .pure-menu-children {
  853. background-color: #fff;
  854. }
  855. .pure-menu-disabled, .pure-menu-heading, .pure-menu-link {
  856. padding: 0.5em 1em;
  857. }
  858. .pure-menu-disabled {
  859. opacity: 0.5;
  860. }
  861. .pure-menu-disabled .pure-menu-link:hover {
  862. background-color: transparent;
  863. }
  864. .pure-menu-active > .pure-menu-link, .pure-menu-link:focus, .pure-menu-link:hover {
  865. background-color: #eee;
  866. }
  867. .pure-menu-selected .pure-menu-link, .pure-menu-selected .pure-menu-link:visited {
  868. color: #000;
  869. }
  870. .pure-table {
  871. border: 1px solid #cbcbcb;
  872. border-collapse: collapse;
  873. border-spacing: 0;
  874. empty-cells: show;
  875. }
  876. .pure-table caption {
  877. color: #000;
  878. font: italic 85%/1 arial, sans-serif;
  879. padding: 1em 0;
  880. text-align: center;
  881. }
  882. .pure-table td, .pure-table th {
  883. border-left: 1px solid #cbcbcb;
  884. border-width: 0 0 0 1px;
  885. font-size: inherit;
  886. margin: 0;
  887. overflow: visible;
  888. padding: 0.5em 1em;
  889. }
  890. .pure-table td:first-child, .pure-table th:first-child {
  891. border-left-width: 0;
  892. }
  893. .pure-table thead {
  894. background-color: #e0e0e0;
  895. color: #000;
  896. text-align: left;
  897. vertical-align: bottom;
  898. }
  899. .pure-table td {
  900. background-color: transparent;
  901. }
  902. .pure-table-odd td, .pure-table-striped tr:nth-child(2n - 1) td {
  903. background-color: #f2f2f2;
  904. }
  905. .pure-table-bordered td {
  906. border-bottom: 1px solid #cbcbcb;
  907. }
  908. .pure-table-bordered tbody > tr:last-child > td {
  909. border-bottom-width: 0;
  910. }
  911. .pure-table-horizontal td, .pure-table-horizontal th {
  912. border-bottom: 1px solid #cbcbcb;
  913. border-width: 0 0 1px;
  914. }
  915. .pure-table-horizontal tbody > tr:last-child > td {
  916. border-bottom-width: 0;
  917. }
  918. </style>
  919. <style>
  920. @media screen and (min-width: 64em) {
  921. .pure-u-lg-1,
  922. .pure-u-lg-1-1,
  923. .pure-u-lg-1-12,
  924. .pure-u-lg-1-2,
  925. .pure-u-lg-1-24,
  926. .pure-u-lg-1-3,
  927. .pure-u-lg-1-4,
  928. .pure-u-lg-1-5,
  929. .pure-u-lg-1-6,
  930. .pure-u-lg-1-8,
  931. .pure-u-lg-10-24,
  932. .pure-u-lg-11-12,
  933. .pure-u-lg-11-24,
  934. .pure-u-lg-12-24,
  935. .pure-u-lg-13-24,
  936. .pure-u-lg-14-24,
  937. .pure-u-lg-15-24,
  938. .pure-u-lg-16-24,
  939. .pure-u-lg-17-24,
  940. .pure-u-lg-18-24,
  941. .pure-u-lg-19-24,
  942. .pure-u-lg-2-24,
  943. .pure-u-lg-2-3,
  944. .pure-u-lg-2-5,
  945. .pure-u-lg-20-24,
  946. .pure-u-lg-21-24,
  947. .pure-u-lg-22-24,
  948. .pure-u-lg-23-24,
  949. .pure-u-lg-24-24,
  950. .pure-u-lg-3-24,
  951. .pure-u-lg-3-4,
  952. .pure-u-lg-3-5,
  953. .pure-u-lg-3-8,
  954. .pure-u-lg-4-24,
  955. .pure-u-lg-4-5,
  956. .pure-u-lg-5-12,
  957. .pure-u-lg-5-24,
  958. .pure-u-lg-5-5,
  959. .pure-u-lg-5-6,
  960. .pure-u-lg-5-8,
  961. .pure-u-lg-6-24,
  962. .pure-u-lg-7-12,
  963. .pure-u-lg-7-24,
  964. .pure-u-lg-7-8,
  965. .pure-u-lg-8-24,
  966. .pure-u-lg-9-24 {
  967. display: inline-block;
  968. letter-spacing: normal;
  969. text-rendering: auto;
  970. vertical-align: top;
  971. word-spacing: normal;
  972. zoom: 1;
  973. }
  974. .pure-u-lg-1-24 {
  975. width: 4.1667%;
  976. }
  977. .pure-u-lg-1-12, .pure-u-lg-2-24 {
  978. width: 8.3333%;
  979. }
  980. .pure-u-lg-1-8, .pure-u-lg-3-24 {
  981. width: 12.5%;
  982. }
  983. .pure-u-lg-1-6, .pure-u-lg-4-24 {
  984. width: 16.6667%;
  985. }
  986. .pure-u-lg-1-5 {
  987. width: 20%;
  988. }
  989. .pure-u-lg-5-24 {
  990. width: 20.8333%;
  991. }
  992. .pure-u-lg-1-4, .pure-u-lg-6-24 {
  993. width: 25%;
  994. }
  995. .pure-u-lg-7-24 {
  996. width: 29.1667%;
  997. }
  998. .pure-u-lg-1-3, .pure-u-lg-8-24 {
  999. width: 33.3333%;
  1000. }
  1001. .pure-u-lg-3-8, .pure-u-lg-9-24 {
  1002. width: 37.5%;
  1003. }
  1004. .pure-u-lg-2-5 {
  1005. width: 40%;
  1006. }
  1007. .pure-u-lg-10-24, .pure-u-lg-5-12 {
  1008. width: 41.6667%;
  1009. }
  1010. .pure-u-lg-11-24 {
  1011. width: 45.8333%;
  1012. }
  1013. .pure-u-lg-1-2, .pure-u-lg-12-24 {
  1014. width: 50%;
  1015. }
  1016. .pure-u-lg-13-24 {
  1017. width: 54.1667%;
  1018. }
  1019. .pure-u-lg-14-24, .pure-u-lg-7-12 {
  1020. width: 58.3333%;
  1021. }
  1022. .pure-u-lg-3-5 {
  1023. width: 60%;
  1024. }
  1025. .pure-u-lg-15-24, .pure-u-lg-5-8 {
  1026. width: 62.5%;
  1027. }
  1028. .pure-u-lg-16-24, .pure-u-lg-2-3 {
  1029. width: 66.6667%;
  1030. }
  1031. .pure-u-lg-17-24 {
  1032. width: 70.8333%;
  1033. }
  1034. .pure-u-lg-18-24, .pure-u-lg-3-4 {
  1035. width: 75%;
  1036. }
  1037. .pure-u-lg-19-24 {
  1038. width: 79.1667%;
  1039. }
  1040. .pure-u-lg-4-5 {
  1041. width: 80%;
  1042. }
  1043. .pure-u-lg-20-24, .pure-u-lg-5-6 {
  1044. width: 83.3333%;
  1045. }
  1046. .pure-u-lg-21-24, .pure-u-lg-7-8 {
  1047. width: 87.5%;
  1048. }
  1049. .pure-u-lg-11-12, .pure-u-lg-22-24 {
  1050. width: 91.6667%;
  1051. }
  1052. .pure-u-lg-23-24 {
  1053. width: 95.8333%;
  1054. }
  1055. .pure-u-lg-1, .pure-u-lg-1-1, .pure-u-lg-24-24, .pure-u-lg-5-5 {
  1056. width: 100%;
  1057. }
  1058. }
  1059. </style>
  1060. <style>
  1061. body {
  1062. color: #777;
  1063. }
  1064. .pure-img-responsive {
  1065. height: auto;
  1066. max-width: 100%;
  1067. }
  1068. #layout, #menu, .menu-link {
  1069. -moz-transition: all 0.2s ease-out;
  1070. -ms-transition: all 0.2s ease-out;
  1071. -o-transition: all 0.2s ease-out;
  1072. -webkit-transition: all 0.2s ease-out;
  1073. transition: all 0.2s ease-out;
  1074. }
  1075. #layout {
  1076. left: 0;
  1077. padding-left: 0;
  1078. position: relative;
  1079. }
  1080. #layout.active #menu {
  1081. left: 10pc;
  1082. width: 10pc;
  1083. }
  1084. #layout.active .menu-link {
  1085. left: 10pc;
  1086. }
  1087. .content {
  1088. line-height: 1.6em;
  1089. margin: 0 auto 50px;
  1090. max-width: 50pc;
  1091. padding: 0 2em;
  1092. }
  1093. .header {
  1094. border-bottom: 1px solid #eee;
  1095. color: #333;
  1096. margin: 0;
  1097. padding: 2.5em 2em 0;
  1098. text-align: center;
  1099. }
  1100. .header h1 {
  1101. font-size: 3em;
  1102. font-weight: 300;
  1103. margin: 0.2em 0;
  1104. }
  1105. .header h2 {
  1106. color: #ccc;
  1107. font-weight: 300;
  1108. margin-top: 0;
  1109. padding: 0;
  1110. }
  1111. .content-subhead {
  1112. color: #888;
  1113. font-weight: 300;
  1114. margin: 50px 0 20px;
  1115. }
  1116. #menu {
  1117. -webkit-overflow-scrolling: touch;
  1118. background: #191818;
  1119. bottom: 0;
  1120. left: 0;
  1121. margin-left: -10pc;
  1122. overflow-y: auto;
  1123. position: fixed;
  1124. top: 0;
  1125. width: 10pc;
  1126. z-index: 1000;
  1127. }
  1128. #menu a {
  1129. border: 0;
  1130. color: #999;
  1131. padding: 0.6em 0 0.6em 0.6em;
  1132. }
  1133. #menu .pure-menu, #menu .pure-menu ul {
  1134. background: transparent;
  1135. border: 0;
  1136. }
  1137. #menu .pure-menu .menu-item-divided, #menu .pure-menu ul {
  1138. border-top: 1px solid #333;
  1139. }
  1140. #menu .pure-menu li a:focus, #menu .pure-menu li a:hover {
  1141. background: #333;
  1142. }
  1143. #menu .pure-menu-heading, #menu .pure-menu-selected {
  1144. background: #1f8dd6;
  1145. }
  1146. #menu .pure-menu-selected a {
  1147. color: #fff;
  1148. }
  1149. #menu .pure-menu-heading {
  1150. color: #fff;
  1151. font-size: 110%;
  1152. margin: 0;
  1153. }
  1154. .menu-link {
  1155. background: rgba(0, 0, 0, 0.7);
  1156. display: block;
  1157. font-size: 10px;
  1158. height: auto;
  1159. left: 0;
  1160. padding: 2.1em 1.6em;
  1161. position: fixed;
  1162. top: 0;
  1163. width: 2em;
  1164. z-index: 10;
  1165. }
  1166. .menu-link:focus, .menu-link:hover {
  1167. background: #000;
  1168. }
  1169. .menu-link span {
  1170. display: block;
  1171. position: relative;
  1172. }
  1173. .menu-link span, .menu-link span:after, .menu-link span:before {
  1174. background-color: #fff;
  1175. height: 0.2em;
  1176. width: 100%;
  1177. }
  1178. .menu-link span:after, .menu-link span:before {
  1179. content: " ";
  1180. margin-top: -0.6em;
  1181. position: absolute;
  1182. }
  1183. .menu-link span:after {
  1184. margin-top: 0.6em;
  1185. }
  1186. @media (min-width: 48em) {
  1187. .content, .header {
  1188. padding-left: 2em;
  1189. padding-right: 2em;
  1190. }
  1191. #layout {
  1192. left: 0;
  1193. padding-left: 10pc;
  1194. }
  1195. #menu {
  1196. left: 10pc;
  1197. }
  1198. .menu-link {
  1199. display: none;
  1200. left: 10pc;
  1201. position: fixed;
  1202. }
  1203. #layout.active .menu-link {
  1204. left: 10pc;
  1205. }
  1206. }
  1207. @media (max-width: 48em) {
  1208. #layout.active {
  1209. left: 10pc;
  1210. position: relative;
  1211. }
  1212. }
  1213. </style>
  1214. <style>
  1215. .iPhoneCheckContainer {
  1216. -webkit-transform: translate3d(0, 0, 0);
  1217. cursor: pointer;
  1218. height: 30px;
  1219. margin: 5px 0 10px;
  1220. overflow: hidden;
  1221. position: relative;
  1222. }
  1223. .iPhoneCheckContainer input {
  1224. filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
  1225. left: 30px;
  1226. opacity: 0;
  1227. position: absolute;
  1228. top: 5px;
  1229. }
  1230. .iPhoneCheckContainer label {
  1231. cursor: pointer;
  1232. display: block;
  1233. font-family: Helvetica Neue, Arial, Helvetica, sans-serif;
  1234. font-size: 17px;
  1235. font-weight: 700;
  1236. height: 27px;
  1237. line-height: 17px;
  1238. overflow: hidden;
  1239. padding-top: 5px;
  1240. position: absolute;
  1241. top: 0;
  1242. white-space: nowrap;
  1243. width: auto;
  1244. }
  1245. .iPhoneCheckContainer, .iPhoneCheckContainer label {
  1246. -khtml-user-select: none;
  1247. -moz-user-select: none;
  1248. user-select: none;
  1249. }
  1250. .iPhoneCheckDisabled {
  1251. filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=50);
  1252. opacity: 0.5;
  1253. }
  1254. div.iPhoneCheckBorderOn {
  1255. background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAbCAYAAABbcS4wAAAA9UlEQVR4AWJkAAIFz1ppINULxDaMil516kDGMQCN44EdQQjDUGFpgPSenCi3yus5V26U3vsKvDtvAOvz/UzbPTquOwdQMK77/ulx279AUBCDl/3gAmo7KKzTOFDbBWsHlz6NGVS3EWoGJKTq24qYhkFoASmUEIIyCK5DeHEEF2OVYVMu4FUMS7bAwX8CaRgxl2NOqZy9Hk1oGG0JaACtje6gauAVwyjMCSw5ZRZmUCDfQRdMK1/KqSfMFhcacGOoZNAGRPzbgi/+DYKPgV8E/lHGFJOb3683DFi8hnH1+/1+9/H2gN/Pd0N/q+XOOAWKuQmgmAMAk5Uf/2+Z9s0AAAAASUVORK5CYII=);
  1256. background-repeat: no-repeat;
  1257. height: 100%;
  1258. left: 0;
  1259. position: absolute;
  1260. width: 4px;
  1261. }
  1262. label.iPhoneCheckLabelOn {
  1263. background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAbCAIAAADUE7lnAAAAnUlEQVQYGQXBwVFbURAEwJ5982UJfHA5AQjAiRAUaToEnwgB1bq77x+fAPr77c8SEb0evwSW9vEzGUu0P15lWLS3F5HE6rlugNU5FxA6EwCdzAaympmxi+hMrMjSkU0w2V49gOj9OgtC77cDhHayK1bSScwybM9IYpH2BJbomYQNqz2BJdsOa4P0TBYrUfucZCO79fwWCatf//4C+A9KeitWlsJe4gAAAABJRU5ErkJggg==);
  1264. background-repeat: repeat-x;
  1265. color: #fff;
  1266. left: 0;
  1267. margin-left: 4px;
  1268. margin-top: 0;
  1269. padding-top: 5px;
  1270. text-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
  1271. }
  1272. label.iPhoneCheckLabelOn span {
  1273. padding-left: 4px;
  1274. }
  1275. label.iPhoneCheckLabelOff {
  1276. background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAbCAIAAADUE7lnAAAAmklEQVR4AUXNh2HFQAgDUAn0e1vIO3jgeLMUBeMm13d3AMdxxBYNw3Dg/fkAJujC6/k8dp7PhwECBvW4NzhT1+ut1w1S58sZfa6iCrYccCGVwFolhcwZvZPhPsbC6XwGbBOFiCBLICEp4T5VIBN0w8oIw92Oigy4gUKwi2m7kNhSiB3dgEA/fYwuFGX8+Q8d6+enfreh0/SFLf+a2DV1eUAGRAAAAABJRU5ErkJggg==);
  1277. background-repeat: repeat-x;
  1278. color: #8b8b8b;
  1279. margin-right: 4px;
  1280. margin-top: 0;
  1281. right: 0;
  1282. text-align: right;
  1283. text-shadow: 0 0 2px hsla(0, 0%, 100%, 0.6);
  1284. }
  1285. label.iPhoneCheckLabelOff span {
  1286. padding-right: 4px;
  1287. }
  1288. div.iPhoneCheckBorderOff {
  1289. background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAbCAYAAABbcS4wAAAA+0lEQVR4AWLw9/d/AsQrgFiaAQTq6+v/Z2Vl/w8MDHwLxFoMkyZN+t/b2/u/qKjof0hICICp8TBsAAaB4POPujyCx8m6WTBV4CpRj/bJvTdCr9crJH1wnWSuictlB9i+5kSRw2ABwDEG+mjovcPdwVoram1ord1AKQWhkSR4BP6AckHfEejeIYdEiHdAZpIwdrGWCq8OlfvSqDgPOJodOlS5kLcOM8LMED87qAN0Ut47ZAYawQew19kXyDF7jASg3kZy3uJSgohv7w+Q9L8vTP8ZgPDfP4Y3b1+DBHYzPHr06P/x4yf+r1279i0QazFs2LDhORCvBmJwzAEA+3M5kWzOkmcAAAAASUVORK5CYII=);
  1290. background-repeat: no-repeat;
  1291. height: 100%;
  1292. position: absolute;
  1293. width: 4px;
  1294. }
  1295. .iPhoneCheckHandle {
  1296. background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAbCAYAAABbcS4wAAAAz0lEQVR4AWJkAIJFixZJA6leILZhBHLUgYxjWlpaQpKSkgwsQE6Lnp6ekLCwMIDrsTisGIaC4MxK91ydgtJ2inEFYc43s1jzcHl7fycDeLhvGj4+P6HrqOpdqRUBlDrfzhtmgJJItwIBDcIeEgNA3eLjOWkS2EOYQ+yYAdlD9rIF6NakzH107ElnkB1oIXZnD8jeenLpNAruatHsYNOyy5+BnQR4/vv/I87tR31s25ZSChoC9Ks5d+7cu5s3bzL8+fMbEtjLly8HxdwEUMwBAEi7Lb9Bc6I2AAAAAElFTkSuQmCC);
  1297. background-repeat: no-repeat;
  1298. cursor: pointer;
  1299. display: block;
  1300. height: 27px;
  1301. left: 0;
  1302. padding-left: 4px;
  1303. position: absolute;
  1304. top: 0;
  1305. width: 0;
  1306. }
  1307. .iPhoneCheckHandleCenter {
  1308. background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAbCAIAAADUE7lnAAAAhUlEQVR4AU2Qwa3GMAyC+RzvP0SvHa2vM/AU5OhvTsZgEOG6Lp3H8zyyJAv6731nLXWRwRJqqNlrA2CuBqAwVXWMN4gIJMcAQnlkllFtmQ/VbARsrosS8eJrPTm2AcUa5GQlNBdExrfCSk0JDjOhZCJ9SA5x7xoGjoyo3Gut31fd963z/gEJqSZcGlcDuwAAAABJRU5ErkJggg==);
  1309. background-repeat: repeat-x;
  1310. height: 100%;
  1311. position: absolute;
  1312. width: 100%;
  1313. }
  1314. .iPhoneCheckHandleRight {
  1315. background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAbCAYAAABbcS4wAAAAz0lEQVR4AWWPMW6EMBREx2PKVGlA2htswylys5zkFxApR8gVKKmpglJtlThL5wyEbznKiMGeLzPPwMzezezFzC7Yta5rnqYpa3CTr0xfCW3bou/7RwDPzMi4f9/RdR2kJ4YQAD0xRmj/0BwDyVeSQYFl0PwGFDGQoFw+iWdwNyV4B0kcCt7hFDhWYUez3OOk4Ow5T1RYccGacmyqAVldrGDJCOBP6R5Qfv9/aZb9BLZty+5hGD4bpyzLsq9vSCnleZ7zOI43+Qq9PuRX+QLpB8XPRQYYBqohAAAAAElFTkSuQmCC);
  1316. background-repeat: no-repeat;
  1317. height: 100%;
  1318. position: absolute;
  1319. right: 0;
  1320. width: 4px;
  1321. }
  1322. </style>
  1323. <style>
  1324. .jQWCP-wWidget {
  1325. background: #eee;
  1326. border: solid 1px #aaa;
  1327. border-radius: 4px;
  1328. box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.5);
  1329. height: 180px;
  1330. padding: 10px;
  1331. position: absolute;
  1332. width: 250px;
  1333. z-index: 1001;
  1334. }
  1335. .jQWCP-wWidget.jQWCP-block {
  1336. border-color: #aaa;
  1337. box-shadow: inset 1px 1px 1px #ccc;
  1338. position: relative;
  1339. }
  1340. .jQWCP-wWheel {
  1341. -moz-border-radius: 50%;
  1342. -webkit-border-radius: 90px;
  1343. background-position: center;
  1344. background-repeat: no-repeat;
  1345. background-size: contain;
  1346. border: solid 1px #aaa;
  1347. border-radius: 50%;
  1348. cursor: crosshair;
  1349. float: left;
  1350. height: 180px;
  1351. margin: -1px 10px -1px -1px;
  1352. position: relative;
  1353. transition: border 0.15s;
  1354. width: 180px;
  1355. }
  1356. .jQWCP-wWheel:hover {
  1357. border-color: #666;
  1358. }
  1359. .jQWCP-wWheelOverlay {
  1360. -moz-border-radius: 50%;
  1361. -webkit-border-radius: 90px;
  1362. background: #000;
  1363. border-radius: 50%;
  1364. height: 100%;
  1365. left: 0;
  1366. opacity: 0;
  1367. position: absolute;
  1368. top: 0;
  1369. width: 100%;
  1370. }
  1371. .jQWCP-wWheelCursor {
  1372. border: solid 2px #fff;
  1373. border-radius: 50%;
  1374. box-shadow: 1px 1px 2px #000;
  1375. cursor: crosshair;
  1376. height: 8px;
  1377. left: 50%;
  1378. margin: -6px;
  1379. position: absolute;
  1380. top: 50%;
  1381. width: 8px;
  1382. }
  1383. .jQWCP-slider-wrapper, .jQWCP-wPreview {
  1384. float: left;
  1385. height: 180px;
  1386. margin-right: 10px;
  1387. position: relative;
  1388. width: 20px;
  1389. }
  1390. .jQWCP-slider-wrapper:last-child,
  1391. .jQWCP-wPreview:last-child,
  1392. .jQWCP-wWheel:last-child {
  1393. margin-right: 0;
  1394. }
  1395. .jQWCP-slider, .jQWCP-wPreviewBox {
  1396. -moz-border-radius: 4px;
  1397. border: solid 1px #aaa;
  1398. border-radius: 4px;
  1399. box-sizing: border-box;
  1400. height: 100%;
  1401. left: 0;
  1402. margin: -1px;
  1403. position: absolute;
  1404. top: 0;
  1405. transition: border 0.15s;
  1406. width: 100%;
  1407. }
  1408. .jQWCP-slider {
  1409. cursor: crosshair;
  1410. }
  1411. .jQWCP-slider-wrapper:hover .jQWCP-slider {
  1412. border-color: #666;
  1413. }
  1414. .jQWCP-scursor {
  1415. border: solid 2px #fff;
  1416. border-radius: 4px;
  1417. box-shadow: 1px 1px 2px #000;
  1418. cursor: crosshair;
  1419. height: 6px;
  1420. left: 0;
  1421. margin: -5px -1px -5px -3px;
  1422. position: absolute;
  1423. right: 0;
  1424. top: 0;
  1425. }
  1426. .jQWCP-wAlphaSlider, .jQWCP-wPreviewBox {
  1427. background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEVAQEB/f39eaJUuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYRBDgK9dKdMgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAARSURBVAjXY/jPwIAVYRf9DwB+vw/x6vMT1wAAAABJRU5ErkJggg==) center center;
  1428. }
  1429. .jQWCP-overlay {
  1430. bottom: 0;
  1431. left: 0;
  1432. position: fixed;
  1433. right: 0;
  1434. top: 0;
  1435. z-index: 1000;
  1436. }
  1437. .jQWCP-mobile.jQWCP-wWidget {
  1438. border-radius: 0;
  1439. bottom: 0;
  1440. box-sizing: border-box;
  1441. height: 75%;
  1442. left: 0 !important;
  1443. max-height: 15pc;
  1444. position: fixed;
  1445. top: auto !important;
  1446. width: 100%;
  1447. }
  1448. </style>
  1449. <style>
  1450. #menu .pure-menu-heading {
  1451. font-size: 100%;
  1452. padding: 0.5em;
  1453. text-transform: initial;
  1454. white-space: normal;
  1455. }
  1456. .pure-g {
  1457. margin-bottom: 0;
  1458. }
  1459. .pure-form legend {
  1460. font-weight: 700;
  1461. letter-spacing: 0;
  1462. margin: 10px 0 1em;
  1463. }
  1464. .pure-form .pure-g > label {
  1465. margin: 0.4em 0 0.2em;
  1466. }
  1467. .pure-form input {
  1468. margin-bottom: 10px;
  1469. }
  1470. .pure-form input[type=text][disabled] {
  1471. color: #777;
  1472. }
  1473. @media screen and (max-width: 32em) {
  1474. .header > h1 {
  1475. font-size: 2em;
  1476. line-height: 100%;
  1477. }
  1478. }
  1479. h2 {
  1480. font-size: 1em;
  1481. }
  1482. .panel {
  1483. display: none;
  1484. }
  1485. .block {
  1486. display: block;
  1487. }
  1488. .content {
  1489. margin: 0;
  1490. }
  1491. .page {
  1492. margin-top: 10px;
  1493. }
  1494. .hint {
  1495. color: #ccc;
  1496. font-size: 80%;
  1497. margin: -10px 0 10px;
  1498. }
  1499. .hint a {
  1500. color: inherit;
  1501. }
  1502. .module, .template, input[name=upgrade], legend.module {
  1503. display: none;
  1504. }
  1505. select {
  1506. margin-bottom: 10px;
  1507. width: 100%;
  1508. }
  1509. input.center {
  1510. margin-bottom: 0;
  1511. }
  1512. div.center {
  1513. margin: 0.5em 0 1em;
  1514. }
  1515. .webmode {
  1516. display: none;
  1517. }
  1518. #credentials {
  1519. font-size: 200%;
  1520. height: 75pt;
  1521. left: 50%;
  1522. margin-left: -200px;
  1523. margin-top: -50px;
  1524. position: fixed;
  1525. text-align: center;
  1526. top: 50%;
  1527. width: 25pc;
  1528. }
  1529. div.state {
  1530. border-top: 1px solid #eee;
  1531. margin-top: 20px;
  1532. padding-top: 30px;
  1533. }
  1534. .state div {
  1535. font-size: 80%;
  1536. }
  1537. .state span {
  1538. font-size: 80%;
  1539. font-weight: 700;
  1540. }
  1541. .right {
  1542. text-align: right;
  1543. }
  1544. .pure-g span.terminal, .pure-g textarea.terminal {
  1545. background-color: #000;
  1546. color: #0f0;
  1547. font-family: Courier New, monospace;
  1548. font-size: 80%;
  1549. line-height: 100%;
  1550. }
  1551. .pure-button {
  1552. border-radius: 4px;
  1553. color: #fff;
  1554. letter-spacing: 0;
  1555. margin-bottom: 10px;
  1556. padding: 8px;
  1557. text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
  1558. }
  1559. .main-buttons {
  1560. margin: 20px auto;
  1561. text-align: center;
  1562. }
  1563. .main-buttons button {
  1564. width: 75pt;
  1565. }
  1566. .button-dbg-clear,
  1567. .button-del-network,
  1568. .button-del-schedule,
  1569. .button-ha-del,
  1570. .button-reboot,
  1571. .button-reconnect,
  1572. .button-rfb-forget,
  1573. .button-settings-factory,
  1574. .button-upgrade {
  1575. background: #c00000;
  1576. }
  1577. .button-add-network,
  1578. .button-apikey,
  1579. .button-dbgcmd,
  1580. .button-ha-add,
  1581. .button-ha-config,
  1582. .button-rfb-learn,
  1583. .button-settings-backup,
  1584. .button-settings-restore,
  1585. .button-update,
  1586. .button-update-password,
  1587. .button-upgrade-browse {
  1588. background: #00c000;
  1589. }
  1590. .button-add-light-schedule, .button-add-switch-schedule {
  1591. background: #00c000;
  1592. display: none;
  1593. }
  1594. .button-more-network, .button-more-schedule, .button-rfb-send, .button-wifi-scan {
  1595. background: #ff8000;
  1596. }
  1597. .button-apikey,
  1598. .button-dbgcmd,
  1599. .button-ha-add,
  1600. .button-upgrade,
  1601. .button-upgrade-browse {
  1602. margin-left: 5px;
  1603. }
  1604. input.slider {
  1605. margin-top: 10px;
  1606. }
  1607. span.slider {
  1608. font-size: 70%;
  1609. letter-spacing: 0;
  1610. margin-left: 10px;
  1611. margin-top: 7px;
  1612. }
  1613. .loading {
  1614. background-image: url(data:image/gif;base64,R0lGODlhFAAUAJEDAMzMzLOzs39/f////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgADACwAAAAAFAAUAAACPJyPqcuNItyCUJoQBo0ANIxpXOctYHaQpYkiHfM2cUrCNT0nqr4uudsz/IC5na/2Mh4Hu+HR6YBaplRDAQAh+QQFCgADACwEAAIADAAGAAACFpwdcYupC8BwSogR46xWZHl0l8ZYQwEAIfkEBQoAAwAsCAACAAoACgAAAhccMKl2uHxGCCvO+eTNmishcCCYjWEZFgAh+QQFCgADACwMAAQABgAMAAACFxwweaebhl4K4VE6r61DiOd5SfiN5VAAACH5BAUKAAMALAgACAAKAAoAAAIYnD8AeKqcHIwwhGntEWLkO3CcB4biNEIFACH5BAUKAAMALAQADAAMAAYAAAIWnDSpAHa4GHgohCHbGdbipnBdSHphAQAh+QQFCgADACwCAAgACgAKAAACF5w0qXa4fF6KUoVQ75UaA7Bs3yeNYAkWACH5BAUKAAMALAIABAAGAAwAAAIXnCU2iMfaRghqTmMp1moAoHyfIYIkWAAAOw==);
  1615. display: none;
  1616. height: 20px;
  1617. margin: 8px 0 0 10px;
  1618. width: 20px;
  1619. }
  1620. #menu .small {
  1621. font-size: 60%;
  1622. padding-left: 9px;
  1623. }
  1624. #menu div.footer {
  1625. color: #999;
  1626. font-size: 80%;
  1627. padding: 10px;
  1628. }
  1629. #menu div.footer a {
  1630. padding: 0;
  1631. text-decoration: none;
  1632. }
  1633. #panel-rfb fieldset {
  1634. margin: 10px 2px;
  1635. padding: 20px;
  1636. }
  1637. #panel-rfb input {
  1638. margin-right: 5px;
  1639. }
  1640. #panel-rfb label {
  1641. padding-top: 5px;
  1642. }
  1643. #panel-rfb input {
  1644. text-align: center;
  1645. }
  1646. #upgrade-progress {
  1647. display: none;
  1648. height: 20px;
  1649. margin-top: 10px;
  1650. width: 100%;
  1651. }
  1652. #downloader, #uploader {
  1653. display: none;
  1654. }
  1655. #networks .pure-g, #schedules .pure-g {
  1656. border-bottom: 1px solid #eee;
  1657. margin-bottom: 10px;
  1658. padding: 10px 0;
  1659. }
  1660. #networks .more {
  1661. display: none;
  1662. }
  1663. #haConfig, #scanResult {
  1664. display: none;
  1665. margin-top: 10px;
  1666. padding: 10px;
  1667. }
  1668. #weblog {
  1669. height: 25pc;
  1670. margin-bottom: 10px;
  1671. }
  1672. </style>
  1673. <!-- endbuild -->
  1674. </head>
  1675. <body>
  1676. <div id="password" class="webmode">
  1677. <div class="content">
  1678. <form id="formPassword" class="pure-form" action="/" method="post">
  1679. <div class="panel block">
  1680. <div class="header">
  1681. <h1>SECURITY</h1>
  1682. <h2>Before using this device you have to change the default password for the user 'admin'. This password will be used for the <strong>AP mode hotspot</strong>, the <strong>web interface</strong> (where you are now) and the <strong>over-the-air updates</strong>.</h2>
  1683. </div>
  1684. <div class="page">
  1685. <fieldset>
  1686. <div class="pure-g">
  1687. <label class="pure-u-1 pure-u-lg-1-4">Admin password</label>
  1688. <input name="adminPass" class="pure-u-1 pure-u-lg-3-4" type="password" tabindex="1" autocomplete="false">
  1689. <div class="pure-u-0 pure-u-lg-1-4"></div>
  1690. <div class="pure-u-1 pure-u-lg-3-4 hint">
  1691. The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br>
  1692. It must have at least <strong>five characters</strong> (numbers and letters and any of these special characters: _,.;:~!?@#$%^&amp;*&lt;&gt;\|(){}[]) and at least <strong>one lowercase</strong> and <strong>one uppercase</strong> or <strong>one number</strong>.</div>
  1693. </div>
  1694. <div class="pure-g">
  1695. <label class="pure-u-1 pure-u-lg-1-4">Repeat password</label>
  1696. <input name="adminPass" class="pure-u-1 pure-u-lg-3-4" type="password" tabindex="2" autocomplete="false">
  1697. </div>
  1698. <div class="pure-u-0 pure-u-lg-1-4 more"></div>
  1699. <button class="pure-button button-update-password" type="button">Update</button>
  1700. </fieldset>
  1701. </div>
  1702. </div>
  1703. </form>
  1704. </div> <!-- content -->
  1705. </div>
  1706. <div id="layout" class="webmode">
  1707. <a id="menuLink" class="menu-link">
  1708. <span></span>
  1709. </a>
  1710. <div id="menu">
  1711. <div class="pure-menu">
  1712. <span class="pure-menu-heading" name="hostname">HOSTNAME</span>
  1713. <span class="pure-menu-heading small" name="title">ESPurna 0.0.0</span>
  1714. <ul class="pure-menu-list">
  1715. <li class="pure-menu-item">
  1716. <a href="#" class="pure-menu-link" data="panel-status">STATUS</a>
  1717. </li>
  1718. <li class="pure-menu-item menu-item-divided">
  1719. <a href="#" class="pure-menu-link" data="panel-general">GENERAL</a>
  1720. </li>
  1721. <li class="pure-menu-item module module-dcz">
  1722. <a href="#" class="pure-menu-link" data="panel-domoticz">DOMOTICZ</a>
  1723. </li>
  1724. <li class="pure-menu-item module module-ha">
  1725. <a href="#" class="pure-menu-link" data="panel-ha">HASS</a>
  1726. </li>
  1727. <li class="pure-menu-item module module-idb">
  1728. <a href="#" class="pure-menu-link" data="panel-idb">INFLUXDB</a>
  1729. </li>
  1730. <li class="pure-menu-item module module-color">
  1731. <a href="#" class="pure-menu-link" data="panel-color">LIGHTS</a>
  1732. </li>
  1733. <li class="pure-menu-item module module-mqtt">
  1734. <a href="#" class="pure-menu-link" data="panel-mqtt">MQTT</a>
  1735. </li>
  1736. <li class="pure-menu-item module module-ntp">
  1737. <a href="#" class="pure-menu-link" data="panel-ntp">NTP</a>
  1738. </li>
  1739. <li class="pure-menu-item module module-rfb">
  1740. <a href="#" class="pure-menu-link" data="panel-rfb">RF</a>
  1741. </li>
  1742. <li class="pure-menu-item module module-sch">
  1743. <a href="#" class="pure-menu-link" data="panel-schedule">SCHEDULE</a>
  1744. </li>
  1745. <li class="pure-menu-item module module-relay">
  1746. <a href="#" class="pure-menu-link" data="panel-relay">SWITCHES</a>
  1747. </li>
  1748. <li class="pure-menu-item module module-tspk">
  1749. <a href="#" class="pure-menu-link" data="panel-thingspeak">THINGSPEAK</a>
  1750. </li>
  1751. <li class="pure-menu-item">
  1752. <a href="#" class="pure-menu-link" data="panel-wifi">WIFI</a>
  1753. </li>
  1754. <li class="pure-menu-item menu-item-divided">
  1755. <a href="#" class="pure-menu-link" data="panel-admin">ADMIN</a>
  1756. </li>
  1757. <li class="pure-menu-item module module-dbg">
  1758. <a href="#" class="pure-menu-link" data="panel-dbg">DEBUG</a>
  1759. </li>
  1760. </ul>
  1761. <div class="main-buttons">
  1762. <button class="pure-button button-update">Save</button>
  1763. <button class="pure-button button-reconnect">Reconnect</button>
  1764. <button class="pure-button button-reboot">Reboot</button>
  1765. </div>
  1766. <div class="footer">
  1767. &copy; 2016-2018<br>
  1768. Xose Pérez<br>
  1769. <a href="http://tinkerman.cat" target="_blank">http://tinkerman.cat</a><br>
  1770. <a href="https://github.com/xoseperez/espurna" target="_blank">ESPurna @ GitHub</a><br>
  1771. GPLv3 license<br>
  1772. </div>
  1773. </div>
  1774. </div>
  1775. <div class="content">
  1776. <div class="panel block" id="panel-status">
  1777. <div class="header">
  1778. <h1>STATUS</h1>
  1779. <h2>Current configuration</h2>
  1780. </div>
  1781. <div class="page">
  1782. <form class="pure-form pure-form-aligned">
  1783. <fieldset>
  1784. <div id="relays"></div>
  1785. <div id="colors"></div>
  1786. <div id="cct"></div>
  1787. <div id="channels"></div>
  1788. <div id="magnitudes"></div>
  1789. <div class="pure-u-1 pure-u-lg-1-2 state">
  1790. <div class="pure-u-1-2">Manufacturer</div>
  1791. <div class="pure-u-11-24"><span class="right" name="manufacturer"></span></div>
  1792. <div class="pure-u-1-2">Device</div>
  1793. <div class="pure-u-11-24"><span class="right" name="device"></span></div>
  1794. <div class="pure-u-1-2">Chip ID</div>
  1795. <div class="pure-u-11-24"><span class="right" name="chipid"></span></div>
  1796. <div class="pure-u-1-2">Wifi MAC</div>
  1797. <div class="pure-u-11-24"><span class="right" name="mac"></span></div>
  1798. <div class="pure-u-1-2">SDK version</div>
  1799. <div class="pure-u-11-24"><span class="right" name="sdk"></span></div>
  1800. <div class="pure-u-1-2">Core version</div>
  1801. <div class="pure-u-11-24"><span class="right" name="core"></span></div>
  1802. <div class="pure-u-1-2">Firmware name</div>
  1803. <div class="pure-u-11-24"><span class="right" name="app_name"></span></div>
  1804. <div class="pure-u-1-2">Firmware version</div>
  1805. <div class="pure-u-11-24"><span class="right" name="app_version"></span></div>
  1806. <!--
  1807. <div class="pure-u-1-2">Firmware revision</div>
  1808. <div class="pure-u-11-24"><span class="right" name="app_revision"></span></div>
  1809. -->
  1810. <div class="pure-u-1-2">Firmware build date</div>
  1811. <div class="pure-u-11-24"><span class="right" name="app_build"></span></div>
  1812. <div class="pure-u-1-2">Firmware size</div>
  1813. <div class="pure-u-11-24"><span class="right" name="sketch_size" post=" bytes"></span></div>
  1814. <div class="pure-u-1-2">Free space</div>
  1815. <div class="pure-u-11-24"><span class="right" name="free_size" post=" bytes"></span></div>
  1816. </div>
  1817. <div class="pure-u-1 pure-u-lg-11-24 state">
  1818. <div class="pure-u-1-2">Network</div>
  1819. <div class="pure-u-11-24"><span class="right" name="network"></span></div>
  1820. <div class="pure-u-1-2">BSSID</div>
  1821. <div class="pure-u-11-24"><span class="right" name="bssid"></span></div>
  1822. <div class="pure-u-1-2">Channel</div>
  1823. <div class="pure-u-11-24"><span class="right" name="channel"></span></div>
  1824. <div class="pure-u-1-2">RSSI</div>
  1825. <div class="pure-u-11-24"><span class="right" name="rssi"></span></div>
  1826. <div class="pure-u-1-2">IP</div>
  1827. <div class="pure-u-11-24"><a href=""><span class="right" name="deviceip"></span></a> (<a href=""><span class="right">telnet</span></a>)</div>
  1828. <div class="pure-u-1-2">Free heap</div>
  1829. <div class="pure-u-11-24"><span class="right" name="heap" post=" bytes"></span></div>
  1830. <div class="pure-u-1-2">Load average</div>
  1831. <div class="pure-u-11-24"><span class="right" name="loadaverage"></span><span>%</span></div>
  1832. <div class="pure-u-1-2">VCC</div>
  1833. <div class="pure-u-11-24"><span class="right" name="vcc">? </span><span>mV</span></div>
  1834. <div class="pure-u-1-2 module module-mqtt">MQTT Status</div>
  1835. <div class="pure-u-11-24 module module-mqtt"><span class="right" name="mqttStatus"></span></div>
  1836. <div class="pure-u-1-2 module module-ntp">NTP Status</div>
  1837. <div class="pure-u-11-24 module module-ntp"><span class="right" name="ntpStatus"></span></div>
  1838. <div class="pure-u-1-2 module module-ntp">Current time</div>
  1839. <div class="pure-u-11-24 module module-ntp"><span class="right" name="now"></span></div>
  1840. <div class="pure-u-1-2">Uptime</div>
  1841. <div class="pure-u-11-24"><span class="right" name="uptime"></span></div>
  1842. <div class="pure-u-1-2">Last update</div>
  1843. <div class="pure-u-11-24"><span class="right" name="ago">?</span><span> seconds ago</span></div>
  1844. </div>
  1845. </fieldset>
  1846. </form>
  1847. </div>
  1848. </div>
  1849. <form id="formSave" class="pure-form" action="/" method="post" enctype="multipart/form-data">
  1850. <div class="panel" id="panel-general">
  1851. <div class="header">
  1852. <h1>GENERAL</h1>
  1853. <h2>General configuration values</h2>
  1854. </div>
  1855. <div class="page">
  1856. <fieldset>
  1857. <div class="pure-g">
  1858. <label class="pure-u-1 pure-u-lg-1-4">Hostname</label>
  1859. <input name="hostname" class="pure-u-1 pure-u-lg-1-4" maxlength="32" type="text" action="reboot" tabindex="1">
  1860. <div class="pure-u-0 pure-u-lg-1-2"></div>
  1861. <div class="pure-u-0 pure-u-lg-1-4"></div>
  1862. <div class="pure-u-1 pure-u-lg-3-4 hint">
  1863. This name will identify this device in your network (http://&lt;hostname&gt;.local).<br>
  1864. Hostname may contain only the ASCII letters 'a' through 'z' (in a case-insensitive manner), the digits '0' through '9', and the hyphen ('-'). They can neither start or end with an hyphen.<br>
  1865. For this setting to take effect you should restart the wifi interface by clicking the "Reconnect" button.
  1866. </div>
  1867. </div>
  1868. <div class="pure-g">
  1869. <label class="pure-u-1 pure-u-lg-1-4">Double click delay</label>
  1870. <input name="btnDelay" class="pure-u-1 pure-u-lg-1-4" type="number" action="reboot" min="0" step="100" max="1000" tabindex="6">
  1871. <div class="pure-u-0 pure-u-lg-1-2"></div>
  1872. <div class="pure-u-0 pure-u-lg-1-4"></div>
  1873. <div class="pure-u-1 pure-u-lg-3-4 hint">
  1874. Delay in milliseconds to detect a double click (from 0 to 1000ms).<br>
  1875. The lower this number the faster the device will respond to button clicks but the harder it will be to get a double click.
  1876. Increase this number if you are having trouble to double click the button.
  1877. Set this value to 0 to disable double click. You won't be able to set the device in AP mode manually but your device will respond immediately to button clicks.<br>
  1878. You will have to <strong>reboot the device</strong> after updating for this setting to apply.
  1879. </div>
  1880. </div>
  1881. <div class="pure-g module module-led">
  1882. <label class="pure-u-1 pure-u-lg-1-4">LED mode</label>
  1883. <select name="ledMode0" class="pure-u-1 pure-u-lg-1-4" tabindex="7">
  1884. <option value="1">WiFi status</option>
  1885. <option value="8">Relay status</option>
  1886. <option value="0">MQTT managed</option>
  1887. <option value="4">Find me</option>
  1888. <option value="9">Relay &amp; WiFi</option>
  1889. <option value="5">Find me &amp; WiFi</option>
  1890. <option value="6">Always ON</option>
  1891. <option value="7">Always OFF</option>
  1892. </select>
  1893. <div class="pure-u-0 pure-u-lg-1-2"></div>
  1894. <div class="pure-u-0 pure-u-lg-1-4"></div>
  1895. <div class="pure-u-1 pure-u-lg-3-4 hint">
  1896. This setting defines the behaviour of the main LED in the board.<br>
  1897. When in "WiFi status" it will blink at 1Hz when trying to connect. If successfully connected it will briefly blink every 5 seconds if in STA mode or every second if in AP mode.<br>
  1898. When in "Relay status" mode the LED will be ON whenever any relay is ON, and OFF otherwise. This is global status notification.<br>
  1899. When in "MQTT managed" mode you will be able to set the LED state sending a message to "&lt;base_topic&gt;/led/0/set" with a payload of 0, 1 or 2 (to toggle it).<br>
  1900. When in "Find me" mode the LED will be ON when all relays are OFF. This is meant to locate switches at night.<br>
  1901. When in "Relay &amp; WiFi" mode it will follow the WiFi status but will stay mostly off when relays are OFF, and mostly ON when any of them is ON.<br>
  1902. When in "Find me &amp; WiFi" mode is the opposite of the "Relay &amp; WiFi", it will follow the WiFi status but will stay mostly on when relays are OFF, and mostly OFF when any of them is ON.<br>
  1903. "Always ON" and "Always OFF" modes are self-explanatory.
  1904. </div>
  1905. </div>
  1906. <div class="pure-g module module-alexa">
  1907. <label class="pure-u-1 pure-u-lg-1-4">Alexa integration</label>
  1908. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="alexaEnabled" tabindex="13"></div>
  1909. </div>
  1910. </fieldset>
  1911. </div>
  1912. </div>
  1913. <div class="panel" id="panel-relay">
  1914. <div class="header">
  1915. <h1>SWITCHES</h1>
  1916. <h2>Switch / relay configuration</h2>
  1917. </div>
  1918. <div class="page">
  1919. <fieldset>
  1920. <legend class="module module-multirelay">General</legend>
  1921. <div class="pure-g module module-multirelay">
  1922. <label class="pure-u-1 pure-u-lg-1-4">Switch sync mode</label>
  1923. <select name="relaySync" class="pure-u-1 pure-u-lg-3-4" tabindex="3">
  1924. <option value="0">No synchronisation</option>
  1925. <option value="1">Zero or one switches active</option>
  1926. <option value="2">One and just one switch active</option>
  1927. <option value="3">All synchronised</option>
  1928. </select>
  1929. <div class="pure-u-0 pure-u-lg-1-4"></div>
  1930. <div class="pure-u-1 pure-u-lg-3-4 hint">Define how the different switches should be synchronized.</div>
  1931. </div>
  1932. <div id="relayConfig"></div>
  1933. </fieldset>
  1934. </div>
  1935. </div>
  1936. <div class="panel" id="panel-color">
  1937. <div class="header">
  1938. <h1>LIGHTS</h1>
  1939. <h2>Lights configuration</h2>
  1940. </div>
  1941. <div class="page">
  1942. <fieldset>
  1943. <div class="pure-g">
  1944. <label class="pure-u-1 pure-u-lg-1-4">Use color</label>
  1945. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useColor" action="reload" tabindex="8"></div>
  1946. <div class="pure-u-0 pure-u-lg-1-2"></div>
  1947. <div class="pure-u-0 pure-u-lg-1-4"></div>
  1948. <div class="pure-u-1 pure-u-lg-3-4 hint">Use the first three channels as RGB channels. This will also enable the color picker in the web UI. Will only work if the device has at least 3 dimmable channels.<br>Reload the page to update the web interface.</div>
  1949. </div>
  1950. <div class="pure-g">
  1951. <label class="pure-u-1 pure-u-lg-1-4">Use RGB picker</label>
  1952. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useRGB" action="reload" tabindex="11"></div>
  1953. <div class="pure-u-0 pure-u-lg-1-2"></div>
  1954. <div class="pure-u-0 pure-u-lg-1-4"></div>
  1955. <div class="pure-u-1 pure-u-lg-3-4 hint">Use RGB color picker if enabled (plus brightness), otherwise use HSV (hue-saturation-value) style</div>
  1956. </div>
  1957. <div class="pure-g">
  1958. <label class="pure-u-1 pure-u-lg-1-4">Use white channel</label>
  1959. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useWhite" action="reload" tabindex="9"></div>
  1960. <div class="pure-u-0 pure-u-lg-1-2"></div>
  1961. <div class="pure-u-0 pure-u-lg-1-4"></div>
  1962. <div class="pure-u-1 pure-u-lg-3-4 hint">Use forth dimmable channel as (cold) white light calculated out of the RGB values.<br>Will only work if the device has at least 4 dimmable channels.<br>Enabling this will render useless the "Channel 4" slider in the status page.<br>Reload the page to update the web interface.</div>
  1963. </div>
  1964. <div class="pure-g">
  1965. <label class="pure-u-1 pure-u-lg-1-4">Use white color temperature</label>
  1966. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useCCT" action="reload" tabindex="10"></div>
  1967. <div class="pure-u-0 pure-u-lg-1-2"></div>
  1968. <div class="pure-u-0 pure-u-lg-1-4"></div>
  1969. <div class="pure-u-1 pure-u-lg-3-4 hint">Use fifth dimmable channel as warm white light and the forth dimmable channel as cold white.<br>Will only work if the device has at least 5 dimmable channels and "white channel" above is also ON.<br>Enabling this will render useless the "Channel 5" slider in the status page.<br>Reload the page to update the web interface.</div>
  1970. </div>
  1971. <div class="pure-g">
  1972. <label class="pure-u-1 pure-u-lg-1-4">Use gamma correction</label>
  1973. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useGamma" tabindex="11"></div>
  1974. <div class="pure-u-0 pure-u-lg-1-2"></div>
  1975. <div class="pure-u-0 pure-u-lg-1-4"></div>
  1976. <div class="pure-u-1 pure-u-lg-3-4 hint">Use gamma correction for RGB channels.<br>Will only work if "use colorpicker" above is also ON.</div>
  1977. </div>
  1978. <div class="pure-g">
  1979. <label class="pure-u-1 pure-u-lg-1-4">Use CSS style</label>
  1980. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useCSS" tabindex="12"></div>
  1981. <div class="pure-u-0 pure-u-lg-1-2"></div>
  1982. <div class="pure-u-0 pure-u-lg-1-4"></div>
  1983. <div class="pure-u-1 pure-u-lg-3-4 hint">Use CSS style to report colors to MQTT and REST API. <br>Red will be reported as "#FF0000" if ON, otherwise "255,0,0"</div>
  1984. </div>
  1985. <div class="pure-g">
  1986. <label class="pure-u-1 pure-u-lg-1-4">Color transitions</label>
  1987. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useTransitions" tabindex="13"></div>
  1988. <div class="pure-u-0 pure-u-lg-1-2"></div>
  1989. <div class="pure-u-0 pure-u-lg-1-4"></div>
  1990. <div class="pure-u-1 pure-u-lg-3-4 hint">If enabled color changes will be smoothed.</div>
  1991. </div>
  1992. <div class="pure-g">
  1993. <label class="pure-u-1 pure-u-lg-1-4">Transition time</label>
  1994. <div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-1" type="number" name="lightTime" min="10" max="5000" tabindex="14"></div>
  1995. <div class="pure-u-0 pure-u-lg-1-2"></div>
  1996. <div class="pure-u-0 pure-u-lg-1-4"></div>
  1997. <div class="pure-u-1 pure-u-lg-3-4 hint">Time in millisecons to transition from one color to another.</div>
  1998. </div>
  1999. <div class="pure-g">
  2000. <div class="pure-u-1 pure-u-lg-1-4"><label>MQTT group</label></div>
  2001. <div class="pure-u-1 pure-u-lg-3-4"><input name="mqttGroupColor" class="pure-u-1" tabindex="15" action="reconnect"></div>
  2002. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2003. <div class="pure-u-1 pure-u-lg-3-4 hint">Sync color between different lights.</div>
  2004. </div>
  2005. </fieldset>
  2006. </div>
  2007. </div>
  2008. <div class="panel" id="panel-admin">
  2009. <div class="header">
  2010. <h1>ADMINISTRATION</h1>
  2011. <h2>Device administration and security settings</h2>
  2012. </div>
  2013. <div class="page">
  2014. <fieldset>
  2015. <div class="pure-g">
  2016. <label class="pure-u-1 pure-u-lg-1-4">Settings</label>
  2017. <div class="pure-u-1-3 pure-u-lg-1-4"><button class="pure-button button-settings-backup pure-u-23-24">Backup</button></div>
  2018. <div class="pure-u-1-3 pure-u-lg-1-4"><button class="pure-button button-settings-restore pure-u-23-24">Restore</button></div>
  2019. <div class="pure-u-1-3 pure-u-lg-1-4"><button class="pure-button button-settings-factory pure-u-1">Factory Reset</button></div>
  2020. </div>
  2021. <div class="pure-g">
  2022. <label class="pure-u-1 pure-u-lg-1-4">Admin password</label>
  2023. <input name="adminPass" class="pure-u-1 pure-u-lg-3-4" type="password" action="reboot" tabindex="11" autocomplete="false">
  2024. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2025. <div class="pure-u-1 pure-u-lg-3-4 hint">
  2026. The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br>
  2027. It must have at least <strong>five characters</strong> (numbers and letters and any of these special characters: _,.;:~!?@#$%^&amp;*&lt;&gt;\|(){}[]) and at least <strong>one lowercase</strong> and <strong>one uppercase</strong> or <strong>one number</strong>.</div>
  2028. </div>
  2029. <div class="pure-g">
  2030. <label class="pure-u-1 pure-u-lg-1-4">Repeat password</label>
  2031. <input name="adminPass" class="pure-u-1 pure-u-lg-3-4" type="password" action="reboot" tabindex="12" autocomplete="false">
  2032. </div>
  2033. <div class="pure-g">
  2034. <label class="pure-u-1 pure-u-lg-1-4">HTTP port</label>
  2035. <input name="webPort" class="pure-u-1 pure-u-lg-1-4" type="text" action="reboot" tabindex="13">
  2036. <div class="pure-u-0 pure-u-lg-1-2"></div>
  2037. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2038. <div class="pure-u-1 pure-u-lg-3-4 hint">
  2039. This is the port for the web interface and API requests.
  2040. If different than 80 (standard HTTP port) you will have to add it explicitly to your requests: http://myip:myport/
  2041. </div>
  2042. </div>
  2043. <div class="pure-g">
  2044. <label class="pure-u-1 pure-u-lg-1-4">Enable WS Auth</label>
  2045. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="wsAuth"></div>
  2046. </div>
  2047. <div class="pure-g">
  2048. <label class="pure-u-1 pure-u-lg-1-4">Enable HTTP API</label>
  2049. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiEnabled"></div>
  2050. </div>
  2051. <div class="pure-g">
  2052. <label class="pure-u-1 pure-u-lg-1-4">Real time API</label>
  2053. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiRealTime"></div>
  2054. <div class="pure-u-0 pure-u-lg-1-2"></div>
  2055. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2056. <div class="pure-u-1 pure-u-lg-3-4 hint">
  2057. By default, some magnitudes are being preprocessed and filtered to avoid spurious values.
  2058. If you want to get real-time values (not preprocessed) in the API turn on this setting.
  2059. </div>
  2060. </div>
  2061. <div class="pure-g">
  2062. <label class="pure-u-1 pure-u-lg-1-4">HTTP API Key</label>
  2063. <input name="apiKey" class="pure-u-3-4 pure-u-lg-1-2" type="text" tabindex="14">
  2064. <div class=" pure-u-1-4 pure-u-lg-1-4"><button class="pure-button button-apikey pure-u-23-24">Auto</button></div>
  2065. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2066. <div class="pure-u-1 pure-u-lg-3-4 hint">
  2067. This is the key you will have to pass with every HTTP request to the API, either to get or write values.
  2068. All API calls must contain the <strong>apikey</strong> parameter with the value above.
  2069. To know what APIs are enabled do a call to <strong>/apis</strong>.
  2070. </div>
  2071. </div>
  2072. <div class="pure-g module module-telnet">
  2073. <label class="pure-u-1 pure-u-lg-1-4">Enable TELNET</label>
  2074. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="telnetSTA"></div>
  2075. <div class="pure-u-0 pure-u-lg-1-2"></div>
  2076. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2077. <div class="pure-u-1 pure-u-lg-3-4 hint">Turn ON to be able to telnet to your device while connected to your home router.<br>TELNET is always enabled in AP mode.</div>
  2078. </div>
  2079. <div class="pure-g module module-nofuss">
  2080. <label class="pure-u-1 pure-u-lg-1-4">Automatic remote updates (NoFUSS)</label>
  2081. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="nofussEnabled"></div>
  2082. </div>
  2083. <div class="pure-g module module-nofuss">
  2084. <label class="pure-u-1 pure-u-lg-1-4">NoFUSS server</label>
  2085. <input name="nofussServer" class="pure-u-1 pure-u-lg-3-4" type="text" tabindex="15">
  2086. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2087. <div class="pure-u-1 pure-u-lg-3-4 hint">This name address of the NoFUSS server for automatic remote updates (see https://bitbucket.org/xoseperez/nofuss).</div>
  2088. </div>
  2089. <div class="pure-g">
  2090. <label class="pure-u-1 pure-u-lg-1-4">Upgrade</label>
  2091. <input class="pure-u-1-2 pure-u-lg-1-2" name="filename" type="text" readonly>
  2092. <div class=" pure-u-1-4 pure-u-lg-1-8"><button class="pure-button button-upgrade-browse pure-u-23-24">Browse</button></div>
  2093. <div class=" pure-u-1-4 pure-u-lg-1-8"><button class="pure-button button-upgrade pure-u-23-24">Upgrade</button></div>
  2094. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2095. <div class="pure-u-1 pure-u-lg-3-4 hint">The device has <span name="free_size"></span> bytes available for OTA updates. If your image is larger than this consider doing a <a href="https://github.com/xoseperez/espurna/wiki/TwoStepUpdates" target="_blank"><strong>two-step update</strong></a>.</div>
  2096. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2097. <div class="pure-u-1 pure-u-lg-3-4"><progress id="upgrade-progress"></progress></div>
  2098. <input name="upgrade" type="file" tabindex="16">
  2099. </div>
  2100. </fieldset>
  2101. </div>
  2102. </div>
  2103. <div class="panel" id="panel-wifi">
  2104. <div class="header">
  2105. <h1>WIFI</h1>
  2106. <h2>You can configure up to 5 different WiFi networks. The device will try to connect in order of signal strength.</h2>
  2107. </div>
  2108. <div class="page">
  2109. <fieldset>
  2110. <legend>General</legend>
  2111. <div class="pure-g">
  2112. <label class="pure-u-1 pure-u-lg-1-4">Scan networks</label>
  2113. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="wifiScan" tabindex="1"></div>
  2114. <div class="pure-u-0 pure-u-lg-1-2"></div>
  2115. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2116. <div class="pure-u-1 pure-u-lg-3-4 hint">
  2117. ESPurna will scan for visible WiFi SSIDs and try to connect to networks defined below in order of <strong>signal strength</strong>, even if multiple AP share the same SSID.
  2118. When disabled, ESPurna will try to connect to the networks in the same order they are listed below.
  2119. Disable this option if you are <strong>connecting to a single access point</strong> (or router) or to a <strong>hidden SSID</strong>.
  2120. </div>
  2121. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2122. <button class="pure-button button-wifi-scan" type="button">Scan now</button>
  2123. <div class="pure-u-0 pure-u-lg-1-4 scan loading"></div>
  2124. </div>
  2125. <div class="pure-g">
  2126. <span class="pure-u-1 terminal" id="scanResult" name="scanResult"></span>
  2127. </div>
  2128. <legend>Networks</legend>
  2129. <div id="networks"></div>
  2130. <button type="button" class="pure-button button-add-network">Add network</button>
  2131. </fieldset>
  2132. </div>
  2133. </div>
  2134. <div class="panel" id="panel-schedule">
  2135. <div class="header">
  2136. <h1>SCHEDULE</h1>
  2137. <h2>Turn switches ON and OFF based on the current time.</h2>
  2138. </div>
  2139. <div class="page">
  2140. <fieldset>
  2141. <div id="schedules"></div>
  2142. <button type="button" class="pure-button button-add-switch-schedule module module-relay">Add switch schedule</button>
  2143. <button type="button" class="pure-button button-add-light-schedule module module-color">Add channel schedule</button>
  2144. </fieldset>
  2145. </div>
  2146. </div>
  2147. <div class="panel" id="panel-mqtt">
  2148. <div class="header">
  2149. <h1>MQTT</h1>
  2150. <h2>Configure an <strong>MQTT broker</strong> in your network and you will be able to change the switch status via an MQTT message.</h2>
  2151. </div>
  2152. <div class="page">
  2153. <fieldset>
  2154. <div class="pure-g">
  2155. <label class="pure-u-1 pure-u-lg-1-4">Enable MQTT</label>
  2156. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="mqttEnabled" tabindex="30"></div>
  2157. </div>
  2158. <div class="pure-g">
  2159. <label class="pure-u-1 pure-u-lg-1-4">MQTT Broker</label>
  2160. <input class="pure-u-1 pure-u-lg-1-4" name="mqttServer" type="text" tabindex="21" placeholder="IP or address of your broker">
  2161. </div>
  2162. <div class="pure-g">
  2163. <label class="pure-u-1 pure-u-lg-1-4">MQTT Port</label>
  2164. <input class="pure-u-1 pure-u-lg-1-4" name="mqttPort" type="text" tabindex="22" value="1883">
  2165. </div>
  2166. <div class="pure-g">
  2167. <label class="pure-u-1 pure-u-lg-1-4">MQTT User</label>
  2168. <input class="pure-u-1 pure-u-lg-1-4" name="mqttUser" type="text" tabindex="23" placeholder="Leave blank if no user" autocomplete="false">
  2169. </div>
  2170. <div class="pure-g">
  2171. <label class="pure-u-1 pure-u-lg-1-4">MQTT Password</label>
  2172. <input class="pure-u-1 pure-u-lg-1-4" name="mqttPassword" type="password" tabindex="24" placeholder="Leave blank if no pass" autocomplete="false">
  2173. </div>
  2174. <div class="pure-g">
  2175. <label class="pure-u-1 pure-u-lg-1-4">MQTT Client ID</label>
  2176. <input class="pure-u-1 pure-u-lg-1-4" name="mqttClientID" type="text" tabindex="25">
  2177. <div class="pure-u-0 pure-u-lg-1-2"></div>
  2178. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2179. <div class="pure-u-1 pure-u-lg-3-4 hint">
  2180. If left empty the firmware will generate a client ID based on the serial number of the chip.
  2181. </div>
  2182. </div>
  2183. <div class="pure-g">
  2184. <label class="pure-u-1 pure-u-lg-1-4">MQTT QoS</label>
  2185. <select class="pure-u-1 pure-u-lg-1-4" name="mqttQoS" tabindex="26">
  2186. <option value="0">0: At most once</option>
  2187. <option value="1">1: At least once</option>
  2188. <option value="2">2: Exactly once</option>
  2189. </select>
  2190. </div>
  2191. <div class="pure-g">
  2192. <label class="pure-u-1 pure-u-lg-1-4">MQTT Retain</label>
  2193. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="mqttRetain" tabindex="27"></div>
  2194. </div>
  2195. <div class="pure-g">
  2196. <label class="pure-u-1 pure-u-lg-1-4">MQTT Keep Alive</label>
  2197. <input class="pure-u-1 pure-u-lg-1-4" type="number" name="mqttKeep" min="10" max="3600" tabindex="28">
  2198. </div>
  2199. <div class="pure-g module module-mqttssl">
  2200. <label class="pure-u-1 pure-u-lg-1-4">Use secure connection (SSL)</label>
  2201. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="mqttUseSSL" tabindex="29"></div>
  2202. </div>
  2203. <div class="pure-g module module-mqttssl">
  2204. <label class="pure-u-1 pure-u-lg-1-4">SSL Fingerprint</label>
  2205. <input class="pure-u-1 pure-u-lg-3-4" name="mqttFP" type="text" maxlength="59" tabindex="30">
  2206. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2207. <div class="pure-u-1 pure-u-lg-3-4 hint">
  2208. This is the fingerprint for the SSL certificate of the server.<br>
  2209. You can get it using <a href="https://www.grc.com/fingerprints.htm" target="_blank">https://www.grc.com/fingerprints.htm</a><br>
  2210. or using openssl from a linux box by typing:<br>
  2211. <pre>$ openssl s_client -connect &lt;host&gt;:&lt;port&gt; &lt; /dev/null 2&gt;/dev/null | openssl x509 -fingerprint -noout -in /dev/stdin</pre>
  2212. </div>
  2213. </div>
  2214. <div class="pure-g">
  2215. <label class="pure-u-1 pure-u-lg-1-4">MQTT Root Topic</label>
  2216. <input class="pure-u-1 pure-u-lg-3-4" name="mqttTopic" type="text" tabindex="31">
  2217. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2218. <div class="pure-u-1 pure-u-lg-3-4 hint">
  2219. This is the root topic for this device. The {hostname} and {mac} placeholders will be replaced by the device hostname and MAC address.<br>
  2220. - <strong>&lt;root&gt;/relay/#/set</strong> Send a 0 or a 1 as a payload to this topic to switch it on or off. You can also send a 2 to toggle its current state. Replace # with the switch ID (starting from 0). If the board has only one switch it will be 0.<br>
  2221. <span class="module module-color">- <strong>&lt;root&gt;/rgb/set</strong> Set the color using this topic, your can either send an "#RRGGBB" value or "RRR,GGG,BBB" (0-255 each).<br></span>
  2222. <span class="module module-color">- <strong>&lt;root&gt;/hsv/set</strong> Set the color using hue (0-360), saturation (0-100) and value (0-100) values, comma separated.<br></span>
  2223. <span class="module module-color">- <strong>&lt;root&gt;/brightness/set</strong> Set the brighness (0-255).<br></span>
  2224. <span class="module module-color">- <strong>&lt;root&gt;/channel/#/set</strong> Set the value for a single color channel (0-255). Replace # with the channel ID (starting from 0 and up to 4 for RGBWC lights).<br></span>
  2225. <span class="module module-color">- <strong>&lt;root&gt;/mired/set</strong> Set the temperature color in mired.<br></span>
  2226. - <strong>&lt;root&gt;/status</strong> The device will report a 1 to this topic every few minutes. Upon MQTT disconnecting this will be set to 0.<br>
  2227. - Other values reported (depending on the build) are: <strong>firmware</strong> and <strong>version</strong>, <strong>hostname</strong>, <strong>IP</strong>, <strong>MAC</strong>, signal strenth (<strong>RSSI</strong>), <strong>uptime</strong> (in seconds), <strong>free heap</strong> and <strong>power supply</strong>.
  2228. </div>
  2229. </div>
  2230. <div class="pure-g">
  2231. <label class="pure-u-1 pure-u-lg-1-4">Use JSON payload</label>
  2232. <div class="pure-u-1 pure-u-lg-3-4"><input type="checkbox" name="mqttUseJson" tabindex="32"></div>
  2233. <div class="pure-u-1 pure-u-lg-1-4"></div>
  2234. <div class="pure-u-1 pure-u-lg-3-4 hint">
  2235. All messages (except the device status) will be included in a JSON payload along with the timestamp and hostname
  2236. and sent under the <strong>&lt;root&gt;/data</strong> topic.<br>
  2237. Messages will be queued and sent after 100ms, so different messages could be merged into a single payload.<br>
  2238. Subscriptions will still be done to single topics.
  2239. </div>
  2240. </div>
  2241. </fieldset>
  2242. </div>
  2243. </div>
  2244. <div class="panel" id="panel-ntp">
  2245. <div class="header">
  2246. <h1>NTP</h1>
  2247. <h2>Configure your NTP (Network Time Protocol) servers and local configuration to keep your device time up to the second for your location.</h2>
  2248. </div>
  2249. <div class="page">
  2250. <fieldset>
  2251. <div class="pure-g">
  2252. <label class="pure-u-1 pure-u-lg-1-4">Device Current Time</label>
  2253. <input class="pure-u-1 pure-u-lg-3-4" name="now" type="text" readonly>
  2254. </div>
  2255. <div class="pure-g">
  2256. <label class="pure-u-1 pure-u-lg-1-4">NTP Server</label>
  2257. <input class="pure-u-1 pure-u-lg-3-4" name="ntpServer" type="text" tabindex="41">
  2258. </div>
  2259. <div class="pure-g">
  2260. <label class="pure-u-1 pure-u-lg-1-4">Time Zone</label>
  2261. <select class="pure-u-1 pure-u-lg-1-4" name="ntpOffset" tabindex="42"></select>
  2262. </div>
  2263. <div class="pure-g">
  2264. <label class="pure-u-1 pure-u-lg-1-4">Enable DST</label>
  2265. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="ntpDST"></div>
  2266. </div>
  2267. <div class="pure-g">
  2268. <label class="pure-u-1 pure-u-lg-1-4">DST Region</label>
  2269. <select class="pure-u-1 pure-u-lg-1-4" name="ntpRegion">
  2270. <option value="0">Europe</option>
  2271. <option value="1">USA</option>
  2272. </select>
  2273. </div>
  2274. </fieldset>
  2275. </div>
  2276. </div>
  2277. <div class="panel" id="panel-domoticz">
  2278. <div class="header">
  2279. <h1>DOMOTICZ</h1>
  2280. <h2>
  2281. Configure the connection to your Domoticz server.
  2282. </h2>
  2283. </div>
  2284. <div class="page">
  2285. <fieldset>
  2286. <legend>General</legend>
  2287. <div class="pure-g">
  2288. <label class="pure-u-1 pure-u-lg-1-4">Enable Domoticz</label>
  2289. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="dczEnabled" tabindex="30"></div>
  2290. </div>
  2291. <div class="pure-g">
  2292. <label class="pure-u-1 pure-u-lg-1-4">Domoticz IN Topic</label>
  2293. <input class="pure-u-1 pure-u-lg-3-4" name="dczTopicIn" type="text" tabindex="31">
  2294. </div>
  2295. <div class="pure-g">
  2296. <label class="pure-u-1 pure-u-lg-1-4">Domoticz OUT Topic</label>
  2297. <input class="pure-u-1 pure-u-lg-3-4" name="dczTopicOut" type="text" action="reconnect" tabindex="32">
  2298. </div>
  2299. <legend>Sensors &amp; actuators</legend>
  2300. <div class="pure-g">
  2301. <div class="pure-u-1 hint">Set IDX to 0 to disable notifications from that component.</div>
  2302. </div>
  2303. <div id="dczRelays"></div>
  2304. </fieldset>
  2305. </div>
  2306. </div>
  2307. <div class="panel" id="panel-ha">
  2308. <div class="header">
  2309. <h1>HOME ASSISTANT</h1>
  2310. <h2>
  2311. Add this device to your Home Assistant.
  2312. </h2>
  2313. </div>
  2314. <div class="page">
  2315. <fieldset>
  2316. <legend>Discover</legend>
  2317. <div class="pure-g">
  2318. <label class="pure-u-1 pure-u-lg-1-4">Discover</label>
  2319. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="haEnabled" tabindex="14"></div>
  2320. <div class="pure-u-0 pure-u-lg-1-2"></div>
  2321. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2322. <div class="pure-u-1 pure-u-lg-3-4 hint">
  2323. Home Assistant auto-discovery feature. Enable and save to add the device to your HA console.
  2324. When using a colour light you might want to disable CSS style so Home Assistant can parse the color.
  2325. </div>
  2326. </div>
  2327. <div class="pure-g">
  2328. <label class="pure-u-1 pure-u-lg-1-4">Prefix</label>
  2329. <input class="pure-u-1 pure-u-lg-1-4" name="haPrefix" type="text" tabindex="15">
  2330. </div>
  2331. <legend>Configuration</legend>
  2332. <div class="pure-g">
  2333. <label class="pure-u-1 pure-u-lg-1-4">Configuration</label>
  2334. <div class="pure-u-1-4 pure-u-lg-3-4"><button class="pure-button button-ha-config pure-u-1-3">Show</button></div>
  2335. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2336. <div class="pure-u-1 pure-u-lg-3-4 hint">
  2337. These are the settings you should copy to your Home Assistant "configuration.yaml" file.
  2338. If any of the sections below (switch, light, sensor) already exists, do not duplicate it,
  2339. simply copy the contents of the section below the ones already present.
  2340. </div>
  2341. </div>
  2342. <div class="pure-g">
  2343. <span class="pure-u-1 terminal" id="haConfig" name="haConfig"></span>
  2344. </div>
  2345. </fieldset>
  2346. </div>
  2347. </div>
  2348. <div class="panel" id="panel-thingspeak">
  2349. <div class="header">
  2350. <h1>THINGSPEAK</h1>
  2351. <h2>
  2352. Send your sensors data to Thingspeak.
  2353. </h2>
  2354. </div>
  2355. <div class="page">
  2356. <fieldset>
  2357. <legend>General</legend>
  2358. <div class="pure-g">
  2359. <label class="pure-u-1 pure-u-lg-1-4">Enable Thingspeak</label>
  2360. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="tspkEnabled" tabindex="30"></div>
  2361. </div>
  2362. <div class="pure-g">
  2363. <label class="pure-u-1 pure-u-lg-1-4">Thingspeak API Key</label>
  2364. <input class="pure-u-1 pure-u-lg-3-4" name="tspkKey" type="text" tabindex="31">
  2365. </div>
  2366. <legend>Sensors &amp; actuators</legend>
  2367. <div class="pure-g">
  2368. <div class="pure-u-1 hint">Enter the field number to send each data to, 0 disable notifications from that component.</div>
  2369. </div>
  2370. <div id="tspkRelays"></div>
  2371. </fieldset>
  2372. </div>
  2373. </div>
  2374. <div class="panel" id="panel-idb">
  2375. <div class="header">
  2376. <h1>INFLUXDB</h1>
  2377. <h2>
  2378. Configure the connection to your InfluxDB server. Leave the host field empty to disable InfluxDB connection.
  2379. </h2>
  2380. </div>
  2381. <div class="page">
  2382. <fieldset>
  2383. <div class="pure-g">
  2384. <label class="pure-u-1 pure-u-lg-1-4">Enable InfluxDB</label>
  2385. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="idbEnabled" tabindex="40"></div>
  2386. </div>
  2387. <div class="pure-g">
  2388. <label class="pure-u-1 pure-u-lg-1-4">Host</label>
  2389. <input class="pure-u-1 pure-u-lg-3-4" name="idbHost" type="text" tabindex="41">
  2390. </div>
  2391. <div class="pure-g">
  2392. <label class="pure-u-1 pure-u-lg-1-4">Port</label>
  2393. <input class="pure-u-1 pure-u-lg-3-4" name="idbPort" type="text" tabindex="42">
  2394. </div>
  2395. <div class="pure-g">
  2396. <label class="pure-u-1 pure-u-lg-1-4">Database</label>
  2397. <input class="pure-u-1 pure-u-lg-3-4" name="idbDatabase" type="text" tabindex="43">
  2398. </div>
  2399. <div class="pure-g">
  2400. <label class="pure-u-1 pure-u-lg-1-4">Username</label>
  2401. <input class="pure-u-1 pure-u-lg-3-4" name="idbUsername" type="text" tabindex="44" autocomplete="false">
  2402. </div>
  2403. <div class="pure-g">
  2404. <label class="pure-u-1 pure-u-lg-1-4">Password</label>
  2405. <input class="pure-u-1 pure-u-lg-3-4" name="idbPassword" type="password" tabindex="45" autocomplete="false">
  2406. </div>
  2407. </fieldset>
  2408. </div>
  2409. </div>
  2410. <div class="panel" id="panel-dbg">
  2411. <div class="header">
  2412. <h1>DEBUG LOG</h1>
  2413. <h2>
  2414. Shows debug messages from the device
  2415. </h2>
  2416. </div>
  2417. <div class="page">
  2418. <fieldset>
  2419. <div class="pure-g module module-cmd">
  2420. <div class="pure-u-1 hint">
  2421. Write a command and click send to execute it on the device. The output will be shown in the debug text area below.
  2422. </div>
  2423. <input name="dbgcmd" class="pure-u-3-4" type="text" tabindex="2">
  2424. <div class=" pure-u-1-4 pure-u-lg-1-4"><button class="pure-button button-dbgcmd pure-u-23-24">Send</button></div>
  2425. </div>
  2426. <div class="pure-g">
  2427. <textarea class="pure-u-1 terminal" id="weblog" name="weblog" wrap="off" readonly></textarea>
  2428. <div class=" pure-u-1-4 pure-u-lg-1-4"><button class="pure-button button-dbg-clear pure-u-23-24">Clear</button></div>
  2429. </div>
  2430. </fieldset>
  2431. </div>
  2432. </div>
  2433. <div class="panel" id="panel-rfb">
  2434. <div class="header">
  2435. <h1>RADIO FREQUENCY</h1>
  2436. <h2>
  2437. Sonoff 433 RF Bridge &amp; RF Link Configuration<br><br>
  2438. This page allows you to configure the RF codes for the Sonoff RFBridge 433 and also for a basic RF receiver.<br><br>
  2439. To learn a new code click <strong>LEARN</strong> (the Sonoff RFBridge will beep) then press a button on the remote, the new code should show up (and the RFBridge will double beep). If the device double beeps but the code does not update it has not been properly learnt. Keep trying.<br><br>
  2440. Modify or create new codes manually and then click <strong>SAVE</strong> to store them in the device memory. If your controlled device uses the same code to switch ON and OFF, learn the code with the ON button and copy paste it to the OFF input box, then click SAVE on the last one to store the value.<br><br>
  2441. Delete any code clicking the <strong>FORGET</strong> button.
  2442. <span class="module module-rfbraw"><br><br>You can also specify 116-chars long RAW codes. Raw codes require a <a target="_blank" href="https://github.com/rhx/RF-Bridge-EFM8BB1">specific firmware for for the EFM8BB1</a>.</span>
  2443. </h2>
  2444. </div>
  2445. <div class="page">
  2446. <fieldset>
  2447. <div id="rfbNodes"></div>
  2448. </fieldset>
  2449. </div>
  2450. </div>
  2451. </form>
  2452. </div> <!-- content -->
  2453. </div> <!-- layout -->
  2454. <!-- Templates -->
  2455. <div id="rfbNodeTemplate" class="template">
  2456. <legend>Switch #<span></span></legend>
  2457. <div class="pure-g">
  2458. <div class="pure-u-1 pure-u-lg-1-4"><label>Switch ON</label></div>
  2459. <input class="pure-u-1 pure-u-lg-1-3" type="text" maxlength="18" name="rfbcode" data-id="1" data-status="1">
  2460. <div class="pure-u-1-3 pure-u-lg-1-8"><button type="button" class="pure-u-23-24 pure-button button-rfb-learn">LEARN</button></div>
  2461. <div class="pure-u-1-3 pure-u-lg-1-8"><button type="button" class="pure-u-23-24 pure-button button-rfb-send">SAVE</button></div>
  2462. <div class="pure-u-1-3 pure-u-lg-1-8"><button type="button" class="pure-u-23-24 pure-button button-rfb-forget">FORGET</button></div>
  2463. </div>
  2464. <div class="pure-g">
  2465. <div class="pure-u-1 pure-u-lg-1-4"><label>Switch OFF</label></div>
  2466. <input class="pure-u-1 pure-u-lg-1-3" type="text" maxlength="18" name="rfbcode" data-id="1" data-status="0">
  2467. <div class="pure-u-1-3 pure-u-lg-1-8"><button type="button" class="pure-u-23-24 pure-button button-rfb-learn">LEARN</button></div>
  2468. <div class="pure-u-1-3 pure-u-lg-1-8"><button type="button" class="pure-u-23-24 pure-button button-rfb-send">SAVE</button></div>
  2469. <div class="pure-u-1-3 pure-u-lg-1-8"><button type="button" class="pure-u-23-24 pure-button button-rfb-forget">FORGET</button></div>
  2470. </div>
  2471. </div>
  2472. <div id="networkTemplate" class="template">
  2473. <div class="pure-g">
  2474. <label class="pure-u-1 pure-u-lg-1-4">Network SSID</label>
  2475. <div class="pure-u-5-6 pure-u-lg-2-3"><input name="ssid" type="text" action="reconnect" class="pure-u-23-24" value="" tabindex="0" placeholder="Network SSID" required autocomplete="false"></div>
  2476. <div class="pure-u-1-6 pure-u-lg-1-12"><button type="button" class="pure-button button-more-network pure-u-1">...</button></div>
  2477. <label class="pure-u-1 pure-u-lg-1-4 more">Password</label>
  2478. <input class="pure-u-1 pure-u-lg-3-4 more" name="pass" type="password" action="reconnect" value="" tabindex="0" autocomplete="false">
  2479. <label class="pure-u-1 pure-u-lg-1-4 more">Static IP</label>
  2480. <input class="pure-u-1 pure-u-lg-3-4 more" name="ip" type="text" action="reconnect" value="" maxlength="15" tabindex="0" autocomplete="false">
  2481. <div class="pure-u-0 pure-u-lg-1-4 more"></div>
  2482. <div class="pure-u-1 pure-u-lg-3-4 hint more">Leave empty for DNS negotiation</div>
  2483. <label class="pure-u-1 pure-u-lg-1-4 more">Gateway IP</label>
  2484. <input class="pure-u-1 pure-u-lg-3-4 more" name="gw" type="text" action="reconnect" value="" maxlength="15" tabindex="0" autocomplete="false">
  2485. <div class="pure-u-0 pure-u-lg-1-4 more"></div>
  2486. <div class="pure-u-1 pure-u-lg-3-4 hint more">Set when using a static IP</div>
  2487. <label class="pure-u-1 pure-u-lg-1-4 more">Network Mask</label>
  2488. <input class="pure-u-1 pure-u-lg-3-4 more" name="mask" type="text" action="reconnect" value="255.255.255.0" maxlength="15" tabindex="0" autocomplete="false">
  2489. <div class="pure-u-0 pure-u-lg-1-4 more"></div>
  2490. <div class="pure-u-1 pure-u-lg-3-4 hint more">Usually 255.255.255.0 for /24 networks</div>
  2491. <label class="pure-u-1 pure-u-lg-1-4 more">DNS IP</label>
  2492. <input class="pure-u-1 pure-u-lg-3-4 more" name="dns" type="text" action="reconnect" value="8.8.8.8" maxlength="15" tabindex="0" autocomplete="false">
  2493. <div class="pure-u-0 pure-u-lg-1-4 more"></div>
  2494. <div class="pure-u-1 pure-u-lg-3-4 hint more">Set the Domain Name Server IP to use when using a static IP</div>
  2495. <div class="pure-u-0 pure-u-lg-1-4 more"></div>
  2496. <button class="pure-button button-del-network more" type="button">Delete network</button>
  2497. </div>
  2498. </div>
  2499. <div id="scheduleTemplate" class="template">
  2500. <div class="pure-g">
  2501. <label class="pure-u-1 pure-u-lg-1-4">When time is</label>
  2502. <div class="pure-u-1-4 pure-u-lg-1-5">
  2503. <input class="pure-u-2-3" name="schHour" type="number" min="0" step="1" max="23" value="0">
  2504. <div class="pure-u-1-4 hint center">&nbsp;h</div>
  2505. </div>
  2506. <div class="pure-u-1-4 pure-u-lg-1-5">
  2507. <input class="pure-u-2-3" name="schMinute" type="number" min="0" step="1" max="59" value="0">
  2508. <div class="pure-u-1-4 hint center">&nbsp;m</div>
  2509. </div>
  2510. <div class="pure-u-0 pure-u-lg-1-3"></div>
  2511. <label class="pure-u-1 pure-u-lg-1-4">Use UTC time</label>
  2512. <div class="pure-u-1 pure-u-lg-3-4"><input type="checkbox" name="schUTC"></div>
  2513. <label class="pure-u-1 pure-u-lg-1-4">And weekday is one of</label>
  2514. <div class="pure-u-2-5 pure-u-lg-1-5">
  2515. <input class="pure-u-23-24 pure-u-lg-23-24" name="schWDs" type="text" maxlength="15" tabindex="0" value="1,2,3,4,5,6,7">
  2516. </div>
  2517. <div class="pure-u-3-5 pure-u-lg-1-2 hint center">&nbsp;1 for Monday, 2 for Tuesday...</div>
  2518. <div id="schActionDiv" class="pure-u-1">
  2519. </div>
  2520. <label class="pure-u-1 pure-u-lg-1-4">Enabled</label>
  2521. <div class="pure-u-1 pure-u-lg-3-4"><input type="checkbox" name="schEnabled"></div>
  2522. <div class="pure-u-0 pure-u-lg-1-4"></div>
  2523. <button class="pure-button button-del-schedule" type="button">Delete schedule</button>
  2524. </div>
  2525. </div>
  2526. <div id="switchActionTemplate" class="template">
  2527. <label class="pure-u-1 pure-u-lg-1-4">Action</label>
  2528. <div class="pure-u-1 pure-u-lg-1-5">
  2529. <select class="pure-u-1 pure-u-lg-23-24" name="schAction">
  2530. <option value="0">Turn OFF</option>
  2531. <option value="1">Turn ON</option>
  2532. <option value="2">Toggle</option>
  2533. </select>
  2534. </div>
  2535. <select class="pure-u-1 pure-u-lg-1-5 isrelay" name="schSwitch"></select>
  2536. <input type="hidden" name="schType" value="1">
  2537. </div>
  2538. <div id="lightActionTemplate" class="template">
  2539. <label class="pure-u-1 pure-u-lg-1-4">Brightness</label>
  2540. <div class="pure-u-1 pure-u-lg-1-5">
  2541. <input class="pure-u-2-3" name="schAction" type="number" min="0" step="1" max="255" value="0">
  2542. </div>
  2543. <select class="pure-u-1 pure-u-lg-1-5 islight" name="schSwitch"></select>
  2544. <input type="hidden" name="schType" value="2">
  2545. </div>
  2546. <div id="relayTemplate" class="template">
  2547. <div class="pure-g">
  2548. <label class="pure-u-1 pure-u-lg-1-4">Switch #<span class="id"></span></label>
  2549. <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" class="relayStatus pure-u-1 pure-u-lg-1-4" data="0"></div>
  2550. </div>
  2551. </div>
  2552. <div id="relayConfigTemplate" class="template">
  2553. <legend>Switch #<span class="id"></span> (GPIO<span class="gpio"></span>)</legend>
  2554. <div class="pure-g">
  2555. <div class="pure-u-1 pure-u-lg-1-4"><label>Boot mode</label></div>
  2556. <select class="pure-u-1 pure-u-lg-3-4" name="relayBoot">
  2557. <option value="0">Always OFF</option>
  2558. <option value="1">Always ON</option>
  2559. <option value="2">Same as before</option>
  2560. <option value="3">Toggle before</option>
  2561. </select>
  2562. </div>
  2563. <div class="pure-g">
  2564. <div class="pure-u-1 pure-u-lg-1-4"><label>Pulse mode</label></div>
  2565. <select class="pure-u-1 pure-u-lg-3-4" name="relayPulse">
  2566. <option value="0">Don't pulse</option>
  2567. <option value="1">Normally OFF</option>
  2568. <option value="2">Normally ON</option>
  2569. </select>
  2570. </div>
  2571. <div class="pure-g">
  2572. <div class="pure-u-1 pure-u-lg-1-4"><label>Pulse time (s)</label></div>
  2573. <div class="pure-u-1 pure-u-lg-1-4"><input name="relayTime" class="pure-u-1" type="number" min="0" step="0.1" max="3600"></div>
  2574. </div>
  2575. <div class="pure-g module module-mqtt">
  2576. <div class="pure-u-1 pure-u-lg-1-4"><label>MQTT group</label></div>
  2577. <div class="pure-u-1 pure-u-lg-3-4"><input name="mqttGroup" class="pure-u-1" tabindex="0" data="0" action="reconnect"></div>
  2578. </div>
  2579. <div class="pure-g module module-mqtt">
  2580. <div class="pure-u-1 pure-u-lg-1-4"><label>MQTT group sync</label></div>
  2581. <select class="pure-u-1 pure-u-lg-3-4" name="mqttGroupInv">
  2582. <option value="0">Same</option>
  2583. <option value="1">Inverse</option>
  2584. </select>
  2585. </div>
  2586. <div class="pure-g module module-mqtt">
  2587. <div class="pure-u-1 pure-u-lg-1-4"><label>On MQTT disconnect</label></div>
  2588. <select class="pure-u-1 pure-u-lg-3-4" name="relayOnDisc">
  2589. <option value="0">Don't change</option>
  2590. <option value="1">Turn the switch OFF</option>
  2591. <option value="2">Turn the switch ON</option>
  2592. </select>
  2593. </div>
  2594. </div>
  2595. <div id="dczRelayTemplate" class="template">
  2596. <div class="pure-g">
  2597. <label class="pure-u-1 pure-u-lg-1-4">Switch</label>
  2598. <div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-1 pure-u-lg-23-24 dczRelayIdx" name="dczRelayIdx" type="number" min="0" tabindex="0" data="0"></div>
  2599. </div>
  2600. </div>
  2601. <div id="dczMagnitudeTemplate" class="template">
  2602. <div class="pure-g">
  2603. <label class="pure-u-1 pure-u-lg-1-4">Magnitude</label>
  2604. <div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-1 pure-u-lg-23-24 center" name="dczMagnitude" type="number" min="0" tabindex="0" data="0"></div>
  2605. <div class="pure-u-1 pure-u-lg-1-2 hint center"></div>
  2606. </div>
  2607. </div>
  2608. <div id="tspkRelayTemplate" class="template">
  2609. <div class="pure-g">
  2610. <label class="pure-u-1 pure-u-lg-1-4">Switch</label>
  2611. <div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-1 pure-u-lg-23-24" name="tspkRelay" type="number" min="0" max="8" tabindex="0" data="0"></div>
  2612. </div>
  2613. </div>
  2614. <div id="tspkMagnitudeTemplate" class="template">
  2615. <div class="pure-g">
  2616. <label class="pure-u-1 pure-u-lg-1-4">Magnitude</label>
  2617. <div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-1 pure-u-lg-23-24 center" name="tspkMagnitude" type="number" min="0" max="8" tabindex="0" data="0"></div>
  2618. <div class="pure-u-1 pure-u-lg-1-2 hint center"></div>
  2619. </div>
  2620. </div>
  2621. <div id="colorRGBTemplate" class="template">
  2622. <div class="pure-g">
  2623. <label class="pure-u-1 pure-u-lg-1-4">Color</label>
  2624. <input class="pure-u-1 pure-u-lg-1-4" data-wcp-layout="block" name="color" readonly>
  2625. </div>
  2626. <div class="pure-g">
  2627. <label class="pure-u-1 pure-u-lg-1-4">Brightness</label>
  2628. <input type="range" min="0" max="255" class="slider pure-u-lg-1-4" id="brightness">
  2629. <span class="slider brightness pure-u-lg-1-4"></span>
  2630. </div>
  2631. </div>
  2632. <div id="colorHSVTemplate" class="template">
  2633. <div class="pure-g">
  2634. <label class="pure-u-1 pure-u-lg-1-4">Color</label>
  2635. <input class="pure-u-1 pure-u-lg-1-4" data-wcp-layout="block" name="color" readonly>
  2636. </div>
  2637. </div>
  2638. <div id="channelTemplate" class="template">
  2639. <div class="pure-g">
  2640. <label class="pure-u-1 pure-u-lg-1-4">Channel #</label>
  2641. <input type="range" min="0" max="255" class="slider channels pure-u-lg-1-4" data="99">
  2642. <span class="slider pure-u-lg-1-4"></span>
  2643. </div>
  2644. </div>
  2645. <div id="miredsTemplate" class="template">
  2646. <div class="pure-g">
  2647. <label class="pure-u-1 pure-u-lg-1-4">Mireds (Cold &harr; Warm)</label>
  2648. <input type="range" min="153" max="500" class="slider pure-u-lg-1-4" id="mireds">
  2649. <span class="slider mireds pure-u-lg-1-4"></span>
  2650. </div>
  2651. </div>
  2652. <div id="magnitudeTemplate" class="template">
  2653. <div class="pure-g">
  2654. <label class="pure-u-1 pure-u-lg-1-4"></label>
  2655. <div class="pure-u-1 pure-u-lg-1-4">
  2656. <input class="pure-u-1 pure-u-lg-23-24 center" type="text" name="magnitude" data="256" readonly>
  2657. </div>
  2658. <div class="pure-u-1 pure-u-lg-1-2 hint center"></div>
  2659. </div>
  2660. </div>
  2661. <iframe id="downloader"></iframe>
  2662. <input id="uploader" type="file">
  2663. </body>
  2664. <!-- build:js script.js -->
  2665. <script>
  2666. /*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */
  2667. !function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c<b?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:h,sort:c.sort,splice:c.splice},r.extend=r.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||r.isFunction(g)||(g={}),h===i&&(g=this,h--);h<i;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(r.isPlainObject(d)||(e=Array.isArray(d)))?(e?(e=!1,f=c&&Array.isArray(c)?c:[]):f=c&&r.isPlainObject(c)?c:{},g[b]=r.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},r.extend({expando:"jQuery"+(q+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===r.type(a)},isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=r.type(a);return("number"===b||"string"===b)&&!isNaN(a-parseFloat(a))},isPlainObject:function(a){var b,c;return!(!a||"[object Object]"!==k.call(a))&&(!(b=e(a))||(c=l.call(b,"constructor")&&b.constructor,"function"==typeof c&&m.call(c)===n))},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?j[k.call(a)]||"object":typeof a},globalEval:function(a){p(a)},camelCase:function(a){return a.replace(t,"ms-").replace(u,v)},each:function(a,b){var c,d=0;if(w(a)){for(c=a.length;d<c;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(s,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(w(Object(a))?r.merge(c,"string"==typeof a?[a]:a):h.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:i.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;f<g;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,f=0,h=[];if(w(a))for(d=a.length;f<d;f++)e=b(a[f],f,c),null!=e&&h.push(e);else for(f in a)e=b(a[f],f,c),null!=e&&h.push(e);return g.apply([],h)},guid:1,proxy:function(a,b){var c,d,e;if("string"==typeof b&&(c=a[b],b=a,a=c),r.isFunction(a))return d=f.call(arguments,2),e=function(){return a.apply(b||this,d.concat(f.call(arguments)))},e.guid=a.guid=a.guid||r.guid++,e},now:Date.now,support:o}),"function"==typeof Symbol&&(r.fn[Symbol.iterator]=c[Symbol.iterator]),r.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){j["[object "+b+"]"]=b.toLowerCase()});function w(a){var b=!!a&&"length"in a&&a.length,c=r.type(a);return"function"!==c&&!r.isWindow(a)&&("array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*
  2668. a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h<i;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},U=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function V(){this.expando=r.expando+V.uid++}V.uid=1,V.prototype={cache:function(a){var b=a[this.expando];return b||(b={},U(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[r.camelCase(b)]=c;else for(d in b)e[r.camelCase(d)]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][r.camelCase(b)]},access:function(a,b,c){return void 0===b||b&&"string"==typeof b&&void 0===c?this.get(a,b):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d=a[this.expando];if(void 0!==d){if(void 0!==b){Array.isArray(b)?b=b.map(r.camelCase):(b=r.camelCase(b),b=b in d?[b]:b.match(L)||[]),c=b.length;while(c--)delete d[b[c]]}(void 0===b||r.isEmptyObject(d))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!r.isEmptyObject(b)}};var W=new V,X=new V,Y=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Z=/[A-Z]/g;function $(a){return"true"===a||"false"!==a&&("null"===a?null:a===+a+""?+a:Y.test(a)?JSON.parse(a):a)}function _(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Z,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c=$(c)}catch(e){}X.set(a,b,c)}else c=void 0;return c}r.extend({hasData:function(a){return X.hasData(a)||W.hasData(a)},data:function(a,b,c){return X.access(a,b,c)},removeData:function(a,b){X.remove(a,b)},_data:function(a,b,c){return W.access(a,b,c)},_removeData:function(a,b){W.remove(a,b)}}),r.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=X.get(f),1===f.nodeType&&!W.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=r.camelCase(d.slice(5)),_(f,d,e[d])));W.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){X.set(this,a)}):T(this,function(b){var c;if(f&&void 0===b){if(c=X.get(f,a),void 0!==c)return c;if(c=_(f,a),void 0!==c)return c}else this.each(function(){X.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?r.queue(this[0],a):void 0===b?this:this.each(function(){var c=r.queue(this,a,b);r._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&r.dequeue(this,a)})},dequeue:function(a){return this.each(function(){r.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=r.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=W.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var aa=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,ba=new RegExp("^(?:([+-])=|)("+aa+")
  2669. null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d<i;d++)if(c=e[d],(c.selected||d===f)&&!c.
  2670. </script>
  2671. <script>
  2672. // Generated by CoffeeScript 1.6.2
  2673. /*eslint quotes: ["error", "double"]*/
  2674. /*eslint-env es6*/
  2675. (function() {
  2676. var iOSCheckbox, matched, userAgent,
  2677. __slice = [].slice;
  2678. if ($.browser == null) {
  2679. userAgent = navigator.userAgent || "";
  2680. jQuery.uaMatch = function(ua) {
  2681. var match;
  2682. ua = ua.toLowerCase();
  2683. match = /(chrome)[ \/]([\w.]+)/.exec(ua) || /(webkit)[ \/]([\w.]+)/.exec(ua) || /(opera)(?:.*version)?[ \/]([\w.]+)/.exec(ua) || /(msie) ([\w.]+)/.exec(ua) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+))?/.exec(ua) || [];
  2684. return {
  2685. browser: match[1] || "",
  2686. version: match[2] || "0"
  2687. };
  2688. };
  2689. matched = jQuery.uaMatch(userAgent);
  2690. jQuery.browser = {};
  2691. if (matched.browser) {
  2692. jQuery.browser[matched.browser] = true;
  2693. jQuery.browser.version = matched.version;
  2694. }
  2695. if (jQuery.browser.webkit) {
  2696. jQuery.browser.safari = true;
  2697. }
  2698. }
  2699. iOSCheckbox = (function() {
  2700. function iOSCheckbox(elem, options) {
  2701. var key, opts, value;
  2702. this.elem = $(elem);
  2703. opts = $.extend({}, iOSCheckbox.defaults, options);
  2704. for (key in opts) {
  2705. if ({}.hasOwnProperty.call(opts, key)) {
  2706. value = opts[key];
  2707. this[key] = value;
  2708. }
  2709. }
  2710. this.elem.data(this.dataName, this);
  2711. this.wrapCheckboxWithDivs();
  2712. this.attachEvents();
  2713. this.disableTextSelection();
  2714. this.calculateDimensions();
  2715. }
  2716. iOSCheckbox.prototype.calculateDimensions = function() {
  2717. if (this.resizeHandle) {
  2718. this.optionallyResize("handle");
  2719. }
  2720. if (this.resizeContainer) {
  2721. this.optionallyResize("container");
  2722. }
  2723. return this.initialPosition();
  2724. };
  2725. iOSCheckbox.prototype.isDisabled = function() {
  2726. return this.elem.is(":disabled");
  2727. };
  2728. iOSCheckbox.prototype.wrapCheckboxWithDivs = function() {
  2729. this.elem.wrap("<div class='" + this.containerClass + "' />");
  2730. this.container = this.elem.parent();
  2731. this.offLabel = $("<label class='" + this.labelOffClass + "'>\n <span>" + this.uncheckedLabel + "</span>\n</label>").appendTo(this.container);
  2732. this.offSpan = this.offLabel.children("span");
  2733. this.onLabel = $("<label class='" + this.labelOnClass + "'>\n <span>" + this.checkedLabel + "</span>\n</label>").appendTo(this.container);
  2734. this.onBorder = $("<div class='iPhoneCheckBorderOn'</div>").appendTo(this.container);
  2735. this.offBorder = $("<div class='iPhoneCheckBorderOff'</div>").appendTo(this.container);
  2736. this.onSpan = this.onLabel.children("span");
  2737. this.handle = $("<div class='" + this.handleClass + "'></div>").appendTo(this.container);
  2738. this.handleCenter = $("<div class='" + this.handleCenterClass + "'></div>").appendTo(this.handle);
  2739. this.handleRight = $("<div class='" + this.handleRightClass + "'></div>").appendTo(this.handle);
  2740. return true;
  2741. };
  2742. iOSCheckbox.prototype.disableTextSelection = function() {
  2743. if ($.browser.msie) {
  2744. return $([this.handle, this.offLabel, this.onLabel, this.container]).attr("unselectable", "on");
  2745. }
  2746. };
  2747. iOSCheckbox.prototype._getDimension = function(elem, dimension) {
  2748. if ($.fn.actual != null) {
  2749. return elem.actual(dimension);
  2750. } else {
  2751. return elem[dimension]();
  2752. }
  2753. };
  2754. iOSCheckbox.prototype.optionallyResize = function(mode) {
  2755. var newWidth, offLabelWidth, offSpan, onLabelWidth, onSpan;
  2756. onSpan = this.onLabel.find("span");
  2757. onLabelWidth = this._getDimension(onSpan, "width");
  2758. onLabelWidth += parseInt(onSpan.css("padding-left"), 10);
  2759. offSpan = this.offLabel.find("span");
  2760. offLabelWidth = this._getDimension(offSpan, "width");
  2761. offLabelWidth += parseInt(offSpan.css("padding-right"), 10);
  2762. if (mode === "container") {
  2763. newWidth = onLabelWidth > offLabelWidth ? onLabelWidth : offLabelWidth;
  2764. newWidth += this._getDimension(this.handle, "width") + this.handleMargin;
  2765. return this.container.css({
  2766. width: newWidth
  2767. });
  2768. } else {
  2769. newWidth = onLabelWidth > offLabelWidth ? onLabelWidth : offLabelWidth;
  2770. this.handleCenter.css({
  2771. width: newWidth + 4
  2772. });
  2773. return this.handle.css({
  2774. width: newWidth + 7
  2775. });
  2776. }
  2777. };
  2778. iOSCheckbox.prototype.onMouseDown = function(event) {
  2779. var x;
  2780. event.preventDefault();
  2781. if (this.isDisabled()) {
  2782. return;
  2783. }
  2784. x = event.pageX || event.originalEvent.changedTouches[0].pageX;
  2785. iOSCheckbox.currentlyClicking = this.handle;
  2786. iOSCheckbox.dragStartPosition = x;
  2787. return iOSCheckbox.handleLeftOffset = parseInt(this.handle.css("left"), 10) || 0;
  2788. };
  2789. iOSCheckbox.prototype.onDragMove = function(event, x) {
  2790. var newWidth, p;
  2791. if (iOSCheckbox.currentlyClicking !== this.handle) {
  2792. return;
  2793. }
  2794. p = (x + iOSCheckbox.handleLeftOffset - iOSCheckbox.dragStartPosition) / this.rightSide;
  2795. if (p < 0) {
  2796. p = 0;
  2797. }
  2798. if (p > 1) {
  2799. p = 1;
  2800. }
  2801. newWidth = p * this.rightSide;
  2802. this.handle.css({
  2803. left: newWidth
  2804. });
  2805. this.onLabel.css({
  2806. width: newWidth + this.handleRadius
  2807. });
  2808. this.offSpan.css({
  2809. marginRight: -newWidth
  2810. });
  2811. return this.onSpan.css({
  2812. marginLeft: -(1 - p) * this.rightSide
  2813. });
  2814. };
  2815. iOSCheckbox.prototype.onDragEnd = function(event, x) {
  2816. var p;
  2817. if (iOSCheckbox.currentlyClicking !== this.handle) {
  2818. return;
  2819. }
  2820. if (this.isDisabled()) {
  2821. return;
  2822. }
  2823. if (iOSCheckbox.dragging) {
  2824. p = (x - iOSCheckbox.dragStartPosition) / this.rightSide;
  2825. this.elem.prop("checked", p >= 0.5).change();
  2826. } else {
  2827. this.elem.prop("checked", !this.elem.prop("checked")).change();
  2828. }
  2829. iOSCheckbox.currentlyClicking = null;
  2830. iOSCheckbox.dragging = null;
  2831. if (typeof this.onChange === "function") {
  2832. this.onChange(this.elem, this.elem.prop("checked"));
  2833. }
  2834. return this.didChange();
  2835. };
  2836. iOSCheckbox.prototype.refresh = function() {
  2837. return this.didChange();
  2838. };
  2839. iOSCheckbox.prototype.didChange = function() {
  2840. var newLeft;
  2841. if (this.isDisabled()) {
  2842. this.container.addClass(this.disabledClass);
  2843. return false;
  2844. } else {
  2845. this.container.removeClass(this.disabledClass);
  2846. }
  2847. newLeft = this.elem.prop("checked") ? this.rightSide + 2 : 0;
  2848. this.handle.animate({
  2849. left: newLeft
  2850. }, this.duration);
  2851. this.onLabel.animate({
  2852. width: newLeft + this.handleRadius
  2853. }, this.duration);
  2854. this.offSpan.animate({
  2855. marginRight: - newLeft
  2856. }, this.duration);
  2857. return this.onSpan.animate({
  2858. marginLeft: newLeft - this.rightSide
  2859. }, this.duration);
  2860. };
  2861. iOSCheckbox.prototype.attachEvents = function() {
  2862. var localMouseMove, localMouseUp, self;
  2863. self = this;
  2864. localMouseMove = function(event) {
  2865. return self.onGlobalMove.apply(self, arguments);
  2866. };
  2867. localMouseUp = function(event) {
  2868. self.onGlobalUp.apply(self, arguments);
  2869. $(document).unbind("mousemove touchmove", localMouseMove);
  2870. return $(document).unbind("mouseup touchend", localMouseUp);
  2871. };
  2872. this.elem.change(function() {
  2873. return self.refresh();
  2874. });
  2875. return this.container.bind("mousedown touchstart", function(event) {
  2876. self.onMouseDown.apply(self, arguments);
  2877. $(document).bind("mousemove touchmove", localMouseMove);
  2878. return $(document).bind("mouseup touchend", localMouseUp);
  2879. });
  2880. };
  2881. iOSCheckbox.prototype.initialPosition = function() {
  2882. var containerWidth, offset;
  2883. containerWidth = this._getDimension(this.container, "width");
  2884. this.offLabel.css({
  2885. width: containerWidth - this.containerRadius - 4
  2886. });
  2887. this.offBorder.css({
  2888. left: containerWidth - 4
  2889. });
  2890. offset = this.containerRadius + 1;
  2891. if ($.browser.msie && $.browser.version < 7) {
  2892. offset -= 3;
  2893. }
  2894. this.rightSide = containerWidth - this._getDimension(this.handle, "width") - offset;
  2895. if (this.elem.is(":checked")) {
  2896. this.handle.css({
  2897. left: this.rightSide
  2898. });
  2899. this.onLabel.css({
  2900. width: this.rightSide + this.handleRadius
  2901. });
  2902. this.offSpan.css({
  2903. marginRight: -this.rightSide,
  2904. });
  2905. } else {
  2906. this.onLabel.css({
  2907. width: 0
  2908. });
  2909. this.onSpan.css({
  2910. marginLeft: -this.rightSide
  2911. });
  2912. }
  2913. if (this.isDisabled()) {
  2914. return this.container.addClass(this.disabledClass);
  2915. }
  2916. };
  2917. iOSCheckbox.prototype.onGlobalMove = function(event) {
  2918. var x;
  2919. if (!(!this.isDisabled() && iOSCheckbox.currentlyClicking)) {
  2920. return;
  2921. }
  2922. event.preventDefault();
  2923. x = event.pageX || event.originalEvent.changedTouches[0].pageX;
  2924. if (!iOSCheckbox.dragging && (Math.abs(iOSCheckbox.dragStartPosition - x) > this.dragThreshold)) {
  2925. iOSCheckbox.dragging = true;
  2926. }
  2927. return this.onDragMove(event, x);
  2928. };
  2929. iOSCheckbox.prototype.onGlobalUp = function(event) {
  2930. var x;
  2931. if (!iOSCheckbox.currentlyClicking) {
  2932. return;
  2933. }
  2934. event.preventDefault();
  2935. x = event.pageX || event.originalEvent.changedTouches[0].pageX;
  2936. this.onDragEnd(event, x);
  2937. return false;
  2938. };
  2939. iOSCheckbox.defaults = {
  2940. duration: 200,
  2941. checkedLabel: "ON",
  2942. uncheckedLabel: "OFF",
  2943. resizeHandle: true,
  2944. resizeContainer: true,
  2945. disabledClass: "iPhoneCheckDisabled",
  2946. containerClass: "iPhoneCheckContainer",
  2947. labelOnClass: "iPhoneCheckLabelOn",
  2948. labelOffClass: "iPhoneCheckLabelOff",
  2949. handleClass: "iPhoneCheckHandle",
  2950. handleCenterClass: "iPhoneCheckHandleCenter",
  2951. handleRightClass: "iPhoneCheckHandleRight",
  2952. dragThreshold: 5,
  2953. handleMargin: 15,
  2954. handleRadius: 4,
  2955. containerRadius: 5,
  2956. dataName: "iphoneStyle",
  2957. onChange: function() {}
  2958. };
  2959. return iOSCheckbox;
  2960. })();
  2961. $.iphoneStyle = this.iOSCheckbox = iOSCheckbox;
  2962. $.fn.iphoneStyle = function() {
  2963. var args, checkbox, dataName, existingControl, method, params, _i, _len, _ref, _ref1, _ref2, _ref3;
  2964. args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
  2965. dataName = (_ref = (_ref1 = args[0]) != null ? _ref1.dataName : void 0) != null ? _ref : iOSCheckbox.defaults.dataName;
  2966. _ref2 = this.filter(":checkbox");
  2967. for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
  2968. checkbox = _ref2[_i];
  2969. existingControl = $(checkbox).data(dataName);
  2970. if (existingControl != null) {
  2971. method = args[0], params = 2 <= args.length ? __slice.call(args, 1) : [];
  2972. if ((_ref3 = existingControl[method]) != null) {
  2973. _ref3.apply(existingControl, params);
  2974. }
  2975. } else {
  2976. new iOSCheckbox(checkbox, args[0]);
  2977. }
  2978. }
  2979. return this;
  2980. };
  2981. $.fn.iOSCheckbox = function(options) {
  2982. var opts;
  2983. if (options == null) {
  2984. options = {};
  2985. }
  2986. opts = $.extend({}, options, {
  2987. resizeHandle: false,
  2988. disabledClass: "iOSCheckDisabled",
  2989. containerClass: "iOSCheckContainer",
  2990. labelOnClass: "iOSCheckLabelOn",
  2991. labelOffClass: "iOSCheckLabelOff",
  2992. handleClass: "iOSCheckHandle",
  2993. handleCenterClass: "iOSCheckHandleCenter",
  2994. handleRightClass: "iOSCheckHandleRight",
  2995. dataName: "iOSCheckbox"
  2996. });
  2997. return this.iphoneStyle(opts);
  2998. };
  2999. }).call(this);
  3000. </script>
  3001. <script>
  3002. /**
  3003. * jQuery Wheel Color Picker v3.0.3
  3004. *
  3005. * http://www.jar2.net/projects/jquery-wheelcolorpicker
  3006. *
  3007. * Author : Fajar Chandra
  3008. * Date : 2017.09.03
  3009. *
  3010. * Copyright © 2011-2017 Fajar Chandra. All rights reserved.
  3011. * Released under MIT License.
  3012. * http://www.opensource.org/licenses/mit-license.php
  3013. */
  3014. !function(a){a.fn.wheelColorPicker=function(){var c=this;if(arguments.length>0)var d=[].shift,e=d.apply(arguments),f="string"==typeof e?e.charAt(0).toUpperCase()+e.slice(1):e;else var e=void 0,f=void 0;var g=arguments;return this.each(function(){var d=a(this).data("jQWCP.instance");if(void 0==d||null==d){var h={};"object"==typeof e&&(h=e),d=new b.ColorPicker(this,h),a(this).data("jQWCP.instance",d)}if(void 0===e||"object"==typeof e);else if("function"==typeof d[e]){var i=d[e].apply(d,g);if(i!==d)return c=i,!1}else if("function"==typeof d["set"+f]&&g.length>0){var i=d["set"+f].apply(d,g);if(i!==d)return c=i,!1}else if("function"==typeof d["get"+f]){var i=d["get"+f].apply(d,g);if(i!==d)return c=i,!1}else if(void 0!==d.options[e]&&g.length>0)d.options[e]=g[0];else{if(void 0!==d.options[e])return c=d.options[e],!1;a.error("Method/option named "+e+" does not exist on jQuery.wheelColorPicker")}}),c};var b=a.fn.wheelColorPicker;b.defaults={format:"hex",preview:!1,live:!0,userinput:!0,validate:!0,autoResize:!0,autoFormat:!0,preserveWheel:null,cssClass:"",layout:"popup",animDuration:200,quality:1,sliders:null,rounding:2,mobile:!0,mobileWidth:480,hideKeyboard:!1,htmlOptions:!0,snap:!1,snapTolerance:.05},b.BUG_RELATIVE_PAGE_ORIGIN=!1,b.ORIGIN={left:0,top:0},b.colorToStr=function(a,b){var c="";switch(b){case"css":c="#";case"hex":var d=Math.round(255*a.r).toString(16);1==d.length&&(d="0"+d);var e=Math.round(255*a.g).toString(16);1==e.length&&(e="0"+e);var f=Math.round(255*a.b).toString(16);1==f.length&&(f="0"+f),c+=d+e+f;break;case"cssa":c="#";case"hexa":var d=Math.round(255*a.r).toString(16);1==d.length&&(d="0"+d);var e=Math.round(255*a.g).toString(16);1==e.length&&(e="0"+e);var f=Math.round(255*a.b).toString(16);1==f.length&&(f="0"+f);var g=Math.round(255*a.a).toString(16);1==g.length&&(g="0"+g),c+=d+e+f+g;break;case"rgb":c="rgb("+Math.round(255*a.r)+","+Math.round(255*a.g)+","+Math.round(255*a.b)+")";break;case"rgb%":c="rgb("+100*a.r+"%,"+100*a.g+"%,"+100*a.b+"%)";break;case"rgba":c="rgba("+Math.round(255*a.r)+","+Math.round(255*a.g)+","+Math.round(255*a.b)+","+a.a+")";break;case"rgba%":c="rgba("+100*a.r+"%,"+100*a.g+"%,"+100*a.b+"%,"+100*a.a+"%)";break;case"hsv":c="hsv("+360*a.h+","+a.s+","+a.v+")";break;case"hsv%":c="hsv("+100*a.h+"%,"+100*a.s+"%,"+100*a.v+"%)";break;case"hsva":c="hsva("+360*a.h+","+a.s+","+a.v+","+a.a+")";break;case"hsva%":c="hsva("+100*a.h+"%,"+100*a.s+"%,"+100*a.v+"%,"+100*a.a+"%)";break;case"hsb":c="hsb("+a.h+","+a.s+","+a.v+")";break;case"hsb%":c="hsb("+100*a.h+"%,"+100*a.s+"%,"+100*a.v+"%)";break;case"hsba":c="hsba("+a.h+","+a.s+","+a.v+","+a.a+")";break;case"hsba%":c="hsba("+100*a.h+"%,"+100*a.s+"%,"+100*a.v+"%,"+100*a.a+"%)"}return c},b.strToColor=function(a){var c,d,b={a:1};if(null!=a.match(/^#[0-9a-f]{3}$/i)||a.match(/^#[0-9a-f]{4}$/i)){if(isNaN(b.r=17*parseInt(a.substr(1,1),16)/255))return!1;if(isNaN(b.g=17*parseInt(a.substr(2,1),16)/255))return!1;if(isNaN(b.b=17*parseInt(a.substr(3,1),16)/255))return!1;if(5==a.length&&isNaN(b.a=17*parseInt(a.substr(4,1),16)/255))return!1}else if(null!=a.match(/^[0-9a-f]{3}$/i)||null!=a.match(/^[0-9a-f]{4}$/i)){if(isNaN(b.r=17*parseInt(a.substr(0,1),16)/255))return!1;if(isNaN(b.g=17*parseInt(a.substr(1,1),16)/255))return!1;if(isNaN(b.b=17*parseInt(a.substr(2,1),16)/255))return!1;if(4==a.length&&isNaN(b.a=17*parseInt(a.substr(3,1),16)/255))return!1}else if(null!=a.match(/^#[0-9a-f]{6}$/i)||null!=a.match(/^#[0-9a-f]{8}$/i)){if(isNaN(b.r=parseInt(a.substr(1,2),16)/255))return!1;if(isNaN(b.g=parseInt(a.substr(3,2),16)/255))return!1;if(isNaN(b.b=parseInt(a.substr(5,2),16)/255))return!1;if(9==a.length&&isNaN(b.a=parseInt(a.substr(7,2),16)/255))return!1}else if(null!=a.match(/^[0-9a-f]{6}$/i)||null!=a.match(/^[0-9a-f]{8}$/i)){if(isNaN(b.r=parseInt(a.substr(0,2),16)/255))return!1;if(isNaN(b.g=parseInt(a.substr(2,2),16)/255))return!1;if(isNaN(b.b=parseInt(a.substr(4,2),16)/255))return!1;if(8==a.length&&isNaN(b.a=parseInt(a.substr(6,2),16)/255))return!1}else if(null!=a.match(/^rgba\s*\(\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9]*)\s*,\s*([0-9\.]+%|[01]?\.?[0-9
  3015. </script>
  3016. <script>
  3017. var websock;
  3018. var password = false;
  3019. var maxNetworks;
  3020. var maxSchedules;
  3021. var messages = [];
  3022. var free_size = 0;
  3023. var urls = {};
  3024. var numChanged = 0;
  3025. var numReboot = 0;
  3026. var numReconnect = 0;
  3027. var numReload = 0;
  3028. var useWhite = false;
  3029. var useCCT = false;
  3030. var now = 0;
  3031. var ago = 0;
  3032. // -----------------------------------------------------------------------------
  3033. // Messages
  3034. // -----------------------------------------------------------------------------
  3035. function initMessages() {
  3036. messages[1] = "Remote update started";
  3037. messages[2] = "OTA update started";
  3038. messages[3] = "Error parsing data!";
  3039. messages[4] = "The file does not look like a valid configuration backup or is corrupted";
  3040. messages[5] = "Changes saved. You should reboot your board now";
  3041. messages[7] = "Passwords do not match!";
  3042. messages[8] = "Changes saved";
  3043. messages[9] = "No changes detected";
  3044. messages[10] = "Session expired, please reload page...";
  3045. }
  3046. // -----------------------------------------------------------------------------
  3047. // Utils
  3048. // -----------------------------------------------------------------------------
  3049. $.fn.enterKey = function (fnc) {
  3050. return this.each(function () {
  3051. $(this).keypress(function (ev) {
  3052. var keycode = parseInt(ev.keyCode ? ev.keyCode : ev.which, 10);
  3053. if (13 === keycode) {
  3054. return fnc.call(this, ev);
  3055. }
  3056. });
  3057. });
  3058. };
  3059. function keepTime() {
  3060. $("span[name='ago']").html(ago);
  3061. ago++;
  3062. if (0 === now) { return; }
  3063. var date = new Date(now * 1000);
  3064. var text = date.toISOString().substring(0, 19).replace("T", " ");
  3065. $("input[name='now']").val(text);
  3066. $("span[name='now']").html(text);
  3067. now++;
  3068. }
  3069. function zeroPad(number, positions) {
  3070. var zeros = "";
  3071. for (var i = 0; i < positions; i++) {
  3072. zeros += "0";
  3073. }
  3074. return (zeros + number).slice(-positions);
  3075. }
  3076. function loadTimeZones() {
  3077. var time_zones = [
  3078. -720, -660, -600, -570, -540,
  3079. -480, -420, -360, -300, -240,
  3080. -210, -180, -120, -60, 0,
  3081. 60, 120, 180, 210, 240,
  3082. 270, 300, 330, 345, 360,
  3083. 390, 420, 480, 510, 525,
  3084. 540, 570, 600, 630, 660,
  3085. 720, 765, 780, 840
  3086. ];
  3087. for (var i in time_zones) {
  3088. var value = time_zones[i];
  3089. var offset = value >= 0 ? value : -value;
  3090. var text = "GMT" + (value >= 0 ? "+" : "-") +
  3091. zeroPad(parseInt(offset / 60, 10), 2) + ":" +
  3092. zeroPad(offset % 60, 2);
  3093. $("select[name='ntpOffset']").append(
  3094. $("<option></option>").
  3095. attr("value",value).
  3096. text(text));
  3097. }
  3098. }
  3099. function validateForm(form) {
  3100. // http://www.the-art-of-web.com/javascript/validate-password/
  3101. // at least one lowercase and one uppercase letter or number
  3102. // at least five characters (letters, numbers or special characters)
  3103. var re_password = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{5,}$/;
  3104. // password
  3105. var adminPass1 = $("input[name='adminPass']", form).first().val();
  3106. if (adminPass1.length > 0 && !re_password.test(adminPass1)) {
  3107. alert("The password you have entered is not valid, it must have at least 5 characters, 1 lowercase and 1 uppercase or number!");
  3108. return false;
  3109. }
  3110. var adminPass2 = $("input[name='adminPass']", form).last().val();
  3111. if (adminPass1 !== adminPass2) {
  3112. alert("Passwords are different!");
  3113. return false;
  3114. }
  3115. // RFCs mandate that a hostname's labels may contain only
  3116. // the ASCII letters 'a' through 'z' (case-insensitive),
  3117. // the digits '0' through '9', and the hyphen.
  3118. // Hostname labels cannot begin or end with a hyphen.
  3119. // No other symbols, punctuation characters, or blank spaces are permitted.
  3120. // Negative lookbehind does not work in Javascript
  3121. // var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{1,32}(?<!-)$');
  3122. var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{0,31}[A-Za-z0-9]$');
  3123. var hostname = $("input[name='hostname']", form).val();
  3124. if (!re_hostname.test(hostname)) {
  3125. alert("Hostname cannot be empty and may only contain the ASCII letters ('A' through 'Z' and 'a' through 'z'), the digits '0' through '9', and the hyphen ('-')! They can neither start or end with an hyphen.");
  3126. return false;
  3127. }
  3128. return true;
  3129. }
  3130. function getValue(element) {
  3131. if ($(element).attr("type") === "checkbox") {
  3132. return $(element).prop("checked") ? 1 : 0;
  3133. } else if ($(element).attr("type") === "radio") {
  3134. if (!$(element).prop("checked")) {
  3135. return null;
  3136. }
  3137. }
  3138. return $(element).val();
  3139. }
  3140. function addValue(data, name, value) {
  3141. // These fields will always be a list of values
  3142. var is_group = [
  3143. "ssid", "pass", "gw", "mask", "ip", "dns",
  3144. "schEnabled", "schSwitch","schAction","schType","schHour","schMinute","schWDs","schUTC",
  3145. "relayBoot", "relayPulse", "relayTime",
  3146. "mqttGroup", "mqttGroupInv", "relayOnDisc",
  3147. "dczRelayIdx", "dczMagnitude",
  3148. "tspkRelay", "tspkMagnitude",
  3149. "ledMode",
  3150. "adminPass"
  3151. ];
  3152. if (name in data) {
  3153. if (!Array.isArray(data[name])) {
  3154. data[name] = [data[name]];
  3155. }
  3156. data[name].push(value);
  3157. } else if (is_group.indexOf(name) >= 0) {
  3158. data[name] = [value];
  3159. } else {
  3160. data[name] = value;
  3161. }
  3162. }
  3163. function getData(form) {
  3164. var data = {};
  3165. // Populate data
  3166. $("input,select", form).each(function() {
  3167. var name = $(this).attr("name");
  3168. var value = getValue(this);
  3169. if (null !== value) {
  3170. addValue(data, name, value);
  3171. }
  3172. });
  3173. // Post process
  3174. addValue(data, "schSwitch", 0xFF);
  3175. delete data["filename"];
  3176. delete data["rfbcode"];
  3177. return data;
  3178. }
  3179. function randomString(length, chars) {
  3180. var mask = "";
  3181. if (chars.indexOf("a") > -1) { mask += "abcdefghijklmnopqrstuvwxyz"; }
  3182. if (chars.indexOf("A") > -1) { mask += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; }
  3183. if (chars.indexOf("#") > -1) { mask += "0123456789"; }
  3184. if (chars.indexOf("@") > -1) { mask += "ABCDEF"; }
  3185. if (chars.indexOf("!") > -1) { mask += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; }
  3186. var result = "";
  3187. for (var i = length; i > 0; --i) {
  3188. result += mask[Math.round(Math.random() * (mask.length - 1))];
  3189. }
  3190. return result;
  3191. }
  3192. function generateAPIKey() {
  3193. var apikey = randomString(16, "@#");
  3194. $("input[name='apiKey']").val(apikey);
  3195. return false;
  3196. }
  3197. function getJson(str) {
  3198. try {
  3199. return JSON.parse(str);
  3200. } catch (e) {
  3201. return false;
  3202. }
  3203. }
  3204. // -----------------------------------------------------------------------------
  3205. // Actions
  3206. // -----------------------------------------------------------------------------
  3207. function sendAction(action, data) {
  3208. websock.send(JSON.stringify({action: action, data: data}));
  3209. }
  3210. function sendConfig(data) {
  3211. websock.send(JSON.stringify({config: data}));
  3212. }
  3213. function resetOriginals() {
  3214. $("input,select").each(function() {
  3215. $(this).attr("original", $(this).val());
  3216. });
  3217. numReboot = numReconnect = numReload = 0;
  3218. }
  3219. function doReload(milliseconds) {
  3220. setTimeout(function() {
  3221. window.location.reload();
  3222. }, parseInt(milliseconds, 10));
  3223. }
  3224. /**
  3225. * Check a file object to see if it is a valid firmware image
  3226. * The file first byte should be 0xE9
  3227. * @param {file} file File object
  3228. * @param {Function} callback Function to call back with the result
  3229. */
  3230. function checkFirmware(file, callback) {
  3231. var reader = new FileReader();
  3232. reader.onloadend = function(evt) {
  3233. if (FileReader.DONE === evt.target.readyState) {
  3234. callback(0xE9 === evt.target.result.charCodeAt(0));
  3235. }
  3236. };
  3237. var blob = file.slice(0, 1);
  3238. reader.readAsBinaryString(blob);
  3239. }
  3240. function doUpgrade() {
  3241. var file = $("input[name='upgrade']")[0].files[0];
  3242. if (typeof file === "undefined") {
  3243. alert("First you have to select a file from your computer.");
  3244. return false;
  3245. }
  3246. if (file.size > free_size) {
  3247. alert("Image it too large to fit in the available space for OTA. Consider doing a two-step update.");
  3248. return false;
  3249. }
  3250. checkFirmware(file, function(ok) {
  3251. if (!ok) {
  3252. alert("The file does not seem to be a valid firmware image.");
  3253. return;
  3254. }
  3255. var data = new FormData();
  3256. data.append("upgrade", file, file.name);
  3257. $.ajax({
  3258. // Your server script to process the upload
  3259. url: urls.upgrade.href,
  3260. type: "POST",
  3261. // Form data
  3262. data: data,
  3263. // Tell jQuery not to process data or worry about content-type
  3264. // You *must* include these options!
  3265. cache: false,
  3266. contentType: false,
  3267. processData: false,
  3268. success: function(data, text) {
  3269. $("#upgrade-progress").hide();
  3270. if ("OK" === data) {
  3271. alert("Firmware image uploaded, board rebooting. This page will be refreshed in 5 seconds.");
  3272. doReload(5000);
  3273. } else {
  3274. alert("There was an error trying to upload the new image, please try again (" + data + ").");
  3275. }
  3276. },
  3277. // Custom XMLHttpRequest
  3278. xhr: function() {
  3279. $("#upgrade-progress").show();
  3280. var myXhr = $.ajaxSettings.xhr();
  3281. if (myXhr.upload) {
  3282. // For handling the progress of the upload
  3283. myXhr.upload.addEventListener("progress", function(e) {
  3284. if (e.lengthComputable) {
  3285. $("progress").attr({ value: e.loaded, max: e.total });
  3286. }
  3287. } , false);
  3288. }
  3289. return myXhr;
  3290. }
  3291. });
  3292. });
  3293. return false;
  3294. }
  3295. function doUpdatePassword() {
  3296. var form = $("#formPassword");
  3297. if (validateForm(form)) {
  3298. sendConfig(getData(form));
  3299. }
  3300. return false;
  3301. }
  3302. function checkChanges() {
  3303. if (numChanged > 0) {
  3304. var response = window.confirm("Some changes have not been saved yet, do you want to save them first?");
  3305. if (response) {
  3306. doUpdate();
  3307. }
  3308. }
  3309. }
  3310. function doAction(question, action) {
  3311. checkChanges();
  3312. if (question) {
  3313. var response = window.confirm(question);
  3314. if (false === response) {
  3315. return false;
  3316. }
  3317. }
  3318. sendAction(action, {});
  3319. doReload(5000);
  3320. return false;
  3321. }
  3322. function doReboot(ask) {
  3323. var question = (typeof ask === "undefined" || false === ask) ?
  3324. null :
  3325. "Are you sure you want to reboot the device?";
  3326. return doAction(question, "reboot");
  3327. }
  3328. function doReconnect(ask) {
  3329. var question = (typeof ask === "undefined" || false === ask) ?
  3330. null :
  3331. "Are you sure you want to disconnect from the current WIFI network?";
  3332. return doAction(question, "reconnect");
  3333. }
  3334. function doUpdate() {
  3335. var form = $("#formSave");
  3336. if (validateForm(form)) {
  3337. // Get data
  3338. sendConfig(getData(form));
  3339. // Empty special fields
  3340. $(".pwrExpected").val(0);
  3341. $("input[name='pwrResetCalibration']").
  3342. prop("checked", false).
  3343. iphoneStyle("refresh");
  3344. $("input[name='pwrResetE']").
  3345. prop("checked", false).
  3346. iphoneStyle("refresh");
  3347. // Change handling
  3348. numChanged = 0;
  3349. setTimeout(function() {
  3350. var response;
  3351. if (numReboot > 0) {
  3352. response = window.confirm("You have to reboot the board for the changes to take effect, do you want to do it now?");
  3353. if (response) { doReboot(false); }
  3354. } else if (numReconnect > 0) {
  3355. response = window.confirm("You have to reconnect to the WiFi for the changes to take effect, do you want to do it now?");
  3356. if (response) { doReconnect(false); }
  3357. } else if (numReload > 0) {
  3358. response = window.confirm("You have to reload the page to see the latest changes, do you want to do it now?");
  3359. if (response) { doReload(0); }
  3360. }
  3361. resetOriginals();
  3362. }, 1000);
  3363. }
  3364. return false;
  3365. }
  3366. function doBackup() {
  3367. document.getElementById("downloader").src = urls.config.href;
  3368. return false;
  3369. }
  3370. function onFileUpload(event) {
  3371. var inputFiles = this.files;
  3372. if (typeof inputFiles === "undefined" || inputFiles.length === 0) {
  3373. return false;
  3374. }
  3375. var inputFile = inputFiles[0];
  3376. this.value = "";
  3377. var response = window.confirm("Previous settings will be overwritten. Are you sure you want to restore this settings?");
  3378. if (!response) {
  3379. return false;
  3380. }
  3381. var reader = new FileReader();
  3382. reader.onload = function(e) {
  3383. var data = getJson(e.target.result);
  3384. if (data) {
  3385. sendAction("restore", data);
  3386. } else {
  3387. window.alert(messages[4]);
  3388. }
  3389. };
  3390. reader.readAsText(inputFile);
  3391. return false;
  3392. }
  3393. function doRestore() {
  3394. if (typeof window.FileReader !== "function") {
  3395. alert("The file API isn't supported on this browser yet.");
  3396. } else {
  3397. $("#uploader").click();
  3398. }
  3399. return false;
  3400. }
  3401. function doFactoryReset() {
  3402. var response = window.confirm("Are you sure you want to restore to factory settings?");
  3403. if (response === false) {
  3404. return false;
  3405. }
  3406. websock.send(JSON.stringify({"action": "factory_reset"}));
  3407. doReload(5000);
  3408. return false;
  3409. }
  3410. function doToggle(element, value) {
  3411. var id = parseInt(element.attr("data"), 10);
  3412. sendAction("relay", {id: id, status: value ? 1 : 0 });
  3413. return false;
  3414. }
  3415. function doScan() {
  3416. $("#scanResult").html("");
  3417. $("div.scan.loading").show();
  3418. sendAction("scan", {});
  3419. return false;
  3420. }
  3421. function doHAConfig() {
  3422. $("#haConfig").html("");
  3423. sendAction("haconfig", {});
  3424. return false;
  3425. }
  3426. function doDebugCommand() {
  3427. var el = $("input[name='dbgcmd']");
  3428. var command = el.val();
  3429. el.val("");
  3430. sendAction("dbgcmd", {command: command});
  3431. return false;
  3432. }
  3433. function doDebugClear() {
  3434. $("#weblog").text("");
  3435. return false;
  3436. }
  3437. // -----------------------------------------------------------------------------
  3438. // Visualization
  3439. // -----------------------------------------------------------------------------
  3440. function toggleMenu() {
  3441. $("#layout").toggleClass("active");
  3442. $("#menu").toggleClass("active");
  3443. $("#menuLink").toggleClass("active");
  3444. }
  3445. function showPanel() {
  3446. $(".panel").hide();
  3447. if ($("#layout").hasClass("active")) { toggleMenu(); }
  3448. $("#" + $(this).attr("data")).show().
  3449. find("input[type='checkbox']").
  3450. iphoneStyle("calculateDimensions").
  3451. iphoneStyle("refresh");
  3452. }
  3453. // -----------------------------------------------------------------------------
  3454. // Relays & magnitudes mapping
  3455. // -----------------------------------------------------------------------------
  3456. function createRelayList(data, container, template_name) {
  3457. var current = $("#" + container + " > div").length;
  3458. if (current > 0) { return; }
  3459. var template = $("#" + template_name + " .pure-g")[0];
  3460. for (var i in data) {
  3461. var line = $(template).clone();
  3462. $("label", line).html("Switch #" + i);
  3463. $("input", line).attr("tabindex", 40 + i).val(data[i]);
  3464. line.appendTo("#" + container);
  3465. }
  3466. }
  3467. function createMagnitudeList(data, container, template_name) {
  3468. var current = $("#" + container + " > div").length;
  3469. if (current > 0) { return; }
  3470. var template = $("#" + template_name + " .pure-g")[0];
  3471. for (var i in data) {
  3472. var magnitude = data[i];
  3473. var line = $(template).clone();
  3474. $("label", line).html(magnitudeType(magnitude.type) + " #" + parseInt(magnitude.index, 10));
  3475. $("div.hint", line).html(magnitude.name);
  3476. $("input", line).attr("tabindex", 40 + i).val(magnitude.idx);
  3477. line.appendTo("#" + container);
  3478. }
  3479. }
  3480. // -----------------------------------------------------------------------------
  3481. // Wifi
  3482. // -----------------------------------------------------------------------------
  3483. function delNetwork() {
  3484. var parent = $(this).parents(".pure-g");
  3485. $(parent).remove();
  3486. }
  3487. function moreNetwork() {
  3488. var parent = $(this).parents(".pure-g");
  3489. $(".more", parent).toggle();
  3490. }
  3491. function addNetwork() {
  3492. var numNetworks = $("#networks > div").length;
  3493. if (numNetworks >= maxNetworks) {
  3494. alert("Max number of networks reached");
  3495. return null;
  3496. }
  3497. var tabindex = 200 + numNetworks * 10;
  3498. var template = $("#networkTemplate").children();
  3499. var line = $(template).clone();
  3500. $(line).find("input").each(function() {
  3501. $(this).attr("tabindex", tabindex);
  3502. tabindex++;
  3503. });
  3504. $(line).find(".button-del-network").on("click", delNetwork);
  3505. $(line).find(".button-more-network").on("click", moreNetwork);
  3506. line.appendTo("#networks");
  3507. return line;
  3508. }
  3509. // -----------------------------------------------------------------------------
  3510. // Relays scheduler
  3511. // -----------------------------------------------------------------------------
  3512. function delSchedule() {
  3513. var parent = $(this).parents(".pure-g");
  3514. $(parent).remove();
  3515. }
  3516. function moreSchedule() {
  3517. var parent = $(this).parents(".pure-g");
  3518. $("div.more", parent).toggle();
  3519. }
  3520. function addSchedule(event) {
  3521. var numSchedules = $("#schedules > div").length;
  3522. if (numSchedules >= maxSchedules) {
  3523. alert("Max number of schedules reached");
  3524. return null;
  3525. }
  3526. var tabindex = 200 + numSchedules * 10;
  3527. var template = $("#scheduleTemplate").children();
  3528. var line = $(template).clone();
  3529. var type = (1 === event.data.schType) ? "switch" : "light";
  3530. template = $("#" + type + "ActionTemplate").children();
  3531. var actionLine = template.clone();
  3532. $(line).find("#schActionDiv").append(actionLine);
  3533. $(line).find("input").each(function() {
  3534. $(this).attr("tabindex", tabindex);
  3535. tabindex++;
  3536. });
  3537. $(line).find(".button-del-schedule").on("click", delSchedule);
  3538. $(line).find(".button-more-schedule").on("click", moreSchedule);
  3539. line.appendTo("#schedules");
  3540. $(line).find("input[type='checkbox']").
  3541. prop("checked", false).
  3542. iphoneStyle("calculateDimensions").
  3543. iphoneStyle("refresh");
  3544. return line;
  3545. }
  3546. // -----------------------------------------------------------------------------
  3547. // Relays
  3548. // -----------------------------------------------------------------------------
  3549. function initRelays(data) {
  3550. var current = $("#relays > div").length;
  3551. if (current > 0) { return; }
  3552. var template = $("#relayTemplate .pure-g")[0];
  3553. for (var i=0; i<data.length; i++) {
  3554. // Add relay fields
  3555. var line = $(template).clone();
  3556. $(".id", line).html(i);
  3557. $("input", line).attr("data", i);
  3558. line.appendTo("#relays");
  3559. $("input[type='checkbox']", line).iphoneStyle({
  3560. onChange: doToggle,
  3561. resizeContainer: true,
  3562. resizeHandle: true,
  3563. checkedLabel: "ON",
  3564. uncheckedLabel: "OFF"
  3565. });
  3566. // Populate the relay SELECTs
  3567. $("select.isrelay").append(
  3568. $("<option></option>").attr("value",i).text("Switch #" + i));
  3569. }
  3570. }
  3571. function initRelayConfig(data) {
  3572. var current = $("#relayConfig > div").length;
  3573. if (current > 0) { return; }
  3574. var template = $("#relayConfigTemplate").children();
  3575. for (var i in data) {
  3576. var relay = data[i];
  3577. var line = $(template).clone();
  3578. $("span.gpio", line).html(relay.gpio);
  3579. $("span.id", line).html(i);
  3580. $("select[name='relayBoot']", line).val(relay.boot);
  3581. $("select[name='relayPulse']", line).val(relay.pulse);
  3582. $("input[name='relayTime']", line).val(relay.pulse_ms);
  3583. $("input[name='mqttGroup']", line).val(relay.group);
  3584. $("select[name='mqttGroupInv']", line).val(relay.group_inv);
  3585. $("select[name='relayOnDisc']", line).val(relay.on_disc);
  3586. line.appendTo("#relayConfig");
  3587. }
  3588. }
  3589. // -----------------------------------------------------------------------------
  3590. // Sensors & Magnitudes
  3591. // -----------------------------------------------------------------------------
  3592. function initMagnitudes(data) {
  3593. // check if already initialized
  3594. var done = $("#magnitudes > div").length;
  3595. if (done > 0) { return; }
  3596. // add templates
  3597. var template = $("#magnitudeTemplate").children();
  3598. for (var i in data) {
  3599. var magnitude = data[i];
  3600. var line = $(template).clone();
  3601. $("label", line).html(magnitudeType(magnitude.type) + " #" + parseInt(magnitude.index, 10));
  3602. $("div.hint", line).html(magnitude.description);
  3603. $("input", line).attr("data", i);
  3604. line.appendTo("#magnitudes");
  3605. }
  3606. }
  3607. // -----------------------------------------------------------------------------
  3608. // Lights
  3609. // -----------------------------------------------------------------------------
  3610. function initColorRGB() {
  3611. // check if already initialized
  3612. var done = $("#colors > div").length;
  3613. if (done > 0) { return; }
  3614. // add template
  3615. var template = $("#colorRGBTemplate").children();
  3616. var line = $(template).clone();
  3617. line.appendTo("#colors");
  3618. // init color wheel
  3619. $("input[name='color']").wheelColorPicker({
  3620. sliders: "wrgbp"
  3621. }).on("sliderup", function() {
  3622. var value = $(this).wheelColorPicker("getValue", "css");
  3623. sendAction("color", {rgb: value});
  3624. });
  3625. // init bright slider
  3626. $("#brightness").on("change", function() {
  3627. var value = $(this).val();
  3628. var parent = $(this).parents(".pure-g");
  3629. $("span", parent).html(value);
  3630. sendAction("color", {brightness: value});
  3631. });
  3632. }
  3633. function initCCT() {
  3634. // check if already initialized
  3635. var done = $("#cct > div").length;
  3636. if (done > 0) { return; }
  3637. $("#miredsTemplate").children().clone().appendTo("#cct");
  3638. $("#mireds").on("change", function() {
  3639. var value = $(this).val();
  3640. var parent = $(this).parents(".pure-g");
  3641. $("span", parent).html(value);
  3642. sendAction("mireds", {mireds: value});
  3643. });
  3644. }
  3645. function initColorHSV() {
  3646. // check if already initialized
  3647. var done = $("#colors > div").length;
  3648. if (done > 0) { return; }
  3649. // add template
  3650. var template = $("#colorHSVTemplate").children();
  3651. var line = $(template).clone();
  3652. line.appendTo("#colors");
  3653. // init color wheel
  3654. $("input[name='color']").wheelColorPicker({
  3655. sliders: "whsvp"
  3656. }).on("sliderup", function() {
  3657. var color = $(this).wheelColorPicker("getColor");
  3658. var value = parseInt(color.h * 360, 10) + "," + parseInt(color.s * 100, 10) + "," + parseInt(color.v * 100, 10);
  3659. sendAction("color", {hsv: value});
  3660. });
  3661. }
  3662. function initChannels(num) {
  3663. // check if already initialized
  3664. var done = $("#channels > div").length > 0;
  3665. if (done) { return; }
  3666. // does it have color channels?
  3667. var colors = $("#colors > div").length > 0;
  3668. // calculate channels to create
  3669. var max = num;
  3670. if (colors) {
  3671. max = num % 3;
  3672. if ((max > 0) & useWhite) {
  3673. max--;
  3674. if (useCCT) {
  3675. max--;
  3676. }
  3677. }
  3678. }
  3679. var start = num - max;
  3680. var onChannelSliderChange = function() {
  3681. var id = $(this).attr("data");
  3682. var value = $(this).val();
  3683. var parent = $(this).parents(".pure-g");
  3684. $("span", parent).html(value);
  3685. sendAction("channel", {id: id, value: value});
  3686. };
  3687. // add templates
  3688. var i = 0;
  3689. var template = $("#channelTemplate").children();
  3690. for (i=0; i<max; i++) {
  3691. var channel_id = start + i;
  3692. var line = $(template).clone();
  3693. $("span.slider", line).attr("data", channel_id);
  3694. $("input.slider", line).attr("data", channel_id).on("change", onChannelSliderChange);
  3695. $("label", line).html("Channel #" + channel_id);
  3696. line.appendTo("#channels");
  3697. }
  3698. for (i=0; i<num; i++) {
  3699. $("select.islight").append(
  3700. $("<option></option>").attr("value",i).text("Channel #" + i));
  3701. }
  3702. }
  3703. // -----------------------------------------------------------------------------
  3704. // RFBridge
  3705. // -----------------------------------------------------------------------------
  3706. function rfbLearn() {
  3707. var parent = $(this).parents(".pure-g");
  3708. var input = $("input", parent);
  3709. sendAction("rfblearn", {id: input.attr("data-id"), status: input.attr("data-status")});
  3710. }
  3711. function rfbForget() {
  3712. var parent = $(this).parents(".pure-g");
  3713. var input = $("input", parent);
  3714. sendAction("rfbforget", {id: input.attr("data-id"), status: input.attr("data-status")});
  3715. }
  3716. function rfbSend() {
  3717. var parent = $(this).parents(".pure-g");
  3718. var input = $("input", parent);
  3719. sendAction("rfbsend", {id: input.attr("data-id"), status: input.attr("data-status"), data: input.val()});
  3720. }
  3721. function addRfbNode() {
  3722. var numNodes = $("#rfbNodes > legend").length;
  3723. var template = $("#rfbNodeTemplate").children();
  3724. var line = $(template).clone();
  3725. var status = true;
  3726. $("span", line).html(numNodes);
  3727. $(line).find("input").each(function() {
  3728. $(this).attr("data-id", numNodes);
  3729. $(this).attr("data-status", status ? 1 : 0);
  3730. status = !status;
  3731. });
  3732. $(line).find(".button-rfb-learn").on("click", rfbLearn);
  3733. $(line).find(".button-rfb-forget").on("click", rfbForget);
  3734. $(line).find(".button-rfb-send").on("click", rfbSend);
  3735. line.appendTo("#rfbNodes");
  3736. return line;
  3737. }
  3738. // -----------------------------------------------------------------------------
  3739. // Processing
  3740. // -----------------------------------------------------------------------------
  3741. function processData(data) {
  3742. // title
  3743. if ("app_name" in data) {
  3744. var title = data.app_name;
  3745. if ("app_version" in data) {
  3746. title = title + " " + data.app_version;
  3747. }
  3748. $("span[name=title]").html(title);
  3749. if ("hostname" in data) {
  3750. title = data.hostname + " - " + title;
  3751. }
  3752. document.title = title;
  3753. }
  3754. Object.keys(data).forEach(function(key) {
  3755. var i;
  3756. var value = data[key];
  3757. // ---------------------------------------------------------------------
  3758. // Web mode
  3759. // ---------------------------------------------------------------------
  3760. if ("webMode" === key) {
  3761. password = (1 === value);
  3762. $("#layout").toggle(!password);
  3763. $("#password").toggle(password);
  3764. }
  3765. // ---------------------------------------------------------------------
  3766. // Actions
  3767. // ---------------------------------------------------------------------
  3768. if ("action" === key) {
  3769. if ("reload" === data.action) { doReload(1000); }
  3770. return;
  3771. }
  3772. // ---------------------------------------------------------------------
  3773. // RFBridge
  3774. // ---------------------------------------------------------------------
  3775. if ("rfbCount" === key) {
  3776. for (i=0; i<data.rfbCount; i++) { addRfbNode(); }
  3777. return;
  3778. }
  3779. if ("rfbrawVisible" === key) {
  3780. $("input[name='rfbcode']").attr("maxlength", 116);
  3781. }
  3782. if ("rfb" === key) {
  3783. var nodes = data.rfb;
  3784. for (i in nodes) {
  3785. var node = nodes[i];
  3786. $("input[name='rfbcode'][data-id='" + node.id + "'][data-status='" + node.status + "']").val(node.data);
  3787. }
  3788. return;
  3789. }
  3790. // ---------------------------------------------------------------------
  3791. // Lights
  3792. // ---------------------------------------------------------------------
  3793. if ("rgb" === key) {
  3794. initColorRGB();
  3795. $("input[name='color']").wheelColorPicker("setValue", value, true);
  3796. return;
  3797. }
  3798. if ("hsv" === key) {
  3799. initColorHSV();
  3800. // wheelColorPicker expects HSV to be between 0 and 1 all of them
  3801. var chunks = value.split(",");
  3802. var obj = {};
  3803. obj.h = chunks[0] / 360;
  3804. obj.s = chunks[1] / 100;
  3805. obj.v = chunks[2] / 100;
  3806. $("input[name='color']").wheelColorPicker("setColor", obj);
  3807. return;
  3808. }
  3809. if ("brightness" === key) {
  3810. $("#brightness").val(value);
  3811. $("span.brightness").html(value);
  3812. return;
  3813. }
  3814. if ("channels" === key) {
  3815. var len = value.length;
  3816. initChannels(len);
  3817. for (i in value) {
  3818. var ch = value[i];
  3819. $("input.slider[data=" + i + "]").val(ch);
  3820. $("span.slider[data=" + i + "]").html(ch);
  3821. }
  3822. return;
  3823. }
  3824. if ("mireds" === key) {
  3825. $("#mireds").val(value);
  3826. $("span.mireds").html(value);
  3827. return;
  3828. }
  3829. if ("useWhite" === key) {
  3830. useWhite = value;
  3831. }
  3832. if ("useCCT" === key) {
  3833. initCCT();
  3834. useCCT = value;
  3835. }
  3836. // ---------------------------------------------------------------------
  3837. // Sensors & Magnitudes
  3838. // ---------------------------------------------------------------------
  3839. if ("magnitudes" === key) {
  3840. initMagnitudes(value);
  3841. for (i in value) {
  3842. var magnitude = value[i];
  3843. var error = magnitude.error || 0;
  3844. var text = (0 === error) ?
  3845. magnitude.value + magnitude.units :
  3846. magnitudeError(error);
  3847. var element = $("input[name='magnitude'][data='" + i + "']");
  3848. element.val(text);
  3849. $("div.hint", element.parent().parent()).html(magnitude.description);
  3850. }
  3851. return;
  3852. }
  3853. // ---------------------------------------------------------------------
  3854. // WiFi
  3855. // ---------------------------------------------------------------------
  3856. if ("maxNetworks" === key) {
  3857. maxNetworks = parseInt(value, 10);
  3858. return;
  3859. }
  3860. if ("wifi" === key) {
  3861. for (i in value) {
  3862. var wifi = value[i];
  3863. var nwk_line = addNetwork();
  3864. Object.keys(wifi).forEach(function(key) {
  3865. $("input[name='" + key + "']", nwk_line).val(wifi[key]);
  3866. });
  3867. }
  3868. return;
  3869. }
  3870. if ("scanResult" === key) {
  3871. $("div.scan.loading").hide();
  3872. $("#scanResult").show();
  3873. }
  3874. // -----------------------------------------------------------------------------
  3875. // Home Assistant
  3876. // -----------------------------------------------------------------------------
  3877. if ("haConfig" === key) {
  3878. $("#haConfig").show();
  3879. }
  3880. // -----------------------------------------------------------------------------
  3881. // Relays scheduler
  3882. // -----------------------------------------------------------------------------
  3883. if ("maxSchedules" === key) {
  3884. maxSchedules = parseInt(value, 10);
  3885. return;
  3886. }
  3887. if ("schedule" === key) {
  3888. for (i in value) {
  3889. var schedule = value[i];
  3890. var sch_line = addSchedule({ data: {schType: schedule["schType"] }});
  3891. Object.keys(schedule).forEach(function(key) {
  3892. var sch_value = schedule[key];
  3893. $("input[name='" + key + "']", sch_line).val(sch_value);
  3894. $("select[name='" + key + "']", sch_line).prop("value", sch_value);
  3895. $("input[type='checkbox'][name='" + key + "']", sch_line).
  3896. prop("checked", sch_value).
  3897. iphoneStyle("refresh");
  3898. });
  3899. }
  3900. return;
  3901. }
  3902. // ---------------------------------------------------------------------
  3903. // Relays
  3904. // ---------------------------------------------------------------------
  3905. if ("relayStatus" === key) {
  3906. initRelays(value);
  3907. for (i in value) {
  3908. // Set the status for each relay
  3909. $("input.relayStatus[data='" + i + "']").
  3910. prop("checked", value[i]).
  3911. iphoneStyle("refresh");
  3912. }
  3913. return;
  3914. }
  3915. // Relay configuration
  3916. if ("relayConfig" === key) {
  3917. initRelayConfig(value);
  3918. return;
  3919. }
  3920. // ---------------------------------------------------------------------
  3921. // Domoticz
  3922. // ---------------------------------------------------------------------
  3923. // Domoticz - Relays
  3924. if ("dczRelays" === key) {
  3925. createRelayList(value, "dczRelays", "dczRelayTemplate");
  3926. return;
  3927. }
  3928. // Domoticz - Magnitudes
  3929. if ("dczMagnitudes" === key) {
  3930. createMagnitudeList(value, "dczMagnitudes", "dczMagnitudeTemplate");
  3931. return;
  3932. }
  3933. // ---------------------------------------------------------------------
  3934. // Thingspeak
  3935. // ---------------------------------------------------------------------
  3936. // Thingspeak - Relays
  3937. if ("tspkRelays" === key) {
  3938. createRelayList(value, "tspkRelays", "tspkRelayTemplate");
  3939. return;
  3940. }
  3941. // Thingspeak - Magnitudes
  3942. if ("tspkMagnitudes" === key) {
  3943. createMagnitudeList(value, "tspkMagnitudes", "tspkMagnitudeTemplate");
  3944. return;
  3945. }
  3946. // ---------------------------------------------------------------------
  3947. // General
  3948. // ---------------------------------------------------------------------
  3949. // Messages
  3950. if ("message" === key) {
  3951. window.alert(messages[value]);
  3952. return;
  3953. }
  3954. // Web log
  3955. if ("weblog" === key) {
  3956. $("#weblog").append(new Text(value));
  3957. $("#weblog").scrollTop($("#weblog")[0].scrollHeight - $("#weblog").height());
  3958. return;
  3959. }
  3960. // Enable options
  3961. var position = key.indexOf("Visible");
  3962. if (position > 0 && position === key.length - 7) {
  3963. var module = key.slice(0,-7);
  3964. $(".module-" + module).css("display", "inherit");
  3965. return;
  3966. }
  3967. if ("deviceip" === key) {
  3968. var a_href = $("span[name='" + key + "']").parent();
  3969. a_href.attr("href", "http://" + value);
  3970. a_href.next().attr("href", "telnet://" + value);
  3971. }
  3972. if ("now" === key) {
  3973. now = value;
  3974. return;
  3975. }
  3976. if ("free_size" === key) {
  3977. free_size = parseInt(value, 10);
  3978. }
  3979. // Pre-process
  3980. if ("mqttStatus" === key) {
  3981. value = value ? "CONNECTED" : "NOT CONNECTED";
  3982. }
  3983. if ("ntpStatus" === key) {
  3984. value = value ? "SYNC'D" : "NOT SYNC'D";
  3985. }
  3986. if ("uptime" === key) {
  3987. ago = 0;
  3988. var uptime = parseInt(value, 10);
  3989. var seconds = uptime % 60; uptime = parseInt(uptime / 60, 10);
  3990. var minutes = uptime % 60; uptime = parseInt(uptime / 60, 10);
  3991. var hours = uptime % 24; uptime = parseInt(uptime / 24, 10);
  3992. var days = uptime;
  3993. value = days + "d " + zeroPad(hours, 2) + "h " + zeroPad(minutes, 2) + "m " + zeroPad(seconds, 2) + "s";
  3994. }
  3995. // ---------------------------------------------------------------------
  3996. // Matching
  3997. // ---------------------------------------------------------------------
  3998. var pre;
  3999. var post;
  4000. // Look for INPUTs
  4001. var input = $("input[name='" + key + "']");
  4002. if (input.length > 0) {
  4003. if (input.attr("type") === "checkbox") {
  4004. input.
  4005. prop("checked", value).
  4006. iphoneStyle("refresh");
  4007. } else if (input.attr("type") === "radio") {
  4008. input.val([value]);
  4009. } else {
  4010. pre = input.attr("pre") || "";
  4011. post = input.attr("post") || "";
  4012. input.val(pre + value + post);
  4013. }
  4014. }
  4015. // Look for SPANs
  4016. var span = $("span[name='" + key + "']");
  4017. if (span.length > 0) {
  4018. pre = span.attr("pre") || "";
  4019. post = span.attr("post") || "";
  4020. span.html(pre + value + post);
  4021. }
  4022. // Look for SELECTs
  4023. var select = $("select[name='" + key + "']");
  4024. if (select.length > 0) {
  4025. select.val(value);
  4026. }
  4027. });
  4028. // Auto generate an APIKey if none defined yet
  4029. if ($("input[name='apiKey']").val() === "") {
  4030. generateAPIKey();
  4031. }
  4032. resetOriginals();
  4033. }
  4034. function hasChanged() {
  4035. var newValue, originalValue;
  4036. if ($(this).attr("type") === "checkbox") {
  4037. newValue = $(this).prop("checked");
  4038. originalValue = ($(this).attr("original") === "true");
  4039. } else {
  4040. newValue = $(this).val();
  4041. originalValue = $(this).attr("original");
  4042. }
  4043. var hasChanged = $(this).attr("hasChanged") || 0;
  4044. var action = $(this).attr("action");
  4045. if (typeof originalValue === "undefined") { return; }
  4046. if ("none" === action) { return; }
  4047. if (newValue !== originalValue) {
  4048. if (0 === hasChanged) {
  4049. ++numChanged;
  4050. if ("reconnect" === action) { ++numReconnect; }
  4051. if ("reboot" === action) { ++numReboot; }
  4052. if ("reload" === action) { ++numReload; }
  4053. $(this).attr("hasChanged", 1);
  4054. }
  4055. } else {
  4056. if (1 === hasChanged) {
  4057. --numChanged;
  4058. if ("reconnect" === action) { --numReconnect; }
  4059. if ("reboot" === action) { --numReboot; }
  4060. if ("reload" === action) { --numReload; }
  4061. $(this).attr("hasChanged", 0);
  4062. }
  4063. }
  4064. }
  4065. // -----------------------------------------------------------------------------
  4066. // Init & connect
  4067. // -----------------------------------------------------------------------------
  4068. function initUrls(root) {
  4069. var paths = ["ws", "upgrade", "config", "auth"];
  4070. urls["root"] = root;
  4071. paths.forEach(function(path) {
  4072. urls[path] = new URL(path, root);
  4073. urls[path].protocol = root.protocol;
  4074. });
  4075. if (root.protocol == "https:") {
  4076. urls.ws.protocol = "wss:";
  4077. } else {
  4078. urls.ws.protocol = "ws:";
  4079. }
  4080. }
  4081. function connectToURL(url) {
  4082. initUrls(url);
  4083. $.ajax({
  4084. 'method': 'GET',
  4085. 'url': urls.auth.href,
  4086. 'xhrFields': { 'withCredentials': true }
  4087. }).done(function(data) {
  4088. if (websock) { websock.close(); }
  4089. websock = new WebSocket(urls.ws.href);
  4090. websock.onmessage = function(evt) {
  4091. var data = getJson(evt.data.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"));
  4092. if (data) {
  4093. processData(data);
  4094. }
  4095. };
  4096. }).fail(function() {
  4097. // Nothing to do, reload page and retry
  4098. });
  4099. }
  4100. function connect(host) {
  4101. if (!host.startsWith("http:") && !host.startsWith("https:")) {
  4102. host = "http://" + host;
  4103. }
  4104. connectToURL(new URL(host));
  4105. }
  4106. function connectToCurrentURL() {
  4107. connectToURL(new URL(window.location));
  4108. }
  4109. $(function() {
  4110. initMessages();
  4111. loadTimeZones();
  4112. setInterval(function() { keepTime(); }, 1000);
  4113. $("#menuLink").on("click", toggleMenu);
  4114. $(".pure-menu-link").on("click", showPanel);
  4115. $("progress").attr({ value: 0, max: 100 });
  4116. $(".button-update").on("click", doUpdate);
  4117. $(".button-update-password").on("click", doUpdatePassword);
  4118. $(".button-reboot").on("click", doReboot);
  4119. $(".button-reconnect").on("click", doReconnect);
  4120. $(".button-wifi-scan").on("click", doScan);
  4121. $(".button-ha-config").on("click", doHAConfig);
  4122. $(".button-dbgcmd").on("click", doDebugCommand);
  4123. $("input[name='dbgcmd']").enterKey(doDebugCommand);
  4124. $(".button-dbg-clear").on("click", doDebugClear);
  4125. $(".button-settings-backup").on("click", doBackup);
  4126. $(".button-settings-restore").on("click", doRestore);
  4127. $(".button-settings-factory").on("click", doFactoryReset);
  4128. $("#uploader").on("change", onFileUpload);
  4129. $(".button-upgrade").on("click", doUpgrade);
  4130. $(".button-apikey").on("click", generateAPIKey);
  4131. $(".button-upgrade-browse").on("click", function() {
  4132. $("input[name='upgrade']")[0].click();
  4133. return false;
  4134. });
  4135. $("input[name='upgrade']").change(function (){
  4136. var file = this.files[0];
  4137. $("input[name='filename']").val(file.name);
  4138. });
  4139. $(".button-add-network").on("click", function() {
  4140. $(".more", addNetwork()).toggle();
  4141. });
  4142. $(".button-add-switch-schedule").on("click", { schType: 1 }, addSchedule);
  4143. $(".button-add-light-schedule").on("click", { schType: 2 }, addSchedule);
  4144. $(document).on("change", "input", hasChanged);
  4145. $(document).on("change", "select", hasChanged);
  4146. // don't autoconnect when opening from filesystem
  4147. if (window.location.protocol === "file:") { return; }
  4148. connectToCurrentURL();
  4149. });
  4150. </script>
  4151. <!-- endbuild -->
  4152. </html>