Add base math, height map, todo, docker files, frontend, backend server, dbs, gorm ...etc...
This commit is contained in:
parent
081646757e
commit
19096a5d76
11
.env
11
.env
@ -0,0 +1,11 @@
|
||||
SERVER_BASE_ADDRESS=0.0.0.0:8080
|
||||
|
||||
MONGO_INITDB_ROOT_USERNAME=moxitech
|
||||
MONGO_INITDB_ROOT_PASSWORD=moxitech
|
||||
MONGO_INITDB_DATABASE=moxitech
|
||||
|
||||
POSTGRES_DB=moxitech
|
||||
POSTGRES_USER=moxitech
|
||||
POSTGRES_PASSWORD=moxitech
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_HOST=postgres
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.env
|
||||
*/*/node_modules
|
31
Makefile
31
Makefile
@ -0,0 +1,31 @@
|
||||
#DEV
|
||||
|
||||
|
||||
|
||||
build:
|
||||
docker compose build
|
||||
|
||||
dev: build
|
||||
docker compose up -d --force-recreate
|
||||
|
||||
devf: dev
|
||||
docker compose logs -f
|
||||
|
||||
up:
|
||||
docker compose up -d --force-recreate
|
||||
|
||||
upf: up
|
||||
docker compose logs -f
|
||||
|
||||
logs:
|
||||
docker compose logs -f
|
||||
|
||||
|
||||
stop:
|
||||
echo "!!!!!!!!!!!!!!!!!EXTERMINATUS!!!!!!!!!!!!!!!!!"
|
||||
docker compose stop
|
||||
start:
|
||||
docker compose start
|
||||
|
||||
drop:
|
||||
docker-compose down --volumes
|
89
TODO.md
Normal file
89
TODO.md
Normal file
@ -0,0 +1,89 @@
|
||||
|
||||
|
||||
#### Главная
|
||||
|
||||
|
||||
Карта и страница
|
||||
1. Размонтирование UseRef
|
||||
2. Удлиннить и переместить в левый нижний угол линейку и добавить возможность ее отключения
|
||||
3. Окно настроек карты (размер, цвета высот, цвета фона, выбор шаблона карты с сервера и тд)
|
||||
* Мертвые зоны карты - недоступность сигнала (x0y0 -> x1y1)
|
||||
4. Загрузка JSON
|
||||
|
||||
|
||||
Логика
|
||||
5. Окно БПЛА =>
|
||||
6. Контексное меню редактирования данных БПЛА (конкретные координаты карты начала маршрута, массив перемещений)
|
||||
7. Контексное меню сетевых параметров (ввод параметров антенны (частота и тд), настройки утухания и тд)
|
||||
8. Окно базовых станций =>
|
||||
9. Направление
|
||||
10. Радиус
|
||||
11. Частота
|
||||
12. Эмуляция движения относительно времени =>
|
||||
13. Задание скорости отдельных компонентов
|
||||
14. Время остановки эмуляции
|
||||
|
||||
Quic select - быстрые действия
|
||||
1. Быстрый выбор на карте мышью точки начала и на shift до отжатия точек движения
|
||||
2. Выбор групп БПЛА
|
||||
3. при зажатом шифте после выбора можно перетаскивать компоненты
|
||||
|
||||
Сервер =>
|
||||
1. Websocket соединение +> комната
|
||||
2. на Websocket соединении комната содержит до 8 участников
|
||||
3. Если ВСЕ участники покинули комнату но не завершили модулирование, соединение закроется через TIME_EXPIRE_ROOM минут с автосейвом по временной метке
|
||||
4. Отложенное вычисление: вычисления которые можно отложить - запускаем в фоне для ускорения модуляции
|
||||
|
||||
Структура данных сервера websocket ::
|
||||
```
|
||||
Соединение:
|
||||
Пользователи: [
|
||||
Пользователь: {
|
||||
Id:
|
||||
TimeActivate: ts(int64)
|
||||
IsActive: bool
|
||||
IsCreator? bool
|
||||
...
|
||||
}
|
||||
],
|
||||
Карта: {
|
||||
Имя <DbTemplateName|CustomNameFromUser>
|
||||
Координаты: [{...}],
|
||||
}
|
||||
БПЛА: [
|
||||
БПЛА: {
|
||||
Id:
|
||||
Name:
|
||||
Checked:{UserId, expire}
|
||||
{coords...}
|
||||
{params...}
|
||||
}
|
||||
]
|
||||
Базовые_станции: [
|
||||
BaseStation: {
|
||||
Id:
|
||||
Name:
|
||||
Checked:{UserId, expire}
|
||||
{coords...}
|
||||
{params...}
|
||||
}
|
||||
]
|
||||
ts_update:
|
||||
```
|
||||
При выборе объекта, если пользователь не убрал выделение, он остаеться выделенным x(120) секунд на сервере, после чего с объектом могут взаимодействовать другие пользователи
|
||||
|
||||
При нажатии на начало вычислений, отображается статус расчета и вскоре становиться активной кнопка просмотр записи
|
||||
#### Настройки
|
||||
|
||||
|
||||
1. Базовые настройки пользователя
|
||||
2. Если админ +>
|
||||
3. Настройки групп пользователей (сколько максимум и тд)
|
||||
3. Настройки времени остановки и автосейва при покидании комнат
|
||||
4. Настройки выгрузки и сохранения данных
|
||||
|
||||
#### Проведенные вычисления
|
||||
|
||||
1. Таблица проведенных вычислений
|
||||
2. Подгрузка из истории
|
||||
|
@ -0,0 +1,82 @@
|
||||
version: '3.9'
|
||||
|
||||
networks:
|
||||
dns_net:
|
||||
driver: bridge
|
||||
|
||||
|
||||
services:
|
||||
mongo:
|
||||
image: mongo
|
||||
container_name: dns-mongo-db
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
|
||||
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
|
||||
MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE}
|
||||
ports:
|
||||
- '27017:27017'
|
||||
networks:
|
||||
- dns_net
|
||||
volumes:
|
||||
- mongodata:/data/db
|
||||
restart: always
|
||||
|
||||
postgres:
|
||||
image: postgres:13
|
||||
container_name: dns-postgres-db
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
ports:
|
||||
- "${POSTGRES_PORT}:5432"
|
||||
networks:
|
||||
- dns_net
|
||||
restart: always
|
||||
|
||||
# nginx:
|
||||
# image: nginx:alpine
|
||||
# container_name: geotouchka-nginx-service
|
||||
# ports:
|
||||
# - "${NGINX_PORT}:80"
|
||||
# volumes:
|
||||
# # - ./frontend/geotouchka-web-application/build:/usr/share/nginx/html
|
||||
# - ./nginx.conf:/etc/nginx/nginx.conf
|
||||
# networks:
|
||||
# - dns_net
|
||||
# depends_on:
|
||||
# - site
|
||||
# restart: always
|
||||
|
||||
server:
|
||||
container_name: dns-server
|
||||
build:
|
||||
context: ./src/server
|
||||
dockerfile: Dockerfile
|
||||
env_file: ".env"
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- dns_net
|
||||
restart: always
|
||||
|
||||
front:
|
||||
container_name: dns-ui
|
||||
build:
|
||||
context: ./src/frontend
|
||||
dockerfile: Dockerfile
|
||||
env_file: ".env"
|
||||
ports:
|
||||
- "3000:3000"
|
||||
networks:
|
||||
- dns_net
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
mongodata:
|
||||
driver: local
|
19
nginx.conf
Normal file
19
nginx.conf
Normal file
@ -0,0 +1,19 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
add_header 'Access-Control-Allow-Origin' 'http://site' always;
|
||||
|
||||
location / {
|
||||
proxy_pass http://site:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
}
|
20
src/frontend/Dockerfile
Normal file
20
src/frontend/Dockerfile
Normal file
@ -0,0 +1,20 @@
|
||||
# Use an official Node.js runtime as a parent image
|
||||
FROM node:18-alpine
|
||||
|
||||
# Set the working directory in the container to /app
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package.json and package-lock.json to the container
|
||||
COPY package*.json ./
|
||||
|
||||
# Install any needed dependencies
|
||||
RUN npm install
|
||||
|
||||
# Bundle app source
|
||||
COPY . .
|
||||
|
||||
# Make port 3000 available to the world outside this container
|
||||
EXPOSE 3000
|
||||
|
||||
# Define the main command to run the application
|
||||
CMD [ "npm", "start" ]
|
70
src/frontend/README.md
Normal file
70
src/frontend/README.md
Normal file
@ -0,0 +1,70 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||
|
||||
The page will reload when you make changes.\
|
||||
You may also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
||||
|
||||
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
||||
|
||||
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `npm run build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
20170
src/frontend/package-lock.json
generated
Normal file
20170
src/frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
src/frontend/package.json
Normal file
49
src/frontend/package.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "dsn-application",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet-defaulticon-compatibility": "^0.1.2",
|
||||
"qrcode.react": "^4.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"socket.io": "^4.8.0",
|
||||
"socket.io-client": "^4.8.0",
|
||||
"three": "^0.169.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"sass": "^1.79.3"
|
||||
}
|
||||
}
|
BIN
src/frontend/public/favicon.ico
Normal file
BIN
src/frontend/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
47
src/frontend/public/index.html
Normal file
47
src/frontend/public/index.html
Normal file
@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
|
||||
/>
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
BIN
src/frontend/public/logo192.png
Normal file
BIN
src/frontend/public/logo192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
src/frontend/public/logo512.png
Normal file
BIN
src/frontend/public/logo512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
25
src/frontend/public/manifest.json
Normal file
25
src/frontend/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
src/frontend/public/robots.txt
Normal file
3
src/frontend/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
23
src/frontend/server.js
Normal file
23
src/frontend/server.js
Normal file
@ -0,0 +1,23 @@
|
||||
// EMULATOR
|
||||
|
||||
const io = require("socket.io")(4000, {
|
||||
cors: {
|
||||
origin: "*", // Разрешить все источники
|
||||
},
|
||||
});
|
||||
|
||||
const devices = [
|
||||
{ id: 1, name: "Устройство 1", lat: 59.9343, lng: 30.3351, timestamp: Date.now() }, // Санкт-Петербург
|
||||
{ id: 2, name: "Устройство 2", lat: 59.9400, lng: 30.3000, timestamp: Date.now() }, // Санкт-Петербург
|
||||
];
|
||||
|
||||
setInterval(() => {
|
||||
// Имитируем обновление местоположения
|
||||
devices.forEach(device => {
|
||||
device.lat += (Math.random() - 0.5) * 0.01;
|
||||
device.lng += (Math.random() - 0.5) * 0.01;
|
||||
device.timestamp = Date.now();
|
||||
});
|
||||
|
||||
io.emit("deviceLocationUpdate", devices); // Отправляем обновленные данные клиенту
|
||||
}, 5000); // Обновляем данные каждые 5 секунд
|
38
src/frontend/src/App.css
Normal file
38
src/frontend/src/App.css
Normal file
@ -0,0 +1,38 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
61
src/frontend/src/App.js
Normal file
61
src/frontend/src/App.js
Normal file
@ -0,0 +1,61 @@
|
||||
import React, { useState } from 'react';
|
||||
import DeviceGroups from "./pages/DeviceGroups";
|
||||
import Devices from "./pages/Devices";
|
||||
import Sidebar from "./Sidebar";
|
||||
import Dashboard from './pages/Dashboard';
|
||||
import UserAccount from './pages/UserAccount'; // Импортируем компонент UserAccount
|
||||
import Login from './pages/Login'; // Импортируем страницу логина
|
||||
import Connections from './pages/Connections'; // Импортируем страницу подключений
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
|
||||
const App = () => {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false); // Статус авторизации
|
||||
const [activeTab, setActiveTab] = useState('map'); // По умолчанию активная вкладка "Карта"
|
||||
|
||||
// Функция для выхода из системы
|
||||
const handleLogout = () => {
|
||||
setIsLoggedIn(false);
|
||||
};
|
||||
|
||||
const handleLogin = () => {
|
||||
setIsLoggedIn(true); // Устанавливаем авторизацию
|
||||
};
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/login"
|
||||
element={isLoggedIn ? <Navigate to="/app" /> : <Login onLogin={handleLogin} />}
|
||||
/>
|
||||
<Route
|
||||
path="/app"
|
||||
element={isLoggedIn ? (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
|
||||
<div style={{ display: 'flex', flex: 1 }}>
|
||||
<Sidebar
|
||||
onSelectTab={setActiveTab}
|
||||
activeTab={activeTab}
|
||||
onLogout={handleLogout} // Передаем функцию handleLogout через пропс onLogout
|
||||
/>
|
||||
<div style={{ padding: '20px', flex: 1 }}>
|
||||
{activeTab === 'map' && <Dashboard />} {/* Подключаем компонент Dashboard */}
|
||||
{activeTab === 'connection' && <Connections />} {/* Страница подключений */}
|
||||
{activeTab === 'account' && <UserAccount />} {/* Подключаем компонент UserAccount */}
|
||||
{activeTab === 'groups' && <DeviceGroups />} {/* Группы устройств */}
|
||||
{activeTab === 'devices' && <Devices />} {/* Устройства */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Navigate to="/login" />
|
||||
)}
|
||||
/>
|
||||
{/* Перенаправляем на /login по умолчанию */}
|
||||
<Route path="*" element={<Navigate to="/login" />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
8
src/frontend/src/App.test.js
Normal file
8
src/frontend/src/App.test.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
34
src/frontend/src/Connections.scss
Normal file
34
src/frontend/src/Connections.scss
Normal file
@ -0,0 +1,34 @@
|
||||
.section {
|
||||
margin-bottom: 20px;
|
||||
|
||||
form {
|
||||
input {
|
||||
margin-right: 10px;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 12px;
|
||||
background-color: #2980b9;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #3498db;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
48
src/frontend/src/DeviceGroups.css
Normal file
48
src/frontend/src/DeviceGroups.css
Normal file
@ -0,0 +1,48 @@
|
||||
.device-groups {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.device-groups h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.group-creation {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.group-creation input {
|
||||
padding: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.group-creation button {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #e8f5e9;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
background-color: #ffebee;
|
||||
}
|
||||
|
||||
li div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
li button {
|
||||
margin-right: 10px;
|
||||
}
|
38
src/frontend/src/Devices.css
Normal file
38
src/frontend/src/Devices.css
Normal file
@ -0,0 +1,38 @@
|
||||
.devices {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.devices h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.devices table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.devices table, .devices th, .devices td {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.devices th, .devices td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.devices th {
|
||||
background-color: #34495e;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.devices tr:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.buttons button {
|
||||
margin-right: 10px;
|
||||
}
|
70
src/frontend/src/Login.scss
Normal file
70
src/frontend/src/Login.scss
Normal file
@ -0,0 +1,70 @@
|
||||
// styles/Login.scss
|
||||
|
||||
.login-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: #f0f0f0;
|
||||
|
||||
.login-box {
|
||||
background-color: #fff;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column; // Выровнять элементы по колонке
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20px;
|
||||
|
||||
label {
|
||||
margin-bottom: 8px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
transition: border-color 0.3s ease;
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px;
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
58
src/frontend/src/Sidebar.css
Normal file
58
src/frontend/src/Sidebar.css
Normal file
@ -0,0 +1,58 @@
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
height: 100vh;
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.sidebar h2 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sidebar ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidebar li {
|
||||
margin: 15px 0;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.sidebar li:hover {
|
||||
background-color: #34495e;
|
||||
}
|
||||
|
||||
.sidebar li.active {
|
||||
background-color: #2980b9;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Стили для кнопки "Выйти" */
|
||||
.logout-button {
|
||||
display: block;
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.logout-button:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
|
||||
.logout-button:focus {
|
||||
outline: none;
|
||||
}
|
49
src/frontend/src/Sidebar.js
Normal file
49
src/frontend/src/Sidebar.js
Normal file
@ -0,0 +1,49 @@
|
||||
import React, { useState } from 'react';
|
||||
import './Sidebar.css';
|
||||
|
||||
const Sidebar = ({ onSelectTab, activeTab, onLogout }) => {
|
||||
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false);
|
||||
|
||||
const toggleSubMenu = () => {
|
||||
setIsSubMenuOpen(!isSubMenuOpen);
|
||||
// Переключение на вкладку "Аккаунт пользователя"
|
||||
if (!isSubMenuOpen) {
|
||||
onSelectTab('account');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="sidebar">
|
||||
<h2>Меню</h2>
|
||||
<ul>
|
||||
<li className={activeTab === 'map' ? 'active' : ''} onClick={() => onSelectTab('map')}>
|
||||
Главная
|
||||
</li>
|
||||
<li className={activeTab === 'connection' ? 'active' : ''} onClick={() => onSelectTab('connection')}>
|
||||
Настройки
|
||||
</li>
|
||||
<li className={activeTab === 'connection' ? 'active' : ''} onClick={() => onSelectTab('connection')}>
|
||||
Проведенные вычисления
|
||||
</li>
|
||||
<li className={activeTab === 'account' ? 'active' : ''} onClick={toggleSubMenu}>
|
||||
Аккаунт пользователя
|
||||
</li>
|
||||
{isSubMenuOpen && (
|
||||
<>
|
||||
<li className={activeTab === 'groups' ? 'active' : ''} onClick={() => onSelectTab('groups')}>
|
||||
Шаблоны
|
||||
</li>
|
||||
<li className={activeTab === 'devices' ? 'active' : ''} onClick={() => onSelectTab('devices')}>
|
||||
Действия
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
<button className="logout-button" onClick={onLogout}>
|
||||
Выйти
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
87
src/frontend/src/UserAccount.scss
Normal file
87
src/frontend/src/UserAccount.scss
Normal file
@ -0,0 +1,87 @@
|
||||
.user-account {
|
||||
padding: 20px;
|
||||
background-color: #f4f4f4;
|
||||
border-radius: 10px;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.user-info, .edit-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 18px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
background-color: #e74c3c;
|
||||
|
||||
&:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
margin-top: 20px;
|
||||
padding: 10px 25px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
background-color: #2ecc71; /* Зелёный цвет */
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #27ae60;
|
||||
transform: scale(1.05); /* Небольшой зум при наведении */
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #1e8449;
|
||||
transform: scale(0.98); /* Легкий уменьшенный эффект при нажатии */
|
||||
}
|
||||
}
|
||||
}
|
13
src/frontend/src/index.css
Normal file
13
src/frontend/src/index.css
Normal file
@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
17
src/frontend/src/index.js
Normal file
17
src/frontend/src/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
1
src/frontend/src/logo.svg
Normal file
1
src/frontend/src/logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
After Width: | Height: | Size: 2.6 KiB |
83
src/frontend/src/pages/Connections.js
Normal file
83
src/frontend/src/pages/Connections.js
Normal file
@ -0,0 +1,83 @@
|
||||
import React, { useState } from 'react';
|
||||
import '../Connections.scss';
|
||||
|
||||
const Connections = () => {
|
||||
const [webhooks, setWebhooks] = useState([]);
|
||||
const [websocketUrl, setWebsocketUrl] = useState('');
|
||||
|
||||
const addWebhook = (newWebhook) => {
|
||||
setWebhooks([...webhooks, newWebhook]);
|
||||
};
|
||||
|
||||
const handleWebhookSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const newWebhook = {
|
||||
url: e.target.webhookUrl.value,
|
||||
groups: e.target.groups.value.split(','),
|
||||
};
|
||||
addWebhook(newWebhook);
|
||||
e.target.reset();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<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 подключение */}
|
||||
<div className="section">
|
||||
<h3>Настройки уведомлений</h3>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
setWebsocketUrl(e.target.websocketUrl.value);
|
||||
}}
|
||||
>
|
||||
<input type="checkbox" id='notification' name="websocketUrl" required />
|
||||
<label for="notification">Уведомления о начале работы</label>
|
||||
<hr/>
|
||||
<input type="checkbox" id='notification' name="websocketUrl" required />
|
||||
<label for="notification">Уведомления о готовности вычислений</label>
|
||||
<hr/>
|
||||
<button type="submit">Подключиться</button>
|
||||
</form>
|
||||
|
||||
{websocketUrl && <p>Подключено по WebSocket: {websocketUrl}</p>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Connections;
|
264
src/frontend/src/pages/Dashboard.js
Normal file
264
src/frontend/src/pages/Dashboard.js
Normal file
@ -0,0 +1,264 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
||||
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader';
|
||||
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
|
||||
|
||||
const Dashboard = () => {
|
||||
const mountRef = useRef(null);
|
||||
const [heightData, setHeightData] = useState([
|
||||
[1, 2, 3, 4, 5],
|
||||
[2, 3, 4, 5, 6],
|
||||
[3, 4, 5, 6, 7],
|
||||
[4, 5, 6, 7, 8],
|
||||
[5, 6, 7, 8, 9],
|
||||
]);
|
||||
|
||||
const [formData, setFormData] = useState({ x: '', y: '', height: '' });
|
||||
const [mapSettings, setMapSettings] = useState({
|
||||
maxHeight: 10,
|
||||
coordX: 0,
|
||||
coordY: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let localRef = null;
|
||||
let width = mountRef.current.clientWidth;
|
||||
let height = mountRef.current.clientHeight;
|
||||
|
||||
// Сцена
|
||||
const scene = new THREE.Scene();
|
||||
|
||||
// Камера
|
||||
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
|
||||
camera.position.set(0, 20, 30);
|
||||
|
||||
// Рендерер
|
||||
const renderer = new THREE.WebGLRenderer();
|
||||
renderer.setSize(width, height);
|
||||
mountRef.current.appendChild(renderer.domElement);
|
||||
|
||||
// Плоскость для высотной карты
|
||||
const geometry = new THREE.PlaneGeometry(20, 20, 4, 4);
|
||||
|
||||
const vertices = geometry.attributes.position.array;
|
||||
const colors = [];
|
||||
const color = new THREE.Color();
|
||||
|
||||
// Находим минимальные и максимальные значения высот
|
||||
const minHeight = Math.min(...heightData.flat());
|
||||
const maxHeight = Math.max(mapSettings.maxHeight, ...heightData.flat());
|
||||
|
||||
for (let i = 0; i < vertices.length; i += 3) {
|
||||
const x = Math.floor(i / 3) % 5;
|
||||
const y = Math.floor(i / (3 * 5));
|
||||
const heightValue = heightData[y][x];
|
||||
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);
|
||||
|
||||
// Оси координат X, Y и высота
|
||||
const axesHelper = new THREE.AxesHelper(10);
|
||||
scene.add(axesHelper);
|
||||
|
||||
// Линейка и числовые обозначения
|
||||
const createRuler = (scene) => {
|
||||
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x000000 });
|
||||
|
||||
// Ось X
|
||||
const xPoints = [];
|
||||
xPoints.push(new THREE.Vector3(0, 0.1, 0)); // Левый нижний угол
|
||||
xPoints.push(new THREE.Vector3(20, 0.1, 0)); // Правая сторона
|
||||
const xGeometry = new THREE.BufferGeometry().setFromPoints(xPoints);
|
||||
const xLine = new THREE.Line(xGeometry, lineMaterial);
|
||||
scene.add(xLine);
|
||||
|
||||
// Ось Y
|
||||
const yPoints = [];
|
||||
yPoints.push(new THREE.Vector3(0, 0.1, 0)); // Левый нижний угол
|
||||
yPoints.push(new THREE.Vector3(0, 0.1, 20)); // Верхняя сторона
|
||||
const yGeometry = new THREE.BufferGeometry().setFromPoints(yPoints);
|
||||
const yLine = new THREE.Line(yGeometry, lineMaterial);
|
||||
scene.add(yLine);
|
||||
|
||||
// Числовые обозначения
|
||||
const fontLoader = new FontLoader();
|
||||
fontLoader.load('https://threejs.org/examples/fonts/helvetiker_regular.typeface.json', (font) => {
|
||||
// Числа на оси X
|
||||
for (let i = 0; i <= 20; i += 5) {
|
||||
const textGeometry = new TextGeometry(i.toString(), {
|
||||
font: font,
|
||||
size: 0.5,
|
||||
height: 0.1,
|
||||
});
|
||||
const textMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
|
||||
const textMesh = new THREE.Mesh(textGeometry, textMaterial);
|
||||
textMesh.position.set(i, 0.1, -0.5);
|
||||
scene.add(textMesh);
|
||||
}
|
||||
|
||||
// Числа на оси Y
|
||||
for (let i = 0; i <= 20; i += 5) {
|
||||
const textGeometry = new TextGeometry(i.toString(), {
|
||||
font: font,
|
||||
size: 0.5,
|
||||
height: 0.1,
|
||||
});
|
||||
const textMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
|
||||
const textMesh = new THREE.Mesh(textGeometry, textMaterial);
|
||||
textMesh.position.set(-0.5, 0.1, i);
|
||||
scene.add(textMesh);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
createRuler(scene);
|
||||
|
||||
// Настройка OrbitControls для взаимодействия с картой
|
||||
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 animate = () => {
|
||||
requestAnimationFrame(animate);
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
};
|
||||
|
||||
animate();
|
||||
if (mountRef.current) localRef = mountRef.current;
|
||||
return () => {
|
||||
mountRef.current.removeChild(renderer.domElement);
|
||||
};
|
||||
}, [heightData, mapSettings]);
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
const { name, value } = event.target;
|
||||
setFormData({
|
||||
...formData,
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddHeight = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const { x, y, height } = formData;
|
||||
|
||||
if (x >= 0 && y >= 0 && x < heightData[0].length && y < heightData.length) {
|
||||
const updatedData = [...heightData];
|
||||
updatedData[y][x] = parseFloat(height);
|
||||
setHeightData(updatedData);
|
||||
setFormData({ x: '', y: '', height: '' });
|
||||
} else {
|
||||
alert('Координаты вне допустимого диапазона!');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSettingsChange = (event) => {
|
||||
const { name, value } = event.target;
|
||||
setMapSettings({
|
||||
...mapSettings,
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ flex: 1 }}>
|
||||
<h1>Drone Network Simulator - окно разработки</h1>
|
||||
|
||||
{/* 3D Карта высот */}
|
||||
<div ref={mountRef} style={{ width: '100%', height: '500px' }} />
|
||||
|
||||
{/* Форма для добавления новых высот */}
|
||||
<form onSubmit={handleAddHeight} style={{ marginTop: '20px' }}>
|
||||
<div>
|
||||
<label>Координата X:</label>
|
||||
<input
|
||||
type="number"
|
||||
name="x"
|
||||
value={formData.x}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Координата Y:</label>
|
||||
<input
|
||||
type="number"
|
||||
name="y"
|
||||
value={formData.y}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Высота:</label>
|
||||
<input
|
||||
type="number"
|
||||
name="height"
|
||||
value={formData.height}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button type="submit">Добавить высоту</button>
|
||||
</form>
|
||||
|
||||
{/* Форма для изменения настроек карты */}
|
||||
<form style={{ marginTop: '20px' }}>
|
||||
<div>
|
||||
<label>Максимальная высота:</label>
|
||||
<input
|
||||
type="number"
|
||||
name="maxHeight"
|
||||
value={mapSettings.maxHeight}
|
||||
onChange={handleSettingsChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Координата X (камера):</label>
|
||||
<input
|
||||
type="number"
|
||||
name="coordX"
|
||||
value={mapSettings.coordX}
|
||||
onChange={handleSettingsChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Координата Y (камера):</label>
|
||||
<input
|
||||
type="number"
|
||||
name="coordY"
|
||||
value={mapSettings.coordY}
|
||||
onChange={handleSettingsChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
87
src/frontend/src/pages/DeviceGroups.js
Normal file
87
src/frontend/src/pages/DeviceGroups.js
Normal file
@ -0,0 +1,87 @@
|
||||
import React, { useState } from 'react';
|
||||
import '../DeviceGroups.css';
|
||||
|
||||
const DeviceGroups = () => {
|
||||
const [groups, setGroups] = useState([]);
|
||||
const [newGroupName, setNewGroupName] = useState('');
|
||||
|
||||
const createGroup = () => {
|
||||
if (newGroupName) {
|
||||
const newGroup = {
|
||||
name: newGroupName,
|
||||
devices: [],
|
||||
active: true,
|
||||
};
|
||||
setGroups([...groups, newGroup]);
|
||||
setNewGroupName('');
|
||||
}
|
||||
};
|
||||
|
||||
const deleteGroup = (groupName) => {
|
||||
setGroups(groups.filter(group => group.name !== groupName));
|
||||
};
|
||||
|
||||
const toggleGroupStatus = (groupName) => {
|
||||
setGroups(groups.map(group =>
|
||||
group.name === groupName ? { ...group, active: !group.active } : group
|
||||
));
|
||||
};
|
||||
|
||||
const addDeviceToGroup = (groupName, device) => {
|
||||
setGroups(groups.map(group =>
|
||||
group.name === groupName ? { ...group, devices: [...group.devices, device] } : group
|
||||
));
|
||||
};
|
||||
|
||||
const removeDeviceFromGroup = (groupName, device) => {
|
||||
setGroups(groups.map(group =>
|
||||
group.name === groupName ? { ...group, devices: group.devices.filter(d => d !== device) } : group
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="device-groups">
|
||||
<h2>Группы устройств</h2>
|
||||
|
||||
<div className="group-creation">
|
||||
<input
|
||||
type="text"
|
||||
value={newGroupName}
|
||||
onChange={(e) => setNewGroupName(e.target.value)}
|
||||
placeholder="Название новой группы"
|
||||
/>
|
||||
<button onClick={createGroup}>Создать группу</button>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
{groups.map(group => (
|
||||
<li key={group.name} className={group.active ? 'active' : 'inactive'}>
|
||||
<div>
|
||||
<span>{group.name}</span>
|
||||
<button onClick={() => deleteGroup(group.name)}>Удалить</button>
|
||||
<button onClick={() => toggleGroupStatus(group.name)}>
|
||||
{group.active ? 'Деактивировать' : 'Активировать'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Устройства в группе:</h4>
|
||||
<ul>
|
||||
{group.devices.map((device, index) => (
|
||||
<li key={index}>
|
||||
{device} <button onClick={() => removeDeviceFromGroup(group.name, device)}>Удалить</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<button onClick={() => addDeviceToGroup(group.name, 'Новое устройство')}>
|
||||
Добавить устройство
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeviceGroups;
|
93
src/frontend/src/pages/Devices.js
Normal file
93
src/frontend/src/pages/Devices.js
Normal file
@ -0,0 +1,93 @@
|
||||
import React, { useState } from 'react';
|
||||
import { QRCodeCanvas } from "qrcode.react";
|
||||
import '../Devices.css';
|
||||
|
||||
const Devices = () => {
|
||||
const [devices, setDevices] = useState([]);
|
||||
const [selectedDevice, setSelectedDevice] = useState(null);
|
||||
const [deviceName, setDeviceName] = useState('');
|
||||
const [deviceStatus, setDeviceStatus] = useState('');
|
||||
|
||||
const addDevice = () => {
|
||||
if (deviceName) {
|
||||
const newDevice = { name: deviceName, status: deviceStatus || 'Неактивен', id: Date.now() };
|
||||
setDevices([...devices, newDevice]);
|
||||
setDeviceName('');
|
||||
setDeviceStatus('');
|
||||
}
|
||||
};
|
||||
|
||||
const removeDevice = () => {
|
||||
if (selectedDevice) {
|
||||
setDevices(devices.filter(device => device.id !== selectedDevice.id));
|
||||
setSelectedDevice(null);
|
||||
}
|
||||
};
|
||||
|
||||
const editDevice = () => {
|
||||
if (selectedDevice) {
|
||||
const updatedDevices = devices.map(device =>
|
||||
device.id === selectedDevice.id
|
||||
? { ...device, name: deviceName, status: deviceStatus }
|
||||
: device
|
||||
);
|
||||
setDevices(updatedDevices);
|
||||
setSelectedDevice(null);
|
||||
setDeviceName('');
|
||||
setDeviceStatus('');
|
||||
}
|
||||
};
|
||||
|
||||
const generateQR = (device) => {
|
||||
return (
|
||||
<QRCodeCanvas value={JSON.stringify(device)} size={128} />
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="devices">
|
||||
<h2>Устройства</h2>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Название устройства"
|
||||
value={deviceName}
|
||||
onChange={(e) => setDeviceName(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Статус устройства"
|
||||
value={deviceStatus}
|
||||
onChange={(e) => setDeviceStatus(e.target.value)}
|
||||
/>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Статус</th>
|
||||
<th>QR Код</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{devices.map((device) => (
|
||||
<tr key={device.id} onClick={() => setSelectedDevice(device)}>
|
||||
<td>{device.name}</td>
|
||||
<td>{device.status}</td>
|
||||
<td>{generateQR(device)}</td>
|
||||
<td>
|
||||
<button onClick={editDevice}>Редактировать</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="buttons">
|
||||
<button onClick={addDevice}>Добавить</button>
|
||||
<button onClick={removeDevice} disabled={!selectedDevice}>Удалить выбранное</button>
|
||||
<button onClick={editDevice} disabled={!selectedDevice}>Сохранить изменения</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Devices;
|
52
src/frontend/src/pages/Login.js
Normal file
52
src/frontend/src/pages/Login.js
Normal file
@ -0,0 +1,52 @@
|
||||
import React, { useState } from 'react';
|
||||
import '../Login.scss';
|
||||
|
||||
const Login = ({ onLogin }) => {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
// Simple login/password check
|
||||
if (username === 'admin' && password === 'admin') {
|
||||
onLogin(); // Call function on successful login
|
||||
} else {
|
||||
setError('Неверный логин или пароль');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="login-container">
|
||||
<div className="login-box"> {/* Added a wrapper for styling */}
|
||||
<h2>Вход</h2>
|
||||
{error && <p className="error">{error}</p>}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="input-group">
|
||||
<label htmlFor="username">Логин</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
placeholder="Логин"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-group">
|
||||
<label htmlFor="password">Пароль</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="Пароль"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button type="submit">Войти</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
84
src/frontend/src/pages/UserAccount.js
Normal file
84
src/frontend/src/pages/UserAccount.js
Normal file
@ -0,0 +1,84 @@
|
||||
import React, { useState } from 'react';
|
||||
import '../UserAccount.scss';
|
||||
|
||||
const UserAccount = () => {
|
||||
const [userData, setUserData] = useState({
|
||||
username: 'Мамут Рахал',
|
||||
email: 'yatupoidayn@mail.ru',
|
||||
phone: '+666',
|
||||
});
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [formData, setFormData] = useState(userData);
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prevData) => ({
|
||||
...prevData,
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
setUserData(formData);
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setFormData(userData);
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="user-account">
|
||||
<h1>Данные</h1>
|
||||
|
||||
{isEditing ? (
|
||||
<div className="edit-form">
|
||||
<label>
|
||||
Имя пользователя:
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
value={formData.username}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Никнейм авторизации:
|
||||
<input
|
||||
type="text"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Пароль:
|
||||
<input
|
||||
type="password"
|
||||
name="phone"
|
||||
value={formData.phone}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</label>
|
||||
<div className="buttons">
|
||||
<button onClick={handleSave}>Сохранить</button>
|
||||
<button onClick={handleCancel}>Отмена</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="user-info">
|
||||
<p><strong>Имя:</strong> {userData.username}</p>
|
||||
<p><strong>Email:</strong> {userData.email}</p>
|
||||
<p><strong>Телефон:</strong> {userData.phone}</p>
|
||||
<button className="edit-button" onClick={() => setIsEditing(true)}>
|
||||
Редактировать
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserAccount;
|
33
src/frontend/src/pages/ex.js
Normal file
33
src/frontend/src/pages/ex.js
Normal file
@ -0,0 +1,33 @@
|
||||
|
||||
// Инициализация WebSocket соединения
|
||||
// const socket = io("ws://localhost:8080");
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// // Получаем местоположение пользователя
|
||||
// if (navigator.geolocation) {
|
||||
// navigator.geolocation.getCurrentPosition(
|
||||
// (position) => {
|
||||
// const { latitude, longitude } = position.coords;
|
||||
// setUserLocation([latitude, longitude]); // Устанавливаем координаты пользователя
|
||||
// setLocationLoaded(true); // Флаг, что местоположение загружено
|
||||
// },
|
||||
// (error) => {
|
||||
// console.error("Ошибка при получении геолокации:", error);
|
||||
// setLocationLoaded(true); // Продолжаем даже при ошибке
|
||||
// }
|
||||
// );
|
||||
// } else {
|
||||
// console.error("Геолокация не поддерживается вашим браузером");
|
||||
// setLocationLoaded(true); // Продолжаем даже если геолокация недоступна
|
||||
// }
|
||||
|
||||
// // Получаем данные устройств в реальном времени
|
||||
// socket.on("deviceLocationUpdate", (data) => {
|
||||
// setDevices(data);
|
||||
// });
|
||||
|
||||
// return () => {
|
||||
// socket.off("deviceLocationUpdate");
|
||||
// };
|
||||
// }, []);
|
1
src/frontend/src/pages/gui.min.js
vendored
Normal file
1
src/frontend/src/pages/gui.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
13
src/frontend/src/reportWebVitals.js
Normal file
13
src/frontend/src/reportWebVitals.js
Normal file
@ -0,0 +1,13 @@
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
5
src/frontend/src/setupTests.js
Normal file
5
src/frontend/src/setupTests.js
Normal file
@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
12
src/server/Dockerfile
Normal file
12
src/server/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
||||
# Build stage
|
||||
FROM golang:1.22.7-alpine3.20 AS builder
|
||||
WORKDIR /var/server
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN go build -o main cmd/main.go
|
||||
|
||||
# Run stage
|
||||
FROM alpine:3.20.3
|
||||
COPY --from=builder /var/server/main /main
|
||||
CMD ["./main"]
|
@ -1,5 +1,18 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
import (
|
||||
"moxitech/dns/internal/server"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
D_EXPORT_VARS()
|
||||
err := server.SpawnServer()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func D_EXPORT_VARS() {
|
||||
os.Setenv("SERVER_BASE_ADDRESS", "0.0.0.0:8080")
|
||||
}
|
||||
|
7
src/server/entity/database/user.go
Normal file
7
src/server/entity/database/user.go
Normal file
@ -0,0 +1,7 @@
|
||||
package database
|
||||
|
||||
type SystemUser struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
}
|
1
src/server/entity/dto/auth.go
Normal file
1
src/server/entity/dto/auth.go
Normal file
@ -0,0 +1 @@
|
||||
package dto
|
47
src/server/entity/dto/modulation_stage.go
Normal file
47
src/server/entity/dto/modulation_stage.go
Normal file
@ -0,0 +1,47 @@
|
||||
package dto
|
||||
|
||||
type Connection struct {
|
||||
Id int `json:"room_id"`
|
||||
User []User `json:"room_users"`
|
||||
Map Map `json:"room_map"`
|
||||
UAVs []UAV `json:"room_UAVs"`
|
||||
BaseStations []BaseStation `json:"room_BSs"`
|
||||
TsCreateRoom int64 `json:"room_ts"`
|
||||
TsUpdateRoom int64 `json:"room_up_ts"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Id int `json:"id"`
|
||||
TimeActivate int64 `json:"ts"`
|
||||
IsActive bool `json:"ia"`
|
||||
IsCreator bool `json:"creator"`
|
||||
}
|
||||
type Map struct {
|
||||
Name string `json:"name"`
|
||||
Coordinates Coords `json:"coords"`
|
||||
}
|
||||
type UAV struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Checked Checked `json:"checked"`
|
||||
Coordinates Coords `json:"coords"`
|
||||
// TODO: направление, параметры и тд
|
||||
}
|
||||
|
||||
type BaseStation struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Checked Checked `json:"checked"`
|
||||
Coordinates Coords `json:"coords"`
|
||||
// TODO: радиус, направление и тд
|
||||
}
|
||||
|
||||
type Coords struct {
|
||||
X int64 `json:"X"`
|
||||
Y int64 `json:"Y"`
|
||||
}
|
||||
|
||||
type Checked struct {
|
||||
Initiator int `json:"initiator"`
|
||||
Expire int64 `json:"expire_ts"` // устанавливает сервер
|
||||
}
|
1
src/server/entity/dto/user.go
Normal file
1
src/server/entity/dto/user.go
Normal file
@ -0,0 +1 @@
|
||||
package dto
|
6
src/server/entity/math/drone.go
Normal file
6
src/server/entity/math/drone.go
Normal file
@ -0,0 +1,6 @@
|
||||
package math
|
||||
|
||||
type Drone struct {
|
||||
X, Y float64
|
||||
GainRx float64
|
||||
}
|
7
src/server/entity/math/station.go
Normal file
7
src/server/entity/math/station.go
Normal file
@ -0,0 +1,7 @@
|
||||
package math
|
||||
|
||||
type Station struct {
|
||||
X, Y float64
|
||||
Frequency, GainTx float64
|
||||
MaxLoss float64
|
||||
}
|
@ -3,16 +3,31 @@ module moxitech/dns
|
||||
go 1.22.7
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/fasthttp/websocket v1.5.10 // indirect
|
||||
github.com/gofiber/contrib/websocket v1.3.2 // indirect
|
||||
github.com/gofiber/fiber/v2 v2.52.5 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/klauspost/compress v1.17.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/compress v1.17.10 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/fasthttp v1.56.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
gorm.io/driver/postgres v1.5.9 // indirect
|
||||
gorm.io/gorm v1.25.12 // indirect
|
||||
)
|
||||
|
@ -1,11 +1,34 @@
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fasthttp/websocket v1.5.10 h1:bc7NIGyrg1L6sd5pRzCIbXpro54SZLEluZCu0rOpcN4=
|
||||
github.com/fasthttp/websocket v1.5.10/go.mod h1:BwHeuXGWzCW1/BIKUKD3+qfCl+cTdsHu/f243NcAI/Q=
|
||||
github.com/gofiber/contrib/websocket v1.3.2 h1:AUq5PYeKwK50s0nQrnluuINYeep1c4nRCJ0NWsV3cvg=
|
||||
github.com/gofiber/contrib/websocket v1.3.2/go.mod h1:07u6QGMsvX+sx7iGNCl5xhzuUVArWwLQ3tBIH24i+S8=
|
||||
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
|
||||
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
||||
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0=
|
||||
github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
@ -13,15 +36,44 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/fasthttp v1.56.0 h1:bEZdJev/6LCBlpdORfrLu/WOZXXxvrUQSiyniuaoW8U=
|
||||
github.com/valyala/fasthttp v1.56.0/go.mod h1:sReBt3XZVnudxuLOx4J/fMrJVorWRiWY2koQKgABiVI=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
||||
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
|
1
src/server/internal/database/mongo.go
Normal file
1
src/server/internal/database/mongo.go
Normal file
@ -0,0 +1 @@
|
||||
package database
|
40
src/server/internal/database/postgres.go
Normal file
40
src/server/internal/database/postgres.go
Normal file
@ -0,0 +1,40 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type GormDbInstance struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
// GiveMeConnectionString создает строку подключения к базе данных
|
||||
func (db *GormDbInstance) GiveMeConnectionString() string {
|
||||
str := fmt.Sprintf("postgresql://%s:%s@%s:%s/%s",
|
||||
os.Getenv("POSTGRES_USER"),
|
||||
os.Getenv("POSTGRES_PASSWORD"),
|
||||
os.Getenv("POSTGRES_HOST"),
|
||||
os.Getenv("POSTGRES_PORT"),
|
||||
os.Getenv("POSTGRES_DB"),
|
||||
)
|
||||
return str
|
||||
}
|
||||
|
||||
// NewDBConnection создает новое подключение к базе данных
|
||||
func (db *GormDbInstance) NewDBConnection() (*gorm.DB, error) {
|
||||
cs := db.GiveMeConnectionString()
|
||||
dbInstance, err := gorm.Open(postgres.Open(cs), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database connection: %w", err)
|
||||
}
|
||||
return dbInstance, nil
|
||||
}
|
||||
|
||||
// AutoMigrate выполняет автоматическую миграцию
|
||||
func (db *GormDbInstance) AutoMigrate(dbInstance *gorm.DB) error {
|
||||
return dbInstance.AutoMigrate()
|
||||
}
|
@ -1,9 +1,15 @@
|
||||
package server
|
||||
|
||||
func SpawnServer() {
|
||||
|
||||
}
|
||||
|
||||
func RunCollector() {
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func SpawnServer() error {
|
||||
app := fiber.New()
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString("Welcome to Drone Network Simulator server-side API")
|
||||
})
|
||||
return app.Listen(os.Getenv("SERVER_BASE_ADDRESS"))
|
||||
}
|
||||
|
1
src/server/internal/server/websocket.go
Normal file
1
src/server/internal/server/websocket.go
Normal file
@ -0,0 +1 @@
|
||||
package server
|
29
src/server/package/math/coverage/coverage.go
Normal file
29
src/server/package/math/coverage/coverage.go
Normal file
@ -0,0 +1,29 @@
|
||||
package coverage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
model "moxitech/dns/entity/math"
|
||||
loss "moxitech/dns/package/math"
|
||||
)
|
||||
|
||||
// CalculateCoverage Расчет зон радиопокрытия
|
||||
func CalculateCoverage(stations []model.Station, drones []model.Drone) {
|
||||
for _, drone := range drones {
|
||||
for _, station := range stations {
|
||||
distance := CalculateDistance(drone.X, drone.Y, station.X, station.Y)
|
||||
signalLoss := loss.FreeSpaceLossWithGain(station.Frequency, distance, station.GainTx, drone.GainRx)
|
||||
|
||||
if signalLoss < station.MaxLoss {
|
||||
fmt.Printf("БПЛА в пределах зоны покрытия базовой станции: %.2f дБ\n", signalLoss)
|
||||
} else {
|
||||
fmt.Println("БПЛА вне зоны покрытия")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Вычисление расстояния
|
||||
func CalculateDistance(x1, y1, x2, y2 float64) float64 {
|
||||
return math.Sqrt(math.Pow(x2-x1, 2) + math.Pow(y2-y1, 2))
|
||||
}
|
20
src/server/package/math/data_rate/data_rate.go
Normal file
20
src/server/package/math/data_rate/data_rate.go
Normal file
@ -0,0 +1,20 @@
|
||||
package datarate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// CalculateDataRate: Функция для расчёта скорости передачи данных в зависимости от SNR
|
||||
func CalculateDataRate(snr float64) float64 {
|
||||
// Формула Шеннона для пропускной способности канала
|
||||
bandwidth := 20.0 // 20 МГц
|
||||
return bandwidth * math.Log2(1+snr)
|
||||
}
|
||||
|
||||
func Test() {
|
||||
snr := 10.0 // отношение сигнал/шум в дБ
|
||||
dataRate := CalculateDataRate(math.Pow(10, snr/10)) // SNR нужно преобразовать в линейную шкалу
|
||||
|
||||
fmt.Printf("Скорость передачи данных: %.2f Мбит/с\n", dataRate)
|
||||
}
|
23
src/server/package/math/loss.go
Normal file
23
src/server/package/math/loss.go
Normal file
@ -0,0 +1,23 @@
|
||||
package loss
|
||||
|
||||
import "math"
|
||||
|
||||
// Функция расчета затухания в свободном пространстве
|
||||
// Принимает частоту в МГц и расстояние в км
|
||||
//
|
||||
// frequency := 2400.0 // 2.4 ГГц
|
||||
// distance := 10.0 // 10 км
|
||||
func FreeSpaceLoss(frequencyMHz, distanceKm float64) float64 {
|
||||
return 20*math.Log10(frequencyMHz) + 20*math.Log10(distanceKm) + 92.45
|
||||
}
|
||||
|
||||
// Функция расчета затухания в свободном пространстве с усилением антенн
|
||||
// Частота в МГц, расстояние в км, коэффициенты усиления передающей и приёмной антенны
|
||||
// frequency := 2400.0 // 2.4 ГГц
|
||||
// distance := 10.0 // 10 км
|
||||
// gainTx := 10.0 // Усиление передающей антенны в дБ
|
||||
// gainRx := 10.0 // Усиление приёмной антенны в дБ
|
||||
func FreeSpaceLossWithGain(frequencyMHz, distanceKm, gainTx, gainRx float64) float64 {
|
||||
loss := 20*math.Log10(frequencyMHz) + 20*math.Log10(distanceKm) + 92.45
|
||||
return loss - gainTx - gainRx
|
||||
}
|
88
src/server/package/math/optimize/genetical.go
Normal file
88
src/server/package/math/optimize/genetical.go
Normal file
@ -0,0 +1,88 @@
|
||||
package optimize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
// Этот код оптимизирует размещение базовых станций,
|
||||
// минимизируя расстояние между ними и целевой областью.
|
||||
// TODO -> больше параметров для оптимизации, таких как покрытие сигнала
|
||||
|
||||
const populationSize = 50
|
||||
const generations = 100
|
||||
const mutationRate = 0.01
|
||||
|
||||
// Структура решения, представляющая координаты базовых станций
|
||||
type StationCoordinate struct {
|
||||
X, Y float64
|
||||
}
|
||||
|
||||
// Оценка решения (чем меньше значение, тем лучше)
|
||||
func Fitness(solution StationCoordinate) float64 {
|
||||
// Например, минимизация расстояния между станцией и каким-то целевым объектом
|
||||
return solution.X*solution.X + solution.Y*solution.Y
|
||||
}
|
||||
|
||||
// Случайное решение
|
||||
func RandomSolution() StationCoordinate {
|
||||
return StationCoordinate{
|
||||
X: rand.Float64() * 100, // случайное число от 0 до 100
|
||||
Y: rand.Float64() * 100,
|
||||
}
|
||||
}
|
||||
|
||||
// Кроссовер (скрещивание)
|
||||
func Crossover(a, b StationCoordinate) StationCoordinate {
|
||||
return StationCoordinate{
|
||||
X: (a.X + b.X) / 2,
|
||||
Y: (a.Y + b.Y) / 2,
|
||||
}
|
||||
}
|
||||
|
||||
// Мутация решения
|
||||
func Mutate(solution *StationCoordinate) {
|
||||
if rand.Float64() < mutationRate {
|
||||
solution.X += rand.NormFloat64() * 10
|
||||
}
|
||||
if rand.Float64() < mutationRate {
|
||||
solution.Y += rand.NormFloat64() * 10
|
||||
}
|
||||
}
|
||||
|
||||
func Test() {
|
||||
|
||||
// Создаем начальную популяцию
|
||||
population := make([]StationCoordinate, populationSize)
|
||||
for i := range population {
|
||||
population[i] = RandomSolution()
|
||||
}
|
||||
|
||||
// Основной цикл алгоритма
|
||||
for gen := 0; gen < generations; gen++ {
|
||||
// Оценка популяции
|
||||
bestFitness := Fitness(population[0])
|
||||
bestSolution := population[0]
|
||||
for _, solution := range population {
|
||||
f := Fitness(solution)
|
||||
if f < bestFitness {
|
||||
bestFitness = f
|
||||
bestSolution = solution
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Поколение %d, Лучшее решение: (%.2f, %.2f) с оценкой %.2f\n",
|
||||
gen, bestSolution.X, bestSolution.Y, bestFitness)
|
||||
|
||||
// Селекция и кроссовер
|
||||
newPopulation := make([]StationCoordinate, populationSize)
|
||||
for i := range newPopulation {
|
||||
parent1 := population[rand.Intn(populationSize)]
|
||||
parent2 := population[rand.Intn(populationSize)]
|
||||
child := Crossover(parent1, parent2)
|
||||
Mutate(&child)
|
||||
newPopulation[i] = child
|
||||
}
|
||||
population = newPopulation
|
||||
}
|
||||
}
|
66
tech_reference.md
Normal file
66
tech_reference.md
Normal file
@ -0,0 +1,66 @@
|
||||
### Программное обеспечение для моделирования сети, обеспечивающая автономные перелёты БПЛА на территории РФ
|
||||
|
||||
#### Решение задачи на которое направлена разработка программного обеспечение является анализ возможности обеспеечение бесшовной связи:
|
||||
|
||||
1. Расчет зон радиопокрытия Красноярского края.
|
||||
|
||||
2. Выбор мест размещения базовых станций (БС) для беспилотных летательных аппаратов (БПЛА) с целью радиопокрытия заданной территории.
|
||||
|
||||
3. Расчет параметров радиоканала
|
||||
|
||||
|
||||
|
||||
#### ЗАДАНИЕ
|
||||
|
||||
|
||||
Расчет зон радиопокрытия:
|
||||
|
||||
Это включает вычисление параметров радиоканала, таких как затухание сигнала, усиление антенн, отношение сигнал-шум, и другие.
|
||||
Подход: Использовать модели распространения радиоволн (например, модель Хатага–Окамура или модель Лос-Анжелес). Можно использовать существующие библиотеки для расчетов затухания радиосигналов, таких как go-waves (если есть) или реализовать свою реализацию на основе теоретических моделей.
|
||||
Шаги:
|
||||
Получить данные о высоте местности и координатах наземных станций.
|
||||
Рассчитать коэффициенты затухания сигнала для каждой базовой станции с учётом высоты, рельефа и других параметров.
|
||||
|
||||
Определение мест размещения базовых станций:
|
||||
|
||||
Это задача оптимизации. Нужно расставить станции таким образом, чтобы минимизировать количество станций и максимизировать покрытие, обеспечивая связь с каждым БПЛА.
|
||||
Подход: Использовать генетические алгоритмы или метод имитации отжига для оптимального размещения станций на карте.
|
||||
Шаги:
|
||||
Создать модель для оценки качества покрытия от конкретного набора станций.
|
||||
Прогонять алгоритм оптимизации, чтобы найти лучшее распределение станций по карте.
|
||||
|
||||
Расчет параметров радиоканала:
|
||||
|
||||
Для каждой связи между БПЛА и наземной станцией нужно рассчитать параметры радиоканала: уровень сигнала, скорость передачи данных, коэффициенты усиления антенн и т.д.
|
||||
Подход: Применить формулу свободного пространства (Free-space path loss) для расчета затухания сигнала, а затем использовать полученные данные для оценки скорости передачи данных и других параметров.
|
||||
Шаги:
|
||||
Реализовать формулу расчета уровня сигнала с учетом дальности и характеристик антенн.
|
||||
Рассчитать скорость передачи данных на основе уровня сигнала и модуляции.
|
||||
|
||||
Моделирование движения БПЛА:
|
||||
|
||||
Это динамическая задача, требующая отслеживания перемещений большого количества БПЛА в реальном времени и моделирования связи с базовыми станциями.
|
||||
Подход: Использовать симулятор физических движений, чтобы моделировать движение БПЛА по заданным траекториям. Затем в реальном времени рассчитывать качество связи для каждого БПЛА на основе его положения.
|
||||
Шаги:
|
||||
Создать функцию для обновления положения каждого БПЛА в реальном времени.
|
||||
На основе нового положения пересчитывать параметры радиосигнала и обновлять статус связи с ближайшей станцией.
|
||||
|
||||
Реализация транзитной передачи данных:
|
||||
|
||||
Если БПЛА не может напрямую связаться с базовой станцией, должна быть реализована возможность передачи данных через другие БПЛА.
|
||||
Подход: Построить сеть связи по типу mesh с использованием алгоритмов маршрутизации. Для этого можно использовать стандартные алгоритмы поиска маршрутов (например, алгоритм Дейкстры или AODV).
|
||||
Шаги:
|
||||
Реализовать алгоритм поиска маршрутов между БПЛА.
|
||||
При отсутствии связи с наземной станцией находить ближайший БПЛА, который может передать данные.
|
||||
|
||||
Отчёты по результатам моделирования:
|
||||
|
||||
После завершения моделирования необходимо выводить отчёт с анализом параметров радиоканала, временных интервалов связи/отсутствия связи и других данных.
|
||||
Подход: Реализовать генерацию отчетов с использованием стандартных инструментов, таких как Go templates для создания HTML или текстовых отчетов.
|
||||
###### MATH
|
||||
Для реализации остальной математической логики, связанной с расчётом зон радиопокрытия, размещением базовых станций и параметров радиоканала, начнём по порядку. Мы разобьём задачу на несколько этапов:
|
||||
|
||||
Расчёт зон радиопокрытия с использованием затухания сигнала и коэффициентов усиления.
|
||||
Оптимизация размещения базовых станций с помощью генетического алгоритма или другого метода оптимизации.
|
||||
Расчёт параметров радиоканала (модуляция, скорость передачи данных).
|
||||
Моделирование движения БПЛА с возможностью передачи данных через другие БПЛА.
|
Loading…
Reference in New Issue
Block a user