From 27310c0bc0adeb1d7e6fff1b35fd110db1a909e2 Mon Sep 17 00:00:00 2001 From: Den Piligrim <89912505+vasiljevdenis@users.noreply.github.com> Date: Sat, 31 Jan 2026 18:44:09 +0300 Subject: [PATCH] func optimization --- client/.dockerignore | 7 + client/Dockerfile | 21 + client/nginx.conf | 17 + client/package-lock.json | 2 +- client/package.json | 4 +- client/src/App.tsx | 1 - client/src/ThemeContext.tsx | 4 - client/src/api.ts | 2 +- client/src/auth/AuthContext.tsx | 44 +- client/src/components/Header.tsx | 8 +- client/src/components/Layout.tsx | 8 +- client/src/pages/DomainsPage.tsx | 34 +- client/src/pages/NotFoundPage.tsx | 1 - client/src/pages/SettingsPage.tsx | 101 +- client/src/pages/SubscriptionsPage.tsx | 101 +- client/tsconfig.app.json | 4 +- client/tsconfig.node.json | 4 +- client/vite.config.ts | 5 +- docker-compose.yml | 37 +- install.sh | 601 ++++---- server/.dockerignore | 6 + server/Dockerfile | 27 + server/src/auth/auth.controller.ts | 2 +- server/src/auth/auth.service.ts | 8 - server/src/auth/jwt-auth.guard.ts | 2 +- server/src/client/client.controller.ts | 7 +- server/src/domains/domains.controller.ts | 3 - server/src/domains/domains.service.ts | 23 +- .../src/inbounds/inbound-builder.service.ts | 7 +- server/src/rotation/rotation.controller.ts | 12 + server/src/rotation/rotation.module.ts | 2 + server/src/rotation/rotation.service.ts | 12 +- server/src/settings/countries.ts | 1307 +++++++++++++++++ server/src/settings/settings.controller.ts | 32 + .../subscriptions/subscriptions.service.ts | 1 - server/src/tunnels/tunnels.module.ts | 2 +- server/src/tunnels/tunnels.service.ts | 7 - server/src/xui/xui.service.ts | 55 +- server/tsconfig.json | 7 +- 39 files changed, 1989 insertions(+), 539 deletions(-) create mode 100644 client/.dockerignore create mode 100644 client/Dockerfile create mode 100644 client/nginx.conf create mode 100644 server/.dockerignore create mode 100644 server/Dockerfile create mode 100644 server/src/rotation/rotation.controller.ts create mode 100644 server/src/settings/countries.ts diff --git a/client/.dockerignore b/client/.dockerignore new file mode 100644 index 0000000..92aae6d --- /dev/null +++ b/client/.dockerignore @@ -0,0 +1,7 @@ +node_modules +dist +build +npm-debug.log +.git +.env +Dockerfile \ No newline at end of file diff --git a/client/Dockerfile b/client/Dockerfile new file mode 100644 index 0000000..553f1b5 --- /dev/null +++ b/client/Dockerfile @@ -0,0 +1,21 @@ +FROM node:24-alpine AS builder + +WORKDIR /app + +COPY package*.json ./ +RUN npm ci + +COPY . . + +ENV VITE_API_URL=/api +RUN npm run build + +FROM nginx:alpine + +COPY --from=builder /app/dist /usr/share/nginx/html + +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/client/nginx.conf b/client/nginx.conf new file mode 100644 index 0000000..f224d69 --- /dev/null +++ b/client/nginx.conf @@ -0,0 +1,17 @@ +server { + listen 80; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + + location /api/ { + proxy_pass http://backend:3000/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index f00c714..5b07b1c 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", - "@types/node": "^24.10.1", + "@types/node": "^24.10.9", "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", diff --git a/client/package.json b/client/package.json index c4196d0..6affd8c 100644 --- a/client/package.json +++ b/client/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc -b && vite build", + "build": "vite build", "lint": "eslint .", "preview": "vite preview" }, @@ -23,7 +23,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", - "@types/node": "^24.10.1", + "@types/node": "^24.10.9", "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", diff --git a/client/src/App.tsx b/client/src/App.tsx index 326d4a1..0bbf17f 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import Layout from './components/Layout'; import SubscriptionsPage from './pages/SubscriptionsPage'; diff --git a/client/src/ThemeContext.tsx b/client/src/ThemeContext.tsx index 0e0a1a2..20c6d51 100644 --- a/client/src/ThemeContext.tsx +++ b/client/src/ThemeContext.tsx @@ -2,7 +2,6 @@ import React, { createContext, useState, useMemo, useContext, useEffect } from ' import { ThemeProvider as MuiThemeProvider, createTheme } from '@mui/material'; import CssBaseline from '@mui/material/CssBaseline'; import useMediaQuery from '@mui/material/useMediaQuery'; -// Импортируем нашу настройку import { getDesignTokens } from './theme'; type ColorMode = 'light' | 'dark' | 'system'; @@ -17,7 +16,6 @@ const ThemeContext = createContext({} as ThemeContextType); export const useThemeContext = () => useContext(ThemeContext); export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - // Читаем из localStorage или ставим 'system' const [mode, setMode] = useState(() => { return (localStorage.getItem('themeMode') as ColorMode) || 'system'; }); @@ -36,7 +34,6 @@ export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ childre }); }; - // Вычисляем реальную тему (light/dark) на основе настроек и системы const theme = useMemo(() => { let activeMode: ColorMode; @@ -46,7 +43,6 @@ export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ childre activeMode = mode; } - // ВАЖНО: Используем нашу функцию getDesignTokens const themeOptions = getDesignTokens(activeMode); return createTheme(themeOptions); diff --git a/client/src/api.ts b/client/src/api.ts index 85d7d79..8f7592a 100644 --- a/client/src/api.ts +++ b/client/src/api.ts @@ -1,7 +1,7 @@ import axios from 'axios'; const api = axios.create({ - baseURL: 'http://localhost:3000/api', + baseURL: `${location.protocol}//${location.hostname}:3000/api`, }); export default api; \ No newline at end of file diff --git a/client/src/auth/AuthContext.tsx b/client/src/auth/AuthContext.tsx index 53121bf..e9b16a4 100644 --- a/client/src/auth/AuthContext.tsx +++ b/client/src/auth/AuthContext.tsx @@ -3,34 +3,54 @@ import api from '../api'; interface AuthContextType { token: string | null; + isAuthenticated: boolean; login: (token: string) => void; logout: () => void; - isAuthenticated: boolean; } -const AuthContext = createContext(null!); +const AuthContext = createContext(null); -export const AuthProvider = ({ children }: { children: React.ReactNode }) => { - const [token, setToken] = useState(localStorage.getItem('token')); +export const useAuth = () => { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +}; + +export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [token, setToken] = useState(() => { + const savedToken = localStorage.getItem('token'); + + if (savedToken) { + api.defaults.headers.common['Authorization'] = `Bearer ${savedToken}`; + } + return savedToken; + }); + + const login = (newToken: string) => { + localStorage.setItem('token', newToken); + api.defaults.headers.common['Authorization'] = `Bearer ${newToken}`; + setToken(newToken); + }; + + const logout = () => { + localStorage.removeItem('token'); + delete api.defaults.headers.common['Authorization']; + setToken(null); + }; useEffect(() => { if (token) { - localStorage.setItem('token', token); api.defaults.headers.common['Authorization'] = `Bearer ${token}`; } else { - localStorage.removeItem('token'); delete api.defaults.headers.common['Authorization']; } }, [token]); - const login = (newToken: string) => setToken(newToken); - const logout = () => setToken(null); - return ( {children} ); -}; - -export const useAuth = () => useContext(AuthContext); \ No newline at end of file +}; \ No newline at end of file diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index 683848f..28b5f45 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { AppBar, Toolbar, Typography, IconButton, Tooltip, Box, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Button, List, ListItem, ListItemText @@ -16,10 +16,8 @@ export default function Header() { const { logout } = useAuth(); const navigate = useNavigate(); - // Состояние для модального окна справки const [helpOpen, setHelpOpen] = useState(false); - // Логика выхода const handleLogout = () => { if (confirm('Вы действительно хотите выйти?')) { logout(); @@ -57,21 +55,18 @@ export default function Header() { - {/* Кнопка Справки */} setHelpOpen(true)}> - {/* Кнопка Темы */} {getThemeIcon()} - {/* Кнопка Выхода */} @@ -82,7 +77,6 @@ export default function Header() { - {/* Модальное окно справки */} setHelpOpen(false)} diff --git a/client/src/components/Layout.tsx b/client/src/components/Layout.tsx index b9e0b00..c60aa77 100644 --- a/client/src/components/Layout.tsx +++ b/client/src/components/Layout.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Toolbar, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Box @@ -6,7 +5,7 @@ import { import { People, Settings, Dns, SwapHoriz } from '@mui/icons-material'; import { useNavigate, useLocation, Outlet } from 'react-router-dom'; -import Header from './Header'; // <--- Новый компонент +import Header from './Header'; import Footer from './Footer'; const drawerWidth = 240; @@ -25,10 +24,8 @@ export default function Layout() { return ( - {/* Шапка */}
- {/* Боковое меню */} - {/* Основной контейнер контента */} - {/* Контент страницы */} - {/* Футер */}