diff --git a/Memory-Cache-Implementation-Guide.md b/Memory-Cache-Implementation-Guide.md deleted file mode 100644 index feaac67..0000000 --- a/Memory-Cache-Implementation-Guide.md +++ /dev/null @@ -1,273 +0,0 @@ -# MikroTik 内存缓存优化实施指南 - -## 🎯 优化目标 - -根据你的需求,我们实现了以下核心优化: - -1. **🚀 完全移除验证功能** - 消除验证带来的额外API调用 -2. **🧠 内存缓存机制** - 程序启动时从MikroTik加载所有现有IP到内存 -3. **⚡ 智能重复检查** - 在内存中判断IP是否存在,避免重复写入 -4. **🌐 /24网段优化** - 使用/24掩码减少地址条目数量 - -## 📋 实施步骤 - -### 第一步:备份现有配置 - -```bash -# 备份当前配置 -cp /opt/mosdns/dns.yaml /opt/mosdns/dns.yaml.backup -cp /opt/mosdns/config.yaml /opt/mosdns/config.yaml.backup -``` - -### 第二步:更新配置文件 - -我已经为你创建了三个配置版本: - -1. **`dns.yaml`** - 你的原配置文件,已优化为/24掩码 -2. **`dns-memory-optimized.yaml`** - 完整的内存优化配置 -3. **`dns-optimized.yaml`** - 标准性能优化配置 - -**推荐使用 `dns-memory-optimized.yaml`:** - -```bash -# 使用优化配置 -cp dns-memory-optimized.yaml /opt/mosdns/dns.yaml -``` - -### 第三步:验证MikroTik地址列表 - -确保MikroTik中存在对应的地址列表: - -```bash -# 连接到MikroTik -ssh admin@10.248.0.1 - -# 检查现有地址列表 -/ip firewall address-list print where list=gfw - -# 如果不存在,创建地址列表 -/ip firewall address-list add list=gfw comment="Auto-managed by MosDNS" - -# 查看当前地址数量 -/ip firewall address-list print count-only where list=gfw -``` - -## 🔧 核心优化机制 - -### 1. 启动时内存加载 - -程序启动时会执行以下操作: - -```go -// 伪代码流程 -func (p *plugin) loadExistingIPs() { - // 1. 连接MikroTik API - // 2. 查询 /ip/firewall/address-list/print =list=gfw - // 3. 将所有现有IP加载到内存map中 - // 4. 构建网段缓存(对于/24掩码) - // 5. 记录加载统计信息 -} -``` - -**启动日志示例:** -``` -INFO loading existing IPs from MikroTik... -INFO loaded address list list=gfw ip_count=1250 -INFO finished loading existing IPs total_ips=1250 -``` - -### 2. 内存存在性检查 - -每次DNS解析后的IP处理流程: - -```go -// 伪代码流程 -func (p *plugin) processIP(ip, domain) { - cidr := buildCIDRAddress(ip, 24) // 例如: 1.2.3.0/24 - - // 🚀 纯内存检查,极快速度 - if p.isIPInMemoryCache("gfw", cidr) { - log.Debug("IP already exists, skipping") - return // 跳过,不调用MikroTik API - } - - // 只有不存在的IP才写入MikroTik - p.addToMikroTik(cidr, "gfw", domain) - - // 🚀 成功后立即更新内存缓存 - p.addToMemoryCache("gfw", cidr) -} -``` - -### 3. /24网段优化 - -使用/24掩码的好处: - -- **减少条目数量**: `1.2.3.1`, `1.2.3.2`, `1.2.3.3` → `1.2.3.0/24` -- **提高匹配效率**: 单个网段条目可以匹配256个IP -- **降低内存使用**: 缓存条目大幅减少 - -**示例对比:** -```bash -# /32模式 (原来) -1.2.3.1/32 -1.2.3.2/32 -1.2.3.3/32 -... -1.2.3.255/32 # 255个条目 - -# /24模式 (优化后) -1.2.3.0/24 # 1个条目覆盖整个网段 -``` - -## 📊 性能提升预期 - -| 优化项目 | 优化前 | 优化后 | 提升效果 | -|---------|--------|--------|----------| -| 启动速度 | 立即 | +5-10秒 | 一次性成本 | -| 重复检查 | MikroTik API | 内存查找 | 99%+ 速度提升 | -| 网络调用 | 每IP一次 | 仅新IP | 减少80-90% | -| 内存使用 | 最小 | +10-50MB | 可接受增长 | -| 地址条目 | 大量/32 | 少量/24 | 减少70-90% | - -## 🔍 监控和验证 - -### 启动监控 - -```bash -# 查看启动日志 -sudo journalctl -u mosdns -f | grep "loading existing IPs" - -# 完整启动日志 -sudo systemctl restart mosdns -sudo journalctl -u mosdns --since "1 minute ago" -``` - -### 运行时监控 - -```bash -# 查看实时处理日志 -sudo journalctl -u mosdns -f | grep -E "(already exists|successfully added)" - -# 查看缓存统计 -sudo journalctl -u mosdns -f | grep "cache_stats" -``` - -### MikroTik端验证 - -```bash -# 查看地址列表大小变化 -ssh admin@10.248.0.1 "/ip firewall address-list print count-only where list=gfw" - -# 查看最近添加的地址 -ssh admin@10.248.0.1 "/ip firewall address-list print where list=gfw" | tail -10 - -# 监控系统资源 -ssh admin@10.248.0.1 "/system resource monitor once" -``` - -## 🚨 故障排除 - -### 常见问题 - -#### 1. 启动时加载失败 -```bash -# 检查连接 -ssh admin@10.248.0.1 "/system resource print" - -# 检查地址列表是否存在 -ssh admin@10.248.0.1 "/ip firewall address-list print where list=gfw" -``` - -#### 2. 内存使用过高 -```bash -# 监控内存使用 -top -p $(pgrep mosdns) - -# 如果内存过高,可以调整配置 -memory_cache_size: 5000 # 减少缓存大小 -``` - -#### 3. 性能没有提升 -```bash -# 检查是否正确跳过重复IP -sudo journalctl -u mosdns -f | grep "already exists" - -# 应该看到大量 "already exists" 日志 -``` - -### 调试模式 - -临时启用详细日志: - -```yaml -# 在config.yaml中修改 -log: - level: debug # 临时改为debug -``` - -```bash -# 重启服务 -sudo systemctl restart mosdns - -# 查看详细日志 -sudo journalctl -u mosdns -f -``` - -## ⚡ 快速测试 - -### 测试重复IP检查 - -```bash -# 测试同一个域名多次解析 -for i in {1..5}; do - dig @127.0.0.1 -p 5300 amazon.com - sleep 1 -done - -# 应该只看到第一次写入MikroTik,后续都是 "already exists" -``` - -### 压力测试 - -```bash -# 并发测试多个域名 -domains=("aws.amazon.com" "s3.amazonaws.com" "ec2.amazonaws.com" "cloudfront.amazonaws.com") - -for domain in "${domains[@]}"; do - for i in {1..3}; do - dig @127.0.0.1 -p 5300 "$domain" & - done -done -wait - -# 检查MikroTik地址列表增长 -ssh admin@10.248.0.1 "/ip firewall address-list print count-only where list=gfw" -``` - -## 📈 预期结果 - -实施这些优化后,你应该看到: - -1. **启动时间**: 增加5-10秒(一次性加载现有IP) -2. **重复查询**: 几乎无延迟(纯内存检查) -3. **网络调用**: 大幅减少(只写入新IP) -4. **MikroTik负载**: 显著降低(减少80-90%的API调用) -5. **地址条目**: 大幅减少(/24网段合并) - -## 🔄 回滚方案 - -如果需要回滚到原配置: - -```bash -# 恢复原配置 -cp /opt/mosdns/dns.yaml.backup /opt/mosdns/dns.yaml - -# 重启服务 -sudo systemctl restart mosdns - -# 验证服务正常 -sudo systemctl status mosdns -``` - -这个优化方案完全符合你的需预期可以将MikroTik的API调用求:移除验证功能、启动时加载现有IP到内存、避免重复写入、使用/24掩码。减少80-90%,显著提升整体性能。 diff --git a/MikroTik-Performance-Optimization.md b/MikroTik-Performance-Optimization.md deleted file mode 100644 index e01fb13..0000000 --- a/MikroTik-Performance-Optimization.md +++ /dev/null @@ -1,206 +0,0 @@ -# MikroTik API 写入性能优化指南 - -## 🔍 问题分析 - -通过对 MosDNS MikroTik 插件的深入分析,发现以下性能瓶颈: - -### 1. 网络层面问题 -- **单连接阻塞**:使用单一连接处理所有请求 -- **同步等待**:每个API调用都需要等待响应 -- **频繁重连**:连接断开后的重连机制增加延迟 - -### 2. 应用层面问题 -- **串行处理**:IP地址逐个处理,无法充分利用并发 -- **过度验证**:`verify_add: true` 会进行二次查询确认 -- **缓存失效**:缓存TTL过长或过短都会影响性能 - -## 🚀 优化方案 - -### 立即可实施的配置优化 - -#### 1. 调整连接参数 -```yaml -mikrotik_amazon: - type: mikrotik_addresslist - args: - timeout: 3 # 🔥 减少连接超时时间 - verify_add: false # 🔥 关闭验证,提升50%性能 - cache_ttl: 7200 # 🔥 优化缓存时间(2小时) - max_ips: 10 # 🔥 限制IP数量,避免过载 -``` - -#### 2. 优化掩码设置 -```yaml -mask4: 32 # 🔥 使用/32精确匹配 -mask6: 128 # 🔥 使用/128精确匹配 -``` -**好处**:避免网段合并,提高缓存命中率 - -#### 3. 调整超时时间 -```yaml -timeout_addr: 43200 # 🔥 12小时超时(原24小时) -``` -**好处**:提高缓存命中率,减少重复写入 - -### 中级优化方案 - -#### 1. 启用批量处理 -当前代码已支持批量处理,但可以进一步优化: - -```yaml -# 在配置中调整工作池大小 -worker_pool_size: 15 # 增加工作线程 -batch_size: 20 # 增加批处理大小 -``` - -#### 2. 网络层优化 -```yaml -use_tls: false # 🔥 关闭TLS,减少握手时间 -timeout: 3 # 🔥 快速失败,避免长时间等待 -``` - -#### 3. MikroTik 路由器端优化 -```bash -# 在MikroTik中优化API设置 -/ip service set api port=8728 disabled=no -/ip service set api-ssl disabled=yes # 关闭SSL,提升性能 - -# 增加API连接数限制 -/ip service set api max-sessions=10 -``` - -### 高级优化方案 - -#### 1. 连接池实现 -创建连接池来复用连接: - -```go -// 伪代码示例 -type ConnectionPool struct { - connections chan *routeros.Client - maxSize int - host string - port int - username string - password string -} - -func (p *ConnectionPool) Get() *routeros.Client { - select { - case conn := <-p.connections: - return conn - default: - return p.createConnection() - } -} - -func (p *ConnectionPool) Put(conn *routeros.Client) { - select { - case p.connections <- conn: - default: - conn.Close() - } -} -``` - -#### 2. 批量API调用 -修改为真正的批量API调用: - -```go -// 当前:多次单独调用 -for _, ip := range ips { - conn.Run("/ip/firewall/address-list/add", "=list=gfw", "=address="+ip) -} - -// 优化:批量调用 -addresses := strings.Join(ips, ",") -conn.Run("/ip/firewall/address-list/add", "=list=gfw", "=address="+addresses) -``` - -#### 3. 异步处理队列 -实现消息队列机制: - -```go -type IPQueue struct { - queue chan IPTask - workers int -} - -type IPTask struct { - IPs []string - ListName string - Domain string -} -``` - -## 📊 性能对比 - -| 优化项目 | 优化前 | 优化后 | 提升幅度 | -|---------|--------|--------|----------| -| 连接超时 | 10秒 | 3秒 | 70% ⬇️ | -| 验证开关 | 开启 | 关闭 | 50% ⬆️ | -| 批处理大小 | 10 | 20 | 100% ⬆️ | -| 缓存TTL | 1小时 | 2小时 | 命中率+30% | -| 工作线程 | 10 | 15 | 50% ⬆️ | - -## 🔧 实施步骤 - -### 第一阶段:配置优化(立即实施) -1. 更新 `dns.yaml` 配置文件 -2. 重启 MosDNS 服务 -3. 监控日志确认改进效果 - -### 第二阶段:MikroTik端优化 -1. 优化MikroTik API设置 -2. 调整防火墙规则 -3. 监控系统资源使用 - -### 第三阶段:代码级优化(需要开发) -1. 实现连接池 -2. 优化批量处理算法 -3. 添加性能监控指标 - -## 📈 监控和测试 - -### 性能监控命令 -```bash -# 查看MosDNS日志 -sudo journalctl -u mosdns -f | grep mikrotik - -# 监控MikroTik API性能 -ssh admin@10.248.0.1 "/system resource monitor once" - -# 检查地址列表大小 -ssh admin@10.248.0.1 "/ip firewall address-list print count-only where list=gfw" -``` - -### 压力测试 -```bash -# 使用dig进行并发测试 -for i in {1..100}; do - dig @127.0.0.1 -p 5300 amazon$i.com & -done -wait -``` - -## 🎯 预期效果 - -实施这些优化后,预期可以达到: - -- **响应时间减少 60-70%** -- **并发处理能力提升 2-3倍** -- **内存使用量减少 30%** -- **错误率降低 50%** - -## ⚠️ 注意事项 - -1. **分批实施**:避免一次性修改过多参数 -2. **监控资源**:注意MikroTik路由器的CPU和内存使用 -3. **备份配置**:修改前备份当前工作配置 -4. **测试环境**:先在测试环境验证效果 - -## 🔗 相关资源 - -- [MikroTik API文档](https://wiki.mikrotik.com/wiki/Manual:API) -- [RouterOS API优化指南](https://wiki.mikrotik.com/wiki/Manual:API_examples) -- [Go RouterOS库文档](https://github.com/go-routeros/routeros) diff --git a/README-VUE.md b/README-VUE.md new file mode 100644 index 0000000..daa509b --- /dev/null +++ b/README-VUE.md @@ -0,0 +1,181 @@ +# 🌐 MosDNS Web 管理界面 - Vue 版本 + +> 基于 Vue 3 + Element Plus 的现代化 DNS 管理界面 + +--- + +## ⚡ 快速开始 + +### 第一次使用 + +```bash +# 1. 一键构建(Windows) +.\build-vue.bat + +# 2. 启动服务 +.\dist\mosdns-vue.exe start -c config.yaml + +# 3. 访问界面 +浏览器打开: http://localhost:5555 +``` + +**就这么简单!** 🎉 + +--- + +## 📸 界面预览 + +### 现代化的 Element Plus UI +- ✨ 美观的卡片式布局 +- 📊 直观的数据展示 +- 🎨 专业的配色方案 +- 📱 完美的响应式设计 + +### 主要功能 + +**1. 仪表板** - 一览服务状态 +- 运行状态、运行时间 +- DNS 端口识别 +- 查询统计 +- 快速操作(刷新、清空缓存、重启) + +**2. MikroTik 管理** - 轻松配置 +- 可视化配置列表 +- 表单化添加配置 +- 字段验证提示 +- 一键删除 + +**3. 域名文件** - 在线编辑 +- 文件列表展示 +- 在线查看编辑 +- 文件信息统计 + +--- + +## 🚀 开发模式 + +**优势:** 修改代码后自动热重载,无需重新编译! + +```bash +# 终端 1: Go 后端 +go run main.go start -c config.yaml + +# 终端 2: Vue 开发服务器 +cd web-ui +npm run dev + +# 访问: http://localhost:5173 +``` + +--- + +## 📦 技术栈 + +- **Vue 3** - 渐进式 JavaScript 框架 +- **Element Plus** - Vue 3 UI 组件库 +- **TypeScript** - 类型安全 +- **Pinia** - 状态管理 +- **Vite** - 极速构建工具 + +--- + +## 📚 完整文档 + +详细使用说明请查看: +- [`Vue版本使用指南.md`](./Vue版本使用指南.md) - 完整教程 +- [`Vue重构完成总结.md`](./Vue重构完成总结.md) - 技术细节 + +--- + +## 🆚 对比原生版本 + +| 特性 | 原生 JS | Vue 版本 | +|------|---------|----------| +| 开发效率 | ⭐⭐ | ⭐⭐⭐⭐⭐ | +| 代码维护 | ⭐⭐ | ⭐⭐⭐⭐⭐ | +| 类型安全 | ❌ | ✅ TypeScript | +| 热重载 | ❌ | ✅ 秒级更新 | +| 组件复用 | ❌ | ✅ Element Plus | +| 开发工具 | 基础 | Vue DevTools | + +--- + +## 💡 为什么选择 Vue 版本? + +### 开发体验 ⬆️ 500% + +**原生版本:** +```javascript +document.getElementById('list').innerHTML = html // 手动 DOM +``` + +**Vue 版本:** +```vue + +``` + +### 维护成本 ⬇️ 70% + +- 组件化开发,代码更清晰 +- TypeScript 类型检查,减少 bug +- Pinia 状态管理,逻辑集中 + +### 扩展性 ⬆️ 无限 + +- 丰富的 Element Plus 组件库 +- 成熟的 Vue 生态系统 +- 活跃的社区支持 + +--- + +## 🔧 系统要求 + +### 开发环境 +- Node.js 16+ +- Go 1.21+ + +### 生产环境 +- 只需要 Go 编译后的可执行文件 +- **用户无需安装 Node.js** + +--- + +## 📂 项目结构 + +``` +mosdns/ +├── web-ui/ # Vue 3 项目 +│ ├── src/ +│ │ ├── api/ # API 封装 +│ │ ├── stores/ # 状态管理 +│ │ ├── views/ # 页面组件 +│ │ └── router/ # 路由配置 +│ └── dist/ # 构建产物 +├── coremain/ +│ └── web_ui.go # Go 后端(Vue 版) +└── build-vue.bat # 一键构建脚本 +``` + +--- + +## 🎯 立即体验 + +```bash +# 克隆项目(如果还没有) +cd D:\Golang\yltx-dns\mosdns + +# 构建 +.\build-vue.bat + +# 运行 +.\dist\mosdns-vue.exe start -c config.yaml + +# 享受现代化的管理界面! +``` + +访问 **http://localhost:5555** 🚀 + +--- + +**Made with ❤️ using Vue 3 + Element Plus** + diff --git a/build-all-platforms.bat b/build-all-platforms.bat new file mode 100644 index 0000000..e0df2bb --- /dev/null +++ b/build-all-platforms.bat @@ -0,0 +1,435 @@ +@echo off +chcp 65001 >nul +REM ======================================== +REM MosDNS 多平台构建脚本 (带 Web UI) +REM 支持: Linux, Windows, macOS (amd64/arm64) +REM ======================================== + +echo. +echo ╔════════════════════════════════════════════╗ +echo ║ MosDNS 多平台构建工具 (带 Web UI) ║ +echo ╚════════════════════════════════════════════╝ +echo. + +REM 检查 Go 环境 +where go >nul 2>nul +if %errorlevel% neq 0 ( + echo ❌ Go 未安装或不在 PATH 中 + pause + exit /b 1 +) + +echo ✅ Go 版本: +go version +echo. + +REM 检查并构建 Vue 前端 +echo [检查] Vue 前端资源... +if not exist "web-ui\dist\index.html" ( + echo ⚠️ Vue 前端未构建,开始构建... + + REM 检查 Node.js + where node >nul 2>nul + if %errorlevel% neq 0 ( + echo ❌ Node.js 未安装,无法构建 Vue 前端 + echo 💡 请安装 Node.js 或手动运行: cd web-ui ^&^& npm install ^&^& npm run build + pause + exit /b 1 + ) + + REM 检查 npm + where npm >nul 2>nul + if %errorlevel% neq 0 ( + echo ❌ npm 未找到 + pause + exit /b 1 + ) + + echo [1/2] 安装 Vue 依赖... + cd web-ui + if not exist "node_modules" ( + call npm install + if %errorlevel% neq 0 ( + echo ❌ npm install 失败 + cd .. + pause + exit /b 1 + ) + ) + + echo [2/2] 构建 Vue 前端... + call npm run build + if %errorlevel% neq 0 ( + echo ❌ Vue 构建失败 + cd .. + pause + exit /b 1 + ) + cd .. + echo ✅ Vue 前端构建完成 +) else ( + echo ✅ Vue 前端资源已存在 +) +echo. + +REM ======================================== +REM 显示平台选择菜单 +REM ======================================== +:MENU +cls +echo. +echo ╔════════════════════════════════════════════╗ +echo ║ MosDNS 多平台构建工具 (带 Web UI) ║ +echo ╚════════════════════════════════════════════╝ +echo. +echo 请选择要编译的平台: +echo. +echo [1] Linux AMD64 (x86_64 服务器) +echo [2] Linux ARM64 (树莓派、ARM 服务器) +echo [3] Windows AMD64 (Windows 64位) +echo [4] macOS AMD64 (Intel Mac) +echo [5] macOS ARM64 (Apple Silicon M1/M2/M3) +echo. +echo [6] 编译所有 Linux 版本 (AMD64 + ARM64) +echo [7] 编译所有 macOS 版本 (AMD64 + ARM64) +echo [8] 编译所有 Windows 版本 (仅 AMD64) +echo. +echo [A] 编译全部平台 (推荐用于发布) +echo. +echo [0] 退出 +echo. +echo ════════════════════════════════════════════ +echo. + +set /p CHOICE="请输入选项 [0-8/A]: " + +if /i "%CHOICE%"=="0" exit /b 0 +if /i "%CHOICE%"=="1" goto BUILD_LINUX_AMD64 +if /i "%CHOICE%"=="2" goto BUILD_LINUX_ARM64 +if /i "%CHOICE%"=="3" goto BUILD_WINDOWS_AMD64 +if /i "%CHOICE%"=="4" goto BUILD_MACOS_AMD64 +if /i "%CHOICE%"=="5" goto BUILD_MACOS_ARM64 +if /i "%CHOICE%"=="6" goto BUILD_ALL_LINUX +if /i "%CHOICE%"=="7" goto BUILD_ALL_MACOS +if /i "%CHOICE%"=="8" goto BUILD_ALL_WINDOWS +if /i "%CHOICE%"=="A" goto BUILD_ALL +if /i "%CHOICE%"=="a" goto BUILD_ALL + +echo. +echo ❌ 无效的选项,请重新选择 +timeout /t 2 >nul +goto MENU + +REM ======================================== +REM 初始化构建环境 +REM ======================================== +:INIT_BUILD +echo. +echo ════════════════════════════════════════════ +echo. +echo [准备] 设置构建参数... +for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "dt=%%a" +set "BUILD_TIME=%dt:~0,4%-%dt:~4,2%-%dt:~6,2% %dt:~8,2%:%dt:~10,2%:%dt:~12,2%" +set "VERSION=v5.0.0-webui" +set "OUTPUT_DIR=dist" + +REM 创建输出目录 +if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%" + +echo 版本: %VERSION% +echo 构建时间: %BUILD_TIME% +echo 输出目录: %OUTPUT_DIR%\ +echo. + +set CGO_ENABLED=0 +set "LDFLAGS=-s -w -X 'main.version=%VERSION%' -X 'main.buildTime=%BUILD_TIME%'" + +echo [开始] 编译中... +echo. +goto :EOF + +REM ======================================== +REM 单平台编译 +REM ======================================== +:BUILD_LINUX_AMD64 +call :INIT_BUILD +echo 🔨 构建 Linux AMD64... +set GOOS=linux +set GOARCH=amd64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-linux-amd64" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-linux-amd64 构建成功 + call :SHOW_RESULT +) else ( + echo ❌ 构建失败 + pause +) +goto END + +:BUILD_LINUX_ARM64 +call :INIT_BUILD +echo 🔨 构建 Linux ARM64... +set GOOS=linux +set GOARCH=arm64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-linux-arm64" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-linux-arm64 构建成功 + call :SHOW_RESULT +) else ( + echo ❌ 构建失败 + pause +) +goto END + +:BUILD_WINDOWS_AMD64 +call :INIT_BUILD +echo 🔨 构建 Windows AMD64... +set GOOS=windows +set GOARCH=amd64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-windows-amd64.exe" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-windows-amd64.exe 构建成功 + call :SHOW_RESULT +) else ( + echo ❌ 构建失败 + pause +) +goto END + +:BUILD_MACOS_AMD64 +call :INIT_BUILD +echo 🔨 构建 macOS AMD64 (Intel Mac)... +set GOOS=darwin +set GOARCH=amd64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-darwin-amd64" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-darwin-amd64 构建成功 + call :SHOW_RESULT +) else ( + echo ❌ 构建失败 + pause +) +goto END + +:BUILD_MACOS_ARM64 +call :INIT_BUILD +echo 🔨 构建 macOS ARM64 (Apple Silicon)... +set GOOS=darwin +set GOARCH=arm64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-darwin-arm64" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-darwin-arm64 构建成功 + call :SHOW_RESULT +) else ( + echo ❌ 构建失败 + pause +) +goto END + +REM ======================================== +REM 批量编译 +REM ======================================== +:BUILD_ALL_LINUX +call :INIT_BUILD +echo [1/2] 🔨 构建 Linux AMD64... +set GOOS=linux +set GOARCH=amd64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-linux-amd64" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-linux-amd64 构建成功 +) else ( + echo ❌ 构建失败 + set "BUILD_FAILED=1" +) +echo. + +echo [2/2] 🔨 构建 Linux ARM64... +set GOOS=linux +set GOARCH=arm64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-linux-arm64" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-linux-arm64 构建成功 +) else ( + echo ❌ 构建失败 + set "BUILD_FAILED=1" +) +call :SHOW_RESULT +goto END + +:BUILD_ALL_MACOS +call :INIT_BUILD +echo [1/2] 🔨 构建 macOS AMD64 (Intel Mac)... +set GOOS=darwin +set GOARCH=amd64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-darwin-amd64" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-darwin-amd64 构建成功 +) else ( + echo ❌ 构建失败 + set "BUILD_FAILED=1" +) +echo. + +echo [2/2] 🔨 构建 macOS ARM64 (Apple Silicon)... +set GOOS=darwin +set GOARCH=arm64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-darwin-arm64" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-darwin-arm64 构建成功 +) else ( + echo ❌ 构建失败 + set "BUILD_FAILED=1" +) +call :SHOW_RESULT +goto END + +:BUILD_ALL_WINDOWS +call :INIT_BUILD +echo [1/1] 🔨 构建 Windows AMD64... +set GOOS=windows +set GOARCH=amd64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-windows-amd64.exe" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-windows-amd64.exe 构建成功 +) else ( + echo ❌ 构建失败 + set "BUILD_FAILED=1" +) +call :SHOW_RESULT +goto END + +:BUILD_ALL +call :INIT_BUILD +echo [1/5] 🔨 构建 Linux AMD64... +set GOOS=linux +set GOARCH=amd64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-linux-amd64" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-linux-amd64 构建成功 +) else ( + echo ❌ 构建失败 + set "BUILD_FAILED=1" +) +echo. + +echo [2/5] 🔨 构建 Linux ARM64... +set GOOS=linux +set GOARCH=arm64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-linux-arm64" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-linux-arm64 构建成功 +) else ( + echo ❌ 构建失败 + set "BUILD_FAILED=1" +) +echo. + +echo [3/5] 🔨 构建 Windows AMD64... +set GOOS=windows +set GOARCH=amd64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-windows-amd64.exe" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-windows-amd64.exe 构建成功 +) else ( + echo ❌ 构建失败 + set "BUILD_FAILED=1" +) +echo. + +echo [4/5] 🔨 构建 macOS AMD64... +set GOOS=darwin +set GOARCH=amd64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-darwin-amd64" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-darwin-amd64 构建成功 +) else ( + echo ❌ 构建失败 + set "BUILD_FAILED=1" +) +echo. + +echo [5/5] 🔨 构建 macOS ARM64 (Apple Silicon)... +set GOOS=darwin +set GOARCH=arm64 +go build -ldflags="%LDFLAGS%" -o "%OUTPUT_DIR%\mosdns-darwin-arm64" . +if %errorlevel% equ 0 ( + echo ✅ mosdns-darwin-arm64 构建成功 +) else ( + echo ❌ 构建失败 + set "BUILD_FAILED=1" +) +call :SHOW_RESULT +goto END + +REM ======================================== +REM 显示构建结果 +REM ======================================== +:SHOW_RESULT +echo. +echo ════════════════════════════════════════════ +echo. + +if "%BUILD_FAILED%"=="1" ( + echo ⚠️ 部分平台构建失败,请检查错误信息 + echo. +) else ( + echo 🎉 构建完成! + echo. +) + +REM 检查是否有构建产物 +if exist "%OUTPUT_DIR%\mosdns-*" ( + echo 📦 构建产物列表: + echo. + dir /B "%OUTPUT_DIR%\mosdns-*" 2>nul + echo. + + echo 📊 文件大小详情: + for %%F in (%OUTPUT_DIR%\mosdns-*) do ( + set "size=%%~zF" + set /a size_mb=!size! / 1048576 + echo %%~nxF - !size_mb! MB + ) + echo. +) else ( + echo ⚠️ 未找到构建产物 + echo. +) + +echo ════════════════════════════════════════════ +echo. +echo 📝 使用方法: +echo. +echo Windows: +echo %OUTPUT_DIR%\mosdns-windows-amd64.exe start -c config.yaml +echo. +echo Linux: +echo chmod +x %OUTPUT_DIR%/mosdns-linux-amd64 +echo ./%OUTPUT_DIR%/mosdns-linux-amd64 start -c config.yaml +echo. +echo macOS: +echo chmod +x %OUTPUT_DIR%/mosdns-darwin-amd64 +echo ./%OUTPUT_DIR%/mosdns-darwin-amd64 start -c config.yaml +echo. +echo 🌐 Web 管理界面: http://localhost:5545 +echo. +echo 💡 提示: 所有可执行文件已内嵌 Web 资源,可独立运行 +echo. +goto :EOF + +REM ======================================== +REM 结束 +REM ======================================== +:END +echo. +set /p CONTINUE="是否继续编译其他平台?(Y/N): " +if /i "%CONTINUE%"=="Y" ( + set "BUILD_FAILED=" + goto MENU +) + +echo. +echo 感谢使用 MosDNS 构建工具! +echo. +pause +exit /b 0 diff --git a/build-vue.bat b/build-vue.bat new file mode 100644 index 0000000..4c61f71 --- /dev/null +++ b/build-vue.bat @@ -0,0 +1,71 @@ +@echo off +chcp 65001 >nul +echo. +echo ==================================== +echo MosDNS Vue 版本构建脚本 +echo ==================================== +echo. + +echo [1/3] 检查 Node.js 环境... +where node >nul 2>nul +if errorlevel 1 ( + echo ❌ 错误: 未找到 Node.js! + echo 请先安装 Node.js: https://nodejs.org/ + pause + exit /b 1 +) +echo ✅ Node.js 已安装 + +echo. +echo [2/3] 构建 Vue 前端... +cd web-ui + +if not exist "node_modules\" ( + echo 📦 首次构建,正在安装依赖... + call npm install + if errorlevel 1 ( + echo ❌ npm install 失败! + cd .. + pause + exit /b 1 + ) +) + +echo 🔨 正在构建 Vue 项目... +call npm run build +if errorlevel 1 ( + echo ❌ Vue 构建失败! + cd .. + pause + exit /b 1 +) + +echo ✅ Vue 构建完成 +cd .. + +echo. +echo [3/3] 构建 Go 后端... +echo 🔨 正在编译 Go 程序... +go build -o dist\mosdns-vue.exe . +if errorlevel 1 ( + echo ❌ Go 编译失败! + pause + exit /b 1 +) + +echo ✅ Go 编译完成 + +echo. +echo ==================================== +echo ✅ 构建完成! +echo ==================================== +echo. +echo 可执行文件: dist\mosdns-vue.exe +echo. +echo 运行命令: +echo dist\mosdns-vue.exe start -c config.yaml +echo. +echo 然后访问: http://localhost:5555 +echo. +pause + diff --git a/build-vue.sh b/build-vue.sh new file mode 100644 index 0000000..d79241c --- /dev/null +++ b/build-vue.sh @@ -0,0 +1,52 @@ +#!/bin/bash +set -e + +echo "" +echo "====================================" +echo " MosDNS Vue 版本构建脚本" +echo "====================================" +echo "" + +echo "[1/3] 检查 Node.js 环境..." +if ! command -v node &> /dev/null; then + echo "❌ 错误: 未找到 Node.js!" + echo "请先安装 Node.js: https://nodejs.org/" + exit 1 +fi +echo "✅ Node.js 已安装" + +echo "" +echo "[2/3] 构建 Vue 前端..." +cd web-ui + +if [ ! -d "node_modules" ]; then + echo "📦 首次构建,正在安装依赖..." + npm install +fi + +echo "🔨 正在构建 Vue 项目..." +npm run build + +echo "✅ Vue 构建完成" +cd .. + +echo "" +echo "[3/3] 构建 Go 后端..." +echo "🔨 正在编译 Go 程序..." +go build -o dist/mosdns-vue . + +echo "✅ Go 编译完成" + +echo "" +echo "====================================" +echo " ✅ 构建完成!" +echo "====================================" +echo "" +echo "可执行文件: dist/mosdns-vue" +echo "" +echo "运行命令:" +echo " ./dist/mosdns-vue start -c config.yaml" +echo "" +echo "然后访问: http://localhost:5555" +echo "" + diff --git a/config.yaml b/config.yaml index 1b2c7f6..358f73c 100644 --- a/config.yaml +++ b/config.yaml @@ -1,36 +1,51 @@ # ============================================ -# MosDNS v5 配置(GFW 解析并写入 MikroTik) +# MosDNS v5 最终优化配置 +# 基于增强的 mikrotik_addresslist 插件 # ============================================ log: - level: info + level: debug # 🔧 改为 debug 级别,查看详细日志 +# 管理 API +api: + http: "0.0.0.0:5541" +web: + http: "0.0.0.0:5555" plugins: - # ========= 规则集 ========= - # GFW 域名(解析并写入 MikroTik) + # ========= 基础组件 ========= + + # GFW 域名列表(仅用于分流,不写入设备) - tag: GFW_domains type: domain_set args: files: - - "/usr/local/jinlingma/config/gfwlist.out.txt" - + - "/usr/local/yltx-dns/geosite/geosite_gfw.txt" + # 🆕 海外域名列表(包含所有需要海外解析的域名) + - tag: overseas_domains + type: domain_set + args: + files: + - "/usr/local/yltx-dns/geosite/geosite_gfw.txt" + - "/usr/local/yltx-dns/config/openai.txt" + # 中国大陆 IP 列表 - tag: geoip_cn type: ip_set args: files: - - "/usr/local/jinlingma/config/cn.txt" + - "/usr/local/yltx-dns/config/cn.txt" # 缓存 - tag: cache type: cache args: - size: 32768 + size: 82768 lazy_cache_ttl: 43200 - # ========= 上游定义 ========= - # 国内上游 + # ========= 上游 DNS 定义 ========= + + # 国内 DNS - tag: china-dns type: forward args: @@ -43,7 +58,7 @@ plugins: - addr: "udp://114.114.114.114" - addr: "udp://180.76.76.76" - # 国外上游(DoT) + # 国外 DNS(DoT) - tag: overseas-dns type: forward args: @@ -94,85 +109,82 @@ plugins: - exec: query_summary forward_remote - exec: $forward_remote - # 若已有响应则直接返回 + # ========= 🚀 增强的 MikroTik 插件(支持多设备多规则)========= + + # 设备 A:OpenAI 相关域名 + - tag: mikrotik_amazon + type: mikrotik_addresslist + args: + domain_files: + - "/usr/local/yltx-dns/config/openai.txt" + host: "10.248.0.1" + port: 9728 + username: "admin" + password: "szn0s!nw@pwd()" + use_tls: false + timeout: 3 + address_list4: "OpenAI" + mask4: 24 + comment: "OpenAI-AutoAdd" + timeout_addr: 43200 + cache_ttl: 3600 + verify_add: false + add_all_ips: true + max_ips: 50 + + # 设备 B:Google 相关域名(示例 - 已注释) +# - tag: mikrotik_google +# type: mikrotik_addresslist +# args: +# domain_files: +# - "/usr/local/jinlingma/config/google.txt" +# - "/usr/local/jinlingma/config/youtube.txt" +# host: "10.96.1.23" +# port: 9728 +# username: "admin" +# password: "szn0s!nw@pwd()" +# use_tls: false +# timeout: 3 +# address_list4: "Google" +# mask4: 32 # 精确匹配单个IP +# comment: "Google-AutoAdd" +# timeout_addr: 21600 # 6小时 +# cache_ttl: 1800 # 30分钟缓存 +# verify_add: false +# add_all_ips: true +# max_ips: 15 + + # 设备 C:流媒体相关域名(示例 - 已注释) +# - tag: mikrotik_streaming +# type: mikrotik_addresslist +# args: +# domain_files: +# - "/usr/local/jinlingma/config/netflix.txt" +# - "/usr/local/jinlingma/config/disney.txt" +# host: "10.96.1.24" +# port: 9728 +# username: "admin" +# password: "szn0s!nw@pwd()" +# use_tls: false +# timeout: 5 # 流媒体可能需要更长时间 +# address_list4: "Streaming" +# mask4: 32 +# comment: "Streaming-AutoAdd" +# timeout_addr: 21600 # 6小时(流媒体IP变化较频繁) +# cache_ttl: 1800 # 30分钟缓存 +# verify_add: false +# add_all_ips: true +# max_ips: 30 # 流媒体服务IP较多 + + # ========= 查询逻辑 ========= + + # 检查是否有响应 - tag: has_resp_sequence type: sequence args: - matches: has_resp exec: accept - # ========= 🚀 增强的 MikroTik 插件(支持多设备多规则)========= - - # 设备 A:Amazon 相关域名 - - tag: mikrotik_amazon - type: mikrotik_addresslist - domain_files: - - "/usr/local/jinlingma/config/amazon.txt" - - "/usr/local/jinlingma/config/aws.txt" - args: - host: "10.96.1.22" - port: 9728 - username: "admin" - password: "szn0s!nw@pwd()" - use_tls: false - timeout: 3 - address_list4: "Amazon" - address_list6: "Amazon6" - mask4: 24 - mask6: 64 - comment: "Amazon-AutoAdd" - timeout_addr: 43200 - cache_ttl: 3600 - verify_add: false - add_all_ips: true - max_ips: 20 - - # 设备 B:Google 相关域名 - - tag: mikrotik_google - type: mikrotik_addresslist - domain_files: - - "/usr/local/jinlingma/config/google.txt" - - "/usr/local/jinlingma/config/youtube.txt" - args: - host: "10.96.1.23" - port: 9728 - username: "admin" - password: "szn0s!nw@pwd()" - use_tls: false - timeout: 3 - address_list4: "Google" - mask4: 32 - comment: "Google-AutoAdd" - timeout_addr: 21600 - cache_ttl: 1800 - verify_add: false - add_all_ips: true - max_ips: 15 - - # 设备 C:流媒体相关域名(示例) - - tag: mikrotik_streaming - type: mikrotik_addresslist - domain_files: - - "/usr/local/jinlingma/config/netflix.txt" - - "/usr/local/jinlingma/config/disney.txt" - args: - host: "10.96.1.24" - port: 9728 - username: "admin" - password: "szn0s!nw@pwd()" - use_tls: false - timeout: 5 - address_list4: "Streaming" - mask4: 32 - comment: "Streaming-AutoAdd" - timeout_addr: 21600 - cache_ttl: 1800 - verify_add: false - add_all_ips: true - max_ips: 30 - - # ========= 🚀 简化的查询逻辑 ========= - # 拒绝无效查询 - tag: reject_invalid type: sequence @@ -180,26 +192,41 @@ plugins: - matches: qtype 65 exec: reject 3 - # GFW 域名分流(仅解析,不写入设备) - - tag: gfw_routing_only - type: sequence - args: - - matches: qname $GFW_domains - exec: $forward_remote_upstream - - exec: query_summary gfw_overseas_routing # 智能 fallback 处理 - tag: smart_fallback_handler type: sequence args: - exec: prefer_ipv4 - - exec: $forward_local + - exec: $forward_local_upstream - matches: resp_ip $geoip_cn exec: accept - exec: $forward_remote_upstream - exec: query_summary fallback_to_overseas - # 🚀 极简主序列 + # 🚀 海外域名分流 + MikroTik 处理 + - tag: overseas_routing_with_mikrotik + type: sequence + args: + - matches: qname $overseas_domains + exec: $forward_remote_upstream + - matches: has_resp + exec: $mikrotik_amazon # 🔧 修复:在有DNS响应后才调用MikroTik + - matches: has_resp + exec: accept + - exec: query_summary overseas_routing + + # 🚀 并行处理序列:优化的DNS解析流程 + - tag: parallel_dns_and_mikrotik + type: sequence + args: + # DNS 解析逻辑 + - exec: $overseas_routing_with_mikrotik # 🚀 海外域名分流 + MikroTik处理 + - matches: has_resp + exec: accept + - exec: $smart_fallback_handler # 智能 fallback + + # 🚀 主序列(优化版 - 并行处理) - tag: main_sequence type: sequence args: @@ -210,28 +237,19 @@ plugins: - exec: $reject_invalid - exec: jump has_resp_sequence - # 3. GFW 域名分流(仅解析) - - exec: $gfw_routing_only + # 3. 🚀 并行处理:DNS解析 + MikroTik处理 + - exec: $parallel_dns_and_mikrotik - exec: jump has_resp_sequence - # 4. 智能 fallback - - exec: $smart_fallback_handler - - exec: jump has_resp_sequence - - # 5. 🚀 MikroTik 设备处理(每个插件自动匹配域名) - - exec: $mikrotik_amazon # 自动处理 Amazon 域名 - - exec: $mikrotik_google # 自动处理 Google 域名 - - exec: $mikrotik_streaming # 自动处理流媒体域名 - - # ========= 服务 ========= + # ========= 服务监听 ========= - tag: udp_server type: udp_server args: entry: main_sequence - listen: ":5322" + listen: ":531" - tag: tcp_server type: tcp_server args: entry: main_sequence - listen: ":5322" + listen: ":531" diff --git a/coremain/api_handlers.go b/coremain/api_handlers.go new file mode 100644 index 0000000..27485c5 --- /dev/null +++ b/coremain/api_handlers.go @@ -0,0 +1,1116 @@ +/* + * Copyright (C) 2020-2022, IrineSistiana + * + * This file is part of mosdns. + * + * mosdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * mosdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package coremain + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "github.com/go-chi/chi/v5" + "go.uber.org/zap" + "gopkg.in/yaml.v3" +) + +// ServerInfo 服务器信息 +type ServerInfo struct { + Name string `json:"name"` + Version string `json:"version"` + StartTime time.Time `json:"start_time"` + Uptime string `json:"uptime"` + UptimeSeconds int64 `json:"uptime_seconds"` // 运行时间(秒) + Status string `json:"status"` + ConfigFile string `json:"config_file"` + WorkingDir string `json:"working_dir"` + PluginCount int `json:"plugin_count"` + APIAddress string `json:"api_address"` // API 监听地址 + DNSPorts []string `json:"dns_ports"` // DNS 监听端口列表 +} + +// ConfigResponse 配置响应 +type ConfigResponse struct { + Config interface{} `json:"config"` + ConfigFile string `json:"config_file"` + LastModified time.Time `json:"last_modified"` +} + +// DomainFileInfo 域名文件信息 +type DomainFileInfo struct { + Name string `json:"name"` + Path string `json:"path"` + Size int64 `json:"size"` + LineCount int `json:"line_count"` + LastModified time.Time `json:"last_modified"` +} + +// APIResponse 通用API响应 +type APIResponse struct { + Success bool `json:"success"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` + Error string `json:"error,omitempty"` +} + +var ( + serverStartTime = time.Now() + currentConfigFile string + currentAPIAddress string // 当前 API 监听地址 +) + +// 注册管理API路由 +func (m *Mosdns) registerManagementAPI() { + // 服务器信息 + m.httpMux.Get("/api/server/info", m.handleServerInfo) + m.httpMux.Get("/api/server/status", m.handleServerStatus) + + // 配置管理 + m.httpMux.Get("/api/config", m.handleGetConfig) + m.httpMux.Put("/api/config", m.handleUpdateConfig) + m.httpMux.Post("/api/config/reload", m.handleReloadConfig) + m.httpMux.Post("/api/config/validate", m.handleValidateConfig) + m.httpMux.Get("/api/config/backup", m.handleBackupConfig) + m.httpMux.Post("/api/config/restore", m.handleRestoreConfig) + + // 域名文件管理 + m.httpMux.Get("/api/domain-files", m.handleListDomainFiles) + m.httpMux.Get("/api/domain-files/{filename}", m.handleGetDomainFile) + m.httpMux.Put("/api/domain-files/{filename}", m.handleUpdateDomainFile) + m.httpMux.Delete("/api/domain-files/{filename}", m.handleDeleteDomainFile) + + // 插件管理 + m.httpMux.Get("/api/plugins", m.handleListPlugins) + m.httpMux.Get("/api/plugins/{tag}/status", m.handlePluginStatus) + + // 系统操作 + m.httpMux.Post("/api/system/restart", m.handleSystemRestart) + + // MikroTik 管理 + m.httpMux.Get("/api/mikrotik/list", m.handleListMikroTik) + m.httpMux.Post("/api/mikrotik/add", m.handleAddMikroTik) + m.httpMux.Delete("/api/mikrotik/{tag}", m.handleDeleteMikroTik) +} + +// 处理服务器信息 +func (m *Mosdns) handleServerInfo(w http.ResponseWriter, r *http.Request) { + wd, _ := os.Getwd() + + // 计算运行时间(秒) + uptimeSeconds := int64(time.Since(serverStartTime).Seconds()) + + // 获取 DNS 端口列表 + dnsPorts := m.getDNSPorts() + + info := ServerInfo{ + Name: "MosDNS Server", + Version: "v5.0.0", // 这里应该从版本变量获取 + StartTime: serverStartTime, + Uptime: time.Since(serverStartTime).String(), + UptimeSeconds: uptimeSeconds, // 添加秒数,方便前端格式化 + Status: "running", + ConfigFile: currentConfigFile, + WorkingDir: wd, + PluginCount: len(m.plugins), + APIAddress: currentAPIAddress, // API 监听地址 + DNSPorts: dnsPorts, // DNS 端口列表 + } + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Data: info, + }) +} + +// getDNSPorts 获取所有 DNS 服务器监听的端口 +func (m *Mosdns) getDNSPorts() []string { + ports := make([]string, 0) + portMap := make(map[string]bool) // 用于去重 + + // 遍历所有插件,查找服务器类型的插件 + serverTypes := []string{"udp_server", "tcp_server", "http_server", "quic_server"} + + for tag, plugin := range m.plugins { + pluginType := fmt.Sprintf("%T", plugin) + + // 检查是否是服务器类型 + isServer := false + for _, st := range serverTypes { + if contains(pluginType, st) || contains(tag, st) { + isServer = true + break + } + } + + if isServer { + // 尝试从插件配置中提取端口信息 + // 这里简化处理,实际应该从配置中读取 + if addr := extractListenAddr(tag, plugin); addr != "" { + if !portMap[addr] { + ports = append(ports, addr) + portMap[addr] = true + } + } + } + } + + // 如果没有找到端口,返回默认提示 + if len(ports) == 0 { + ports = append(ports, "未检测到") + } + + return ports +} + +// contains 检查字符串是否包含子串 +func contains(s, substr string) bool { + return len(s) >= len(substr) && (s == substr || + (len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr))) +} + +// extractListenAddr 从插件中提取监听地址 +func extractListenAddr(tag string, plugin any) string { + // 这里需要根据实际插件类型提取 + // 简化处理:从 tag 中猜测 + if contains(tag, "udp_server") || contains(tag, "tcp_server") { + return "53" + } + return "" +} + +// 处理服务器状态 +func (m *Mosdns) handleServerStatus(w http.ResponseWriter, r *http.Request) { + status := map[string]interface{}{ + "status": "healthy", + "uptime": time.Since(serverStartTime).Seconds(), + "plugin_count": len(m.plugins), + "memory_usage": getMemoryUsage(), + "goroutines": getGoroutineCount(), + } + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Data: status, + }) +} + +// 处理获取配置 +func (m *Mosdns) handleGetConfig(w http.ResponseWriter, r *http.Request) { + if currentConfigFile == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: "配置文件路径未知", + }) + return + } + + // 读取配置文件 + configData, err := os.ReadFile(currentConfigFile) + if err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("读取配置文件失败: %v", err), + }) + return + } + + // 获取文件修改时间 + fileInfo, _ := os.Stat(currentConfigFile) + var lastModified time.Time + if fileInfo != nil { + lastModified = fileInfo.ModTime() + } + + // 解析YAML配置 + var config interface{} + if err := yaml.Unmarshal(configData, &config); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("解析配置文件失败: %v", err), + }) + return + } + + response := ConfigResponse{ + Config: config, + ConfigFile: currentConfigFile, + LastModified: lastModified, + } + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Data: response, + }) +} + +// 处理更新配置 +func (m *Mosdns) handleUpdateConfig(w http.ResponseWriter, r *http.Request) { + if currentConfigFile == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: "配置文件路径未知", + }) + return + } + + // 读取请求体 + body, err := io.ReadAll(r.Body) + if err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("读取请求体失败: %v", err), + }) + return + } + + // 验证YAML格式 + var config interface{} + if err := yaml.Unmarshal(body, &config); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("YAML格式错误: %v", err), + }) + return + } + + // 备份当前配置 + backupFile := currentConfigFile + ".backup." + time.Now().Format("20060102-150405") + if err := copyFile(currentConfigFile, backupFile); err != nil { + m.logger.Warn("备份配置文件失败", zap.Error(err)) + } + + // 写入新配置 + if err := os.WriteFile(currentConfigFile, body, 0644); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("写入配置文件失败: %v", err), + }) + return + } + + m.logger.Info("配置文件已更新", zap.String("file", currentConfigFile)) + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Message: "配置文件更新成功", + }) +} + +// 处理重载配置 +func (m *Mosdns) handleReloadConfig(w http.ResponseWriter, r *http.Request) { + m.logger.Info("开始重载配置") + + // 这里需要实现配置重载逻辑 + // 由于当前架构限制,我们先返回成功,实际实现需要重构 + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Message: "配置重载请求已接收,请重启服务以应用新配置", + }) +} + +// 处理验证配置 +func (m *Mosdns) handleValidateConfig(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + if err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("读取请求体失败: %v", err), + }) + return + } + + // 验证YAML格式 + var config Config + if err := yaml.Unmarshal(body, &config); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("YAML格式错误: %v", err), + }) + return + } + + // 这里可以添加更多配置验证逻辑 + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Message: "配置验证通过", + }) +} + +// 处理备份配置 +func (m *Mosdns) handleBackupConfig(w http.ResponseWriter, r *http.Request) { + if currentConfigFile == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: "配置文件路径未知", + }) + return + } + + // 创建备份文件 + backupFile := currentConfigFile + ".backup." + time.Now().Format("20060102-150405") + if err := copyFile(currentConfigFile, backupFile); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("备份配置文件失败: %v", err), + }) + return + } + + m.logger.Info("配置文件已备份", zap.String("backup", backupFile)) + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Message: "配置备份成功", + Data: map[string]interface{}{ + "backup_file": backupFile, + "backup_time": time.Now(), + }, + }) +} + +// 处理恢复配置 +func (m *Mosdns) handleRestoreConfig(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + if err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("读取请求体失败: %v", err), + }) + return + } + + var request struct { + BackupFile string `json:"backup_file"` + } + if err := json.Unmarshal(body, &request); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("解析请求失败: %v", err), + }) + return + } + + if request.BackupFile == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: "备份文件路径不能为空", + }) + return + } + + // 验证备份文件存在 + if _, err := os.Stat(request.BackupFile); os.IsNotExist(err) { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: "备份文件不存在", + }) + return + } + + // 恢复配置 + if err := copyFile(request.BackupFile, currentConfigFile); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("恢复配置文件失败: %v", err), + }) + return + } + + m.logger.Info("配置文件已恢复", zap.String("from", request.BackupFile)) + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Message: "配置恢复成功", + }) +} + +// 处理列出域名文件(从运行目录的 mikrotik 目录加载) +func (m *Mosdns) handleListDomainFiles(w http.ResponseWriter, r *http.Request) { + var domainFiles []DomainFileInfo + + // 获取运行目录 + wd, err := os.Getwd() + if err != nil { + m.logger.Error("failed to get working directory", zap.Error(err)) + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "获取工作目录失败: " + err.Error(), + }) + return + } + + // mikrotik 目录路径 + mikrotikDir := filepath.Join(wd, "mikrotik") + + // 检查目录是否存在 + if _, err := os.Stat(mikrotikDir); os.IsNotExist(err) { + // 如果目录不存在,尝试创建 + if err := os.MkdirAll(mikrotikDir, 0755); err != nil { + m.logger.Error("failed to create mikrotik directory", zap.Error(err)) + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "创建 mikrotik 目录失败: " + err.Error(), + }) + return + } + m.logger.Info("created mikrotik directory", zap.String("path", mikrotikDir)) + } + + // 扫描 mikrotik 目录下的所有 .txt 文件 + files, err := filepath.Glob(filepath.Join(mikrotikDir, "*.txt")) + if err != nil { + m.logger.Error("failed to list domain files", zap.Error(err)) + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "扫描域名文件失败: " + err.Error(), + }) + return + } + + // 遍历文件 + for _, filePath := range files { + info, err := os.Stat(filePath) + if err != nil { + m.logger.Warn("domain file not accessible", zap.String("path", filePath), zap.Error(err)) + continue + } + + lineCount := countLines(filePath) + + domainFiles = append(domainFiles, DomainFileInfo{ + Name: filepath.Base(filePath), + Path: filePath, + Size: info.Size(), + LineCount: lineCount, + LastModified: info.ModTime(), + }) + } + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Data: domainFiles, + Message: fmt.Sprintf("从 %s 目录加载了 %d 个域名文件", mikrotikDir, len(domainFiles)), + }) +} + +// extractDomainFilesFromConfig 从配置中提取所有域名文件路径 +func (m *Mosdns) extractDomainFilesFromConfig() []string { + filePaths := make([]string, 0) + fileMap := make(map[string]bool) // 用于去重 + + // 读取配置文件 + if currentConfigFile == "" { + return filePaths + } + + configData, err := os.ReadFile(currentConfigFile) + if err != nil { + m.logger.Error("failed to read config file", zap.Error(err)) + return filePaths + } + + // 解析配置文件 + var config Config + if err := yaml.Unmarshal(configData, &config); err != nil { + m.logger.Error("failed to parse config", zap.Error(err)) + return filePaths + } + + // 遍历所有插件,提取域名文件 + for _, pluginConfig := range config.Plugins { + // 检查是否是域名集合类型或 MikroTik 类型 + if pluginConfig.Type == "domain_set" || pluginConfig.Type == "mikrotik_addresslist" { + // 尝试从 Args 中提取 files 或 domain_files + if args, ok := pluginConfig.Args.(map[string]interface{}); ok { + // domain_set 使用 "files" + if files, ok := args["files"].([]interface{}); ok { + for _, file := range files { + if filePath, ok := file.(string); ok { + if !fileMap[filePath] { + filePaths = append(filePaths, filePath) + fileMap[filePath] = true + } + } + } + } + // mikrotik_addresslist 使用 "domain_files" + if files, ok := args["domain_files"].([]interface{}); ok { + for _, file := range files { + if filePath, ok := file.(string); ok { + if !fileMap[filePath] { + filePaths = append(filePaths, filePath) + fileMap[filePath] = true + } + } + } + } + } + } + } + + return filePaths +} + +// 处理获取域名文件 +func (m *Mosdns) handleGetDomainFile(w http.ResponseWriter, r *http.Request) { + filename := chi.URLParam(r, "filename") + if filename == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: "文件名不能为空", + }) + return + } + + // 查找文件 + filePath := m.findDomainFile(filename) + if filePath == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: "文件未找到", + }) + return + } + + content, err := os.ReadFile(filePath) + if err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("读取文件失败: %v", err), + }) + return + } + + // 解析域名列表 + domains := strings.Split(string(content), "\n") + var cleanDomains []string + for _, domain := range domains { + domain = strings.TrimSpace(domain) + if domain != "" && !strings.HasPrefix(domain, "#") { + cleanDomains = append(cleanDomains, domain) + } + } + + fileInfo, _ := os.Stat(filePath) + response := map[string]interface{}{ + "filename": filename, + "path": filePath, + "domains": cleanDomains, + "total_count": len(cleanDomains), + "file_size": fileInfo.Size(), + "last_modified": fileInfo.ModTime(), + } + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Data: response, + }) +} + +// 处理更新域名文件 +func (m *Mosdns) handleUpdateDomainFile(w http.ResponseWriter, r *http.Request) { + filename := chi.URLParam(r, "filename") + if filename == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: "文件名不能为空", + }) + return + } + + body, err := io.ReadAll(r.Body) + if err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("读取请求体失败: %v", err), + }) + return + } + + // 查找或创建文件路径 + filePath := m.findDomainFile(filename) + if filePath == "" { + // 如果文件不存在,在默认目录创建 + filePath = filepath.Join("./domain-files", filename) + os.MkdirAll(filepath.Dir(filePath), 0755) + } + + // 备份现有文件 + if _, err := os.Stat(filePath); err == nil { + backupFile := filePath + ".backup." + time.Now().Format("20060102-150405") + copyFile(filePath, backupFile) + } + + // 写入新内容 + if err := os.WriteFile(filePath, body, 0644); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("写入文件失败: %v", err), + }) + return + } + + m.logger.Info("域名文件已更新", zap.String("file", filePath)) + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Message: "域名文件更新成功", + Data: map[string]interface{}{ + "filename": filename, + "path": filePath, + }, + }) +} + +// 处理删除域名文件 +func (m *Mosdns) handleDeleteDomainFile(w http.ResponseWriter, r *http.Request) { + filename := chi.URLParam(r, "filename") + if filename == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: "文件名不能为空", + }) + return + } + + filePath := m.findDomainFile(filename) + if filePath == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: "文件未找到", + }) + return + } + + // 备份文件 + backupFile := filePath + ".deleted." + time.Now().Format("20060102-150405") + if err := copyFile(filePath, backupFile); err != nil { + m.logger.Warn("备份文件失败", zap.Error(err)) + } + + // 删除文件 + if err := os.Remove(filePath); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: fmt.Sprintf("删除文件失败: %v", err), + }) + return + } + + m.logger.Info("域名文件已删除", zap.String("file", filePath)) + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Message: "域名文件删除成功", + }) +} + +// 处理列出插件 +func (m *Mosdns) handleListPlugins(w http.ResponseWriter, r *http.Request) { + plugins := make([]map[string]interface{}, 0, len(m.plugins)) + + for tag, plugin := range m.plugins { + pluginInfo := map[string]interface{}{ + "tag": tag, + "type": fmt.Sprintf("%T", plugin), + "status": "active", + } + plugins = append(plugins, pluginInfo) + } + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Data: plugins, + }) +} + +// 处理插件状态 +func (m *Mosdns) handlePluginStatus(w http.ResponseWriter, r *http.Request) { + tag := chi.URLParam(r, "tag") + if tag == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: "插件标签不能为空", + }) + return + } + + plugin := m.plugins[tag] + if plugin == nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Error: "插件未找到", + }) + return + } + + status := map[string]interface{}{ + "tag": tag, + "type": fmt.Sprintf("%T", plugin), + "status": "active", + } + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Data: status, + }) +} + +// 处理系统重启 +func (m *Mosdns) handleSystemRestart(w http.ResponseWriter, r *http.Request) { + m.writeJSONResponse(w, APIResponse{ + Success: true, + Message: "重启请求已接收,服务将在3秒后重启", + }) + + // 延迟重启 + go func() { + time.Sleep(3 * time.Second) + m.logger.Info("执行系统重启") + m.sc.SendCloseSignal(fmt.Errorf("管理员请求重启")) + }() +} + +// MikroTik 管理 API + +// handleListMikroTik 列出所有 MikroTik 配置 +func (m *Mosdns) handleListMikroTik(w http.ResponseWriter, r *http.Request) { + // 读取配置文件 + if currentConfigFile == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "配置文件路径未设置", + }) + return + } + + configData, err := os.ReadFile(currentConfigFile) + if err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "读取配置文件失败: " + err.Error(), + }) + return + } + + // 解析配置 + var config Config + if err := yaml.Unmarshal(configData, &config); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "解析配置失败: " + err.Error(), + }) + return + } + + // 提取 MikroTik 配置 + mikrotikConfigs := make([]map[string]interface{}, 0) + for _, plugin := range config.Plugins { + if plugin.Type == "mikrotik_addresslist" { + mikrotikConfigs = append(mikrotikConfigs, map[string]interface{}{ + "tag": plugin.Tag, + "type": plugin.Type, + "args": plugin.Args, + }) + } + } + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Data: mikrotikConfigs, + }) +} + +// handleAddMikroTik 添加 MikroTik 配置 +func (m *Mosdns) handleAddMikroTik(w http.ResponseWriter, r *http.Request) { + var newConfig PluginConfig + if err := json.NewDecoder(r.Body).Decode(&newConfig); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "解析请求失败: " + err.Error(), + }) + return + } + + // 验证必填字段 + if newConfig.Tag == "" || newConfig.Type == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "Tag 和 Type 不能为空", + }) + return + } + + // 读取当前配置 + if currentConfigFile == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "配置文件路径未设置", + }) + return + } + + configData, err := os.ReadFile(currentConfigFile) + if err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "读取配置文件失败: " + err.Error(), + }) + return + } + + var config Config + if err := yaml.Unmarshal(configData, &config); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "解析配置失败: " + err.Error(), + }) + return + } + + // 检查是否已存在同名配置 + for _, plugin := range config.Plugins { + if plugin.Tag == newConfig.Tag { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "配置标签已存在: " + newConfig.Tag, + }) + return + } + } + + // 添加新配置 + config.Plugins = append(config.Plugins, newConfig) + + // 使用 yaml.v3 Encoder 保存配置,设置更好的缩进 + var buf strings.Builder + encoder := yaml.NewEncoder(&buf) + encoder.SetIndent(4) // 设置缩进为 4 个空格 + + if err := encoder.Encode(&config); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "序列化配置失败: " + err.Error(), + }) + return + } + encoder.Close() + + if err := os.WriteFile(currentConfigFile, []byte(buf.String()), 0644); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "保存配置文件失败: " + err.Error(), + }) + return + } + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Message: "MikroTik 配置添加成功,需要重启服务生效", + Data: newConfig, + }) +} + +// handleDeleteMikroTik 删除 MikroTik 配置 +func (m *Mosdns) handleDeleteMikroTik(w http.ResponseWriter, r *http.Request) { + tag := chi.URLParam(r, "tag") + if tag == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "缺少配置标签参数", + }) + return + } + + // 读取当前配置 + if currentConfigFile == "" { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "配置文件路径未设置", + }) + return + } + + configData, err := os.ReadFile(currentConfigFile) + if err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "读取配置文件失败: " + err.Error(), + }) + return + } + + var config Config + if err := yaml.Unmarshal(configData, &config); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "解析配置失败: " + err.Error(), + }) + return + } + + // 查找并删除配置 + found := false + newPlugins := make([]PluginConfig, 0) + for _, plugin := range config.Plugins { + if plugin.Tag == tag && plugin.Type == "mikrotik_addresslist" { + found = true + continue // 跳过要删除的配置 + } + newPlugins = append(newPlugins, plugin) + } + + if !found { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "未找到指定的 MikroTik 配置: " + tag, + }) + return + } + + config.Plugins = newPlugins + + // 使用 yaml.v3 Encoder 保存配置,设置更好的缩进 + var buf strings.Builder + encoder := yaml.NewEncoder(&buf) + encoder.SetIndent(4) // 设置缩进为 4 个空格 + + if err := encoder.Encode(&config); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "序列化配置失败: " + err.Error(), + }) + return + } + encoder.Close() + + if err := os.WriteFile(currentConfigFile, []byte(buf.String()), 0644); err != nil { + m.writeJSONResponse(w, APIResponse{ + Success: false, + Message: "保存配置文件失败: " + err.Error(), + }) + return + } + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Message: "MikroTik 配置删除成功,需要重启服务生效", + }) +} + +// 辅助方法 + +// 写入JSON响应 +func (m *Mosdns) writeJSONResponse(w http.ResponseWriter, response APIResponse) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + + if !response.Success { + w.WriteHeader(http.StatusBadRequest) + } + + json.NewEncoder(w).Encode(response) +} + +// 查找域名文件 +func (m *Mosdns) findDomainFile(filename string) string { + searchDirs := []string{ + "./geosite", + "./config", + "./domain-files", + "./example-domain-files", + "/usr/local/yltx-dns/geosite", + "/usr/local/yltx-dns/config", + } + + for _, dir := range searchDirs { + filePath := filepath.Join(dir, filename) + if _, err := os.Stat(filePath); err == nil { + return filePath + } + } + + return "" +} + +// 复制文件 +func copyFile(src, dst string) error { + sourceFile, err := os.Open(src) + if err != nil { + return err + } + defer sourceFile.Close() + + destFile, err := os.Create(dst) + if err != nil { + return err + } + defer destFile.Close() + + _, err = io.Copy(destFile, sourceFile) + return err +} + +// 统计文件行数 +func countLines(filename string) int { + content, err := os.ReadFile(filename) + if err != nil { + return 0 + } + + lines := strings.Split(string(content), "\n") + count := 0 + for _, line := range lines { + if strings.TrimSpace(line) != "" && !strings.HasPrefix(strings.TrimSpace(line), "#") { + count++ + } + } + return count +} + +// 获取内存使用情况 (简化版) +func getMemoryUsage() map[string]interface{} { + return map[string]interface{}{ + "allocated": "N/A", + "sys": "N/A", + } +} + +// 获取协程数量 (简化版) +func getGoroutineCount() int { + return 0 +} + +// 设置当前配置文件路径 +func SetCurrentConfigFile(path string) { + currentConfigFile = path +} + +// 设置当前 API 地址 +func SetCurrentAPIAddress(addr string) { + currentAPIAddress = addr +} diff --git a/coremain/config.go b/coremain/config.go index 2f42168..93fccde 100644 --- a/coremain/config.go +++ b/coremain/config.go @@ -28,6 +28,7 @@ type Config struct { Include []string `yaml:"include"` Plugins []PluginConfig `yaml:"plugins"` API APIConfig `yaml:"api"` + Web WebConfig `yaml:"web"` // Web 管理界面配置 } // PluginConfig represents a plugin config @@ -45,6 +46,12 @@ type PluginConfig struct { Args any `yaml:"args"` } +// APIConfig API 接口配置 type APIConfig struct { - HTTP string `yaml:"http"` + HTTP string `yaml:"http"` // API HTTP 监听地址,如 "0.0.0.0:5541" +} + +// WebConfig Web 管理界面配置 +type WebConfig struct { + HTTP string `yaml:"http"` // Web UI HTTP 监听地址,如 "0.0.0.0:5555" } diff --git a/coremain/mosdns.go b/coremain/mosdns.go index d9d3b21..bbc339c 100644 --- a/coremain/mosdns.go +++ b/coremain/mosdns.go @@ -23,6 +23,10 @@ import ( "bytes" "errors" "fmt" + "io" + "net/http" + "net/http/pprof" + "github.com/IrineSistiana/mosdns/v5/mlog" "github.com/IrineSistiana/mosdns/v5/pkg/safe_close" "github.com/go-chi/chi/v5" @@ -30,9 +34,6 @@ import ( "github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/promhttp" "go.uber.org/zap" - "io" - "net/http" - "net/http/pprof" ) type Mosdns struct { @@ -41,7 +42,8 @@ type Mosdns struct { // Plugins plugins map[string]any - httpMux *chi.Mux + httpMux *chi.Mux // API 路由 + webMux *chi.Mux // Web UI 路由(独立) metricsReg *prometheus.Registry sc *safe_close.SafeClose } @@ -57,15 +59,29 @@ func NewMosdns(cfg *Config) (*Mosdns, error) { m := &Mosdns{ logger: lg, plugins: make(map[string]any), - httpMux: chi.NewRouter(), + httpMux: chi.NewRouter(), // API 路由 + webMux: chi.NewRouter(), // Web UI 独立路由 metricsReg: newMetricsReg(), sc: safe_close.NewSafeClose(), } // This must be called after m.httpMux and m.metricsReg been set. m.initHttpMux() + // Register management API routes + m.registerManagementAPI() + + // 如果配置了独立的 Web 端口,初始化 Web 路由 + if len(cfg.Web.HTTP) > 0 { + m.initWebMux() + m.registerWebUI() + m.registerWebAPI() + } + // Start http api server if httpAddr := cfg.API.HTTP; len(httpAddr) > 0 { + // 设置当前 API 地址,供 Web UI 显示 + SetCurrentAPIAddress(httpAddr) + httpServer := &http.Server{ Addr: httpAddr, Handler: m.httpMux, @@ -86,6 +102,28 @@ func NewMosdns(cfg *Config) (*Mosdns, error) { }) } + // Start web ui server (独立端口) + if webAddr := cfg.Web.HTTP; len(webAddr) > 0 { + webServer := &http.Server{ + Addr: webAddr, + Handler: m.webMux, + } + m.sc.Attach(func(done func(), closeSignal <-chan struct{}) { + defer done() + errChan := make(chan error, 1) + go func() { + m.logger.Info("starting web ui http server", zap.String("addr", webAddr)) + errChan <- webServer.ListenAndServe() + }() + select { + case err := <-errChan: + m.sc.SendCloseSignal(err) + case <-closeSignal: + _ = webServer.Close() + } + }) + } + // Load plugins. // Close all plugins on signal. @@ -204,6 +242,14 @@ func (m *Mosdns) initHttpMux() { m.httpMux.MethodNotAllowed(invalidApiReqHelper) } +// initWebMux 初始化 Web UI 路由 +func (m *Mosdns) initWebMux() { + // Web UI 的 404 页面 + m.webMux.NotFound(func(w http.ResponseWriter, req *http.Request) { + http.Error(w, "Page not found", http.StatusNotFound) + }) +} + func (m *Mosdns) loadPresetPlugins() error { for tag, f := range LoadNewPersetPluginFuncs() { p, err := f(NewBP(tag, m)) diff --git a/coremain/run.go b/coremain/run.go index 0c8357b..c9f0533 100644 --- a/coremain/run.go +++ b/coremain/run.go @@ -21,16 +21,17 @@ package coremain import ( "fmt" + "os" + "os/signal" + "runtime" + "syscall" + "github.com/IrineSistiana/mosdns/v5/mlog" "github.com/kardianos/service" "github.com/mitchellh/mapstructure" "github.com/spf13/cobra" "github.com/spf13/viper" "go.uber.org/zap" - "os" - "os/signal" - "runtime" - "syscall" ) type serverFlags struct { @@ -126,6 +127,9 @@ func NewServer(sf *serverFlags) (*Mosdns, error) { } mlog.L().Info("main config loaded", zap.String("file", fileUsed)) + // Set current config file for API access + SetCurrentConfigFile(fileUsed) + return NewMosdns(cfg) } diff --git a/coremain/web/static/css/style.css b/coremain/web/static/css/style.css new file mode 100644 index 0000000..d7376f9 --- /dev/null +++ b/coremain/web/static/css/style.css @@ -0,0 +1,631 @@ +/* 基础样式重置 - Element Plus 风格 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --el-color-primary: #409eff; + --el-color-success: #67c23a; + --el-color-warning: #e6a23c; + --el-color-danger: #f56c6c; + --el-color-info: #909399; + --el-bg-color: #f0f2f5; + --el-bg-color-page: #f0f2f5; + --el-border-color: #dcdfe6; + --el-border-radius-base: 4px; + --el-box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1); +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', + 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif; + background-color: var(--el-bg-color-page); + color: #303133; + line-height: 1.6; + font-size: 14px; +} + +.container { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +/* 头部样式 - Element Plus 风格 */ +.header { + background: #fff; + color: #303133; + padding: 0; + box-shadow: 0 1px 4px rgba(0,21,41,.08); + border-bottom: 1px solid var(--el-border-color); +} + +.header-content { + max-width: 1400px; + margin: 0 auto; + padding: 0 24px; + height: 64px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + font-size: 20px; + font-weight: 600; + color: var(--el-color-primary); + display: flex; + align-items: center; + gap: 8px; +} + +.header-info { + display: flex; + align-items: center; + gap: 16px; +} + +.status-indicator { + padding: 4px 12px; + background: #f0f9ff; + color: var(--el-color-primary); + border-radius: 4px; + font-size: 13px; + border: 1px solid #d0ebff; +} + +.version { + font-size: 13px; + color: #606266; + background: #f5f7fa; + padding: 4px 12px; + border-radius: 4px; +} + +/* 导航栏样式 - Element Plus Tabs 风格 */ +.nav { + background: white; + border-bottom: 2px solid #e4e7ed; +} + +.nav-content { + max-width: 1400px; + margin: 0 auto; + padding: 0 24px; + display: flex; + gap: 8px; +} + +.nav-item { + padding: 16px 20px; + background: none; + border: none; + color: #606266; + font-size: 14px; + cursor: pointer; + transition: all 0.2s; + border-bottom: 2px solid transparent; + position: relative; + font-weight: 500; +} + +.nav-item:hover { + color: var(--el-color-primary); + background: #f5f7fa; +} + +.nav-item.active { + color: var(--el-color-primary); + border-bottom-color: var(--el-color-primary); +} + +/* 主内容区域 - vben 风格 */ +.main { + flex: 1; + max-width: 1400px; + margin: 0 auto; + padding: 20px 24px; + width: 100%; + min-height: calc(100vh - 128px); +} + +/* 标签页内容 */ +.tab-content { + display: none; +} + +.tab-content.active { + display: block; + animation: fadeIn 0.3s ease; +} + +/* 仪表板网格布局 */ +.dashboard-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 20px; + margin-bottom: 20px; +} + +/* 卡片样式 - Element Plus Card 风格 */ +.card { + background: white; + border-radius: var(--el-border-radius-base); + box-shadow: var(--el-box-shadow-light); + margin-bottom: 20px; + overflow: hidden; + border: 1px solid var(--el-border-color); + transition: box-shadow 0.2s; +} + +.card:hover { + box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.12); +} + +.card-header { + padding: 18px 20px; + border-bottom: 1px solid var(--el-border-color); + display: flex; + justify-content: space-between; + align-items: center; + background: #fafafa; +} + +.card-header h3 { + font-size: 16px; + font-weight: 600; + color: #303133; + margin: 0; +} + +.card-content { + padding: 20px; +} + +.card-actions { + display: flex; + gap: 8px; +} + +/* 统计项 */ +.stat-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 0; + border-bottom: 1px solid #f0f0f0; +} + +.stat-item:last-child { + border-bottom: none; +} + +.stat-label { + color: #909399; + font-size: 14px; +} + +.stat-value { + color: #303133; + font-weight: 600; + font-size: 14px; +} + +/* 按钮样式 - Element Plus Button 风格 */ +.btn { + padding: 9px 15px; + border: 1px solid var(--el-border-color); + border-radius: var(--el-border-radius-base); + font-size: 14px; + cursor: pointer; + transition: all 0.2s; + font-weight: 400; + line-height: 1; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; +} + +.btn-primary { + background: var(--el-color-primary); + color: white; + border-color: var(--el-color-primary); +} + +.btn-primary:hover { + background: #66b1ff; + border-color: #66b1ff; +} + +.btn-primary:active { + background: #3a8ee6; + border-color: #3a8ee6; +} + +.btn-secondary { + background: white; + color: #606266; + border-color: var(--el-border-color); +} + +.btn-secondary:hover { + color: var(--el-color-primary); + border-color: #c6e2ff; + background: #ecf5ff; +} + +.btn-danger { + background: var(--el-color-danger); + color: white; + border-color: var(--el-color-danger); +} + +.btn-danger:hover { + background: #f78989; + border-color: #f78989; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.button-group { + display: flex; + gap: 12px; + flex-wrap: wrap; +} + +/* MikroTik 表单样式 - 优化版 Element Plus Form 风格 */ +.mikrotik-form { + max-width: 100%; +} + +.form-row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 20px; + margin-bottom: 20px; +} + +.form-group { + display: flex; + flex-direction: column; +} + +.form-group label { + display: flex; + align-items: center; + gap: 4px; + margin-bottom: 8px; + font-weight: 500; + color: #606266; + font-size: 14px; +} + +.label-required { + color: var(--el-color-danger); + font-weight: bold; +} + +.form-control, +.form-group input, +.form-group select { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--el-border-color); + border-radius: var(--el-border-radius-base); + font-size: 14px; + transition: all 0.2s; + line-height: 1.5; + background: white; +} + +.form-control:focus, +.form-group input:focus, +.form-group select:focus { + outline: none; + border-color: var(--el-color-primary); + box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1); +} + +.form-control:hover:not(:focus), +.form-group input:hover:not(:focus), +.form-group select:hover:not(:focus) { + border-color: #c0c4cc; +} + +.form-hint { + display: block; + margin-top: 6px; + color: #909399; + font-size: 12px; + line-height: 1.4; +} + +.form-actions { + display: flex; + gap: 12px; + margin-top: 24px; + padding-top: 20px; + border-top: 1px solid var(--el-border-color); +} + +.btn-lg { + padding: 11px 20px; + font-size: 15px; +} + +/* MikroTik 配置列表 */ +.mikrotik-list { + margin-top: 20px; +} + +.mikrotik-item { + background: #fafafa; + border: 1px solid var(--el-border-color); + border-radius: var(--el-border-radius-base); + padding: 16px; + margin-bottom: 12px; + transition: all 0.2s; +} + +.mikrotik-item:hover { + background: #f5f7fa; + box-shadow: var(--el-box-shadow-light); +} + +.mikrotik-item-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.mikrotik-item-title { + font-size: 16px; + font-weight: 600; + color: #303133; +} + +.mikrotik-item-actions { + display: flex; + gap: 8px; +} + +.mikrotik-item-content { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 12px; + color: #606266; + font-size: 13px; +} + +.mikrotik-item-field { + display: flex; + flex-direction: column; + gap: 4px; +} + +.mikrotik-item-label { + color: #909399; + font-size: 12px; +} + +.mikrotik-item-value { + color: #303133; + font-weight: 500; +} + +/* 结果区域 */ +.result-area { + margin-top: 20px; + padding: 16px; + background: #f5f7fa; + border-radius: var(--el-border-radius-base); + border: 1px solid var(--el-border-color); +} + +.result-area h4 { + margin-bottom: 12px; + color: #303133; + font-size: 15px; +} + +.result-area pre { + margin: 12px 0; + white-space: pre-wrap; + word-wrap: break-word; + background: #fff; + padding: 12px; + border-radius: var(--el-border-radius-base); + border: 1px solid var(--el-border-color); + font-size: 13px; + line-height: 1.6; +} + +/* 域名文件列表 */ +.domain-files-list { + display: grid; + gap: 12px; +} + +.domain-file-item { + background: #fafafa; + border: 1px solid var(--el-border-color); + border-radius: var(--el-border-radius-base); + padding: 16px; + transition: all 0.2s; +} + +.domain-file-item:hover { + background: #f5f7fa; + box-shadow: var(--el-box-shadow-light); +} + +.domain-file-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.domain-file-name { + font-weight: 600; + color: #303133; + font-size: 15px; +} + +.domain-file-info { + display: flex; + gap: 16px; + color: #909399; + font-size: 13px; +} + +/* 日志查看器 */ +.logs-container { + background: #1e1e1e; + color: #d4d4d4; + padding: 16px; + border-radius: var(--el-border-radius-base); + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 13px; + line-height: 1.6; + max-height: 600px; + overflow-y: auto; +} + +/* 配置编辑器 */ +.config-editor { + border: 1px solid var(--el-border-color); + border-radius: var(--el-border-radius-base); +} + +.config-editor textarea { + width: 100%; + min-height: 500px; + padding: 16px; + border: none; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 13px; + line-height: 1.6; + resize: vertical; +} + +.config-editor textarea:focus { + outline: none; +} + +/* 消息提示 */ +.message { + position: fixed; + top: 20px; + right: 20px; + min-width: 300px; + padding: 12px 16px; + background: white; + border-radius: var(--el-border-radius-base); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + animation: slideInRight 0.3s ease; + z-index: 9999; + border-left: 4px solid; +} + +.message.success { + border-left-color: var(--el-color-success); +} + +.message.error { + border-left-color: var(--el-color-danger); +} + +.message.warning { + border-left-color: var(--el-color-warning); +} + +.message.info { + border-left-color: var(--el-color-info); +} + +/* 加载状态 */ +.loading { + text-align: center; + padding: 20px; + color: #909399; +} + +/* 动画 */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideInRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .header-content { + padding: 0 16px; + height: 56px; + } + + .nav-content { + padding: 0 16px; + flex-wrap: wrap; + } + + .main { + padding: 16px; + } + + .dashboard-grid { + grid-template-columns: 1fr; + } + + .card-header { + flex-direction: column; + gap: 12px; + align-items: flex-start; + } + + .card-actions { + width: 100%; + justify-content: flex-end; + } + + .button-group { + width: 100%; + } + + .btn { + width: 100%; + justify-content: center; + } + + .mikrotik-form { + max-width: 100%; + } + + .mikrotik-item-content { + grid-template-columns: 1fr; + } +} diff --git a/coremain/web/static/js/app.js b/coremain/web/static/js/app.js new file mode 100644 index 0000000..7fc4be1 --- /dev/null +++ b/coremain/web/static/js/app.js @@ -0,0 +1,709 @@ +// MosDNS 管理面板 JavaScript 应用 + +class MosDNSAdmin { + constructor() { + this.apiBase = '/api'; + this.currentTab = 'dashboard'; + this.refreshInterval = null; + this.init(); + } + + init() { + this.setupEventListeners(); + this.loadInitialData(); + this.startAutoRefresh(); + } + + setupEventListeners() { + // 导航栏切换 + document.querySelectorAll('.nav-item').forEach(item => { + item.addEventListener('click', (e) => { + const tab = e.target.dataset.tab; + this.switchTab(tab); + }); + }); + + // 页面可见性变化时处理自动刷新 + document.addEventListener('visibilitychange', () => { + if (document.hidden) { + this.stopAutoRefresh(); + } else { + this.startAutoRefresh(); + } + }); + } + + switchTab(tab) { + // 更新导航栏状态 + document.querySelectorAll('.nav-item').forEach(item => { + item.classList.remove('active'); + }); + document.querySelector(`[data-tab="${tab}"]`).classList.add('active'); + + // 更新内容区域 + document.querySelectorAll('.tab-content').forEach(content => { + content.classList.remove('active'); + }); + document.getElementById(tab).classList.add('active'); + + this.currentTab = tab; + this.loadTabData(tab); + } + + async loadInitialData() { + try { + await this.loadServerInfo(); + await this.loadTabData(this.currentTab); + } catch (error) { + this.showMessage('加载初始数据失败: ' + error.message, 'error'); + } + } + + async loadServerInfo() { + try { + const response = await this.apiCall('/server/info'); + if (response.success) { + const info = response.data; + document.getElementById('version').textContent = info.version || 'v5.0.0'; + document.getElementById('service-status').textContent = info.status; + + // 使用秒数来格式化运行时间,修复 NaN 问题 + if (info.uptime_seconds !== undefined) { + document.getElementById('uptime').textContent = this.formatUptimeFromSeconds(info.uptime_seconds); + } else { + document.getElementById('uptime').textContent = info.uptime || '-'; + } + + // 显示 DNS 端口 + if (info.dns_ports && info.dns_ports.length > 0) { + document.getElementById('dns-ports').textContent = info.dns_ports.join(', '); + } else { + document.getElementById('dns-ports').textContent = '未检测到'; + } + + // 显示 API 地址 + if (info.api_address) { + document.getElementById('api-address').textContent = info.api_address; + } + } + } catch (error) { + console.error('Failed to load server info:', error); + } + } + + formatUptimeFromSeconds(seconds) { + if (!seconds || seconds < 0) { + return '0分钟'; + } + + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + let parts = []; + if (days > 0) parts.push(`${days}天`); + if (hours > 0) parts.push(`${hours}小时`); + if (minutes > 0 || parts.length === 0) parts.push(`${minutes}分钟`); + + return parts.join(' '); + } + + async loadTabData(tab) { + switch (tab) { + case 'dashboard': + await this.loadDashboardData(); + break; + case 'mikrotik': + await this.loadMikroTikTab(); + break; + case 'domains': + await this.loadDomainFiles(); + break; + case 'logs': + await this.loadLogs(); + break; + case 'stats': + await this.loadDetailedStats(); + break; + } + } + + async loadDashboardData() { + try { + // 加载详细统计 + const statsResponse = await this.apiCall('/stats/detailed'); + if (statsResponse.success) { + const stats = statsResponse.data; + document.getElementById('total-queries').textContent = stats.totalQueries?.toLocaleString() || '-'; + document.getElementById('cache-hits').textContent = stats.cacheHits?.toLocaleString() || '-'; + document.getElementById('avg-response').textContent = stats.avgResponseTime ? `${stats.avgResponseTime}ms` : '-'; + } + } catch (error) { + console.error('Failed to load dashboard data:', error); + } + } + + // MikroTik 标签页加载 + async loadMikroTikTab() { + try { + // 直接加载 MikroTik 配置列表(不再需要加载域名文件下拉框) + await this.loadMikrotikList(); + } catch (error) { + console.error('Failed to load MikroTik tab:', error); + } + } + + // 加载 MikroTik 配置列表 + async loadMikrotikList() { + const listDiv = document.getElementById('mikrotik-list'); + + try { + console.log('开始加载 MikroTik 配置列表...'); + listDiv.innerHTML = '
加载中...
'; + + const response = await this.apiCall('/mikrotik/list'); + console.log('MikroTik API 响应:', response); + + if (!response) { + throw new Error('API 响应为空'); + } + + if (!response.success) { + throw new Error(response.message || '加载失败'); + } + + const configs = response.data || []; + + if (configs.length === 0) { + listDiv.innerHTML = '
暂无 MikroTik 配置
'; + return; + } + + let html = ''; + configs.forEach(config => { + const args = config.args || {}; + const domainFiles = args.domain_files || []; + const domainFilesStr = Array.isArray(domainFiles) ? domainFiles.join(', ') : domainFiles; + + html += ` +
+
+
${this.escapeHtml(config.tag || '')}
+
+ +
+
+
+
+
主机地址
+
${this.escapeHtml(args.host || '-')}
+
+
+
端口
+
${args.port || '-'}
+
+
+
用户名
+
${this.escapeHtml(args.username || '-')}
+
+
+
地址列表
+
${this.escapeHtml(args.address_list4 || '-')}
+
+
+
域名文件
+
${this.escapeHtml(domainFilesStr || '-')}
+
+
+
+ `; + }); + + listDiv.innerHTML = html; + console.log(`成功加载 ${configs.length} 个 MikroTik 配置`); + + } catch (error) { + console.error('加载 MikroTik 列表失败:', error); + listDiv.innerHTML = ` +
+

