From 673e72a15a63a032fb6fe51da67c4c195564ef40 Mon Sep 17 00:00:00 2001 From: jze9 Date: Mon, 27 Jul 2026 12:22:36 +0500 Subject: [PATCH] =?UTF-8?q?feat(infra):=20=D0=BF=D0=B5=D1=80=D0=B5=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=20=D0=BF=D1=80=D0=BE=D0=B4/test=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=89=D1=83=D1=8E=20=D0=B8=D0=BD=D1=84=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D1=83=20=D0=B2?= =?UTF-8?q?=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20self-hosted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Postgres/Redis/RabbitMQ/MinIO/Ollama теперь общие серверы сети (адреса в .env), локально в докере остаётся только Elasticsearch + app-сервисы. Старый docker-compose.yml (полностью автономный стек) сохранён как docker-compose.selfhosted.yml.example на случай отдельного GPU-сервера в будущем. Добавлен docker-compose.prod.yml (app + свой nginx с TLS, собирающий фронтенд в статику). Makefile переписан под три режима (dev/test-up/prod). Мелкая чистка неиспользуемых импортов во фронтенде. --- Makefile | 95 ++++++++----- README.md | 18 ++- docker-compose.prod.yml | 129 ++++++++++++++++++ ...l => docker-compose.selfhosted.yml.example | 14 +- infra/nginx/Dockerfile | 10 ++ infra/nginx/nginx.conf | 6 + services/frontend/src/components/DropZone.tsx | 2 +- .../frontend/src/components/GostCitation.tsx | 1 - services/frontend/src/components/Layout.tsx | 3 +- .../src/components/PlagiarismReport.tsx | 1 - .../frontend/src/components/SourceCard.tsx | 1 - .../frontend/src/components/StatusBadge.tsx | 1 - services/frontend/src/components/TaskCard.tsx | 1 - services/frontend/src/pages/Bibliography.tsx | 4 +- services/frontend/src/pages/Cabinet.tsx | 1 - services/frontend/src/pages/Home.tsx | 1 - services/frontend/src/pages/Pricing.tsx | 1 - services/frontend/src/pages/Search.tsx | 13 +- services/frontend/src/pages/Task.tsx | 7 +- .../frontend/src/pages/admin/Documents.tsx | 2 +- services/frontend/src/pages/admin/Sources.tsx | 2 +- services/frontend/src/pages/admin/Staging.tsx | 2 +- services/frontend/src/pages/admin/Storage.tsx | 1 - services/frontend/src/pages/admin/Users.tsx | 2 +- services/frontend/src/pages/admin/Works.tsx | 2 +- services/frontend/src/vite-env.d.ts | 1 + 26 files changed, 245 insertions(+), 76 deletions(-) create mode 100644 docker-compose.prod.yml rename docker-compose.yml => docker-compose.selfhosted.yml.example (89%) create mode 100644 infra/nginx/Dockerfile create mode 100644 services/frontend/src/vite-env.d.ts diff --git a/Makefile b/Makefile index d6fc2cc..ca2149d 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,18 @@ -.PHONY: dev build migrate logs shell-api shell-gpu lint test down ps restart clean +.PHONY: dev build migrate logs shell-api shell-gpu lint test down ps restart clean test-up test-down test-logs test-ps # ─── Переменные ──────────────────────────────────────────────────────────────── -COMPOSE = docker compose -COMPOSE_DEV = docker compose -f docker-compose.yml -f docker-compose.dev.yml +# Прод: docker-compose.prod.yml — app-сервисы + локальный ES, остальная инфра +# (postgres/redis/rabbitmq/minio/ollama) уже существует и берётся из .env. +# Dev (полностью локальный sandbox, свои postgres/redis/rabbitmq/minio/ollama + +# hot reload) — поверх self-hosted примера, используется редко. +# Test (то, что реально гоняется на машине разработчика день в день) — hot +# reload против той же общей инфры, что и прод. +COMPOSE_PROD = docker compose -f docker-compose.prod.yml +COMPOSE_DEV = docker compose -f docker-compose.selfhosted.yml.example -f docker-compose.dev.yml +COMPOSE_TEST = docker compose -f docker-compose.test.yml PROJECT = anti-plagiarism -# ─── Разработка ──────────────────────────────────────────────────────────────── +# ─── Разработка (полностью локальный sandbox, редко используется) ───────────── dev: @cp -n .env.example .env 2>/dev/null || true $(COMPOSE_DEV) up --build @@ -14,99 +21,112 @@ dev-d: @cp -n .env.example .env 2>/dev/null || true $(COMPOSE_DEV) up --build -d +# ─── Test (hot reload против общей инфры — повседневная разработка) ────────── +test-up: + $(COMPOSE_TEST) up --build -d + +test-down: + $(COMPOSE_TEST) down + +test-logs: + $(COMPOSE_TEST) logs -f + +test-ps: + $(COMPOSE_TEST) ps + # ─── Продакшн ───────────────────────────────────────────────────────────────── build: - $(COMPOSE) build + $(COMPOSE_PROD) build up: - $(COMPOSE) up -d + $(COMPOSE_PROD) up -d down: - $(COMPOSE) down + $(COMPOSE_PROD) down restart: - $(COMPOSE) restart + $(COMPOSE_PROD) restart ps: - $(COMPOSE) ps + $(COMPOSE_PROD) ps # ─── Миграции ───────────────────────────────────────────────────────────────── migrate: - $(COMPOSE) exec api alembic upgrade head + $(COMPOSE_PROD) exec api alembic upgrade head migrate-dev: $(COMPOSE_DEV) exec api alembic upgrade head makemigration: @read -p "Migration name: " name; \ - $(COMPOSE) exec api alembic revision --autogenerate -m "$$name" + $(COMPOSE_PROD) exec api alembic revision --autogenerate -m "$$name" downgrade: - $(COMPOSE) exec api alembic downgrade -1 + $(COMPOSE_PROD) exec api alembic downgrade -1 # ─── Логи ───────────────────────────────────────────────────────────────────── logs: - $(COMPOSE) logs -f + $(COMPOSE_PROD) logs -f logs-api: - $(COMPOSE) logs -f api + $(COMPOSE_PROD) logs -f api logs-gpu: - $(COMPOSE) logs -f worker-gpu + $(COMPOSE_PROD) logs -f worker-gpu logs-indexer: - $(COMPOSE) logs -f worker-indexer + $(COMPOSE_PROD) logs -f worker-indexer logs-notifier: - $(COMPOSE) logs -f worker-notifier + $(COMPOSE_PROD) logs -f worker-notifier logs-gost: - $(COMPOSE) logs -f worker-gost + $(COMPOSE_PROD) logs -f worker-gost # ─── Шеллы ──────────────────────────────────────────────────────────────────── shell-api: - $(COMPOSE) exec api bash + $(COMPOSE_PROD) exec api bash shell-gpu: - $(COMPOSE) exec worker-gpu bash + $(COMPOSE_PROD) exec worker-gpu bash shell-indexer: - $(COMPOSE) exec worker-indexer bash + $(COMPOSE_PROD) exec worker-indexer bash shell-db: - $(COMPOSE) exec postgres psql -U antiplagiator antiplagiator + @echo "Postgres общий (192.168.1.11) — подключайся напрямую: psql -h 192.168.1.11 -U antiplagiator antiplagiator" shell-redis: - $(COMPOSE) exec redis redis-cli + @echo "Redis общий (192.168.1.19) — подключайся напрямую: redis-cli -h 192.168.1.19 -n 3" # ─── Линтинг и тесты ────────────────────────────────────────────────────────── lint: - $(COMPOSE) exec api ruff check . --fix - $(COMPOSE) exec api mypy app/ + $(COMPOSE_PROD) exec api ruff check . --fix + $(COMPOSE_PROD) exec api mypy app/ lint-frontend: cd services/frontend && npm run lint test: - $(COMPOSE) exec api pytest tests/ -v + $(COMPOSE_PROD) exec api pytest tests/ -v test-cov: - $(COMPOSE) exec api pytest tests/ -v --cov=app --cov-report=html + $(COMPOSE_PROD) exec api pytest tests/ -v --cov=app --cov-report=html # ─── Утилиты ────────────────────────────────────────────────────────────────── +# ВНИМАНИЕ: -v удаляет только ЛОКАЛЬНЫЕ volume (es_prod_data, faiss_index_prod) — +# общие postgres/redis/rabbitmq/minio не в Compose, их не затронет. clean: - $(COMPOSE) down -v --remove-orphans + $(COMPOSE_PROD) down -v --remove-orphans docker system prune -f # Запустить парсер (пример: make parse PARSER=openalex ARGS="--limit 1000") parse: - $(COMPOSE) exec api python /scripts/run_parser.py $(PARSER) $(ARGS) + $(COMPOSE_PROD) exec api python /scripts/run_parser.py $(PARSER) $(ARGS) -# Создать MinIO бакеты +# Создать MinIO бакеты (запускать один раз на общем MinIO 192.168.1.21, не локально) minio-init: - $(COMPOSE) exec minio mc alias set local http://localhost:9000 minioadmin changeme - $(COMPOSE) exec minio mc mb local/documents || true - $(COMPOSE) exec minio mc mb local/backups || true + @echo "MinIO общий (192.168.1.21) — бакеты documents/backups создаются на нём, см. .env MINIO_*" # Создать ES индекс es-init: @@ -115,13 +135,14 @@ es-init: # Статистика Celery flower: @echo "Flower доступен по адресу http://localhost:5555" - @$(COMPOSE) ps flower + @$(COMPOSE_PROD) ps flower help: @echo "Академический помощник — команды make:" @echo "" - @echo " make dev — запустить в режиме разработки (hot reload)" - @echo " make build — собрать Docker образы" + @echo " make test-up — запустить тестовый стек (hot reload, общая инфра)" + @echo " make dev — запустить полностью локальный sandbox (редко нужно)" + @echo " make build — собрать прод-образы" @echo " make up — запустить в продакшн режиме" @echo " make down — остановить все сервисы" @echo " make migrate — применить миграции Alembic" @@ -129,4 +150,4 @@ help: @echo " make shell-api — открыть shell в контейнере api" @echo " make lint — запустить линтер" @echo " make test — запустить тесты" - @echo " make clean — удалить все контейнеры и volumes" + @echo " make clean — удалить прод-контейнеры и ЛОКАЛЬНЫЕ volume" diff --git a/README.md b/README.md index ea36b12..b5c8ed6 100644 --- a/README.md +++ b/README.md @@ -134,21 +134,31 @@ python scripts/run_parser.py arxiv \ Смотри `.env.example` для полного списка переменных. Обязательно смените `SECRET_KEY`, `POSTGRES_PASSWORD`, `MINIO_SECRET_KEY`. +## Три docker-compose файла + +| Файл | Назначение | +|------|-----------| +| `docker-compose.prod.yml` | **Реальный прод.** app-сервисы + nginx (собранный фронтенд + TLS) + локальный Elasticsearch. Postgres/Redis/RabbitMQ/MinIO/Ollama — уже существующие общие серверы, адреса в `.env`. | +| `docker-compose.test.yml` | Повседневная разработка — hot reload против той же общей инфры, что и прод (`make test-up`). | +| `docker-compose.selfhosted.yml.example` | Не используется. Полностью автономный вариант (свои Postgres/Redis/RabbitMQ/ES/MinIO/Ollama + GPU passthrough) — на случай отдельного выделенного сервера в будущем. | + ## Продакшн деплой ```bash # На сервере cp .env.example .env -nano .env # Настроить все пароли и ключи +nano .env # Настроить все пароли и ключи, указать реальные адреса общих Postgres/Redis/RabbitMQ/MinIO/Ollama + +# Сертификат ДО первого запуска nginx-контейнера (standalone, порт 80 должен быть свободен) +certbot certonly --standalone -d academic.jze9.ru make build make up make migrate - -# SSL сертификат (Let's Encrypt) -certbot --nginx -d academic.jze9.ru ``` +Nginx работает внутри контейнера (`docker-compose.prod.yml`, сервис `nginx`, Dockerfile в `infra/nginx/Dockerfile`) — собирает `services/frontend` в статику и отдаёт её вместе с проксированием `/api/`, `/ws/` на `api:8000` по конфигу `infra/nginx/nginx.conf`. Контейнер монтирует `/etc/letsencrypt` с хоста как read-only — сертификат обновляется на хосте (`certbot renew`), контейнер просто читает его. + ## Лицензия MIT diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..9bb00d6 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,129 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# ПРОД стек: локально — nginx (static+proxy), api, воркеры, elasticsearch. +# Удалённо (через .env) — postgres, redis, minio, ollama, rabbitmq. +# Тот же принцип, что и docker-compose.test.yml (не дублировать общую инфру), +# но без bind-mount'ов исходников, без --reload, с собранным фронтендом и nginx/TLS. +# Запуск: docker compose -f docker-compose.prod.yml up -d --build +# ═══════════════════════════════════════════════════════════════════════════════ + +networks: + antiplagiator: + driver: bridge + +volumes: + es_prod_data: + faiss_index_prod: + +x-app-env: &app-env + env_file: .env + networks: + - antiplagiator + restart: unless-stopped + +services: + + # ─── Локальная инфраструктура ────────────────────────────────────────────── + elasticsearch: + image: elasticsearch:8.13.0 + container_name: antiplagiator-elasticsearch + environment: + - discovery.type=single-node + - xpack.security.enabled=false + - xpack.ml.enabled=false + - ES_JAVA_OPTS=-Xms2g -Xmx2g + - bootstrap.memory_lock=true + - cluster.name=antiplagiator-prod + ulimits: + memlock: + soft: -1 + hard: -1 + volumes: + - es_prod_data:/usr/share/elasticsearch/data + networks: + - antiplagiator + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -qv '\"status\":\"red\"'"] + interval: 15s + timeout: 10s + retries: 10 + start_period: 60s + + # ─── Приложение ──────────────────────────────────────────────────────────── + # RabbitMQ вынесен на отдельный сервер 192.168.20.82 (см. RABBITMQ_URL в .env) + api: + build: + context: ./services/api + dockerfile: Dockerfile + container_name: antiplagiator-api + <<: *app-env + depends_on: + elasticsearch: + condition: service_healthy + + worker-gpu: + build: + context: ./services/worker-gpu + dockerfile: Dockerfile + container_name: antiplagiator-worker-gpu + <<: *app-env + volumes: + - faiss_index_prod:/data/index + depends_on: + elasticsearch: + condition: service_healthy + + worker-indexer: + build: + context: ./services/worker-indexer + dockerfile: Dockerfile + container_name: antiplagiator-worker-indexer + <<: *app-env + volumes: + - ./scripts/parsers:/parsers:ro + depends_on: + elasticsearch: + condition: service_healthy + + worker-notifier: + build: + context: ./services/worker-notifier + dockerfile: Dockerfile + container_name: antiplagiator-worker-notifier + <<: *app-env + + worker-gost: + build: + context: ./services/worker-gost + dockerfile: Dockerfile + container_name: antiplagiator-worker-gost + <<: *app-env + + flower: + image: mher/flower:2.0 + container_name: antiplagiator-flower + command: celery --broker=${RABBITMQ_URL} flower --port=5555 + environment: + CELERY_BROKER_URL: ${RABBITMQ_URL} + CELERY_RESULT_BACKEND: ${REDIS_URL} + networks: + - antiplagiator + restart: unless-stopped + # Flower не публикуется наружу напрямую — доступ через ssh-туннель при необходимости + + # ─── Nginx: собранный фронтенд (static) + proxy /api, /ws → api:8000, TLS ── + nginx: + build: + context: . + dockerfile: infra/nginx/Dockerfile + container_name: antiplagiator-nginx + networks: + - antiplagiator + ports: + - "80:80" + - "443:443" + volumes: + - /etc/letsencrypt:/etc/letsencrypt:ro + depends_on: + - api + restart: unless-stopped diff --git a/docker-compose.yml b/docker-compose.selfhosted.yml.example similarity index 89% rename from docker-compose.yml rename to docker-compose.selfhosted.yml.example index d94a57c..1cdf17e 100644 --- a/docker-compose.yml +++ b/docker-compose.selfhosted.yml.example @@ -1,5 +1,13 @@ # ═══════════════════════════════════════════════════════════════════════════════ -# Академический помощник — Production Docker Compose +# Академический помощник — self-hosted вариант (свои Postgres/Redis/RabbitMQ/ +# ES/MinIO/Ollama + GPU passthrough для ollama/worker-gpu). +# +# НЕ используется сейчас — реальный прод разворачивается через +# docker-compose.prod.yml поверх уже существующей общей инфры (Postgres .11, +# Redis .19, RabbitMQ .82, MinIO .21, Ollama CT108). Держим этот файл на случай, +# если когда-нибудь появится отдельный GPU-сервер под этот проект — тогда +# у worker-indexer нужно будет ещё добавить volume ./scripts/parsers:/parsers:ro +# (в этом файле его сейчас нет, в отличие от prod/test). # ═══════════════════════════════════════════════════════════════════════════════ networks: @@ -13,7 +21,7 @@ volumes: elasticsearch_data: minio_data: ollama_data: - faiss_index: + faiss_index_selfhosted: # ─── Общие переменные окружения для app-сервисов ──────────────────────────── x-app-env: &app-env @@ -177,7 +185,7 @@ services: <<: *app-env command: celery -A app.celery_app worker -Q queue.gpu -c 1 -n gpu@%h --loglevel=info volumes: - - faiss_index:/data/index + - faiss_index_selfhosted:/data/index depends_on: postgres: condition: service_healthy diff --git a/infra/nginx/Dockerfile b/infra/nginx/Dockerfile new file mode 100644 index 0000000..91c8c53 --- /dev/null +++ b/infra/nginx/Dockerfile @@ -0,0 +1,10 @@ +FROM node:20-slim AS build +WORKDIR /app +COPY services/frontend/package.json services/frontend/package-lock.json* ./ +RUN npm install +COPY services/frontend/ . +RUN npm run build + +FROM nginx:1.27-alpine +COPY --from=build /app/dist /var/www/academic +COPY infra/nginx/nginx.conf /etc/nginx/nginx.conf diff --git a/infra/nginx/nginx.conf b/infra/nginx/nginx.conf index 906e216..9439925 100644 --- a/infra/nginx/nginx.conf +++ b/infra/nginx/nginx.conf @@ -72,6 +72,12 @@ http { add_header X-Frame-Options DENY; add_header X-XSS-Protection "1; mode=block"; + # ── Health check (не под /api/ в самом приложении) ───────────────────── + location = /health { + proxy_pass http://api/health; + proxy_set_header Host $host; + } + # ── API proxy ────────────────────────────────────────────────────────── location /api/ { proxy_pass http://api/api/; diff --git a/services/frontend/src/components/DropZone.tsx b/services/frontend/src/components/DropZone.tsx index 5ef36c3..acb6981 100644 --- a/services/frontend/src/components/DropZone.tsx +++ b/services/frontend/src/components/DropZone.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import { useCallback } from 'react'; import { useDropzone } from 'react-dropzone'; import { Upload, FileText, X } from 'lucide-react'; import { clsx } from 'clsx'; diff --git a/services/frontend/src/components/GostCitation.tsx b/services/frontend/src/components/GostCitation.tsx index 2a87c94..f3ad86b 100644 --- a/services/frontend/src/components/GostCitation.tsx +++ b/services/frontend/src/components/GostCitation.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Copy, Check } from 'lucide-react'; import { useState } from 'react'; diff --git a/services/frontend/src/components/Layout.tsx b/services/frontend/src/components/Layout.tsx index 8696a74..90f0c43 100644 --- a/services/frontend/src/components/Layout.tsx +++ b/services/frontend/src/components/Layout.tsx @@ -1,6 +1,5 @@ -import React from 'react'; import { Link, NavLink, useNavigate } from 'react-router-dom'; -import { GraduationCap, Search, BookOpen, Upload, LayoutDashboard, LogOut, User, BookMarked } from 'lucide-react'; +import { GraduationCap, Search, BookOpen, Upload, LayoutDashboard, LogOut } from 'lucide-react'; import { clsx } from 'clsx'; import { useAuthStore } from '../store/auth'; import { EmailVerificationModal } from './EmailVerificationModal'; diff --git a/services/frontend/src/components/PlagiarismReport.tsx b/services/frontend/src/components/PlagiarismReport.tsx index 1b86750..4d26275 100644 --- a/services/frontend/src/components/PlagiarismReport.tsx +++ b/services/frontend/src/components/PlagiarismReport.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { AlertTriangle, CheckCircle, Info } from 'lucide-react'; import { clsx } from 'clsx'; import type { PlagiarismResultData } from '../types'; diff --git a/services/frontend/src/components/SourceCard.tsx b/services/frontend/src/components/SourceCard.tsx index 584da75..011d4d7 100644 --- a/services/frontend/src/components/SourceCard.tsx +++ b/services/frontend/src/components/SourceCard.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { ExternalLink, BookmarkPlus, BookmarkCheck, Copy } from 'lucide-react'; import { clsx } from 'clsx'; import toast from 'react-hot-toast'; diff --git a/services/frontend/src/components/StatusBadge.tsx b/services/frontend/src/components/StatusBadge.tsx index 8c83f2d..d9eb02c 100644 --- a/services/frontend/src/components/StatusBadge.tsx +++ b/services/frontend/src/components/StatusBadge.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { clsx } from 'clsx'; import type { TaskStatus } from '../types'; diff --git a/services/frontend/src/components/TaskCard.tsx b/services/frontend/src/components/TaskCard.tsx index d3854c3..d28ff69 100644 --- a/services/frontend/src/components/TaskCard.tsx +++ b/services/frontend/src/components/TaskCard.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Link } from 'react-router-dom'; import { formatDistanceToNow } from 'date-fns'; import { ru } from 'date-fns/locale'; diff --git a/services/frontend/src/pages/Bibliography.tsx b/services/frontend/src/pages/Bibliography.tsx index 20b0064..0c6ac17 100644 --- a/services/frontend/src/pages/Bibliography.tsx +++ b/services/frontend/src/pages/Bibliography.tsx @@ -1,16 +1,14 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { useMutation } from '@tanstack/react-query'; import { BookOpen, Copy, Trash2, BookMarked } from 'lucide-react'; import toast from 'react-hot-toast'; import { GostCitation } from '../components/GostCitation'; import { useBibliographyStore } from '../store/bibliography'; -import { useAuthStore } from '../store/auth'; import { api } from '../api/client'; import type { GostResultData } from '../types'; export function Bibliography() { const { sources, removeSource, clearSources } = useBibliographyStore(); - const { isAuthenticated } = useAuthStore(); const [style, setStyle] = useState<'7.1' | '7.0.5'>('7.1'); const [formattedResult, setFormattedResult] = useState(null); diff --git a/services/frontend/src/pages/Cabinet.tsx b/services/frontend/src/pages/Cabinet.tsx index c6938af..6ad3fc6 100644 --- a/services/frontend/src/pages/Cabinet.tsx +++ b/services/frontend/src/pages/Cabinet.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Navigate, useNavigate } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { Crown, Search, Shield, BookOpen, ShieldAlert } from 'lucide-react'; diff --git a/services/frontend/src/pages/Home.tsx b/services/frontend/src/pages/Home.tsx index 5ace1a7..14bf5ba 100644 --- a/services/frontend/src/pages/Home.tsx +++ b/services/frontend/src/pages/Home.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { useNavigate } from 'react-router-dom'; import { Search, Shield, BookOpen, Zap } from 'lucide-react'; import { SearchBar } from '../components/SearchBar'; diff --git a/services/frontend/src/pages/Pricing.tsx b/services/frontend/src/pages/Pricing.tsx index 1450e7e..aad2214 100644 --- a/services/frontend/src/pages/Pricing.tsx +++ b/services/frontend/src/pages/Pricing.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Check } from 'lucide-react'; import { clsx } from 'clsx'; import { PLAN_LIMITS } from '../types'; diff --git a/services/frontend/src/pages/Search.tsx b/services/frontend/src/pages/Search.tsx index 3c479b1..ed04230 100644 --- a/services/frontend/src/pages/Search.tsx +++ b/services/frontend/src/pages/Search.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { useQuery, useMutation } from '@tanstack/react-query'; import { Filter, Loader2 } from 'lucide-react'; @@ -41,16 +41,13 @@ export function Search() { }); // Поллинг задачи - const { - data: task, - isLoading: isPolling, - } = useQuery({ + const { data: task } = useQuery({ queryKey: ['task', taskId], queryFn: () => tasksApi.get(taskId!).then((r) => r.data as Task), enabled: !!taskId, - refetchInterval: (data) => { - if (!data) return 3000; - if (data.status === 'done' || data.status === 'failed') return false; + refetchInterval: (query) => { + const status = query.state.data?.status; + if (status === 'done' || status === 'failed') return false; return 3000; }, }); diff --git a/services/frontend/src/pages/Task.tsx b/services/frontend/src/pages/Task.tsx index b11de26..9bee3f2 100644 --- a/services/frontend/src/pages/Task.tsx +++ b/services/frontend/src/pages/Task.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { useParams, Navigate } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { Loader2, ArrowLeft } from 'lucide-react'; @@ -22,9 +21,9 @@ export function Task() { const { data: task, isLoading } = useQuery({ queryKey: ['task', taskId], queryFn: () => tasksApi.get(taskId).then((r) => r.data as TaskType), - refetchInterval: (data) => { - if (!data) return 3000; - return (data.status === 'done' || data.status === 'failed') ? false : 3000; + refetchInterval: (query) => { + const status = query.state.data?.status; + return (status === 'done' || status === 'failed') ? false : 3000; }, }); diff --git a/services/frontend/src/pages/admin/Documents.tsx b/services/frontend/src/pages/admin/Documents.tsx index 7aa1ef4..644fb2a 100644 --- a/services/frontend/src/pages/admin/Documents.tsx +++ b/services/frontend/src/pages/admin/Documents.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Trash2, Search } from 'lucide-react'; import toast from 'react-hot-toast'; diff --git a/services/frontend/src/pages/admin/Sources.tsx b/services/frontend/src/pages/admin/Sources.tsx index 39c1de0..66db6e2 100644 --- a/services/frontend/src/pages/admin/Sources.tsx +++ b/services/frontend/src/pages/admin/Sources.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Play, Trash2, Plus } from 'lucide-react'; import toast from 'react-hot-toast'; diff --git a/services/frontend/src/pages/admin/Staging.tsx b/services/frontend/src/pages/admin/Staging.tsx index 744891f..12fb861 100644 --- a/services/frontend/src/pages/admin/Staging.tsx +++ b/services/frontend/src/pages/admin/Staging.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Check, X, Eye } from 'lucide-react'; import toast from 'react-hot-toast'; diff --git a/services/frontend/src/pages/admin/Storage.tsx b/services/frontend/src/pages/admin/Storage.tsx index aef4ba3..9b9464f 100644 --- a/services/frontend/src/pages/admin/Storage.tsx +++ b/services/frontend/src/pages/admin/Storage.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { useQuery } from '@tanstack/react-query'; import { HardDrive } from 'lucide-react'; import { adminApi } from '../../api/client'; diff --git a/services/frontend/src/pages/admin/Users.tsx b/services/frontend/src/pages/admin/Users.tsx index b7b797e..5cf0a28 100644 --- a/services/frontend/src/pages/admin/Users.tsx +++ b/services/frontend/src/pages/admin/Users.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Trash2, Search } from 'lucide-react'; import toast from 'react-hot-toast'; diff --git a/services/frontend/src/pages/admin/Works.tsx b/services/frontend/src/pages/admin/Works.tsx index 9d7231b..8e32332 100644 --- a/services/frontend/src/pages/admin/Works.tsx +++ b/services/frontend/src/pages/admin/Works.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { RefreshCw, Trash2 } from 'lucide-react'; import toast from 'react-hot-toast'; diff --git a/services/frontend/src/vite-env.d.ts b/services/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/services/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +///