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

10
DOCS.md
View File

@ -5,3 +5,13 @@ SOCKET MESSAGING:
localhost:9091/storeEvent?client_id=1&message={"me": "update"}
// CONNECTION:
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:
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

4
ToDO.md.save Normal file
View File

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

View File

@ -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",

View File

@ -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",

View File

@ -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);
}
};

View File

@ -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}/>
<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
mouseState={mouseState}
mountRef={mountRef}
openModal={openModal}
sendMessage={sendMessage}
RunSimulatorSignal={RunSimulatorSignal}
getCustomCookie={getCustomCookie}
/>
{isSimulationRunning ? (
// isSimulationRunning
<SimulatorInterface
mouseState={mouseState}
mountRef={mountRef}
openModal={openModal}
sendMessage={sendMessage}
RunSimulatorSignal={RunSimulatorSignal}
getCustomCookie={getCustomCookie}
/>
) : (
<ProgramInterface
mouseState={mouseState}
mountRef={mountRef}
openModal={openModal}
sendMessage={sendMessage}
RunSimulatorSignal={RunSimulatorSignal}
getCustomCookie={getCustomCookie}
/>
)}
<CtxMenu
contextMenu={contextMenu}
handleContextMenuClick={handleContextMenuClick}

View File

@ -1,14 +1,23 @@
import React from 'react';
import ModalWindow from '../components/Modal/Modal';
export const ModalDelete = ({isModalDeleteOpen, closeAllModals, handleDeleteSignal}) => {
export const ModalDelete = ({ isModalDeleteOpen, closeAllModals, handleDeleteSignal }) => {
return (
<ModalWindow isOpen={isModalDeleteOpen} onClose={closeAllModals}>
<h2>Удаление</h2>
<h2>Вы точно хотите удалить объект X?</h2>
<button onClick={closeAllModals}>Отмена</button>
<button onClick={() => handleDeleteSignal(1) }>Удалить</button>
</ModalWindow>
)
}
<ModalWindow isOpen={isModalDeleteOpen} onClose={closeAllModals}>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Удаление</h5>
</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';
export const ModalJson= ({isModalOpen, closeAllModals}) => {
return (
<ModalWindow isOpen={isModalOpen} onClose={closeAllModals}>
<h2>Загрузка JSON</h2>
<p>Выберите или переташите файл для загрузки</p>
<input type='file'/>
<button onClick={closeAllModals}>Отмена</button>
<button onClick={() => alert('Загружено')}>Загрузить</button>
export const ModalJson = ({ isModalOpen, closeAllModals }) => {
return (
<ModalWindow isOpen={isModalOpen} onClose={closeAllModals}>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Загрузка JSON</h5>
<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>
)
}
);
};

View File

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

View File

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

View File

@ -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

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"
"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)
}

View File

@ -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)