3 var daynames = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс']
6 function toggleMenu(e) {
7 active = (document.getElementById('menuLink').className.indexOf('active') !== -1)
8 if (active || e.target.id == 'menuLink' || e.target.id == 'menuBtn') {
9 elements = [ document.getElementById('layout'), document.getElementById('menu'), document.getElementById('menuLink') ]
10 for (const element of elements) {
12 element.classList.add('active')
14 element.classList.remove('active')
17 if (e.target.id == 'menuLink' || e.target.id == 'menuBtn') {
24 document.getElementById('layout').addEventListener('click', toggleMenu)
28 return r.replace(/[\x26\x0A\<>'"]/g,function(r){return"&#"+r.charCodeAt(0)+";"})
31 function getAnchor() {
32 return window.location.hash.slice(1);
35 function drawHeader(project) {
36 var menu_header = document.getElementById('_ui_menu_header')
37 menu_header.innerHTML = project.name
38 document.title = project.name + '/' + project.version
41 function parseJsonQ(url, callback) {
42 var req = new XMLHttpRequest();
44 req.onreadystatechange = function () {
45 if (this.readyState != 4) return;
46 if (this.status != 200 && this.status != 500 && this.status != 404) {
47 setTimeout(parseJsonQ(url, callback),30000);
50 var json = JSON.parse(this.responseText)
54 req.open("GET", url, true);
59 function updateElement(id, value) {
60 var element = document.getElementById("_ui_element_"+id)
62 var ui_class = element.dataset.ui_class;
65 element.innerText = value
75 element.checked = value
79 var subname = '_ui_elpart_'+i+'_'+id
80 var subelement = document.getElementById(subname)
82 subelement.className = "weekday-selected"
84 subelement.className = "weekday"
91 function updateValues(json) {
92 for (var key in json) {
93 var obj = document.getElementById("_ui_element_"+key)
95 updateElement(key, json[key])
97 parameters[key] = json[key]
99 var notification = document.getElementById('_ui_notification');
100 if (parameters['_changed']) {
101 notification.innerHTML = '<input type="button" id="save" value="Сохранить" class="pure-button" onclick="sendAction(\'save\')">'
102 notification.removeAttribute('hidden')
104 notification.innerHTML = ''
105 notification.hidden = true
109 function sendUpdate(id) {
110 var input = document.getElementById('_ui_element_'+id)
111 var ui_class = input.dataset.ui_class;
117 if (input.checkValidity() && input.value != parameters[id]) {
118 parseJsonQ('/config/set?name=' + id + '&value=' + encodeURIComponent(input.value), function(json) {
124 parseJsonQ('/config/set?name=' + id + '&value=' + encodeURIComponent(input.selectedOptions[0].value), function(json) {
129 parseJsonQ('/config/set?name=' + id + '&value=' + (input.checked?'true':'false'), function(json) {
134 parseJsonQ('/config/set?name=' + id + '&value=' + input.dataset.value, function(json) {
141 function sendAction(name, params = {}) {
142 var url = '/action?name=' + name
143 for (var param in params) {
144 url += '&'+param+'='+encodeURIComponent(params[param])
146 parseJsonQ(url, function(json) {
147 if (json.result == 'FAILED') {
158 function showPwd(id) {
159 var x = document.getElementById('_ui_element_' + id)
160 if (x.type === "password") {
167 function openSelect(id) {
168 var selector = document.getElementById('_ui_elemmodal_'+id);
169 selector.removeAttribute("hidden")
172 function closeSelect(id) {
173 var selector = document.getElementById('_ui_elemmodal_'+id);
174 selector.hidden = true
177 function closeMsg() {
178 document.getElementById("_ui_message").hidden = true;
182 var msg = document.getElementById('_ui_message');
183 msg.classList.add("fadeout")
184 msgT = setTimeout(()=> { closeMsg() }, 5000);
187 function openMsg(msgText) {
188 document.getElementById("_ui_message_text").innerText = msgText;
189 document.getElementById("_ui_message").classList.remove("fadeout");
190 document.getElementById("_ui_message").removeAttribute('hidden');
193 window.clearTimeout(msgT);
195 msgT = setTimeout(()=> { fadeMsg(); }, 5000);
198 function selectWiFi(id, ssid) {
200 var x = document.getElementById('_ui_element_' + id)
201 updateElement(x,ssid);
205 function getWiFi(id) {
206 var list = document.getElementById('_ui_elemselect_'+id)
208 var req = new XMLHttpRequest();
210 req.onreadystatechange = function () {
211 if (this.readyState != 4) return;
212 if (this.status != 200 && this.status != 500 && this.status != 404) {
213 setTimeout(getWiFi(id),30000);
216 var json = JSON.parse(this.responseText)
217 var table = '<table cellpadding="5" border="0" align="center"><thead class="table-header"><tr><td>SSID</td><td>BSSID</td><td>RSSI</td><td>Канал</td><td>Защита</td></tr></thead><tbody>'
219 setTimeout(getWiFi(id),5000);
222 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? "Автоматически" : "Не определено";
223 table += '<tr onclick="selectWiFi(\''+id+'\',\''+json[idx].ssid+'\')"><td>'+json[idx].ssid+'</td><td>'+json[idx].bssid+'</td><td>'+json[idx].rssi+'</td><td>'+json[idx].channel+'</td><td>'+encryption+'</td></tr>'
226 table += '</tbody></table>'
227 list.innerHTML = table;
230 req.open("GET", "/wifi/scan", true);
234 function clickDay(id, day) {
235 value = parameters[id].split('')
236 day_value = value[day]
237 day_value = (day_value=='0')?'1':'0'
238 value[day] = day_value
239 element = document.getElementById('_ui_element_' + id)
240 value = value.join('')
241 element.dataset.value = value;
245 function sendTime(id) {
246 var value = document.getElementById('_ui_element_' + id).value
248 var date = new Date(value)
249 var timestamp = Math.floor(date.getTime()/1000);
250 sendAction('time',{"timestamp":timestamp})
254 function downloadFile(url, name) {
255 const a = document.createElement('a')
257 a.download = name?name:url.split('/').pop()
258 document.body.appendChild(a)
260 document.body.removeChild(a)
263 function uploadConfig() {
264 var elem = document.getElementById('_config_file')
266 var data = elem.files[0]
268 fetch('/config/put', {method:'PUT',body:data});
273 function elementHTML(element) {
275 if (parameters[element.id] || !isNaN(parameters[element.id])) {
276 value = parameters[element.id]
277 } else if (element.value) {
278 value = element.value
282 switch (element.type) {
284 return '<div class="pure-u-1 pure-u-md-1-3"><hr></div>'
286 return '<div class="pure-u-1 pure-u-md-1-3"><div align="center"><input type="button" id="'
287 + element.id + '" value="' + encode(element.label) + '" class="pure-button" onclick="sendAction(\'' + element.id + '\')" /></div></div>'
289 return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
290 + '<div class="hinted"><input data-ui_class="password" type="password" id="_ui_element_' + element.id + '" value="' + encode(value)
291 + '" class="pure-u-1" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" />'
292 + '<z class="hint" onclick="showPwd(\'' + element.id + '\')"></z></div></div>'
295 if (element.pattern) {
296 pattern = ' pattern="' + encode(element.pattern) + '"'
298 return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
299 + '<input data-ui_class="input" type="text" id="_ui_element_' + element.id + '" value="' + encode(value) +'"'+ pattern
300 + ' class="pure-u-1" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" /></div>'
303 if (element.pattern) {
304 pattern = ' pattern="' + encode(element.pattern) + '"'
306 return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
307 + '<div class="hinted"><input data-ui_class="input" type="text" id="_ui_element_' + element.id + '" value="' + encode(value) +'"'+ pattern
308 + ' class="pure-u-1" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" />'
309 + '<z class="hint" onclick="openSelect(\'' + element.id+ '\'); getWiFi(\'' + element.id + '\')"></z></div>'
310 + '<div class="modal" id="_ui_elemmodal_' + element.id + '" hidden>'
311 + '<div class="modal-content">'
312 + '<div id="_ui_elemselect_' + element.id + '"></div>'
313 + '<div class="pure-u-1 pure-u-md-1-3"><div align="center"><input type="button" id="_ui_button_'
314 + element.id + '" value="Закрыть" class="pure-button" onclick="closeSelect(\'' + element.id + '\')"></div>'
315 + '</div></div></div></div>'
317 return '<div class="pure-u-1 pure-u-md-1-3"><label class="switch socket" for="_ui_element_' + element.id + '">'
318 + '<input class="switch" data-ui_class="checkbox" type="checkbox" id="_ui_element_' + element.id + '"' + (parameters[element.id]?' checked':'') + ' onchange="sendUpdate(\'' + element.id + '\')" />'
319 + '<span class="switch slider">'+ encode(element.label) + '</span>'
322 var options = '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
323 + '<select class="pure-u-24-24" data-ui_class="select" id="_ui_element_' + element.id + '" onchange="sendUpdate(\'' + element.id + '\')">'
324 for (const option of element.options) {
325 var list_option = '<option value="' + encode(option.value) + '" ';
326 if (option.value == parameters[element.id]) {
327 list_option += 'selected '
329 list_option += '>' + encode(option.text) + '</option>'
330 options += list_option
332 options += '</select></div>'
335 days = '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
336 + '<table data-ui_class="week" data-value="' + value + '" id="_ui_element_' + element.id + '" cellpadding="5" border="0" class="week"><tbody><tr>'
337 for (i=0; i<7; i++) {
338 a_enabled = (value[i] == "1")
339 days = days + '<td><div class="weekday' + (a_enabled?"-selected":"") + '" id="_ui_elpart_'+i+'_'+element.id+'" onclick="clickDay(\'' + element.id + '\', ' + i + ')">'+ daynames[i] + '</div></td>'
341 days += '</tr></tbody></table></div>'
345 now.setMinutes(now.getMinutes() - now.getTimezoneOffset())
346 value = now.toISOString().slice(0, -1);
347 return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
348 +'<input id="_ui_element_'+element.id+'" data-ui_class="timeset" class="inline-input" type="datetime-local" value="'+value+'">'
349 + '<div class="send-button" onclick="sendTime(\''+element.id+'\')">-></div></div>'
351 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>'
353 return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
354 + '<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+'" ':'')
355 + 'id="_ui_element_' + element.id + '" value="' + encode(value) +'"'+ pattern
356 + ' class="pure-u-1" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" /></div>'
358 return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
359 + '<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+'" ':'')
360 + 'id="_ui_element_' + element.id + '" value="' + encode(value) +'"'+ pattern
361 + ' class="pure-u-1" maxlength="99" oninput="sendUpdate(\'' + element.id + '\')" /></div>'
363 return '<div class="pure-u-1 pure-u-md-1-3"><label for="_ui_element_' + element.id + '">' + encode(element.label) + '</label>'
364 + '<div align="center"><input type="button" class="pure-button row-button" value="Сохранить..." onclick="downloadFile(\'/config/get\',\'config.json\')">'
365 + '<label for="_config_file" class="pure-button row-button">Восстановить...</label>'
366 + '<input type="button" class="pure-button row-button" value="Настройки по умолчанию" onclick="confirm(\'Вы точно хотите сбросить настройки?\')?sendAction(\'reset\'):console.log(\'Не надо так не надо...\')">'
367 + '<input type="file" id="_config_file" onchange="uploadConfig()" hidden>'
371 return '<div class="pure-u-1 pure-u-md-1-3"><table class="texttable" cellpadding="5" border="0" align="center"><tbody><tr><td class="value-name" align="right">'
372 + encode(element.label)+ '</td><td id="_ui_element_'
373 + element.id + '" data-ui_class="table" ' + (element.color?'style="color:'+ element.color+'" ':'')+ '>' + encode(value) + '</td></tr></tbody></table></div>'
377 function drawPage(id) {
379 for (const page of pages) {
380 var menu_link = document.getElementById('_ui_pglink_' + page.id)
382 menu_link.classList.remove("pure-menu-selected")
384 menu_link.classList.add("pure-menu-selected")
389 var page_header = document.getElementById("_ui_page_header")
390 page_header.innerHTML = pages[idx].title
391 var page_content = document.getElementById("_ui_page_content");
393 for (const element of pages[idx].elements) {
394 content = content + elementHTML(element) + '\n'
396 page_content.innerHTML = content
397 window.location.hash = id
400 function drawNavigator(project, pages) {
401 var menu = document.getElementById('_ui_menu_list');
403 for (const page of pages) {
404 list = list + '<li id="_ui_pglink_' + page.id
405 + '" class="pure-menu-item"><a class="pure-menu-link" onclick="drawPage(\''+ page.id +'\')" href="#' + page.id + '">'
406 + (page.icon?'<span class="icon">'+page.icon+'</span>':'')
407 + page.title+'</a></li>'
409 menu.innerHTML = list
412 function drawContacts(contacts) {
413 if (!contacts) return;
414 var contact_list = '<hr><h4 class="pure-u1">Контакты</h4>'
415 for (contact of contacts) {
416 const url = new URL(contact)
418 switch (url.protocol) {
421 ref = '<span class="icon"></span>'+url.hostname
424 ref = '<span class="icon"></span>'+url.pathname
427 ref = '<span class="icon"></span>'+url.pathname
428 contact = 'tg://resolve?domain='+url.pathname
431 ref = '<span class="icon"></span>'+url.pathname
433 contact_list += '<a href="'+contact+'">'+ref+'</a>'
435 var footer = document.getElementById('_ui_contacts');
436 footer.innerHTML = contact_list
439 function drawUI(ui) {
440 drawHeader(ui.project)
442 drawNavigator(ui.project, pages)
443 drawContacts(ui.project.contacts)
444 var anchor = getAnchor()
448 drawPage(pages[0].id)
453 parseJsonQ("/ui", drawUI);
459 if (!!window.EventSource) {
460 var source = new EventSource('/events');
461 openMsg('Соединение установлено')
463 source.onerror = function(e) {
464 if (source.readyState == 2) {
465 openMsg('Соединение прервано')
466 setTimeout(initES, 5000);
470 source.addEventListener('update', function(e) {
471 updateValues(JSON.parse(e.data));
473 source.addEventListener('message', function(e) {
481 function drawConfig(cfg) {
486 parseJsonQ("/config/get", drawConfig);