diff --git a/.gitignore b/.gitignore index 425ab26..72b9a92 100644 --- a/.gitignore +++ b/.gitignore @@ -1,47 +1,47 @@ -# General -.DS_Store -.AppleDouble -.LSOverride -Icon -._* -Thumbs.db -ehthumbs.db -Desktop.ini -*.log -*.tmp -*.bak -*.swp -*.swo -*~ -.idea/ -.vscode/ -*.sublime-project -*.sublime-workspace - -# Backend (Go) -/mengyamonitor-backend/mengyamonitor-backend -/mengyamonitor-backend/mengyamonitor-backend.exe -/mengyamonitor-backend/*.exe -/mengyamonitor-backend/*.out -/mengyamonitor-backend/bin/ -/mengyamonitor-backend/dist/ -/mengyamonitor-backend/vendor/ -/mengyamonitor-backend/.env -/mengyamonitor-backend/debug -/mengyamonitor-backend/__debug_bin - -# Frontend (Node/React/Vite) -/mengyamonitor-frontend/node_modules/ -/mengyamonitor-frontend/dist/ -/mengyamonitor-frontend/build/ -/mengyamonitor-frontend/coverage/ -/mengyamonitor-frontend/.env -/mengyamonitor-frontend/.env.local -/mengyamonitor-frontend/.env.development.local -/mengyamonitor-frontend/.env.test.local -/mengyamonitor-frontend/.env.production.local -/mengyamonitor-frontend/npm-debug.log* -/mengyamonitor-frontend/yarn-debug.log* -/mengyamonitor-frontend/yarn-error.log* -/mengyamonitor-frontend/pnpm-debug.log* -/mengyamonitor-frontend/.eslintcache +# General +.DS_Store +.AppleDouble +.LSOverride +Icon +._* +Thumbs.db +ehthumbs.db +Desktop.ini +*.log +*.tmp +*.bak +*.swp +*.swo +*~ +.idea/ +.vscode/ +*.sublime-project +*.sublime-workspace + +# Backend (Go) +/mengyamonitor-backend/mengyamonitor-backend +/mengyamonitor-backend/mengyamonitor-backend.exe +/mengyamonitor-backend/*.exe +/mengyamonitor-backend/*.out +/mengyamonitor-backend/bin/ +/mengyamonitor-backend/dist/ +/mengyamonitor-backend/vendor/ +/mengyamonitor-backend/.env +/mengyamonitor-backend/debug +/mengyamonitor-backend/__debug_bin + +# Frontend (Node/React/Vite) +/mengyamonitor-frontend/node_modules/ +/mengyamonitor-frontend/dist/ +/mengyamonitor-frontend/build/ +/mengyamonitor-frontend/coverage/ +/mengyamonitor-frontend/.env +/mengyamonitor-frontend/.env.local +/mengyamonitor-frontend/.env.development.local +/mengyamonitor-frontend/.env.test.local +/mengyamonitor-frontend/.env.production.local +/mengyamonitor-frontend/npm-debug.log* +/mengyamonitor-frontend/yarn-debug.log* +/mengyamonitor-frontend/yarn-error.log* +/mengyamonitor-frontend/pnpm-debug.log* +/mengyamonitor-frontend/.eslintcache diff --git a/README.md b/README.md index 991379d..54712b4 100644 --- a/README.md +++ b/README.md @@ -1,156 +1,156 @@ -# 萌芽监控面板 - -一个简洁高效的 Linux 服务器监控面板,采用前后端分离架构。 - -## 项目概述 - -### 功能 -- 实时监控 Linux 服务器的 CPU、内存、存储、GPU 使用情况 -- 支持同时监控多个服务器 -- 卡片式展示,直观清晰 -- 详情弹窗查看完整信息 - -### 技术栈 -- **前端**: React 19 + TypeScript + Vite -- **后端**: Go (原生 net/http 库) -- **风格**: 白色柔和风格界面 - -## 快速开始 - -### 后端部署 - -1. 进入后端目录 -```bash -cd mengyamonitor-backend -``` - -2. 编译(在 Linux 服务器上) -```bash -go build -o mengyamonitor-backend -``` - -3. 运行 -```bash -./mengyamonitor-backend -``` -默认监听端口: 9292 - -4. 可选:配置环境变量 -```bash -PORT=8080 ./mengyamonitor-backend -``` - -### 前端开发 - -1. 进入前端目录 -```bash -cd mengyamonitor-frontend -``` - -2. 安装依赖 -```bash -npm install -``` - -3. 启动开发服务器 -```bash -npm run dev -``` -默认访问端口: 2929 - -4. 构建生产版本 -```bash -npm run build -``` - -## 使用说明 - -### 1. 部署后端到服务器 -将编译好的 `mengyamonitor-backend` 二进制文件上传到需要监控的 Linux 服务器并运行。 - -### 2. 配置前端 -在前端界面点击"添加服务器",输入: -- 服务器名称:例如"生产服务器1" -- 服务器地址:例如"http://192.168.1.100:9292" - -### 3. 查看监控数据 -- 主界面显示所有服务器的基本信息(CPU、内存使用率) -- 点击"查看详情"可查看完整的系统信息 -- 数据每2秒自动刷新 - -## 项目结构 - -``` -萌芽监控面板/ -├── mengyamonitor-backend/ # 后端服务 -│ ├── main.go # 主程序和HTTP服务器 -│ ├── systeminfo.go # 系统信息采集 -│ ├── go.mod # Go模块配置 -│ └── README.md # 后端文档 -│ -├── mengyamonitor-frontend/ # 前端应用 -│ ├── src/ -│ │ ├── api/ # API调用层 -│ │ ├── components/ # React组件 -│ │ ├── hooks/ # 自定义Hooks -│ │ ├── types/ # TypeScript类型 -│ │ ├── utils/ # 工具函数 -│ │ ├── App.tsx # 主应用 -│ │ └── main.tsx # 入口文件 -│ ├── package.json -│ ├── vite.config.ts -│ └── README.md # 前端文档 -│ -└── 需求.txt # 需求文档 -``` - -## API 接口 - -### GET /api/health -健康检查 - -### GET /api/metrics -获取系统监控指标 -```json -{ - "data": { - "hostname": "server1", - "timestamp": "2025-12-10T10:00:00Z", - "cpu": { "usagePercent": 23.45, ... }, - "memory": { "usedPercent": 50.0, ... }, - "storage": [...], - "gpu": [...], - "os": { ... }, - "uptimeSeconds": 864000.5 - } -} -``` - -## 注意事项 - -1. **系统支持**: 后端仅支持 Linux 系统 -2. **权限要求**: 需要读取 `/proc` 文件系统的权限 -3. **GPU 监控**: 需要安装 `nvidia-smi` 工具(可选) -4. **网络访问**: 确保前端可以访问后端的 9292 端口 -5. **CORS**: 后端已配置允许跨域访问 - -## 常见问题 - -**Q: 前端无法连接后端?** -A: 检查服务器防火墙是否开放 9292 端口,确保后端服务正在运行。 - -**Q: GPU 信息显示不可用?** -A: 如果服务器没有 NVIDIA GPU 或未安装 nvidia-smi,GPU 信息会显示为"不可用",这是正常的。 - -**Q: 如何将前端打包成桌面应用?** -A: 可以使用 Electron 或 Tauri 框架将前端打包成桌面应用,详见前端 README。 - -## 开发者 - -- 前后端分离架构,代码结构清晰 -- 符合企业级开发规范 -- 易于扩展和维护 - -## License - -MIT +# 萌芽监控面板 + +一个简洁高效的 Linux 服务器监控面板,采用前后端分离架构。 + +## 项目概述 + +### 功能 +- 实时监控 Linux 服务器的 CPU、内存、存储、GPU 使用情况 +- 支持同时监控多个服务器 +- 卡片式展示,直观清晰 +- 详情弹窗查看完整信息 + +### 技术栈 +- **前端**: React 19 + TypeScript + Vite +- **后端**: Go (原生 net/http 库) +- **风格**: 白色柔和风格界面 + +## 快速开始 + +### 后端部署 + +1. 进入后端目录 +```bash +cd mengyamonitor-backend +``` + +2. 编译(在 Linux 服务器上) +```bash +go build -o mengyamonitor-backend +``` + +3. 运行 +```bash +./mengyamonitor-backend +``` +默认监听端口: 9292 + +4. 可选:配置环境变量 +```bash +PORT=8080 ./mengyamonitor-backend +``` + +### 前端开发 + +1. 进入前端目录 +```bash +cd mengyamonitor-frontend +``` + +2. 安装依赖 +```bash +npm install +``` + +3. 启动开发服务器 +```bash +npm run dev +``` +默认访问端口: 2929 + +4. 构建生产版本 +```bash +npm run build +``` + +## 使用说明 + +### 1. 部署后端到服务器 +将编译好的 `mengyamonitor-backend` 二进制文件上传到需要监控的 Linux 服务器并运行。 + +### 2. 配置前端 +在前端界面点击"添加服务器",输入: +- 服务器名称:例如"生产服务器1" +- 服务器地址:例如"http://192.168.1.100:9292" + +### 3. 查看监控数据 +- 主界面显示所有服务器的基本信息(CPU、内存使用率) +- 点击"查看详情"可查看完整的系统信息 +- 数据每2秒自动刷新 + +## 项目结构 + +``` +萌芽监控面板/ +├── mengyamonitor-backend/ # 后端服务 +│ ├── main.go # 主程序和HTTP服务器 +│ ├── systeminfo.go # 系统信息采集 +│ ├── go.mod # Go模块配置 +│ └── README.md # 后端文档 +│ +├── mengyamonitor-frontend/ # 前端应用 +│ ├── src/ +│ │ ├── api/ # API调用层 +│ │ ├── components/ # React组件 +│ │ ├── hooks/ # 自定义Hooks +│ │ ├── types/ # TypeScript类型 +│ │ ├── utils/ # 工具函数 +│ │ ├── App.tsx # 主应用 +│ │ └── main.tsx # 入口文件 +│ ├── package.json +│ ├── vite.config.ts +│ └── README.md # 前端文档 +│ +└── 需求.txt # 需求文档 +``` + +## API 接口 + +### GET /api/health +健康检查 + +### GET /api/metrics +获取系统监控指标 +```json +{ + "data": { + "hostname": "server1", + "timestamp": "2025-12-10T10:00:00Z", + "cpu": { "usagePercent": 23.45, ... }, + "memory": { "usedPercent": 50.0, ... }, + "storage": [...], + "gpu": [...], + "os": { ... }, + "uptimeSeconds": 864000.5 + } +} +``` + +## 注意事项 + +1. **系统支持**: 后端仅支持 Linux 系统 +2. **权限要求**: 需要读取 `/proc` 文件系统的权限 +3. **GPU 监控**: 需要安装 `nvidia-smi` 工具(可选) +4. **网络访问**: 确保前端可以访问后端的 9292 端口 +5. **CORS**: 后端已配置允许跨域访问 + +## 常见问题 + +**Q: 前端无法连接后端?** +A: 检查服务器防火墙是否开放 9292 端口,确保后端服务正在运行。 + +**Q: GPU 信息显示不可用?** +A: 如果服务器没有 NVIDIA GPU 或未安装 nvidia-smi,GPU 信息会显示为"不可用",这是正常的。 + +**Q: 如何将前端打包成桌面应用?** +A: 可以使用 Electron 或 Tauri 框架将前端打包成桌面应用,详见前端 README。 + +## 开发者 + +- 前后端分离架构,代码结构清晰 +- 符合企业级开发规范 +- 易于扩展和维护 + +## License + +MIT diff --git a/build-frontend.bat b/build-frontend.bat new file mode 100644 index 0000000..f9feee2 --- /dev/null +++ b/build-frontend.bat @@ -0,0 +1,7 @@ +@echo off +chcp 65001 >nul +echo 构建前端项目... +cd mengyamonitor-frontend +echo 正在安装依赖... +call npm install +npm run build diff --git a/mengyamonitor-backend/BUILD.md b/mengyamonitor-backend/BUILD.md deleted file mode 100644 index 7550137..0000000 --- a/mengyamonitor-backend/BUILD.md +++ /dev/null @@ -1,88 +0,0 @@ -# 编译说明 - 兼容旧版本系统 - -## 问题说明 - -如果在 Debian 11 或其他旧版本系统上运行时出现 GLIBC 版本错误: -``` -./mengyamonitor-backend: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found -``` - -这是因为编译时使用了较新版本的 GLIBC,而目标系统的 GLIBC 版本较旧。 - -## 解决方案 - -### 方案 1:在目标系统上编译(推荐) - -在 Debian 11 服务器上直接编译: - -```bash -cd mengyamonitor-backend - -# 禁用 CGO,静态链接 -export CGO_ENABLED=0 -go build -ldflags="-s -w" -o mengyamonitor-backend . - -# 或者使用提供的脚本 -chmod +x build.sh -./build.sh -``` - -### 方案 2:使用静态链接编译 - -在任何系统上编译,但使用静态链接: - -```bash -cd mengyamonitor-backend - -# 禁用 CGO -export CGO_ENABLED=0 -export GOOS=linux -export GOARCH=amd64 - -# 编译(静态链接,不依赖系统库) -go build -ldflags="-s -w" -o mengyamonitor-backend . -``` - -### 方案 3:使用 Docker 编译 - -使用 Docker 在 Debian 11 环境中编译: - -```bash -# 使用 Debian 11 镜像编译 -docker run --rm -v $(pwd):/app -w /app golang:1.21-bullseye sh -c " - export CGO_ENABLED=0 - go build -ldflags='-s -w' -o mengyamonitor-backend . -" - -# 或者使用多阶段构建 -docker build -t mengyamonitor-backend -f Dockerfile.build . -``` - -## 验证编译结果 - -编译完成后,检查二进制文件: - -```bash -# 检查文件类型 -file mengyamonitor-backend - -# 检查依赖(如果是静态链接,应该显示 "not a dynamic executable") -ldd mengyamonitor-backend - -# 检查 GLIBC 依赖(应该没有或很少) -objdump -T mengyamonitor-backend | grep GLIBC -``` - -## 编译参数说明 - -- `CGO_ENABLED=0`: 禁用 CGO,使用纯 Go 实现,不依赖系统 C 库 -- `-ldflags="-s -w"`: 减小二进制文件大小 - - `-s`: 省略符号表和调试信息 - - `-w`: 省略 DWARF 符号表 - -## 注意事项 - -1. 禁用 CGO 后,某些需要 C 库的功能可能不可用,但本项目不依赖 CGO -2. 静态链接的二进制文件会稍大一些,但兼容性更好 -3. 如果必须使用 CGO,需要在目标系统上编译,或使用相同 GLIBC 版本的系统编译 - diff --git a/mengyamonitor-backend/README.md b/mengyamonitor-backend/README.md index 160f6fe..da6aad8 100644 --- a/mengyamonitor-backend/README.md +++ b/mengyamonitor-backend/README.md @@ -1,121 +1,121 @@ -# 萌芽监控面板 - 后端服务 - -## 概述 -Linux 服务器监控后端服务,使用 Go 原生 net/http 库实现。 - -## 功能 -- CPU 使用率和负载监控 -- 内存使用情况 -- 磁盘存储监控 -- GPU 监控(支持 NVIDIA) -- 操作系统信息 -- 系统运行时间 - -## API 端点 - -### `GET /api/health` -健康检查端点 -```json -{ - "status": "ok", - "timestamp": "2025-12-10T10:00:00Z" -} -``` - -### `GET /api/metrics` -获取系统监控指标 -```json -{ - "data": { - "hostname": "server1", - "timestamp": "2025-12-10T10:00:00Z", - "cpu": { - "model": "Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz", - "cores": 8, - "usagePercent": 23.45, - "loadAverages": [1.2, 1.5, 1.8] - }, - "memory": { - "totalBytes": 16777216000, - "usedBytes": 8388608000, - "freeBytes": 8388608000, - "usedPercent": 50.0 - }, - "storage": [{ - "mount": "/", - "totalBytes": 107374182400, - "usedBytes": 53687091200, - "freeBytes": 53687091200, - "usedPercent": 50.0 - }], - "gpu": [{ - "name": "Tesla T4", - "memoryTotalMB": 15360, - "memoryUsedMB": 512, - "utilizationPercent": 15.0, - "status": "ok" - }], - "os": { - "kernel": "Linux version 5.15.0", - "distro": "Ubuntu 22.04 LTS", - "architecture": "amd64" - }, - "uptimeSeconds": 864000.5 - } -} -``` - -## 运行方式 - -### 开发环境 -```bash -go run . -``` - -### 生产环境 - -#### 标准编译 -```bash -# 编译 -go build -o mengyamonitor-backend - -# 运行 -./mengyamonitor-backend -``` - -#### 兼容旧版本系统编译(推荐) - -如果需要在 Debian 11 或其他旧版本系统上运行,使用静态链接编译: - -```bash -# 禁用 CGO,静态链接(不依赖系统 GLIBC 版本) -export CGO_ENABLED=0 -go build -ldflags="-s -w" -o mengyamonitor-backend . - -# 或使用提供的脚本 -chmod +x build.sh -./build.sh -``` - -这样可以避免 GLIBC 版本兼容性问题。详细说明请参考 [BUILD.md](./BUILD.md)。 - -### 环境变量 -- `HOST`: 监听地址,默认 `0.0.0.0` -- `PORT`: 监听端口,默认 `9292` - -示例: -```bash -PORT=8080 ./mengyamonitor-backend -``` - -## 部署到服务器 - -1. 将编译好的二进制文件上传到目标服务器 -2. 赋予执行权限:`chmod +x mengyamonitor-backend` -3. 运行服务:`./mengyamonitor-backend` -4. 可选:使用 systemd 或 supervisor 管理服务进程 - -## 注意事项 -- 仅支持 Linux 系统 -- GPU 监控需要安装 nvidia-smi 工具 -- 需要读取 /proc 文件系统的权限 +# 萌芽监控面板 - 后端服务 + +## 概述 +Linux 服务器监控后端服务,使用 Go 原生 net/http 库实现。 + +## 功能 +- CPU 使用率和负载监控 +- 内存使用情况 +- 磁盘存储监控 +- GPU 监控(支持 NVIDIA) +- 操作系统信息 +- 系统运行时间 + +## API 端点 + +### `GET /api/health` +健康检查端点 +```json +{ + "status": "ok", + "timestamp": "2025-12-10T10:00:00Z" +} +``` + +### `GET /api/metrics` +获取系统监控指标 +```json +{ + "data": { + "hostname": "server1", + "timestamp": "2025-12-10T10:00:00Z", + "cpu": { + "model": "Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz", + "cores": 8, + "usagePercent": 23.45, + "loadAverages": [1.2, 1.5, 1.8] + }, + "memory": { + "totalBytes": 16777216000, + "usedBytes": 8388608000, + "freeBytes": 8388608000, + "usedPercent": 50.0 + }, + "storage": [{ + "mount": "/", + "totalBytes": 107374182400, + "usedBytes": 53687091200, + "freeBytes": 53687091200, + "usedPercent": 50.0 + }], + "gpu": [{ + "name": "Tesla T4", + "memoryTotalMB": 15360, + "memoryUsedMB": 512, + "utilizationPercent": 15.0, + "status": "ok" + }], + "os": { + "kernel": "Linux version 5.15.0", + "distro": "Ubuntu 22.04 LTS", + "architecture": "amd64" + }, + "uptimeSeconds": 864000.5 + } +} +``` + +## 运行方式 + +### 开发环境 +```bash +go run . +``` + +### 生产环境 + +#### 标准编译 +```bash +# 编译 +go build -o mengyamonitor-backend + +# 运行 +./mengyamonitor-backend +``` + +#### 兼容旧版本系统编译(推荐) + +如果需要在 Debian 11 或其他旧版本系统上运行,使用静态链接编译: + +```bash +# 禁用 CGO,静态链接(不依赖系统 GLIBC 版本) +export CGO_ENABLED=0 +go build -ldflags="-s -w" -o mengyamonitor-backend . + +# 或使用提供的脚本 +chmod +x build.sh +./build.sh +``` + +这样可以避免 GLIBC 版本兼容性问题。详细说明请参考 [BUILD.md](./BUILD.md)。 + +### 环境变量 +- `HOST`: 监听地址,默认 `0.0.0.0` +- `PORT`: 监听端口,默认 `9292` + +示例: +```bash +PORT=8080 ./mengyamonitor-backend +``` + +## 部署到服务器 + +1. 将编译好的二进制文件上传到目标服务器 +2. 赋予执行权限:`chmod +x mengyamonitor-backend` +3. 运行服务:`./mengyamonitor-backend` +4. 可选:使用 systemd 或 supervisor 管理服务进程 + +## 注意事项 +- 仅支持 Linux 系统 +- GPU 监控需要安装 nvidia-smi 工具 +- 需要读取 /proc 文件系统的权限 diff --git a/mengyamonitor-backend/build.sh b/mengyamonitor-backend/build_amd64.sh similarity index 100% rename from mengyamonitor-backend/build.sh rename to mengyamonitor-backend/build_amd64.sh diff --git a/mengyamonitor-backend/build_arm64.sh b/mengyamonitor-backend/build_arm64.sh new file mode 100644 index 0000000..a51970a --- /dev/null +++ b/mengyamonitor-backend/build_arm64.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Linux ARM64 交叉编译脚本 + +echo "开始交叉编译 mengyamonitor-backend (Linux ARM64)..." + +# 禁用 CGO,使用纯 Go 编译(不依赖系统 C 库) +export CGO_ENABLED=0 + +# 设置目标平台为 Linux ARM64 +export GOOS=linux +export GOARCH=arm64 + +# 编译(静态链接) +go build -ldflags="-s -w" -o mengyamonitor-backend-arm64 . + +if [ $? -eq 0 ]; then + echo "编译成功!" + echo "二进制文件: mengyamonitor-backend-arm64" + echo "" + echo "目标平台: Linux ARM64" + echo "编译模式: 静态链接,无外部依赖" + echo "" + echo "检查文件信息:" + file mengyamonitor-backend-arm64 2>/dev/null || echo "文件已生成: mengyamonitor-backend-arm64" + echo "" + ls -lh mengyamonitor-backend-arm64 +else + echo "编译失败!" + exit 1 +fi diff --git a/mengyamonitor-backend/collector.go b/mengyamonitor-backend/collector.go index 0e7078f..ace0ecf 100644 --- a/mengyamonitor-backend/collector.go +++ b/mengyamonitor-backend/collector.go @@ -1,103 +1,103 @@ -package main - -import ( - "bufio" - "os" - "runtime" - "strconv" - "strings" - "time" -) - -// CollectMetrics 收集所有系统监控指标 -func CollectMetrics() (*Metrics, error) { - hostname, _ := os.Hostname() - - cpuModel := firstMatchInFile("/proc/cpuinfo", "model name") - cpuUsage, err := readCPUUsage() - if err != nil { - return nil, err - } - cpuTemp := readCPUTemperature() - perCoreUsage := readPerCoreUsage() - - mem, err := readMemory() - if err != nil { - return nil, err - } - - storage, err := readAllStorage() - if err != nil { - return nil, err - } - - gpu := readGPU() - network := readNetworkInterfaces() - systemStats := readSystemStats() - systemStats.DockerStats = readDockerStats() - - osInfo := readOSInfo() - uptime := readUptime() - - loads := readLoadAverages() - - return &Metrics{ - Hostname: hostname, - Timestamp: time.Now().UTC(), - CPU: CPUMetrics{ - Model: cpuModel, - Cores: runtime.NumCPU(), - UsagePercent: round(cpuUsage, 2), - LoadAverages: loads, - Temperature: cpuTemp, - PerCoreUsage: perCoreUsage, - }, - Memory: mem, - Storage: storage, - GPU: gpu, - Network: network, - System: systemStats, - OS: osInfo, - UptimeSeconds: uptime, - }, nil -} - -func readOSInfo() OSInfo { - distro := readOSRelease() - kernel := strings.TrimSpace(readFirstLine("/proc/version")) - arch := runtime.GOARCH - return OSInfo{ - Kernel: kernel, - Distro: distro, - Architecture: arch, - } -} - -func readOSRelease() string { - f, err := os.Open("/etc/os-release") - if err != nil { - return runtime.GOOS - } - defer f.Close() - scanner := bufio.NewScanner(f) - for scanner.Scan() { - line := scanner.Text() - if strings.HasPrefix(line, "PRETTY_NAME=") { - return strings.Trim(line[len("PRETTY_NAME="):], "\"") - } - } - return runtime.GOOS -} - -func readUptime() float64 { - line := readFirstLine("/proc/uptime") - fields := strings.Fields(line) - if len(fields) == 0 { - return 0 - } - v, err := strconv.ParseFloat(fields[0], 64) - if err != nil { - return 0 - } - return v -} +package main + +import ( + "bufio" + "os" + "runtime" + "strconv" + "strings" + "time" +) + +// CollectMetrics 收集所有系统监控指标 +func CollectMetrics() (*Metrics, error) { + hostname, _ := os.Hostname() + + cpuModel := firstMatchInFile("/proc/cpuinfo", "model name") + cpuUsage, err := readCPUUsage() + if err != nil { + return nil, err + } + cpuTemp := readCPUTemperature() + perCoreUsage := readPerCoreUsage() + + mem, err := readMemory() + if err != nil { + return nil, err + } + + storage, err := readAllStorage() + if err != nil { + return nil, err + } + + gpu := readGPU() + network := readNetworkInterfaces() + systemStats := readSystemStats() + systemStats.DockerStats = readDockerStats() + + osInfo := readOSInfo() + uptime := readUptime() + + loads := readLoadAverages() + + return &Metrics{ + Hostname: hostname, + Timestamp: time.Now().UTC(), + CPU: CPUMetrics{ + Model: cpuModel, + Cores: runtime.NumCPU(), + UsagePercent: round(cpuUsage, 2), + LoadAverages: loads, + Temperature: cpuTemp, + PerCoreUsage: perCoreUsage, + }, + Memory: mem, + Storage: storage, + GPU: gpu, + Network: network, + System: systemStats, + OS: osInfo, + UptimeSeconds: uptime, + }, nil +} + +func readOSInfo() OSInfo { + distro := readOSRelease() + kernel := strings.TrimSpace(readFirstLine("/proc/version")) + arch := runtime.GOARCH + return OSInfo{ + Kernel: kernel, + Distro: distro, + Architecture: arch, + } +} + +func readOSRelease() string { + f, err := os.Open("/etc/os-release") + if err != nil { + return runtime.GOOS + } + defer f.Close() + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "PRETTY_NAME=") { + return strings.Trim(line[len("PRETTY_NAME="):], "\"") + } + } + return runtime.GOOS +} + +func readUptime() float64 { + line := readFirstLine("/proc/uptime") + fields := strings.Fields(line) + if len(fields) == 0 { + return 0 + } + v, err := strconv.ParseFloat(fields[0], 64) + if err != nil { + return 0 + } + return v +} diff --git a/mengyamonitor-backend/cpu.go b/mengyamonitor-backend/cpu.go index d6994b1..4850a51 100644 --- a/mengyamonitor-backend/cpu.go +++ b/mengyamonitor-backend/cpu.go @@ -1,182 +1,182 @@ -package main - -import ( - "bufio" - "errors" - "os" - "strconv" - "strings" - "time" -) - -// readCPUUsage 读取CPU整体使用率 -func readCPUUsage() (float64, error) { - idle1, total1, err := readCPUTicks() - if err != nil { - return 0, err - } - time.Sleep(250 * time.Millisecond) - idle2, total2, err := readCPUTicks() - if err != nil { - return 0, err - } - if total2 == total1 { - return 0, errors.New("cpu totals unchanged") - } - idleDelta := float64(idle2 - idle1) - totalDelta := float64(total2 - total1) - usage := (1.0 - idleDelta/totalDelta) * 100 - if usage < 0 { - usage = 0 - } - return usage, nil -} - -// readCPUTicks 读取CPU的idle和total ticks -func readCPUTicks() (idle, total uint64, err error) { - f, err := os.Open("/proc/stat") - if err != nil { - return 0, 0, err - } - defer f.Close() - scanner := bufio.NewScanner(f) - if !scanner.Scan() { - return 0, 0, errors.New("failed to scan /proc/stat") - } - fields := strings.Fields(scanner.Text()) - if len(fields) < 5 { - return 0, 0, errors.New("unexpected /proc/stat format") - } - // fields[0] is "cpu" - var vals []uint64 - for _, f := range fields[1:] { - v, err := strconv.ParseUint(f, 10, 64) - if err != nil { - return 0, 0, err - } - vals = append(vals, v) - } - for _, v := range vals { - total += v - } - if len(vals) > 3 { - idle = vals[3] // idle time - // 注意:不包含 iowait,因为 iowait 不算真正的空闲时间 - // 如果系统有 iowait,它会在 total 中,但不应该算作 idle - } - return idle, total, nil -} - -// readPerCoreUsage 读取每个CPU核心的使用率 -func readPerCoreUsage() []CoreUsage { - coreUsages := []CoreUsage{} - - // 第一次读取 - cores1 := readPerCoreTicks() - time.Sleep(100 * time.Millisecond) // 减少到100ms - // 第二次读取 - cores2 := readPerCoreTicks() - - for i := 0; i < len(cores1) && i < len(cores2); i++ { - idle1, total1 := cores1[i][0], cores1[i][1] - idle2, total2 := cores2[i][0], cores2[i][1] - - if total2 == total1 { - continue - } - - idleDelta := float64(idle2 - idle1) - totalDelta := float64(total2 - total1) - usage := (1.0 - idleDelta/totalDelta) * 100 - if usage < 0 { - usage = 0 - } - - coreUsages = append(coreUsages, CoreUsage{ - Core: i, - Percent: round(usage, 1), - }) - } - - return coreUsages -} - -// readPerCoreTicks 读取每个CPU核心的ticks -func readPerCoreTicks() [][2]uint64 { - var result [][2]uint64 - - f, err := os.Open("/proc/stat") - if err != nil { - return result - } - defer f.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - line := scanner.Text() - if !strings.HasPrefix(line, "cpu") { - continue - } - if strings.HasPrefix(line, "cpu ") { - continue // 跳过总的cpu行 - } - - fields := strings.Fields(line) - if len(fields) < 5 { - continue - } - - var vals []uint64 - for _, f := range fields[1:] { - v, err := strconv.ParseUint(f, 10, 64) - if err != nil { - break - } - vals = append(vals, v) - } - - if len(vals) < 5 { - continue - } - - var total, idle uint64 - for _, v := range vals { - total += v - } - idle = vals[3] // idle time only, not including iowait - - result = append(result, [2]uint64{idle, total}) - } - - return result -} - -// readCPUTemperature 读取CPU温度 -func readCPUTemperature() float64 { - // 尝试从常见位置读取温度 - paths := []string{ - "/sys/class/thermal/thermal_zone0/temp", - "/sys/class/hwmon/hwmon0/temp1_input", - "/sys/class/hwmon/hwmon1/temp1_input", - } - for _, path := range paths { - if temp := readTempFromFile(path); temp > 0 { - return temp - } - } - return 0 -} - -// readLoadAverages 读取系统负载平均值 -func readLoadAverages() []float64 { - line := readFirstLine("/proc/loadavg") - fields := strings.Fields(line) - res := make([]float64, 0, 3) - for i := 0; i < len(fields) && i < 3; i++ { - v, err := strconv.ParseFloat(fields[i], 64) - if err == nil { - res = append(res, v) - } - } - return res -} +package main + +import ( + "bufio" + "errors" + "os" + "strconv" + "strings" + "time" +) + +// readCPUUsage 读取CPU整体使用率 +func readCPUUsage() (float64, error) { + idle1, total1, err := readCPUTicks() + if err != nil { + return 0, err + } + time.Sleep(250 * time.Millisecond) + idle2, total2, err := readCPUTicks() + if err != nil { + return 0, err + } + if total2 == total1 { + return 0, errors.New("cpu totals unchanged") + } + idleDelta := float64(idle2 - idle1) + totalDelta := float64(total2 - total1) + usage := (1.0 - idleDelta/totalDelta) * 100 + if usage < 0 { + usage = 0 + } + return usage, nil +} + +// readCPUTicks 读取CPU的idle和total ticks +func readCPUTicks() (idle, total uint64, err error) { + f, err := os.Open("/proc/stat") + if err != nil { + return 0, 0, err + } + defer f.Close() + scanner := bufio.NewScanner(f) + if !scanner.Scan() { + return 0, 0, errors.New("failed to scan /proc/stat") + } + fields := strings.Fields(scanner.Text()) + if len(fields) < 5 { + return 0, 0, errors.New("unexpected /proc/stat format") + } + // fields[0] is "cpu" + var vals []uint64 + for _, f := range fields[1:] { + v, err := strconv.ParseUint(f, 10, 64) + if err != nil { + return 0, 0, err + } + vals = append(vals, v) + } + for _, v := range vals { + total += v + } + if len(vals) > 3 { + idle = vals[3] // idle time + // 注意:不包含 iowait,因为 iowait 不算真正的空闲时间 + // 如果系统有 iowait,它会在 total 中,但不应该算作 idle + } + return idle, total, nil +} + +// readPerCoreUsage 读取每个CPU核心的使用率 +func readPerCoreUsage() []CoreUsage { + coreUsages := []CoreUsage{} + + // 第一次读取 + cores1 := readPerCoreTicks() + time.Sleep(100 * time.Millisecond) // 减少到100ms + // 第二次读取 + cores2 := readPerCoreTicks() + + for i := 0; i < len(cores1) && i < len(cores2); i++ { + idle1, total1 := cores1[i][0], cores1[i][1] + idle2, total2 := cores2[i][0], cores2[i][1] + + if total2 == total1 { + continue + } + + idleDelta := float64(idle2 - idle1) + totalDelta := float64(total2 - total1) + usage := (1.0 - idleDelta/totalDelta) * 100 + if usage < 0 { + usage = 0 + } + + coreUsages = append(coreUsages, CoreUsage{ + Core: i, + Percent: round(usage, 1), + }) + } + + return coreUsages +} + +// readPerCoreTicks 读取每个CPU核心的ticks +func readPerCoreTicks() [][2]uint64 { + var result [][2]uint64 + + f, err := os.Open("/proc/stat") + if err != nil { + return result + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + if !strings.HasPrefix(line, "cpu") { + continue + } + if strings.HasPrefix(line, "cpu ") { + continue // 跳过总的cpu行 + } + + fields := strings.Fields(line) + if len(fields) < 5 { + continue + } + + var vals []uint64 + for _, f := range fields[1:] { + v, err := strconv.ParseUint(f, 10, 64) + if err != nil { + break + } + vals = append(vals, v) + } + + if len(vals) < 5 { + continue + } + + var total, idle uint64 + for _, v := range vals { + total += v + } + idle = vals[3] // idle time only, not including iowait + + result = append(result, [2]uint64{idle, total}) + } + + return result +} + +// readCPUTemperature 读取CPU温度 +func readCPUTemperature() float64 { + // 尝试从常见位置读取温度 + paths := []string{ + "/sys/class/thermal/thermal_zone0/temp", + "/sys/class/hwmon/hwmon0/temp1_input", + "/sys/class/hwmon/hwmon1/temp1_input", + } + for _, path := range paths { + if temp := readTempFromFile(path); temp > 0 { + return temp + } + } + return 0 +} + +// readLoadAverages 读取系统负载平均值 +func readLoadAverages() []float64 { + line := readFirstLine("/proc/loadavg") + fields := strings.Fields(line) + res := make([]float64, 0, 3) + for i := 0; i < len(fields) && i < 3; i++ { + v, err := strconv.ParseFloat(fields[i], 64) + if err == nil { + res = append(res, v) + } + } + return res +} diff --git a/mengyamonitor-backend/go.mod b/mengyamonitor-backend/go.mod index 9920de7..ddf7f21 100644 --- a/mengyamonitor-backend/go.mod +++ b/mengyamonitor-backend/go.mod @@ -1,3 +1,3 @@ -module mengyamonitor-backend - -go 1.22 +module mengyamonitor-backend + +go 1.22 diff --git a/mengyamonitor-backend/gpu.go b/mengyamonitor-backend/gpu.go index 3bc6900..e0d4a7e 100644 --- a/mengyamonitor-backend/gpu.go +++ b/mengyamonitor-backend/gpu.go @@ -1,48 +1,48 @@ -package main - -import ( - "os/exec" - "strconv" - "strings" -) - -// readGPU 读取GPU信息 -func readGPU() []GPUMetrics { - _, err := exec.LookPath("nvidia-smi") - if err != nil { - return []GPUMetrics{{Status: "not_available"}} - } - // Query GPU info including temperature - cmd := exec.Command("nvidia-smi", "--query-gpu=name,memory.total,memory.used,utilization.gpu,temperature.gpu", "--format=csv,noheader,nounits") - out, err := cmd.Output() - if err != nil { - return []GPUMetrics{{Status: "error", Name: "nvidia-smi", UtilizationPercent: 0}} - } - lines := strings.Split(strings.TrimSpace(string(out)), "\n") - gpus := make([]GPUMetrics, 0, len(lines)) - for _, line := range lines { - parts := strings.Split(line, ",") - for i := range parts { - parts[i] = strings.TrimSpace(parts[i]) - } - if len(parts) < 5 { - continue - } - total, _ := strconv.ParseInt(parts[1], 10, 64) - used, _ := strconv.ParseInt(parts[2], 10, 64) - util, _ := strconv.ParseFloat(parts[3], 64) - temp, _ := strconv.ParseFloat(parts[4], 64) - gpus = append(gpus, GPUMetrics{ - Name: parts[0], - MemoryTotalMB: total, - MemoryUsedMB: used, - UtilizationPercent: round(util, 2), - Temperature: temp, - Status: "ok", - }) - } - if len(gpus) == 0 { - return []GPUMetrics{{Status: "not_available"}} - } - return gpus -} +package main + +import ( + "os/exec" + "strconv" + "strings" +) + +// readGPU 读取GPU信息 +func readGPU() []GPUMetrics { + _, err := exec.LookPath("nvidia-smi") + if err != nil { + return []GPUMetrics{{Status: "not_available"}} + } + // Query GPU info including temperature + cmd := exec.Command("nvidia-smi", "--query-gpu=name,memory.total,memory.used,utilization.gpu,temperature.gpu", "--format=csv,noheader,nounits") + out, err := cmd.Output() + if err != nil { + return []GPUMetrics{{Status: "error", Name: "nvidia-smi", UtilizationPercent: 0}} + } + lines := strings.Split(strings.TrimSpace(string(out)), "\n") + gpus := make([]GPUMetrics, 0, len(lines)) + for _, line := range lines { + parts := strings.Split(line, ",") + for i := range parts { + parts[i] = strings.TrimSpace(parts[i]) + } + if len(parts) < 5 { + continue + } + total, _ := strconv.ParseInt(parts[1], 10, 64) + used, _ := strconv.ParseInt(parts[2], 10, 64) + util, _ := strconv.ParseFloat(parts[3], 64) + temp, _ := strconv.ParseFloat(parts[4], 64) + gpus = append(gpus, GPUMetrics{ + Name: parts[0], + MemoryTotalMB: total, + MemoryUsedMB: used, + UtilizationPercent: round(util, 2), + Temperature: temp, + Status: "ok", + }) + } + if len(gpus) == 0 { + return []GPUMetrics{{Status: "not_available"}} + } + return gpus +} diff --git a/mengyamonitor-backend/latency.go b/mengyamonitor-backend/latency.go index 015835c..564fc1c 100644 --- a/mengyamonitor-backend/latency.go +++ b/mengyamonitor-backend/latency.go @@ -1,56 +1,56 @@ -package main - -import ( - "fmt" - "net" - "time" -) - -// LatencyInfo 延迟信息 -type LatencyInfo struct { - ClientToServer float64 `json:"clientToServer"` // 客户端到服务器延迟(ms),由前端计算 - External map[string]string `json:"external"` // 外部网站延迟 -} - -// checkExternalLatency 检测外部网站延迟 -func checkExternalLatency(host string, timeout time.Duration) string { - start := time.Now() - - // 尝试 TCP 连接 80 端口 - conn, err := net.DialTimeout("tcp", host+":80", timeout) - if err != nil { - // 如果 80 端口失败,尝试 443 (HTTPS) - conn, err = net.DialTimeout("tcp", host+":443", timeout) - if err != nil { - return "超时" - } - } - defer conn.Close() - - latency := time.Since(start).Milliseconds() - - // 检查是否超时(超过超时时间的一半就认为可能有问题) - if latency > timeout.Milliseconds()/2 { - return "超时" - } - - return fmt.Sprintf("%d ms", latency) -} - -// readExternalLatencies 读取外部网站延迟 -func readExternalLatencies() map[string]string { - latencies := make(map[string]string) - timeout := 3 * time.Second - - // 检测百度 - latencies["baidu.com"] = checkExternalLatency("baidu.com", timeout) - - // 检测谷歌 - latencies["google.com"] = checkExternalLatency("google.com", timeout) - - // 检测 GitHub - latencies["github.com"] = checkExternalLatency("github.com", timeout) - - return latencies -} - +package main + +import ( + "fmt" + "net" + "time" +) + +// LatencyInfo 延迟信息 +type LatencyInfo struct { + ClientToServer float64 `json:"clientToServer"` // 客户端到服务器延迟(ms),由前端计算 + External map[string]string `json:"external"` // 外部网站延迟 +} + +// checkExternalLatency 检测外部网站延迟 +func checkExternalLatency(host string, timeout time.Duration) string { + start := time.Now() + + // 尝试 TCP 连接 80 端口 + conn, err := net.DialTimeout("tcp", host+":80", timeout) + if err != nil { + // 如果 80 端口失败,尝试 443 (HTTPS) + conn, err = net.DialTimeout("tcp", host+":443", timeout) + if err != nil { + return "超时" + } + } + defer conn.Close() + + latency := time.Since(start).Milliseconds() + + // 检查是否超时(超过超时时间的一半就认为可能有问题) + if latency > timeout.Milliseconds()/2 { + return "超时" + } + + return fmt.Sprintf("%d ms", latency) +} + +// readExternalLatencies 读取外部网站延迟 +func readExternalLatencies() map[string]string { + latencies := make(map[string]string) + timeout := 3 * time.Second + + // 检测百度 + latencies["baidu.com"] = checkExternalLatency("baidu.com", timeout) + + // 检测谷歌 + latencies["google.com"] = checkExternalLatency("google.com", timeout) + + // 检测 GitHub + latencies["github.com"] = checkExternalLatency("github.com", timeout) + + return latencies +} + diff --git a/mengyamonitor-backend/main.go b/mengyamonitor-backend/main.go index 12475a8..39c2d6d 100644 --- a/mengyamonitor-backend/main.go +++ b/mengyamonitor-backend/main.go @@ -1,265 +1,265 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "os" - "runtime" - "time" -) - -// envelope keeps JSON responses consistent. -type envelope map[string]any - -func main() { - host := getenv("HOST", "0.0.0.0") - port := getenv("PORT", "9292") - - mux := http.NewServeMux() - mux.HandleFunc("/", rootHandler) - mux.HandleFunc("/api/health", healthHandler) - // 拆分的细粒度API端点 - mux.HandleFunc("/api/metrics/cpu", cpuMetricsHandler) - mux.HandleFunc("/api/metrics/memory", memoryMetricsHandler) - mux.HandleFunc("/api/metrics/storage", storageMetricsHandler) - mux.HandleFunc("/api/metrics/gpu", gpuMetricsHandler) - mux.HandleFunc("/api/metrics/network", networkMetricsHandler) - mux.HandleFunc("/api/metrics/system", systemMetricsHandler) - mux.HandleFunc("/api/metrics/docker", dockerMetricsHandler) - mux.HandleFunc("/api/metrics/latency", latencyMetricsHandler) - - srv := &http.Server{ - Addr: fmt.Sprintf("%s:%s", host, port), - Handler: loggingMiddleware(corsMiddleware(mux)), - ReadHeaderTimeout: 5 * time.Second, - } - - log.Printf("server starting on http://%s:%s", host, port) - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("server error: %v", err) - } -} - -func rootHandler(w http.ResponseWriter, r *http.Request) { - respondJSON(w, http.StatusOK, envelope{ - "service": "萌芽监控面板 API", - "version": "1.0.0", - "endpoints": []map[string]string{ - { - "path": "/", - "description": "API 信息和可用端点列表", - }, - { - "path": "/api/health", - "description": "健康检查", - }, - { - "path": "/api/metrics/cpu", - "description": "获取 CPU 监控数据", - }, - { - "path": "/api/metrics/memory", - "description": "获取内存监控数据", - }, - { - "path": "/api/metrics/storage", - "description": "获取存储监控数据", - }, - { - "path": "/api/metrics/gpu", - "description": "获取 GPU 监控数据", - }, - { - "path": "/api/metrics/network", - "description": "获取网络接口监控数据", - }, - { - "path": "/api/metrics/system", - "description": "获取系统统计信息(进程、包、磁盘速度等)", - }, - { - "path": "/api/metrics/docker", - "description": "获取 Docker 容器监控数据", - }, - }, - }) -} - -func healthHandler(w http.ResponseWriter, r *http.Request) { - respondJSON(w, http.StatusOK, envelope{ - "status": "ok", - "timestamp": time.Now().UTC(), - }) -} - -// CPU监控数据 -func cpuMetricsHandler(w http.ResponseWriter, r *http.Request) { - cpuModel := firstMatchInFile("/proc/cpuinfo", "model name") - cpuUsage, err := readCPUUsage() - if err != nil { - cpuUsage = 0 - } - cpuTemp := readCPUTemperature() - perCoreUsage := readPerCoreUsage() - loads := readLoadAverages() - - cores := runtime.NumCPU() - respondJSON(w, http.StatusOK, envelope{ - "data": CPUMetrics{ - Model: cpuModel, - Cores: cores, - UsagePercent: round(cpuUsage, 2), - LoadAverages: loads, - Temperature: cpuTemp, - PerCoreUsage: perCoreUsage, - }, - }) -} - -// 内存监控数据 -func memoryMetricsHandler(w http.ResponseWriter, r *http.Request) { - mem, err := readMemory() - if err != nil { - respondJSON(w, http.StatusInternalServerError, envelope{ - "error": "failed to read memory", - }) - return - } - respondJSON(w, http.StatusOK, envelope{ - "data": mem, - }) -} - -// 存储监控数据 -func storageMetricsHandler(w http.ResponseWriter, r *http.Request) { - storage, err := readAllStorage() - if err != nil { - respondJSON(w, http.StatusInternalServerError, envelope{ - "error": "failed to read storage", - }) - return - } - respondJSON(w, http.StatusOK, envelope{ - "data": storage, - }) -} - -// GPU监控数据 -func gpuMetricsHandler(w http.ResponseWriter, r *http.Request) { - gpu := readGPU() - respondJSON(w, http.StatusOK, envelope{ - "data": gpu, - }) -} - -// 网络监控数据 -func networkMetricsHandler(w http.ResponseWriter, r *http.Request) { - network := readNetworkInterfaces() - respondJSON(w, http.StatusOK, envelope{ - "data": network, - }) -} - -// 系统统计信息 -func systemMetricsHandler(w http.ResponseWriter, r *http.Request) { - stats := readSystemStats() - - // 读取系统基本信息 - hostname, _ := os.Hostname() - osInfo := readOSInfo() - uptime := readUptime() - - // 计算总网络速度(汇总所有接口) - networkInterfaces := readNetworkInterfaces() - var totalRxSpeed, totalTxSpeed float64 - for _, iface := range networkInterfaces { - totalRxSpeed += iface.RxSpeed // bytes/s - totalTxSpeed += iface.TxSpeed // bytes/s - } - // 转换为 MB/s - stats.NetworkRxSpeed = round(totalRxSpeed/1024/1024, 2) - stats.NetworkTxSpeed = round(totalTxSpeed/1024/1024, 2) - - respondJSON(w, http.StatusOK, envelope{ - "data": map[string]interface{}{ - "hostname": hostname, - "os": osInfo, - "uptimeSeconds": uptime, - "processCount": stats.ProcessCount, - "packageCount": stats.PackageCount, - "packageManager": stats.PackageManager, - "temperature": stats.Temperature, - "diskReadSpeed": stats.DiskReadSpeed, - "diskWriteSpeed": stats.DiskWriteSpeed, - "networkRxSpeed": stats.NetworkRxSpeed, - "networkTxSpeed": stats.NetworkTxSpeed, - "topProcesses": stats.TopProcesses, - "systemLogs": stats.SystemLogs, - }, - }) -} - -// Docker监控数据 -func dockerMetricsHandler(w http.ResponseWriter, r *http.Request) { - docker := readDockerStats() - respondJSON(w, http.StatusOK, envelope{ - "data": docker, - }) -} - -// 延迟监控数据 -func latencyMetricsHandler(w http.ResponseWriter, r *http.Request) { - // 记录请求开始时间,用于计算客户端到服务器的延迟 - startTime := time.Now() - - // 读取外部网站延迟 - externalLatencies := readExternalLatencies() - - // 计算服务器处理时间(这个可以作为参考,实际客户端延迟由前端计算) - serverProcessTime := time.Since(startTime).Milliseconds() - - respondJSON(w, http.StatusOK, envelope{ - "data": map[string]interface{}{ - "serverProcessTime": serverProcessTime, // 服务器处理时间(参考) - "external": externalLatencies, - }, - }) -} - -func respondJSON(w http.ResponseWriter, status int, body envelope) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - if err := json.NewEncoder(w).Encode(body); err != nil { - log.Printf("write json error: %v", err) - } -} - -func getenv(key, fallback string) string { - if v, ok := os.LookupEnv(key); ok && v != "" { - return v - } - return fallback -} - -func loggingMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - next.ServeHTTP(w, r) - log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start)) - }) -} - -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type") - if r.Method == http.MethodOptions { - w.WriteHeader(http.StatusNoContent) - return - } - next.ServeHTTP(w, r) - }) -} +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "runtime" + "time" +) + +// envelope keeps JSON responses consistent. +type envelope map[string]any + +func main() { + host := getenv("HOST", "0.0.0.0") + port := getenv("PORT", "9292") + + mux := http.NewServeMux() + mux.HandleFunc("/", rootHandler) + mux.HandleFunc("/api/health", healthHandler) + // 拆分的细粒度API端点 + mux.HandleFunc("/api/metrics/cpu", cpuMetricsHandler) + mux.HandleFunc("/api/metrics/memory", memoryMetricsHandler) + mux.HandleFunc("/api/metrics/storage", storageMetricsHandler) + mux.HandleFunc("/api/metrics/gpu", gpuMetricsHandler) + mux.HandleFunc("/api/metrics/network", networkMetricsHandler) + mux.HandleFunc("/api/metrics/system", systemMetricsHandler) + mux.HandleFunc("/api/metrics/docker", dockerMetricsHandler) + mux.HandleFunc("/api/metrics/latency", latencyMetricsHandler) + + srv := &http.Server{ + Addr: fmt.Sprintf("%s:%s", host, port), + Handler: loggingMiddleware(corsMiddleware(mux)), + ReadHeaderTimeout: 5 * time.Second, + } + + log.Printf("server starting on http://%s:%s", host, port) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("server error: %v", err) + } +} + +func rootHandler(w http.ResponseWriter, r *http.Request) { + respondJSON(w, http.StatusOK, envelope{ + "service": "萌芽监控面板 API", + "version": "1.0.0", + "endpoints": []map[string]string{ + { + "path": "/", + "description": "API 信息和可用端点列表", + }, + { + "path": "/api/health", + "description": "健康检查", + }, + { + "path": "/api/metrics/cpu", + "description": "获取 CPU 监控数据", + }, + { + "path": "/api/metrics/memory", + "description": "获取内存监控数据", + }, + { + "path": "/api/metrics/storage", + "description": "获取存储监控数据", + }, + { + "path": "/api/metrics/gpu", + "description": "获取 GPU 监控数据", + }, + { + "path": "/api/metrics/network", + "description": "获取网络接口监控数据", + }, + { + "path": "/api/metrics/system", + "description": "获取系统统计信息(进程、包、磁盘速度等)", + }, + { + "path": "/api/metrics/docker", + "description": "获取 Docker 容器监控数据", + }, + }, + }) +} + +func healthHandler(w http.ResponseWriter, r *http.Request) { + respondJSON(w, http.StatusOK, envelope{ + "status": "ok", + "timestamp": time.Now().UTC(), + }) +} + +// CPU监控数据 +func cpuMetricsHandler(w http.ResponseWriter, r *http.Request) { + cpuModel := firstMatchInFile("/proc/cpuinfo", "model name") + cpuUsage, err := readCPUUsage() + if err != nil { + cpuUsage = 0 + } + cpuTemp := readCPUTemperature() + perCoreUsage := readPerCoreUsage() + loads := readLoadAverages() + + cores := runtime.NumCPU() + respondJSON(w, http.StatusOK, envelope{ + "data": CPUMetrics{ + Model: cpuModel, + Cores: cores, + UsagePercent: round(cpuUsage, 2), + LoadAverages: loads, + Temperature: cpuTemp, + PerCoreUsage: perCoreUsage, + }, + }) +} + +// 内存监控数据 +func memoryMetricsHandler(w http.ResponseWriter, r *http.Request) { + mem, err := readMemory() + if err != nil { + respondJSON(w, http.StatusInternalServerError, envelope{ + "error": "failed to read memory", + }) + return + } + respondJSON(w, http.StatusOK, envelope{ + "data": mem, + }) +} + +// 存储监控数据 +func storageMetricsHandler(w http.ResponseWriter, r *http.Request) { + storage, err := readAllStorage() + if err != nil { + respondJSON(w, http.StatusInternalServerError, envelope{ + "error": "failed to read storage", + }) + return + } + respondJSON(w, http.StatusOK, envelope{ + "data": storage, + }) +} + +// GPU监控数据 +func gpuMetricsHandler(w http.ResponseWriter, r *http.Request) { + gpu := readGPU() + respondJSON(w, http.StatusOK, envelope{ + "data": gpu, + }) +} + +// 网络监控数据 +func networkMetricsHandler(w http.ResponseWriter, r *http.Request) { + network := readNetworkInterfaces() + respondJSON(w, http.StatusOK, envelope{ + "data": network, + }) +} + +// 系统统计信息 +func systemMetricsHandler(w http.ResponseWriter, r *http.Request) { + stats := readSystemStats() + + // 读取系统基本信息 + hostname, _ := os.Hostname() + osInfo := readOSInfo() + uptime := readUptime() + + // 计算总网络速度(汇总所有接口) + networkInterfaces := readNetworkInterfaces() + var totalRxSpeed, totalTxSpeed float64 + for _, iface := range networkInterfaces { + totalRxSpeed += iface.RxSpeed // bytes/s + totalTxSpeed += iface.TxSpeed // bytes/s + } + // 转换为 MB/s + stats.NetworkRxSpeed = round(totalRxSpeed/1024/1024, 2) + stats.NetworkTxSpeed = round(totalTxSpeed/1024/1024, 2) + + respondJSON(w, http.StatusOK, envelope{ + "data": map[string]interface{}{ + "hostname": hostname, + "os": osInfo, + "uptimeSeconds": uptime, + "processCount": stats.ProcessCount, + "packageCount": stats.PackageCount, + "packageManager": stats.PackageManager, + "temperature": stats.Temperature, + "diskReadSpeed": stats.DiskReadSpeed, + "diskWriteSpeed": stats.DiskWriteSpeed, + "networkRxSpeed": stats.NetworkRxSpeed, + "networkTxSpeed": stats.NetworkTxSpeed, + "topProcesses": stats.TopProcesses, + "systemLogs": stats.SystemLogs, + }, + }) +} + +// Docker监控数据 +func dockerMetricsHandler(w http.ResponseWriter, r *http.Request) { + docker := readDockerStats() + respondJSON(w, http.StatusOK, envelope{ + "data": docker, + }) +} + +// 延迟监控数据 +func latencyMetricsHandler(w http.ResponseWriter, r *http.Request) { + // 记录请求开始时间,用于计算客户端到服务器的延迟 + startTime := time.Now() + + // 读取外部网站延迟 + externalLatencies := readExternalLatencies() + + // 计算服务器处理时间(这个可以作为参考,实际客户端延迟由前端计算) + serverProcessTime := time.Since(startTime).Milliseconds() + + respondJSON(w, http.StatusOK, envelope{ + "data": map[string]interface{}{ + "serverProcessTime": serverProcessTime, // 服务器处理时间(参考) + "external": externalLatencies, + }, + }) +} + +func respondJSON(w http.ResponseWriter, status int, body envelope) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + if err := json.NewEncoder(w).Encode(body); err != nil { + log.Printf("write json error: %v", err) + } +} + +func getenv(key, fallback string) string { + if v, ok := os.LookupEnv(key); ok && v != "" { + return v + } + return fallback +} + +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + next.ServeHTTP(w, r) + log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start)) + }) +} + +func corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusNoContent) + return + } + next.ServeHTTP(w, r) + }) +} diff --git a/mengyamonitor-backend/memory.go b/mengyamonitor-backend/memory.go index 5fd2731..4299a08 100644 --- a/mengyamonitor-backend/memory.go +++ b/mengyamonitor-backend/memory.go @@ -1,64 +1,64 @@ -package main - -import ( - "bufio" - "os" - "strconv" - "strings" -) - -// readMemory 读取内存信息 -func readMemory() (MemoryMetrics, error) { - totals := map[string]uint64{} - f, err := os.Open("/proc/meminfo") - if err != nil { - return MemoryMetrics{}, err - } - defer f.Close() - scanner := bufio.NewScanner(f) - for scanner.Scan() { - line := scanner.Text() - parts := strings.Split(line, ":") - if len(parts) < 2 { - continue - } - key := strings.TrimSpace(parts[0]) - fields := strings.Fields(strings.TrimSpace(parts[1])) - if len(fields) == 0 { - continue - } - value, err := strconv.ParseUint(fields[0], 10, 64) - if err != nil { - continue - } - totals[key] = value * 1024 // kB to bytes - } - total := totals["MemTotal"] - - // 优先使用 MemAvailable(Linux 3.14+),如果没有则计算 - var free uint64 - if available, ok := totals["MemAvailable"]; ok && available > 0 { - free = available - } else { - // 回退到 MemFree + Buffers + Cached(适用于较老的系统) - free = totals["MemFree"] - if buffers, ok := totals["Buffers"]; ok { - free += buffers - } - if cached, ok := totals["Cached"]; ok { - free += cached - } - } - - used := total - free - usedPercent := 0.0 - if total > 0 { - usedPercent = (float64(used) / float64(total)) * 100 - } - return MemoryMetrics{ - TotalBytes: total, - UsedBytes: used, - FreeBytes: free, - UsedPercent: round(usedPercent, 2), - }, nil -} +package main + +import ( + "bufio" + "os" + "strconv" + "strings" +) + +// readMemory 读取内存信息 +func readMemory() (MemoryMetrics, error) { + totals := map[string]uint64{} + f, err := os.Open("/proc/meminfo") + if err != nil { + return MemoryMetrics{}, err + } + defer f.Close() + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Split(line, ":") + if len(parts) < 2 { + continue + } + key := strings.TrimSpace(parts[0]) + fields := strings.Fields(strings.TrimSpace(parts[1])) + if len(fields) == 0 { + continue + } + value, err := strconv.ParseUint(fields[0], 10, 64) + if err != nil { + continue + } + totals[key] = value * 1024 // kB to bytes + } + total := totals["MemTotal"] + + // 优先使用 MemAvailable(Linux 3.14+),如果没有则计算 + var free uint64 + if available, ok := totals["MemAvailable"]; ok && available > 0 { + free = available + } else { + // 回退到 MemFree + Buffers + Cached(适用于较老的系统) + free = totals["MemFree"] + if buffers, ok := totals["Buffers"]; ok { + free += buffers + } + if cached, ok := totals["Cached"]; ok { + free += cached + } + } + + used := total - free + usedPercent := 0.0 + if total > 0 { + usedPercent = (float64(used) / float64(total)) * 100 + } + return MemoryMetrics{ + TotalBytes: total, + UsedBytes: used, + FreeBytes: free, + UsedPercent: round(usedPercent, 2), + }, nil +} diff --git a/mengyamonitor-backend/network.go b/mengyamonitor-backend/network.go index 5a61e02..824b952 100644 --- a/mengyamonitor-backend/network.go +++ b/mengyamonitor-backend/network.go @@ -1,110 +1,112 @@ -package main - -import ( - "os" - "os/exec" - "strconv" - "strings" - "time" -) - -// readNetworkInterfaces 读取网络接口信息(包含瞬时流量速度) - 优化版:所有接口并行采样 -func readNetworkInterfaces() []NetworkInterface { - interfaces := []NetworkInterface{} - - // 读取网络接口列表 - entries, err := os.ReadDir("/sys/class/net") - if err != nil { - return interfaces - } - - // 收集要监控的接口名称 - var validIfaces []string - for _, entry := range entries { - ifName := entry.Name() - if ifName == "lo" { // 跳过回环接口 - continue - } - - // 跳过 Docker 相关网络接口 - if strings.HasPrefix(ifName, "docker") || - strings.HasPrefix(ifName, "br-") || - strings.HasPrefix(ifName, "veth") { - continue - } - - validIfaces = append(validIfaces, ifName) - } - - // 第一次批量读取所有接口的流量 - firstReadings := make(map[string][2]uint64) // [rx, tx] - for _, ifName := range validIfaces { - rxPath := "/sys/class/net/" + ifName + "/statistics/rx_bytes" - txPath := "/sys/class/net/" + ifName + "/statistics/tx_bytes" - - var rxBytes, txBytes uint64 - if rxStr := readFirstLine(rxPath); rxStr != "" { - rxBytes, _ = strconv.ParseUint(strings.TrimSpace(rxStr), 10, 64) - } - if txStr := readFirstLine(txPath); txStr != "" { - txBytes, _ = strconv.ParseUint(strings.TrimSpace(txStr), 10, 64) - } - firstReadings[ifName] = [2]uint64{rxBytes, txBytes} - } - - // 等待500ms(所有接口一起等待,而不是每个接口单独等待) - time.Sleep(500 * time.Millisecond) - - // 第二次批量读取并构建结果 - for _, ifName := range validIfaces { - iface := NetworkInterface{ - Name: ifName, - } - - // 读取 MAC 地址 - macPath := "/sys/class/net/" + ifName + "/address" - iface.MACAddress = strings.TrimSpace(readFirstLine(macPath)) - - // 读取 IP 地址 (使用 ip addr show) - cmd := exec.Command("ip", "addr", "show", ifName) - if out, err := cmd.Output(); err == nil { - lines := strings.Split(string(out), "\n") - for _, line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "inet ") { - fields := strings.Fields(line) - if len(fields) >= 2 { - iface.IPAddress = strings.Split(fields[1], "/")[0] - break - } - } - } - } - - // 读取第二次流量统计 - rxPath := "/sys/class/net/" + ifName + "/statistics/rx_bytes" - txPath := "/sys/class/net/" + ifName + "/statistics/tx_bytes" - - var rxBytes2, txBytes2 uint64 - if rxStr := readFirstLine(rxPath); rxStr != "" { - rxBytes2, _ = strconv.ParseUint(strings.TrimSpace(rxStr), 10, 64) - } - if txStr := readFirstLine(txPath); txStr != "" { - txBytes2, _ = strconv.ParseUint(strings.TrimSpace(txStr), 10, 64) - } - - // 设置累计流量 - iface.RxBytes = rxBytes2 - iface.TxBytes = txBytes2 - - // 计算瞬时速度 (bytes/s,乘以2因为采样间隔是0.5秒) - if first, ok := firstReadings[ifName]; ok { - iface.RxSpeed = float64(rxBytes2-first[0]) * 2 - iface.TxSpeed = float64(txBytes2-first[1]) * 2 - } - - interfaces = append(interfaces, iface) - } - - return interfaces -} +package main + +import ( + "os" + "os/exec" + "strconv" + "strings" + "time" +) + +// readNetworkInterfaces 读取网络接口信息(包含瞬时流量速度) - 优化版:所有接口并行采样 +func readNetworkInterfaces() []NetworkInterface { + interfaces := []NetworkInterface{} + + // 读取网络接口列表 + entries, err := os.ReadDir("/sys/class/net") + if err != nil { + return interfaces + } + + // 收集要监控的接口名称 + var validIfaces []string + for _, entry := range entries { + ifName := entry.Name() + if ifName == "lo" { // 跳过回环接口 + continue + } + + // 跳过 Docker 相关网络接口 + if strings.HasPrefix(ifName, "docker") || + strings.HasPrefix(ifName, "br-") || + strings.HasPrefix(ifName, "wwan") || + strings.HasPrefix(ifName, "nm-bridge") || + strings.HasPrefix(ifName, "veth") { + continue + } + + validIfaces = append(validIfaces, ifName) + } + + // 第一次批量读取所有接口的流量 + firstReadings := make(map[string][2]uint64) // [rx, tx] + for _, ifName := range validIfaces { + rxPath := "/sys/class/net/" + ifName + "/statistics/rx_bytes" + txPath := "/sys/class/net/" + ifName + "/statistics/tx_bytes" + + var rxBytes, txBytes uint64 + if rxStr := readFirstLine(rxPath); rxStr != "" { + rxBytes, _ = strconv.ParseUint(strings.TrimSpace(rxStr), 10, 64) + } + if txStr := readFirstLine(txPath); txStr != "" { + txBytes, _ = strconv.ParseUint(strings.TrimSpace(txStr), 10, 64) + } + firstReadings[ifName] = [2]uint64{rxBytes, txBytes} + } + + // 等待500ms(所有接口一起等待,而不是每个接口单独等待) + time.Sleep(500 * time.Millisecond) + + // 第二次批量读取并构建结果 + for _, ifName := range validIfaces { + iface := NetworkInterface{ + Name: ifName, + } + + // 读取 MAC 地址 + macPath := "/sys/class/net/" + ifName + "/address" + iface.MACAddress = strings.TrimSpace(readFirstLine(macPath)) + + // 读取 IP 地址 (使用 ip addr show) + cmd := exec.Command("ip", "addr", "show", ifName) + if out, err := cmd.Output(); err == nil { + lines := strings.Split(string(out), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "inet ") { + fields := strings.Fields(line) + if len(fields) >= 2 { + iface.IPAddress = strings.Split(fields[1], "/")[0] + break + } + } + } + } + + // 读取第二次流量统计 + rxPath := "/sys/class/net/" + ifName + "/statistics/rx_bytes" + txPath := "/sys/class/net/" + ifName + "/statistics/tx_bytes" + + var rxBytes2, txBytes2 uint64 + if rxStr := readFirstLine(rxPath); rxStr != "" { + rxBytes2, _ = strconv.ParseUint(strings.TrimSpace(rxStr), 10, 64) + } + if txStr := readFirstLine(txPath); txStr != "" { + txBytes2, _ = strconv.ParseUint(strings.TrimSpace(txStr), 10, 64) + } + + // 设置累计流量 + iface.RxBytes = rxBytes2 + iface.TxBytes = txBytes2 + + // 计算瞬时速度 (bytes/s,乘以2因为采样间隔是0.5秒) + if first, ok := firstReadings[ifName]; ok { + iface.RxSpeed = float64(rxBytes2-first[0]) * 2 + iface.TxSpeed = float64(txBytes2-first[1]) * 2 + } + + interfaces = append(interfaces, iface) + } + + return interfaces +} diff --git a/mengyamonitor-backend/system.go b/mengyamonitor-backend/system.go index e1d7bc4..0d81d1c 100644 --- a/mengyamonitor-backend/system.go +++ b/mengyamonitor-backend/system.go @@ -1,390 +1,390 @@ -package main - -import ( - "bufio" - "context" - "os" - "os/exec" - "strconv" - "strings" - "time" -) - -// readSystemStats 读取系统统计信息 -func readSystemStats() SystemStats { - stats := SystemStats{} - - // 读取进程数量 - stats.ProcessCount = countProcesses() - - // 读取已安装包数量和包管理器类型 - stats.PackageCount, stats.PackageManager = countPackages() - - // 读取系统温度 - stats.Temperature = readSystemTemperature() - - // 读取磁盘读写速度 - stats.DiskReadSpeed, stats.DiskWriteSpeed = readDiskSpeed() - - // 读取 Top 5 进程 - stats.TopProcesses = readTopProcesses() - - // 读取系统日志 - stats.SystemLogs = readSystemLogs(10) - - return stats -} - -func countProcesses() int { - entries, err := os.ReadDir("/proc") - if err != nil { - return 0 - } - count := 0 - for _, entry := range entries { - if entry.IsDir() { - // 进程目录是数字命名的 - if _, err := strconv.Atoi(entry.Name()); err == nil { - count++ - } - } - } - return count -} - -func countPackages() (int, string) { - // 尝试不同的包管理器 - // dpkg (Debian/Ubuntu) - if _, err := exec.LookPath("dpkg"); err == nil { - cmd := exec.Command("dpkg", "-l") - out, err := cmd.Output() - if err == nil { - lines := strings.Split(string(out), "\n") - count := 0 - for _, line := range lines { - if strings.HasPrefix(line, "ii ") { - count++ - } - } - return count, "dpkg (apt)" - } - } - - // rpm (RedHat/CentOS/Fedora) - if _, err := exec.LookPath("rpm"); err == nil { - cmd := exec.Command("rpm", "-qa") - out, err := cmd.Output() - if err == nil { - lines := strings.Split(strings.TrimSpace(string(out)), "\n") - return len(lines), "rpm (yum/dnf)" - } - } - - // pacman (Arch Linux) - if _, err := exec.LookPath("pacman"); err == nil { - cmd := exec.Command("pacman", "-Q") - out, err := cmd.Output() - if err == nil { - lines := strings.Split(strings.TrimSpace(string(out)), "\n") - return len(lines), "pacman" - } - } - - return 0, "unknown" -} - -func readSystemTemperature() float64 { - var cpuTemp float64 = 0 - var fallbackTemp float64 = 0 - - // 1. 优先读取 thermal_zone (通常是 CPU 温度) - thermalDir := "/sys/class/thermal" - entries, err := os.ReadDir(thermalDir) - if err == nil { - for _, entry := range entries { - if !strings.HasPrefix(entry.Name(), "thermal_zone") { - continue - } - tempPath := thermalDir + "/" + entry.Name() + "/temp" - if temp := readTempFromFile(tempPath); temp > 0 && temp > 20 && temp < 120 { - // thermal_zone0 通常是 CPU - if entry.Name() == "thermal_zone0" { - cpuTemp = temp - break - } else if fallbackTemp == 0 { - fallbackTemp = temp - } - } - } - } - - // 2. 扫描所有 hwmon 设备,查找 CPU 温度 - hwmonDir := "/sys/class/hwmon" - entries, err = os.ReadDir(hwmonDir) - if err == nil { - for _, entry := range entries { - hwmonPath := hwmonDir + "/" + entry.Name() - - // 读取 name 文件,检查是否是 CPU 相关 - namePath := hwmonPath + "/name" - name := strings.ToLower(strings.TrimSpace(readFirstLine(namePath))) - - // 检查是否是 CPU 温度传感器 - isCPU := strings.Contains(name, "cpu") || - strings.Contains(name, "core") || - strings.Contains(name, "k10temp") || - strings.Contains(name, "coretemp") || - strings.Contains(name, "zenpower") - - // 尝试读取 temp1_input (通常是 CPU) - temp1Path := hwmonPath + "/temp1_input" - if temp := readTempFromFile(temp1Path); temp > 0 && temp > 20 && temp < 120 { - if isCPU { - cpuTemp = temp - break - } else if fallbackTemp == 0 { - fallbackTemp = temp - } - } - - // 也尝试 temp2_input - temp2Path := hwmonPath + "/temp2_input" - if temp := readTempFromFile(temp2Path); temp > 0 && temp > 20 && temp < 120 { - if isCPU && cpuTemp == 0 { - cpuTemp = temp - } else if fallbackTemp == 0 { - fallbackTemp = temp - } - } - } - } - - // 优先返回 CPU 温度,如果没有则返回其他温度 - if cpuTemp > 0 { - return cpuTemp - } - return fallbackTemp -} - -// readDiskSpeed 读取磁盘瞬时读写速度 (MB/s) -func readDiskSpeed() (float64, float64) { - // 第一次读取 - readSectors1, writeSectors1 := getDiskSectors() - if readSectors1 == 0 && writeSectors1 == 0 { - return 0, 0 - } - - // 等待1秒 - time.Sleep(1 * time.Second) - - // 第二次读取 - readSectors2, writeSectors2 := getDiskSectors() - - // 计算差值(扇区数) - readDiff := readSectors2 - readSectors1 - writeDiff := writeSectors2 - writeSectors1 - - // 扇区大小通常是 512 字节,转换为 MB/s - readSpeed := float64(readDiff) * 512 / 1024 / 1024 - writeSpeed := float64(writeDiff) * 512 / 1024 / 1024 - - return round(readSpeed, 2), round(writeSpeed, 2) -} - -func getDiskSectors() (uint64, uint64) { - f, err := os.Open("/proc/diskstats") - if err != nil { - return 0, 0 - } - defer f.Close() - - scanner := bufio.NewScanner(f) - - var maxRead uint64 = 0 - var mainDevice string - - // 第一次遍历:找到读写量最大的主磁盘(通常是系统盘) - for scanner.Scan() { - line := scanner.Text() - fields := strings.Fields(line) - if len(fields) < 14 { - continue - } - - deviceName := fields[2] - // 跳过分区(分区名通常包含数字,如 sda1, vda1, nvme0n1p1) - if strings.ContainsAny(deviceName, "0123456789") && - !strings.HasPrefix(deviceName, "nvme") && - !strings.HasPrefix(deviceName, "loop") { - continue - } - - // 跳过虚拟设备 - if strings.HasPrefix(deviceName, "loop") || - strings.HasPrefix(deviceName, "ram") || - strings.HasPrefix(deviceName, "zram") { - continue - } - - readSectors, _ := strconv.ParseUint(fields[5], 10, 64) - // 选择读写量最大的作为主磁盘 - if readSectors > maxRead { - maxRead = readSectors - mainDevice = deviceName - } - } - - // 第二次遍历:读取主磁盘的数据 - f.Close() - f, err = os.Open("/proc/diskstats") - if err != nil { - return 0, 0 - } - scanner = bufio.NewScanner(f) - for scanner.Scan() { - line := scanner.Text() - fields := strings.Fields(line) - if len(fields) < 14 { - continue - } - - if fields[2] == mainDevice { - readSectors, _ := strconv.ParseUint(fields[5], 10, 64) - writeSectors, _ := strconv.ParseUint(fields[9], 10, 64) - return readSectors, writeSectors - } - } - - // 如果没找到,尝试常见的设备名(向后兼容) - f.Close() - f, err = os.Open("/proc/diskstats") - if err != nil { - return 0, 0 - } - scanner = bufio.NewScanner(f) - for scanner.Scan() { - line := scanner.Text() - fields := strings.Fields(line) - if len(fields) < 14 { - continue - } - - deviceName := fields[2] - if deviceName == "sda" || deviceName == "vda" || deviceName == "nvme0n1" { - readSectors, _ := strconv.ParseUint(fields[5], 10, 64) - writeSectors, _ := strconv.ParseUint(fields[9], 10, 64) - return readSectors, writeSectors - } - } - - return 0, 0 -} - -// readTopProcesses 读取 Top 5 进程 (按 CPU 使用率) -func readTopProcesses() []ProcessInfo { - processes := []ProcessInfo{} - - // 读取系统总内存 - memInfo, _ := readMemory() - totalMemGB := float64(memInfo.TotalBytes) / 1024 / 1024 / 1024 - - // 使用 ps 命令获取进程信息,添加超时控制 - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - cmd := exec.CommandContext(ctx, "ps", "aux", "--sort=-%cpu", "--no-headers") - out, err := cmd.Output() - if err != nil { - return processes - } - - lines := strings.Split(string(out), "\n") - count := 0 - - for _, line := range lines { - if count >= 5 { // 只取前5个 - break - } - - line = strings.TrimSpace(line) - if line == "" { - continue - } - - fields := strings.Fields(line) - if len(fields) < 11 { - continue - } - - pid, _ := strconv.Atoi(fields[1]) - cpu, _ := strconv.ParseFloat(fields[2], 64) - mem, _ := strconv.ParseFloat(fields[3], 64) - - // 计算内存MB数 - memoryMB := (mem / 100) * totalMemGB * 1024 - - // 命令可能包含空格,从第11个字段开始拼接 - command := strings.Join(fields[10:], " ") - if len(command) > 50 { - command = command[:50] + "..." - } - - processes = append(processes, ProcessInfo{ - PID: pid, - Name: fields[10], - CPU: round(cpu, 1), - Memory: round(mem, 1), - MemoryMB: round(memoryMB, 1), - Command: command, - }) - count++ - } - - return processes -} - -// readSystemLogs 读取系统最新日志 -func readSystemLogs(count int) []string { - logs := []string{} - - // 尝试使用 journalctl 读取系统日志 - if _, err := exec.LookPath("journalctl"); err == nil { - cmd := exec.Command("journalctl", "-n", strconv.Itoa(count), "--no-pager", "-o", "short") - out, err := cmd.Output() - if err == nil { - lines := strings.Split(strings.TrimSpace(string(out)), "\n") - for _, line := range lines { - if line != "" { - logs = append(logs, line) - } - } - return logs - } - } - - // 如果 journalctl 不可用,尝试读取 /var/log/syslog 或 /var/log/messages - logFiles := []string{"/var/log/syslog", "/var/log/messages"} - for _, logFile := range logFiles { - f, err := os.Open(logFile) - if err != nil { - continue - } - defer f.Close() - - // 读取最后几行 - var lines []string - scanner := bufio.NewScanner(f) - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } - - // 取最后count行 - start := len(lines) - count - if start < 0 { - start = 0 - } - logs = lines[start:] - break - } - - return logs -} +package main + +import ( + "bufio" + "context" + "os" + "os/exec" + "strconv" + "strings" + "time" +) + +// readSystemStats 读取系统统计信息 +func readSystemStats() SystemStats { + stats := SystemStats{} + + // 读取进程数量 + stats.ProcessCount = countProcesses() + + // 读取已安装包数量和包管理器类型 + stats.PackageCount, stats.PackageManager = countPackages() + + // 读取系统温度 + stats.Temperature = readSystemTemperature() + + // 读取磁盘读写速度 + stats.DiskReadSpeed, stats.DiskWriteSpeed = readDiskSpeed() + + // 读取 Top 5 进程 + stats.TopProcesses = readTopProcesses() + + // 读取系统日志 + stats.SystemLogs = readSystemLogs(10) + + return stats +} + +func countProcesses() int { + entries, err := os.ReadDir("/proc") + if err != nil { + return 0 + } + count := 0 + for _, entry := range entries { + if entry.IsDir() { + // 进程目录是数字命名的 + if _, err := strconv.Atoi(entry.Name()); err == nil { + count++ + } + } + } + return count +} + +func countPackages() (int, string) { + // 尝试不同的包管理器 + // dpkg (Debian/Ubuntu) + if _, err := exec.LookPath("dpkg"); err == nil { + cmd := exec.Command("dpkg", "-l") + out, err := cmd.Output() + if err == nil { + lines := strings.Split(string(out), "\n") + count := 0 + for _, line := range lines { + if strings.HasPrefix(line, "ii ") { + count++ + } + } + return count, "dpkg (apt)" + } + } + + // rpm (RedHat/CentOS/Fedora) + if _, err := exec.LookPath("rpm"); err == nil { + cmd := exec.Command("rpm", "-qa") + out, err := cmd.Output() + if err == nil { + lines := strings.Split(strings.TrimSpace(string(out)), "\n") + return len(lines), "rpm (yum/dnf)" + } + } + + // pacman (Arch Linux) + if _, err := exec.LookPath("pacman"); err == nil { + cmd := exec.Command("pacman", "-Q") + out, err := cmd.Output() + if err == nil { + lines := strings.Split(strings.TrimSpace(string(out)), "\n") + return len(lines), "pacman" + } + } + + return 0, "unknown" +} + +func readSystemTemperature() float64 { + var cpuTemp float64 = 0 + var fallbackTemp float64 = 0 + + // 1. 优先读取 thermal_zone (通常是 CPU 温度) + thermalDir := "/sys/class/thermal" + entries, err := os.ReadDir(thermalDir) + if err == nil { + for _, entry := range entries { + if !strings.HasPrefix(entry.Name(), "thermal_zone") { + continue + } + tempPath := thermalDir + "/" + entry.Name() + "/temp" + if temp := readTempFromFile(tempPath); temp > 0 && temp > 20 && temp < 120 { + // thermal_zone0 通常是 CPU + if entry.Name() == "thermal_zone0" { + cpuTemp = temp + break + } else if fallbackTemp == 0 { + fallbackTemp = temp + } + } + } + } + + // 2. 扫描所有 hwmon 设备,查找 CPU 温度 + hwmonDir := "/sys/class/hwmon" + entries, err = os.ReadDir(hwmonDir) + if err == nil { + for _, entry := range entries { + hwmonPath := hwmonDir + "/" + entry.Name() + + // 读取 name 文件,检查是否是 CPU 相关 + namePath := hwmonPath + "/name" + name := strings.ToLower(strings.TrimSpace(readFirstLine(namePath))) + + // 检查是否是 CPU 温度传感器 + isCPU := strings.Contains(name, "cpu") || + strings.Contains(name, "core") || + strings.Contains(name, "k10temp") || + strings.Contains(name, "coretemp") || + strings.Contains(name, "zenpower") + + // 尝试读取 temp1_input (通常是 CPU) + temp1Path := hwmonPath + "/temp1_input" + if temp := readTempFromFile(temp1Path); temp > 0 && temp > 20 && temp < 120 { + if isCPU { + cpuTemp = temp + break + } else if fallbackTemp == 0 { + fallbackTemp = temp + } + } + + // 也尝试 temp2_input + temp2Path := hwmonPath + "/temp2_input" + if temp := readTempFromFile(temp2Path); temp > 0 && temp > 20 && temp < 120 { + if isCPU && cpuTemp == 0 { + cpuTemp = temp + } else if fallbackTemp == 0 { + fallbackTemp = temp + } + } + } + } + + // 优先返回 CPU 温度,如果没有则返回其他温度 + if cpuTemp > 0 { + return cpuTemp + } + return fallbackTemp +} + +// readDiskSpeed 读取磁盘瞬时读写速度 (MB/s) +func readDiskSpeed() (float64, float64) { + // 第一次读取 + readSectors1, writeSectors1 := getDiskSectors() + if readSectors1 == 0 && writeSectors1 == 0 { + return 0, 0 + } + + // 等待1秒 + time.Sleep(1 * time.Second) + + // 第二次读取 + readSectors2, writeSectors2 := getDiskSectors() + + // 计算差值(扇区数) + readDiff := readSectors2 - readSectors1 + writeDiff := writeSectors2 - writeSectors1 + + // 扇区大小通常是 512 字节,转换为 MB/s + readSpeed := float64(readDiff) * 512 / 1024 / 1024 + writeSpeed := float64(writeDiff) * 512 / 1024 / 1024 + + return round(readSpeed, 2), round(writeSpeed, 2) +} + +func getDiskSectors() (uint64, uint64) { + f, err := os.Open("/proc/diskstats") + if err != nil { + return 0, 0 + } + defer f.Close() + + scanner := bufio.NewScanner(f) + + var maxRead uint64 = 0 + var mainDevice string + + // 第一次遍历:找到读写量最大的主磁盘(通常是系统盘) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) < 14 { + continue + } + + deviceName := fields[2] + // 跳过分区(分区名通常包含数字,如 sda1, vda1, nvme0n1p1) + if strings.ContainsAny(deviceName, "0123456789") && + !strings.HasPrefix(deviceName, "nvme") && + !strings.HasPrefix(deviceName, "loop") { + continue + } + + // 跳过虚拟设备 + if strings.HasPrefix(deviceName, "loop") || + strings.HasPrefix(deviceName, "ram") || + strings.HasPrefix(deviceName, "zram") { + continue + } + + readSectors, _ := strconv.ParseUint(fields[5], 10, 64) + // 选择读写量最大的作为主磁盘 + if readSectors > maxRead { + maxRead = readSectors + mainDevice = deviceName + } + } + + // 第二次遍历:读取主磁盘的数据 + f.Close() + f, err = os.Open("/proc/diskstats") + if err != nil { + return 0, 0 + } + scanner = bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) < 14 { + continue + } + + if fields[2] == mainDevice { + readSectors, _ := strconv.ParseUint(fields[5], 10, 64) + writeSectors, _ := strconv.ParseUint(fields[9], 10, 64) + return readSectors, writeSectors + } + } + + // 如果没找到,尝试常见的设备名(向后兼容) + f.Close() + f, err = os.Open("/proc/diskstats") + if err != nil { + return 0, 0 + } + scanner = bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) < 14 { + continue + } + + deviceName := fields[2] + if deviceName == "sda" || deviceName == "vda" || deviceName == "nvme0n1" { + readSectors, _ := strconv.ParseUint(fields[5], 10, 64) + writeSectors, _ := strconv.ParseUint(fields[9], 10, 64) + return readSectors, writeSectors + } + } + + return 0, 0 +} + +// readTopProcesses 读取 Top 5 进程 (按 CPU 使用率) +func readTopProcesses() []ProcessInfo { + processes := []ProcessInfo{} + + // 读取系统总内存 + memInfo, _ := readMemory() + totalMemGB := float64(memInfo.TotalBytes) / 1024 / 1024 / 1024 + + // 使用 ps 命令获取进程信息,添加超时控制 + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, "ps", "aux", "--sort=-%cpu", "--no-headers") + out, err := cmd.Output() + if err != nil { + return processes + } + + lines := strings.Split(string(out), "\n") + count := 0 + + for _, line := range lines { + if count >= 5 { // 只取前5个 + break + } + + line = strings.TrimSpace(line) + if line == "" { + continue + } + + fields := strings.Fields(line) + if len(fields) < 11 { + continue + } + + pid, _ := strconv.Atoi(fields[1]) + cpu, _ := strconv.ParseFloat(fields[2], 64) + mem, _ := strconv.ParseFloat(fields[3], 64) + + // 计算内存MB数 + memoryMB := (mem / 100) * totalMemGB * 1024 + + // 命令可能包含空格,从第11个字段开始拼接 + command := strings.Join(fields[10:], " ") + if len(command) > 50 { + command = command[:50] + "..." + } + + processes = append(processes, ProcessInfo{ + PID: pid, + Name: fields[10], + CPU: round(cpu, 1), + Memory: round(mem, 1), + MemoryMB: round(memoryMB, 1), + Command: command, + }) + count++ + } + + return processes +} + +// readSystemLogs 读取系统最新日志 +func readSystemLogs(count int) []string { + logs := []string{} + + // 尝试使用 journalctl 读取系统日志 + if _, err := exec.LookPath("journalctl"); err == nil { + cmd := exec.Command("journalctl", "-n", strconv.Itoa(count), "--no-pager", "-o", "short") + out, err := cmd.Output() + if err == nil { + lines := strings.Split(strings.TrimSpace(string(out)), "\n") + for _, line := range lines { + if line != "" { + logs = append(logs, line) + } + } + return logs + } + } + + // 如果 journalctl 不可用,尝试读取 /var/log/syslog 或 /var/log/messages + logFiles := []string{"/var/log/syslog", "/var/log/messages"} + for _, logFile := range logFiles { + f, err := os.Open(logFile) + if err != nil { + continue + } + defer f.Close() + + // 读取最后几行 + var lines []string + scanner := bufio.NewScanner(f) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + // 取最后count行 + start := len(lines) - count + if start < 0 { + start = 0 + } + logs = lines[start:] + break + } + + return logs +} diff --git a/mengyamonitor-backend/systeminfo_linux.go b/mengyamonitor-backend/systeminfo_linux.go index 8b531ae..8189b1d 100644 --- a/mengyamonitor-backend/systeminfo_linux.go +++ b/mengyamonitor-backend/systeminfo_linux.go @@ -1,113 +1,113 @@ -//go:build linux -// +build linux - -package main - -import ( - "bufio" - "os" - "strings" - "syscall" -) - -func readStorage() ([]StorageMetrics, error) { - // For simplicity, report root mount. Can be extended to iterate mounts. - var stat syscall.Statfs_t - if err := syscall.Statfs("/", &stat); err != nil { - return nil, err - } - total := stat.Blocks * uint64(stat.Bsize) - free := stat.Bfree * uint64(stat.Bsize) - used := total - free - usedPercent := 0.0 - if total > 0 { - usedPercent = (float64(used) / float64(total)) * 100 - } - return []StorageMetrics{{ - Mount: "/", - TotalBytes: total, - UsedBytes: used, - FreeBytes: free, - UsedPercent: round(usedPercent, 2), - }}, nil -} - -// readAllStorage 读取所有挂载的存储设备 -func readAllStorage() ([]StorageMetrics, error) { - storages := []StorageMetrics{} - - // 读取 /proc/mounts 获取所有挂载点 - f, err := os.Open("/proc/mounts") - if err != nil { - return readStorage() // 降级到只读根目录 - } - defer f.Close() - - scanner := bufio.NewScanner(f) - seen := make(map[string]bool) - - for scanner.Scan() { - line := scanner.Text() - fields := strings.Fields(line) - if len(fields) < 3 { - continue - } - - device := fields[0] - mountPoint := fields[1] - fsType := fields[2] - - // 跳过虚拟文件系统 - if strings.HasPrefix(device, "/dev/") == false { - continue - } - - // 跳过特殊文件系统类型 - skipTypes := map[string]bool{ - "tmpfs": true, "devtmpfs": true, "squashfs": true, - "overlay": true, "aufs": true, "proc": true, - "sysfs": true, "devpts": true, "cgroup": true, - } - if skipTypes[fsType] { - continue - } - - // 避免重复挂载点 - if seen[mountPoint] { - continue - } - seen[mountPoint] = true - - // 获取该挂载点的统计信息 - var stat syscall.Statfs_t - if err := syscall.Statfs(mountPoint, &stat); err != nil { - continue - } - - total := stat.Blocks * uint64(stat.Bsize) - free := stat.Bfree * uint64(stat.Bsize) - used := total - free - usedPercent := 0.0 - if total > 0 { - usedPercent = (float64(used) / float64(total)) * 100 - } - - // 只添加有容量的存储 - if total > 0 { - storages = append(storages, StorageMetrics{ - Mount: mountPoint, - TotalBytes: total, - UsedBytes: used, - FreeBytes: free, - UsedPercent: round(usedPercent, 2), - }) - } - } - - // 如果没有找到任何存储,至少返回根目录 - if len(storages) == 0 { - return readStorage() - } - - return storages, nil -} +//go:build linux +// +build linux + +package main + +import ( + "bufio" + "os" + "strings" + "syscall" +) + +func readStorage() ([]StorageMetrics, error) { + // For simplicity, report root mount. Can be extended to iterate mounts. + var stat syscall.Statfs_t + if err := syscall.Statfs("/", &stat); err != nil { + return nil, err + } + total := stat.Blocks * uint64(stat.Bsize) + free := stat.Bfree * uint64(stat.Bsize) + used := total - free + usedPercent := 0.0 + if total > 0 { + usedPercent = (float64(used) / float64(total)) * 100 + } + return []StorageMetrics{{ + Mount: "/", + TotalBytes: total, + UsedBytes: used, + FreeBytes: free, + UsedPercent: round(usedPercent, 2), + }}, nil +} + +// readAllStorage 读取所有挂载的存储设备 +func readAllStorage() ([]StorageMetrics, error) { + storages := []StorageMetrics{} + + // 读取 /proc/mounts 获取所有挂载点 + f, err := os.Open("/proc/mounts") + if err != nil { + return readStorage() // 降级到只读根目录 + } + defer f.Close() + + scanner := bufio.NewScanner(f) + seen := make(map[string]bool) + + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) < 3 { + continue + } + + device := fields[0] + mountPoint := fields[1] + fsType := fields[2] + + // 跳过虚拟文件系统 + if strings.HasPrefix(device, "/dev/") == false { + continue + } + + // 跳过特殊文件系统类型 + skipTypes := map[string]bool{ + "tmpfs": true, "devtmpfs": true, "squashfs": true, + "overlay": true, "aufs": true, "proc": true, + "sysfs": true, "devpts": true, "cgroup": true, + } + if skipTypes[fsType] { + continue + } + + // 避免重复挂载点 + if seen[mountPoint] { + continue + } + seen[mountPoint] = true + + // 获取该挂载点的统计信息 + var stat syscall.Statfs_t + if err := syscall.Statfs(mountPoint, &stat); err != nil { + continue + } + + total := stat.Blocks * uint64(stat.Bsize) + free := stat.Bfree * uint64(stat.Bsize) + used := total - free + usedPercent := 0.0 + if total > 0 { + usedPercent = (float64(used) / float64(total)) * 100 + } + + // 只添加有容量的存储 + if total > 0 { + storages = append(storages, StorageMetrics{ + Mount: mountPoint, + TotalBytes: total, + UsedBytes: used, + FreeBytes: free, + UsedPercent: round(usedPercent, 2), + }) + } + } + + // 如果没有找到任何存储,至少返回根目录 + if len(storages) == 0 { + return readStorage() + } + + return storages, nil +} diff --git a/mengyamonitor-backend/systeminfo_other.go b/mengyamonitor-backend/systeminfo_other.go index f4c0924..2aaf8ac 100644 --- a/mengyamonitor-backend/systeminfo_other.go +++ b/mengyamonitor-backend/systeminfo_other.go @@ -1,14 +1,14 @@ -//go:build !linux -// +build !linux - -package main - -import "errors" - -func readStorage() ([]StorageMetrics, error) { - return nil, errors.New("storage monitoring is only supported on Linux") -} - -func readAllStorage() ([]StorageMetrics, error) { - return nil, errors.New("storage monitoring is only supported on Linux") -} +//go:build !linux +// +build !linux + +package main + +import "errors" + +func readStorage() ([]StorageMetrics, error) { + return nil, errors.New("storage monitoring is only supported on Linux") +} + +func readAllStorage() ([]StorageMetrics, error) { + return nil, errors.New("storage monitoring is only supported on Linux") +} diff --git a/mengyamonitor-backend/utils.go b/mengyamonitor-backend/utils.go index 7a028e8..84f606a 100644 --- a/mengyamonitor-backend/utils.go +++ b/mengyamonitor-backend/utils.go @@ -1,66 +1,66 @@ -package main - -import ( - "bufio" - "math" - "os" - "strconv" - "strings" -) - -// round 将浮点数四舍五入到指定小数位 -func round(v float64, places int) float64 { - factor := math.Pow(10, float64(places)) - return math.Round(v*factor) / factor -} - -// readFirstLine 读取文件的第一行 -func readFirstLine(path string) string { - f, err := os.Open(path) - if err != nil { - return "" - } - defer f.Close() - scanner := bufio.NewScanner(f) - if scanner.Scan() { - return scanner.Text() - } - return "" -} - -// firstMatchInFile 在文件中查找第一个匹配指定前缀的行,并返回冒号后的内容 -func firstMatchInFile(path, prefix string) string { - f, err := os.Open(path) - if err != nil { - return "" - } - defer f.Close() - scanner := bufio.NewScanner(f) - for scanner.Scan() { - line := scanner.Text() - if strings.HasPrefix(strings.TrimSpace(line), prefix) { - parts := strings.SplitN(line, ":", 2) - if len(parts) == 2 { - return strings.TrimSpace(parts[1]) - } - } - } - return "" -} - -// readTempFromFile 从文件读取温度值 -func readTempFromFile(path string) float64 { - content := readFirstLine(path) - if content == "" { - return 0 - } - val, err := strconv.ParseFloat(strings.TrimSpace(content), 64) - if err != nil { - return 0 - } - // 温度通常以毫度为单位 - if val > 1000 { - return round(val/1000, 1) - } - return round(val, 1) -} +package main + +import ( + "bufio" + "math" + "os" + "strconv" + "strings" +) + +// round 将浮点数四舍五入到指定小数位 +func round(v float64, places int) float64 { + factor := math.Pow(10, float64(places)) + return math.Round(v*factor) / factor +} + +// readFirstLine 读取文件的第一行 +func readFirstLine(path string) string { + f, err := os.Open(path) + if err != nil { + return "" + } + defer f.Close() + scanner := bufio.NewScanner(f) + if scanner.Scan() { + return scanner.Text() + } + return "" +} + +// firstMatchInFile 在文件中查找第一个匹配指定前缀的行,并返回冒号后的内容 +func firstMatchInFile(path, prefix string) string { + f, err := os.Open(path) + if err != nil { + return "" + } + defer f.Close() + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(strings.TrimSpace(line), prefix) { + parts := strings.SplitN(line, ":", 2) + if len(parts) == 2 { + return strings.TrimSpace(parts[1]) + } + } + } + return "" +} + +// readTempFromFile 从文件读取温度值 +func readTempFromFile(path string) float64 { + content := readFirstLine(path) + if content == "" { + return 0 + } + val, err := strconv.ParseFloat(strings.TrimSpace(content), 64) + if err != nil { + return 0 + } + // 温度通常以毫度为单位 + if val > 1000 { + return round(val/1000, 1) + } + return round(val, 1) +} diff --git a/mengyamonitor-frontend/README.md b/mengyamonitor-frontend/README.md index d17fa10..dcedbed 100644 --- a/mengyamonitor-frontend/README.md +++ b/mengyamonitor-frontend/README.md @@ -1,110 +1,110 @@ -# 萌芽监控面板 - 前端 - -## 概述 -服务器监控面板前端应用,使用 React + TypeScript + Vite 构建。 - -## 功能特性 -- 🖥️ 多服务器监控支持 -- 📊 实时数据展示(CPU、内存、存储、GPU) -- 🔄 自动刷新(2秒轮询) -- 📱 响应式设计 -- 🎨 白色柔和风格界面 -- 💾 本地存储配置 - -## 开发 - -### 安装依赖 -```bash -npm install -``` - -### 启动开发服务器 -```bash -npm run dev -``` -访问 http://localhost:2929 - -### 构建生产版本 -```bash -npm run build -``` -生产文件将输出到 `dist` 目录 - -### 预览生产版本 -```bash -npm run preview -``` - -## 使用说明 - -### 添加服务器 -1. 点击右上角"添加服务器"按钮 -2. 输入服务器名称(如:生产服务器1) -3. 输入服务器地址(如:http://192.168.1.100:9292) -4. 点击"添加"按钮 - -### 查看详情 -点击服务器卡片上的"查看详情"按钮,可以查看完整的系统信息: -- 系统信息(主机名、操作系统、内核、架构、运行时间) -- CPU 详细信息(型号、核心数、使用率、负载) -- 内存详细信息(总容量、已使用、可用、使用率) -- 存储详细信息(挂载点、容量、使用情况) -- GPU 信息(名称、显存、利用率) - -### 移除服务器 -点击服务器卡片右上角的"×"按钮,确认后即可移除。 - -## 项目结构 -``` -src/ -├── api/ # API 调用 -│ └── monitor.ts # 监控 API -├── components/ # React 组件 -│ ├── ServerCard/ # 服务器卡片组件 -│ └── ServerDetail/ # 服务器详情弹窗 -├── hooks/ # 自定义 Hooks -│ └── useServerMonitor.ts # 服务器监控 Hook -├── types/ # TypeScript 类型定义 -│ └── index.ts -├── utils/ # 工具函数 -│ ├── format.ts # 格式化函数 -│ └── storage.ts # 本地存储 -├── App.tsx # 主应用组件 -├── App.css # 主应用样式 -├── main.tsx # 应用入口 -└── index.css # 全局样式 -``` - -## 配置说明 - -### 修改端口 -编辑 `vite.config.ts` 文件中的 `server.port` 配置: -```typescript -server: { - port: 2929, // 修改为你想要的端口 -} -``` - -### 修改轮询间隔 -编辑 `src/App.tsx` 文件中的 `useServerMonitor` 第二个参数: -```typescript -const statuses = useServerMonitor(servers, 2000); // 2000ms = 2秒 -``` - -## 部署 - -### 静态文件部署 -1. 构建项目:`npm run build` -2. 将 `dist` 目录部署到 Web 服务器(如 Nginx、Apache) -3. 或使用静态托管服务(如 Vercel、Netlify) - -### 桌面应用 -可以使用 Electron 或 Tauri 将前端打包成桌面应用: -- Electron: https://www.electronjs.org/ -- Tauri: https://tauri.app/ - -## 注意事项 -- 确保后端服务器已启动并可访问 -- 后端服务器需要配置 CORS 允许跨域访问 -- 服务器地址需要包含协议(http:// 或 https://) -- 本地存储的服务器配置保存在浏览器 localStorage 中 +# 萌芽监控面板 - 前端 + +## 概述 +服务器监控面板前端应用,使用 React + TypeScript + Vite 构建。 + +## 功能特性 +- 🖥️ 多服务器监控支持 +- 📊 实时数据展示(CPU、内存、存储、GPU) +- 🔄 自动刷新(2秒轮询) +- 📱 响应式设计 +- 🎨 白色柔和风格界面 +- 💾 本地存储配置 + +## 开发 + +### 安装依赖 +```bash +npm install +``` + +### 启动开发服务器 +```bash +npm run dev +``` +访问 http://localhost:2929 + +### 构建生产版本 +```bash +npm run build +``` +生产文件将输出到 `dist` 目录 + +### 预览生产版本 +```bash +npm run preview +``` + +## 使用说明 + +### 添加服务器 +1. 点击右上角"添加服务器"按钮 +2. 输入服务器名称(如:生产服务器1) +3. 输入服务器地址(如:http://192.168.1.100:9292) +4. 点击"添加"按钮 + +### 查看详情 +点击服务器卡片上的"查看详情"按钮,可以查看完整的系统信息: +- 系统信息(主机名、操作系统、内核、架构、运行时间) +- CPU 详细信息(型号、核心数、使用率、负载) +- 内存详细信息(总容量、已使用、可用、使用率) +- 存储详细信息(挂载点、容量、使用情况) +- GPU 信息(名称、显存、利用率) + +### 移除服务器 +点击服务器卡片右上角的"×"按钮,确认后即可移除。 + +## 项目结构 +``` +src/ +├── api/ # API 调用 +│ └── monitor.ts # 监控 API +├── components/ # React 组件 +│ ├── ServerCard/ # 服务器卡片组件 +│ └── ServerDetail/ # 服务器详情弹窗 +├── hooks/ # 自定义 Hooks +│ └── useServerMonitor.ts # 服务器监控 Hook +├── types/ # TypeScript 类型定义 +│ └── index.ts +├── utils/ # 工具函数 +│ ├── format.ts # 格式化函数 +│ └── storage.ts # 本地存储 +├── App.tsx # 主应用组件 +├── App.css # 主应用样式 +├── main.tsx # 应用入口 +└── index.css # 全局样式 +``` + +## 配置说明 + +### 修改端口 +编辑 `vite.config.ts` 文件中的 `server.port` 配置: +```typescript +server: { + port: 2929, // 修改为你想要的端口 +} +``` + +### 修改轮询间隔 +编辑 `src/App.tsx` 文件中的 `useServerMonitor` 第二个参数: +```typescript +const statuses = useServerMonitor(servers, 2000); // 2000ms = 2秒 +``` + +## 部署 + +### 静态文件部署 +1. 构建项目:`npm run build` +2. 将 `dist` 目录部署到 Web 服务器(如 Nginx、Apache) +3. 或使用静态托管服务(如 Vercel、Netlify) + +### 桌面应用 +可以使用 Electron 或 Tauri 将前端打包成桌面应用: +- Electron: https://www.electronjs.org/ +- Tauri: https://tauri.app/ + +## 注意事项 +- 确保后端服务器已启动并可访问 +- 后端服务器需要配置 CORS 允许跨域访问 +- 服务器地址需要包含协议(http:// 或 https://) +- 本地存储的服务器配置保存在浏览器 localStorage 中 diff --git a/mengyamonitor-frontend/index.html b/mengyamonitor-frontend/index.html index 26c2ebe..3f0b36e 100644 --- a/mengyamonitor-frontend/index.html +++ b/mengyamonitor-frontend/index.html @@ -1,9 +1,15 @@ - + - + + + + + + + 萌芽监控面板 diff --git a/mengyamonitor-frontend/package-lock.json b/mengyamonitor-frontend/package-lock.json index 30bd1ff..7088438 100644 --- a/mengyamonitor-frontend/package-lock.json +++ b/mengyamonitor-frontend/package-lock.json @@ -8,6 +8,9 @@ "name": "mengyamonitor-frontend", "version": "0.0.0", "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "react": "^19.2.0", "react-dom": "^19.2.0" }, @@ -23,17 +26,18 @@ "globals": "^16.5.0", "typescript": "~5.9.3", "typescript-eslint": "^8.46.4", - "vite": "^7.2.4" + "vite": "^7.2.4", + "vite-plugin-pwa": "^1.2.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==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -42,9 +46,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -57,7 +61,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -84,14 +87,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -100,14 +103,27 @@ "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==", + "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/compat-data": "^7.27.2", + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -117,6 +133,63 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -127,30 +200,44 @@ "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==", + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "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==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -159,12 +246,75 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { + "node_modules/@babel/helper-optimise-call-expression": { "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==", + "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.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "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" } @@ -199,6 +349,21 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "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", @@ -214,13 +379,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -229,6 +394,811 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "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-async-generator-functions": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "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-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "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-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "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-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "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-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "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-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "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-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "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-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "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-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "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-react-jsx-self": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", @@ -261,34 +1231,341 @@ "@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==", + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "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-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "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-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "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-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "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-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "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-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "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-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", + "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -296,9 +1573,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -309,6 +1586,59 @@ "node": ">=6.9.0" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -960,6 +2290,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -992,6 +2332,17 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -1017,6 +2368,77 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.53.3", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", @@ -1325,6 +2747,19 @@ "win32" ] }, + "node_modules/@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1390,7 +2825,6 @@ "integrity": "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1401,7 +2835,6 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1416,6 +2849,20 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.49.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.49.0.tgz", @@ -1461,7 +2908,6 @@ "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", @@ -1713,7 +3159,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1771,6 +3216,130 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", + "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.6", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz", + "integrity": "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.6", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", + "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.6" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1819,7 +3388,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1834,6 +3402,63 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1902,6 +3527,23 @@ "dev": true, "license": "MIT" }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1916,6 +3558,20 @@ "dev": true, "license": "MIT" }, + "node_modules/core-js-compat": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1931,6 +3587,16 @@ "node": ">= 8" } }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -1938,6 +3604,60 @@ "dev": true, "license": "MIT" }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1963,6 +3683,83 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "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/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", @@ -1970,6 +3767,142 @@ "dev": true, "license": "ISC" }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -2041,7 +3974,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2210,6 +4142,13 @@ "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==", + "dev": true, + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2241,6 +4180,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2272,6 +4228,39 @@ "node": ">=16.0.0" } }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/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/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2310,6 +4299,55 @@ "dev": true, "license": "ISC" }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2325,6 +4363,57 @@ "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==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2335,6 +4424,95 @@ "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==", + "dev": true, + "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-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2348,6 +4526,45 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "16.5.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", @@ -2361,6 +4578,56 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2371,6 +4638,77 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -2388,6 +4726,13 @@ "hermes-estree": "0.25.1" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2425,6 +4770,156 @@ "node": ">=0.8.19" } }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2435,6 +4930,42 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2448,6 +4979,241 @@ "node": ">=0.10.0" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2455,6 +5221,40 @@ "dev": true, "license": "ISC" }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2495,6 +5295,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2522,6 +5329,29 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2532,6 +5362,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2562,6 +5402,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2569,6 +5423,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "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", @@ -2579,6 +5440,26 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2592,6 +5473,16 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2632,6 +5523,50 @@ "dev": true, "license": "MIT" }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2650,6 +5585,24 @@ "node": ">= 0.8.0" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -2682,6 +5635,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2715,6 +5675,40 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2728,7 +5722,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2736,6 +5729,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -2775,6 +5778,19 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2785,12 +5801,21 @@ "node": ">=6" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/react": { "version": "19.2.1", "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -2817,6 +5842,139 @@ "node": ">=0.10.0" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2869,6 +6027,82 @@ "fsevents": "~2.3.2" } }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -2885,6 +6119,65 @@ "semver": "bin/semver.js" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2908,6 +6201,119 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "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/smob": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.1.tgz", + "integrity": "sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2918,6 +6324,161 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -2944,6 +6505,67 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -2961,6 +6583,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -2974,6 +6606,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2987,13 +6625,103 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3026,6 +6754,25 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", @@ -3033,6 +6780,84 @@ "dev": true, "license": "MIT" }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", @@ -3080,7 +6905,6 @@ "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -3150,6 +6974,56 @@ } } }, + "node_modules/vite-plugin-pwa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.2.0.tgz", + "integrity": "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.6", + "pretty-bytes": "^6.1.1", + "tinyglobby": "^0.2.10", + "workbox-build": "^7.4.0", + "workbox-window": "^7.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vite-pwa/assets-generator": "^1.0.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "workbox-build": "^7.4.0", + "workbox-window": "^7.4.0" + }, + "peerDependenciesMeta": { + "@vite-pwa/assets-generator": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3166,6 +7040,95 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -3176,6 +7139,367 @@ "node": ">=0.10.0" } }, + "node_modules/workbox-background-sync": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.0.tgz", + "integrity": "sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.0.tgz", + "integrity": "sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-build": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.0.tgz", + "integrity": "sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.24.4", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^2.4.1", + "@rollup/plugin-terser": "^0.4.3", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^11.0.1", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.79.2", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "7.4.0", + "workbox-broadcast-update": "7.4.0", + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-google-analytics": "7.4.0", + "workbox-navigation-preload": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-range-requests": "7.4.0", + "workbox-recipes": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0", + "workbox-streams": "7.4.0", + "workbox-sw": "7.4.0", + "workbox-window": "7.4.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/workbox-build/node_modules/@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/workbox-build/node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/workbox-build/node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/workbox-build/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-build/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/workbox-build/node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-build/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-build/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/workbox-build/node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/workbox-build/node_modules/rollup": { + "version": "2.80.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", + "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.0.tgz", + "integrity": "sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-core": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.0.tgz", + "integrity": "sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-expiration": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.0.tgz", + "integrity": "sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-google-analytics": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.0.tgz", + "integrity": "sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-background-sync": "7.4.0", + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.0.tgz", + "integrity": "sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-precaching": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.0.tgz", + "integrity": "sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" + } + }, + "node_modules/workbox-range-requests": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.0.tgz", + "integrity": "sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-recipes": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.0.tgz", + "integrity": "sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" + } + }, + "node_modules/workbox-routing": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.0.tgz", + "integrity": "sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-strategies": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.0.tgz", + "integrity": "sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-streams": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.0.tgz", + "integrity": "sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0" + } + }, + "node_modules/workbox-sw": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.0.tgz", + "integrity": "sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-window": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.0.tgz", + "integrity": "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "7.4.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -3202,7 +7526,6 @@ "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/mengyamonitor-frontend/package.json b/mengyamonitor-frontend/package.json index 17acb5b..8903a70 100644 --- a/mengyamonitor-frontend/package.json +++ b/mengyamonitor-frontend/package.json @@ -10,6 +10,9 @@ "preview": "vite preview" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "react": "^19.2.0", "react-dom": "^19.2.0" }, @@ -25,6 +28,7 @@ "globals": "^16.5.0", "typescript": "~5.9.3", "typescript-eslint": "^8.46.4", - "vite": "^7.2.4" + "vite": "^7.2.4", + "vite-plugin-pwa": "^1.2.0" } } diff --git a/mengyamonitor-frontend/public/logo.svg b/mengyamonitor-frontend/public/logo.svg new file mode 100644 index 0000000..70974ae --- /dev/null +++ b/mengyamonitor-frontend/public/logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/mengyamonitor-frontend/src/App.css b/mengyamonitor-frontend/src/App.css index b8bc260..7522e95 100644 --- a/mengyamonitor-frontend/src/App.css +++ b/mengyamonitor-frontend/src/App.css @@ -16,25 +16,87 @@ border-radius: var(--radius-md); box-shadow: var(--shadow-sm); border: 1px solid var(--border-color); + transition: all 0.3s ease; +} + +.header-actions { + display: flex; + align-items: center; + gap: 0.5rem; } .app-header h1 { margin: 0; - font-size: 0.75rem; - color: var(--text-light); + font-size: 1.5rem; + color: var(--text-main); display: flex; align-items: center; - gap: 0.25rem; - font-weight: 400; + gap: 0.75rem; + font-weight: 600; + transform: translateY(-3px); } .app-logo { - width: 16px; - height: 16px; - border-radius: 4px; + width: 36px; + height: 36px; + border-radius: 6px; display: inline-block; } +.btn-icon { + background: var(--surface-color); + color: var(--text-main); + border: 1px solid var(--border-color); + padding: 0.5rem; + width: 32px; + height: 32px; + border-radius: var(--radius-md); + font-size: 1rem; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: var(--shadow-sm); + display: flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +.btn-icon:hover { + background: var(--bg-color); + border-color: var(--primary-color); + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.btn-show-header { + position: fixed; + top: 5px; + right: 5px; + width: 24px; + height: 24px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.5); + border: 1px solid var(--border-color); + color: var(--text-secondary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.75rem; + z-index: 1000; + opacity: 0.5; + transition: all 0.2s; + padding: 0; +} + +.btn-show-header:hover { + opacity: 1; + background: var(--surface-color); + box-shadow: var(--shadow-sm); + transform: scale(1.1); + color: var(--primary-color); +} + .btn-add { background: var(--primary-color); color: white; @@ -163,16 +225,7 @@ justify-content: center; } - .server-grid { - grid-template-columns: 1fr; - } - - .add-form { - flex-direction: column; - padding: 1.5rem; - } - - .add-form input { - min-width: 100%; + .header-actions { + justify-content: center; } } diff --git a/mengyamonitor-frontend/src/App.tsx b/mengyamonitor-frontend/src/App.tsx index 891f866..151abe4 100644 --- a/mengyamonitor-frontend/src/App.tsx +++ b/mengyamonitor-frontend/src/App.tsx @@ -1,34 +1,80 @@ import { useState, useEffect } from 'react'; import type { ServerConfig } from './types'; -import { loadServers, saveServers, removeServer } from './utils/storage'; +import { loadServers, saveServers, removeServer, exportServersToClipboard, importServersFromClipboard } from './utils/storage'; import { useServerMonitor } from './hooks/useServerMonitor'; import { ServerCard } from './components/ServerCard/ServerCard'; import { ServerDetail } from './components/ServerDetail/ServerDetail'; +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import type { DragEndEvent } from '@dnd-kit/core'; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + rectSortingStrategy, +} from '@dnd-kit/sortable'; import './App.css'; +const formatServerUrl = (url: string) => { + let formatted = url.trim(); + + // Fix common typo .op -> .top based on user requirement + if (formatted.endsWith('.op')) { + formatted = formatted.slice(0, -3) + '.top'; + } + + if (formatted && !/^https?:\/\//i.test(formatted)) { + return `http://${formatted}`; + } + return formatted; +}; + function App() { const [servers, setServers] = useState([]); const [showAddForm, setShowAddForm] = useState(false); const [selectedServerId, setSelectedServerId] = useState(null); const [newServerForm, setNewServerForm] = useState({ name: '', url: '' }); + const [showHeader, setShowHeader] = useState(true); const statuses = useServerMonitor(servers, 2000); + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); + useEffect(() => { const loaded = loadServers(); setServers(loaded); }, []); + const handleUrlBlur = () => { + const formatted = formatServerUrl(newServerForm.url); + if (formatted !== newServerForm.url) { + setNewServerForm(prev => ({ ...prev, url: formatted })); + } + }; + const handleAddServer = () => { if (!newServerForm.name || !newServerForm.url) { alert('请填写服务器名称和地址'); return; } + const formattedUrl = formatServerUrl(newServerForm.url); + const newServer: ServerConfig = { id: Date.now().toString(), name: newServerForm.name, - url: newServerForm.url, + url: formattedUrl, enabled: true, }; @@ -51,20 +97,94 @@ function App() { setSelectedServerId(serverId); }; + const handleExportServers = async () => { + try { + await exportServersToClipboard(); + alert('服务器配置已复制到剪贴板!'); + } catch (error) { + alert(error instanceof Error ? error.message : '导出失败'); + } + }; + + const handleImportServers = async () => { + if (!confirm('导入服务器配置将添加到现有服务器列表中,是否继续?')) { + return; + } + + try { + const importedServers = await importServersFromClipboard(); + const updated = [...servers, ...importedServers]; + setServers(updated); + saveServers(updated); + alert(`成功导入 ${importedServers.length} 个服务器配置!`); + } catch (error) { + alert(error instanceof Error ? error.message : '导入失败'); + } + }; + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + + if (over && active.id !== over.id) { + setServers((items) => { + const oldIndex = items.findIndex((item) => item.id === active.id); + const newIndex = items.findIndex((item) => item.id === over.id); + const newOrder = arrayMove(items, oldIndex, newIndex); + saveServers(newOrder); + return newOrder; + }); + } + }; + const selectedStatus = selectedServerId ? statuses[selectedServerId] : null; const selectedServer = servers.find(s => s.id === selectedServerId); return (
-
-

