diff --git a/DOCS.md b/DOCS.md index c58909a..56be61d 100644 --- a/DOCS.md +++ b/DOCS.md @@ -4,4 +4,14 @@ SOCKET MESSAGING: // PORT CHANGE IN .env localhost:9091/storeEvent?client_id=1&message={"me": "update"} // CONNECTION: - ws://localhost:9091/ws/1 \ No newline at end of file + ws://localhost:9091/ws/1 + +DATABASE LOGS TYPE: + 1 - INFO + 2 - WARNING + 3 - ERROR + 4 - ADMIN_ACTION + 5 - USER_ACTION + ... + 10 - RESERV + diff --git a/Makefile b/Makefile index 409fffc..98091ad 100644 --- a/Makefile +++ b/Makefile @@ -3,29 +3,29 @@ build: - docker compose build + docker-compose build dev: build - docker compose up -d --force-recreate + docker-compose up -d --force-recreate devf: dev - docker compose logs -f + docker-compose logs -f up: - docker compose up -d --force-recreate + docker-compose up -d --force-recreate upf: up - docker compose logs -f + docker-compose logs -f logs: - docker compose logs -f + docker-compose logs -f stop: echo "!!!!!!!!!!!!!!!!!EXTERMINATUS!!!!!!!!!!!!!!!!!" - docker compose stop + docker-compose stop start: - docker compose start + docker-compose start drop: docker-compose down --volumes \ No newline at end of file diff --git a/ToDO.md.save b/ToDO.md.save new file mode 100644 index 0000000..a328d40 --- /dev/null +++ b/ToDO.md.save @@ -0,0 +1,4 @@ + + +!1 => Написать хук для воспроизведения симуляции +!2 => diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index c489771..cbaa1ed 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "dsn-application", "version": "0.1.0", "dependencies": { + "@mui/material": "^6.1.3", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -2445,6 +2446,60 @@ "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": { "version": "4.4.0", "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==", "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": { "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", @@ -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": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz", @@ -4769,6 +5040,14 @@ "@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": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -6611,6 +6890,14 @@ "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": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -7638,6 +7925,15 @@ "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": { "version": "1.4.1", "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": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -17951,6 +18262,11 @@ "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": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index 597268c..0cfa176 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@mui/material": "^6.1.3", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/src/frontend/src/Services/WebsocketHook.js b/src/frontend/src/Services/WebsocketHook.js index ad2aa59..ee50100 100644 --- a/src/frontend/src/Services/WebsocketHook.js +++ b/src/frontend/src/Services/WebsocketHook.js @@ -27,7 +27,7 @@ const useWebsocketConnection = (userToken, roomHash) => { return prevState; }); } catch (error) { - console.error('Error parsing WebSocket message:', error); + console.error('Error parsing WebSocket message:', error, event.data); } }; diff --git a/src/frontend/src/pages/Dashboard.js b/src/frontend/src/pages/Dashboard.js index 05ffb14..3d3abe5 100644 --- a/src/frontend/src/pages/Dashboard.js +++ b/src/frontend/src/pages/Dashboard.js @@ -14,11 +14,12 @@ import { ModalJson } from './Modal/MJson'; import { ModalMap } from './Modal/MMap'; import { CtxMenu } from './ContextMenu/CtxMenu'; import { ProgramInterface } from './MainScreen/ProgramInterface'; - +import { SimulatorInterface } from './SimScreen/SimScreen'; const Dashboard = () => { const mountRef = useRef(null); // Указатель монтирования const sceneRef = useRef(null); // Указатель на сцену + const cameraRef = useRef(null); // Указатель на камеру const {websocketStruct, sendMessage} = useWebsocketConnection(getCustomCookie("userToken"), 1); const [heightData, setHeightData] = useState([new HeightPoint()]); // Высоты const [formData, setFormData] = useState({ x: '', y: '', height: '' }); // Форма для ввода данных карты @@ -29,7 +30,6 @@ const Dashboard = () => { const [selectedBaseStation, setSelectedBaseStation] = useState(null); // Состояние для выбранной базовой станции const [mapSettings, setMapSettings] = useState({maxHeight: 20, coordX: 0, coordY: 0, }); // Настройки карты const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 }); // контекстное меню сцены - const [objectType, setObjectType] = useState(1); // 1 = Drone, 2 = Base Station const [formObjectData, setFormObjectData] = useState({ name: "", @@ -52,6 +52,204 @@ const Dashboard = () => { meshName: "", // Название сети }); 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 foundDrone = drones.find(drone => drone.name.toLowerCase() === searchTerm.toLowerCase()); const foundBaseStation = baseStation.find(base => base.name.toLowerCase() === searchTerm.toLowerCase()); @@ -122,204 +320,6 @@ const Dashboard = () => { 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 = () => { @@ -418,12 +418,8 @@ const containsObjectRecursively = (parent, target) => { }; // Открытие контекстного меню const handleContextMenuClick = (action) => { - if (action === 'add_base'){ - handleAddBaseStation(); - } - else if (action === 'add') { - handleAddDrone(); - } else if (action === 'save') { + if (action === 'add_base'){ handleAddBaseStation();} + else if (action === 'add') { handleAddDrone(); } else if (action === 'save') { console.log('Сохранить промежуточный результат'); } setContextMenu({ ...contextMenu, visible: false }); @@ -471,16 +467,35 @@ const containsObjectRecursively = (parent, target) => { handleInputChange={handleInputChange} mapSettings={mapSettings} handleSettingsChange={handleSettingsChange}/> - + {/* Окно программы */} - + {isSimulationRunning ? ( + // isSimulationRunning + + ) : ( + + )} { +export const ModalDelete = ({ isModalDeleteOpen, closeAllModals, handleDeleteSignal }) => { return ( - -

