mirror of
https://github.com/IoTManagerProject/IoTManagerWeb.git
synced 2026-03-26 15:02:21 +03:00
working version
This commit is contained in:
11
.cursor/plans/IoTManagerWeb.code-workspace
Normal file
11
.cursor/plans/IoTManagerWeb.code-workspace
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "../.."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../../../IoTManager"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@
|
|||||||
.venv-mock/
|
.venv-mock/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
_backup_refactor/
|
||||||
|
|||||||
38
.vscode/tasks.json
vendored
Normal file
38
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Run frontend (dev)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "npm run dev",
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"env": {}
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"panel": "new"
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Run scripts (mock backend)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "if [ -d .venv-mock ]; then .venv-mock/bin/python scripts/mock_backend.py --host 0.0.0.0 --ws-port 81 --http-port 8081; else python3 -m venv .venv-mock && .venv-mock/bin/pip install -r scripts/requirements-mock.txt && .venv-mock/bin/python scripts/mock_backend.py --host 0.0.0.0 --ws-port 81 --http-port 8081; fi",
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"env": {}
|
||||||
|
},
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"panel": "new"
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
758
src/App.svelte
758
src/App.svelte
@@ -36,7 +36,16 @@
|
|||||||
//import LogPage from "./pages/Log.svelte";
|
//import LogPage from "./pages/Log.svelte";
|
||||||
//import AboutPage from "./pages/About.svelte";
|
//import AboutPage from "./pages/About.svelte";
|
||||||
|
|
||||||
import CloudIcon from "./svg/Cloud.svelte";
|
import * as portal from "./api/portal.js";
|
||||||
|
import * as firmware from "./api/firmware.js";
|
||||||
|
import * as deviceSocket from "./api/deviceSocket.js";
|
||||||
|
import * as deviceConnection from "./lib/deviceConnection.js";
|
||||||
|
import * as deviceListManager from "./lib/deviceListManager.js";
|
||||||
|
import * as blobProtocol from "./lib/blobProtocol.js";
|
||||||
|
import * as wsReconnect from "./lib/wsReconnect.js";
|
||||||
|
import AppHeader from "./components/layout/AppHeader.svelte";
|
||||||
|
import AppNav from "./components/layout/AppNav.svelte";
|
||||||
|
import AppFooter from "./components/layout/AppFooter.svelte";
|
||||||
|
|
||||||
//****************************************************constants section*********************************************************/
|
//****************************************************constants section*********************************************************/
|
||||||
//******************************************************************************************************************************/
|
//******************************************************************************************************************************/
|
||||||
@@ -107,9 +116,7 @@
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
var ackTimeoutsArr = [];
|
// ack state lives in wsReconnect.createAck
|
||||||
var startMillis = [];
|
|
||||||
var ping = [];
|
|
||||||
|
|
||||||
let incDeviceList = [];
|
let incDeviceList = [];
|
||||||
let layoutJson = [];
|
let layoutJson = [];
|
||||||
@@ -139,8 +146,7 @@
|
|||||||
|
|
||||||
//===============================================
|
//===============================================
|
||||||
|
|
||||||
//web sockets
|
// web sockets: pool in api/deviceSocket.js
|
||||||
let socket = [];
|
|
||||||
let socketConnected = false;
|
let socketConnected = false;
|
||||||
let selectedDeviceData = undefined;
|
let selectedDeviceData = undefined;
|
||||||
let selectedWs = 0;
|
let selectedWs = 0;
|
||||||
@@ -201,47 +207,74 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getUser = async () => {
|
const getUser = async () => {
|
||||||
try {
|
const JWT = Cookies.get("token_iotm2");
|
||||||
const JWT = Cookies.get("token_iotm2");
|
const res = await portal.getUser(JWT);
|
||||||
let res = await fetch("https://portal.iotmanager.org/api/user/email", {
|
if (res.ok) {
|
||||||
headers: {
|
userdata = res.userdata;
|
||||||
"Content-Type": "application/json",
|
serverOnline = true;
|
||||||
Authorization: `Bearer ${JWT}`,
|
} else {
|
||||||
},
|
if (!res.serverOnline) serverOnline = false;
|
||||||
mode: "cors",
|
else {
|
||||||
method: "GET",
|
console.log("error", "getUser");
|
||||||
});
|
|
||||||
if (res.ok) {
|
|
||||||
userdata = await res.json();
|
|
||||||
serverOnline = true;
|
|
||||||
} else {
|
|
||||||
console.log("error", res.statusText);
|
|
||||||
serverOnline = true;
|
serverOnline = true;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.log("error", e);
|
|
||||||
serverOnline = false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//****************************************************web sockets section******************************************************/
|
//****************************************************web sockets section******************************************************/
|
||||||
function connectToAllDevices() {
|
function getIP(ws) {
|
||||||
getSelectedDeviceData(selectedWs);
|
return deviceConnection.getIP(ws, deviceList);
|
||||||
for (let i = 0; i < deviceList.length; i++) {
|
}
|
||||||
deviceList[i].ws = i;
|
|
||||||
if (deviceList[i].status === false || deviceList[i].status === undefined) {
|
function wsSendMsg(ws, msg) {
|
||||||
wsConnect(i);
|
if (deviceSocket.send(ws, msg)) {
|
||||||
wsEventAdd(i);
|
if (debug) console.log("[i]", getIP(ws), ws, "msg send success", msg);
|
||||||
}
|
} else {
|
||||||
|
if (debug) console.log("[e]", getIP(ws), ws, "msg not send");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function printAllCreatedWs() {
|
const openHandler = () =>
|
||||||
if (socket) {
|
deviceConnection.createOpenHandler({
|
||||||
for (let i = 0; i < socket.length; i++) {
|
markDeviceStatus,
|
||||||
if (debug) console.log("[i]", "[ws]", "WebSocket client No: ", i);
|
sendMsg: wsSendMsg,
|
||||||
}
|
firstDevListRequest,
|
||||||
|
currentPageName,
|
||||||
|
selectedWs,
|
||||||
|
sendCurrentPageNameToSelectedWs,
|
||||||
|
});
|
||||||
|
|
||||||
|
function messageHandler(ws, data) {
|
||||||
|
if (typeof data === "string") {
|
||||||
|
if (data === "/tstr|") ack(ws, true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (data instanceof Blob) {
|
||||||
|
if (ws === selectedWs) parseBlob(data, ws);
|
||||||
|
if (currentPageName === "/|") parseAllBlob(data, ws);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createConnection(wsIndex, ip) {
|
||||||
|
if (ip === "error") {
|
||||||
|
if (debug) console.log("[e]", "device list wrong");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (debug) console.log("[i]", ip, wsIndex, "started connecting...");
|
||||||
|
deviceSocket.createConnection(wsIndex, ip, {
|
||||||
|
onOpen: (ws) => openHandler()(ws),
|
||||||
|
onMessage: messageHandler,
|
||||||
|
onClose: (ws) => markDeviceStatus(ws, false),
|
||||||
|
onError: (ws) => markDeviceStatus(ws, false),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectToAllDevices() {
|
||||||
|
deviceConnection.connectToAllDevices(deviceList, getSelectedDeviceData, selectedWs, createConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
function printAllCreatedWs() {
|
||||||
|
if (debug) console.log("[i]", "[ws]", "device count:", deviceList.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
function markDeviceStatus(ws, status) {
|
function markDeviceStatus(ws, status) {
|
||||||
@@ -266,332 +299,58 @@
|
|||||||
layoutJson = layoutJson.filter((item) => item.ws !== ws);
|
layoutJson = layoutJson.filter((item) => item.ws !== ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
function wsConnect(ws) {
|
const blobHandlers = {
|
||||||
let ip = getIP(ws);
|
setItemsJson: (v) => (itemsJson = v),
|
||||||
if (ip === "error") {
|
setParsedItemsJson: (v) => (parsed.itemsJson = v),
|
||||||
if (debug) console.log("[e]", "device list wrong");
|
setWidgetsJson: (v) => (widgetsJson = v),
|
||||||
} else {
|
setParsedWidgetsJson: (v) => (parsed.widgetsJson = v),
|
||||||
socket[ws] = new WebSocket("ws://" + ip + ":81");
|
setConfigJson: (v) => (configJson = v),
|
||||||
socket.binaryType = "blob";
|
setParsedConfigJson: (v) => (parsed.configJson = v),
|
||||||
if (debug) console.log("[i]", ip, ws, "started connecting...");
|
setScenarioTxt: (v) => (scenarioTxt = v),
|
||||||
}
|
setSettingsJson: (v) => (settingsJson = v),
|
||||||
}
|
setParsedSettingsJson: (v) => (parsed.settingsJson = v),
|
||||||
|
setSsidJson: (v) => (ssidJson = v),
|
||||||
function getIP(ws) {
|
setParsedSsidJson: (v) => (parsed.ssidJson = v),
|
||||||
let ret = "error";
|
setErrorsJson: (v) => (errorsJson = v),
|
||||||
deviceList.forEach((device) => {
|
setParsedErrorsJson: (v) => (parsed.errorsJson = v),
|
||||||
if (ws === device.ws) {
|
setParsedIncDeviceList: (v) => (parsed.incDeviceList = v),
|
||||||
ret = device.ip;
|
onDevlis: async (json) => {
|
||||||
}
|
incDeviceList = json;
|
||||||
});
|
deviceConnection.handleDevListReceived(incDeviceList, deviceList, firstDevListRequest, {
|
||||||
return ret;
|
setDeviceList: (list) => (deviceList = list),
|
||||||
}
|
setFirstDevListRequest: (v) => (firstDevListRequest = v),
|
||||||
|
setParsedDeviceListJson: (v) => (parsed.deviceListJson = v),
|
||||||
function wsEventAdd(ws) {
|
onParced,
|
||||||
if (socket[ws]) {
|
selectedDeviceDataRefresh,
|
||||||
let ip = getIP(ws);
|
connectToAllDevices,
|
||||||
socket[ws].addEventListener("open", function (event) {
|
|
||||||
//if (debug) console.log("[i]", ip, ws, "completed connecting");
|
|
||||||
|
|
||||||
markDeviceStatus(ws, true);
|
|
||||||
//при первом подключении запросим список устройств
|
|
||||||
if (firstDevListRequest && ws === 0) wsSendMsg(ws, "/devlist|");
|
|
||||||
//при подключении отправляем название страницы
|
|
||||||
if (currentPageName === "/|") {
|
|
||||||
//всем устройствам
|
|
||||||
wsSendMsg(ws, currentPageName);
|
|
||||||
} else {
|
|
||||||
//только выбранному
|
|
||||||
if (ws === selectedWs) {
|
|
||||||
sendCurrentPageNameToSelectedWs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
},
|
||||||
//события веб сокетов
|
setFlashProfileJson: (v) => (flashProfileJson = v),
|
||||||
socket[ws].addEventListener("message", function (event) {
|
setParsedFlashProfileJson: (v) => (parsed.flashProfileJson = v),
|
||||||
if (typeof event.data === "string") {
|
setOtaJson: (v) => (otaJson = v),
|
||||||
let data = event.data;
|
setParsedOtaJson: (v) => (parsed.otaJson = v),
|
||||||
if (data === "/tstr|") {
|
addCoreMsg: (msg) => addCoreMsg(msg),
|
||||||
//прилетело подтверждение значит устройство онлайн
|
onParced: () => onParced(),
|
||||||
|
};
|
||||||
ack(ws, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (event.data instanceof Blob) {
|
|
||||||
//принимаем данные только для выбранного устройства
|
|
||||||
if (ws === selectedWs) {
|
|
||||||
parseBlob(event.data, ws);
|
|
||||||
}
|
|
||||||
//собираем данные со всех устройств только в случае если пользователь на dashboard
|
|
||||||
if (currentPageName === "/|") {
|
|
||||||
parseAllBlob(event.data, ws);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
socket[ws].addEventListener("close", (event) => {
|
|
||||||
if (debug) console.log("[e]", ip, "connection closed");
|
|
||||||
//socket[ws].close();
|
|
||||||
markDeviceStatus(ws, false);
|
|
||||||
});
|
|
||||||
socket[ws].addEventListener("error", function (event) {
|
|
||||||
if (debug) console.log("[e]", ip, "connection error");
|
|
||||||
//socket[ws].close();
|
|
||||||
markDeviceStatus(ws, false);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (debug) console.log("[e]", "socket not exist");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function parseBlob(blob, ws) {
|
async function parseBlob(blob, ws) {
|
||||||
//получаем заголовок
|
await blobProtocol.parseBlob(blob, ws, blobHandlers);
|
||||||
var blobHeader = blob.slice(0, 6);
|
|
||||||
let header = await blobHeader.text();
|
|
||||||
//console.log("header: ", header);
|
|
||||||
//получаем размер
|
|
||||||
var blobSize = blob.slice(7, 11);
|
|
||||||
let size = await blobSize.text();
|
|
||||||
//console.log("size: ", size);
|
|
||||||
|
|
||||||
if (header === "itemsj") {
|
|
||||||
let out = {};
|
|
||||||
if (await getPayloadAsJson(blob, size, out)) {
|
|
||||||
itemsJson = out.json;
|
|
||||||
parsed.itemsJson = true;
|
|
||||||
if (blobDebug) console.log("[✔]", "itemsJson: ", itemsJson);
|
|
||||||
} else {
|
|
||||||
parsed.itemsJson = false;
|
|
||||||
if (blobDebug) console.log("[e]", "itemsJson parse error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (header === "widget") {
|
|
||||||
let out = {};
|
|
||||||
if (await getPayloadAsJson(blob, size, out)) {
|
|
||||||
widgetsJson = out.json;
|
|
||||||
parsed.widgetsJson = true;
|
|
||||||
if (blobDebug) console.log("[✔]", "widgetsJson: ", widgetsJson);
|
|
||||||
} else {
|
|
||||||
parsed.widgetsJson = false;
|
|
||||||
if (blobDebug) console.log("[e]", "widgetsJson parse error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (header === "config") {
|
|
||||||
let out = {};
|
|
||||||
if (await getPayloadAsJson(blob, size, out)) {
|
|
||||||
configJson = out.json;
|
|
||||||
parsed.configJson = true;
|
|
||||||
if (blobDebug) console.log("[✔]", "configJson: ", configJson);
|
|
||||||
} else {
|
|
||||||
parsed.configJson = false;
|
|
||||||
if (blobDebug) console.log("[e]", "configJson parse error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (header === "scenar") {
|
|
||||||
scenarioTxt = await getPayloadAsTxt(blob, size);
|
|
||||||
if (blobDebug) console.log("[i]", "scenarioTxt: ", scenarioTxt);
|
|
||||||
}
|
|
||||||
if (header === "settin") {
|
|
||||||
let out = {};
|
|
||||||
if (await getPayloadAsJson(blob, size, out)) {
|
|
||||||
settingsJson = out.json;
|
|
||||||
parsed.settingsJson = true;
|
|
||||||
if (blobDebug) console.log("[✔]", "settingsJson: ", settingsJson);
|
|
||||||
} else {
|
|
||||||
parsed.settingsJson = false;
|
|
||||||
if (blobDebug) console.log("[e]", "settingsJson parse error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (header === "ssidli") {
|
|
||||||
let out = {};
|
|
||||||
if (await getPayloadAsJson(blob, size, out)) {
|
|
||||||
ssidJson = out.json;
|
|
||||||
parsed.ssidJson = true;
|
|
||||||
if (blobDebug) console.log("[✔]", "ssidJson: ", ssidJson);
|
|
||||||
} else {
|
|
||||||
parsed.ssidJson = false;
|
|
||||||
if (blobDebug) console.log("[e]", "ssidJson parse error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//прием данных об ошибках
|
|
||||||
if (header === "errors") {
|
|
||||||
let out = {};
|
|
||||||
if (await getPayloadAsJson(blob, size, out)) {
|
|
||||||
errorsJson = out.json;
|
|
||||||
parsed.errorsJson = true;
|
|
||||||
if (blobDebug) console.log("[✔]", "errorsJson: ", errorsJson);
|
|
||||||
} else {
|
|
||||||
parsed.errorsJson = false;
|
|
||||||
if (blobDebug) console.log("[e]", "errorsJson parse error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//приход списка устройств
|
|
||||||
if (header === "devlis") {
|
|
||||||
let out = {};
|
|
||||||
if (await getPayloadAsJson(blob, size, out)) {
|
|
||||||
incDeviceList = [];
|
|
||||||
incDeviceList = out.json;
|
|
||||||
parsed.incDeviceList = true;
|
|
||||||
if (blobDebug) console.log("[✔]", "incDeviceList: ", incDeviceList);
|
|
||||||
await initDevList();
|
|
||||||
} else {
|
|
||||||
parsed.incDeviceList = false;
|
|
||||||
if (blobDebug) console.log("[e]", "incDeviceList parse error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//приход профиля
|
|
||||||
if (header === "prfile") {
|
|
||||||
let out = {};
|
|
||||||
if (await getPayloadAsJson(blob, size, out)) {
|
|
||||||
flashProfileJson = out.json;
|
|
||||||
parsed.flashProfileJson = true;
|
|
||||||
if (blobDebug) console.log("[✔]", "flashProfileJson: ", flashProfileJson);
|
|
||||||
} else {
|
|
||||||
parsed.flashProfileJson = false;
|
|
||||||
if (blobDebug) console.log("[e]", "flashProfileJson parse error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header === "otaupd") {
|
|
||||||
let out = {};
|
|
||||||
if (await getPayloadAsJson(blob, size, out)) {
|
|
||||||
otaJson = out.json;
|
|
||||||
parsed.otaJson = true;
|
|
||||||
if (blobDebug) console.log("[✔]", "otaJson: ", otaJson);
|
|
||||||
} else {
|
|
||||||
parsed.otaJson = false;
|
|
||||||
if (blobDebug) console.log("[e]", "otaJson parse error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header === "corelg") {
|
|
||||||
let txt = await getPayloadAsTxt(blob, size);
|
|
||||||
//console.log("[--]", ws, txt);
|
|
||||||
addCoreMsg(txt);
|
|
||||||
}
|
|
||||||
|
|
||||||
await onParced();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allBlobHandlers = {
|
||||||
|
updateWidget: (v) => updateWidget(v),
|
||||||
|
combineLayoutsInOne: (ws, layout) => combineLayoutsInOne(ws, layout),
|
||||||
|
mergeParams: (devParams) => {
|
||||||
|
paramsJson = { ...paramsJson, ...devParams };
|
||||||
|
paramsJson = paramsJson;
|
||||||
|
},
|
||||||
|
updateAllStatuses: (ws) => updateAllStatuses(ws),
|
||||||
|
onParced: () => onParced(),
|
||||||
|
apdateWidgetByArray: (v) => apdateWidgetByArray(v),
|
||||||
|
};
|
||||||
|
|
||||||
async function parseAllBlob(blob, ws) {
|
async function parseAllBlob(blob, ws) {
|
||||||
//получаем заголовок
|
await blobProtocol.parseAllBlob(blob, ws, allBlobHandlers);
|
||||||
var blobHeader = blob.slice(0, 6);
|
|
||||||
let header = await blobHeader.text();
|
|
||||||
//console.log("header: ", header);
|
|
||||||
//получаем размер
|
|
||||||
var blobSize = blob.slice(7, 11);
|
|
||||||
let size = await blobSize.text();
|
|
||||||
//console.log("size: ", size);
|
|
||||||
|
|
||||||
if (header === "status") {
|
|
||||||
let out = {};
|
|
||||||
if (await getPayloadAsJson(blob, size, out)) {
|
|
||||||
let statusJson = out.json;
|
|
||||||
updateWidget(statusJson);
|
|
||||||
//if (blobDebug)
|
|
||||||
console.log("[✔]", "statusJson: ", statusJson);
|
|
||||||
} else {
|
|
||||||
if (blobDebug) console.log("[e]", "statusJson parse error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (header === "layout") {
|
|
||||||
let out = {};
|
|
||||||
if (await getPayloadAsJson(blob, size, out)) {
|
|
||||||
let devLayout = out.json;
|
|
||||||
combineLayoutsInOne(ws, devLayout);
|
|
||||||
if (blobDebug) console.log("[✔]", "devLayout: ", devLayout);
|
|
||||||
} else {
|
|
||||||
if (blobDebug) console.log("[e]", "devLayout parse error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (header === "params") {
|
|
||||||
let out = {};
|
|
||||||
if (await getPayloadAsJson(blob, size, out)) {
|
|
||||||
let devParams = out.json;
|
|
||||||
paramsJson = {
|
|
||||||
...paramsJson,
|
|
||||||
...devParams,
|
|
||||||
};
|
|
||||||
paramsJson = paramsJson;
|
|
||||||
updateAllStatuses(ws);
|
|
||||||
onParced();
|
|
||||||
if (blobDebug) console.log("[✔]", "devParams: ", devParams);
|
|
||||||
} else {
|
|
||||||
if (blobDebug) console.log("[e]", "devParams parse error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (header === "charta") {
|
|
||||||
let txt = await getPayloadAsTxt(blob, size);
|
|
||||||
txt = "[" + txt.substring(0, txt.length - 1) + "]";
|
|
||||||
let chartJson;
|
|
||||||
try {
|
|
||||||
chartJson = JSON.parse(txt);
|
|
||||||
if (blobDebug) console.log("[i]", "chart data json: ", chartJson);
|
|
||||||
} catch (e) {
|
|
||||||
if (blobDebug) console.log("[e]", "chart json data parce error, return");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let out = {};
|
|
||||||
let addJson = {};
|
|
||||||
if (await getJsonAsJson(blob, size, out)) {
|
|
||||||
addJson = out.json;
|
|
||||||
if (blobDebug) console.log("[i]", "chart add json: ", addJson);
|
|
||||||
} else {
|
|
||||||
if (blobDebug) console.log("[e]", "chart json add-ns parce error, return");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let finalDataJson = {};
|
|
||||||
finalDataJson.status = chartJson;
|
|
||||||
|
|
||||||
finalDataJson = {
|
|
||||||
...finalDataJson,
|
|
||||||
...addJson,
|
|
||||||
};
|
|
||||||
if (blobDebug) console.log("[✔]", "chartJson: ", finalDataJson);
|
|
||||||
apdateWidgetByArray(finalDataJson);
|
|
||||||
}
|
|
||||||
if (header === "chartb") {
|
|
||||||
let out = {};
|
|
||||||
if (await getPayloadAsJson(blob, size, out)) {
|
|
||||||
let status = out.json;
|
|
||||||
apdateWidgetByArray(status);
|
|
||||||
if (blobDebug) console.log("[✔]", "chart status: ", status);
|
|
||||||
} else {
|
|
||||||
if (blobDebug) console.log("[e]", "status parse error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getJsonAsJson(blob, size, out) {
|
|
||||||
let partBlob = blob.slice(12, size);
|
|
||||||
let txt = await partBlob.text();
|
|
||||||
try {
|
|
||||||
out.json = JSON.parse(txt);
|
|
||||||
out.parse = true;
|
|
||||||
} catch (e) {
|
|
||||||
if (debug) console.log("[e]", "json parce error: ", txt);
|
|
||||||
out.parse = false;
|
|
||||||
}
|
|
||||||
return out.parse;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getPayloadAsJson(blob, size, out) {
|
|
||||||
let partBlob = blob.slice(size, blob.length);
|
|
||||||
let txt = await partBlob.text();
|
|
||||||
try {
|
|
||||||
out.json = JSON.parse(txt);
|
|
||||||
out.parse = true;
|
|
||||||
} catch (e) {
|
|
||||||
if (debug) console.log("[e]", "json parse error: ", txt);
|
|
||||||
out.parse = false;
|
|
||||||
}
|
|
||||||
return out.parse;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getPayloadAsTxt(blob, size) {
|
|
||||||
let txtBlob = blob.slice(size, blob.length);
|
|
||||||
let txt = await txtBlob.text();
|
|
||||||
return txt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onParced() {
|
async function onParced() {
|
||||||
@@ -636,43 +395,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getModInfo = async () => {
|
const getModInfo = async () => {
|
||||||
try {
|
const res = await portal.getModInfo();
|
||||||
let res = await fetch("https://portal.iotmanager.org/compiler/allmodinfo", {
|
if (res.ok) allmodeinfo = res.allmodeinfo;
|
||||||
mode: "cors",
|
else console.log("error", "getModInfo");
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
|
||||||
allmodeinfo = await res.json();
|
|
||||||
allmodeinfo = allmodeinfo.message;
|
|
||||||
} else {
|
|
||||||
console.log("error", res.statusText);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("error", e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProfile = async () => {
|
const getProfile = async () => {
|
||||||
try {
|
const JWT = Cookies.get("token_iotm2");
|
||||||
const JWT = Cookies.get("token_iotm2");
|
const res = await portal.getProfile(JWT);
|
||||||
let res = await fetch("https://portal.iotmanager.org/compiler/profile", {
|
if (res.ok) {
|
||||||
headers: {
|
profile = res.profile;
|
||||||
"Content-Type": "application/json",
|
await markProfileAsPerThisDevProfile();
|
||||||
Authorization: `Bearer ${JWT}`,
|
} else console.log("error", "getProfile");
|
||||||
},
|
|
||||||
mode: "cors",
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
|
||||||
profile = await res.json();
|
|
||||||
profile = profile.message;
|
|
||||||
await markProfileAsPerThisDevProfile();
|
|
||||||
} else {
|
|
||||||
console.log("error", res.statusText);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("error", e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const markProfileAsPerThisDevProfile = async () => {
|
const markProfileAsPerThisDevProfile = async () => {
|
||||||
@@ -692,52 +426,18 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function initDevList() {
|
function devListOverride() {
|
||||||
if (firstDevListRequest) {
|
deviceList = deviceListManager.devListOverride(incDeviceList);
|
||||||
//при первом запросе листа устройств запишем его целеком
|
|
||||||
devListOverride();
|
|
||||||
} else {
|
|
||||||
//при последующих прилетах списка устройств мы переписываем в массиве только то что изменилось
|
|
||||||
devListCombine();
|
|
||||||
}
|
|
||||||
firstDevListRequest = false;
|
|
||||||
deviceList = deviceList;
|
|
||||||
parsed.deviceListJson = true;
|
|
||||||
if (blobDebug) console.log("[✔]", "deviceList parced");
|
|
||||||
onParced();
|
|
||||||
selectedDeviceDataRefresh();
|
|
||||||
//затем подключимся к всему полученному списку устройств
|
|
||||||
connectToAllDevices();
|
|
||||||
}
|
|
||||||
|
|
||||||
//перезапись листа устройств
|
|
||||||
async function devListOverride() {
|
|
||||||
deviceList = incDeviceList;
|
|
||||||
sortList(deviceList);
|
|
||||||
//установим заведомо статус уже присутствующего устроства в true для того что бы предотвратить повторное подключение!!!
|
|
||||||
deviceList[0].status = true;
|
|
||||||
console.log("[i]", "[devlist]", "devlist overrided");
|
console.log("[i]", "[devlist]", "devlist overrided");
|
||||||
}
|
}
|
||||||
|
|
||||||
//добавление только новых элементов в лист устройств (если такого ip не было)
|
function devListCombine() {
|
||||||
async function devListCombine() {
|
deviceList = deviceListManager.devListCombine(deviceList, incDeviceList);
|
||||||
deviceList = combineArrays(deviceList, incDeviceList);
|
|
||||||
sortList(deviceList);
|
|
||||||
console.log("[i]", "[devlist]", "devlist combined");
|
console.log("[i]", "[devlist]", "devlist combined");
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortList(list) {
|
function combineArrays(A, B) {
|
||||||
let firstDev = list.shift();
|
return deviceListManager.combineArrays(A, B);
|
||||||
list.sort(function (a, b) {
|
|
||||||
if (a.name < b.name) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a.name > b.name) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
list.unshift(firstDev);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//***********************************************************dashboard***************************************************************/
|
//***********************************************************dashboard***************************************************************/
|
||||||
@@ -1029,67 +729,31 @@
|
|||||||
wsSendMsg(ws, "/control|" + key + "/" + status);
|
wsSendMsg(ws, "/control|" + key + "/" + status);
|
||||||
}
|
}
|
||||||
|
|
||||||
function scale(number, inMin, inMax, outMin, outMax) {
|
const ack = wsReconnect.createAck({
|
||||||
return ((number - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
|
markDeviceStatus,
|
||||||
}
|
getDeviceList: () => deviceList,
|
||||||
|
setDeviceList: (list) => (deviceList = list),
|
||||||
|
waitingAckTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
//тикер который тикает каждую секунду и на каждую 0 секунду запускает проверку соединения
|
const wsTestMsgTask = wsReconnect.createWsTestMsgTask({
|
||||||
function wsTestMsgTask() {
|
getDeviceList: () => deviceList,
|
||||||
tickerTask = setTimeout(wsTestMsgTask, 1000);
|
send: wsSendMsg,
|
||||||
if (!preventReconnect) {
|
markDeviceStatus,
|
||||||
remainingTimeout--;
|
connectDevice: (ws) => createConnection(ws, getIP(ws)),
|
||||||
if (rebootOrUpdateProcess && socketConnected) {
|
ack,
|
||||||
rebootOrUpdateProcess = false;
|
getRemainingTimeout: () => remainingTimeout,
|
||||||
showAwaitingCircle = false;
|
setRemainingTimeout: (v) => (remainingTimeout = v),
|
||||||
reconnectTimeout = 60;
|
reconnectTimeout,
|
||||||
remainingTimeout = reconnectTimeout;
|
getPreventReconnect: () => preventReconnect,
|
||||||
}
|
setPercent: (v) => (percent = v),
|
||||||
percent = scale(remainingTimeout, reconnectTimeout, 0, 0, 100);
|
getRebootOrUpdateProcess: () => rebootOrUpdateProcess,
|
||||||
if (remainingTimeout <= 0) {
|
setRebootOrUpdateProcess: (v) => (rebootOrUpdateProcess = v),
|
||||||
if (debug) console.log("[i]", "----timer tick----");
|
getSocketConnected: () => socketConnected,
|
||||||
printAllCreatedWs();
|
setShowAwaitingCircle: (v) => (showAwaitingCircle = v),
|
||||||
remainingTimeout = reconnectTimeout;
|
setReconnectTimeout: (v) => (reconnectTimeout = v),
|
||||||
deviceList.forEach((device) => {
|
printAllCreatedWs,
|
||||||
if (device.status === false || device.status === undefined) {
|
});
|
||||||
wsConnect(device.ws);
|
|
||||||
wsEventAdd(device.ws);
|
|
||||||
} else {
|
|
||||||
wsSendMsg(device.ws, "/tst|");
|
|
||||||
ack(device.ws, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ack(ws, st) {
|
|
||||||
if (!st) {
|
|
||||||
startMillis[ws] = Date.now();
|
|
||||||
ackTimeoutsArr[ws] = setTimeout(function () {
|
|
||||||
markDeviceStatus(ws, false);
|
|
||||||
}, waitingAckTimeout);
|
|
||||||
} else {
|
|
||||||
if (ackTimeoutsArr[ws]) clearTimeout(ackTimeoutsArr[ws]);
|
|
||||||
if (startMillis[ws]) {
|
|
||||||
ping[ws] = Date.now() - startMillis[ws];
|
|
||||||
}
|
|
||||||
for (let i = 0; i < deviceList.length; i++) {
|
|
||||||
if (deviceList[i].ws === ws) {
|
|
||||||
deviceList[i].ping = ping[ws];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deviceList = deviceList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function wsSendMsg(ws, msg) {
|
|
||||||
if (socket[ws] && socket[ws].readyState === 1) {
|
|
||||||
socket[ws].send(msg);
|
|
||||||
if (debug) console.log("[i]", getIP(ws), ws, "msg send success", msg);
|
|
||||||
} else {
|
|
||||||
if (debug) console.log("[e]", getIP(ws), ws, "msg not send");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendToAllDevices(msg) {
|
function sendToAllDevices(msg) {
|
||||||
deviceList.forEach((device) => {
|
deviceList.forEach((device) => {
|
||||||
@@ -1124,12 +788,6 @@
|
|||||||
|
|
||||||
//***********************************************************dev list******************************************************************/
|
//***********************************************************dev list******************************************************************/
|
||||||
|
|
||||||
function combineArrays(A, B) {
|
|
||||||
var ids = new Set(A.map((d) => d.ip));
|
|
||||||
let output = [...A, ...B.filter((d) => !ids.has(d.ip))];
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
//всякий раз когда список устройств был обновлен
|
//всякий раз когда список устройств был обновлен
|
||||||
function selectedDeviceDataRefresh() {
|
function selectedDeviceDataRefresh() {
|
||||||
//запишем в переменную selectedDeviceData выбранное устройство, что бы в коде было известно выбранное устройство
|
//запишем в переменную selectedDeviceData выбранное устройство, что бы в коде было известно выбранное устройство
|
||||||
@@ -1252,30 +910,14 @@
|
|||||||
|
|
||||||
async function getVersionsList() {
|
async function getVersionsList() {
|
||||||
versionsList = {};
|
versionsList = {};
|
||||||
if (settingsJson.serverip) {
|
const res = await firmware.getVersionsList(settingsJson.serverip);
|
||||||
try {
|
if (res.ok && res.data) {
|
||||||
let url = settingsJson.serverip + "/iotm/ver.json";
|
versionsList = res.data[errorsJson.bn];
|
||||||
console.log("url", url);
|
choosingVersion = errorsJson.bver;
|
||||||
let res = await fetch(url, {
|
console.log(JSON.stringify(versionsList));
|
||||||
mode: "cors",
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
|
||||||
versionsList = await res.json();
|
|
||||||
versionsList = versionsList[errorsJson.bn];
|
|
||||||
choosingVersion = errorsJson.bver;
|
|
||||||
console.log(JSON.stringify(versionsList));
|
|
||||||
} else {
|
|
||||||
choosingVersion = undefined;
|
|
||||||
console.log("error, versions list not received", res.statusText);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
choosingVersion = undefined;
|
|
||||||
console.log("error, versions list not received");
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
console.log("error, server missing");
|
choosingVersion = undefined;
|
||||||
|
if (settingsJson.serverip) console.log("error, versions list not received");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1302,70 +944,14 @@
|
|||||||
<ModalPass checkPassword={(pass) => checkPassword(pass)} />
|
<ModalPass checkPassword={(pass) => checkPassword(pass)} />
|
||||||
{/if}-->
|
{/if}-->
|
||||||
|
|
||||||
<header class="h-10 w-full bg-gray-100 overflow-auto shadow-md">
|
<AppHeader
|
||||||
<div class="flex content-center items-center justify-end">
|
{deviceList}
|
||||||
{#if showDropdown}
|
bind:selectedWs
|
||||||
<div class="px-15 py-1">
|
{showDropdown}
|
||||||
<select class="border border-indigo-500 border-1" bind:value={selectedWs} on:change={() => devicesDropdownChange()}>
|
{socketConnected}
|
||||||
{#each deviceList as device}
|
{devicesDropdownChange}
|
||||||
<option value={device.ws}>
|
/>
|
||||||
{device.name}
|
<AppNav bind:opened onCheck={() => onCheck()} {userdata} />
|
||||||
</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!--<div class="pl-4 pr-1 py-1">-->
|
|
||||||
<!--<BookIcon color={socketConnected === true ? "text-green-500" : "text-red-500"} />-->
|
|
||||||
<!--</div>-->
|
|
||||||
<div class="pl-4 pr-4 py-1">
|
|
||||||
<CloudIcon color={socketConnected === true ? "text-green-500" : "text-red-500"} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<nav class="flex">
|
|
||||||
<input class="w-0 h-0" bind:checked={opened} on:change={() => onCheck()} id="menu__toggle" type="checkbox" />
|
|
||||||
|
|
||||||
<label class="menu__btn" for="menu__toggle">
|
|
||||||
<span />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<ul class="menu__box">
|
|
||||||
<li>
|
|
||||||
<a class="menu__item" href="/">{"Управление"}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="menu__item" href="/config">{"Конфигуратор"}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="menu__item" href="/connection">{"Подключение"}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="menu__item" href="/system">{"Системные"}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="menu__item" href="/list">{"Устройства"}</a>
|
|
||||||
</li>
|
|
||||||
{#if userdata}
|
|
||||||
<li>
|
|
||||||
<a class="menu__item" href="/profile">{"Модули"}</a>
|
|
||||||
</li>
|
|
||||||
{:else}
|
|
||||||
<li>
|
|
||||||
<a class="menu__item" href="/login">{"Вход"}</a>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
<li class="flex flex-col pl-6 pt-3 w-full h-screen">
|
|
||||||
<!--<select class="border border-indigo-500 border-1 h-6 w-12" bind:value={$locale}>
|
|
||||||
{#each locales as l}
|
|
||||||
<option value={l}>{l}</option>
|
|
||||||
{/each}
|
|
||||||
</select>-->
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main class="flex-1 overflow-y-auto p-0 {opened === true && !preventMove ? 'ml-36' : 'ml-0'}">
|
<main class="flex-1 overflow-y-auto p-0 {opened === true && !preventMove ? 'ml-36' : 'ml-0'}">
|
||||||
<ul class="menu__main">
|
<ul class="menu__main">
|
||||||
@@ -1400,9 +986,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="h-4 bg-gray-100 border-gray-300 shadow-lg">
|
<AppFooter />
|
||||||
<div class="flex justify-center content-center text-xxs text-gray-500">Developed by Dmitry Borisenko</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="postcss" global>
|
<style lang="postcss" global>
|
||||||
|
|||||||
75
src/api/deviceSocket.js
Normal file
75
src/api/deviceSocket.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* WebSocket pool for devices: create by wsIndex, events via callbacks.
|
||||||
|
* No parsing or reconnect logic — App provides callbacks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const sockets = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create WebSocket for device at wsIndex, bind events to callbacks.
|
||||||
|
* @param {number} wsIndex - device index (same as device.ws)
|
||||||
|
* @param {string} ip - device IP
|
||||||
|
* @param {object} callbacks - { onOpen(ws), onMessage(ws, data), onClose(ws), onError(ws) }
|
||||||
|
*/
|
||||||
|
export function createConnection(wsIndex, ip, callbacks) {
|
||||||
|
const url = "ws://" + ip + ":81";
|
||||||
|
const socket = new WebSocket(url);
|
||||||
|
socket.binaryType = "blob"; // fix: set on instance, not on array
|
||||||
|
sockets.set(wsIndex, socket);
|
||||||
|
|
||||||
|
socket.addEventListener("open", () => {
|
||||||
|
if (callbacks.onOpen) callbacks.onOpen(wsIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener("message", (event) => {
|
||||||
|
if (callbacks.onMessage) callbacks.onMessage(wsIndex, event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener("close", () => {
|
||||||
|
if (callbacks.onClose) callbacks.onClose(wsIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener("error", () => {
|
||||||
|
if (callbacks.onError) callbacks.onError(wsIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send message if socket exists and is open.
|
||||||
|
* @param {number} wsIndex
|
||||||
|
* @param {string} msg
|
||||||
|
*/
|
||||||
|
export function send(wsIndex, msg) {
|
||||||
|
const socket = sockets.get(wsIndex);
|
||||||
|
if (socket && socket.readyState === 1) {
|
||||||
|
socket.send(msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} wsIndex
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isOpen(wsIndex) {
|
||||||
|
const socket = sockets.get(wsIndex);
|
||||||
|
return !!(socket && socket.readyState === 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get socket instance (for compatibility / debug). Prefer send/isOpen.
|
||||||
|
* @param {number} wsIndex
|
||||||
|
* @returns {WebSocket|undefined}
|
||||||
|
*/
|
||||||
|
export function getSocket(wsIndex) {
|
||||||
|
return sockets.get(wsIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove socket from pool (e.g. on close if needed). Does not close the socket.
|
||||||
|
* @param {number} wsIndex
|
||||||
|
*/
|
||||||
|
export function removeSocket(wsIndex) {
|
||||||
|
sockets.delete(wsIndex);
|
||||||
|
}
|
||||||
28
src/api/firmware.js
Normal file
28
src/api/firmware.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Firmware / OTA: load ver.json from device-configured server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { get } from "./http.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load ver.json from serverip/iotm/ver.json.
|
||||||
|
* @param {string} serverip - base URL (e.g. from settingsJson.serverip)
|
||||||
|
* @returns {Promise<{ ok: boolean, data?: object }>} data is full ver.json
|
||||||
|
*/
|
||||||
|
export async function getVersionsList(serverip) {
|
||||||
|
if (!serverip) {
|
||||||
|
console.log("error", "server missing");
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const url = serverip + "/iotm/ver.json";
|
||||||
|
const res = await get(url);
|
||||||
|
if (res.ok) {
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
}
|
||||||
|
return { ok: false };
|
||||||
|
} catch (e) {
|
||||||
|
console.log("error", "versions list not received", e);
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/api/http.js
Normal file
63
src/api/http.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Generic fetch wrapper: JSON, errors, headers.
|
||||||
|
* Returns { ok, data, status, statusText } or throws on network error.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DEFAULT_OPTIONS = {
|
||||||
|
mode: "cors",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @param {RequestInit} options
|
||||||
|
* @returns {Promise<{ ok: boolean, data?: any, status: number, statusText: string }>}
|
||||||
|
*/
|
||||||
|
export async function request(url, options = {}) {
|
||||||
|
const opts = {
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
...DEFAULT_OPTIONS.headers,
|
||||||
|
...(options.headers || {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const res = await fetch(url, opts);
|
||||||
|
let data;
|
||||||
|
const contentType = res.headers.get("content-type");
|
||||||
|
if (contentType && contentType.includes("application/json")) {
|
||||||
|
try {
|
||||||
|
data = await res.json();
|
||||||
|
} catch {
|
||||||
|
data = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data = await res.text();
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ok: res.ok,
|
||||||
|
data,
|
||||||
|
status: res.status,
|
||||||
|
statusText: res.statusText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET request, returns parsed JSON when possible.
|
||||||
|
*/
|
||||||
|
export async function get(url, headers = {}) {
|
||||||
|
return request(url, { method: "GET", headers });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST request with JSON body.
|
||||||
|
*/
|
||||||
|
export async function post(url, body, headers = {}) {
|
||||||
|
return request(url, {
|
||||||
|
method: "POST",
|
||||||
|
body: typeof body === "string" ? body : JSON.stringify(body || {}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
}
|
||||||
172
src/api/portal.js
Normal file
172
src/api/portal.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/**
|
||||||
|
* Portal API: https://portal.iotmanager.org
|
||||||
|
* Auth/user, configurations, compiler (profile, orders, etc.).
|
||||||
|
*
|
||||||
|
* CORS: If you see "Redirect is not allowed for a preflight request", the server
|
||||||
|
* is redirecting OPTIONS requests (e.g. to HTTPS or login). Fix on server:
|
||||||
|
* do not redirect preflight; return 200 with CORS headers for OPTIONS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { get, post } from "./http.js";
|
||||||
|
|
||||||
|
const BASE = "https://portal.iotmanager.org";
|
||||||
|
|
||||||
|
function authHeaders(token) {
|
||||||
|
return {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/user/email — current user by JWT.
|
||||||
|
* @param {string} JWT
|
||||||
|
* @returns {Promise<{ ok: boolean, userdata?: object, serverOnline: boolean }>}
|
||||||
|
*/
|
||||||
|
export async function getUser(JWT) {
|
||||||
|
try {
|
||||||
|
const res = await get(BASE + "/api/user/email", authHeaders(JWT));
|
||||||
|
if (res.ok) {
|
||||||
|
return { ok: true, userdata: res.data, serverOnline: true };
|
||||||
|
}
|
||||||
|
return { ok: false, serverOnline: true };
|
||||||
|
} catch (e) {
|
||||||
|
console.log("error", e);
|
||||||
|
return { ok: false, serverOnline: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/auth/login — login, returns token in content.message.
|
||||||
|
* @param {object} user { username, password }
|
||||||
|
* @returns {Promise<{ ok: boolean, data: object }>}
|
||||||
|
*/
|
||||||
|
export async function login(user) {
|
||||||
|
const res = await post(BASE + "/api/auth/login", user);
|
||||||
|
return { ok: res.ok, data: res.data };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /compiler/allmodinfo — all module info.
|
||||||
|
* @returns {Promise<{ ok: boolean, allmodeinfo?: object }>}
|
||||||
|
*/
|
||||||
|
export async function getModInfo() {
|
||||||
|
try {
|
||||||
|
const res = await get(BASE + "/compiler/allmodinfo", { "Content-Type": "application/json" });
|
||||||
|
if (res.ok && res.data && res.data.message) {
|
||||||
|
return { ok: true, allmodeinfo: res.data.message };
|
||||||
|
}
|
||||||
|
return { ok: false };
|
||||||
|
} catch (e) {
|
||||||
|
console.log("error", e);
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /compiler/profile — user compiler profile (JWT).
|
||||||
|
* @param {string} JWT
|
||||||
|
* @returns {Promise<{ ok: boolean, profile?: object }>}
|
||||||
|
*/
|
||||||
|
export async function getProfile(JWT) {
|
||||||
|
try {
|
||||||
|
const res = await get(BASE + "/compiler/profile", authHeaders(JWT));
|
||||||
|
if (res.ok && res.data && res.data.message) {
|
||||||
|
return { ok: true, profile: res.data.message };
|
||||||
|
}
|
||||||
|
return { ok: false };
|
||||||
|
} catch (e) {
|
||||||
|
console.log("error", e);
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/configurations/get — list configurations (JWT).
|
||||||
|
* @param {string} JWT
|
||||||
|
* @returns {Promise<{ ok: boolean, configurations?: array }>}
|
||||||
|
*/
|
||||||
|
export async function getConfigurations(JWT) {
|
||||||
|
try {
|
||||||
|
const res = await get(BASE + "/api/configurations/get", authHeaders(JWT));
|
||||||
|
if (res.ok) {
|
||||||
|
return { ok: true, configurations: res.data };
|
||||||
|
}
|
||||||
|
return { ok: false };
|
||||||
|
} catch (e) {
|
||||||
|
console.log("error", e);
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/configurations/add — publish config (JWT, body: category, topic, text, config, scenario, gallery, type, username).
|
||||||
|
* @param {string} JWT
|
||||||
|
* @param {object} body
|
||||||
|
* @returns {Promise<{ ok: boolean, data?: object, errors?: array }>}
|
||||||
|
*/
|
||||||
|
export async function addConfiguration(JWT, body) {
|
||||||
|
try {
|
||||||
|
const res = await post(BASE + "/api/configurations/add", body, authHeaders(JWT));
|
||||||
|
if (res.ok && res.data) {
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
}
|
||||||
|
return { ok: false, errors: res.data?.message };
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /compiler/userorders — user build orders (JWT).
|
||||||
|
* @param {string} JWT
|
||||||
|
* @returns {Promise<{ ok: boolean, userBuilds?: array }>}
|
||||||
|
*/
|
||||||
|
export async function getUserOrders(JWT) {
|
||||||
|
try {
|
||||||
|
const res = await get(BASE + "/compiler/userorders", authHeaders(JWT));
|
||||||
|
if (res.ok) {
|
||||||
|
return { ok: true, userBuilds: res.data };
|
||||||
|
}
|
||||||
|
return { ok: false };
|
||||||
|
} catch (e) {
|
||||||
|
console.log("error", e);
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /compiler/delete/builds/:orderId — delete build (JWT).
|
||||||
|
* @param {string} JWT
|
||||||
|
* @param {string} orderId
|
||||||
|
* @returns {Promise<{ ok: boolean }>}
|
||||||
|
*/
|
||||||
|
export async function deleteBuild(JWT, orderId) {
|
||||||
|
try {
|
||||||
|
const res = await get(BASE + "/compiler/delete/builds/" + orderId, authHeaders(JWT));
|
||||||
|
return { ok: res.ok };
|
||||||
|
} catch (e) {
|
||||||
|
console.log("error", e);
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /compiler/order — place compiler order (JWT, body: profile with username).
|
||||||
|
* @param {string} JWT
|
||||||
|
* @param {object} profile
|
||||||
|
* @returns {Promise<{ ok: boolean, data?: object, errors?: array }>}
|
||||||
|
*/
|
||||||
|
export async function placeOrder(JWT, profile) {
|
||||||
|
try {
|
||||||
|
const res = await post(BASE + "/compiler/order", profile, authHeaders(JWT));
|
||||||
|
if (res.ok) {
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
}
|
||||||
|
return { ok: false, errors: res.data?.message };
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/components/layout/AppFooter.svelte
Normal file
3
src/components/layout/AppFooter.svelte
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<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>
|
||||||
|
</footer>
|
||||||
28
src/components/layout/AppHeader.svelte
Normal file
28
src/components/layout/AppHeader.svelte
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<script>
|
||||||
|
import CloudIcon from "../../svg/Cloud.svelte";
|
||||||
|
|
||||||
|
export let deviceList = [];
|
||||||
|
export let selectedWs = 0;
|
||||||
|
export let showDropdown = true;
|
||||||
|
export let socketConnected = false;
|
||||||
|
export let devicesDropdownChange = () => {};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<header class="h-10 w-full bg-gray-100 overflow-auto shadow-md">
|
||||||
|
<div class="flex content-center items-center justify-end">
|
||||||
|
{#if showDropdown}
|
||||||
|
<div class="px-15 py-1">
|
||||||
|
<select class="border border-indigo-500 border-1" bind:value={selectedWs} on:change={() => devicesDropdownChange()}>
|
||||||
|
{#each deviceList as device}
|
||||||
|
<option value={device.ws}>
|
||||||
|
{device.name}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="pl-4 pr-4 py-1">
|
||||||
|
<CloudIcon color={socketConnected === true ? "text-green-500" : "text-red-500"} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
41
src/components/layout/AppNav.svelte
Normal file
41
src/components/layout/AppNav.svelte
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script>
|
||||||
|
export let opened = true;
|
||||||
|
export let onCheck = () => {};
|
||||||
|
export let userdata = null;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav class="flex">
|
||||||
|
<input class="w-0 h-0" bind:checked={opened} on:change={() => onCheck()} id="menu__toggle" type="checkbox" />
|
||||||
|
|
||||||
|
<label class="menu__btn" for="menu__toggle">
|
||||||
|
<span />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<ul class="menu__box">
|
||||||
|
<li>
|
||||||
|
<a class="menu__item" href="/">{"Управление"}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="menu__item" href="/config">{"Конфигуратор"}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="menu__item" href="/connection">{"Подключение"}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="menu__item" href="/system">{"Системные"}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="menu__item" href="/list">{"Устройства"}</a>
|
||||||
|
</li>
|
||||||
|
{#if userdata}
|
||||||
|
<li>
|
||||||
|
<a class="menu__item" href="/profile">{"Модули"}</a>
|
||||||
|
</li>
|
||||||
|
{:else}
|
||||||
|
<li>
|
||||||
|
<a class="menu__item" href="/login">{"Вход"}</a>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
<li class="flex flex-col pl-6 pt-3 w-full h-screen"></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
186
src/lib/blobProtocol.js
Normal file
186
src/lib/blobProtocol.js
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
/**
|
||||||
|
* Blob protocol: 6-byte header, 4-byte size (text), then payload.
|
||||||
|
* Helpers: getPayloadAsJson, getPayloadAsTxt, getJsonAsJson.
|
||||||
|
* parseBlob / parseAllBlob invoke callbacks for each header; no state.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse size string from blob (bytes 7-11) to number.
|
||||||
|
* @param {string} sizeStr
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function parseSize(sizeStr) {
|
||||||
|
const n = parseInt(sizeStr, 10);
|
||||||
|
return isNaN(n) ? 0 : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload as JSON (bytes size..length). Out: { json, parse }.
|
||||||
|
*/
|
||||||
|
export async function getPayloadAsJson(blob, size, out) {
|
||||||
|
const sizeNum = typeof size === "string" ? parseSize(size) : size;
|
||||||
|
const partBlob = blob.slice(sizeNum, blob.length);
|
||||||
|
const txt = await partBlob.text();
|
||||||
|
try {
|
||||||
|
out.json = JSON.parse(txt);
|
||||||
|
out.parse = true;
|
||||||
|
} catch (e) {
|
||||||
|
out.parse = false;
|
||||||
|
}
|
||||||
|
return out.parse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload as text (bytes size..length).
|
||||||
|
*/
|
||||||
|
export async function getPayloadAsTxt(blob, size) {
|
||||||
|
const sizeNum = typeof size === "string" ? parseSize(size) : size;
|
||||||
|
const txtBlob = blob.slice(sizeNum, blob.length);
|
||||||
|
return await txtBlob.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON from bytes 12..size (charta metadata only).
|
||||||
|
*/
|
||||||
|
export async function getJsonAsJson(blob, size, out) {
|
||||||
|
const sizeNum = typeof size === "string" ? parseSize(size) : size;
|
||||||
|
const partBlob = blob.slice(12, sizeNum);
|
||||||
|
const txt = await partBlob.text();
|
||||||
|
try {
|
||||||
|
out.json = JSON.parse(txt);
|
||||||
|
out.parse = true;
|
||||||
|
} catch (e) {
|
||||||
|
out.parse = false;
|
||||||
|
}
|
||||||
|
return out.parse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read header (6 bytes) and size (4 bytes) from blob.
|
||||||
|
*/
|
||||||
|
export async function readHeader(blob) {
|
||||||
|
const blobHeader = blob.slice(0, 6);
|
||||||
|
const header = await blobHeader.text();
|
||||||
|
const blobSize = blob.slice(7, 11);
|
||||||
|
const size = await blobSize.text();
|
||||||
|
return { header, size };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parseBlob: selected device blobs. Calls handlers per header, then onParced.
|
||||||
|
* Handlers: setItemsJson, setWidgetsJson, setConfigJson, setScenarioTxt, setSettingsJson,
|
||||||
|
* setSsidJson, setErrorsJson, onDevlis(incDeviceList), setFlashProfileJson, setOtaJson, addCoreMsg, onParced.
|
||||||
|
* For each *Json also setParsed* (e.g. setParsedItemsJson(true)).
|
||||||
|
*/
|
||||||
|
export async function parseBlob(blob, ws, handlers) {
|
||||||
|
const { header, size } = await readHeader(blob);
|
||||||
|
const out = {};
|
||||||
|
|
||||||
|
if (header === "itemsj") {
|
||||||
|
if (await getPayloadAsJson(blob, size, out)) {
|
||||||
|
handlers.setItemsJson(out.json);
|
||||||
|
if (handlers.setParsedItemsJson) handlers.setParsedItemsJson(true);
|
||||||
|
} else if (handlers.setParsedItemsJson) handlers.setParsedItemsJson(false);
|
||||||
|
}
|
||||||
|
if (header === "widget") {
|
||||||
|
if (await getPayloadAsJson(blob, size, out)) {
|
||||||
|
handlers.setWidgetsJson(out.json);
|
||||||
|
if (handlers.setParsedWidgetsJson) handlers.setParsedWidgetsJson(true);
|
||||||
|
} else if (handlers.setParsedWidgetsJson) handlers.setParsedWidgetsJson(false);
|
||||||
|
}
|
||||||
|
if (header === "config") {
|
||||||
|
if (await getPayloadAsJson(blob, size, out)) {
|
||||||
|
handlers.setConfigJson(out.json);
|
||||||
|
if (handlers.setParsedConfigJson) handlers.setParsedConfigJson(true);
|
||||||
|
} else if (handlers.setParsedConfigJson) handlers.setParsedConfigJson(false);
|
||||||
|
}
|
||||||
|
if (header === "scenar") {
|
||||||
|
const txt = await getPayloadAsTxt(blob, size);
|
||||||
|
handlers.setScenarioTxt(txt);
|
||||||
|
}
|
||||||
|
if (header === "settin") {
|
||||||
|
if (await getPayloadAsJson(blob, size, out)) {
|
||||||
|
handlers.setSettingsJson(out.json);
|
||||||
|
if (handlers.setParsedSettingsJson) handlers.setParsedSettingsJson(true);
|
||||||
|
} else if (handlers.setParsedSettingsJson) handlers.setParsedSettingsJson(false);
|
||||||
|
}
|
||||||
|
if (header === "ssidli") {
|
||||||
|
if (await getPayloadAsJson(blob, size, out)) {
|
||||||
|
handlers.setSsidJson(out.json);
|
||||||
|
if (handlers.setParsedSsidJson) handlers.setParsedSsidJson(true);
|
||||||
|
} else if (handlers.setParsedSsidJson) handlers.setParsedSsidJson(false);
|
||||||
|
}
|
||||||
|
if (header === "errors") {
|
||||||
|
if (await getPayloadAsJson(blob, size, out)) {
|
||||||
|
handlers.setErrorsJson(out.json);
|
||||||
|
if (handlers.setParsedErrorsJson) handlers.setParsedErrorsJson(true);
|
||||||
|
} else if (handlers.setParsedErrorsJson) handlers.setParsedErrorsJson(false);
|
||||||
|
}
|
||||||
|
if (header === "devlis") {
|
||||||
|
if (await getPayloadAsJson(blob, size, out)) {
|
||||||
|
if (handlers.setParsedIncDeviceList) handlers.setParsedIncDeviceList(true);
|
||||||
|
if (handlers.onDevlis) await handlers.onDevlis(out.json);
|
||||||
|
} else if (handlers.setParsedIncDeviceList) handlers.setParsedIncDeviceList(false);
|
||||||
|
}
|
||||||
|
if (header === "prfile") {
|
||||||
|
if (await getPayloadAsJson(blob, size, out)) {
|
||||||
|
handlers.setFlashProfileJson(out.json);
|
||||||
|
if (handlers.setParsedFlashProfileJson) handlers.setParsedFlashProfileJson(true);
|
||||||
|
} else if (handlers.setParsedFlashProfileJson) handlers.setParsedFlashProfileJson(false);
|
||||||
|
}
|
||||||
|
if (header === "otaupd") {
|
||||||
|
if (await getPayloadAsJson(blob, size, out)) {
|
||||||
|
handlers.setOtaJson(out.json);
|
||||||
|
if (handlers.setParsedOtaJson) handlers.setParsedOtaJson(true);
|
||||||
|
} else if (handlers.setParsedOtaJson) handlers.setParsedOtaJson(false);
|
||||||
|
}
|
||||||
|
if (header === "corelg") {
|
||||||
|
const txt = await getPayloadAsTxt(blob, size);
|
||||||
|
if (handlers.addCoreMsg) handlers.addCoreMsg(txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handlers.onParced) await handlers.onParced();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parseAllBlob: dashboard blobs (layout, status, params, charta, chartb).
|
||||||
|
* Handlers: updateWidget(statusJson), combineLayoutsInOne(ws, devLayout), mergeParams(devParams), updateAllStatuses(ws), onParced, apdateWidgetByArray(data).
|
||||||
|
*/
|
||||||
|
export async function parseAllBlob(blob, ws, handlers) {
|
||||||
|
const { header, size } = await readHeader(blob);
|
||||||
|
const out = {};
|
||||||
|
|
||||||
|
if (header === "status") {
|
||||||
|
if (await getPayloadAsJson(blob, size, out)) handlers.updateWidget(out.json);
|
||||||
|
}
|
||||||
|
if (header === "layout") {
|
||||||
|
if (await getPayloadAsJson(blob, size, out)) handlers.combineLayoutsInOne(ws, out.json);
|
||||||
|
}
|
||||||
|
if (header === "params") {
|
||||||
|
if (await getPayloadAsJson(blob, size, out)) {
|
||||||
|
handlers.mergeParams(out.json);
|
||||||
|
if (handlers.updateAllStatuses) handlers.updateAllStatuses(ws);
|
||||||
|
if (handlers.onParced) await handlers.onParced();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (header === "charta") {
|
||||||
|
// Two fragments: payload size..length is array text; metadata 12..size is JSON
|
||||||
|
const txt = await getPayloadAsTxt(blob, size);
|
||||||
|
const fixedTxt = "[" + txt.substring(0, txt.length - 1) + "]";
|
||||||
|
let chartJson;
|
||||||
|
try {
|
||||||
|
chartJson = JSON.parse(fixedTxt);
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const addOut = {};
|
||||||
|
if (!(await getJsonAsJson(blob, size, addOut))) return;
|
||||||
|
const finalDataJson = { status: chartJson, ...addOut.json };
|
||||||
|
if (handlers.apdateWidgetByArray) handlers.apdateWidgetByArray(finalDataJson);
|
||||||
|
}
|
||||||
|
if (header === "chartb") {
|
||||||
|
if (await getPayloadAsJson(blob, size, out)) {
|
||||||
|
if (handlers.apdateWidgetByArray) handlers.apdateWidgetByArray(out.json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/lib/deviceConnection.js
Normal file
75
src/lib/deviceConnection.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* Device connection lifecycle: getIP, connectToAllDevices, onOpen behaviour, handleDevListReceived.
|
||||||
|
* No blob parsing; no socket implementation — receives createConnection from api.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { initDevList } from "./deviceListManager.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve device IP by ws index (device.ws === ws).
|
||||||
|
* @param {number} ws
|
||||||
|
* @param {Array} deviceList
|
||||||
|
* @returns {string} ip or "error"
|
||||||
|
*/
|
||||||
|
export function getIP(ws, deviceList) {
|
||||||
|
for (const device of deviceList) {
|
||||||
|
if (device.ws === ws) return device.ip;
|
||||||
|
}
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate deviceList, assign ws = i; for each with status false/undefined call createConnection(i).
|
||||||
|
* @param {Array} deviceList - mutated: deviceList[i].ws = i
|
||||||
|
* @param {function} getSelectedDeviceData - (selectedWs) => void, called once at start
|
||||||
|
* @param {number} selectedWs
|
||||||
|
* @param {function} createConnection - (wsIndex, ip, callbacks) => void
|
||||||
|
*/
|
||||||
|
export function connectToAllDevices(deviceList, getSelectedDeviceData, selectedWs, createConnection) {
|
||||||
|
getSelectedDeviceData(selectedWs);
|
||||||
|
for (let i = 0; i < deviceList.length; i++) {
|
||||||
|
deviceList[i].ws = i;
|
||||||
|
if (deviceList[i].status === false || deviceList[i].status === undefined) {
|
||||||
|
const ip = getIP(i, deviceList);
|
||||||
|
if (ip !== "error") {
|
||||||
|
createConnection(i, ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build onOpen handler: mark online, send /devlist| when first and ws===0, send page name.
|
||||||
|
* @param {object} options - markDeviceStatus(ws, bool), sendMsg(ws, msg), firstDevListRequest, currentPageName, selectedWs, sendCurrentPageNameToSelectedWs()
|
||||||
|
* @returns {function(ws: number)} onOpen(ws)
|
||||||
|
*/
|
||||||
|
export function createOpenHandler(options) {
|
||||||
|
const {
|
||||||
|
markDeviceStatus,
|
||||||
|
sendMsg,
|
||||||
|
firstDevListRequest,
|
||||||
|
currentPageName,
|
||||||
|
selectedWs,
|
||||||
|
sendCurrentPageNameToSelectedWs,
|
||||||
|
} = options;
|
||||||
|
return function onOpen(ws) {
|
||||||
|
markDeviceStatus(ws, true);
|
||||||
|
if (firstDevListRequest && ws === 0) sendMsg(ws, "/devlist|");
|
||||||
|
if (currentPageName === "/|") {
|
||||||
|
sendMsg(ws, currentPageName);
|
||||||
|
} else {
|
||||||
|
if (ws === selectedWs) sendCurrentPageNameToSelectedWs();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When devlis blob received: run initDevList (override/combine, then connect).
|
||||||
|
* @param {Array} incDeviceList - list from device
|
||||||
|
* @param {Array} deviceList - current list (read)
|
||||||
|
* @param {boolean} firstDevListRequest
|
||||||
|
* @param {object} callbacks - setDeviceList, setFirstDevListRequest, setParsedDeviceListJson, onParced, selectedDeviceDataRefresh, connectToAllDevices
|
||||||
|
*/
|
||||||
|
export function handleDevListReceived(incDeviceList, deviceList, firstDevListRequest, callbacks) {
|
||||||
|
initDevList(deviceList, incDeviceList, firstDevListRequest, callbacks);
|
||||||
|
}
|
||||||
73
src/lib/deviceListManager.js
Normal file
73
src/lib/deviceListManager.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* Device list data only: merge, sort, override/combine.
|
||||||
|
* No sockets or pages; state updates via callbacks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge arrays by unique IP (from A, then B items whose ip not in A).
|
||||||
|
* @param {Array} A
|
||||||
|
* @param {Array} B
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
export function combineArrays(A, B) {
|
||||||
|
const ids = new Set(A.map((d) => d.ip));
|
||||||
|
return [...A, ...B.filter((d) => !ids.has(d.ip))];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort list by name, keep first element at index 0.
|
||||||
|
* Mutates list in place.
|
||||||
|
* @param {Array} list
|
||||||
|
*/
|
||||||
|
export function sortList(list) {
|
||||||
|
const firstDev = list.shift();
|
||||||
|
list.sort((a, b) => {
|
||||||
|
if (a.name < b.name) return -1;
|
||||||
|
if (a.name > b.name) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
list.unshift(firstDev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace deviceList with incDeviceList, sort, set first device status true.
|
||||||
|
* @param {Array} incDeviceList
|
||||||
|
* @returns {Array} new device list
|
||||||
|
*/
|
||||||
|
export function devListOverride(incDeviceList) {
|
||||||
|
const list = [...incDeviceList];
|
||||||
|
sortList(list);
|
||||||
|
list[0].status = true;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge deviceList with incDeviceList by IP, sort.
|
||||||
|
* @param {Array} deviceList
|
||||||
|
* @param {Array} incDeviceList
|
||||||
|
* @returns {Array} new device list
|
||||||
|
*/
|
||||||
|
export function devListCombine(deviceList, incDeviceList) {
|
||||||
|
const list = combineArrays(deviceList, incDeviceList);
|
||||||
|
sortList(list);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init after receiving devlis: override or combine, then run callbacks.
|
||||||
|
* @param {Array} deviceList
|
||||||
|
* @param {Array} incDeviceList
|
||||||
|
* @param {boolean} firstDevListRequest
|
||||||
|
* @param {object} callbacks - setDeviceList(list), setFirstDevListRequest(bool), connectToAllDevices(), onParced(), selectedDeviceDataRefresh(), setParsedDeviceListJson(bool)
|
||||||
|
*/
|
||||||
|
export function initDevList(deviceList, incDeviceList, firstDevListRequest, callbacks) {
|
||||||
|
const list = firstDevListRequest
|
||||||
|
? devListOverride(incDeviceList)
|
||||||
|
: devListCombine(deviceList, incDeviceList);
|
||||||
|
callbacks.setDeviceList(list);
|
||||||
|
callbacks.setFirstDevListRequest(false);
|
||||||
|
if (callbacks.setParsedDeviceListJson) callbacks.setParsedDeviceListJson(true);
|
||||||
|
if (callbacks.onParced) callbacks.onParced();
|
||||||
|
if (callbacks.selectedDeviceDataRefresh) callbacks.selectedDeviceDataRefresh();
|
||||||
|
if (callbacks.connectToAllDevices) callbacks.connectToAllDevices();
|
||||||
|
}
|
||||||
95
src/lib/wsReconnect.js
Normal file
95
src/lib/wsReconnect.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* Reconnect ticker and ack timeouts (heartbeat). No deviceConnection import; getIP/connectDevice passed in.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scale number from one range to another.
|
||||||
|
*/
|
||||||
|
function scale(number, inMin, inMax, outMin, outMax) {
|
||||||
|
return ((number - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ackTimeoutsStore = {};
|
||||||
|
const startMillisStore = {};
|
||||||
|
const pingStore = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create ack(ws, st). On st false: set timeout to call markDeviceStatus(ws, false). On st true: clear timeout, set ping on deviceList.
|
||||||
|
* @param {object} deps - markDeviceStatus(ws, bool), getDeviceList(), setDeviceList(list), waitingAckTimeout
|
||||||
|
* @returns {function(ws: number, st: boolean)} ack
|
||||||
|
*/
|
||||||
|
export function createAck(deps) {
|
||||||
|
const { markDeviceStatus, getDeviceList, setDeviceList, waitingAckTimeout } = deps;
|
||||||
|
return function ack(ws, st) {
|
||||||
|
if (!st) {
|
||||||
|
startMillisStore[ws] = Date.now();
|
||||||
|
ackTimeoutsStore[ws] = setTimeout(() => {
|
||||||
|
markDeviceStatus(ws, false);
|
||||||
|
}, waitingAckTimeout);
|
||||||
|
} else {
|
||||||
|
if (ackTimeoutsStore[ws]) clearTimeout(ackTimeoutsStore[ws]);
|
||||||
|
if (startMillisStore[ws]) pingStore[ws] = Date.now() - startMillisStore[ws];
|
||||||
|
const deviceList = getDeviceList();
|
||||||
|
for (let i = 0; i < deviceList.length; i++) {
|
||||||
|
if (deviceList[i].ws === ws) deviceList[i].ping = pingStore[ws];
|
||||||
|
}
|
||||||
|
setDeviceList(deviceList);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the 1s ticker: decrement remainingTimeout, update percent; at 0 reconnect or send /tst| and ack.
|
||||||
|
* @param {object} deps - getDeviceList, send(ws, msg), markDeviceStatus, connectDevice(ws), ack(ws, st), getRemainingTimeout, setRemainingTimeout, reconnectTimeout, getPreventReconnect, setPercent, getRebootOrUpdateProcess, getSocketConnected, setShowAwaitingCircle, setReconnectTimeout, printAllCreatedWs (optional)
|
||||||
|
* @returns {function()} wsTestMsgTask - call once to start; it reschedules itself via setTimeout(wsTestMsgTask, 1000)
|
||||||
|
*/
|
||||||
|
export function createWsTestMsgTask(deps) {
|
||||||
|
const {
|
||||||
|
getDeviceList,
|
||||||
|
send,
|
||||||
|
markDeviceStatus,
|
||||||
|
connectDevice,
|
||||||
|
ack,
|
||||||
|
getRemainingTimeout,
|
||||||
|
setRemainingTimeout,
|
||||||
|
reconnectTimeout,
|
||||||
|
getPreventReconnect,
|
||||||
|
setPercent,
|
||||||
|
getRebootOrUpdateProcess,
|
||||||
|
setRebootOrUpdateProcess,
|
||||||
|
getSocketConnected,
|
||||||
|
setShowAwaitingCircle,
|
||||||
|
setReconnectTimeout,
|
||||||
|
printAllCreatedWs,
|
||||||
|
} = deps;
|
||||||
|
|
||||||
|
function wsTestMsgTask() {
|
||||||
|
const schedule = () => setTimeout(wsTestMsgTask, 1000);
|
||||||
|
schedule(); // reschedule first (same as original)
|
||||||
|
if (getPreventReconnect()) return;
|
||||||
|
let remaining = getRemainingTimeout() - 1;
|
||||||
|
if (getRebootOrUpdateProcess() && getSocketConnected()) {
|
||||||
|
if (setRebootOrUpdateProcess) setRebootOrUpdateProcess(false);
|
||||||
|
if (setReconnectTimeout) setReconnectTimeout(60);
|
||||||
|
remaining = 60;
|
||||||
|
if (setShowAwaitingCircle) setShowAwaitingCircle(false);
|
||||||
|
}
|
||||||
|
setRemainingTimeout(remaining);
|
||||||
|
setPercent(scale(remaining, reconnectTimeout, 0, 0, 100));
|
||||||
|
if (remaining <= 0) {
|
||||||
|
if (setReconnectTimeout) setReconnectTimeout(reconnectTimeout);
|
||||||
|
setRemainingTimeout(reconnectTimeout);
|
||||||
|
if (printAllCreatedWs) printAllCreatedWs();
|
||||||
|
const deviceList = getDeviceList();
|
||||||
|
deviceList.forEach((device) => {
|
||||||
|
if (device.status === false || device.status === undefined) {
|
||||||
|
connectDevice(device.ws);
|
||||||
|
} else {
|
||||||
|
send(device.ws, "/tst|");
|
||||||
|
ack(device.ws, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wsTestMsgTask;
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
import OpenIcon from "../svg/Open.svelte";
|
import OpenIcon from "../svg/Open.svelte";
|
||||||
import Alarm from "../components/Alarm.svelte";
|
import Alarm from "../components/Alarm.svelte";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
import * as portal from "../api/portal.js";
|
||||||
|
|
||||||
export let configJson;
|
export let configJson;
|
||||||
export let widgetsJson;
|
export let widgetsJson;
|
||||||
@@ -206,16 +206,10 @@
|
|||||||
export let moduleOrder = (id, key, value) => {};
|
export let moduleOrder = (id, key, value) => {};
|
||||||
|
|
||||||
const makePost = async () => {
|
const makePost = async () => {
|
||||||
let iotmpostDefault = {
|
const body = {
|
||||||
category: "",
|
category: "",
|
||||||
topic: {
|
topic: { ru: "", en: "" },
|
||||||
ru: "",
|
text: { ru: "", en: "" },
|
||||||
en: "",
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
ru: "",
|
|
||||||
en: "",
|
|
||||||
},
|
|
||||||
config: configJson,
|
config: configJson,
|
||||||
scenario: scenarioTxt,
|
scenario: scenarioTxt,
|
||||||
gallery: [],
|
gallery: [],
|
||||||
@@ -223,51 +217,25 @@
|
|||||||
username: userdata.username,
|
username: userdata.username,
|
||||||
};
|
};
|
||||||
const JWT = Cookies.get("token_iotm2");
|
const JWT = Cookies.get("token_iotm2");
|
||||||
try {
|
const res = await portal.addConfiguration(JWT, body);
|
||||||
let res = await fetch("https://portal.iotmanager.org/api/configurations/add", {
|
if (res.ok && res.data && res.data.result) {
|
||||||
mode: "cors",
|
console.log(res.data.result);
|
||||||
method: "POST",
|
if (res.data.result.acknowledged) {
|
||||||
headers: {
|
window.open("https://portal.iotmanager.org/configs?id=" + res.data.result.insertedId + "&token=" + JWT, "_blank");
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${JWT}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(iotmpostDefault),
|
|
||||||
});
|
|
||||||
const content = await res.json();
|
|
||||||
if (res.ok) {
|
|
||||||
console.log(content.result);
|
|
||||||
if (content.result.acknowledged) {
|
|
||||||
window.open("https://portal.iotmanager.org/configs?id=" + content.result.insertedId + "&token=" + Cookies.get("token_iotm2"), "_blank");
|
|
||||||
}
|
|
||||||
errors = [{ msg: "ok_success" }];
|
|
||||||
} else {
|
|
||||||
errors = content.message;
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
errors = [{ msg: "ok_success" }];
|
||||||
console.log(e);
|
} else if (res.errors) {
|
||||||
|
errors = res.errors;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getConfigs = async () => {
|
const getConfigs = async () => {
|
||||||
try {
|
const JWT = Cookies.get("token_iotm2");
|
||||||
const JWT = Cookies.get("token_iotm2");
|
const res = await portal.getConfigurations(JWT);
|
||||||
let res = await fetch("https://portal.iotmanager.org/api/configurations/get", {
|
if (res.ok && res.configurations != null) {
|
||||||
headers: {
|
configurations = res.configurations;
|
||||||
"Content-Type": "application/json",
|
} else {
|
||||||
Authorization: `Bearer ${JWT}`,
|
console.log("error", "getConfigurations");
|
||||||
},
|
|
||||||
mode: "cors",
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
|
||||||
configurations = await res.json();
|
|
||||||
//configurations.push({ topic: { ru: "123" } });
|
|
||||||
console.log("error", configurations);
|
|
||||||
} else {
|
|
||||||
console.log("error", res.statusText);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("error", e);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<Range bind:value={widget.status} widget={widget} wsPush={(ws, topic, status) => wsPush(ws, topic, status)} />
|
<Range bind:value={widget.status} widget={widget} wsPush={(ws, topic, status) => wsPush(ws, topic, status)} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if widget.widget === "chart"}
|
{#if widget.widget === "chart"}
|
||||||
<Chart bind:value={widget.status} widget={widget} />
|
<Chart widget={widget} />
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import { t, locale, locales } from "../i18n";
|
import { t, locale, locales } from "../i18n";
|
||||||
import { router } from "tinro";
|
import { router } from "tinro";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
import * as portal from "../api/portal.js";
|
||||||
export let show = true;
|
export let show = true;
|
||||||
export let serverOnline;
|
export let serverOnline;
|
||||||
|
|
||||||
@@ -12,25 +13,12 @@
|
|||||||
|
|
||||||
const login = async (user) => {
|
const login = async (user) => {
|
||||||
errors = [];
|
errors = [];
|
||||||
try {
|
const res = await portal.login(user);
|
||||||
let res = await fetch("https://portal.iotmanager.org/api/auth/login", {
|
if (res.ok && res.data && res.data.message) {
|
||||||
mode: "cors",
|
errors = [{ msg: "ok_success_login" }];
|
||||||
method: "POST",
|
saveToken(res.data.message);
|
||||||
headers: {
|
} else if (res.data && res.data.message) {
|
||||||
Accept: "application/json",
|
errors = res.data.message;
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(user),
|
|
||||||
});
|
|
||||||
const content = await res.json();
|
|
||||||
if (res.ok) {
|
|
||||||
errors = [{ msg: "ok_success_login" }];
|
|
||||||
saveToken(content.message);
|
|
||||||
} else {
|
|
||||||
errors = content.message;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import { router } from "tinro";
|
import { router } from "tinro";
|
||||||
import { t, locale, locales } from "../i18n";
|
import { t, locale, locales } from "../i18n";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
import * as portal from "../api/portal.js";
|
||||||
export let show;
|
export let show;
|
||||||
export let flashProfileJson;
|
export let flashProfileJson;
|
||||||
export let otaJson;
|
export let otaJson;
|
||||||
@@ -51,73 +52,33 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getUserBuilds = async () => {
|
const getUserBuilds = async () => {
|
||||||
try {
|
const JWT = Cookies.get("token_iotm2");
|
||||||
const JWT = Cookies.get("token_iotm2");
|
const res = await portal.getUserOrders(JWT);
|
||||||
let res = await fetch("https://portal.iotmanager.org/compiler/userorders", {
|
if (res.ok && res.userBuilds != null) {
|
||||||
headers: {
|
userBuilds = res.userBuilds;
|
||||||
"Content-Type": "application/json",
|
checkStatus(userBuilds);
|
||||||
Authorization: `Bearer ${JWT}`,
|
} else {
|
||||||
},
|
console.log("error", "getUserOrders");
|
||||||
mode: "cors",
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
|
||||||
userBuilds = await res.json();
|
|
||||||
checkStatus(userBuilds);
|
|
||||||
} else {
|
|
||||||
console.log("error", res.statusText);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("error", e);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const delBuild = async (ord) => {
|
const delBuild = async (ord) => {
|
||||||
try {
|
const JWT = Cookies.get("token_iotm2");
|
||||||
const JWT = Cookies.get("token_iotm2");
|
const res = await portal.deleteBuild(JWT, ord.orderId);
|
||||||
let res = await fetch("https://portal.iotmanager.org/compiler/delete/builds/" + ord.orderId, {
|
if (res.ok) await getUserBuilds();
|
||||||
headers: {
|
else console.log("error", "deleteBuild");
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${JWT}`,
|
|
||||||
},
|
|
||||||
mode: "cors",
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
|
||||||
await getUserBuilds();
|
|
||||||
} else {
|
|
||||||
console.log("error", res.statusText);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("error", e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const placeOrder = async () => {
|
const placeOrder = async () => {
|
||||||
delete profile["_id"];
|
delete profile["_id"];
|
||||||
//добавим в тело имя пользователя
|
|
||||||
profile.username = userdata.username;
|
profile.username = userdata.username;
|
||||||
const JWT = Cookies.get("token_iotm2");
|
const JWT = Cookies.get("token_iotm2");
|
||||||
try {
|
const res = await portal.placeOrder(JWT, profile);
|
||||||
let res = await fetch("https://portal.iotmanager.org/compiler/order", {
|
if (res.ok) {
|
||||||
mode: "cors",
|
errors = [{ msg: "ok_success" }];
|
||||||
method: "POST",
|
await getUserBuilds();
|
||||||
headers: {
|
} else if (res.errors) {
|
||||||
"Content-Type": "application/json",
|
errors = res.errors;
|
||||||
Authorization: `Bearer ${JWT}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(profile),
|
|
||||||
});
|
|
||||||
const content = await res.json();
|
|
||||||
if (res.ok) {
|
|
||||||
errors = [{ msg: "ok_success" }];
|
|
||||||
await getUserBuilds();
|
|
||||||
console.log(content.message);
|
|
||||||
} else {
|
|
||||||
errors = content.message;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
export let widget;
|
export let widget;
|
||||||
|
|
||||||
let datachart = {
|
let datachart = {
|
||||||
labels: [0, 0],
|
labels: ["0", "0"],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
name: widget.descr,
|
name: (widget && widget.descr) ? widget.descr : "",
|
||||||
values: [0, 0],
|
values: [0, 0],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -36,33 +36,40 @@
|
|||||||
|
|
||||||
function collectDataToArr() {
|
function collectDataToArr() {
|
||||||
if (prevStatus !== widget.status && !firstTime) {
|
if (prevStatus !== widget.status && !firstTime) {
|
||||||
if (Array.isArray(widget.status)) {
|
if (Array.isArray(widget.status) && widget.status.length > 0) {
|
||||||
//console.log("[i]", "=======================================================");
|
|
||||||
prevStatus = widget.status;
|
prevStatus = widget.status;
|
||||||
|
|
||||||
if (widget.maxCount === 0) {
|
if (widget.maxCount === 0) {
|
||||||
clearCart();
|
clearCart();
|
||||||
widget.status = [];
|
widget.status = [];
|
||||||
console.log("[i]", "clear cart data");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const safeLabels = [];
|
||||||
|
const safeValues = [];
|
||||||
for (let i = 0; i < widget.status.length; i++) {
|
for (let i = 0; i < widget.status.length; i++) {
|
||||||
|
const pt = widget.status[i];
|
||||||
|
const x = Number(pt?.x);
|
||||||
|
const y1 = Number(pt?.y1);
|
||||||
|
if (Number.isNaN(x) || Number.isNaN(y1)) continue;
|
||||||
if (type === "bar") {
|
if (type === "bar") {
|
||||||
labels[i] = getDDMM(widget.status[i].x);
|
safeLabels.push(getDDMM(x));
|
||||||
} else if (i === 0) {
|
} else if (i === 0) {
|
||||||
labels[i] = getDDMM(widget.status[i].x);
|
safeLabels.push(getDDMM(x));
|
||||||
} else {
|
} else {
|
||||||
labels[i] = getHHMM(widget.status[i].x);
|
safeLabels.push(getHHMM(x));
|
||||||
}
|
}
|
||||||
values[i] = [widget.status[i].y1];
|
safeValues.push([y1]);
|
||||||
}
|
}
|
||||||
|
if (safeLabels.length === 0 || safeValues.length === 0) return;
|
||||||
|
|
||||||
|
labels = safeLabels;
|
||||||
|
values = safeValues;
|
||||||
datachart = {
|
datachart = {
|
||||||
labels: labels,
|
labels: labels,
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
name: widget.descr,
|
name: widget.descr || "",
|
||||||
values: values,
|
values: values,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -86,18 +93,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clearCart() {
|
function clearCart() {
|
||||||
widget.status = [];
|
if (widget) widget.status = [];
|
||||||
|
|
||||||
labels = [];
|
labels = [];
|
||||||
values = [];
|
values = [];
|
||||||
|
|
||||||
datachart = {
|
datachart = {
|
||||||
labels: [0, 0],
|
labels: ["0", "0"],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{ name: (widget && widget.descr) ? widget.descr : "", values: [0, 0] },
|
||||||
name: widget.descr,
|
|
||||||
values: [0, 0],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user