From d7e83948981782e84c474f2247f29a89ce1ccb2a Mon Sep 17 00:00:00 2001 From: Vladiysss <139554971+Vladiysss@users.noreply.github.com> Date: Sat, 18 Apr 2026 15:44:05 +0300 Subject: [PATCH] Init --- .env.example | 10 +--- .idea/.name | 1 + Dockerfile | 38 +++++++------- docker-compose.yml | 7 +-- docker/nginx/default.conf.template | 83 ------------------------------ src/KBBoard/index.js | 14 ++--- 6 files changed, 28 insertions(+), 125 deletions(-) create mode 100644 .idea/.name delete mode 100644 docker/nginx/default.conf.template diff --git a/.env.example b/.env.example index 0b647ba..b73fb27 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,3 @@ -# URL бэкенда (без завершающего слэша) -# В Dokploy задаётся через UI → Environment -BACKEND_URL=http://26.22.232.18:24454 - -# Порт, по которому фронтенд доступен на хосте -FRONTEND_PORT=3000 - # Dev-only: используется при локальном `npm start` -REACT_APP_BACKEND_URL=http://26.22.232.18:24454 +# В проде /api маршрутизируется через Traefik в Dokploy +REACT_APP_BACKEND_URL=https://back.fool-stack.ru diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..945ce43 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +index.js \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1fc3e63..e334a7f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,39 +5,41 @@ FROM node:20-alpine AS builder WORKDIR /app -# Устанавливаем зависимости (кэшируется слой) +# Зависимости (кэшируется) COPY package.json package-lock.json ./ -RUN npm ci --no-audit --no-fund +RUN npm install --no-audit --no-fund --legacy-peer-deps -# Копируем исходники +# Исходники COPY public ./public COPY src ./src ENV GENERATE_SOURCEMAP=false \ - CI=true \ + DISABLE_ESLINT_PLUGIN=true \ NODE_OPTIONS=--max_old_space_size=4096 RUN npm run build -# ─── Stage 2: runtime (nginx) ──────────────────────────────────── -FROM nginx:1.27-alpine AS runtime +# ─── Stage 2: runtime — лёгкий статический сервер ──────────────── +FROM node:20-alpine AS runtime -# nginx сам запустит envsubst для шаблона перед стартом -ENV NGINX_ENVSUBST_TEMPLATE_SUFFIX=.template \ - NGINX_ENVSUBST_OUTPUT_DIR=/etc/nginx/conf.d \ - BACKEND_URL=http://backend:8000 +WORKDIR /app -# Убираем дефолтный конфиг, кладём наш шаблон -RUN rm /etc/nginx/conf.d/default.conf -COPY docker/nginx/default.conf.template /etc/nginx/templates/default.conf.template +# `serve` — минималистичный SPA-сервер (~2 МБ), флаг -s = SPA fallback на index.html +RUN npm install -g serve@14.2.4 && \ + addgroup -S app && adduser -S app -G app -# Билд статики -COPY --from=builder /app/build /usr/share/nginx/html +# Только готовый билд +COPY --from=builder --chown=app:app /app/build ./build -EXPOSE 80 +USER app + +ENV NODE_ENV=production \ + PORT=24452 + +EXPOSE 24452 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ - CMD wget -qO- http://localhost/ > /dev/null 2>&1 || exit 1 + CMD wget -qO- http://localhost:24452/ > /dev/null 2>&1 || exit 1 -# Точка входа от nginx-image уже знает про templates +CMD ["serve", "-s", "build", "-l", "24452", "--no-clipboard"] diff --git a/docker-compose.yml b/docker-compose.yml index 5aeaf2e..ea70019 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,13 +6,10 @@ services: image: fool-stack-frontend:latest container_name: fool-stack-frontend restart: unless-stopped - environment: - # URL бэкенда, к которому nginx проксирует /api и /static/avatars - BACKEND_URL: ${BACKEND_URL:-https://back.fool-stack.ru/} expose: - - "80" + - "24452" healthcheck: - test: ["CMD", "wget", "-qO-", "http://localhost/"] + test: ["CMD", "wget", "-qO-", "http://localhost:24452/"] interval: 30s timeout: 5s retries: 3 diff --git a/docker/nginx/default.conf.template b/docker/nginx/default.conf.template deleted file mode 100644 index dd0b7f9..0000000 --- a/docker/nginx/default.conf.template +++ /dev/null @@ -1,83 +0,0 @@ -map $http_upgrade $connection_upgrade { - default upgrade; - '' close; -} - -server { - listen 80; - server_name _; - - root /usr/share/nginx/html; - index index.html; - - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header Referrer-Policy "no-referrer-when-downgrade" always; - add_header X-XSS-Protection "1; mode=block" always; - - # Gzip - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_comp_level 6; - gzip_types - text/plain - text/css - text/javascript - application/javascript - application/json - application/xml - image/svg+xml - font/ttf - font/otf - font/woff - font/woff2; - - # SPA fallback - location / { - try_files $uri $uri/ /index.html; - } - - # Hashed static assets — агрессивный кэш - location /static/ { - expires 1y; - access_log off; - add_header Cache-Control "public, immutable"; - try_files $uri =404; - } - - # HTML не кэшируется (чтобы обновления прилетали сразу) - location = /index.html { - add_header Cache-Control "no-store, no-cache, must-revalidate" always; - expires -1; - } - - # API + WebSocket → backend - location /api/ { - proxy_pass ${BACKEND_URL}; - proxy_http_version 1.1; - - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - - proxy_connect_timeout 10s; - proxy_read_timeout 300s; - proxy_send_timeout 300s; - } - - # Загруженные аватары → backend - location /static/avatars/ { - proxy_pass ${BACKEND_URL}; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - - error_page 404 /index.html; -} diff --git a/src/KBBoard/index.js b/src/KBBoard/index.js index 0bcb7f6..4c3af35 100644 --- a/src/KBBoard/index.js +++ b/src/KBBoard/index.js @@ -83,7 +83,6 @@ const KBBoard = () => { }; - const [socket, setSocket] = useState(null); useEffect(() => { let ws; const connect = async () => { @@ -91,16 +90,9 @@ const KBBoard = () => { const res = await getWsTicketAPI(); const token = res.data.token; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - ws = new WebSocket(`${protocol}//26.22.232.18:24454/api/boards/ws/${id}?token=${token}`); - //ws = new WebSocket(`${protocol}//ws.back.fool-stack.ru/api/boards/ws/${id}?token=${token}`); - ws.onopen = () => { - console.log('WebSocket соединение установлено'); - setSocket(ws); - }; - ws.onmessage = (event) => { - const message = JSON.parse(event.data); - loadBoardData(true); - }; + ws = new WebSocket(`${protocol}//${window.location.host}/api/boards/ws/${id}?token=${token}`); + ws.onopen = () => console.log('WebSocket соединение установлено'); + ws.onmessage = () => loadBoardData(true); ws.onclose = () => console.log('WebSocket соединение закрыто'); ws.onerror = (error) => console.error('Ошибка WebSocket:', error); } catch (e) {