From 347edb81032792a62d71b556cc89e47aa65c01f5 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Mon, 22 Jun 2026 13:11:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=A2=9E=E5=8A=A0=20web=20?= =?UTF-8?q?=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/web.db | Bin 0 -> 36864 bytes gui/app.py | 91 +- main.py | 7 + pyproject.toml | 9 +- requirements.txt | 8 + start_web.sh | 62 + web/backend/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 152 bytes .../__pycache__/database.cpython-312.pyc | Bin 0 -> 2295 bytes web/backend/__pycache__/deps.cpython-312.pyc | Bin 0 -> 2459 bytes web/backend/__pycache__/main.cpython-312.pyc | Bin 0 -> 1945 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 4743 bytes .../__pycache__/permissions.cpython-312.pyc | Bin 0 -> 2212 bytes .../__pycache__/schemas.cpython-312.pyc | Bin 0 -> 5390 bytes .../__pycache__/security.cpython-312.pyc | Bin 0 -> 2334 bytes web/backend/database.py | 53 + web/backend/deps.py | 50 + web/backend/main.py | 51 + web/backend/models.py | 89 + web/backend/permissions.py | 70 + web/backend/routers/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 160 bytes .../__pycache__/accounts.cpython-312.pyc | Bin 0 -> 7871 bytes .../routers/__pycache__/auth.cpython-312.pyc | Bin 0 -> 3405 bytes .../routers/__pycache__/login.cpython-312.pyc | Bin 0 -> 6854 bytes .../routers/__pycache__/proxy.cpython-312.pyc | Bin 0 -> 6779 bytes .../routers/__pycache__/users.cpython-312.pyc | Bin 0 -> 6074 bytes web/backend/routers/accounts.py | 152 + web/backend/routers/auth.py | 57 + web/backend/routers/login.py | 137 + web/backend/routers/proxy.py | 146 + web/backend/routers/users.py | 117 + web/backend/schemas.py | 116 + web/backend/security.py | 38 + web/backend/services/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 161 bytes .../__pycache__/login_service.cpython-312.pyc | Bin 0 -> 6945 bytes web/backend/services/login_service.py | 139 + web/frontend/.gitignore | 24 + web/frontend/README.md | 73 + web/frontend/eslint.config.js | 22 + web/frontend/index.html | 13 + web/frontend/package-lock.json | 4084 +++++++++++++++++ web/frontend/package.json | 35 + web/frontend/public/favicon.svg | 1 + web/frontend/public/icons.svg | 24 + web/frontend/src/App.tsx | 42 + web/frontend/src/api/index.ts | 31 + web/frontend/src/api/modules.ts | 60 + web/frontend/src/assets/hero.png | Bin 0 -> 13057 bytes web/frontend/src/assets/react.svg | 1 + web/frontend/src/assets/vite.svg | 1 + web/frontend/src/index.css | 9 + web/frontend/src/layouts/MainLayout.tsx | 114 + web/frontend/src/main.tsx | 10 + web/frontend/src/pages/AccountsPage.tsx | 174 + web/frontend/src/pages/DashboardPage.tsx | 48 + web/frontend/src/pages/LoginPage.tsx | 66 + web/frontend/src/pages/LoginTasksPage.tsx | 208 + web/frontend/src/pages/ProxyPage.tsx | 109 + web/frontend/src/pages/UsersPage.tsx | 164 + web/frontend/src/store/auth.ts | 59 + web/frontend/tsconfig.app.json | 25 + web/frontend/tsconfig.json | 7 + web/frontend/tsconfig.node.json | 24 + web/frontend/vite.config.ts | 17 + 66 files changed, 6816 insertions(+), 21 deletions(-) create mode 100644 data/web.db create mode 100755 start_web.sh create mode 100644 web/backend/__init__.py create mode 100644 web/backend/__pycache__/__init__.cpython-312.pyc create mode 100644 web/backend/__pycache__/database.cpython-312.pyc create mode 100644 web/backend/__pycache__/deps.cpython-312.pyc create mode 100644 web/backend/__pycache__/main.cpython-312.pyc create mode 100644 web/backend/__pycache__/models.cpython-312.pyc create mode 100644 web/backend/__pycache__/permissions.cpython-312.pyc create mode 100644 web/backend/__pycache__/schemas.cpython-312.pyc create mode 100644 web/backend/__pycache__/security.cpython-312.pyc create mode 100644 web/backend/database.py create mode 100644 web/backend/deps.py create mode 100644 web/backend/main.py create mode 100644 web/backend/models.py create mode 100644 web/backend/permissions.py create mode 100644 web/backend/routers/__init__.py create mode 100644 web/backend/routers/__pycache__/__init__.cpython-312.pyc create mode 100644 web/backend/routers/__pycache__/accounts.cpython-312.pyc create mode 100644 web/backend/routers/__pycache__/auth.cpython-312.pyc create mode 100644 web/backend/routers/__pycache__/login.cpython-312.pyc create mode 100644 web/backend/routers/__pycache__/proxy.cpython-312.pyc create mode 100644 web/backend/routers/__pycache__/users.cpython-312.pyc create mode 100644 web/backend/routers/accounts.py create mode 100644 web/backend/routers/auth.py create mode 100644 web/backend/routers/login.py create mode 100644 web/backend/routers/proxy.py create mode 100644 web/backend/routers/users.py create mode 100644 web/backend/schemas.py create mode 100644 web/backend/security.py create mode 100644 web/backend/services/__init__.py create mode 100644 web/backend/services/__pycache__/__init__.cpython-312.pyc create mode 100644 web/backend/services/__pycache__/login_service.cpython-312.pyc create mode 100644 web/backend/services/login_service.py create mode 100644 web/frontend/.gitignore create mode 100644 web/frontend/README.md create mode 100644 web/frontend/eslint.config.js create mode 100644 web/frontend/index.html create mode 100644 web/frontend/package-lock.json create mode 100644 web/frontend/package.json create mode 100644 web/frontend/public/favicon.svg create mode 100644 web/frontend/public/icons.svg create mode 100644 web/frontend/src/App.tsx create mode 100644 web/frontend/src/api/index.ts create mode 100644 web/frontend/src/api/modules.ts create mode 100644 web/frontend/src/assets/hero.png create mode 100644 web/frontend/src/assets/react.svg create mode 100644 web/frontend/src/assets/vite.svg create mode 100644 web/frontend/src/index.css create mode 100644 web/frontend/src/layouts/MainLayout.tsx create mode 100644 web/frontend/src/main.tsx create mode 100644 web/frontend/src/pages/AccountsPage.tsx create mode 100644 web/frontend/src/pages/DashboardPage.tsx create mode 100644 web/frontend/src/pages/LoginPage.tsx create mode 100644 web/frontend/src/pages/LoginTasksPage.tsx create mode 100644 web/frontend/src/pages/ProxyPage.tsx create mode 100644 web/frontend/src/pages/UsersPage.tsx create mode 100644 web/frontend/src/store/auth.ts create mode 100644 web/frontend/tsconfig.app.json create mode 100644 web/frontend/tsconfig.json create mode 100644 web/frontend/tsconfig.node.json create mode 100644 web/frontend/vite.config.ts diff --git a/data/web.db b/data/web.db new file mode 100644 index 0000000000000000000000000000000000000000..ecf8064d22fa4a83c7a613935a2b1a1e35a5124a GIT binary patch literal 36864 zcmeI5O>Em#9Kh|QT|TxX95!WRD)rP=Y6Q*v6({KsEbZKnElbw4soEhC+{8`2C2?ju zTf+g=jqSiRX&jJtK;lD8NC<5w5CS9)+&OUIFaZ)0-MZ@}#DxRzIdST=3CoV_{#MgC z@BjW^zyI@dolvfC9rK18m2luAKf zQ`T}NwWw5bhNNH6bIX#EU(G26TbpMhBPQlWY9@73T%@}6r_w3PrbCTbhC){lx?I7b zA@bT~Ls~A$wobNPchkpwVmv9f8}fRT0`fgCPKg;Rm7b?k*<|t+%ImQ_Pz#C7#B?Gv zBJiQM4pKg^)+z?drlY=YNJdR>X>)7{Tg|KL1x2Rj#l?C2vMlSmRFqr2B22TJCQF7~ z$Sv19+Rit{=>m7Bh5SHt0IyGI^8VcUf`8{YP4A@43WiTh{x>Bs5Kn>Mu zb8R06mcZT_4+E>68nIakU45zF6&x5KuLsOwV0E&d_p}WFtCbq*T78^HMJjJqhGApv z<*y?5msPFMsvR2<+Iz@lNh!6&I}DiOT#sgQN?EGrP*9g-t<5B^d&831qpE6#HDs{J z#>n2RArC1U3`;RU(gl48VIynXudhTd@O2_6fQ?^>q_bJoaN zl`~&Cn_#l3nc1xE?9kp7PY7lUHDYOpt{fm;!C;Vl&tr}>Q*8cnw6Sig^*#e^pZ~2R zH@7P3tF5GW%mSKP+L}esS)lD4qUbpZjmt~2y_QdRaMr2?^HlMKW7ZqO@6Db5mx;ie z{-4kjY(M}A00AHX1b_e#00KY&2mk>f00h<#IK9NcdjDb1a3Z1Cs`#oBbCg{^%CJWn znl5W=r!(^T#f9v2c8Z(IUXsE_GAmSO)YFTGa;{KUj@70k`7@{GDJDH}%9x#K+Ss`J z*@KU6KKS~^=DiPDniWQAVU%SlIxaca=of4?4WW;ClZcj!Uh|nq;V& z6dOlWJ71*KsG zT^QQ@@z&<8JKaNLVl>T0I)#QMoUlGfdWqnA|Bxq|NJxdUQfcz4M>2|`FDdg&a;23Y`Q|s* z9)9=5!@Hk8ymfu!gWEgf5W;*U&IuhNY)ax@G_IY2--y6Jfk)^GHXr~5fB+Bx0zd!= z00AHX1b_e#00KbZ86&XIIY3(1X`DXiAlbS&;&3~I9`iy1{`{W|{7s-A*nj{K00KY& z2mk>f00e*l5C8%|00;nq-9*4cdI-1A=fe+_m(O>c2>cwl88{o*@BhR9mA~W{{C&P( ze4qHrNCX=Y00KY&2mk>f00e*l5C8%|;CUxN``m;l7(C!>e6!nJSwyNCtLQ3te<}T^eJo*-Ym=~fn7kSNv{e%Xaz?utgj5SuHFk~IAj3R3& zfwo%!Ono*QN3Happ5kn12z(6PHPGL4oaU_t5iS~MBV&Eo&(J_KP8`)(V~GbV*=;d4 zZc{*LIOe$1)U|T7>9#(6@1)Gc7<7xn>kbppk!^sKGQQPff00e*l5C8%|00?v<(B~vQ&ORqWI*_yvfBzr-(?8gN01yBI zKmZ5;0U!VbfB+Bx0zd!=0D)ah0I&ZK@Bepkfgv>@00e*l5C8%|00;m9AOHk_01yBI Gh`@iLA`t5U literal 0 HcmV?d00001 diff --git a/gui/app.py b/gui/app.py index 0313851..1bddcee 100644 --- a/gui/app.py +++ b/gui/app.py @@ -3,6 +3,7 @@ import json import re import threading +import time import tkinter as tk from tkinter import ttk, messagebox, filedialog from datetime import datetime @@ -566,17 +567,15 @@ class DouyuLoginApp: self.whitelist_status_var.set(status) self.whitelist_test_btn.configure(state='normal' if enabled else 'disabled') - @staticmethod - def _get_exit_ip_for_test(proxy_config: ProxyConfig) -> Optional[str]: + def _get_exit_ip_for_test(self, proxy_config: ProxyConfig) -> Optional[str]: """获取出口IP用于白名单测试""" proxy_url = None if proxy_config.enabled: if proxy_config.api_url: try: resp = requests.get(proxy_config.api_url, timeout=10) - match = re.search(r'(\d+\.\d+\.\d+\.\d+):(\d+)', resp.text) - if match: - proxy_url = f"http://{match.group(1)}:{match.group(2)}" + resp.raise_for_status() + proxy_url = DouyuLoginApp._parse_proxy_url(resp.text.strip()) except Exception: pass if not proxy_url: @@ -755,15 +754,37 @@ class DouyuLoginApp: self.proxy_test_queue.put(result) @staticmethod - def _fetch_proxy_from_api(api_url: str) -> str: - """从代理API获取一个代理地址。""" - response = requests.get(api_url, timeout=10) - response.raise_for_status() - text = response.text.strip() + def _parse_proxy_url(text: str) -> str: + """从代理API响应中解析代理地址。""" + # ip:port 格式 match = re.search(r'(\d+\.\d+\.\d+\.\d+):(\d+)', text) - if not match: - return '' - return f'http://{match.group(1)}:{match.group(2)}' + if match: + return f'http://{match.group(1)}:{match.group(2)}' + + # JSON 格式: {"ip": "...", "port": ...} 等 + try: + data = json.loads(text) if text else {} + if isinstance(data, dict): + ip = data.get('ip') or data.get('host') or data.get('proxy') + port = data.get('port') + if ip and port: + return f'http://{ip}:{port}' + if ip: + match = re.search(r'(\d+\.\d+\.\d+\.\d+):?(\d+)?', str(ip)) + if match: + return f'http://{match.group(1)}:{match.group(2) or "80"}' + except Exception: + pass + return '' + + @staticmethod + def _extract_whitelist_ip_from_error(text: str) -> Optional[str]: + """从代理API白名单错误消息中提取需要添加的IP。""" + if '添加白名单' in text or '白名单' in text: + match = re.search(r'(\d+\.\d+\.\d+\.\d+)', text) + if match: + return match.group(1) + return None @staticmethod def _extract_origin_ip(response: requests.Response) -> str: @@ -795,7 +816,7 @@ class DouyuLoginApp: 'http': proxy_config.http or proxy_url, 'https': proxy_config.https or proxy_url, } - last_error = '' + errors = [] for name, url in PROXY_TEST_TARGETS: try: @@ -809,28 +830,58 @@ class DouyuLoginApp: origin = DouyuLoginApp._extract_origin_ip(response) return True, f'代理可用,出口IP: {origin}({name})' except Exception as exc: - last_error = f'{name} 验证失败: {exc}' + err_msg = str(exc) + if 'Tunnel connection failed' in err_msg or '503' in err_msg: + detail = '代理拒绝连接(白名单可能未生效)' + elif 'timed out' in err_msg.lower(): + detail = '连接超时' + else: + detail = type(exc).__name__ + errors.append(f'{name}: {detail}') - return False, last_error or '代理验证失败' + return False, '; '.join(errors) or '代理验证失败' def _resolve_working_proxy_config(self, proxy_config: ProxyConfig) -> dict: """获取并验证可用代理,成功后返回可用于登录的代理配置。""" attempts = PROXY_API_TEST_ATTEMPTS if proxy_config.api_url else PROXY_STATIC_TEST_ATTEMPTS last_message = '' + synced_whitelist = False for attempt in range(1, attempts + 1): proxy_url = proxy_config.http or proxy_config.https if proxy_config.api_url: self.log_queue.put(('info', f'代理预检 {attempt}/{attempts}: 正在获取代理')) try: - proxy_url = self._fetch_proxy_from_api(proxy_config.api_url) + response = requests.get(proxy_config.api_url, timeout=10) + response.raise_for_status() + text = response.text.strip() + proxy_url = self._parse_proxy_url(text) + + # 代理API返回了白名单错误,提取需要添加的IP + if not proxy_url and not synced_whitelist and proxy_config.whitelist_enabled: + whitelist_ip = self._extract_whitelist_ip_from_error(text) + if whitelist_ip: + self.log_queue.put( + ('warning', f'代理需要白名单IP: {whitelist_ip},自动同步...')) + manager = WhitelistManager( + proxy_config.whitelist_uid, proxy_config.whitelist_ukey) + ok, msg = manager.sync_ip(whitelist_ip) + self.log_queue.put( + ('success' if ok else 'error', f'白名单同步: {msg}')) + if ok: + synced_whitelist = True + self.log_queue.put(('info', '白名单已更新,等待2秒后重试获取代理...')) + time.sleep(2) + continue + last_message = f'白名单同步失败: {msg}' + else: + last_message = f'代理API响应无法解析: {text[:60]}' + elif not proxy_url: + last_message = f'代理API响应无法解析: {text[:60]}' except Exception as exc: last_message = f'代理API请求失败: {exc}' - self.log_queue.put(('warning', f'代理预检 {attempt}/{attempts}: {last_message}')) - continue if not proxy_url: - last_message = '代理API未返回有效的 ip:port' self.log_queue.put(('warning', f'代理预检 {attempt}/{attempts}: {last_message}')) continue else: diff --git a/main.py b/main.py index 4204df2..b2ace40 100644 --- a/main.py +++ b/main.py @@ -118,6 +118,7 @@ def main(): parser.add_argument('-i', '--index', type=int, default=0, help='单账号登录时的账号索引') parser.add_argument('-b', '--batch', action='store_true', help='批量登录模式') parser.add_argument('-g', '--gui', action='store_true', help='启动GUI界面') + parser.add_argument('-w', '--web', action='store_true', help='启动Web后台') parser.add_argument('-v', '--verbose', action='store_true', help='详细日志') args = parser.parse_args() @@ -133,6 +134,12 @@ def main(): app.run() return + # 启动Web后台 + if args.web: + from web.backend.main import run + run() + return + # 加载配置 try: config = Config(args.config) diff --git a/pyproject.toml b/pyproject.toml index 13ebb54..129355d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,13 @@ dependencies = [ "Pillow>=10.0.0", "PyYAML>=6.0", "loguru>=0.7.0", + "fastapi>=0.110.0", + "uvicorn[standard]>=0.27.0", + "sqlalchemy>=2.0.0", + "python-jose[cryptography]>=3.3.0", + "bcrypt>=4.0.0", + "pydantic>=2.0.0", + "python-multipart>=0.0.9", ] [project.scripts] @@ -22,4 +29,4 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["douyu", "geetest", "utils", "gui"] +packages = ["douyu", "geetest", "utils", "gui", "web"] diff --git a/requirements.txt b/requirements.txt index 98c411d..a4bdf8f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,11 @@ opencv-python-headless>=4.8.0 Pillow>=10.0.0 PyYAML>=6.0 loguru>=0.7.0 +# Web 后端 +fastapi>=0.110.0 +uvicorn[standard]>=0.27.0 +sqlalchemy>=2.0.0 +python-jose[cryptography]>=3.3.0 +bcrypt>=4.0.0 +pydantic>=2.0.0 +python-multipart>=0.0.9 diff --git a/start_web.sh b/start_web.sh new file mode 100755 index 0000000..5789caf --- /dev/null +++ b/start_web.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -e + +ROOT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$ROOT_DIR" + +PYTHON="$ROOT_DIR/.venv/bin/python" +FRONTEND_DIR="$ROOT_DIR/web/frontend" +BACKEND_PORT=8000 +FRONTEND_PORT=5173 + +# 检查 Python 虚拟环境 +if [ ! -f "$PYTHON" ]; then + echo "❌ 未找到 .venv,正在创建..." + uv venv .venv + uv pip install -e . +fi + +# 检查前端依赖 +if [ ! -d "$FRONTEND_DIR/node_modules" ]; then + echo "❌ 前端依赖未安装,正在安装..." + cd "$FRONTEND_DIR" && npm install && cd "$ROOT_DIR" +fi + +echo "" +echo "==============================" +echo " 斗鱼批量登录 - Web 后台" +echo "==============================" +echo " 后端: http://localhost:$BACKEND_PORT" +echo " 前端: http://localhost:$FRONTEND_PORT" +echo " 默认账号: admin / admin123" +echo " 按 Ctrl+C 停止所有服务" +echo "==============================" +echo "" + +# 清理子进程 +cleanup() { + echo "" + echo "正在停止服务..." + kill $BACKEND_PID $FRONTEND_PID 2>/dev/null + wait $BACKEND_PID $FRONTEND_PID 2>/dev/null + echo "已停止" +} +trap cleanup EXIT INT TERM + +# 启动后端 +echo "启动后端 (FastAPI :$BACKEND_PORT)..." +"$PYTHON" -m uvicorn web.backend.main:app --host 0.0.0.0 --port $BACKEND_PORT --reload & +BACKEND_PID=$! + +# 等后端就绪 +sleep 2 + +# 启动前端 +echo "启动前端 (Vite :$FRONTEND_PORT)..." +cd "$FRONTEND_DIR" +npm run dev -- --port $FRONTEND_PORT --host 0.0.0.0 & +FRONTEND_PID=$! +cd "$ROOT_DIR" + +# 等待子进程 +wait diff --git a/web/backend/__init__.py b/web/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/backend/__pycache__/__init__.cpython-312.pyc b/web/backend/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15c429fb55844d42f900b996788282b652c9703f GIT binary patch literal 152 zcmX@j%ge<81PAt8WP#|%AOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdWv(AuoLW?@UzwYu zpPZkPTCAUvUs_ojpOc@SnHOJBsb8L&q@R?SoSmANq8}fhnU`4-AFo$Xd5gm)H$SB` ZC)KWq6=)745Ep|OADI~$8H<>KEC4CYB=7(L literal 0 HcmV?d00001 diff --git a/web/backend/__pycache__/database.cpython-312.pyc b/web/backend/__pycache__/database.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a8a8a285d329282a94a6a69ca6548182089d3ab GIT binary patch literal 2295 zcma)7UrZE77@z%fdv|-s9e;&did2;5V&r05D>kMHL}{&+m{cEnFWcp2ls$IuaCWb7 zMjA9lK~Shhr5aLgZKcJ3{bMysjD7A4L=(5-vc+!dw#`K{xyPTpnwv+5PGvCbj z&Cc)pe&76-mnQ?-zPef)t9Ai!n@w_!&h0i+#sP2`Ko9|tgS^fee1zxNSkMJSjEMGF z)Ln)Ykqmdl%>f=D9(hKn9c!^dr=KhTuMWDl`2c_tlMxxo|7uq{*RwN*ZsxZ2 zRmCW7nZ$$IHPccOE!$*stFBq-?Ql3u3Zl&@+M<}MffTEm9tIoODbYkcj-r;L;w>f- zQM5S`5{THO#UYW^q?L#!3`4VsmyI{;NwYaRdw07NsjQ;kj77ydz|owrW+Mzh4y6v@ z1>37!z@B9RyD0DB5gsI0-JCNbyG|~GekueJnRIM-jd#m&iZ@2;3`T7^7nZD^vM05 z9?xF+VK?s#{x+G(PG2pzPpks_3u?Kq3zrM$g<9^ZXfR~iH}f$SG!j%gEjF9;xjE}& zIWEspT2gLuipw#T1C}?Zof9WMB-mM=xqK;me7JH+WoFkGnXYd#-9wp^y$_heq9XxE_%D(#cbD8xVWjp#$};XwOGXh2G>FJ)vj2)A#~8( zeEyytJk4A_=A-t{wCx~=PmEpgnU=%Je3Pfy3`HtA=*n3@iB=-dDeuT^1UdwIM(`OI zbh=&0Sy0~av4ZD!@z~w6*WVyMGueu; zV$U}w85f@TzjV^6jG4~|X}Wll)MK0$E0ULUr!eP4#0eb{uVyN0lvP6_VPvSdr7SbkG{?J<-6l*z$@{`Q{sL9zGKiD1=uuz*$L zk4!&3bFi+cZWZ`-RbhQVXbcq+x1zvAR8iP7U^VMn6J{At*bAWQwDcP(%&tKKPZTu? zY+#myn9-hi6lJrfBkDqOUJ~M7U9W7Y+xQ-F87)x5cs|?9aK!9&wM!Xhv~(b0ytKLJ z5ZAMc1|zAXckoKO!1BaAK*c?QHWAo0fxw`3ka9yYWhP(8d`=w6tz9ex)_X`EiKjSat IitL~M2MQ-i>Hq)$ literal 0 HcmV?d00001 diff --git a/web/backend/__pycache__/deps.cpython-312.pyc b/web/backend/__pycache__/deps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2563ff6e48f1bad2c3cb85ede803c4de4b1968e8 GIT binary patch literal 2459 zcmZ`)Yituo5Z-&*$@vw>5Z)M)02UBKpfsg4lvf3m21JNxEu`zkw*i}T9QMuxgCiMA zMNl8K0vbw-NL8Rzp|mQk=&u%4{ntNEgA{X0q*jmz|4c(A%CF9z?U;wU((Qh?J3Bi& z^UdtoS_U|tZ(IX;4cj-^D*$QCd7Jwe4q7YIUmDQLS8)9Qzh>P(do}qJ`Cd9;$ zXklKn#q1%wg$1oFRvsz`EUGq5iaA0K3)?kk%oTE3xJ+}$JRy&T%e9J_H{@jyi?FCl z*BpIJ-iA;m%&@q!_;3A9uC}qN6Z=#bwySRJX%kh?aUnECt-wB5@gCD#_8VeG5lq3C*NoT%%uLNI}OTQk;m5Cv%8x_j3qLgM5;H613zz1Wb|RzQ_1 z4#!n2E8#GR%SL=ZraSl+-BRMCf+VhCxdW3}#8Rjqb_bP?NKk=egIauV#5%&-*+Mi^ zu#m~Bt)@t@ks$41mR_dNAfSkYZ$brIdXQ0YCPK!fRFp3Ymqr?7m5VUf_&&NDS{GY} zj3OOS3Khf9#ih73n_`bKDK1);mwmEVg-J<1#pfl

