Create drone model & First connect frontend + backend

This commit is contained in:
moxitech 2024-10-06 07:10:24 +07:00
parent 5eef7db6a0
commit 63f4e70896
77 changed files with 60209 additions and 553 deletions

2
.env
View File

@ -1,3 +1,5 @@
NONE_DEV_MODE=1
NGINX_PORT=80
SERVER_BASE_ADDRESS=0.0.0.0:8080 SERVER_BASE_ADDRESS=0.0.0.0:8080

View File

@ -36,19 +36,19 @@ services:
- dns_net - dns_net
restart: always restart: always
# nginx: nginx:
# image: nginx:alpine image: nginx:alpine
# container_name: geotouchka-nginx-service container_name: dns-nginx
# ports: ports:
# - "${NGINX_PORT}:80" - "${NGINX_PORT}:80"
# volumes: volumes:
# # - ./frontend/geotouchka-web-application/build:/usr/share/nginx/html # - ./frontend/geotouchka-web-application/build:/usr/share/nginx/html
# - ./nginx.conf:/etc/nginx/nginx.conf - ./nginx.conf:/etc/nginx/nginx.conf
# networks: networks:
# - dns_net - dns_net
# depends_on: depends_on:
# - site - front
# restart: always restart: always
server: server:
container_name: dns-server container_name: dns-server

View File

@ -6,10 +6,10 @@ http {
server { server {
listen 80; listen 80;
server_name localhost; server_name localhost;
add_header 'Access-Control-Allow-Origin' 'http://site' always; add_header 'Access-Control-Allow-Origin' 'http://front' always;
location / { location / {
proxy_pass http://site:3000; proxy_pass http://front:3000;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@ -11,16 +11,13 @@
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"leaflet": "^1.9.4", "bootstrap-icons": "^1.11.3",
"leaflet-defaulticon-compatibility": "^0.1.2",
"qrcode.react": "^4.0.1", "qrcode.react": "^4.0.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-leaflet": "^4.2.1", "react-modal": "^3.16.1",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.26.2",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"socket.io": "^4.8.0",
"socket.io-client": "^4.8.0",
"three": "^0.169.0", "three": "^0.169.0",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },
@ -3551,17 +3548,6 @@
} }
} }
}, },
"node_modules/@react-leaflet/core": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz",
"integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==",
"license": "Hippocratic-2.1",
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.19.2", "version": "1.19.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz",
@ -3686,12 +3672,6 @@
"@sinonjs/commons": "^1.7.0" "@sinonjs/commons": "^1.7.0"
} }
}, },
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/@surma/rollup-plugin-off-main-thread": { "node_modules/@surma/rollup-plugin-off-main-thread": {
"version": "2.2.3", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
@ -4346,21 +4326,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
"license": "MIT"
},
"node_modules/@types/cors": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
"integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/eslint": { "node_modules/@types/eslint": {
"version": "8.56.12", "version": "8.56.12",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz",
@ -6182,15 +6147,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"license": "MIT",
"engines": {
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/batch": { "node_modules/batch": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
@ -6316,6 +6272,21 @@
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/bootstrap-icons": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
"integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
]
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -6887,19 +6858,6 @@
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/cosmiconfig": { "node_modules/cosmiconfig": {
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@ -7856,100 +7814,6 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/engine.io": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.1.tgz",
"integrity": "sha512-NEpDCw9hrvBW+hVEOK4T7v0jFJ++KgtPl4jKFwsZVfG1XhS0dCrSb3VMb9gPAd7VAdW52VT1EnaNiU2vM8C0og==",
"license": "MIT",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/engine.io-client": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.1.tgz",
"integrity": "sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-client/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/engine.io/node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/engine.io/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.17.1", "version": "5.17.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
@ -8999,6 +8863,11 @@
"url": "https://github.com/sindresorhus/execa?sponsor=1" "url": "https://github.com/sindresorhus/execa?sponsor=1"
} }
}, },
"node_modules/exenv": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
"integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw=="
},
"node_modules/exit": { "node_modules/exit": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
@ -13497,18 +13366,6 @@
"shell-quote": "^1.8.1" "shell-quote": "^1.8.1"
} }
}, },
"node_modules/leaflet": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause"
},
"node_modules/leaflet-defaulticon-compatibility": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/leaflet-defaulticon-compatibility/-/leaflet-defaulticon-compatibility-0.1.2.tgz",
"integrity": "sha512-IrKagWxkTwzxUkFIumy/Zmo3ksjuAu3zEadtOuJcKzuXaD76Gwvg2Z1mLyx7y52ykOzM8rAH5ChBs4DnfdGa6Q==",
"license": "BSD-2-Clause"
},
"node_modules/leven": { "node_modules/leven": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
@ -16046,7 +15903,6 @@
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.0.1.tgz", "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.0.1.tgz",
"integrity": "sha512-Lpj0tPBn561WiQ3QQWXbkx8xTtB8BZkJeMZWLJIL8iaPBCoWzW1IpCeU3gY5MDqsb0+efCvEGkl9O0naP64crA==", "integrity": "sha512-Lpj0tPBn561WiQ3QQWXbkx8xTtB8BZkJeMZWLJIL8iaPBCoWzW1IpCeU3gY5MDqsb0+efCvEGkl9O0naP64crA==",
"license": "ISC",
"peerDependencies": { "peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0" "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
} }
@ -16402,18 +16258,27 @@
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-leaflet": { "node_modules/react-lifecycles-compat": {
"version": "4.2.1", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
"license": "Hippocratic-2.1", },
"node_modules/react-modal": {
"version": "3.16.1",
"resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.1.tgz",
"integrity": "sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==",
"dependencies": { "dependencies": {
"@react-leaflet/core": "^2.1.0" "exenv": "^1.2.0",
"prop-types": "^15.7.2",
"react-lifecycles-compat": "^3.0.0",
"warning": "^4.0.3"
},
"engines": {
"node": ">=8"
}, },
"peerDependencies": { "peerDependencies": {
"leaflet": "^1.9.0", "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18",
"react": "^18.0.0", "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18"
"react-dom": "^18.0.0"
} }
}, },
"node_modules/react-refresh": { "node_modules/react-refresh": {
@ -17518,83 +17383,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/socket.io": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz",
"integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"license": "MIT",
"dependencies": {
"debug": "~4.3.4",
"ws": "~8.17.1"
}
},
"node_modules/socket.io-adapter/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/socket.io-client": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.0.tgz",
"integrity": "sha512-C0jdhD5yQahMws9alf/yvtsMGTaIDBnZ8Rb5HU56svyq0l5LIrGzIDZZD5pHQlmzxLuU91Gz+VpQMKgCTNYtkw==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.6.1",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/sockjs": { "node_modules/sockjs": {
"version": "0.3.24", "version": "0.3.24",
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
@ -19174,6 +18962,14 @@
"makeerror": "1.0.12" "makeerror": "1.0.12"
} }
}, },
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/watchpack": { "node_modules/watchpack": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
@ -20095,14 +19891,6 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz",
"integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@ -6,16 +6,13 @@
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"leaflet": "^1.9.4", "bootstrap-icons": "^1.11.3",
"leaflet-defaulticon-compatibility": "^0.1.2",
"qrcode.react": "^4.0.1", "qrcode.react": "^4.0.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-leaflet": "^4.2.1", "react-modal": "^3.16.1",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.26.2",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"socket.io": "^4.8.0",
"socket.io-client": "^4.8.0",
"three": "^0.169.0", "three": "^0.169.0",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },

