update signal logic :: simulator init & correct parsing

This commit is contained in:
moxitech 2024-10-16 06:41:24 +07:00
parent 1116ccfb36
commit d3bcb58a35
19 changed files with 938 additions and 366 deletions

12
DOCS.md
View File

@ -4,4 +4,14 @@ SOCKET MESSAGING:
// PORT CHANGE IN .env // PORT CHANGE IN .env
localhost:9091/storeEvent?client_id=1&message={"me": "update"} localhost:9091/storeEvent?client_id=1&message={"me": "update"}
// CONNECTION: // CONNECTION:
ws://localhost:9091/ws/1 ws://localhost:9091/ws/1
DATABASE LOGS TYPE:
1 - INFO
2 - WARNING
3 - ERROR
4 - ADMIN_ACTION
5 - USER_ACTION
...
10 - RESERV

View File

@ -3,29 +3,29 @@
build: build:
docker compose build docker-compose build
dev: build dev: build
docker compose up -d --force-recreate docker-compose up -d --force-recreate
devf: dev devf: dev
docker compose logs -f docker-compose logs -f
up: up:
docker compose up -d --force-recreate docker-compose up -d --force-recreate
upf: up upf: up
docker compose logs -f docker-compose logs -f
logs: logs:
docker compose logs -f docker-compose logs -f
stop: stop:
echo "!!!!!!!!!!!!!!!!!EXTERMINATUS!!!!!!!!!!!!!!!!!" echo "!!!!!!!!!!!!!!!!!EXTERMINATUS!!!!!!!!!!!!!!!!!"
docker compose stop docker-compose stop
start: start:
docker compose start docker-compose start
drop: drop:
docker-compose down --volumes docker-compose down --volumes

4
ToDO.md.save Normal file
View File

@ -0,0 +1,4 @@
!1 => Написать хук для воспроизведения симуляции
!2 =>

View File

@ -8,6 +8,7 @@
"name": "dsn-application", "name": "dsn-application",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@mui/material": "^6.1.3",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
@ -2445,6 +2446,60 @@
"postcss-selector-parser": "^6.0.10" "postcss-selector-parser": "^6.0.10"
} }
}, },
"node_modules/@emotion/cache": {
"version": "11.13.1",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz",
"integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==",
"dependencies": {
"@emotion/memoize": "^0.9.0",
"@emotion/sheet": "^1.4.0",
"@emotion/utils": "^1.4.0",
"@emotion/weak-memoize": "^0.4.0",
"stylis": "4.2.0"
}
},
"node_modules/@emotion/hash": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="
},
"node_modules/@emotion/memoize": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
"integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
},
"node_modules/@emotion/serialize": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz",
"integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==",
"dependencies": {
"@emotion/hash": "^0.9.2",
"@emotion/memoize": "^0.9.0",
"@emotion/unitless": "^0.10.0",
"@emotion/utils": "^1.4.1",
"csstype": "^3.0.2"
}
},
"node_modules/@emotion/sheet": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="
},
"node_modules/@emotion/unitless": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
"integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="
},
"node_modules/@emotion/utils": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz",
"integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA=="
},
"node_modules/@emotion/weak-memoize": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="
},
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@ -3425,6 +3480,213 @@
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@mui/core-downloads-tracker": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.3.tgz",
"integrity": "sha512-ajMUgdfhTb++rwqj134Cq9f4SRN8oXUqMRnY72YBnXiXai3olJLLqETheRlq3MM8wCKrbq7g6j7iWL1VvP44VQ==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
}
},
"node_modules/@mui/material": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.3.tgz",
"integrity": "sha512-loV5MBoMKLrK80JeWINmQ1A4eWoLv51O2dBPLJ260IAhupkB3Wol8lEQTEvvR2vO3o6xRHuXe1WaQEP6N3riqg==",
"dependencies": {
"@babel/runtime": "^7.25.6",
"@mui/core-downloads-tracker": "^6.1.3",
"@mui/system": "^6.1.3",
"@mui/types": "^7.2.18",
"@mui/utils": "^6.1.3",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.11",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1",
"react-is": "^18.3.1",
"react-transition-group": "^4.4.5"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@mui/material-pigment-css": "^6.1.3",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@mui/material-pigment-css": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/material/node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
},
"node_modules/@mui/private-theming": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.3.tgz",
"integrity": "sha512-XK5OYCM0x7gxWb/WBEySstBmn+dE3YKX7U7jeBRLm6vHU5fGUd7GiJWRirpivHjOK9mRH6E1MPIVd+ze5vguKQ==",
"dependencies": {
"@babel/runtime": "^7.25.6",
"@mui/utils": "^6.1.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/styled-engine": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.3.tgz",
"integrity": "sha512-i4yh9m+eMZE3cNERpDhVr6Wn73Yz6C7MH0eE2zZvw8d7EFkIJlCQNZd1xxGZqarD2DDq2qWHcjIOucWGhxACtA==",
"dependencies": {
"@babel/runtime": "^7.25.6",
"@emotion/cache": "^11.13.1",
"@emotion/serialize": "^1.3.2",
"@emotion/sheet": "^1.4.0",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
}
}
},
"node_modules/@mui/system": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.3.tgz",
"integrity": "sha512-ILaD9UsLTBLjMcep3OumJMXh1PYr7aqnkHm/L47bH46+YmSL1zWAX6tWG8swEQROzW2GvYluEMp5FreoxOOC6w==",
"dependencies": {
"@babel/runtime": "^7.25.6",
"@mui/private-theming": "^6.1.3",
"@mui/styled-engine": "^6.1.3",
"@mui/types": "^7.2.18",
"@mui/utils": "^6.1.3",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/types": {
"version": "7.2.18",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.18.tgz",
"integrity": "sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==",
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/utils": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.3.tgz",
"integrity": "sha512-4JBpLkjprlKjN10DGb1aiy/ii9TKbQ601uSHtAmYFAS879QZgAD7vRnv/YBE4iBbc7NXzFgbQMCOFrupXWekIA==",
"dependencies": {
"@babel/runtime": "^7.25.6",
"@mui/types": "^7.2.18",
"@types/prop-types": "^15.7.13",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"react-is": "^18.3.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/utils/node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
},
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
"version": "5.1.1-v1", "version": "5.1.1-v1",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
@ -3549,6 +3811,15 @@
} }
} }
}, },
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.19.2", "version": "1.19.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz",
@ -4769,6 +5040,14 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/react-transition-group": {
"version": "4.4.11",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz",
"integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/resolve": { "node_modules/@types/resolve": {
"version": "1.17.1", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@ -6611,6 +6890,14 @@
"wrap-ansi": "^7.0.0" "wrap-ansi": "^7.0.0"
} }
}, },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"engines": {
"node": ">=6"
}
},
"node_modules/co": { "node_modules/co": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -7638,6 +7925,15 @@
"utila": "~0.4" "utila": "~0.4"
} }
}, },
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/dom-serializer": { "node_modules/dom-serializer": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
@ -16404,6 +16700,21 @@
} }
} }
}, },
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -17951,6 +18262,11 @@
"postcss": "^8.2.15" "postcss": "^8.2.15"
} }
}, },
"node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
},
"node_modules/sucrase": { "node_modules/sucrase": {
"version": "3.35.0", "version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",

View File

@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@mui/material": "^6.1.3",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",

View File

@ -27,7 +27,7 @@ const useWebsocketConnection = (userToken, roomHash) => {
return prevState; return prevState;
}); });
} catch (error) { } catch (error) {
console.error('Error parsing WebSocket message:', error); console.error('Error parsing WebSocket message:', error, event.data);
} }
}; };

