mirror of
https://github.com/IoTManagerProject/IoTManagerWeb.git
synced 2026-03-26 23:12:34 +03:00
364 lines
12 KiB
Svelte
364 lines
12 KiB
Svelte
<script>
|
||
import Card from "../components/Card.svelte";
|
||
import CrossIcon from "../svg/Cross.svelte";
|
||
import OpenIcon from "../svg/Open.svelte";
|
||
import Alarm from "../components/Alarm.svelte";
|
||
import { onMount } from "svelte";
|
||
import Cookies from "js-cookie";
|
||
import * as portal from "../api/portal.js";
|
||
|
||
export let configJson;
|
||
export let widgetsJson;
|
||
export let itemsJson;
|
||
export let scenarioTxt;
|
||
export let userdata;
|
||
|
||
export let show;
|
||
|
||
let itemsJsonBind = 0;
|
||
let configsBind = 0;
|
||
let debug = false;
|
||
|
||
let configurations = null;
|
||
|
||
export let saveConfig = () => {};
|
||
export let rebootEsp = () => {};
|
||
//export let cleanLogs = () => {};
|
||
|
||
let exportJson = {};
|
||
|
||
onMount(async () => {
|
||
await getConfigs();
|
||
});
|
||
|
||
function elementsDropdownChange() {
|
||
for (let i = 0; i < itemsJson.length; i++) {
|
||
let item = Object.assign({}, itemsJson[i]);
|
||
if (itemsJsonBind === item.num) {
|
||
delete item.num;
|
||
delete item.name;
|
||
item.id = item.id + randomInteger(0, 100);
|
||
configJson.push(item);
|
||
configJson = configJson;
|
||
itemsJsonBind = 0;
|
||
if (debug) console.log("[i]", "item added");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
function randomInteger(min, max) {
|
||
// получить случайное число от (min-0.5) до (max+0.5)
|
||
let rand = min - 0.5 + Math.random() * (max - min + 1);
|
||
return Math.round(rand);
|
||
}
|
||
|
||
function deleteLineFromConfig(num) {
|
||
for (let i = 0; i < configJson.length; i++) {
|
||
if (num === i) {
|
||
configJson.splice(i, 1);
|
||
configJson = configJson;
|
||
if (debug) console.log("[i]", "item " + num + " deleted from config");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
$: scenarioTxt, windowHeight();
|
||
|
||
let height;
|
||
|
||
function windowHeight() {
|
||
let scenStr = scenarioTxt;
|
||
height = scenStr.split("\n").length + 1;
|
||
}
|
||
|
||
// Function to download data to a file
|
||
function saveFile2(data, filename, type) {
|
||
var file = new Blob([data], { type: type });
|
||
if (window.navigator.msSaveOrOpenBlob)
|
||
// IE10+
|
||
window.navigator.msSaveOrOpenBlob(file, filename);
|
||
else {
|
||
// Others
|
||
var a = document.createElement("a"),
|
||
url = URL.createObjectURL(file);
|
||
a.href = url;
|
||
a.download = filename;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
|
||
setTimeout(function () {
|
||
document.body.removeChild(a);
|
||
window.URL.revokeObjectURL(url);
|
||
}, 0);
|
||
}
|
||
}
|
||
|
||
function saveFile(data, filename, type) {
|
||
var file = new Blob([data], { type: type });
|
||
if (window.navigator.msSaveOrOpenBlob) {
|
||
window.navigator.msSaveOrOpenBlob(file, filename);
|
||
} else {
|
||
const a = document.createElement("a");
|
||
document.body.appendChild(a);
|
||
const url = window.URL.createObjectURL(file);
|
||
a.href = url;
|
||
a.download = filename;
|
||
a.click();
|
||
setTimeout(() => {
|
||
window.URL.revokeObjectURL(url);
|
||
document.body.removeChild(a);
|
||
}, 0);
|
||
}
|
||
}
|
||
|
||
const syntaxHighlight = (json) => {
|
||
try {
|
||
json = JSON.stringify(JSON.parse(json), null, 4);
|
||
} catch (e) {
|
||
return json;
|
||
}
|
||
json = json.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||
json = json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
|
||
return match;
|
||
});
|
||
return json;
|
||
};
|
||
|
||
function createExportFile() {
|
||
exportJson.mark = "iotm";
|
||
exportJson.config = configJson;
|
||
|
||
let exportAsText = syntaxHighlight(JSON.stringify(exportJson));
|
||
|
||
exportAsText = exportAsText + "\n\nscenario=>" + scenarioTxt;
|
||
saveFile(exportAsText, "export.json", "application/json");
|
||
}
|
||
|
||
let template = null;
|
||
let files = null;
|
||
|
||
const alertErr = "Файл не является файлом конфигурации";
|
||
const alertOk = "Применить конфигурацию?\nне забудьте нажать кнопку 'сохранить на устройстве'";
|
||
|
||
$: if (files) {
|
||
const fileText = files[0].text();
|
||
fileText.then((text) => {
|
||
template = text;
|
||
|
||
if (!template.includes("scenario=>")) {
|
||
window.alert(alertErr);
|
||
return;
|
||
}
|
||
|
||
let jsonPart = selectToMarker(template, "scenario=>");
|
||
let txtPart = deleteBeforeDelimiter(template, "scenario=>");
|
||
|
||
if (!IsJsonParse(jsonPart)) {
|
||
window.alert(alertErr);
|
||
return;
|
||
}
|
||
|
||
let json = JSON.parse(jsonPart);
|
||
|
||
if (json.mark !== "iotm") {
|
||
window.alert(alertErr);
|
||
return;
|
||
}
|
||
|
||
if (window.confirm(alertOk)) {
|
||
configJson = [];
|
||
scenarioTxt = "";
|
||
configJson = json.config;
|
||
scenarioTxt = txtPart;
|
||
console.log("config updated");
|
||
}
|
||
});
|
||
files = null;
|
||
}
|
||
|
||
function reset() {
|
||
files = null;
|
||
document.getElementById("formFile").value = "";
|
||
}
|
||
|
||
function IsJsonParse(str) {
|
||
try {
|
||
JSON.parse(str);
|
||
} catch (e) {
|
||
if (debug) console.log("[e]", "json parce error: ", str);
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function selectToMarker(str, found) {
|
||
let p = str.indexOf(found);
|
||
return str.substring(0, p);
|
||
}
|
||
|
||
function deleteBeforeDelimiter(str, found) {
|
||
let p = str.indexOf(found) + found.length;
|
||
return str.substring(p);
|
||
}
|
||
|
||
export let moduleOrder = (id, key, value) => {};
|
||
|
||
const makePost = async () => {
|
||
const body = {
|
||
category: "",
|
||
topic: { ru: "", en: "" },
|
||
text: { ru: "", en: "" },
|
||
config: configJson,
|
||
scenario: scenarioTxt,
|
||
gallery: [],
|
||
type: "iotmpost",
|
||
username: userdata.username,
|
||
};
|
||
const JWT = Cookies.get("token_iotm2");
|
||
const res = await portal.addConfiguration(JWT, body);
|
||
if (res.ok && res.data && res.data.result) {
|
||
console.log(res.data.result);
|
||
if (res.data.result.acknowledged) {
|
||
window.open(portal.BASE + "/configs?id=" + res.data.result.insertedId + "&token=" + JWT, "_blank");
|
||
}
|
||
errors = [{ msg: "ok_success" }];
|
||
} else if (res.errors) {
|
||
errors = res.errors;
|
||
}
|
||
};
|
||
|
||
const getConfigs = async () => {
|
||
const JWT = Cookies.get("token_iotm2");
|
||
const res = await portal.getConfigurations(JWT);
|
||
if (res.ok && res.configurations != null) {
|
||
configurations = res.configurations;
|
||
} else {
|
||
console.log("error", "getConfigurations");
|
||
}
|
||
};
|
||
|
||
function setConfScen() {
|
||
configJson = configurations[configsBind].config;
|
||
scenarioTxt = configurations[configsBind].scenario;
|
||
}
|
||
</script>
|
||
|
||
{#if show}
|
||
<div class="my-4">
|
||
<div class="grd-2col1">
|
||
<Card title="Конфигуратор">
|
||
<div class="grd-2col2">
|
||
<select class="slct-lg" bind:value={itemsJsonBind} on:change={() => elementsDropdownChange()}>
|
||
{#each itemsJson as item}
|
||
{#if item.header}
|
||
<optgroup label={item.header} />
|
||
{/if}
|
||
{#if !item.header}
|
||
<option value={item.num}>
|
||
{item.name}
|
||
</option>
|
||
{/if}
|
||
{/each}
|
||
</select>
|
||
<select class="slct-lg" bind:value={configsBind} on:change={() => setConfScen()}>
|
||
{#if configurations}
|
||
{#each configurations as config, i}
|
||
<option value={i}>
|
||
{config.topic["ru"]}
|
||
</option>
|
||
{/each}
|
||
{/if}
|
||
</select>
|
||
</div>
|
||
<table class="tbl">
|
||
<thead class="bg-gray-100">
|
||
<tr class="txt-sz txt-pad">
|
||
<th class="tbl-hd">Тип</th>
|
||
<th class="tbl-hd">Id</th>
|
||
<th class="tbl-hd">Виджет</th>
|
||
<th class="tbl-hd">Вкладка</th>
|
||
<th class="tbl-hd">Название</th>
|
||
<th class="tbl-hd w-7" />
|
||
<th class="tbl-hd w-7" />
|
||
</tr>
|
||
</thead>
|
||
<tbody class="bg-white">
|
||
{#each configJson as element, i}
|
||
<tr class="txt-sz txt-pad align-middle">
|
||
<td class="tbl-bdy-lg">{element.subtype}</td>
|
||
<td class="tbl-bdy-lg"><input bind:value={element.id} class="ipt-lg w-full" type="text" /></td>
|
||
<td class="tbl-bdy-lg"
|
||
><select bind:value={element.widget} class="ipt-lg w-full">
|
||
{#each widgetsJson as select}
|
||
<option value={select.name}>
|
||
{select.label}
|
||
</option>
|
||
{/each}
|
||
</select></td>
|
||
<td class="tbl-bdy-lg"><input bind:value={element.page} class="ipt-lg w-full" type="text" /></td>
|
||
<td class="tbl-bdy-lg"><input bind:value={element.descr} class="ipt-lg w-full" type="text" /></td>
|
||
<td class="tbl-bdy-lg"><OpenIcon click={() => (element.show = !element.show)} /></td>
|
||
<td class="tbl-bdy-lg"><CrossIcon click={() => deleteLineFromConfig(i)} /></td>
|
||
</tr>
|
||
{#if element.show}
|
||
{#each Object.entries(element) as [key, param]}
|
||
{#if key != "type" && key != "subtype" && key != "id" && key != "widget" && key != "page" && key != "descr" && key != "show"}
|
||
<tr class="txt-sz txt-pad">
|
||
<td />
|
||
<td />
|
||
<td />
|
||
{#if key.startsWith("btn")}
|
||
<td class="tbl-bdy-sm text-right">
|
||
<button on:click={() => moduleOrder(element.id, key.substring(4), element[key])} class="h-3 sm:h-6 md:h-6 lg:h-6 xl:h-6 2xl:h-6 w-auto bg-blue-100 inline-flex items-center border border-gray-300 hover:bg-blue-200">{key.substring(4)}</button>
|
||
</td>
|
||
{#if element[key] != "nil"}
|
||
<td class="tbl-bdy-sm text-center">
|
||
<input bind:value={element[key]} class="ipt-sm w-full text-sm" type="text" />
|
||
</td>
|
||
{/if}
|
||
{:else}
|
||
<td class="tbl-bdy-sm text-right">
|
||
<p class="txt-ita">{key}</p>
|
||
</td>
|
||
<td class="tbl-bdy-sm text-center">
|
||
<input bind:value={element[key]} class="ipt-sm w-full text-sm" type="text" />
|
||
</td>
|
||
{/if}
|
||
</tr>
|
||
{/if}
|
||
{/each}
|
||
<!--<br />-->
|
||
{/if}
|
||
{/each}
|
||
</tbody>
|
||
</table>
|
||
</Card>
|
||
|
||
<Card title="Сценарии">
|
||
<textarea bind:value={scenarioTxt} rows={height} class="px-2 bg-gray-50 border-2 border-gray-200 rounded text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-indigo-500 w-full" />
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
<div class="grd-1col1">
|
||
<Card>
|
||
<div class="grd-2col1">
|
||
<button class="btn-lg" on:click={() => saveConfig()}>{"Сохранить на устройстве"}</button>
|
||
<button class="btn-lg" on:click={() => rebootEsp()}>{"Перезагрузить устройство"}</button>
|
||
<button class="btn-lg" on:click={() => createExportFile()}>{"Экспорт конфигурации"}</button>
|
||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||
<label on:click={() => reset()} class="btn-lg cursor-pointer select-none">
|
||
<input bind:files={files} accept="application/JSON" type="file" id="formFile" />
|
||
{"Импорт конфигурации"}
|
||
</label>
|
||
</div>
|
||
{#if userdata}
|
||
<button class="btn-lg mt-4" on:click={() => makePost()}>{"Опубликовать конфигурацию на портале"}</button>
|
||
{/if}
|
||
</Card>
|
||
</div>
|
||
{:else}
|
||
<Alarm title="Загрузка..." />
|
||
{/if}
|