feat(infra): переход прод/test на общую инфраструктуру вместо self-hosted
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). Мелкая чистка неиспользуемых импортов во фронтенде.
This commit is contained in:
95
Makefile
95
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"
|
||||
|
||||
18
README.md
18
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
|
||||
|
||||
129
docker-compose.prod.yml
Normal file
129
docker-compose.prod.yml
Normal file
@@ -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
|
||||
@@ -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
|
||||
10
infra/nginx/Dockerfile
Normal file
10
infra/nginx/Dockerfile
Normal file
@@ -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
|
||||
@@ -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/;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { Copy, Check } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { AlertTriangle, CheckCircle, Info } from 'lucide-react';
|
||||
import { clsx } from 'clsx';
|
||||
import type { PlagiarismResultData } from '../types';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import type { TaskStatus } from '../types';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<GostResultData | null>(null);
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { Check } from 'lucide-react';
|
||||
import { clsx } from 'clsx';
|
||||
import { PLAN_LIMITS } from '../types';
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { HardDrive } from 'lucide-react';
|
||||
import { adminApi } from '../../api/client';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
1
services/frontend/src/vite-env.d.ts
vendored
Normal file
1
services/frontend/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
Reference in New Issue
Block a user