NEW FRONTEND
This commit is contained in:
parent
d3bcb58a35
commit
f2cedef6ae
7
src/usn-frontend/.dockerignore
Normal file
7
src/usn-frontend/.dockerignore
Normal file
@ -0,0 +1,7 @@
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
.git
|
69
src/usn-frontend/Dockerfile
Normal file
69
src/usn-frontend/Dockerfile
Normal file
@ -0,0 +1,69 @@
|
||||
|
||||
|
||||
FROM node:18-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||
elif [ -f package-lock.json ]; then npm ci; \
|
||||
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Next.js collects completely anonymous telemetry data about general usage.
|
||||
# Learn more here: https://nextjs.org/telemetry
|
||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN yarn build
|
||||
|
||||
# If using npm comment out above and use below instead
|
||||
# RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
# Uncomment the following line in case you want to disable telemetry during runtime.
|
||||
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# Set the correct permission for prerender cache
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT 3000
|
||||
# set hostname to localhost
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
|
||||
# server.js is created by next build from the standalone output
|
||||
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
|
||||
CMD ["node", "server.js"]
|
36
src/usn-frontend/README.md
Normal file
36
src/usn-frontend/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
5
src/usn-frontend/next-env.d.ts
vendored
Normal file
5
src/usn-frontend/next-env.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
6
src/usn-frontend/next.config.mjs
Normal file
6
src/usn-frontend/next.config.mjs
Normal file
@ -0,0 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'standalone'
|
||||
};
|
||||
|
||||
export default nextConfig;
|
3325
src/usn-frontend/package-lock.json
generated
Normal file
3325
src/usn-frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
src/usn-frontend/package.json
Normal file
30
src/usn-frontend/package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "usn-frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.1.10",
|
||||
"@heroicons/react": "^2.1.5",
|
||||
"@react-three/drei": "^9.114.6",
|
||||
"@react-three/fiber": "^8.17.10",
|
||||
"leva": "^0.9.35",
|
||||
"next": "14.2.15",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"three": "^0.169.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
8
src/usn-frontend/postcss.config.mjs
Normal file
8
src/usn-frontend/postcss.config.mjs
Normal file
@ -0,0 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
BIN
src/usn-frontend/public/image.png
Normal file
BIN
src/usn-frontend/public/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 173 KiB |
34
src/usn-frontend/public/logo.svg
Normal file
34
src/usn-frontend/public/logo.svg
Normal file
@ -0,0 +1,34 @@
|
||||
<svg width="274" height="269" viewBox="0 0 274 269" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M74 97L89 110.714V187.714H74V97Z" fill="#343131"/>
|
||||
<path d="M213.018 73.6823L213.018 97.6984L138.211 176.182L125.711 162.182L213.018 73.6823Z" fill="#343131"/>
|
||||
<path d="M63.6724 98.0433L64.7108 73.6823L150.711 163.182L138.348 176.038L63.6724 98.0433Z" fill="#343131"/>
|
||||
<path d="M65 89.2857L80 103V180H65V89.2857Z" fill="#D9D9D9"/>
|
||||
<rect x="205" y="160" width="20" height="4" fill="white"/>
|
||||
<path d="M194 112L208 98V188H194V112Z" fill="#343131"/>
|
||||
<path d="M199 104L213 90V180H199V104Z" fill="#D9D9D9"/>
|
||||
<path d="M213.308 58L213.308 82.0161L138.5 160.5L126 146.5L213.308 58Z" fill="#D9D9D9"/>
|
||||
<path d="M63.9616 82.361L65 58L151 147.5L138.637 160.356L63.9616 82.361Z" fill="#D9D9D9"/>
|
||||
<rect x="213" y="137" width="51" height="7" fill="white"/>
|
||||
<rect x="246" y="114" width="20" height="7" fill="white"/>
|
||||
<rect x="21" y="190" width="110" height="7" fill="white"/>
|
||||
<rect x="38" y="211" width="135" height="7" fill="white"/>
|
||||
<rect x="54" y="228" width="87" height="7" fill="white"/>
|
||||
<rect x="243" y="162" width="20" height="3" fill="white"/>
|
||||
<circle cx="151.5" cy="231.5" r="10" stroke="white" stroke-width="5"/>
|
||||
<circle cx="140.5" cy="193.5" r="10" stroke="white" stroke-width="5"/>
|
||||
<circle cx="184.5" cy="214.5" r="10" stroke="white" stroke-width="5"/>
|
||||
<circle cx="235.5" cy="160.5" r="9" stroke="white" stroke-width="5"/>
|
||||
<circle cx="236.5" cy="117.5" r="9" stroke="white" stroke-width="5"/>
|
||||
<path d="M266.5 134C266.5 204.337 208.377 261.5 136.5 261.5C64.623 261.5 6.5 204.337 6.5 134C6.5 63.6629 64.623 6.5 136.5 6.5C208.377 6.5 266.5 63.6629 266.5 134Z" stroke="white" stroke-width="9"/>
|
||||
<path d="M270.5 134.5C270.5 206.789 210.791 265.5 137 265.5C63.2089 265.5 3.5 206.789 3.5 134.5C3.5 62.2112 63.2089 3.5 137 3.5C210.791 3.5 270.5 62.2112 270.5 134.5Z" stroke="#560303" stroke-width="7"/>
|
||||
<mask id="path-23-inside-1_4_98" fill="white">
|
||||
<ellipse cx="236" cy="80.5" rx="7" ry="6.5"/>
|
||||
</mask>
|
||||
<path d="M236 80.5C236 80.3661 236.032 80.2339 236.08 80.1284C236.125 80.0298 236.171 79.9811 236.187 79.9666C236.214 79.941 236.166 80 236 80V94C243.234 94 250 88.4353 250 80.5H236ZM236 80C235.834 80 235.786 79.941 235.813 79.9666C235.829 79.9811 235.875 80.0298 235.92 80.1284C235.968 80.2339 236 80.3661 236 80.5H222C222 88.4353 228.766 94 236 94V80ZM236 80.5C236 80.6339 235.968 80.7661 235.92 80.8716C235.875 80.9702 235.829 81.0189 235.813 81.0334C235.786 81.059 235.834 81 236 81V67C228.766 67 222 72.5647 222 80.5H236ZM236 81C236.166 81 236.214 81.059 236.187 81.0334C236.171 81.0189 236.125 80.9702 236.08 80.8716C236.032 80.7661 236 80.6339 236 80.5H250C250 72.5647 243.234 67 236 67V81Z" fill="#560303" mask="url(#path-23-inside-1_4_98)"/>
|
||||
<circle cx="176.5" cy="50.5" r="7" fill="#560303" stroke="#560303" stroke-width="7"/>
|
||||
<rect x="39" y="117" width="19" height="7" transform="rotate(-90 39 117)" fill="white"/>
|
||||
<rect x="39" y="162" width="29" height="7" transform="rotate(-90 39 162)" fill="white"/>
|
||||
<path d="M51.5 126C51.5 130.591 47.5765 134.5 42.5 134.5C37.4235 134.5 33.5 130.591 33.5 126C33.5 121.409 37.4235 117.5 42.5 117.5C47.5765 117.5 51.5 121.409 51.5 126Z" stroke="white" stroke-width="5"/>
|
||||
<circle cx="42.5" cy="165.5" r="4.5" stroke="white" stroke-width="4"/>
|
||||
<circle cx="42.5" cy="95.5" r="5.5" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
BIN
src/usn-frontend/public/map/map.fbx
Normal file
BIN
src/usn-frontend/public/map/map.fbx
Normal file
Binary file not shown.
BIN
src/usn-frontend/public/map/map.glb
Normal file
BIN
src/usn-frontend/public/map/map.glb
Normal file
Binary file not shown.
2
src/usn-frontend/public/map/map.mtl
Normal file
2
src/usn-frontend/public/map/map.mtl
Normal file
@ -0,0 +1,2 @@
|
||||
# Blender 4.2.3 LTS MTL File: 'None'
|
||||
# www.blender.org
|
474394
src/usn-frontend/public/map/map.obj
Normal file
474394
src/usn-frontend/public/map/map.obj
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/usn-frontend/public/objects/bs.glb
Normal file
BIN
src/usn-frontend/public/objects/bs.glb
Normal file
Binary file not shown.
BIN
src/usn-frontend/public/objects/drone.glb
Normal file
BIN
src/usn-frontend/public/objects/drone.glb
Normal file
Binary file not shown.
105
src/usn-frontend/src/app/components/Sidebar/Sidebar.tsx
Normal file
105
src/usn-frontend/src/app/components/Sidebar/Sidebar.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
"use client";
|
||||
import { ArrowPathIcon, ChartPieIcon, CursorArrowRaysIcon, FingerPrintIcon, SquaresPlusIcon } from '@heroicons/react/24/outline';
|
||||
import { useState } from 'react';
|
||||
|
||||
const sidebarItems = [
|
||||
{
|
||||
name: 'Главная',
|
||||
icon: <ChartPieIcon className="w-6 h-6" aria-hidden="true" />,
|
||||
to: '/'
|
||||
},
|
||||
{
|
||||
name: 'Симуляция',
|
||||
icon: <SquaresPlusIcon className="w-6 h-6" aria-hidden="true" />,
|
||||
to: '/pages/simulations',
|
||||
badge: {
|
||||
content: '0',
|
||||
classes: 'inline-flex items-center justify-center px-2 ms-3 text-sm font-medium text-gray-800 bg-gray-100 rounded-full dark:bg-gray-700 dark:text-gray-300',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'История',
|
||||
icon: <ArrowPathIcon className="w-6 h-6" aria-hidden="true" />,
|
||||
to: '/pages/history',
|
||||
badge: {
|
||||
content: '3',
|
||||
classes: 'inline-flex items-center justify-center w-3 h-3 p-3 ms-3 text-sm font-medium text-blue-800 bg-blue-100 rounded-full dark:bg-blue-900 dark:text-blue-300',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Пользователь',
|
||||
icon: <FingerPrintIcon className="w-6 h-6" aria-hidden="true" />,
|
||||
to: '/pages/user',
|
||||
},
|
||||
{
|
||||
name: 'Документация',
|
||||
icon: <CursorArrowRaysIcon className="w-6 h-6" aria-hidden="true" />,
|
||||
to: '/pages/docs',
|
||||
},
|
||||
// {
|
||||
// name: 'Регистрация',
|
||||
// icon: <PlayCircleIcon className="w-6 h-6" aria-hidden="true" />,
|
||||
// href: '#',
|
||||
// },
|
||||
// {
|
||||
// name: 'Вход',
|
||||
// icon: <PhoneIcon className="w-6 h-6" aria-hidden="true" />,
|
||||
// href: '#',
|
||||
// },
|
||||
];
|
||||
|
||||
export default function NavBar() {
|
||||
return (
|
||||
<main>
|
||||
<button
|
||||
data-drawer-target="default-sidebar"
|
||||
data-drawer-toggle="default-sidebar"
|
||||
aria-controls="default-sidebar"
|
||||
type="button"
|
||||
className="inline-flex items-center p-2 mt-2 ms-3 text-sm text-gray-500 rounded-lg sm:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
|
||||
>
|
||||
<span className="sr-only">Open sidebar</span>
|
||||
<svg
|
||||
className="w-6 h-6"
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
fillRule="evenodd"
|
||||
d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<aside
|
||||
id="default-sidebar"
|
||||
className="fixed top-0 left-0 z-40 w-64 h-screen transition-transform -translate-x-full sm:translate-x-0"
|
||||
aria-label="Sidebar"
|
||||
>
|
||||
<div className="h-full px-3 py-4 overflow-y-auto bg-gray-50 dark:bg-gray-800">
|
||||
<ul className="space-y-2 font-medium">
|
||||
{sidebarItems.map((item) => (
|
||||
<li key={item.name}>
|
||||
<a
|
||||
href={item.to}
|
||||
className="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group"
|
||||
>
|
||||
{item.icon}
|
||||
<span className="ms-3">{item.name}</span>
|
||||
{item.badge && (
|
||||
<span className={item.badge.classes}>{item.badge.content}</span>
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
|
||||
</main>
|
||||
);
|
||||
}
|
188
src/usn-frontend/src/app/components/Threejs/ThreeJsInstance.tsx
Normal file
188
src/usn-frontend/src/app/components/Threejs/ThreeJsInstance.tsx
Normal file
@ -0,0 +1,188 @@
|
||||
"use client";
|
||||
import { Canvas } from '@react-three/fiber';
|
||||
import { OrbitControls, useGLTF, SpotLight } from '@react-three/drei';
|
||||
import { useState } from 'react';
|
||||
import { Leva, useControls } from 'leva';
|
||||
|
||||
interface Drone {
|
||||
id: number;
|
||||
name?: string;
|
||||
position: [number, number, number];
|
||||
frequency: number;
|
||||
signalRadius: number;
|
||||
}
|
||||
|
||||
interface BaseStation {
|
||||
id: number;
|
||||
name?: string;
|
||||
position: [number, number, number];
|
||||
frequency: number;
|
||||
signalRadius: number;
|
||||
antennaDirection: [number, number, number];
|
||||
}
|
||||
|
||||
const ThreeJsInstance = () => {
|
||||
const [drones, setDrones] = useState<Drone[]>([]);
|
||||
const [baseStations, setBaseStations] = useState<BaseStation[]>([]);
|
||||
const [selectedObject, setSelectedObject] = useState<{ type: 'drone' | 'baseStation'; id: number } | null>(null);
|
||||
|
||||
const addDrone = (drone?: Drone) => {
|
||||
setDrones((prev) => [
|
||||
...prev,
|
||||
drone || {
|
||||
id: prev.length,
|
||||
name: `Drone ${prev.length + 1}`,
|
||||
position: [Math.random() * 10, 5, Math.random() * 10],
|
||||
frequency: Math.random() * 100 + 400, // Example frequency range between 400-500
|
||||
signalRadius: Math.random() * 5 + 5, // Example signal radius between 5-10
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const addBaseStation = (baseStation?: BaseStation) => {
|
||||
setBaseStations((prev) => [
|
||||
...prev,
|
||||
baseStation || {
|
||||
id: prev.length,
|
||||
name: `Base Station ${prev.length + 1}`,
|
||||
position: [Math.random() * 10, 20, Math.random() * 10],
|
||||
frequency: Math.random() * 100 + 400, // Example frequency range between 400-500
|
||||
signalRadius: Math.random() * 5 + 5, // Example signal radius between 5-10
|
||||
antennaDirection: [0, 1, 0],
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const addDroneWithParameters = (position: [number, number, number], frequency: number, signalRadius: number) => {
|
||||
addDrone({
|
||||
id: drones.length,
|
||||
name: `Drone ${drones.length + 1}`,
|
||||
position,
|
||||
frequency,
|
||||
signalRadius,
|
||||
});
|
||||
};
|
||||
|
||||
const addBaseStationWithParameters = (position: [number, number, number], frequency: number, signalRadius: number, antennaDirection: [number, number, number]) => {
|
||||
addBaseStation({
|
||||
id: baseStations.length,
|
||||
name: `Base Station ${baseStations.length + 1}`,
|
||||
position,
|
||||
frequency,
|
||||
signalRadius,
|
||||
antennaDirection,
|
||||
});
|
||||
};
|
||||
|
||||
const handleObjectClick = (type: 'drone' | 'baseStation', id: number) => {
|
||||
setSelectedObject({ type, id });
|
||||
};
|
||||
|
||||
useControls({
|
||||
addNewDrone: {
|
||||
label: '+Дрон',
|
||||
value: false,
|
||||
onChange: () => {
|
||||
addDrone();
|
||||
},
|
||||
},
|
||||
addNewBaseStation: {
|
||||
label: '+BS',
|
||||
value: false,
|
||||
onChange: () => {
|
||||
addBaseStation();
|
||||
},
|
||||
},
|
||||
addDroneWithParameters: {
|
||||
label: 'Добавить дрон с параметрами',
|
||||
value: false,
|
||||
onChange: () => {
|
||||
addDroneWithParameters([1, 2, 3], 450, 8);
|
||||
},
|
||||
},
|
||||
addBaseStationWithParameters: {
|
||||
label: 'Добавить базовую станцию с параметрами',
|
||||
value: false,
|
||||
onChange: () => {
|
||||
addBaseStationWithParameters([4, 20, 4], 470, 10, [0, 1, 0]);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Canvas style={{ height: '800px', width: '900px' }}>
|
||||
<ambientLight intensity={0.5} />
|
||||
<directionalLight position={[5, 10, 5]} intensity={1} />
|
||||
<pointLight position={[10, 10, 10]} intensity={0.8} />
|
||||
<spotLight position={[-10, 15, 10]} angle={0.3} intensity={0.7} castShadow />
|
||||
<OrbitControls />
|
||||
<MapModel />
|
||||
{baseStations.map((baseStation) => (
|
||||
<BaseStationModel
|
||||
key={baseStation.id}
|
||||
onClick={() => handleObjectClick('baseStation', baseStation.id)}
|
||||
isSelected={selectedObject?.type === 'baseStation' && selectedObject.id === baseStation.id}
|
||||
position={baseStation.position}
|
||||
/>
|
||||
))}
|
||||
{drones.map((drone) => (
|
||||
<DroneModel
|
||||
key={drone.id}
|
||||
position={drone.position}
|
||||
onClick={() => handleObjectClick('drone', drone.id)}
|
||||
isSelected={selectedObject?.type === 'drone' && selectedObject.id === drone.id}
|
||||
/>
|
||||
))}
|
||||
</Canvas>
|
||||
<Leva collapsed={false} />
|
||||
|
||||
<button onClick={() => addDrone()} className="p-2 m-2 bg-green-500 text-white rounded">Добавить дрон</button>
|
||||
<button onClick={() => addBaseStation()} className="p-2 m-2 bg-blue-500 text-white rounded">Добавить базовую станцию</button>
|
||||
{selectedObject && (
|
||||
<div className="p-2 m-2 bg-blue-500 text-white rounded">
|
||||
{selectedObject.type === 'drone'
|
||||
? `Выбран дрон с ID: ${selectedObject.id}, имя: ${drones.find((d) => d.id === selectedObject.id)?.name}`
|
||||
: `Выбрана базовая станция с ID: ${selectedObject.id}, имя: ${baseStations.find((b) => b.id === selectedObject.id)?.name}`}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MapModel = () => {
|
||||
const { scene } = useGLTF('/map/map.glb');
|
||||
return <primitive object={scene} scale={[1.5, 1.5, 1.5]} />;
|
||||
};
|
||||
|
||||
const DroneModel = ({ position, onClick, isSelected }: { position: [number, number, number]; onClick: () => void; isSelected: boolean }) => {
|
||||
const { scene } = useGLTF('/objects/drone.glb');
|
||||
return (
|
||||
<group onClick={onClick}>
|
||||
<primitive object={scene} position={[position[0] + 0.8, position[1], position[2] - 0.5]} scale={[0.2, 0.2, 0.2]} />
|
||||
{isSelected && (
|
||||
<mesh position={position}>
|
||||
<sphereGeometry args={[0.5, 16, 16]} />
|
||||
<meshBasicMaterial color="red" wireframe />
|
||||
</mesh>
|
||||
)}
|
||||
</group>
|
||||
);
|
||||
};
|
||||
|
||||
const BaseStationModel = ({ position, onClick, isSelected }: { position: [number, number, number]; onClick: () => void; isSelected: boolean }) => {
|
||||
const { scene } = useGLTF('/objects/bs.glb');
|
||||
return (
|
||||
<group onClick={onClick}>
|
||||
<primitive object={scene} position={position} scale={[0.7, 0.7, 0.7]} />
|
||||
{isSelected && (
|
||||
<mesh position={position}>
|
||||
<sphereGeometry args={[0.5, 10, 10]} />
|
||||
<meshBasicMaterial color="red" wireframe />
|
||||
</mesh>
|
||||
)}
|
||||
</group>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThreeJsInstance;
|
BIN
src/usn-frontend/src/app/favicon.ico
Normal file
BIN
src/usn-frontend/src/app/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 266 KiB |
BIN
src/usn-frontend/src/app/fonts/JetBrainsMono-Bold.woff2
Normal file
BIN
src/usn-frontend/src/app/fonts/JetBrainsMono-Bold.woff2
Normal file
Binary file not shown.
27
src/usn-frontend/src/app/globals.css
Normal file
27
src/usn-frontend/src/app/globals.css
Normal file
@ -0,0 +1,27 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
41
src/usn-frontend/src/app/layout.tsx
Normal file
41
src/usn-frontend/src/app/layout.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import type { Metadata } from "next";
|
||||
import localFont from "next/font/local";
|
||||
import "./globals.css";
|
||||
import NavBar from "./components/Sidebar/Sidebar";
|
||||
import { useState } from 'react';
|
||||
|
||||
const geistSans = localFont({
|
||||
src: "./fonts/JetBrainsMono-Bold.woff2",
|
||||
variable: "--font-geist-sans",
|
||||
weight: "100 900",
|
||||
});
|
||||
const geistMono = localFont({
|
||||
src: "./fonts/JetBrainsMono-Bold.woff2",
|
||||
variable: "--font-geist-mono",
|
||||
weight: "100 900",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Universal[DRONE::EDITION] Network Simulator",
|
||||
description: "Drone edition",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<NavBar/>
|
||||
|
||||
<div className="p-4 sm:ml-64">
|
||||
{children}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
28
src/usn-frontend/src/app/page.tsx
Normal file
28
src/usn-frontend/src/app/page.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<h1 className="text-4xl sm:text-6xl font-bold text-center sm:text-left">
|
||||
Добро пожаловать в DRONE NETWORK SIMULATOR
|
||||
</h1>
|
||||
<p className="text-lg sm:text-xl text-center sm:text-left">
|
||||
Управляйте и тестируйте поведение вашей сети дронов в безопасной среде. Этот симулятор
|
||||
предоставляет реалистичное моделирование сети дронов.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<a href="/pages/simulations" className="px-6 py-3 bg-blue-600 text-white rounded-md hover:bg-blue-700">
|
||||
Начать Симуляцию
|
||||
</a>
|
||||
<a href="/pages/docs" className="px-6 py-3 border border-blue-600 text-blue-600 rounded-md hover:bg-blue-50">
|
||||
Узнать Больше
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
<p className="text-sm text-gray-500">© 2024 Симулятор Сетей Дронов. Все права защищены.</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
19
src/usn-frontend/src/app/pages/docs/page.tsx
Normal file
19
src/usn-frontend/src/app/pages/docs/page.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
|
||||
|
||||
const Docs: React.FC = () => {
|
||||
return (
|
||||
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<h1 className="text-4xl sm:text-5xl font-bold text-center sm:text-left">
|
||||
Документация
|
||||
</h1>
|
||||
<p className="text-gray-600">Возможно однажды она будет написана...</p>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Docs;
|
74
src/usn-frontend/src/app/pages/history/page.tsx
Normal file
74
src/usn-frontend/src/app/pages/history/page.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
|
||||
const simulations = [
|
||||
{
|
||||
name: 'Симуляция 1',
|
||||
droneCount: 10,
|
||||
baseStationCount: 3,
|
||||
startTime: '2024-01-15 10:00',
|
||||
lastUpdate: '2024-01-15 12:30',
|
||||
sizeOnServer: '15MB'
|
||||
},
|
||||
{
|
||||
name: 'Симуляция 2',
|
||||
droneCount: 20,
|
||||
baseStationCount: 5,
|
||||
startTime: '2024-02-10 09:00',
|
||||
lastUpdate: '2024-02-10 11:00',
|
||||
sizeOnServer: '25MB'
|
||||
},
|
||||
{
|
||||
name: 'Симуляция 3',
|
||||
droneCount: 15,
|
||||
baseStationCount: 4,
|
||||
startTime: '2024-03-05 14:00',
|
||||
lastUpdate: '2024-03-05 15:45',
|
||||
sizeOnServer: '18MB'
|
||||
}
|
||||
];
|
||||
|
||||
const History: React.FC = () => {
|
||||
return (
|
||||
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<h1 className="text-4xl sm:text-5xl font-bold text-center sm:text-left">
|
||||
История Симуляций
|
||||
</h1>
|
||||
<table className="w-full max-w-4xl text-left text-gray-800 bg-white shadow-md rounded-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="p-4 border-b">Название</th>
|
||||
<th className="p-4 border-b">Кол-во Дронов</th>
|
||||
<th className="p-4 border-b">Кол-во Базовых Станций</th>
|
||||
<th className="p-4 border-b">Время Начала</th>
|
||||
<th className="p-4 border-b">Последнее Обновление</th>
|
||||
<th className="p-4 border-b">Размер на Сервере</th>
|
||||
<th className="p-4 border-b">Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{simulations.map((simulation, index) => (
|
||||
<tr key={index} className="odd:bg-gray-100 even:bg-gray-50">
|
||||
<td className="p-4 border-b">{simulation.name}</td>
|
||||
<td className="p-4 border-b">{simulation.droneCount}</td>
|
||||
<td className="p-4 border-b">{simulation.baseStationCount}</td>
|
||||
<td className="p-4 border-b">{simulation.startTime}</td>
|
||||
<td className="p-4 border-b">{simulation.lastUpdate}</td>
|
||||
<td className="p-4 border-b">{simulation.sizeOnServer}</td>
|
||||
<td className="p-4 border-b">
|
||||
<button className='button bg-red-600'>DELETE</button>
|
||||
<button className='button bg-blue-600'>RELOAD</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
<p className="text-sm text-gray-500">© 2024 История Симуляций. Все права защищены.</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default History;
|
18
src/usn-frontend/src/app/pages/simulations/page.tsx
Normal file
18
src/usn-frontend/src/app/pages/simulations/page.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import ThreeJsInstance from '@/app/components/Threejs/ThreeJsInstance';
|
||||
import React from 'react';
|
||||
|
||||
|
||||
const Simulations: React.FC = () => {
|
||||
return (
|
||||
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main>
|
||||
<ThreeJsInstance/>
|
||||
</main>
|
||||
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Simulations;
|
57
src/usn-frontend/src/app/pages/user/page.tsx
Normal file
57
src/usn-frontend/src/app/pages/user/page.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
|
||||
interface UserInfo {
|
||||
email: string;
|
||||
fullName: string;
|
||||
createdAt: string;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
interface UserStats {
|
||||
simulations: number;
|
||||
}
|
||||
|
||||
const randomUser: UserInfo = {
|
||||
email: 'example@domain.com',
|
||||
fullName: 'Грицков Никита',
|
||||
createdAt: new Date().toLocaleString(),
|
||||
avatarUrl: '/image.png',
|
||||
};
|
||||
|
||||
const randomUserStats: UserStats = {
|
||||
simulations: 2,
|
||||
};
|
||||
|
||||
const User: React.FC = () => {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-start sm:items-start">
|
||||
<h1 className="text-4xl sm:text-5xl font-bold text-center sm:text-left text-white">
|
||||
Пользователь
|
||||
</h1>
|
||||
<div className="flex flex-col items-start bg-black rounded-lg shadow-md p-6 w-64">
|
||||
<img src={randomUser.avatarUrl} alt="User Avatar" className="w-16 h-16 rounded-full mb-4" />
|
||||
<div className="mb-4">
|
||||
<h2 className="text-2xl font-semibold text-white">{randomUser.fullName}</h2>
|
||||
<p className="text-sm text-gray-300">{randomUser.email}</p>
|
||||
<p className="text-xs text-gray-500">Аккаунт создан: {randomUser.createdAt}</p>
|
||||
</div>
|
||||
<button className="mt-auto text-blue-400 text-sm flex items-center gap-1 hover:underline">
|
||||
Настройки
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
<section className="flex flex-col gap-4 row-start-2 w-full sm:w-auto bg-black rounded-lg shadow-md p-6">
|
||||
<h2 className="text-2xl font-semibold text-white">Статистика пользователя</h2>
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-lg text-white">Симуляций проведено: {randomUserStats.simulations}</p>
|
||||
</div>
|
||||
</section>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
{/* Добавьте контент футера здесь, если необходимо */}
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default User;
|
6
src/usn-frontend/src/app/service/Websocket.ts
Normal file
6
src/usn-frontend/src/app/service/Websocket.ts
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
|
||||
export const useWebsocket = () => {
|
||||
|
||||
}
|
19
src/usn-frontend/tailwind.config.ts
Normal file
19
src/usn-frontend/tailwind.config.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
background: "var(--background)",
|
||||
foreground: "var(--foreground)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
export default config;
|
26
src/usn-frontend/tsconfig.json
Normal file
26
src/usn-frontend/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user