Binary file not shown.

View File

@ -0,0 +1 @@
{"accessors":[],"asset":{"generator":"Open CASCADE Technology 7.6 [dev.opencascade.org]","version":"2.0"},"bufferViews":[],"buffers":[{"byteLength":0,"uri":"drone_normalized_gltf.bin"}],"meshes":[],"nodes":[],"scene":0,"scenes":[{"nodes":[]}]}

View File

@ -1,7 +1,12 @@
.App { .App {
text-align: center; text-align: center;
} }
.coler {
background-color: #0d1117;
color: azure;
}
.App-logo { .App-logo {
height: 40vmin; height: 40vmin;
pointer-events: none; pointer-events: none;

View File

@ -9,7 +9,9 @@ import Login from './pages/Login'; // Импортируем страницу
import Connections from './pages/Connections'; // Импортируем страницу подключений import Connections from './pages/Connections'; // Импортируем страницу подключений
import PrevCalc from './pages/PrevCalc'; // Импортируем страницу подключений import PrevCalc from './pages/PrevCalc'; // Импортируем страницу подключений
import Docs from './pages/Docs'; // Импортируем страницу подключений import Docs from './pages/Docs'; // Импортируем страницу подключений
import "./css/bulma.min.css" import './css/bootstrap-5.3.3-dist/css/bootstrap.min.css';
import './App.css';
import "bootstrap-icons/font/bootstrap-icons.css";
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
const App = () => { const App = () => {
@ -42,7 +44,7 @@ const App = () => {
activeTab={activeTab} activeTab={activeTab}
onLogout={handleLogout} // Передаем функцию handleLogout через пропс onLogout onLogout={handleLogout} // Передаем функцию handleLogout через пропс onLogout
/> />
<div style={{ padding: '20px', flex: 1 }}> <div className='coler' style={{ padding: '20px', flex: 1 }}>
{activeTab === 'main' && <Main />} {/* Подключаем компонент Dashboard */} {activeTab === 'main' && <Main />} {/* Подключаем компонент Dashboard */}
{activeTab === 'map' && <Dashboard />} {/* Подключаем компонент Dashboard */} {activeTab === 'map' && <Dashboard />} {/* Подключаем компонент Dashboard */}
{activeTab === 'connection' && <Connections />} {/* Страница подключений */} {activeTab === 'connection' && <Connections />} {/* Страница подключений */}

View File

@ -1,9 +0,0 @@
.main__imager {
height: 100%;
background-image: url('../public/blueprints.jpg');
}
.main__t_b {
color: black;
}

View File

@ -0,0 +1,6 @@
const InvokeStartModulation = () => {
}

View File

@ -5,7 +5,7 @@ export const giveMeServerAddress = () => {
if (process.env.SERVER_BASE_ADDRESS) { if (process.env.SERVER_BASE_ADDRESS) {
return process.env.SERVER_BASE_ADDRESS return process.env.SERVER_BASE_ADDRESS
} }
return "http://localhost:3000"; return "http://localhost:8080";
} }
export const ServerRequest = async (req, type, data = null) => { export const ServerRequest = async (req, type, data = null) => {
@ -23,7 +23,7 @@ export const ServerRequest = async (req, type, data = null) => {
} }
try { try {
const response = await fetch(`${addr}/${req}`, options); // выполняем запрос const response = await fetch(`${addr}${req}`, options); // выполняем запрос
const responseData = await response.json(); // парсим JSON ответ const responseData = await response.json(); // парсим JSON ответ
return { return {
@ -38,3 +38,7 @@ export const ServerRequest = async (req, type, data = null) => {
}; };
} }
}; };
export const ServerWebsocketRequest = async () => {
}

View File

@ -0,0 +1,13 @@
.main__imager {
height: 100%;
/* background-image: url('../../../public/blueprints.jpg'); */
}
.main__t_b {
color: black;
}
.title {
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,597 @@
/*!
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,594 @@
/*!
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -21,44 +21,11 @@ const Connections = () => {
return ( return (
<div> <div>
<h2>Подключения</h2> <h2>Настройки</h2>
{/* Webhook подключение */}
{/* <div className="section">
<h3>Подключения Webhooks</h3>
<form onSubmit={handleWebhookSubmit}>
<input type="text" name="webhookUrl" placeholder="URL Webhook" required />
<input type="text" name="groups" placeholder="Группы (через запятую)" required />
<button type="submit">Добавить Webhook</button>
</form>
<ul>
{webhooks.map((webhook, index) => (
<li key={index}>
Webhook: {webhook.url} (Группы: {webhook.groups.join(', ')})
</li>
))}
</ul>
</div> */}
{/* WebSocket подключение
<div className="section">
<h3>Подключения WebSockets</h3>
<form
onSubmit={(e) => {
e.preventDefault();
setWebsocketUrl(e.target.websocketUrl.value);
}}
>
<input type="text" name="websocketUrl" placeholder="URL WebSocket" required />
<button type="submit">Подключиться</button>
</form>
{websocketUrl && <p>Подключено по WebSocket: {websocketUrl}</p>}
</div> */}
{/* WebSocket подключение */} {/* WebSocket подключение */}
<div className="section"> <div className="section">
<h3>Настройки уведомлений</h3> <h5>Уведомления</h5>
<form <form
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();

View File

@ -0,0 +1,29 @@
.background_color {
background: var(--bs-border-color);
}
.coler-border{
border: 5px solid #2c3e50;
background: #5b7996;
padding: 20px;
}
.context {
display: flex;
flex-direction: column;
flex-flow: column;
}
.context li {
color: aliceblue;
}
.context .dot {
width: 10px; /* Ширина точки */
height: 10px; /* Высота точки */
background-color: blue; /* Синий цвет */
border-radius: 50%; /* Скругляем углы до круга */
margin-bottom: 10px;
}
.context .btn {
background-color: rgba(91, 100, 185, 0.34);
}

View File

@ -1,65 +1,63 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import * as THREE from 'three'; import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader';
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
import Drone from './model/Drone'; import Drone from './model/Drone';
import HeightPoint from './model/HeightPoint'; import HeightPoint from './model/HeightPoint';
import { RandomHeightPoint } from './helpers/generateRandomHeightPoints'; import { RandomHeightPoint } from './helpers/generateRandomHeightPoints'
import ModalWindow from './components/Modal/Modal';
// class HeightPoint { import './Dashboard.css';
// constructor(x, y, height) {
// this.x = x;
// this.y = y;
// this.height = height;
// }
// }
// import { NavBar } from './components/Navbar/Navbar';
const Dashboard = () => { const Dashboard = () => {
const mountRef = useRef(null); const mountRef = useRef(null); // Указатель монтирования
const sceneRef = useRef(null); const sceneRef = useRef(null); // Указатель на сцену
const [state, setState] = useState({ const mouse = new THREE.Vector2(); // Мышка
heightData: RandomHeightPoint(), const [heightData, setHeightData] = useState(RandomHeightPoint()); // Высоты
drones: [], const [formData, setFormData] = useState({ x: '', y: '', height: '' }); // Форма для ввода данных карты
selectedDrone: null, const [mouseState, setMouseState] = useState({ x: 0, y: 0, z: 0 }); // Форма для ввода данных карты
baseStation: [],
selectedBaseStation: null, const [drones, setDrones] = useState([]) // Все дроны
formData: { x: '', y: '', height: '' }, const [selectedDrone, setSelectedDrone] = useState(null); // Состояние для выбранного дрона
mapSettings: { maxHeight: 20, coordX: 0, coordY: 0 }, const [baseStation, setBaseStation] = useState([]) // Все базовые станции
contextMenu: { visible: false, x: 0, y: 0 }, const [selectedBaseStation, setSelectedBaseStation] = useState(null); // Состояние для выбранной базовой станции
mouseState: { x: 0, y: 0, z: 0 }, const [mapSettings, setMapSettings] = useState({maxHeight: 20, coordX: 0, coordY: 0, }); // Настройки карты
});
const mouse = new THREE.Vector2();
const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 }); // контекстное меню сцены
useEffect(() => { useEffect(() => {
if (!mountRef.current) return; if (!mountRef.current) return;
let width = mountRef.current.clientWidth; let width = mountRef.current.clientWidth;
let height = mountRef.current.clientHeight; let height = mountRef.current.clientHeight;
// Создаем объект сцены
// Создание сцены и камеры
const scene = new THREE.Scene(); const scene = new THREE.Scene();
// Сохраняем ссылку на сцену
sceneRef.current = scene; sceneRef.current = scene;
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); // Создаем камеру
camera.position.set(0, 20, 30); const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 2000);
camera.position.set(0, 80, 120);
const renderer = new THREE.WebGLRenderer(); const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height); renderer.setSize(width, height);
mountRef.current.appendChild(renderer.domElement); mountRef.current.appendChild(renderer.domElement);
// Плоскость для высотной карты // Плоскость для высотной карты :: TODO :: W/H/Ws/Hs change
const geometry = new THREE.PlaneGeometry(20, 20, 4, 4); const geometry = new THREE.PlaneGeometry(200, 200, 20, 20);
const vertices = geometry.attributes.position.array; const vertices = geometry.attributes.position.array;
const colors = []; const colors = [];
const color = new THREE.Color(); const color = new THREE.Color();
const { heightData, mapSettings } = state;
const minHeight = Math.min(...heightData.map(point => point.height));
const maxHeight = Math.max(mapSettings.maxHeight, ...heightData.map(point => point.height));
// Задание высот и цветов вершин 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) { for (let i = 0; i < vertices.length; i += 3) {
const x = Math.floor(i / 3) % 5; const x = Math.floor(i / 3) % 5;
const y = Math.floor(i / (3 * 5)); const y = Math.floor(i / (3 * 5));
const heightPoint = heightData.find(point => point.x === x && point.y === y);
const heightPoint = heightData.find((point) => point.x === x && point.y === y);
const heightValue = heightPoint ? heightPoint.height : 0; const heightValue = heightPoint ? heightPoint.height : 0;
vertices[i + 2] = heightValue; vertices[i + 2] = heightValue;
@ -68,6 +66,7 @@ const Dashboard = () => {
color.setHSL(0.7 * (1 - normalizedHeight), 1, 0.5); color.setHSL(0.7 * (1 - normalizedHeight), 1, 0.5);
colors.push(color.r, color.g, color.b); colors.push(color.r, color.g, color.b);
} }
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)); geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
const material = new THREE.MeshBasicMaterial({ const material = new THREE.MeshBasicMaterial({
@ -80,146 +79,277 @@ const Dashboard = () => {
mesh.rotation.x = -Math.PI / 2; mesh.rotation.x = -Math.PI / 2;
scene.add(mesh); scene.add(mesh);
const axesHelper = new THREE.AxesHelper(10);
scene.add(axesHelper);
const controls = new OrbitControls(camera, renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; controls.enableDamping = true;
const light = new THREE.DirectionalLight(0xffffff, 1); const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 50, 50).normalize(); light.position.set(0, 50, 50).normalize();
scene.add(light); scene.add(light);
// Обработка кликов на сцене
const handleClick = (event) => {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
console.log('Mouse coordinates:', mouse);
const raycaster = new THREE.Raycaster();
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 => drone.getObject().children[0] === selected);
if (drone) {
if (selectedDrone) {
selectedDrone.getObject().children[0].material.color.set(0xff0000);
}
drone.getObject().children[0].material.color.set(0xff1111);
setSelectedDrone(drone);
}
} else {
if (selectedDrone) {
selectedDrone.getObject().children[0].material.color.set(0xff0000);
setSelectedDrone(null);
}
}
};
const handleMouseMove = (event) => {
let x = event.x;
let y = event.y;
setMouseState({ x: x, y: y, z: 0 });
}
const animate = () => { const animate = () => {
requestAnimationFrame(animate); requestAnimationFrame(animate);
controls.update(); controls.update();
renderer.render(scene, camera); renderer.render(scene, camera);
}; };
animate();
// События мыши
const handleClick = (event) => {
const { drones, selectedDrone } = state;
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(drones.map(drone => drone.getObject()), true);
if (intersects.length > 0) {
const selected = intersects[0].object;
const drone = drones.find(drone => drone.getObject().children[0] === selected);
if (drone) {
if (selectedDrone) selectedDrone.getObject().children[0].material.color.set(0xff0000);
drone.getObject().children[0].material.color.set(0xff1111);
setState(prevState => ({ ...prevState, selectedDrone: drone }));
}
} else if (selectedDrone) {
selectedDrone.getObject().children[0].material.color.set(0xff0000);
setState(prevState => ({ ...prevState, selectedDrone: null }));
}
};
renderer.domElement.addEventListener('click', handleClick); renderer.domElement.addEventListener('click', handleClick);
renderer.domElement.addEventListener('mousemove', (event) => { renderer.domElement.addEventListener('mousemove', handleMouseMove);
setState(prevState => ({
...prevState,
mouseState: { x: event.clientX, y: event.clientY, z: 0 } animate();
})); drones.forEach(droneData => {
scene.add(droneData.getObject());
}); });
// Обработка нажатия ПКМ
renderer.domElement.addEventListener('contextmenu', (event) => {
event.preventDefault();
setContextMenu({
visible: true,
x: event.clientX,
y: event.clientY,
});
});
return () => { return () => {
if (mountRef.current) { if (mountRef.current) {
mountRef.current.removeChild(renderer.domElement); mountRef.current.removeChild(renderer.domElement);
} }
}; };
}, [state.drones, state.heightData, state.mapSettings]); }, [drones, heightData, mapSettings]);
// Добавление дрона в сцену
const handleAddDrone = () => { const handleAddDrone = () => {
const { drones } = state; if (sceneRef.current) {
const drone = new Drone(Date.now() / 1000); const current = Date.now();
const droneMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 }); const drone = new Drone(current / 1000);
drone.getObject().children[0].material = droneMaterial; // Создаем новый материал для каждого дрона
drone.setPosition(Math.random() * 20 - 10, 3, Math.random() * 20 - 10); drone.setPosition(Math.random() * 20 - 10, 20, Math.random() * 20 - 10);
setState(prevState => ({ setDrones((prevDrones) => [...prevDrones, drone]);
...prevState, console.log(drones.map(drone => drone.getObject().children[0]));
drones: [...prevState.drones, drone] sceneRef.current.add(drone.getObject());
})); }
sceneRef.current.add(drone.getObject());
}; };
const handleInputChange = (event) => { // Обработчик ввода на форму
const { name, value } = event.target; // const handleInputChange = (event) => {
setState(prevState => ({ // const { name, value } = event.target;
...prevState, // setFormData({
formData: { ...prevState.formData, [name]: value } // ...formData,
})); // [name]: value,
}; // });
// };
const handleAddHeight = (event) => { // Добавление элемента карты высот (высоты)
event.preventDefault(); // const handleAddHeight = (event) => {
const { formData, heightData } = state; // event.preventDefault();
const newPoint = new HeightPoint(parseInt(formData.x), parseInt(formData.y), parseFloat(formData.height)); // const { x, y, height } = formData;
setState(prevState => ({ // const newPoint = new HeightPoint(parseInt(x), parseInt(y), parseFloat(height));
...prevState, // setHeightData((prevData) => [...prevData, newPoint]);
heightData: [...prevState.heightData, newPoint], // setFormData({ x: '', y: '', height: '' });
formData: { x: '', y: '', height: '' } // };
}));
};
const handleSettingsChange = (event) => { // Изменение настроек
const { name, value } = event.target; // const handleSettingsChange = (event) => {
setState(prevState => ({ // const { name, value } = event.target;
...prevState, // setMapSettings({
mapSettings: { ...prevState.mapSettings, [name]: value } // ...mapSettings,
})); // [name]: value,
}; // });
// };
// Открытие контекстного меню
const handleContextMenuClick = (action) => { const handleContextMenuClick = (action) => {
if (action === 'add') handleAddDrone(); if (action === 'add') {
setState(prevState => ({ handleAddDrone();
...prevState, console.log('Добавить объект');
contextMenu: { ...prevState.contextMenu, visible: false } } else if (action === 'save') {
})); console.log('Сохранить промежуточный результат');
}
setContextMenu({ ...contextMenu, visible: false });
}; };
// IS LOADING
// <div class="spinner-border text-primary" role="status">
// <span class="visually-hidden">Loading...</span>
// </div>
return ( return (
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
<h1>Drone Network Simulator</h1> <div className='col'>
<div> {/* TODO: USERS BADGE */}
<p>x: {state.mouseState.x} y: {state.mouseState.y} z: {state.mouseState.z}</p> <h4><b>Drone</b> Network Simulator - окно разработки</h4>
<div ref={mountRef} style={{ width: '100%', height: '500px', position: 'relative' }} />
</div> </div>
{state.contextMenu.visible && ( {/* <NavBar/> */}
<ul style={{ position: 'absolute', top: `${state.contextMenu.y}px`, left: `${state.contextMenu.x}px`, backgroundColor: 'white', padding: '10px', zIndex: 1000 }}>
<li className="button" onClick={() => handleContextMenuClick('add')}>Добавить объект</li> <div className='coler-border'>
<li className="button" onClick={() => handleContextMenuClick('save')}>Сохранить промежуточный результат</li> <div>
<li className="button" onClick={() => handleContextMenuClick('cancel')}>Отмена</li> <p>X: {mouseState.x} Y: {mouseState.y} Z: {mouseState.z}</p>
</div>
<div ref={mountRef} style={{
minWidth: '100%',
minHeight: '60vh',
position: 'relative'
}} />
<div class="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
<div class="btn-group me-2" role="group" aria-label="First group">
<button type="button" class="btn btn-success">
<i class="bi-plus"/>Добавить
</button>
<button type="button" class="btn btn-success">
<i class="bi-trash"/>Удалить
</button>
<button type="button" class="btn btn-success">
<i class="bi-search"/>Выбрать
</button>
<button type="button" class="btn btn-success">
<i class="bi-save"/>Сохранить
</button>
</div>
<div class="btn-group me-2" role="group" aria-label="Second group">
<button type="button" class="btn btn-secondary">
<i class="bi-box-arrow-in-down"/>Загрузить данные
</button>
<button type="button" class="btn btn-secondary">
<i class="bi-filetype-json"/>Выгрузить
</button>
<button type="button" class="btn btn-secondary">
<i class="bi-map"/>Настройки карты
</button>
{/* <button type="button" class="btn btn-secondary">6</button> */}
</div>
<div class="btn-group" role="group" aria-label="Third group">
<button type="button" class="btn btn-info">
<i class="bi-play"/>
</button>
</div>
</div>
</div>
{/* Контекстное меню */}
{contextMenu.visible && (
<ul
style={{
position: 'absolute',
top: `${contextMenu.y}px`,
left: `${contextMenu.x}px`,
backgroundColor: 'transparent',
listStyle: 'none',
padding: '2px',
boxShadow: '0px 0px 5px rgba(0,0,0,0.3)',
zIndex: 1000,
}}
>
<div className='context'>
<div class="dot"></div>
<li className={"btn"} onClick={() => handleContextMenuClick('add')}>Добавить объект</li>
<li className={"btn"} onClick={() => handleContextMenuClick('save')}>Сохранить промежуточный результат</li>
<li className={"btn"} onClick={() => handleContextMenuClick('cancel')}>Отмена</li>
</div>
</ul> </ul>
)} )}
<div className="columns"> <div className="columns">
<div className='column'> {/* Форма для добавления новых высот */}
<form onSubmit={handleAddHeight} style={{ marginTop: '20px' }}> {/* <div className='column'>
<label>Координата X:</label> <form onSubmit={handleAddHeight} style={{ marginTop: '20px' }}>
<input className="input" type="number" name="x" value={state.formData.x} onChange={handleInputChange} /> <div>
<label>Координата Y:</label> <label>Координата X:</label>
<input className="input" type="number" name="y" value={state.formData.y} onChange={handleInputChange} /> <input
<label>Высота:</label> className="input is-primary"
<input className="input" type="number" name="height" value={state.formData.height} onChange={handleInputChange} /> type="number"
<button type="submit" className="button">Добавить координаты</button> name="x"
</form> value={formData.x}
</div> onChange={handleInputChange}
<div className="column"> required
<form style={{ marginTop: '20px' }}> />
<label>Максимальная высота</label> </div>
<input className="input" type="number" name="maxHeight" value={state.mapSettings.maxHeight} onChange={handleSettingsChange} /> <div>
<label>Координата X</label> <label>Координата Y:</label>
<input className="input" type="number" name="coordX" value={state.mapSettings.coordX} onChange={handleSettingsChange} /> <input
<label>Координата Y</label> className="input is-primary"
<input className="input" type="number" name="coordY" value={state.mapSettings.coordY} onChange={handleSettingsChange} /> type="number"
<button type="submit" className="button">Применить</button> name="y"
</form> value={formData.y}
</div> onChange={handleInputChange}
required
/>
</div>
<div>
<label>Высота:</label>
<input
className="input is-primary"
type="number"
name="height"
value={formData.height}
onChange={handleInputChange}
required
/>
</div>
<button className='button' 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>
</div>
</div> */}
</div> </div>
<button className="button is-danger" onClick={handleAddDrone}>Добавить дрон</button>
</div> </div>
); );
}; };