P8PD z9E0fXJJBKTE!4>b#HIu_VYH`r^lbOo`Qzi)FO7eDbmYW0qaR#9?Ag70_fkqA+-^j| zih)hBz#z_)@*I;XK}=D_h7!?C+dizQnCK>_Ct3qeLSsb)=*%gUq4LUQ%jK4)buGI# zZ{N8kv}q%e=o$w+B6f;|0|`u$#6hvx7SRk$Og5sLT%_GFg|-OM4YMqw10jvbK^zc? z8zhN`io3%EtJF?K(`9@pY}z`Mq!w3H)0P0b%Akd(K&Kq@TkY|RAZ4;1Ovbbzy@wuD zz+p1gNNPbNwL!lLT^c>KqpF%A z-;6=uvVPyPtb0MWqPq9ZjHm9Qjjs~Z+jHf1Va|}Zwige0mvq~*lDB(p);llbn3t`Z z({tqGBfT#SR0W<|$Z^OqGgporu2T&s8_sNeuW?ZF_e=guzOSmjsJgb{J6qqj{&@`p z(o2KV+J0&6fV3{XF>903sdFnYHlA{(YzepV-3V5>n8jjUK1fc4|K)`f76(*XT&t0VwCMVv9?kv*-bs`Gr*`_;k2}P>Go~VJxZgjd*-0K_78XMkazCY4Vi{5 zx4m04j;%wUn%+HwwX6DTR}FYx>=v@plyfcp(tJzmr^UCTxAtTjcHZ_jXB^F-B4eL9 z0s7!YBh)M_pR5`HccsRXCD7^A=WqM-278~klhna_fC|zrVB5XgCAXKpGEm$20Exn^ z&($0qep~qu9da(@s7yDJr6AfAs4{J`tj5E#Y}(r3xrO&DZ;0hbt9*G1^L07~PDP9) zp}{s?`T983?W9(tVKjdjO&>wChEZS`O&dl_|MJvi>ek-&yqb1Cup{sEbF&937T$qz!Rg=wVJhRv hO0FCS`@c8y zMLL~8uwMIYbap^N=#C&-OKT!q{Srbeh#(yiOe7oY7A^sJa^AsvUDQ zT?0HuG&}Be=p7Lrw-ZiMPdX_*<)rnrlhHFyr{0MXl}QIxrzDbCQdXNT^)3lfl_W`u zc3n@e$_=)jrPe(>DslS3O9-rKnR?xwIiUcfwO`c=1F_S}G;3mnrmrzr!r))WUcbK=y? zXI`=hvFW_Y=%#4EHIT4eD=^4pLFI}W1~Xg@ead_uH^Exy20mA9Z`yLXT4CO~DwoYl zrQB?uaE@SPkqlsK7?3f9VSt0}^L7dZ-B7OL6%5Is8}d9Z;BhAAzJQrX%NHs`zaJ{S zBD|NXj$JByg!(1oh1JlALNF@T(mb6kO`7F7>XMRUT5hpY<#F4ZqJG76Sr&|>gf(IE z7xe2Q`bkMHom^Kse)Hkdq1UrH9QFfp_v*^#2cO)1+E{*bM2GYsOD4TJ3i9iC|3AlsT` zB44ge!F_`UFp&w*H>skteUFL|lF!5&$m7He%t;dlI3B4oFASiAtOF<}n#7UN(8m-5W)LWM%oOoNik6_!9Ta0@N zq-AP}{k2rCmhOF^DVnq>*E0w!-=Xd=QFl%0uH}Z;9zU_x{d}!&cP-bqd>{h#B*M7| zD2Ana_qO!%lPgEB9KD71){xxLUp~BY?8>oQXsFE@T{(W`_${=r&H1NHiz6aeFXqqZ sFAaW;^Yr5Xb!kU#gF*(s=v_XpC_Zm*ON|a%A`{ zpW=CJ*JT{px>Q}(nQ~@bDOc8=a%Vj$Pu83AW_>AN)}QjTe$GrF8%zbE?ULM?P&S+j z^PGb_&PkqkILRxAZ`7H-QW2*0f!1%+MwvDM+MrDvW7-gC!!~W4X(OPG+O+k>STbI~ zz4n|wwQ;^YIrr1=FQU&RPrZ_;%es=4lV0jRs%8q=9QB;c>GFU~sQZjgl-vOI98uMb zEas@QSH7TAZ##_Xg)!9Mp%Pgc$aTp@>g+tz(+x8QiJTF2Ma^l0N`8?y-8heknw&lviBHu@j+E2E$u8L~)k$?zPV0a?sD+PLa^1j*;ej5|Ysz!O zX}+g?Rv)j`$35k{;i%aCmfc^oOM$7N)_SiyXdq9NtVoK&5d2snv7oC;E=}aDoYNN%!?2EI9d#FS%DI9}y}3drBlcxv z>QZu&d|{D;0cd1Nz{R~)U(xTj)Vd1m4fl!ZAtA5k3jilsqrrX?Yq)-`JK0FRf{+ul zvLH~uAY@gkkim9P5Y83EOvU061W8SUIwS}>ZphRVL<>QVQ->lE#3J?LV$e)Od05G? zN+e-W)CQ@O0QiXq`}h=15YxJHUZ!q1I*|+!AI^jfhQrg-Idz!&;Z$M8k|65TZ|)u2 z1b-VEvoC|i>c4g~$e02LnVOlW)hfD3V(Z-SCveSs} zDxI7S)nDlte_^z%95+HcOC7VZHRIm$w%@c~Z#7~EOP#j&yC?qG^ZOnn)?4cQmz!I= zW#U`6zH{$QVl-)gbvBTF)kUu!63&0Qkl==JP^J6o;h<)(g@am1v5#lL0BGlxYQ(qO zgGgv*uZ{Oj`7Qq}a6ljn*mP#FaKosG6tsd6hPsdy#vtlf=B@d)&kOM%0!du1Kf4Xi za>Zlw)d2k(rLY7cXMmrIT0vtC)0=^$dR9#sNXRCbjKz`pAB`=3aQoxm{d(>PA5w1> zuDv7<9TrO~C|XIR~Xt<4~)@9PNdUQrvO zp33wPKAr_rcXBP+iv7Nd0(VG~D7K@pxJ!26E0UM&1VO!3-clEEQ_0XU`w}d5%zTG1 zm=}O+=OM*KY$}Xr1k)r9SS;05@*Fy_XilET*ESSLXoB&c227IbuoaE$MKweCer(}+ zGO+;~+MhvG2->_lLC=g1m5&*bHXtV>tnED*zp`(9^XQ>+gAr^k9h+@ff3;)cg=<~Y zTa1PSrS93L=Bwu?(%0UeK4LWOzvpb2ZQL;adO2-0wv~Ej<4xmhCbySU_gfC!-Sa2i zi1(Jd{vV+qCN@qq-aF8B?~VR@sR1J~2oWOQJpT5r7mfI_i(Td}af7S#c+Z#QF-zsRGE7uJrp;Y`e|~Cm;p)uX zo%bKj%*=gnl5~LDq#XRdfz-VIg zYtZUWHa!*LsJBnl(}M!IVubNmoP&b8HC@yT8u6lgcUo146iEJAS<}P;C>g8Sv9Guq zsc4{I$tl_(OP?mgUcr^FAQc`DJEK-;Xukx3q}uQxv1xTuVbX3jPS0~@#^r- z`bG zag>l|!%RYSm@q#HQl3mO z-U&EdiAz?x)sOC&AVy$26mC|wa2UZIrDG49n#YHyT}IPB%nYpVYq@r?OpHVuM0G2b z#EphM7rRZQ;+j@RDh10^Ar&uaE(@t%jD_&=Np1@jo++)4;S|t{< zVu(S_Dux*7I#`9&4MF`2mNe9ZR}qY@wc5sw*A6vD-MUByWW5qxC0U0$XSF!G5@X+n zAB>>rfsd94Q3;qESI3xcV$8ZSv>1_H5IU_3z!oF)9E+37F9i0ux$JUqvD1VUE}=V_ zFv~eiq13OP%ZQoupqwq*N;|f5q#)a=xkJn}1g|+XTpP1nC0X-og@0v;eegd631dfA zY_j5uB@0F}b_&Z=3H82Y-W^{dG7N)?h0`Pu|E%MAewK^QanF3_aPW@LI1rCfeCp=- zZGY$1ExE(I&R^QI#Nqc+OO)^6FG))relKke^X>eI6K(Ba3-WtMo?YVLYpL1KAG)M} K%E1>qlK%iVbcUP& literal 0 HcmV?d00001 diff --git a/web/backend/__pycache__/permissions.cpython-312.pyc b/web/backend/__pycache__/permissions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c92afda39b08614eea23331f899df04fae724882 GIT binary patch literal 2212 zcmZWp-A_|z7=KT{_&9tNT4<3^Cn!asZn|V43ubd(m^fV|yWyrOeUG(TT5`^*T4EOB zWWpR$w;|wI1Qlg7tNYL~E$Sbzn;MqL&XtLrwj@hD%C35~=Xp;lY&#df^E}V{eE!~# z-%Cr234E(h-Z}AC0U<9?S^q3KVNC;Jf>6>!0)%qZ@D)dm)C6dz7Qg~p2v|gm0eM;i zSW3$P%c&Kxf>r|Bs2#A1Rs%Yy6R?JE0j#BUfc3Niu#q+aZl!MkZlle#g>IiV&T&1Q zfd~eHI|2Ru_q&rb+~IV=Pw0;8Sii<^psmx!9usxZHoB9^X1YtT(00gWsR8$6Cfae{ z)KkE$Znjc*%tCjAttk4Y>#ayLIHkV*bag70KKDS4UtM`Hvhv%d%hG1}G`gp9>)W~3JdL(o2mO8fZa&cIVe~}u$r~WXJir;^^I5JT6?@(4u zppo7AQPp8Aluk@#E_|;p{FJ_WKA&qPF_a@BHVV12W$U>Def?okpI5!Rs1D8PAsY_* zP?0J|7UtI-po$v5o|+!r@cbV+xm0{G><@%Gd%d#nINKO?>~iM*LTco}>V>iNR6>0; zo-NaTaPJr?&iqh1DZ1NABB6dhJ7C-FyiUW`e1txIuFb3GH-~0ZcgOSV`r-Q}kxpF8#2)Fcrf`hRuS4mFw{;HG%aakzoOu$< z#O9c&9~Z?WhJ%7)5d}FShJ3mA0z3)9p0K&K4uAC3iE`wq{dgRan&qoK2et59E`{0qo-HOpK$SzK_ zLNW}(!q-0I8=vxxGp3o|CEk@Zxz=ETD8(Due7OP9zClkNC==u? zC+EpKOV%IWQyXNYZVbW;m?LBIt{S_gQ^wqo#eHF4gQw@_Uk|J)#5bhh4==fyYC`K95J?H`8pwJE53P z3Q@~Y8hHzbJ_!tE`~6*bG{ uj#mXH4A;sGT-&6i5%|r>^ZRF`Yba{P25#@94b6M!x3S-k&3~iD_TV2OJR@=d literal 0 HcmV?d00001 diff --git a/web/backend/__pycache__/schemas.cpython-312.pyc b/web/backend/__pycache__/schemas.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..431864691db7de4927c584358df83481dfe12919 GIT binary patch literal 5390 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 literal 0 HcmV?d00001 diff --git a/web/backend/database.py b/web/backend/database.py new file mode 100644 index 0000000..0c68d32 --- /dev/null +++ b/web/backend/database.py @@ -0,0 +1,53 @@ +"""数据库引擎与会话管理""" + +from pathlib import Path +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base + +DB_PATH = Path(__file__).parent.parent.parent / "data" / "web.db" +DB_PATH.parent.mkdir(parents=True, exist_ok=True) + +engine = create_engine( + f"sqlite:///{DB_PATH}", + connect_args={"check_same_thread": False}, + echo=False, +) + +SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False) +Base = declarative_base() + + +def get_db(): + """FastAPI 依赖:提供数据库会话,请求结束自动关闭。""" + db = SessionLocal() + try: + yield db + finally: + db.close() + + +def init_db(): + """建表 + 写入初始数据。""" + Base.metadata.create_all(bind=engine) + _seed() + + +def _seed(): + """写入默认超管账号和角色。""" + from .models import User + from .security import hash_password + + db = SessionLocal() + try: + if not db.query(User).first(): + admin = User( + username="admin", + password_hash=hash_password("admin123"), + role="super_admin", + is_active=True, + remark="默认超级管理员", + ) + db.add(admin) + db.commit() + finally: + db.close() diff --git a/web/backend/deps.py b/web/backend/deps.py new file mode 100644 index 0000000..91f856a --- /dev/null +++ b/web/backend/deps.py @@ -0,0 +1,50 @@ +"""FastAPI 依赖注入""" + +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from sqlalchemy.orm import Session +from jose import JWTError + +from .database import get_db +from .security import decode_access_token +from .models import User +from .permissions import get_role_permissions + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login") + + +def get_current_user( + token: str = Depends(oauth2_scheme), + db: Session = Depends(get_db), +) -> User: + credentials_exc = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="无效的认证凭据", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = decode_access_token(token) + if payload is None: + raise credentials_exc + user_id: int = payload.get("sub") + if user_id is None: + raise credentials_exc + except JWTError: + raise credentials_exc + + user = db.query(User).filter(User.id == int(user_id)).first() + if user is None or not user.is_active: + raise credentials_exc + return user + + +def require_permission(permission: str): + """权限检查依赖工厂。用法: Depends(require_permission('user:create'))""" + def checker(current_user: User = Depends(get_current_user)) -> User: + if not current_user.is_active: + raise HTTPException(status_code=403, detail="账号已禁用") + perms = get_role_permissions(current_user.role) + if permission not in perms: + raise HTTPException(status_code=403, detail=f"无权限: {permission}") + return current_user + return checker diff --git a/web/backend/main.py b/web/backend/main.py new file mode 100644 index 0000000..c1deca0 --- /dev/null +++ b/web/backend/main.py @@ -0,0 +1,51 @@ +"""FastAPI 入口""" + +import uvicorn +from contextlib import asynccontextmanager +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from .database import init_db +from .routers import auth, users, accounts, login, proxy + + +@asynccontextmanager +async def lifespan(app: FastAPI): + init_db() + yield + + +app = FastAPI( + title="斗鱼批量登录后台", + version="1.0.0", + lifespan=lifespan, +) + +# CORS(开发期允许前端 localhost:5173) +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:5173", "http://localhost:3000"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# 注册路由 +app.include_router(auth.router) +app.include_router(users.router) +app.include_router(accounts.router) +app.include_router(login.router) +app.include_router(proxy.router) + + +@app.get("/api/health") +def health(): + return {"status": "ok"} + + +def run(): + uvicorn.run("web.backend.main:app", host="0.0.0.0", port=8000, reload=True) + + +if __name__ == "__main__": + run() diff --git a/web/backend/models.py b/web/backend/models.py new file mode 100644 index 0000000..d3e0be6 --- /dev/null +++ b/web/backend/models.py @@ -0,0 +1,89 @@ +"""ORM 模型""" + +from datetime import datetime +from sqlalchemy import ( + Column, Integer, String, Boolean, Text, DateTime, ForeignKey, JSON, +) +from sqlalchemy.orm import relationship +from .database import Base + + +class User(Base): + """系统用户""" + __tablename__ = "users" + + id = Column(Integer, primary_key=True, autoincrement=True) + username = Column(String(64), unique=True, nullable=False, index=True) + password_hash = Column(String(256), nullable=False) + role = Column(String(32), nullable=False, default="support") # super_admin / operation / support + is_active = Column(Boolean, default=True) + remark = Column(String(256), default="") + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + # 客服被分配的账号 + assigned_accounts = relationship("Account", back_populates="assigned_user", foreign_keys="Account.assigned_to") + + +class Account(Base): + """斗鱼账号""" + __tablename__ = "accounts" + + id = Column(Integer, primary_key=True, autoincrement=True) + username = Column(String(128), nullable=False) + password = Column(String(256), nullable=False) + email = Column(String(128), nullable=False) + email_password = Column(String(256), nullable=False) + email_imap_server = Column(String(128), default="") + email_imap_port = Column(Integer, default=993) + assigned_to = Column(Integer, ForeignKey("users.id"), nullable=True, index=True) + remark = Column(String(256), default="") + created_at = Column(DateTime, default=datetime.utcnow) + + assigned_user = relationship("User", back_populates="assigned_accounts", foreign_keys=[assigned_to]) + login_tasks = relationship("LoginTask", back_populates="account") + + +class LoginTask(Base): + """单次登录任务""" + __tablename__ = "login_tasks" + + id = Column(Integer, primary_key=True, autoincrement=True) + batch_id = Column(String(64), nullable=False, index=True) # 批次ID + account_id = Column(Integer, ForeignKey("accounts.id"), nullable=False) + status = Column(String(32), default="pending") # pending / running / success / failed / error + cookie = Column(Text, default="") + message = Column(String(512), default="") + created_by = Column(Integer, ForeignKey("users.id"), nullable=False) + created_at = Column(DateTime, default=datetime.utcnow) + finished_at = Column(DateTime, nullable=True) + + account = relationship("Account", back_populates="login_tasks") + + +class ProxyConfig(Base): + """代理配置(全局单条记录)""" + __tablename__ = "proxy_config" + + id = Column(Integer, primary_key=True, autoincrement=True) + enabled = Column(Boolean, default=False) + api_url = Column(String(512), default="") + http = Column(String(256), default="") + https = Column(String(256), default="") + # 白名单 + whitelist_enabled = Column(Boolean, default=False) + whitelist_uid = Column(String(64), default="") + whitelist_ukey = Column(String(128), default="") + + +class AuditLog(Base): + """操作审计日志""" + __tablename__ = "audit_logs" + + id = Column(Integer, primary_key=True, autoincrement=True) + user_id = Column(Integer, nullable=True) + username = Column(String(64), default="") + action = Column(String(128), nullable=False) + target = Column(String(256), default="") + detail = Column(Text, default="") + created_at = Column(DateTime, default=datetime.utcnow) diff --git a/web/backend/permissions.py b/web/backend/permissions.py new file mode 100644 index 0000000..686cac1 --- /dev/null +++ b/web/backend/permissions.py @@ -0,0 +1,70 @@ +"""权限点定义与角色默认权限""" + +# ---- 权限点 ---- +PERMISSIONS = { + # 用户管理 + "user:view": "查看用户列表", + "user:create": "创建用户", + "user:edit": "编辑用户", + "user:delete": "删除用户", + "user:assign_permissions": "分配权限", + # 账号管理 + "account:view_all": "查看所有账号(完整字段)", + "account:view_assigned": "查看分配给自己的账号(仅用户名)", + "account:import": "导入账号", + "account:assign": "分配账号给客服", + "account:delete": "删除账号", + # 登录任务 + "login:batch": "发起批量登录", + "login:view_all": "查看所有登录任务", + "login:view_assigned": "查看自己账号的登录任务", + # Cookie + "cookie:view": "查看 Cookie", + "cookie:export": "导出 Cookie", + # 代理 & 白名单 + "proxy:manage": "代理配置管理", + "whitelist:manage": "白名单配置管理", + "whitelist:test": "测试白名单", + # 系统 + "system:settings": "系统设置", + "audit:view": "查看审计日志", +} + +# ---- 角色默认权限 ---- +ROLE_PERMISSIONS = { + "super_admin": list(PERMISSIONS.keys()), # 全部权限 + "operation": [ + "account:view_all", + "account:import", + "account:assign", + "account:delete", + "login:batch", + "login:view_all", + "cookie:view", + "cookie:export", + "proxy:manage", + "whitelist:manage", + "whitelist:test", + ], + "support": [ + "account:view_assigned", + "login:view_assigned", + ], +} + +# 角色中文映射 +ROLE_LABELS = { + "super_admin": "超级管理员", + "operation": "运营", + "support": "客服", +} + + +def get_role_permissions(role: str) -> list[str]: + """获取角色拥有的权限列表。""" + return ROLE_PERMISSIONS.get(role, []) + + +def has_permission(role: str, permission: str) -> bool: + """检查角色是否拥有某权限。""" + return permission in get_role_permissions(role) diff --git a/web/backend/routers/__init__.py b/web/backend/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/backend/routers/__pycache__/__init__.cpython-312.pyc b/web/backend/routers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eecbfe6cb4b639ec27bc1da5b8a6442f4a4dfc99 GIT binary patch literal 160 zcmX@j%ge<81PAt8WP#|%AOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdWv?GvoLW?@UzwYu zpPZkPTCAUvUs_ojpOc@SnHOJBsb8L&q@R?SoSmANqFS^`oWAD@|*SrQ+wS5SG2 f!zMRBr8Fniu80+A9wQJJgBTx~85tRin1L(+qNFH0 literal 0 HcmV?d00001 diff --git a/web/backend/routers/__pycache__/accounts.cpython-312.pyc b/web/backend/routers/__pycache__/accounts.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..228fd13f54908a1d57c99b9776aeffd183a96a57 GIT binary patch literal 7871 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 literal 0 HcmV?d00001 diff --git a/web/backend/routers/__pycache__/auth.cpython-312.pyc b/web/backend/routers/__pycache__/auth.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7b98e27558e41aa2d23ebaca45809a007b12f42 GIT binary patch literal 3405 zcmaJ@Z%kX)6~FKK`Pt^#{4q8V{v>83T`gFwZ6P6ROO**qND#CM)tZ+|*2C{X9(fNt z_dPcyqZ&+AL)LC28d?&mQi)VGM4*y(lgQRd>({AY>>0`CDqT0N6!K|KvQPW8bD#Y% zAZJ(hyXT&J&bj}7=XdW{K3@fb!hG2^{>X*Uzi7iQd{KBh%OG?U$w)*pBeNQ#Ga|!K z*`cwzLv-kz$mveeY3VqP*Il9uWT(t)Zrvk#EZL=1=z=KdUeRl{-I`CY6f5;AvC3+D zv}(OZtkM0V-)dKAwR%7d=s_`PwFRwCuNUhX#3IEZdu87kvs}5v7Un26C=KI3wkKYy zuWG|(FzIPufofwN%bg_!EZ8Rb$^F-+ncroLwsBN3l46qrXf zjS)*?tW22EB+TTBCn6=(h?F!hBq=HjtA{{*0xP5H+k`WvG2;p!(P&(VP>GAtqIz6b zG~&R@CBn)hu_9jRjv+Vv9q@np7HDsxDQ3Kg4BBZIbKAo{?{TNySE@@^<961RI?)s= zr@i<*LfbJtJIsrsjmLM`DHexJ!M1>C6ajn66gytIM+HqA`G(J|v3qF_FFWj>Uozn7 zxBE)Cd7KV)r-cN}JmtuJrAZOOq&9k6Xn zwb^RR<>vz0GG`;ZWcRXXi7WWpV=xpAZFam;ZJ!B)%tN+z%29s4<;R=imMiSh&Z4j| zsNMk7CF$O6&M$s9ySj1x*WbG^P9i_V)N5Go7dmlxN!@DIE>^7;b?9W zryb=AZa-(e@Ng9=kr5{|BgI4xo#PC7}$ps5?8<-adPUWaZYGO243 zx~oP+jwdIRQEOwOiOI-iWh62p#ekXQ2)3Biun6dIViHrNS|2*7@pt%4qkjm#xvpzn z^F8l(XN6E&2z?y5+j6Jne%ortXC3L*?u_tqR_IL&y&0izx@TRen*YUuad+y@)cxUX zXJ5LrZ>|31!^);bN9xGnTIEp6J(Lrw=DL5?T~vx|mFH9L^WS<8EISr^R{HMsEqNcv ze^LLarb8z)-u~&{hu+Xa;O@RV`!e44>E1k#oUW^{%)IhJ*R7t9`fm3v$}9Rk{mZ`8 zu(&3iU*q1)as1W(nf`ZAe=9WPf{i&}&D^#AvGf1AG2y)kHVS|?!fib3%kDx3p;k=oLabwI~K03v|2Dx}@OM?<%V z?rYhjr_)DIr-t85DHqoSZH?2nGcV^2UjKPYXiITz)&>uT_Y+q%D#v5dD5g6`T%!_j ze?rA{4>0Ag0!LOFmo!O>T~PGN!*Q%*nk&`|P#Ted*yzd#Z#{kti;D|n={6L|U)7wn zXxL&`3yc=d3IKE&G^k|YYC0ArIKs(F!oc*A00=HTD!D`)NZH~+x@rrI)<)bz)1L zHx^H=d5%r<0B_cPwP~hl_KgL7x+%jPS#ST*{i91SKSs3-KRcDD)k6IfT0M0jwlmLy zl<%NkjWdn2y&1;8-rSZtcqG%@`51W^{`&9}T2bTLJPT63!5-pVhN)i7PycNjP>cQnFw4$yJk6DncHUAHn>UzALB?j+8}Qa{@Dxmj*-8awNopSC$0Yd?EAm9*E>lBluz`PKm{d(BNmWi? z=}N*Ql6I;4-0st-PoM5S-|6;0TdigU>HNoC!Q)j3eMLU>#21Js8)$^a(S9T%4-zSn z4pAOTD`^i6B@<%8jEA8}8yjN7oQDe=Jch8*V+`{iK5X)sv^Fke4qH4HpberiB!sOV zYuM(oY2SRv9(H&f;VMs6*y(YGt35SZy(v^1uJhD|>pk__w>i`hUgTL6ZuB&2-YDv3=uVQSC4fAwQk%3yYU`&LL5;Ljbaa!lCD=%B58?8A2&kp(pZgUW376|jGA;AME*18Kxxv6{;cRK zlj#PT`zz?*tjiP^OFZcd{aQ}5#d)t8qTQ|WHPG|H#anZK{}=af-J1LP)WaL^Ke+Hg z>zdXdONaJG{YNCFb?&eKa(?`8=f{6L_tE$xGEkqJQhCv*NJ=0qxjEIeX;1HuqH#sS zs&T6nlOm$5T6^~G+q3PMUy3P#Xhby?#i`Xra%({LMZdx@A zNQzfHqzZ-lT~WU;FC^jv$oG?f(|kYz1@)G)Qno6^KEy9vWAwdzib_yHu8g%n zDh9ny-#Z5Acir`ClN4dv^i5T~Q1qLqzGlU|oFCfLe4RGty6jZ5!aDih2##!wSIJX>vj7EgJ1{+b|Hh-_==|I7KKz@% znj5?EF0EQ*#izt&uRkhEszH<#Um*0IEq}m{2Bbl+FBA%#C;c0s|NQT!<|jwz#@^45 z@ZcAx^Mc$4Omt8!zP!ir2Ee7+M zI+86X8CeU}q#TCXiGj#~YS^cd;ECc9l>>WXMP))##BITigr8UjlkKH)+7wmOQC}z^ zYBpkgFc}7{rVAwA+yH{WTjmEcn$2G(HsfYD?#h z4RsDmhrnL~I7P%xtO1Xl_q-i3eLV`U6>kFBWPWlxEOZj(Y)G(=z4G%{?%JBBnVA>2 z&Dyrl@Y{0+WVMg=jP}gdE>8<9GD3SwXip0r$!*Y5Rh{h3+MQ!3&YT!wvNp%q;nBkv z2Gh2djBQ2Aw&FK~SB_5|zrHuMvMX&{pRv7?vb~bFZ5m7lC|KNeG# zoipstCtx%LxTFBI$^-+kB*&r=S@MR-j?-4~DLf*FP{X5tf_xl-*9VI)ObS7FE$L@{ zm{E!-1Rku2J~|)7Qk3pbWN(70ghnNlD9I0!t-TGti74w`0XZuB zd{?TXgM8Wl>LKf(y2l>UBz4 zs#74pg7uye3slr4VZ=hhSP2^=a3cdrEA+8SCDg+1TbPbfG7a}9(r5L;HiXJr+rQrm zeL5=EKKK3FYwXp6&GmJGIAspv6b>+$(=d4c(r+J3zFm&Kb0gyqr=|geF|p^P&mdEg z+l-0)yni>BAX9bdGzqgKzAzwi9-dW$KN>v}kW^zB(9Sm?VX_n@g!L(^r9Th}$cF*B ze2RM!CX>a)+i2*9e?ZvTU^EcX;19n>S~Mj(3H3b5G+g7OSd=h_nY3#vaZy!uD^Tjqa7daNG;o*cD$Bx^rakqcZKSV(2^2bF0Z??VQNE4 zSo?&btIQ+ZV;=DiB0xANOdiNIccz*e{t~op=@={c-OhE z%bTwBO!fS_?>c>R(?`7@_WpkNXU=SW!+7Fc;yQIh_(1rq?O)dZ?FMxDey*+Pa=u_jXLg}qTGBR-Uv-`61vUZ_}$#t zxkt&9b0;U~-~09bkES5Bg#a?Y8P@V zt@HE0x`at|?`AR4P-RHq$r4LwF{PWy@8NtDrKg z^oirv-O%+a05j<*g%!N9`HjmEv8q{NilW^EZ{0ed`Rr^{%=8@|KzBUY|Ah z&9Hs>MXBi=ECg?53!|DK#FMq>ho{zvLJpuo3y_&DMeH(ut5Pa zLxnr9E?Z>4FBf)baUNWL9@?Fo_`n6Jbcb5l13Y@qI&|g~{955>T8hfz1;+w0@VN}( zR=A&`V1-f0o)wLg#mGP#3#|E$rrkP3?0f*7SwD!5F$d5f1!EN>CXfs7kq1zO)mu%d zWV;~Q^$Su0UNm((P-uuqE86=1=hsJhj~Rrs6(`r6;saFc{GU(5;mV_0`7;-wS|41~ zOmO4y!j&s7sca}3jXl!N5xiBAhcCEysHVdbT!{|BT@Y^v!O(>ZNk<_%-bubJaLs}{ zTuf1oK6xnO4@6b&4NaPjJUjs-JUbD&`Gd(1`T~l#KZ@}I=&hP$xZZe!GQ6G%`|{&$ z!x&o0m}5mXiZ^xKqfJ@1~&OaY#o#f*C&{gj3dw*gP;6AkUd1- z!o|pdEHxQ(W6Io^HoG#cYld~*chrwt&sopg;nHDoW-M(fOIzBqG`TfrMs+P&N6ll7 zX)~POeYPjXHs{P{u03mW=4jUX26fl9G~;Sdx!R`>y#MCAZ(j9&d}!9yo2lzP%Vg_X zGIj1$oqKv|x~?PR=$LVIwh;QzFVA~nrg-m-d&leul8wg`sXW{%Ky8aQuPQG|9fgswGhX=83L zrnhvX+pQF#+ZaMGuLo(jyBoIFF#k})0PSv9jb5)9^?SXz9~8wel8m@#)!6TY04f&1 z#EW6F+?qSb1X%K0#3uhFYzvA~(GR{twm%@p7pVD5wEuIo{|mI_0b27V z>Pw@(FVRb1qIF-ORbQZ&{@vu9XrGSEtbKXbv@vObBeHBo(tft)tbgLgvw?FhX|x>b zX#TCHlT9OkoTlm?8&GxQ#ESEscWRnau8!&9wCm+`&Bn*ZHPkLDYii6fgt*7sa~vU{ zv(5Rn*7$@$bbXH2Y7fG{N%*%KPd1M9T;4a`o?5;x*_fuf?lraCaV{!IpK-OPYt}wC zHc`&3t?KO7(O`}NS`!UrK{8N8@cJB0>beW6&RAR0R4eFO>nc~0&x9NmTA_xDqCNQ%d^3Q(X zy}GjOh_t)2XGY&WkMDfv^*w&)==UWhCJNG#na2j-EvKl@v7jb4m$|n^Ls1tfj`C0( z&1r(PhnBw@j|RTlpf;rQ=t6prK4kD1XynlanGow?A+P5QL1W0|G0Ax*XbxFCmQabu z8nSt8avd8i4V8JyLgk+FklkaK>x@B1sKQf0QyPlbaVE|@Kwq_7fmvYkRB|Oe%UOAA zzmBuLr1PxeD+k+@wnEC4@?}@c)LtM-&zr8$3I~?pSHYF5HR`utBTKjQ7Le|ENq;3L z(o+qiY%7$n>fm>BDQZ0=1c#)B+)H(hy*McMHxaxut>K`&f&0iRy zR&7_mx!qDms4EzuVMODsk2b(6zW9$fznu6{)7qvlFMg1ncscv>YYRVo`O6R9y>soo zFMsm>0b^%oZ z%7y`6^m2XBU3XZ3p5_B2JUr479_kMaNNjtQ3y9wj4?wfEpy8>gD3ukI4@Wqk$V;|; zAlo;}!4_8fTbH95xA#;I>QGrX+1@$dp@XFS_-ME zrExk&3r~nBSxyD@)F7kQE`4LPDwW3ZjY}LhJ@P|X8fG9DrS%epkIW+`#RMPtU2LxPpQ`3VdTzIg8 zYa|qO`9Xr<;=<99s5clM2n=~6Bd(KtpR3R3KMo4xBC`Gn%IUO5MkK2j6(mf&e!{~U z6V%62_z68g5>&=yIk)c2x?A=&X;bs8X?@DHK5g2V=$JEGKe0BXtxeZ28wAuEqJgsUC3$a=CPuw#m8 zy#=cn)G4)v6gcwzK!%~PP00gCem20N2B88*;K)k`4mp4oG(#3R@|8YDG*lh-$DRK`ox*iiE-Us`g55 z50;Jb?X%P#q@23_s#Zo7zo+PEPH~(%iHU;2c>|opVrIw};w6L6j}BNeh&}?U=CsP{ zC^7tyuP?}R5(6&L8zn(WcT5x`k{+obm7P2m5cyz05WOm6$x>AmMz9tXkMko0*Gg)U zR3kxeBWVI0Sq~YfjNgWUYH=zlVVhDC+P0MWogSKok??MJ_!9tm<=mWP37ojk9%ask*jA$1PoTrmA+f z%9*NiVyPliT{m03Ayo~MZs{sBI>YInQ#~LPGQFpI$Ay=kx((T`Q(b2to7{V;_hRq# zs@b;oR9kzd);ZmJUrSr|(Dx}?-$5gv(ic68Y^tMrm&gm^GT(ukDcdLq)&$TDVHp+I zEIIxq9|E?jEoO;=p3`zVPXC7HAXq~&OB|*J3%Gr4F&m8QpuULZkz+BP&~Kzbb4y^3 zD^l@3oZ%{?!nC+vM3*I}L3GtC6352$gQx>5Qog?-W*}=hW6V&@-ncvfe4XOV58<(> z7WF#yQyr{RC-0P1*{O%EjI+gnlPi5g!^67ZU$4pZMA5i5Cev_5EE@JUgXz{*8CC6M*oh-quKr z5cT^3T}ezx!B`8ZId21=69XY0g7$-&bMNd0_;d1sIj;}Cf-nec1fDu<9;tGcSzMyM>54(QQ=HkG4k$_)>$m;@`WHS(_W&xef0)~$V zCPXKj+6N$pJ}~bKj|PH4pKDv|W`LXw2IK%QkSBmqVvg}X4uH&|f{82h`>+2zn|<}= z?5}>ZqiKNy)~ExBGUaM}Xz zBmQH6LHS`((hmSQib%#hnwCn5FCg&Ven3Zp=o6!YlU;JIh{;ZPgMmKT%SuKCT*xjS zA&#UE`9zRS^8&_abikMtCu~A*rMO^n7+UcvBRm6#m!R%hD1GB6rYhC_nyu${{&;7m zwkf%3SIV|K$?m=l4wpU8eq7oz*>S1oVo#zc!YcA|OzjIQ! zG_a~0Z3q9w1k_|hrANuvtUmi_6_KtVX*&LaMmdpIlj{no~=||oi zO15m9etJ53ZS4KAnYLfNklfyttlK+byvtIS3V9Chl&v$#b}s5n`W`xCt2{S$W-McI zXX@8ZSEn31Gme@}#Uq!$n{>1-8mZFSdz8M^G_G5;P{xv3wm!wy-(njxwT;Qv&Kd4^ zfe!=81Ba4NADRg#JD*K%d@f}>l4Otk`F=fBTKyG8<60VOb|G;)57qmPOz6*Q%p{c#4V>|VmM|Zg$ z)Ni*N-DTR}?q=K;?F|DB`5P8JQisv)(%z_N-0QVBnrY0h*8?53x$;3+4l|=mL3zhF z!Vc=Z9CrGEs$nNY$2B2MTnjgM`86P}6Z7|of-rlF=L$iHGss~`Uks`d3-l}k z(Bz(s(o+Kg2e^s>y`p^!!^O8jyXV2L#hEx8V{o(>o1bTq5R?m-`av|{6)A3n+2tMg zQX_;*qZ}@E#R5d}J{)`1n4iB%M7$)YK?FoA5@(8;2JzOjBIWy=V`d1KKnEXL!2(6D zAQVE7Db8bE7A}pPHg1U-2@5BpjTzN8sIv&Y5Il>nle2N97&akj+OOomfBY}4%<)&O ztn49k&aX?J7iwTd?qm&vh%#5CSe{?jhmyE8Rzl2NCATVO0U%Kovm#VbB!H}HxvWvj z5OfiI$_WBCSjkc9?5hPK`J1i1YOAv*ieX0yy7~77h`aNzyp{dszu!6iPWH!B*@?6B zKYuHG`d8U=Z}uEeV=+8Jls>28?|t(9lSmbKPvnOKqBjun{vhB}oHT@O)aJbWeB>sN zLUj~DIaDc+HhHGQJ#N?GCp(Xl9neg6B0*n7b|Kk~lv_~t-*kvxgy`$+a7*$)IT(#Vh>&c$h4ioyk> z9zaGkaKKQx)6|5IOWKeyK%U07XOJ9}8FD9_6A4_bEE`zm=Kv*E=cy93t@7lT*4qb? z{uX}1@8!V#-w?RF=|Ackz9t7Tas-9jxxu$;exXNe^{`{QZ{>H?;KM)SN@$TW4e6HI5TH1M--xtmTYXh z-Zm3U9_~wa`4Ne51xTcGM*Q8#ha+jn(+U!)X}q-I;)ct<*IOqTgdBExYYwMu&nDSt zVS&|6muwepQ%t&Qb4pi{*gJk?j%x&7+9Ss=BFN*VkRINjlvr zb7xZ5`RDtaa=4;w&QgjfqU`JYRV%9=2xvLKS=-*yWv6b`v>UrtQySgq*DI?nAW1;$64UPkych`(bkqc)Fu}JL$ts)(`hxyrd}yRK zOhN>)g2ckps;|!nAQ^F@WRRc51cD|>5DXB6@ybP?5wNdMAZVckFOLLGQ_^vKL?C#v zfJapMflskM*_@iOS{sRq1aJA0E)oX7j-ZpE0>4ek7idMp*(YG|XprAU!q9|w2?3wn zV5ew$j%xm#GX9Y=e@bmjQQQ7Rtw(d!8>bPZe zCk%@QhPF+dT%_QWT(k8aeit2-hCN+#s%Cu8on7_jEJuRhiEovaQXk97gu7R@G$Z{ODsk+vw7t(dR(vIDC z89Qyu7;6@_@VyQ4^za2$vX*~^Ejfu;w-F`Qo~j*xE=^acb6PG-{|F6O6DNh4+a+aR z$u%gXa#4eIi{0>tj4UQu(vYSbm&r-GcBkvUmv*$@)o-P{6hWAg1z`pSHQm) literal 0 HcmV?d00001 diff --git a/web/backend/routers/__pycache__/users.cpython-312.pyc b/web/backend/routers/__pycache__/users.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59250bf883cb241c05d373746cc3e579d402682b GIT binary patch literal 6074 zcmcIoU2Gdw7M}5pXU6}j<2pZ%Ay5|8Y)OEo5ZW{lnzm^P1xi84T4cGNNov;%A@ zih~m9l0+1x*^QJ`LZV$&s-tALun$|bVx@iGml#KinJsG3l5P8hOUnySd(Mm}NyAXO z>~f{KKj+?i&b@ce_uVt|50A%5LGu5-J>~OK)W5J{CcOt5H@j^VHBE_BloDytmZoJ} z)Fv}gMrNZdjWQ-}m$@hhbyl>ed0B`GdYwxX&PywQ?XD&}Gi_MmZP_%5~8?nzB)nO>~LwetO*u7iBW)Fih<|mFnIVhm8ViwPsdI(sVg(gcb2l7eXHf_ zs1&y!*g8ysevh?G1_=V)IPOwK~eIsUEYIXRR!O6nC{QfwX3w z2$~@ETS@~zlpdoM8}JJabAmtYRgpIIWNpdZ|GaIEH^!L~nmT!mrSi;6;J*jRM(B+2 znk^}60=~I{xGZTbx|rrjDzSJ%O}-&%9FgQWIjT7mx{rymxT?9DB}|pVBI3a)Y^8I@6yVh-C*`-;@rw9*!rDg56f4qf{|m zyCplQInznSaC(JAKqoFtISJV?RdG@t-$c)N&(xv!4=lMi72TW8b)VmRc5lhOWy$?W z(fvrt{n+sCvd1^P=bs+`7+n^;6P|I;)RD5UWp`T4?lMk z)o(lKS6y~Y5JHNFbk1cJ#-copj4x&{COaM8aMxB#{mkUP4WqD&`C-2ryQqprXV zuj+Zmy!_sw)s_8N+gJle$_jR-eVASHYH{i!2ef4&pXcXTL&4pzTty7(33OVmz|s6@2ZgExUXZPmDh?qpur= zHhj_mW~W;wTMB^-q1n*qk?o(_E(JD2t6{dG6xe!;XWd+dV(lDx2#x|mp|`5}nCUdS18exk34V01k280otP`^i%ybgPS8hb6)c_PhzK%r6Qy^Q7 zKC2{}(?7Hf65S00cHy8VgG9ZQ+dI)d-agg+e&>>_x#(&>7dT&cwyvb=L+u}&A*es6n?0%^+LyNM=9Js#x4qtWsn0eFYuvq zpl-TnvS*Av=|JJo$j}1cTyALkD115$LN5xBj~rj%!{uPZbZRmMLe~mG-W9_>SWqtv z%?=^Mb|S$(wKDzxz^~P)7QPuJRk|&XMQXt~@a7-j9Dmt3_O1z)$uoB(RK|)>MHcnn z7Vp|cZf(2^M7Bb?u;Xe{H<&f+m=B~D)GG4U(bc?QK_>rmlq)(!r|A071`(M10717W z-IyV2A>_)z*I_I!%Ev`k%%JSq4*@JTt=Q+F{ zFnrU89fE%jybQlj;{B4wE4f4h>Jj%(5P<$=c{{o>@Iyx<2>%GH-{PwQl7Vfc+%@r$ zF~NX#FLX)pDCZ#4b0D%w<6|)~lZeGMzAp}ea5hO0S_Jp1v!&)$UQfrPS`r-<~KO+P^TxVB} z6{V~~(7OpbFnrb<|IQeQqcs*k$n{Tgjm>7D`ZNw6reRe2e9bW&p*n=7$s@PE0VXm#WP><_+?1pf= z#SN}sOfL#Ki@l@!T list[str]: + if '|' in line: + return line.split('|') + if '\t' in line: + return line.split('\t') + if ',' in line: + return next(csv.reader([line])) + return line.split() + + +@router.get("", response_model=list[AccountOut]) +def list_accounts( + assigned_only: bool = Query(False), + db: Session = Depends(get_db), + current: User = Depends(get_current_user), +): + """列表:按角色返回不同字段和范围。""" + query = db.query(Account) + + # 权限控制:客服只能看分配给自己的 + if not has_permission(current.role, "account:view_all"): + if has_permission(current.role, "account:view_assigned"): + query = query.filter(Account.assigned_to == current.id) + else: + raise HTTPException(status_code=403, detail="无权查看账号") + + if assigned_only and has_permission(current.role, "account:view_all"): + query = query.filter(Account.assigned_to.isnot(None)) + + accounts = query.order_by(Account.id).all() + result = [] + for acc in accounts: + item = AccountOut( + id=acc.id, username=acc.username, remark=acc.remark or "", + assigned_to=acc.assigned_to, + assigned_username=acc.assigned_user.username if acc.assigned_user else None, + created_at=acc.created_at, + ) + # 运营+超管可看完整字段 + if has_permission(current.role, "account:view_all"): + item.password = acc.password + item.email = acc.email + item.email_password = acc.email_password + result.append(item) + return result + + +@router.post("/import") +def import_accounts( + req: AccountImport, + db: Session = Depends(get_db), + current: User = Depends(require_permission("account:import")), +): + """批量导入账号。格式:用户名|密码|邮箱|邮箱密码""" + from douyu.email_verifier import get_email_config_for_account + + accounts = [] + skipped = 0 + for line_num, line in enumerate(req.text.strip().split('\n'), 1): + line = line.strip() + if not line or line.startswith('#'): + continue + parts = _split_account_line(line) + if len(parts) != 4: + skipped += 1 + continue + + username, password, email, email_password = [p.strip() for p in parts] + if not all([username, password, email, email_password]): + skipped += 1 + continue + if not EMAIL_PATTERN.match(email): + skipped += 1 + continue + + email_cfg = get_email_config_for_account(email) + accounts.append(Account( + username=username, + password=password, + email=email, + email_password=email_password, + email_imap_server=email_cfg['server'], + email_imap_port=email_cfg.get('port', 993), + )) + + if accounts: + db.add_all(accounts) + db.add(AuditLog(user_id=current.id, username=current.username, + action="account:import", target=f"导入{len(accounts)}个")) + db.commit() + + return {"message": f"导入成功 {len(accounts)} 个,跳过 {skipped} 个", "success": True, "count": len(accounts)} + + +@router.put("/{account_id}/assign") +def assign_account( + account_id: int, + req: AccountAssign, + db: Session = Depends(get_db), + current: User = Depends(require_permission("account:assign")), +): + acc = db.query(Account).filter(Account.id == account_id).first() + if not acc: + raise HTTPException(status_code=404, detail="账号不存在") + + if req.assigned_to: + target = db.query(User).filter(User.id == req.assigned_to).first() + if not target: + raise HTTPException(status_code=404, detail="目标用户不存在") + if target.role != "support": + raise HTTPException(status_code=400, detail="只能分配给客服角色") + + acc.assigned_to = req.assigned_to + db.add(AuditLog(user_id=current.id, username=current.username, + action="account:assign", target=acc.username)) + db.commit() + return {"message": "已分配", "success": True} + + +@router.delete("/{account_id}") +def delete_account( + account_id: int, + db: Session = Depends(get_db), + current: User = Depends(require_permission("account:delete")), +): + acc = db.query(Account).filter(Account.id == account_id).first() + if not acc: + raise HTTPException(status_code=404, detail="账号不存在") + + db.add(AuditLog(user_id=current.id, username=current.username, + action="account:delete", target=acc.username)) + db.delete(acc) + db.commit() + return {"message": "已删除", "success": True} diff --git a/web/backend/routers/auth.py b/web/backend/routers/auth.py new file mode 100644 index 0000000..81d2bc6 --- /dev/null +++ b/web/backend/routers/auth.py @@ -0,0 +1,57 @@ +"""认证路由""" + +from datetime import datetime +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session + +from ..database import get_db +from ..models import User, AuditLog +from ..security import verify_password, create_access_token +from ..permissions import get_role_permissions, ROLE_LABELS +from ..schemas import LoginRequest, TokenResponse +from ..deps import get_current_user + +router = APIRouter(prefix="/api/auth", tags=["认证"]) + + +@router.post("/login", response_model=TokenResponse) +def login(req: LoginRequest, db: Session = Depends(get_db)): + user = db.query(User).filter(User.username == req.username).first() + if not user or not verify_password(req.password, user.password_hash): + raise HTTPException(status_code=401, detail="用户名或密码错误") + if not user.is_active: + raise HTTPException(status_code=403, detail="账号已禁用,请联系管理员") + + token = create_access_token({"sub": str(user.id), "role": user.role}) + perms = get_role_permissions(user.role) + + # 审计 + db.add(AuditLog(user_id=user.id, username=user.username, action="login", target="auth")) + db.commit() + + return TokenResponse( + access_token=token, + role=user.role, + username=user.username, + permissions=perms, + ) + + +@router.get("/me") +def me(current_user: User = Depends(get_current_user)): + return { + "id": current_user.id, + "username": current_user.username, + "role": current_user.role, + "role_label": ROLE_LABELS.get(current_user.role, current_user.role), + "is_active": current_user.is_active, + "remark": current_user.remark, + "permissions": get_role_permissions(current_user.role), + } + + +@router.post("/logout") +def logout(current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): + db.add(AuditLog(user_id=current_user.id, username=current_user.username, action="logout", target="auth")) + db.commit() + return {"message": "已登出"} diff --git a/web/backend/routers/login.py b/web/backend/routers/login.py new file mode 100644 index 0000000..c909390 --- /dev/null +++ b/web/backend/routers/login.py @@ -0,0 +1,137 @@ +"""登录任务路由 + WebSocket 实时日志""" + +import asyncio +import threading +from datetime import datetime +from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisconnect +from sqlalchemy.orm import Session + +from ..database import get_db, SessionLocal +from ..models import User, Account, LoginTask, ProxyConfig as ProxyConfigModel +from ..schemas import LoginBatchRequest, LoginTaskOut +from ..deps import get_current_user, require_permission +from ..permissions import has_permission +from ..services.login_service import LoginBatchRunner + +router = APIRouter(prefix="/api/login", tags=["登录任务"]) + +# 运行中的批次: batch_id -> {runner, log_queue, loop} +_active_batches: dict[str, dict] = {} + + +@router.post("/batch") +def create_batch( + req: LoginBatchRequest, + db: Session = Depends(get_db), + current: User = Depends(require_permission("login:batch")), +): + """创建批量登录任务。""" + if not req.account_ids: + raise HTTPException(status_code=400, detail="请选择账号") + + # 读取代理配置 + proxy = db.query(ProxyConfigModel).first() + + # 权限过滤账号 + valid_ids = [] + for aid in req.account_ids: + acc = db.query(Account).filter(Account.id == aid).first() + if not acc: + continue + if not has_permission(current.role, "login:view_all"): + if acc.assigned_to != current.id: + continue + valid_ids.append(aid) + + if not valid_ids: + raise HTTPException(status_code=403, detail="没有可登录的账号") + + # 创建执行器(用独立的 DB 会话,因为在线程中运行) + thread_db = SessionLocal() + runner = LoginBatchRunner( + db=thread_db, + account_ids=valid_ids, + created_by=current.id, + creator_role=current.role, + max_geetest_retries=req.max_geetest_retries, + proxy_config=proxy, + ) + + batch_id = runner.batch_id + + # 启动线程 + thread = threading.Thread(target=runner.run, daemon=True) + thread.start() + + return {"batch_id": batch_id, "count": len(valid_ids), "success": True} + + +@router.get("/tasks", response_model=list[LoginTaskOut]) +def list_tasks( + batch_id: str | None = None, + db: Session = Depends(get_db), + current: User = Depends(get_current_user), +): + """查看登录任务列表。""" + query = db.query(LoginTask) + + # 客服只能看自己账号的任务 + if not has_permission(current.role, "login:view_all"): + query = query.join(Account, LoginTask.account_id == Account.id).filter( + Account.assigned_to == current.id + ) + + if batch_id: + query = query.filter(LoginTask.batch_id == batch_id) + + tasks = query.order_by(LoginTask.id.desc()).limit(200).all() + result = [] + for t in tasks: + acc = db.query(Account).filter(Account.id == t.account_id).first() + result.append(LoginTaskOut( + id=t.id, batch_id=t.batch_id, account_id=t.account_id, + account_username=acc.username if acc else "", + status=t.status, cookie=t.cookie or "", message=t.message or "", + created_by=t.created_by, created_at=t.created_at, finished_at=t.finished_at, + )) + return result + + +@router.post("/stop/{batch_id}") +def stop_batch( + batch_id: str, + current: User = Depends(require_permission("login:batch")), +): + batch = _active_batches.get(batch_id) + if batch: + batch["runner"].stop() + return {"message": "已发送停止信号", "success": True} + raise HTTPException(status_code=404, detail="批次不存在或已结束") + + +@router.websocket("/ws/login/{batch_id}") +async def ws_login_logs(websocket: WebSocket, batch_id: str): + """WebSocket 推送登录实时日志。""" + await websocket.accept() + + log_queue = asyncio.Queue() + loop = asyncio.get_event_loop() + + # 查找已运行的批次,或等待新批次 + # 简化:直接把 log_queue 注册到全局,前端创建批次后连 ws + _active_batches[batch_id] = { + "log_queue": log_queue, + "loop": loop, + } + + try: + while True: + try: + msg = await asyncio.wait_for(log_queue.get(), timeout=30) + await websocket.send_json(msg) + except asyncio.TimeoutError: + await websocket.send_json({"level": "heartbeat", "message": ""}) + except WebSocketDisconnect: + pass + finally: + _active_batches.pop(batch_id, None) diff --git a/web/backend/routers/proxy.py b/web/backend/routers/proxy.py new file mode 100644 index 0000000..0e5a7ee --- /dev/null +++ b/web/backend/routers/proxy.py @@ -0,0 +1,146 @@ +"""代理 & 白名单配置路由""" + +import re +import time +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session + +from ..database import get_db +from ..models import User, ProxyConfig as ProxyConfigModel, AuditLog +from ..schemas import ProxyConfigOut, ProxyConfigUpdate, MessageResponse +from ..deps import require_permission + +router = APIRouter(prefix="/api/proxy", tags=["代理与白名单"]) + + +def _get_or_create(db: Session) -> ProxyConfigModel: + cfg = db.query(ProxyConfigModel).first() + if not cfg: + cfg = ProxyConfigModel() + db.add(cfg) + db.commit() + db.refresh(cfg) + return cfg + + +@router.get("", response_model=ProxyConfigOut) +def get_proxy_config( + db: Session = Depends(get_db), + _: User = Depends(require_permission("proxy:manage")), +): + return _get_or_create(db) + + +@router.put("", response_model=ProxyConfigOut) +def update_proxy_config( + req: ProxyConfigUpdate, + db: Session = Depends(get_db), + current: User = Depends(require_permission("proxy:manage")), +): + cfg = _get_or_create(db) + cfg.enabled = req.enabled + cfg.api_url = req.api_url + cfg.http = req.http + cfg.https = req.https + cfg.whitelist_enabled = req.whitelist_enabled + cfg.whitelist_uid = req.whitelist_uid + cfg.whitelist_ukey = req.whitelist_ukey + db.commit() + db.refresh(cfg) + + db.add(AuditLog(user_id=current.id, username=current.username, + action="proxy:update", target="proxy_config")) + db.commit() + return cfg + + +@router.post("/test") +def test_proxy( + db: Session = Depends(get_db), + current: User = Depends(require_permission("proxy:manage")), +): + """测试代理连通性。""" + import requests as req_lib + cfg = _get_or_create(db) + if not cfg.enabled: + return {"success": False, "message": "代理未启用"} + + proxy_url = cfg.http or cfg.https + if cfg.api_url and not proxy_url: + try: + resp = req_lib.get(cfg.api_url, timeout=10) + match = re.search(r'(\d+\.\d+\.\d+\.\d+):(\d+)', resp.text) + if match: + proxy_url = f"http://{match.group(1)}:{match.group(2)}" + except Exception as e: + return {"success": False, "message": f"代理API请求失败: {e}"} + + if not proxy_url: + return {"success": False, "message": "无可用代理地址"} + + try: + resp = req_lib.get( + "https://qifu-api.baidubce.com/ip/local/geo/v1/district", + proxies={"http": proxy_url, "https": proxy_url}, + timeout=(4, 6), + headers={"User-Agent": "Mozilla/5.0"}, + ) + resp.raise_for_status() + return {"success": True, "message": f"代理可用,响应: {resp.text[:100]}"} + except Exception as e: + return {"success": False, "message": f"代理验证失败: {e}"} + + +@router.post("/whitelist/test") +def test_whitelist( + db: Session = Depends(get_db), + current: User = Depends(require_permission("whitelist:test")), +): + """测试白名单连接并自动同步出口IP。""" + from douyu.whitelist import WhitelistManager, get_exit_ip_via_proxy + import requests as req_lib + + cfg = _get_or_create(db) + if not cfg.whitelist_enabled: + return {"success": False, "message": "白名单未启用"} + if not cfg.whitelist_uid or not cfg.whitelist_ukey: + return {"success": False, "message": "未配置白名单UID/UKEY"} + + manager = WhitelistManager(cfg.whitelist_uid, cfg.whitelist_ukey) + + # 测试API连接 + ok, msg = manager.test_connection() + if not ok: + return {"success": False, "message": msg} + + # 获取出口IP + proxy_url = cfg.http or cfg.https + if cfg.api_url and not proxy_url: + try: + resp = req_lib.get(cfg.api_url, timeout=10) + match = re.search(r'(\d+\.\d+\.\d+\.\d+):(\d+)', resp.text) + if match: + proxy_url = f"http://{match.group(1)}:{match.group(2)}" + except Exception: + pass + + exit_ip = None + if proxy_url: + exit_ip = get_exit_ip_via_proxy(proxy_url) + if not exit_ip: + try: + resp = req_lib.get("https://4.ipw.cn", timeout=6, headers={"User-Agent": "Mozilla/5.0"}) + match = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', resp.text) + if match: + exit_ip = match.group(1) + except Exception: + pass + + if not exit_ip: + return {"success": False, "message": "API连接正常但无法获取出口IP"} + + # 同步白名单 + sync_ok, sync_msg = manager.sync_ip(exit_ip) + if sync_ok: + return {"success": True, "message": f"出口IP {exit_ip} 已同步: {sync_msg}"} + return {"success": False, "message": f"同步失败: {sync_msg}"} diff --git a/web/backend/routers/users.py b/web/backend/routers/users.py new file mode 100644 index 0000000..2cda1dc --- /dev/null +++ b/web/backend/routers/users.py @@ -0,0 +1,117 @@ +"""用户管理路由(超管)""" + +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session + +from ..database import get_db +from ..models import User, AuditLog +from ..security import hash_password +from ..permissions import ROLE_LABELS, get_role_permissions +from ..schemas import UserCreate, UserUpdate, UserInfo +from ..deps import require_permission, get_current_user + +router = APIRouter(prefix="/api/users", tags=["用户管理"]) + + +@router.get("", response_model=list[UserInfo]) +def list_users( + db: Session = Depends(get_db), + _: User = Depends(require_permission("user:view")), +): + users = db.query(User).order_by(User.id).all() + result = [] + for u in users: + result.append(UserInfo( + id=u.id, + username=u.username, + role=u.role, + is_active=u.is_active, + remark=u.remark or "", + created_at=u.created_at, + permissions=get_role_permissions(u.role), + )) + return result + + +@router.post("", response_model=UserInfo) +def create_user( + req: UserCreate, + db: Session = Depends(get_db), + current: User = Depends(require_permission("user:create")), +): + if db.query(User).filter(User.username == req.username).first(): + raise HTTPException(status_code=400, detail="用户名已存在") + + user = User( + username=req.username, + password_hash=hash_password(req.password), + role=req.role, + remark=req.remark, + is_active=True, + ) + db.add(user) + db.commit() + db.refresh(user) + + db.add(AuditLog(user_id=current.id, username=current.username, + action="user:create", target=user.username)) + db.commit() + + return UserInfo( + id=user.id, username=user.username, role=user.role, + is_active=user.is_active, remark=user.remark or "", + permissions=get_role_permissions(user.role), + ) + + +@router.put("/{user_id}", response_model=UserInfo) +def update_user( + user_id: int, + req: UserUpdate, + db: Session = Depends(get_db), + current: User = Depends(require_permission("user:edit")), +): + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException(status_code=404, detail="用户不存在") + + if req.password: + user.password_hash = hash_password(req.password) + if req.role is not None: + user.role = req.role + if req.is_active is not None: + user.is_active = req.is_active + if req.remark is not None: + user.remark = req.remark + + db.commit() + db.refresh(user) + + db.add(AuditLog(user_id=current.id, username=current.username, + action="user:edit", target=user.username)) + db.commit() + + return UserInfo( + id=user.id, username=user.username, role=user.role, + is_active=user.is_active, remark=user.remark or "", + permissions=get_role_permissions(user.role), + ) + + +@router.delete("/{user_id}") +def delete_user( + user_id: int, + db: Session = Depends(get_db), + current: User = Depends(require_permission("user:delete")), +): + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException(status_code=404, detail="用户不存在") + if user.role == "super_admin": + raise HTTPException(status_code=400, detail="不能删除超级管理员") + + db.add(AuditLog(user_id=current.id, username=current.username, + action="user:delete", target=user.username)) + db.delete(user) + db.commit() + return {"message": "已删除", "success": True} diff --git a/web/backend/schemas.py b/web/backend/schemas.py new file mode 100644 index 0000000..e47e8d6 --- /dev/null +++ b/web/backend/schemas.py @@ -0,0 +1,116 @@ +"""Pydantic 请求/响应模型""" + +from datetime import datetime +from typing import Optional +from pydantic import BaseModel, Field + + +# ---- 认证 ---- +class LoginRequest(BaseModel): + username: str + password: str + + +class TokenResponse(BaseModel): + access_token: str + token_type: str = "bearer" + role: str + username: str + permissions: list[str] + + +class UserInfo(BaseModel): + id: int + username: str + role: str + is_active: bool + remark: str = "" + created_at: Optional[datetime] = None + permissions: list[str] = [] + + class Config: + from_attributes = True + + +# ---- 用户管理 ---- +class UserCreate(BaseModel): + username: str = Field(..., min_length=2, max_length=64) + password: str = Field(..., min_length=6, max_length=128) + role: str = Field("support", pattern="^(super_admin|operation|support)$") + remark: str = "" + + +class UserUpdate(BaseModel): + password: Optional[str] = None + role: Optional[str] = None + is_active: Optional[bool] = None + remark: Optional[str] = None + + +# ---- 账号 ---- +class AccountImport(BaseModel): + """批量导入,文本格式:用户名|密码|邮箱|邮箱密码""" + text: str + + +class AccountAssign(BaseModel): + assigned_to: Optional[int] = None + + +class AccountOut(BaseModel): + id: int + username: str + # 敏感字段根据角色决定是否返回 + password: Optional[str] = None + email: Optional[str] = None + email_password: Optional[str] = None + assigned_to: Optional[int] = None + assigned_username: Optional[str] = None + remark: str = "" + created_at: Optional[datetime] = None + + class Config: + from_attributes = True + + +# ---- 登录任务 ---- +class LoginBatchRequest(BaseModel): + account_ids: list[int] + max_geetest_retries: int = 5 + + +class LoginTaskOut(BaseModel): + id: int + batch_id: str + account_id: int + account_username: str = "" + status: str + cookie: str = "" + message: str = "" + created_by: int + created_at: Optional[datetime] = None + finished_at: Optional[datetime] = None + + class Config: + from_attributes = True + + +# ---- 代理配置 ---- +class ProxyConfigOut(BaseModel): + enabled: bool = False + api_url: str = "" + http: str = "" + https: str = "" + whitelist_enabled: bool = False + whitelist_uid: str = "" + whitelist_ukey: str = "" + + +class ProxyConfigUpdate(ProxyConfigOut): + pass + + +# ---- 通用 ---- +class MessageResponse(BaseModel): + message: str + success: bool = True diff --git a/web/backend/security.py b/web/backend/security.py new file mode 100644 index 0000000..820ae54 --- /dev/null +++ b/web/backend/security.py @@ -0,0 +1,38 @@ +"""安全模块:密码哈希 + JWT""" + +from datetime import datetime, timedelta, timezone +from typing import Optional +import bcrypt +from jose import jwt, JWTError + +SECRET_KEY = "douyu-web-secret-change-in-production" +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_HOURS = 24 + + +def hash_password(password: str) -> str: + pwd_bytes = password.encode("utf-8") + # bcrypt 限制72字节,截断 + pwd_bytes = pwd_bytes[:72] + return bcrypt.hashpw(pwd_bytes, bcrypt.gensalt()).decode("utf-8") + + +def verify_password(plain: str, hashed: str) -> bool: + try: + return bcrypt.checkpw(plain.encode("utf-8")[:72], hashed.encode("utf-8")) + except Exception: + return False + + +def create_access_token(data: dict, expires_hours: int = ACCESS_TOKEN_EXPIRE_HOURS) -> str: + to_encode = data.copy() + expire = datetime.now(timezone.utc) + timedelta(hours=expires_hours) + to_encode.update({"exp": expire}) + return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + + +def decode_access_token(token: str) -> Optional[dict]: + try: + return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + except JWTError: + return None diff --git a/web/backend/services/__init__.py b/web/backend/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/backend/services/__pycache__/__init__.cpython-312.pyc b/web/backend/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d3e53cef9859f112f4bc2fa77f5ae0593e006b6 GIT binary patch literal 161 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 A*#H0l literal 0 HcmV?d00001 diff --git a/web/backend/services/login_service.py b/web/backend/services/login_service.py new file mode 100644 index 0000000..5cfba3a --- /dev/null +++ b/web/backend/services/login_service.py @@ -0,0 +1,139 @@ +"""登录服务:复用 douyu/ 核心模块,在线程池中执行登录并推送日志。""" + +import asyncio +import threading +import time +import uuid +from datetime import datetime +from typing import Optional + +from sqlalchemy.orm import Session + +from douyu import DouyuLogin +from douyu.config import Account, ProxyConfig as DouyuProxyConfig +from ..models import Account as AccountModel, LoginTask, ProxyConfig as ProxyConfigModel +from ..permissions import has_permission + + +class LoginBatchRunner: + """批量登录执行器,在线程中运行,通过 asyncio.Queue 推送日志。""" + + def __init__( + self, + db: Session, + account_ids: list[int], + created_by: int, + creator_role: str, + max_geetest_retries: int = 5, + proxy_config: Optional[ProxyConfigModel] = None, + log_queue: Optional[asyncio.Queue] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + ): + self.db = db + self.account_ids = account_ids + self.created_by = created_by + self.creator_role = creator_role + self.max_geetest_retries = max_geetest_retries + self.proxy_config = proxy_config + self.log_queue = log_queue + self.loop = loop + self.batch_id = uuid.uuid4().hex[:12] + self._stop = threading.Event() + + def stop(self): + self._stop.set() + + def _push_log(self, level: str, message: str): + if self.log_queue and self.loop: + asyncio.run_coroutine_threadsafe( + self.log_queue.put({"level": level, "message": message}), + self.loop, + ) + + def run(self): + """在线程中执行批量登录。""" + batch_id = self.batch_id + self._push_log("info", f"批量登录任务 {batch_id} 开始,共 {len(self.account_ids)} 个账号") + + # 创建任务记录 + tasks = [] + for aid in self.account_ids: + acc = self.db.query(AccountModel).filter(AccountModel.id == aid).first() + if not acc: + continue + # 权限检查:客服只能跑分配给自己的 + if not has_permission(self.creator_role, "login:view_all"): + if acc.assigned_to != self.created_by: + self._push_log("warning", f"跳过无权账号: {acc.username}") + continue + + task = LoginTask( + batch_id=batch_id, + account_id=aid, + status="pending", + created_by=self.created_by, + ) + self.db.add(task) + tasks.append((task, acc)) + + self.db.commit() + + # 构建代理配置 + proxy_url = None + proxy_api_url = None + if self.proxy_config and self.proxy_config.enabled: + if self.proxy_config.http or self.proxy_config.https: + proxy_url = { + 'http': self.proxy_config.http or self.proxy_config.https, + 'https': self.proxy_config.https or self.proxy_config.http, + } + elif self.proxy_config.api_url: + proxy_api_url = self.proxy_config.api_url + + for i, (task, acc) in enumerate(tasks): + if self._stop.is_set(): + self._push_log("warning", "任务已停止") + break + + task.status = "running" + self.db.commit() + + self._push_log("info", f"[{i+1}/{len(tasks)}] 开始登录: {acc.username}") + + try: + account = Account( + username=acc.username, + password=acc.password, + email=acc.email, + email_password=acc.email_password, + email_imap_server=acc.email_imap_server or "", + email_imap_port=acc.email_imap_port or 993, + ) + + loginer = DouyuLogin( + account, + proxy=proxy_url, + proxy_api_url=proxy_api_url, + max_geetest_retries=self.max_geetest_retries, + ) + result = loginer.login() + + if result.success: + task.status = "success" + task.cookie = result.cookie + task.message = "登录成功" + self._push_log("success", f"[{i+1}] {acc.username} 登录成功") + else: + task.status = "failed" + task.message = result.message + self._push_log("error", f"[{i+1}] {acc.username} 登录失败: {result.message}") + + except Exception as e: + task.status = "error" + task.message = str(e) + self._push_log("error", f"[{i+1}] {acc.username} 登录异常: {e}") + + task.finished_at = datetime.utcnow() + self.db.commit() + + self._push_log("info", f"批量登录任务 {batch_id} 完成") diff --git a/web/frontend/.gitignore b/web/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/web/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/web/frontend/README.md b/web/frontend/README.md new file mode 100644 index 0000000..7dbf7eb --- /dev/null +++ b/web/frontend/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/web/frontend/eslint.config.js b/web/frontend/eslint.config.js new file mode 100644 index 0000000..ef614d2 --- /dev/null +++ b/web/frontend/eslint.config.js @@ -0,0 +1,22 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + }, + }, +]) diff --git a/web/frontend/index.html b/web/frontend/index.html new file mode 100644 index 0000000..0fca6f0 --- /dev/null +++ b/web/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + frontend + + +