- 萌芽监控面板 - 萌芽监控面板 -

- + )} + + {showHeader && ( +
+

+ 萌芽监控面板 + 萌芽监控面板 +

+
+ + + + +
+ )} {showAddForm && (
@@ -79,6 +199,7 @@ function App() { placeholder="服务器地址 (例如: http://192.168.1.100:9292)" value={newServerForm.url} onChange={(e) => setNewServerForm({ ...newServerForm, url: e.target.value })} + onBlur={handleUrlBlur} />
) : ( - servers.map((server) => { - const status = statuses[server.id]; - // Calculate storage usage (max of all mounts) - const storageUsage = status?.metrics?.storage?.reduce((max, s) => Math.max(max, s.usedPercent), 0) || 0; - - return ( - - ); - }) + + s.id)} strategy={rectSortingStrategy}> + {servers.map((server) => { + const status = statuses[server.id]; + // Calculate storage usage (max of all mounts) + const storageUsage = status?.metrics?.storage?.reduce((max, s) => Math.max(max, s.usedPercent), 0) || 0; + + return ( + + ); + })} + + )} diff --git a/mengyamonitor-frontend/src/api/monitor.ts b/mengyamonitor-frontend/src/api/monitor.ts index 0821b37..a5b22e4 100644 --- a/mengyamonitor-frontend/src/api/monitor.ts +++ b/mengyamonitor-frontend/src/api/monitor.ts @@ -1,101 +1,101 @@ -import type { ServerMetrics } from '../types'; - -export const fetchServerMetrics = async (serverUrl: string): Promise => { - // 测量客户端到服务器的延迟(使用健康检查端点) - const clientToServerLatency = await measureLatency(`${serverUrl}/api/health`); - - // 并行请求各个端点 - const endpoints = [ - '/api/metrics/cpu', - '/api/metrics/memory', - '/api/metrics/storage', - '/api/metrics/gpu', - '/api/metrics/network', - '/api/metrics/system', - '/api/metrics/docker', - '/api/metrics/latency', - ]; - - const results = await Promise.all( - endpoints.map(endpoint => - fetch(`${serverUrl}${endpoint}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }) - .then(res => { - if (!res.ok) throw new Error(`Failed to fetch ${endpoint}`); - return res.json(); - }) - .catch(err => { - console.error(`Error fetching ${endpoint}:`, err); - return null; - }) - ) - ); - - // 合并所有结果 - const [cpuRes, memRes, storageRes, gpuRes, networkRes, systemRes, dockerRes, latencyRes] = results; - - const system = systemRes?.data || {}; - const docker = dockerRes?.data || {}; - const latency = latencyRes?.data || {}; - - // 将 docker 数据合并到 system.dockerStats - return { - hostname: system.hostname || 'Unknown', - timestamp: new Date().toISOString(), - cpu: cpuRes?.data || {}, - memory: memRes?.data || {}, - storage: storageRes?.data || [], - gpu: gpuRes?.data || [], - network: networkRes?.data || [], - system: { - ...system, - dockerStats: docker - }, - os: system.os || { kernel: '', distro: '', architecture: '' }, - uptimeSeconds: system.uptimeSeconds || 0, - latency: { - clientToServer: clientToServerLatency, - external: latency.external || {}, - }, - }; -}; - -// 测量延迟 -async function measureLatency(url: string): Promise { - try { - const startTime = performance.now(); - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - const endTime = performance.now(); - - if (response.ok) { - return Math.round(endTime - startTime); - } - return -1; // 表示失败 - } catch { - return -1; // 表示超时或失败 - } -} - -export const checkServerHealth = async (serverUrl: string): Promise => { - try { - const url = `${serverUrl}/api/health`; - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - return response.ok; - } catch { - return false; - } -}; +import type { ServerMetrics } from '../types'; + +export const fetchServerMetrics = async (serverUrl: string): Promise => { + // 测量客户端到服务器的延迟(使用健康检查端点) + const clientToServerLatency = await measureLatency(`${serverUrl}/api/health`); + + // 并行请求各个端点 + const endpoints = [ + '/api/metrics/cpu', + '/api/metrics/memory', + '/api/metrics/storage', + '/api/metrics/gpu', + '/api/metrics/network', + '/api/metrics/system', + '/api/metrics/docker', + '/api/metrics/latency', + ]; + + const results = await Promise.all( + endpoints.map(endpoint => + fetch(`${serverUrl}${endpoint}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(res => { + if (!res.ok) throw new Error(`Failed to fetch ${endpoint}`); + return res.json(); + }) + .catch(err => { + console.error(`Error fetching ${endpoint}:`, err); + return null; + }) + ) + ); + + // 合并所有结果 + const [cpuRes, memRes, storageRes, gpuRes, networkRes, systemRes, dockerRes, latencyRes] = results; + + const system = systemRes?.data || {}; + const docker = dockerRes?.data || {}; + const latency = latencyRes?.data || {}; + + // 将 docker 数据合并到 system.dockerStats + return { + hostname: system.hostname || 'Unknown', + timestamp: new Date().toISOString(), + cpu: cpuRes?.data || {}, + memory: memRes?.data || {}, + storage: storageRes?.data || [], + gpu: gpuRes?.data || [], + network: networkRes?.data || [], + system: { + ...system, + dockerStats: docker + }, + os: system.os || { kernel: '', distro: '', architecture: '' }, + uptimeSeconds: system.uptimeSeconds || 0, + latency: { + clientToServer: clientToServerLatency, + external: latency.external || {}, + }, + }; +}; + +// 测量延迟 +async function measureLatency(url: string): Promise { + try { + const startTime = performance.now(); + const response = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + const endTime = performance.now(); + + if (response.ok) { + return Math.round(endTime - startTime); + } + return -1; // 表示失败 + } catch { + return -1; // 表示超时或失败 + } +} + +export const checkServerHealth = async (serverUrl: string): Promise => { + try { + const url = `${serverUrl}/api/health`; + const response = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + return response.ok; + } catch { + return false; + } +}; diff --git a/mengyamonitor-frontend/src/assets/react.svg b/mengyamonitor-frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/mengyamonitor-frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/mengyamonitor-frontend/src/components/Common/CircularProgress.tsx b/mengyamonitor-frontend/src/components/Common/CircularProgress.tsx index 186cf71..d20a3ec 100644 --- a/mengyamonitor-frontend/src/components/Common/CircularProgress.tsx +++ b/mengyamonitor-frontend/src/components/Common/CircularProgress.tsx @@ -52,7 +52,7 @@ export const CircularProgress = ({ className="progress-value" textAnchor="middle" dy="0.3em" - fontSize={valueFontSize} + fontSize={subLabelFontSize} > {subLabel} diff --git a/mengyamonitor-frontend/src/components/ServerCard/ServerCard.css b/mengyamonitor-frontend/src/components/ServerCard/ServerCard.css index a6c690a..e69f0b2 100644 --- a/mengyamonitor-frontend/src/components/ServerCard/ServerCard.css +++ b/mengyamonitor-frontend/src/components/ServerCard/ServerCard.css @@ -1,381 +1,387 @@ -.server-card { - background: var(--surface-color); - border-radius: var(--radius-lg); - padding: 1.5rem; - box-shadow: var(--shadow-sm); - transition: all 0.3s ease; - border: 1px solid var(--border-color); - display: flex; - flex-direction: column; - height: 100%; -} - -.server-card:hover { - box-shadow: var(--shadow-lg); - transform: translateY(-4px); - border-color: var(--primary-color); -} - -.server-card.online { - border-top: 4px solid var(--success-color); -} - -.server-card.offline { - border-top: 4px solid var(--text-light); - opacity: 0.9; -} - -.card-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 0.5rem; -} - -.server-info { - display: flex; - align-items: center; - gap: 0.75rem; -} - -.status-indicator { - width: 10px; - height: 10px; - border-radius: 50%; - position: relative; -} - -.status-online { - background: var(--success-color); - box-shadow: 0 0 0 4px rgba(134, 239, 172, 0.2); -} - -.status-online::after { - content: ''; - position: absolute; - top: -4px; - left: -4px; - right: -4px; - bottom: -4px; - border-radius: 50%; - animation: pulse 2s infinite; - border: 1px solid var(--success-color); -} - -@keyframes pulse { - 0% { transform: scale(1); opacity: 0.5; } - 70% { transform: scale(1.5); opacity: 0; } - 100% { transform: scale(1); opacity: 0; } -} - -.status-offline { - background: var(--text-light); -} - -.server-name-group { - display: flex; - flex-direction: column; - gap: 0.25rem; -} - -.server-name { - margin: 0; - font-size: 1.125rem; - font-weight: 600; - color: var(--text-main); -} - -.server-hostname { - font-size: 0.75rem; - color: var(--text-light); - font-weight: 400; -} - -.btn-remove { - background: transparent; - border: none; - font-size: 1.5rem; - color: var(--text-light); - cursor: pointer; - padding: 0; - width: 24px; - height: 24px; - line-height: 24px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s; -} - -.btn-remove:hover { - background: var(--danger-color); - color: white; -} - -/* Main Metrics */ -.card-main-metrics { - flex: 1; - display: flex; - flex-direction: column; - gap: 1rem; -} - -.metrics-grid-main { - display: flex; - justify-content: space-around; - align-items: center; - padding: 0.5rem 0; -} - -.circular-progress-small { - display: flex; - align-items: center; - justify-content: center; -} - -.circular-progress-small .progress-ring-bg { - fill: none; - stroke: #e2e8f0; - stroke-width: 6; -} - -.circular-progress-small .progress-ring-circle { - fill: none; - stroke-width: 6; - stroke-linecap: round; - transition: stroke-dashoffset 0.5s ease; - transform: rotate(-90deg); - transform-origin: center; -} - -.circular-progress-small .progress-value-small { - font-size: 14px; - font-weight: 600; - fill: var(--text-main); -} - -.circular-progress-small .progress-label-small { - font-size: 10px; - fill: var(--text-secondary); -} - -/* Info Grid */ -.card-info-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 0.75rem; - padding: 0.75rem; - background: var(--bg-color); - border-radius: var(--radius-md); -} - -.info-section { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.info-row { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 0.75rem; - writing-mode: horizontal-tb; - text-orientation: mixed; -} - -.info-label { - color: var(--text-light); - font-weight: 500; - writing-mode: horizontal-tb; - text-orientation: mixed; - white-space: nowrap; -} - -.info-value { - color: var(--text-main); - font-weight: 600; - font-family: monospace; - font-size: 0.7rem; - max-width: 120px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - text-align: right; -} - -/* Performance Grid */ -.card-performance-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 0.75rem; - padding: 0.75rem; - background: var(--bg-color); - border-radius: var(--radius-md); -} - -.performance-section { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.performance-header { - margin-bottom: 0.25rem; -} - -.performance-label { - font-size: 0.75rem; - font-weight: 600; - color: var(--text-secondary); -} - -.performance-row { - display: flex; - flex-direction: column; - gap: 0.4rem; -} - -.performance-item { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 0.7rem; -} - -.performance-item-label { - color: var(--text-light); - font-weight: 500; -} - -.performance-item-value { - color: var(--primary-color); - font-weight: 600; - font-family: monospace; - text-align: right; -} - -/* Footer Info */ -.card-footer-info { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.75rem; - background: var(--bg-color); - border-radius: var(--radius-md); - margin-top: 0.5rem; -} - -.footer-row { - display: flex; - align-items: center; - gap: 0.5rem; - font-size: 0.75rem; -} - -.footer-label { - color: var(--text-light); - font-weight: 500; -} - -.footer-value { - color: var(--text-main); - font-weight: 600; - font-family: monospace; -} - - -/* Metrics List */ -.metrics-list { - display: flex; - flex-direction: column; - gap: 1rem; - margin-bottom: 1.5rem; -} - -.metric-item { - width: 100%; -} - -.metric-header { - display: flex; - justify-content: space-between; - margin-bottom: 0.25rem; - font-size: 0.875rem; -} - -.metric-label { - color: var(--text-secondary); - font-weight: 500; -} - -.metric-value { - font-weight: 600; - color: var(--text-main); -} - -.progress-bar-bg { - width: 100%; - height: 8px; - background: var(--bg-color); - border-radius: 4px; - overflow: hidden; -} - -.progress-bar-fill { - height: 100%; - border-radius: 4px; - transition: width 0.5s ease; -} - -.uptime-info { - display: flex; - justify-content: space-between; - font-size: 0.875rem; - padding-top: 0.5rem; - border-top: 1px solid var(--border-color); -} - -.uptime-label { - color: var(--text-light); -} - -.uptime-value { - color: var(--text-secondary); - font-family: monospace; -} - -.offline-state { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - background: var(--bg-color); - border-radius: var(--radius-md); - margin-bottom: 1.5rem; - color: var(--text-light); -} - -.card-footer { - margin-top: auto; -} - -.btn-detail { - width: 100%; - background: var(--surface-hover); - border: 1px solid var(--border-color); - padding: 0.75rem; - border-radius: var(--radius-md); - font-size: 0.875rem; - font-weight: 600; - color: var(--text-secondary); - cursor: pointer; - transition: all 0.2s; -} - -.btn-detail:hover { - background: var(--primary-color); - color: white; - border-color: var(--primary-color); -} +.server-card { + background: var(--surface-color); + border-radius: var(--radius-lg); + padding: 1.5rem; + box-shadow: var(--shadow-sm); + transition: all 0.3s ease; + border: 1px solid var(--border-color); + display: flex; + flex-direction: column; + height: 100%; +} + +.server-card:hover { + box-shadow: var(--shadow-lg); + transform: translateY(-4px); + border-color: var(--primary-color); +} + +.server-card.online { + border-top: 4px solid var(--success-color); +} + +.server-card.offline { + border-top: 4px solid var(--text-light); + opacity: 0.9; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 0.5rem; + cursor: grab; + user-select: none; +} + +.card-header:active { + cursor: grabbing; +} + +.server-info { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.status-indicator { + width: 10px; + height: 10px; + border-radius: 50%; + position: relative; +} + +.status-online { + background: var(--success-color); + box-shadow: 0 0 0 4px rgba(134, 239, 172, 0.2); +} + +.status-online::after { + content: ''; + position: absolute; + top: -4px; + left: -4px; + right: -4px; + bottom: -4px; + border-radius: 50%; + animation: pulse 2s infinite; + border: 1px solid var(--success-color); +} + +@keyframes pulse { + 0% { transform: scale(1); opacity: 0.5; } + 70% { transform: scale(1.5); opacity: 0; } + 100% { transform: scale(1); opacity: 0; } +} + +.status-offline { + background: var(--text-light); +} + +.server-name-group { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.server-name { + margin: 0; + font-size: 1.125rem; + font-weight: 600; + color: var(--text-main); +} + +.server-hostname { + font-size: 0.75rem; + color: var(--text-light); + font-weight: 400; +} + +.btn-remove { + background: transparent; + border: none; + font-size: 1.5rem; + color: var(--text-light); + cursor: pointer; + padding: 0; + width: 24px; + height: 24px; + line-height: 24px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; +} + +.btn-remove:hover { + background: var(--danger-color); + color: white; +} + +/* Main Metrics */ +.card-main-metrics { + flex: 1; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.metrics-grid-main { + display: flex; + justify-content: space-around; + align-items: center; + padding: 0.5rem 0; +} + +.circular-progress-small { + display: flex; + align-items: center; + justify-content: center; +} + +.circular-progress-small .progress-ring-bg { + fill: none; + stroke: #e2e8f0; + stroke-width: 6; +} + +.circular-progress-small .progress-ring-circle { + fill: none; + stroke-width: 6; + stroke-linecap: round; + transition: stroke-dashoffset 0.5s ease; + transform: rotate(-90deg); + transform-origin: center; +} + +.circular-progress-small .progress-value-small { + font-size: 14px; + font-weight: 600; + fill: var(--text-main); +} + +.circular-progress-small .progress-label-small { + font-size: 10px; + fill: var(--text-secondary); +} + +/* Info Grid */ +.card-info-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.75rem; + padding: 0.75rem; + background: var(--bg-color); + border-radius: var(--radius-md); +} + +.info-section { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.info-row { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.75rem; + writing-mode: horizontal-tb; + text-orientation: mixed; +} + +.info-label { + color: var(--text-light); + font-weight: 500; + writing-mode: horizontal-tb; + text-orientation: mixed; + white-space: nowrap; +} + +.info-value { + color: var(--text-main); + font-weight: 600; + font-family: monospace; + font-size: 0.7rem; + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: right; +} + +/* Performance Grid */ +.card-performance-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.75rem; + padding: 0.75rem; + background: var(--bg-color); + border-radius: var(--radius-md); +} + +.performance-section { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.performance-header { + margin-bottom: 0.25rem; +} + +.performance-label { + font-size: 0.75rem; + font-weight: 600; + color: var(--text-secondary); +} + +.performance-row { + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.performance-item { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.7rem; +} + +.performance-item-label { + color: var(--text-light); + font-weight: 500; +} + +.performance-item-value { + color: var(--primary-color); + font-weight: 600; + font-family: monospace; + text-align: right; +} + +/* Footer Info */ +.card-footer-info { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem; + background: var(--bg-color); + border-radius: var(--radius-md); + margin-top: 0.5rem; +} + +.footer-row { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.75rem; +} + +.footer-label { + color: var(--text-light); + font-weight: 500; +} + +.footer-value { + color: var(--text-main); + font-weight: 600; + font-family: monospace; +} + + +/* Metrics List */ +.metrics-list { + display: flex; + flex-direction: column; + gap: 1rem; + margin-bottom: 1.5rem; +} + +.metric-item { + width: 100%; +} + +.metric-header { + display: flex; + justify-content: space-between; + margin-bottom: 0.25rem; + font-size: 0.875rem; +} + +.metric-label { + color: var(--text-secondary); + font-weight: 500; +} + +.metric-value { + font-weight: 600; + color: var(--text-main); +} + +.progress-bar-bg { + width: 100%; + height: 8px; + background: var(--bg-color); + border-radius: 4px; + overflow: hidden; +} + +.progress-bar-fill { + height: 100%; + border-radius: 4px; + transition: width 0.5s ease; +} + +.uptime-info { + display: flex; + justify-content: space-between; + font-size: 0.875rem; + padding-top: 0.5rem; + border-top: 1px solid var(--border-color); +} + +.uptime-label { + color: var(--text-light); +} + +.uptime-value { + color: var(--text-secondary); + font-family: monospace; +} + +.offline-state { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-color); + border-radius: var(--radius-md); + margin-bottom: 1.5rem; + color: var(--text-light); +} + +.card-footer { + margin-top: auto; +} + +.btn-detail { + width: 100%; + background: var(--surface-hover); + border: 1px solid var(--border-color); + padding: 0.75rem; + border-radius: var(--radius-md); + font-size: 0.875rem; + font-weight: 600; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s; +} + +.btn-detail:hover { + background: var(--primary-color); + color: white; + border-color: var(--primary-color); +} diff --git a/mengyamonitor-frontend/src/components/ServerCard/ServerCard.tsx b/mengyamonitor-frontend/src/components/ServerCard/ServerCard.tsx index e574fb3..9d3c27b 100644 --- a/mengyamonitor-frontend/src/components/ServerCard/ServerCard.tsx +++ b/mengyamonitor-frontend/src/components/ServerCard/ServerCard.tsx @@ -1,5 +1,7 @@ import type { ServerConfig, ServerMetrics } from '../../types'; import { formatUptime, formatBytes } from '../../utils/format'; +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; import './ServerCard.css'; const CircularProgress = ({ value, label, color, size = 60 }: { value: number; label: string; color: string; size?: number }) => { @@ -95,6 +97,21 @@ export const ServerCard = ({ onDetail, onRemove, }: ServerCardProps) => { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: server.id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.5 : 1, + }; + // 从 URL 提取 IP 地址 const extractIP = (url: string): string => { try { @@ -142,8 +159,12 @@ export const ServerCard = ({ const latencyColor = getLatencyColor(latency); return ( -
-
+
+
@@ -153,7 +174,15 @@ export const ServerCard = ({ )}
-
diff --git a/mengyamonitor-frontend/src/components/ServerDetail/ServerDetail.tsx b/mengyamonitor-frontend/src/components/ServerDetail/ServerDetail.tsx index c9f43c7..e64b971 100644 --- a/mengyamonitor-frontend/src/components/ServerDetail/ServerDetail.tsx +++ b/mengyamonitor-frontend/src/components/ServerDetail/ServerDetail.tsx @@ -1,521 +1,521 @@ -import type { ServerMetrics } from '../../types'; -import { formatBytes, formatUptime, formatPercent } from '../../utils/format'; -import { CircularProgress } from '../Common/CircularProgress'; -import './ServerDetail.css'; - -interface ServerDetailProps { - metrics: ServerMetrics; - serverName: string; - onClose: () => void; -} - -export const ServerDetail = ({ metrics, serverName, onClose }: ServerDetailProps) => { - return ( -
-
e.stopPropagation()}> -
-

{serverName} - 详细信息

- -
- -
-
-

系统信息

-
- {/* 系统基本信息 */} -
-

基本信息

-
-
-
- 主机名 - {metrics.hostname} -
-
-
-
- 操作系统 - {metrics.os.distro} -
-
- -
-
- 架构 - {metrics.os.architecture} -
-
- -
-
- 内核版本 - {metrics.os.kernel} -
-
- -
-
- - {/* 运行时信息 */} -
-

运行时信息

-
-
-
- 运行时间 - {formatUptime(metrics.uptimeSeconds)} -
-
-
-
- 进程数量 - {metrics.system.processCount} -
-
- {metrics.system.packageCount > 0 && ( -
-
- 已安装软件包 - {metrics.system.packageCount} ({metrics.system.packageManager}) -
-
- )} -
-
- - {/* 性能指标 */} -
-

性能指标

-
-
-
- 磁盘读取 - {metrics.system.diskReadSpeed.toFixed(2)} MB/s -
-
-
-
- 磁盘写入 - {metrics.system.diskWriteSpeed.toFixed(2)} MB/s -
-
- {metrics.system.temperature && metrics.system.temperature > 0 && ( -
-
- 系统温度 - {metrics.system.temperature.toFixed(1)}°C -
-
- )} - {(metrics.system.networkRxSpeed !== undefined || metrics.system.networkTxSpeed !== undefined) && ( - <> -
-
- 网络下载 - {(metrics.system.networkRxSpeed || 0).toFixed(2)} MB/s -
-
-
-
- 网络上传 - {(metrics.system.networkTxSpeed || 0).toFixed(2)} MB/s -
-
- - )} -
-
- - {/* 延迟信息 */} - {metrics.latency && ( -
-

延迟检测

-
-
-
- 客户端到服务器 - - {metrics.latency.clientToServer >= 0 ? `${metrics.latency.clientToServer} ms` : '超时'} - -
-
- {metrics.latency.external && ( - <> -
-
- 百度 (baidu.com) - {metrics.latency.external['baidu.com'] || '超时'} -
-
-
-
- 谷歌 (google.com) - {metrics.latency.external['google.com'] || '超时'} -
-
-
-
- GitHub (github.com) - {metrics.latency.external['github.com'] || '超时'} -
-
- - )} -
-
- )} -
-
- -
-

CPU

-
-
-
- 80 ? '#f87171' : '#86efac'} - subLabel={formatPercent(metrics.cpu.usagePercent)} - /> -
- {metrics.cpu.loadAverages && metrics.cpu.loadAverages.length > 0 && metrics.cpu.cores > 0 && ( -
- 0.8 ? '#fde047' : '#a7f3d0'} - subLabel={metrics.cpu.loadAverages[0].toFixed(2)} - /> -
- )} -
-
-
- 型号 - {metrics.cpu.model} -
-
- 核心数 - {metrics.cpu.cores} -
-
- 使用率 - {formatPercent(metrics.cpu.usagePercent)} -
- {metrics.cpu.temperature && metrics.cpu.temperature > 0 && ( -
- 温度 - {metrics.cpu.temperature.toFixed(1)}°C -
- )} - {metrics.cpu.loadAverages && metrics.cpu.loadAverages.length > 0 && ( -
- 负载平均值 - {metrics.cpu.loadAverages.join(' / ')} -
- )} -
-
- - {metrics.cpu.perCoreUsage && metrics.cpu.perCoreUsage.length > 0 && ( -
-

各核心使用率

-
- {metrics.cpu.perCoreUsage.map((core) => ( -
- 80 ? '#f87171' : '#86efac'} - subLabel={formatPercent(core.percent)} - /> -
- ))} -
-
- )} -
- -
-

内存

-
-
- 80 ? '#fde047' : '#6ee7b7'} - subLabel={formatPercent(metrics.memory.usedPercent)} - /> -
-
-
- 总容量 - {formatBytes(metrics.memory.totalBytes)} -
-
- 已使用 - {formatBytes(metrics.memory.usedBytes)} -
-
- 可用 - {formatBytes(metrics.memory.freeBytes)} -
-
- 使用率 - {formatPercent(metrics.memory.usedPercent)} -
-
-
-
- -
-

存储

-
- {metrics.storage.map((disk, index) => ( -
- 90 ? '#f87171' : '#a7f3d0'} - subLabel={formatPercent(disk.usedPercent)} - /> -
-
总: {formatBytes(disk.totalBytes)}
-
用: {formatBytes(disk.usedBytes)}
-
-
- ))} -
-
- - {metrics.gpu && metrics.gpu.length > 0 && metrics.gpu[0].status !== 'not_available' && ( -
-

GPU

- {metrics.gpu.map((gpu, index) => ( -
-
- {gpu.name} - {gpu.status} -
-
-
利用率
-
-
-
-
{formatPercent(gpu.utilizationPercent)}
-
-
-
- 显存总量 - {gpu.memoryTotalMB} MB -
-
- 显存使用 - {gpu.memoryUsedMB} MB -
- {gpu.temperature && gpu.temperature > 0 && ( -
- 温度 - {gpu.temperature.toFixed(1)}°C -
- )} -
-
- ))} -
- )} - - {metrics.network && metrics.network.length > 0 && ( -
-

网络接口

-
- {metrics.network.map((iface, index) => ( -
-
-
-
- {iface.name} - {iface.ipAddress && ( - - {iface.ipAddress} - - )} -
-
-
-
- {(iface.macAddress || iface.ipAddress) && ( -
- {iface.macAddress && ( -
- MAC 地址 - {iface.macAddress} -
- )} - {iface.ipAddress && ( -
- IP 地址 - {iface.ipAddress} -
- )} -
- )} -
-
-
- 接收流量 - {formatBytes(iface.rxBytes)} -
-
-
-
- 发送流量 - {formatBytes(iface.txBytes)} -
-
-
-
-
- ))} -
-
- )} - - {metrics.system.topProcesses && metrics.system.topProcesses.length > 0 && ( -
-

Top 5 进程

-
- {metrics.system.topProcesses.map((proc, index) => ( -
-
{index + 1}
-
-
-
- {proc.name} - PID: {proc.pid} -
-
-
-
-
- CPU - {proc.cpu}% -
-
-
-
-
-
-
- 内存 - {proc.memory}% ({proc.memoryMB.toFixed(1)} MB) -
-
-
-
-
-
-
-
- ))} -
-
- )} - - {metrics.system.dockerStats && metrics.system.dockerStats.available && ( -
-

Docker 信息

- {metrics.system.dockerStats.version && ( -
-
- Docker 版本 - {metrics.system.dockerStats.version} -
-
- 运行中的容器 - {metrics.system.dockerStats.running} -
-
- 停止的容器 - {metrics.system.dockerStats.stopped} -
-
- 镜像数量 - {metrics.system.dockerStats.imageCount} -
-
- )} - - {metrics.system.dockerStats.runningNames && metrics.system.dockerStats.runningNames.length > 0 && ( -
-

运行中的容器

-
- {metrics.system.dockerStats.runningNames.map((name, index) => ( - - {name} - - ))} -
-
- )} - - {metrics.system.dockerStats.stoppedNames && metrics.system.dockerStats.stoppedNames.length > 0 && ( -
-

停止的容器

-
- {metrics.system.dockerStats.stoppedNames.map((name, index) => ( - - {name} - - ))} -
-
- )} - - {metrics.system.dockerStats.imageNames && metrics.system.dockerStats.imageNames.length > 0 && ( -
-

镜像列表

-
- {metrics.system.dockerStats.imageNames.map((name, index) => ( - - {name} - - ))} -
-
- )} -
- )} - - {metrics.system.systemLogs && metrics.system.systemLogs.length > 0 && ( -
-

系统日志

-
- {metrics.system.systemLogs.map((log, index) => ( -
- {log} -
- ))} -
-
- )} -
-
-
- ); -}; +import type { ServerMetrics } from '../../types'; +import { formatBytes, formatUptime, formatPercent } from '../../utils/format'; +import { CircularProgress } from '../Common/CircularProgress'; +import './ServerDetail.css'; + +interface ServerDetailProps { + metrics: ServerMetrics; + serverName: string; + onClose: () => void; +} + +export const ServerDetail = ({ metrics, serverName, onClose }: ServerDetailProps) => { + return ( +
+
e.stopPropagation()}> +
+

{serverName} - 详细信息

+ +
+ +
+
+

系统信息

+
+ {/* 系统基本信息 */} +
+

基本信息

+
+
+
+ 主机名 + {metrics.hostname} +
+
+
+
+ 操作系统 + {metrics.os.distro} +
+
+ +
+
+ 架构 + {metrics.os.architecture} +
+
+ +
+
+ 内核版本 + {metrics.os.kernel} +
+
+ +
+
+ + {/* 运行时信息 */} +
+

运行时信息

+
+
+
+ 运行时间 + {formatUptime(metrics.uptimeSeconds)} +
+
+
+
+ 进程数量 + {metrics.system.processCount} +
+
+ {metrics.system.packageCount > 0 && ( +
+
+ 已安装软件包 + {metrics.system.packageCount} ({metrics.system.packageManager}) +
+
+ )} +
+
+ + {/* 性能指标 */} +
+

性能指标

+
+
+
+ 磁盘读取 + {metrics.system.diskReadSpeed.toFixed(2)} MB/s +
+
+
+
+ 磁盘写入 + {metrics.system.diskWriteSpeed.toFixed(2)} MB/s +
+
+ {metrics.system.temperature && metrics.system.temperature > 0 && ( +
+
+ 系统温度 + {metrics.system.temperature.toFixed(1)}°C +
+
+ )} + {(metrics.system.networkRxSpeed !== undefined || metrics.system.networkTxSpeed !== undefined) && ( + <> +
+
+ 网络下载 + {(metrics.system.networkRxSpeed || 0).toFixed(2)} MB/s +
+
+
+
+ 网络上传 + {(metrics.system.networkTxSpeed || 0).toFixed(2)} MB/s +
+
+ + )} +
+
+ + {/* 延迟信息 */} + {metrics.latency && ( +
+

延迟检测

+
+
+
+ 客户端到服务器 + + {metrics.latency.clientToServer >= 0 ? `${metrics.latency.clientToServer} ms` : '超时'} + +
+
+ {metrics.latency.external && ( + <> +
+
+ 百度 (baidu.com) + {metrics.latency.external['baidu.com'] || '超时'} +
+
+
+
+ 谷歌 (google.com) + {metrics.latency.external['google.com'] || '超时'} +
+
+
+
+ GitHub (github.com) + {metrics.latency.external['github.com'] || '超时'} +
+
+ + )} +
+
+ )} +
+
+ +
+

CPU

+
+
+
+ 80 ? '#f87171' : '#86efac'} + subLabel={formatPercent(metrics.cpu.usagePercent)} + /> +
+ {metrics.cpu.loadAverages && metrics.cpu.loadAverages.length > 0 && metrics.cpu.cores > 0 && ( +
+ 0.8 ? '#fde047' : '#a7f3d0'} + subLabel={metrics.cpu.loadAverages[0].toFixed(2)} + /> +
+ )} +
+
+
+ 型号 + {metrics.cpu.model} +
+
+ 核心数 + {metrics.cpu.cores} +
+
+ 使用率 + {formatPercent(metrics.cpu.usagePercent)} +
+ {metrics.cpu.temperature && metrics.cpu.temperature > 0 && ( +
+ 温度 + {metrics.cpu.temperature.toFixed(1)}°C +
+ )} + {metrics.cpu.loadAverages && metrics.cpu.loadAverages.length > 0 && ( +
+ 负载平均值 + {metrics.cpu.loadAverages.join(' / ')} +
+ )} +
+
+ + {metrics.cpu.perCoreUsage && metrics.cpu.perCoreUsage.length > 0 && ( +
+

各核心使用率

+
+ {metrics.cpu.perCoreUsage.map((core) => ( +
+ 80 ? '#f87171' : '#86efac'} + subLabel={formatPercent(core.percent)} + /> +
+ ))} +
+
+ )} +
+ +
+