View File

@ -50,7 +50,13 @@ const DeviceGroups = () => {
onChange={(e) => setNewGroupName(e.target.value)} onChange={(e) => setNewGroupName(e.target.value)}
placeholder="Название шаблона" placeholder="Название шаблона"
/> />
<button onClick={createGroup}>Создать шаблон</button> <input
type="text"
value={newGroupName}
onChange={(e) => setNewGroupName(e.target.value)}
placeholder="JSON Template"
/>
<button className='btn btn-success' onClick={createGroup}>Создать шаблон</button>
</div> </div>
<ul> <ul>

View File

@ -3,13 +3,13 @@ import React from 'react';
const Docs = () => { const Docs = () => {
return ( return (
<div className="block"> <div className="block">
<div class="block"> <div className="block">
Документация <strong>Drone Network Simulator</strong>. Документация <strong>Drone Network Simulator</strong>.
</div> </div>
<div class="block"> <div className="block">
created by <strong>@moxitech</strong> created by <strong>@moxitech</strong>
</div> </div>
<div class="block"> <div className="block">
Для начала работы необходимо войти в систему Для начала работы необходимо войти в систему
</div> </div>
</div> </div>

View File

@ -1,34 +1,59 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import '../Main.css'; import '../css/Custom/Main.css';
import { InfoBlock } from './components/DroneAssets/InfoBlock'; import { ServerRequest } from '../Services/Server';
const Main = () => { const Main = () => {
const [rooms, setRooms] = useState(null)
const makeSimulation = () => { useEffect(() => {
connectSimulation()
}, [])
const createSimulation = () => {
// POST $BASE_ADDR/createSimulation?userToken=? // POST $BASE_ADDR/createSimulation?userToken=?
} }
const connectSimulation = () => { const connectSimulation = () => {
// POST $BASE_ADDR/connectSimulation?userToken=? ServerRequest("/simulations/active", "GET").then( x => {
console.log(x.data, x.status)
if (x.status === 200) {
setRooms(x.data.rooms_ids)
} else {
setRooms(null)
}
})
} }
return ( return (
<div className='main__imager'> <div className='main__imager' style={{marginTop: "2%"}}>
<InfoBlock /> <div className="row align-items-start">
<div class="columns "> <div className="col">
<div class="column"> <h1 className="title">Создать новую симуляцию</h1>
<h1 class="title">Создать новую симуляцию</h1> <hr/>
<button class="button">Create</button> <button className="btn btn-primary">Create</button>
</div> </div>
<div class="column"> <div className="col">
<h1 class="title">Присоединиться к моделированию</h1> <h1 className="title">Присоединиться к моделированию</h1>
<div> <hr/>
<p>Расчет полета до камчатки</p> {rooms && rooms.length > 0 ? (
<button class="button">Connect</button> rooms.map(room => (
<div className='row' key={room}>
<div className='col'>
<h3>{room.name} | UUID : {room.uuid}</h3>
</div>
<div className='col'>
<button className="btn btn-primary">
Connect
</button>
</div>
</div>
))
) : (
<p>Пока никто не работает 😒</p>
)}
</div> </div>
</div> </div>
</div> </div>
</div>
) )
}; };

View File

@ -4,20 +4,30 @@ import '../UserAccount.scss';
const PrevCalc = () => { const PrevCalc = () => {
return ( return (
<table class="table"> <table className="table">
<thead> <thead>
<th><abbr title="Уникальный идентификатор в базе данных">UID</abbr></th> <tr>
<th><abbr title="Название">Имя</abbr></th> <th scope='col'><abbr title="Уникальный идентификатор в базе данных">UID</abbr></th>
<th>Пользователь</th> <th scope='col'><abbr title="Название">Имя</abbr></th>
<th>Время начала</th> <th scope='col'>Пользователь</th>
<th>Время конца</th> <th scope='col'>Время начала</th>
<th scope='col'>Время конца</th>
<th scope='col'>Действия</th>
</tr>
</thead> </thead>
<tbody> <tbody>
<th>1</th> <tr>
<td>Калькуляция полета до варшавы</td> <th scope="row">1</th>
<td>Гитлер</td> <td>Калькуляция полета до варшавы</td>
<td>1:00</td> <td>Гитлер</td>
<td>4:04</td> <td>1:00</td>
<td>4:04</td>
<td>
<button className='btn btn-primary'>
Загрузить
</button>
</td>
</tr>
</tbody> </tbody>
</table> </table>
) )

View File

@ -1,35 +0,0 @@
import React from "react"
export const InfoBlock = () => {
return(
<nav className="level">
<div className="level-item has-text-centered">
<div>
<p className="heading">Активных сессий</p>
<p className="title">0</p>
</div>
</div>
<div className="level-item has-text-centered">
<div>
<p className="heading">Горутин</p>
<p className="title">123</p>
</div>
</div>
<div className="level-item has-text-centered">
<div>
<p className="heading">Нагрузка сервера</p>
<p className="title">456K</p>
</div>
</div>
<div className="level-item has-text-centered">
<div>
<p className="heading">Объем хранилища</p>
<p className="title">789</p>
</div>
</div>
</nav>
)
}

View File

@ -0,0 +1 @@
{"accessors":[],"asset":{"generator":"Open CASCADE Technology 7.6 [dev.opencascade.org]","version":"2.0"},"bufferViews":[],"buffers":[{"byteLength":0,"uri":"drone_normalized_gltf.bin"}],"meshes":[],"nodes":[],"scene":0,"scenes":[{"nodes":[]}]}

View File

@ -0,0 +1,13 @@
.m_size_fixed {
color: antiquewhite;
width: 200px;
height: 200px;
}
.m_black_color {
color: black;
}
.m_font_scale_32 {
font-size: 32px;
}

View File

@ -0,0 +1,18 @@
import React, { useState } from 'react';
import Modal from 'react-modal';
import './Modal.css'
const ModalWindow = ({ isOpen, closeModal, openModal, title }) => {
return (
<div className=''>
<Modal className={"m_size_fixed"} isOpen={isOpen} onRequestClose={closeModal}>
<h2 className='m_black_color m_font_scale_32'>Это модальное окно!</h2>
<button className='button is-primary' onClick={closeModal}>Закрыть</button>
</Modal>
</div>
);
}
export default ModalWindow;

View File

@ -0,0 +1,10 @@
import React from "react"
export const NavBar = () => {
return(
<p></p>
)
}

View File

@ -2,8 +2,8 @@ import HeightPoint from '../model/HeightPoint';
export const RandomHeightPoint = () => { export const RandomHeightPoint = () => {
let result = []; let result = [];
for (let i = 0; i < 20; i++) { for (let i = 0; i < 100; i++) {
for (let j = 0; j < 20; j++) { for (let j = 0; j < 100; j++) {
result.push(new HeightPoint(i, j, Math.random() * 10 + 1)); result.push(new HeightPoint(i, j, Math.random() * 10 + 1));
} }
} }

View File

@ -1,14 +1,20 @@
import * as THREE from 'three'; import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
class Drone { class Drone {
constructor(name) { constructor(name, scale = 0.5) {
this.object = new THREE.Object3D(); this.object = new THREE.Object3D();
this.geometry = new THREE.BoxGeometry(1, 1, 1);
this.material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
this.name = name; this.name = name;
const cube = new THREE.Mesh(this.geometry, this.material);
// cube.material.M = new THREE.Color(0x000000); // Инициализируем с базовым эмиссивным цветом // Инициализируем GLTFLoader
this.object.add(cube); const loader = new GLTFLoader();
// Загружаем модель из файла GLTF
loader.load("/ImageToStl.com_drone.glb", (gltf) => {
this.object.add(gltf.scene); // Добавляем сцену из GLTF к объекту дрона
gltf.scene.scale.set(scale, scale, scale); // Применяем масштаб
}, undefined, (error) => {
console.error("Ошибка загрузки GLTF:", error);
});
} }
setPosition(x, y, z) { setPosition(x, y, z) {
@ -18,8 +24,7 @@ class Drone {
getObject() { getObject() {
return this.object; return this.object;
} }
} }
export default Drone; export default Drone;

View File

@ -37,6 +37,78 @@ func (o *Modulation) DeleteObjectIfExists(name string) {
} }
} }
} }
func (o *Modulation) MirrorPayloadToSimulationStruct() {
}
func (o *Modulation) RunSimulator() {
// // Пример карты высот, замените на настоящие данные
// heightData := [][]float64{
// {0, 10, 15, 20},
// {5, 15, 25, 30},
// {10, 20, 35, 40},
// {15, 25, 40, 50},
// }
// // Определяем карту высот
//
// mapObj := &simulator.Map{
// Name: "Example Map",
// MinBound: [3]float64{-1000, -1000, 0},
// MaxBound: [3]float64{1000, 1000, 500},
// HeightData: heightData,
// }
//
// // Определяем дронов
//
// drones := []*simulator.Drone{
// {
// ID: 1,
// Name: "Drone 1",
// Coords: [3]float64{100, 100, 50},
// Params: simulator.DroneParams{
// AntennaRadius: 500,
// AntennaDirection: [3]float64{1, 0, 0},
// Waypoints: [][3]float64{{200, 200, 50}},
// Speed: 10,
// MeshName: "MeshA",
// },
// },
// }
//
// // Определяем базовые станции
//
// baseStations := []*simulator.BaseStation{
// {
// ID: 1,
// Name: "BaseStation1",
// Coords: [3]float64{0, 0, 0},
// Params: simulator.BaseStationParams{
// AntennaRadius: 2000,
// AntennaDirection: [3]float64{1, 0, 0},
// },
// },
// }
//
// // Создаем симуляцию
//
// sim := &simulator.NetworkSimulation{
// Map: mapObj,
// Drones: drones,
// BaseStations: baseStations,
// TimeStep: 2,
// }
//
// // Запуск симуляции на 300 секунд
// result_sim := sim.Simulate(300)
// result := u_sorting.SortMap(result_sim)
//
// for time, state := range result {
// for localstate, f := range state {
// fmt.Printf("%v| %v sec :: %v\n", time, localstate, f)
// }
// }
}
// SpawnExampleData создает случайные данные и перезаписывает карту // SpawnExampleData создает случайные данные и перезаписывает карту
func (o *Modulation) SpawnExampleData() bool { func (o *Modulation) SpawnExampleData() bool {

View File

@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/websocket/v2" "github.com/gofiber/websocket/v2"
) )
@ -20,6 +21,10 @@ var (
func SpawnServer() error { func SpawnServer() error {
app := fiber.New() app := fiber.New()
app.Use(cors.New(cors.Config{
AllowHeaders: "*",
AllowOrigins: "*",
}))
go DispatchRooms() go DispatchRooms()
Rooms = make(map[string]*WebsocketRoom) Rooms = make(map[string]*WebsocketRoom)
app.Get("/", func(c *fiber.Ctx) error { app.Get("/", func(c *fiber.Ctx) error {

View File

@ -216,11 +216,12 @@ func (room *WebsocketRoom) InsertMapFromMongo(name string) {
} }
// RunSimulation запускает симуляцию в горутине // RunSimulation запускает симуляцию в горутине
func (room *WebsocketRoom) RunSimulation(name string) { func (room *WebsocketRoom) RunSimulation(name string) (int, error) {
// TODO +NormalizeDataForSimulation() // TODO +NormalizeDataForSimulation()
roomsMutex.Lock() roomsMutex.Lock()
defer roomsMutex.Unlock() defer roomsMutex.Unlock()
room.Modulation.DeleteObjectIfExists(name) room.Modulation.MirrorPayloadToSimulationStruct()
return -1, nil
} }
// NormalizeDataForSimulation - Преобразование данных для отправки в симуляцию // NormalizeDataForSimulation - Преобразование данных для отправки в симуляцию

View File

@ -83,11 +83,12 @@ func (sim *NetworkSimulation) Simulate(totalTime int) map[int]map[string][]inter
// Проверка соединения с базовой станцией // Проверка соединения с базовой станцией
if !IsDroneInNetwork(d, sim.BaseStations, sim.Map) { if !IsDroneInNetwork(d, sim.BaseStations, sim.Map) {
// Если дрон имеет mesh-сеть, ищем другой дрон для ретрансляции // Если дрон имеет mesh-сеть, ищем другой дрон для ретрансляции
closestDrone := FindClosestDroneToBaseStation(d, sim.Drones, sim.BaseStations[0], sim.Map) FindClosestDroneToBaseStation(d, sim.Drones, sim.BaseStations[0], sim.Map)
if closestDrone != nil { // closestDrone :=
// Дрон может передавать данные через другой дрон // if closestDrone != nil {
// Логику передачи можно добавить сюда // Дрон может передавать данные через другой дрон
} // Логику передачи можно добавить сюда
// }
} }
// Добавляем данные о дроне в текущее состояние // Добавляем данные о дроне в текущее состояние
@ -243,6 +244,7 @@ func CalculateDataRate(modulation string, bandwidth float64) float64 {
return spectralEfficiency * bandwidth // скорость передачи данных в Mbps return spectralEfficiency * bandwidth // скорость передачи данных в Mbps
} }
// MakeExampleMap создает статическую карту
func MakeExampleMap() Map { func MakeExampleMap() Map {
heightData := [][]float64{ heightData := [][]float64{
{0, 10, 15, 20}, {0, 10, 15, 20},