+ + + diff --git a/web/frontend/package-lock.json b/web/frontend/package-lock.json new file mode 100644 index 0000000..510c6de --- /dev/null +++ b/web/frontend/package-lock.json @@ -0,0 +1,4084 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@ant-design/icons": "^6.2.5", + "antd": "^6.4.4", + "axios": "^1.18.0", + "dayjs": "^1.11.21", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-router-dom": "^7.18.0" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.12.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.59.2", + "vite": "^8.0.12" + } + }, + "node_modules/@ant-design/colors": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz", + "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz", + "integrity": "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.2.tgz", + "integrity": "sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^2.1.2", + "@babel/runtime": "^7.23.2", + "@rc-component/util": "^1.4.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz", + "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==", + "license": "MIT", + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.2.5.tgz", + "integrity": "sha512-0hKtoKqTjGFOndUyJLJmC9Cg6k4rEO7rLo6xmgbNJH+/ZX1C57RVals2v1j1knHl9n7Q+sBOveTvn931wLOCKw==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/icons-svg": "^4.4.2", + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/react-slick": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-2.0.0.tgz", + "integrity": "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "clsx": "^2.1.1", + "json2mq": "^0.2.0", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-6.0.0.tgz", + "integrity": "sha512-D3AGQwdyE58gmvx6waVSXJ80JGO+IY5L2O8HDnSOex7JNlzB3GuN/4hyHNTdhy2qtOhkpbIjmeAN3tL993wKbA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/cascader": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.16.1.tgz", + "integrity": "sha512-wxLopwM+EBed0zNNGdnGE4coYoqcO+XD42fHgn+pDvO+XzhNFbdgSlSNXdKocIYqccvqgWvoxDPNb0OVRdi59A==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.7.1", + "@rc-component/tree": "~1.3.2", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/checkbox": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-2.0.0.tgz", + "integrity": "sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/collapse": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.2.0.tgz", + "integrity": "sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-3.1.1.tgz", + "integrity": "sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-2.0.2.tgz", + "integrity": "sha512-uiGpAlblCNlziHPwj4S4Iy/oemeuz/hR03mbiEjTCXwsqOIN3BOzsRMyDwpyO5Fm0vIEEJRUf9ZtbRLbhksuTA==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/dialog": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/dialog/-/dialog-1.9.0.tgz", + "integrity": "sha512-zbAAogkg4kkKum79sLE6M+vq1jSAW25zdkafrahgcTP9t9S//SD634Znd1A4c8F2Gc12ZKnehGLsVaaOvZzD2A==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.3", + "@rc-component/portal": "^2.1.0", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/drawer": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@rc-component/drawer/-/drawer-1.4.2.tgz", + "integrity": "sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.1.3", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/dropdown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.2.tgz", + "integrity": "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/@rc-component/form": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@rc-component/form/-/form-1.8.5.tgz", + "integrity": "sha512-d24EYtvUOBhxEtSd/EqIu9DaMuqrWF2IRIvAFCTM6NQ/GJIYNr8DvEpUSUlv2uPxEJ0ZPwYQ+wwlGIAaiHvdrw==", + "license": "MIT", + "dependencies": { + "@rc-component/async-validator": "^6.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/image": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/image/-/image-1.9.0.tgz", + "integrity": "sha512-khF7w7xkBH5B1bsBcI1FSUZdkyd1aqpl2eYyILCqCzzQH3XdfehGUaZTnptyaJJfs09/R5hv9jXWyazOMFIClQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/portal": "^2.1.2", + "@rc-component/util": "^1.10.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/input": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@rc-component/input/-/input-1.3.1.tgz", + "integrity": "sha512-iFvTUT9W+JC/MSin2aGAk8NqsVlTzcExNC9DZariON1IWirju9NoNeEk47an4Q8iHazkoVI/y1LnDi88+CPcig==", + "license": "MIT", + "dependencies": { + "@rc-component/resize-observer": "^1.1.1", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/input-number": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@rc-component/input-number/-/input-number-1.6.2.tgz", + "integrity": "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==", + "license": "MIT", + "dependencies": { + "@rc-component/mini-decimal": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mentions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/mentions/-/mentions-1.9.0.tgz", + "integrity": "sha512-WUwfFKDSOF5S9UPsNsXcLYtzjTxBGsftTXWRbZuxX6BYrsySISTnujfJNgaaQ6qVzaCDJ35QUkZKvsYxip1C5g==", + "license": "MIT", + "dependencies": { + "@rc-component/input": "~1.3.0", + "@rc-component/menu": "~1.3.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/menu": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@rc-component/menu/-/menu-1.3.1.tgz", + "integrity": "sha512-pSZl9nBPgKgxN0aaW7NilIBEwWsc+43S+ulGdWAg9afak96dNOGWsGx0DLLBB1VQsAJvo6bQMTDzXoPlEHsBEw==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.4.tgz", + "integrity": "sha512-xiuXcaCwyOWpD8a8scdExFl+bntNphAW8XeenL1ig2en0AAZY0Pcp4pC0dI22qJ+NvxKn9RoNIoRdqYU3BLH4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/motion": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@rc-component/motion/-/motion-1.3.3.tgz", + "integrity": "sha512-Xh3IszxvlSv3/PLYFyC2UZi9LNB83yOnkB/LNmRzaypZLvkhqUIPS7MQpGZcCMWrNsXV2p6YTSWbSGvFpEle9A==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz", + "integrity": "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/notification": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@rc-component/notification/-/notification-2.0.7.tgz", + "integrity": "sha512-nqZzpf6BPdaj+3ILx7si79LLmqPKyUmQoXa+/9gg0SkH0v1DbD66oJgRMSBEVnd/zUT3D4gwxWIHUKebYf2ZXQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/overflow": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/overflow/-/overflow-1.0.1.tgz", + "integrity": "sha512-syfmgAABaHCnCDzPwHZ/2tuvIcpOO3jefYZMmfkN+pmo8HKTzsfhS57vxo4ksPdN0By+uWVJhJWNFozNBxi2eA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/pagination": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.3.0.tgz", + "integrity": "sha512-12ahTY+HPITg1L2bjWKXUqBJe/oOnpA2QsChdCjthqLVf/e19StiCsv8OLKpWoHbc+8PFEkNjRqRqrLoRBHjFw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/picker": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.10.0.tgz", + "integrity": "sha512-vVOXP2RVWozwpERGUFAehVH1Jz6o/uRrAb9qSZm1LC+iJs8rvEwFo1bzz2jlOYV+uWwu0dIuG86tnDui14Ea0w==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/trigger": "^3.6.15", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/@rc-component/portal": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-2.2.1.tgz", + "integrity": "sha512-ck+r1kW/JSv0wxPji3KN2ss9K6Z0qqwusw/mf/0JobXhZ8hC2ejZwCJObW/SvDi0uhA0VzmCnx0CaCci95tcmA==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/progress": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/progress/-/progress-1.0.2.tgz", + "integrity": "sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-2.0.0.tgz", + "integrity": "sha512-aAv3QhPP1xyafuTZOxub6a54pCeBnN3IwQkpETrBtthq4BL5IgxnCbuoBWPDpdLw1y1j6BgBUCAKV92+yX06Dw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/rate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/rate/-/rate-1.0.1.tgz", + "integrity": "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/resize-observer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.1.2.tgz", + "integrity": "sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/segmented": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/segmented/-/segmented-1.3.0.tgz", + "integrity": "sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/select": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.7.1.tgz", + "integrity": "sha512-GZ1cMJk2xQh0VHyOQjjG8drYL4iu24NcbkXioUcReQOCUr+ub/3fmRonZe6cRPEZhWMbJdeHsqnEltogDaZ5Tg==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.11.1", + "@rc-component/virtual-list": "^1.2.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/slider": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/slider/-/slider-1.0.1.tgz", + "integrity": "sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/steps": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rc-component/steps/-/steps-1.2.2.tgz", + "integrity": "sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/switch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rc-component/switch/-/switch-1.0.3.tgz", + "integrity": "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/table": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.10.2.tgz", + "integrity": "sha512-b3PjqB9Gp25p5t/zq+9QrbXbodkptT8/zvLmwgd2FNPUUtaYyDnQqfxeD5a7ao8E8lpinLHsi2u2vdfPhyNvAw==", + "license": "MIT", + "dependencies": { + "@rc-component/context": "^2.0.1", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.11.1", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tabs": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.9.1.tgz", + "integrity": "sha512-6mY08Fce6aNOHuGsxbzT+f2ekgL9mg1cGGHkittMlVGymjGg+kGupu5v90sRxcUd/paRU9jclLLXtF/PkK1FUA==", + "license": "MIT", + "dependencies": { + "@rc-component/dropdown": "~1.0.0", + "@rc-component/menu": "~1.3.0", + "@rc-component/motion": "^1.1.3", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tooltip": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/tooltip/-/tooltip-1.4.0.tgz", + "integrity": "sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.7.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-2.4.0.tgz", + "integrity": "sha512-aui4r4TqmTzwaBgcQxHYep8kM8PTjZFufjokObpy35KfFeZ0k9ArquWFZqegQlH24P14t+F0qO0mGTgzlav1yg==", + "license": "MIT", + "dependencies": { + "@rc-component/portal": "^2.2.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.7.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tree": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rc-component/tree/-/tree-1.3.2.tgz", + "integrity": "sha512-bJFj46wEkpBPnWyTm18XmgAgNQ/4YvprxMOPPY2a6rmhGJYxLuNKEFiL5Qej4Qctu9wHJm8WW+v2SYskafE0kA==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/util": "^1.11.1", + "@rc-component/virtual-list": "^1.2.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/tree-select": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.10.0.tgz", + "integrity": "sha512-E1U4pn2LAbXEhLJdzIzid7WYbIuFbkTIctuFoeC6weppf8UbPR3+YYB6/ay0c0ksand4gXMRQpa1Z60Auo7VJA==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.7.0", + "@rc-component/tree": "~1.3.0", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/trigger": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.9.1.tgz", + "integrity": "sha512-LNsYvz60mrLJ/kRvKcHE7boUvcQfVMCfRqZ71x3Fo9AOiZ1KKIEqkzMA8DNvz2V3Bcvir/vwQNn7JF1NPODQ7Q==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.2.0", + "@rc-component/resize-observer": "^1.1.1", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/upload": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/upload/-/upload-1.1.1.tgz", + "integrity": "sha512-GvYWSKeaJTOxxC5p6+nOSadzfvXA1h8C/iHFPFZX+szH3JUXrvs+DLiW8YUTBgvMh8m63mJeHrlYlJzAlg+pDA==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/util": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.11.1.tgz", + "integrity": "sha512-awVlI3ub2vqfqkYxOBc/uQ0efm3jw0wcrhtO/YWLyZfxiKXczKwNbVuhlnyxytDt7H9pbbVQiqr+O6MLATtRYg==", + "license": "MIT", + "dependencies": { + "is-mobile": "^5.0.0", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/virtual-list": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.2.0.tgz", + "integrity": "sha512-iavRm1Jo4GDbASQwdGa7jFyk93RvSOo9xHyBT4QL1pgFJj/Fdf1G+3RErH7/7BmAMvx2AkF62mjGYxDbXsK9TQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", + "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/type-utils": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.61.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", + "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", + "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.61.1", + "@typescript-eslint/types": "^8.61.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", + "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", + "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", + "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", + "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", + "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.61.1", + "@typescript-eslint/tsconfig-utils": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", + "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", + "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/antd": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/antd/-/antd-6.4.4.tgz", + "integrity": "sha512-lgPz4KhfhiYddV/qPYo0ieqWimCVgV2OQF72mbeGNixE753JWNnmEc7UNGy08wBS/zZ7hxrmX0pc5aX7EUaIIg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/cssinjs": "^2.1.2", + "@ant-design/cssinjs-utils": "^2.1.2", + "@ant-design/fast-color": "^3.0.1", + "@ant-design/icons": "^6.2.5", + "@ant-design/react-slick": "~2.0.0", + "@babel/runtime": "^7.29.2", + "@rc-component/cascader": "~1.16.1", + "@rc-component/checkbox": "~2.0.0", + "@rc-component/collapse": "~1.2.0", + "@rc-component/color-picker": "~3.1.1", + "@rc-component/dialog": "~1.9.0", + "@rc-component/drawer": "~1.4.2", + "@rc-component/dropdown": "~1.0.2", + "@rc-component/form": "~1.8.3", + "@rc-component/image": "~1.9.0", + "@rc-component/input": "~1.3.1", + "@rc-component/input-number": "~1.6.2", + "@rc-component/mentions": "~1.9.0", + "@rc-component/menu": "~1.3.1", + "@rc-component/motion": "^1.3.3", + "@rc-component/mutate-observer": "^2.0.1", + "@rc-component/notification": "~2.0.7", + "@rc-component/pagination": "~1.3.0", + "@rc-component/picker": "~1.10.0", + "@rc-component/progress": "~1.0.2", + "@rc-component/qrcode": "~2.0.0", + "@rc-component/rate": "~1.0.1", + "@rc-component/resize-observer": "^1.1.2", + "@rc-component/segmented": "~1.3.0", + "@rc-component/select": "~1.7.1", + "@rc-component/slider": "~1.0.1", + "@rc-component/steps": "~1.2.2", + "@rc-component/switch": "~1.0.3", + "@rc-component/table": "~1.10.2", + "@rc-component/tabs": "~1.9.1", + "@rc-component/tooltip": "~1.4.0", + "@rc-component/tour": "~2.4.0", + "@rc-component/tree": "~1.3.2", + "@rc-component/tree-select": "~1.10.0", + "@rc-component/trigger": "^3.9.1", + "@rc-component/upload": "~1.1.1", + "@rc-component/util": "^1.11.1", + "clsx": "^2.1.1", + "dayjs": "^1.11.11", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", + "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.376", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz", + "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.5.0.tgz", + "integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.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", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.3.tgz", + "integrity": "sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.48", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", + "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-router": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.0.tgz", + "integrity": "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.0.tgz", + "integrity": "sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.1.tgz", + "integrity": "sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.61.1", + "@typescript-eslint/parser": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/web/frontend/package.json b/web/frontend/package.json new file mode 100644 index 0000000..fa43c51 --- /dev/null +++ b/web/frontend/package.json @@ -0,0 +1,35 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@ant-design/icons": "^6.2.5", + "antd": "^6.4.4", + "axios": "^1.18.0", + "dayjs": "^1.11.21", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-router-dom": "^7.18.0" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.12.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.59.2", + "vite": "^8.0.12" + } +} diff --git a/web/frontend/public/favicon.svg b/web/frontend/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/web/frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/frontend/public/icons.svg b/web/frontend/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/web/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/frontend/src/App.tsx b/web/frontend/src/App.tsx new file mode 100644 index 0000000..b4058b0 --- /dev/null +++ b/web/frontend/src/App.tsx @@ -0,0 +1,42 @@ +import { useState, useCallback } from 'react'; +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import { ConfigProvider } from 'antd'; +import zhCN from 'antd/locale/zh_CN'; +import LoginPage from './pages/LoginPage'; +import MainLayout from './layouts/MainLayout'; +import DashboardPage from './pages/DashboardPage'; +import AccountsPage from './pages/AccountsPage'; +import LoginTasksPage from './pages/LoginTasksPage'; +import ProxyPage from './pages/ProxyPage'; +import UsersPage from './pages/UsersPage'; +import { getToken } from './store/auth'; + +function App() { + // 用 state 驱动重渲染,登录/登出时调 refreshAuth() + const [authVersion, setAuthVersion] = useState(0); + const refreshAuth = useCallback(() => setAuthVersion((v) => v + 1), []); + const isLoggedIn = !!getToken(); + + return ( + + + + } /> + : } + > + } /> + } /> + } /> + } /> + } /> + + } /> + + + + ); +} + +export default App; diff --git a/web/frontend/src/api/index.ts b/web/frontend/src/api/index.ts new file mode 100644 index 0000000..99b3d7a --- /dev/null +++ b/web/frontend/src/api/index.ts @@ -0,0 +1,31 @@ +import axios from 'axios'; + +const api = axios.create({ + baseURL: '/api', + timeout: 30000, +}); + +// 请求拦截:携带 token +api.interceptors.request.use((config) => { + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +// 响应拦截:统一错误处理 +api.interceptors.response.use( + (response) => response.data, + (error) => { + if (error.response?.status === 401) { + localStorage.removeItem('token'); + localStorage.removeItem('user'); + window.location.href = '/login'; + } + const msg = error.response?.data?.detail || error.message || '请求失败'; + return Promise.reject(new Error(msg)); + } +); + +export default api; diff --git a/web/frontend/src/api/modules.ts b/web/frontend/src/api/modules.ts new file mode 100644 index 0000000..3c2de94 --- /dev/null +++ b/web/frontend/src/api/modules.ts @@ -0,0 +1,60 @@ +import api from './index'; + +export interface LoginResult { + access_token: string; + token_type: string; + role: string; + username: string; + permissions: string[]; +} + +export interface UserInfo { + id: number; + username: string; + role: string; + is_active: boolean; + remark: string; + permissions: string[]; +} + +export const authApi = { + login: (username: string, password: string) => + api.post('/auth/login', { username, password }), + + me: () => api.get('/auth/me'), + + logout: () => api.post('/auth/logout'), +}; + +export const userApi = { + list: () => api.get('/users'), + create: (data: { username: string; password: string; role: string; remark?: string }) => + api.post('/users', data), + update: (id: number, data: { password?: string; role?: string; is_active?: boolean; remark?: string }) => + api.put(`/users/${id}`, data), + delete: (id: number) => api.delete(`/users/${id}`), +}; + +export const accountApi = { + list: (assigned_only?: boolean) => + api.get('/accounts', { params: assigned_only ? { assigned_only: true } : {} }), + import: (text: string) => api.post('/accounts/import', { text }), + assign: (id: number, assigned_to: number | null) => + api.put(`/accounts/${id}/assign`, { assigned_to }), + delete: (id: number) => api.delete(`/accounts/${id}`), +}; + +export const loginApi = { + createBatch: (account_ids: number[], max_geetest_retries?: number) => + api.post('/login/batch', { account_ids, max_geetest_retries }), + listTasks: (batch_id?: string) => + api.get('/login/tasks', { params: batch_id ? { batch_id } : {} }), + stop: (batch_id: string) => api.post(`/login/stop/${batch_id}`), +}; + +export const proxyApi = { + get: () => api.get('/proxy'), + update: (data: any) => api.put('/proxy', data), + test: () => api.post('/proxy/test'), + testWhitelist: () => api.post('/proxy/whitelist/test'), +}; diff --git a/web/frontend/src/assets/hero.png b/web/frontend/src/assets/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..02251f4b956c55af2d76fd0788124d7eee2b45eb GIT binary patch literal 13057 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 P00000NkvXXu0mjft=yBf literal 0 HcmV?d00001 diff --git a/web/frontend/src/assets/react.svg b/web/frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/web/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/frontend/src/assets/vite.svg b/web/frontend/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/web/frontend/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/web/frontend/src/index.css b/web/frontend/src/index.css new file mode 100644 index 0000000..f60ecaf --- /dev/null +++ b/web/frontend/src/index.css @@ -0,0 +1,9 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} diff --git a/web/frontend/src/layouts/MainLayout.tsx b/web/frontend/src/layouts/MainLayout.tsx new file mode 100644 index 0000000..005cef2 --- /dev/null +++ b/web/frontend/src/layouts/MainLayout.tsx @@ -0,0 +1,114 @@ +import { useEffect, useState } from 'react'; +import { Layout, Menu, Dropdown, Avatar, Space, Typography } from 'antd'; +import { + DashboardOutlined, UserOutlined, LogoutOutlined, + CloudServerOutlined, TeamOutlined, ApiOutlined, +} from '@ant-design/icons'; +import { useNavigate, useLocation, Outlet } from 'react-router-dom'; +import { getUser, clearAuth, hasPerm, type AuthUser } from '../store/auth'; +import { authApi } from '../api/modules'; + +const { Header, Sider, Content } = Layout; +const { Text } = Typography; + +const ROLE_LABELS: Record = { + super_admin: '超级管理员', + operation: '运营', + support: '客服', +}; + +export default function MainLayout({ onLogout }: { onLogout?: () => void }) { + const navigate = useNavigate(); + const location = useLocation(); + const [user] = useState(getUser()); + const [collapsed, setCollapsed] = useState(false); + + useEffect(() => { + if (!user) navigate('/login'); + }, [user]); + + if (!user) return null; + + const menuItems: any[] = []; + + // Dashboard - 所有人可见 + menuItems.push({ key: '/', label: '概览', icon: }); + + // 账号管理 + if (hasPerm(user, 'account:view_all') || hasPerm(user, 'account:view_assigned')) { + menuItems.push({ key: '/accounts', label: '账号管理', icon: }); + } + + // 登录任务 + if (hasPerm(user, 'login:batch') || hasPerm(user, 'login:view_assigned')) { + menuItems.push({ key: '/login-tasks', label: '登录任务', icon: }); + } + + // 代理配置 + if (hasPerm(user, 'proxy:manage')) { + menuItems.push({ key: '/proxy', label: '代理配置', icon: }); + } + + // 用户管理 + if (hasPerm(user, 'user:view')) { + menuItems.push({ key: '/users', label: '用户管理', icon: }); + } + + const handleLogout = async () => { + try { + await authApi.logout(); + } catch {} + clearAuth(); + onLogout?.(); // 触发 App 重渲染 + navigate('/login', { replace: true }); + }; + + const userMenu = { + items: [ + { + key: 'logout', + label: '退出登录', + icon: , + onClick: handleLogout, + }, + ], + }; + + return ( + + +
+ {collapsed ? '鱼' : '斗鱼登录后台'} +
+ navigate(key)} + /> + + +
+ + + } /> + {user.username} + ({ROLE_LABELS[user.role] || user.role}) + + +
+ + + +
+ + ); +} diff --git a/web/frontend/src/main.tsx b/web/frontend/src/main.tsx new file mode 100644 index 0000000..dfacde0 --- /dev/null +++ b/web/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App'; +import './index.css'; + +createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/web/frontend/src/pages/AccountsPage.tsx b/web/frontend/src/pages/AccountsPage.tsx new file mode 100644 index 0000000..65bf52e --- /dev/null +++ b/web/frontend/src/pages/AccountsPage.tsx @@ -0,0 +1,174 @@ +import { useEffect, useState } from 'react'; +import { + Table, Button, Modal, Input, Select, message, Popconfirm, Typography, +} from 'antd'; +import { ImportOutlined, DeleteOutlined } from '@ant-design/icons'; +import { accountApi, userApi } from '../api/modules'; +import { getUser, hasPerm } from '../store/auth'; + +const { TextArea } = Input; +const { Text } = Typography; + +export default function AccountsPage() { + const [accounts, setAccounts] = useState([]); + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(false); + const [importOpen, setImportOpen] = useState(false); + const [importText, setImportText] = useState(''); + const [importing, setImporting] = useState(false); + const user = getUser(); + + const canViewAll = hasPerm(user, 'account:view_all'); + const canImport = hasPerm(user, 'account:import'); + const canAssign = hasPerm(user, 'account:assign'); + const canDelete = hasPerm(user, 'account:delete'); + + const loadAccounts = async () => { + setLoading(true); + try { + const data = await accountApi.list(); + setAccounts(data); + } catch (e: any) { + message.error(e.message); + } finally { + setLoading(false); + } + }; + + const loadUsers = async () => { + try { + const data = await userApi.list(); + setUsers(data.filter((u: any) => u.role === 'support')); + } catch {} + }; + + useEffect(() => { + loadAccounts(); + if (canAssign) loadUsers(); + }, []); + + const handleImport = async () => { + if (!importText.trim()) { + message.warning('请输入账号数据'); + return; + } + setImporting(true); + try { + const result = await accountApi.import(importText); + message.success(result.message); + setImportOpen(false); + setImportText(''); + loadAccounts(); + } catch (e: any) { + message.error(e.message); + } finally { + setImporting(false); + } + }; + + const handleAssign = async (accountId: number, assignedTo: number | null) => { + try { + await accountApi.assign(accountId, assignedTo); + message.success('已分配'); + loadAccounts(); + } catch (e: any) { + message.error(e.message); + } + }; + + const handleDelete = async (id: number) => { + try { + await accountApi.delete(id); + message.success('已删除'); + loadAccounts(); + } catch (e: any) { + message.error(e.message); + } + }; + + const columns: any[] = [ + { title: 'ID', dataIndex: 'id', width: 60 }, + { title: '用户名', dataIndex: 'username' }, + ]; + + if (canViewAll) { + columns.push( + { title: '密码', dataIndex: 'password', width: 120 }, + { title: '邮箱', dataIndex: 'email' }, + { title: '邮箱密码', dataIndex: 'email_password', width: 120 }, + ); + } + + columns.push({ + title: '分配给', + dataIndex: 'assigned_username', + render: (_: any, record: any) => { + if (canAssign) { + return ( +