View File

@ -14,11 +14,12 @@ import { ModalJson } from './Modal/MJson';
import { ModalMap } from './Modal/MMap'; import { ModalMap } from './Modal/MMap';
import { CtxMenu } from './ContextMenu/CtxMenu'; import { CtxMenu } from './ContextMenu/CtxMenu';
import { ProgramInterface } from './MainScreen/ProgramInterface'; import { ProgramInterface } from './MainScreen/ProgramInterface';
import { SimulatorInterface } from './SimScreen/SimScreen';
const Dashboard = () => { const Dashboard = () => {
const mountRef = useRef(null); // Указатель монтирования const mountRef = useRef(null); // Указатель монтирования
const sceneRef = useRef(null); // Указатель на сцену const sceneRef = useRef(null); // Указатель на сцену
const cameraRef = useRef(null); // Указатель на камеру
const {websocketStruct, sendMessage} = useWebsocketConnection(getCustomCookie("userToken"), 1); const {websocketStruct, sendMessage} = useWebsocketConnection(getCustomCookie("userToken"), 1);
const [heightData, setHeightData] = useState([new HeightPoint()]); // Высоты const [heightData, setHeightData] = useState([new HeightPoint()]); // Высоты
const [formData, setFormData] = useState({ x: '', y: '', height: '' }); // Форма для ввода данных карты const [formData, setFormData] = useState({ x: '', y: '', height: '' }); // Форма для ввода данных карты
@ -29,7 +30,6 @@ const Dashboard = () => {
const [selectedBaseStation, setSelectedBaseStation] = useState(null); // Состояние для выбранной базовой станции const [selectedBaseStation, setSelectedBaseStation] = useState(null); // Состояние для выбранной базовой станции
const [mapSettings, setMapSettings] = useState({maxHeight: 20, coordX: 0, coordY: 0, }); // Настройки карты const [mapSettings, setMapSettings] = useState({maxHeight: 20, coordX: 0, coordY: 0, }); // Настройки карты
const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 }); // контекстное меню сцены const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 }); // контекстное меню сцены
const [objectType, setObjectType] = useState(1); // 1 = Drone, 2 = Base Station const [objectType, setObjectType] = useState(1); // 1 = Drone, 2 = Base Station
const [formObjectData, setFormObjectData] = useState({ const [formObjectData, setFormObjectData] = useState({
name: "", name: "",
@ -52,6 +52,204 @@ const Dashboard = () => {
meshName: "", // Название сети meshName: "", // Название сети
}); });
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [isSimulationRunning, setIsSimulationRunning] = useState(false);
// Рекурсивная функция для поиска объекта среди детей
const containsObjectRecursively = (parent, target) => {
if (parent === target) {
return true;
}
for (let child of parent.children) {
if (containsObjectRecursively(child, target)) {
return true;
}
}
return false;
};
// Обработка websocket
useEffect(() => {
if (websocketStruct && websocketStruct.modulation) {
const modulation = websocketStruct.modulation;
// Парсинг карты
const map = modulation.map;
setMapSettings({
name: map.name,
minBound: map.map.MinBound,
maxBound: map.map.MaxBound,
heightData: map.map.HeightData,
maxHeight: 20,
});
setHeightData(map.map.HeightData.flatMap((row, x) => row.map((height, y) => new HeightPoint(x, y, height))));
// Парсинг объектов
const parsedDrones = [];
const parsedBaseStations = [];
modulation.objects.forEach((obj) => {
if (obj.type === 1) {
const drone = new Drone(obj.name);
drone.setPosition(...obj.coords);
parsedDrones.push(drone);
} else if (obj.type === 2) {
const base = new BaseStation(obj.name);
base.setPosition(...obj.coords);
parsedBaseStations.push(base);
}
});
setDrones(parsedDrones);
setBaseStation(parsedBaseStations);
}
}, [websocketStruct]);
useEffect(() => {
if (!mountRef.current) return;
let width = mountRef.current.clientWidth;
let height = mountRef.current.clientHeight;
// Создаем объект сцены
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
// Сохраняем ссылку на сцену
sceneRef.current = scene;
// Создаем камеру
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 2000);
camera.position.set(0, 80, 120);
cameraRef.current = camera;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
mountRef.current.appendChild(renderer.domElement);
// Плоскость для высотной карты :: TODO :: W/H/Ws/Hs change
const geometry = new THREE.PlaneGeometry(200, 200, 20, 20);
const vertices = geometry.attributes.position.array;
const colors = [];
const color = new THREE.Color();
const minHeight = Math.min(...heightData.map((point) => point.height));
const maxHeight = Math.max(mapSettings.maxHeight, ...heightData.map((point) => point.height));
for (let i = 0; i < vertices.length; i += 3) {
const x = Math.floor(i / 3) % 5;
const y = Math.floor(i / (3 * 5));
const heightPoint = heightData.find((point) => point.x === x && point.y === y);
const heightValue = heightPoint ? heightPoint.height : 0;
vertices[i + 2] = heightValue;
const normalizedHeight = (heightValue - minHeight) / (maxHeight - minHeight);
color.setHSL(0.7 * (1 - normalizedHeight), 1, 0.5);
colors.push(color.r, color.g, color.b);
}
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
const material = new THREE.MeshBasicMaterial({
vertexColors: true,
side: THREE.DoubleSide,
wireframe: false,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.rotation.x = -Math.PI / 2;
scene.add(mesh);
const axesHelper = new THREE.AxesHelper(100);
scene.add(axesHelper);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 50, 50).normalize();
scene.add(light);
const handleMouseMove = (event) => {
let x = event.x;
let y = event.y;
setMouseState({ x: x, y: y, z: 0 });
}
const animate = () => {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
renderer.domElement.addEventListener('click', handleClick);
renderer.domElement.addEventListener('mousemove', handleMouseMove);
animate();
drones.forEach(droneData => {
scene.add(droneData.getObject());
});
baseStation.forEach(b => {
scene.add(b.getObject());
});
// Обработка нажатия ПКМ
renderer.domElement.addEventListener('contextmenu', (event) => {
event.preventDefault();
setContextMenu({
visible: true,
x: event.clientX,
y: event.clientY,
});
});
return () => {
if (mountRef.current) {
mountRef.current.removeChild(renderer.domElement);
}
};
}, [heightData, mapSettings]);
// Обработка кликов на сцене
const handleClick = (event) => {
// Рассчитываем координаты мыши в нормализованной системе координат и округляем до целого числа
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
console.log('Mouse coordinates:', mouse);
// Создаем объект Raycaster для проверки пересечений
const raycaster = new THREE.Raycaster();
const rayHelper = new THREE.ArrowHelper(raycaster.ray.direction, raycaster.ray.origin, 100, 0xff4444);
sceneRef.current.add(rayHelper);
raycaster.setFromCamera(mouse, cameraRef.current);
// Проверяем пересечения с объектами дронов
const intersects = raycaster.intersectObjects(drones.map(drone => drone.getObject()), true);
console.log('Intersections:', intersects);
console.log('Drones:', drones.map(drone => [drone.getObject(), drone.getObject().position, drone.name]));
if (intersects.length > 0) {
const selected = intersects[0].object;
console.log('Clicked on:', selected); // Лог для проверки объекта
// Поиск дрона рекурсивно
const drone = drones.find(drone => containsObjectRecursively(drone.getObject(), selected));
if (drone) {
// Сбрасываем цвет предыдущего выбранного дрона
if (selectedDrone) {
drone.getObject().visible = true;
}
// Устанавливаем цвет выбранного дрона
drone.getObject().visible = false;
console.log(drone);
setSelectedDrone(drone);
}
} else {
// Сбрасываем выбор, если ни один дрон не был выбран
if (selectedDrone) {
selectedDrone.getObject().visible = true;
setSelectedDrone(null);
}
}
};
// Остальной код компонента остается неизменным
const handleSearch = () => { const handleSearch = () => {
const foundDrone = drones.find(drone => drone.name.toLowerCase() === searchTerm.toLowerCase()); const foundDrone = drones.find(drone => drone.name.toLowerCase() === searchTerm.toLowerCase());
const foundBaseStation = baseStation.find(base => base.name.toLowerCase() === searchTerm.toLowerCase()); const foundBaseStation = baseStation.find(base => base.name.toLowerCase() === searchTerm.toLowerCase());
@ -122,204 +320,6 @@ const Dashboard = () => {
isModalSearchOpen: false, isModalSearchOpen: false,
}); });
}; };
// Обработка websocket
useEffect(() => {
if (websocketStruct && websocketStruct.modulation) {
const modulation = websocketStruct.modulation;
// Парсинг карты
const map = modulation.map;
setMapSettings({
name: map.name,
minBound: map.map.MinBound,
maxBound: map.map.MaxBound,
heightData: map.map.HeightData,
maxHeight: 20,
});
setHeightData(map.map.HeightData.flatMap((row, x) => row.map((height, y) => new HeightPoint(x, y, height))));
// Парсинг объектов
const parsedDrones = [];
const parsedBaseStations = [];
modulation.objects.forEach((obj) => {
if (obj.type === 1) {
const drone = new Drone(obj.name);
drone.setPosition(...obj.coords);
parsedDrones.push(drone);
} else if (obj.type === 2) {
const base = new BaseStation(obj.name);
base.setPosition(...obj.coords);
parsedBaseStations.push(base);
}
});
setDrones(parsedDrones);
setBaseStation(parsedBaseStations);
}
}, [websocketStruct]);
useEffect(() => {
if (!mountRef.current) return;
let width = mountRef.current.clientWidth;
let height = mountRef.current.clientHeight;
// Создаем объект сцены
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
// Сохраняем ссылку на сцену
sceneRef.current = scene;
// Создаем камеру
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 2000);
camera.position.set(0, 80, 120);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
mountRef.current.appendChild(renderer.domElement);
// Плоскость для высотной карты :: TODO :: W/H/Ws/Hs change
const geometry = new THREE.PlaneGeometry(200, 200, 20, 20);
const vertices = geometry.attributes.position.array;
const colors = [];
const color = new THREE.Color();
const minHeight = Math.min(...heightData.map((point) => point.height));
const maxHeight = Math.max(mapSettings.maxHeight, ...heightData.map((point) => point.height));
for (let i = 0; i < vertices.length; i += 3) {
const x = Math.floor(i / 3) % 5;
const y = Math.floor(i / (3 * 5));
const heightPoint = heightData.find((point) => point.x === x && point.y === y);
const heightValue = heightPoint ? heightPoint.height : 0;
vertices[i + 2] = heightValue;
const normalizedHeight = (heightValue - minHeight) / (maxHeight - minHeight);
color.setHSL(0.7 * (1 - normalizedHeight), 1, 0.5);
colors.push(color.r, color.g, color.b);
}
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
const material = new THREE.MeshBasicMaterial({
vertexColors: true,
side: THREE.DoubleSide,
wireframe: false,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.rotation.x = -Math.PI / 2;
scene.add(mesh);
const axesHelper = new THREE.AxesHelper(100);
scene.add(axesHelper);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 50, 50).normalize();
scene.add(light);
// Обработка кликов на сцене
const handleClick = (event) => {
// Рассчитываем координаты мыши в нормализованной системе координат и округляем до целого числа
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
console.log('Mouse coordinates:', mouse);
// Создаем объект Raycaster для проверки пересечений
const raycaster = new THREE.Raycaster();
const rayHelper = new THREE.ArrowHelper(raycaster.ray.direction, raycaster.ray.origin, 100, 0xff4444);
scene.add(rayHelper);
raycaster.setFromCamera(mouse, camera);
// Проверяем пересечения с объектами дронов
const intersects = raycaster.intersectObjects(drones.map(drone => drone.getObject()), true);
console.log('Intersections:', intersects);
console.log('Drones:', drones.map(drone => [drone.getObject(), drone.getObject().position, drone.name]));
if (intersects.length > 0) {
const selected = intersects[0].object;
console.log('Clicked on:', selected); // Лог для проверки объекта
// Поиск дрона рекурсивно
const drone = drones.find(drone => containsObjectRecursively(drone.getObject(), selected));
if (drone) {
// Сбрасываем цвет предыдущего выбранного дрона
if (selectedDrone) {
drone.getObject().visible = true;
// selectedDrone.getObject().material.color.set(0xff0000);
}
// Устанавливаем цвет выбранного дрона
// drone.getObject().material.color.set(0xff1111);
drone.getObject().visible = false;
console.log(drone);
setSelectedDrone(drone);
}
} else {
// Сбрасываем выбор, если ни один дрон не был выбран
if (selectedDrone) {
// selectedDrone.getObject().material.color.set(0xff0000);
selectedDrone .getObject().visible = true;
setSelectedDrone(null);
}
}
};
// Рекурсивная функция для поиска объекта среди детей
const containsObjectRecursively = (parent, target) => {
if (parent === target) {
return true;
}
for (let child of parent.children) {
if (containsObjectRecursively(child, target)) {
return true;
}
}
return false;
};
const handleMouseMove = (event) => {
let x = event.x;
let y = event.y;
setMouseState({ x: x, y: y, z: 0 });
}
const animate = () => {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
renderer.domElement.addEventListener('click', handleClick);
renderer.domElement.addEventListener('mousemove', handleMouseMove);
animate();
drones.forEach(droneData => {
scene.add(droneData.getObject());
});
baseStation.forEach(b => {
scene.add(b.getObject());
});
// Обработка нажатия ПКМ
renderer.domElement.addEventListener('contextmenu', (event) => {
event.preventDefault();
setContextMenu({
visible: true,
x: event.clientX,
y: event.clientY,
});
});
return () => {
if (mountRef.current) {
mountRef.current.removeChild(renderer.domElement);
}
};
}, [heightData, mapSettings]);
// Добавление дрона в сцену // Добавление дрона в сцену
const handleAddDrone = () => { const handleAddDrone = () => {
@ -418,12 +418,8 @@ const containsObjectRecursively = (parent, target) => {
}; };
// Открытие контекстного меню // Открытие контекстного меню
const handleContextMenuClick = (action) => { const handleContextMenuClick = (action) => {
if (action === 'add_base'){ if (action === 'add_base'){ handleAddBaseStation();}
handleAddBaseStation(); else if (action === 'add') { handleAddDrone(); } else if (action === 'save') {
}
else if (action === 'add') {
handleAddDrone();
} else if (action === 'save') {
console.log('Сохранить промежуточный результат'); console.log('Сохранить промежуточный результат');
} }
setContextMenu({ ...contextMenu, visible: false }); setContextMenu({ ...contextMenu, visible: false });
@ -471,16 +467,35 @@ const containsObjectRecursively = (parent, target) => {
handleInputChange={handleInputChange} handleInputChange={handleInputChange}
mapSettings={mapSettings} mapSettings={mapSettings}
handleSettingsChange={handleSettingsChange}/> handleSettingsChange={handleSettingsChange}/>
<ul className="nav">
<li className="nav-item">
<a className="nav-link active"onClick={() => setIsSimulationRunning(true)} >Активная</a>
</li>
<li className="nav-item">
<a className="nav-link" onClick={() => setIsSimulationRunning(true)}>Симуляция</a>
</li>
</ul>
{/* Окно программы */} {/* Окно программы */}
<ProgramInterface {isSimulationRunning ? (
mouseState={mouseState} // isSimulationRunning
mountRef={mountRef} <SimulatorInterface
openModal={openModal} mouseState={mouseState}
sendMessage={sendMessage} mountRef={mountRef}
RunSimulatorSignal={RunSimulatorSignal} openModal={openModal}
getCustomCookie={getCustomCookie} sendMessage={sendMessage}
/> RunSimulatorSignal={RunSimulatorSignal}
getCustomCookie={getCustomCookie}
/>
) : (
<ProgramInterface
mouseState={mouseState}
mountRef={mountRef}
openModal={openModal}
sendMessage={sendMessage}
RunSimulatorSignal={RunSimulatorSignal}
getCustomCookie={getCustomCookie}
/>
)}
<CtxMenu <CtxMenu
contextMenu={contextMenu} contextMenu={contextMenu}
handleContextMenuClick={handleContextMenuClick} handleContextMenuClick={handleContextMenuClick}

View File

@ -1,14 +1,23 @@
import React from 'react'; import React from 'react';
import ModalWindow from '../components/Modal/Modal'; import ModalWindow from '../components/Modal/Modal';
export const ModalDelete = ({ isModalDeleteOpen, closeAllModals, handleDeleteSignal }) => {
export const ModalDelete = ({isModalDeleteOpen, closeAllModals, handleDeleteSignal}) => {
return ( return (
<ModalWindow isOpen={isModalDeleteOpen} onClose={closeAllModals}> <ModalWindow isOpen={isModalDeleteOpen} onClose={closeAllModals}>
<h2>Удаление</h2> <div className="modal-dialog">
<h2>Вы точно хотите удалить объект X?</h2> <div className="modal-content">
<button onClick={closeAllModals}>Отмена</button> <div className="modal-header">
<button onClick={() => handleDeleteSignal(1) }>Удалить</button> <h5 className="modal-title">Удаление</h5>
</ModalWindow> </div>
) <div className="modal-body">
} <p>Вы точно хотите удалить объект X?</p>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={closeAllModals}>Отмена</button>
<button type="button" className="btn btn-danger" onClick={() => handleDeleteSignal(1)}>Удалить</button>
</div>
</div>
</div>
</ModalWindow>
);
};

View File

@ -2,14 +2,25 @@ import React from 'react';
import ModalWindow from '../components/Modal/Modal'; import ModalWindow from '../components/Modal/Modal';
export const ModalJson= ({isModalOpen, closeAllModals}) => { export const ModalJson = ({ isModalOpen, closeAllModals }) => {
return ( return (
<ModalWindow isOpen={isModalOpen} onClose={closeAllModals}> <ModalWindow isOpen={isModalOpen} onClose={closeAllModals}>
<h2>Загрузка JSON</h2> <div className="modal-dialog">
<p>Выберите или переташите файл для загрузки</p> <div className="modal-content">
<input type='file'/> <div className="modal-header">
<button onClick={closeAllModals}>Отмена</button> <h5 className="modal-title">Загрузка JSON</h5>
<button onClick={() => alert('Загружено')}>Загрузить</button> <button type="button" className="btn-close" aria-label="Close" onClick={closeAllModals}></button>
</div>
<div className="modal-body">
<p>Выберите или переташите файл для загрузки</p>
<input type="file" className="form-control" />
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={closeAllModals}>Отмена</button>
<button type="button" className="btn btn-success" onClick={() => alert('Загружено')}>Загрузить</button>
</div>
</div>
</div>
</ModalWindow> </ModalWindow>
) );
} };

View File

@ -1,18 +1,21 @@
import React from 'react'; import React from 'react';
import ModalWindow from '../components/Modal/Modal'; import ModalWindow from '../components/Modal/Modal';
export const ModalMap = ({ isModalMapOpen, closeAllModals, handleAddHeight, formData, handleInputChange, mapSettings, handleSettingsChange }) => {
export const ModalMap = ({isModalMapOpen, closeAllModals, handleAddHeight, formData, handleInputChange, mapSettings, handleSettingsChange}) => { return (
return ( <ModalWindow isOpen={isModalMapOpen} onClose={closeAllModals}>
<ModalWindow isOpen={isModalMapOpen} onClose={closeAllModals}> <div className="modal-header">
<h2>Настройки карты</h2> <h5 className="modal-title">Настройки карты</h5>
<div className="columns"> <button type="button" className="btn-close" aria-label="Close" onClick={closeAllModals}></button>
<div className='column'> </div>
<form onSubmit={handleAddHeight} style={{ marginTop: '20px' }}> <div className="modal-body">
<div> <div className="row">
<label>Координата X:</label> <div className="col-md-6">
<form onSubmit={handleAddHeight} className="mt-3">
<div className="mb-3">
<label className="form-label">Координата X:</label>
<input <input
className="input is-primary" className="form-control"
type="number" type="number"
name="x" name="x"
value={formData.x} value={formData.x}
@ -20,10 +23,10 @@ export const ModalMap = ({isModalMapOpen, closeAllModals, handleAddHeight, formD
required required
/> />
</div> </div>
<div> <div className="mb-3">
<label>Координата Y:</label> <label className="form-label">Координата Y:</label>
<input <input
className="input is-primary" className="form-control"
type="number" type="number"
name="y" name="y"
value={formData.y} value={formData.y}
@ -31,10 +34,10 @@ export const ModalMap = ({isModalMapOpen, closeAllModals, handleAddHeight, formD
required required
/> />
</div> </div>
<div> <div className="mb-3">
<label>Высота:</label> <label className="form-label">Высота:</label>
<input <input
className="input is-primary" className="form-control"
type="number" type="number"
name="height" name="height"
value={formData.height} value={formData.height}
@ -42,28 +45,31 @@ export const ModalMap = ({isModalMapOpen, closeAllModals, handleAddHeight, formD
required required
/> />
</div> </div>
<button className='button' type="submit">Добавить точку высоты</button> <button className="btn btn-primary" type="submit">Добавить точку высоты</button>
</form> </form>
</div> </div>
{/* Настройки карты */} {/* Настройки карты */}
<div className='column'> <div className="col-md-6">
<div style={{ marginTop: '20px' }}> <div className="mt-3">
<h2>Настройки карты</h2> <h5>Настройки карты</h5>
<div> <div className="mb-3">
<label>Максимальная высота:</label> <label className="form-label">Максимальная высота:</label>
<input <input
className="input" className="form-control"
type="number" type="number"
name="maxHeight" name="maxHeight"
value={mapSettings.maxHeight} value={mapSettings.maxHeight}
onChange={handleSettingsChange} onChange={handleSettingsChange}
/> />
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<button onClick={closeAllModals}>Отмена</button> <div className="modal-footer">
<button onClick={() => alert('Обновлена карта!')}>Готово</button> <button className="btn btn-secondary" onClick={closeAllModals}>Отмена</button>
</ModalWindow> <button className="btn btn-success" onClick={() => alert('Обновлена карта!')}>Готово</button>
) </div>
} </ModalWindow>
);
};

View File

@ -2,17 +2,28 @@ import React from 'react';
import ModalWindow from '../components/Modal/Modal'; import ModalWindow from '../components/Modal/Modal';
export const ModalSearch = ({isModalSearchOpen, closeAllModals, handleSearch, searchTerm, setSearchTerm}) => { export const ModalSearch = ({ isModalSearchOpen, closeAllModals, handleSearch, searchTerm, setSearchTerm }) => {
return ( return (
<ModalWindow isOpen={isModalSearchOpen} onClose={closeAllModals}> <ModalWindow isOpen={isModalSearchOpen} onClose={closeAllModals}>
<h2>Поиск</h2> <div className="modal-dialog">
<input <div className="modal-content">
type="text" <div className="modal-header">
placeholder="Введите имя" <h5 className="modal-title">Поиск</h5>
value={searchTerm} </div>
onChange={(e) => setSearchTerm(e.target.value)} <div className="modal-body">
/> <input
<button onClick={handleSearch}>Найти</button> type="text"
className="form-control"
placeholder="Введите имя"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-primary" onClick={handleSearch}>Найти</button>
</div>
</div>
</div>
</ModalWindow> </ModalWindow>
) );
} };

View File

@ -2,35 +2,42 @@ import React from 'react';
import '../UserAccount.scss'; import '../UserAccount.scss';
const PrevCalc = () => { const PrevCalc = () => {
return ( return (
<table className="table"> <div className="container mt-4">
<thead> <h2 className="mb-4 text-center">История Калькуляций</h2>
<tr> <table className="table table-hover table-striped table-bordered">
<th scope='col'><abbr title="Уникальный идентификатор в базе данных">UID</abbr></th> <thead className="thead-dark">
<th scope='col'><abbr title="Название">Имя</abbr></th> <tr>
<th scope='col'>Пользователь</th> <th scope='col'><abbr title="Уникальный идентификатор в базе данных">UID</abbr></th>
<th scope='col'>Время начала</th> <th scope='col'><abbr title="Название">Имя</abbr></th>
<th scope='col'>Время конца</th> <th scope='col'>Пользователь</th>
<th scope='col'>Действия</th> <th scope='col'>Время начала</th>
</tr> <th scope='col'>Время конца</th>
</thead> <th scope='col'>Действия</th>
<tbody> </tr>
<tr> </thead>
<th scope="row">1</th> <tbody>
<td>Калькуляция полета до варшавы</td> <tr>
<td>Гитлер</td> <th scope="row">1</th>
<td>1:00</td> <td>Калькуляция полета до Варшавы</td>
<td>4:04</td> <td>Гитлер</td>
<td> <td>1:00</td>
<button className='btn btn-primary'> <td>4:04</td>
Загрузить <td>
</button> <div className="btn-group" role="group">
</td> <button className='btn btn-primary'>
</tr> Загрузить
</tbody> </button>
</table> <button className='btn btn-danger ml-2'>
) Удалить
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
);
}; };
export default PrevCalc; export default PrevCalc;

View File

@ -0,0 +1,78 @@
import React, { useEffect, useRef, useState } from 'react';
export const SimulatorInterface = ({ mouseState, sendMessage, RunSimulatorSignal, getCustomCookie }) => {
const [time, setTime] = useState(0);
const [simulationState, setSimulationState] = useState({});
const [isPlaying, setIsPlaying] = useState(false);
const mountRef = useRef(null);
useEffect(() => {
if (mountRef.current) {
// Инициализация сцены происходит при монтировании компонента
initializeScene(mountRef.current);
}
}, [mountRef.current]);
const initializeScene = (container) => {
// Здесь должна быть логика создания и инициализации сцены внутри контейнера
// Например, Three.js сцена
console.log('Scene initialized inside container:', container);
};
const handleSliderChange = (event) => {
const newValue = event.target.value;
setTime(newValue);
// Обновляем состояние симуляции на основе времени
const newSimulationState = getSimulationState(newValue);
setSimulationState(newSimulationState);
};
const handlePlayPause = () => {
if (isPlaying) {
setIsPlaying(false);
} else {
setIsPlaying(true);
sendMessage(RunSimulatorSignal(getCustomCookie("userToken")));
}
};
const getSimulationState = (timeValue) => {
// Здесь должна быть логика получения состояния симуляции на основе времени
// Например, используйте временную шкалу и объекты, чтобы получить правильное состояние
return {};
};
return (
<div className='coler-border'>
<div>
<p> X: {mouseState.x} Y: {mouseState.y} Z: {mouseState.z}</p>
</div>
<div ref={mountRef} style={{
minWidth: '100%',
minHeight: '60vh',
position: 'relative'
}} />
<div style={{ display: 'flex', alignItems: 'center', margin: '20px 0' }}>
<button onClick={handlePlayPause} style={{
backgroundColor: '#007bff',
color: 'white',
border: 'none',
padding: '10px 20px',
cursor: 'pointer',
borderRadius: '4px'
}}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<input
type="range"
value={time}
onChange={handleSliderChange}
min={0}
max={100}
step={1}
style={{ flexGrow: 1, marginLeft: '20px', marginRight: '20px' }}
/>
</div>
</div>
);
};

View File

@ -0,0 +1,10 @@
package database
import "gorm.io/gorm"
// Struct has represent a system log
type Logs struct {
gorm.Model
Log string `json:"log"`
LogType int `json:"type"`
}

View File

@ -23,10 +23,10 @@ type MapPartial struct {
} }
type SystemObject struct { type SystemObject struct {
Name string `json:"name"` Name string `json:"name"`
Type int `json:"type"` // 0: drone, 1: base_station Type int `json:"type"` // 0: drone, 1: base_station
Coords [3]int `json:"coords"` Coords [3]int `json:"coords"`
Params *map[string]string `json:"params"` Params *map[string]interface{} `json:"params"`
} }
// DeleteObjectIfExists удаляет объект если он найден // DeleteObjectIfExists удаляет объект если он найден
@ -63,11 +63,11 @@ func (o *Modulation) StartNewSimulation(timestep int, user_id string) {
continue continue
} }
params := *v.Params params := v.Params
if v.Type == 1 { if v.Type == 1 {
// Парсинг параметров для дронов // Парсинг параметров для дронов
antennaDirection := [3]float64{1, 0, 0} // значение по умолчанию antennaDirection := [3]float64{1, 0, 0} // значение по умолчанию
if dir, ok := params["antennaDirections"]; ok && dir != "" { if dir, ok := (*params)["$1"].(string); ok && dir != "" {
directionValues := strings.Split(dir, ",") directionValues := strings.Split(dir, ",")
if len(directionValues) == 3 { if len(directionValues) == 3 {
antennaDirection[0], _ = strconv.ParseFloat(directionValues[0], 64) antennaDirection[0], _ = strconv.ParseFloat(directionValues[0], 64)
@ -77,12 +77,12 @@ func (o *Modulation) StartNewSimulation(timestep int, user_id string) {
} }
antennaRadius := 100.0 // значение по умолчанию antennaRadius := 100.0 // значение по умолчанию
if radius, ok := params["antennaRadius"]; ok && radius != "" { if radius, ok := (*params)["$1"].(string); ok && radius != "" {
antennaRadius, _ = strconv.ParseFloat(radius, 64) antennaRadius, _ = strconv.ParseFloat(radius, 64)
} }
waypoints := [][3]float64{} waypoints := [][3]float64{}
if wp, ok := params["wayPoints"]; ok && wp != "" { if wp, ok := (*params)["$1"].(string); ok && wp != "" {
// Парсинг waypoints, предполагаем, что это строка вида "x1,y1,z1;x2,y2,z2" // Парсинг waypoints, предполагаем, что это строка вида "x1,y1,z1;x2,y2,z2"
waypointStrings := strings.Split(wp, ";") waypointStrings := strings.Split(wp, ";")
for _, waypointStr := range waypointStrings { for _, waypointStr := range waypointStrings {
@ -97,12 +97,12 @@ func (o *Modulation) StartNewSimulation(timestep int, user_id string) {
} }
speed := 0.0 speed := 0.0
if spd, ok := params["speed"]; ok && spd != "" { if spd, ok := (*params)["$1"].(string); ok && spd != "" {
speed, _ = strconv.ParseFloat(spd, 64) speed, _ = strconv.ParseFloat(spd, 64)
} }
meshName := "" meshName := ""
if mesh, ok := params["meshName"]; ok { if mesh, ok := (*params)["$1"].(string); ok {
meshName = mesh meshName = mesh
} }
@ -124,7 +124,7 @@ func (o *Modulation) StartNewSimulation(timestep int, user_id string) {
if v.Type == 2 { if v.Type == 2 {
// Парсинг параметров для базовых станций // Парсинг параметров для базовых станций
antennaDirection := [3]float64{1, 0, 0} // значение по умолчанию antennaDirection := [3]float64{1, 0, 0} // значение по умолчанию
if dir, ok := params["antennaDirections"]; ok && dir != "" { if dir, ok := (*params)["$1"].(string); ok && dir != "" {
directionValues := strings.Split(dir, ",") directionValues := strings.Split(dir, ",")
if len(directionValues) == 3 { if len(directionValues) == 3 {
antennaDirection[0], _ = strconv.ParseFloat(directionValues[0], 64) antennaDirection[0], _ = strconv.ParseFloat(directionValues[0], 64)
@ -134,7 +134,7 @@ func (o *Modulation) StartNewSimulation(timestep int, user_id string) {
} }
antennaRadius := 200.0 // значение по умолчанию antennaRadius := 200.0 // значение по умолчанию
if radius, ok := params["antennaRadius"]; ok && radius != "" { if radius, ok := (*params)["$1"].(string); ok && radius != "" {
antennaRadius, _ = strconv.ParseFloat(radius, 64) antennaRadius, _ = strconv.ParseFloat(radius, 64)
} }
@ -173,10 +173,10 @@ func (o *Modulation) SpawnExampleData() bool {
} }
// AddObject добавляет объект станции или дрона // AddObject добавляет объект станции или дрона
func (o *Modulation) AddObject(name string, obj_type int, coords [3]int, params map[string]string) { func (o *Modulation) AddObject(name string, obj_type int, coords [3]int, params map[string]interface{}) error {
if obj_type != 1 && obj_type != 2 { if obj_type != 1 && obj_type != 2 {
// Значит объект не станция или не дрон // Значит объект не станция или не дрон
return return fmt.Errorf("ошибка добавления объекта: \n object type: %v", obj_type)
} }
fmt.Println(name, obj_type, coords, params) fmt.Println(name, obj_type, coords, params)
if o.Objects == nil { if o.Objects == nil {
@ -192,10 +192,10 @@ func (o *Modulation) AddObject(name string, obj_type int, coords [3]int, params
Params: &params, Params: &params,
} }
o.Objects = append(o.Objects, obj) o.Objects = append(o.Objects, obj)
return nil
} }
// UpdateObject обновляет объект станции или дрона по имени func (o *Modulation) UpdateObject(name string, obj_type int, coords [3]int, params map[string]interface{}) {
func (o *Modulation) UpdateObject(name string, obj_type int, coords [3]int, params map[string]string) {
if obj_type != 1 && obj_type != 2 { if obj_type != 1 && obj_type != 2 {
// Значит объект не станция или не дрон // Значит объект не станция или не дрон
return return

View File

@ -47,6 +47,7 @@ func NewDBConnection() error {
func (db *GormDbInstance) AutoMigrate() error { func (db *GormDbInstance) AutoMigrate() error {
return DbDriver.DB.AutoMigrate( return DbDriver.DB.AutoMigrate(
database.User{}, database.User{},
database.Logs{},
) )
} }
func (db *GormDbInstance) AutoMakeSuperUser() error { func (db *GormDbInstance) AutoMakeSuperUser() error {
@ -68,6 +69,13 @@ func (db *GormDbInstance) AutoMakeSuperUser() error {
} }
} }
func (db *GormDbInstance) RegisterLog(type_log int, text_log string) error {
if type_log > 0 && type_log < 10 {
db.DB.Create(&database.Logs{LogType: type_log, Log: text_log})
}
return nil
}
func (db *GormDbInstance) GetUserByName(name string) (*database.User, error) { func (db *GormDbInstance) GetUserByName(name string) (*database.User, error) {
// Find user by name // Find user by name
var user database.User var user database.User

View File

@ -0,0 +1,69 @@
package logs_handler
import (
database_entity "moxitech/dns/entity/database"
"moxitech/dns/internal/database"
"strconv"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
)
func GetLogs(c *fiber.Ctx) error {
var logs []database_entity.Logs
typeLogs := c.Query("type")
if typeLogs == "" {
typeLogs = "*"
}
countLogs := c.Query("count")
var logsCountParsed int
var err error
if countLogs != "" {
logsCountParsed, err = strconv.Atoi(countLogs)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid count parameter",
})
}
}
db := database.DbDriver.DB // GORM DB instance
query := db.Model(&database_entity.Logs{})
if typeLogs != "*" {
query = query.Where("type = ?", typeLogs)
}
if logsCountParsed > 0 {
query = query.Limit(logsCountParsed)
}
if err := query.Find(&logs).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": "No logs found",
})
}
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to retrieve logs",
})
}
return c.JSON(logs)
}
func DeleteAllLogs(c *fiber.Ctx) error {
db := database.DbDriver.DB // GORM DB instance
if err := db.Exec("DELETE FROM logs").Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to delete logs",
})
}
return c.JSON(fiber.Map{
"message": "All logs have been deleted successfully",
})
}

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"moxitech/dns/entity/dto" "moxitech/dns/entity/dto"
"moxitech/dns/internal/server/handlers/authorization" "moxitech/dns/internal/server/handlers/authorization"
logs_handler "moxitech/dns/internal/server/handlers/logs"
sim_queue "moxitech/dns/package/simulation" sim_queue "moxitech/dns/package/simulation"
"os" "os"
"runtime" "runtime"
@ -47,6 +48,9 @@ func SpawnServer() error {
// GET http://localhost:8080/simulations/from/history // GET http://localhost:8080/simulations/from/history
// app.Get("/simulations/from/history", authorization.AuthUser) // app.Get("/simulations/from/history", authorization.AuthUser)
// GET http://localhost:8080/system/logs
app.Get("/system/logs", logs_handler.GetLogs)
// GET http://localhost:8080/get/server/state // GET http://localhost:8080/get/server/state
app.Get("/get/server/state", GetServerState) app.Get("/get/server/state", GetServerState)
// WebSocket маршрут // WebSocket маршрут
@ -102,7 +106,7 @@ func CreateOrConnectSocket(c *websocket.Conn) {
// Читаем сообщения от клиента // Читаем сообщения от клиента
messageType, msg, err := c.ReadMessage() messageType, msg, err := c.ReadMessage()
if messageType != websocket.TextMessage { if messageType != websocket.TextMessage {
err = c.WriteMessage(websocket.BinaryMessage, []byte("PLS USE JSON TEXT STRUCTURE!")) err = c.WriteMessage(websocket.BinaryMessage, []byte("{\"error\":\"Use text instead\" }"))
if err != nil { if err != nil {
fmt.Printf("Error writing message: %v\n", err) fmt.Printf("Error writing message: %v\n", err)
break break
@ -116,7 +120,7 @@ func CreateOrConnectSocket(c *websocket.Conn) {
fmt.Printf("Received message from user %s: %s\n", userToken, msg) fmt.Printf("Received message from user %s: %s\n", userToken, msg)
err, ok = room.WebsocketAction(msg) err, ok = room.WebsocketAction(msg)
if err != nil { if err != nil {
err = c.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("%v", err))) err = c.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("{\"error\": \"%v\"}", err)))
if err != nil { if err != nil {
fmt.Printf("Error writing message: %v\n", err) fmt.Printf("Error writing message: %v\n", err)
} }

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"moxitech/dns/entity/websocket" "moxitech/dns/entity/websocket"
"moxitech/dns/internal/database"
"moxitech/dns/package/math/simulator" "moxitech/dns/package/math/simulator"
"strconv" "strconv"
"strings" "strings"
@ -98,11 +99,12 @@ func (room *WebsocketRoom) DeleteMapObjectRequest(message websocket.SignalMessag
// InsertObject вставляет базовые случайные данные в симуляцию // InsertObject вставляет базовые случайные данные в симуляцию
func (room *WebsocketRoom) InsertObject(message websocket.SignalMessage) error { func (room *WebsocketRoom) InsertObject(message websocket.SignalMessage) error {
// Тип объекта (тип сигнала) // Тип объекта (тип сигнала)
type_obj := message.Signal typeObj := message.Signal
// Определяем структуру для данных в поле `data` // Определяем структуру для данных в поле `data`
var parsedData struct { var parsedData struct {
Name string `json:"name"` Name string `json:"name"`
Params map[string]string `json:"params"` Params map[string]interface{} `json:"params"`
} }
// Парсим `data` в структуру // Парсим `data` в структуру
@ -111,43 +113,44 @@ func (room *WebsocketRoom) InsertObject(message websocket.SignalMessage) error {
return fmt.Errorf("[insert] error parsing data: %v", err) return fmt.Errorf("[insert] error parsing data: %v", err)
} }
fmt.Println("st 1")
// Получаем значения name и params // Получаем значения name и params
name := parsedData.Name name := parsedData.Name
params := parsedData.Params params := parsedData.Params
// Проверяем наличие координат в параметрах // Проверяем наличие координат в параметрах
if params["coords"] == "" { coordsValue, ok := params["coords"].(string)
return fmt.Errorf("[insert] coords is empty") if !ok || coordsValue == "" {
return fmt.Errorf("[insert] coords is missing or empty")
} }
fmt.Println("st 2")
// Разделяем строку координат на отдельные элементы // Разделяем строку координат на отдельные элементы
coordinateStrings := strings.Split(params["coords"], ",") coordinateStrings := strings.Split(coordsValue, ",")
// Инициализируем массив для хранения координат // Инициализируем массив для хранения координат
coordinates := make([]int, len(coordinateStrings)) coordinates := make([]int, len(coordinateStrings))
fmt.Println("st 3")
// Преобразуем каждую координату в целое число // Преобразуем каждую координату в целое число
for i, coord := range coordinateStrings { for i, coord := range coordinateStrings {
parsedCoord, err := strconv.Atoi(strings.TrimSpace(coord)) parsedCoord, err := strconv.Atoi(strings.TrimSpace(coord))
if err != nil { if err != nil {
fmt.Println(fmt.Errorf("[insert] invalid coordinate: %s", coord))
return fmt.Errorf("[insert] invalid coordinate: %s", coord) return fmt.Errorf("[insert] invalid coordinate: %s", coord)
} }
coordinates[i] = parsedCoord coordinates[i] = parsedCoord
} }
fmt.Println("st 4")
params["coords"] = "" // Проверяем, что количество координат соответствует ожидаемому (например, 3 координаты)
if len(coordinates) != 3 {
return fmt.Errorf("[insert] invalid number of coordinates: expected 3, got %d", len(coordinates))
}
// Обновляем значение координат в параметрах
params["coords"] = [3]int{coordinates[0], coordinates[1], coordinates[2]}
// Добавляем объект в комнату // Добавляем объект в комнату
roomsMutex.Lock() roomsMutex.Lock()
defer roomsMutex.Unlock() defer roomsMutex.Unlock()
fmt.Println("st 5")
room.Modulation.AddObject(name, type_obj, [3]int{coordinates[0], coordinates[1], coordinates[2]}, params)
return nil return room.Modulation.AddObject(name, typeObj, [3]int{coordinates[0], coordinates[1], coordinates[2]}, params)
} }
// UpdateObjectRequest вставляет базовые случайные данные в симуляцию // UpdateObjectRequest вставляет базовые случайные данные в симуляцию
@ -156,8 +159,8 @@ func (room *WebsocketRoom) UpdateObjectRequest(message websocket.SignalMessage)
type_obj := message.Signal - 20 type_obj := message.Signal - 20
// Определяем структуру для данных в поле `data` // Определяем структуру для данных в поле `data`
var parsedData struct { var parsedData struct {
Name string `json:"name"` Name string `json:"name"`
Params map[string]string `json:"params"` Params map[string]interface{} `json:"params"`
} }
// Парсим `data` в структуру // Парсим `data` в структуру
@ -169,14 +172,14 @@ func (room *WebsocketRoom) UpdateObjectRequest(message websocket.SignalMessage)
// Получаем значения name и params // Получаем значения name и params
name := parsedData.Name name := parsedData.Name
params := parsedData.Params params := parsedData.Params
// Проверяем наличие координат в параметрах // Проверяем наличие координат в параметрах
if params["coords"] == "" { coordsValue, ok := params["coords"].(string)
return fmt.Errorf("[insert] coords is empty") if !ok || coordsValue == "" {
return fmt.Errorf("[insert] coords is missing or empty")
} }
// Разделяем строку координат на отдельные элементы // Разделяем строку координат на отдельные элементы
coordinateStrings := strings.Split(params["coords"], ",") coordinateStrings := strings.Split(coordsValue, ",")
// Инициализируем массив для хранения координат // Инициализируем массив для хранения координат
coordinates := make([]int, len(coordinateStrings)) coordinates := make([]int, len(coordinateStrings))
@ -189,7 +192,15 @@ func (room *WebsocketRoom) UpdateObjectRequest(message websocket.SignalMessage)
} }
coordinates[i] = parsedCoord coordinates[i] = parsedCoord
} }
params["coords"] = ""
// Проверяем, что количество координат соответствует ожидаемому (например, 3 координаты)
if len(coordinates) != 3 {
return fmt.Errorf("[insert] invalid number of coordinates: expected 3, got %d", len(coordinates))
}
// Обновляем значение координат в параметрах
params["coords"] = [3]int{coordinates[0], coordinates[1], coordinates[2]}
// Добавляем объект в комнату // Добавляем объект в комнату
roomsMutex.Lock() roomsMutex.Lock()
defer roomsMutex.Unlock() defer roomsMutex.Unlock()
@ -265,15 +276,15 @@ func (room *WebsocketRoom) WebsocketAction(message []byte) (error, bool) {
fmt.Printf("[WebsocketAction] %v", err) fmt.Printf("[WebsocketAction] %v", err)
return fmt.Errorf("error parsing occured: %v \n waiting for signal struct", err), false return fmt.Errorf("error parsing occured: %v \n waiting for signal struct", err), false
} }
fmt.Println("Run collector")
room.UpdateGroupUptime() room.UpdateGroupUptime()
switch Signal.Signal { switch Signal.Signal {
case 0: case 0:
fmt.Println("Uptime updated")
room.UpdateGroupUptime() room.UpdateGroupUptime()
return nil, false return nil, false
case 1: case 1:
room.InsertObject(Signal) fmt.Println("Начата вставка объекта")
return nil, true return room.InsertObject(Signal), true
case 2: case 2:
fmt.Println("Начата вставка объекта") fmt.Println("Начата вставка объекта")
room.InsertObject(Signal) room.InsertObject(Signal)
@ -300,7 +311,9 @@ func (room *WebsocketRoom) WebsocketAction(message []byte) (error, bool) {
// SYNC SIGNAL // SYNC SIGNAL
case 100: case 100:
fmt.Println("Запуск симуляции : ", Signal) fmt.Println("Запуск симуляции : ", Signal)
database.DbDriver.RegisterLog(1, "Симуляция инициирована пользователем")
room.RunSimulation(Signal) room.RunSimulation(Signal)
case 101: case 101:
// room.StoreMongo(Signal) // room.StoreMongo(Signal)