mirror of
https://git.oceanpay.cc/danial/kami_apple_exchage.git
synced 2025-12-18 22:29:09 +00:00
docs(项目): 添加项目文档并进行代码调整
- 新增 CODEBUDDY.md、GEMINI.md、GEMINI_CN.md 等项目文档 - 更新 Dockerfile 和其他配置文件 - 优化部分代码结构,如 orders.py、tasks.py 等 - 新增 .dockerignore 文件
This commit is contained in:
@@ -13,7 +13,9 @@
|
||||
"Bash(docker run:*)",
|
||||
"Bash(curl:*)",
|
||||
"Bash(docker logs:*)",
|
||||
"Bash(docker rm:*)"
|
||||
"Bash(docker rm:*)",
|
||||
"Bash(docker:*)",
|
||||
"Bash(openssl:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
170
.dockerignore
Normal file
170
.dockerignore
Normal file
@@ -0,0 +1,170 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
docker-compose*.yml
|
||||
.dockerignore
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
.venv/
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
.tox/
|
||||
htmlcov/
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
uv.lock
|
||||
poetry.lock
|
||||
Pipfile.lock
|
||||
pdm.lock
|
||||
requirements*.txt
|
||||
setup.py
|
||||
setup.cfg
|
||||
pyproject.toml
|
||||
*.egg-info/
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
*.rdb
|
||||
test-results/
|
||||
playwright-report/
|
||||
playwright/.cache/
|
||||
.uv/
|
||||
|
||||
# Node.js
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error-log*
|
||||
.next/
|
||||
out/
|
||||
dist/
|
||||
build/
|
||||
*.tsbuildinfo
|
||||
next-env-d.ts
|
||||
.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
.pnpm-debug.log*
|
||||
.vercel
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
*.code-workspace
|
||||
.history/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
*.stackdump
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*~
|
||||
.fuse_hidden*
|
||||
.directory
|
||||
.Trash-*
|
||||
.nfs*
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
log/
|
||||
|
||||
# Environment variables
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
*.tmp
|
||||
*.temp
|
||||
cache/
|
||||
.cache/
|
||||
|
||||
# Data directories
|
||||
data/
|
||||
uploads/
|
||||
downloads/
|
||||
apple_data/
|
||||
exchange_logs/
|
||||
transaction_logs/
|
||||
user_uploads/
|
||||
|
||||
# Database files
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# SSL certificates
|
||||
*.pem
|
||||
*.key
|
||||
*.crt
|
||||
*.cert
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
*.backup
|
||||
*.old
|
||||
*.orig
|
||||
|
||||
# Compressed files
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
*.7z
|
||||
|
||||
# Documentation
|
||||
*.md
|
||||
docs/
|
||||
README*
|
||||
|
||||
# Testing and coverage
|
||||
coverage/
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
|
||||
# Claude Code
|
||||
.claude/
|
||||
|
||||
# Kubernetes
|
||||
*.kubeconfig
|
||||
.kube/
|
||||
|
||||
# Terraform
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
.terraform/
|
||||
.terraform.lock.hcl
|
||||
|
||||
# Ansible
|
||||
*.retry
|
||||
|
||||
# Monitoring
|
||||
*.prof
|
||||
*.pprof
|
||||
*.trace
|
||||
48
CODEBUDDY.md
Normal file
48
CODEBUDDY.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# CODEBUDDY.md
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Frontend (Next.js)
|
||||
- `cd frontend && bun install` - Install dependencies
|
||||
- `cd frontend && bun run dev` - Start development server
|
||||
- `cd frontend && bun run build` - Build production version
|
||||
- `cd frontend && bun run lint` - Run ESLint checks
|
||||
- `cd frontend && bun run test` - Run tests
|
||||
|
||||
### Backend (FastAPI)
|
||||
- `cd backend && uv sync` - Install dependencies
|
||||
- `cd backend && uv run python app/main.py` - Run development server
|
||||
- `cd backend && uv run pytest` - Run tests
|
||||
- `cd backend && uv run black .` - Format code with Black
|
||||
- `cd backend && uv run ruff check .` - Lint with Ruff
|
||||
|
||||
### Docker Development
|
||||
- `cd backend && docker-compose up -d` - Start all services
|
||||
- `cd backend && docker-compose down` - Stop all services
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Project Structure
|
||||
- **Frontend**: Next.js 15 with App Router, React 18, TypeScript
|
||||
- **Backend**: FastAPI with Python 3.13, async/await architecture
|
||||
- **Infrastructure**: Docker, Redis, PostgreSQL
|
||||
|
||||
### Key Directories
|
||||
- **Frontend**: `frontend/src/app/` (Next.js pages), `frontend/src/components/` (UI components)
|
||||
- **Backend**: `backend/app/api/` (FastAPI routes), `backend/app/core/` (core functionality), `backend/app/services/` (business logic)
|
||||
|
||||
### Core Features
|
||||
- **Frontend**: Real-time dashboard, Apple-style UI, theme switching
|
||||
- **Backend**: Distributed web scraping, Redis-based locking, Celery task queue
|
||||
|
||||
### API Integration
|
||||
- Backend OpenAPI spec at `http://localhost:8000/openapi.json`
|
||||
- Frontend auto-generates API client code
|
||||
|
||||
### State Management
|
||||
- **Frontend**: React Context + TanStack Query
|
||||
- **Backend**: PostgreSQL + Redis
|
||||
|
||||
### Deployment
|
||||
- **Development**: Docker Compose for services, Bun/UV for apps
|
||||
- **Production**: Docker containers with Nginx, Kubernetes support
|
||||
100
DRAFT_SECURITY_REPORT.md
Normal file
100
DRAFT_SECURITY_REPORT.md
Normal file
@@ -0,0 +1,100 @@
|
||||
## Vulnerability Report
|
||||
|
||||
### 1. Container Runs as Root
|
||||
|
||||
* **Vulnerability:** The Docker container runs as the `root` user.
|
||||
* **Severity:** High
|
||||
* **Location:** `backend/Dockerfile`
|
||||
* **Line Content:** Not applicable (absence of `USER` instruction).
|
||||
* **Description:** Running a container as `root` is a security risk. If an attacker gains control of the application, they will have root privileges inside the container, which can be used to escalate privileges further.
|
||||
* **Recommendation:** Create a non-root user and switch to it using the `USER` instruction in the Dockerfile.
|
||||
|
||||
### 2. Hardcoded Database Credentials
|
||||
|
||||
* **Vulnerability:** The `DATABASE_URL` in the `.env.production` file contains a hardcoded password.
|
||||
* **Severity:** Critical
|
||||
* **Location:** `backend/.env.production`
|
||||
* **Line Content:** `DATABASE_URL="postgresql+asyncpg://postgres:password@db:5432/apple_exchange"`
|
||||
* **Description:** The production database credentials are hardcoded in the `.env.production` file, which is then copied into the Docker image. This makes it easy for an attacker to gain access to the database if they can read the contents of the image.
|
||||
* **Recommendation:** Use a secrets management solution to inject the database password into the container at runtime. The `DATABASE_URL` should be constructed using an environment variable for the password, similar to how `JWT_SECRET_KEY` and `ENCRYPTION_KEY` are handled.
|
||||
|
||||
### 3. Test Container Runs as Root
|
||||
|
||||
* **Vulnerability:** The test Docker container runs as the `root` user.
|
||||
* **Severity:** High
|
||||
* **Location:** `backend/Dockerfile.test`
|
||||
* **Line Content:** Not applicable (absence of `USER` instruction).
|
||||
* **Description:** Running a container as `root` is a security risk, even in a test environment. If an attacker gains control of the application, they will have root privileges inside the container, which can be used to escalate privileges further.
|
||||
* **Recommendation:** Create a non-root user and switch to it using the `USER` instruction in the Dockerfile.
|
||||
|
||||
### 4. Worker Container Runs as Root
|
||||
|
||||
* **Vulnerability:** The worker Docker container runs as the `root` user.
|
||||
* **Severity:** High
|
||||
* **Location:** `backend/Dockerfile.worker`
|
||||
* **Line Content:** Not applicable (absence of `USER` instruction).
|
||||
* **Description:** Running a container as `root` is a security risk. If an attacker gains control of the application, they will have root privileges inside the container, which can be used to escalate privileges further.
|
||||
* **Recommendation:** Create a non-root user and switch to it using the `USER` instruction in the Dockerfile.
|
||||
|
||||
### 5. Potential IDOR in `get_order` endpoint
|
||||
|
||||
* **Vulnerability:** Potential Insecure Direct Object Reference (IDOR) in the `get_order` endpoint.
|
||||
* **Severity:** High
|
||||
* **Location:** `backend/app/api/v1/orders.py`
|
||||
* **Line Content:** `async def get_order(order_id: str, db: AsyncSession = Depends(get_async_db)) -> OrderDetailResponse:`
|
||||
* **Description:** The `get_order` endpoint takes an `order_id` from the URL and retrieves the order details. There are no checks to verify that the authenticated user has permission to access the requested order. An attacker could potentially enumerate order IDs to view orders belonging to other users.
|
||||
* **Recommendation:** Implement an access control check to ensure that the current user is authorized to view the requested order. This typically involves checking if the `order.user_id` matches the `current_user.id`.
|
||||
|
||||
### 6. Missing Access Control on Task Management Endpoints
|
||||
|
||||
* **Vulnerability:** Missing access control on task management endpoints.
|
||||
* **Severity:** High
|
||||
* **Location:** `backend/app/api/v1/tasks.py`
|
||||
* **Line Content:** `async def toggle_task_state(request: TaskControlRequest) -> TaskControlResponse:`
|
||||
* **Description:** The endpoints in `tasks.py` for controlling the task queue (`/toggle`, `/status`, etc.) do not have any authorization checks. This means that any user, including unauthenticated users, could potentially pause, resume, or otherwise interfere with the task queue, leading to a denial of service.
|
||||
* **Recommendation:** Implement a robust access control mechanism for all task management endpoints. Only authorized users (e.g., administrators) should be able to perform these actions. This can be achieved using FastAPI's dependency injection system to require an authenticated user with the necessary permissions.
|
||||
|
||||
### 7. Potential IDOR in `user_data` endpoints
|
||||
|
||||
* **Vulnerability:** Potential Insecure Direct Object Reference (IDOR) in the `user_data` endpoints.
|
||||
* **Severity:** High
|
||||
* **Location:** `backend/app/api/v1/user_data.py`
|
||||
* **Line Content:** `async def get_user_data(user_id: str, service: UserDataService = Depends(get_user_data_service)):` and `async def delete_user_data(user_id: str, service: UserDataService = Depends(get_user_data_service)):`
|
||||
* **Description:** The `get_user_data` and `delete_user_data` endpoints take a `user_id` from the URL and retrieve or delete the user's data. There are no checks to verify that the authenticated user has permission to access or delete the requested user's data. An attacker could potentially enumerate user IDs to view or delete user data belonging to other users.
|
||||
* **Recommendation:** Implement an access control check to ensure that the current user is authorized to view or delete the requested user data. This typically involves checking if the `user_id` matches the `current_user.id` or if the current user has administrative privileges.
|
||||
|
||||
### 8. Potential IDOR in `get_task_status` endpoint
|
||||
|
||||
* **Vulnerability:** Potential Insecure Direct Object Reference (IDOR) in the `get_task_status` endpoint.
|
||||
* **Severity:** Medium
|
||||
* **Location:** `backend/app/api/v1/user_data.py`
|
||||
* **Line Content:** `async def get_task_status(task_id: str, service: UserDataService = Depends(get_user_data_service)):`
|
||||
* **Description:** The `get_task_status` endpoint takes a `task_id` from the URL and retrieves the task status. There are no checks to verify that the authenticated user has permission to access the requested task status. An attacker could potentially enumerate task IDs to view the status of tasks belonging to other users.
|
||||
* **Recommendation:** Implement an access control check to ensure that the current user is authorized to view the requested task status. This could involve associating tasks with users and checking for ownership.
|
||||
|
||||
### 9. Permissive CORS and `ALLOWED_HOSTS` Configuration
|
||||
|
||||
* **Vulnerability:** The `ALLOWED_HOSTS` and `CORS_ORIGINS` are set to `["*"]` by default.
|
||||
* **Severity:** Medium
|
||||
* **Location:** `backend/app/core/config.py`
|
||||
* **Line Content:** `ALLOWED_HOSTS: list[str] = Field(default=["*"], description="允许的主机列表")` and `CORS_ORIGINS: list[str] = Field(default=["*"], description="CORS允许的源列表")`
|
||||
* **Description:** The `ALLOWED_HOSTS` and `CORS_ORIGINS` settings are configured to allow any host. This is a permissive setting that could be a security risk in a production environment. A permissive CORS policy can make the application vulnerable to cross-site request forgery (CSRF) attacks, and a permissive `ALLOWED_HOSTS` setting can make the application vulnerable to host header attacks.
|
||||
* **Recommendation:** In a production environment, `ALLOWED_HOSTS` and `CORS_ORIGINS` should be set to a specific list of trusted domains.
|
||||
|
||||
### 10. Use of `KEYS` command in Redis
|
||||
|
||||
* **Vulnerability:** The `list_states` method uses the `KEYS` command to get all keys matching a pattern.
|
||||
* **Severity:** Low
|
||||
* **Location:** `backend/app/core/state_manager.py`
|
||||
* **Line Content:** `keys = await redis_client.keys(search_pattern)`
|
||||
* **Description:** The `KEYS` command can block the Redis server while it is executing, and can cause performance issues on a Redis instance with a large number of keys. This could be a potential denial of service vector.
|
||||
* **Recommendation:** Use the `SCAN` command instead of `KEYS` to iterate over the keys in a non-blocking way.
|
||||
|
||||
### 11. Storage of PII in Plain Text
|
||||
|
||||
* **Vulnerability:** Personally Identifiable Information (PII) is stored in plain text in the database.
|
||||
* **Severity:** Medium
|
||||
* **Location:** `backend/app/models/user_data.py`
|
||||
* **Line Content:** Not applicable (model definition).
|
||||
* **Description:** The `UserData` model contains sensitive user information such as name, address, email, and phone number. This data is stored in the database without any encryption at the application level. If the database is compromised, all of this sensitive user data will be exposed in plain text.
|
||||
* **Recommendation:** Encrypt sensitive PII in the database. This can be done at the application level using a library like `sqlalchemy-utils` with the `EncryptedType` or by using database-level encryption features.
|
||||
88
GEMINI.md
Normal file
88
GEMINI.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Gemini Project Context: Apple Gift Card Exchange Platform
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a full-stack application for an Apple gift card exchange platform. It features a modern, scalable architecture with a real-time monitoring dashboard and an Apple-style user interface.
|
||||
|
||||
* **Frontend:** A Next.js application written in TypeScript, using Tailwind CSS for styling and Radix UI for components. It communicates with the backend via a REST API.
|
||||
* **Backend:** A Python-based microservices architecture using FastAPI. It leverages Celery for asynchronous task processing, Playwright for browser automation, and PostgreSQL for the database.
|
||||
* **Architecture:** The system is designed as a distributed system with a frontend, a backend API, and a pool of Celery workers. It uses Redis for caching and as a message broker.
|
||||
|
||||
## Building and Running
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* Python 3.13+
|
||||
* Node.js 18+
|
||||
* Docker & Docker Compose
|
||||
* PostgreSQL
|
||||
* Redis
|
||||
|
||||
### Development Mode
|
||||
|
||||
1. **Start Dependencies:**
|
||||
```bash
|
||||
cd backend
|
||||
docker-compose up -d redis postgres
|
||||
```
|
||||
|
||||
2. **Start Backend:**
|
||||
```bash
|
||||
cd backend
|
||||
uv sync
|
||||
uv run python app/main.py
|
||||
```
|
||||
|
||||
3. **Start Frontend:**
|
||||
```bash
|
||||
cd frontend
|
||||
bun install
|
||||
bun run dev
|
||||
```
|
||||
|
||||
4. **Start Workers:**
|
||||
```bash
|
||||
cd backend
|
||||
uv run celery -A app.core.celery_app worker --loglevel=info
|
||||
uv run celery -A app.core.celery_app beat --loglevel=info
|
||||
```
|
||||
|
||||
### Docker Development
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
* **Frontend:** `http://localhost:3000`
|
||||
* **Backend API:** `http://localhost:8000`
|
||||
* **API Documentation:** `http://localhost:8000/docs`
|
||||
|
||||
## Development Conventions
|
||||
|
||||
### Code Quality
|
||||
|
||||
* **Backend:**
|
||||
* **Formatting:** Black
|
||||
* **Import Sorting:** isort
|
||||
* **Type Checking:** MyPy
|
||||
* **Linting:** Ruff
|
||||
* **Testing:** pytest
|
||||
* **Frontend:**
|
||||
* **Formatting:** Prettier
|
||||
* **Linting:** ESLint
|
||||
* **Type Checking:** TypeScript
|
||||
* **Testing:** Jest + React Testing Library
|
||||
|
||||
### Git Workflow
|
||||
|
||||
* Create feature branches from `master`.
|
||||
* Use conventional commit messages (e.g., `feat:`, `fix:`, `docs:`).
|
||||
* Submit pull requests for code review.
|
||||
|
||||
## Key Files
|
||||
|
||||
* `frontend/`: Contains the Next.js frontend application.
|
||||
* `backend/`: Contains the FastAPI backend application.
|
||||
* `deploy/`: Contains deployment scripts and configurations.
|
||||
* `README.md`: The main README file with detailed project information.
|
||||
88
GEMINI_CN.md
Normal file
88
GEMINI_CN.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Gemini 项目背景:苹果礼品卡兑换平台
|
||||
|
||||
## 项目概述
|
||||
|
||||
这是一个用于苹果礼品卡兑換平台的全栈应用程序。它采用现代、可扩展的架构,具有实时监控仪表板和苹果风格的用户界面。
|
||||
|
||||
* **前端:** 一个使用 TypeScript 编写的 Next.js 应用程序,使用 Tailwind CSS 进行样式设计,并使用 Radix UI 作为组件。它通过 REST API 与后端通信。
|
||||
* **后端:** 一个基于 Python 的微服务架构,使用 FastAPI。它利用 Celery 进行异步任务处理,使用 Playwright 进行浏览器自动化,并使用 PostgreSQL 作为数据库。
|
||||
* **架构:** 该系统设计为一个分布式系统,包括一个前端、一个后端 API 和一个 Celery 工作者池。它使用 Redis 进行缓存和作为消息代理。
|
||||
|
||||
## 构建和运行
|
||||
|
||||
### 先决条件
|
||||
|
||||
* Python 3.13+
|
||||
* Node.js 18+
|
||||
* Docker 和 Docker Compose
|
||||
* PostgreSQL
|
||||
* Redis
|
||||
|
||||
### 开发模式
|
||||
|
||||
1. **启动依赖项:**
|
||||
```bash
|
||||
cd backend
|
||||
docker-compose up -d redis postgres
|
||||
```
|
||||
|
||||
2. **启动后端:**
|
||||
```bash
|
||||
cd backend
|
||||
uv sync
|
||||
uv run python app/main.py
|
||||
```
|
||||
|
||||
3. **启动前端:**
|
||||
```bash
|
||||
cd frontend
|
||||
bun install
|
||||
bun run dev
|
||||
```
|
||||
|
||||
4. **启动工作者:**
|
||||
```bash
|
||||
cd backend
|
||||
uv run celery -A app.core.celery_app worker --loglevel=info
|
||||
uv run celery -A app.core.celery_app beat --loglevel=info
|
||||
```
|
||||
|
||||
### Docker 开发
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
* **前端:** `http://localhost:3000`
|
||||
* **后端 API:** `http://localhost:8000`
|
||||
* **API 文档:** `http://localhost:8000/docs`
|
||||
|
||||
## 开发规范
|
||||
|
||||
### 代码质量
|
||||
|
||||
* **后端:**
|
||||
* **格式化:** Black
|
||||
* **导入排序:** isort
|
||||
* **类型检查:** MyPy
|
||||
* **代码检查:** Ruff
|
||||
* **测试:** pytest
|
||||
* **前端:**
|
||||
* **格式化:** Prettier
|
||||
* **代码检查:** ESLint
|
||||
* **类型检查:** TypeScript
|
||||
* **测试:** Jest + React Testing Library
|
||||
|
||||
### Git 工作流程
|
||||
|
||||
* 从 `master` 创建功能分支。
|
||||
* 使用常规提交消息 (例如, `feat:`, `fix:`, `docs:`)。
|
||||
* 提交拉取请求以进行代码审查。
|
||||
|
||||
## 关键文件
|
||||
|
||||
* `frontend/`: 包含 Next.js 前端应用程序。
|
||||
* `backend/`: 包含 FastAPI 后端应用程序。
|
||||
* `deploy/`: 包含部署脚本和配置。
|
||||
* `README.md`: 包含详细项目信息的主 README 文件。
|
||||
73
SECURITY_ANALYSIS_TODO.md
Normal file
73
SECURITY_ANALYSIS_TODO.md
Normal file
@@ -0,0 +1,73 @@
|
||||
- [x] Define the audit scope.
|
||||
- [x] SAST Recon on backend/Dockerfile
|
||||
- [x] Investigate content of `.env.production`
|
||||
- [x] SAST Recon on backend/Dockerfile.test
|
||||
- [x] SAST Recon on backend/Dockerfile.worker
|
||||
- [x] SAST Recon on backend/app/api/v1/links.py
|
||||
- [x] SAST Recon on backend/app/api/v1/orders.py
|
||||
- [x] SAST Recon on backend/app/api/v1/tasks.py
|
||||
- [x] SAST Recon on backend/app/api/v1/user_data.py
|
||||
- [x] SAST Recon on backend/app/core/celery_app.py
|
||||
- [x] SAST Recon on backend/app/core/config.py
|
||||
- [x] SAST Recon on backend/app/core/exceptions.py
|
||||
- [x] SAST Recon on backend/app/core/redis_manager.py
|
||||
- [x] SAST Recon on backend/app/core/state_manager.py
|
||||
- [x] SAST Recon on backend/app/models/giftcards.py
|
||||
- [x] SAST Recon on backend/app/models/links.py
|
||||
- [x] SAST Recon on backend/app/models/orders.py
|
||||
- [x] SAST Recon on backend/app/models/user_data.py
|
||||
- [x] SAST Recon on backend/app/repositories/order_repository.py
|
||||
- [x] Investigate `BaseRepository`
|
||||
- [x] SAST Recon on backend/app/repositories/user_data_repository.py
|
||||
- [x] SAST Recon on backend/app/schemas/user_data.py
|
||||
- [ ] SAST Recon on backend/app/services/link_service.py
|
||||
- [ ] SAST Recon on backend/app/services/order_business_service.py
|
||||
- [ ] SAST Recon on backend/app/services/playwright_service.py
|
||||
- [ ] SAST Recon on backend/app/services/task_service.py
|
||||
- [ ] SAST Recon on backend/app/services/user_data_service.py
|
||||
- [ ] SAST Recon on backend/app/tasks/__init__.py
|
||||
- [ ] SAST Recon on backend/app/tasks/crawler_tasks.py
|
||||
- [ ] SAST Recon on backend/docker-entrypoint-simple.sh
|
||||
- [ ] SAST Recon on backend/docker-entrypoint.sh
|
||||
- [ ] SAST Recon on backend/run.py
|
||||
- [ ] SAST Recon on backend/test_gunicorn.py
|
||||
- [ ] SAST Recon on backend/test_tasks.py
|
||||
- [ ] SAST Recon on deploy/deploy.sh
|
||||
- [ ] SAST Recon on deploy/docker-compose.combined.yml
|
||||
- [ ] SAST Recon on deploy/redis.conf
|
||||
- [ ] SAST Recon on frontend/Dockerfile
|
||||
- [ ] SAST Recon on frontend/build-docker.sh
|
||||
- [ ] SAST Recon on frontend/next.config.js
|
||||
- [ ] SAST Recon on frontend/orval.config.ts
|
||||
- [ ] SAST Recon on frontend/postcss.config.js
|
||||
- [ ] SAST Recon on frontend/postcss.config.mjs
|
||||
- [ ] SAST Recon on frontend/src/app/layout.tsx
|
||||
- [ ] SAST Recon on frontend/src/components/dashboard/gift-card-input.tsx
|
||||
- [ ] SAST Recon on frontend/src/components/dashboard/link-item.tsx
|
||||
- [ ] SAST Recon on frontend/src/components/dashboard/link-management.tsx
|
||||
- [ ] SAST Recon on frontend/src/components/dashboard/order-stats-card.tsx
|
||||
- [ ] SAST Recon on frontend/src/components/dashboard/task-control.tsx
|
||||
- [ ] SAST Recon on frontend/src/components/dashboard/task-list.tsx
|
||||
- [ ] SAST Recon on frontend/src/components/dashboard/uploaded-data-display.tsx
|
||||
- [ ] SAST Recon on frontend/src/components/layout/loading-screen.tsx
|
||||
- [ ] SAST Recon on frontend/src/components/ui/apple-button.tsx
|
||||
- [ ] SAST Recon on frontend/src/lib/api/generated/schemas/index.ts
|
||||
- [ ] SAST Recon on frontend/src/lib/api/generated/schemas/userDataUploadResponse.ts
|
||||
- [ ] SAST Recon on frontend/src/styles/globals.css
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/animations/apple-animations.module.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/components/apple-components-extended.module.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/components/apple-components.module.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/components/apple-system-components.module.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/components/dynamic-backgrounds.module.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/components/glass-components.module.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/main.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/tailwind.module.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/themes/apple-design-tokens.module.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/themes/glass.module.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/utils/apple-mixins.module.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/utils/grid-system.module.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/utils/mixins.module.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/utils/variables.module.scss
|
||||
- [ ] SAST Recon on frontend/src/styles/sass/utils/visual-effects.module.scss
|
||||
- [ ] SAST Recon on frontend/tailwind.config.js
|
||||
- [ ] Conduct the final review of all findings as per your **Minimizing False Positives** operating principle and generate the final report.
|
||||
@@ -9,12 +9,13 @@ DEBUG=true
|
||||
|
||||
# 服务配置
|
||||
HOST=0.0.0.0
|
||||
PORT=3001
|
||||
PORT=8000
|
||||
WORKERS=1
|
||||
|
||||
# 数据库配置
|
||||
# DATABASE_URL=sqlite:///./data/kami_data.db
|
||||
DATABASE_URL=postgresql+asyncpg://postgres:123456@localhost:5432/apple_exchange
|
||||
# DATABASE_URL=postgresql+asyncpg://postgres:123456@localhost:5432/apple_exchange
|
||||
DATABASE_URL=postgresql+asyncpg://postgres:password@localhost:5444/apple_exchange
|
||||
DATABASE_POOL_SIZE=10
|
||||
DATABASE_MAX_OVERFLOW=20
|
||||
DATABASE_TIMEOUT=30
|
||||
|
||||
@@ -31,9 +31,6 @@ COPY app ./app
|
||||
COPY run.py ./
|
||||
COPY .env.production .env
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:8000/api/v1/health/liveness || exit 1
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["./docker-entrypoint.sh"]
|
||||
@@ -1,11 +0,0 @@
|
||||
FROM python:3.13-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY docker-entrypoint-simple.sh ./
|
||||
RUN chmod +x docker-entrypoint-simple.sh
|
||||
|
||||
COPY docker-entrypoint.sh ./
|
||||
RUN chmod +x docker-entrypoint.sh
|
||||
|
||||
CMD ["./docker-entrypoint-simple.sh"]
|
||||
@@ -23,7 +23,6 @@ def get_link_service(db: AsyncSession = Depends(get_async_db)) -> LinksService:
|
||||
return LinksService(db)
|
||||
|
||||
|
||||
|
||||
@router.post("/", response_model=LinkResponse)
|
||||
async def create_link(
|
||||
link_data: LinkCreate, link_service: LinksService = Depends(get_link_service)
|
||||
@@ -65,8 +64,11 @@ async def get_links(
|
||||
logger.error(f"获取链接列表失败: {str(e)}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail="获取链接列表失败")
|
||||
|
||||
|
||||
@router.get("/{link_id}", response_model=LinkResponse)
|
||||
async def get_link(link_id: int, link_service: LinksService = Depends(get_link_service)):
|
||||
async def get_link(
|
||||
link_id: int, link_service: LinksService = Depends(get_link_service)
|
||||
):
|
||||
"""获取单个链接详情"""
|
||||
try:
|
||||
link = await link_service.get_link(str(link_id))
|
||||
|
||||
@@ -9,6 +9,9 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.core.database import get_async_db
|
||||
from app.core.log import get_logger
|
||||
from app.models.orders import OrderResultStatus
|
||||
from app.schemas.link import LinkResponse
|
||||
from app.schemas.task import GiftCardInfoResponse
|
||||
from app.schemas.user_data import UserInfoResponse
|
||||
from app.services.order_business_service import OrderService
|
||||
from app.schemas.order import (
|
||||
OrderStatsResponse,
|
||||
@@ -36,7 +39,7 @@ async def get_order_stats(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/list", summary="获取订单列表", response_model=list[OrderDetailResponse])
|
||||
@router.get("/list", summary="获取订单列表")
|
||||
async def get_orders(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
@@ -45,16 +48,68 @@ async def get_orders(
|
||||
) -> list[OrderDetailResponse]:
|
||||
"""获取订单列表"""
|
||||
try:
|
||||
result = await OrderService(db).get_order_list( skip, limit, status_filter)
|
||||
logger.info(
|
||||
f"获取订单列表成功: skip={skip}, limit={limit}, status_filter={status_filter}"
|
||||
)
|
||||
orders = await OrderService(db).get_order_list(skip, limit, status_filter)
|
||||
# Convert SQLAlchemy models to Pydantic response schemas
|
||||
result = []
|
||||
for order in orders:
|
||||
link_response = LinkResponse(
|
||||
id=order.links.id,
|
||||
url=order.links.url,
|
||||
amount=order.links.amount,
|
||||
created_at=order.links.created_at.isoformat(),
|
||||
updated_at=order.links.updated_at.isoformat(),
|
||||
)
|
||||
gift_cards = []
|
||||
if order.gift_cards:
|
||||
for gc in order.gift_cards:
|
||||
gift_cards.append(GiftCardInfoResponse(
|
||||
id=gc.id,
|
||||
card_code=gc.card_code,
|
||||
status=gc.status.value,
|
||||
failure_reason=gc.failure_reason,
|
||||
order_result_id=order.id,
|
||||
created_at=gc.created_at.isoformat(),
|
||||
updated_at=gc.updated_at.isoformat()
|
||||
))
|
||||
|
||||
user_data = UserInfoResponse(
|
||||
id=order.user_data.id,
|
||||
email=order.user_data.email,
|
||||
phone=order.user_data.phone,
|
||||
created_at=order.user_data.created_at.isoformat(),
|
||||
updated_at=order.user_data.updated_at.isoformat(),
|
||||
full_name=order.user_data.full_name,
|
||||
full_address=order.user_data.full_address,
|
||||
first_name=order.user_data.first_name,
|
||||
last_name=order.user_data.last_name,
|
||||
street_address=order.user_data.street_address,
|
||||
city=order.user_data.city,
|
||||
state=order.user_data.state,
|
||||
zip_code=order.user_data.zip_code
|
||||
)
|
||||
|
||||
# Handle gift_cards being None by converting to empty list
|
||||
gift_cards = order.gift_cards if order.gift_cards is not None else []
|
||||
order_detail = OrderDetailResponse(
|
||||
id=order.id,
|
||||
status=order.status.value,
|
||||
created_at=order.created_at.isoformat(),
|
||||
updated_at=order.updated_at.isoformat(),
|
||||
final_order_url=order.final_order_url,
|
||||
failure_reason=order.failure_reason,
|
||||
user_data_id=order.user_data_id,
|
||||
links_id=order.links_id,
|
||||
user_data=user_data,
|
||||
links=link_response,
|
||||
gift_cards=gift_cards
|
||||
)
|
||||
result.append(order_detail)
|
||||
return result
|
||||
except ValueError as e:
|
||||
logger.warning(f"获取订单列表失败 - 参数验证错误: {str(e)}")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"获取订单列表失败: {e}", exc_info=True)
|
||||
logger.error(f"获取订单列表失败: {traceback.format_exc()}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"获取订单列表失败: {str(e)}",
|
||||
@@ -67,9 +122,9 @@ async def export_orders(
|
||||
):
|
||||
"""导出订单数据为Excel文件"""
|
||||
try:
|
||||
excel_content, filename = await OrderService(db, ).export_orders_to_excel(
|
||||
status_filter
|
||||
)
|
||||
excel_content, filename = await OrderService(
|
||||
db,
|
||||
).export_orders_to_excel(status_filter)
|
||||
logger.info(f"导出订单数据成功: filename={filename}")
|
||||
|
||||
return Response(
|
||||
@@ -90,9 +145,7 @@ async def export_orders(
|
||||
|
||||
|
||||
@router.get("/{order_id}", summary="获取订单详情", response_model=OrderDetailResponse)
|
||||
async def get_order(
|
||||
order_id: str, db: AsyncSession = Depends(get_async_db)
|
||||
) -> OrderDetailResponse:
|
||||
async def get_order(order_id: str, db: AsyncSession = Depends(get_async_db)) -> OrderDetailResponse:
|
||||
"""获取订单详情"""
|
||||
try:
|
||||
order = await OrderService(db).get_order_detail(order_id)
|
||||
@@ -103,8 +156,25 @@ async def get_order(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="订单不存在"
|
||||
)
|
||||
|
||||
# Convert SQLAlchemy model to Pydantic response schema
|
||||
# Handle gift_cards being None by converting to empty list
|
||||
gift_cards = order.gift_cards if order.gift_cards is not None else []
|
||||
order_detail = OrderDetailResponse(
|
||||
id=order.id,
|
||||
status=order.status.value,
|
||||
created_at=order.created_at.isoformat(),
|
||||
updated_at=order.updated_at.isoformat(),
|
||||
final_order_url=order.final_order_url,
|
||||
failure_reason=order.failure_reason,
|
||||
user_data_id=order.user_data_id,
|
||||
links_id=order.links_id,
|
||||
user_data=order.user_data,
|
||||
links=order.links,
|
||||
gift_cards=gift_cards
|
||||
)
|
||||
|
||||
logger.info(f"获取订单详情成功: {order_id}")
|
||||
return order
|
||||
return order_detail
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
|
||||
@@ -14,7 +14,15 @@ from app.core.redis_manager import redis_manager
|
||||
from app.core.log import get_logger
|
||||
from app.core.state_manager import task_state_manager
|
||||
from app.services.task_service import TaskService
|
||||
from app.schemas.task import GiftCardSubmissionRequest, GiftCardSubmissionResponse, QueueStatsResponse, TaskListResponse, TaskControlRequest, TaskControlResponse, TaskStateResponse
|
||||
from app.schemas.task import (
|
||||
GiftCardSubmissionRequest,
|
||||
GiftCardSubmissionResponse,
|
||||
QueueStatsResponse,
|
||||
TaskListResponse,
|
||||
TaskControlRequest,
|
||||
TaskControlResponse,
|
||||
TaskStateResponse,
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
router = APIRouter()
|
||||
@@ -85,8 +93,9 @@ async def get_task_status() -> TaskStateResponse:
|
||||
detail=f"获取任务状态失败: {str(e)}",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/list", response_model=TaskListResponse)
|
||||
async def get_task_list( db: AsyncSession = Depends(get_async_db)) -> TaskListResponse:
|
||||
async def get_task_list(db: AsyncSession = Depends(get_async_db)) -> TaskListResponse:
|
||||
"""
|
||||
获取任务列表
|
||||
|
||||
@@ -105,6 +114,7 @@ async def get_task_list( db: AsyncSession = Depends(get_async_db)) -> TaskListRe
|
||||
detail=f"获取任务列表失败: {str(e)}",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/queue/stats", summary="获取队列统计", response_model=QueueStatsResponse)
|
||||
async def get_queue_stats() -> QueueStatsResponse:
|
||||
"""获取Celery队列统计信息"""
|
||||
@@ -146,7 +156,9 @@ async def get_queue_stats() -> QueueStatsResponse:
|
||||
|
||||
|
||||
@router.post("/submit", response_model=GiftCardSubmissionResponse)
|
||||
async def submit_gift_card(request: GiftCardSubmissionRequest, db: AsyncSession = Depends(get_async_db)):
|
||||
async def submit_gift_card(
|
||||
request: GiftCardSubmissionRequest, db: AsyncSession = Depends(get_async_db)
|
||||
):
|
||||
"""
|
||||
提交礼品卡信息并更新任务状态
|
||||
|
||||
@@ -162,14 +174,10 @@ async def submit_gift_card(request: GiftCardSubmissionRequest, db: AsyncSession
|
||||
return await task_service.submit_gift_card(request)
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(e)
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
|
||||
except RuntimeError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=str(e)
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"提交礼品卡失败: {e}")
|
||||
@@ -177,4 +185,3 @@ async def submit_gift_card(request: GiftCardSubmissionRequest, db: AsyncSession
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"提交礼品卡失败: {str(e)}",
|
||||
)
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ celery_app.autodiscover_tasks(["app.tasks"])
|
||||
# 显式导入任务模块以确保任务被注册
|
||||
try:
|
||||
from app.tasks import crawler_tasks
|
||||
|
||||
logger.info("成功导入 crawler_tasks 模块")
|
||||
except ImportError as e:
|
||||
logger.error(f"导入 crawler_tasks 模块失败: {e}")
|
||||
|
||||
@@ -72,8 +72,6 @@ class LogContext:
|
||||
if exc_type:
|
||||
get_logger().bind(**self.context).exception(
|
||||
f"Context exception: {exc_val}",
|
||||
exc_type=exc_type.__name__,
|
||||
exc_message=str(exc_val),
|
||||
)
|
||||
|
||||
def get_enriched_context(self) -> Dict[str, Any]:
|
||||
|
||||
@@ -4,6 +4,7 @@ FastAPI中间件模块
|
||||
"""
|
||||
|
||||
import time
|
||||
import traceback
|
||||
import uuid
|
||||
from typing import Callable, Awaitable, Optional
|
||||
|
||||
@@ -40,13 +41,13 @@ class RequestLoggingMiddleware(BaseHTTPMiddleware):
|
||||
# 使用日志上下文
|
||||
with LogContext(request_id=request_id):
|
||||
# 记录请求开始
|
||||
logger.info(
|
||||
"请求开始",
|
||||
method=request.method,
|
||||
url=str(request.url),
|
||||
client_ip=request.client.host if request.client else "unknown",
|
||||
user_agent=request.headers.get("user-agent", "unknown"),
|
||||
)
|
||||
# logger.info(
|
||||
# "请求开始",
|
||||
# method=str(request.method),
|
||||
# url=str(request.url),
|
||||
# client_ip=str(request.client.host) if request.client else "unknown",
|
||||
# user_agent=str(request.headers.get("user-agent", "unknown")),
|
||||
# )
|
||||
|
||||
try:
|
||||
# 执行请求
|
||||
@@ -72,7 +73,7 @@ class RequestLoggingMiddleware(BaseHTTPMiddleware):
|
||||
# 记录请求失败
|
||||
logger.error(
|
||||
"请求失败",
|
||||
error=str(e),
|
||||
error=traceback.format_exc(),
|
||||
process_time=process_time,
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
@@ -259,7 +259,7 @@ class RedisManager:
|
||||
is_paused, _ = await self.get_task_pause_state()
|
||||
return is_paused
|
||||
|
||||
# 保存user_data_id
|
||||
# 保存user_data_id
|
||||
async def save_user_data_id(self, user_data_id: str) -> bool:
|
||||
"""保存user_data_id"""
|
||||
try:
|
||||
|
||||
@@ -131,14 +131,6 @@ def create_app() -> FastAPI:
|
||||
redoc_url="/redoc" if settings.DEBUG else None,
|
||||
openapi_url="/openapi.json" if settings.DEBUG else None,
|
||||
lifespan=lifespan,
|
||||
contact={
|
||||
"name": "Apple Exchange Team",
|
||||
"email": "team@apple-exchange.com",
|
||||
},
|
||||
license_info={
|
||||
"name": "MIT License",
|
||||
"url": "https://opensource.org/licenses/MIT",
|
||||
},
|
||||
servers=[
|
||||
{
|
||||
"url": f"http://{settings.HOST}:{settings.PORT}",
|
||||
@@ -238,8 +230,8 @@ def setup_middleware(app: FastAPI):
|
||||
# 设置自定义中间件(日志、安全头等)
|
||||
setup_custom_middleware(app)
|
||||
|
||||
# 设置 OpenTelemetry 中间件
|
||||
add_metrics_middleware(app)
|
||||
# # 设置 OpenTelemetry 中间件
|
||||
# add_metrics_middleware(app)
|
||||
|
||||
# 添加API日志记录中间件
|
||||
add_api_logging_middleware(app)
|
||||
|
||||
@@ -33,9 +33,7 @@ class GiftCards(BaseModel):
|
||||
card_code: Mapped[str] = mapped_column(
|
||||
String(255), nullable=False, comment="礼品卡号码"
|
||||
)
|
||||
card_value: Mapped[float] = mapped_column(
|
||||
nullable=False, comment="卡片面额"
|
||||
)
|
||||
card_value: Mapped[float] = mapped_column(nullable=False, comment="卡片面额")
|
||||
status: Mapped[GiftCardStatus] = mapped_column(
|
||||
Enum(GiftCardStatus),
|
||||
default=GiftCardStatus.PENDING,
|
||||
|
||||
@@ -25,9 +25,7 @@ class Links(BaseModel):
|
||||
amount: Mapped[float] = mapped_column(Float, nullable=False, comment="金额")
|
||||
|
||||
# 关系映射
|
||||
orders: Mapped[list["Orders"]] = relationship(
|
||||
"Orders", back_populates="links"
|
||||
)
|
||||
orders: Mapped[list["Orders"]] = relationship("Orders", back_populates="links")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Links(id={self.id}, url={self.url}, amount={self.amount})>"
|
||||
|
||||
@@ -54,9 +54,7 @@ class Orders(BaseModel):
|
||||
)
|
||||
|
||||
# 关系映射
|
||||
user_data: Mapped["UserData"] = relationship(
|
||||
"UserData", back_populates="orders"
|
||||
)
|
||||
user_data: Mapped["UserData"] = relationship("UserData", back_populates="orders")
|
||||
links: Mapped["Links"] = relationship("Links", back_populates="orders")
|
||||
gift_cards: Mapped["list[GiftCards]"] = relationship(
|
||||
"GiftCards", back_populates="order", cascade="all, delete-orphan"
|
||||
|
||||
@@ -35,9 +35,7 @@ class UserData(BaseModel):
|
||||
phone: Mapped[str] = mapped_column(String(50), nullable=False, comment="电话号码")
|
||||
|
||||
# 关系映射
|
||||
orders: Mapped[list["Orders"]] = relationship(
|
||||
"Orders", back_populates="user_data"
|
||||
)
|
||||
orders: Mapped[list["Orders"]] = relationship("Orders", back_populates="user_data")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
|
||||
@@ -56,7 +56,10 @@ class BaseRepository(Generic[ModelType]):
|
||||
return instance
|
||||
|
||||
async def get_by_id(
|
||||
self, id_: str, relations: list[str] | None = None, include_deleted: bool = False
|
||||
self,
|
||||
id_: str,
|
||||
relations: list[str] | None = None,
|
||||
include_deleted: bool = False,
|
||||
) -> ModelType | None:
|
||||
"""
|
||||
根据ID获取记录
|
||||
@@ -186,7 +189,10 @@ class BaseRepository(Generic[ModelType]):
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def get_one_by_filter(
|
||||
self, relations: list[str] | None = None, include_deleted: bool = False, **filters
|
||||
self,
|
||||
relations: list[str] | None = None,
|
||||
include_deleted: bool = False,
|
||||
**filters,
|
||||
) -> ModelType | None:
|
||||
"""
|
||||
根据条件获取单条记录
|
||||
@@ -484,7 +490,11 @@ class BaseRepository(Generic[ModelType]):
|
||||
|
||||
# 获取数据
|
||||
items = await self.get_by_filter(
|
||||
relations=relations, order_by=order_by, desc=desc, include_deleted=include_deleted, **filters
|
||||
relations=relations,
|
||||
order_by=order_by,
|
||||
desc=desc,
|
||||
include_deleted=include_deleted,
|
||||
**filters,
|
||||
)
|
||||
|
||||
# 应用分页
|
||||
@@ -551,7 +561,9 @@ class BaseRepository(Generic[ModelType]):
|
||||
await self.db_session.commit()
|
||||
return result.rowcount > 0
|
||||
else:
|
||||
raise ValueError(f"Model {self.model.__name__} does not support soft delete")
|
||||
raise ValueError(
|
||||
f"Model {self.model.__name__} does not support soft delete"
|
||||
)
|
||||
|
||||
async def restore_by_id(self, id: str) -> bool:
|
||||
"""
|
||||
@@ -572,7 +584,9 @@ class BaseRepository(Generic[ModelType]):
|
||||
await self.db_session.commit()
|
||||
return result.rowcount > 0
|
||||
else:
|
||||
raise ValueError(f"Model {self.model.__name__} does not support soft delete")
|
||||
raise ValueError(
|
||||
f"Model {self.model.__name__} does not support soft delete"
|
||||
)
|
||||
|
||||
async def get_deleted(
|
||||
self,
|
||||
@@ -600,10 +614,12 @@ class BaseRepository(Generic[ModelType]):
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
relations=relations,
|
||||
**filters
|
||||
**filters,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Model {self.model.__name__} does not support soft delete")
|
||||
raise ValueError(
|
||||
f"Model {self.model.__name__} does not support soft delete"
|
||||
)
|
||||
|
||||
# ==================== 自定义查询构建器 ====================
|
||||
|
||||
|
||||
@@ -48,7 +48,9 @@ class TaskRepository(BaseRepository):
|
||||
if order:
|
||||
# 手动加载关联数据(如果需要)
|
||||
if order.user_data_id:
|
||||
user_query = select(UserData).where(UserData.id == order.user_data_id)
|
||||
user_query = select(UserData).where(
|
||||
UserData.id == order.user_data_id
|
||||
)
|
||||
user_result = await self.db_session.execute(user_query)
|
||||
order.user_data = user_result.scalar_one_or_none()
|
||||
|
||||
@@ -58,7 +60,9 @@ class TaskRepository(BaseRepository):
|
||||
order.links = link_result.scalar_one_or_none()
|
||||
|
||||
# 获取礼品卡信息
|
||||
gift_card_query = select(GiftCards).where(GiftCards.order_result_id == order_id)
|
||||
gift_card_query = select(GiftCards).where(
|
||||
GiftCards.order_result_id == order_id
|
||||
)
|
||||
gift_card_result = await self.db_session.execute(gift_card_query)
|
||||
order.gift_cards = gift_card_result.scalars().all()
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ from .order import (
|
||||
UploadUrlRequest,
|
||||
UploadUrlResponse,
|
||||
UserInfoResponse,
|
||||
GiftCardResponse,
|
||||
)
|
||||
from .task import (
|
||||
ProcessOrderRequest,
|
||||
@@ -16,6 +15,7 @@ from .task import (
|
||||
TaskStatusResponse,
|
||||
WorkerStatusResponse,
|
||||
QueueStatsResponse,
|
||||
GiftCardResponse,
|
||||
)
|
||||
from .link import (
|
||||
LinkCreate,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
链接相关的Pydantic模型
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
|
||||
|
||||
@@ -34,6 +35,19 @@ class LinkResponse(LinkBase):
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@classmethod
|
||||
def from_orm(cls, obj):
|
||||
"""Custom ORM conversion to handle datetime serialization"""
|
||||
data = {}
|
||||
for field in cls.model_fields:
|
||||
value = getattr(obj, field, None)
|
||||
if isinstance(value, datetime):
|
||||
data[field] = value.isoformat()
|
||||
else:
|
||||
data[field] = value
|
||||
|
||||
return cls(**data)
|
||||
|
||||
|
||||
class LinkListResponse(BaseModel):
|
||||
"""链接列表响应模型"""
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from app.models.giftcards import GiftCardStatus
|
||||
from app.models.orders import OrderResultStatus
|
||||
from app.schemas.link import LinkResponse
|
||||
from app.schemas.gift_card import GiftCardInfoResponse
|
||||
from app.schemas.user_data import UserInfoResponse
|
||||
|
||||
|
||||
class OrderStatsResponse(BaseModel):
|
||||
@@ -17,49 +20,28 @@ class OrderStatsResponse(BaseModel):
|
||||
last_update: str
|
||||
|
||||
|
||||
class UserInfoResponse(BaseModel):
|
||||
"""用户信息响应 - 与数据表字段完全一致"""
|
||||
class OrderDetailResponse(BaseModel):
|
||||
"""订单详情响应 - 与数据库结构完全一致"""
|
||||
|
||||
id: str = Field(..., description="用户数据唯一标识")
|
||||
first_name: str = Field(..., description="名字")
|
||||
last_name: str = Field(..., description="姓氏")
|
||||
email: str = Field(..., description="邮箱地址")
|
||||
phone: str = Field(..., description="电话号码")
|
||||
street_address: str = Field(..., description="街道地址")
|
||||
city: str = Field(..., description="城市")
|
||||
state: str = Field(..., description="州/省")
|
||||
zip_code: str = Field(..., description="邮政编码")
|
||||
id: str = Field(..., description="订单ID")
|
||||
status: OrderResultStatus = Field(..., description="订单状态")
|
||||
created_at: str = Field(..., description="创建时间")
|
||||
updated_at: str = Field(..., description="更新时间")
|
||||
final_order_url: str | None = Field(None, description="最终订单URL")
|
||||
failure_reason: str | None = Field(None, description="失败原因")
|
||||
user_data_id: str = Field(..., description="用户数据ID")
|
||||
links_id: str = Field(..., description="链接ID")
|
||||
|
||||
# 关联关系
|
||||
user_data: UserInfoResponse = Field(description="用户数据")
|
||||
links: LinkResponse = Field(description="链接信息")
|
||||
gift_cards: list[GiftCardInfoResponse] = Field(
|
||||
default_factory=list, description="礼品卡列表"
|
||||
)
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GiftCardResponse(BaseModel):
|
||||
"""礼品卡响应模型 - 与数据表字段完全一致"""
|
||||
|
||||
id: str = Field(..., description="礼品卡ID")
|
||||
card_code: str = Field(..., description="礼品卡号码")
|
||||
status: GiftCardStatus = Field(..., description="礼品卡状态")
|
||||
failure_reason: str | None = Field(None, description="失败原因")
|
||||
order_result_id: str = Field(..., description="订单结果ID")
|
||||
created_at: str = Field(..., description="创建时间")
|
||||
updated_at: str = Field(..., description="更新时间")
|
||||
|
||||
|
||||
class OrderDetailResponse(BaseModel):
|
||||
"""订单详情响应"""
|
||||
|
||||
id: str
|
||||
status: str
|
||||
order_number: str | None
|
||||
final_order_url: str | None
|
||||
failure_reason: str | None
|
||||
created_at: str
|
||||
user_info: UserInfoResponse
|
||||
gift_cards: list[GiftCardResponse]
|
||||
|
||||
|
||||
class UploadUrlRequest(BaseModel):
|
||||
"""上传URL请求"""
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
用户数据相关的Pydantic模型
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, Field, field_validator, ConfigDict
|
||||
|
||||
|
||||
@@ -96,6 +97,25 @@ class UserInfoResponse(BaseModel):
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@classmethod
|
||||
def from_orm(cls, obj):
|
||||
"""Custom ORM conversion to handle datetime serialization"""
|
||||
data = {}
|
||||
for field in cls.model_fields:
|
||||
value = getattr(obj, field, None)
|
||||
if isinstance(value, datetime):
|
||||
data[field] = value.isoformat()
|
||||
else:
|
||||
data[field] = value
|
||||
|
||||
# Add computed fields
|
||||
if hasattr(obj, 'first_name') and hasattr(obj, 'last_name'):
|
||||
data['full_name'] = f"{obj.first_name} {obj.last_name}".strip()
|
||||
if hasattr(obj, 'street_address') and hasattr(obj, 'city') and hasattr(obj, 'state') and hasattr(obj, 'zip_code'):
|
||||
data['full_address'] = f"{obj.street_address}, {obj.city}, {obj.state} {obj.zip_code}".strip(", ")
|
||||
|
||||
return cls(**data)
|
||||
|
||||
|
||||
class UserDataStatsResponse(BaseModel):
|
||||
"""用户数据统计响应模型"""
|
||||
|
||||
@@ -15,9 +15,6 @@ from app.repositories.order_repository import OrderRepository
|
||||
from app.repositories.link_repository import LinkRepository
|
||||
from app.schemas.order import (
|
||||
OrderStatsResponse,
|
||||
OrderDetailResponse,
|
||||
UserInfoResponse,
|
||||
GiftCardResponse,
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@@ -51,19 +48,15 @@ class OrderService:
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
status_filter: OrderResultStatus | None = None,
|
||||
) -> list[OrderDetailResponse]:
|
||||
"""获取订单列表"""
|
||||
orders = await self.order_repo.get_orders_with_relations(
|
||||
) -> list[Orders]:
|
||||
"""获取订单列表(包含所有关联数据)"""
|
||||
return await self.order_repo.get_orders_with_relations(
|
||||
skip, limit, status_filter
|
||||
)
|
||||
return [self._convert_order_to_response(order) for order in orders]
|
||||
|
||||
async def get_order_detail(self, order_id: str) -> OrderDetailResponse | None:
|
||||
"""获取订单详情"""
|
||||
order = await self.order_repo.get_order_with_full_details(order_id)
|
||||
if not order:
|
||||
return None
|
||||
return self._convert_order_to_response(order)
|
||||
async def get_order_detail(self, order_id: str) -> Orders | None:
|
||||
"""获取订单详情(包含所有关联数据)"""
|
||||
return await self.order_repo.get_order_with_full_details(order_id)
|
||||
|
||||
async def export_orders_to_excel(
|
||||
self, status_filter: str | None = None
|
||||
@@ -151,108 +144,12 @@ class OrderService:
|
||||
|
||||
return excel_content, filename
|
||||
|
||||
@staticmethod
|
||||
def _convert_order_to_response(order: Orders) -> OrderDetailResponse:
|
||||
"""将订单模型转换为响应模型"""
|
||||
# 处理用户信息
|
||||
if hasattr(order, "user_data") and order.user_data:
|
||||
user_data = order.user_data
|
||||
address_parts = []
|
||||
if hasattr(user_data, "street_address") and user_data.street_address:
|
||||
address_parts.append(user_data.street_address)
|
||||
if hasattr(user_data, "city") and user_data.city:
|
||||
address_parts.append(user_data.city)
|
||||
if hasattr(user_data, "state") and user_data.state:
|
||||
address_parts.append(user_data.state)
|
||||
if hasattr(user_data, "zip_code") and user_data.zip_code:
|
||||
address_parts.append(user_data.zip_code)
|
||||
|
||||
user_info = UserInfoResponse(
|
||||
id=str(getattr(user_data, "id", 0)),
|
||||
first_name=getattr(user_data, "first_name", ""),
|
||||
last_name=getattr(user_data, "last_name", ""),
|
||||
email=getattr(user_data, "email", ""),
|
||||
phone=getattr(user_data, "phone", ""),
|
||||
street_address=getattr(user_data, "street_address", ""),
|
||||
city=getattr(user_data, "city", ""),
|
||||
state=getattr(user_data, "state", ""),
|
||||
zip_code=getattr(user_data, "zip_code", ""),
|
||||
created_at=(
|
||||
getattr(user_data, "created_at", datetime.now()).isoformat()
|
||||
if hasattr(user_data, "created_at") and user_data.created_at
|
||||
else datetime.now().isoformat()
|
||||
),
|
||||
updated_at=(
|
||||
getattr(user_data, "updated_at", datetime.now()).isoformat()
|
||||
if hasattr(user_data, "updated_at") and user_data.updated_at
|
||||
else datetime.now().isoformat()
|
||||
),
|
||||
)
|
||||
else:
|
||||
# 当没有用户数据时,创建空的UserInfoResponse对象
|
||||
user_info = UserInfoResponse(
|
||||
id="0",
|
||||
first_name="",
|
||||
last_name="",
|
||||
email="",
|
||||
phone="",
|
||||
street_address="",
|
||||
city="",
|
||||
state="",
|
||||
zip_code="",
|
||||
created_at=datetime.now().isoformat(),
|
||||
updated_at=datetime.now().isoformat(),
|
||||
)
|
||||
|
||||
# 处理礼品卡信息
|
||||
gift_cards = []
|
||||
if hasattr(order, "gift_cards") and order.gift_cards:
|
||||
for gc in order.gift_cards:
|
||||
gc_status = gc.status
|
||||
status_value = (
|
||||
gc_status.value if hasattr(gc_status, "value") else "unknown"
|
||||
)
|
||||
|
||||
gift_cards.append(
|
||||
GiftCardResponse(
|
||||
id=str(getattr(gc, "id", 0)),
|
||||
card_code=getattr(gc, "card_code", ""),
|
||||
status=status_value,
|
||||
failure_reason=getattr(gc, "failure_reason", ""),
|
||||
order_result_id=getattr(gc, "order_result_id", ""),
|
||||
created_at=(
|
||||
getattr(gc, "created_at", "").isoformat()
|
||||
if hasattr(getattr(gc, "created_at", ""), "isoformat")
|
||||
else str(getattr(gc, "created_at", ""))
|
||||
),
|
||||
updated_at=(
|
||||
getattr(gc, "updated_at", "").isoformat()
|
||||
if hasattr(getattr(gc, "updated_at", ""), "isoformat")
|
||||
else str(getattr(gc, "updated_at", ""))
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
return OrderDetailResponse(
|
||||
id=order.id,
|
||||
status=order.status.value,
|
||||
order_number=getattr(order, "order_number", ""),
|
||||
final_order_url=getattr(order, "final_order_url", ""),
|
||||
failure_reason=getattr(order, "failure_reason", ""),
|
||||
created_at=order.created_at.isoformat(),
|
||||
user_info=user_info,
|
||||
gift_cards=gift_cards,
|
||||
)
|
||||
|
||||
# 订单处理相关方法
|
||||
async def get_pending_orders(self) -> list[Orders]:
|
||||
"""获取pending状态的订单"""
|
||||
from sqlalchemy import select
|
||||
|
||||
query = (
|
||||
select(Orders)
|
||||
.where(Orders.status == OrderResultStatus.PENDING)
|
||||
)
|
||||
query = select(Orders).where(Orders.status == OrderResultStatus.PENDING)
|
||||
result = await self.db.execute(query)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@@ -290,7 +187,7 @@ class OrderService:
|
||||
order = await self.order_repo.create(
|
||||
user_data_id=user_data_id,
|
||||
links_id=links_id,
|
||||
status=OrderResultStatus.PENDING
|
||||
status=OrderResultStatus.PENDING,
|
||||
)
|
||||
logger.info(f"成功创建订单: {order.id}, 用户数据ID: {user_data_id}")
|
||||
return order.id
|
||||
|
||||
@@ -407,9 +407,7 @@ class AppleOrderProcessor:
|
||||
# 获取礼品卡信息
|
||||
async with db_manager.get_async_session() as db:
|
||||
gift_card_service = GiftCardService(db)
|
||||
card_code = await gift_card_service.get_card_code(
|
||||
self.order_id
|
||||
)
|
||||
card_code = await gift_card_service.get_card_code(self.order_id)
|
||||
|
||||
if card_code:
|
||||
return card_code
|
||||
|
||||
@@ -17,7 +17,7 @@ from app.schemas.task import (
|
||||
TaskLinkInfo,
|
||||
TaskCardInfo,
|
||||
GiftCardSubmissionRequest,
|
||||
GiftCardSubmissionResponse
|
||||
GiftCardSubmissionResponse,
|
||||
)
|
||||
from app.repositories.task_repository import TaskRepository
|
||||
from app.enums.task import OrderTaskStatus
|
||||
@@ -41,8 +41,8 @@ class TaskService:
|
||||
"""
|
||||
try:
|
||||
# 获取所有任务状态
|
||||
all_tasks: list[TaskState] = await task_state_manager.state_manager.list_states(
|
||||
StateType.TASK
|
||||
all_tasks: list[TaskState] = (
|
||||
await task_state_manager.state_manager.list_states(StateType.TASK)
|
||||
)
|
||||
|
||||
task_list = []
|
||||
@@ -60,7 +60,7 @@ class TaskService:
|
||||
updated_at=task.updated_at,
|
||||
completed_at=task.completed_at,
|
||||
failed_at=task.failed_at,
|
||||
error_message=task.error_message
|
||||
error_message=task.error_message,
|
||||
)
|
||||
|
||||
# 如果有订单ID,获取关联的用户、链接和礼品卡信息
|
||||
@@ -75,14 +75,16 @@ class TaskService:
|
||||
success=True,
|
||||
tasks=task_list,
|
||||
total=len(task_list),
|
||||
message="获取任务列表成功"
|
||||
message="获取任务列表成功",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取任务列表失败: {traceback.format_exc()}")
|
||||
raise
|
||||
|
||||
async def _enrich_task_with_order_data(self, task_item: TaskListItem, order_id: str) -> None:
|
||||
async def _enrich_task_with_order_data(
|
||||
self, task_item: TaskListItem, order_id: str
|
||||
) -> None:
|
||||
"""
|
||||
使用订单数据丰富任务信息
|
||||
|
||||
@@ -107,16 +109,14 @@ class TaskService:
|
||||
street_address=user_data.street_address,
|
||||
city=user_data.city,
|
||||
state=user_data.state,
|
||||
zip_code=user_data.zip_code
|
||||
zip_code=user_data.zip_code,
|
||||
)
|
||||
|
||||
# 获取链接信息
|
||||
if order.links:
|
||||
link = order.links
|
||||
task_item.link_info = TaskLinkInfo(
|
||||
url=link.url,
|
||||
amount=link.amount,
|
||||
code=link.id # 使用链接ID作为代码
|
||||
url=link.url, amount=link.amount, code=link.id # 使用链接ID作为代码
|
||||
)
|
||||
|
||||
# 获取礼品卡信息
|
||||
@@ -125,13 +125,15 @@ class TaskService:
|
||||
task_item.card_info = TaskCardInfo(
|
||||
card_number=gift_card.card_code,
|
||||
status=gift_card.status.value,
|
||||
failure_reason=gift_card.failure_reason
|
||||
failure_reason=gift_card.failure_reason,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取订单关联数据失败: {e}")
|
||||
|
||||
async def submit_gift_card(self, request: GiftCardSubmissionRequest) -> GiftCardSubmissionResponse:
|
||||
async def submit_gift_card(
|
||||
self, request: GiftCardSubmissionRequest
|
||||
) -> GiftCardSubmissionResponse:
|
||||
"""
|
||||
提交礼品卡信息并更新任务状态
|
||||
|
||||
@@ -149,25 +151,25 @@ class TaskService:
|
||||
|
||||
# 更新任务结果,包含礼品卡信息
|
||||
updated_result = task.result.copy() if task.result else {}
|
||||
updated_result.update({
|
||||
"card_code": request.card_code,
|
||||
"card_value": request.card_value,
|
||||
"submitted_at": datetime.now().isoformat()
|
||||
})
|
||||
updated_result.update(
|
||||
{
|
||||
"card_code": request.card_code,
|
||||
"card_value": request.card_value,
|
||||
"submitted_at": datetime.now().isoformat(),
|
||||
}
|
||||
)
|
||||
|
||||
# 更新任务状态为已接收礼品卡
|
||||
success = await task_state_manager.update_task_state(
|
||||
task_id=request.task_id,
|
||||
status=OrderTaskStatus.GIFT_CARD_RECEIVED,
|
||||
result=updated_result
|
||||
result=updated_result,
|
||||
)
|
||||
|
||||
if success:
|
||||
logger.info(f"礼品卡提交成功,任务ID: {request.task_id}")
|
||||
return GiftCardSubmissionResponse(
|
||||
success=True,
|
||||
task_id=request.task_id,
|
||||
message="礼品卡提交成功"
|
||||
success=True, task_id=request.task_id, message="礼品卡提交成功"
|
||||
)
|
||||
else:
|
||||
raise RuntimeError("更新任务状态失败")
|
||||
@@ -192,20 +194,28 @@ class TaskService:
|
||||
return None
|
||||
|
||||
# 获取任务基础信息
|
||||
task_dict = task.model_dump() if hasattr(task, 'model_dump') else task.dict() if hasattr(task, 'dict') else vars(task)
|
||||
task_dict = (
|
||||
task.model_dump()
|
||||
if hasattr(task, "model_dump")
|
||||
else task.dict() if hasattr(task, "dict") else vars(task)
|
||||
)
|
||||
|
||||
# 创建任务列表项
|
||||
task_item = TaskListItem(
|
||||
task_id=task_dict.get('identifier', ''),
|
||||
worker_id=task_dict.get('worker_id'),
|
||||
order_id=task_dict.get('result', {}).get('order_id') if task_dict.get('result') else None,
|
||||
status=task_dict.get('status', ''),
|
||||
progress=task_dict.get('progress', 0.0),
|
||||
created_at=task_dict.get('created_at', ''),
|
||||
updated_at=task_dict.get('updated_at', ''),
|
||||
completed_at=task_dict.get('completed_at'),
|
||||
failed_at=task_dict.get('failed_at'),
|
||||
error_message=task_dict.get('error_message')
|
||||
task_id=task_dict.get("identifier", ""),
|
||||
worker_id=task_dict.get("worker_id"),
|
||||
order_id=(
|
||||
task_dict.get("result", {}).get("order_id")
|
||||
if task_dict.get("result")
|
||||
else None
|
||||
),
|
||||
status=task_dict.get("status", ""),
|
||||
progress=task_dict.get("progress", 0.0),
|
||||
created_at=task_dict.get("created_at", ""),
|
||||
updated_at=task_dict.get("updated_at", ""),
|
||||
completed_at=task_dict.get("completed_at"),
|
||||
failed_at=task_dict.get("failed_at"),
|
||||
error_message=task_dict.get("error_message"),
|
||||
)
|
||||
|
||||
# 如果有订单ID,获取关联的用户、链接和礼品卡信息
|
||||
@@ -236,20 +246,28 @@ class TaskService:
|
||||
|
||||
filtered_tasks = []
|
||||
for task in all_tasks:
|
||||
task_dict = task.model_dump() if hasattr(task, 'model_dump') else task.dict() if hasattr(task, 'dict') else vars(task)
|
||||
task_dict = (
|
||||
task.model_dump()
|
||||
if hasattr(task, "model_dump")
|
||||
else task.dict() if hasattr(task, "dict") else vars(task)
|
||||
)
|
||||
|
||||
if task_dict.get('status') == status:
|
||||
if task_dict.get("status") == status:
|
||||
task_item = TaskListItem(
|
||||
task_id=task_dict.get('identifier', ''),
|
||||
worker_id=task_dict.get('worker_id'),
|
||||
order_id=task_dict.get('result', {}).get('order_id') if task_dict.get('result') else None,
|
||||
status=task_dict.get('status', ''),
|
||||
progress=task_dict.get('progress', 0.0),
|
||||
created_at=task_dict.get('created_at', ''),
|
||||
updated_at=task_dict.get('updated_at', ''),
|
||||
completed_at=task_dict.get('completed_at'),
|
||||
failed_at=task_dict.get('failed_at'),
|
||||
error_message=task_dict.get('error_message')
|
||||
task_id=task_dict.get("identifier", ""),
|
||||
worker_id=task_dict.get("worker_id"),
|
||||
order_id=(
|
||||
task_dict.get("result", {}).get("order_id")
|
||||
if task_dict.get("result")
|
||||
else None
|
||||
),
|
||||
status=task_dict.get("status", ""),
|
||||
progress=task_dict.get("progress", 0.0),
|
||||
created_at=task_dict.get("created_at", ""),
|
||||
updated_at=task_dict.get("updated_at", ""),
|
||||
completed_at=task_dict.get("completed_at"),
|
||||
failed_at=task_dict.get("failed_at"),
|
||||
error_message=task_dict.get("error_message"),
|
||||
)
|
||||
|
||||
# 如果有订单ID,获取关联的用户、链接和礼品卡信息
|
||||
|
||||
@@ -16,7 +16,8 @@ from app.schemas.user_data import (
|
||||
UserDataListResponse,
|
||||
UserDataUploadResponse,
|
||||
UserDataStatsResponse,
|
||||
UserInfoResponse, UserDataBase,
|
||||
UserInfoResponse,
|
||||
UserDataBase,
|
||||
)
|
||||
from app.models.user_data import UserData
|
||||
from app.models.orders import OrderResultStatus
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Any
|
||||
from datetime import datetime
|
||||
|
||||
@@ -45,9 +46,7 @@ def process_apple_order(
|
||||
task_id = getattr(current_task.request, "id", "unknown")
|
||||
worker_id = order_id
|
||||
|
||||
logger.info(
|
||||
f"开始处理Apple订单: task_id={task_id}, worker_id={worker_id}"
|
||||
)
|
||||
logger.info(f"开始处理Apple订单: task_id={task_id}, worker_id={worker_id}")
|
||||
|
||||
# 使用asyncio运行异步任务
|
||||
loop = asyncio.new_event_loop()
|
||||
@@ -71,6 +70,8 @@ async def _process_apple_order_async(
|
||||
if await redis_manager.is_task_paused():
|
||||
is_paused, reason = await redis_manager.get_task_pause_state()
|
||||
logger.info(f"任务已暂停,跳过订单处理: {order_id}, 原因: {reason}")
|
||||
time.sleep(10) # 等待一段时间以防止频繁检查
|
||||
process_apple_order.retry(countdown=60) # 1分钟后重试
|
||||
return {
|
||||
"success": False,
|
||||
"is_paused": True,
|
||||
@@ -173,15 +174,12 @@ def batch_process_orders():
|
||||
loop.close()
|
||||
|
||||
|
||||
|
||||
|
||||
async def _batch_process_orders_async():
|
||||
"""异步批量处理订单"""
|
||||
|
||||
# 检查任务是否暂停
|
||||
if await redis_manager.is_task_paused():
|
||||
is_paused, reason = await redis_manager.get_task_pause_state()
|
||||
logger.info(f"任务已暂停,跳过批量处理: {reason}")
|
||||
return None
|
||||
|
||||
# 获取数据库会话
|
||||
@@ -208,14 +206,14 @@ async def _batch_process_orders_async():
|
||||
|
||||
try:
|
||||
# 开始创建订单
|
||||
order_id = await order_service.create_order(user_data_id, link_info.link.id)
|
||||
order_id = await order_service.create_order(
|
||||
user_data_id, link_info.link.id
|
||||
)
|
||||
process_apple_order.delay(order_id)
|
||||
except Exception as e:
|
||||
logger.error(f"创建订单失败: {e}")
|
||||
|
||||
|
||||
|
||||
|
||||
# @celery_app.task(name="app.tasks.crawler_tasks.recover_stalled_orders")
|
||||
# def recover_stalled_orders() -> dict[str, Any]:
|
||||
# """
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
echo "Starting service..."
|
||||
pwd
|
||||
ls -la
|
||||
exec "$@"
|
||||
@@ -55,56 +55,69 @@ reload = False
|
||||
statsd_host = None
|
||||
statsd_prefix = ""
|
||||
|
||||
|
||||
# 工作进程启动时的钩子函数
|
||||
def on_starting(server):
|
||||
"""工作进程启动前的钩子函数"""
|
||||
pass
|
||||
|
||||
|
||||
def on_reload(server):
|
||||
"""重载时的钩子函数"""
|
||||
pass
|
||||
|
||||
|
||||
def when_ready(server):
|
||||
"""服务器准备就绪时的钩子函数"""
|
||||
pass
|
||||
|
||||
|
||||
def on_exit(server):
|
||||
"""服务器退出时的钩子函数"""
|
||||
pass
|
||||
|
||||
|
||||
# 工作进程相关的钩子函数
|
||||
def pre_fork(server, worker):
|
||||
"""工作进程fork前的钩子函数"""
|
||||
pass
|
||||
|
||||
|
||||
def post_fork(server, worker):
|
||||
"""工作进程fork后的钩子函数"""
|
||||
pass
|
||||
|
||||
|
||||
def post_worker_init(worker):
|
||||
"""工作进程初始化后的钩子函数"""
|
||||
pass
|
||||
|
||||
|
||||
def worker_int(worker):
|
||||
"""工作进程收到中断信号时的钩子函数"""
|
||||
pass
|
||||
|
||||
|
||||
def worker_abort(worker):
|
||||
"""工作进程异常终止时的钩子函数"""
|
||||
pass
|
||||
|
||||
|
||||
def child_exit(server, worker):
|
||||
"""子进程退出时的钩子函数"""
|
||||
pass
|
||||
|
||||
|
||||
def pre_exec(server):
|
||||
"""工作进程执行前的钩子函数"""
|
||||
pass
|
||||
|
||||
|
||||
def pre_request(worker, req):
|
||||
"""请求处理前的钩子函数"""
|
||||
pass
|
||||
|
||||
|
||||
def post_request(worker, req, environ, resp):
|
||||
"""请求处理后的钩子函数"""
|
||||
pass
|
||||
@@ -19,21 +19,25 @@ worker_connections = 1000
|
||||
timeout = 30
|
||||
keepalive = 2
|
||||
|
||||
|
||||
def test_imports():
|
||||
"""测试必要的模块导入"""
|
||||
try:
|
||||
import uvicorn
|
||||
import gunicorn
|
||||
|
||||
print("✅ Uvicorn和Gunicorn导入成功")
|
||||
return True
|
||||
except ImportError as e:
|
||||
print(f"❌ 模块导入失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_celery():
|
||||
"""测试Celery应用"""
|
||||
try:
|
||||
from app.core.celery_app import get_celery_app
|
||||
|
||||
app = get_celery_app()
|
||||
print("✅ Celery应用创建成功")
|
||||
return True
|
||||
@@ -41,10 +45,12 @@ def test_celery():
|
||||
print(f"❌ Celery应用创建失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_database():
|
||||
"""测试数据库连接"""
|
||||
try:
|
||||
from app.core.database import get_database
|
||||
|
||||
db = get_database()
|
||||
print("✅ 数据库连接配置成功")
|
||||
return True
|
||||
@@ -52,6 +58,7 @@ def test_database():
|
||||
print(f"❌ 数据库配置失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("🚀 开始测试Docker容器环境...")
|
||||
@@ -84,5 +91,6 @@ def main():
|
||||
print("⚠️ 有测试失败,请检查配置。")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -10,6 +10,7 @@ from pathlib import Path
|
||||
project_root = Path(__file__).parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
|
||||
def test_task_registration():
|
||||
"""测试任务注册"""
|
||||
try:
|
||||
@@ -25,13 +26,13 @@ def test_task_registration():
|
||||
print(f"已注册的任务数量: {len(registered_tasks)}")
|
||||
print("已注册的任务:")
|
||||
for task_name in sorted(registered_tasks):
|
||||
if not task_name.startswith('celery.'):
|
||||
if not task_name.startswith("celery."):
|
||||
print(f" ✓ {task_name}")
|
||||
|
||||
# 检查特定任务
|
||||
target_tasks = [
|
||||
'app.tasks.crawler_tasks.batch_process_orders',
|
||||
'app.tasks.crawler_tasks.process_apple_order'
|
||||
"app.tasks.crawler_tasks.batch_process_orders",
|
||||
"app.tasks.crawler_tasks.process_apple_order",
|
||||
]
|
||||
|
||||
print("\n检查目标任务:")
|
||||
@@ -48,9 +49,11 @@ def test_task_registration():
|
||||
except Exception as e:
|
||||
print(f"❌ 任务注册测试失败: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_task_registration()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
329
deploy/SWARM_GUIDE.md
Normal file
329
deploy/SWARM_GUIDE.md
Normal file
@@ -0,0 +1,329 @@
|
||||
# Docker Swarm 部署指南
|
||||
|
||||
## 概述
|
||||
|
||||
本指南说明如何使用 Docker Swarm 部署 Apple Gift Card Exchange Platform。Docker Swarm 是 Docker 原生的容器编排工具,适用于中小规模的生产环境部署。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 环境准备
|
||||
|
||||
确保系统已安装以下软件:
|
||||
- Docker (版本 20.10+)
|
||||
- Docker Compose (版本 1.29+)
|
||||
|
||||
### 2. 初始化 Swarm 集群
|
||||
|
||||
#### Linux/macOS
|
||||
```bash
|
||||
cd deploy
|
||||
chmod +x swarm-init.sh
|
||||
./swarm-init.sh
|
||||
```
|
||||
|
||||
#### Windows
|
||||
```cmd
|
||||
cd deploy
|
||||
swarm-init.bat
|
||||
```
|
||||
|
||||
### 3. 配置环境变量
|
||||
|
||||
编辑 `deploy/.env` 文件:
|
||||
```env
|
||||
POSTGRES_PASSWORD=your_secure_password_here
|
||||
LETSENCRYPT_EMAIL=your_email@example.com
|
||||
FLOWER_AUTH=admin:$(openssl passwd -crypt admin)
|
||||
```
|
||||
|
||||
## 详细部署步骤
|
||||
|
||||
### 步骤 1: 初始化 Docker Swarm
|
||||
|
||||
如果 Docker Swarm 未激活,运行初始化脚本:
|
||||
|
||||
```bash
|
||||
# Linux/macOS
|
||||
./swarm-init.sh init
|
||||
|
||||
# Windows
|
||||
swarm-init.bat init
|
||||
```
|
||||
|
||||
### 步骤 2: 构建镜像
|
||||
|
||||
构建所有必要的 Docker 镜像:
|
||||
|
||||
```bash
|
||||
# Linux/macOS
|
||||
./swarm-init.sh build
|
||||
|
||||
# Windows
|
||||
swarm-init.bat build
|
||||
```
|
||||
|
||||
### 步骤 3: 部署应用
|
||||
|
||||
部署整个应用到 Swarm 集群:
|
||||
|
||||
```bash
|
||||
# Linux/macOS
|
||||
./swarm-init.sh deploy
|
||||
|
||||
# Windows
|
||||
swarm-init.bat deploy
|
||||
```
|
||||
|
||||
## 服务管理
|
||||
|
||||
### 查看服务状态
|
||||
|
||||
```bash
|
||||
# 查看所有服务
|
||||
docker stack services apple-exchange
|
||||
|
||||
# 查看特定服务
|
||||
docker service ps apple-exchange_frontend
|
||||
docker service ps apple-exchange_api
|
||||
docker service ps apple-exchange_worker
|
||||
```
|
||||
|
||||
### 查看日志
|
||||
|
||||
```bash
|
||||
# 查看服务日志
|
||||
docker service logs apple-exchange_api
|
||||
docker service logs apple-exchange_frontend
|
||||
docker service logs apple-exchange_worker
|
||||
|
||||
# 使用脚本
|
||||
./swarm-init.sh logs api
|
||||
./swarm-init.sh logs frontend
|
||||
```
|
||||
|
||||
### 扩展服务
|
||||
|
||||
```bash
|
||||
# 扩展前端服务
|
||||
docker service scale apple-exchange_frontend=3
|
||||
|
||||
# 扩展 API 服务
|
||||
docker service scale apple-exchange_api=2
|
||||
|
||||
# 扩展 Worker 服务
|
||||
docker service scale apple-exchange_worker=5
|
||||
```
|
||||
|
||||
### 更新服务
|
||||
|
||||
```bash
|
||||
# 重新部署所有服务
|
||||
docker stack deploy -c deploy/docker-compose.swarm.yml apple-exchange
|
||||
|
||||
# 更新单个服务
|
||||
docker service update --image apple-exchange-api:latest apple-exchange_api
|
||||
```
|
||||
|
||||
### 移除部署
|
||||
|
||||
```bash
|
||||
# 移除整个 stack
|
||||
docker stack rm apple-exchange
|
||||
|
||||
# 使用脚本
|
||||
./swarm-init.sh remove
|
||||
```
|
||||
|
||||
## 服务架构
|
||||
|
||||
### 核心服务
|
||||
|
||||
1. **frontend** - Next.js 前端应用
|
||||
- 副本数: 2
|
||||
- 端口: 8080 (内部)
|
||||
- 限制: 0.5 CPU, 512MB 内存
|
||||
|
||||
2. **api** - FastAPI 后端服务
|
||||
- 副本数: 2
|
||||
- 端口: 8000 (内部)
|
||||
- 限制: 1 CPU, 1GB 内存
|
||||
|
||||
3. **worker** - Celery 工作进程
|
||||
- 副本数: 3
|
||||
- 限制: 2 CPU, 2GB 内存
|
||||
- 部署约束: 仅在 worker 节点运行
|
||||
|
||||
4. **beat** - Celery 定时任务调度器
|
||||
- 副本数: 1
|
||||
- 部署约束: 仅在 manager 节点运行
|
||||
|
||||
### 基础设施服务
|
||||
|
||||
5. **db** - PostgreSQL 数据库
|
||||
- 副本数: 1
|
||||
- 部署约束: 仅在 manager 节点运行
|
||||
- 数据持久化
|
||||
|
||||
6. **redis** - Redis 缓存和消息代理
|
||||
- 副本数: 1
|
||||
- 部署约束: 仅在 manager 节点运行
|
||||
- 数据持久化
|
||||
|
||||
7. **traefik** - 反向代理和负载均衡器
|
||||
- 副本数: 1
|
||||
- 部署约束: 仅在 manager 节点运行
|
||||
- 自动 HTTPS 证书管理
|
||||
|
||||
### 监控服务
|
||||
|
||||
8. **flower** - Celery 任务监控
|
||||
- 副本数: 1
|
||||
- 基本认证保护
|
||||
- 可选服务
|
||||
|
||||
## 网络和存储
|
||||
|
||||
### 网络配置
|
||||
|
||||
- **app-network**: Overlay 网络,用于服务间通信
|
||||
- **端口映射**:
|
||||
- 80, 443: Traefik 入口
|
||||
- 8080: Traefik Dashboard
|
||||
- 5432: PostgreSQL (可选)
|
||||
- 6379: Redis (可选)
|
||||
|
||||
### 存储卷
|
||||
|
||||
- **postgres_data**: PostgreSQL 数据
|
||||
- **redis_data**: Redis 数据
|
||||
- **shared_storage**: 共享存储
|
||||
- **playwright_browsers**: Playwright 浏览器缓存
|
||||
- **logs**: 应用日志
|
||||
- **data**: 应用数据
|
||||
- **screenshots**: 截图存储
|
||||
- **traefik_letsencrypt**: Let's Encrypt 证书
|
||||
|
||||
## 访问地址
|
||||
|
||||
部署完成后,可以通过以下地址访问服务:
|
||||
|
||||
- **主应用**: https://apple-exchange.com
|
||||
- **API**: https://api.apple-exchange.com
|
||||
- **Flower 监控**: https://flower.apple-exchange.com
|
||||
- **Traefik Dashboard**: https://traefik.apple-exchange.com
|
||||
|
||||
## 高级配置
|
||||
|
||||
### 节点标签
|
||||
|
||||
为优化部署,可以为节点添加标签:
|
||||
|
||||
```bash
|
||||
# 为数据库节点添加标签
|
||||
docker node update --label-add db=true node-name
|
||||
|
||||
# 为缓存节点添加标签
|
||||
docker node update --label-add redis=true node-name
|
||||
|
||||
# 为工作节点添加标签
|
||||
docker node update --label-add worker=true node-name
|
||||
```
|
||||
|
||||
### 资源限制
|
||||
|
||||
根据服务器配置调整资源限制:
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2.0'
|
||||
memory: 4G
|
||||
reservations:
|
||||
cpus: '1.0'
|
||||
memory: 2G
|
||||
```
|
||||
|
||||
### 健康检查
|
||||
|
||||
所有服务都配置了健康检查,确保服务正常运行:
|
||||
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **Docker Swarm 未激活**
|
||||
```bash
|
||||
docker swarm init
|
||||
```
|
||||
|
||||
2. **镜像构建失败**
|
||||
```bash
|
||||
docker build -t apple-exchange-api:latest ../backend
|
||||
```
|
||||
|
||||
3. **服务启动失败**
|
||||
```bash
|
||||
docker service logs apple-exchange_api
|
||||
```
|
||||
|
||||
4. **网络问题**
|
||||
```bash
|
||||
docker network ls
|
||||
docker network inspect apple-exchange_app-network
|
||||
```
|
||||
|
||||
### 调试命令
|
||||
|
||||
```bash
|
||||
# 查看集群状态
|
||||
docker info
|
||||
|
||||
# 查看节点状态
|
||||
docker node ls
|
||||
|
||||
# 查看服务详情
|
||||
docker service inspect apple-exchange_api
|
||||
|
||||
# 进入容器调试
|
||||
docker exec -it $(docker ps -q -f "name=apple-exchange-api") bash
|
||||
```
|
||||
|
||||
## 生产环境建议
|
||||
|
||||
### 安全性
|
||||
|
||||
1. 使用强密码和环境变量
|
||||
2. 启用 HTTPS 和 Let's Encrypt
|
||||
3. 限制 Traefik Dashboard 访问
|
||||
4. 定期更新基础镜像
|
||||
|
||||
### 监控
|
||||
|
||||
1. 启用 Flower 监控
|
||||
2. 配置日志收集
|
||||
3. 设置告警规则
|
||||
4. 监控资源使用情况
|
||||
|
||||
### 备份
|
||||
|
||||
1. 定期备份数据库
|
||||
2. 备份 Redis 数据
|
||||
3. 备份配置文件
|
||||
4. 测试恢复流程
|
||||
|
||||
### 扩展性
|
||||
|
||||
1. 水平扩展服务副本
|
||||
2. 使用多节点集群
|
||||
3. 配置负载均衡
|
||||
4. 优化资源使用
|
||||
@@ -1,5 +1,5 @@
|
||||
# Apple Gift Card Exchange Platform - Combined Docker Compose
|
||||
# 包含前端和后端的完整部署配置
|
||||
# Apple Gift Card Exchange Platform - Docker Swarm Stack Configuration
|
||||
# 适用于 Docker Swarm 生产环境部署
|
||||
|
||||
services:
|
||||
# ===== 前端服务 =====
|
||||
@@ -7,14 +7,10 @@ services:
|
||||
build:
|
||||
context: ../frontend
|
||||
dockerfile: Dockerfile
|
||||
container_name: apple-exchange-frontend
|
||||
ports:
|
||||
- "3000:8080"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- NEXT_PUBLIC_ENV=production
|
||||
- NEXT_PUBLIC_API_URL=http://api:8000/api
|
||||
- NEXT_PUBLIC_TELEMETRY_ENDPOINT=http://otel-collector:4318/v1/traces
|
||||
networks:
|
||||
- app-network
|
||||
depends_on:
|
||||
@@ -27,11 +23,14 @@ services:
|
||||
start_period: 40s
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
replicas: 2
|
||||
update_config:
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
failure_action: rollback
|
||||
rollback_config:
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
delay: 5s
|
||||
@@ -44,17 +43,18 @@ services:
|
||||
reservations:
|
||||
cpus: '0.2'
|
||||
memory: 256M
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
# ===== 后端 API 服务 =====
|
||||
api:
|
||||
build:
|
||||
context: ../backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: apple-exchange-api
|
||||
environment:
|
||||
- SERVICE_TYPE=api
|
||||
- ENVIRONMENT=production
|
||||
- DATABASE_URL=postgresql+asyncpg://postgres:password@db:5432/apple_exchange
|
||||
- DATABASE_URL=postgresql+asyncpg://postgres:${POSTGRES_PASSWORD}@db:5432/apple_exchange
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- CELERY_BROKER_URL=redis://redis:6379/0
|
||||
- CELERY_RESULT_BACKEND=redis://redis:6379/1
|
||||
@@ -66,8 +66,6 @@ services:
|
||||
- data:/app/data
|
||||
- screenshots:/app/screenshots
|
||||
- shared_storage:/app/shared
|
||||
ports:
|
||||
- "8000:8000"
|
||||
networks:
|
||||
- app-network
|
||||
depends_on:
|
||||
@@ -81,7 +79,7 @@ services:
|
||||
start_period: 40s
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
replicas: 2
|
||||
update_config:
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
@@ -101,17 +99,18 @@ services:
|
||||
reservations:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
ports:
|
||||
- "8000:8000"
|
||||
|
||||
# ===== Celery Worker 服务 =====
|
||||
worker:
|
||||
build:
|
||||
context: ../backend
|
||||
dockerfile: Dockerfile.worker
|
||||
container_name: apple-exchange-worker
|
||||
environment:
|
||||
- SERVICE_TYPE=worker
|
||||
- ENVIRONMENT=production
|
||||
- DATABASE_URL=postgresql+asyncpg://postgres:password@db:5432/apple_exchange
|
||||
- DATABASE_URL=postgresql+asyncpg://postgres:${POSTGRES_PASSWORD}@db:5432/apple_exchange
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- CELERY_BROKER_URL=redis://redis:6379/0
|
||||
- CELERY_RESULT_BACKEND=redis://redis:6379/1
|
||||
@@ -140,7 +139,7 @@ services:
|
||||
start_period: 60s
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
replicas: 3
|
||||
update_config:
|
||||
parallelism: 2
|
||||
delay: 10s
|
||||
@@ -160,17 +159,19 @@ services:
|
||||
reservations:
|
||||
cpus: '1.0'
|
||||
memory: 1G
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == worker
|
||||
|
||||
# ===== Celery Beat 调度服务 =====
|
||||
beat:
|
||||
build:
|
||||
context: ../backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: apple-exchange-beat
|
||||
environment:
|
||||
- SERVICE_TYPE=beat
|
||||
- ENVIRONMENT=production
|
||||
- DATABASE_URL=postgresql+asyncpg://postgres:password@db:5432/apple_exchange
|
||||
- DATABASE_URL=postgresql+asyncpg://postgres:${POSTGRES_PASSWORD}@db:5432/apple_exchange
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- CELERY_BROKER_URL=redis://redis:6379/0
|
||||
- CELERY_RESULT_BACKEND=redis://redis:6379/1
|
||||
@@ -207,19 +208,19 @@ services:
|
||||
reservations:
|
||||
cpus: '0.2'
|
||||
memory: 256M
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
|
||||
# ===== PostgreSQL 数据库 =====
|
||||
db:
|
||||
image: postgres:17-alpine
|
||||
container_name: apple-exchange-db
|
||||
environment:
|
||||
- POSTGRES_DB=apple_exchange
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=password
|
||||
- POSTGRES_PASSWORD=ajNHYrqSNaTdPFq3
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
networks:
|
||||
- app-network
|
||||
healthcheck:
|
||||
@@ -246,17 +247,16 @@ services:
|
||||
reservations:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
- node.labels.db == true
|
||||
|
||||
# ===== Redis 缓存和消息代理 =====
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: apple-exchange-redis
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
- ./deploy/redis.conf:/usr/local/etc/redis/redis.conf
|
||||
command: redis-server /usr/local/etc/redis/redis.conf
|
||||
ports:
|
||||
- "6379:6379"
|
||||
networks:
|
||||
- app-network
|
||||
healthcheck:
|
||||
@@ -283,39 +283,10 @@ services:
|
||||
reservations:
|
||||
cpus: '0.2'
|
||||
memory: 256M
|
||||
|
||||
# ===== 可选:Celery Flower 监控服务 =====
|
||||
flower:
|
||||
build:
|
||||
context: ../backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: apple-exchange-flower
|
||||
environment:
|
||||
- SERVICE_TYPE=flower
|
||||
- ENVIRONMENT=production
|
||||
- DATABASE_URL=postgresql+asyncpg://postgres:password@db:5432/apple_exchange
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- CELERY_BROKER_URL=redis://redis:6379/0
|
||||
- CELERY_RESULT_BACKEND=redis://redis:6379/1
|
||||
ports:
|
||||
- "5555:5555"
|
||||
networks:
|
||||
- app-network
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
profiles:
|
||||
- monitoring
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
reservations:
|
||||
cpus: '0.2'
|
||||
memory: 256M
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
- node.labels.redis == true
|
||||
|
||||
|
||||
# ===== 数据卷 =====
|
||||
@@ -345,8 +316,6 @@ volumes:
|
||||
# ===== 网络 =====
|
||||
networks:
|
||||
app-network:
|
||||
driver: bridge
|
||||
driver: overlay
|
||||
name: apple-exchange-network
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.20.0.0/16
|
||||
attachable: true
|
||||
218
deploy/docker-compose.yml
Normal file
218
deploy/docker-compose.yml
Normal file
@@ -0,0 +1,218 @@
|
||||
# Apple Gift Card Exchange Platform - Combined Docker Compose
|
||||
# 包含前端和后端的完整部署配置
|
||||
|
||||
services:
|
||||
# ===== 前端服务 =====
|
||||
frontend:
|
||||
build:
|
||||
context: ../frontend
|
||||
dockerfile: Dockerfile
|
||||
container_name: apple-exchange-frontend
|
||||
ports:
|
||||
- "3000:8080"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- NEXT_PUBLIC_ENV=production
|
||||
networks:
|
||||
- app-network
|
||||
depends_on:
|
||||
- api
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
# ===== 后端 API 服务 =====
|
||||
api:
|
||||
build:
|
||||
context: ../backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: apple-exchange-api
|
||||
environment:
|
||||
- SERVICE_TYPE=api
|
||||
- ENVIRONMENT=production
|
||||
- DATABASE_URL=postgresql+asyncpg://postgres:password@db:5432/apple_exchange
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- CELERY_BROKER_URL=redis://redis:6379/0
|
||||
- CELERY_RESULT_BACKEND=redis://redis:6379/1
|
||||
- WORKERS=4
|
||||
- SCREENSHOT_DIR=/app/screenshots
|
||||
- LOG_DIR=/app/logs
|
||||
volumes:
|
||||
- logs:/app/logs
|
||||
- data:/app/data
|
||||
- screenshots:/app/screenshots
|
||||
- shared_storage:/app/shared
|
||||
networks:
|
||||
- app-network
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/health/liveness"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
# ===== Celery Worker 服务 =====
|
||||
worker:
|
||||
build:
|
||||
context: ../backend
|
||||
dockerfile: Dockerfile.worker
|
||||
container_name: apple-exchange-worker
|
||||
environment:
|
||||
- SERVICE_TYPE=worker
|
||||
- ENVIRONMENT=production
|
||||
- DATABASE_URL=postgresql+asyncpg://postgres:password@db:5432/apple_exchange
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- CELERY_BROKER_URL=redis://redis:6379/0
|
||||
- CELERY_RESULT_BACKEND=redis://redis:6379/1
|
||||
- CELERY_CONCURRENCY=2
|
||||
- CELERY_MAX_TASKS_PER_CHILD=1000
|
||||
- CELERY_PREFETCH_MULTIPLIER=1
|
||||
- SCREENSHOT_DIR=/app/screenshots
|
||||
- LOG_DIR=/app/logs
|
||||
- PLAYWRIGHT_BROWSERS_PATH=/app/playwright-browsers
|
||||
volumes:
|
||||
- logs:/app/logs
|
||||
- data:/app/data
|
||||
- screenshots:/app/screenshots
|
||||
- shared_storage:/app/shared
|
||||
- playwright_browsers:/app/playwright-browsers
|
||||
networks:
|
||||
- app-network
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "from app.core.celery_app import get_celery_app; app = get_celery_app(); print('Worker healthy')"]
|
||||
interval: 60s
|
||||
timeout: 30s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
# ===== Celery Beat 调度服务 =====
|
||||
beat:
|
||||
build:
|
||||
context: ../backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: apple-exchange-beat
|
||||
environment:
|
||||
- SERVICE_TYPE=beat
|
||||
- ENVIRONMENT=production
|
||||
- DATABASE_URL=postgresql+asyncpg://postgres:password@db:5432/apple_exchange
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- CELERY_BROKER_URL=redis://redis:6379/0
|
||||
- CELERY_RESULT_BACKEND=redis://redis:6379/1
|
||||
volumes:
|
||||
- logs:/app/logs
|
||||
- data:/app/data
|
||||
networks:
|
||||
- app-network
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
# ===== PostgreSQL 数据库 =====
|
||||
db:
|
||||
image: postgres:17-alpine
|
||||
container_name: apple-exchange-db
|
||||
environment:
|
||||
- POSTGRES_DB=apple_exchange
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5444:5432"
|
||||
networks:
|
||||
- app-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# ===== Redis 缓存和消息代理 =====
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: apple-exchange-redis
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
- ./deploy/redis.conf:/usr/local/etc/redis/redis.conf
|
||||
command: redis-server /usr/local/etc/redis/redis.conf
|
||||
ports:
|
||||
- "6379:6379"
|
||||
networks:
|
||||
- app-network
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# ===== 可选:Celery Flower 监控服务 =====
|
||||
flower:
|
||||
build:
|
||||
context: ../backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: apple-exchange-flower
|
||||
environment:
|
||||
- SERVICE_TYPE=flower
|
||||
- ENVIRONMENT=production
|
||||
- DATABASE_URL=postgresql+asyncpg://postgres:password@db:5432/apple_exchange
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- CELERY_BROKER_URL=redis://redis:6379/0
|
||||
- CELERY_RESULT_BACKEND=redis://redis:6379/1
|
||||
ports:
|
||||
- "5555:5555"
|
||||
networks:
|
||||
- app-network
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
profiles:
|
||||
- monitoring
|
||||
|
||||
|
||||
# ===== 数据卷 =====
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
||||
name: apple-exchange-postgres-data
|
||||
redis_data:
|
||||
driver: local
|
||||
name: apple-exchange-redis-data
|
||||
shared_storage:
|
||||
driver: local
|
||||
name: apple-exchange-shared-storage
|
||||
playwright_browsers:
|
||||
driver: local
|
||||
name: apple-exchange-playwright-browsers
|
||||
logs:
|
||||
driver: local
|
||||
name: apple-exchange-logs
|
||||
data:
|
||||
driver: local
|
||||
name: apple-exchange-data
|
||||
screenshots:
|
||||
driver: local
|
||||
name: apple-exchange-screenshots
|
||||
|
||||
# ===== 网络 =====
|
||||
networks:
|
||||
app-network:
|
||||
driver: bridge
|
||||
name: apple-exchange-network
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.20.0.0/16
|
||||
@@ -1,42 +0,0 @@
|
||||
# Redis configuration for Apple Exchange Platform
|
||||
|
||||
# Basic settings
|
||||
port 6379
|
||||
bind 0.0.0.0
|
||||
|
||||
# Memory management
|
||||
maxmemory 512mb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# Persistence
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
|
||||
# Security
|
||||
protected-mode no
|
||||
|
||||
# Logging
|
||||
loglevel notice
|
||||
|
||||
# Network
|
||||
timeout 0
|
||||
tcp-keepalive 300
|
||||
|
||||
# Clients
|
||||
maxclients 10000
|
||||
|
||||
# Advanced config
|
||||
rdbcompression yes
|
||||
rdbchecksum yes
|
||||
stop-writes-on-bgsave-error yes
|
||||
|
||||
# Lua scripting
|
||||
lua-time-limit 5000
|
||||
|
||||
# Slow log
|
||||
slowlog-log-slower-than 10000
|
||||
slowlog-max-len 128
|
||||
|
||||
# Latency monitoring
|
||||
latency-monitor-threshold 100
|
||||
352
deploy/swarm-init.bat
Normal file
352
deploy/swarm-init.bat
Normal file
@@ -0,0 +1,352 @@
|
||||
@echo off
|
||||
REM Apple Gift Card Exchange Platform - Docker Swarm 初始化脚本 (Windows)
|
||||
REM 用于初始化 Docker Swarm 集群和部署应用
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM 配置变量
|
||||
set STACK_NAME=apple-exchange
|
||||
set DEPLOY_DIR=%~dp0
|
||||
set COMPOSE_FILE=%DEPLOY_DIR%docker-compose.swarm.yml
|
||||
|
||||
REM 颜色定义 (Windows 10+)
|
||||
for /f "tokens=4-5 delims=. " %%i in ('ver') do set VERSION=%%i.%%j
|
||||
if "%VERSION%" geq "10.0" (
|
||||
set "RED=[91m"
|
||||
set "GREEN=[92m"
|
||||
set "YELLOW=[93m"
|
||||
set "BLUE=[94m"
|
||||
set "NC=[0m"
|
||||
) else (
|
||||
set "RED="
|
||||
set "GREEN="
|
||||
set "YELLOW="
|
||||
set "BLUE="
|
||||
set "NC="
|
||||
)
|
||||
|
||||
REM 日志函数
|
||||
:log_info
|
||||
echo %BLUE%[INFO]%NC% %~1
|
||||
goto :eof
|
||||
|
||||
:log_success
|
||||
echo %GREEN%[SUCCESS]%NC% %~1
|
||||
goto :eof
|
||||
|
||||
:log_warning
|
||||
echo %YELLOW%[WARNING]%NC% %~1
|
||||
goto :eof
|
||||
|
||||
:log_error
|
||||
echo %RED%[ERROR]%NC% %~1
|
||||
goto :eof
|
||||
|
||||
REM 检查 Docker 是否运行
|
||||
:check_docker
|
||||
call :log_info "检查 Docker 运行状态..."
|
||||
docker info >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
call :log_error "Docker 未运行,请启动 Docker 后重试"
|
||||
exit /b 1
|
||||
)
|
||||
call :log_success "Docker 运行正常"
|
||||
goto :eof
|
||||
|
||||
REM 检查 Docker Swarm 是否已初始化
|
||||
:check_swarm
|
||||
call :log_info "检查 Docker Swarm 状态..."
|
||||
docker info | findstr /C:"Swarm: active" >nul
|
||||
if errorlevel 1 (
|
||||
call :log_warning "Docker Swarm 未激活"
|
||||
exit /b 1
|
||||
) else (
|
||||
call :log_success "Docker Swarm 已激活"
|
||||
exit /b 0
|
||||
)
|
||||
goto :eof
|
||||
|
||||
REM 初始化 Docker Swarm
|
||||
:init_swarm
|
||||
call :log_info "初始化 Docker Swarm..."
|
||||
docker swarm init
|
||||
if errorlevel 1 (
|
||||
call :log_error "Docker Swarm 初始化失败"
|
||||
exit /b 1
|
||||
)
|
||||
call :log_success "Docker Swarm 初始化成功"
|
||||
|
||||
REM 添加节点标签
|
||||
call :log_info "添加节点标签..."
|
||||
for /f "delims=" %%i in ('docker node ls --filter role=manager -q') do (
|
||||
docker node update --label-add db=true %%i >nul 2>&1
|
||||
docker node update --label-add redis=true %%i >nul 2>&1
|
||||
)
|
||||
call :log_success "节点标签设置完成"
|
||||
goto :eof
|
||||
|
||||
REM 创建必要的配置文件
|
||||
:create_configs
|
||||
call :log_info "创建 Docker 配置..."
|
||||
|
||||
REM 创建 redis.conf
|
||||
echo # Redis 配置文件> "%DEPLOY_DIR%redis.conf"
|
||||
echo bind 0.0.0.0>> "%DEPLOY_DIR%redis.conf"
|
||||
echo port 6379>> "%DEPLOY_DIR%redis.conf"
|
||||
echo protected-mode no>> "%DEPLOY_DIR%redis.conf"
|
||||
echo timeout 0>> "%DEPLOY_DIR%redis.conf"
|
||||
echo tcp-keepalive 300>> "%DEPLOY_DIR%redis.conf"
|
||||
echo daemonize no>> "%DEPLOY_DIR%redis.conf"
|
||||
echo supervised no>> "%DEPLOY_DIR%redis.conf"
|
||||
echo loglevel notice>> "%DEPLOY_DIR%redis.conf"
|
||||
echo logfile "">> "%DEPLOY_DIR%redis.conf"
|
||||
echo databases 16>> "%DEPLOY_DIR%redis.conf"
|
||||
echo always-show-logo yes>> "%DEPLOY_DIR%redis.conf"
|
||||
echo save 900 1>> "%DEPLOY_DIR%redis.conf"
|
||||
echo save 300 10>> "%DEPLOY_DIR%redis.conf"
|
||||
echo save 60 10000>> "%DEPLOY_DIR%redis.conf"
|
||||
echo stop-writes-on-bgsave-error yes>> "%DEPLOY_DIR%redis.conf"
|
||||
echo rdbcompression yes>> "%DEPLOY_DIR%redis.conf"
|
||||
echo rdbchecksum yes>> "%DEPLOY_DIR%redis.conf"
|
||||
echo dbfilename dump.rdb>> "%DEPLOY_DIR%redis.conf"
|
||||
echo dir /data>> "%DEPLOY_DIR%redis.conf"
|
||||
echo replica-serve-stale-data yes>> "%DEPLOY_DIR%redis.conf"
|
||||
echo replica-read-only yes>> "%DEPLOY_DIR%redis.conf"
|
||||
echo repl-diskless-sync no>> "%DEPLOY_DIR%redis.conf"
|
||||
echo repl-diskless-sync-delay 5>> "%DEPLOY_DIR%redis.conf"
|
||||
echo repl-ping-replica-period 10>> "%DEPLOY_DIR%redis.conf"
|
||||
echo repl-timeout 60>> "%DEPLOY_DIR%redis.conf"
|
||||
echo repl-disable-tcp-nodelay no>> "%DEPLOY_DIR%redis.conf"
|
||||
echo repl-backlog-size 1mb>> "%DEPLOY_DIR%redis.conf"
|
||||
echo repl-backlog-ttl 3600>> "%DEPLOY_DIR%redis.conf"
|
||||
echo replica-priority 100>> "%DEPLOY_DIR%redis.conf"
|
||||
echo maxmemory-policy allkeys-lru>> "%DEPLOY_DIR%redis.conf"
|
||||
echo lazyfree-lazy-eviction no>> "%DEPLOY_DIR%redis.conf"
|
||||
echo lazyfree-lazy-expire no>> "%DEPLOY_DIR%redis.conf"
|
||||
echo lazyfree-lazy-server-del no>> "%DEPLOY_DIR%redis.conf"
|
||||
echo replica-lazy-flush no>> "%DEPLOY_DIR%redis.conf"
|
||||
echo appendonly yes>> "%DEPLOY_DIR%redis.conf"
|
||||
echo appendfilename "appendonly.aof">> "%DEPLOY_DIR%redis.conf"
|
||||
echo appendfsync everysec>> "%DEPLOY_DIR%redis.conf"
|
||||
echo no-appendfsync-on-rewrite no>> "%DEPLOY_DIR%redis.conf"
|
||||
echo auto-aof-rewrite-percentage 100>> "%DEPLOY_DIR%redis.conf"
|
||||
echo auto-aof-rewrite-min-size 64mb>> "%DEPLOY_DIR%redis.conf"
|
||||
echo aof-load-truncated yes>> "%DEPLOY_DIR%redis.conf"
|
||||
echo aof-use-rdb-preamble yes>> "%DEPLOY_DIR%redis.conf"
|
||||
echo lua-time-limit 5000>> "%DEPLOY_DIR%redis.conf"
|
||||
echo slowlog-log-slower-than 10000>> "%DEPLOY_DIR%redis.conf"
|
||||
echo slowlog-max-len 128>> "%DEPLOY_DIR%redis.conf"
|
||||
echo latency-monitor-threshold 0>> "%DEPLOY_DIR%redis.conf"
|
||||
echo notify-keyspace-events "">> "%DEPLOY_DIR%redis.conf"
|
||||
echo hash-max-ziplist-entries 512>> "%DEPLOY_DIR%redis.conf"
|
||||
echo hash-max-ziplist-value 64>> "%DEPLOY_DIR%redis.conf"
|
||||
echo list-max-ziplist-size -2>> "%DEPLOY_DIR%redis.conf"
|
||||
echo list-compress-depth 0>> "%DEPLOY_DIR%redis.conf"
|
||||
echo set-max-intset-entries 512>> "%DEPLOY_DIR%redis.conf"
|
||||
echo zset-max-ziplist-entries 128>> "%DEPLOY_DIR%redis.conf"
|
||||
echo zset-max-ziplist-value 64>> "%DEPLOY_DIR%redis.conf"
|
||||
echo hll-sparse-max-bytes 3000>> "%DEPLOY_DIR%redis.conf"
|
||||
echo stream-node-max-bytes 4096>> "%DEPLOY_DIR%redis.conf"
|
||||
echo stream-node-max-entries 100>> "%DEPLOY_DIR%redis.conf"
|
||||
echo activerehashing yes>> "%DEPLOY_DIR%redis.conf"
|
||||
echo client-output-buffer-limit normal 0 0 0>> "%DEPLOY_DIR%redis.conf"
|
||||
echo client-output-buffer-limit replica 256mb 64mb 60>> "%DEPLOY_DIR%redis.conf"
|
||||
echo client-output-buffer-limit pubsub 32mb 8mb 60>> "%DEPLOY_DIR%redis.conf"
|
||||
echo hz 10>> "%DEPLOY_DIR%redis.conf"
|
||||
echo dynamic-hz yes>> "%DEPLOY_DIR%redis.conf"
|
||||
echo aof-rewrite-incremental-fsync yes>> "%DEPLOY_DIR%redis.conf"
|
||||
echo rdb-save-incremental-fsync yes>> "%DEPLOY_DIR%redis.conf"
|
||||
|
||||
REM 创建 postgresql.conf
|
||||
echo # PostgreSQL 配置文件> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo listen_addresses = '*'>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo port = 5432>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo max_connections = 200>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo shared_buffers = 256MB>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo effective_cache_size = 1GB>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo maintenance_work_mem = 64MB>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo checkpoint_completion_target = 0.9>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo wal_buffers = 16MB>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo default_statistics_target = 100>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo random_page_cost = 1.1>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo effective_io_concurrency = 200>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo work_mem = 4MB>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo min_wal_size = 1GB>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo max_wal_size = 4GB>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo max_worker_processes = 4>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo max_parallel_workers_per_gather = 2>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo max_parallel_workers = 4>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo max_parallel_maintenance_workers = 2>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo log_line_prefix = '%%t [%%p]: [%%l-1] user=%%u,db=%%d,app=%%a,client=%%h '>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo log_timezone = 'UTC'>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo datestyle = 'iso, mdy'>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo timezone = 'UTC'>> "%DEPLOY_DIR%postgresql.conf"
|
||||
echo default_text_search_config = 'pg_catalog.english'>> "%DEPLOY_DIR%postgresql.conf"
|
||||
|
||||
REM 创建 .env 文件模板
|
||||
echo # 环境变量配置> "%DEPLOY_DIR%.env.template"
|
||||
echo POSTGRES_PASSWORD=your_secure_password_here>> "%DEPLOY_DIR%.env.template"
|
||||
echo LETSENCRYPT_EMAIL=your_email@example.com>> "%DEPLOY_DIR%.env.template"
|
||||
echo FLOWER_AUTH=admin:$(openssl passwd -crypt admin)>> "%DEPLOY_DIR%.env.template"
|
||||
|
||||
call :log_success "配置文件创建完成"
|
||||
goto :eof
|
||||
|
||||
REM 构建镜像
|
||||
:build_images
|
||||
call :log_info "构建 Docker 镜像..."
|
||||
|
||||
REM 构建前端镜像
|
||||
if exist "..\frontend" (
|
||||
call :log_info "构建前端镜像..."
|
||||
docker build -t apple-exchange-frontend:latest ..\frontend
|
||||
if errorlevel 1 (
|
||||
call :log_error "前端镜像构建失败"
|
||||
exit /b 1
|
||||
)
|
||||
call :log_success "前端镜像构建完成"
|
||||
)
|
||||
|
||||
REM 构建后端镜像
|
||||
if exist "..\backend" (
|
||||
call :log_info "构建后端镜像..."
|
||||
docker build -t apple-exchange-api:latest ..\backend
|
||||
if errorlevel 1 (
|
||||
call :log_error "后端镜像构建失败"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 构建 Worker 镜像
|
||||
if exist "..\backend\Dockerfile.worker" (
|
||||
docker build -f ..\backend\Dockerfile.worker -t apple-exchange-worker:latest ..\backend
|
||||
if errorlevel 1 (
|
||||
call :log_error "Worker 镜像构建失败"
|
||||
exit /b 1
|
||||
)
|
||||
) else (
|
||||
REM 如果没有专门的 Dockerfile.worker,使用 API 镜像
|
||||
docker tag apple-exchange-api:latest apple-exchange-worker:latest
|
||||
)
|
||||
|
||||
call :log_success "后端镜像构建完成"
|
||||
)
|
||||
goto :eof
|
||||
|
||||
REM 部署 Stack
|
||||
:deploy_stack
|
||||
call :log_info "部署 Docker Stack: %STACK_NAME%"
|
||||
|
||||
REM 检查环境变量文件
|
||||
if not exist "%DEPLOY_DIR%.env" (
|
||||
call :log_warning "环境变量文件 .env 不存在,使用模板创建"
|
||||
copy "%DEPLOY_DIR%.env.template" "%DEPLOY_DIR%.env" >nul
|
||||
call :log_warning "请编辑 .env 文件设置正确的环境变量"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 部署 Stack
|
||||
docker stack deploy -c "%COMPOSE_FILE%" %STACK_NAME%
|
||||
if errorlevel 1 (
|
||||
call :log_error "Stack 部署失败"
|
||||
exit /b 1
|
||||
)
|
||||
call :log_success "Stack 部署成功"
|
||||
goto :eof
|
||||
|
||||
REM 显示部署状态
|
||||
:show_status
|
||||
call :log_info "部署状态:"
|
||||
echo ----------------------------------------
|
||||
docker stack services %STACK_NAME%
|
||||
echo ----------------------------------------
|
||||
|
||||
call :log_info "查看服务状态:"
|
||||
echo docker stack services %STACK_NAME%
|
||||
echo docker service ps %STACK_NAME%_frontend
|
||||
echo docker service ps %STACK_NAME%_api
|
||||
echo docker service ps %STACK_NAME%_worker
|
||||
echo docker service ps %STACK_NAME%_db
|
||||
echo docker service ps %STACK_NAME%_redis
|
||||
|
||||
call :log_info "查看日志:"
|
||||
echo docker service logs %STACK_NAME%_frontend
|
||||
echo docker service logs %STACK_NAME%_api
|
||||
echo docker service logs %STACK_NAME%_worker
|
||||
|
||||
call :log_info "访问地址:"
|
||||
echo 应用: https://apple-exchange.com
|
||||
echo API: https://api.apple-exchange.com
|
||||
echo Flower 监控: https://flower.apple-exchange.com
|
||||
echo Traefik Dashboard: https://traefik.apple-exchange.com
|
||||
goto :eof
|
||||
|
||||
REM 主函数
|
||||
:main
|
||||
call :log_info "开始 Docker Swarm 部署..."
|
||||
|
||||
REM 检查必要的文件
|
||||
if not exist "%COMPOSE_FILE%" (
|
||||
call :log_error "Docker Compose 文件不存在: %COMPOSE_FILE%"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 检查 Docker
|
||||
call :check_docker
|
||||
|
||||
REM 检查 Swarm
|
||||
call :check_swarm
|
||||
if errorlevel 1 (
|
||||
call :init_swarm
|
||||
)
|
||||
|
||||
REM 创建配置文件
|
||||
call :create_configs
|
||||
|
||||
REM 构建镜像
|
||||
call :build_images
|
||||
|
||||
REM 部署 Stack
|
||||
call :deploy_stack
|
||||
|
||||
REM 显示状态
|
||||
call :show_status
|
||||
|
||||
call :log_success "Docker Swarm 部署完成!"
|
||||
goto :eof
|
||||
|
||||
REM 脚本选项
|
||||
if "%1"=="" goto main
|
||||
if "%1%"=="init" (
|
||||
call :check_docker
|
||||
call :check_swarm
|
||||
if errorlevel 1 (
|
||||
call :init_swarm
|
||||
)
|
||||
goto :eof
|
||||
)
|
||||
if "%1%"=="build" (
|
||||
call :build_images
|
||||
goto :eof
|
||||
)
|
||||
if "%1%"=="deploy" (
|
||||
call :deploy_stack
|
||||
goto :eof
|
||||
)
|
||||
if "%1%"=="status" (
|
||||
call :show_status
|
||||
goto :eof
|
||||
)
|
||||
if "%1%"=="remove" (
|
||||
call :log_info "移除 Stack: %STACK_NAME%"
|
||||
docker stack rm %STACK_NAME%
|
||||
goto :eof
|
||||
)
|
||||
if "%1%"=="logs" (
|
||||
if "%2%"=="" (
|
||||
docker service logs %STACK_NAME%_api
|
||||
) else (
|
||||
docker service logs %STACK_NAME%_%2%
|
||||
)
|
||||
goto :eof
|
||||
)
|
||||
goto main
|
||||
346
deploy/swarm-init.sh
Normal file
346
deploy/swarm-init.sh
Normal file
@@ -0,0 +1,346 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Apple Gift Card Exchange Platform - Docker Swarm 初始化脚本
|
||||
# 用于初始化 Docker Swarm 集群和部署应用
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 配置变量
|
||||
STACK_NAME="apple-exchange"
|
||||
DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COMPOSE_FILE="${DEPLOY_DIR}/docker-compose.swarm.yml"
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查 Docker 是否运行
|
||||
check_docker() {
|
||||
log_info "检查 Docker 运行状态..."
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
log_error "Docker 未运行,请启动 Docker 后重试"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Docker 运行正常"
|
||||
}
|
||||
|
||||
# 检查 Docker Swarm 是否已初始化
|
||||
check_swarm() {
|
||||
log_info "检查 Docker Swarm 状态..."
|
||||
if docker info | grep -q "Swarm: active"; then
|
||||
log_success "Docker Swarm 已激活"
|
||||
return 0
|
||||
else
|
||||
log_warning "Docker Swarm 未激活"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 初始化 Docker Swarm
|
||||
init_swarm() {
|
||||
log_info "初始化 Docker Swarm..."
|
||||
|
||||
# 获取当前主机 IP
|
||||
local advertise_addr=$(ip route get 8.8.8.8 | awk '{print $7; exit}')
|
||||
|
||||
if docker swarm init --advertise-addr "${advertise_addr}"; then
|
||||
log_success "Docker Swarm 初始化成功"
|
||||
|
||||
# 添加节点标签
|
||||
log_info "添加节点标签..."
|
||||
docker node update --label-add db=true $(docker node ls --filter role=manager -q) >/dev/null 2>&1 || true
|
||||
docker node update --label-add redis=true $(docker node ls --filter role=manager -q) >/dev/null 2>&1 || true
|
||||
|
||||
log_success "节点标签设置完成"
|
||||
else
|
||||
log_error "Docker Swarm 初始化失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 创建必要的配置文件
|
||||
create_configs() {
|
||||
log_info "创建 Docker 配置..."
|
||||
|
||||
# 创建 redis.conf
|
||||
cat > "${DEPLOY_DIR}/redis.conf" << 'EOF'
|
||||
# Redis 配置文件
|
||||
bind 0.0.0.0
|
||||
port 6379
|
||||
protected-mode no
|
||||
tcp-backlog 511
|
||||
timeout 0
|
||||
tcp-keepalive 300
|
||||
daemonize no
|
||||
supervised no
|
||||
pidfile /var/run/redis_6379.pid
|
||||
loglevel notice
|
||||
logfile ""
|
||||
databases 16
|
||||
always-show-logo yes
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
stop-writes-on-bgsave-error yes
|
||||
rdbcompression yes
|
||||
rdbchecksum yes
|
||||
dbfilename dump.rdb
|
||||
dir /data
|
||||
replica-serve-stale-data yes
|
||||
replica-read-only yes
|
||||
repl-diskless-sync no
|
||||
repl-diskless-sync-delay 5
|
||||
repl-ping-replica-period 10
|
||||
repl-timeout 60
|
||||
repl-disable-tcp-nodelay no
|
||||
repl-backlog-size 1mb
|
||||
repl-backlog-ttl 3600
|
||||
replica-priority 100
|
||||
maxmemory-policy allkeys-lru
|
||||
lazyfree-lazy-eviction no
|
||||
lazyfree-lazy-expire no
|
||||
lazyfree-lazy-server-del no
|
||||
replica-lazy-flush no
|
||||
appendonly yes
|
||||
appendfilename "appendonly.aof"
|
||||
appendfsync everysec
|
||||
no-appendfsync-on-rewrite no
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
aof-load-truncated yes
|
||||
aof-use-rdb-preamble yes
|
||||
lua-time-limit 5000
|
||||
slowlog-log-slower-than 10000
|
||||
slowlog-max-len 128
|
||||
latency-monitor-threshold 0
|
||||
notify-keyspace-events ""
|
||||
hash-max-ziplist-entries 512
|
||||
hash-max-ziplist-value 64
|
||||
list-max-ziplist-size -2
|
||||
list-compress-depth 0
|
||||
set-max-intset-entries 512
|
||||
zset-max-ziplist-entries 128
|
||||
zset-max-ziplist-value 64
|
||||
hll-sparse-max-bytes 3000
|
||||
stream-node-max-bytes 4096
|
||||
stream-node-max-entries 100
|
||||
activerehashing yes
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit replica 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
hz 10
|
||||
dynamic-hz yes
|
||||
aof-rewrite-incremental-fsync yes
|
||||
rdb-save-incremental-fsync yes
|
||||
EOF
|
||||
|
||||
# 创建 postgresql.conf
|
||||
cat > "${DEPLOY_DIR}/postgresql.conf" << 'EOF'
|
||||
# PostgreSQL 配置文件
|
||||
listen_addresses = '*'
|
||||
port = 5432
|
||||
max_connections = 200
|
||||
shared_buffers = 256MB
|
||||
effective_cache_size = 1GB
|
||||
maintenance_work_mem = 64MB
|
||||
checkpoint_completion_target = 0.9
|
||||
wal_buffers = 16MB
|
||||
default_statistics_target = 100
|
||||
random_page_cost = 1.1
|
||||
effective_io_concurrency = 200
|
||||
work_mem = 4MB
|
||||
min_wal_size = 1GB
|
||||
max_wal_size = 4GB
|
||||
max_worker_processes = 4
|
||||
max_parallel_workers_per_gather = 2
|
||||
max_parallel_workers = 4
|
||||
max_parallel_maintenance_workers = 2
|
||||
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
|
||||
log_timezone = 'UTC'
|
||||
datestyle = 'iso, mdy'
|
||||
timezone = 'UTC'
|
||||
default_text_search_config = 'pg_catalog.english'
|
||||
EOF
|
||||
|
||||
# 创建 .env 文件模板
|
||||
cat > "${DEPLOY_DIR}/.env.template" << 'EOF'
|
||||
# 环境变量配置
|
||||
POSTGRES_PASSWORD=your_secure_password_here
|
||||
LETSENCRYPT_EMAIL=your_email@example.com
|
||||
FLOWER_AUTH=admin:$(openssl passwd -crypt admin)
|
||||
EOF
|
||||
|
||||
log_success "配置文件创建完成"
|
||||
}
|
||||
|
||||
# 构建镜像
|
||||
build_images() {
|
||||
log_info "构建 Docker 镜像..."
|
||||
|
||||
# 构建前端镜像
|
||||
if [ -d "../frontend" ]; then
|
||||
log_info "构建前端镜像..."
|
||||
docker build -t apple-exchange-frontend:latest ../frontend || {
|
||||
log_error "前端镜像构建失败"
|
||||
exit 1
|
||||
}
|
||||
log_success "前端镜像构建完成"
|
||||
fi
|
||||
|
||||
# 构建后端镜像
|
||||
if [ -d "../backend" ]; then
|
||||
log_info "构建后端镜像..."
|
||||
docker build -t apple-exchange-api:latest ../backend || {
|
||||
log_error "后端镜像构建失败"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 构建 Worker 镜像
|
||||
if [ -f "../backend/Dockerfile.worker" ]; then
|
||||
docker build -f ../backend/Dockerfile.worker -t apple-exchange-worker:latest ../backend || {
|
||||
log_error "Worker 镜像构建失败"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
# 如果没有专门的 Dockerfile.worker,使用 API 镜像
|
||||
docker tag apple-exchange-api:latest apple-exchange-worker:latest
|
||||
fi
|
||||
|
||||
log_success "后端镜像构建完成"
|
||||
fi
|
||||
}
|
||||
|
||||
# 部署 Stack
|
||||
deploy_stack() {
|
||||
log_info "部署 Docker Stack: ${STACK_NAME}"
|
||||
|
||||
# 检查环境变量文件
|
||||
if [ ! -f "${DEPLOY_DIR}/.env" ]; then
|
||||
log_warning "环境变量文件 .env 不存在,使用模板创建"
|
||||
cp "${DEPLOY_DIR}/.env.template" "${DEPLOY_DIR}/.env"
|
||||
log_warning "请编辑 .env 文件设置正确的环境变量"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 部署 Stack
|
||||
if docker stack deploy -c "${COMPOSE_FILE}" "${STACK_NAME}"; then
|
||||
log_success "Stack 部署成功"
|
||||
else
|
||||
log_error "Stack 部署失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 显示部署状态
|
||||
show_status() {
|
||||
log_info "部署状态:"
|
||||
echo "----------------------------------------"
|
||||
docker stack services "${STACK_NAME}"
|
||||
echo "----------------------------------------"
|
||||
|
||||
log_info "查看服务状态:"
|
||||
echo " docker stack services ${STACK_NAME}"
|
||||
echo " docker service ps ${STACK_NAME}_frontend"
|
||||
echo " docker service ps ${STACK_NAME}_api"
|
||||
echo " docker service ps ${STACK_NAME}_worker"
|
||||
echo " docker service ps ${STACK_NAME}_db"
|
||||
echo " docker service ps ${STACK_NAME}_redis"
|
||||
|
||||
log_info "查看日志:"
|
||||
echo " docker service logs ${STACK_NAME}_frontend"
|
||||
echo " docker service logs ${STACK_NAME}_api"
|
||||
echo " docker service logs ${STACK_NAME}_worker"
|
||||
|
||||
log_info "访问地址:"
|
||||
echo " 应用: https://apple-exchange.com"
|
||||
echo " API: https://api.apple-exchange.com"
|
||||
echo " Flower 监控: https://flower.apple-exchange.com"
|
||||
echo " Traefik Dashboard: https://traefik.apple-exchange.com"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
log_info "开始 Docker Swarm 部署..."
|
||||
|
||||
# 检查必要的文件
|
||||
if [ ! -f "${COMPOSE_FILE}" ]; then
|
||||
log_error "Docker Compose 文件不存在: ${COMPOSE_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查 Docker
|
||||
check_docker
|
||||
|
||||
# 检查 Swarm
|
||||
if ! check_swarm; then
|
||||
init_swarm
|
||||
fi
|
||||
|
||||
# 创建配置文件
|
||||
create_configs
|
||||
|
||||
# 构建镜像
|
||||
build_images
|
||||
|
||||
# 部署 Stack
|
||||
deploy_stack
|
||||
|
||||
# 显示状态
|
||||
show_status
|
||||
|
||||
log_success "Docker Swarm 部署完成!"
|
||||
}
|
||||
|
||||
# 脚本选项
|
||||
case "${1:-}" in
|
||||
"init")
|
||||
check_docker
|
||||
if ! check_swarm; then
|
||||
init_swarm
|
||||
fi
|
||||
;;
|
||||
"build")
|
||||
build_images
|
||||
;;
|
||||
"deploy")
|
||||
deploy_stack
|
||||
;;
|
||||
"status")
|
||||
show_status
|
||||
;;
|
||||
"remove")
|
||||
log_info "移除 Stack: ${STACK_NAME}"
|
||||
docker stack rm "${STACK_NAME}"
|
||||
;;
|
||||
"logs")
|
||||
shift
|
||||
if [ $# -eq 0 ]; then
|
||||
docker service logs "${STACK_NAME}_api"
|
||||
else
|
||||
docker service logs "${STACK_NAME}_${1}"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
main
|
||||
;;
|
||||
esac
|
||||
@@ -14,7 +14,8 @@
|
||||
"Bash(docker rm:*)",
|
||||
"Bash(docker stop:*)",
|
||||
"Bash(rm:*)",
|
||||
"Bash(docker:*)"
|
||||
"Bash(docker:*)",
|
||||
"Read(//e/**)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@@ -31,11 +31,10 @@ tsconfig.tsbuildinfo
|
||||
# Development files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.env.local
|
||||
.env.development
|
||||
.env.test
|
||||
.env.production
|
||||
!.env.production
|
||||
|
||||
# Docker and deployment files
|
||||
deploy/
|
||||
|
||||
@@ -20,6 +20,7 @@ RUN bun install
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
COPY .env.production .env
|
||||
|
||||
# Build static export
|
||||
RUN bun run build
|
||||
@@ -27,6 +28,7 @@ RUN bun run build
|
||||
# Stage 2: Nginx production stage
|
||||
FROM nginx:alpine AS runner
|
||||
|
||||
|
||||
# Install required system dependencies
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
@@ -65,7 +67,7 @@ server {
|
||||
image/svg+xml;
|
||||
|
||||
# Static files caching
|
||||
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp)$ {
|
||||
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp)\$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
@@ -82,6 +84,14 @@ server {
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# 需要配置反向代理
|
||||
location /api/ {
|
||||
proxy_pass http://apple-exchange-api:8000;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
# Handle static assets
|
||||
location /static/ {
|
||||
alias /usr/share/nginx/html/static/;
|
||||
|
||||
78
frontend/GEMINI.md
Normal file
78
frontend/GEMINI.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Gemini Project Context: Frontend for Apple Gift Card Exchange
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is the frontend for the Apple Gift Card Exchange platform, also referred to as a "Crawler Monitoring System." It's a modern, responsive web application built with Next.js and TypeScript, designed to provide a real-time monitoring dashboard for backend processes. The UI is heavily inspired by Apple's Human Interface Guidelines (HIG), utilizing a custom-themed shadcn/ui component library, Tailwind CSS, and glass morphism effects.
|
||||
|
||||
* **Framework:** Next.js 14+ (with Turbopack)
|
||||
* **Language:** TypeScript
|
||||
* **Styling:** Tailwind CSS, Sass, and a custom Apple-style design system.
|
||||
* **UI Components:** shadcn/ui, Radix UI, Headless UI
|
||||
* **State Management:** TanStack Query (React Query) for server state, React Hooks for local state.
|
||||
* **API Communication:** Axios client with API types and hooks generated by `orval` from an OpenAPI specification.
|
||||
* **Code Quality:** ESLint for linting, Prettier for formatting.
|
||||
|
||||
## Building and Running
|
||||
|
||||
The project uses `bun` as the package manager.
|
||||
|
||||
1. **Install Dependencies:**
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
2. **Run Development Server:**
|
||||
The development server runs on `http://localhost:3000`.
|
||||
```bash
|
||||
bun run dev
|
||||
```
|
||||
|
||||
3. **Build for Production:**
|
||||
This command builds the application for production usage.
|
||||
```bash
|
||||
bun run build
|
||||
```
|
||||
|
||||
4. **Run Production Server:**
|
||||
Starts the production server.
|
||||
```bash
|
||||
bun run start
|
||||
```
|
||||
|
||||
5. **Linting:**
|
||||
Run the linter to check for code quality issues.
|
||||
```bash
|
||||
bun run lint
|
||||
```
|
||||
|
||||
6. **Generate API Client:**
|
||||
The frontend's API client is generated from the backend's OpenAPI schema. If the backend API changes, run this command to update the client.
|
||||
```bash
|
||||
bun run generate:api
|
||||
```
|
||||
|
||||
## Development Conventions
|
||||
|
||||
* **Styling:** Follow the Apple HIG design principles. Use Tailwind CSS utility classes and the existing component styles in `src/styles` and `src/components/ui`.
|
||||
* **API Interaction:** Do not write API fetching code manually. Use the generated hooks from `src/lib/api/generated/`. If an endpoint is missing, regenerate the API client.
|
||||
* **State Management:** Use TanStack Query for all data fetched from the server. Use component-level state (e.g., `useState`) for UI-related state.
|
||||
* **Environment Variables:** Create a `.env.local` file from `env.example` for local development settings.
|
||||
* **Components:** Reusable, low-level components are in `src/components/ui`. Feature-specific components are in `src/components/dashboard`, `src/components/forms`, etc.
|
||||
|
||||
## Key Files and Directories
|
||||
|
||||
* `src/app/`: Contains the pages and layouts for the application (App Router).
|
||||
* `src/components/`: Contains all React components.
|
||||
* `ui/`: General-purpose, reusable UI components (Button, Card, etc.).
|
||||
* `dashboard/`: Components specific to the main monitoring dashboard.
|
||||
* `layout/`: Components that define the page structure (Header, Footer, etc.).
|
||||
* `src/lib/`: Core logic, utilities, and API communication.
|
||||
* `api/generated/`: API client code generated by `orval`. **Do not edit manually.**
|
||||
* `contexts/`: React contexts for shared state (e.g., theme).
|
||||
* `hooks/`: Custom React hooks.
|
||||
* `utils.ts`: General utility functions.
|
||||
* `src/styles/`: Global styles and Sass files.
|
||||
* `tailwind.config.js`: Configuration for the Tailwind CSS design system, including custom colors and effects.
|
||||
* `next.config.js`: Configuration for the Next.js framework.
|
||||
* `orval.config.ts`: Configuration for the `orval` API client generator.
|
||||
* `GEMINI.md`: This file, containing the project context.
|
||||
@@ -80,15 +80,15 @@
|
||||
|
||||
"@asyncapi/specs": ["@asyncapi/specs@6.10.0", "https://registry.npmmirror.com/@asyncapi/specs/-/specs-6.10.0.tgz", { "dependencies": { "@types/json-schema": "^7.0.11" } }, "sha512-vB5oKLsdrLUORIZ5BXortZTlVyGWWMC1Nud/0LtgxQ3Yn2738HigAD6EVqScvpPsDUI/bcLVsYEXN4dtXQHVng=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.28.2", "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.2.tgz", {}, "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA=="],
|
||||
"@babel/runtime": ["@babel/runtime@7.28.4", "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="],
|
||||
|
||||
"@base-ui-components/react": ["@base-ui-components/react@1.0.0-beta.1", "https://registry.npmmirror.com/@base-ui-components/react/-/react-1.0.0-beta.1.tgz", { "dependencies": { "@babel/runtime": "^7.27.6", "@floating-ui/react-dom": "^2.1.3", "@floating-ui/utils": "^0.2.9", "reselect": "^5.1.1", "tabbable": "^6.2.0", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-7zmGiz4/+HKnv99lWftItoSMqnj2PdSvt2krh0/GP+Rj0xK0NMnFI/gIVvP7CB2G+k0JPUrRWXjXa3y08oiakg=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.4.5", "https://registry.npmmirror.com/@emnapi/core/-/core-1.4.5.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" } }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="],
|
||||
"@emnapi/core": ["@emnapi/core@1.5.0", "https://registry.npmmirror.com/@emnapi/core/-/core-1.5.0.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.4.5", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.4.5.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg=="],
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.5.0", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.5.0.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.4", "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g=="],
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="],
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@9.33.0", "https://registry.npmmirror.com/@eslint/js/-/js-9.33.0.tgz", {}, "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A=="],
|
||||
"@eslint/js": ["@eslint/js@9.35.0", "https://registry.npmmirror.com/@eslint/js/-/js-9.35.0.tgz", {}, "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.6.tgz", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
|
||||
|
||||
@@ -164,23 +164,23 @@
|
||||
|
||||
"@floating-ui/core": ["@floating-ui/core@1.7.3", "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.3.tgz", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="],
|
||||
|
||||
"@floating-ui/dom": ["@floating-ui/dom@1.7.3", "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.3.tgz", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag=="],
|
||||
"@floating-ui/dom": ["@floating-ui/dom@1.7.4", "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.4.tgz", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="],
|
||||
|
||||
"@floating-ui/react": ["@floating-ui/react@0.26.28", "https://registry.npmmirror.com/@floating-ui/react/-/react-0.26.28.tgz", { "dependencies": { "@floating-ui/react-dom": "^2.1.2", "@floating-ui/utils": "^0.2.8", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw=="],
|
||||
|
||||
"@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.5", "https://registry.npmmirror.com/@floating-ui/react-dom/-/react-dom-2.1.5.tgz", { "dependencies": { "@floating-ui/dom": "^1.7.3" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-HDO/1/1oH9fjj4eLgegrlH3dklZpHtUYYFiVwMUwfGvk9jWDRWqkklA2/NFScknrcNSspbV868WjXORvreDX+Q=="],
|
||||
"@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.6", "https://registry.npmmirror.com/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", { "dependencies": { "@floating-ui/dom": "^1.7.4" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw=="],
|
||||
|
||||
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
|
||||
|
||||
"@gerrit0/mini-shiki": ["@gerrit0/mini-shiki@3.12.1", "https://registry.npmmirror.com/@gerrit0/mini-shiki/-/mini-shiki-3.12.1.tgz", { "dependencies": { "@shikijs/engine-oniguruma": "^3.12.1", "@shikijs/langs": "^3.12.1", "@shikijs/themes": "^3.12.1", "@shikijs/types": "^3.12.1", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-qA9/VGm7No0kxb7k0oKFT0DSJ6BtuMMtC7JQdZn9ElMALE9hjbyoaS13Y8OWr0qHwzh07KHt3Wbw34az/FLsrg=="],
|
||||
"@gerrit0/mini-shiki": ["@gerrit0/mini-shiki@3.12.2", "https://registry.npmmirror.com/@gerrit0/mini-shiki/-/mini-shiki-3.12.2.tgz", { "dependencies": { "@shikijs/engine-oniguruma": "^3.12.2", "@shikijs/langs": "^3.12.2", "@shikijs/themes": "^3.12.2", "@shikijs/types": "^3.12.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-HKZPmO8OSSAAo20H2B3xgJdxZaLTwtlMwxg0967scnrDlPwe6j5+ULGHyIqwgTbFCn9yv/ff8CmfWZLE9YKBzA=="],
|
||||
|
||||
"@headlessui/react": ["@headlessui/react@2.2.6", "https://registry.npmmirror.com/@headlessui/react/-/react-2.2.6.tgz", { "dependencies": { "@floating-ui/react": "^0.26.16", "@react-aria/focus": "^3.20.2", "@react-aria/interactions": "^3.25.0", "@tanstack/react-virtual": "^3.13.9", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-gN5CT8Kf4IWwL04GQOjZ/ZnHMFoeFHZmVSFoDKeTmbtmy9oFqQqJMthdBiO3Pl5LXk2w03fGFLpQV6EW84vjjQ=="],
|
||||
"@headlessui/react": ["@headlessui/react@2.2.7", "https://registry.npmmirror.com/@headlessui/react/-/react-2.2.7.tgz", { "dependencies": { "@floating-ui/react": "^0.26.16", "@react-aria/focus": "^3.20.2", "@react-aria/interactions": "^3.25.0", "@tanstack/react-virtual": "^3.13.9", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-WKdTymY8Y49H8/gUc/lIyYK1M+/6dq0Iywh4zTZVAaiTDprRfioxSgD0wnXTQTBpjpGJuTL1NO/mqEvc//5SSg=="],
|
||||
|
||||
"@hookform/resolvers": ["@hookform/resolvers@3.10.0", "https://registry.npmmirror.com/@hookform/resolvers/-/resolvers-3.10.0.tgz", { "peerDependencies": { "react-hook-form": "^7.0.0" } }, "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.1", "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||
|
||||
"@humanfs/node": ["@humanfs/node@0.16.6", "https://registry.npmmirror.com/@humanfs/node/-/node-0.16.6.tgz", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
|
||||
"@humanfs/node": ["@humanfs/node@0.16.7", "https://registry.npmmirror.com/@humanfs/node/-/node-0.16.7.tgz", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
|
||||
|
||||
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||
|
||||
@@ -236,15 +236,15 @@
|
||||
|
||||
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="],
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.11.tgz", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="],
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="],
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "https://registry.npmmirror.com/@jsdevtools/ono/-/ono-7.1.3.tgz", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="],
|
||||
|
||||
@@ -256,25 +256,25 @@
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||
|
||||
"@next/env": ["@next/env@15.4.6", "https://registry.npmmirror.com/@next/env/-/env-15.4.6.tgz", {}, "sha512-yHDKVTcHrZy/8TWhj0B23ylKv5ypocuCwey9ZqPyv4rPdUdRzpGCkSi03t04KBPyU96kxVtUqx6O3nE1kpxASQ=="],
|
||||
"@next/env": ["@next/env@15.5.3", "https://registry.npmmirror.com/@next/env/-/env-15.5.3.tgz", {}, "sha512-RSEDTRqyihYXygx/OJXwvVupfr9m04+0vH8vyy0HfZ7keRto6VX9BbEk0J2PUk0VGy6YhklJUSrgForov5F9pw=="],
|
||||
|
||||
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.4.6", "https://registry.npmmirror.com/@next/eslint-plugin-next/-/eslint-plugin-next-15.4.6.tgz", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-2NOu3ln+BTcpnbIDuxx6MNq+pRrCyey4WSXGaJIyt0D2TYicHeO9QrUENNjcf673n3B1s7hsiV5xBYRCK1Q8kA=="],
|
||||
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.5.3", "https://registry.npmmirror.com/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.3.tgz", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-SdhaKdko6dpsSr0DldkESItVrnPYB1NS2NpShCSX5lc7SSQmLZt5Mug6t2xbiuVWEVDLZSuIAoQyYVBYp0dR5g=="],
|
||||
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.4.6", "https://registry.npmmirror.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.6.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-667R0RTP4DwxzmrqTs4Lr5dcEda9OxuZsVFsjVtxVMVhzSpo6nLclXejJVfQo2/g7/Z9qF3ETDmN3h65mTjpTQ=="],
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.3", "https://registry.npmmirror.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.3.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-nzbHQo69+au9wJkGKTU9lP7PXv0d1J5ljFpvb+LnEomLtSbJkbZyEs6sbF3plQmiOB2l9OBtN2tNSvCH1nQ9Jg=="],
|
||||
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.4.6", "https://registry.npmmirror.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.4.6.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-KMSFoistFkaiQYVQQnaU9MPWtp/3m0kn2Xed1Ces5ll+ag1+rlac20sxG+MqhH2qYWX1O2GFOATQXEyxKiIscg=="],
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.3", "https://registry.npmmirror.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.3.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-w83w4SkOOhekJOcA5HBvHyGzgV1W/XvOfpkrxIse4uPWhYTTRwtGEM4v/jiXwNSJvfRvah0H8/uTLBKRXlef8g=="],
|
||||
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.4.6", "https://registry.npmmirror.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.4.6.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-PnOx1YdO0W7m/HWFeYd2A6JtBO8O8Eb9h6nfJia2Dw1sRHoHpNf6lN1U4GKFRzRDBi9Nq2GrHk9PF3Vmwf7XVw=="],
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.3", "https://registry.npmmirror.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.3.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-+m7pfIs0/yvgVu26ieaKrifV8C8yiLe7jVp9SpcIzg7XmyyNE7toC1fy5IOQozmr6kWl/JONC51osih2RyoXRw=="],
|
||||
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.4.6", "https://registry.npmmirror.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.4.6.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-XBbuQddtY1p5FGPc2naMO0kqs4YYtLYK/8aPausI5lyOjr4J77KTG9mtlU4P3NwkLI1+OjsPzKVvSJdMs3cFaw=="],
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.3", "https://registry.npmmirror.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.3.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-u3PEIzuguSenoZviZJahNLgCexGFhso5mxWCrrIMdvpZn6lkME5vc/ADZG8UUk5K1uWRy4hqSFECrON6UKQBbQ=="],
|
||||
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.4.6", "https://registry.npmmirror.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.4.6.tgz", { "os": "linux", "cpu": "x64" }, "sha512-+WTeK7Qdw82ez3U9JgD+igBAP75gqZ1vbK6R8PlEEuY0OIe5FuYXA4aTjL811kWPf7hNeslD4hHK2WoM9W0IgA=="],
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.3", "https://registry.npmmirror.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.3.tgz", { "os": "linux", "cpu": "x64" }, "sha512-lDtOOScYDZxI2BENN9m0pfVPJDSuUkAD1YXSvlJF0DKwZt0WlA7T7o3wrcEr4Q+iHYGzEaVuZcsIbCps4K27sA=="],
|
||||
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.4.6", "https://registry.npmmirror.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.4.6.tgz", { "os": "linux", "cpu": "x64" }, "sha512-XP824mCbgQsK20jlXKrUpZoh/iO3vUWhMpxCz8oYeagoiZ4V0TQiKy0ASji1KK6IAe3DYGfj5RfKP6+L2020OQ=="],
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.3", "https://registry.npmmirror.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.3.tgz", { "os": "linux", "cpu": "x64" }, "sha512-9vWVUnsx9PrY2NwdVRJ4dUURAQ8Su0sLRPqcCCxtX5zIQUBES12eRVHq6b70bbfaVaxIDGJN2afHui0eDm+cLg=="],
|
||||
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.4.6", "https://registry.npmmirror.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.4.6.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-FxrsenhUz0LbgRkNWx6FRRJIPe/MI1JRA4W4EPd5leXO00AZ6YU8v5vfx4MDXTvN77lM/EqsE3+6d2CIeF5NYg=="],
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.3", "https://registry.npmmirror.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.3.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-1CU20FZzY9LFQigRi6jM45oJMU3KziA5/sSG+dXeVaTm661snQP6xu3ykGxxwU5sLG3sh14teO/IOEPVsQMRfA=="],
|
||||
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.4.6", "https://registry.npmmirror.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.6.tgz", { "os": "win32", "cpu": "x64" }, "sha512-T4ufqnZ4u88ZheczkBTtOF+eKaM14V8kbjud/XrAakoM5DKQWjW09vD6B9fsdsWS2T7D5EY31hRHdta7QKWOng=="],
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.3", "https://registry.npmmirror.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.3.tgz", { "os": "win32", "cpu": "x64" }, "sha512-JMoLAq3n3y5tKXPQwCK5c+6tmwkuFDa2XAxz8Wm4+IVthdBZdZGh+lmiLUHg9f9IDwIQpUjp+ysd6OkYTyZRZw=="],
|
||||
|
||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||
|
||||
@@ -336,13 +336,13 @@
|
||||
|
||||
"@radix-ui/number": ["@radix-ui/number@1.1.1", "https://registry.npmmirror.com/@radix-ui/number/-/number-1.1.1.tgz", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
|
||||
|
||||
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.2.tgz", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="],
|
||||
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.3.tgz", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
|
||||
|
||||
"@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.7", "https://registry.npmmirror.com/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", { "dependencies": { "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A=="],
|
||||
|
||||
"@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.11", "https://registry.npmmirror.com/@radix-ui/react-accordion/-/react-accordion-1.2.11.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collapsible": "1.1.11", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A=="],
|
||||
"@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.12", "https://registry.npmmirror.com/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA=="],
|
||||
|
||||
"@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.14", "https://registry.npmmirror.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.14.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.14", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IOZfZ3nPvN6lXpJTBCunFQPRSvK8MDgSc1FB85xnIpUKOw9en0dJj8JmCAxV7BiZdtYlUpmrQjoTFkVYtdoWzQ=="],
|
||||
"@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "https://registry.npmmirror.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="],
|
||||
|
||||
"@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "https://registry.npmmirror.com/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="],
|
||||
|
||||
@@ -350,9 +350,9 @@
|
||||
|
||||
"@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "https://registry.npmmirror.com/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="],
|
||||
|
||||
"@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.2", "https://registry.npmmirror.com/@radix-ui/react-checkbox/-/react-checkbox-1.3.2.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA=="],
|
||||
"@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "https://registry.npmmirror.com/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="],
|
||||
|
||||
"@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.11", "https://registry.npmmirror.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.11.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg=="],
|
||||
"@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "https://registry.npmmirror.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="],
|
||||
|
||||
"@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "https://registry.npmmirror.com/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="],
|
||||
|
||||
@@ -360,77 +360,77 @@
|
||||
|
||||
"@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.2.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
|
||||
|
||||
"@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.15", "https://registry.npmmirror.com/@radix-ui/react-context-menu/-/react-context-menu-2.2.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-UsQUMjcYTsBjTSXw0P3GO0werEQvUY2plgRQuKoCTtkNr45q1DiL51j4m7gxhABzZ0BadoXNsIbg7F3KwiUBbw=="],
|
||||
"@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.16", "https://registry.npmmirror.com/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww=="],
|
||||
|
||||
"@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.14", "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw=="],
|
||||
"@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="],
|
||||
|
||||
"@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="],
|
||||
|
||||
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.10", "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ=="],
|
||||
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
|
||||
|
||||
"@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.15", "https://registry.npmmirror.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ=="],
|
||||
"@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "https://registry.npmmirror.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="],
|
||||
|
||||
"@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.2", "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA=="],
|
||||
"@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
|
||||
|
||||
"@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "https://registry.npmmirror.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="],
|
||||
|
||||
"@radix-ui/react-form": ["@radix-ui/react-form@0.1.7", "https://registry.npmmirror.com/@radix-ui/react-form/-/react-form-0.1.7.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IXLKFnaYvFg/KkeV5QfOX7tRnwHXp127koOFUjLWMTrRv5Rny3DQcAtIFFeA/Cli4HHM8DuJCXAUsgnFVJndlw=="],
|
||||
"@radix-ui/react-form": ["@radix-ui/react-form@0.1.8", "https://registry.npmmirror.com/@radix-ui/react-form/-/react-form-0.1.8.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ=="],
|
||||
|
||||
"@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.14", "https://registry.npmmirror.com/@radix-ui/react-hover-card/-/react-hover-card-1.1.14.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-CPYZ24Mhirm+g6D8jArmLzjYu4Eyg3TTUHswR26QgzXBHBe64BO/RHOJKzmF/Dxb4y4f9PKyJdwm/O/AhNkb+Q=="],
|
||||
"@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.15", "https://registry.npmmirror.com/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg=="],
|
||||
|
||||
"@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "https://registry.npmmirror.com/@radix-ui/react-id/-/react-id-1.1.1.tgz", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="],
|
||||
|
||||
"@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "https://registry.npmmirror.com/@radix-ui/react-label/-/react-label-2.1.7.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="],
|
||||
|
||||
"@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.15", "https://registry.npmmirror.com/@radix-ui/react-menu/-/react-menu-2.1.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew=="],
|
||||
"@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "https://registry.npmmirror.com/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="],
|
||||
|
||||
"@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.15", "https://registry.npmmirror.com/@radix-ui/react-menubar/-/react-menubar-1.1.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Z71C7LGD+YDYo3TV81paUs8f3Zbmkvg6VLRQpKYfzioOE6n7fOhA3ApK/V/2Odolxjoc4ENk8AYCjohCNayd5A=="],
|
||||
"@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.16", "https://registry.npmmirror.com/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA=="],
|
||||
|
||||
"@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.13", "https://registry.npmmirror.com/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.13.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-WG8wWfDiJlSF5hELjwfjSGOXcBR/ZMhBFCGYe8vERpC39CQYZeq1PQ2kaYHdye3V95d06H89KGMsVCIE4LWo3g=="],
|
||||
"@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.14", "https://registry.npmmirror.com/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w=="],
|
||||
|
||||
"@radix-ui/react-one-time-password-field": ["@radix-ui/react-one-time-password-field@0.1.7", "https://registry.npmmirror.com/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.7.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-w1vm7AGI8tNXVovOK7TYQHrAGpRF7qQL+ENpT1a743De5Zmay2RbWGKAiYDKIyIuqptns+znCKwNztE2xl1n0Q=="],
|
||||
"@radix-ui/react-one-time-password-field": ["@radix-ui/react-one-time-password-field@0.1.8", "https://registry.npmmirror.com/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg=="],
|
||||
|
||||
"@radix-ui/react-password-toggle-field": ["@radix-ui/react-password-toggle-field@0.1.2", "https://registry.npmmirror.com/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.2.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F90uYnlBsLPU1UbSLciLsWQmk8+hdWa6SFw4GXaIdNWxFxI5ITKVdAG64f+Twaa9ic6xE7pqxPyUmodrGjT4pQ=="],
|
||||
"@radix-ui/react-password-toggle-field": ["@radix-ui/react-password-toggle-field@0.1.3", "https://registry.npmmirror.com/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw=="],
|
||||
|
||||
"@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.14", "https://registry.npmmirror.com/@radix-ui/react-popover/-/react-popover-1.1.14.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw=="],
|
||||
"@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "https://registry.npmmirror.com/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="],
|
||||
|
||||
"@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.7", "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ=="],
|
||||
"@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="],
|
||||
|
||||
"@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="],
|
||||
|
||||
"@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="],
|
||||
"@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
|
||||
|
||||
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||
|
||||
"@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.7", "https://registry.npmmirror.com/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg=="],
|
||||
|
||||
"@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.3.7", "https://registry.npmmirror.com/@radix-ui/react-radio-group/-/react-radio-group-1.3.7.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9w5XhD0KPOrm92OTTE0SysH3sYzHsSTHNvZgUBo/VZ80VdYyB5RneDbc0dKpURS24IxkoFRu/hI0i4XyfFwY6g=="],
|
||||
"@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.3.8", "https://registry.npmmirror.com/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ=="],
|
||||
|
||||
"@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.10", "https://registry.npmmirror.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q=="],
|
||||
"@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "https://registry.npmmirror.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="],
|
||||
|
||||
"@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.9", "https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.9.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A=="],
|
||||
"@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="],
|
||||
|
||||
"@radix-ui/react-select": ["@radix-ui/react-select@2.2.5", "https://registry.npmmirror.com/@radix-ui/react-select/-/react-select-2.2.5.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA=="],
|
||||
"@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "https://registry.npmmirror.com/@radix-ui/react-select/-/react-select-2.2.6.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="],
|
||||
|
||||
"@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "https://registry.npmmirror.com/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="],
|
||||
|
||||
"@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.5", "https://registry.npmmirror.com/@radix-ui/react-slider/-/react-slider-1.3.5.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-rkfe2pU2NBAYfGaxa3Mqosi7VZEWX5CxKaanRv0vZd4Zhl9fvQrg0VM93dv3xGLGfrHuoTRF3JXH8nb9g+B3fw=="],
|
||||
"@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.6", "https://registry.npmmirror.com/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw=="],
|
||||
|
||||
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||
|
||||
"@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.5", "https://registry.npmmirror.com/@radix-ui/react-switch/-/react-switch-1.2.5.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ=="],
|
||||
"@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "https://registry.npmmirror.com/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="],
|
||||
|
||||
"@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.12", "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw=="],
|
||||
"@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="],
|
||||
|
||||
"@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.14", "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.14.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-nAP5FBxBJGQ/YfUB+r+O6USFVkWq3gAInkxyEnmvEV5jtSbfDhfa4hwX8CraCnbjMLsE7XSf/K75l9xXY7joWg=="],
|
||||
"@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.15", "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g=="],
|
||||
|
||||
"@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.9", "https://registry.npmmirror.com/@radix-ui/react-toggle/-/react-toggle-1.1.9.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ZoFkBBz9zv9GWer7wIjvdRxmh2wyc2oKWw6C6CseWd6/yq1DK/l5lJ+wnsmFwJZbBYqr02mrf8A2q/CVCuM3ZA=="],
|
||||
"@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.10", "https://registry.npmmirror.com/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ=="],
|
||||
|
||||
"@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.10", "https://registry.npmmirror.com/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.10.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-toggle": "1.1.9", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kiU694Km3WFLTC75DdqgM/3Jauf3rD9wxeS9XtyWFKsBUeZA337lC+6uUazT7I1DhanZ5gyD5Stf8uf2dbQxOQ=="],
|
||||
"@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.11", "https://registry.npmmirror.com/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q=="],
|
||||
|
||||
"@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.10", "https://registry.npmmirror.com/@radix-ui/react-toolbar/-/react-toolbar-1.1.10.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-toggle-group": "1.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-jiwQsduEL++M4YBIurjSa+voD86OIytCod0/dbIxFZDLD8NfO1//keXYMfsW8BPcfqwoNjt+y06XcJqAb4KR7A=="],
|
||||
"@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.11", "https://registry.npmmirror.com/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-toggle-group": "1.1.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg=="],
|
||||
|
||||
"@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.7", "https://registry.npmmirror.com/@radix-ui/react-tooltip/-/react-tooltip-1.2.7.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw=="],
|
||||
"@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "https://registry.npmmirror.com/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="],
|
||||
|
||||
"@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
|
||||
|
||||
@@ -454,19 +454,19 @@
|
||||
|
||||
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "https://registry.npmmirror.com/@radix-ui/rect/-/rect-1.1.1.tgz", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
|
||||
|
||||
"@react-aria/focus": ["@react-aria/focus@3.21.0", "https://registry.npmmirror.com/@react-aria/focus/-/focus-3.21.0.tgz", { "dependencies": { "@react-aria/interactions": "^3.25.4", "@react-aria/utils": "^3.30.0", "@react-types/shared": "^3.31.0", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-7NEGtTPsBy52EZ/ToVKCu0HSelE3kq9qeis+2eEq90XSuJOMaDHUQrA7RC2Y89tlEwQB31bud/kKRi9Qme1dkA=="],
|
||||
"@react-aria/focus": ["@react-aria/focus@3.21.1", "https://registry.npmmirror.com/@react-aria/focus/-/focus-3.21.1.tgz", { "dependencies": { "@react-aria/interactions": "^3.25.5", "@react-aria/utils": "^3.30.1", "@react-types/shared": "^3.32.0", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-hmH1IhHlcQ2lSIxmki1biWzMbGgnhdxJUM0MFfzc71Rv6YAzhlx4kX3GYn4VNcjCeb6cdPv4RZ5vunV4kgMZYQ=="],
|
||||
|
||||
"@react-aria/interactions": ["@react-aria/interactions@3.25.4", "https://registry.npmmirror.com/@react-aria/interactions/-/interactions-3.25.4.tgz", { "dependencies": { "@react-aria/ssr": "^3.9.10", "@react-aria/utils": "^3.30.0", "@react-stately/flags": "^3.1.2", "@react-types/shared": "^3.31.0", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-HBQMxgUPHrW8V63u9uGgBymkMfj6vdWbB0GgUJY49K9mBKMsypcHeWkWM6+bF7kxRO728/IK8bWDV6whDbqjHg=="],
|
||||
"@react-aria/interactions": ["@react-aria/interactions@3.25.5", "https://registry.npmmirror.com/@react-aria/interactions/-/interactions-3.25.5.tgz", { "dependencies": { "@react-aria/ssr": "^3.9.10", "@react-aria/utils": "^3.30.1", "@react-stately/flags": "^3.1.2", "@react-types/shared": "^3.32.0", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-EweYHOEvMwef/wsiEqV73KurX/OqnmbzKQa2fLxdULbec5+yDj6wVGaRHIzM4NiijIDe+bldEl5DG05CAKOAHA=="],
|
||||
|
||||
"@react-aria/ssr": ["@react-aria/ssr@3.9.10", "https://registry.npmmirror.com/@react-aria/ssr/-/ssr-3.9.10.tgz", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ=="],
|
||||
|
||||
"@react-aria/utils": ["@react-aria/utils@3.30.0", "https://registry.npmmirror.com/@react-aria/utils/-/utils-3.30.0.tgz", { "dependencies": { "@react-aria/ssr": "^3.9.10", "@react-stately/flags": "^3.1.2", "@react-stately/utils": "^3.10.8", "@react-types/shared": "^3.31.0", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-ydA6y5G1+gbem3Va2nczj/0G0W7/jUVo/cbN10WA5IizzWIwMP5qhFr7macgbKfHMkZ+YZC3oXnt2NNre5odKw=="],
|
||||
"@react-aria/utils": ["@react-aria/utils@3.30.1", "https://registry.npmmirror.com/@react-aria/utils/-/utils-3.30.1.tgz", { "dependencies": { "@react-aria/ssr": "^3.9.10", "@react-stately/flags": "^3.1.2", "@react-stately/utils": "^3.10.8", "@react-types/shared": "^3.32.0", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-zETcbDd6Vf9GbLndO6RiWJadIZsBU2MMm23rBACXLmpRztkrIqPEb2RVdlLaq1+GklDx0Ii6PfveVjx+8S5U6A=="],
|
||||
|
||||
"@react-stately/flags": ["@react-stately/flags@3.1.2", "https://registry.npmmirror.com/@react-stately/flags/-/flags-3.1.2.tgz", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg=="],
|
||||
|
||||
"@react-stately/utils": ["@react-stately/utils@3.10.8", "https://registry.npmmirror.com/@react-stately/utils/-/utils-3.10.8.tgz", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g=="],
|
||||
|
||||
"@react-types/shared": ["@react-types/shared@3.31.0", "https://registry.npmmirror.com/@react-types/shared/-/shared-3.31.0.tgz", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-ua5U6V66gDcbLZe4P2QeyNgPp4YWD1ymGA6j3n+s8CGExtrCPe64v+g4mvpT8Bnb985R96e4zFT61+m0YCwqMg=="],
|
||||
"@react-types/shared": ["@react-types/shared@3.32.0", "https://registry.npmmirror.com/@react-types/shared/-/shared-3.32.0.tgz", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-t+cligIJsZYFMSPFMvsJMjzlzde06tZMOIOFa1OV5Z0BcMowrb2g4mB57j/9nP28iJIRYn10xCniQts+qadrqQ=="],
|
||||
|
||||
"@rtsao/scc": ["@rtsao/scc@1.1.0", "https://registry.npmmirror.com/@rtsao/scc/-/scc-1.1.0.tgz", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
|
||||
|
||||
@@ -516,13 +516,13 @@
|
||||
|
||||
"@swc/helpers": ["@swc/helpers@0.5.15", "https://registry.npmmirror.com/@swc/helpers/-/helpers-0.5.15.tgz", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
||||
|
||||
"@tanstack/query-core": ["@tanstack/query-core@5.85.5", "https://registry.npmmirror.com/@tanstack/query-core/-/query-core-5.85.5.tgz", {}, "sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w=="],
|
||||
"@tanstack/query-core": ["@tanstack/query-core@5.87.4", "https://registry.npmmirror.com/@tanstack/query-core/-/query-core-5.87.4.tgz", {}, "sha512-uNsg6zMxraEPDVO2Bn+F3/ctHi+Zsk+MMpcN8h6P7ozqD088F6mFY5TfGM7zuyIrL7HKpDyu6QHfLWiDxh3cuw=="],
|
||||
|
||||
"@tanstack/query-devtools": ["@tanstack/query-devtools@5.84.0", "https://registry.npmmirror.com/@tanstack/query-devtools/-/query-devtools-5.84.0.tgz", {}, "sha512-fbF3n+z1rqhvd9EoGp5knHkv3p5B2Zml1yNRjh7sNXklngYI5RVIWUrUjZ1RIcEoscarUb0+bOvIs5x9dwzOXQ=="],
|
||||
"@tanstack/query-devtools": ["@tanstack/query-devtools@5.87.3", "https://registry.npmmirror.com/@tanstack/query-devtools/-/query-devtools-5.87.3.tgz", {}, "sha512-LkzxzSr2HS1ALHTgDmJH5eGAVsSQiuwz//VhFW5OqNk0OQ+Fsqba0Tsf+NzWRtXYvpgUqwQr4b2zdFZwxHcGvg=="],
|
||||
|
||||
"@tanstack/react-query": ["@tanstack/react-query@5.85.5", "https://registry.npmmirror.com/@tanstack/react-query/-/react-query-5.85.5.tgz", { "dependencies": { "@tanstack/query-core": "5.85.5" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A=="],
|
||||
"@tanstack/react-query": ["@tanstack/react-query@5.87.4", "https://registry.npmmirror.com/@tanstack/react-query/-/react-query-5.87.4.tgz", { "dependencies": { "@tanstack/query-core": "5.87.4" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-T5GT/1ZaNsUXf5I3RhcYuT17I4CPlbZgyLxc/ZGv7ciS6esytlbjb3DgUFO6c8JWYMDpdjSWInyGZUErgzqhcA=="],
|
||||
|
||||
"@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.85.5", "https://registry.npmmirror.com/@tanstack/react-query-devtools/-/react-query-devtools-5.85.5.tgz", { "dependencies": { "@tanstack/query-devtools": "5.84.0" }, "peerDependencies": { "@tanstack/react-query": "^5.85.5", "react": "^18 || ^19" } }, "sha512-6Ol6Q+LxrCZlQR4NoI5181r+ptTwnlPG2t7H9Sp3klxTBhYGunONqcgBn2YKRPsaKiYM8pItpKMdMXMEINntMQ=="],
|
||||
"@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.87.4", "https://registry.npmmirror.com/@tanstack/react-query-devtools/-/react-query-devtools-5.87.4.tgz", { "dependencies": { "@tanstack/query-devtools": "5.87.3" }, "peerDependencies": { "@tanstack/react-query": "^5.87.4", "react": "^18 || ^19" } }, "sha512-JYcnVJBBW1DCPyNGM0S2CyrLpe6KFiL2gpYd/k9tAp62Du7+Y27zkzd+dKFyxpFadYaTxsx4kUA7YvnkMLVUoQ=="],
|
||||
|
||||
"@tanstack/react-table": ["@tanstack/react-table@8.21.3", "https://registry.npmmirror.com/@tanstack/react-table/-/react-table-8.21.3.tgz", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww=="],
|
||||
|
||||
@@ -532,7 +532,7 @@
|
||||
|
||||
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.12", "https://registry.npmmirror.com/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz", {}, "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="],
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@types/d3-array": ["@types/d3-array@3.2.1", "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.1.tgz", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="],
|
||||
|
||||
@@ -566,11 +566,11 @@
|
||||
|
||||
"@types/json5": ["@types/json5@0.0.29", "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
|
||||
|
||||
"@types/node": ["@types/node@24.2.1", "https://registry.npmmirror.com/@types/node/-/node-24.2.1.tgz", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="],
|
||||
"@types/node": ["@types/node@24.3.1", "https://registry.npmmirror.com/@types/node/-/node-24.3.1.tgz", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g=="],
|
||||
|
||||
"@types/prop-types": ["@types/prop-types@15.7.15", "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
|
||||
|
||||
"@types/react": ["@types/react@18.3.23", "https://registry.npmmirror.com/@types/react/-/react-18.3.23.tgz", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w=="],
|
||||
"@types/react": ["@types/react@18.3.24", "https://registry.npmmirror.com/@types/react/-/react-18.3.24.tgz", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@18.3.7", "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.7.tgz", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="],
|
||||
|
||||
@@ -578,25 +578,25 @@
|
||||
|
||||
"@types/urijs": ["@types/urijs@1.19.25", "https://registry.npmmirror.com/@types/urijs/-/urijs-1.19.25.tgz", {}, "sha512-XOfUup9r3Y06nFAZh3WvO0rBU4OtlfPB/vgxpjg+NRdGU6CN6djdc6OEiH+PcqHCY6eFLo9Ista73uarf4gnBg=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.39.0", "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.39.0", "@typescript-eslint/type-utils": "8.39.0", "@typescript-eslint/utils": "8.39.0", "@typescript-eslint/visitor-keys": "8.39.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.39.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw=="],
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.43.0", "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.43.0.tgz", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.43.0", "@typescript-eslint/type-utils": "8.43.0", "@typescript-eslint/utils": "8.43.0", "@typescript-eslint/visitor-keys": "8.43.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.43.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.39.0", "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.39.0.tgz", { "dependencies": { "@typescript-eslint/scope-manager": "8.39.0", "@typescript-eslint/types": "8.39.0", "@typescript-eslint/typescript-estree": "8.39.0", "@typescript-eslint/visitor-keys": "8.39.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg=="],
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.43.0", "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.43.0.tgz", { "dependencies": { "@typescript-eslint/scope-manager": "8.43.0", "@typescript-eslint/types": "8.43.0", "@typescript-eslint/typescript-estree": "8.43.0", "@typescript-eslint/visitor-keys": "8.43.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw=="],
|
||||
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.39.0", "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.39.0.tgz", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.39.0", "@typescript-eslint/types": "^8.39.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew=="],
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.43.0", "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.43.0.tgz", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.43.0", "@typescript-eslint/types": "^8.43.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.39.0", "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz", { "dependencies": { "@typescript-eslint/types": "8.39.0", "@typescript-eslint/visitor-keys": "8.39.0" } }, "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A=="],
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.43.0", "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.43.0.tgz", { "dependencies": { "@typescript-eslint/types": "8.43.0", "@typescript-eslint/visitor-keys": "8.43.0" } }, "sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg=="],
|
||||
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.39.0", "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ=="],
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.43.0", "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.43.0.tgz", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.39.0", "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz", { "dependencies": { "@typescript-eslint/types": "8.39.0", "@typescript-eslint/typescript-estree": "8.39.0", "@typescript-eslint/utils": "8.39.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q=="],
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.43.0", "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.43.0.tgz", { "dependencies": { "@typescript-eslint/types": "8.43.0", "@typescript-eslint/typescript-estree": "8.43.0", "@typescript-eslint/utils": "8.43.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.39.0", "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.39.0.tgz", {}, "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg=="],
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.43.0", "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.43.0.tgz", {}, "sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.39.0", "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz", { "dependencies": { "@typescript-eslint/project-service": "8.39.0", "@typescript-eslint/tsconfig-utils": "8.39.0", "@typescript-eslint/types": "8.39.0", "@typescript-eslint/visitor-keys": "8.39.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw=="],
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.43.0", "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.43.0.tgz", { "dependencies": { "@typescript-eslint/project-service": "8.43.0", "@typescript-eslint/tsconfig-utils": "8.43.0", "@typescript-eslint/types": "8.43.0", "@typescript-eslint/visitor-keys": "8.43.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.39.0", "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.39.0.tgz", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.39.0", "@typescript-eslint/types": "8.39.0", "@typescript-eslint/typescript-estree": "8.39.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ=="],
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.43.0", "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.43.0.tgz", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.43.0", "@typescript-eslint/types": "8.43.0", "@typescript-eslint/typescript-estree": "8.43.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.39.0", "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz", { "dependencies": { "@typescript-eslint/types": "8.39.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA=="],
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.43.0", "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.43.0.tgz", { "dependencies": { "@typescript-eslint/types": "8.43.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw=="],
|
||||
|
||||
"@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "https://registry.npmmirror.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="],
|
||||
|
||||
@@ -738,7 +738,7 @@
|
||||
|
||||
"axe-core": ["axe-core@4.10.3", "https://registry.npmmirror.com/axe-core/-/axe-core-4.10.3.tgz", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="],
|
||||
|
||||
"axios": ["axios@1.11.0", "https://registry.npmmirror.com/axios/-/axios-1.11.0.tgz", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA=="],
|
||||
"axios": ["axios@1.12.0", "https://registry.npmmirror.com/axios/-/axios-1.12.0.tgz", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg=="],
|
||||
|
||||
"axobject-query": ["axobject-query@4.1.0", "https://registry.npmmirror.com/axobject-query/-/axobject-query-4.1.0.tgz", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||
|
||||
@@ -750,7 +750,7 @@
|
||||
|
||||
"braces": ["braces@3.0.3", "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browserslist": ["browserslist@4.25.2", "https://registry.npmmirror.com/browserslist/-/browserslist-4.25.2.tgz", { "dependencies": { "caniuse-lite": "^1.0.30001733", "electron-to-chromium": "^1.5.199", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA=="],
|
||||
"browserslist": ["browserslist@4.25.4", "https://registry.npmmirror.com/browserslist/-/browserslist-4.25.4.tgz", { "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
@@ -768,7 +768,7 @@
|
||||
|
||||
"camelcase-css": ["camelcase-css@2.0.1", "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001733", "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", {}, "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q=="],
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001741", "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", {}, "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
@@ -872,7 +872,7 @@
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.199", "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", {}, "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ=="],
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.218", "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz", {}, "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@9.2.2", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
@@ -912,9 +912,9 @@
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@9.33.0", "https://registry.npmmirror.com/eslint/-/eslint-9.33.0.tgz", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.33.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA=="],
|
||||
"eslint": ["eslint@9.35.0", "https://registry.npmmirror.com/eslint/-/eslint-9.35.0.tgz", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.35.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg=="],
|
||||
|
||||
"eslint-config-next": ["eslint-config-next@15.4.6", "https://registry.npmmirror.com/eslint-config-next/-/eslint-config-next-15.4.6.tgz", { "dependencies": { "@next/eslint-plugin-next": "15.4.6", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-4uznvw5DlTTjrZgYZjMciSdDDMO2SWIuQgUNaFyC2O3Zw3Z91XeIejeVa439yRq2CnJb/KEvE4U2AeN/66FpUA=="],
|
||||
"eslint-config-next": ["eslint-config-next@15.5.3", "https://registry.npmmirror.com/eslint-config-next/-/eslint-config-next-15.5.3.tgz", { "dependencies": { "@next/eslint-plugin-next": "15.5.3", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-e6j+QhQFOr5pfsc8VJbuTD9xTXJaRvMHYjEeLPA2pFkheNlgPLCkxdvhxhfuM4KGcqSZj2qEnpHisdTVs3BxuQ=="],
|
||||
|
||||
"eslint-config-prettier": ["eslint-config-prettier@10.1.8", "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="],
|
||||
|
||||
@@ -972,7 +972,7 @@
|
||||
|
||||
"fastq": ["fastq@1.19.1", "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||
|
||||
"fdir": ["fdir@6.4.6", "https://registry.npmmirror.com/fdir/-/fdir-6.4.6.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="],
|
||||
"fdir": ["fdir@6.5.0", "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||
|
||||
@@ -1268,7 +1268,7 @@
|
||||
|
||||
"neo-async": ["neo-async@2.6.2", "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
|
||||
|
||||
"next": ["next@15.4.6", "https://registry.npmmirror.com/next/-/next-15.4.6.tgz", { "dependencies": { "@next/env": "15.4.6", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.6", "@next/swc-darwin-x64": "15.4.6", "@next/swc-linux-arm64-gnu": "15.4.6", "@next/swc-linux-arm64-musl": "15.4.6", "@next/swc-linux-x64-gnu": "15.4.6", "@next/swc-linux-x64-musl": "15.4.6", "@next/swc-win32-arm64-msvc": "15.4.6", "@next/swc-win32-x64-msvc": "15.4.6", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-us++E/Q80/8+UekzB3SAGs71AlLDsadpFMXVNM/uQ0BMwsh9m3mr0UNQIfjKed8vpWXsASe+Qifrnu1oLIcKEQ=="],
|
||||
"next": ["next@15.5.3", "https://registry.npmmirror.com/next/-/next-15.5.3.tgz", { "dependencies": { "@next/env": "15.5.3", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.3", "@next/swc-darwin-x64": "15.5.3", "@next/swc-linux-arm64-gnu": "15.5.3", "@next/swc-linux-arm64-musl": "15.5.3", "@next/swc-linux-x64-gnu": "15.5.3", "@next/swc-linux-x64-musl": "15.5.3", "@next/swc-win32-arm64-msvc": "15.5.3", "@next/swc-win32-x64-msvc": "15.5.3", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-r/liNAx16SQj4D+XH/oI1dlpv9tdKJ6cONYPwwcCC46f2NjpaRWY+EKCzULfgQYV6YKXjHBchff2IZBSlZmJNw=="],
|
||||
|
||||
"next-themes": ["next-themes@0.4.6", "https://registry.npmmirror.com/next-themes/-/next-themes-0.4.6.tgz", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
||||
|
||||
@@ -1282,7 +1282,7 @@
|
||||
|
||||
"node-readfiles": ["node-readfiles@0.2.0", "https://registry.npmmirror.com/node-readfiles/-/node-readfiles-0.2.0.tgz", { "dependencies": { "es6-promise": "^3.2.1" } }, "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.19", "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
|
||||
"node-releases": ["node-releases@2.0.20", "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.20.tgz", {}, "sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA=="],
|
||||
|
||||
"normalize-path": ["normalize-path@3.0.0", "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||
|
||||
@@ -1400,7 +1400,7 @@
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"radix-ui": ["radix-ui@1.4.2", "https://registry.npmmirror.com/radix-ui/-/radix-ui-1.4.2.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-accessible-icon": "1.1.7", "@radix-ui/react-accordion": "1.2.11", "@radix-ui/react-alert-dialog": "1.1.14", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-aspect-ratio": "1.1.7", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.2", "@radix-ui/react-collapsible": "1.1.11", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-context-menu": "2.2.15", "@radix-ui/react-dialog": "1.1.14", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-dropdown-menu": "2.1.15", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-form": "0.1.7", "@radix-ui/react-hover-card": "1.1.14", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-menu": "2.1.15", "@radix-ui/react-menubar": "1.1.15", "@radix-ui/react-navigation-menu": "1.2.13", "@radix-ui/react-one-time-password-field": "0.1.7", "@radix-ui/react-password-toggle-field": "0.1.2", "@radix-ui/react-popover": "1.1.14", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-progress": "1.1.7", "@radix-ui/react-radio-group": "1.3.7", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-scroll-area": "1.2.9", "@radix-ui/react-select": "2.2.5", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-slider": "1.3.5", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-switch": "1.2.5", "@radix-ui/react-tabs": "1.1.12", "@radix-ui/react-toast": "1.2.14", "@radix-ui/react-toggle": "1.1.9", "@radix-ui/react-toggle-group": "1.1.10", "@radix-ui/react-toolbar": "1.1.10", "@radix-ui/react-tooltip": "1.2.7", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-escape-keydown": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-fT/3YFPJzf2WUpqDoQi005GS8EpCi+53VhcLaHUj5fwkPYiZAjk1mSxFvbMA8Uq71L03n+WysuYC+mlKkXxt/Q=="],
|
||||
"radix-ui": ["radix-ui@1.4.3", "https://registry.npmmirror.com/radix-ui/-/radix-ui-1.4.3.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-accessible-icon": "1.1.7", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-aspect-ratio": "1.1.7", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-context-menu": "2.2.16", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-form": "0.1.8", "@radix-ui/react-hover-card": "1.1.15", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-menubar": "1.1.16", "@radix-ui/react-navigation-menu": "1.2.14", "@radix-ui/react-one-time-password-field": "0.1.8", "@radix-ui/react-password-toggle-field": "0.1.3", "@radix-ui/react-popover": "1.1.15", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-progress": "1.1.7", "@radix-ui/react-radio-group": "1.3.8", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-scroll-area": "1.2.10", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-slider": "1.3.6", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-switch": "1.2.6", "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-toggle-group": "1.1.11", "@radix-ui/react-toolbar": "1.1.11", "@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-escape-keydown": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA=="],
|
||||
|
||||
"randombytes": ["randombytes@2.1.0", "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
|
||||
|
||||
@@ -1588,7 +1588,7 @@
|
||||
|
||||
"tiny-invariant": ["tiny-invariant@1.3.3", "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.14", "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.14.tgz", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
@@ -1692,13 +1692,11 @@
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.3.1.tgz", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
|
||||
|
||||
"@ibm-cloud/openapi-ruleset/minimatch": ["minimatch@6.2.0", "https://registry.npmmirror.com/minimatch/-/minimatch-6.2.0.tgz", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg=="],
|
||||
|
||||
"@isaacs/cliui/string-width": ["string-width@5.1.2", "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
"@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
"@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.2.tgz", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
||||
|
||||
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
@@ -1802,9 +1800,9 @@
|
||||
|
||||
"@ibm-cloud/openapi-ruleset/minimatch/brace-expansion": ["brace-expansion@2.0.2", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.1.0.tgz", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
|
||||
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
||||
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"@next/eslint-plugin-next/fast-glob/glob-parent": ["glob-parent@5.1.2", "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ NEXT_PUBLIC_USE_PROXY=true
|
||||
# 目标API URL(当使用代理时,这是实际的目标服务器)
|
||||
NEXT_PUBLIC_TARGET_API_URL=http://your-test-api-server.com
|
||||
|
||||
# 直接API URL(当不使用代理时)
|
||||
NEXT_PUBLIC_API_BASE_URL=http://localhost:3001
|
||||
|
||||
# 遥测端点
|
||||
NEXT_PUBLIC_TELEMETRY_ENDPOINT=http://localhost:4318/v1/traces
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const ovalCOnfig = {
|
||||
const orvalConfig = {
|
||||
// 爬虫监控系统 API 配置
|
||||
petstore: {
|
||||
prettier: true,
|
||||
@@ -66,4 +66,4 @@ const ovalCOnfig = {
|
||||
},
|
||||
};
|
||||
|
||||
export default ovalCOnfig;
|
||||
export default orvalConfig;
|
||||
@@ -1,4 +1,5 @@
|
||||
ignoredBuiltDependencies:
|
||||
- '@parcel/watcher'
|
||||
- sharp
|
||||
- unrs-resolver
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Trash2, ExternalLink, Calendar, DollarSign, Copy, Loader2 } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { toast } from "sonner";
|
||||
// 导入 Tooltip 组件
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/animate-ui/base/tooltip";
|
||||
@@ -77,10 +74,8 @@ export function LinkItem({ link, onDelete, isDeleting = false }: LinkItemProps)
|
||||
{link.url}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="max-w-md break-all text-sm">
|
||||
{link.url}
|
||||
</div>
|
||||
<TooltipContent className="max-w-md break-all rounded-2xl shadow-2xl border-0 bg-white/90 dark:bg-gray-900/90 backdrop-blur-xl px-4 py-3 text-gray-900 dark:text-gray-100">
|
||||
{link.url}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useMemo } from "react";
|
||||
import { RefreshCw, ShoppingCart, Calendar, User, Filter, ExternalLink } from "lucide-react";
|
||||
import { RefreshCw, ShoppingCart, Calendar, User, Filter, ExternalLink, Info, Link as LinkIcon, CreditCard, AlertTriangle, Hash, Clock, CheckCircle, XCircle, Loader, Package } from "lucide-react";
|
||||
import {
|
||||
useGetOrdersApiV1OrdersListGet,
|
||||
} from "@/lib/api/generated/order-management.gen";
|
||||
import { OrderResultStatus, OrderDetailResponse } from "@/lib/api/generated/schemas";
|
||||
import { AppleButton } from "@/components/ui/apple-button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from "@/components/ui/pagination";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface OrderListProps {
|
||||
@@ -23,29 +24,179 @@ const statusConfig = {
|
||||
pending: {
|
||||
label: "待处理",
|
||||
variant: "secondary" as const,
|
||||
bgColor: "bg-gray-100 dark:bg-gray-800",
|
||||
textColor: "text-gray-700 dark:text-gray-300",
|
||||
icon: Clock,
|
||||
color: "text-amber-600 dark:text-amber-400",
|
||||
bgColor: "bg-amber-50 dark:bg-amber-900/20",
|
||||
borderColor: "border-amber-200 dark:border-amber-700"
|
||||
},
|
||||
processing: {
|
||||
label: "处理中",
|
||||
variant: "processing" as const,
|
||||
bgColor: "bg-yellow-100 dark:bg-yellow-900/30",
|
||||
textColor: "text-yellow-700 dark:text-yellow-300",
|
||||
icon: Loader,
|
||||
color: "text-blue-600 dark:text-blue-400",
|
||||
bgColor: "bg-blue-50 dark:bg-blue-900/20",
|
||||
borderColor: "border-blue-200 dark:border-blue-700"
|
||||
},
|
||||
success: {
|
||||
label: "成功",
|
||||
variant: "success" as const,
|
||||
bgColor: "bg-green-100 dark:bg-green-900/30",
|
||||
textColor: "text-green-700 dark:text-green-300",
|
||||
icon: CheckCircle,
|
||||
color: "text-green-600 dark:text-green-400",
|
||||
bgColor: "bg-green-50 dark:bg-green-900/20",
|
||||
borderColor: "border-green-200 dark:border-green-700"
|
||||
},
|
||||
failure: {
|
||||
label: "失败",
|
||||
variant: "failure" as const,
|
||||
bgColor: "bg-red-100 dark:bg-red-900/30",
|
||||
textColor: "text-red-700 dark:text-red-300",
|
||||
icon: XCircle,
|
||||
color: "text-red-600 dark:text-red-400",
|
||||
bgColor: "bg-red-50 dark:bg-red-900/20",
|
||||
borderColor: "border-red-200 dark:border-red-700"
|
||||
},
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleString('zh-CN', {
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit'
|
||||
});
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string, className?: string) => {
|
||||
const config = statusConfig[status as keyof typeof statusConfig] || statusConfig.pending;
|
||||
const Icon = config.icon;
|
||||
const isProcessing = status === 'processing';
|
||||
return (
|
||||
<Badge
|
||||
variant={config.variant}
|
||||
className={cn(
|
||||
"px-3 py-1.5 text-xs font-medium flex items-center gap-1.5 border transition-all duration-200 hover:shadow-sm",
|
||||
config.color,
|
||||
config.bgColor,
|
||||
config.borderColor,
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Icon className={cn("w-3 h-3", isProcessing && "animate-spin")} />
|
||||
{config.label}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const getOrderItemStyle = (status: string) => {
|
||||
const config = statusConfig[status as keyof typeof statusConfig] || statusConfig.pending;
|
||||
return cn(
|
||||
"group relative p-4 pl-12 rounded-xl bg-white/50 dark:bg-gray-800/50 border-l-4 hover:bg-white/80 dark:hover:bg-gray-800/80 transition-all duration-200 cursor-pointer",
|
||||
config.borderColor
|
||||
);
|
||||
};
|
||||
|
||||
// 模态框内容组件
|
||||
const OrderDetailModal = ({ order, isOpen, onOpenChange }: { order: OrderDetailResponse | null, isOpen: boolean, onOpenChange: (open: boolean) => void }) => {
|
||||
if (!order) return null;
|
||||
|
||||
const renderDetailItem = (label: string, value: React.ReactNode, fullWidth = false) => (
|
||||
<div className={cn("flex flex-col gap-1", fullWidth ? "col-span-2" : "col-span-1")}>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">{label}</p>
|
||||
<div className="text-sm font-medium text-gray-800 dark:text-gray-200 break-words">{value || "N/A"}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderSection = (title: string, icon: React.ReactNode, children: React.ReactNode) => (
|
||||
<div className="p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50 border border-gray-200/50 dark:border-gray-700/50">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
{icon}
|
||||
<h4 className="text-md font-semibold text-gray-800 dark:text-gray-200">{title}</h4>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="apple-glass-card max-w-4xl max-h-[90vh] overflow-y-auto rounded-2xl shadow-2xl border-0 bg-white/90 dark:bg-gray-900/90 backdrop-blur-xl [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
|
||||
<DialogHeader className="p-6 pb-4">
|
||||
<DialogTitle className="text-xl font-bold text-gray-900 dark:text-gray-100">订单详情</DialogTitle>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="max-h-[calc(90vh-120px)] px-6 pb-6">
|
||||
<div className="space-y-4">
|
||||
{renderSection("基本信息", <Info className="w-5 h-5 text-blue-500" />,
|
||||
<>
|
||||
{renderDetailItem("状态", getStatusBadge(order.status))}
|
||||
{renderDetailItem("创建时间", formatDate(order.created_at))}
|
||||
{renderDetailItem("更新时间", formatDate(order.updated_at))}
|
||||
{order.final_order_url && renderDetailItem("最终订单URL", order.final_order_url.toString(), true)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{order.user_data && renderSection("用户信息", <User className="w-5 h-5 text-purple-500" />,
|
||||
<>
|
||||
{renderDetailItem("姓名", order.user_data.full_name)}
|
||||
{renderDetailItem("邮箱", order.user_data.email)}
|
||||
{renderDetailItem("电话", order.user_data.phone)}
|
||||
{renderDetailItem("地址", order.user_data.full_address, true)}
|
||||
{renderDetailItem("名字", order.user_data.first_name)}
|
||||
{renderDetailItem("姓氏", order.user_data.last_name)}
|
||||
{renderDetailItem("街道地址", order.user_data.street_address)}
|
||||
{renderDetailItem("城市", order.user_data.city)}
|
||||
{renderDetailItem("州/省", order.user_data.state)}
|
||||
{renderDetailItem("邮政编码", order.user_data.zip_code)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{order.links && renderSection("链接信息", <LinkIcon className="w-5 h-5 text-green-500" />,
|
||||
<>
|
||||
{renderDetailItem("链接URL", order.links.url, true)}
|
||||
{renderDetailItem("金额", `¥${order.links.amount.toFixed(2)}`)}
|
||||
{renderDetailItem("链接创建时间", formatDate(order.links.created_at))}
|
||||
{renderDetailItem("链接更新时间", formatDate(order.links.updated_at))}
|
||||
<div className="col-span-2">
|
||||
<AppleButton onClick={() => window.open(order.links.url, '_blank')} size="sm">
|
||||
<ExternalLink className="w-4 h-4 mr-2" />
|
||||
打开链接
|
||||
</AppleButton>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{order.gift_cards && order.gift_cards.length > 0 && (
|
||||
<div className="p-4 rounded-lg bg-gray-50 dark:bg-gray-800/50 border border-gray-200/50 dark:border-gray-700/50">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<CreditCard className="w-5 h-5 text-orange-500" />
|
||||
<h4 className="text-md font-semibold text-gray-800 dark:text-gray-200">礼品卡 ({order.gift_cards.length})</h4>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{order.gift_cards.map((card, index) => (
|
||||
<div key={index} className="p-3 rounded-md bg-white dark:bg-gray-700/50 border border-gray-200 dark:border-gray-600/50">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-2 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<Hash className="w-3 h-3 text-gray-400" />
|
||||
<span className="font-mono text-gray-800 dark:text-gray-200">{card.card_code}</span>
|
||||
</div>
|
||||
<div>{getStatusBadge(card.status)}</div>
|
||||
{card.failure_reason && <p className="text-red-500 text-xs col-span-full">原因: {card.failure_reason.toString()}</p>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{order.failure_reason && renderSection("失败信息", <AlertTriangle className="w-5 h-5 text-red-500" />,
|
||||
<div className="col-span-2 text-sm text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 p-3 rounded-md">
|
||||
{order.failure_reason.toString()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export function OrderList({
|
||||
refreshEnabled = false,
|
||||
refreshInterval = 5000,
|
||||
@@ -53,74 +204,28 @@ export function OrderList({
|
||||
}: OrderListProps) {
|
||||
const [statusFilter, setStatusFilter] = useState<OrderResultStatus | "all">("all");
|
||||
const [page, setPage] = useState(1);
|
||||
const [selectedOrder, setSelectedOrder] = useState<OrderDetailResponse | null>(null);
|
||||
const pageSize = 20;
|
||||
|
||||
// 获取订单列表
|
||||
const {
|
||||
data: ordersData,
|
||||
isLoading,
|
||||
error,
|
||||
refetch,
|
||||
isRefetching
|
||||
} = useGetOrdersApiV1OrdersListGet(
|
||||
{
|
||||
skip: (page - 1) * pageSize,
|
||||
limit: pageSize,
|
||||
status_filter: statusFilter === "all" ? undefined : statusFilter,
|
||||
},
|
||||
{
|
||||
query: {
|
||||
enabled: true,
|
||||
refetchInterval: refreshEnabled ? refreshInterval : false,
|
||||
retry: 2,
|
||||
staleTime: 30000,
|
||||
},
|
||||
}
|
||||
const { data: ordersData, isLoading, error, refetch, isRefetching } = useGetOrdersApiV1OrdersListGet(
|
||||
{ skip: (page - 1) * pageSize, limit: pageSize, status_filter: statusFilter === "all" ? undefined : statusFilter },
|
||||
{ query: { enabled: true, refetchInterval: refreshEnabled ? refreshInterval : false, retry: 2, staleTime: 30000 } }
|
||||
);
|
||||
|
||||
// 过滤数据
|
||||
const filteredOrders = useMemo(() => {
|
||||
if (!ordersData) return [];
|
||||
return [...ordersData];
|
||||
}, [ordersData]);
|
||||
|
||||
const handleRefresh = () => {
|
||||
refetch();
|
||||
};
|
||||
const filteredOrders = useMemo(() => ordersData ? [...ordersData] : [], [ordersData]);
|
||||
|
||||
const handleRefresh = () => refetch();
|
||||
const handleStatusFilterChange = (value: string) => {
|
||||
setStatusFilter(value as OrderResultStatus | "all");
|
||||
setPage(1); // 重置页码
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
const config = statusConfig[status as keyof typeof statusConfig] || statusConfig.pending;
|
||||
return (
|
||||
<Badge variant={config.variant} className="px-2 py-1">
|
||||
{config.label}
|
||||
</Badge>
|
||||
);
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const handleOrderClick = (order: OrderDetailResponse) => {
|
||||
if (order.final_order_url) {
|
||||
window.open(order.final_order_url.toString(), '_blank');
|
||||
}
|
||||
setSelectedOrder(order);
|
||||
};
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = Math.max(1, Math.ceil(filteredOrders.length / pageSize));
|
||||
const hasNextPage = filteredOrders.length === pageSize; // 如果返回数据等于页大小,可能还有下一页
|
||||
const hasNextPage = filteredOrders.length === pageSize;
|
||||
const hasPreviousPage = page > 1;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
@@ -129,16 +234,9 @@ export function OrderList({
|
||||
<div className="w-16 h-16 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center mb-4">
|
||||
<ShoppingCart className="w-8 h-8 text-red-600 dark:text-red-400" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
||||
加载订单失败
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{(error as any)?.message || "请检查网络连接或稍后重试"}
|
||||
</p>
|
||||
<AppleButton onClick={handleRefresh} variant="outline">
|
||||
<RefreshCw className="w-4 h-4 mr-2" />
|
||||
重试
|
||||
</AppleButton>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">加载订单失败</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">{error instanceof Error ? error.message : "请检查网络连接或稍后重试"}</p>
|
||||
<AppleButton onClick={handleRefresh} variant="outline"><RefreshCw className="w-4 h-4 mr-2" />重试</AppleButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -146,110 +244,76 @@ export function OrderList({
|
||||
|
||||
return (
|
||||
<div className={cn("apple-glass-card rounded-2xl p-6 transition-all duration-300", className)}>
|
||||
{/* 头部 */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-full flex items-center justify-center bg-blue-600">
|
||||
<ShoppingCart className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div className="w-10 h-10 rounded-full flex items-center justify-center bg-blue-600"><ShoppingCart className="w-5 h-5 text-white" /></div>
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
订单列表
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
查看和管理所有订单信息 • {filteredOrders.length} 个订单
|
||||
</p>
|
||||
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100">订单列表</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">查看和管理所有订单信息 • {filteredOrders.length} 个订单</p>
|
||||
</div>
|
||||
</div>
|
||||
<AppleButton
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleRefresh}
|
||||
disabled={isLoading || isRefetching}
|
||||
className="w-10 h-10 rounded-xl"
|
||||
>
|
||||
<RefreshCw
|
||||
className={cn("h-4 w-4", isRefetching && "animate-spin")}
|
||||
/>
|
||||
<AppleButton variant="outline" size="icon" onClick={handleRefresh} disabled={isLoading || isRefetching} className="w-10 h-10 rounded-xl">
|
||||
<RefreshCw className={cn("h-4 w-4", isRefetching && "animate-spin")} />
|
||||
</AppleButton>
|
||||
</div>
|
||||
|
||||
{/* 状态筛选按钮 */}
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Filter className="w-4 h-4 text-gray-500" />
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">状态筛选:</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<AppleButton
|
||||
variant={statusFilter === "all" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => handleStatusFilterChange("all")}
|
||||
className={cn(
|
||||
"transition-all duration-200",
|
||||
statusFilter === "all"
|
||||
? "bg-blue-600 text-white shadow-md"
|
||||
: "bg-white/50 dark:bg-gray-800/50 hover:bg-white/80 dark:hover:bg-gray-800/80"
|
||||
)}
|
||||
>
|
||||
全部状态
|
||||
</AppleButton>
|
||||
<AppleButton
|
||||
variant={statusFilter === "pending" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => handleStatusFilterChange("pending")}
|
||||
className={cn(
|
||||
"transition-all duration-200",
|
||||
statusFilter === "pending"
|
||||
? "bg-gray-600 text-white shadow-md"
|
||||
: "bg-white/50 dark:bg-gray-800/50 hover:bg-white/80 dark:hover:bg-gray-800/80"
|
||||
)}
|
||||
>
|
||||
待处理
|
||||
</AppleButton>
|
||||
<AppleButton
|
||||
variant={statusFilter === "processing" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => handleStatusFilterChange("processing")}
|
||||
className={cn(
|
||||
"transition-all duration-200",
|
||||
statusFilter === "processing"
|
||||
? "bg-yellow-600 text-white shadow-md"
|
||||
: "bg-white/50 dark:bg-gray-800/50 hover:bg-white/80 dark:hover:bg-gray-800/80"
|
||||
)}
|
||||
>
|
||||
处理中
|
||||
</AppleButton>
|
||||
<AppleButton
|
||||
variant={statusFilter === "success" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => handleStatusFilterChange("success")}
|
||||
className={cn(
|
||||
"transition-all duration-200",
|
||||
statusFilter === "success"
|
||||
? "bg-green-600 text-white shadow-md"
|
||||
: "bg-white/50 dark:bg-gray-800/50 hover:bg-white/80 dark:hover:bg-gray-800/80"
|
||||
)}
|
||||
>
|
||||
成功
|
||||
</AppleButton>
|
||||
<AppleButton
|
||||
variant={statusFilter === "failure" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => handleStatusFilterChange("failure")}
|
||||
className={cn(
|
||||
"transition-all duration-200",
|
||||
statusFilter === "failure"
|
||||
? "bg-red-600 text-white shadow-md"
|
||||
: "bg-white/50 dark:bg-gray-800/50 hover:bg-white/80 dark:hover:bg-gray-800/80"
|
||||
)}
|
||||
>
|
||||
失败
|
||||
</AppleButton>
|
||||
{(["all", "pending", "processing", "success", "failure"] as const).map(status => {
|
||||
const isActive = statusFilter === status;
|
||||
|
||||
return (
|
||||
<AppleButton
|
||||
key={status}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleStatusFilterChange(status)}
|
||||
className={cn(
|
||||
"transition-all duration-200 border",
|
||||
isActive
|
||||
? (status === "all"
|
||||
? "bg-blue-600 text-white shadow-md border-blue-600"
|
||||
: (() => {
|
||||
const config = statusConfig[status as keyof typeof statusConfig];
|
||||
return cn(
|
||||
"text-white shadow-md border",
|
||||
config.bgColor.replace("/20", ""),
|
||||
config.borderColor
|
||||
);
|
||||
})()
|
||||
)
|
||||
: "bg-white/50 dark:bg-gray-800/50 hover:bg-white/80 dark:hover:bg-gray-800/80 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{status !== "all" && (
|
||||
(() => {
|
||||
const config = statusConfig[status as keyof typeof statusConfig];
|
||||
const Icon = config.icon;
|
||||
const isProcessing = status === 'processing';
|
||||
return (
|
||||
<Icon
|
||||
className={cn(
|
||||
"w-3 h-3",
|
||||
isActive ? "text-white" : config.color,
|
||||
isProcessing && "animate-spin"
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})()
|
||||
)}
|
||||
{status === "all" ? "全部状态" : statusConfig[status as keyof typeof statusConfig].label}
|
||||
</div>
|
||||
</AppleButton>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 订单列表 */}
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col items-center justify-center py-12">
|
||||
<div className="w-8 h-8 border-2 border-blue-600 border-t-transparent rounded-full animate-spin mb-4"></div>
|
||||
@@ -260,47 +324,60 @@ export function OrderList({
|
||||
<div className="w-16 h-16 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center mb-4">
|
||||
<ShoppingCart className="w-8 h-8 text-gray-400" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
||||
暂无订单数据
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
{statusFilter !== "all" ? "没有找到符合条件的订单" : "还没有任何订单记录"}
|
||||
</p>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">暂无订单数据</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400">{statusFilter !== "all" ? "没有找到符合条件的订单" : "还没有任何订单记录"}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{filteredOrders.map((order) => (
|
||||
<div
|
||||
key={order.id}
|
||||
className="group p-4 rounded-xl bg-white/50 dark:bg-gray-800/50 border border-gray-200/50 dark:border-gray-700/50 hover:bg-white/80 dark:hover:bg-gray-800/80 transition-all duration-200 cursor-pointer"
|
||||
className={getOrderItemStyle(order.status)}
|
||||
onClick={() => handleOrderClick(order)}
|
||||
>
|
||||
{/* 左侧状态图标装饰 */}
|
||||
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
||||
{(() => {
|
||||
const config = statusConfig[order.status as keyof typeof statusConfig] || statusConfig.pending;
|
||||
const Icon = config.icon;
|
||||
const isProcessing = order.status === 'processing';
|
||||
return (
|
||||
<Icon
|
||||
className={cn(
|
||||
"w-5 h-5",
|
||||
config.color,
|
||||
isProcessing && "animate-spin"
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
{getStatusBadge(order.status)}
|
||||
{order.final_order_url && (
|
||||
<ExternalLink className="w-4 h-4 text-gray-400 group-hover:text-blue-600 transition-colors" />
|
||||
)}
|
||||
<div className="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
<Info className="w-3 h-3" />
|
||||
<span>点击查看详情</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3 text-sm">
|
||||
{order.order_number && (
|
||||
<div className="flex items-center gap-2">
|
||||
<ShoppingCart className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-gray-600 dark:text-gray-400">订单号:</span>
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100 truncate">
|
||||
{order.order_number.toString()}
|
||||
</span>
|
||||
{order.user_data && (
|
||||
<div className="flex items-center gap-2" title={order.user_data.full_name}>
|
||||
<User className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-gray-600 dark:text-gray-400">用户:</span>
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100 truncate">{order.user_data.full_name}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{order.user_info && (
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-gray-600 dark:text-gray-400">用户:</span>
|
||||
{order.links && (
|
||||
<div className="flex items-center gap-2" title={order.links.url}>
|
||||
<LinkIcon className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-gray-600 dark:text-gray-400">链接:</span>
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100 truncate">
|
||||
{`${order.user_info.first_name || ''} ${order.user_info.last_name || ''}`.trim() || order.user_info.email}
|
||||
¥{order.links.amount.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -308,17 +385,14 @@ export function OrderList({
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-gray-600 dark:text-gray-400">创建时间:</span>
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100">
|
||||
{formatDate(order.created_at)}
|
||||
</span>
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100">{formatDate(order.created_at)}</span>
|
||||
</div>
|
||||
|
||||
{order.gift_cards && order.gift_cards.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<CreditCard className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-gray-600 dark:text-gray-400">礼品卡:</span>
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100">
|
||||
{order.gift_cards.length} 张
|
||||
</span>
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100">{order.gift_cards.length} 张</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -337,11 +411,13 @@ export function OrderList({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 分页控件 */}
|
||||
{filteredOrders.length > 0 && (
|
||||
<div className="mt-6 flex justify-between items-center">
|
||||
<div className="mt-6 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
第 {page} 页 • 显示 {filteredOrders.length} 条记录
|
||||
<div>第 {page} 页 • 显示 {filteredOrders.length} 条记录</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-500 mt-1">
|
||||
{hasNextPage ? "可能还有更多数据" : "已到达最后一页"}
|
||||
</div>
|
||||
</div>
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
@@ -350,26 +426,19 @@ export function OrderList({
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (page > 1) setPage(page - 1);
|
||||
if (hasPreviousPage) setPage(page - 1);
|
||||
}}
|
||||
className={cn(
|
||||
"cursor-pointer transition-colors",
|
||||
page <= 1 && "pointer-events-none opacity-50"
|
||||
!hasPreviousPage && "pointer-events-none opacity-50"
|
||||
)}
|
||||
/>
|
||||
</PaginationItem>
|
||||
|
||||
{/* 页码显示 - 简化版本 */}
|
||||
<PaginationItem>
|
||||
<PaginationLink
|
||||
href="#"
|
||||
isActive={true}
|
||||
className="cursor-default"
|
||||
>
|
||||
<PaginationLink href="#" isActive={true} className="cursor-default">
|
||||
{page}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
href="#"
|
||||
@@ -387,6 +456,8 @@ export function OrderList({
|
||||
</Pagination>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<OrderDetailModal order={selectedOrder} isOpen={!!selectedOrder} onOpenChange={(open) => !open && setSelectedOrder(null)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,20 +5,20 @@
|
||||
* Apple礼品卡兑换服务后端API - 基于FastAPI的现代异步微服务架构
|
||||
* OpenAPI spec version: 2.0.0
|
||||
*/
|
||||
import type { GiftCardStatus } from "./giftCardStatus";
|
||||
import type { GiftCardResponseFailureReason } from "./giftCardResponseFailureReason";
|
||||
import type { GiftCardInfoResponseFailureReason } from "./giftCardInfoResponseFailureReason";
|
||||
|
||||
/**
|
||||
* 礼品卡响应模型 - 与数据表字段完全一致
|
||||
* 礼品卡信息响应模型 - 与数据表字段完全一致
|
||||
*/
|
||||
export interface GiftCardResponse {
|
||||
export interface GiftCardInfoResponse {
|
||||
/** 礼品卡ID */
|
||||
id: string;
|
||||
/** 礼品卡号码 */
|
||||
card_code: string;
|
||||
status: GiftCardStatus;
|
||||
/** 礼品卡状态 */
|
||||
status: string;
|
||||
/** 失败原因 */
|
||||
failure_reason?: GiftCardResponseFailureReason;
|
||||
failure_reason?: GiftCardInfoResponseFailureReason;
|
||||
/** 订单结果ID */
|
||||
order_result_id: string;
|
||||
/** 创建时间 */
|
||||
@@ -9,4 +9,4 @@
|
||||
/**
|
||||
* 失败原因
|
||||
*/
|
||||
export type GiftCardResponseFailureReason = string | null;
|
||||
export type GiftCardInfoResponseFailureReason = string | null;
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.11.2 🍺
|
||||
* Do not edit manually.
|
||||
* Apple Gift Card Exchange
|
||||
* Apple礼品卡兑换服务后端API - 基于FastAPI的现代异步微服务架构
|
||||
* OpenAPI spec version: 2.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 礼品卡状态
|
||||
*/
|
||||
export type GiftCardStatus =
|
||||
(typeof GiftCardStatus)[keyof typeof GiftCardStatus];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const GiftCardStatus = {
|
||||
pending: "pending",
|
||||
success: "success",
|
||||
failure: "failure",
|
||||
} as const;
|
||||
@@ -10,9 +10,8 @@ export * from "./exportOrdersApiV1OrdersExportGetParams";
|
||||
export * from "./getLinksApiV1LinksGetParams";
|
||||
export * from "./getOrdersApiV1OrdersListGetParams";
|
||||
export * from "./getUserDataListApiV1UserDataGetParams";
|
||||
export * from "./giftCardResponse";
|
||||
export * from "./giftCardResponseFailureReason";
|
||||
export * from "./giftCardStatus";
|
||||
export * from "./giftCardInfoResponse";
|
||||
export * from "./giftCardInfoResponseFailureReason";
|
||||
export * from "./giftCardSubmissionRequest";
|
||||
export * from "./giftCardSubmissionResponse";
|
||||
export * from "./hTTPValidationError";
|
||||
@@ -22,7 +21,6 @@ export * from "./linkResponse";
|
||||
export * from "./orderDetailResponse";
|
||||
export * from "./orderDetailResponseFailureReason";
|
||||
export * from "./orderDetailResponseFinalOrderUrl";
|
||||
export * from "./orderDetailResponseOrderNumber";
|
||||
export * from "./orderResultStatus";
|
||||
export * from "./orderStatsResponse";
|
||||
export * from "./orderTaskStatus";
|
||||
|
||||
@@ -5,22 +5,37 @@
|
||||
* Apple礼品卡兑换服务后端API - 基于FastAPI的现代异步微服务架构
|
||||
* OpenAPI spec version: 2.0.0
|
||||
*/
|
||||
import type { OrderDetailResponseOrderNumber } from "./orderDetailResponseOrderNumber";
|
||||
import type { OrderResultStatus } from "./orderResultStatus";
|
||||
import type { OrderDetailResponseFinalOrderUrl } from "./orderDetailResponseFinalOrderUrl";
|
||||
import type { OrderDetailResponseFailureReason } from "./orderDetailResponseFailureReason";
|
||||
import type { UserInfoResponse } from "./userInfoResponse";
|
||||
import type { GiftCardResponse } from "./giftCardResponse";
|
||||
import type { LinkResponse } from "./linkResponse";
|
||||
import type { GiftCardInfoResponse } from "./giftCardInfoResponse";
|
||||
|
||||
/**
|
||||
* 订单详情响应
|
||||
* 订单详情响应 - 与数据库结构完全一致
|
||||
*/
|
||||
export interface OrderDetailResponse {
|
||||
/** 订单ID */
|
||||
id: string;
|
||||
status: string;
|
||||
order_number: OrderDetailResponseOrderNumber;
|
||||
final_order_url: OrderDetailResponseFinalOrderUrl;
|
||||
failure_reason: OrderDetailResponseFailureReason;
|
||||
/** 订单状态 */
|
||||
status: OrderResultStatus;
|
||||
/** 创建时间 */
|
||||
created_at: string;
|
||||
user_info: UserInfoResponse;
|
||||
gift_cards: GiftCardResponse[];
|
||||
/** 更新时间 */
|
||||
updated_at: string;
|
||||
/** 最终订单URL */
|
||||
final_order_url?: OrderDetailResponseFinalOrderUrl;
|
||||
/** 失败原因 */
|
||||
failure_reason?: OrderDetailResponseFailureReason;
|
||||
/** 用户数据ID */
|
||||
user_data_id: string;
|
||||
/** 链接ID */
|
||||
links_id: string;
|
||||
/** 用户数据 */
|
||||
user_data: UserInfoResponse;
|
||||
/** 链接信息 */
|
||||
links: LinkResponse;
|
||||
/** 礼品卡列表 */
|
||||
gift_cards?: GiftCardInfoResponse[];
|
||||
}
|
||||
|
||||
@@ -6,4 +6,7 @@
|
||||
* OpenAPI spec version: 2.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 失败原因
|
||||
*/
|
||||
export type OrderDetailResponseFailureReason = string | null;
|
||||
|
||||
@@ -6,4 +6,7 @@
|
||||
* OpenAPI spec version: 2.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 最终订单URL
|
||||
*/
|
||||
export type OrderDetailResponseFinalOrderUrl = string | null;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.11.2 🍺
|
||||
* Do not edit manually.
|
||||
* Apple Gift Card Exchange
|
||||
* Apple礼品卡兑换服务后端API - 基于FastAPI的现代异步微服务架构
|
||||
* OpenAPI spec version: 2.0.0
|
||||
*/
|
||||
|
||||
export type OrderDetailResponseOrderNumber = string | null;
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 用户信息响应 - 与数据表字段完全一致
|
||||
* 用户信息响应模型 - 与数据表字段完全一致
|
||||
*/
|
||||
export interface UserInfoResponse {
|
||||
/** 用户数据唯一标识 */
|
||||
@@ -32,4 +32,8 @@ export interface UserInfoResponse {
|
||||
created_at: string;
|
||||
/** 更新时间 */
|
||||
updated_at: string;
|
||||
/** 完整姓名 */
|
||||
full_name: string;
|
||||
/** 完整地址 */
|
||||
full_address: string;
|
||||
}
|
||||
|
||||
@@ -10,13 +10,7 @@ export const config = {
|
||||
// API基础URL - 支持代理模式
|
||||
apiBaseUrl: process.env.NEXT_PUBLIC_USE_PROXY === 'true'
|
||||
? '/'
|
||||
: (process.env.NEXT_PUBLIC_TARGET_API_URL || 'http://localhost:3001'),
|
||||
|
||||
// 目标API URL(用于代理)
|
||||
targetApiUrl: process.env.NEXT_PUBLIC_TARGET_API_URL || 'http://localhost:3001',
|
||||
|
||||
// 是否使用代理模式
|
||||
useProxy: process.env.NEXT_PUBLIC_USE_PROXY === 'true',
|
||||
: (process.env.NEXT_PUBLIC_TARGET_API_URL || 'http://localhost:3000'),
|
||||
|
||||
// 环境
|
||||
env,
|
||||
|
||||
Reference in New Issue
Block a user