内存

+
+
+ 80 ? '#fde047' : '#6ee7b7'} + subLabel={formatPercent(metrics.memory.usedPercent)} + /> +
+
+
+ 总容量 + {formatBytes(metrics.memory.totalBytes)} +
+
+ 已使用 + {formatBytes(metrics.memory.usedBytes)} +
+
+ 可用 + {formatBytes(metrics.memory.freeBytes)} +
+
+ 使用率 + {formatPercent(metrics.memory.usedPercent)} +
+
+
+
+ +
+

存储

+
+ {metrics.storage.map((disk, index) => ( +
+ 90 ? '#f87171' : '#a7f3d0'} + subLabel={formatPercent(disk.usedPercent)} + /> +
+
总: {formatBytes(disk.totalBytes)}
+
用: {formatBytes(disk.usedBytes)}
+
+
+ ))} +
+
+ + {metrics.gpu && metrics.gpu.length > 0 && metrics.gpu[0].status !== 'not_available' && ( +
+

GPU

+ {metrics.gpu.map((gpu, index) => ( +
+
+ {gpu.name} + {gpu.status} +
+
+
利用率
+
+
+
+
{formatPercent(gpu.utilizationPercent)}
+
+
+
+ 显存总量 + {gpu.memoryTotalMB} MB +
+
+ 显存使用 + {gpu.memoryUsedMB} MB +
+ {gpu.temperature && gpu.temperature > 0 && ( +
+ 温度 + {gpu.temperature.toFixed(1)}°C +
+ )} +
+
+ ))} +
+ )} + + {metrics.network && metrics.network.length > 0 && ( +
+

网络接口

+
+ {metrics.network.map((iface, index) => ( +
+
+
+
+ {iface.name} + {iface.ipAddress && ( + + {iface.ipAddress} + + )} +
+
+
+
+ {(iface.macAddress || iface.ipAddress) && ( +
+ {iface.macAddress && ( +
+ MAC 地址 + {iface.macAddress} +
+ )} + {iface.ipAddress && ( +
+ IP 地址 + {iface.ipAddress} +
+ )} +
+ )} +
+
+
+ 接收流量 + {formatBytes(iface.rxBytes)} +
+
+
+
+ 发送流量 + {formatBytes(iface.txBytes)} +
+
+
+
+
+ ))} +
+
+ )} + + {metrics.system.topProcesses && metrics.system.topProcesses.length > 0 && ( +
+

Top 5 进程

+
+ {metrics.system.topProcesses.map((proc, index) => ( +
+
{index + 1}
+
+
+
+ {proc.name} + PID: {proc.pid} +
+
+
+
+
+ CPU + {proc.cpu}% +
+
+
+
+
+
+
+ 内存 + {proc.memory}% ({proc.memoryMB.toFixed(1)} MB) +
+
+
+
+
+
+
+
+ ))} +
+
+ )} + + {metrics.system.dockerStats && metrics.system.dockerStats.available && ( +
+

Docker 信息

+ {metrics.system.dockerStats.version && ( +
+
+ Docker 版本 + {metrics.system.dockerStats.version} +
+
+ 运行中的容器 + {metrics.system.dockerStats.running} +
+
+ 停止的容器 + {metrics.system.dockerStats.stopped} +
+
+ 镜像数量 + {metrics.system.dockerStats.imageCount} +
+
+ )} + + {metrics.system.dockerStats.runningNames && metrics.system.dockerStats.runningNames.length > 0 && ( +
+

运行中的容器

+
+ {metrics.system.dockerStats.runningNames.map((name, index) => ( + + {name} + + ))} +
+
+ )} + + {metrics.system.dockerStats.stoppedNames && metrics.system.dockerStats.stoppedNames.length > 0 && ( +
+

停止的容器

+
+ {metrics.system.dockerStats.stoppedNames.map((name, index) => ( + + {name} + + ))} +
+
+ )} + + {metrics.system.dockerStats.imageNames && metrics.system.dockerStats.imageNames.length > 0 && ( +
+

镜像列表

+
+ {metrics.system.dockerStats.imageNames.map((name, index) => ( + + {name} + + ))} +
+
+ )} +
+ )} + + {metrics.system.systemLogs && metrics.system.systemLogs.length > 0 && ( +
+

系统日志

+
+ {metrics.system.systemLogs.map((log, index) => ( +
+ {log} +
+ ))} +
+
+ )} +
+
+
+ ); +}; diff --git a/mengyamonitor-frontend/src/hooks/useServerMonitor.ts b/mengyamonitor-frontend/src/hooks/useServerMonitor.ts index 36dad51..1105267 100644 --- a/mengyamonitor-frontend/src/hooks/useServerMonitor.ts +++ b/mengyamonitor-frontend/src/hooks/useServerMonitor.ts @@ -1,58 +1,58 @@ -import { useState, useEffect, useCallback } from 'react'; -import type { ServerConfig, ServerStatus } from '../types'; -import { fetchServerMetrics } from '../api/monitor'; - -export const useServerMonitor = (servers: ServerConfig[], interval: number = 2000) => { - const [statuses, setStatuses] = useState>({}); - - const fetchMetrics = useCallback(async (server: ServerConfig) => { - if (!server.enabled) { - return; - } - - try { - const metrics = await fetchServerMetrics(server.url); - setStatuses(prev => ({ - ...prev, - [server.id]: { - serverId: server.id, - online: true, - metrics, - lastUpdate: Date.now(), - }, - })); - } catch (error) { - setStatuses(prev => ({ - ...prev, - [server.id]: { - serverId: server.id, - online: false, - error: error instanceof Error ? error.message : 'Unknown error', - lastUpdate: Date.now(), - }, - })); - } - }, []); - - useEffect(() => { - // Initial fetch - servers.forEach(server => { - if (server.enabled) { - fetchMetrics(server); - } - }); - - // Set up polling - const timer = setInterval(() => { - servers.forEach(server => { - if (server.enabled) { - fetchMetrics(server); - } - }); - }, interval); - - return () => clearInterval(timer); - }, [servers, interval, fetchMetrics]); - - return statuses; -}; +import { useState, useEffect, useCallback } from 'react'; +import type { ServerConfig, ServerStatus } from '../types'; +import { fetchServerMetrics } from '../api/monitor'; + +export const useServerMonitor = (servers: ServerConfig[], interval: number = 2000) => { + const [statuses, setStatuses] = useState>({}); + + const fetchMetrics = useCallback(async (server: ServerConfig) => { + if (!server.enabled) { + return; + } + + try { + const metrics = await fetchServerMetrics(server.url); + setStatuses(prev => ({ + ...prev, + [server.id]: { + serverId: server.id, + online: true, + metrics, + lastUpdate: Date.now(), + }, + })); + } catch (error) { + setStatuses(prev => ({ + ...prev, + [server.id]: { + serverId: server.id, + online: false, + error: error instanceof Error ? error.message : 'Unknown error', + lastUpdate: Date.now(), + }, + })); + } + }, []); + + useEffect(() => { + // Initial fetch + servers.forEach(server => { + if (server.enabled) { + fetchMetrics(server); + } + }); + + // Set up polling + const timer = setInterval(() => { + servers.forEach(server => { + if (server.enabled) { + fetchMetrics(server); + } + }); + }, interval); + + return () => clearInterval(timer); + }, [servers, interval, fetchMetrics]); + + return statuses; +}; diff --git a/mengyamonitor-frontend/src/types/index.ts b/mengyamonitor-frontend/src/types/index.ts index 92b98b7..1bd6af4 100644 --- a/mengyamonitor-frontend/src/types/index.ts +++ b/mengyamonitor-frontend/src/types/index.ts @@ -1,125 +1,125 @@ -export interface ServerConfig { - id: string; - name: string; - url: string; - enabled: boolean; -} - -export interface CPUMetrics { - model: string; - cores: number; - usagePercent: number; - loadAverages?: number[]; - temperature?: number; - perCoreUsage?: CoreUsage[]; -} - -export interface CoreUsage { - core: number; - percent: number; -} - -export interface MemoryMetrics { - totalBytes: number; - usedBytes: number; - freeBytes: number; - usedPercent: number; -} - -export interface StorageMetrics { - mount: string; - totalBytes: number; - usedBytes: number; - freeBytes: number; - usedPercent: number; -} - -export interface GPUMetrics { - name: string; - memoryTotalMB: number; - memoryUsedMB: number; - utilizationPercent: number; - temperature?: number; - status: string; -} - -export interface NetworkInterface { - name: string; - ipAddress: string; - macAddress: string; - rxBytes: number; - txBytes: number; - rxSpeed: number; - txSpeed: number; -} - -export interface SystemStats { - processCount: number; - packageCount: number; - packageManager: string; - temperature?: number; - diskReadSpeed: number; - diskWriteSpeed: number; - networkRxSpeed?: number; - networkTxSpeed?: number; - topProcesses: ProcessInfo[]; - dockerStats: DockerStats; - systemLogs?: string[]; -} - -export interface ProcessInfo { - pid: number; - name: string; - cpu: number; - memory: number; - memoryMB: number; - command: string; -} - -export interface DockerStats { - available: boolean; - version?: string; - running: number; - stopped: number; - imageCount: number; - runningNames?: string[]; - stoppedNames?: string[]; - imageNames?: string[]; -} - -export interface OSInfo { - kernel: string; - distro: string; - architecture: string; -} - -export interface LatencyInfo { - clientToServer: number; - external: { - 'baidu.com'?: string; - 'google.com'?: string; - 'github.com'?: string; - }; -} - -export interface ServerMetrics { - hostname: string; - timestamp: string; - cpu: CPUMetrics; - memory: MemoryMetrics; - storage: StorageMetrics[]; - gpu: GPUMetrics[]; - network: NetworkInterface[]; - system: SystemStats; - os: OSInfo; - uptimeSeconds: number; - latency?: LatencyInfo; -} - -export interface ServerStatus { - serverId: string; - online: boolean; - metrics?: ServerMetrics; - error?: string; - lastUpdate: number; -} +export interface ServerConfig { + id: string; + name: string; + url: string; + enabled: boolean; +} + +export interface CPUMetrics { + model: string; + cores: number; + usagePercent: number; + loadAverages?: number[]; + temperature?: number; + perCoreUsage?: CoreUsage[]; +} + +export interface CoreUsage { + core: number; + percent: number; +} + +export interface MemoryMetrics { + totalBytes: number; + usedBytes: number; + freeBytes: number; + usedPercent: number; +} + +export interface StorageMetrics { + mount: string; + totalBytes: number; + usedBytes: number; + freeBytes: number; + usedPercent: number; +} + +export interface GPUMetrics { + name: string; + memoryTotalMB: number; + memoryUsedMB: number; + utilizationPercent: number; + temperature?: number; + status: string; +} + +export interface NetworkInterface { + name: string; + ipAddress: string; + macAddress: string; + rxBytes: number; + txBytes: number; + rxSpeed: number; + txSpeed: number; +} + +export interface SystemStats { + processCount: number; + packageCount: number; + packageManager: string; + temperature?: number; + diskReadSpeed: number; + diskWriteSpeed: number; + networkRxSpeed?: number; + networkTxSpeed?: number; + topProcesses: ProcessInfo[]; + dockerStats: DockerStats; + systemLogs?: string[]; +} + +export interface ProcessInfo { + pid: number; + name: string; + cpu: number; + memory: number; + memoryMB: number; + command: string; +} + +export interface DockerStats { + available: boolean; + version?: string; + running: number; + stopped: number; + imageCount: number; + runningNames?: string[]; + stoppedNames?: string[]; + imageNames?: string[]; +} + +export interface OSInfo { + kernel: string; + distro: string; + architecture: string; +} + +export interface LatencyInfo { + clientToServer: number; + external: { + 'baidu.com'?: string; + 'google.com'?: string; + 'github.com'?: string; + }; +} + +export interface ServerMetrics { + hostname: string; + timestamp: string; + cpu: CPUMetrics; + memory: MemoryMetrics; + storage: StorageMetrics[]; + gpu: GPUMetrics[]; + network: NetworkInterface[]; + system: SystemStats; + os: OSInfo; + uptimeSeconds: number; + latency?: LatencyInfo; +} + +export interface ServerStatus { + serverId: string; + online: boolean; + metrics?: ServerMetrics; + error?: string; + lastUpdate: number; +} diff --git a/mengyamonitor-frontend/src/utils/format.ts b/mengyamonitor-frontend/src/utils/format.ts index bc3dfa4..1817dca 100644 --- a/mengyamonitor-frontend/src/utils/format.ts +++ b/mengyamonitor-frontend/src/utils/format.ts @@ -1,45 +1,45 @@ -export const formatBytes = (bytes: number): string => { - if (bytes === 0 || bytes === null || bytes === undefined || isNaN(bytes) || !isFinite(bytes)) { - return '0 B'; - } - - // 处理负数 - if (bytes < 0) { - bytes = 0; - } - - const k = 1024; - const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; - - // 处理小于 1 的情况 - if (bytes < 1) { - return bytes.toFixed(2) + ' ' + sizes[0]; - } - - const i = Math.floor(Math.log(bytes) / Math.log(k)); - // 确保索引在有效范围内 - const sizeIndex = Math.max(0, Math.min(i, sizes.length - 1)); - const value = Math.round((bytes / Math.pow(k, sizeIndex)) * 100) / 100; - - // 确保 sizes[sizeIndex] 存在 - const unit = sizes[sizeIndex] || sizes[0]; - - return value + ' ' + unit; -}; - -export const formatUptime = (seconds: number): string => { - const days = Math.floor(seconds / 86400); - const hours = Math.floor((seconds % 86400) / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - - const parts: string[] = []; - if (days > 0) parts.push(`${days}天`); - if (hours > 0) parts.push(`${hours}小时`); - if (minutes > 0) parts.push(`${minutes}分钟`); - - return parts.length > 0 ? parts.join(' ') : '刚刚启动'; -}; - -export const formatPercent = (value: number): string => { - return `${Math.round(value * 10) / 10}%`; -}; +export const formatBytes = (bytes: number): string => { + if (bytes === 0 || bytes === null || bytes === undefined || isNaN(bytes) || !isFinite(bytes)) { + return '0 B'; + } + + // 处理负数 + if (bytes < 0) { + bytes = 0; + } + + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + + // 处理小于 1 的情况 + if (bytes < 1) { + return bytes.toFixed(2) + ' ' + sizes[0]; + } + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + // 确保索引在有效范围内 + const sizeIndex = Math.max(0, Math.min(i, sizes.length - 1)); + const value = Math.round((bytes / Math.pow(k, sizeIndex)) * 100) / 100; + + // 确保 sizes[sizeIndex] 存在 + const unit = sizes[sizeIndex] || sizes[0]; + + return value + ' ' + unit; +}; + +export const formatUptime = (seconds: number): string => { + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + const parts: string[] = []; + if (days > 0) parts.push(`${days}天`); + if (hours > 0) parts.push(`${hours}小时`); + if (minutes > 0) parts.push(`${minutes}分钟`); + + return parts.length > 0 ? parts.join(' ') : '刚刚启动'; +}; + +export const formatPercent = (value: number): string => { + return `${Math.round(value * 10) / 10}%`; +}; diff --git a/mengyamonitor-frontend/src/utils/storage.ts b/mengyamonitor-frontend/src/utils/storage.ts index 01014eb..7a4bdc0 100644 --- a/mengyamonitor-frontend/src/utils/storage.ts +++ b/mengyamonitor-frontend/src/utils/storage.ts @@ -1,53 +1,115 @@ -import type { ServerConfig } from '../types'; - -const STORAGE_KEY = 'mengya_monitor_servers'; - -export const loadServers = (): ServerConfig[] => { - try { - const stored = localStorage.getItem(STORAGE_KEY); - if (stored) { - const parsed = JSON.parse(stored); - // Ensure it's an array - if (Array.isArray(parsed)) { - return parsed; - } - } - } catch (error) { - console.error('Failed to load servers:', error); - } - return []; -}; - -export const saveServers = (servers: ServerConfig[]): void => { - try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(servers)); - } catch (error) { - console.error('Failed to save servers:', error); - } -}; - -export const addServer = (server: Omit): ServerConfig => { - const servers = loadServers(); - const newServer: ServerConfig = { - ...server, - id: Date.now().toString(), - }; - servers.push(newServer); - saveServers(servers); - return newServer; -}; - -export const updateServer = (id: string, updates: Partial): void => { - const servers = loadServers(); - const index = servers.findIndex(s => s.id === id); - if (index !== -1) { - servers[index] = { ...servers[index], ...updates }; - saveServers(servers); - } -}; - -export const removeServer = (id: string): void => { - const servers = loadServers(); - const filtered = servers.filter(s => s.id !== id); - saveServers(filtered); -}; +import type { ServerConfig } from '../types'; + +const STORAGE_KEY = 'mengya_monitor_servers'; + +export const loadServers = (): ServerConfig[] => { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) { + const parsed = JSON.parse(stored); + // Ensure it's an array + if (Array.isArray(parsed)) { + return parsed; + } + } + } catch (error) { + console.error('Failed to load servers:', error); + } + return []; +}; + +export const saveServers = (servers: ServerConfig[]): void => { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(servers)); + } catch (error) { + console.error('Failed to save servers:', error); + } +}; + +export const addServer = (server: Omit): ServerConfig => { + const servers = loadServers(); + const newServer: ServerConfig = { + ...server, + id: Date.now().toString(), + }; + servers.push(newServer); + saveServers(servers); + return newServer; +}; + +export const updateServer = (id: string, updates: Partial): void => { + const servers = loadServers(); + const index = servers.findIndex(s => s.id === id); + if (index !== -1) { + servers[index] = { ...servers[index], ...updates }; + saveServers(servers); + } +}; + +export const removeServer = (id: string): void => { + const servers = loadServers(); + const filtered = servers.filter(s => s.id !== id); + saveServers(filtered); +}; + +/** + * 导出服务器配置到剪贴板 + * @returns Promise 返回导出的 JSON 字符串 + */ +export const exportServersToClipboard = async (): Promise => { + const servers = loadServers(); + const json = JSON.stringify(servers, null, 2); + + try { + await navigator.clipboard.writeText(json); + return json; + } catch (error) { + console.error('Failed to copy to clipboard:', error); + throw new Error('无法复制到剪贴板,请检查浏览器权限'); + } +}; + +/** + * 从剪贴板导入服务器配置 + * @returns Promise 返回导入的服务器配置数组 + */ +export const importServersFromClipboard = async (): Promise => { + try { + const text = await navigator.clipboard.readText(); + const parsed = JSON.parse(text); + + if (!Array.isArray(parsed)) { + throw new Error('无效的配置格式:必须是服务器配置数组'); + } + + // 验证每个配置项的基本结构 + const validServers: ServerConfig[] = []; + for (const item of parsed) { + if (item && typeof item === 'object' && + typeof item.name === 'string' && + typeof item.url === 'string') { + // 为新导入的服务器生成新的 ID,避免 ID 冲突 + validServers.push({ + id: Date.now().toString() + Math.random().toString(36).substr(2, 9), + name: item.name, + url: item.url, + enabled: item.enabled !== undefined ? item.enabled : true, + }); + } + } + + if (validServers.length === 0) { + throw new Error('未找到有效的服务器配置'); + } + + return validServers; + } catch (error) { + if (error instanceof SyntaxError) { + throw new Error('无效的 JSON 格式'); + } + if (error instanceof Error) { + throw error; + } + throw new Error('读取剪贴板失败,请检查浏览器权限'); + } +}; \ No newline at end of file diff --git a/mengyamonitor-frontend/vite.config.ts b/mengyamonitor-frontend/vite.config.ts index a4807b9..b59b8f6 100644 --- a/mengyamonitor-frontend/vite.config.ts +++ b/mengyamonitor-frontend/vite.config.ts @@ -1,9 +1,49 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +import { VitePWA } from 'vite-plugin-pwa' // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + react(), + VitePWA({ + registerType: 'autoUpdate', + includeAssets: ['logo.svg'], + manifest: { + name: '萌芽监控面板', + short_name: '萌芽监控', + description: '服务器监控面板 - 萌芽监控', + theme_color: '#1a1a2e', + background_color: '#16213e', + display: 'standalone', + orientation: 'portrait-primary', + scope: '/', + start_url: '/', + icons: [ + { src: '/logo.svg', sizes: 'any', type: 'image/svg+xml', purpose: 'any maskable' } + ], + categories: ['utilities', 'productivity'] + }, + workbox: { + globPatterns: ['**/*.{js,css,html,ico,svg,woff2}'], + runtimeCaching: [ + { + urlPattern: /^https:\/\/.*\/api\//i, + handler: 'NetworkFirst', + options: { + cacheName: 'api-cache', + expiration: { maxEntries: 32, maxAgeSeconds: 60 * 5 }, + networkTimeoutSeconds: 10, + cacheableResponse: { statuses: [0, 200] } + } + } + ] + }, + devOptions: { + enabled: true + } + }) + ], server: { port: 2929, host: true, diff --git a/start-backend.bat b/start-backend.bat new file mode 100644 index 0000000..c7d4de8 --- /dev/null +++ b/start-backend.bat @@ -0,0 +1,6 @@ +@echo off +chcp 65001 >nul +echo 启动后端服务器... +cd mengyadriftbottle-backend +go mod tidy +go run main.go diff --git a/start-frontend.bat b/start-frontend.bat new file mode 100644 index 0000000..958c19b --- /dev/null +++ b/start-frontend.bat @@ -0,0 +1,7 @@ +@echo off +chcp 65001 >nul +echo 启动前端开发服务器... +cd mengyamonitor-frontend +echo 正在安装依赖... +call npm install +npm run dev \ No newline at end of file diff --git a/萌芽监控面板 b/萌芽监控面板 new file mode 100644 index 0000000..e69de29 diff --git a/需求.txt b/需求.txt index 18f5df8..4067185 100644 --- a/需求.txt +++ b/需求.txt @@ -1,8 +1,8 @@ -1.实现一个服务器监控面板,目前初步支持Linux服务器,监控服务器基本信息,比如:cpu/GPU,内存,储存,操作系统等 -2.采用企业级前后端分离架构,前端使用React框架,后端使用golang原版自带的net库 -3.原理十分简单,后端go获取Linux相关硬件信息,并封装成相关后端json API路由,前端每隔1秒或者2秒获取后端信息 -4.对外访问使用默认端口:2929 后端默认端口为9292 -5.由于是监控面板 前端可以一次性对接多个服务器后端卡片式展示相关信息,前端面板可以展示一些基本信息,用户可以点击详情按钮查看更多详细信息 -6.前端项目已经初始化 前后端代码架构必须分类整齐符合标准方便我阅读修改 -7.注意前端相当于一个只是客户端用于请求可以配置后端服务器,这两个是分开的,前端可以打包部署在用户电脑上比如做成软件app,而后端构建打包后放到各个服务器上 +1.实现一个服务器监控面板,目前初步支持Linux服务器,监控服务器基本信息,比如:cpu/GPU,内存,储存,操作系统等 +2.采用企业级前后端分离架构,前端使用React框架,后端使用golang原版自带的net库 +3.原理十分简单,后端go获取Linux相关硬件信息,并封装成相关后端json API路由,前端每隔1秒或者2秒获取后端信息 +4.对外访问使用默认端口:2929 后端默认端口为9292 +5.由于是监控面板 前端可以一次性对接多个服务器后端卡片式展示相关信息,前端面板可以展示一些基本信息,用户可以点击详情按钮查看更多详细信息 +6.前端项目已经初始化 前后端代码架构必须分类整齐符合标准方便我阅读修改 +7.注意前端相当于一个只是客户端用于请求可以配置后端服务器,这两个是分开的,前端可以打包部署在用户电脑上比如做成软件app,而后端构建打包后放到各个服务器上 8.前端页面风格偏白色柔和风 \ No newline at end of file