add auto man mode

This commit is contained in:
Dmitry Borisenko
2023-06-08 22:37:43 +02:00
parent 05548c091b
commit 10cb821fd6
5 changed files with 150 additions and 80 deletions

View File

@@ -2,7 +2,8 @@
/* /*
Svelte IoT Manager app Svelte IoT Manager app
created by Dmitry Borisenko created by Dmitry Borisenko
Sandgasse 46/4, Vienna 1190, Austria Vienna, Austria 1030, Juchgasse 5/17
+43 67761588253
*/ */
//******************************************************import section*********************************************************/ //******************************************************import section*********************************************************/
@@ -34,7 +35,7 @@
const debug = true; const debug = true;
const LOG_MAX_MESSAGES = 100; const LOG_MAX_MESSAGES = 100;
const reconnectTimeout = 120000; //период проверки соединения с устройством const reconnectTimeout = 120000; //период проверки соединения с устройством
const waitingAckTimeout = 18000; //время ожидания ответа от устройства const waitingAckTimeout = 12000; //время ожидания ответа от устройства
const rebootingTimeout = 20000; const rebootingTimeout = 20000;
const updatingTimeout = 130000; const updatingTimeout = 130000;
let opened = false; let opened = false;
@@ -45,12 +46,13 @@
//****************************************************variable section**********************************************************/ //****************************************************variable section**********************************************************/
//******************************************************************************************************************************/ //******************************************************************************************************************************/
let myip = document.location.hostname; let myip = document.location.hostname;
if (devMode) myip = "192.168.88.238"; if (devMode) myip = "192.168.1.238";
//Flags //Flags
let firstDevListRequest = true; let firstDevListRequest = true;
let showInput = false; let showInput = false;
let showModalFlag = false; let showModalFlag = false;
let showDropdown = true;
let rebootingUpdatingInProgress = false; let rebootingUpdatingInProgress = false;
let myTimeout = undefined; let myTimeout = undefined;
@@ -125,6 +127,8 @@
let newDevice = {}; let newDevice = {};
let coreMessages = []; let coreMessages = [];
let timeOut;
let parcedEvent = 0; let parcedEvent = 0;
//***********************************************************navigation********************************************************/ //***********************************************************navigation********************************************************/
@@ -144,6 +148,13 @@
if (currentPageName != "/dev") { if (currentPageName != "/dev") {
clearData(); clearData();
} }
if (currentPageName === "/list" || "/") {
showDropdown = false;
} else {
showDropdown = true;
}
currentPageName = currentPageName + "|"; currentPageName = currentPageName + "|";
console.log("[i]", "user on page:", currentPageName); console.log("[i]", "user on page:", currentPageName);
@@ -164,10 +175,13 @@
onMount(async () => { onMount(async () => {
console.log("[i]", "mounted"); console.log("[i]", "mounted");
whenDeviceListWasUpdated(); whenDeviceListWasUpdated();
//флаг первого запроса списка устройств
firstDevListRequest = true; firstDevListRequest = true;
//вначале подключимся к известному нам ip этого устройства
connectToAllDevices(); connectToAllDevices();
wsTestMsgTask(); wsTestMsgTask();
//sortingLayout(); //sortingLayout();
ticker();
}); });
//****************************************************web sockets section******************************************************/ //****************************************************web sockets section******************************************************/
@@ -181,6 +195,7 @@
if (!device.status) { if (!device.status) {
wsConnect(ws); wsConnect(ws);
wsEventAdd(ws); wsEventAdd(ws);
} else {
} }
ws++; ws++;
}); });
@@ -592,14 +607,18 @@
deviceList = incDeviceList; deviceList = incDeviceList;
deviceList[0].status = true; deviceList[0].status = true;
} else { } else {
//при последующих прилетах списка устройств мы переписываем в массиве только то что изменилось
deviceList = combineArrays(deviceList, incDeviceList); deviceList = combineArrays(deviceList, incDeviceList);
} }
//deviceList = incDeviceList;
firstDevListRequest = false; firstDevListRequest = false;
deviceList = deviceList; deviceList = deviceList;
parsed.deviceListJson = true; parsed.deviceListJson = true;
if (blobDebug) console.log("[✔]", "deviceList parced"); if (blobDebug) console.log("[✔]", "deviceList parced");
onParced(); onParced();
whenDeviceListWasUpdated(); whenDeviceListWasUpdated();
console.log("[✔]", deviceList);
//затем подключимся к всему полученному списку устройств
connectToAllDevices(); connectToAllDevices();
} }
@@ -746,18 +765,27 @@
function saveSett() { function saveSett() {
var size = Object.keys(settingsJson).length; var size = Object.keys(settingsJson).length;
//console.log("[i]", "settingsJson length: " + size); console.log("[i]", "settingsJson length: " + size);
if (size > 5) { if (size > 5) {
jsonArrWrite(deviceList, "ip", getIP(selectedWs), "name", settingsJson.name); jsonArrWrite(deviceList, "ip", getIP(selectedWs), "name", settingsJson.name);
deviceList = deviceList; deviceList = deviceList;
wsSendMsg(selectedWs, "/sgnittes|" + JSON.stringify(settingsJson)); wsSendMsg(selectedWs, "/sgnittes|" + JSON.stringify(settingsJson));
} else { } else {
window.alert("Ошибка"); window.alert("Ошибка размера settingsJson (возможно не был передан странице)");
} }
clearData(); clearData();
sendCurrentPageName(); sendCurrentPageName();
} }
function saveList() {
//при сохранении списка в память необходимо удалить все статусы
let devListForSave = Object.assign([], deviceList);
for (let i = 0; i < devListForSave.length; i++) {
delete devListForSave[i].status;
}
wsSendMsg(selectedWs, "/tsil|" + JSON.stringify(devListForSave));
}
function cleanLogs() { function cleanLogs() {
wsSendMsg(selectedWs, "/clean|"); wsSendMsg(selectedWs, "/clean|");
} }
@@ -859,6 +887,16 @@
layoutJson = []; layoutJson = [];
paramsJson = {}; //? paramsJson = {}; //?
//deviceList = [
// {
// name: "--",
// id: "--",
// ip: myip,
// ws: 0,
// status: false,
// },
//];
for (const [key, value] of Object.entries(pageReady)) { for (const [key, value] of Object.entries(pageReady)) {
pageReady[key] = false; pageReady[key] = false;
} }
@@ -877,10 +915,10 @@
} }
function clearFlags() { function clearFlags() {
for (let i = 0; i < deviceList.length; i++) { //for (let i = 0; i < deviceList.length; i++) {
deviceList[i].pp = false; //deviceList[i].pp = false;
deviceList[i].lp = false; //deviceList[i].lp = false;
} //}
} }
function wsPush(ws, topic, status) { function wsPush(ws, topic, status) {
@@ -895,6 +933,7 @@
setTimeout(wsTestMsgTask, reconnectTimeout); setTimeout(wsTestMsgTask, reconnectTimeout);
if (!rebootingUpdatingInProgress) { if (!rebootingUpdatingInProgress) {
if (debug) console.log("[i]", "----timer tick----"); if (debug) console.log("[i]", "----timer tick----");
if (!firstTime) { if (!firstTime) {
deviceList.forEach((device) => { deviceList.forEach((device) => {
if (!getDeviceStatus(device.ws)) { if (!getDeviceStatus(device.ws)) {
@@ -976,6 +1015,7 @@
} }
} }
//функция которая записывает в переменную данные выбранного юзером устройства
function getSelectedDeviceData(ws) { function getSelectedDeviceData(ws) {
for (let i = 0; i < deviceList.length; i++) { for (let i = 0; i < deviceList.length; i++) {
let device = deviceList[i]; let device = deviceList[i];
@@ -1265,15 +1305,18 @@
{/if} {/if}
<header class="h-10 w-full bg-gray-100 overflow-auto shadow-md"> <header class="h-10 w-full bg-gray-100 overflow-auto shadow-md">
<div class="flex content-center items-center justify-end"> <div class="flex content-center items-center justify-end">
<div class="px-15 py-1"> {#if showDropdown}
<select class="border border-indigo-500 border-4" bind:value={selectedWs} on:change={() => devicesDropdownChange()}> <div class="px-15 py-1">
{#each deviceList as device} <select class="border border-indigo-500 border-4" bind:value={selectedWs} on:change={() => devicesDropdownChange()}>
<option value={device.ws}> {#each deviceList as device}
{device.name} <option value={device.ws}>
</option> {device.name}
{/each} </option>
</select> {/each}
</div> </select>
</div>
{/if}
<!--<div class="pl-4 pr-1 py-1">--> <!--<div class="pl-4 pr-1 py-1">-->
<!--<BookIcon color={socketConnected === true ? "text-green-500" : "text-red-500"} />--> <!--<BookIcon color={socketConnected === true ? "text-green-500" : "text-red-500"} />-->
<!--</div>--> <!--</div>-->
@@ -1324,22 +1367,22 @@
<DashboardPage show={pageReady.dash} layoutJson={layoutJson} pages={pages} wsPush={(ws, topic, status) => wsPush(ws, topic, status)} /> <DashboardPage show={pageReady.dash} layoutJson={layoutJson} pages={pages} wsPush={(ws, topic, status) => wsPush(ws, topic, status)} />
</Route> </Route>
<Route path="/config"> <Route path="/config">
<ConfigPage show={pageReady.config} bind:configJson bind:scenarioTxt widgetsJson={widgetsJson} itemsJson={itemsJson} saveConfig={() => saveConfig()} cleanLogs={() => cleanLogs()} rebootEsp={() => rebootEsp()} moduleOrder={(id, key, value) => moduleOrder(id, key, value)} /> <ConfigPage show={pageReady.config} bind:configJson={configJson} bind:scenarioTxt={scenarioTxt} widgetsJson={widgetsJson} itemsJson={itemsJson} saveConfig={() => saveConfig()} cleanLogs={() => cleanLogs()} rebootEsp={() => rebootEsp()} moduleOrder={(id, key, value) => moduleOrder(id, key, value)} />
</Route> </Route>
<Route path="/connection"> <Route path="/connection">
<ConnectionPage show={pageReady.connection} rebootEsp={() => rebootEsp()} ssidClick={() => ssidClick()} saveSett={() => saveSett()} saveMqtt={() => saveMqtt()} settingsJson={settingsJson} errorsJson={errorsJson} ssidJson={ssidJson} /> <ConnectionPage show={pageReady.connection} rebootEsp={() => rebootEsp()} ssidClick={() => ssidClick()} saveSett={() => saveSett()} saveMqtt={() => saveMqtt()} settingsJson={settingsJson} errorsJson={errorsJson} ssidJson={ssidJson} />
</Route> </Route>
<Route path="/list"> <Route path="/list">
<ListPage show={pageReady.list} deviceList={deviceList} showInput={showInput} addDevInList={() => addDevInList()} newDevice={newDevice} sendToAllDevices={(msg) => sendToAllDevices(msg)} /> <ListPage show={pageReady.list} deviceList={deviceList} settingsJson={settingsJson} showInput={showInput} addDevInList={() => addDevInList()} newDevice={newDevice} sendToAllDevices={(msg) => sendToAllDevices(msg)} saveList={() => saveList()} />
</Route> </Route>
<Route path="/system"> <Route path="/system">
<SystemPage show={pageReady.system} errorsJson={errorsJson} settingsJson={settingsJson} saveSett={() => saveSett()} rebootEsp={() => rebootEsp()} cleanLogs={() => cleanLogs()} cancelAlarm={(alarmKey) => cancelAlarm(alarmKey)} versionsList={versionsList} bind:choosingVersion startUpdate={() => startUpdate()} coreMessages={coreMessages} /> <SystemPage show={pageReady.system} errorsJson={errorsJson} settingsJson={settingsJson} saveSett={() => saveSett()} rebootEsp={() => rebootEsp()} cleanLogs={() => cleanLogs()} cancelAlarm={(alarmKey) => cancelAlarm(alarmKey)} versionsList={versionsList} bind:choosingVersion={choosingVersion} startUpdate={() => startUpdate()} coreMessages={coreMessages} />
</Route> </Route>
{#if devMode} {#if devMode}
<Route path="/dev"> <Route path="/dev">
<Card title="Кнопка"> <!--<Card title="Кнопка">
<button class="btn-lg" on:click={() => test()}>{"Тест"}</button> <button class="btn-lg" on:click={() => test()}>{"Тест"}</button>
</Card> </Card>-->
<DevPage show={pageReady.dev} layoutJson={layoutJson} errorsJson={errorsJson} settingsJson={settingsJson} configJson={configJson} itemsJson={itemsJson} paramsJson={paramsJson} /> <DevPage show={pageReady.dev} layoutJson={layoutJson} errorsJson={errorsJson} settingsJson={settingsJson} configJson={configJson} itemsJson={itemsJson} paramsJson={paramsJson} />
</Route> </Route>
{/if} {/if}
@@ -1348,7 +1391,7 @@
</ul> </ul>
</main> </main>
<footer class="h-4 bg-gray-100 border-gray-200 shadow-lg"> <footer class="h-4 bg-gray-100 border-gray-300 shadow-lg">
<div class="flex justify-center content-center text-xxs text-gray-500">Developed by Dmitry Borisenko</div> <div class="flex justify-center content-center text-xxs text-gray-500">Developed by Dmitry Borisenko</div>
</footer> </footer>
</div> </div>

View File

@@ -35,38 +35,39 @@
</script> </script>
{#if show} {#if show}
<div class="grd-1col1 animate-pulse">
{#if empty}
<Card title={"Ваша панель управления пуста, вначале добавьте новые элементы в конфигураторе!"} />
{/if}
</div>
<div class="my-4"> <div class="my-4">
<div class="grd-3col1"> <div class="grd-1col1 animate-pulse">
{#each pages as pagesName, p} {#if empty}
<Card title={pagesName.page}> <Card title={"Ваша панель управления пуста, вначале добавьте новые элементы в конфигураторе!"} />
{#each layoutJson as widget, l} {/if}
{#if widget.page === pagesName.page} </div>
{#if widget.widget === "input"}
<Input bind:value={widget.status} widget={widget} wsPush={(ws, topic, status) => wsPush(ws, topic, status)} /> <div class="grd-3col1">
{#each pages as pagesName, p}
<Card title={pagesName.page}>
{#each layoutJson as widget, l}
{#if widget.page === pagesName.page}
{#if widget.widget === "input"}
<Input bind:value={widget.status} widget={widget} wsPush={(ws, topic, status) => wsPush(ws, topic, status)} />
{/if}
{#if widget.widget === "toggle"}
<Toggle bind:value={widget.status} widget={widget} wsPush={(ws, topic, status) => wsPush(ws, topic, status)} />
{/if}
{#if widget.widget === "anydata"}
<Anydata bind:value={widget.status} widget={widget} />
{/if}
{#if widget.widget === "range"}
<Range bind:value={widget.status} widget={widget} wsPush={(ws, topic, status) => wsPush(ws, topic, status)} />
{/if}
{#if widget.widget === "chart"}
<Chart bind:value={widget.status} widget={widget} />
{/if}
{/if} {/if}
{#if widget.widget === "toggle"} {/each}
<Toggle bind:value={widget.status} widget={widget} wsPush={(ws, topic, status) => wsPush(ws, topic, status)} /> </Card>
{/if} {/each}
{#if widget.widget === "anydata"} </div>
<Anydata bind:value={widget.status} widget={widget} />
{/if}
{#if widget.widget === "range"}
<Range bind:value={widget.status} widget={widget} wsPush={(ws, topic, status) => wsPush(ws, topic, status)} />
{/if}
{#if widget.widget === "chart"}
<Chart bind:value={widget.status} widget={widget} />
{/if}
{/if}
{/each}
</Card>
{/each}
</div> </div>
</div>
{:else} {:else}
<Alarm title="Загрузка..." /> <Alarm title="Загрузка..." />
{/if} {/if}

View File

@@ -26,25 +26,27 @@
</script> </script>
{#if show} {#if show}
<div class="grd-3col1"> <div class="my-4">
<Card title="layoutJson"> <div class="grd-3col1">
<textarea on:input={layoutJson} rows="23" class="w-full" id="1">{syntaxHighlight(JSON.stringify(layoutJson))}</textarea> <Card title="layoutJson">
</Card> <textarea on:input={layoutJson} rows="23" class="w-full" id="1">{syntaxHighlight(JSON.stringify(layoutJson))}</textarea>
<Card title="paramsJson"> </Card>
<textarea on:input={paramsJson} rows="23" class="w-full" id="4">{syntaxHighlight(JSON.stringify(paramsJson))}</textarea> <Card title="paramsJson">
</Card> <textarea on:input={paramsJson} rows="23" class="w-full" id="4">{syntaxHighlight(JSON.stringify(paramsJson))}</textarea>
<Card title="errorsJson"> </Card>
<textarea on:input={errorsJson} rows="23" class="w-full" id="2">{syntaxHighlight(JSON.stringify(errorsJson))}</textarea> <Card title="errorsJson">
</Card> <textarea on:input={errorsJson} rows="23" class="w-full" id="2">{syntaxHighlight(JSON.stringify(errorsJson))}</textarea>
<Card title="settingsJson"> </Card>
<textarea on:input={settingsJson} rows="23" class="w-full" id="3">{syntaxHighlight(JSON.stringify(settingsJson))}</textarea> <Card title="settingsJson">
</Card> <textarea on:input={settingsJson} rows="23" class="w-full" id="3">{syntaxHighlight(JSON.stringify(settingsJson))}</textarea>
<Card title="configJson"> </Card>
<textarea on:input={configJson} rows="23" class="w-full" id="3">{syntaxHighlight(JSON.stringify(configJson))}</textarea> <Card title="configJson">
</Card> <textarea on:input={configJson} rows="23" class="w-full" id="3">{syntaxHighlight(JSON.stringify(configJson))}</textarea>
<Card title="itemsJson"> </Card>
<textarea on:input={itemsJson} rows="23" class="w-full" id="4">{syntaxHighlight(JSON.stringify(itemsJson))}</textarea> <Card title="itemsJson">
</Card> <textarea on:input={itemsJson} rows="23" class="w-full" id="4">{syntaxHighlight(JSON.stringify(itemsJson))}</textarea>
</Card>
</div>
</div> </div>
{:else} {:else}
<Alarm title="Загрузка..." /> <Alarm title="Загрузка..." />

View File

@@ -10,8 +10,10 @@
export let deviceList; export let deviceList;
export let showInput; export let showInput;
export let newDevice = {}; export let newDevice = {};
export let settingsJson;
export let addDevInList = () => {}; export let addDevInList = () => {};
export let saveList = () => {};
export let sendToAllDevices = (msg) => {}; export let sendToAllDevices = (msg) => {};
@@ -32,7 +34,7 @@
{#if show} {#if show}
<div class="my-4"> <div class="my-4">
<div class="grd-1col1"> <div class="grd-1col1">
<Card title={"Список устройств"}> <Card title={settingsJson.udps ? "Список устройств (авто режим)" : "Список устройств (ручной режим)"}>
<table class="tbl"> <table class="tbl">
<thead class="bg-gray-100"> <thead class="bg-gray-100">
<tr class="txt-sz txt-pad"> <tr class="txt-sz txt-pad">
@@ -52,7 +54,9 @@
<td class="tbl-bdy-lg ipt-lg w-full">{device.id}</td> <td class="tbl-bdy-lg ipt-lg w-full">{device.id}</td>
<td class="tbl-bdy-lg ipt-lg w-full {device.status ? 'bg-green-50' : 'bg-red-50'}">{device.status ? "online" : "offline"}</td> <td class="tbl-bdy-lg ipt-lg w-full {device.status ? 'bg-green-50' : 'bg-red-50'}">{device.status ? "online" : "offline"}</td>
<td class="tbl-bdy-lg ipt-lg w-full">{device.ping ? device.ping : "-"}</td> <td class="tbl-bdy-lg ipt-lg w-full">{device.ping ? device.ping : "-"}</td>
<td class="tbl-bdy-lg"><CrossIcon click={() => deleteLineFromDevlist(i)} /></td> {#if i > 0}
<td class="tbl-bdy-lg"><CrossIcon click={() => deleteLineFromDevlist(i)} /></td>
{/if}
</tr> </tr>
{/each} {/each}
{#if showInput} {#if showInput}
@@ -65,14 +69,17 @@
{/if} {/if}
</tbody> </tbody>
</table> </table>
<div class="grd-2col1"> <div class="grd-3col1">
<button class="btn-lg" on:click={() => ((showInput = !showInput), addDevInList())}>{showInput ? "Сохранить" : "Добавить устройство"}</button> {#if !settingsJson.udps}
<button class="btn-lg" on:click={() => ((showInput = !showInput), addDevInList())}>{showInput ? "Добавить" : "Добавить устройство"}</button>
{/if}
<button class="btn-lg" on:click={() => saveList()}>{"Сохранить список"}</button>
<button class="btn-lg" on:click={(msg) => sendToAllDevices("/reboot|")}>{"Перезагрузить все устройства"}</button> <button class="btn-lg" on:click={(msg) => sendToAllDevices("/reboot|")}>{"Перезагрузить все устройства"}</button>
</div> </div>
</Card> </Card>
</div> </div>
<Alarm> <Alarm>
<p>Прошитые прошивкой IoT Manager устройства появятся в списке автоматически в течении минуты. Для обновления названий устройств нужно обновить страницу. Устройства должны быть подключены к одному wifi роутеру.</p> <p>Авто режим - список создается автоматически, можно нажать кнопку "сохранить список" что бы использовать его потом в ручном режиме. Ручной режим - используется сохраненный список, возможно ручное добавление удаление устройств. Для переключения режима перейдите на страницу "системные"</p>
</Alarm> </Alarm>
</div> </div>
{:else} {:else}

View File

@@ -334,7 +334,7 @@
<div class="relative"> <div class="relative">
<input bind:checked={settingsJson.log} on:change={() => (paramsBeenChanged = true)} id="log" type="checkbox" class="sr-only" /> <input bind:checked={settingsJson.log} on:change={() => (paramsBeenChanged = true)} id="log" type="checkbox" class="sr-only" />
<div class="block {settingsJson.log ? 'bg-blue-600' : 'bg-gray-600'} w-10 h-6 rounded-full shadow-lg" /> <div class="block {settingsJson.log ? 'bg-blue-600' : 'bg-gray-600'} w-10 h-6 rounded-full shadow-lg" />
<div class="dot bg-gray-100 absolute left-1 top-1 w-4 h-4 rounded-full transition shadow-lg" /> <div class="dot bg-gray-100 absolute left-1 top-1 w-4 h-4 rounded-full transition shadow-lg" />
</div> </div>
</label> </label>
</div> </div>
@@ -350,7 +350,7 @@
<div class="relative"> <div class="relative">
<input bind:checked={settingsJson.mqttin} on:change={() => (reboot = true)} id="mqtt" type="checkbox" class="sr-only" /> <input bind:checked={settingsJson.mqttin} on:change={() => (reboot = true)} id="mqtt" type="checkbox" class="sr-only" />
<div class="block {settingsJson.mqttin ? 'bg-blue-600' : 'bg-gray-600'} w-10 h-6 rounded-full shadow-lg" /> <div class="block {settingsJson.mqttin ? 'bg-blue-600' : 'bg-gray-600'} w-10 h-6 rounded-full shadow-lg" />
<div class="dot bg-gray-100 absolute left-1 top-1 w-4 h-4 rounded-full transition shadow-lg" /> <div class="dot bg-gray-100 absolute left-1 top-1 w-4 h-4 rounded-full transition shadow-lg" />
</div> </div>
</label> </label>
</div> </div>
@@ -367,11 +367,12 @@
<div class="relative"> <div class="relative">
<input bind:checked={settingsJson.i2c} on:change={() => (reboot = true)} id="i2c" type="checkbox" class="sr-only" /> <input bind:checked={settingsJson.i2c} on:change={() => (reboot = true)} id="i2c" type="checkbox" class="sr-only" />
<div class="block {settingsJson.i2c ? 'bg-blue-600' : 'bg-gray-600'} w-10 h-6 rounded-full shadow-lg" /> <div class="block {settingsJson.i2c ? 'bg-blue-600' : 'bg-gray-600'} w-10 h-6 rounded-full shadow-lg" />
<div class="dot bg-gray-100 absolute left-1 top-1 w-4 h-4 rounded-full transition shadow-lg" /> <div class="dot bg-gray-100 absolute left-1 top-1 w-4 h-4 rounded-full transition shadow-lg" />
</div> </div>
</label> </label>
</div> </div>
</div> </div>
{#if settingsJson.i2c === true} {#if settingsJson.i2c === true}
<div class="flex mb-2 h-6 items-center"> <div class="flex mb-2 h-6 items-center">
<div class="w-2/3"> <div class="w-2/3">
@@ -399,6 +400,22 @@
</div> </div>
{/if} {/if}
<!--Dev list mode-->
<div class="flex mb-2 h-6 items-center">
<div class="w-2/3">
<p class="pr-4 text-gray-500 font-bold text-sm truncate">Автоматический поиск устройств по UDP</p>
</div>
<div class="flex justify-center w-1/3">
<label for="udps" class="items-center cursor-pointer">
<div class="relative">
<input bind:checked={settingsJson.udps} on:change={() => (reboot = true)} id="udps" type="checkbox" class="sr-only" />
<div class="block {settingsJson.udps ? 'bg-blue-600' : 'bg-gray-600'} w-10 h-6 rounded-full shadow-lg" />
<div class="dot bg-gray-100 absolute left-1 top-1 w-4 h-4 rounded-full transition shadow-lg" />
</div>
</label>
</div>
</div>
<!--control--> <!--control-->
<!--<div class="grd-2col1">--> <!--<div class="grd-2col1">-->
{#if paramsBeenChanged} {#if paramsBeenChanged}