diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b8c0baf..756cd90 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,27 +1,31 @@ # Copilot Instructions for Ospabhost 8.1 -## Архитектура проекта -- **Монорепозиторий**: две основные части — `backend` (Express, TypeScript, Prisma) и `frontend` (React, Vite, TypeScript). + +## Архитектура и основные компоненты +- **Монорепозиторий**: две части — `backend` (Express, TypeScript, Prisma) и `frontend` (React, Vite, TypeScript). - **Backend**: - - Основной сервер: `backend/src/index.ts` — точка входа, маршрутизация, CORS, логирование. - - Модули: `backend/src/modules/*` — бизнес-логика по доменам (auth, ticket, check, os, server, tariff). - - Интеграция с Proxmox: через API, см. `backend/src/modules/server/proxmoxApi.ts`. - - ORM: Prisma, схема — `backend/prisma/schema.prisma`. - - Статические файлы чеков: `backend/uploads/checks`. + - Точка входа: `backend/src/index.ts` (Express, маршруты `/api/*`, CORS, логирование). + - Модули: `backend/src/modules/*` — домены (auth, ticket, check, os, server, tariff), каждый экспортирует маршруты и сервисы. + - Интеграция с Proxmox: через API, см. `backend/src/modules/server/proxmoxApi.ts` (создание/управление контейнерами, смена пароля root, статистика). + - ORM: Prisma, схема — `backend/prisma/schema.prisma`, миграции и seed-скрипты — в `backend/prisma/`. + - Статические файлы чеков: `backend/uploads/checks` (доступны по `/uploads/checks`). - **Frontend**: - SPA на React + Vite, точка входа: `frontend/src/main.tsx`. - Страницы: `frontend/src/pages/*`, компоненты: `frontend/src/components/*`. - - Контекст авторизации: `frontend/src/context/authcontext.tsx`, `useAuth.ts`. + - Авторизация: `frontend/src/context/authcontext.tsx`, `useAuth.ts` (контекст, хуки). + - Дашборд: `frontend/src/pages/dashboard/mainpage.tsx` — реализует сайдбар, вкладки, загрузку данных пользователя, обработку токена, обновление данных через кастомное событие `userDataUpdate`. -## Важные паттерны и конвенции -- **Маршруты API**: начинаются с `/api/`, см. `backend/src/index.ts`. -- **Модули backend**: каждый домен — отдельная папка, экспортирует маршруты и сервисы. -- **Работа с Proxmox**: все операции (создание контейнера, управление, статистика) через функции из `proxmoxApi.ts`. -- **Статусные поля**: для сущностей (Server, Check, Ticket) используются строковые статусы (`creating`, `running`, `pending`, `open` и т.д.). +## Ключевые паттерны и конвенции +- **API**: все маршруты backend — с префиксом `/api/`. +- **Модули backend**: каждый домен — отдельная папка, экспортирует маршруты и сервисы (см. пример: `server/proxmoxApi.ts`). +- **Работа с Proxmox**: все операции через функции из `proxmoxApi.ts`, параметры берутся из `.env`. +- **Статусные поля**: для Server, Check, Ticket — строковые статусы (`creating`, `running`, `pending`, `open` и др.). - **Пароли**: генерируются через `generateSecurePassword` (см. `proxmoxApi.ts`). - **Описание тарифа**: парсится для выделения ресурсов (ядра, RAM, SSD) при создании контейнера. +- **Frontend**: авторизация через контекст, проверка токена, автоматический logout при ошибке 401. +- **Дашборд**: вкладки и права оператора определяются по полю `operator` в userData, обновление данных через событие `userDataUpdate`. -## Сборка и запуск +## Сборка, запуск и workflow - **Backend**: - `npm run dev` — запуск с hot-reload (ts-node-dev). - `npm run build` — компиляция TypeScript. @@ -32,9 +36,9 @@ - `npm run preview` — предпросмотр production-сборки. - `npm run lint` — проверка ESLint. -## Взаимодействие компонентов -- **Frontend ↔ Backend**: через REST API, адреса `/api/*`. -- **Backend ↔ Proxmox**: через HTTP API, параметры берутся из `.env`. +## Интеграции и взаимодействие +- **Frontend ↔ Backend**: через REST API (`/api/*`), авторизация через JWT-токен в localStorage. +- **Backend ↔ Proxmox**: через HTTP API, параметры из `.env`. - **Prisma**: миграции и seed-скрипты — в `backend/prisma/`. ## Внешние зависимости @@ -45,15 +49,16 @@ - `backend/src/index.ts` — точка входа, маршрутизация. - `backend/src/modules/server/proxmoxApi.ts` — интеграция с Proxmox. - `backend/prisma/schema.prisma` — схема данных. -- `frontend/src/pages/*` — страницы SPA. +- `frontend/src/pages/dashboard/mainpage.tsx` — дашборд, обработка токена, сайдбар, вкладки. - `frontend/src/context/authcontext.tsx` — авторизация. -## Особенности +## Особенности и conventions - **CORS**: разрешены только локальные адреса для разработки. - **Логирование**: каждый запрос логируется с датой и методом. - **Статические файлы**: чеки доступны по `/uploads/checks`. - **Пароли root**: генерируются и меняются через API Proxmox. +- **Frontend**: сайдбар и вкладки строятся динамически, права оператора определяются по userData. --- -_Обновите этот файл при изменении архитектуры или ключевых паттернов. Для уточнения разделов — дайте обратную связь!_ \ No newline at end of file +_Обновляйте этот файл при изменении архитектуры, workflow или паттернов. Для уточнения разделов — дайте обратную связь!_ \ No newline at end of file diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index e77f371..6e14c05 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -2606,6 +2606,18 @@ "node": ">= 0.6.0" } }, + "node_modules/proxmox-api": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/proxmox-api/-/proxmox-api-1.1.1.tgz", + "integrity": "sha512-2qH7pxKBBHa7WtEBmxPaBY2FZEH2R04hqr9zD9PmErLzJ7RGGcfNcXoS/v5G4vBM2Igmnx0EAYBstPwwfDwHnA==", + "license": "GPL-3.0", + "dependencies": { + "undici": "^6.19.8" + }, + "funding": { + "url": "https://github.com/sponsors/urielch" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -3450,6 +3462,15 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", + "integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.11.0.tgz", diff --git a/ospabhost/README.md b/ospabhost/README.md new file mode 100644 index 0000000..4ce7e00 --- /dev/null +++ b/ospabhost/README.md @@ -0,0 +1 @@ +Ospab.host site version 8 diff --git a/ospabhost/backend/package-lock.json b/ospabhost/backend/package-lock.json index ff582fd..d786f14 100644 --- a/ospabhost/backend/package-lock.json +++ b/ospabhost/backend/package-lock.json @@ -10,6 +10,8 @@ "license": "ISC", "dependencies": { "@prisma/client": "^6.16.2", + "@types/ssh2": "^1.15.5", + "@types/ws": "^8.18.1", "axios": "^1.12.2", "bcrypt": "^6.0.0", "bcryptjs": "^2.4.3", @@ -18,8 +20,10 @@ "express": "^4.21.2", "jsonwebtoken": "^9.0.2", "multer": "^2.0.2", - "nodemailer": "^6.9.16", - "socket.io": "^4.8.1" + "proxmox-api": "^1.1.1", + "ssh2": "^1.17.0", + "ws": "^8.18.3", + "xterm": "^5.3.0" }, "devDependencies": { "@types/bcrypt": "^6.0.0", @@ -29,7 +33,7 @@ "@types/jsonwebtoken": "^9.0.10", "@types/multer": "^2.0.0", "@types/node": "^20.12.12", - "@types/nodemailer": "^6.4.17", + "@types/xterm": "^2.0.3", "prisma": "^6.16.2", "ts-node-dev": "^2.0.0", "typescript": "^5.4.5" @@ -161,12 +165,6 @@ "@prisma/debug": "6.16.2" } }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "license": "MIT" - }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -244,6 +242,7 @@ "version": "2.8.19", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -326,16 +325,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/@types/nodemailer": { - "version": "6.4.17", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", - "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -373,6 +362,30 @@ "@types/send": "*" } }, + "node_modules/@types/ssh2": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz", + "integrity": "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2/node_modules/@types/node": { + "version": "18.19.127", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.127.tgz", + "integrity": "sha512-gSjxjrnKXML/yo0BO099uPixMqfpJU0TKYjpfLU7TrtA2WWDki412Np/RSTPRil1saKBhvVVKzVx/p/6p94nVA==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ssh2/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, "node_modules/@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", @@ -387,6 +400,22 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/xterm": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/xterm/-/xterm-2.0.3.tgz", + "integrity": "sha512-Owlz29ThHtn2RQry87juaNYeIc4Dr8ykLLX0JKKt4SdO6ujwJnsXCpBAr6bwo/f4L3xSfM9KA7OnPPf9Xit6tA==", + "dev": true, + "license": "MIT" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -459,6 +488,15 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -483,15 +521,6 @@ "dev": true, "license": "MIT" }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "license": "MIT", - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, "node_modules/bcrypt": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", @@ -506,6 +535,15 @@ "node": ">= 18" } }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/bcryptjs": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", @@ -585,6 +623,15 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/buildcheck": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", + "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -789,6 +836,20 @@ "node": ">= 0.10" } }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -948,67 +1009,6 @@ "node": ">= 0.8" } }, - "node_modules/engine.io": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", - "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", - "license": "MIT", - "dependencies": { - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.7.2", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1767,6 +1767,13 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/nan": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", + "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", + "license": "MIT", + "optional": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1803,15 +1810,6 @@ "node-gyp-build-test": "build-test.js" } }, - "node_modules/nodemailer": { - "version": "6.9.16", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", - "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", - "license": "MIT-0", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1989,6 +1987,18 @@ } } }, + "node_modules/proxmox-api": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/proxmox-api/-/proxmox-api-1.1.1.tgz", + "integrity": "sha512-2qH7pxKBBHa7WtEBmxPaBY2FZEH2R04hqr9zD9PmErLzJ7RGGcfNcXoS/v5G4vBM2Igmnx0EAYBstPwwfDwHnA==", + "license": "GPL-3.0", + "dependencies": { + "undici": "^6.19.8" + }, + "funding": { + "url": "https://github.com/sponsors/urielch" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2308,116 +2318,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/socket.io": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", - "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.6.0", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", - "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", - "license": "MIT", - "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" - } - }, - "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-adapter/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-parser/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2439,6 +2339,23 @@ "source-map": "^0.6.0" } }, + "node_modules/ssh2": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz", + "integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==", + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.10", + "nan": "^2.23.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -2667,6 +2584,12 @@ "strip-json-comments": "^2.0.0" } }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -2700,6 +2623,15 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", + "integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -2754,9 +2686,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -2783,6 +2715,13 @@ "node": ">=0.4" } }, + "node_modules/xterm": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/xterm/-/xterm-5.3.0.tgz", + "integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==", + "deprecated": "This package is now deprecated. Move to @xterm/xterm instead.", + "license": "MIT" + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/ospabhost/backend/package.json b/ospabhost/backend/package.json index 4f02ecb..fea0902 100644 --- a/ospabhost/backend/package.json +++ b/ospabhost/backend/package.json @@ -5,7 +5,7 @@ "main": "dist/index.js", "scripts": { "dev": "ts-node-dev --respawn --transpile-only src/index.ts", - "start": "node dist/index.js", + "start": "node dist/src/index.js", "build": "tsc" }, "keywords": [], @@ -13,6 +13,8 @@ "license": "ISC", "dependencies": { "@prisma/client": "^6.16.2", + "@types/ssh2": "^1.15.5", + "@types/ws": "^8.18.1", "axios": "^1.12.2", "bcrypt": "^6.0.0", "bcryptjs": "^2.4.3", @@ -21,8 +23,10 @@ "express": "^4.21.2", "jsonwebtoken": "^9.0.2", "multer": "^2.0.2", - "nodemailer": "^6.9.16", - "socket.io": "^4.8.1" + "proxmox-api": "^1.1.1", + "ssh2": "^1.17.0", + "ws": "^8.18.3", + "xterm": "^5.3.0" }, "devDependencies": { "@types/bcrypt": "^6.0.0", @@ -32,7 +36,7 @@ "@types/jsonwebtoken": "^9.0.10", "@types/multer": "^2.0.0", "@types/node": "^20.12.12", - "@types/nodemailer": "^6.4.17", + "@types/xterm": "^2.0.3", "prisma": "^6.16.2", "ts-node-dev": "^2.0.0", "typescript": "^5.4.5" diff --git a/ospabhost/backend/prisma/schema.prisma b/ospabhost/backend/prisma/schema.prisma index 2346b28..c4a58c7 100644 --- a/ospabhost/backend/prisma/schema.prisma +++ b/ospabhost/backend/prisma/schema.prisma @@ -17,8 +17,12 @@ model Tariff { description String? createdAt DateTime @default(now()) servers Server[] + +@@map("tariff") } + + model OperatingSystem { id Int @id @default(autoincrement()) name String @unique @@ -26,6 +30,8 @@ model OperatingSystem { template String? // путь к шаблону для контейнера createdAt DateTime @default(now()) servers Server[] + + @@map("operatingsystem") } model Server { @@ -60,6 +66,8 @@ model Server { diskUsage Float? @default(0) networkIn Float? @default(0) networkOut Float? @default(0) + + @@map("server") } model User { @@ -76,6 +84,8 @@ model User { balance Float @default(0) servers Server[] notifications Notification[] + + @@map("user") } model Check { @@ -86,6 +96,8 @@ model Check { fileUrl String createdAt DateTime @default(now()) user User @relation("UserChecks", fields: [userId], references: [id]) + +@@map("check") } model Plan { @@ -98,6 +110,8 @@ model Plan { userId Int owner User @relation("UserPlans", fields: [userId], references: [id]) services Service[] @relation("PlanServices") + +@@map("plan") } model Service { @@ -106,6 +120,8 @@ model Service { price Float planId Int? plan Plan? @relation("PlanServices", fields: [planId], references: [id]) + +@@map("service") } model Ticket { @@ -118,6 +134,8 @@ model Ticket { updatedAt DateTime @updatedAt responses Response[] @relation("TicketResponses") user User? @relation("UserTickets", fields: [userId], references: [id]) + +@@map("ticket") } model Response { @@ -128,6 +146,10 @@ model Response { createdAt DateTime @default(now()) ticket Ticket @relation("TicketResponses", fields: [ticketId], references: [id]) operator User @relation("OperatorResponses", fields: [operatorId], references: [id]) + +@@map("response") + + } model Notification { id Int @id @default(autoincrement()) @@ -136,4 +158,6 @@ model Notification { title String message String createdAt DateTime @default(now()) + +@@map("notification") } \ No newline at end of file diff --git a/ospabhost/backend/proxmox/proxmoxApi.ts b/ospabhost/backend/proxmox/proxmoxApi.ts index ec79e7e..7aae82f 100644 --- a/ospabhost/backend/proxmox/proxmoxApi.ts +++ b/ospabhost/backend/proxmox/proxmoxApi.ts @@ -43,3 +43,4 @@ export async function createContainer({ vmid, hostname, password, ostemplate, st throw new Error('Ошибка создания контейнера: ' + (err instanceof Error ? err.message : err)); } } + diff --git a/ospabhost/backend/src/index.ts b/ospabhost/backend/src/index.ts index 3b8ea4e..99947b1 100644 --- a/ospabhost/backend/src/index.ts +++ b/ospabhost/backend/src/index.ts @@ -1,8 +1,6 @@ import express from 'express'; import cors from 'cors'; import dotenv from 'dotenv'; -import http from 'http'; -import { Server as SocketIOServer } from 'socket.io'; import authRoutes from './modules/auth/auth.routes'; import ticketRoutes from './modules/ticket/ticket.routes'; import checkRoutes from './modules/check/check.routes'; @@ -10,25 +8,18 @@ import proxmoxRoutes from '../proxmox/proxmox.routes'; import tariffRoutes from './modules/tariff'; import osRoutes from './modules/os'; import serverRoutes from './modules/server'; -import { MonitoringService } from './modules/server/monitoring.service'; dotenv.config(); const app = express(); -const server = http.createServer(app); - -// Настройка Socket.IO с CORS -const io = new SocketIOServer(server, { - cors: { - origin: ['http://localhost:3000', 'http://localhost:5173'], - methods: ['GET', 'POST'], - credentials: true - } -}); // ИСПРАВЛЕНО: более точная настройка CORS app.use(cors({ - origin: ['http://localhost:3000', 'http://localhost:5173'], // Vite обычно использует 5173 + origin: [ + 'http://localhost:3000', + 'http://localhost:5173', + 'https://ospab.host' + ], // Vite обычно использует 5173 credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] @@ -78,13 +69,19 @@ app.use('/api/server', serverRoutes); const PORT = process.env.PORT || 5000; -// Инициализация сервиса мониторинга -const monitoringService = new MonitoringService(io); -monitoringService.startMonitoring(); +import { setupConsoleWSS } from './modules/server/server.console'; +import https from 'https'; +import fs from 'fs'; -server.listen(PORT, () => { - console.log(`🚀 Сервер запущен на порту ${PORT}`); +const sslOptions = { + key: fs.readFileSync('/etc/apache2/ssl/ospab.host.key'), + cert: fs.readFileSync('/etc/apache2/ssl/ospab.host.crt'), +}; + +const httpsServer = https.createServer(sslOptions, app); +setupConsoleWSS(httpsServer); + +httpsServer.listen(PORT, () => { + console.log(`🚀 HTTPS сервер запущен на порту ${PORT}`); console.log(`📊 База данных: ${process.env.DATABASE_URL ? 'подключена' : 'НЕ НАСТРОЕНА'}`); - console.log(`🔌 WebSocket сервер запущен`); - console.log(`📡 Мониторинг серверов активен`); }); \ No newline at end of file diff --git a/ospabhost/backend/src/modules/os/os.routes.ts b/ospabhost/backend/src/modules/os/os.routes.ts index ae2d675..bb8410a 100644 --- a/ospabhost/backend/src/modules/os/os.routes.ts +++ b/ospabhost/backend/src/modules/os/os.routes.ts @@ -1,10 +1,14 @@ + import { Router } from 'express'; import { PrismaClient } from '@prisma/client'; +import { authMiddleware } from '../auth/auth.middleware'; const router = Router(); const prisma = new PrismaClient(); -// GET /api/os — получить все ОС +router.use(authMiddleware); + +// GET /api/os — получить все ОС (только для авторизованных) router.get('/', async (req, res) => { try { const oses = await prisma.operatingSystem.findMany(); diff --git a/ospabhost/backend/src/modules/server/proxmoxApi.ts b/ospabhost/backend/src/modules/server/proxmoxApi.ts index 4399b6b..a75af12 100644 --- a/ospabhost/backend/src/modules/server/proxmoxApi.ts +++ b/ospabhost/backend/src/modules/server/proxmoxApi.ts @@ -1,3 +1,19 @@ +// Смена root-пароля через SSH (для LXC) +import { exec } from 'child_process'; + +export async function changeRootPasswordSSH(vmid: number): Promise<{ status: string; password?: string; message?: string }> { + const newPassword = generateSecurePassword(); + return new Promise((resolve) => { + exec(`ssh -o StrictHostKeyChecking=no root@${process.env.PROXMOX_NODE} pct set ${vmid} --password ${newPassword}`, (err, stdout, stderr) => { + if (err) { + console.error('Ошибка смены пароля через SSH:', stderr); + resolve({ status: 'error', message: stderr }); + } else { + resolve({ status: 'success', password: newPassword }); + } + }); + }); +} import axios from 'axios'; import crypto from 'crypto'; import dotenv from 'dotenv'; diff --git a/ospabhost/backend/src/modules/server/server.console.ts b/ospabhost/backend/src/modules/server/server.console.ts new file mode 100644 index 0000000..16f49cc --- /dev/null +++ b/ospabhost/backend/src/modules/server/server.console.ts @@ -0,0 +1,70 @@ +import { Server as WebSocketServer, WebSocket } from 'ws'; +import { Client as SSHClient } from 'ssh2'; +import dotenv from 'dotenv'; +import { IncomingMessage } from 'http'; +import { Server as HttpServer } from 'http'; +dotenv.config(); + +export function setupConsoleWSS(server: HttpServer) { + const wss = new WebSocketServer({ noServer: true }); + + wss.on('connection', (ws: WebSocket, req: IncomingMessage) => { + const url = req.url || ''; + const match = url.match(/\/api\/server\/(\d+)\/console/); + const vmid = match ? match[1] : null; + if (!vmid) { + ws.close(); + return; + } + + // Получаем IP и root-пароль из БД (упрощённо) + // Здесь можно добавить реальный запрос к Prisma + const host = process.env.PROXMOX_IP || process.env.PROXMOX_NODE; + const username = 'root'; + const password = process.env.PROXMOX_ROOT_PASSWORD; + + const ssh = new SSHClient(); + const port = process.env.PROXMOX_SSH_PORT ? Number(process.env.PROXMOX_SSH_PORT) : 22; + ssh.on('ready', () => { + ssh.shell((err: Error | undefined, stream: any) => { + if (err) { + ws.send('Ошибка запуска shell: ' + err.message); + ws.close(); + ssh.end(); + return; + } + ws.on('message', (msg: string | Buffer) => { + stream.write(msg.toString()); + }); + stream.on('data', (data: Buffer) => { + ws.send(data.toString()); + }); + stream.on('close', () => { + ws.close(); + ssh.end(); + }); + }); + }).connect({ + host, + port, + username, + password, + hostVerifier: (hash: string) => { + console.log('SSH fingerprint:', hash); + return true; // всегда принимаем fingerprint + } + }); + + ws.on('close', () => { + ssh.end(); + }); + }); + + server.on('upgrade', (request: IncomingMessage, socket: any, head: Buffer) => { + if (request.url?.startsWith('/api/server/') && request.url?.endsWith('/console')) { + wss.handleUpgrade(request, socket, head, (ws: WebSocket) => { + wss.emit('connection', ws, request); + }); + } + }); +} diff --git a/ospabhost/backend/src/modules/server/server.controller.ts b/ospabhost/backend/src/modules/server/server.controller.ts index bf2e1a1..08833e5 100644 --- a/ospabhost/backend/src/modules/server/server.controller.ts +++ b/ospabhost/backend/src/modules/server/server.controller.ts @@ -66,13 +66,13 @@ export async function createServer(req: Request, res: Response) { }); } - // Сохраняем сервер в БД с реальным статусом + // Сохраняем сервер в БД, статус всегда 'running' после покупки const server = await prisma.server.create({ data: { userId, tariffId, osId, - status: result.containerStatus || 'creating', + status: 'running', proxmoxId: Number(result.vmid), ipAddress: result.ipAddress, rootPassword: result.rootPassword, @@ -146,10 +146,11 @@ async function handleControl(req: Request, res: Response, action: 'start' | 'sto const result = await controlContainer(server.proxmoxId, action); // Polling статуса VM после управления let newStatus = server.status; + let actionSuccess = false; + let status = ''; + let attempts = 0; + const maxAttempts = 10; if (result.status === 'success') { - let status = ''; - let attempts = 0; - const maxAttempts = 10; while (attempts < maxAttempts) { await new Promise(resolve => setTimeout(resolve, 3000)); const stats = await getContainerStats(server.proxmoxId); @@ -158,6 +159,7 @@ async function handleControl(req: Request, res: Response, action: 'start' | 'sto if ((action === 'start' && status === 'running') || (action === 'stop' && status === 'stopped') || (action === 'restart' && status === 'running')) { + actionSuccess = true; break; } } @@ -178,6 +180,11 @@ async function handleControl(req: Request, res: Response, action: 'start' | 'sto } await prisma.server.update({ where: { id }, data: { status: newStatus } }); } + // Если статус изменился, считаем действие успешным даже если result.status !== 'success' + if (newStatus !== server.status) { + return res.json({ status: 'success', newStatus, message: 'Статус сервера изменён успешно' }); + } + // Если не удалось, возвращаем исходный ответ res.json({ ...result, status: newStatus }); } catch (error: any) { res.status(500).json({ error: error?.message || 'Ошибка управления сервером' }); @@ -208,7 +215,9 @@ export async function changeRootPassword(req: Request, res: Response) { const id = Number(req.params.id); const server = await prisma.server.findUnique({ where: { id } }); if (!server || !server.proxmoxId) return res.status(404).json({ error: 'Сервер не найден или нет VMID' }); - const result = await proxmoxChangeRootPassword(server.proxmoxId); + // Используем SSH для смены пароля + const { changeRootPasswordSSH } = require('./proxmoxApi'); + const result = await changeRootPasswordSSH(server.proxmoxId); if (result?.status === 'success' && result.password) { await prisma.server.update({ where: { id }, data: { rootPassword: result.password } }); } diff --git a/ospabhost/backend/src/modules/tariff/tariff.routes.ts b/ospabhost/backend/src/modules/tariff/tariff.routes.ts index ad90acd..e2f5671 100644 --- a/ospabhost/backend/src/modules/tariff/tariff.routes.ts +++ b/ospabhost/backend/src/modules/tariff/tariff.routes.ts @@ -1,10 +1,10 @@ + import { Router } from 'express'; import { PrismaClient } from '@prisma/client'; const router = Router(); const prisma = new PrismaClient(); -// GET /api/tariff — получить все тарифы router.get('/', async (req, res) => { try { const tariffs = await prisma.tariff.findMany(); diff --git a/ospabhost/backend/tsconfig.json b/ospabhost/backend/tsconfig.json index 3da1428..31e046f 100644 --- a/ospabhost/backend/tsconfig.json +++ b/ospabhost/backend/tsconfig.json @@ -10,7 +10,7 @@ "forceConsistentCasingInFileNames": true, "moduleResolution": "node16", "resolveJsonModule": true, - "noEmit": true, + "outDir": "./dist", "verbatimModuleSyntax": false }, "include": ["src/**/*.ts"], diff --git a/ospabhost/frontend/index.html b/ospabhost/frontend/index.html index b2a40d0..97fe878 100644 --- a/ospabhost/frontend/index.html +++ b/ospabhost/frontend/index.html @@ -2,7 +2,9 @@ - + + + diff --git a/ospabhost/frontend/package-lock.json b/ospabhost/frontend/package-lock.json index a835f85..519e3da 100644 --- a/ospabhost/frontend/package-lock.json +++ b/ospabhost/frontend/package-lock.json @@ -13,8 +13,7 @@ "react-dom": "^19.1.1", "react-icons": "^5.5.0", "react-qr-code": "^2.0.18", - "recharts": "^2.15.0", - "socket.io-client": "^4.8.1" + "xterm": "^5.3.0" }, "devDependencies": { "@eslint/js": "^9.33.0", @@ -281,15 +280,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -1404,12 +1394,6 @@ "win32" ] }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "license": "MIT" - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1455,69 +1439,6 @@ "@babel/types": "^7.28.2" } }, - "node_modules/@types/d3-array": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "license": "MIT" - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" - }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "license": "MIT" - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "license": "MIT", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "license": "MIT" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", - "license": "MIT", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-shape": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", - "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", - "license": "MIT", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "license": "MIT" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2200,15 +2121,6 @@ "node": ">= 6" } }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2307,129 +2219,9 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, "license": "MIT" }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", - "dependencies": { - "d3-path": "^3.1.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", - "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2448,12 +2240,6 @@ } } }, - "node_modules/decimal.js-light": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", - "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", - "license": "MIT" - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2484,16 +2270,6 @@ "dev": true, "license": "MIT" }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2529,45 +2305,6 @@ "dev": true, "license": "MIT" }, - "node_modules/engine.io-client": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", - "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.17.1", - "xmlhttprequest-ssl": "~2.1.1" - } - }, - "node_modules/engine.io-client/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2856,12 +2593,6 @@ "node": ">=0.10.0" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2869,15 +2600,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-equals": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz", - "integrity": "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -3312,15 +3034,6 @@ "node": ">=0.8.19" } }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3552,12 +3265,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3668,6 +3375,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -4234,37 +3942,6 @@ "react-dom": ">=18" } }, - "node_modules/react-smooth": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", - "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", - "license": "MIT", - "dependencies": { - "fast-equals": "^5.0.1", - "prop-types": "^15.8.1", - "react-transition-group": "^4.4.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -4288,44 +3965,6 @@ "node": ">=8.10.0" } }, - "node_modules/recharts": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.0.tgz", - "integrity": "sha512-cIvMxDfpAmqAmVgc4yb7pgm/O1tmmkl/CjrvXuW+62/+7jj/iF9Ykm+hb/UJt42TREHMyd3gb+pkgoa2MxgDIw==", - "license": "MIT", - "dependencies": { - "clsx": "^2.0.0", - "eventemitter3": "^4.0.1", - "lodash": "^4.17.21", - "react-is": "^18.3.1", - "react-smooth": "^4.0.0", - "recharts-scale": "^0.4.4", - "tiny-invariant": "^1.3.1", - "victory-vendor": "^36.6.8" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/recharts-scale": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", - "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", - "license": "MIT", - "dependencies": { - "decimal.js-light": "^2.4.1" - } - }, - "node_modules/recharts/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -4492,68 +4131,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/socket.io-client": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", - "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.2", - "engine.io-client": "~6.6.1", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-client/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4791,12 +4368,6 @@ "node": ">=0.8" } }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -4977,28 +4548,6 @@ "dev": true, "license": "MIT" }, - "node_modules/victory-vendor": { - "version": "36.9.2", - "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", - "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", - "license": "MIT AND ISC", - "dependencies": { - "@types/d3-array": "^3.0.3", - "@types/d3-ease": "^3.0.0", - "@types/d3-interpolate": "^3.0.1", - "@types/d3-scale": "^4.0.2", - "@types/d3-shape": "^3.1.0", - "@types/d3-time": "^3.0.0", - "@types/d3-timer": "^3.0.0", - "d3-array": "^3.1.6", - "d3-ease": "^3.0.1", - "d3-interpolate": "^3.0.1", - "d3-scale": "^4.0.2", - "d3-shape": "^3.1.0", - "d3-time": "^3.0.0", - "d3-timer": "^3.0.1" - } - }, "node_modules/vite": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", @@ -5226,34 +4775,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xmlhttprequest-ssl": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", - "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", - "engines": { - "node": ">=0.4.0" - } + "node_modules/xterm": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/xterm/-/xterm-5.3.0.tgz", + "integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==", + "deprecated": "This package is now deprecated. Move to @xterm/xterm instead.", + "license": "MIT" }, "node_modules/yallist": { "version": "3.1.1", diff --git a/ospabhost/frontend/package.json b/ospabhost/frontend/package.json index 180bc64..2f0b6db 100644 --- a/ospabhost/frontend/package.json +++ b/ospabhost/frontend/package.json @@ -15,8 +15,7 @@ "react-dom": "^19.1.1", "react-icons": "^5.5.0", "react-qr-code": "^2.0.18", - "recharts": "^2.15.0", - "socket.io-client": "^4.8.1" + "xterm": "^5.3.0" }, "devDependencies": { "@eslint/js": "^9.33.0", diff --git a/ospabhost/frontend/public/android-chrome-192x192.png b/ospabhost/frontend/public/android-chrome-192x192.png new file mode 100644 index 0000000..c25f9a3 Binary files /dev/null and b/ospabhost/frontend/public/android-chrome-192x192.png differ diff --git a/ospabhost/frontend/public/android-chrome-512x512.png b/ospabhost/frontend/public/android-chrome-512x512.png new file mode 100644 index 0000000..014049e Binary files /dev/null and b/ospabhost/frontend/public/android-chrome-512x512.png differ diff --git a/ospabhost/frontend/public/apple-touch-icon.png b/ospabhost/frontend/public/apple-touch-icon.png new file mode 100644 index 0000000..c6b8d44 Binary files /dev/null and b/ospabhost/frontend/public/apple-touch-icon.png differ diff --git a/ospabhost/frontend/public/favicon-16x16.png b/ospabhost/frontend/public/favicon-16x16.png new file mode 100644 index 0000000..971d412 Binary files /dev/null and b/ospabhost/frontend/public/favicon-16x16.png differ diff --git a/ospabhost/frontend/public/favicon-32x32.png b/ospabhost/frontend/public/favicon-32x32.png new file mode 100644 index 0000000..1ce854a Binary files /dev/null and b/ospabhost/frontend/public/favicon-32x32.png differ diff --git a/ospabhost/frontend/public/favicon.ico b/ospabhost/frontend/public/favicon.ico new file mode 100644 index 0000000..c5def41 Binary files /dev/null and b/ospabhost/frontend/public/favicon.ico differ diff --git a/ospabhost/frontend/public/me.jpg b/ospabhost/frontend/public/me.jpg new file mode 100644 index 0000000..6923194 Binary files /dev/null and b/ospabhost/frontend/public/me.jpg differ diff --git a/ospabhost/frontend/public/site.webmanifest b/ospabhost/frontend/public/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/ospabhost/frontend/public/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/ospabhost/frontend/public/vite.svg b/ospabhost/frontend/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/ospabhost/frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ospabhost/frontend/src/assets/logo.svg b/ospabhost/frontend/src/assets/logo.svg new file mode 100644 index 0000000..d5b8a04 --- /dev/null +++ b/ospabhost/frontend/src/assets/logo.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ospabhost/frontend/src/assets/react.svg b/ospabhost/frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/ospabhost/frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ospabhost/frontend/src/components/ServerConsole.tsx b/ospabhost/frontend/src/components/ServerConsole.tsx new file mode 100644 index 0000000..9d66cc4 --- /dev/null +++ b/ospabhost/frontend/src/components/ServerConsole.tsx @@ -0,0 +1,120 @@ +import React, { useEffect, useRef } from 'react'; +import { useParams } from 'react-router-dom'; +import { Terminal } from 'xterm'; +import 'xterm/css/xterm.css'; + +const ServerConsole: React.FC = () => { + const { id } = useParams(); + const termRef = useRef(null); + const wsRef = useRef(null); + const xtermRef = useRef(null); + const reconnectAttempts = useRef(0); + + // Логгер ошибок + const logError = (msg: string, err?: unknown) => { + console.error('[Console]', msg, err); + }; + + useEffect(() => { + let disposed = false; + if (!termRef.current) return; + let term: Terminal | null = null; + try { + term = new Terminal({ + rows: 24, + cols: 80, + fontSize: 16, + theme: { + background: '#18181b', + foreground: '#e5e7eb', + }, + cursorBlink: true, + }); + term.open(termRef.current); + xtermRef.current = term; + } catch (err) { + logError('Ошибка инициализации xterm', err); + return; + } + + // Resize обработка + const handleResize = () => { + try { + term?.resize( + Math.floor(termRef.current?.offsetWidth ? termRef.current.offsetWidth / 9 : 80), + Math.floor(termRef.current?.offsetHeight ? termRef.current.offsetHeight / 20 : 24) + ); + } catch (err) { + logError('Ошибка resize терминала', err); + } + }; + window.addEventListener('resize', handleResize); + setTimeout(handleResize, 100); + + // WebSocket с авто-подключением + const connectWS = () => { + if (disposed) return; + const ws = new WebSocket(`wss://ospab.host:5000/api/server/${id}/console`); + wsRef.current = ws; + + ws.onopen = () => { + reconnectAttempts.current = 0; + term?.write('\x1b[32mПодключено к серверу\x1b[0m\r\n'); + }; + ws.onmessage = (event) => { + try { + term?.write(event.data); + } catch (err) { + logError('Ошибка вывода данных в терминал', err); + } + }; + ws.onclose = (event) => { + logError('WebSocket закрыт', event); + term?.write('\r\n\x1b[31mОтключено от сервера\x1b[0m\r\n'); + // Авто-подключение (до 5 попыток) + if (!disposed && reconnectAttempts.current < 5) { + reconnectAttempts.current++; + setTimeout(connectWS, 1000 * reconnectAttempts.current); + } + }; + ws.onerror = (event) => { + logError('WebSocket ошибка', event); + term?.write('\r\n\x1b[31mОшибка соединения\x1b[0m\r\n'); + }; + + term?.onData((data: string) => { + try { + if (ws.readyState === WebSocket.OPEN) { + ws.send(data); + } + } catch (err) { + logError('Ошибка отправки данных по WebSocket', err); + } + }); + }; + connectWS(); + + return () => { + disposed = true; + try { + wsRef.current?.close(); + } catch (err) { + logError('Ошибка закрытия WebSocket', err); + } + try { + term?.dispose(); + } catch (err) { + logError('Ошибка dispose терминала', err); + } + window.removeEventListener('resize', handleResize); + }; + }, [id]); + + return ( +
+
+
+ ); +}; + +export default ServerConsole; diff --git a/ospabhost/frontend/src/components/dashboardtempl.tsx b/ospabhost/frontend/src/components/dashboardtempl.tsx index d8b4b25..171838a 100644 --- a/ospabhost/frontend/src/components/dashboardtempl.tsx +++ b/ospabhost/frontend/src/components/dashboardtempl.tsx @@ -10,7 +10,8 @@ const DashboardTempl: React.FC = ({ children }) => { return (
-
+
+
{children}
diff --git a/ospabhost/frontend/src/components/footer.tsx b/ospabhost/frontend/src/components/footer.tsx index 56c839c..dc000c8 100644 --- a/ospabhost/frontend/src/components/footer.tsx +++ b/ospabhost/frontend/src/components/footer.tsx @@ -1,4 +1,5 @@ import { Link } from 'react-router-dom'; +import logo from '../assets/logo.svg'; const Footer = () => { const currentYear = new Date().getFullYear(); @@ -9,6 +10,9 @@ const Footer = () => {
{/* About Section */}
+
+ Логотип +

О нас

ospab.host - это надежный хостинг для ваших проектов. Мы предлагаем высокую производительность и круглосуточную поддержку. diff --git a/ospabhost/frontend/src/components/header.tsx b/ospabhost/frontend/src/components/header.tsx index d6d7ba3..efa4f0a 100644 --- a/ospabhost/frontend/src/components/header.tsx +++ b/ospabhost/frontend/src/components/header.tsx @@ -1,5 +1,6 @@ import { Link } from 'react-router-dom'; import useAuth from '../context/useAuth'; +import logo from '../assets/logo.svg'; const Header = () => { const { isLoggedIn, logout } = useAuth(); @@ -9,10 +10,13 @@ const Header = () => { }; return ( -

+
-
- ospab.host +
+ + Логотип + ospab.host +
Тарифы diff --git a/ospabhost/frontend/src/components/pagetempl.tsx b/ospabhost/frontend/src/components/pagetempl.tsx index 7dd5eb3..58d4658 100644 --- a/ospabhost/frontend/src/components/pagetempl.tsx +++ b/ospabhost/frontend/src/components/pagetempl.tsx @@ -10,7 +10,7 @@ const PageTmpl: React.FC = ({ children }) => { return (
-
+
{children}