67c213ffc792a5bd1f39884038100372e3414c53
[esp-clock.git] / data / web / script.js
1 (function (window, document) {
2
3     // we fetch the elements each time because docusaurus removes the previous
4     // element references on page navigation
5     function getElements() {
6         return {
7             layout: document.getElementById('layout'),
8             menu: document.getElementById('menu'),
9             menuLink: document.getElementById('menuLink')
10         };
11     }
12
13     function toggleClass(element, className) {
14         var classes = element.className.split(/\s+/);
15         var length = classes.length;
16         var i = 0;
17
18         for (; i < length; i++) {
19             if (classes[i] === className) {
20                 classes.splice(i, 1);
21                 break;
22             }
23         }
24         // The className is not found
25         if (length === classes.length) {
26             classes.push(className);
27         }
28
29         element.className = classes.join(' ');
30     }
31
32     function toggleAll() {
33         var active = 'active';
34         var elements = getElements();
35
36         toggleClass(elements.layout, active);
37         toggleClass(elements.menu, active);
38         toggleClass(elements.menuLink, active);
39     }
40     
41     function handleEvent(e) {
42         var elements = getElements();
43         
44         if (e.target.id === elements.menuLink.id) {
45             toggleAll();
46             e.preventDefault();
47         } else if (elements.menu.className.indexOf('active') !== -1) {
48             toggleAll();
49         }
50     }
51     
52     document.addEventListener('click', handleEvent);
53
54 }(this, this.document));
55
56 var pages
57 var parameters = {}
58 var daynames = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'] 
59
60 function encode(r){
61   r = String(r)
62   return r.replace(/[\x26\x0A\<>'"]/g,function(r){return"&#"+r.charCodeAt(0)+";"})
63 }
64
65 function getAnchor() {
66     return window.location.hash;
67 }
68
69 function DrawHeader(project) {
70   var menu_header = document.getElementById('_ui_menu_header')
71   menu_header.innerHTML = project.name
72   document.title = project.name + '/' + project.version
73 }
74
75 function ParseJsonQ(url, callback) {
76   var req = new XMLHttpRequest();
77
78   req.onreadystatechange = function () {
79     if (this.readyState != 4) return; 
80     if (this.status != 200 && this.status != 500 && this.status != 404) {
81       setTimeout(ParseJsonQ(url, callback),30000);
82       return;
83     }
84     var json = JSON.parse(this.responseText)
85     callback(json);
86   };
87
88   req.open("GET", url, true);
89   req.send()
90
91 }
92
93 function UpdateElement(id, value) {
94   var element = document.getElementById("_ui_element_"+id)
95   if (!element) return;
96   var ui_class = element.dataset.ui_class;
97   switch (ui_class) {
98     case "table":
99       element.innerText = value
100       break
101     case "input":
102     case "password":
103     case "select":
104     case "number":
105     case "range":
106       element.value = value  
107       break
108     case "checkbox":
109       element.checked = value
110       break
111     case "week":
112       for (i=0; i<7; i++) {
113         var subname = '_ui_elpart_'+i+'_'+id
114         var subelement = document.getElementById(subname) 
115         if (value[i]==1) { 
116           subelement.className = "weekday-selected"
117         } else {
118           subelement.className = "weekday"
119         }
120       }
121       break
122   }  
123 }
124
125 function UpdateValues(json) {
126   for (var key in json) {
127     var obj = document.getElementById("_ui_element_"+key)
128     if (obj) {
129       UpdateElement(key, json[key])
130     }
131     parameters[key] = json[key]
132   }
133   var notification = document.getElementById('_ui_notification');
134   if (parameters['_changed']) {
135     notification.innerHTML = '<input style="margin:1em .5em 0;" type="button" id="save" value="Сохранить внесенные изменения" class="pure-button pure-button-primary" onclick="sendAction(\'save\')">'
136     notification.removeAttribute('hidden')
137   } else {
138     notification.innerHTML = ''
139     notification.hidden = true
140   }
141 }
142
143 function sendUpdate(id) {
144   var input = document.getElementById('_ui_element_'+id)
145   var ui_class = input.dataset.ui_class;
146   switch (ui_class) {
147     case 'input':
148     case 'password':
149     case 'number':
150     case 'range':
151       if (input.checkValidity() && input.value != parameters[id]) {
152         ParseJsonQ('/config/set?name=' + id + '&value=' + encodeURIComponent(input.value), function(json) {
153           UpdateValues(json)
154         })
155       }
156       break
157     case 'select':
158       ParseJsonQ('/config/set?name=' + id + '&value=' + encodeURIComponent(input.selectedOptions[0].value), function(json) {
159         UpdateValues(json)
160       })
161       break;
162     case 'checkbox':
163       ParseJsonQ('/config/set?name=' + id + '&value=' + (input.checked?'true':'false'), function(json) {
164         UpdateValues(json)
165       })      
166       break;
167     case 'week':        
168       ParseJsonQ('/config/set?name=' + id + '&value=' + input.dataset.value, function(json) {
169         UpdateValues(json)
170       })      
171       break;
172   }
173 }
174
175 function sendAction(name) {
176   ParseJsonQ('/action?name=' + name, function(json) {
177     if (json.result == 'FAILED') {
178       alert(json.message)
179       if (json.page) {
180         DrawPage(json.page)
181       }
182     } else {
183       location.reload()
184     }
185   })
186 }
187
188 function ShowPwd(id) {
189   var x = document.getElementById('_ui_element_' + id)
190   if (x.type === "password") {
191     x.type = "text";
192   } else {
193     x.type = "password";
194   }
195 }
196
197 function OpenSelect(id) {
198   var selector = document.getElementById('_ui_elemmodal_'+id);
199   selector.removeAttribute("hidden")
200 }
201
202 function CloseSelect(id) {
203   var selector = document.getElementById('_ui_elemmodal_'+id);
204   selector.hidden = true
205 }
206
207 function SelectWiFi(id, ssid) {
208   CloseSelect(id);
209   var x = document.getElementById('_ui_element_' + id)
210   UpdateElement(x,ssid);
211   sendUpdate(id)
212 }
213
214 function getWiFi(id) {
215   var list = document.getElementById('_ui_elemselect_'+id)
216
217   var req = new XMLHttpRequest();
218
219   req.onreadystatechange = function () {
220     if (this.readyState != 4) return; 
221     if (this.status != 200 && this.status != 500 && this.status != 404) {
222       setTimeout(getWiFi(id),30000);
223       return;
224     }
225     var json = JSON.parse(this.responseText)
226     var table = '<table cellpadding="5" border="0" align="center"><thead class="table-header"><tr><td style="padding: 1rem">SSID</td><td style="padding: 1rem">BSSID</td><td style="padding: 1rem">RSSI</td><td style="padding: 1rem">Канал</td><td style="padding: 1rem">Защита</td></tr></thead></tbody style="border-bottom: lightgrey 1px solid">'
227     if (!json.length) {
228       setTimeout(getWiFi(id),5000);
229     }
230     for (idx in json) {
231       var encryption = json[idx].secure == 2? "TKIP" : json[idx].secure == 5? "WEP" : json[idx].secure == 4? "CCMP" : json[idx].secure == 7? "нет" : json[idx].secure == 8? "Автоматически" : "Не определено";
232       table += '<tr onclick="SelectWiFi(\''+id+'\',\''+json[idx].ssid+'\')"><td style="padding: 1rem">'+json[idx].ssid+'</td><td style="padding: 1rem">'+json[idx].bssid+'</td><td style="padding: 1rem">'+json[idx].rssi+'</td><td style="padding: 1rem">'+json[idx].channel+'</td><td style="padding: 1rem">'+encryption+'</td></tr>'
233     }
234     
235     table += '</tbody></table>'
236     list.innerHTML = table;
237   };
238
239   req.open("GET", "/wifi/scan", true);
240   req.send()
241
242 }
243
244 function ClickDay(id, day) {
245   value = parameters[id].split('')
246   day_value = value[day]
247   day_value = (day_value=='0')?'1':'0'
248   value[day] = day_value
249   element = document.getElementById('_ui_element_' + id)
250   value = value.join('')
251   element.dataset.value = value;
252   sendUpdate(id);
253 }
254
255 function ElementHTML(element) {
256   var value
257   if (parameters[element.id] || !isNaN(parameters[element.id])) {
258     value = parameters[element.id]
259   } else if (element.value) {
260     value = element.value
261   } else {
262     value = ""
263   }
264   switch (element.type) {
265     case 'hr':
266       return '<div class="pure-u-1 pure-u-md-1-3"><hr></div>'
267     case 'button':
268       return '<div class="pure-u-1 pure-u-md-1-3"><div align="center"><input style="margin:1em .5em 0;" type="button" id="' 
269         + element.id + '" value="' + encode(element.label) + '" class="pure-button pure-button-primary" onclick="sendAction(\'' + element.id + '\')" /></div></div>'
270     case 'password':
271       return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
272         + '<input data-ui_class="password" type="password" id="_ui_element_' + element.id + '" value="' + encode(value) 
273         + '" class="pure-u-1" style="display:inline-block" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" />'
274         + '<div class="hint" onclick="ShowPwd(\'' + element.id + '\')">&#x1F441;</div></div>'
275     case 'input':
276       var pattern = ""
277       if (element.pattern) {
278         pattern = ' pattern="' + encode(element.pattern) + '"'
279       }
280       return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
281         + '<input data-ui_class="input" type="text" id="_ui_element_' + element.id + '" value="' + encode(value) +'"'+ pattern
282         + ' class="pure-u-1" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" /></div>'
283     case 'input-wifi':
284       var pattern = ""
285       if (element.pattern) {
286         pattern = ' pattern="' + encode(element.pattern) + '"'
287       }
288       return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
289         + '<input data-ui_class="input" type="text" id="_ui_element_' + element.id + '" value="' + encode(value) +'"'+ pattern
290         + ' class="pure-u-1" style="display:inline-block" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" />' 
291         + '<div class="hint" onclick="OpenSelect(\'' + element.id+ '\'); getWiFi(\'' + element.id + '\')">&#128246;</div>'
292         + '<div class="modal" id="_ui_elemmodal_' + element.id + '" hidden>' 
293         + '<div class="modal-content">'
294         + '<div id="_ui_elemselect_' + element.id + '"></div>'
295         + '<div class="pure-u-1 pure-u-md-1-3"><div align="center"><input style="margin:1em .5em 0;" type="button" id="_ui_button_' 
296         + element.id + '" value="Закрыть" class="pure-button pure-button-primary" onclick="CloseSelect(\'' + element.id + '\')"></div>'
297         + '</div></div></div>'
298     case 'checkbox':
299       return '<div class="pure-u-1 pure-u-md-1-3"><label class="switch socket" for="_ui_element_' + element.id + '">'
300         + '<input class="switch" data-ui_class="checkbox" type="checkbox" id="_ui_element_' + element.id + '"' + (parameters[element.id]?' checked':'') + ' onchange="sendUpdate(\'' + element.id + '\')" />'
301         + '<span class="switch slider">'+ encode(element.label) + '</span>'
302         + '</label></div>'
303     case 'select':
304       var options = '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
305         + '<select class="pure-u-24-24" data-ui_class="select" id="_ui_element_' + element.id + '" onchange="sendUpdate(\'' + element.id + '\')">'
306       for (const option of element.options) {
307         var list_option = '<option value="' + encode(option.value) + '" ';
308         if (option.value == parameters[element.id]) {
309           list_option += 'selected '
310         }
311         list_option += '>' + encode(option.text) + '</option>'
312         options += list_option
313       }
314       options += '</select></div>'
315       return options
316     case 'week':
317       days = '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>' 
318         + '<table data-ui_class="week" data-value="' + value + '" id="_ui_element_' + element.id + '" cellpadding="5" border="0" align="left"><tbody><tr>'
319       for (i=0; i<7; i++) {
320         a_enabled = (value[i] == "1")
321         days = days + '<td><div class="weekday' + (a_enabled?"-selected":"") + '" id="_ui_elpart_'+i+'_'+element.id+'" onclick="ClickDay(\'' + element.id + '\', ' + i + ')">'+ daynames[i] + '</div></td>'
322       }
323       days += '</tr></tbody></table></div>'
324       return days
325     case 'text':
326       return '<div class="pure-u-1 pure-u-md-1-3"><h2 id="_ui_element_'+ element.id +'" ' + (element.color?'style="color:'+ element.color+'" ':'')+ '>' + encode(value) + '</h2></div>'
327     case 'number':
328       return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
329         + '<input data-ui_class="number" type="number" '+ (!isNaN(element.min)?'min="'+element.min+'" ':'') + (!isNaN(element.max)?'max="'+element.max+'" ':'') + (!isNaN(element.step)?'step="'+element.step+'" ':'')
330         + 'id="_ui_element_' + element.id + '" value="' + encode(value) +'"'+ pattern
331         + ' class="pure-u-1" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" /></div>'
332     case 'range':
333       return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
334         + '<input data-ui_class="range" type="range" '+ (!isNaN(element.min)?'min="'+element.min+'" ':'') + (!isNaN(element.max)?'max="'+element.max+'" ':'') + (!isNaN(element.step)?'step="'+element.step+'" ':'')
335         + 'id="_ui_element_' + element.id + '" value="' + encode(value) +'"'+ pattern
336         + ' class="pure-u-1" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" /></div>'
337     case 'table':
338     default:
339       return '<div class="pure-u-1 pure-u-md-1-3"><table cellpadding="5" border="0" align="center"><tbody><tr><td style="padding-right: 10px; width: 50%;" align="right"><pre>' 
340         + encode(element.label)+ '</pre></td><td style="padding-left: 10px;" id="cT"><pre id="_ui_element_' 
341         + element.id + '" data-ui_class="table" ' + (element.color?'style="color:'+ element.color+'" ':'')+ '>' + encode(value) + '</pre></td></tr></tbody></table></div>'
342   }
343 }
344
345 function DrawPage(id) {
346   var idx =0, i=0
347   for (const page of pages) {
348     var menu_link = document.getElementById('_ui_pglink_' + page.id)  
349     if (page.id != id) {
350       menu_link.classList.remove("pure-menu-selected")
351     } else {
352       menu_link.classList.add("pure-menu-selected")
353       idx = i
354     }
355     i++
356   }
357   var page_header = document.getElementById("_ui_page_header")
358   page_header.innerHTML = pages[idx].title
359   var page_content = document.getElementById("_ui_page_content");
360   var content = ''
361   for (const element of pages[idx].elements) {
362     content = content + ElementHTML(element) + '\n'
363   }
364   page_content.innerHTML = content
365   window.location.hash = id
366 }
367
368 function DrawNavigator(project, pages) {
369   var menu = document.getElementById('_ui_menu_list');
370   var list = ''
371   for (const page of pages) {
372     list = list + '<li id="_ui_pglink_' + page.id 
373       + '" class="pure-menu-item"><a class="pure-menu-link" onclick="DrawPage(\''+ page.id +'\')" href="#' + page.id + '">'
374       + page.title+'</a></li>'
375   }
376   menu.innerHTML = list
377 }
378
379 function DrawContacts(contacts) {
380   if (!contacts) return;
381   var contact_list = '<hr><h4 class="pure-u1">Контакты</h4>'
382   for (const contact of contacts) {
383     const url = new URL(contact)
384     var ref
385     console.log(url)
386     switch (url.protocol) {
387       case 'http':
388       case 'https:':
389         ref = '&#8962; '+url.hostname
390         break
391       case 'mailto:':
392         ref = '&#9993; '+url.pathname
393         break
394       default:
395         ref = '&#128389; '+url.pathname
396     }
397     contact_list += '<a href="'+contact+'">'+ref+'</a>'
398   }
399   var footer = document.getElementById('_ui_contacts');
400   footer.innerHTML = contact_list
401 }
402
403 function DrawUI(ui) {
404   DrawHeader(ui.project)
405   pages = ui.pages
406   DrawNavigator(ui.project, pages)
407   DrawContacts(ui.project.contacts)
408   var anchor = getAnchor()
409   if (anchor) {
410     DrawPage(anchor)
411   } else {
412     DrawPage(pages[0].id)
413   }
414 }
415
416 function GetUI() {
417   ParseJsonQ("/ui", DrawUI);
418 }
419
420 GetUI()
421
422 function initES() {
423   if (!!window.EventSource) {
424     var source = new EventSource('/events');
425
426     source.onerror = function(e) {
427       if (source.readyState == 2) {
428         setTimeout(initES, 5000);
429       }
430     };
431         
432     source.addEventListener('keepalive', function(e) {
433       UpdateValues(JSON.parse(e.data));
434     }, false);
435
436     source.addEventListener('update', function(e) {
437       UpdateValues(JSON.parse(e.data));
438     }, false);
439   }
440 }
441
442 initES();
443
444 function DrawConfig(cfg) {
445   UpdateValues(cfg)
446 }
447
448 function GetCfg() {
449   ParseJsonQ("/config/get", DrawConfig);
450 }
451
452 GetCfg()