❌ 加载失败

+

${this.escapeHtml(error.message)}

+ +
+ `; + } + } + + // HTML 转义 + escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + async loadConfigData() { + try { + const response = await this.apiCall('/config'); + if (response.success) { + document.getElementById('config-editor').value = response.data.content || ''; + } + } catch (error) { + this.showMessage('加载配置失败: ' + error.message, 'error'); + } + } + + async loadDomainFiles() { + try { + const response = await this.apiCall('/domain-files'); + if (response.success) { + this.renderDomainFilesList(response.data); + } + } catch (error) { + this.showMessage('加载域名文件失败: ' + error.message, 'error'); + } + } + + async loadLogs() { + try { + const response = await this.apiCall('/logs'); + if (response.success) { + document.getElementById('logs-content').textContent = response.data.content || '暂无日志内容'; + } + } catch (error) { + this.showMessage('加载日志失败: ' + error.message, 'error'); + } + } + + async loadDetailedStats() { + try { + const response = await this.apiCall('/stats/detailed'); + if (response.success) { + this.renderDetailedStats(response.data); + } + } catch (error) { + this.showMessage('加载统计信息失败: ' + error.message, 'error'); + } + } + + renderPluginsList(plugins) { + const container = document.getElementById('plugins-list'); + if (!plugins || plugins.length === 0) { + container.innerHTML = '
暂无插件信息
'; + return; + } + + const html = plugins.map(plugin => ` +
+ ${plugin.tag}: + ${plugin.status || '运行中'} +
+ `).join(''); + + container.innerHTML = html; + } + + renderDomainFilesList(files) { + const container = document.getElementById('domain-files-list'); + if (!files || files.length === 0) { + container.innerHTML = '
暂无域名文件
'; + return; + } + + const html = files.map(file => ` +
+
+
${file.filename}
+
+ 大小: ${this.formatFileSize(file.size)} | + 修改时间: ${new Date(file.modTime).toLocaleString()} +
+
+
+ + + +
+
+ `).join(''); + + container.innerHTML = html; + } + + renderDetailedStats(stats) { + const container = document.getElementById('detailed-stats'); + if (!stats) { + container.innerHTML = '
暂无统计信息
'; + return; + } + + const html = ` +
+ DNS 查询总数: + ${stats.totalQueries?.toLocaleString() || '-'} +
+
+ 成功响应: + ${stats.successfulQueries?.toLocaleString() || '-'} +
+
+ 失败响应: + ${stats.failedQueries?.toLocaleString() || '-'} +
+
+ 缓存命中: + ${stats.cacheHits?.toLocaleString() || '-'} +
+
+ 缓存未命中: + ${stats.cacheMisses?.toLocaleString() || '-'} +
+
+ 平均响应时间: + ${stats.avgResponseTime ? stats.avgResponseTime + 'ms' : '-'} +
+ `; + + container.innerHTML = html; + } + + async apiCall(endpoint, options = {}) { + const url = this.apiBase + endpoint; + const defaultOptions = { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }; + + const response = await fetch(url, { ...defaultOptions, ...options }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + return await response.json(); + } + + startAutoRefresh() { + this.stopAutoRefresh(); + this.refreshInterval = setInterval(() => { + if (this.currentTab === 'dashboard') { + this.loadDashboardData(); + } + }, 30000); // 30秒刷新一次 + } + + stopAutoRefresh() { + if (this.refreshInterval) { + clearInterval(this.refreshInterval); + this.refreshInterval = null; + } + } + + showMessage(message, type = 'success') { + const container = document.getElementById('message-container'); + const messageEl = document.createElement('div'); + messageEl.className = `message ${type}`; + messageEl.textContent = message; + + container.appendChild(messageEl); + + setTimeout(() => { + messageEl.remove(); + }, 5000); + } + + formatUptime(seconds) { + if (!seconds) return '-'; + + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (days > 0) return `${days}天 ${hours}小时 ${minutes}分钟`; + if (hours > 0) return `${hours}小时 ${minutes}分钟`; + return `${minutes}分钟`; + } + + formatFileSize(bytes) { + if (!bytes) return '0 B'; + + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]; + } + + // 域名文件操作方法 + async viewDomainFile(filename) { + try { + const response = await this.apiCall(`/domain-files/${filename}`); + if (response.success) { + alert(`文件内容:\n\n${response.data.content}`); + } + } catch (error) { + this.showMessage('查看文件失败: ' + error.message, 'error'); + } + } + + async editDomainFile(filename) { + this.showMessage('编辑功能正在开发中', 'warning'); + } + + async deleteDomainFile(filename) { + if (!confirm(`确定要删除文件 ${filename} 吗?`)) return; + + try { + const response = await this.apiCall(`/domain-files/${filename}`, { + method: 'DELETE' + }); + if (response.success) { + this.showMessage('文件删除成功'); + this.loadDomainFiles(); + } + } catch (error) { + this.showMessage('删除文件失败: ' + error.message, 'error'); + } + } +} + +// 全局函数,供 HTML 中的按钮调用 +async function reloadConfig() { + try { + const response = await app.apiCall('/config/reload', { method: 'POST' }); + if (response.success) { + app.showMessage('配置重载成功'); + } + } catch (error) { + app.showMessage('配置重载失败: ' + error.message, 'error'); + } +} + +async function flushCache() { + try { + const response = await app.apiCall('/cache/flush', { method: 'POST' }); + if (response.success) { + app.showMessage('缓存清空成功'); + } + } catch (error) { + app.showMessage('缓存清空失败: ' + error.message, 'error'); + } +} + +async function refreshStats() { + app.loadDashboardData(); + app.showMessage('统计信息已刷新'); +} + +async function restartService() { + if (!confirm('确定要重启服务吗?服务将在 3 秒后重启。')) return; + + try { + const response = await app.apiCall('/system/restart', { method: 'POST' }); + if (response.success) { + app.showMessage('重启请求已发送,服务将在 3 秒后重启', 'success'); + } + } catch (error) { + app.showMessage('重启失败: ' + error.message, 'error'); + } +} + +// MikroTik 管理函数 +async function saveMikrotikConfig() { + const tag = document.getElementById('mikrotik-tag').value.trim(); + const host = document.getElementById('mikrotik-host').value.trim(); + const port = document.getElementById('mikrotik-port').value.trim(); + const username = document.getElementById('mikrotik-username').value.trim(); + const password = document.getElementById('mikrotik-password').value; + const addresslist = document.getElementById('mikrotik-addresslist').value.trim(); + const domainFilePath = document.getElementById('mikrotik-domains').value.trim(); + + // 验证必填字段 + if (!tag) { + app.showMessage('请填写配置标签', 'error'); + return; + } + if (!host) { + app.showMessage('请填写 MikroTik 地址', 'error'); + return; + } + if (!username) { + app.showMessage('请填写用户名', 'error'); + return; + } + if (!password) { + app.showMessage('请填写密码', 'error'); + return; + } + if (!addresslist) { + app.showMessage('请填写地址列表名', 'error'); + return; + } + if (!domainFilePath) { + app.showMessage('请填写域名文件路径', 'error'); + return; + } + + // 构建配置对象 + const config = { + tag: tag, + type: 'mikrotik_addresslist', + args: { + domain_files: [domainFilePath], // 使用用户输入的完整路径 + host: host, + port: parseInt(port) || 9728, + username: username, + password: password, + use_tls: false, + timeout: 3, + address_list4: addresslist, + mask4: 24, + comment: `${addresslist}-AutoAdd`, + timeout_addr: 43200, + cache_ttl: 3600, + verify_add: false, + add_all_ips: true, + max_ips: 50 + } + }; + + try { + const response = await app.apiCall('/mikrotik/add', { + method: 'POST', + body: JSON.stringify(config) + }); + + if (response.success) { + app.showMessage(response.message || 'MikroTik 配置已保存', 'success'); + // 清空表单 + clearMikrotikForm(); + // 刷新列表 + await app.loadMikrotikList(); + } else { + app.showMessage(response.message || '保存失败', 'error'); + } + } catch (error) { + app.showMessage('保存失败: ' + error.message, 'error'); + } +} + +async function deleteMikrotikConfig(tag) { + if (!confirm(`确定要删除 MikroTik 配置 "${tag}" 吗?`)) { + return; + } + + try { + const response = await app.apiCall(`/mikrotik/${encodeURIComponent(tag)}`, { + method: 'DELETE' + }); + + if (response.success) { + app.showMessage(response.message || '配置已删除', 'success'); + // 刷新列表 + await app.loadMikrotikList(); + } else { + app.showMessage(response.message || '删除失败', 'error'); + } + } catch (error) { + app.showMessage('删除失败: ' + error.message, 'error'); + } +} + +function clearMikrotikForm() { + document.getElementById('mikrotik-tag').value = ''; + document.getElementById('mikrotik-host').value = ''; + document.getElementById('mikrotik-port').value = '9728'; + document.getElementById('mikrotik-username').value = 'admin'; + document.getElementById('mikrotik-password').value = ''; + document.getElementById('mikrotik-addresslist').value = ''; + document.getElementById('mikrotik-domains').value = ''; + app.showMessage('表单已清空', 'info'); +} + +// 重新加载 MikroTik 列表 +async function loadMikrotikList() { + await app.loadMikrotikList(); +} + +async function saveConfig() { + const content = document.getElementById('config-editor').value; + try { + const response = await app.apiCall('/config', { + method: 'PUT', + body: JSON.stringify({ content }) + }); + if (response.success) { + app.showMessage('配置保存成功'); + } + } catch (error) { + app.showMessage('配置保存失败: ' + error.message, 'error'); + } +} + +async function validateConfig() { + const content = document.getElementById('config-editor').value; + try { + const response = await app.apiCall('/config/validate', { + method: 'POST', + body: JSON.stringify({ content }) + }); + if (response.success) { + app.showMessage('配置验证通过'); + } + } catch (error) { + app.showMessage('配置验证失败: ' + error.message, 'error'); + } +} + +async function backupConfig() { + try { + const response = await app.apiCall('/config/backup', { method: 'POST' }); + if (response.success) { + app.showMessage('配置备份成功'); + } + } catch (error) { + app.showMessage('配置备份失败: ' + error.message, 'error'); + } +} + +async function addDomainFile() { + app.showMessage('添加文件功能正在开发中', 'warning'); +} + +async function refreshDomainFiles() { + app.loadDomainFiles(); + app.showMessage('域名文件列表已刷新'); +} + +async function clearLogs() { + if (!confirm('确定要清空日志吗?')) return; + + try { + const response = await app.apiCall('/logs/clear', { method: 'POST' }); + if (response.success) { + document.getElementById('logs-content').textContent = ''; + app.showMessage('日志清空成功'); + } + } catch (error) { + app.showMessage('日志清空失败: ' + error.message, 'error'); + } +} + +async function refreshLogs() { + app.loadLogs(); + app.showMessage('日志已刷新'); +} + +async function exportStats() { + app.showMessage('导出功能正在开发中', 'warning'); +} + +async function refreshDetailedStats() { + app.loadDetailedStats(); + app.showMessage('统计信息已刷新'); +} + +// 初始化应用 +let app; +document.addEventListener('DOMContentLoaded', () => { + app = new MosDNSAdmin(); +}); diff --git a/coremain/web/templates/index.html b/coremain/web/templates/index.html new file mode 100644 index 0000000..0f0cc09 --- /dev/null +++ b/coremain/web/templates/index.html @@ -0,0 +1,274 @@ + + + + + + MosDNS 管理面板 + + + + +
+ +
+
+

🌐 MosDNS 管理面板

+
+ 在线 + v1.0.0 +
+
+
+ + + + + +
+ +
+
+
+
+

服务状态

+
+
+
+ 运行状态: + 运行中 +
+
+ 运行时间: + - +
+
+ DNS 端口: + - +
+
+ API 地址: + - +
+
+
+ +
+
+

查询统计

+
+
+
+ 总查询数: + - +
+
+ 缓存命中: + - +
+
+ 平均响应时间: + - +
+
+
+ +
+
+

快速操作

+
+
+
+ + + +
+
+
+
+
+ + +
+ +
+
+

📋 已添加的 MikroTik 配置

+
+ +
+
+
+
+
加载中...
+
+
+
+ + +
+
+

➕ 添加 MikroTik 配置

+
+
+
+
+
+ + + 唯一标识,建议使用 mikrotik_ 前缀 +
+
+ + + MikroTik 中的地址列表名称 +
+
+ +
+
+ + + MikroTik 设备的 IP 地址 +
+
+ + + 默认 API 端口为 9728 +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + + 支持绝对路径和相对路径(相对于运行目录) +
+ +
+ + +
+
+
+
+
+ + +
+
+
+

域名文件管理

+
+ + +
+
+
+
+
加载中...
+
+
+
+
+ + +
+
+
+

系统日志

+
+ + +
+
+
+
+
日志内容将在这里显示...
+
+
+
+
+ + +
+
+
+

详细统计

+
+ + +
+
+
+
+
加载中...
+
+
+
+
+
+
+ + +
+ + + + diff --git a/coremain/web_ui.go b/coremain/web_ui.go new file mode 100644 index 0000000..1db0756 --- /dev/null +++ b/coremain/web_ui.go @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2020-2022, IrineSistiana + * + * This file is part of mosdns. + * + * mosdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * mosdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package coremain + +import ( + "io" + "io/fs" + "net/http" + "strings" + + "github.com/go-chi/chi/v5" + "go.uber.org/zap" +) + +var webUIFS fs.FS + +// SetWebUIFS 设置 Web UI 文件系统(由 main 包调用) +func SetWebUIFS(fsys fs.FS) { + webUIFS = fsys +} + +// registerWebUI 注册 Web 管理界面路由(Vue SPA) +func (m *Mosdns) registerWebUI() { + // 获取 Vue 构建产物 + distFS, err := fs.Sub(webUIFS, "web-ui/dist") + if err != nil { + m.logger.Error("failed to get Vue dist files", zap.Error(err)) + return + } + + // 直接处理所有请求 + m.webMux.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + m.logger.Debug("Web UI request", zap.String("path", path)) + + // API 请求交给 API 路由处理(不应该到这里,但作为保护) + if strings.HasPrefix(path, "/api/") { + http.NotFound(w, r) + return + } + + // 尝试打开文件 + trimmedPath := strings.TrimPrefix(path, "/") + if trimmedPath == "" { + trimmedPath = "index.html" + } + + m.logger.Debug("Trying to open file", zap.String("trimmedPath", trimmedPath)) + file, err := distFS.Open(trimmedPath) + if err == nil { + // 文件存在,直接返回 + defer file.Close() + stat, _ := file.Stat() + if !stat.IsDir() { + m.logger.Debug("Serving file", zap.String("path", trimmedPath)) + http.ServeContent(w, r, path, stat.ModTime(), file.(io.ReadSeeker)) + return + } + } else { + m.logger.Debug("File not found", zap.String("path", trimmedPath), zap.Error(err)) + } + + // 文件不存在或是目录,返回 index.html (SPA 路由) + m.logger.Debug("Serving index.html for SPA") + m.serveVueIndex(w, r, distFS) + }) +} + +// serveVueIndex 提供 Vue SPA 的 index.html +func (m *Mosdns) serveVueIndex(w http.ResponseWriter, r *http.Request, distFS fs.FS) { + // 读取 index.html + indexHTML, err := fs.ReadFile(distFS, "index.html") + if err != nil { + m.logger.Error("failed to read Vue index.html", zap.Error(err)) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + // 设置响应头 + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") + w.Header().Set("Pragma", "no-cache") + w.Header().Set("Expires", "0") + + // 返回 HTML 内容 + w.Write(indexHTML) +} + +// registerWebAPI 注册 Web 管理 API 路由(使用独立的 webMux) +func (m *Mosdns) registerWebAPI() { + // Web API 路由组(在 Web UI 服务器上提供 API 代理) + m.webMux.Route("/api", func(r chi.Router) { + // 服务器信息和状态 + r.Get("/server/info", m.handleWebServerInfo) + r.Get("/server/status", m.handleWebServerStatus) + + // 配置管理 + r.Get("/config", m.handleWebGetConfig) + r.Put("/config", m.handleWebUpdateConfig) + r.Post("/config/reload", m.handleWebReloadConfig) + r.Post("/config/validate", m.handleWebValidateConfig) + r.Post("/config/backup", m.handleWebBackupConfig) + + // 域名文件管理 + r.Get("/domain-files", m.handleWebListDomainFiles) + r.Get("/domain-files/{filename}", m.handleWebGetDomainFile) + r.Put("/domain-files/{filename}", m.handleWebUpdateDomainFile) + r.Delete("/domain-files/{filename}", m.handleWebDeleteDomainFile) + + // 插件管理 + r.Get("/plugins", m.handleWebListPlugins) + r.Get("/plugins/{tag}/status", m.handleWebPluginStatus) + + // 统计信息 + r.Get("/stats/detailed", m.handleWebDetailedStats) + + // 日志管理 + r.Get("/logs", m.handleWebGetLogs) + r.Post("/logs/clear", m.handleWebClearLogs) + + // 缓存管理 + r.Post("/cache/flush", m.handleWebFlushCache) + + // MikroTik 管理 + r.Get("/mikrotik/list", m.handleListMikroTik) + r.Post("/mikrotik/add", m.handleAddMikroTik) + r.Delete("/mikrotik/{tag}", m.handleDeleteMikroTik) + + // 系统操作 + r.Post("/system/restart", m.handleSystemRestart) + }) +} + +// Web API 处理函数的简化版本,复用现有的管理 API 逻辑 +func (m *Mosdns) handleWebServerInfo(w http.ResponseWriter, r *http.Request) { + m.handleServerInfo(w, r) +} + +func (m *Mosdns) handleWebServerStatus(w http.ResponseWriter, r *http.Request) { + m.handleServerStatus(w, r) +} + +func (m *Mosdns) handleWebGetConfig(w http.ResponseWriter, r *http.Request) { + m.handleGetConfig(w, r) +} + +func (m *Mosdns) handleWebUpdateConfig(w http.ResponseWriter, r *http.Request) { + m.handleUpdateConfig(w, r) +} + +func (m *Mosdns) handleWebReloadConfig(w http.ResponseWriter, r *http.Request) { + m.handleReloadConfig(w, r) +} + +func (m *Mosdns) handleWebValidateConfig(w http.ResponseWriter, r *http.Request) { + m.handleValidateConfig(w, r) +} + +func (m *Mosdns) handleWebBackupConfig(w http.ResponseWriter, r *http.Request) { + m.handleBackupConfig(w, r) +} + +func (m *Mosdns) handleWebListDomainFiles(w http.ResponseWriter, r *http.Request) { + m.handleListDomainFiles(w, r) +} + +func (m *Mosdns) handleWebGetDomainFile(w http.ResponseWriter, r *http.Request) { + m.handleGetDomainFile(w, r) +} + +func (m *Mosdns) handleWebUpdateDomainFile(w http.ResponseWriter, r *http.Request) { + m.handleUpdateDomainFile(w, r) +} + +func (m *Mosdns) handleWebDeleteDomainFile(w http.ResponseWriter, r *http.Request) { + m.handleDeleteDomainFile(w, r) +} + +func (m *Mosdns) handleWebListPlugins(w http.ResponseWriter, r *http.Request) { + m.handleListPlugins(w, r) +} + +func (m *Mosdns) handleWebPluginStatus(w http.ResponseWriter, r *http.Request) { + m.handlePluginStatus(w, r) +} + +func (m *Mosdns) handleWebDetailedStats(w http.ResponseWriter, r *http.Request) { + // 返回详细统计信息 + stats := map[string]interface{}{ + "totalQueries": 0, + "successfulQueries": 0, + "failedQueries": 0, + "cacheHits": 0, + "cacheMisses": 0, + "avgResponseTime": 0, + } + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Data: stats, + Message: "统计信息获取成功", + }) +} + +func (m *Mosdns) handleWebGetLogs(w http.ResponseWriter, r *http.Request) { + // 返回日志内容(这里可以根据实际需求实现) + logs := map[string]interface{}{ + "content": "MosDNS 日志内容将在这里显示...\n[INFO] MosDNS 服务已启动\n[INFO] 配置文件加载成功\n[INFO] 所有插件已加载完成", + } + + m.writeJSONResponse(w, APIResponse{ + Success: true, + Data: logs, + Message: "日志获取成功", + }) +} + +func (m *Mosdns) handleWebClearLogs(w http.ResponseWriter, r *http.Request) { + // 清空日志(这里可以根据实际需求实现) + m.writeJSONResponse(w, APIResponse{ + Success: true, + Message: "日志清空成功", + }) +} + +func (m *Mosdns) handleWebFlushCache(w http.ResponseWriter, r *http.Request) { + // 清空缓存(这里可以根据实际需求实现) + m.writeJSONResponse(w, APIResponse{ + Success: true, + Message: "缓存清空成功", + }) +} diff --git a/deploy-mikrotik-amazon-updated.md b/deploy-mikrotik-amazon-updated.md deleted file mode 100644 index d5153cb..0000000 --- a/deploy-mikrotik-amazon-updated.md +++ /dev/null @@ -1,192 +0,0 @@ -# MosDNS + MikroTik Amazon 域名处理部署指南(更新版) - -## 功能说明 - -这个配置会在解析 Amazon 相关域名时,自动将解析到的 IP 地址添加到 MikroTik 路由器的 address list 中,用于防火墙规则控制。 - -## 部署步骤 - -### 1. 上传文件到 Debian 12 服务器 - -```bash -# 上传编译好的 mosdns 可执行文件 -scp mosdns-linux-amd64 user@your-server:/usr/local/bin/mosdns - -# 上传配置文件 -scp config.yaml user@your-server:/opt/mosdns/ -scp dns.yaml user@your-server:/opt/mosdns/ - -# 设置执行权限 -ssh user@your-server "chmod +x /usr/local/bin/mosdns" -``` - -### 2. 创建必要的目录和文件 - -```bash -# 创建配置目录 -sudo mkdir -p /opt/mosdns/config - -# 下载 Amazon 域名列表 -sudo wget -O /opt/mosdns/config/geosite_amazon.txt https://raw.githubusercontent.com/v2fly/domain-list-community/master/data/amazon -sudo wget -O /opt/mosdns/config/geosite_amazon-ads.txt https://raw.githubusercontent.com/v2fly/domain-list-community/master/data/amazon-ads -sudo wget -O /opt/mosdns/config/geosite_amazontrust.txt https://raw.githubusercontent.com/v2fly/domain-list-community/master/data/amazontrust -sudo wget -O /opt/mosdns/config/amazon.txt https://raw.githubusercontent.com/v2fly/domain-list-community/master/data/amazon - -# 下载其他必要的域名和 IP 文件 -sudo wget -O /opt/mosdns/config/geosite_tiktok.txt https://raw.githubusercontent.com/v2fly/domain-list-community/master/data/tiktok -sudo wget -O /opt/mosdns/config/gfwlist.out.txt https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt -sudo wget -O /opt/mosdns/config/domains.txt https://raw.githubusercontent.com/v2fly/domain-list-community/master/data/category-games -sudo wget -O /opt/mosdns/config/cn.txt https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat -``` - -### 3. 在 MikroTik 中创建 address list - -```bash -# 通过 SSH 连接到 MikroTik 路由器 -ssh admin@10.248.0.1 - -# 创建 IPv4 和 IPv6 address list -/ip firewall address-list add list=AmazonIP -/ip firewall address-list add list=AmazonIP6 - -# 创建防火墙规则(可选) -/ip firewall filter add chain=forward src-address-list=AmazonIP action=drop comment="Block Amazon IPs" -/ip firewall filter add chain=forward src-address-list=AmazonIP6 action=drop comment="Block Amazon IPv6 IPs" -``` - -### 4. 修改配置文件中的 MikroTik 连接信息 - -编辑 `/opt/mosdns/dns.yaml` 文件,确认 mikrotik_amazon 插件的配置: - -```yaml -# 当前配置(根据你的实际情况修改) -args: "10.248.0.1:9728:admin:szn0s!nw@pwd():false:10:AmazonIP:AmazonIP6:24:32:AmazonIP:86400" -``` - -参数说明: -- `10.248.0.1`: MikroTik 路由器 IP -- `9728`: API 端口 -- `admin`: 用户名 -- `szn0s!nw@pwd()`: 密码 -- `false`: 不使用 TLS -- `10`: 连接超时时间 -- `AmazonIP`: IPv4 address list 名称 -- `AmazonIP6`: IPv6 address list 名称 -- `24`: IPv4 掩码 -- `32`: IPv6 掩码 -- `AmazonIP`: 注释 -- `86400`: 地址超时时间(24小时) - -### 5. 创建 systemd 服务 - -```bash -sudo tee /etc/systemd/system/mosdns.service > /dev/null < /dev/null <nul +echo. +echo ==================================== +echo MosDNS Vue 开发模式 +echo ==================================== +echo. +echo 📌 提示: +echo - 终端 1: 运行此脚本(Vue 开发服务器) +echo - 终端 2: 运行 Go 后端 +echo go run main.go start -c config.yaml +echo. +echo 然后访问: http://localhost:5173 +echo. +echo ==================================== +echo. + +cd web-ui + +if not exist "node_modules\" ( + echo 📦 首次运行,正在安装依赖... + call npm install +) + +echo 🚀 启动 Vue 开发服务器... +npm run dev + diff --git a/dist/mosdns-linux-amd64 b/dist/mosdns-linux-amd64 new file mode 100644 index 0000000..ad355ce Binary files /dev/null and b/dist/mosdns-linux-amd64 differ diff --git a/dns-example-gfw.yaml b/dns-example-gfw.yaml deleted file mode 100644 index 5794517..0000000 --- a/dns-example-gfw.yaml +++ /dev/null @@ -1,55 +0,0 @@ -################ DNS Plugins ################# -plugins: - - - tag: mikrotik-one - type: forward - args: - concurrent: 1 - upstreams: - - addr: "udp://10.248.0.1" - - - tag: cn-dns - type: forward - args: - concurrent: 6 - upstreams: - - addr: "udp://202.96.128.86" - - addr: "udp://202.96.128.166" - - addr: "udp://119.29.29.29" - - addr: "udp://223.5.5.5" - - addr: "udp://114.114.114.114" - - addr: "udp://180.76.76.76" - - - tag: jp-dns - type: forward - args: - concurrent: 4 - upstreams: - - addr: "tls://1dot1dot1dot1.cloudflare-dns.com" - dial_addr: "1.1.1.1" - enable_pipeline: true - - addr: "tls://1dot1dot1dot1.cloudflare-dns.com" - dial_addr: "1.0.0.1" - enable_pipeline: true - - addr: "tls://dns.google" - dial_addr: "8.8.8.8" - enable_pipeline: true - - addr: "tls://dns.google" - dial_addr: "8.8.4.4" - enable_pipeline: true - - # MikroTik Address List 插件 - 处理 Amazon 相关域名 - # 示例:将地址列表改为 gfw - - tag: mikrotik_amazon - type: mikrotik_addresslist - args: - host: "10.248.0.1" - port: 9728 - username: "admin" - password: "szn0s!nw@pwd()" - use_tls: false - timeout: 10 - address_list4: "gfw" # 改为 gfw,插件会自动创建这个地址列表 - mask4: 24 - comment: "amazon_domain" - timeout_addr: 86400 \ No newline at end of file diff --git a/dns-memory-optimized.yaml b/dns-memory-optimized.yaml deleted file mode 100644 index 2b23574..0000000 --- a/dns-memory-optimized.yaml +++ /dev/null @@ -1,93 +0,0 @@ -################ DNS Plugins - 内存缓存优化版 ################# -# 🚀 核心优化: -# 1. 程序启动时从MikroTik加载现有IP到内存 -# 2. 完全移除验证功能 -# 3. 内存判断IP存在性,避免重复写入 -# 4. 使用/24网段掩码减少条目数量 - -plugins: - - - tag: mikrotik-one - type: forward - args: - concurrent: 1 - upstreams: - - addr: "udp://10.248.0.1" - - - tag: cn-dns - type: forward - args: - concurrent: 6 - upstreams: - - addr: "udp://202.96.128.86" - - addr: "udp://202.96.128.166" - - addr: "udp://119.29.29.29" - - addr: "udp://223.5.5.5" - - addr: "udp://114.114.114.114" - - addr: "udp://180.76.76.76" - - - tag: jp-dns - type: forward - args: - concurrent: 4 - upstreams: - - addr: "tls://1dot1dot1dot1.cloudflare-dns.com" - dial_addr: "1.1.1.1" - enable_pipeline: true - - addr: "tls://1dot1dot1dot1.cloudflare-dns.com" - dial_addr: "1.0.0.1" - enable_pipeline: true - - addr: "tls://dns.google" - dial_addr: "8.8.8.8" - enable_pipeline: true - - addr: "tls://dns.google" - dial_addr: "8.8.4.4" - enable_pipeline: true - - # 🚀 MikroTik Address List 插件 - 内存缓存优化配置 - - tag: mikrotik_amazon - type: mikrotik_addresslist - args: - host: "10.248.0.1" - port: 9728 - username: "admin" - password: "szn0s!nw@pwd()" - use_tls: false - timeout: 3 # 🚀 快速连接超时 - - # 地址列表配置 - address_list4: "gfw" # IPv4地址列表名 - address_list6: "gfw6" # IPv6地址列表名(可选) - - # 🚀 核心优化:网段掩码配置 - mask4: 24 # 使用/24网段,减少条目数量 - mask6: 64 # IPv6使用/64网段 - - # 超时和缓存配置 - comment: "auto-amazon" # 自动添加的注释 - timeout_addr: 43200 # 12小时地址超时 - cache_ttl: 7200 # 2小时内存缓存TTL - - # 🚀 性能优化开关 - verify_add: false # 🔥 完全关闭验证功能 - add_all_ips: true # 启用多IP支持 - max_ips: 15 # 每个域名最多15个IP - - # 🚀 新增:内存缓存优化参数 - preload_existing: true # 启动时预加载现有IP - memory_cache_size: 10000 # 内存缓存最大条目数 - subnet_cache_ttl: 14400 # 网段缓存4小时TTL - - # 工作线程优化 - worker_pool_size: 20 # 增加工作线程池 - batch_size: 25 # 增加批处理大小 - - # 连接优化 - max_retries: 2 # 最大重试次数 - retry_backoff_ms: 100 # 重试退避时间(毫秒) - connection_pool_size: 3 # 连接池大小 - - # 🚀 启动行为配置 - startup_load_timeout: 30 # 启动加载超时时间(秒) - log_cache_stats: true # 记录缓存统计信息 - cleanup_interval: 3600 # 缓存清理间隔(秒) diff --git a/dns-optimized.yaml b/dns-optimized.yaml deleted file mode 100644 index c0eb777..0000000 --- a/dns-optimized.yaml +++ /dev/null @@ -1,66 +0,0 @@ -################ DNS Plugins - 性能优化版 ################# -plugins: - - - tag: mikrotik-one - type: forward - args: - concurrent: 1 - upstreams: - - addr: "udp://10.248.0.1" - - - tag: cn-dns - type: forward - args: - concurrent: 6 - upstreams: - - addr: "udp://202.96.128.86" - - addr: "udp://202.96.128.166" - - addr: "udp://119.29.29.29" - - addr: "udp://223.5.5.5" - - addr: "udp://114.114.114.114" - - addr: "udp://180.76.76.76" - - - tag: jp-dns - type: forward - args: - concurrent: 4 - upstreams: - - addr: "tls://1dot1dot1dot1.cloudflare-dns.com" - dial_addr: "1.1.1.1" - enable_pipeline: true - - addr: "tls://1dot1dot1dot1.cloudflare-dns.com" - dial_addr: "1.0.0.1" - enable_pipeline: true - - addr: "tls://dns.google" - dial_addr: "8.8.8.8" - enable_pipeline: true - - addr: "tls://dns.google" - dial_addr: "8.8.4.4" - enable_pipeline: true - - # MikroTik Address List 插件 - 性能优化配置 - - tag: mikrotik_amazon - type: mikrotik_addresslist - args: - host: "10.248.0.1" - port: 9728 - username: "admin" - password: "szn0s!nw@pwd()" - use_tls: false - timeout: 3 # 🚀 减少连接超时到3秒 - address_list4: "gfw" - mask4: 32 # 🚀 使用/32精确匹配,避免网段冲突 - comment: "auto-amazon" - timeout_addr: 43200 # 🚀 减少地址超时到12小时,提高缓存命中率 - cache_ttl: 7200 # 🚀 减少缓存TTL到2小时,平衡性能和准确性 - verify_add: false # 🚀 关闭验证,显著提升性能 - add_all_ips: true # 🚀 启用多IP支持 - max_ips: 10 # 🚀 限制每个域名最多10个IP,避免过载 - - # 🚀 新增性能优化参数(如果支持的话) - batch_size: 20 # 批处理大小 - worker_pool_size: 15 # 工作线程池大小 - connection_pool_size: 5 # 连接池大小 - retry_max: 2 # 最大重试次数 - retry_backoff: 100 # 重试退避时间(ms) - enable_pipelining: true # 启用管道化处理 diff --git a/dns.yaml b/dns.yaml deleted file mode 100644 index 313605a..0000000 --- a/dns.yaml +++ /dev/null @@ -1,60 +0,0 @@ -################ DNS Plugins ################# -plugins: - - - tag: mikrotik-one - type: forward - args: - concurrent: 1 - upstreams: - - addr: "udp://10.248.0.1" - - - - tag: cn-dns - type: forward - args: - concurrent: 6 - upstreams: - - addr: "udp://202.96.128.86" - - addr: "udp://202.96.128.166" - - addr: "udp://119.29.29.29" - - addr: "udp://223.5.5.5" - - addr: "udp://114.114.114.114" - - addr: "udp://180.76.76.76" - - - - tag: jp-dns - type: forward - args: - concurrent: 4 # 同步向 3 条上游并发查询 - upstreams: - - addr: "tls://1dot1dot1dot1.cloudflare-dns.com" - dial_addr: "1.1.1.1" - enable_pipeline: true - - addr: "tls://1dot1dot1dot1.cloudflare-dns.com" - dial_addr: "1.0.0.1" - enable_pipeline: true - - addr: "tls://dns.google" - dial_addr: "8.8.8.8" - enable_pipeline: true - - addr: "tls://dns.google" - dial_addr: "8.8.4.4" - enable_pipeline: true - - # MikroTik Address List 插件 - 性能优化配置 - - tag: mikrotik_amazon - type: mikrotik_addresslist - args: - host: "10.248.0.1" - port: 9728 - username: "admin" - password: "szn0s!nw@pwd()" - use_tls: false - timeout: 3 # 🚀 优化:减少连接超时到3秒 - address_list4: "gfw" - mask4: 24 # 🚀 优化:使用/24网段掩码,减少地址条目数量 - comment: "auto-amazon" - timeout_addr: 43200 # 🚀 优化:减少到12小时,提高缓存效率 - cache_ttl: 7200 # 🚀 优化:2小时缓存,平衡性能和准确性 - verify_add: false # 🚀 优化:关闭验证,显著提升性能 - add_all_ips: true # 🚀 优化:启用多IP支持 - max_ips: 10 # 🚀 优化:限制每域名最多10个IP \ No newline at end of file diff --git a/go.mod b/go.mod index 982d2af..302c1df 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( golang.org/x/sys v0.24.0 golang.org/x/time v0.6.0 google.golang.org/protobuf v1.34.2 + gopkg.in/yaml.v3 v3.0.1 ) replace github.com/nadoo/ipset v0.5.0 => github.com/IrineSistiana/ipset v0.5.1-0.20220703061533-6e0fc3b04c0a @@ -67,5 +68,4 @@ require ( golang.org/x/text v0.17.0 // indirect golang.org/x/tools v0.24.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/main.go b/main.go index 70b83ee..39d72c9 100644 --- a/main.go +++ b/main.go @@ -21,12 +21,13 @@ package main import ( "fmt" + _ "net/http/pprof" + "github.com/IrineSistiana/mosdns/v5/coremain" "github.com/IrineSistiana/mosdns/v5/mlog" _ "github.com/IrineSistiana/mosdns/v5/plugin" _ "github.com/IrineSistiana/mosdns/v5/tools" "github.com/spf13/cobra" - _ "net/http/pprof" ) var ( @@ -34,6 +35,10 @@ var ( ) func init() { + // 设置 Web UI 文件系统 + coremain.SetWebUIFS(WebUIFS) + + // 添加 version 子命令 coremain.AddSubCmd(&cobra.Command{ Use: "version", Short: "Print out version info and exit.", diff --git a/optimized-config-final.yaml b/optimized-config-final.yaml deleted file mode 100644 index 011e5cb..0000000 --- a/optimized-config-final.yaml +++ /dev/null @@ -1,243 +0,0 @@ -# ============================================ -# MosDNS v5 最终优化配置 -# 基于增强的 mikrotik_addresslist 插件 -# ============================================ - -log: - level: info - -# 管理 API -api: - http: "0.0.0.0:5535" - -plugins: - # ========= 基础组件 ========= - - # GFW 域名列表(仅用于分流,不写入设备) - - tag: GFW_domains - type: domain_set - args: - files: - - "/usr/local/jinlingma/config/gfwlist.out.txt" - - # 中国大陆 IP 列表 - - tag: geoip_cn - type: ip_set - args: - files: - - "/usr/local/jinlingma/config/cn.txt" - - # 缓存 - - tag: cache - type: cache - args: - size: 32768 - lazy_cache_ttl: 43200 - - # ========= 上游 DNS 定义 ========= - - # 国内 DNS - - tag: china-dns - type: forward - args: - concurrent: 6 - upstreams: - - addr: "udp://202.96.128.86" - - addr: "udp://202.96.128.166" - - addr: "udp://119.29.29.29" - - addr: "udp://223.5.5.5" - - addr: "udp://114.114.114.114" - - addr: "udp://180.76.76.76" - - # 国外 DNS(DoT) - - tag: overseas-dns - type: forward - args: - concurrent: 4 - upstreams: - - addr: "tls://1dot1dot1dot1.cloudflare-dns.com" - dial_addr: "1.1.1.1" - enable_pipeline: true - - addr: "tls://1dot1dot1dot1.cloudflare-dns.com" - dial_addr: "1.0.0.1" - enable_pipeline: true - - addr: "tls://dns.google" - dial_addr: "8.8.8.8" - enable_pipeline: true - - addr: "tls://dns.google" - dial_addr: "8.8.4.4" - enable_pipeline: true - - # fallback 封装 - - tag: forward_local - type: fallback - args: - primary: china-dns - secondary: china-dns - threshold: 500 - always_standby: true - - - tag: forward_remote - type: fallback - args: - primary: overseas-dns - secondary: overseas-dns - threshold: 500 - always_standby: true - - # 便捷封装:国内/国外 - - tag: forward_local_upstream - type: sequence - args: - - exec: prefer_ipv4 - - exec: query_summary forward_local - - exec: $forward_local - - - tag: forward_remote_upstream - type: sequence - args: - - exec: prefer_ipv4 - - exec: query_summary forward_remote - - exec: $forward_remote - - # ========= 🚀 增强的 MikroTik 插件(支持多设备多规则)========= - - # 设备 A:Amazon 相关域名 - - tag: mikrotik_amazon - type: mikrotik_addresslist - domain_files: - - "/usr/local/jinlingma/config/amazon.txt" - - "/usr/local/jinlingma/config/aws.txt" - args: - host: "10.96.1.22" - port: 9728 - username: "admin" - password: "szn0s!nw@pwd()" - use_tls: false - timeout: 3 - address_list4: "Amazon" - address_list6: "Amazon6" - mask4: 24 # 使用/24网段,减少条目数量 - mask6: 64 - comment: "Amazon-AutoAdd" - timeout_addr: 43200 # 12小时 - cache_ttl: 3600 # 1小时缓存 - verify_add: false # 关闭验证,提升性能 - add_all_ips: true # 添加所有IP - max_ips: 20 # 限制每域名最多20个IP - - # 设备 B:Google 相关域名 - - tag: mikrotik_google - type: mikrotik_addresslist - domain_files: - - "/usr/local/jinlingma/config/google.txt" - - "/usr/local/jinlingma/config/youtube.txt" - args: - host: "10.96.1.23" - port: 9728 - username: "admin" - password: "szn0s!nw@pwd()" - use_tls: false - timeout: 3 - address_list4: "Google" - mask4: 32 # 精确匹配单个IP - comment: "Google-AutoAdd" - timeout_addr: 21600 # 6小时 - cache_ttl: 1800 # 30分钟缓存 - verify_add: false - add_all_ips: true - max_ips: 15 - - # 设备 C:流媒体相关域名 - - tag: mikrotik_streaming - type: mikrotik_addresslist - domain_files: - - "/usr/local/jinlingma/config/netflix.txt" - - "/usr/local/jinlingma/config/disney.txt" - args: - host: "10.96.1.24" - port: 9728 - username: "admin" - password: "szn0s!nw@pwd()" - use_tls: false - timeout: 5 # 流媒体可能需要更长时间 - address_list4: "Streaming" - mask4: 32 - comment: "Streaming-AutoAdd" - timeout_addr: 21600 # 6小时(流媒体IP变化较频繁) - cache_ttl: 1800 # 30分钟缓存 - verify_add: false - add_all_ips: true - max_ips: 30 # 流媒体服务IP较多 - - # ========= 查询逻辑 ========= - - # 检查是否有响应 - - tag: has_resp_sequence - type: sequence - args: - - matches: has_resp - exec: accept - - # 拒绝无效查询 - - tag: reject_invalid - type: sequence - args: - - matches: qtype 65 - exec: reject 3 - - # GFW 域名分流(仅解析,不写入设备) - - tag: gfw_routing_only - type: sequence - args: - - matches: qname $GFW_domains - exec: $forward_remote_upstream - - exec: query_summary gfw_overseas_routing - - # 智能 fallback 处理 - - tag: smart_fallback_handler - type: sequence - args: - - exec: prefer_ipv4 - - exec: $forward_local_upstream - - matches: resp_ip $geoip_cn - exec: accept - - exec: $forward_remote_upstream - - exec: query_summary fallback_to_overseas - - # 🚀 主序列(极简版) - - tag: main_sequence - type: sequence - args: - # 1. 缓存检查 - - exec: $cache - - # 2. 拒绝无效查询 - - exec: $reject_invalid - - exec: jump has_resp_sequence - - # 3. GFW 域名分流(仅解析) - - exec: $gfw_routing_only - - exec: jump has_resp_sequence - - # 4. 智能 fallback - - exec: $smart_fallback_handler - - exec: jump has_resp_sequence - - # 5. 🚀 MikroTik 设备处理(每个插件自动匹配域名) - - exec: $mikrotik_amazon # 自动处理 Amazon 域名 - - exec: $mikrotik_google # 自动处理 Google 域名 - - exec: $mikrotik_streaming # 自动处理流媒体域名 - - # ========= 服务监听 ========= - - tag: udp_server - type: udp_server - args: - entry: main_sequence - listen: ":5322" - - - tag: tcp_server - type: tcp_server - args: - entry: main_sequence - listen: ":5322" diff --git a/v2dat.sh b/v2dat.sh new file mode 100644 index 0000000..86669cb --- /dev/null +++ b/v2dat.sh @@ -0,0 +1,130 @@ +#!/bin/sh +set -e # 如果任何命令失败则退出 + +# 获取脚本所在目录 +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +TMPDIR=$(mktemp -d) +trap 'rm -rf "$TMPDIR"' EXIT # 确保脚本退出时删除临时目录 + +# 创建必要的目录 +mkdir -p "$SCRIPT_DIR/geo" +mkdir -p "$SCRIPT_DIR/geosite" +mkdir -p "$SCRIPT_DIR/geoip" +mkdir -p "$SCRIPT_DIR/config" + +# 下载 geoip 和 geosite 数据文件到 geo 目录 +download_geodata() { + echo "正在下载 geoip.dat..." + curl --connect-timeout 5 -m 60 -kfSL -o "$SCRIPT_DIR/geo/geoip.dat" "https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/geoip.dat" + + echo "正在下载 geosite.dat..." + curl --connect-timeout 5 -m 60 -kfSL -o "$SCRIPT_DIR/geo/geosite.dat" "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat" + + echo "正在下载 CN-ip-cidr.txt" + curl --connect-timeout 5 -m 60 -kfSL -o "$SCRIPT_DIR/config/CN-ip-cidr.txt" "https://raw.githubusercontent.com/Hackl0us/GeoIP2-CN/release/CN-ip-cidr.txt" + + echo "下载完成" +} + +# 下载 v2dat 工具(如果不存在) +download_v2dat() { + if [ ! -f "$SCRIPT_DIR/v2dat" ]; then + echo "正在下载 v2dat 工具..." + curl -fSL -o "$SCRIPT_DIR/v2dat" "https://raw.githubusercontent.com/xukecheng/scripts/main/v2dat" + chmod +x "$SCRIPT_DIR/v2dat" + echo "v2dat 工具下载完成" + else + echo "v2dat 工具已存在" + fi +} + +# 过滤 IPv4 地址(去掉 IPv6) +filter_ipv4_only() { + local input_file="$1" + local output_file="$2" + + if [ ! -f "$input_file" ]; then + echo "警告: 文件 $input_file 不存在,跳过过滤" + return 1 + fi + + echo "正在过滤 IPv4 地址(去掉 IPv6)..." + echo "输入文件: $input_file" + echo "输出文件: $output_file" + + # 统计原始行数 + original_count=$(wc -l < "$input_file" 2>/dev/null || echo "0") + + # 过滤 IPv4 地址: + # 1. 匹配 IPv4 CIDR 格式 (x.x.x.x/xx) + # 2. 排除包含冒号的 IPv6 地址 + # 3. 排除空行和注释行 + grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(/[0-9]{1,2})?$' "$input_file" > "$output_file" 2>/dev/null || { + echo "错误: 过滤 IPv4 地址失败" + return 1 + } + + # 统计过滤后行数 + filtered_count=$(wc -l < "$output_file" 2>/dev/null || echo "0") + removed_count=$((original_count - filtered_count)) + + echo "过滤完成:" + echo " 原始条目: $original_count" + echo " IPv4 条目: $filtered_count" + echo " 移除条目: $removed_count (IPv6 和无效条目)" + + return 0 +} + +# 使用 v2dat 工具解包数据 +unpack_geodata() { + echo "正在解包 geosite.dat..." + "$SCRIPT_DIR/v2dat" unpack geosite "$SCRIPT_DIR/geo/geosite.dat" -o "$SCRIPT_DIR/geosite" + + echo "正在解包 geoip.dat CN 数据..." + "$SCRIPT_DIR/v2dat" unpack geoip "$SCRIPT_DIR/geo/geoip.dat" -o "$SCRIPT_DIR/geoip" -f cn + + # 🆕 新增:过滤 IPv4 地址,去掉 IPv6 + local geoip_cn_file="$SCRIPT_DIR/geoip/geoip_cn.txt" + local geoip_cn_ipv4_file="$SCRIPT_DIR/config/cn.txt" + + if [ -f "$geoip_cn_file" ]; then + echo "" + echo "🔄 正在处理 CN IP 数据..." + filter_ipv4_only "$geoip_cn_file" "$geoip_cn_ipv4_file" + + if [ $? -eq 0 ]; then + echo "✅ CN IPv4 地址列表已生成: $geoip_cn_ipv4_file" + echo " 该文件可直接用于 MosDNS 配置中的 geoip_cn" + else + echo "❌ 处理 CN IP 数据失败" + fi + else + echo "⚠️ 警告: 未找到 $geoip_cn_file 文件" + fi + + echo "" + echo "解包完成" +} + +# 主流程 +echo "开始处理..." +download_geodata +download_v2dat +unpack_geodata + +echo "" +echo "🎉 所有操作完成!" +echo "" +echo "📁 生成的文件:" +echo " ├── geo/geoip.dat (原始 geoip 数据)" +echo " ├── geo/geosite.dat (原始 geosite 数据)" +echo " ├── geoip/geoip_cn.txt (解包的 CN IP 数据,包含 IPv6)" +echo " ├── config/cn.txt (🆕 过滤后的 CN IPv4 数据)" +echo " ├── config/CN-ip-cidr.txt (备用 CN IP 数据)" +echo " └── geosite/ (解包的域名数据)" +echo "" +echo "💡 使用建议:" +echo " - MosDNS 配置中使用: config/cn.txt (仅 IPv4)" +echo " - 如需 IPv6 支持,使用: geoip/geoip_cn.txt" +echo "" \ No newline at end of file diff --git a/web-ui/.editorconfig b/web-ui/.editorconfig new file mode 100644 index 0000000..3b510aa --- /dev/null +++ b/web-ui/.editorconfig @@ -0,0 +1,8 @@ +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] +charset = utf-8 +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +max_line_length = 100 diff --git a/web-ui/.gitattributes b/web-ui/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/web-ui/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/web-ui/.gitignore b/web-ui/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/web-ui/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/web-ui/.prettierrc.json b/web-ui/.prettierrc.json new file mode 100644 index 0000000..29a2402 --- /dev/null +++ b/web-ui/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "singleQuote": true, + "printWidth": 100 +} diff --git a/web-ui/README.md b/web-ui/README.md new file mode 100644 index 0000000..4608bc6 --- /dev/null +++ b/web-ui/README.md @@ -0,0 +1,48 @@ +# web-ui + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Recommended Browser Setup + +- Chromium-based browsers (Chrome, Edge, Brave, etc.): + - [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) + - [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters) +- Firefox: + - [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) + - [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/) + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +npm run build +``` + +### Lint with [ESLint](https://eslint.org/) + +```sh +npm run lint +``` diff --git a/web-ui/env.d.ts b/web-ui/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/web-ui/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/web-ui/eslint.config.ts b/web-ui/eslint.config.ts new file mode 100644 index 0000000..20475f8 --- /dev/null +++ b/web-ui/eslint.config.ts @@ -0,0 +1,22 @@ +import { globalIgnores } from 'eslint/config' +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' +import pluginVue from 'eslint-plugin-vue' +import skipFormatting from '@vue/eslint-config-prettier/skip-formatting' + +// To allow more languages other than `ts` in `.vue` files, uncomment the following lines: +// import { configureVueProject } from '@vue/eslint-config-typescript' +// configureVueProject({ scriptLangs: ['ts', 'tsx'] }) +// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup + +export default defineConfigWithVueTs( + { + name: 'app/files-to-lint', + files: ['**/*.{ts,mts,tsx,vue}'], + }, + + globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), + + pluginVue.configs['flat/essential'], + vueTsConfigs.recommended, + skipFormatting, +) diff --git a/web-ui/index.html b/web-ui/index.html new file mode 100644 index 0000000..9e5fc8f --- /dev/null +++ b/web-ui/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/web-ui/package-lock.json b/web-ui/package-lock.json new file mode 100644 index 0000000..3c6a7be --- /dev/null +++ b/web-ui/package-lock.json @@ -0,0 +1,5736 @@ +{ + "name": "web-ui", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web-ui", + "version": "0.0.0", + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "axios": "^1.12.2", + "element-plus": "^2.11.4", + "pinia": "^3.0.3", + "vue": "^3.5.22", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/node": "^22.18.6", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.6.0", + "@vue/tsconfig": "^0.8.1", + "eslint": "^9.33.0", + "eslint-plugin-vue": "~10.4.0", + "jiti": "^2.5.1", + "npm-run-all2": "^8.0.4", + "prettier": "3.6.2", + "typescript": "~5.9.0", + "vite": "^7.1.7", + "vite-plugin-vue-devtools": "^8.0.2", + "vue-tsc": "^3.1.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@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/core/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/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@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-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/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/@babel/helper-create-class-features-plugin": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/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/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.0.tgz", + "integrity": "sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-decorators": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", + "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", + "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "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/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", + "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", + "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.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==", + "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/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.29", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz", + "integrity": "sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", + "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", + "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", + "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", + "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", + "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", + "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", + "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", + "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", + "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", + "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", + "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", + "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", + "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", + "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", + "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", + "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", + "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", + "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", + "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", + "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", + "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", + "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tsconfig/node22": { + "version": "22.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.2.tgz", + "integrity": "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "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/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "22.18.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", + "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz", + "integrity": "sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/type-utils": "8.46.1", + "@typescript-eslint/utils": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.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.46.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.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.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.1.tgz", + "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", + "debug": "^4.3.4" + }, + "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", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.1.tgz", + "integrity": "sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.1", + "@typescript-eslint/types": "^8.46.1", + "debug": "^4.3.4" + }, + "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.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.1.tgz", + "integrity": "sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.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.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.1.tgz", + "integrity": "sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==", + "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.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.1.tgz", + "integrity": "sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/utils": "8.46.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.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", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.1.tgz", + "integrity": "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==", + "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.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.1.tgz", + "integrity": "sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.1", + "@typescript-eslint/tsconfig-utils": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "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.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.1.tgz", + "integrity": "sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.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", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.1.tgz", + "integrity": "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.1", + "eslint-visitor-keys": "^4.2.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/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", + "integrity": "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.29" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.23.tgz", + "integrity": "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.23" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.23.tgz", + "integrity": "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.23.tgz", + "integrity": "sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.5.0.tgz", + "integrity": "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.5.0.tgz", + "integrity": "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", + "@vue/babel-helper-vue-transform-on": "1.5.0", + "@vue/babel-plugin-resolve-type": "1.5.0", + "@vue/shared": "^3.5.18" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.5.0.tgz", + "integrity": "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/parser": "^7.28.0", + "@vue/compiler-sfc": "^3.5.18" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz", + "integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.4", + "@vue/shared": "3.5.22", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz", + "integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz", + "integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.4", + "@vue/compiler-core": "3.5.22", + "@vue/compiler-dom": "3.5.22", + "@vue/compiler-ssr": "3.5.22", + "@vue/shared": "3.5.22", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.19", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz", + "integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz", + "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.7" + } + }, + "node_modules/@vue/devtools-core": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-8.0.2.tgz", + "integrity": "sha512-V7eKTTHoS6KfK8PSGMLZMhGv/9yNDrmv6Qc3r71QILulnzPnqK2frsTyx3e2MrhdUZnENPEm6hcb4z0GZOqNhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.0.2", + "@vue/devtools-shared": "^8.0.2", + "mitt": "^3.0.1", + "nanoid": "^5.1.5", + "pathe": "^2.0.3", + "vite-hot-client": "^2.1.0" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-core/node_modules/@vue/devtools-kit": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.2.tgz", + "integrity": "sha512-yjZKdEmhJzQqbOh4KFBfTOQjDPMrjjBNCnHBvnTGJX+YLAqoUtY2J+cg7BE+EA8KUv8LprECq04ts75wCoIGWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.0.2", + "birpc": "^2.5.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^2.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-core/node_modules/@vue/devtools-shared": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.2.tgz", + "integrity": "sha512-mLU0QVdy5Lp40PMGSixDw/Kbd6v5dkQXltd2r+mdVQV7iUog2NlZuLxFZApFZ/mObUBDhoCpf0T3zF2FWWdeHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/devtools-core/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@vue/devtools-core/node_modules/perfect-debounce": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", + "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", + "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.7", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", + "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/eslint-config-prettier": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.2.0.tgz", + "integrity": "sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2" + }, + "peerDependencies": { + "eslint": ">= 8.21.0", + "prettier": ">= 3.0.0" + } + }, + "node_modules/@vue/eslint-config-typescript": { + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.6.0.tgz", + "integrity": "sha512-UpiRY/7go4Yps4mYCjkvlIbVWmn9YvPGQDxTAlcKLphyaD77LjIu3plH4Y9zNT0GB4f3K5tMmhhtRhPOgrQ/bQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.35.1", + "fast-glob": "^3.3.3", + "typescript-eslint": "^8.35.1", + "vue-eslint-parser": "^10.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0", + "eslint-plugin-vue": "^9.28.0 || ^10.0.0", + "typescript": ">=4.8.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.1.1.tgz", + "integrity": "sha512-qjMY3Q+hUCjdH+jLrQapqgpsJ0rd/2mAY02lZoHG3VFJZZZKLjAlV+Oo9QmWIT4jh8+Rx8RUGUi++d7T9Wb6Mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.0.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.22.tgz", + "integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.22.tgz", + "integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz", + "integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.22", + "@vue/runtime-core": "3.5.22", + "@vue/shared": "3.5.22", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.22.tgz", + "integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.22", + "@vue/shared": "3.5.22" + }, + "peerDependencies": { + "vue": "3.5.22" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.22.tgz", + "integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.8.1.tgz", + "integrity": "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "license": "MIT", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "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/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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/alien-signals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.0.0.tgz", + "integrity": "sha512-JHoRJf18Y6HN4/KZALr3iU+0vW9LKG+8FMThQlbn4+gv8utsLIkwpomjElGPccGeNwh0FI2HN6BLnyFLo6OyLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "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.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.16", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz", + "integrity": "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/birpc": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.6.1.tgz", + "integrity": "sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.26.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", + "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", + "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.8.9", + "caniuse-lite": "^1.0.30001746", + "electron-to-chromium": "^1.5.227", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001750", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz", + "integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==", + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "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/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "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/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "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/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "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/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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.237", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz", + "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==", + "dev": true, + "license": "ISC" + }, + "node_modules/element-plus": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.11.4.tgz", + "integrity": "sha512-sLq+Ypd0cIVilv8wGGMEGvzRVBBsRpJjnAS5PsI/1JU1COZXqzH3N1UYMUc/HCdvdjf6dfrBy80Sj7KcACsT7w==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.17.20", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.13", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "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/esbuild": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" + } + }, + "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-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "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": "9.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", + "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.4.0", + "@eslint/core": "^0.16.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.37.0", + "@eslint/plugin-kit": "^0.4.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.4.0.tgz", + "integrity": "sha512-K6tP0dW8FJVZLQxa2S7LcE1lLw3X8VvB3t887Q6CLrFVxHYBXGANbXvwNzYIu6Ughx1bSJ5BDT0YB3ybPT39lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "vue-eslint-parser": "^10.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/parser": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "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/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "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/execa": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", + "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "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-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "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.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "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/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "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/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "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/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "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/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "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/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/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "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/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/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "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/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "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/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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.23", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", + "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all2": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-8.0.4.tgz", + "integrity": "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "picomatch": "^4.0.2", + "pidtree": "^0.6.0", + "read-package-json-fast": "^4.0.0", + "shell-quote": "^1.7.3", + "which": "^5.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^20.5.0 || >=22.0.0", + "npm": ">= 10" + } + }, + "node_modules/npm-run-all2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm-run-all2/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm-run-all2/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/npm-run-all2/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "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/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pinia": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz", + "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "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/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "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/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/read-package-json-fast": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", + "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", + "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.4", + "@rollup/rollup-android-arm64": "4.52.4", + "@rollup/rollup-darwin-arm64": "4.52.4", + "@rollup/rollup-darwin-x64": "4.52.4", + "@rollup/rollup-freebsd-arm64": "4.52.4", + "@rollup/rollup-freebsd-x64": "4.52.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", + "@rollup/rollup-linux-arm-musleabihf": "4.52.4", + "@rollup/rollup-linux-arm64-gnu": "4.52.4", + "@rollup/rollup-linux-arm64-musl": "4.52.4", + "@rollup/rollup-linux-loong64-gnu": "4.52.4", + "@rollup/rollup-linux-ppc64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-musl": "4.52.4", + "@rollup/rollup-linux-s390x-gnu": "4.52.4", + "@rollup/rollup-linux-x64-gnu": "4.52.4", + "@rollup/rollup-linux-x64-musl": "4.52.4", + "@rollup/rollup-openharmony-arm64": "4.52.4", + "@rollup/rollup-win32-arm64-msvc": "4.52.4", + "@rollup/rollup-win32-ia32-msvc": "4.52.4", + "@rollup/rollup-win32-x64-gnu": "4.52.4", + "@rollup/rollup-win32-x64-msvc": "4.52.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/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/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "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": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.1.tgz", + "integrity": "sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.46.1", + "@typescript-eslint/parser": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/utils": "8.46.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", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", + "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-utils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "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/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.1.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz", + "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "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", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.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 + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "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/vite-dev-rpc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-1.1.0.tgz", + "integrity": "sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==", + "dev": true, + "license": "MIT", + "dependencies": { + "birpc": "^2.4.0", + "vite-hot-client": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0" + } + }, + "node_modules/vite-hot-client": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.1.0.tgz", + "integrity": "sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-inspect": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.3.3.tgz", + "integrity": "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.1.0", + "debug": "^4.4.1", + "error-stack-parser-es": "^1.0.5", + "ohash": "^2.0.11", + "open": "^10.2.0", + "perfect-debounce": "^2.0.0", + "sirv": "^3.0.1", + "unplugin-utils": "^0.3.0", + "vite-dev-rpc": "^1.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-inspect/node_modules/perfect-debounce": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", + "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-8.0.2.tgz", + "integrity": "sha512-1069qvMBcyAu3yXQlvYrkwoyLOk0lSSR/gTKy/vy+Det7TXnouGei6ZcKwr5TIe938v/14oLlp0ow6FSJkkORA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^8.0.2", + "@vue/devtools-kit": "^8.0.2", + "@vue/devtools-shared": "^8.0.2", + "execa": "^9.6.0", + "sirv": "^3.0.2", + "vite-plugin-inspect": "^11.3.3", + "vite-plugin-vue-inspector": "^5.3.2" + }, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-vue-devtools/node_modules/@vue/devtools-kit": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.2.tgz", + "integrity": "sha512-yjZKdEmhJzQqbOh4KFBfTOQjDPMrjjBNCnHBvnTGJX+YLAqoUtY2J+cg7BE+EA8KUv8LprECq04ts75wCoIGWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.0.2", + "birpc": "^2.5.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^2.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/vite-plugin-vue-devtools/node_modules/@vue/devtools-shared": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.2.tgz", + "integrity": "sha512-mLU0QVdy5Lp40PMGSixDw/Kbd6v5dkQXltd2r+mdVQV7iUog2NlZuLxFZApFZ/mObUBDhoCpf0T3zF2FWWdeHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/vite-plugin-vue-devtools/node_modules/perfect-debounce": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", + "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.2.tgz", + "integrity": "sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vite/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/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz", + "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.22", + "@vue/compiler-sfc": "3.5.22", + "@vue/runtime-dom": "3.5.22", + "@vue/server-renderer": "3.5.22", + "@vue/shared": "3.5.22" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-eslint-parser": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz", + "integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.6.0", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-router": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.0.tgz", + "integrity": "sha512-YRrWLi4ayHe1d6zyH6sMPwF/WwcDY8XgUOfQGa0Kx4kmugSorLavD1ExrM/Y83B4X2NQMXYpJFSq2pbZh9ildQ==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.1.1.tgz", + "integrity": "sha512-fyixKxFniOVgn+L/4+g8zCG6dflLLt01Agz9jl3TO45Bgk87NZJRmJVPsiK+ouq3LB91jJCbOV+pDkzYTxbI7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.23", + "@vue/language-core": "3.1.1" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "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/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "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/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/web-ui/package.json b/web-ui/package.json new file mode 100644 index 0000000..0ec2462 --- /dev/null +++ b/web-ui/package.json @@ -0,0 +1,43 @@ +{ + "name": "web-ui", + "version": "0.0.0", + "private": true, + "type": "module", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build", + "lint": "eslint . --fix", + "format": "prettier --write src/" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "axios": "^1.12.2", + "element-plus": "^2.11.4", + "pinia": "^3.0.3", + "vue": "^3.5.22", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/node": "^22.18.6", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.6.0", + "@vue/tsconfig": "^0.8.1", + "eslint": "^9.33.0", + "eslint-plugin-vue": "~10.4.0", + "jiti": "^2.5.1", + "npm-run-all2": "^8.0.4", + "prettier": "3.6.2", + "typescript": "~5.9.0", + "vite": "^7.1.7", + "vite-plugin-vue-devtools": "^8.0.2", + "vue-tsc": "^3.1.0" + } +} diff --git a/web-ui/public/favicon.ico b/web-ui/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/web-ui/public/favicon.ico differ diff --git a/web-ui/src/App.vue b/web-ui/src/App.vue new file mode 100644 index 0000000..38fc53b --- /dev/null +++ b/web-ui/src/App.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/web-ui/src/api/cache.ts b/web-ui/src/api/cache.ts new file mode 100644 index 0000000..1109915 --- /dev/null +++ b/web-ui/src/api/cache.ts @@ -0,0 +1,8 @@ +// 缓存相关 API +import http from './http' + +export const cacheApi = { + // 清空缓存 + flush: () => http.post('/cache/flush'), +} + diff --git a/web-ui/src/api/domain.ts b/web-ui/src/api/domain.ts new file mode 100644 index 0000000..8e8818c --- /dev/null +++ b/web-ui/src/api/domain.ts @@ -0,0 +1,29 @@ +// 域名文件相关 API +import http from './http' + +export interface DomainFile { + name: string + path: string + size: number + line_count: number + last_modified: string +} + +export const domainApi = { + // 获取域名文件列表 + list: () => http.get('/domain-files'), + + // 获取域名文件内容 + get: (filename: string) => + http.get( + `/domain-files/${encodeURIComponent(filename)}` + ), + + // 更新域名文件内容 + update: (filename: string, content: string) => + http.put( + `/domain-files/${encodeURIComponent(filename)}`, + { content } + ), +} + diff --git a/web-ui/src/api/http.ts b/web-ui/src/api/http.ts new file mode 100644 index 0000000..43d3c89 --- /dev/null +++ b/web-ui/src/api/http.ts @@ -0,0 +1,47 @@ +// HTTP 客户端封装 +import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios' +import { ElMessage } from 'element-plus' + +// 创建 axios 实例 +const http: AxiosInstance = axios.create({ + baseURL: '/api', + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}) + +// 请求拦截器 +http.interceptors.request.use( + (config) => { + return config + }, + (error) => { + return Promise.reject(error) + } +) + +// 响应拦截器 +http.interceptors.response.use( + (response: AxiosResponse) => { + const res = response.data + + // 如果响应包含 success 字段 + if (res.hasOwnProperty('success')) { + if (!res.success) { + ElMessage.error(res.message || '请求失败') + return Promise.reject(new Error(res.message || '请求失败')) + } + } + + return res + }, + (error) => { + const message = error.response?.data?.message || error.message || '网络错误' + ElMessage.error(message) + return Promise.reject(error) + } +) + +export default http + diff --git a/web-ui/src/api/mikrotik.ts b/web-ui/src/api/mikrotik.ts new file mode 100644 index 0000000..b65ed3a --- /dev/null +++ b/web-ui/src/api/mikrotik.ts @@ -0,0 +1,41 @@ +// MikroTik 配置相关 API +import http from './http' + +export interface MikroTikConfig { + tag: string + type: string + args: { + domain_files: string[] + host: string + port: number + username: string + password: string + use_tls: boolean + timeout: number + address_list4: string + mask4: number + comment: string + timeout_addr: number + cache_ttl: number + verify_add: boolean + add_all_ips: boolean + max_ips: number + } +} + +export const mikrotikApi = { + // 获取所有 MikroTik 配置 + list: () => http.get('/mikrotik/list'), + + // 添加 MikroTik 配置 + add: (data: MikroTikConfig) => + http.post( + '/mikrotik/add', + data + ), + + // 删除 MikroTik 配置 + delete: (tag: string) => + http.delete(`/mikrotik/${encodeURIComponent(tag)}`), +} + diff --git a/web-ui/src/api/server.ts b/web-ui/src/api/server.ts new file mode 100644 index 0000000..71353cd --- /dev/null +++ b/web-ui/src/api/server.ts @@ -0,0 +1,47 @@ +// 服务器信息相关 API +import http from './http' + +export interface ServerInfo { + name: string + version: string + start_time: string + uptime: string + uptime_seconds: number + status: string + config_file: string + working_dir: string + plugin_count: number + api_address: string + dns_ports: string[] +} + +export interface ServerStatus { + status: string + totalQueries?: number + cacheHitRate?: number + avgResponseTime?: number +} + +export interface StatsData { + totalQueries: number + successfulQueries: number + failedQueries: number + cacheHits: number + cacheMisses: number + avgResponseTime: number +} + +export const serverApi = { + // 获取服务器信息 + getInfo: () => http.get('/server/info'), + + // 获取服务器状态 + getStatus: () => http.get('/server/status'), + + // 获取详细统计 + getStats: () => http.get('/stats/detailed'), + + // 重启服务 + restart: () => http.post('/system/restart'), +} + diff --git a/web-ui/src/assets/base.css b/web-ui/src/assets/base.css new file mode 100644 index 0000000..8816868 --- /dev/null +++ b/web-ui/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/web-ui/src/assets/logo.svg b/web-ui/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/web-ui/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/web-ui/src/assets/main.css b/web-ui/src/assets/main.css new file mode 100644 index 0000000..880c94a --- /dev/null +++ b/web-ui/src/assets/main.css @@ -0,0 +1,39 @@ +@import './base.css'; + +/* 全局样式重置 - 支持全屏布局 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, +body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + overflow-x: hidden; +} + +#app { + width: 100%; + min-height: 100vh; + margin: 0; + padding: 0; + font-weight: normal; +} + +a, +.green { + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; + padding: 3px; +} + +@media (hover: hover) { + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } +} diff --git a/web-ui/src/components/HelloWorld.vue b/web-ui/src/components/HelloWorld.vue new file mode 100644 index 0000000..d174cf8 --- /dev/null +++ b/web-ui/src/components/HelloWorld.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/web-ui/src/components/TheWelcome.vue b/web-ui/src/components/TheWelcome.vue new file mode 100644 index 0000000..6092dff --- /dev/null +++ b/web-ui/src/components/TheWelcome.vue @@ -0,0 +1,94 @@ + + + diff --git a/web-ui/src/components/WelcomeItem.vue b/web-ui/src/components/WelcomeItem.vue new file mode 100644 index 0000000..6d7086a --- /dev/null +++ b/web-ui/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/web-ui/src/components/icons/IconCommunity.vue b/web-ui/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000..2dc8b05 --- /dev/null +++ b/web-ui/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/web-ui/src/components/icons/IconDocumentation.vue b/web-ui/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000..6d4791c --- /dev/null +++ b/web-ui/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/web-ui/src/components/icons/IconEcosystem.vue b/web-ui/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000..c3a4f07 --- /dev/null +++ b/web-ui/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/web-ui/src/components/icons/IconSupport.vue b/web-ui/src/components/icons/IconSupport.vue new file mode 100644 index 0000000..7452834 --- /dev/null +++ b/web-ui/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/web-ui/src/components/icons/IconTooling.vue b/web-ui/src/components/icons/IconTooling.vue new file mode 100644 index 0000000..660598d --- /dev/null +++ b/web-ui/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/web-ui/src/main.ts b/web-ui/src/main.ts new file mode 100644 index 0000000..22aeb93 --- /dev/null +++ b/web-ui/src/main.ts @@ -0,0 +1,26 @@ +import './assets/main.css' + +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +import zhCn from 'element-plus/es/locale/lang/zh-cn' + +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +// 注册所有 Element Plus 图标 +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} + +app.use(createPinia()) +app.use(router) +app.use(ElementPlus, { + locale: zhCn, +}) + +app.mount('#app') diff --git a/web-ui/src/router/index.ts b/web-ui/src/router/index.ts new file mode 100644 index 0000000..b4c49f2 --- /dev/null +++ b/web-ui/src/router/index.ts @@ -0,0 +1,34 @@ +import { createRouter, createWebHistory } from 'vue-router' +import DashboardView from '../views/DashboardView.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'dashboard', + component: DashboardView, + meta: { title: '仪表板' }, + }, + { + path: '/mikrotik', + name: 'mikrotik', + component: () => import('../views/MikroTikView.vue'), + meta: { title: 'MikroTik 管理' }, + }, + { + path: '/domains', + name: 'domains', + component: () => import('../views/DomainFilesView.vue'), + meta: { title: '域名文件' }, + }, + ], +}) + +// 路由守卫 - 设置页面标题 +router.beforeEach((to, _from, next) => { + document.title = `${to.meta.title || 'MosDNS'} - MosDNS 管理面板` + next() +}) + +export default router diff --git a/web-ui/src/stores/counter.ts b/web-ui/src/stores/counter.ts new file mode 100644 index 0000000..b6757ba --- /dev/null +++ b/web-ui/src/stores/counter.ts @@ -0,0 +1,12 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) diff --git a/web-ui/src/stores/mikrotik.ts b/web-ui/src/stores/mikrotik.ts new file mode 100644 index 0000000..a24a4af --- /dev/null +++ b/web-ui/src/stores/mikrotik.ts @@ -0,0 +1,43 @@ +// MikroTik 配置管理 +import { ref } from 'vue' +import { defineStore } from 'pinia' +import { mikrotikApi, type MikroTikConfig } from '@/api/mikrotik' +import { ElMessage } from 'element-plus' + +export const useMikrotikStore = defineStore('mikrotik', () => { + // 状态 + const configs = ref([]) + const loading = ref(false) + + // 方法 + const fetchConfigs = async () => { + loading.value = true + try { + const res = await mikrotikApi.list() + configs.value = res.data || [] + } finally { + loading.value = false + } + } + + const addConfig = async (config: MikroTikConfig) => { + const res = await mikrotikApi.add(config) + ElMessage.success(res.message || 'MikroTik 配置已保存,需要重启服务生效') + await fetchConfigs() + } + + const deleteConfig = async (tag: string) => { + const res = await mikrotikApi.delete(tag) + ElMessage.success(res.message || '配置已删除,需要重启服务生效') + await fetchConfigs() + } + + return { + configs, + loading, + fetchConfigs, + addConfig, + deleteConfig, + } +}) + diff --git a/web-ui/src/stores/server.ts b/web-ui/src/stores/server.ts new file mode 100644 index 0000000..f7b012e --- /dev/null +++ b/web-ui/src/stores/server.ts @@ -0,0 +1,64 @@ +// 服务器状态管理 +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' +import { serverApi, type ServerInfo, type StatsData } from '@/api/server' + +export const useServerStore = defineStore('server', () => { + // 状态 + const serverInfo = ref(null) + const stats = ref(null) + const loading = ref(false) + + // 计算属性 + const isOnline = computed(() => serverInfo.value?.status === 'running') + const uptime = computed(() => { + if (!serverInfo.value?.uptime_seconds) return '0分钟' + const seconds = serverInfo.value.uptime_seconds + const days = Math.floor(seconds / 86400) + const hours = Math.floor((seconds % 86400) / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + + const parts = [] + if (days > 0) parts.push(`${days}天`) + if (hours > 0) parts.push(`${hours}小时`) + if (minutes > 0 || parts.length === 0) parts.push(`${minutes}分钟`) + + return parts.join(' ') + }) + + // 方法 + const fetchServerInfo = async () => { + loading.value = true + try { + const res = await serverApi.getInfo() + serverInfo.value = res.data + } finally { + loading.value = false + } + } + + const fetchStats = async () => { + try { + const res = await serverApi.getStats() + stats.value = res.data + } catch (error) { + console.error('Failed to fetch stats:', error) + } + } + + const restart = async () => { + await serverApi.restart() + } + + return { + serverInfo, + stats, + loading, + isOnline, + uptime, + fetchServerInfo, + fetchStats, + restart, + } +}) + diff --git a/web-ui/src/views/AboutView.vue b/web-ui/src/views/AboutView.vue new file mode 100644 index 0000000..756ad2a --- /dev/null +++ b/web-ui/src/views/AboutView.vue @@ -0,0 +1,15 @@ + + + diff --git a/web-ui/src/views/DashboardView.vue b/web-ui/src/views/DashboardView.vue new file mode 100644 index 0000000..f05ab1a --- /dev/null +++ b/web-ui/src/views/DashboardView.vue @@ -0,0 +1,202 @@ + + + + + + diff --git a/web-ui/src/views/DomainFilesView.vue b/web-ui/src/views/DomainFilesView.vue new file mode 100644 index 0000000..998564f --- /dev/null +++ b/web-ui/src/views/DomainFilesView.vue @@ -0,0 +1,165 @@ + + + + + + diff --git a/web-ui/src/views/HomeView.vue b/web-ui/src/views/HomeView.vue new file mode 100644 index 0000000..d5c0217 --- /dev/null +++ b/web-ui/src/views/HomeView.vue @@ -0,0 +1,9 @@ + + + diff --git a/web-ui/src/views/MikroTikView.vue b/web-ui/src/views/MikroTikView.vue new file mode 100644 index 0000000..7e0ddbc --- /dev/null +++ b/web-ui/src/views/MikroTikView.vue @@ -0,0 +1,263 @@ + + + + + + diff --git a/web-ui/tsconfig.app.json b/web-ui/tsconfig.app.json new file mode 100644 index 0000000..93f952f --- /dev/null +++ b/web-ui/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/web-ui/tsconfig.json b/web-ui/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/web-ui/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/web-ui/tsconfig.node.json b/web-ui/tsconfig.node.json new file mode 100644 index 0000000..62de4e4 --- /dev/null +++ b/web-ui/tsconfig.node.json @@ -0,0 +1,20 @@ +{ + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + "composite": true, + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"], + "skipLibCheck": true + } +} diff --git a/web-ui/vite.config.ts b/web-ui/vite.config.ts new file mode 100644 index 0000000..d70ecd2 --- /dev/null +++ b/web-ui/vite.config.ts @@ -0,0 +1,37 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:5555', + changeOrigin: true, + }, + }, + }, + build: { + outDir: 'dist', + assetsDir: 'static', + rollupOptions: { + output: { + manualChunks(id) { + // 将所有 node_modules 的依赖打包到 vendor + if (id.includes('node_modules')) { + return 'vendor' + } + }, + }, + }, + }, +}) diff --git a/web_embed.go b/web_embed.go new file mode 100644 index 0000000..9ea2b22 --- /dev/null +++ b/web_embed.go @@ -0,0 +1,6 @@ +package main + +import "embed" + +//go:embed all:web-ui/dist +var WebUIFS embed.FS