Удаление

-

Вы точно хотите удалить объект X?

- - -
- ) -} \ No newline at end of file + +
+
+
+
Удаление
+
+
+

Вы точно хотите удалить объект X?

+
+
+ + +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/frontend/src/pages/Modal/MJson.js b/src/frontend/src/pages/Modal/MJson.js index 2001d05..bca3a6c 100644 --- a/src/frontend/src/pages/Modal/MJson.js +++ b/src/frontend/src/pages/Modal/MJson.js @@ -2,14 +2,25 @@ import React from 'react'; import ModalWindow from '../components/Modal/Modal'; -export const ModalJson= ({isModalOpen, closeAllModals}) => { - return ( - -

Загрузка JSON

-

Выберите или переташите файл для загрузки

- - - +export const ModalJson = ({ isModalOpen, closeAllModals }) => { + return ( + +
+
+
+
Загрузка JSON
+ +
+
+

Выберите или переташите файл для загрузки

+ +
+
+ + +
+
+
- ) -} \ No newline at end of file + ); +}; \ No newline at end of file diff --git a/src/frontend/src/pages/Modal/MMap.js b/src/frontend/src/pages/Modal/MMap.js index 814a5ad..5a8ea65 100644 --- a/src/frontend/src/pages/Modal/MMap.js +++ b/src/frontend/src/pages/Modal/MMap.js @@ -1,18 +1,21 @@ import React from 'react'; import ModalWindow from '../components/Modal/Modal'; - -export const ModalMap = ({isModalMapOpen, closeAllModals, handleAddHeight, formData, handleInputChange, mapSettings, handleSettingsChange}) => { - return ( - -

Настройки карты

-
-
-
-
- +export const ModalMap = ({ isModalMapOpen, closeAllModals, handleAddHeight, formData, handleInputChange, mapSettings, handleSettingsChange }) => { + return ( + +
+
Настройки карты
+ +
+
+
+
+ +
+
-
- +
+
-
- +
+
- +
- {/* Настройки карты */} -
-
-

Настройки карты

-
- - + {/* Настройки карты */} +
+
+
Настройки карты
+
+ + +
-
- - - - ) -} \ No newline at end of file +
+
+ + +
+ + ); +}; \ No newline at end of file diff --git a/src/frontend/src/pages/Modal/MSearch.js b/src/frontend/src/pages/Modal/MSearch.js index f7d0609..e80ef83 100644 --- a/src/frontend/src/pages/Modal/MSearch.js +++ b/src/frontend/src/pages/Modal/MSearch.js @@ -2,17 +2,28 @@ import React from 'react'; import ModalWindow from '../components/Modal/Modal'; -export const ModalSearch = ({isModalSearchOpen, closeAllModals, handleSearch, searchTerm, setSearchTerm}) => { - return ( - -

Поиск

- setSearchTerm(e.target.value)} - /> - +export const ModalSearch = ({ isModalSearchOpen, closeAllModals, handleSearch, searchTerm, setSearchTerm }) => { + return ( + +
+
+
+
Поиск
+
+
+ setSearchTerm(e.target.value)} + /> +
+
+ +
+
+
- ) -} \ No newline at end of file + ); +}; \ No newline at end of file diff --git a/src/frontend/src/pages/PrevCalc.js b/src/frontend/src/pages/PrevCalc.js index 0421892..256b957 100644 --- a/src/frontend/src/pages/PrevCalc.js +++ b/src/frontend/src/pages/PrevCalc.js @@ -2,35 +2,42 @@ import React from 'react'; import '../UserAccount.scss'; const PrevCalc = () => { - return ( - - - - - - - - - - - - - - - - - - - - - -
UIDИмяПользовательВремя началаВремя концаДействия
1Калькуляция полета до варшавыГитлер1:004:04 - -
- ) +
+

История Калькуляций

+ + + + + + + + + + + + + + + + + + + + + +
UIDИмяПользовательВремя началаВремя концаДействия
1Калькуляция полета до ВаршавыГитлер1:004:04 +
+ + +
+
+
+ ); }; export default PrevCalc; \ No newline at end of file diff --git a/src/frontend/src/pages/SimScreen/SimScreen.js b/src/frontend/src/pages/SimScreen/SimScreen.js new file mode 100644 index 0000000..2275c94 --- /dev/null +++ b/src/frontend/src/pages/SimScreen/SimScreen.js @@ -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 ( +
+
+

X: {mouseState.x} Y: {mouseState.y} Z: {mouseState.z}

+
+
+
+ + +
+
+ ); +}; \ No newline at end of file diff --git a/src/server/entity/database/logs.go b/src/server/entity/database/logs.go new file mode 100644 index 0000000..f0261e6 --- /dev/null +++ b/src/server/entity/database/logs.go @@ -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"` +} diff --git a/src/server/entity/websocket/models.go b/src/server/entity/websocket/models.go index efe9405..66fa94c 100644 --- a/src/server/entity/websocket/models.go +++ b/src/server/entity/websocket/models.go @@ -23,10 +23,10 @@ type MapPartial struct { } type SystemObject struct { - Name string `json:"name"` - Type int `json:"type"` // 0: drone, 1: base_station - Coords [3]int `json:"coords"` - Params *map[string]string `json:"params"` + Name string `json:"name"` + Type int `json:"type"` // 0: drone, 1: base_station + Coords [3]int `json:"coords"` + Params *map[string]interface{} `json:"params"` } // DeleteObjectIfExists удаляет объект если он найден @@ -63,11 +63,11 @@ func (o *Modulation) StartNewSimulation(timestep int, user_id string) { continue } - params := *v.Params + params := v.Params if v.Type == 1 { // Парсинг параметров для дронов 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, ",") if len(directionValues) == 3 { antennaDirection[0], _ = strconv.ParseFloat(directionValues[0], 64) @@ -77,12 +77,12 @@ func (o *Modulation) StartNewSimulation(timestep int, user_id string) { } antennaRadius := 100.0 // значение по умолчанию - if radius, ok := params["antennaRadius"]; ok && radius != "" { + if radius, ok := (*params)["$1"].(string); ok && radius != "" { antennaRadius, _ = strconv.ParseFloat(radius, 64) } 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" waypointStrings := strings.Split(wp, ";") for _, waypointStr := range waypointStrings { @@ -97,12 +97,12 @@ func (o *Modulation) StartNewSimulation(timestep int, user_id string) { } speed := 0.0 - if spd, ok := params["speed"]; ok && spd != "" { + if spd, ok := (*params)["$1"].(string); ok && spd != "" { speed, _ = strconv.ParseFloat(spd, 64) } meshName := "" - if mesh, ok := params["meshName"]; ok { + if mesh, ok := (*params)["$1"].(string); ok { meshName = mesh } @@ -124,7 +124,7 @@ func (o *Modulation) StartNewSimulation(timestep int, user_id string) { if v.Type == 2 { // Парсинг параметров для базовых станций 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, ",") if len(directionValues) == 3 { antennaDirection[0], _ = strconv.ParseFloat(directionValues[0], 64) @@ -134,7 +134,7 @@ func (o *Modulation) StartNewSimulation(timestep int, user_id string) { } antennaRadius := 200.0 // значение по умолчанию - if radius, ok := params["antennaRadius"]; ok && radius != "" { + if radius, ok := (*params)["$1"].(string); ok && radius != "" { antennaRadius, _ = strconv.ParseFloat(radius, 64) } @@ -173,10 +173,10 @@ func (o *Modulation) SpawnExampleData() bool { } // 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 { // Значит объект не станция или не дрон - return + return fmt.Errorf("ошибка добавления объекта: \n object type: %v", obj_type) } fmt.Println(name, obj_type, coords, params) if o.Objects == nil { @@ -192,10 +192,10 @@ func (o *Modulation) AddObject(name string, obj_type int, coords [3]int, params Params: ¶ms, } o.Objects = append(o.Objects, obj) + return nil } -// UpdateObject обновляет объект станции или дрона по имени -func (o *Modulation) UpdateObject(name string, obj_type int, coords [3]int, params map[string]string) { +func (o *Modulation) UpdateObject(name string, obj_type int, coords [3]int, params map[string]interface{}) { if obj_type != 1 && obj_type != 2 { // Значит объект не станция или не дрон return diff --git a/src/server/internal/database/postgres.go b/src/server/internal/database/postgres.go index 9118033..41ff8b0 100644 --- a/src/server/internal/database/postgres.go +++ b/src/server/internal/database/postgres.go @@ -47,6 +47,7 @@ func NewDBConnection() error { func (db *GormDbInstance) AutoMigrate() error { return DbDriver.DB.AutoMigrate( database.User{}, + database.Logs{}, ) } 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) { // Find user by name var user database.User diff --git a/src/server/internal/server/handlers/logs/logs_handler.go b/src/server/internal/server/handlers/logs/logs_handler.go new file mode 100644 index 0000000..7e44575 --- /dev/null +++ b/src/server/internal/server/handlers/logs/logs_handler.go @@ -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", + }) +} diff --git a/src/server/internal/server/server.go b/src/server/internal/server/server.go index 852851e..09a1dc5 100644 --- a/src/server/internal/server/server.go +++ b/src/server/internal/server/server.go @@ -5,6 +5,7 @@ import ( "fmt" "moxitech/dns/entity/dto" "moxitech/dns/internal/server/handlers/authorization" + logs_handler "moxitech/dns/internal/server/handlers/logs" sim_queue "moxitech/dns/package/simulation" "os" "runtime" @@ -47,6 +48,9 @@ func SpawnServer() error { // GET http://localhost:8080/simulations/from/history // 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 app.Get("/get/server/state", GetServerState) // WebSocket маршрут @@ -102,7 +106,7 @@ func CreateOrConnectSocket(c *websocket.Conn) { // Читаем сообщения от клиента messageType, msg, err := c.ReadMessage() 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 { fmt.Printf("Error writing message: %v\n", err) break @@ -116,7 +120,7 @@ func CreateOrConnectSocket(c *websocket.Conn) { fmt.Printf("Received message from user %s: %s\n", userToken, msg) err, ok = room.WebsocketAction(msg) 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 { fmt.Printf("Error writing message: %v\n", err) } diff --git a/src/server/internal/server/websocket.go b/src/server/internal/server/websocket.go index c2da07f..06b41a0 100644 --- a/src/server/internal/server/websocket.go +++ b/src/server/internal/server/websocket.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "moxitech/dns/entity/websocket" + "moxitech/dns/internal/database" "moxitech/dns/package/math/simulator" "strconv" "strings" @@ -98,11 +99,12 @@ func (room *WebsocketRoom) DeleteMapObjectRequest(message websocket.SignalMessag // InsertObject вставляет базовые случайные данные в симуляцию func (room *WebsocketRoom) InsertObject(message websocket.SignalMessage) error { // Тип объекта (тип сигнала) - type_obj := message.Signal + typeObj := message.Signal + // Определяем структуру для данных в поле `data` var parsedData struct { - Name string `json:"name"` - Params map[string]string `json:"params"` + Name string `json:"name"` + Params map[string]interface{} `json:"params"` } // Парсим `data` в структуру @@ -111,43 +113,44 @@ func (room *WebsocketRoom) InsertObject(message websocket.SignalMessage) error { return fmt.Errorf("[insert] error parsing data: %v", err) } - fmt.Println("st 1") // Получаем значения name и params name := parsedData.Name params := parsedData.Params // Проверяем наличие координат в параметрах - if params["coords"] == "" { - return fmt.Errorf("[insert] coords is empty") + coordsValue, ok := params["coords"].(string) + 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)) - fmt.Println("st 3") // Преобразуем каждую координату в целое число for i, coord := range coordinateStrings { parsedCoord, err := strconv.Atoi(strings.TrimSpace(coord)) if err != nil { - fmt.Println(fmt.Errorf("[insert] invalid coordinate: %s", coord)) return fmt.Errorf("[insert] invalid coordinate: %s", coord) } 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() 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 вставляет базовые случайные данные в симуляцию @@ -156,8 +159,8 @@ func (room *WebsocketRoom) UpdateObjectRequest(message websocket.SignalMessage) type_obj := message.Signal - 20 // Определяем структуру для данных в поле `data` var parsedData struct { - Name string `json:"name"` - Params map[string]string `json:"params"` + Name string `json:"name"` + Params map[string]interface{} `json:"params"` } // Парсим `data` в структуру @@ -169,14 +172,14 @@ func (room *WebsocketRoom) UpdateObjectRequest(message websocket.SignalMessage) // Получаем значения name и params name := parsedData.Name params := parsedData.Params - // Проверяем наличие координат в параметрах - if params["coords"] == "" { - return fmt.Errorf("[insert] coords is empty") + coordsValue, ok := params["coords"].(string) + 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)) @@ -189,7 +192,15 @@ func (room *WebsocketRoom) UpdateObjectRequest(message websocket.SignalMessage) } 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() defer roomsMutex.Unlock() @@ -265,15 +276,15 @@ func (room *WebsocketRoom) WebsocketAction(message []byte) (error, bool) { fmt.Printf("[WebsocketAction] %v", err) return fmt.Errorf("error parsing occured: %v \n waiting for signal struct", err), false } - fmt.Println("Run collector") room.UpdateGroupUptime() switch Signal.Signal { case 0: + fmt.Println("Uptime updated") room.UpdateGroupUptime() return nil, false case 1: - room.InsertObject(Signal) - return nil, true + fmt.Println("Начата вставка объекта") + return room.InsertObject(Signal), true case 2: fmt.Println("Начата вставка объекта") room.InsertObject(Signal) @@ -300,7 +311,9 @@ func (room *WebsocketRoom) WebsocketAction(message []byte) (error, bool) { // SYNC SIGNAL case 100: fmt.Println("Запуск симуляции : ", Signal) + database.DbDriver.RegisterLog(1, "Симуляция инициирована пользователем") room.RunSimulation(Signal) + case 101: // room.StoreMongo(Signal)