commit ac4cc3e02d1db079ccaad360c1756164f46ab255 Author: zaunist Date: Thu Mar 20 22:31:18 2025 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..619ff07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,78 @@ +# 依赖目录 +node_modules/ +.pnp/ +.pnp.js +./backend/node_modules/ +./backend/dist/ +./backend/build/ +./backend/out/ +./frontend/node_modules/ +./frontend/dist/ +./frontend/build/ +./frontend/out/ + + +# 测试覆盖率 +coverage/ + +# 环境变量 +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# 日志文件 +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# 编辑器目录和文件 +.idea/ +.vscode/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +.DS_Store + +# Go 相关 +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +go.work + +# 临时文件 +*.tmp +*.temp +.cache/ +.temp/ +*/.wrangler/ + +# 系统文件 +.DS_Store +Thumbs.db + +# 数据库文件 +*.sqlite +*.sqlite3 +*.db + +# 压缩文件 +*.zip +*.tar.gz +*.rar + +# 其他 +*.bak +*.swp +*.swo +*~ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bf1851d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 XUGOU + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bfa85a8 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# XUGOU - 基于CloudFlare搭建的轻量化监控平台 + +
+ +![XUGOU Logo](frontend/public/logo.svg) + +XUGOU 是一个基于 CloudFlare 的轻量化系统监控平台,提供系统监控和状态页面功能。 + +[English](./README_EN.md) | 简体中文 + +
+ +## ✨ 核心特性 + +- 🖥️ **系统监控** + - 实时监控 CPU、内存、磁盘、网络等系统指标 + - 支持自定义监控间隔 + - 多平台支持(Windows、Linux、macOS) + +- 🌐 **HTTP 监控** + - 支持 HTTP/HTTPS 接口监控 + - 自定义请求方法、头部和请求体 + - 响应时间、状态码和内容检查 + +- 📊 **数据可视化** + - 实时数据图表展示 + - 历史数据趋势分析 + - 自定义仪表盘 + +- 🌍 **状态页面** + - 自定义状态页面 + - 支持多监控项展示 + - 响应式设计 + +## 🏗️ 系统架构 + +XUGOU 采用现代化的系统架构,包含以下组件: + +- **Agent**: 轻量级系统监控客户端 +- **Backend**: 基于 Cloudflare Workers 的后端服务 +- **Frontend**: 基于 React + TypeScript 的现代化前端界面 + +## 🚀 快速开始 + +### 环境要求 + +- Node.js >= 18 +- Go >= 1.24 + +### 部署步骤 + +1. 克隆项目 +```bash +git clone https://github.com/yourusername/xugou.git +cd xugou +``` + +2. 安装依赖 +```bash +# 前端依赖安装 +cd frontend +npm install + +# 后端依赖安装 +cd ../backend +npm install + +``` + +3. 启动服务 +```bash +# 启动后端服务 +cd backend +npm run dev + +# 启动前端服务 +cd frontend +npm run dev + +``` + +5. 访问系统 +打开浏览器访问 `http://localhost:5173` + +## 详细的视频部署教程 + +还没录 + +## ⭐ 支持我们 + +如果这个项目对您有帮助,请给我们一个 ⭐️ 支持! + +## 🤝 贡献 + +欢迎所有形式的贡献,无论是新功能、bug 修复还是文档改进。 + +## 📄 开源协议 + +本项目采用 MIT 协议开源,详见 [LICENSE](./LICENSE) 文件。 \ No newline at end of file diff --git a/agent/README.md b/agent/README.md new file mode 100644 index 0000000..39eb85f --- /dev/null +++ b/agent/README.md @@ -0,0 +1,128 @@ +# Xugou Agent + +Xugou Agent 是一个系统监控客户端,用于收集系统信息并上报到监控服务器。它可以收集 CPU、内存、磁盘、网络等系统信息,并定期上报到指定的服务器。 + +## 功能特点 + +- 收集系统基本信息(主机名、操作系统、平台等) +- 监控 CPU 使用率和负载 +- 监控内存使用情况 +- 监控磁盘使用情况 +- 监控网络接口状态 +- 支持自定义收集间隔 +- 支持 HTTP 上报和控制台输出 +- 支持配置文件和环境变量配置 + +## 安装 + +### 从源码构建 + +```bash +git clone https://github.com/xugou/agent.git +cd agent +go build -o xugou-agent +``` + +### 使用预编译二进制文件 + +从 [Releases](https://github.com/xugou/agent/releases) 页面下载适合您系统的预编译二进制文件。 + +## 使用方法 + +### 基本命令 + +```bash +# 显示帮助信息 +./xugou-agent --help + +# 显示版本信息 +./xugou-agent version + +# 启动客户端 +./xugou-agent start + +# 配置客户端 +./xugou-agent config +``` + +### 配置选项 + +可以通过命令行参数、配置文件或环境变量来配置 Xugou Agent: + +#### 命令行参数 + +```bash +# 指定配置文件 +./xugou-agent --config /path/to/config.yaml + +# 指定服务器地址 +./xugou-agent --server https://monitor.example.com + +# 指定 API 令牌 +./xugou-agent --token YOUR_API_TOKEN + +# 指定日志级别 +./xugou-agent --log-level debug + +# 指定收集间隔(秒) +./xugou-agent start --interval 30 +``` + +#### 配置文件 + +默认配置文件位于 `$HOME/.xugou-agent.yaml`,格式如下: + +```yaml +server: https://monitor.example.com +token: YOUR_API_TOKEN +interval: 60 +log_level: info +``` + +#### 环境变量 + +所有配置选项也可以通过环境变量设置,环境变量名称格式为 `XUGOU_*`: + +```bash +export XUGOU_SERVER=https://monitor.example.com +export XUGOU_TOKEN=YOUR_API_TOKEN +export XUGOU_INTERVAL=60 +export XUGOU_LOG_LEVEL=info +``` + +### 使用控制台输出 + +如果您只想查看收集的系统信息而不上报到服务器,可以将服务器地址设置为 `console`: + +```bash +./xugou-agent --server console start +``` + +## 开发 + +### 依赖项 + +- Go 1.18 或更高版本 +- github.com/spf13/cobra +- github.com/spf13/viper +- github.com/shirou/gopsutil/v3 + +### 项目结构 + +``` +agent/ +├── cmd/ +│ └── agent/ # 命令行命令 +│ ├── root.go # 根命令 +│ ├── start.go # 启动命令 +│ ├── config.go # 配置命令 +│ └── version.go # 版本命令 +├── pkg/ +│ ├── collector/ # 数据收集器 +│ └── reporter/ # 数据上报器 +└── main.go # 程序入口 +``` + +## 许可证 + +MIT \ No newline at end of file diff --git a/agent/cmd/agent/config.go b/agent/cmd/agent/config.go new file mode 100644 index 0000000..0972f7d --- /dev/null +++ b/agent/cmd/agent/config.go @@ -0,0 +1,77 @@ +package agent + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "gopkg.in/yaml.v3" +) + +func init() { + configCmd := &cobra.Command{ + Use: "config", + Short: "配置 Xugou Agent", + Long: `配置 Xugou Agent 的服务器地址、API 令牌等设置`, + Run: runConfig, + } + + rootCmd.AddCommand(configCmd) +} + +func runConfig(cmd *cobra.Command, args []string) { + // 获取当前配置 + server := viper.GetString("server") + token := viper.GetString("token") + interval := viper.GetInt("interval") + logLevel := viper.GetString("log_level") + + // 创建配置结构 + config := map[string]interface{}{ + "server": server, + "token": token, + "interval": interval, + "log_level": logLevel, + } + + // 确定配置文件路径 + configPath := cfgFile + if configPath == "" { + home, err := os.UserHomeDir() + if err != nil { + fmt.Println("错误: 无法获取用户主目录:", err) + os.Exit(1) + } + configPath = filepath.Join(home, ".xugou-agent.yaml") + } + + // 将配置写入文件 + data, err := yaml.Marshal(config) + if err != nil { + fmt.Println("错误: 无法序列化配置:", err) + os.Exit(1) + } + + err = os.WriteFile(configPath, data, 0644) + if err != nil { + fmt.Println("错误: 无法写入配置文件:", err) + os.Exit(1) + } + + fmt.Println("配置已保存到:", configPath) + fmt.Println("当前配置:") + fmt.Printf(" 服务器地址: %s\n", server) + fmt.Printf(" API 令牌: %s\n", maskToken(token)) + fmt.Printf(" 收集间隔: %d秒\n", interval) + fmt.Printf(" 日志级别: %s\n", logLevel) +} + +// maskToken 对令牌进行掩码处理,只显示前4位和后4位 +func maskToken(token string) string { + if len(token) <= 8 { + return token + } + return token[:4] + "..." + token[len(token)-4:] +} diff --git a/agent/cmd/agent/root.go b/agent/cmd/agent/root.go new file mode 100644 index 0000000..6813fd8 --- /dev/null +++ b/agent/cmd/agent/root.go @@ -0,0 +1,73 @@ +package agent + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + cfgFile string + rootCmd = &cobra.Command{ + Use: "xugou-agent", + Short: "Xugou Agent - 系统监控客户端", + Long: `Xugou Agent 是一个系统监控客户端,用于收集系统信息并上报到监控服务器。 +它可以收集 CPU、内存、磁盘、网络等系统信息,并定期上报到指定的服务器。`, + } +) + +// Execute 执行根命令 +func Execute() error { + return rootCmd.Execute() +} + +func init() { + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "配置文件路径 (默认为 $HOME/.xugou-agent.yaml)") + rootCmd.PersistentFlags().String("server", "", "监控服务器地址") + rootCmd.PersistentFlags().String("token", "", "API 令牌") + rootCmd.PersistentFlags().String("log-level", "info", "日志级别 (debug, info, warn, error)") + rootCmd.PersistentFlags().Int("agent-id", 0, "客户端 ID,需要与服务器中注册的 ID 一致") + + viper.BindPFlag("server", rootCmd.PersistentFlags().Lookup("server")) + viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token")) + viper.BindPFlag("log_level", rootCmd.PersistentFlags().Lookup("log-level")) + viper.BindPFlag("agent_id", rootCmd.PersistentFlags().Lookup("agent-id")) +} + +func initConfig() { + if cfgFile != "" { + // 使用指定的配置文件 + viper.SetConfigFile(cfgFile) + } else { + // 查找用户主目录 + home, err := os.UserHomeDir() + if err != nil { + fmt.Println("错误: 无法获取用户主目录:", err) + os.Exit(1) + } + + // 在主目录中查找 .xugou-agent.yaml 文件 + viper.AddConfigPath(home) + viper.SetConfigName(".xugou-agent") + viper.SetConfigType("yaml") + cfgFile = filepath.Join(home, ".xugou-agent.yaml") + } + + // 读取环境变量 + viper.AutomaticEnv() + viper.SetEnvPrefix("XUGOU") + + // 如果找到配置文件,则读取它 + if err := viper.ReadInConfig(); err == nil { + fmt.Println("使用配置文件:", viper.ConfigFileUsed()) + } else { + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + fmt.Println("警告: 配置文件读取错误:", err) + } + } +} diff --git a/agent/cmd/agent/start.go b/agent/cmd/agent/start.go new file mode 100644 index 0000000..e900f64 --- /dev/null +++ b/agent/cmd/agent/start.go @@ -0,0 +1,111 @@ +package agent + +import ( + "context" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/xugou/agent/pkg/collector" + "github.com/xugou/agent/pkg/reporter" +) + +func init() { + startCmd := &cobra.Command{ + Use: "start", + Short: "启动 Xugou Agent", + Long: `启动 Xugou Agent 开始收集系统信息并上报到服务器`, + Run: runStart, + } + + startCmd.Flags().IntP("interval", "i", 60, "数据收集和上报间隔(秒)") + viper.BindPFlag("interval", startCmd.Flags().Lookup("interval")) + + rootCmd.AddCommand(startCmd) +} + +func runStart(cmd *cobra.Command, args []string) { + // 检查必要的配置 + token := viper.GetString("token") + server := viper.GetString("server") + interval := viper.GetInt("interval") + + if token == "" { + fmt.Println("错误: 未设置 API 令牌,请使用 --token 参数或在配置文件中设置") + os.Exit(1) + } + + if server == "" { + fmt.Println("错误: 未设置服务器地址,请使用 --server 参数或在配置文件中设置") + os.Exit(1) + } + + fmt.Println("Xugou Agent 启动中...") + fmt.Printf("服务器地址: %s\n", server) + fmt.Printf("收集间隔: %d秒\n", interval) + fmt.Println("使用令牌自动注册/上报数据") + + // 设置上下文,用于处理取消信号 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // 初始化数据收集器和上报器 + dataCollector := collector.NewCollector() + var dataReporter reporter.Reporter + + // 根据配置决定使用哪种上报器 + if server == "console" { + dataReporter = reporter.NewConsoleReporter() + fmt.Println("使用控制台上报器") + } else { + dataReporter = reporter.NewHTTPReporter(server, token) + fmt.Println("使用HTTP上报器") + } + + // 设置定时器,按指定间隔收集和上报数据 + ticker := time.NewTicker(time.Duration(interval) * time.Second) + defer ticker.Stop() + + // 设置信号处理,用于优雅退出 + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + + // 启动时立即执行一次收集和上报 + go collectAndReport(ctx, dataCollector, dataReporter) + + fmt.Println("Xugou Agent 已启动,按 Ctrl+C 停止") + + // 主循环 + for { + select { + case <-ticker.C: + go collectAndReport(ctx, dataCollector, dataReporter) + case sig := <-sigCh: + fmt.Printf("收到信号 %v,正在停止...\n", sig) + return + } + } +} + +// collectAndReport 收集并上报系统信息 +func collectAndReport(ctx context.Context, c collector.Collector, r reporter.Reporter) { + // 收集系统信息 + info, err := c.Collect(ctx) + if err != nil { + fmt.Printf("收集系统信息失败: %v\n", err) + return + } + + // 上报系统信息 + err = r.Report(ctx, info) + if err != nil { + fmt.Printf("上报系统信息失败: %v\n", err) + return + } + + fmt.Printf("系统信息已收集并上报,时间: %s\n", info.Timestamp.Format("2006-01-02 15:04:05")) +} diff --git a/agent/cmd/agent/version.go b/agent/cmd/agent/version.go new file mode 100644 index 0000000..dc82652 --- /dev/null +++ b/agent/cmd/agent/version.go @@ -0,0 +1,32 @@ +package agent + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var ( + // 版本信息,可以在编译时通过 -ldflags 设置 + Version = "0.1.0" + GitCommit = "unknown" + BuildDate = "unknown" +) + +func init() { + versionCmd := &cobra.Command{ + Use: "version", + Short: "显示版本信息", + Long: `显示 Xugou Agent 的版本信息、构建日期和 Git 提交哈希`, + Run: runVersion, + } + + rootCmd.AddCommand(versionCmd) +} + +func runVersion(cmd *cobra.Command, args []string) { + fmt.Println("Xugou Agent 版本信息:") + fmt.Printf("版本: %s\n", Version) + fmt.Printf("Git 提交: %s\n", GitCommit) + fmt.Printf("构建日期: %s\n", BuildDate) +} diff --git a/agent/go.mod b/agent/go.mod new file mode 100644 index 0000000..13e5416 --- /dev/null +++ b/agent/go.mod @@ -0,0 +1,33 @@ +module github.com/xugou/agent + +go 1.24.0 + +require ( + github.com/shirou/gopsutil/v3 v3.24.5 + github.com/spf13/cobra v1.9.1 + github.com/spf13/viper v1.20.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/sagikazarmark/locafero v0.8.0 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.14.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect +) diff --git a/agent/go.sum b/agent/go.sum new file mode 100644 index 0000000..87c2ca9 --- /dev/null +++ b/agent/go.sum @@ -0,0 +1,77 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.8.0 h1:mXaMVw7IqxNBxfv3LdWt9MDmcWDQ1fagDH918lOdVaQ= +github.com/sagikazarmark/locafero v0.8.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= +github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/agent/main.go b/agent/main.go new file mode 100644 index 0000000..4678630 --- /dev/null +++ b/agent/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + "os" + + "github.com/xugou/agent/cmd/agent" +) + +func main() { + if err := agent.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/agent/pkg/collector/collector.go b/agent/pkg/collector/collector.go new file mode 100644 index 0000000..ced344d --- /dev/null +++ b/agent/pkg/collector/collector.go @@ -0,0 +1,191 @@ +package collector + +import ( + "context" + "fmt" + "runtime" + "time" + + "github.com/shirou/gopsutil/v3/cpu" + "github.com/shirou/gopsutil/v3/disk" + "github.com/shirou/gopsutil/v3/host" + "github.com/shirou/gopsutil/v3/load" + "github.com/shirou/gopsutil/v3/mem" + "github.com/shirou/gopsutil/v3/net" +) + +// SystemInfo 包含系统的各种信息 +type SystemInfo struct { + Timestamp time.Time `json:"timestamp"` + Hostname string `json:"hostname"` + Platform string `json:"platform"` + OS string `json:"os"` + Version string `json:"version"` // 操作系统版本 + CPUInfo CPUInfo `json:"cpu"` + MemoryInfo MemoryInfo `json:"memory"` + DiskInfo []DiskInfo `json:"disks"` + NetworkInfo []NetworkInfo `json:"network"` + LoadInfo LoadInfo `json:"load"` +} + +// CPUInfo 包含CPU相关信息 +type CPUInfo struct { + Usage float64 `json:"usage"` + Cores int `json:"cores"` + ModelName string `json:"model_name"` + Temperature float64 `json:"temperature,omitempty"` +} + +// MemoryInfo 包含内存相关信息 +type MemoryInfo struct { + Total uint64 `json:"total"` + Used uint64 `json:"used"` + Free uint64 `json:"free"` + UsageRate float64 `json:"usage_rate"` +} + +// DiskInfo 包含磁盘相关信息 +type DiskInfo struct { + Device string `json:"device"` + MountPoint string `json:"mount_point"` + Total uint64 `json:"total"` + Used uint64 `json:"used"` + Free uint64 `json:"free"` + UsageRate float64 `json:"usage_rate"` + FSType string `json:"fs_type"` +} + +// NetworkInfo 包含网络相关信息 +type NetworkInfo struct { + Interface string `json:"interface"` + BytesSent uint64 `json:"bytes_sent"` + BytesRecv uint64 `json:"bytes_recv"` + PacketsSent uint64 `json:"packets_sent"` + PacketsRecv uint64 `json:"packets_recv"` +} + +// LoadInfo 包含系统负载信息 +type LoadInfo struct { + Load1 float64 `json:"load1"` + Load5 float64 `json:"load5"` + Load15 float64 `json:"load15"` +} + +// Collector 定义数据收集器接口 +type Collector interface { + Collect(ctx context.Context) (*SystemInfo, error) +} + +// DefaultCollector 是默认的数据收集器实现 +type DefaultCollector struct{} + +// NewCollector 创建一个新的数据收集器 +func NewCollector() Collector { + return &DefaultCollector{} +} + +// Collect 收集系统信息 +func (c *DefaultCollector) Collect(ctx context.Context) (*SystemInfo, error) { + info := &SystemInfo{ + Timestamp: time.Now(), + } + + // 获取主机信息 + hostInfo, err := host.Info() + if err != nil { + return nil, fmt.Errorf("获取主机信息失败: %w", err) + } + info.Hostname = hostInfo.Hostname + info.Platform = hostInfo.Platform + info.OS = hostInfo.OS + // 设置操作系统版本,格式化为更有意义的信息 + info.Version = fmt.Sprintf("%s %s (%s)", hostInfo.Platform, hostInfo.PlatformVersion, hostInfo.KernelVersion) + + // 获取CPU信息 + cpuPercent, err := cpu.Percent(time.Second, false) + if err != nil { + return nil, fmt.Errorf("获取CPU使用率失败: %w", err) + } + + cpuInfo, err := cpu.Info() + if err != nil { + return nil, fmt.Errorf("获取CPU信息失败: %w", err) + } + + var modelName string + if len(cpuInfo) > 0 { + modelName = cpuInfo[0].ModelName + } + + info.CPUInfo = CPUInfo{ + Usage: cpuPercent[0], + Cores: runtime.NumCPU(), + ModelName: modelName, + } + + // 获取内存信息 + memInfo, err := mem.VirtualMemory() + if err != nil { + return nil, fmt.Errorf("获取内存信息失败: %w", err) + } + + info.MemoryInfo = MemoryInfo{ + Total: memInfo.Total, + Used: memInfo.Used, + Free: memInfo.Free, + UsageRate: memInfo.UsedPercent, + } + + // 获取磁盘信息 + partitions, err := disk.Partitions(false) + if err != nil { + return nil, fmt.Errorf("获取磁盘分区信息失败: %w", err) + } + + for _, partition := range partitions { + usage, err := disk.Usage(partition.Mountpoint) + if err != nil { + continue + } + + diskInfo := DiskInfo{ + Device: partition.Device, + MountPoint: partition.Mountpoint, + Total: usage.Total, + Used: usage.Used, + Free: usage.Free, + UsageRate: usage.UsedPercent, + FSType: partition.Fstype, + } + info.DiskInfo = append(info.DiskInfo, diskInfo) + } + + // 获取网络信息 + netIOCounters, err := net.IOCounters(true) + if err != nil { + return nil, fmt.Errorf("获取网络信息失败: %w", err) + } + + for _, netIO := range netIOCounters { + networkInfo := NetworkInfo{ + Interface: netIO.Name, + BytesSent: netIO.BytesSent, + BytesRecv: netIO.BytesRecv, + PacketsSent: netIO.PacketsSent, + PacketsRecv: netIO.PacketsRecv, + } + info.NetworkInfo = append(info.NetworkInfo, networkInfo) + } + + // 获取系统负载 + loadAvg, err := load.Avg() + if err == nil { + info.LoadInfo = LoadInfo{ + Load1: loadAvg.Load1, + Load5: loadAvg.Load5, + Load15: loadAvg.Load15, + } + } + + return info, nil +} diff --git a/agent/pkg/reporter/reporter.go b/agent/pkg/reporter/reporter.go new file mode 100644 index 0000000..a479efc --- /dev/null +++ b/agent/pkg/reporter/reporter.go @@ -0,0 +1,277 @@ +package reporter + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net" + "net/http" + "time" + + "github.com/xugou/agent/pkg/collector" +) + +// Reporter 定义数据上报器接口 +type Reporter interface { + Report(ctx context.Context, info *collector.SystemInfo) error +} + +// HTTPReporter 是基于HTTP的数据上报器实现 +type HTTPReporter struct { + serverURL string + apiToken string + client *http.Client + lastNetworkRX uint64 // 上次网络接收字节数 + lastNetworkTX uint64 // 上次网络发送字节数 + lastUpdateTime time.Time // 上次更新时间 + registered bool // 是否已注册 +} + +// NewHTTPReporter 创建一个新的HTTP数据上报器 +func NewHTTPReporter(serverURL, apiToken string) Reporter { + return &HTTPReporter{ + serverURL: serverURL, + apiToken: apiToken, + client: &http.Client{ + Timeout: 10 * time.Second, + }, + lastUpdateTime: time.Now(), + registered: false, + } +} + +// StatusPayload 定义上报到后端的数据结构 +type StatusPayload struct { + Token string `json:"token"` // API令牌 + CPUUsage float64 `json:"cpu_usage"` + MemoryTotal uint64 `json:"memory_total"` + MemoryUsed uint64 `json:"memory_used"` + DiskTotal uint64 `json:"disk_total"` + DiskUsed uint64 `json:"disk_used"` + NetworkRX uint64 `json:"network_rx"` + NetworkTX uint64 `json:"network_tx"` + Hostname string `json:"hostname"` // 主机名 + IPAddress string `json:"ip_address"` // IP地址 + OS string `json:"os"` // 操作系统 + Version string `json:"version"` // 操作系统版本 +} + +// RegisterPayload 定义注册到后端的数据结构 +type RegisterPayload struct { + Token string `json:"token"` // API令牌 + Name string `json:"name"` // 客户端名称 + Hostname string `json:"hostname"` // 主机名 + IPAddress string `json:"ip_address"` // IP地址 + OS string `json:"os"` // 操作系统 + Version string `json:"version"` // 操作系统版本 +} + +// RegisterResponse 定义注册响应结构 +type RegisterResponse struct { + Success bool `json:"success"` + Message string `json:"message"` + Agent struct { + ID int `json:"id"` + } `json:"agent"` +} + +// ensureRegistered 确保客户端已经在服务器端注册 +func (r *HTTPReporter) ensureRegistered(ctx context.Context, info *collector.SystemInfo) error { + if r.registered { + return nil + } + + // 构建注册的数据结构 + payload := RegisterPayload{ + Token: r.apiToken, + Name: info.Hostname, + Hostname: info.Hostname, + IPAddress: getLocalIP(), + OS: info.OS, + Version: info.Version, + } + + // 将数据转换为JSON + data, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("序列化注册信息失败: %w", err) + } + + // 构建注册URL + url := fmt.Sprintf("%s/api/agents/register", r.serverURL) + + // 创建HTTP请求 + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(data)) + if err != nil { + return fmt.Errorf("创建注册HTTP请求失败: %w", err) + } + + // 设置请求头 + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "XUGOU-Agent/1.0") + + // 发送请求 + resp, err := r.client.Do(req) + if err != nil { + return fmt.Errorf("发送注册HTTP请求失败: %w", err) + } + defer resp.Body.Close() + + // 检查响应状态码 + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("服务器返回错误状态码: %d", resp.StatusCode) + } + + // 解析响应 + var response RegisterResponse + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&response); err != nil { + return fmt.Errorf("解析注册响应失败: %w", err) + } + + if !response.Success { + return fmt.Errorf("注册失败: %s", response.Message) + } + + fmt.Printf("客户端注册成功,通过Token: %s\n", r.apiToken) + r.registered = true + return nil +} + +// Report 将系统信息上报到服务器 +func (r *HTTPReporter) Report(ctx context.Context, info *collector.SystemInfo) error { + // 确保客户端已注册 + if err := r.ensureRegistered(ctx, info); err != nil { + return fmt.Errorf("客户端注册失败: %w", err) + } + + // 提取所有磁盘的总容量和使用量 + var diskTotal, diskUsed uint64 + for _, disk := range info.DiskInfo { + diskTotal += disk.Total + diskUsed += disk.Used + } + + // 提取网络传输数据(简单汇总所有网络接口) + var currentNetworkRX, currentNetworkTX uint64 + for _, net := range info.NetworkInfo { + currentNetworkRX += net.BytesRecv + currentNetworkTX += net.BytesSent + } + + // 计算网络流量速率(KB/s) + now := time.Now() + var networkRXRate, networkTXRate float64 + + if !r.lastUpdateTime.IsZero() { + timeDiff := now.Sub(r.lastUpdateTime).Seconds() + if timeDiff > 0 { + networkRXRate = float64(currentNetworkRX-r.lastNetworkRX) / timeDiff / 1024 // 转换为KB/s + networkTXRate = float64(currentNetworkTX-r.lastNetworkTX) / timeDiff / 1024 // 转换为KB/s + } + } + + // 更新历史数据 + r.lastNetworkRX = currentNetworkRX + r.lastNetworkTX = currentNetworkTX + r.lastUpdateTime = now + + // 构建上报的数据结构 + payload := StatusPayload{ + Token: r.apiToken, + CPUUsage: info.CPUInfo.Usage, + MemoryTotal: info.MemoryInfo.Total / 1024, // 转换为 KB + MemoryUsed: info.MemoryInfo.Used / 1024, // 转换为 KB + DiskTotal: diskTotal / 1024, // 转换为 KB + DiskUsed: diskUsed / 1024, // 转换为 KB + NetworkRX: uint64(networkRXRate), // 网络接收速率(KB/s) + NetworkTX: uint64(networkTXRate), // 网络发送速率(KB/s) + Hostname: info.Hostname, // 添加主机名 + OS: info.OS, // 添加操作系统信息 + Version: info.Version, // 添加操作系统版本 + IPAddress: getLocalIP(), // 获取本地IP地址 + } + + // 将数据转换为JSON + data, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("序列化系统信息失败: %w", err) + } + + // 构建请求URL - 使用新的基于token的API + url := fmt.Sprintf("%s/api/agents/status", r.serverURL) + + // 创建HTTP请求 + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(data)) + if err != nil { + return fmt.Errorf("创建HTTP请求失败: %w", err) + } + + // 设置请求头 + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "XUGOU-Agent/1.0") + + // 发送请求 + resp, err := r.client.Do(req) + if err != nil { + return fmt.Errorf("发送HTTP请求失败: %w", err) + } + defer resp.Body.Close() + + // 检查响应状态码 + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("服务器返回错误状态码: %d", resp.StatusCode) + } + + fmt.Printf("成功上报数据到服务器,token: %s, CPU: %.2f%%, 内存: %.2f%%\n", + r.apiToken, payload.CPUUsage, float64(payload.MemoryUsed)/float64(payload.MemoryTotal)*100) + + return nil +} + +// ConsoleReporter 是将数据输出到控制台的上报器实现 +type ConsoleReporter struct{} + +// NewConsoleReporter 创建一个新的控制台数据上报器 +func NewConsoleReporter() Reporter { + return &ConsoleReporter{} +} + +// Report 将系统信息输出到控制台 +func (r *ConsoleReporter) Report(ctx context.Context, info *collector.SystemInfo) error { + data, err := json.MarshalIndent(info, "", " ") + if err != nil { + return fmt.Errorf("序列化系统信息失败: %w", err) + } + + fmt.Println("系统信息收集时间:", info.Timestamp.Format("2006-01-02 15:04:05")) + fmt.Println("主机名:", info.Hostname) + fmt.Println("平台:", info.Platform, info.OS) + fmt.Println("CPU使用率:", info.CPUInfo.Usage, "%") + fmt.Println("内存使用率:", info.MemoryInfo.UsageRate, "%") + fmt.Println("系统负载:", info.LoadInfo.Load1, info.LoadInfo.Load5, info.LoadInfo.Load15) + fmt.Println("详细信息:") + fmt.Println(string(data)) + + return nil +} + +// getLocalIP 获取本地IP地址 +func getLocalIP() string { + addrs, err := net.InterfaceAddrs() + if err != nil { + return "未知" + } + + for _, addr := range addrs { + // 检查IP地址类型 + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + return ipnet.IP.String() + } + } + } + + return "未知" +} diff --git a/agent/xugou-agent b/agent/xugou-agent new file mode 100755 index 0000000..195d0db Binary files /dev/null and b/agent/xugou-agent differ diff --git a/backend/dist/db/schema.d.ts b/backend/dist/db/schema.d.ts new file mode 100644 index 0000000..e705de6 --- /dev/null +++ b/backend/dist/db/schema.d.ts @@ -0,0 +1,3 @@ +export declare const CREATE_USERS_TABLE = "\nCREATE TABLE IF NOT EXISTS users (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n username TEXT NOT NULL UNIQUE,\n password TEXT NOT NULL,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP\n)\n"; +export declare const CREATE_MONITORS_TABLE = "\nCREATE TABLE IF NOT EXISTS monitors (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL,\n url TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'unknown',\n last_check TIMESTAMP,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP\n)\n"; +export declare const CREATE_AGENTS_TABLE = "\nCREATE TABLE IF NOT EXISTS agents (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n cpu_usage INTEGER,\n memory_total INTEGER,\n memory_used INTEGER,\n disk_total INTEGER,\n disk_used INTEGER,\n network_rx INTEGER,\n network_tx INTEGER\n)\n"; diff --git a/backend/dist/db/schema.js b/backend/dist/db/schema.js new file mode 100644 index 0000000..c81ddaa --- /dev/null +++ b/backend/dist/db/schema.js @@ -0,0 +1,42 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CREATE_AGENTS_TABLE = exports.CREATE_MONITORS_TABLE = exports.CREATE_USERS_TABLE = void 0; +// 用户表 +exports.CREATE_USERS_TABLE = ` +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +) +`; +// 监控表 +exports.CREATE_MONITORS_TABLE = ` +CREATE TABLE IF NOT EXISTS monitors ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + url TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'unknown', + last_check TIMESTAMP, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +) +`; +// 客户端表 +exports.CREATE_AGENTS_TABLE = ` +CREATE TABLE IF NOT EXISTS agents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + cpu_usage INTEGER, + memory_total INTEGER, + memory_used INTEGER, + disk_total INTEGER, + disk_used INTEGER, + network_rx INTEGER, + network_tx INTEGER +) +`; +//# sourceMappingURL=schema.js.map \ No newline at end of file diff --git a/backend/dist/db/schema.js.map b/backend/dist/db/schema.js.map new file mode 100644 index 0000000..dda5829 --- /dev/null +++ b/backend/dist/db/schema.js.map @@ -0,0 +1 @@ +{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":";;;AAAA,MAAM;AACO,QAAA,kBAAkB,GAAG;;;;;;;;CAQjC,CAAC;AAEF,MAAM;AACO,QAAA,qBAAqB,GAAG;;;;;;;;;;CAUpC,CAAC;AAEF,OAAO;AACM,QAAA,mBAAmB,GAAG;;;;;;;;;;;;;;CAclC,CAAC"} \ No newline at end of file diff --git a/backend/dist/index.d.ts b/backend/dist/index.d.ts new file mode 100644 index 0000000..82bac30 --- /dev/null +++ b/backend/dist/index.d.ts @@ -0,0 +1,15 @@ +import { Bindings } from './models/db'; +declare global { + namespace NodeJS { + interface ProcessEnv { + NODE_ENV?: string; + PORT?: string; + JWT_SECRET?: string; + } + } +} +declare const _default: { + fetch: (request: Request, Env?: {} | Bindings | undefined, executionCtx?: import("hono").ExecutionContext) => Response | Promise; + scheduled(event: any, env: any, ctx: any): Promise; +}; +export default _default; diff --git a/backend/dist/index.js b/backend/dist/index.js new file mode 100644 index 0000000..da293ba --- /dev/null +++ b/backend/dist/index.js @@ -0,0 +1,68 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const logger_1 = require("hono/logger"); +const cors_1 = require("hono/cors"); +const pretty_json_1 = require("hono/pretty-json"); +// 导入路由 +const auth_1 = __importDefault(require("./routes/auth")); +const monitors_1 = __importDefault(require("./routes/monitors")); +const agents_1 = __importDefault(require("./routes/agents")); +const users_1 = __importDefault(require("./routes/users")); +const status_1 = __importDefault(require("./routes/status")); +const database_1 = __importDefault(require("./setup/database")); +const tasks_1 = require("./tasks"); +// 创建Hono应用 +const app = new hono_1.Hono(); +// 中间件 +app.use('*', (0, logger_1.logger)()); +app.use('*', (0, cors_1.cors)({ + origin: ['http://localhost:5173', 'https://XUGOU.pages.dev'], // 允许前端开发服务器和生产环境 + credentials: true, +})); +app.use('*', (0, pretty_json_1.prettyJSON)()); +// 公共路由 +app.get('/', (c) => c.json({ message: 'XUGOU API 服务正在运行' })); +// 获取 JWT 密钥 +const getJwtSecret = (c) => { + // 在 Cloudflare Workers 环境中,使用 env 变量 + if (typeof process === 'undefined') { + return c.env.JWT_SECRET || 'your-secret-key-change-in-production'; + } + // 在 Node.js 环境中,使用 process.env + return process.env.JWT_SECRET || 'your-secret-key-change-in-production'; +}; +// 路由注册 +app.route('/api/auth', auth_1.default); +app.route('/api/monitors', monitors_1.default); +app.route('/api/agents', agents_1.default); +app.route('/api/users', users_1.default); +app.route('/api/status', status_1.default); +app.route('/api', database_1.default); +// 添加监控检查触发路由 +app.get('/api/trigger-check', async (c) => { + const { scheduled } = tasks_1.monitorTask; + if (scheduled) { + await scheduled(null, c.env, null); + } + return c.json({ success: true, message: '监控检查已触发' }); +}); +// 导出 fetch 函数供 Cloudflare Workers 使用 +exports.default = { + // 处理 HTTP 请求 + fetch: app.fetch.bind(app), + // 添加定时任务,每分钟执行一次监控检查和客户端状态检查 + async scheduled(event, env, ctx) { + try { + // 执行所有定时任务 + await (0, tasks_1.runScheduledTasks)(event, env, ctx); + } + catch (error) { + console.error('定时任务执行出错:', error); + } + } +}; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/backend/dist/index.js.map b/backend/dist/index.js.map new file mode 100644 index 0000000..6d1e7fa --- /dev/null +++ b/backend/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAAA,+BAA4B;AAC5B,wCAAqC;AACrC,oCAAiC;AAGjC,kDAA8C;AAoC9C,OAAO;AACP,yDAAuC;AACvC,iEAA8C;AAC9C,6DAA0C;AAC1C,2DAAwC;AACxC,6DAA2C;AAC3C,gEAA4C;AAC5C,mCAAyD;AAEzD,WAAW;AACX,MAAM,GAAG,GAAG,IAAI,WAAI,EAA0B,CAAC;AAE/C,MAAM;AACN,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,IAAA,eAAM,GAAE,CAAC,CAAC;AACvB,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,IAAA,WAAI,EAAC;IAChB,MAAM,EAAE,CAAC,uBAAuB,EAAE,yBAAyB,CAAC,EAAE,iBAAiB;IAC/E,WAAW,EAAE,IAAI;CAClB,CAAC,CAAC,CAAC;AACJ,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,IAAA,wBAAU,GAAE,CAAC,CAAC;AAE3B,OAAO;AACP,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC;AAE7D,YAAY;AACZ,MAAM,YAAY,GAAG,CAAC,CAAM,EAAE,EAAE;IAC9B,qCAAqC;IACrC,IAAI,OAAO,OAAO,KAAK,WAAW,EAAE,CAAC;QACnC,OAAO,CAAC,CAAC,GAAG,CAAC,UAAU,IAAI,sCAAsC,CAAC;IACpE,CAAC;IACD,+BAA+B;IAC/B,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,sCAAsC,CAAC;AAC1E,CAAC,CAAC;AAEF,OAAO;AACP,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,cAAU,CAAC,CAAC;AACnC,GAAG,CAAC,KAAK,CAAC,eAAe,EAAE,kBAAa,CAAC,CAAC;AAC1C,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,gBAAW,CAAC,CAAC;AACtC,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,eAAU,CAAC,CAAC;AACpC,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,gBAAY,CAAC,CAAC;AACvC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,kBAAY,CAAC,CAAC;AAEhC,aAAa;AACb,GAAG,CAAC,GAAG,CAAC,oBAAoB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACxC,MAAM,EAAE,SAAS,EAAE,GAAG,mBAAW,CAAC;IAClC,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC;AAEH,qCAAqC;AACrC,kBAAe;IACb,aAAa;IACb,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;IAE1B,6BAA6B;IAC7B,KAAK,CAAC,SAAS,CAAC,KAAU,EAAE,GAAQ,EAAE,GAAQ;QAC5C,IAAI,CAAC;YACH,WAAW;YACX,MAAM,IAAA,yBAAiB,EAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;CACF,CAAC"} \ No newline at end of file diff --git a/backend/dist/init-db.d.ts b/backend/dist/init-db.d.ts new file mode 100644 index 0000000..b071963 --- /dev/null +++ b/backend/dist/init-db.d.ts @@ -0,0 +1,6 @@ +import { Hono } from 'hono'; +import { Bindings } from './models/db'; +declare const initDb: Hono<{ + Bindings: Bindings; +}, import("hono/types").BlankSchema, "/">; +export default initDb; diff --git a/backend/dist/init-db.js b/backend/dist/init-db.js new file mode 100644 index 0000000..65275b0 --- /dev/null +++ b/backend/dist/init-db.js @@ -0,0 +1,188 @@ +"use strict"; +// 初始化数据库脚本 +// 用于在 Cloudflare Workers 环境中创建数据库表和初始数据 +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const bcryptjs_1 = __importDefault(require("bcryptjs")); +const initDb = new hono_1.Hono(); +// 初始化数据库路由 +initDb.get('/init-db', async (c) => { + try { + console.log('开始初始化数据库...'); + // 先删除已有的表 + console.log('删除已有的表...'); + try { + await c.env.DB.exec("DROP TABLE IF EXISTS status_page_agents"); + await c.env.DB.exec("DROP TABLE IF EXISTS status_page_monitors"); + await c.env.DB.exec("DROP TABLE IF EXISTS status_page_config"); + await c.env.DB.exec("DROP TABLE IF EXISTS monitor_status_history"); + await c.env.DB.exec("DROP TABLE IF EXISTS monitor_checks"); + await c.env.DB.exec("DROP TABLE IF EXISTS metrics"); + await c.env.DB.exec("DROP TABLE IF EXISTS agents"); + await c.env.DB.exec("DROP TABLE IF EXISTS monitors"); + await c.env.DB.exec("DROP TABLE IF EXISTS users"); + } + catch (error) { + console.log('删除表出错,可能表不存在:', error); + } + console.log('创建用户表...'); + // 创建用户表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password TEXT NOT NULL, email TEXT, role TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)"); + console.log('创建监控表...'); + // 创建监控表(单行格式) + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS monitors (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, url TEXT NOT NULL, method TEXT NOT NULL, interval INTEGER NOT NULL, timeout INTEGER NOT NULL, expected_status INTEGER NOT NULL, headers TEXT NOT NULL, body TEXT, created_by INTEGER NOT NULL, active BOOLEAN NOT NULL, status TEXT DEFAULT 'pending', uptime REAL DEFAULT 100.0, response_time INTEGER DEFAULT 0, last_checked TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (created_by) REFERENCES users(id))"); + console.log('创建监控历史记录表...'); + // 创建监控历史记录表(单行格式) + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS monitor_checks (id INTEGER PRIMARY KEY AUTOINCREMENT, monitor_id INTEGER NOT NULL, status TEXT NOT NULL, response_time INTEGER, status_code INTEGER, error TEXT, checked_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (monitor_id) REFERENCES monitors(id))"); + console.log('创建监控状态历史表...'); + // 创建监控状态历史表(单行格式) + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS monitor_status_history (id INTEGER PRIMARY KEY AUTOINCREMENT, monitor_id INTEGER NOT NULL, status TEXT NOT NULL, timestamp TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (monitor_id) REFERENCES monitors(id))"); + console.log('创建客户端表...'); + // 创建客户端表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS agents (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, description TEXT, token TEXT NOT NULL UNIQUE, created_by INTEGER NOT NULL, active BOOLEAN NOT NULL, last_seen TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (created_by) REFERENCES users(id))"); + console.log('创建指标表...'); + // 创建指标表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS metrics (id INTEGER PRIMARY KEY AUTOINCREMENT, agent_id INTEGER NOT NULL, timestamp TEXT NOT NULL, cpu_usage REAL NOT NULL, cpu_cores INTEGER NOT NULL, memory_total INTEGER NOT NULL, memory_used INTEGER NOT NULL, memory_free INTEGER NOT NULL, disk_total INTEGER NOT NULL, disk_used INTEGER NOT NULL, disk_free INTEGER NOT NULL, network_rx INTEGER NOT NULL, network_tx INTEGER NOT NULL, FOREIGN KEY (agent_id) REFERENCES agents(id))"); + console.log('创建状态页配置表...'); + // 创建状态页配置表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS status_page_config (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, title TEXT NOT NULL DEFAULT '系统状态', description TEXT DEFAULT '系统当前运行状态', logo_url TEXT DEFAULT '', custom_css TEXT DEFAULT '', created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id))"); + console.log('创建状态页监控项关联表...'); + // 创建状态页监控项关联表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS status_page_monitors (config_id INTEGER NOT NULL, monitor_id INTEGER NOT NULL, PRIMARY KEY (config_id, monitor_id), FOREIGN KEY (config_id) REFERENCES status_page_config(id) ON DELETE CASCADE, FOREIGN KEY (monitor_id) REFERENCES monitors(id) ON DELETE CASCADE)"); + console.log('创建状态页客户端关联表...'); + // 创建状态页客户端关联表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS status_page_agents (config_id INTEGER NOT NULL, agent_id INTEGER NOT NULL, PRIMARY KEY (config_id, agent_id), FOREIGN KEY (config_id) REFERENCES status_page_config(id) ON DELETE CASCADE, FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE)"); + console.log('检查管理员用户...'); + // 检查是否已存在管理员用户 + const adminUser = await c.env.DB.prepare('SELECT id FROM users WHERE username = ?').bind('admin').first(); + // 如果不存在管理员用户,则创建一个 + if (!adminUser) { + console.log('创建管理员用户...'); + // 密码: admin123 + const salt = await bcryptjs_1.default.genSalt(10); + const hashedPassword = await bcryptjs_1.default.hash('admin123', salt); + const now = new Date().toISOString(); + await c.env.DB.prepare(`INSERT INTO users (username, password, email, role, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?)`).bind('admin', hashedPassword, 'admin@example.com', 'admin', now, now).run(); + } + console.log('添加示例监控数据...'); + // 添加示例监控数据 + await addSampleMonitors(c); + console.log('添加示例客户端数据...'); + // 添加示例客户端数据 + await addSampleAgents(c); + return c.json({ + success: true, + message: '数据库初始化成功', + tables: ['users', 'monitors', 'monitor_checks', 'monitor_status_history', 'agents', 'metrics', 'status_page_config', 'status_page_monitors', 'status_page_agents'] + }); + } + catch (error) { + console.error('数据库初始化错误:', error); + return c.json({ + success: false, + message: '数据库初始化失败', + error: error instanceof Error ? error.message : String(error) + }, 500); + } +}); +// 添加示例监控数据 +async function addSampleMonitors(c) { + // 检查是否已有示例监控数据 + const existingMonitors = await c.env.DB.prepare('SELECT COUNT(*) as count FROM monitors').first(); + if (existingMonitors.count === 0) { + console.log('添加示例监控...'); + const now = new Date().toISOString(); + const userId = 1; // 管理员用户ID + // 添加API服务监控 + const apiMonitor = await c.env.DB.prepare(`INSERT INTO monitors (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind('API服务', 'https://api.example.com/status', 'GET', 60, 30, 200, '{}', '', userId, true, 'up', 99.98, 120, now, now, now).run(); + // 添加用户服务监控 + const userMonitor = await c.env.DB.prepare(`INSERT INTO monitors (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind('用户服务', 'https://users.example.com/health', 'GET', 30, 30, 200, '{}', '', userId, true, 'up', 99.95, 150, now, now, now).run(); + // 添加支付服务监控 + const paymentMonitor = await c.env.DB.prepare(`INSERT INTO monitors (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind('支付服务', 'https://payments.example.com/status', 'GET', 60, 30, 200, '{}', '', userId, true, 'up', 99.9, 180, now, now, now).run(); + // 添加数据库服务监控 + const dbMonitor = await c.env.DB.prepare(`INSERT INTO monitors (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind('数据库服务', 'https://db.example.com/health', 'GET', 120, 30, 200, '{}', '', userId, true, 'down', 99.5, 300, now, now, now).run(); + } +} +// 添加示例客户端数据 +async function addSampleAgents(c) { + // 检查是否已有示例客户端数据 + const existingAgents = await c.env.DB.prepare('SELECT COUNT(*) as count FROM agents').first(); + if (existingAgents.count === 0) { + console.log('添加示例客户端...'); + const now = new Date().toISOString(); + const userId = 1; // 管理员用户ID + // 主服务器 + const primaryAgent = await c.env.DB.prepare(`INSERT INTO agents (name, description, token, created_by, active, last_seen, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).bind('主服务器', 'Web应用和数据库服务器', 'primary-server-token-123456', userId, true, now, now, now).run(); + // 备份服务器 + const backupAgent = await c.env.DB.prepare(`INSERT INTO agents (name, description, token, created_by, active, last_seen, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).bind('备份服务器', '备份和灾难恢复服务器', 'backup-server-token-654321', userId, true, now, now, now).run(); + // API服务器 + const apiAgent = await c.env.DB.prepare(`INSERT INTO agents (name, description, token, created_by, active, last_seen, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).bind('API服务器', 'API网关和服务', 'api-server-token-789012', userId, true, now, now, now).run(); + // 添加客户端指标数据 + await addSampleMetrics(c); + } +} +// 添加示例指标数据 +async function addSampleMetrics(c) { + console.log('添加示例指标数据...'); + // 获取所有客户端 + const agents = await c.env.DB.prepare('SELECT id FROM agents').all(); + if (agents.results && agents.results.length > 0) { + const now = new Date().toISOString(); + const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000).toISOString(); + const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000).toISOString(); + for (const agent of agents.results) { + // 当前时间的指标 + await c.env.DB.prepare(`INSERT INTO metrics (agent_id, timestamp, cpu_usage, cpu_cores, memory_total, memory_used, memory_free, disk_total, disk_used, disk_free, network_rx, network_tx) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(agent.id, now, (Math.random() * 30 + 10).toFixed(2), // CPU使用率 10-40% + 4, // 4核CPU + 16384, // 总内存 16GB + Math.floor(Math.random() * 6000 + 4000), // 使用内存 4-10GB + Math.floor(16384 - (Math.random() * 6000 + 4000)), // 剩余内存 + 1048576, // 总硬盘 1TB + Math.floor(Math.random() * 300000 + 400000), // 使用硬盘 400-700GB + Math.floor(1048576 - (Math.random() * 300000 + 400000)), // 剩余硬盘 + Math.floor(Math.random() * 1000 + 100), // 网络接收 100-1100KB/s + Math.floor(Math.random() * 500 + 50) // 网络发送 50-550KB/s + ).run(); + // 5分钟前的指标 + await c.env.DB.prepare(`INSERT INTO metrics (agent_id, timestamp, cpu_usage, cpu_cores, memory_total, memory_used, memory_free, disk_total, disk_used, disk_free, network_rx, network_tx) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(agent.id, fiveMinutesAgo, (Math.random() * 30 + 5).toFixed(2), // CPU使用率 5-35% + 4, // 4核CPU + 16384, // 总内存 16GB + Math.floor(Math.random() * 5000 + 3000), // 使用内存 3-8GB + Math.floor(16384 - (Math.random() * 5000 + 3000)), // 剩余内存 + 1048576, // 总硬盘 1TB + Math.floor(Math.random() * 300000 + 400000), // 使用硬盘 400-700GB + Math.floor(1048576 - (Math.random() * 300000 + 400000)), // 剩余硬盘 + Math.floor(Math.random() * 800 + 50), // 网络接收 50-850KB/s + Math.floor(Math.random() * 400 + 30) // 网络发送 30-430KB/s + ).run(); + // 10分钟前的指标 + await c.env.DB.prepare(`INSERT INTO metrics (agent_id, timestamp, cpu_usage, cpu_cores, memory_total, memory_used, memory_free, disk_total, disk_used, disk_free, network_rx, network_tx) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(agent.id, tenMinutesAgo, (Math.random() * 25 + 3).toFixed(2), // CPU使用率 3-28% + 4, // 4核CPU + 16384, // 总内存 16GB + Math.floor(Math.random() * 4000 + 2000), // 使用内存 2-6GB + Math.floor(16384 - (Math.random() * 4000 + 2000)), // 剩余内存 + 1048576, // 总硬盘 1TB + Math.floor(Math.random() * 300000 + 400000), // 使用硬盘 400-700GB + Math.floor(1048576 - (Math.random() * 300000 + 400000)), // 剩余硬盘 + Math.floor(Math.random() * 500 + 30), // 网络接收 30-530KB/s + Math.floor(Math.random() * 300 + 20) // 网络发送 20-320KB/s + ).run(); + } + } +} +exports.default = initDb; +//# sourceMappingURL=init-db.js.map \ No newline at end of file diff --git a/backend/dist/init-db.js.map b/backend/dist/init-db.js.map new file mode 100644 index 0000000..ad7b3ce --- /dev/null +++ b/backend/dist/init-db.js.map @@ -0,0 +1 @@ +{"version":3,"file":"init-db.js","sourceRoot":"","sources":["../src/init-db.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/models/agent.d.ts b/backend/dist/models/agent.d.ts new file mode 100644 index 0000000..7697bcc --- /dev/null +++ b/backend/dist/models/agent.d.ts @@ -0,0 +1,20 @@ +export interface Agent { + id: number; + name: string; + token: string; + created_by: number; + status: string; + created_at: string; + updated_at: string; + hostname?: string; + ip_address?: string; + os?: string; + version?: string; + cpu_usage?: number; + memory_total?: number; + memory_used?: number; + disk_total?: number; + disk_used?: number; + network_rx?: number; + network_tx?: number; +} diff --git a/backend/dist/models/agent.js b/backend/dist/models/agent.js new file mode 100644 index 0000000..265bba9 --- /dev/null +++ b/backend/dist/models/agent.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=agent.js.map \ No newline at end of file diff --git a/backend/dist/models/agent.js.map b/backend/dist/models/agent.js.map new file mode 100644 index 0000000..3d0fb87 --- /dev/null +++ b/backend/dist/models/agent.js.map @@ -0,0 +1 @@ +{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/models/agent.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/backend/dist/models/db.d.ts b/backend/dist/models/db.d.ts new file mode 100644 index 0000000..60a758e --- /dev/null +++ b/backend/dist/models/db.d.ts @@ -0,0 +1,23 @@ +export interface D1Database { + prepare(query: string): D1PreparedStatement; + dump(): Promise; + batch(statements: D1PreparedStatement[]): Promise[]>; + exec(query: string): Promise>; +} +export interface D1PreparedStatement { + bind(...values: any[]): D1PreparedStatement; + first(colName?: string): Promise; + run(): Promise>; + all(): Promise>; + raw(): Promise; +} +export interface D1Result { + results?: T[]; + success: boolean; + error?: string; + meta?: object; +} +export type Bindings = { + DB: D1Database; + JWT_SECRET: string; +}; diff --git a/backend/dist/models/db.js b/backend/dist/models/db.js new file mode 100644 index 0000000..ca71552 --- /dev/null +++ b/backend/dist/models/db.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=db.js.map \ No newline at end of file diff --git a/backend/dist/models/db.js.map b/backend/dist/models/db.js.map new file mode 100644 index 0000000..0f0e2e1 --- /dev/null +++ b/backend/dist/models/db.js.map @@ -0,0 +1 @@ +{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/models/db.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/backend/dist/models/monitor.d.ts b/backend/dist/models/monitor.d.ts new file mode 100644 index 0000000..2a42ae1 --- /dev/null +++ b/backend/dist/models/monitor.d.ts @@ -0,0 +1,34 @@ +export interface Monitor { + id: number; + name: string; + url: string; + method: string; + interval: number; + timeout: number; + expected_status: number; + headers: string; + body: string; + created_by: number; + active: boolean; + status: string; + uptime: number; + response_time: number; + last_checked: string; + created_at: string; + updated_at: string; +} +export interface MonitorStatusHistory { + id: number; + monitor_id: number; + status: string; + timestamp: string; +} +export interface MonitorCheck { + id: number; + monitor_id: number; + status: string; + response_time: number; + status_code: number; + error: string | null; + checked_at: string; +} diff --git a/backend/dist/models/monitor.js b/backend/dist/models/monitor.js new file mode 100644 index 0000000..3dcd153 --- /dev/null +++ b/backend/dist/models/monitor.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=monitor.js.map \ No newline at end of file diff --git a/backend/dist/models/monitor.js.map b/backend/dist/models/monitor.js.map new file mode 100644 index 0000000..0dd5fff --- /dev/null +++ b/backend/dist/models/monitor.js.map @@ -0,0 +1 @@ +{"version":3,"file":"monitor.js","sourceRoot":"","sources":["../../src/models/monitor.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/backend/dist/monitor-checker.d.ts b/backend/dist/monitor-checker.d.ts new file mode 100644 index 0000000..69977fa --- /dev/null +++ b/backend/dist/monitor-checker.d.ts @@ -0,0 +1,6 @@ +import { Bindings } from './models/db'; +declare const _default: { + scheduled(event: any, env: any, ctx: any): Promise; + fetch: (request: Request, Env?: {} | Bindings | undefined, executionCtx?: import("hono").ExecutionContext) => Response | Promise; +}; +export default _default; diff --git a/backend/dist/monitor-checker.js b/backend/dist/monitor-checker.js new file mode 100644 index 0000000..5782850 --- /dev/null +++ b/backend/dist/monitor-checker.js @@ -0,0 +1,164 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const monitorChecker = new hono_1.Hono(); +// 生成随机令牌 +async function generateToken() { + const buffer = new Uint8Array(32); + crypto.getRandomValues(buffer); + return Array.from(buffer) + .map(b => b.toString(16).padStart(2, '0')) + .join(''); +} +// 监控检查的主要函数 +async function checkMonitors(c) { + try { + console.log('开始执行监控检查...'); + // 获取当前时间 + const now = new Date(); + // 查询需要检查的监控 + // 条件:active=true 且 (last_checked 为 null 或 当前时间 - last_checked > interval) + const monitors = await c.env.DB.prepare(` + SELECT * FROM monitors + WHERE active = true + AND (last_checked IS NULL OR datetime('now') > datetime(last_checked, '+' || interval || ' seconds')) + `).all(); + console.log(`找到 ${monitors?.results?.length || 0} 个需要检查的监控`); + if (!monitors.results || monitors.results.length === 0) { + return { success: true, message: '没有需要检查的监控', checked: 0 }; + } + // 检查每个监控 + const results = await Promise.all(monitors.results.map(async (monitor) => { + return await checkSingleMonitor(c, monitor); + })); + return { + success: true, + message: '监控检查完成', + checked: results.length, + results: results + }; + } + catch (error) { + console.error('监控检查出错:', error); + return { success: false, message: '监控检查出错', error: String(error) }; + } +} +// 检查单个监控的函数 +async function checkSingleMonitor(c, monitor) { + console.log(`检查监控: ${monitor.name} (ID: ${monitor.id})`); + try { + const startTime = Date.now(); + let response; + let error = null; + // 解析 headers + let headers = {}; + try { + headers = JSON.parse(monitor.headers || '{}'); + } + catch (e) { + console.error('解析请求头错误:', e); + } + // 执行请求 + try { + const controller = new AbortController(); + // 设置超时 + const timeoutId = setTimeout(() => controller.abort(), monitor.timeout * 1000); + response = await fetch(monitor.url, { + method: monitor.method, + headers: headers, + body: monitor.method !== 'GET' && monitor.method !== 'HEAD' ? monitor.body : undefined, + signal: controller.signal + }); + clearTimeout(timeoutId); + } + catch (e) { + error = e instanceof Error ? e.message : String(e); + } + const responseTime = Date.now() - startTime; + const now = new Date().toISOString(); + let status = 'down'; + // 判断状态 + if (response && response.status === monitor.expected_status) { + status = 'up'; + } + // 计算新的正常运行时间 + let newUptime = monitor.uptime; + if (monitor.last_checked) { + // 如果之前的状态是 up,但现在是 down,或者之前是 down,现在是 up,则需要调整 uptime + if ((monitor.status === 'up' && status === 'down') || (monitor.status === 'down' && status === 'up')) { + // 计算自上次检查以来的时间(小时) + const lastChecked = new Date(monitor.last_checked); + const nowDate = new Date(now); + const hoursSinceLastCheck = (nowDate.getTime() - lastChecked.getTime()) / (1000 * 60 * 60); + // 如果现在是 up,增加正常运行时间比率 + if (status === 'up') { + // 计算新的正常运行时间百分比 + const totalHours = monitor.uptime * hoursSinceLastCheck / 100 + (status === 'up' ? hoursSinceLastCheck : 0); + newUptime = (totalHours / (hoursSinceLastCheck * 2)) * 100; + } + else { + // 如果现在是 down,减少正常运行时间比率 + const downHours = hoursSinceLastCheck; + const totalHours = monitor.uptime * hoursSinceLastCheck / 100 - downHours; + newUptime = (totalHours / hoursSinceLastCheck) * 100; + } + // 确保 uptime 在 0-100 范围内 + newUptime = Math.max(0, Math.min(100, newUptime)); + } + } + // 更新监控状态 + await c.env.DB.prepare(` + UPDATE monitors + SET status = ?, response_time = ?, last_checked = ?, uptime = ? + WHERE id = ? + `).bind(status, responseTime, now, newUptime, monitor.id).run(); + // 添加检查记录 + await c.env.DB.prepare(` + INSERT INTO monitor_checks + (monitor_id, status, response_time, status_code, error, checked_at) + VALUES (?, ?, ?, ?, ?, ?) + `).bind(monitor.id, status, responseTime, response ? response.status : null, error, now).run(); + // 添加状态历史记录(仅当状态变化时) + if (monitor.status !== status) { + await c.env.DB.prepare(` + INSERT INTO monitor_status_history + (monitor_id, status, timestamp) + VALUES (?, ?, ?) + `).bind(monitor.id, status, now).run(); + } + return { + monitor_id: monitor.id, + name: monitor.name, + status, + previous_status: monitor.status, + response_time: responseTime, + status_code: response ? response.status : null, + error, + checked_at: now + }; + } + catch (error) { + console.error(`检查监控 ${monitor.id} 出错:`, error); + return { + monitor_id: monitor.id, + name: monitor.name, + status: 'error', + error: String(error), + checked_at: new Date().toISOString() + }; + } +} +// 定义触发器路由 - 通过HTTP请求触发监控检查 +monitorChecker.get('/api/trigger-check', async (c) => { + const result = await checkMonitors(c); + return c.json(result); +}); +// 在 Cloudflare Workers 中设置定时触发器 +exports.default = { + async scheduled(event, env, ctx) { + const c = { env }; + await checkMonitors(c); + }, + fetch: monitorChecker.fetch +}; +//# sourceMappingURL=monitor-checker.js.map \ No newline at end of file diff --git a/backend/dist/monitor-checker.js.map b/backend/dist/monitor-checker.js.map new file mode 100644 index 0000000..f9d8ec1 --- /dev/null +++ b/backend/dist/monitor-checker.js.map @@ -0,0 +1 @@ +{"version":3,"file":"monitor-checker.js","sourceRoot":"","sources":["../src/monitor-checker.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/routes/agents.d.ts b/backend/dist/routes/agents.d.ts new file mode 100644 index 0000000..4a14991 --- /dev/null +++ b/backend/dist/routes/agents.d.ts @@ -0,0 +1,11 @@ +import { Hono } from 'hono'; +import { Bindings } from '../models/db'; +import { Agent } from '../models/agent'; +declare const agents: Hono<{ + Bindings: Bindings; + Variables: { + agent: Agent; + jwtPayload: any; + }; +}, import("hono/types").BlankSchema, "/">; +export default agents; diff --git a/backend/dist/routes/agents.js b/backend/dist/routes/agents.js new file mode 100644 index 0000000..666b7c7 --- /dev/null +++ b/backend/dist/routes/agents.js @@ -0,0 +1,384 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const jwt_1 = require("hono/jwt"); +const jwt_2 = require("../utils/jwt"); +const agents = new hono_1.Hono(); +// 中间件:JWT 认证 +agents.use('*', async (c, next) => { + // 跳过特定路由的认证 (客户端上报指标接口和注册接口) + if ((c.req.path.endsWith('/status') || c.req.path.endsWith('/register')) && c.req.method === 'POST') { + return next(); + } + const jwtMiddleware = (0, jwt_1.jwt)({ + secret: (0, jwt_2.getJwtSecret)(c) + }); + return jwtMiddleware(c, next); +}); +// 获取所有客户端 +agents.get('/', async (c) => { + try { + const payload = c.get('jwtPayload'); + // 根据用户角色过滤客户端 + let result; + if (payload.role === 'admin') { + result = await c.env.DB.prepare('SELECT * FROM agents ORDER BY created_at DESC').all(); + } + else { + result = await c.env.DB.prepare('SELECT * FROM agents WHERE created_by = ? ORDER BY created_at DESC').bind(payload.id).all(); + } + return c.json({ success: true, agents: result.results || [] }); + } + catch (error) { + console.error('获取客户端列表错误:', error); + return c.json({ + success: false, + message: '获取客户端列表失败', + error: error instanceof Error ? error.message : String(error) + }, 500); + } +}); +// 创建新客户端 +agents.post('/', async (c) => { + try { + const { name } = await c.req.json(); + const payload = c.get('jwtPayload'); + const token = await (0, jwt_2.generateToken)(); + const now = new Date().toISOString(); + // 插入新客户端 + const result = await c.env.DB.prepare(`INSERT INTO agents + (name, token, created_by, status, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?)`).bind(name, token, payload.id, 'inactive', now, now).run(); + if (!result.success) { + throw new Error('创建客户端失败'); + } + // 获取新创建的客户端 + const newAgent = await c.env.DB.prepare('SELECT * FROM agents WHERE rowid = last_insert_rowid()').first(); + return c.json({ + success: true, + message: '客户端创建成功', + agent: newAgent // 创建时返回完整信息,包括令牌 + }, 201); + } + catch (error) { + console.error('创建客户端错误:', error); + return c.json({ success: false, message: '创建客户端失败' }, 500); + } +}); +// 获取单个客户端 +agents.get('/:id', async (c) => { + try { + const agentId = Number(c.req.param('id')); + const payload = c.get('jwtPayload'); + // 获取客户端信息 + const agent = await c.env.DB.prepare(`SELECT * FROM agents WHERE id = ?`).bind(agentId).first(); + if (!agent) { + return c.json({ success: false, message: '客户端不存在' }, 404); + } + // 检查权限 + if (payload.role !== 'admin' && agent.created_by !== payload.id) { + return c.json({ success: false, message: '无权访问此客户端' }, 403); + } + // 不返回令牌,但保留其他所有字段 + const { token, ...rest } = agent; + return c.json({ + success: true, + agent: { + ...rest, + cpu_usage: rest.cpu_usage || 0, + memory_total: rest.memory_total || 0, + memory_used: rest.memory_used || 0, + disk_total: rest.disk_total || 0, + disk_used: rest.disk_used || 0, + network_rx: rest.network_rx || 0, + network_tx: rest.network_tx || 0 + } + }); + } + catch (error) { + console.error('获取客户端详情错误:', error); + return c.json({ success: false, message: '获取客户端详情失败' }, 500); + } +}); +// 更新客户端信息 +agents.put('/:id', async (c) => { + try { + const agentId = Number(c.req.param('id')); + const payload = c.get('jwtPayload'); + // 获取当前客户端数据 + const agent = await c.env.DB.prepare('SELECT * FROM agents WHERE id = ?').bind(agentId).first(); + if (!agent) { + return c.json({ success: false, message: '客户端不存在' }, 404); + } + // 检查权限 + if (payload.role !== 'admin' && agent.created_by !== payload.id) { + return c.json({ success: false, message: '无权修改此客户端' }, 403); + } + // 获取更新数据 + const updateData = await c.req.json(); + const { name, hostname, ip_address, os, version, status } = updateData; + // 准备更新的字段和值 + const fieldsToUpdate = []; + const values = []; + if (name !== undefined) { + fieldsToUpdate.push('name = ?'); + values.push(name); + } + if (hostname !== undefined) { + fieldsToUpdate.push('hostname = ?'); + values.push(hostname); + } + if (ip_address !== undefined) { + fieldsToUpdate.push('ip_address = ?'); + values.push(ip_address); + } + if (os !== undefined) { + fieldsToUpdate.push('os = ?'); + values.push(os); + } + if (version !== undefined) { + fieldsToUpdate.push('version = ?'); + values.push(version); + } + if (status !== undefined) { + fieldsToUpdate.push('status = ?'); + values.push(status); + } + fieldsToUpdate.push('updated_at = ?'); + values.push(new Date().toISOString()); + // 添加客户端ID作为最后一个参数 + values.push(agentId); + // 如果没有要更新的字段,直接返回 + if (fieldsToUpdate.length === 1) { // 只有updated_at + return c.json({ + success: true, + message: '没有更新任何字段', + agent + }); + } + // 执行更新 + const updateSql = ` + UPDATE agents + SET ${fieldsToUpdate.join(', ')} + WHERE id = ? + `; + const result = await c.env.DB.prepare(updateSql).bind(...values).run(); + if (!result.success) { + throw new Error('更新客户端失败'); + } + // 获取更新后的客户端数据 + const updatedAgent = await c.env.DB.prepare('SELECT * FROM agents WHERE id = ?').bind(agentId).first(); + return c.json({ + success: true, + message: '客户端信息已更新', + agent: updatedAgent + }); + } + catch (error) { + console.error('更新客户端错误:', error); + return c.json({ success: false, message: '更新客户端失败' }, 500); + } +}); +// 更新客户端状态 +agents.post('/:id/status', async (c) => { + try { + const agentId = Number(c.req.param('id')); + const { cpu_usage, memory_total, memory_used, disk_total, disk_used, network_rx, network_tx, hostname, ip_address, os, version } = await c.req.json(); + // 更新客户端状态和资源指标 + const result = await c.env.DB.prepare(`UPDATE agents SET + status = 'active', + cpu_usage = ?, + memory_total = ?, + memory_used = ?, + disk_total = ?, + disk_used = ?, + network_rx = ?, + network_tx = ?, + hostname = ?, + ip_address = ?, + os = ?, + version = ?, + updated_at = ? + WHERE id = ?`).bind(cpu_usage, memory_total, memory_used, disk_total, disk_used, network_rx, network_tx, hostname, ip_address, os, version, new Date().toISOString(), agentId).run(); + if (!result.success) { + throw new Error('更新客户端状态失败'); + } + return c.json({ + success: true, + message: '客户端状态已更新' + }); + } + catch (error) { + console.error('更新客户端状态错误:', error); + return c.json({ success: false, message: '更新客户端状态失败' }, 500); + } +}); +// 删除客户端 +agents.delete('/:id', async (c) => { + try { + const agentId = Number(c.req.param('id')); + const payload = c.get('jwtPayload'); + // 获取客户端信息 + const agent = await c.env.DB.prepare('SELECT * FROM agents WHERE id = ?').bind(agentId).first(); + if (!agent) { + return c.json({ success: false, message: '客户端不存在' }, 404); + } + // 检查权限 + if (payload.role !== 'admin' && agent.created_by !== payload.id) { + return c.json({ success: false, message: '无权删除此客户端' }, 403); + } + // 执行删除客户端 + const result = await c.env.DB.prepare('DELETE FROM agents WHERE id = ?').bind(agent.id).run(); + if (!result.success) { + throw new Error('删除客户端失败'); + } + return c.json({ + success: true, + message: '客户端已删除' + }); + } + catch (error) { + console.error('删除客户端错误:', error); + return c.json({ success: false, message: '删除客户端失败' }, 500); + } +}); +// 重新生成客户端令牌 +agents.post('/:id/token', async (c) => { + try { + const agentId = Number(c.req.param('id')); + const payload = c.get('jwtPayload'); + // 获取客户端信息 + const agent = await c.env.DB.prepare('SELECT * FROM agents WHERE id = ?').bind(agentId).first(); + if (!agent) { + return c.json({ success: false, message: '客户端不存在' }, 404); + } + // 检查权限 + if (payload.role !== 'admin' && agent.created_by !== payload.id) { + return c.json({ success: false, message: '无权为此客户端重新生成令牌' }, 403); + } + // 生成新令牌 + const newToken = await (0, jwt_2.generateToken)(); + // 更新客户端令牌 + const result = await c.env.DB.prepare('UPDATE agents SET token = ?, updated_at = ? WHERE id = ?').bind(newToken, new Date().toISOString(), agent.id).run(); + if (!result.success) { + throw new Error('更新客户端令牌失败'); + } + return c.json({ + success: true, + message: '客户端令牌已重新生成', + token: newToken + }); + } + catch (error) { + console.error('重新生成客户端令牌错误:', error); + return c.json({ success: false, message: '重新生成客户端令牌失败' }, 500); + } +}); +// 生成临时注册Token +agents.post('/token/generate', async (c) => { + try { + const payload = c.get('jwtPayload'); + // 生成新令牌 + const newToken = await (0, jwt_2.generateToken)(); + // 可以选择将此token存储在临时表中,或者使用其他方式验证(例如,设置过期时间) + // 这里为简化操作,只返回令牌 + return c.json({ + success: true, + message: '已生成客户端注册令牌', + token: newToken + }); + } + catch (error) { + console.error('生成注册令牌错误:', error); + return c.json({ success: false, message: '生成注册令牌失败' }, 500); + } +}); +// 客户端自注册接口 +agents.post('/register', async (c) => { + try { + const { token, name, hostname, ip_address, os, version } = await c.req.json(); + if (!token) { + return c.json({ success: false, message: '缺少注册令牌' }, 400); + } + // 查找管理员用户作为客户端创建者 + const adminUser = await c.env.DB.prepare('SELECT id FROM users WHERE role = ?').bind('admin').first(); + if (!adminUser) { + return c.json({ success: false, message: '无法找到管理员用户' }, 500); + } + const now = new Date().toISOString(); + // 查找是否已存在使用相同token的客户端 + const existingAgent = await c.env.DB.prepare('SELECT id FROM agents WHERE token = ?').bind(token).first(); + if (existingAgent) { + return c.json({ + success: false, + message: '客户端已注册', + agent: existingAgent + }); + } + // 如果客户端不存在,则创建新客户端 + const result = await c.env.DB.prepare(`INSERT INTO agents + (name, token, created_by, status, created_at, updated_at, hostname, ip_address, os, version) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(name || 'New Agent', token, adminUser.id, 'active', now, now, hostname || null, ip_address || null, os || null, version || null).run(); + if (!result.success) { + throw new Error('创建客户端失败'); + } + // 获取新创建的客户端 + const newAgent = await c.env.DB.prepare('SELECT id FROM agents WHERE token = ?').bind(token).first(); + return c.json({ + success: true, + message: '客户端注册成功', + agent: newAgent + }, 201); + } + catch (error) { + console.error('客户端注册错误:', error); + return c.json({ + success: false, + message: '客户端注册失败', + error: error instanceof Error ? error.message : String(error) + }, 500); + } +}); +// 通过令牌更新客户端状态 +agents.post('/status', async (c) => { + try { + const { token, cpu_usage, memory_total, memory_used, disk_total, disk_used, network_rx, network_tx, hostname, ip_address, os, version } = await c.req.json(); + if (!token) { + return c.json({ success: false, message: '缺少API令牌' }, 400); + } + // 通过token查找客户端 + const agent = await c.env.DB.prepare('SELECT id FROM agents WHERE token = ?').bind(token).first(); + if (!agent) { + return c.json({ success: false, message: '客户端不存在或令牌无效' }, 404); + } + // 更新客户端状态和资源指标 + const result = await c.env.DB.prepare(`UPDATE agents SET + status = 'active', + cpu_usage = ?, + memory_total = ?, + memory_used = ?, + disk_total = ?, + disk_used = ?, + network_rx = ?, + network_tx = ?, + hostname = ?, + ip_address = ?, + os = ?, + version = ?, + updated_at = ? + WHERE id = ?`).bind(cpu_usage, memory_total, memory_used, disk_total, disk_used, network_rx, network_tx, hostname, ip_address, os, version, new Date().toISOString(), agent.id).run(); + if (!result.success) { + throw new Error('更新客户端状态失败'); + } + return c.json({ + success: true, + message: '客户端状态已更新' + }); + } + catch (error) { + console.error('更新客户端状态错误:', error); + return c.json({ success: false, message: '更新客户端状态失败' }, 500); + } +}); +exports.default = agents; +//# sourceMappingURL=agents.js.map \ No newline at end of file diff --git a/backend/dist/routes/agents.js.map b/backend/dist/routes/agents.js.map new file mode 100644 index 0000000..c6c45a6 --- /dev/null +++ b/backend/dist/routes/agents.js.map @@ -0,0 +1 @@ +{"version":3,"file":"agents.js","sourceRoot":"","sources":["../../src/routes/agents.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/routes/auth.d.ts b/backend/dist/routes/auth.d.ts new file mode 100644 index 0000000..198b1e5 --- /dev/null +++ b/backend/dist/routes/auth.d.ts @@ -0,0 +1,28 @@ +import { Hono } from 'hono'; +type Bindings = { + DB: D1Database; + JWT_SECRET: string; +}; +interface D1Database { + prepare(query: string): D1PreparedStatement; + dump(): Promise; + batch(statements: D1PreparedStatement[]): Promise[]>; + exec(query: string): Promise>; +} +interface D1PreparedStatement { + bind(...values: any[]): D1PreparedStatement; + first(colName?: string): Promise; + run(): Promise>; + all(): Promise>; + raw(): Promise; +} +interface D1Result { + results?: T[]; + success: boolean; + error?: string; + meta?: object; +} +declare const auth: Hono<{ + Bindings: Bindings; +}, import("hono/types").BlankSchema, "/">; +export default auth; diff --git a/backend/dist/routes/auth.js b/backend/dist/routes/auth.js new file mode 100644 index 0000000..be3955b --- /dev/null +++ b/backend/dist/routes/auth.js @@ -0,0 +1,101 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const jwt_1 = require("hono/jwt"); +const bcryptjs_1 = __importDefault(require("bcryptjs")); +const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); +const jwt_2 = require("../utils/jwt"); +const auth = new hono_1.Hono(); +// 注册路由 +auth.post('/register', async (c) => { + try { + const { username, password, email } = await c.req.json(); + // 检查用户名是否已存在 + const existingUser = await c.env.DB.prepare('SELECT * FROM users WHERE username = ?').bind(username).first(); + if (existingUser) { + return c.json({ success: false, message: '用户名已存在' }, 400); + } + // 加密密码 + const salt = await bcryptjs_1.default.genSalt(10); + const hashedPassword = await bcryptjs_1.default.hash(password, salt); + // 创建新用户 + const result = await c.env.DB.prepare('INSERT INTO users (username, password, email, role, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)').bind(username, hashedPassword, email || null, 'user', new Date().toISOString(), new Date().toISOString()).run(); + if (!result.success) { + throw new Error('数据库插入失败'); + } + // 获取新创建的用户ID + const newUser = await c.env.DB.prepare('SELECT id, username, role FROM users WHERE username = ?').bind(username).first(); + return c.json({ + success: true, + message: '注册成功', + user: newUser + }, 201); + } + catch (error) { + console.error('注册错误:', error); + return c.json({ success: false, message: '注册失败' }, 500); + } +}); +// 登录路由 +auth.post('/login', async (c) => { + try { + const { username, password } = await c.req.json(); + // 查找用户 + const user = await c.env.DB.prepare('SELECT * FROM users WHERE username = ?').bind(username).first(); + if (!user) { + return c.json({ success: false, message: '用户名或密码错误' }, 401); + } + // 验证密码 + const isPasswordValid = await bcryptjs_1.default.compare(password, user.password); + if (!isPasswordValid) { + return c.json({ success: false, message: '用户名或密码错误' }, 401); + } + // 生成JWT令牌 + const payload = { + id: user.id, + username: user.username, + role: user.role + }; + const secret = (0, jwt_2.getJwtSecret)(c); + const token = jsonwebtoken_1.default.sign(payload, secret, { expiresIn: '24h' }); + return c.json({ + success: true, + message: '登录成功', + token, + user: { id: user.id, username: user.username, role: user.role } + }); + } + catch (error) { + console.error('登录错误:', error); + return c.json({ success: false, message: '登录失败' }, 500); + } +}); +// 获取当前用户信息 +auth.use('/me', async (c, next) => { + const jwtMiddleware = (0, jwt_1.jwt)({ + secret: (0, jwt_2.getJwtSecret)(c) + }); + return jwtMiddleware(c, next); +}); +auth.get('/me', async (c) => { + try { + const payload = c.get('jwtPayload'); + const user = await c.env.DB.prepare('SELECT id, username, email, role FROM users WHERE id = ?').bind(payload.id).first(); + if (!user) { + return c.json({ success: false, message: '用户不存在' }, 404); + } + return c.json({ + success: true, + user + }); + } + catch (error) { + console.error('获取用户信息错误:', error); + return c.json({ success: false, message: '获取用户信息失败' }, 500); + } +}); +exports.default = auth; +//# sourceMappingURL=auth.js.map \ No newline at end of file diff --git a/backend/dist/routes/auth.js.map b/backend/dist/routes/auth.js.map new file mode 100644 index 0000000..c4616d2 --- /dev/null +++ b/backend/dist/routes/auth.js.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/routes/monitors.d.ts b/backend/dist/routes/monitors.d.ts new file mode 100644 index 0000000..a5b2417 --- /dev/null +++ b/backend/dist/routes/monitors.d.ts @@ -0,0 +1,6 @@ +import { Hono } from 'hono'; +import { Bindings } from '../models/db'; +declare const monitors: Hono<{ + Bindings: Bindings; +}, import("hono/types").BlankSchema, "/">; +export default monitors; diff --git a/backend/dist/routes/monitors.js b/backend/dist/routes/monitors.js new file mode 100644 index 0000000..b729bbf --- /dev/null +++ b/backend/dist/routes/monitors.js @@ -0,0 +1,375 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const jwt_1 = require("hono/jwt"); +const jwt_2 = require("../utils/jwt"); +const monitors = new hono_1.Hono(); +// 中间件:JWT 认证 +monitors.use('*', async (c, next) => { + const jwtMiddleware = (0, jwt_1.jwt)({ + secret: (0, jwt_2.getJwtSecret)(c) + }); + return jwtMiddleware(c, next); +}); +// 获取所有监控 +monitors.get('/', async (c) => { + try { + const payload = c.get('jwtPayload'); + // 根据用户角色过滤监控 + let result; + if (payload.role === 'admin') { + result = await c.env.DB.prepare('SELECT * FROM monitors ORDER BY created_at DESC').all(); + } + else { + result = await c.env.DB.prepare('SELECT * FROM monitors WHERE created_by = ? ORDER BY created_at DESC').bind(payload.id).all(); + } + // 获取所有监控的历史状态数据 + if (result.results && result.results.length > 0) { + const monitorsWithHistory = await Promise.all(result.results.map(async (monitor) => { + const historyResult = await c.env.DB.prepare(`SELECT * FROM monitor_status_history + WHERE monitor_id = ? + ORDER BY timestamp ASC`).bind(monitor.id).all(); + return { + ...monitor, + history: historyResult.results || [] + }; + })); + return c.json({ success: true, monitors: monitorsWithHistory }); + } + return c.json({ success: true, monitors: result.results }); + } + catch (error) { + console.error('获取监控列表错误:', error); + return c.json({ success: false, message: '获取监控列表失败' }, 500); + } +}); +// 获取单个监控 +monitors.get('/:id', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + const monitor = await c.env.DB.prepare('SELECT * FROM monitors WHERE id = ?').bind(id).first(); + if (!monitor) { + return c.json({ success: false, message: '监控不存在' }, 404); + } + // 检查权限 + if (payload.role !== 'admin' && monitor.created_by !== payload.id) { + return c.json({ success: false, message: '无权访问此监控' }, 403); + } + // 获取历史状态数据 + const historyResult = await c.env.DB.prepare(`SELECT * FROM monitor_status_history + WHERE monitor_id = ? + ORDER BY timestamp ASC`).bind(id).all(); + // 获取最近的检查历史记录 + const checksResult = await c.env.DB.prepare(`SELECT * FROM monitor_checks + WHERE monitor_id = ? + ORDER BY checked_at DESC + LIMIT 5`).bind(id).all(); + return c.json({ + success: true, + monitor: { + ...monitor, + history: historyResult.results || [], + checks: checksResult.results || [] + } + }); + } + catch (error) { + console.error('获取监控详情错误:', error); + return c.json({ success: false, message: '获取监控详情失败' }, 500); + } +}); +// 创建监控 +monitors.post('/', async (c) => { + try { + const payload = c.get('jwtPayload'); + const data = await c.req.json(); + // 验证必填字段 + if (!data.name || !data.url || !data.method) { + return c.json({ success: false, message: '缺少必填字段' }, 400); + } + const now = new Date().toISOString(); + // 将 headers 对象转换为 JSON 字符串 + const headers = data.headers ? JSON.stringify(data.headers) : '{}'; + // 插入新监控 + const result = await c.env.DB.prepare(`INSERT INTO monitors + (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(data.name, data.url, data.method, data.interval || 60, data.timeout || 30, data.expectedStatus || 200, headers, data.body || '', payload.id, true, 'pending', 100.0, 0, null, now, now).run(); + if (!result.success) { + throw new Error('创建监控失败'); + } + // 获取新创建的监控 + const newMonitor = await c.env.DB.prepare('SELECT * FROM monitors WHERE rowid = last_insert_rowid()').first(); + return c.json({ success: true, monitor: newMonitor }, 201); + } + catch (error) { + console.error('创建监控错误:', error); + return c.json({ success: false, message: '创建监控失败' }, 500); + } +}); +// 更新监控 +monitors.put('/:id', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + const data = await c.req.json(); + // 检查监控是否存在 + const monitor = await c.env.DB.prepare('SELECT * FROM monitors WHERE id = ?').bind(id).first(); + if (!monitor) { + return c.json({ success: false, message: '监控不存在' }, 404); + } + // 检查权限 + if (payload.role !== 'admin' && monitor.created_by !== payload.id) { + return c.json({ success: false, message: '无权修改此监控' }, 403); + } + // 准备更新数据 + const updates = []; + const values = []; + if (data.name !== undefined) { + updates.push('name = ?'); + values.push(data.name); + } + if (data.url !== undefined) { + updates.push('url = ?'); + values.push(data.url); + } + if (data.method !== undefined) { + updates.push('method = ?'); + values.push(data.method); + } + if (data.interval !== undefined) { + updates.push('interval = ?'); + values.push(data.interval); + } + if (data.timeout !== undefined) { + updates.push('timeout = ?'); + values.push(data.timeout); + } + if (data.expectedStatus !== undefined) { + updates.push('expected_status = ?'); + values.push(data.expectedStatus); + } + if (data.headers !== undefined) { + updates.push('headers = ?'); + values.push(JSON.stringify(data.headers)); + } + if (data.body !== undefined) { + updates.push('body = ?'); + values.push(data.body); + } + if (data.active !== undefined) { + updates.push('active = ?'); + values.push(data.active); + } + if (data.status !== undefined) { + updates.push('status = ?'); + values.push(data.status); + } + if (data.uptime !== undefined) { + updates.push('uptime = ?'); + values.push(data.uptime); + } + if (data.responseTime !== undefined) { + updates.push('response_time = ?'); + values.push(data.responseTime); + } + if (data.lastChecked !== undefined) { + updates.push('last_checked = ?'); + values.push(data.lastChecked); + } + updates.push('updated_at = ?'); + values.push(new Date().toISOString()); + // 添加 ID 作为 WHERE 条件 + values.push(id); + // 执行更新 + const result = await c.env.DB.prepare(`UPDATE monitors SET ${updates.join(', ')} WHERE id = ?`).bind(...values).run(); + if (!result.success) { + throw new Error('更新监控失败'); + } + // 获取更新后的监控 + const updatedMonitor = await c.env.DB.prepare('SELECT * FROM monitors WHERE id = ?').bind(id).first(); + return c.json({ success: true, monitor: updatedMonitor }); + } + catch (error) { + console.error('更新监控错误:', error); + return c.json({ success: false, message: '更新监控失败' }, 500); + } +}); +// 删除监控 +monitors.delete('/:id', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + // 检查监控是否存在 + const monitor = await c.env.DB.prepare('SELECT * FROM monitors WHERE id = ?').bind(id).first(); + if (!monitor) { + return c.json({ success: false, message: '监控不存在' }, 404); + } + // 检查权限 + if (payload.role !== 'admin' && monitor.created_by !== payload.id) { + return c.json({ success: false, message: '无权删除此监控' }, 403); + } + // 先删除关联的历史数据 + await c.env.DB.prepare('DELETE FROM monitor_status_history WHERE monitor_id = ?').bind(id).run(); + await c.env.DB.prepare('DELETE FROM monitor_checks WHERE monitor_id = ?').bind(id).run(); + // 执行删除 + const result = await c.env.DB.prepare('DELETE FROM monitors WHERE id = ?').bind(id).run(); + if (!result.success) { + throw new Error('删除监控失败'); + } + return c.json({ success: true, message: '监控已删除' }); + } + catch (error) { + console.error('删除监控错误:', error); + return c.json({ success: false, message: '删除监控失败' }, 500); + } +}); +// 获取监控状态历史 +monitors.get('/:id/history', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + // 检查监控是否存在 + const monitor = await c.env.DB.prepare('SELECT * FROM monitors WHERE id = ?').bind(id).first(); + if (!monitor) { + return c.json({ success: false, message: '监控不存在' }, 404); + } + // 检查权限 + if (payload.role !== 'admin' && monitor.created_by !== payload.id) { + return c.json({ success: false, message: '无权访问此监控历史' }, 403); + } + // 获取历史状态 + const historyResult = await c.env.DB.prepare(`SELECT * FROM monitor_status_history + WHERE monitor_id = ? + ORDER BY timestamp ASC`).bind(id).all(); + return c.json({ + success: true, + history: historyResult.results || [] + }); + } + catch (error) { + console.error('获取监控历史错误:', error); + return c.json({ success: false, message: '获取监控历史失败' }, 500); + } +}); +// 获取监控检查记录 +monitors.get('/:id/checks', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + const limit = parseInt(c.req.query('limit') || '10'); + // 检查监控是否存在 + const monitor = await c.env.DB.prepare('SELECT * FROM monitors WHERE id = ?').bind(id).first(); + if (!monitor) { + return c.json({ success: false, message: '监控不存在' }, 404); + } + // 检查权限 + if (payload.role !== 'admin' && monitor.created_by !== payload.id) { + return c.json({ success: false, message: '无权访问此监控检查记录' }, 403); + } + // 获取检查记录 + const checksResult = await c.env.DB.prepare(`SELECT * FROM monitor_checks + WHERE monitor_id = ? + ORDER BY checked_at DESC + LIMIT ?`).bind(id, limit).all(); + return c.json({ + success: true, + checks: checksResult.results || [] + }); + } + catch (error) { + console.error('获取监控检查记录错误:', error); + return c.json({ success: false, message: '获取监控检查记录失败' }, 500); + } +}); +// 手动检查单个监控 +monitors.post('/:id/check', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + // 检查监控是否存在 + const monitor = await c.env.DB.prepare('SELECT * FROM monitors WHERE id = ?').bind(id).first(); + if (!monitor) { + return c.json({ success: false, message: '监控不存在' }, 404); + } + // 检查权限 + if (payload.role !== 'admin' && monitor.created_by !== payload.id) { + return c.json({ success: false, message: '无权访问此监控' }, 403); + } + // 触发监控检查 + const startTime = Date.now(); + let response; + let error = null; + // 解析 headers + let headers = {}; + try { + headers = JSON.parse(monitor.headers || '{}'); + } + catch (e) { + console.error('解析请求头错误:', e); + } + // 执行请求 + try { + const controller = new AbortController(); + // 设置超时 + const timeoutId = setTimeout(() => controller.abort(), monitor.timeout * 1000); + response = await fetch(monitor.url, { + method: monitor.method, + headers: headers, + body: monitor.method !== 'GET' && monitor.method !== 'HEAD' ? monitor.body : undefined, + signal: controller.signal + }); + clearTimeout(timeoutId); + } + catch (e) { + error = e instanceof Error ? e.message : String(e); + } + const responseTime = Date.now() - startTime; + const now = new Date().toISOString(); + let status = 'down'; + // 判断状态 + if (response && response.status === monitor.expected_status) { + status = 'up'; + } + // 更新监控状态 + await c.env.DB.prepare(` + UPDATE monitors + SET status = ?, response_time = ?, last_checked = ? + WHERE id = ? + `).bind(status, responseTime, now, monitor.id).run(); + // 添加检查记录 + await c.env.DB.prepare(` + INSERT INTO monitor_checks + (monitor_id, status, response_time, status_code, error, checked_at) + VALUES (?, ?, ?, ?, ?, ?) + `).bind(monitor.id, status, responseTime, response ? response.status : null, error, now).run(); + // 添加状态历史记录(仅当状态变化时) + if (monitor.status !== status) { + await c.env.DB.prepare(` + INSERT INTO monitor_status_history + (monitor_id, status, timestamp) + VALUES (?, ?, ?) + `).bind(monitor.id, status, now).run(); + } + return c.json({ + success: true, + message: '监控检查完成', + result: { + monitor_id: monitor.id, + name: monitor.name, + status, + previous_status: monitor.status, + response_time: responseTime, + status_code: response ? response.status : null, + error, + checked_at: now + } + }); + } + catch (error) { + console.error('手动检查监控错误:', error); + return c.json({ success: false, message: '手动检查监控失败' }, 500); + } +}); +exports.default = monitors; +//# sourceMappingURL=monitors.js.map \ No newline at end of file diff --git a/backend/dist/routes/monitors.js.map b/backend/dist/routes/monitors.js.map new file mode 100644 index 0000000..795043a --- /dev/null +++ b/backend/dist/routes/monitors.js.map @@ -0,0 +1 @@ +{"version":3,"file":"monitors.js","sourceRoot":"","sources":["../../src/routes/monitors.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/routes/status.d.ts b/backend/dist/routes/status.d.ts new file mode 100644 index 0000000..e7df377 --- /dev/null +++ b/backend/dist/routes/status.d.ts @@ -0,0 +1,6 @@ +import { Hono } from 'hono'; +import { Bindings } from '../models/db'; +declare const app: Hono<{ + Bindings: Bindings; +}, import("hono/types").BlankSchema, "/">; +export default app; diff --git a/backend/dist/routes/status.js b/backend/dist/routes/status.js new file mode 100644 index 0000000..31ab1ac --- /dev/null +++ b/backend/dist/routes/status.js @@ -0,0 +1,409 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const jwt_1 = require("hono/jwt"); +const jwt_2 = require("../utils/jwt"); +// 创建 Hono 路由 +const app = new hono_1.Hono(); +// 保护管理员路由 +const adminRoutes = new hono_1.Hono() + .use('*', async (c, next) => { + try { + const jwtMiddleware = (0, jwt_1.jwt)({ + secret: (0, jwt_2.getJwtSecret)(c) + }); + await jwtMiddleware(c, next); + const payload = c.get('jwtPayload'); + if (!payload || !payload.id) { + return c.json({ error: '未授权' }, 401); + } + // 这里不再调用next(),防止重复调用 + } + catch (error) { + console.error('JWT认证错误:', error); + return c.json({ error: '认证失败' }, 401); + } +}); +// 获取状态页配置 +adminRoutes.get('/config', async (c) => { + const payload = c.get('jwtPayload'); + const userId = payload.id; + try { + // 获取用户的状态页配置 + const configResult = await c.env.DB.prepare('SELECT * FROM status_page_config WHERE user_id = ?').bind(userId).all(); + let config = null; + if (configResult.results && configResult.results.length > 0) { + config = configResult.results[0]; + } + // 获取配置的监控项 + const monitorsResult = await c.env.DB.prepare('SELECT m.id, m.name, CASE WHEN spm.monitor_id IS NOT NULL THEN 1 ELSE 0 END as selected ' + + 'FROM monitors m ' + + 'LEFT JOIN status_page_monitors spm ON m.id = spm.monitor_id AND spm.config_id = ? ' + + 'WHERE m.created_by = ?').bind(config?.id || 0, userId).all(); + // 获取配置的客户端 + const agentsResult = await c.env.DB.prepare('SELECT a.id, a.name, CASE WHEN spa.agent_id IS NOT NULL THEN 1 ELSE 0 END as selected ' + + 'FROM agents a ' + + 'LEFT JOIN status_page_agents spa ON a.id = spa.agent_id AND spa.config_id = ? ' + + 'WHERE a.created_by = ?').bind(config?.id || 0, userId).all(); + // 构建响应 + const response = { + title: config?.title || '系统状态', + description: config?.description || '当前系统运行状态', + logoUrl: config?.logo_url || '', + customCss: config?.custom_css || '', + monitors: monitorsResult.results?.map(m => ({ + id: m.id, + name: m.name, + selected: m.selected === 1 + })) || [], + agents: agentsResult.results?.map(a => ({ + id: a.id, + name: a.name, + selected: a.selected === 1 + })) || [] + }; + return c.json(response); + } + catch (error) { + console.error('获取状态页配置失败:', error); + return c.json({ error: '获取状态页配置失败' }, 500); + } +}); +// 保存状态页配置 +adminRoutes.post('/config', async (c) => { + const payload = c.get('jwtPayload'); + const userId = payload.id; + const data = await c.req.json(); + console.log('接收到的配置数据:', JSON.stringify(data)); + if (!data) { + console.log('无效的请求数据'); + return c.json({ error: '无效的请求数据' }, 400); + } + try { + // 检查是否已存在配置 + const existingConfig = await c.env.DB.prepare('SELECT id FROM status_page_config WHERE user_id = ?').bind(userId).first(); + console.log('现有配置查询结果:', existingConfig); + let configId; + if (existingConfig && existingConfig.id) { + // 更新现有配置 + console.log('更新现有配置ID:', existingConfig.id); + await c.env.DB.prepare('UPDATE status_page_config SET title = ?, description = ?, logo_url = ?, custom_css = ? WHERE id = ?').bind(data.title, data.description, data.logoUrl, data.customCss, existingConfig.id).run(); + configId = existingConfig.id; + } + else { + // 创建新配置 + console.log('创建新配置'); + const insertResult = await c.env.DB.prepare('INSERT INTO status_page_config (user_id, title, description, logo_url, custom_css) VALUES (?, ?, ?, ?, ?)').bind(userId, data.title, data.description, data.logoUrl, data.customCss).run(); + console.log('插入配置结果:', insertResult); + if (!insertResult.success) { + throw new Error('创建状态页配置失败'); + } + // 获取新插入的ID + const lastInsertId = await c.env.DB.prepare('SELECT last_insert_rowid() as id').first(); + console.log('获取的最后插入ID:', lastInsertId); + if (!lastInsertId || typeof lastInsertId.id !== 'number') { + throw new Error('获取配置ID失败'); + } + configId = lastInsertId.id; + } + // 清除现有的监控项关联 + console.log('清除配置ID的现有监控关联:', configId); + const deleteMonitorsResult = await c.env.DB.prepare('DELETE FROM status_page_monitors WHERE config_id = ?').bind(configId).run(); + console.log('删除现有监控关联结果:', deleteMonitorsResult); + // 清除现有的客户端关联 + console.log('清除配置ID的现有客户端关联:', configId); + const deleteAgentsResult = await c.env.DB.prepare('DELETE FROM status_page_agents WHERE config_id = ?').bind(configId).run(); + console.log('删除现有客户端关联结果:', deleteAgentsResult); + // 添加选定的监控项 + console.log('接收到的监控项IDs:', data.monitors); + if (Array.isArray(data.monitors) && data.monitors.length > 0) { + console.log(`添加 ${data.monitors.length} 个监控项`); + for (const monitorId of data.monitors) { + const insertResult = await c.env.DB.prepare('INSERT INTO status_page_monitors (config_id, monitor_id) VALUES (?, ?)').bind(configId, monitorId).run(); + console.log(`添加监控项 ${monitorId} 结果:`, insertResult); + } + } + else { + console.log('没有选中的监控项需要添加'); + } + // 添加选定的客户端 + console.log('接收到的客户端IDs:', data.agents); + if (Array.isArray(data.agents) && data.agents.length > 0) { + console.log(`添加 ${data.agents.length} 个客户端`); + for (const agentId of data.agents) { + const insertResult = await c.env.DB.prepare('INSERT INTO status_page_agents (config_id, agent_id) VALUES (?, ?)').bind(configId, agentId).run(); + console.log(`添加客户端 ${agentId} 结果:`, insertResult); + } + } + else { + console.log('没有选中的客户端需要添加'); + } + return c.json({ success: true, configId }); + } + catch (error) { + console.error('保存状态页配置失败:', error); + return c.json({ error: '保存状态页配置失败' }, 500); + } +}); +// 公共路由 +// 获取状态页数据(公开访问) +app.get('/data', async (c) => { + try { + // 获取所有配置 + console.log('获取状态页配置...'); + const configsResult = await c.env.DB.prepare('SELECT * FROM status_page_config').all(); + console.log('状态页配置查询结果:', JSON.stringify(configsResult)); + if (!configsResult.results || configsResult.results.length === 0) { + console.log('未找到状态页配置,返回默认配置'); + // 尝试创建一个默认配置 + const defaultConfig = await createDefaultConfig(c); + if (defaultConfig) { + return c.json({ + success: true, + data: defaultConfig + }); + } + return c.json({ + success: true, + data: { + title: '系统状态', + description: '实时监控系统状态', + logoUrl: '', + customCss: '', + monitors: [], + agents: [] + } + }); + } + // 简单处理:获取第一个配置 + const config = configsResult.results[0]; + console.log('找到状态页配置:', config); + // 获取选中的监控项 + const selectedMonitors = await c.env.DB.prepare('SELECT monitor_id FROM status_page_monitors WHERE config_id = ?').bind(config.id).all(); + console.log('选中的监控项:', JSON.stringify(selectedMonitors)); + // 获取选中的客户端 + const selectedAgents = await c.env.DB.prepare('SELECT agent_id FROM status_page_agents WHERE config_id = ?').bind(config.id).all(); + console.log('选中的客户端:', JSON.stringify(selectedAgents)); + // 获取监控项详细信息 + let monitors = []; + if (selectedMonitors.results && selectedMonitors.results.length > 0) { + const monitorIds = selectedMonitors.results.map(m => m.monitor_id); + const placeholders = monitorIds.map(() => '?').join(','); + console.log(`查询监控项详情, IDs: ${monitorIds}, SQL占位符: ${placeholders}`); + if (monitorIds.length > 0) { + const monitorsResult = await c.env.DB.prepare(`SELECT * FROM monitors WHERE id IN (${placeholders})`).bind(...monitorIds).all(); + console.log('监控项查询结果:', JSON.stringify(monitorsResult)); + if (monitorsResult.results) { + // 获取每个监控的历史记录 + monitors = await Promise.all(monitorsResult.results.map(async (monitor) => { + const historyResult = await c.env.DB.prepare(`SELECT status, timestamp + FROM monitor_status_history + WHERE monitor_id = ? + ORDER BY timestamp DESC + LIMIT 24`).bind(monitor.id).all(); + // 将历史记录转换为状态数组 + const history = historyResult.results + ? historyResult.results.map(h => h.status) + : Array(24).fill('unknown'); + return { + ...monitor, + history + }; + })); + } + } + } + else { + console.log('没有配置的监控项,返回空列表'); + } + // 获取客户端详细信息 + let agents = []; + if (selectedAgents.results && selectedAgents.results.length > 0) { + const agentIds = selectedAgents.results.map(a => a.agent_id); + const placeholders = agentIds.map(() => '?').join(','); + console.log(`查询客户端详情, IDs: ${agentIds}, SQL占位符: ${placeholders}`); + if (agentIds.length > 0) { + const agentsResult = await c.env.DB.prepare(`SELECT * FROM agents WHERE id IN (${placeholders})`).bind(...agentIds).all(); + console.log('客户端查询结果:', JSON.stringify(agentsResult)); + if (agentsResult.results) { + agents = agentsResult.results; + } + } + } + else { + // 修改: 不再自动获取所有客户端并关联 + console.log('没有配置的客户端,返回空列表'); + // 这里不再获取所有活跃客户端和自动关联 + } + // 构建并返回响应 + console.log('返回状态页数据:', { + monitors: monitors.length, + agents: agents.length + }); + // 为监控项添加必要的字段 + const enrichedMonitors = monitors.map((monitor) => ({ + ...monitor, + status: monitor.status || 'unknown', + uptime: monitor.uptime || 0, + response_time: monitor.response_time || 0, + history: monitor.history || Array(24).fill('unknown') + })); + // 为客户端添加资源使用情况字段 + const enrichedAgents = agents.map((agent) => { + // 计算内存使用百分比 (如果有总量和使用量) + const memoryPercent = agent.memory_total && agent.memory_used + ? (agent.memory_used / agent.memory_total) * 100 + : null; + // 计算磁盘使用百分比 (如果有总量和使用量) + const diskPercent = agent.disk_total && agent.disk_used + ? (agent.disk_used / agent.disk_total) * 100 + : null; + return { + ...agent, + // 使用数据库中的 status 字段,不重新计算 + cpu: agent.cpu_usage || 0, // 使用数据库中的CPU使用率 + memory: memoryPercent || 0, // 使用数据库中的内存使用百分比 + disk: diskPercent || 0, // 使用数据库中的磁盘使用百分比 + network_rx: agent.network_rx || 0, // 使用数据库中的网络下载速率 + network_tx: agent.network_tx || 0, // 使用数据库中的网络上传速率 + hostname: agent.hostname || "未知主机", + ip_address: agent.ip_address || "0.0.0.0", + os: agent.os || "未知系统", + version: agent.version || "未知版本" + }; + }); + return c.json({ + success: true, + data: { + title: config.title, + description: config.description, + logoUrl: config.logo_url, + customCss: config.custom_css, + monitors: enrichedMonitors, + agents: enrichedAgents.map(agent => ({ + id: agent.id, + name: agent.name, + status: agent.status, + cpu: agent.cpu, + memory: agent.memory, + disk: agent.disk, + network_rx: agent.network_rx, + network_tx: agent.network_tx, + hostname: agent.hostname, + ip_address: agent.ip_address, + os: agent.os, + version: agent.version + })) + } + }); + } + catch (error) { + console.error('获取状态页数据失败:', error); + return c.json({ success: false, message: '获取状态页数据失败', error: String(error) }, 500); + } +}); +// 创建默认配置 +async function createDefaultConfig(c) { + try { + console.log('创建默认状态页配置'); + // 查找管理员账户 + const admin = await c.env.DB.prepare('SELECT id FROM users WHERE role = ?').bind('admin').first(); + if (!admin) { + console.log('未找到管理员账户'); + return null; + } + // 创建默认配置 + const insertResult = await c.env.DB.prepare('INSERT INTO status_page_config (user_id, title, description, logo_url, custom_css) VALUES (?, ?, ?, ?, ?)').bind(admin.id, '系统状态', '实时监控系统状态', '', '').run(); + if (!insertResult.success) { + console.log('创建默认配置失败'); + return null; + } + // 获取新插入的ID + const lastInsertId = await c.env.DB.prepare('SELECT last_insert_rowid() as id').first(); + if (!lastInsertId || typeof lastInsertId.id !== 'number') { + console.log('获取配置ID失败'); + return null; + } + const configId = lastInsertId.id; + console.log(`创建的默认配置ID: ${configId}`); + // 获取所有监控项目 + let monitors; + try { + // 获取监控项 + monitors = await c.env.DB.prepare('SELECT * FROM monitors WHERE active = 1').all(); + } + catch (error) { + console.error('获取监控项失败:', error); + monitors = { results: [] }; + } + // 获取所有客户端 + let agents; + try { + // 获取所有客户端 + agents = await c.env.DB.prepare('SELECT * FROM agents').all(); + } + catch (error) { + console.error('获取客户端失败:', error); + agents = { results: [] }; + } + // 关联监控项 + if (monitors.results && monitors.results.length > 0) { + console.log(`找到 ${monitors.results.length} 个活跃监控项`); + for (const monitor of monitors.results) { + await c.env.DB.prepare('INSERT INTO status_page_monitors (config_id, monitor_id) VALUES (?, ?)').bind(configId, monitor.id).run(); + } + } + // 关联客户端 + if (agents.results && agents.results.length > 0) { + console.log(`找到 ${agents.results.length} 个活跃客户端`); + for (const agent of agents.results) { + await c.env.DB.prepare('INSERT INTO status_page_agents (config_id, agent_id) VALUES (?, ?)').bind(configId, agent.id).run(); + } + } + // 返回创建的配置 + return { + title: '系统状态', + description: '实时监控系统状态', + logoUrl: '', + customCss: '', + monitors: monitors.results ? monitors.results.map((monitor) => ({ + ...monitor, + status: monitor.status || 'unknown', + uptime: monitor.uptime || 0, + response_time: monitor.response_time || 0, + history: Array(24).fill('unknown') + })) : [], + agents: agents.results ? agents.results.map((agent) => { + // 计算内存使用百分比 (如果有总量和使用量) + const memoryPercent = agent.memory_total && agent.memory_used + ? (agent.memory_used / agent.memory_total) * 100 + : null; + // 计算磁盘使用百分比 (如果有总量和使用量) + const diskPercent = agent.disk_total && agent.disk_used + ? (agent.disk_used / agent.disk_total) * 100 + : null; + return { + ...agent, + // 使用数据库中的 status 字段,不重新计算 + cpu: agent.cpu_usage || 0, // 使用数据库中的CPU使用率 + memory: memoryPercent || 0, // 使用数据库中的内存使用百分比 + disk: diskPercent || 0, // 使用数据库中的磁盘使用百分比 + network_rx: agent.network_rx || 0, // 使用数据库中的网络下载速率 + network_tx: agent.network_tx || 0, // 使用数据库中的网络上传速率 + hostname: agent.hostname || "未知主机", + ip_address: agent.ip_address || "0.0.0.0", + os: agent.os || "未知系统", + version: agent.version || "未知版本" + }; + }) : [] + }; + } + catch (error) { + console.error('创建默认配置失败:', error); + return null; + } +} +// 注册管理员路由 +app.route('/', adminRoutes); +exports.default = app; +//# sourceMappingURL=status.js.map \ No newline at end of file diff --git a/backend/dist/routes/status.js.map b/backend/dist/routes/status.js.map new file mode 100644 index 0000000..eba7a8d --- /dev/null +++ b/backend/dist/routes/status.js.map @@ -0,0 +1 @@ +{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/routes/status.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/routes/users.d.ts b/backend/dist/routes/users.d.ts new file mode 100644 index 0000000..ebc841b --- /dev/null +++ b/backend/dist/routes/users.d.ts @@ -0,0 +1,6 @@ +import { Hono } from 'hono'; +import { Bindings } from '../models/db'; +declare const users: Hono<{ + Bindings: Bindings; +}, import("hono/types").BlankSchema, "/">; +export default users; diff --git a/backend/dist/routes/users.js b/backend/dist/routes/users.js new file mode 100644 index 0000000..88ca62a --- /dev/null +++ b/backend/dist/routes/users.js @@ -0,0 +1,237 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const jwt_1 = require("hono/jwt"); +const bcryptjs_1 = require("bcryptjs"); +const jwt_2 = require("../utils/jwt"); +const users = new hono_1.Hono(); +// 中间件:JWT 认证 +users.use('*', async (c, next) => { + const jwtMiddleware = (0, jwt_1.jwt)({ + secret: (0, jwt_2.getJwtSecret)(c) + }); + return jwtMiddleware(c, next); +}); +// 获取所有用户(仅管理员) +users.get('/', async (c) => { + try { + const payload = c.get('jwtPayload'); + // 检查权限 + if (payload.role !== 'admin') { + return c.json({ success: false, message: '无权访问用户列表' }, 403); + } + // 查询所有用户,不包括密码 + const result = await c.env.DB.prepare('SELECT id, username, email, role, created_at, updated_at FROM users ORDER BY id').all(); + return c.json({ success: true, users: result.results }); + } + catch (error) { + console.error('获取用户列表错误:', error); + return c.json({ success: false, message: '获取用户列表失败' }, 500); + } +}); +// 获取单个用户 +users.get('/:id', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + // 检查权限(仅允许管理员或用户本人) + if (payload.role !== 'admin' && payload.id !== id) { + return c.json({ success: false, message: '无权访问此用户信息' }, 403); + } + // 查询用户,不包括密码 + const user = await c.env.DB.prepare('SELECT id, username, email, role, created_at, updated_at FROM users WHERE id = ?').bind(id).first(); + if (!user) { + return c.json({ success: false, message: '用户不存在' }, 404); + } + return c.json({ success: true, user }); + } + catch (error) { + console.error('获取用户详情错误:', error); + return c.json({ success: false, message: '获取用户详情失败' }, 500); + } +}); +// 创建用户(仅管理员) +users.post('/', async (c) => { + try { + const payload = c.get('jwtPayload'); + // 检查权限 + if (payload.role !== 'admin') { + return c.json({ success: false, message: '无权创建用户' }, 403); + } + const data = await c.req.json(); + // 验证必填字段 + if (!data.username || !data.password || !data.role) { + return c.json({ success: false, message: '缺少必填字段' }, 400); + } + // 检查用户名是否已存在 + const existingUser = await c.env.DB.prepare('SELECT id FROM users WHERE username = ?').bind(data.username).first(); + if (existingUser) { + return c.json({ success: false, message: '用户名已存在' }, 400); + } + // 检查角色是否有效 + const validRoles = ['admin', 'manager', 'viewer']; + if (!validRoles.includes(data.role)) { + return c.json({ success: false, message: '无效的角色' }, 400); + } + // 哈希密码 + const hashedPassword = await (0, bcryptjs_1.hash)(data.password, 10); + const now = new Date().toISOString(); + // 插入新用户 + const result = await c.env.DB.prepare(`INSERT INTO users (username, password, email, role, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?)`).bind(data.username, hashedPassword, data.email || null, data.role, now, now).run(); + if (!result.success) { + throw new Error('创建用户失败'); + } + // 获取新创建的用户 + const newUser = await c.env.DB.prepare('SELECT id, username, email, role, created_at, updated_at FROM users WHERE rowid = last_insert_rowid()').first(); + return c.json({ success: true, user: newUser }, 201); + } + catch (error) { + console.error('创建用户错误:', error); + return c.json({ success: false, message: '创建用户失败' }, 500); + } +}); +// 更新用户 +users.put('/:id', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + // 检查权限(仅允许管理员或用户本人) + if (payload.role !== 'admin' && payload.id !== id) { + return c.json({ success: false, message: '无权修改此用户信息' }, 403); + } + // 检查用户是否存在 + const user = await c.env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(id).first(); + if (!user) { + return c.json({ success: false, message: '用户不存在' }, 404); + } + const data = await c.req.json(); + // 非管理员不能修改角色 + if (payload.role !== 'admin' && data.role && data.role !== user.role) { + return c.json({ success: false, message: '无权修改用户角色' }, 403); + } + // 准备更新数据 + const updates = []; + const values = []; + if (data.username !== undefined) { + // 检查新用户名是否已存在 + if (data.username !== user.username) { + const existingUser = await c.env.DB.prepare('SELECT id FROM users WHERE username = ? AND id != ?').bind(data.username, id).first(); + if (existingUser) { + return c.json({ success: false, message: '用户名已存在' }, 400); + } + updates.push('username = ?'); + values.push(data.username); + } + } + if (data.email !== undefined) { + updates.push('email = ?'); + values.push(data.email); + } + if (data.role !== undefined) { + updates.push('role = ?'); + values.push(data.role); + } + // 如果提供了新密码,则更新密码 + if (data.password) { + const hashedPassword = await (0, bcryptjs_1.hash)(data.password, 10); + updates.push('password = ?'); + values.push(hashedPassword); + } + updates.push('updated_at = ?'); + values.push(new Date().toISOString()); + // 添加 ID 作为 WHERE 条件 + values.push(id); + // 如果没有要更新的字段,直接返回 + if (updates.length <= 1) { + return c.json({ success: false, message: '没有提供要更新的字段' }, 400); + } + // 执行更新 + const result = await c.env.DB.prepare(`UPDATE users SET ${updates.join(', ')} WHERE id = ?`).bind(...values).run(); + if (!result.success) { + throw new Error('更新用户失败'); + } + // 获取更新后的用户 + const updatedUser = await c.env.DB.prepare('SELECT id, username, email, role, created_at, updated_at FROM users WHERE id = ?').bind(id).first(); + return c.json({ success: true, user: updatedUser }); + } + catch (error) { + console.error('更新用户错误:', error); + return c.json({ success: false, message: '更新用户失败' }, 500); + } +}); +// 删除用户(仅管理员) +users.delete('/:id', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + // 检查权限 + if (payload.role !== 'admin') { + return c.json({ success: false, message: '无权删除用户' }, 403); + } + // 防止删除自己 + if (payload.id === id) { + return c.json({ success: false, message: '不能删除当前登录的用户' }, 400); + } + // 检查用户是否存在 + const user = await c.env.DB.prepare('SELECT id FROM users WHERE id = ?').bind(id).first(); + if (!user) { + return c.json({ success: false, message: '用户不存在' }, 404); + } + // 执行删除 + const result = await c.env.DB.prepare('DELETE FROM users WHERE id = ?').bind(id).run(); + if (!result.success) { + throw new Error('删除用户失败'); + } + return c.json({ success: true, message: '用户已删除' }); + } + catch (error) { + console.error('删除用户错误:', error); + return c.json({ success: false, message: '删除用户失败' }, 500); + } +}); +// 修改密码 +users.post('/:id/change-password', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + // 检查权限(仅允许用户本人或管理员) + if (payload.role !== 'admin' && payload.id !== id) { + return c.json({ success: false, message: '无权修改此用户密码' }, 403); + } + const { currentPassword, newPassword } = await c.req.json(); + // 管理员可以不提供当前密码 + if (payload.role !== 'admin' && !currentPassword) { + return c.json({ success: false, message: '必须提供当前密码' }, 400); + } + if (!newPassword) { + return c.json({ success: false, message: '必须提供新密码' }, 400); + } + // 获取用户 + const user = await c.env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(id).first(); + if (!user) { + return c.json({ success: false, message: '用户不存在' }, 404); + } + // 非管理员需要验证当前密码 + if (payload.role !== 'admin') { + const isPasswordValid = await (0, bcryptjs_1.compare)(currentPassword, user.password); + if (!isPasswordValid) { + return c.json({ success: false, message: '当前密码不正确' }, 400); + } + } + // 哈希新密码 + const hashedPassword = await (0, bcryptjs_1.hash)(newPassword, 10); + // 更新密码 + const result = await c.env.DB.prepare('UPDATE users SET password = ?, updated_at = ? WHERE id = ?').bind(hashedPassword, new Date().toISOString(), id).run(); + if (!result.success) { + throw new Error('更新密码失败'); + } + return c.json({ success: true, message: '密码已更新' }); + } + catch (error) { + console.error('修改密码错误:', error); + return c.json({ success: false, message: '修改密码失败' }, 500); + } +}); +exports.default = users; +//# sourceMappingURL=users.js.map \ No newline at end of file diff --git a/backend/dist/routes/users.js.map b/backend/dist/routes/users.js.map new file mode 100644 index 0000000..2c60e60 --- /dev/null +++ b/backend/dist/routes/users.js.map @@ -0,0 +1 @@ +{"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/routes/users.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/scheduled.d.ts b/backend/dist/scheduled.d.ts new file mode 100644 index 0000000..b480661 --- /dev/null +++ b/backend/dist/scheduled.d.ts @@ -0,0 +1 @@ +export declare const checkAgentsStatus: (env: any) => Promise; diff --git a/backend/dist/scheduled.js b/backend/dist/scheduled.js new file mode 100644 index 0000000..3eb243f --- /dev/null +++ b/backend/dist/scheduled.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.checkAgentsStatus = void 0; +// 定期检查客户端活跃状态 +const checkAgentsStatus = async (env) => { + try { + console.log('定时任务: 检查客户端活跃状态...'); + const now = new Date(); + // 计算15分钟前的时间 + const cutoffTime = new Date(now.getTime() - 15 * 60 * 1000).toISOString(); + // 更新超过15分钟未活动的客户端为非活跃 + const result = await env.DB.prepare(` + UPDATE agents + SET active = 0 + WHERE active = 1 AND (last_seen IS NULL OR last_seen < ?) + `).bind(cutoffTime).run(); + console.log(`定时任务: 已更新 ${result.changes} 个客户端状态为离线`); + } + catch (error) { + console.error('定时任务: 检查客户端活跃状态出错:', error); + } +}; +exports.checkAgentsStatus = checkAgentsStatus; +//# sourceMappingURL=scheduled.js.map \ No newline at end of file diff --git a/backend/dist/scheduled.js.map b/backend/dist/scheduled.js.map new file mode 100644 index 0000000..cdaf397 --- /dev/null +++ b/backend/dist/scheduled.js.map @@ -0,0 +1 @@ +{"version":3,"file":"scheduled.js","sourceRoot":"","sources":["../src/scheduled.ts"],"names":[],"mappings":";;;AAAA,aAAa;AACN,MAAM,iBAAiB,GAAG,KAAK,EAAE,GAAQ,EAAE,EAAE;IAClD,IAAI,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;QAEjC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,aAAa;QACb,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QAE1E,qBAAqB;QACrB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC;;;;KAInC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,CAAC;QAE1B,OAAO,CAAC,GAAG,CAAC,aAAa,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;IACtD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC,CAAC;AAnBW,QAAA,iBAAiB,qBAmB5B"} \ No newline at end of file diff --git a/backend/dist/setup/database.d.ts b/backend/dist/setup/database.d.ts new file mode 100644 index 0000000..9932157 --- /dev/null +++ b/backend/dist/setup/database.d.ts @@ -0,0 +1,6 @@ +import { Hono } from 'hono'; +import { Bindings } from '../models/db'; +declare const initDb: Hono<{ + Bindings: Bindings; +}, import("hono/types").BlankSchema, "/">; +export default initDb; diff --git a/backend/dist/setup/database.js b/backend/dist/setup/database.js new file mode 100644 index 0000000..a687bc3 --- /dev/null +++ b/backend/dist/setup/database.js @@ -0,0 +1,163 @@ +"use strict"; +// 初始化数据库脚本 +// 用于在 Cloudflare Workers 环境中创建数据库表和初始数据 +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const bcryptjs_1 = __importDefault(require("bcryptjs")); +const initDb = new hono_1.Hono(); +// 初始化数据库路由 +initDb.get('/init-db', async (c) => { + try { + console.log('开始初始化数据库...'); + // 先删除已有的表 + console.log('删除已有的表...'); + try { + await c.env.DB.exec("DROP TABLE IF EXISTS status_page_agents"); + await c.env.DB.exec("DROP TABLE IF EXISTS status_page_monitors"); + await c.env.DB.exec("DROP TABLE IF EXISTS status_page_config"); + await c.env.DB.exec("DROP TABLE IF EXISTS monitor_status_history"); + await c.env.DB.exec("DROP TABLE IF EXISTS monitor_checks"); + await c.env.DB.exec("DROP TABLE IF EXISTS agents"); + await c.env.DB.exec("DROP TABLE IF EXISTS monitors"); + await c.env.DB.exec("DROP TABLE IF EXISTS users"); + } + catch (error) { + console.log('删除表出错,可能表不存在:', error); + } + console.log('创建用户表...'); + // 创建用户表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password TEXT NOT NULL, email TEXT, role TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)"); + console.log('创建监控表...'); + // 创建监控表(单行格式) + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS monitors (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, url TEXT NOT NULL, method TEXT NOT NULL, interval INTEGER NOT NULL, timeout INTEGER NOT NULL, expected_status INTEGER NOT NULL, headers TEXT NOT NULL, body TEXT, created_by INTEGER NOT NULL, active BOOLEAN NOT NULL, status TEXT DEFAULT 'pending', uptime REAL DEFAULT 100.0, response_time INTEGER DEFAULT 0, last_checked TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (created_by) REFERENCES users(id))"); + console.log('创建监控历史记录表...'); + // 创建监控历史记录表(单行格式) + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS monitor_checks (id INTEGER PRIMARY KEY AUTOINCREMENT, monitor_id INTEGER NOT NULL, status TEXT NOT NULL, response_time INTEGER, status_code INTEGER, error TEXT, checked_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (monitor_id) REFERENCES monitors(id))"); + console.log('创建监控状态历史表...'); + // 创建监控状态历史表(单行格式) + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS monitor_status_history (id INTEGER PRIMARY KEY AUTOINCREMENT, monitor_id INTEGER NOT NULL, status TEXT NOT NULL, timestamp TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (monitor_id) REFERENCES monitors(id))"); + console.log('创建客户端表...'); + // 创建客户端表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS agents (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, token TEXT NOT NULL UNIQUE, created_by INTEGER NOT NULL, status TEXT DEFAULT 'inactive', created_at TEXT NOT NULL, updated_at TEXT NOT NULL, hostname TEXT, ip_address TEXT, os TEXT, version TEXT, cpu_usage REAL, memory_total INTEGER, memory_used INTEGER, disk_total INTEGER, disk_used INTEGER, network_rx INTEGER, network_tx INTEGER, FOREIGN KEY (created_by) REFERENCES users(id))"); + console.log('创建状态页配置表...'); + // 创建状态页配置表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS status_page_config (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, title TEXT NOT NULL DEFAULT '系统状态', description TEXT DEFAULT '系统当前运行状态', logo_url TEXT DEFAULT '', custom_css TEXT DEFAULT '', created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id))"); + console.log('创建状态页监控项关联表...'); + // 创建状态页监控项关联表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS status_page_monitors (config_id INTEGER NOT NULL, monitor_id INTEGER NOT NULL, PRIMARY KEY (config_id, monitor_id), FOREIGN KEY (config_id) REFERENCES status_page_config(id) ON DELETE CASCADE, FOREIGN KEY (monitor_id) REFERENCES monitors(id) ON DELETE CASCADE)"); + console.log('创建状态页客户端关联表...'); + // 创建状态页客户端关联表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS status_page_agents (config_id INTEGER NOT NULL, agent_id INTEGER NOT NULL, PRIMARY KEY (config_id, agent_id), FOREIGN KEY (config_id) REFERENCES status_page_config(id) ON DELETE CASCADE, FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE)"); + console.log('检查管理员用户...'); + // 检查是否已存在管理员用户 + const adminUser = await c.env.DB.prepare('SELECT id FROM users WHERE username = ?').bind('admin').first(); + // 如果不存在管理员用户,则创建一个 + if (!adminUser) { + console.log('创建管理员用户...'); + // 密码: admin123 + const salt = await bcryptjs_1.default.genSalt(10); + const hashedPassword = await bcryptjs_1.default.hash('admin123', salt); + const now = new Date().toISOString(); + await c.env.DB.prepare(`INSERT INTO users (username, password, email, role, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?)`).bind('admin', hashedPassword, 'admin@example.com', 'admin', now, now).run(); + } + console.log('添加示例监控数据...'); + // 添加示例监控数据 + await addSampleMonitors(c); + console.log('添加示例客户端数据...'); + // 添加示例客户端数据 + await addSampleAgents(c); + return c.json({ + success: true, + message: '数据库初始化成功', + tables: ['users', 'monitors', 'monitor_checks', 'monitor_status_history', 'agents', 'status_page_config', 'status_page_monitors', 'status_page_agents'] + }); + } + catch (error) { + console.error('数据库初始化错误:', error); + return c.json({ + success: false, + message: '数据库初始化失败', + error: error instanceof Error ? error.message : String(error) + }, 500); + } +}); +// 添加示例监控数据 +async function addSampleMonitors(c) { + // 检查是否已有示例监控数据 + const existingMonitors = await c.env.DB.prepare('SELECT COUNT(*) as count FROM monitors').first(); + if (existingMonitors.count === 0) { + console.log('添加示例监控...'); + const now = new Date().toISOString(); + const userId = 1; // 管理员用户ID + // 添加API服务监控 + const apiMonitor = await c.env.DB.prepare(`INSERT INTO monitors (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind('API服务', 'https://api.example.com/status', 'GET', 60, 30, 200, '{}', '', userId, true, 'up', 99.98, 120, now, now, now).run(); + // 添加用户服务监控 + const userMonitor = await c.env.DB.prepare(`INSERT INTO monitors (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind('用户服务', 'https://users.example.com/health', 'GET', 30, 30, 200, '{}', '', userId, true, 'up', 99.95, 150, now, now, now).run(); + // 添加支付服务监控 + const paymentMonitor = await c.env.DB.prepare(`INSERT INTO monitors (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind('支付服务', 'https://payments.example.com/status', 'GET', 60, 30, 200, '{}', '', userId, true, 'up', 99.9, 180, now, now, now).run(); + // 添加数据库服务监控 + const dbMonitor = await c.env.DB.prepare(`INSERT INTO monitors (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind('数据库服务', 'https://db.example.com/health', 'GET', 120, 30, 200, '{}', '', userId, true, 'down', 99.5, 300, now, now, now).run(); + } +} +// 添加示例客户端数据 +async function addSampleAgents(c) { + // 检查是否已有示例客户端数据 + const existingAgents = await c.env.DB.prepare('SELECT COUNT(*) as count FROM agents').first(); + if (existingAgents.count === 0) { + console.log('添加示例客户端...'); + const now = new Date().toISOString(); + const userId = 1; // 管理员用户ID + // 主服务器 + const primaryAgent = await c.env.DB.prepare(`INSERT INTO agents (name, token, created_by, status, created_at, updated_at, cpu_usage, memory_total, memory_used, disk_total, disk_used, network_rx, network_tx, hostname, ip_address, os, version) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind('主服务器', 'primary-server-token-123456', userId, 'active', now, now, 25.5, // CPU使用率 + 16384, // 内存总量 (MB) + 8192, // 内存使用量 (MB) + 500000, // 磁盘总量 (MB) + 250000, // 磁盘使用量 (MB) + 1024, // 网络接收流量 (KB) + 512, // 网络发送流量 (KB) + 'primary-server', // 主机名 + '192.168.1.10', // IP地址 + 'Linux Ubuntu 20.04', // 操作系统 + '1.0.0' // 版本 + ).run(); + // 备份服务器 + const backupAgent = await c.env.DB.prepare(`INSERT INTO agents (name, token, created_by, status, created_at, updated_at, cpu_usage, memory_total, memory_used, disk_total, disk_used, network_rx, network_tx, hostname, ip_address, os, version) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind('备份服务器', 'backup-server-token-123456', userId, 'active', now, now, 15.2, // CPU使用率 + 8192, // 内存总量 (MB) + 4096, // 内存使用量 (MB) + 1000000, // 磁盘总量 (MB) + 400000, // 磁盘使用量 (MB) + 512, // 网络接收流量 (KB) + 256, // 网络发送流量 (KB) + 'backup-server', // 主机名 + '192.168.1.11', // IP地址 + 'Linux CentOS 8', // 操作系统 + '1.0.0' // 版本 + ).run(); + // API服务器 + const apiAgent = await c.env.DB.prepare(`INSERT INTO agents (name, token, created_by, status, created_at, updated_at, cpu_usage, memory_total, memory_used, disk_total, disk_used, network_rx, network_tx, hostname, ip_address, os, version) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind('API服务器', 'api-server-token-123456', userId, 'active', now, now, 35.8, // CPU使用率 + 4096, // 内存总量 (MB) + 2048, // 内存使用量 (MB) + 250000, // 磁盘总量 (MB) + 125000, // 磁盘使用量 (MB) + 2048, // 网络接收流量 (KB) + 1024, // 网络发送流量 (KB) + 'api-server', // 主机名 + '192.168.1.12', // IP地址 + 'Linux Debian 11', // 操作系统 + '1.0.0' // 版本 + ).run(); + } +} +exports.default = initDb; +//# sourceMappingURL=database.js.map \ No newline at end of file diff --git a/backend/dist/setup/database.js.map b/backend/dist/setup/database.js.map new file mode 100644 index 0000000..18056c9 --- /dev/null +++ b/backend/dist/setup/database.js.map @@ -0,0 +1 @@ +{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/setup/database.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/tasks/agent-task.d.ts b/backend/dist/tasks/agent-task.d.ts new file mode 100644 index 0000000..b480661 --- /dev/null +++ b/backend/dist/tasks/agent-task.d.ts @@ -0,0 +1 @@ +export declare const checkAgentsStatus: (env: any) => Promise; diff --git a/backend/dist/tasks/agent-task.js b/backend/dist/tasks/agent-task.js new file mode 100644 index 0000000..fef8020 --- /dev/null +++ b/backend/dist/tasks/agent-task.js @@ -0,0 +1,17 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.checkAgentsStatus = void 0; +// 定期检查客户端状态的任务 +const checkAgentsStatus = async (env) => { + try { + console.log('定时任务: 检查客户端状态...'); + // 所有客户端默认都是inactive状态,只有当客户端明确报告活跃时才会被设置为active + // 这个任务不做任何状态变更,只打印一条日志 + console.log('定时任务: 客户端状态由客户端主动报告,无需定时任务检查'); + } + catch (error) { + console.error('定时任务: 检查客户端状态出错:', error); + } +}; +exports.checkAgentsStatus = checkAgentsStatus; +//# sourceMappingURL=agent-task.js.map \ No newline at end of file diff --git a/backend/dist/tasks/agent-task.js.map b/backend/dist/tasks/agent-task.js.map new file mode 100644 index 0000000..18c66f3 --- /dev/null +++ b/backend/dist/tasks/agent-task.js.map @@ -0,0 +1 @@ +{"version":3,"file":"agent-task.js","sourceRoot":"","sources":["../../src/tasks/agent-task.ts"],"names":[],"mappings":";;;AAAA,eAAe;AACR,MAAM,iBAAiB,GAAG,KAAK,EAAE,GAAQ,EAAE,EAAE;IAClD,IAAI,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAEhC,gDAAgD;QAChD,uBAAuB;QACvB,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC,CAAC;AAVW,QAAA,iBAAiB,qBAU5B"} \ No newline at end of file diff --git a/backend/dist/tasks/index.d.ts b/backend/dist/tasks/index.d.ts new file mode 100644 index 0000000..838c610 --- /dev/null +++ b/backend/dist/tasks/index.d.ts @@ -0,0 +1,4 @@ +import monitorTask from './monitor-task'; +import { checkAgentsStatus } from './agent-task'; +export declare const runScheduledTasks: (event: any, env: any, ctx: any) => Promise; +export { monitorTask, checkAgentsStatus }; diff --git a/backend/dist/tasks/index.js b/backend/dist/tasks/index.js new file mode 100644 index 0000000..a9b882d --- /dev/null +++ b/backend/dist/tasks/index.js @@ -0,0 +1,27 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.checkAgentsStatus = exports.monitorTask = exports.runScheduledTasks = void 0; +// 导出所有定时任务 +const monitor_task_1 = __importDefault(require("./monitor-task")); +exports.monitorTask = monitor_task_1.default; +const agent_task_1 = require("./agent-task"); +Object.defineProperty(exports, "checkAgentsStatus", { enumerable: true, get: function () { return agent_task_1.checkAgentsStatus; } }); +// 统一的定时任务处理函数 +const runScheduledTasks = async (event, env, ctx) => { + try { + // 执行监控检查任务 + if (monitor_task_1.default.scheduled) { + await monitor_task_1.default.scheduled(event, env, ctx); + } + // 执行客户端状态检查任务 + await (0, agent_task_1.checkAgentsStatus)(env); + } + catch (error) { + console.error('定时任务执行出错:', error); + } +}; +exports.runScheduledTasks = runScheduledTasks; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/backend/dist/tasks/index.js.map b/backend/dist/tasks/index.js.map new file mode 100644 index 0000000..880038a --- /dev/null +++ b/backend/dist/tasks/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tasks/index.ts"],"names":[],"mappings":";;;;;;AAAA,WAAW;AACX,kEAAyC;AAmBvC,sBAnBK,sBAAW,CAmBL;AAlBb,6CAAiD;AAmB/C,kGAnBO,8BAAiB,OAmBP;AAjBnB,cAAc;AACP,MAAM,iBAAiB,GAAG,KAAK,EAAE,KAAU,EAAE,GAAQ,EAAE,GAAQ,EAAE,EAAE;IACxE,IAAI,CAAC;QACH,WAAW;QACX,IAAI,sBAAW,CAAC,SAAS,EAAE,CAAC;YAC1B,MAAM,sBAAW,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC/C,CAAC;QAED,cAAc;QACd,MAAM,IAAA,8BAAiB,EAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACpC,CAAC;AACH,CAAC,CAAC;AAZW,QAAA,iBAAiB,qBAY5B"} \ No newline at end of file diff --git a/backend/dist/tasks/monitor-task.d.ts b/backend/dist/tasks/monitor-task.d.ts new file mode 100644 index 0000000..4321ee1 --- /dev/null +++ b/backend/dist/tasks/monitor-task.d.ts @@ -0,0 +1,6 @@ +import { Bindings } from '../models/db'; +declare const _default: { + scheduled(event: any, env: any, ctx: any): Promise; + fetch: (request: Request, Env?: {} | Bindings | undefined, executionCtx?: import("hono").ExecutionContext) => Response | Promise; +}; +export default _default; diff --git a/backend/dist/tasks/monitor-task.js b/backend/dist/tasks/monitor-task.js new file mode 100644 index 0000000..600468d --- /dev/null +++ b/backend/dist/tasks/monitor-task.js @@ -0,0 +1,106 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const hono_1 = require("hono"); +const monitorTask = new hono_1.Hono(); +// 监控检查的主要函数 +async function checkMonitors(c) { + try { + console.log('开始执行监控检查...'); + // 获取当前时间 + const now = new Date(); + // 查询需要检查的监控 + // 条件:active=true 且 (last_checked 为 null 或 当前时间 - last_checked > interval) + const monitors = await c.env.DB.prepare(` + SELECT * FROM monitors + WHERE active = true + AND (last_checked IS NULL OR datetime('now') > datetime(last_checked, '+' || interval || ' seconds')) + `).all(); + console.log(`找到 ${monitors?.results?.length || 0} 个需要检查的监控`); + if (!monitors.results || monitors.results.length === 0) { + return { success: true, message: '没有需要检查的监控', checked: 0 }; + } + // 检查每个监控 + const results = await Promise.all(monitors.results.map(async (monitor) => { + return await checkSingleMonitor(c, monitor); + })); + return { + success: true, + message: '监控检查完成', + checked: results.length, + results: results + }; + } + catch (error) { + console.error('监控检查出错:', error); + return { success: false, message: '监控检查出错', error: String(error) }; + } +} +// 检查单个监控的函数 +async function checkSingleMonitor(c, monitor) { + try { + console.log(`开始检查监控项: ${monitor.name} (${monitor.url})`); + const startTime = Date.now(); + const response = await fetch(monitor.url, { + method: monitor.method, + headers: { + 'User-Agent': 'Xugou-Monitor/1.0', + ...(monitor.headers ? JSON.parse(monitor.headers) : {}) + }, + body: monitor.body || undefined + }); + const endTime = Date.now(); + const responseTime = endTime - startTime; + // 检查响应状态 + const isUp = response.status === monitor.expected_status; + const status = isUp ? 'up' : 'down'; + // 记录状态历史 + await c.env.DB.prepare(`INSERT INTO monitor_status_history (monitor_id, status, timestamp) + VALUES (?, ?, datetime('now'))`).bind(monitor.id, status).run(); + // 更新监控项状态 + await c.env.DB.prepare(`UPDATE monitors + SET status = ?, + last_checked = datetime('now'), + response_time = ?, + uptime = CASE + WHEN status = 'up' THEN uptime + 1 + ELSE uptime + END + WHERE id = ?`).bind(status, responseTime, monitor.id).run(); + console.log(`监控项检查完成: ${monitor.name}, 状态: ${status}, 响应时间: ${responseTime}ms`); + return { + success: true, + status, + responseTime + }; + } + catch (error) { + console.error(`检查监控项失败: ${monitor.name}`, error); + // 记录错误状态 + await c.env.DB.prepare(`INSERT INTO monitor_status_history (monitor_id, status, timestamp) + VALUES (?, ?, datetime('now'))`).bind(monitor.id, 'down').run(); + // 更新监控项状态 + await c.env.DB.prepare(`UPDATE monitors + SET status = 'down', + last_checked = datetime('now') + WHERE id = ?`).bind(monitor.id).run(); + return { + success: false, + status: 'down', + error: error.message + }; + } +} +// 定义触发器路由 - 通过HTTP请求触发监控检查 +monitorTask.get('/api/trigger-check', async (c) => { + const result = await checkMonitors(c); + return c.json(result); +}); +// 在 Cloudflare Workers 中设置定时触发器 +exports.default = { + async scheduled(event, env, ctx) { + const c = { env }; + await checkMonitors(c); + }, + fetch: monitorTask.fetch +}; +//# sourceMappingURL=monitor-task.js.map \ No newline at end of file diff --git a/backend/dist/tasks/monitor-task.js.map b/backend/dist/tasks/monitor-task.js.map new file mode 100644 index 0000000..4b82116 --- /dev/null +++ b/backend/dist/tasks/monitor-task.js.map @@ -0,0 +1 @@ +{"version":3,"file":"monitor-task.js","sourceRoot":"","sources":["../../src/tasks/monitor-task.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/utils/jwt.d.ts b/backend/dist/utils/jwt.d.ts new file mode 100644 index 0000000..df4348c --- /dev/null +++ b/backend/dist/utils/jwt.d.ts @@ -0,0 +1,18 @@ +/** + * JWT工具类,提供JWT相关的通用功能 + */ +/** + * 获取JWT密钥 + * 优先从环境变量中获取JWT_SECRET,如果不存在则使用默认值 + * + * @param c Cloudflare环境上下文 + * @returns JWT密钥 + */ +export declare const getJwtSecret: (c: any) => string; +/** + * 生成随机令牌 + * 生成用于API密钥或认证令牌的随机字符串 + * + * @returns 生成的随机令牌 + */ +export declare function generateToken(): Promise; diff --git a/backend/dist/utils/jwt.js b/backend/dist/utils/jwt.js new file mode 100644 index 0000000..8b2c4ee --- /dev/null +++ b/backend/dist/utils/jwt.js @@ -0,0 +1,35 @@ +"use strict"; +/** + * JWT工具类,提供JWT相关的通用功能 + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getJwtSecret = void 0; +exports.generateToken = generateToken; +/** + * 获取JWT密钥 + * 优先从环境变量中获取JWT_SECRET,如果不存在则使用默认值 + * + * @param c Cloudflare环境上下文 + * @returns JWT密钥 + */ +const getJwtSecret = (c) => { + // 在Cloudflare Workers环境中,使用env变量 + if (typeof process === 'undefined') { + return c.env.JWT_SECRET || 'your-secret-key-change-in-production'; + } + // 在Node.js环境中,使用process.env + return process.env.JWT_SECRET || 'your-secret-key-change-in-production'; +}; +exports.getJwtSecret = getJwtSecret; +/** + * 生成随机令牌 + * 生成用于API密钥或认证令牌的随机字符串 + * + * @returns 生成的随机令牌 + */ +async function generateToken() { + const array = new Uint8Array(32); + crypto.getRandomValues(array); + return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); +} +//# sourceMappingURL=jwt.js.map \ No newline at end of file diff --git a/backend/dist/utils/jwt.js.map b/backend/dist/utils/jwt.js.map new file mode 100644 index 0000000..f25cc4d --- /dev/null +++ b/backend/dist/utils/jwt.js.map @@ -0,0 +1 @@ +{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../src/utils/jwt.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAwBH,sCAIC;AA1BD;;;;;;GAMG;AACI,MAAM,YAAY,GAAG,CAAC,CAAM,EAAU,EAAE;IAC7C,iCAAiC;IACjC,IAAI,OAAO,OAAO,KAAK,WAAW,EAAE,CAAC;QACnC,OAAO,CAAC,CAAC,GAAG,CAAC,UAAU,IAAI,sCAAsC,CAAC;IACpE,CAAC;IACD,4BAA4B;IAC5B,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,sCAAsC,CAAC;AAC1E,CAAC,CAAC;AAPW,QAAA,YAAY,gBAOvB;AAEF;;;;;GAKG;AACI,KAAK,UAAU,aAAa;IACjC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IACjC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAChF,CAAC"} \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000..6bfd60d --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,2294 @@ +{ + "name": "backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "backend", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@hono/node-server": "^1.13.8", + "@types/bcrypt": "^5.0.2", + "@types/bcryptjs": "^2.4.6", + "@types/jsonwebtoken": "^9.0.9", + "@types/node": "^22.13.10", + "bcryptjs": "^3.0.2", + "hono": "^4.7.4", + "jsonwebtoken": "^9.0.2", + "typescript": "^5.8.2" + }, + "devDependencies": { + "nodemon": "^3.1.9", + "ts-node": "^10.9.2", + "wrangler": "^3.114.1" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz", + "integrity": "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "mime": "^3.0.0" + }, + "engines": { + "node": ">=16.13" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/@cloudflare/unenv-preset/-/unenv-preset-2.0.2.tgz", + "integrity": "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.14", + "workerd": "^1.20250124.0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20250310.0", + "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250310.0.tgz", + "integrity": "sha512-LkLJO6F8lRNaCbK5sQCITi66SyCirDpffRuI5/5iILDJWQU4KVvAOKPvHrd4E5h/WDm9FGd22zMJwky7SxaNjg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20250310.0", + "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250310.0.tgz", + "integrity": "sha512-WythDJQbsU3Ii1hhA7pJZLBQlHezeYWAnaMnv3gS2Exj45oF8G4chFvrO7zCzjlcJXwSeBTtQRJqxw9AiUDhyA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20250310.0", + "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250310.0.tgz", + "integrity": "sha512-LbP769tT4/5QBHSj4lCt99QIKTi6cU+wYhLfF7rEtYHBnZS2+nIw9xttAzxeERx/aFrU+mxLcYPFV8fUeVxGng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20250310.0", + "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250310.0.tgz", + "integrity": "sha512-FzWeKM6id20EMZACaDg0Kkvg1C4lvXZgLBXVI6h6xaXTNFReoyEp4v4eMrRTuja5ec5k+m5iGKjP4/bMWJp9ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20250310.0", + "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250310.0.tgz", + "integrity": "sha512-04OgaDzm8/8nkjF3tovB+WywZLjSdAHCQT2omXKCwH3EDd1kpd8vvzE1pErtdIyKCOf9/sArY4BhPdxRj7ijlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild-plugins/node-globals-polyfill": { + "version": "0.2.3", + "resolved": "https://registry.npmmirror.com/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", + "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/@esbuild-plugins/node-modules-polyfill": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", + "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", + "dev": true, + "license": "ISC", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "rollup-plugin-node-polyfills": "^0.2.1" + }, + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@hono/node-server": { + "version": "1.13.8", + "resolved": "https://registry.npmmirror.com/@hono/node-server/-/node-server-1.13.8.tgz", + "integrity": "sha512-fsn8ucecsAXUoVxrUil0m13kOEq4mkX4/4QozCqmY+HpGfKl74OYSn8JcMA8GnG0ClfdRI4/ZSeG7zhFaVg+wg==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmmirror.com/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.9", + "resolved": "https://registry.npmmirror.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", + "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.13.10", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/as-table": { + "version": "1.0.55", + "resolved": "https://registry.npmmirror.com/as-table/-/as-table-1.0.55.tgz", + "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "printable-characters": "^1.0.42" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-3.0.2.tgz", + "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/exit-hook": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/exit-hook/-/exit-hook-2.2.1.tgz", + "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exsolve": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.4.tgz", + "integrity": "sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-source": { + "version": "2.0.12", + "resolved": "https://registry.npmmirror.com/get-source/-/get-source-2.0.12.tgz", + "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "data-uri-to-buffer": "^2.0.0", + "source-map": "^0.6.1" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/hono": { + "version": "4.7.4", + "resolved": "https://registry.npmmirror.com/hono/-/hono-4.7.4.tgz", + "integrity": "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmmirror.com/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/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmmirror.com/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/miniflare": { + "version": "3.20250310.0", + "resolved": "https://registry.npmmirror.com/miniflare/-/miniflare-3.20250310.0.tgz", + "integrity": "sha512-TQAxoo2ZiQYjiOJoK3bbcyjKD/u1E3akYOeSHc2Zcp1sLVydrgzSjmxtrn65/3BfDIrUgfYHyy9wspT6wzBy/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "acorn": "8.14.0", + "acorn-walk": "8.3.2", + "exit-hook": "2.2.1", + "glob-to-regexp": "0.4.1", + "stoppable": "1.1.0", + "undici": "^5.28.5", + "workerd": "1.20250310.0", + "ws": "8.18.0", + "youch": "3.2.3", + "zod": "3.22.3" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=16.13" + } + }, + "node_modules/miniflare/node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/miniflare/node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmmirror.com/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/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/printable-characters": { + "version": "1.0.42", + "resolved": "https://registry.npmmirror.com/printable-characters/-/printable-characters-1.0.42.tgz", + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rollup-plugin-inject": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", + "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^0.6.1", + "magic-string": "^0.25.3", + "rollup-pluginutils": "^2.8.1" + } + }, + "node_modules/rollup-plugin-node-polyfills": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", + "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rollup-plugin-inject": "^3.0.0" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmmirror.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmmirror.com/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/stacktracey": { + "version": "2.1.8", + "resolved": "https://registry.npmmirror.com/stacktracey/-/stacktracey-2.1.8.tgz", + "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "as-table": "^1.0.36", + "get-source": "^2.0.12" + } + }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmmirror.com/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "5.28.5", + "resolved": "https://registry.npmmirror.com/undici/-/undici-5.28.5.tgz", + "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/unenv": { + "version": "2.0.0-rc.14", + "resolved": "https://registry.npmmirror.com/unenv/-/unenv-2.0.0-rc.14.tgz", + "integrity": "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "exsolve": "^1.0.1", + "ohash": "^2.0.10", + "pathe": "^2.0.3", + "ufo": "^1.5.4" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/workerd": { + "version": "1.20250310.0", + "resolved": "https://registry.npmmirror.com/workerd/-/workerd-1.20250310.0.tgz", + "integrity": "sha512-bAaZ9Bmts3mArbIrXYAtr+ZRsAJAAUEsCtvwfBavIYXaZ5sgdEOJBEiBbvsHp6CsVObegOM85tIWpYLpbTxQrQ==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20250310.0", + "@cloudflare/workerd-darwin-arm64": "1.20250310.0", + "@cloudflare/workerd-linux-64": "1.20250310.0", + "@cloudflare/workerd-linux-arm64": "1.20250310.0", + "@cloudflare/workerd-windows-64": "1.20250310.0" + } + }, + "node_modules/wrangler": { + "version": "3.114.1", + "resolved": "https://registry.npmmirror.com/wrangler/-/wrangler-3.114.1.tgz", + "integrity": "sha512-GuS6SrnAZZDiNb20Vf2Ww0KCfnctHUEzi5GyML1i2brfQPI6BikgI/W/u6XDtYtah0OkbIWIiNJ+SdhWT7KEcw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.3.4", + "@cloudflare/unenv-preset": "2.0.2", + "@esbuild-plugins/node-globals-polyfill": "0.2.3", + "@esbuild-plugins/node-modules-polyfill": "0.2.2", + "blake3-wasm": "2.1.5", + "esbuild": "0.17.19", + "miniflare": "3.20250310.0", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.14", + "workerd": "1.20250310.0" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=16.17.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2", + "sharp": "^0.33.5" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20250310.0" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/youch": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/youch/-/youch-3.2.3.tgz", + "integrity": "sha512-ZBcWz/uzZaQVdCvfV4uk616Bbpf2ee+F/AvuKDR5EwX/Y4v06xWdtMluqTD7+KlZdM93lLm9gMZYo0sKBS0pgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie": "^0.5.0", + "mustache": "^4.2.0", + "stacktracey": "^2.1.8" + } + }, + "node_modules/zod": { + "version": "3.22.3", + "resolved": "https://registry.npmmirror.com/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..c8afb40 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,36 @@ +{ + "name": "backend", + "version": "1.0.0", + "main": "dist/index.js", + "type": "module", + "scripts": { + "dev": "wrangler dev", + "build": "tsc", + "start": "node dist/index.js", + "deploy": "wrangler deploy", + "deploy:dev": "wrangler deploy --env development", + "deploy:prod": "wrangler deploy --env production", + "dev:wrangler": "wrangler dev" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "@hono/node-server": "^1.13.8", + "@types/bcryptjs": "^2.4.6", + "@types/jsonwebtoken": "^9.0.9", + "@types/node": "^22.13.10", + "bcryptjs": "^2.4.3", + "hono": "^4.7.4", + "jsonwebtoken": "^9.0.2", + "typescript": "^5.8.2" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20250317.0", + "nodemon": "^3.1.9", + "ts-node": "^10.9.2", + "wrangler": "^3.114.1" + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" +} diff --git a/backend/schema.sql b/backend/schema.sql new file mode 100644 index 0000000..5132e22 --- /dev/null +++ b/backend/schema.sql @@ -0,0 +1,120 @@ +DROP TABLE IF EXISTS status_page_agents; +DROP TABLE IF EXISTS status_page_monitors; +DROP TABLE IF EXISTS status_page_config; +DROP TABLE IF EXISTS monitor_status_history; +DROP TABLE IF EXISTS monitor_checks; +DROP TABLE IF EXISTS agents; +DROP TABLE IF EXISTS monitors; +DROP TABLE IF EXISTS users; + +-- 用户表 +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL, + email TEXT, + role TEXT NOT NULL DEFAULT 'viewer', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 监控表 +CREATE TABLE IF NOT EXISTS monitors ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + url TEXT NOT NULL, + method TEXT NOT NULL DEFAULT 'GET', + interval INTEGER NOT NULL DEFAULT 60, + timeout INTEGER NOT NULL DEFAULT 30, + expected_status INTEGER NOT NULL DEFAULT 200, + headers TEXT DEFAULT '{}', + body TEXT DEFAULT '', + created_by INTEGER NOT NULL, + active BOOLEAN NOT NULL DEFAULT 1, + status TEXT DEFAULT 'pending', + uptime REAL DEFAULT 100.0, + response_time INTEGER DEFAULT 0, + last_checked TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (created_by) REFERENCES users(id) +); + +-- 监控历史记录表 +CREATE TABLE IF NOT EXISTS monitor_checks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + monitor_id INTEGER NOT NULL, + status TEXT NOT NULL, + response_time INTEGER, + status_code INTEGER, + error TEXT, + checked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (monitor_id) REFERENCES monitors(id) +); + +-- 监控状态历史表(用于UI状态条展示) +CREATE TABLE IF NOT EXISTS monitor_status_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + monitor_id INTEGER NOT NULL, + status TEXT NOT NULL, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (monitor_id) REFERENCES monitors(id) +); + +-- 客户端表 +CREATE TABLE IF NOT EXISTS agents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + token TEXT NOT NULL UNIQUE, + created_by INTEGER NOT NULL, + status TEXT DEFAULT 'inactive', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + hostname TEXT, + ip_address TEXT, + os TEXT, + version TEXT, + cpu_usage REAL, + memory_total INTEGER, + memory_used INTEGER, + disk_total INTEGER, + disk_used INTEGER, + network_rx INTEGER, + network_tx INTEGER, + FOREIGN KEY (created_by) REFERENCES users(id) +); + +-- 状态页配置表 +CREATE TABLE IF NOT EXISTS status_page_config ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + title TEXT NOT NULL DEFAULT '系统状态', + description TEXT DEFAULT '系统当前运行状态', + logo_url TEXT DEFAULT '', + custom_css TEXT DEFAULT '', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) +); + +-- 状态页监控项关联表 +CREATE TABLE IF NOT EXISTS status_page_monitors ( + config_id INTEGER NOT NULL, + monitor_id INTEGER NOT NULL, + PRIMARY KEY (config_id, monitor_id), + FOREIGN KEY (config_id) REFERENCES status_page_config(id) ON DELETE CASCADE, + FOREIGN KEY (monitor_id) REFERENCES monitors(id) ON DELETE CASCADE +); + +-- 状态页客户端关联表 +CREATE TABLE IF NOT EXISTS status_page_agents ( + config_id INTEGER NOT NULL, + agent_id INTEGER NOT NULL, + PRIMARY KEY (config_id, agent_id), + FOREIGN KEY (config_id) REFERENCES status_page_config(id) ON DELETE CASCADE, + FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE +); + +-- 初始管理员用户 (密码: admin123) +INSERT OR IGNORE INTO users (id, username, password, role) +VALUES (1, 'admin', '$2a$10$X7o4c5/QR.uQr.1aDqUIO.VMbDwGy5ETSqMxE8tRP5oG5GQIMCg1W', 'admin'); \ No newline at end of file diff --git a/backend/src/db/schema.ts b/backend/src/db/schema.ts new file mode 100644 index 0000000..edca9b8 --- /dev/null +++ b/backend/src/db/schema.ts @@ -0,0 +1,40 @@ +// 用户表 +export const CREATE_USERS_TABLE = ` +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +) +`; + +// 监控表 +export const CREATE_MONITORS_TABLE = ` +CREATE TABLE IF NOT EXISTS monitors ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + url TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'unknown', + last_check TIMESTAMP, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +) +`; + +// 客户端表 +export const CREATE_AGENTS_TABLE = ` +CREATE TABLE IF NOT EXISTS agents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + cpu_usage INTEGER, + memory_total INTEGER, + memory_used INTEGER, + disk_total INTEGER, + disk_used INTEGER, + network_rx INTEGER, + network_tx INTEGER +) +`; \ No newline at end of file diff --git a/backend/src/index.ts b/backend/src/index.ts new file mode 100644 index 0000000..6ca29f8 --- /dev/null +++ b/backend/src/index.ts @@ -0,0 +1,106 @@ +import { Hono } from 'hono'; +import { logger } from 'hono/logger'; +import { cors } from 'hono/cors'; +import { jwt } from 'hono/jwt'; +import { Bindings } from './models/db'; +import { prettyJSON } from 'hono/pretty-json'; + +// 声明环境变量类型 +declare global { + namespace NodeJS { + interface ProcessEnv { + NODE_ENV?: string; + PORT?: string; + JWT_SECRET?: string; + } + } +} + +// 定义 D1 数据库类型 +interface D1Database { + prepare(query: string): D1PreparedStatement; + dump(): Promise; + batch(statements: D1PreparedStatement[]): Promise[]>; + exec(query: string): Promise>; +} + +interface D1PreparedStatement { + bind(...values: any[]): D1PreparedStatement; + first(colName?: string): Promise; + run(): Promise>; + all(): Promise>; + raw(): Promise; +} + +interface D1Result { + results?: T[]; + success: boolean; + error?: string; + meta?: object; +} + +// 导入路由 +import authRoutes from './routes/auth'; +import monitorRoutes from './routes/monitors'; +import agentRoutes from './routes/agents'; +import userRoutes from './routes/users'; +import statusRoutes from './routes/status'; +import initDbRoutes from './setup/database'; +import { monitorTask, runScheduledTasks } from './tasks'; + +// 创建Hono应用 +const app = new Hono<{ Bindings: Bindings }>(); + +// 中间件 +app.use('*', logger()); +app.use('*', cors({ + origin: ['http://localhost:5173', 'https://XUGOU.pages.dev'], // 允许前端开发服务器和生产环境 + credentials: true, +})); +app.use('*', prettyJSON()); + +// 公共路由 +app.get('/', (c) => c.json({ message: 'XUGOU API 服务正在运行' })); + +// 获取 JWT 密钥 +const getJwtSecret = (c: any) => { + // 在 Cloudflare Workers 环境中,使用 env 变量 + if (typeof process === 'undefined') { + return c.env.JWT_SECRET || 'your-secret-key-change-in-production'; + } + // 在 Node.js 环境中,使用 process.env + return process.env.JWT_SECRET || 'your-secret-key-change-in-production'; +}; + +// 路由注册 +app.route('/api/auth', authRoutes); +app.route('/api/monitors', monitorRoutes); +app.route('/api/agents', agentRoutes); +app.route('/api/users', userRoutes); +app.route('/api/status', statusRoutes); +app.route('/api', initDbRoutes); + +// 添加监控检查触发路由 +app.get('/api/trigger-check', async (c) => { + const { scheduled } = monitorTask; + if (scheduled) { + await scheduled(null, c.env, null); + } + return c.json({ success: true, message: '监控检查已触发' }); +}); + +// 导出 fetch 函数供 Cloudflare Workers 使用 +export default { + // 处理 HTTP 请求 + fetch: app.fetch.bind(app), + + // 添加定时任务,每分钟执行一次监控检查和客户端状态检查 + async scheduled(event: any, env: any, ctx: any) { + try { + // 执行所有定时任务 + await runScheduledTasks(event, env, ctx); + } catch (error) { + console.error('定时任务执行出错:', error); + } + } +}; \ No newline at end of file diff --git a/backend/src/models/agent.ts b/backend/src/models/agent.ts new file mode 100644 index 0000000..94b5e30 --- /dev/null +++ b/backend/src/models/agent.ts @@ -0,0 +1,23 @@ +// 客户端类型定义 +export interface Agent { + id: number; + name: string; + token: string; + created_by: number; + status: string; + created_at: string; + updated_at: string; + hostname?: string; + ip_address?: string; + os?: string; + version?: string; + + // 资源指标字段 + cpu_usage?: number; + memory_total?: number; + memory_used?: number; + disk_total?: number; + disk_used?: number; + network_rx?: number; + network_tx?: number; +} \ No newline at end of file diff --git a/backend/src/models/db.ts b/backend/src/models/db.ts new file mode 100644 index 0000000..e684037 --- /dev/null +++ b/backend/src/models/db.ts @@ -0,0 +1,28 @@ +// 通用数据库类型定义 +export interface D1Database { + prepare(query: string): D1PreparedStatement; + dump(): Promise; + batch(statements: D1PreparedStatement[]): Promise[]>; + exec(query: string): Promise>; +} + +export interface D1PreparedStatement { + bind(...values: any[]): D1PreparedStatement; + first(colName?: string): Promise; + run(): Promise>; + all(): Promise>; + raw(): Promise; +} + +export interface D1Result { + results?: T[]; + success: boolean; + error?: string; + meta?: object; +} + +// 通用绑定类型 +export type Bindings = { + DB: D1Database; + JWT_SECRET: string; +}; \ No newline at end of file diff --git a/backend/src/models/monitor.ts b/backend/src/models/monitor.ts new file mode 100644 index 0000000..f82803c --- /dev/null +++ b/backend/src/models/monitor.ts @@ -0,0 +1,39 @@ +// 监控类型定义 +export interface Monitor { + id: number; + name: string; + url: string; + method: string; + interval: number; + timeout: number; + expected_status: number; + headers: string; + body: string; + created_by: number; + active: boolean; + status: string; + uptime: number; + response_time: number; + last_checked: string; + created_at: string; + updated_at: string; +} + +// 监控历史记录类型 +export interface MonitorStatusHistory { + id: number; + monitor_id: number; + status: string; + timestamp: string; +} + +// 监控检查记录类型 +export interface MonitorCheck { + id: number; + monitor_id: number; + status: string; + response_time: number; + status_code: number; + error: string | null; + checked_at: string; +} \ No newline at end of file diff --git a/backend/src/routes/agents.ts b/backend/src/routes/agents.ts new file mode 100644 index 0000000..f4e00d0 --- /dev/null +++ b/backend/src/routes/agents.ts @@ -0,0 +1,564 @@ +import { Hono } from 'hono'; +import { jwt } from 'hono/jwt'; +import { Context, Next } from 'hono'; +import { Bindings } from '../models/db'; +import { Agent } from '../models/agent'; +import { getJwtSecret, generateToken } from '../utils/jwt'; + +const agents = new Hono<{ Bindings: Bindings; Variables: { agent: Agent; jwtPayload: any } }>(); + +// 中间件:JWT 认证 +agents.use('*', async (c, next) => { + // 跳过特定路由的认证 (客户端上报指标接口和注册接口) + if ((c.req.path.endsWith('/status') || c.req.path.endsWith('/register')) && c.req.method === 'POST') { + return next(); + } + + const jwtMiddleware = jwt({ + secret: getJwtSecret(c) + }); + return jwtMiddleware(c, next); +}); + +// 获取所有客户端 +agents.get('/', async (c) => { + try { + const payload = c.get('jwtPayload'); + + // 根据用户角色过滤客户端 + let result; + if (payload.role === 'admin') { + result = await c.env.DB.prepare( + 'SELECT * FROM agents ORDER BY created_at DESC' + ).all(); + } else { + result = await c.env.DB.prepare( + 'SELECT * FROM agents WHERE created_by = ? ORDER BY created_at DESC' + ).bind(payload.id).all(); + } + + return c.json({ success: true, agents: result.results || [] }); + } catch (error) { + console.error('获取客户端列表错误:', error); + return c.json({ + success: false, + message: '获取客户端列表失败', + error: error instanceof Error ? error.message : String(error) + }, 500); + } +}); + +// 创建新客户端 +agents.post('/', async (c) => { + try { + const { name } = await c.req.json(); + const payload = c.get('jwtPayload'); + + const token = await generateToken(); + const now = new Date().toISOString(); + + // 插入新客户端 + const result = await c.env.DB.prepare( + `INSERT INTO agents + (name, token, created_by, status, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?)` + ).bind( + name, + token, + payload.id, + 'inactive', + now, + now + ).run(); + + if (!result.success) { + throw new Error('创建客户端失败'); + } + + // 获取新创建的客户端 + const newAgent = await c.env.DB.prepare( + 'SELECT * FROM agents WHERE rowid = last_insert_rowid()' + ).first(); + + return c.json({ + success: true, + message: '客户端创建成功', + agent: newAgent // 创建时返回完整信息,包括令牌 + }, 201); + } catch (error) { + console.error('创建客户端错误:', error); + return c.json({ success: false, message: '创建客户端失败' }, 500); + } +}); + +// 获取单个客户端 +agents.get('/:id', async (c) => { + try { + const agentId = Number(c.req.param('id')); + const payload = c.get('jwtPayload'); + + // 获取客户端信息 + const agent = await c.env.DB.prepare( + `SELECT * FROM agents WHERE id = ?` + ).bind(agentId).first(); + + if (!agent) { + return c.json({ success: false, message: '客户端不存在' }, 404); + } + + // 检查权限 + if (payload.role !== 'admin' && agent.created_by !== payload.id) { + return c.json({ success: false, message: '无权访问此客户端' }, 403); + } + + // 不返回令牌,但保留其他所有字段 + const { token, ...rest } = agent; + + return c.json({ + success: true, + agent: { + ...rest, + cpu_usage: rest.cpu_usage || 0, + memory_total: rest.memory_total || 0, + memory_used: rest.memory_used || 0, + disk_total: rest.disk_total || 0, + disk_used: rest.disk_used || 0, + network_rx: rest.network_rx || 0, + network_tx: rest.network_tx || 0 + } + }); + } catch (error) { + console.error('获取客户端详情错误:', error); + return c.json({ success: false, message: '获取客户端详情失败' }, 500); + } +}); + +// 更新客户端信息 +agents.put('/:id', async (c) => { + try { + const agentId = Number(c.req.param('id')); + const payload = c.get('jwtPayload'); + + // 获取当前客户端数据 + const agent = await c.env.DB.prepare( + 'SELECT * FROM agents WHERE id = ?' + ).bind(agentId).first(); + + if (!agent) { + return c.json({ success: false, message: '客户端不存在' }, 404); + } + + // 检查权限 + if (payload.role !== 'admin' && agent.created_by !== payload.id) { + return c.json({ success: false, message: '无权修改此客户端' }, 403); + } + + // 获取更新数据 + const updateData = await c.req.json(); + const { name, hostname, ip_address, os, version, status } = updateData; + + // 准备更新的字段和值 + const fieldsToUpdate = []; + const values = []; + + if (name !== undefined) { + fieldsToUpdate.push('name = ?'); + values.push(name); + } + + if (hostname !== undefined) { + fieldsToUpdate.push('hostname = ?'); + values.push(hostname); + } + + if (ip_address !== undefined) { + fieldsToUpdate.push('ip_address = ?'); + values.push(ip_address); + } + + if (os !== undefined) { + fieldsToUpdate.push('os = ?'); + values.push(os); + } + + if (version !== undefined) { + fieldsToUpdate.push('version = ?'); + values.push(version); + } + + if (status !== undefined) { + fieldsToUpdate.push('status = ?'); + values.push(status); + } + + fieldsToUpdate.push('updated_at = ?'); + values.push(new Date().toISOString()); + + // 添加客户端ID作为最后一个参数 + values.push(agentId); + + // 如果没有要更新的字段,直接返回 + if (fieldsToUpdate.length === 1) { // 只有updated_at + return c.json({ + success: true, + message: '没有更新任何字段', + agent + }); + } + + // 执行更新 + const updateSql = ` + UPDATE agents + SET ${fieldsToUpdate.join(', ')} + WHERE id = ? + `; + + const result = await c.env.DB.prepare(updateSql).bind(...values).run(); + + if (!result.success) { + throw new Error('更新客户端失败'); + } + + // 获取更新后的客户端数据 + const updatedAgent = await c.env.DB.prepare( + 'SELECT * FROM agents WHERE id = ?' + ).bind(agentId).first(); + + return c.json({ + success: true, + message: '客户端信息已更新', + agent: updatedAgent + }); + } catch (error) { + console.error('更新客户端错误:', error); + return c.json({ success: false, message: '更新客户端失败' }, 500); + } +}); + +// 更新客户端状态 +agents.post('/:id/status', async (c) => { + try { + const agentId = Number(c.req.param('id')); + const { + cpu_usage, + memory_total, + memory_used, + disk_total, + disk_used, + network_rx, + network_tx, + hostname, + ip_address, + os, + version + } = await c.req.json(); + + // 更新客户端状态和资源指标 + const result = await c.env.DB.prepare( + `UPDATE agents SET + status = 'active', + cpu_usage = ?, + memory_total = ?, + memory_used = ?, + disk_total = ?, + disk_used = ?, + network_rx = ?, + network_tx = ?, + hostname = ?, + ip_address = ?, + os = ?, + version = ?, + updated_at = ? + WHERE id = ?` + ).bind( + cpu_usage, + memory_total, + memory_used, + disk_total, + disk_used, + network_rx, + network_tx, + hostname, + ip_address, + os, + version, + new Date().toISOString(), + agentId + ).run(); + + if (!result.success) { + throw new Error('更新客户端状态失败'); + } + + return c.json({ + success: true, + message: '客户端状态已更新' + }); + } catch (error) { + console.error('更新客户端状态错误:', error); + return c.json({ success: false, message: '更新客户端状态失败' }, 500); + } +}); + +// 删除客户端 +agents.delete('/:id', async (c) => { + try { + const agentId = Number(c.req.param('id')); + const payload = c.get('jwtPayload'); + + // 获取客户端信息 + const agent = await c.env.DB.prepare( + 'SELECT * FROM agents WHERE id = ?' + ).bind(agentId).first(); + + if (!agent) { + return c.json({ success: false, message: '客户端不存在' }, 404); + } + + // 检查权限 + if (payload.role !== 'admin' && agent.created_by !== payload.id) { + return c.json({ success: false, message: '无权删除此客户端' }, 403); + } + + // 执行删除客户端 + const result = await c.env.DB.prepare( + 'DELETE FROM agents WHERE id = ?' + ).bind(agent.id).run(); + + if (!result.success) { + throw new Error('删除客户端失败'); + } + + return c.json({ + success: true, + message: '客户端已删除' + }); + } catch (error) { + console.error('删除客户端错误:', error); + return c.json({ success: false, message: '删除客户端失败' }, 500); + } +}); + +// 重新生成客户端令牌 +agents.post('/:id/token', async (c) => { + try { + const agentId = Number(c.req.param('id')); + const payload = c.get('jwtPayload'); + + // 获取客户端信息 + const agent = await c.env.DB.prepare( + 'SELECT * FROM agents WHERE id = ?' + ).bind(agentId).first(); + + if (!agent) { + return c.json({ success: false, message: '客户端不存在' }, 404); + } + + // 检查权限 + if (payload.role !== 'admin' && agent.created_by !== payload.id) { + return c.json({ success: false, message: '无权为此客户端重新生成令牌' }, 403); + } + + // 生成新令牌 + const newToken = await generateToken(); + + // 更新客户端令牌 + const result = await c.env.DB.prepare( + 'UPDATE agents SET token = ?, updated_at = ? WHERE id = ?' + ).bind( + newToken, + new Date().toISOString(), + agent.id + ).run(); + + if (!result.success) { + throw new Error('更新客户端令牌失败'); + } + + return c.json({ + success: true, + message: '客户端令牌已重新生成', + token: newToken + }); + } catch (error) { + console.error('重新生成客户端令牌错误:', error); + return c.json({ success: false, message: '重新生成客户端令牌失败' }, 500); + } +}); + +// 生成临时注册Token +agents.post('/token/generate', async (c) => { + try { + const payload = c.get('jwtPayload'); + + // 生成新令牌 + const newToken = await generateToken(); + + // 可以选择将此token存储在临时表中,或者使用其他方式验证(例如,设置过期时间) + // 这里为简化操作,只返回令牌 + + return c.json({ + success: true, + message: '已生成客户端注册令牌', + token: newToken + }); + } catch (error) { + console.error('生成注册令牌错误:', error); + return c.json({ success: false, message: '生成注册令牌失败' }, 500); + } +}); + +// 客户端自注册接口 +agents.post('/register', async (c) => { + try { + const { token, name, hostname, ip_address, os, version } = await c.req.json(); + + if (!token) { + return c.json({ success: false, message: '缺少注册令牌' }, 400); + } + + // 查找管理员用户作为客户端创建者 + const adminUser = await c.env.DB.prepare( + 'SELECT id FROM users WHERE role = ?' + ).bind('admin').first<{id: number}>(); + + if (!adminUser) { + return c.json({ success: false, message: '无法找到管理员用户' }, 500); + } + + const now = new Date().toISOString(); + + // 查找是否已存在使用相同token的客户端 + const existingAgent = await c.env.DB.prepare( + 'SELECT id FROM agents WHERE token = ?' + ).bind(token).first(); + + if (existingAgent) { + return c.json({ + success: false, + message: '客户端已注册', + agent: existingAgent + }); + } + + // 如果客户端不存在,则创建新客户端 + const result = await c.env.DB.prepare( + `INSERT INTO agents + (name, token, created_by, status, created_at, updated_at, hostname, ip_address, os, version) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + ).bind( + name || 'New Agent', + token, + adminUser.id, + 'active', + now, + now, + hostname || null, + ip_address || null, + os || null, + version || null + ).run(); + + if (!result.success) { + throw new Error('创建客户端失败'); + } + + // 获取新创建的客户端 + const newAgent = await c.env.DB.prepare( + 'SELECT id FROM agents WHERE token = ?' + ).bind(token).first(); + + return c.json({ + success: true, + message: '客户端注册成功', + agent: newAgent + }, 201); + } catch (error) { + console.error('客户端注册错误:', error); + return c.json({ + success: false, + message: '客户端注册失败', + error: error instanceof Error ? error.message : String(error) + }, 500); + } +}); + +// 通过令牌更新客户端状态 +agents.post('/status', async (c) => { + try { + const { + token, + cpu_usage, + memory_total, + memory_used, + disk_total, + disk_used, + network_rx, + network_tx, + hostname, + ip_address, + os, + version + } = await c.req.json(); + + if (!token) { + return c.json({ success: false, message: '缺少API令牌' }, 400); + } + + // 通过token查找客户端 + const agent = await c.env.DB.prepare( + 'SELECT id FROM agents WHERE token = ?' + ).bind(token).first<{id: number}>(); + + if (!agent) { + return c.json({ success: false, message: '客户端不存在或令牌无效' }, 404); + } + + // 更新客户端状态和资源指标 + const result = await c.env.DB.prepare( + `UPDATE agents SET + status = 'active', + cpu_usage = ?, + memory_total = ?, + memory_used = ?, + disk_total = ?, + disk_used = ?, + network_rx = ?, + network_tx = ?, + hostname = ?, + ip_address = ?, + os = ?, + version = ?, + updated_at = ? + WHERE id = ?` + ).bind( + cpu_usage, + memory_total, + memory_used, + disk_total, + disk_used, + network_rx, + network_tx, + hostname, + ip_address, + os, + version, + new Date().toISOString(), + agent.id + ).run(); + + if (!result.success) { + throw new Error('更新客户端状态失败'); + } + + return c.json({ + success: true, + message: '客户端状态已更新' + }); + } catch (error) { + console.error('更新客户端状态错误:', error); + return c.json({ success: false, message: '更新客户端状态失败' }, 500); + } +}); + +export default agents; \ No newline at end of file diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts new file mode 100644 index 0000000..f18c348 --- /dev/null +++ b/backend/src/routes/auth.ts @@ -0,0 +1,170 @@ +import { Hono } from 'hono'; +import { jwt } from 'hono/jwt'; +import bcrypt from 'bcryptjs'; +import jsonwebtoken from 'jsonwebtoken'; +import { getJwtSecret } from '../utils/jwt'; + +// 导入 D1 数据库类型 +type Bindings = { + DB: D1Database; + JWT_SECRET: string; +}; + +interface D1Database { + prepare(query: string): D1PreparedStatement; + dump(): Promise; + batch(statements: D1PreparedStatement[]): Promise[]>; + exec(query: string): Promise>; +} + +interface D1PreparedStatement { + bind(...values: any[]): D1PreparedStatement; + first(colName?: string): Promise; + run(): Promise>; + all(): Promise>; + raw(): Promise; +} + +interface D1Result { + results?: T[]; + success: boolean; + error?: string; + meta?: object; +} + +// 用户类型定义 +interface User { + id: number; + username: string; + password: string; + email?: string; + role: string; + created_at?: string; + updated_at?: string; +} + +const auth = new Hono<{ Bindings: Bindings }>(); + +// 注册路由 +auth.post('/register', async (c) => { + try { + const { username, password, email } = await c.req.json(); + + // 检查用户名是否已存在 + const existingUser = await c.env.DB.prepare( + 'SELECT * FROM users WHERE username = ?' + ).bind(username).first(); + + if (existingUser) { + return c.json({ success: false, message: '用户名已存在' }, 400); + } + + // 加密密码 + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash(password, salt); + + // 创建新用户 + const result = await c.env.DB.prepare( + 'INSERT INTO users (username, password, email, role, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)' + ).bind( + username, + hashedPassword, + email || null, + 'user', + new Date().toISOString(), + new Date().toISOString() + ).run(); + + if (!result.success) { + throw new Error('数据库插入失败'); + } + + // 获取新创建的用户ID + const newUser = await c.env.DB.prepare( + 'SELECT id, username, role FROM users WHERE username = ?' + ).bind(username).first<{id: number, username: string, role: string}>(); + + return c.json({ + success: true, + message: '注册成功', + user: newUser + }, 201); + } catch (error) { + console.error('注册错误:', error); + return c.json({ success: false, message: '注册失败' }, 500); + } +}); + +// 登录路由 +auth.post('/login', async (c) => { + try { + const { username, password } = await c.req.json(); + + // 查找用户 + const user = await c.env.DB.prepare( + 'SELECT * FROM users WHERE username = ?' + ).bind(username).first(); + + if (!user) { + return c.json({ success: false, message: '用户名或密码错误' }, 401); + } + + // 验证密码 + const isPasswordValid = await bcrypt.compare(password, user.password); + if (!isPasswordValid) { + return c.json({ success: false, message: '用户名或密码错误' }, 401); + } + + // 生成JWT令牌 + const payload = { + id: user.id, + username: user.username, + role: user.role + }; + + const secret = getJwtSecret(c); + const token = jsonwebtoken.sign(payload, secret, { expiresIn: '24h' }); + + return c.json({ + success: true, + message: '登录成功', + token, + user: { id: user.id, username: user.username, role: user.role } + }); + } catch (error) { + console.error('登录错误:', error); + return c.json({ success: false, message: '登录失败' }, 500); + } +}); + +// 获取当前用户信息 +auth.use('/me', async (c, next) => { + const jwtMiddleware = jwt({ + secret: getJwtSecret(c) + }); + return jwtMiddleware(c, next); +}); + +auth.get('/me', async (c) => { + try { + const payload = c.get('jwtPayload'); + + const user = await c.env.DB.prepare( + 'SELECT id, username, email, role FROM users WHERE id = ?' + ).bind(payload.id).first<{id: number, username: string, email?: string, role: string}>(); + + if (!user) { + return c.json({ success: false, message: '用户不存在' }, 404); + } + + return c.json({ + success: true, + user + }); + } catch (error) { + console.error('获取用户信息错误:', error); + return c.json({ success: false, message: '获取用户信息失败' }, 500); + } +}); + +export default auth; \ No newline at end of file diff --git a/backend/src/routes/monitors.ts b/backend/src/routes/monitors.ts new file mode 100644 index 0000000..f557fd6 --- /dev/null +++ b/backend/src/routes/monitors.ts @@ -0,0 +1,520 @@ +import { Hono } from 'hono'; +import { jwt } from 'hono/jwt'; +import { Bindings } from '../models/db'; +import { Monitor, MonitorStatusHistory } from '../models/monitor'; +import { getJwtSecret } from '../utils/jwt'; + +const monitors = new Hono<{ Bindings: Bindings }>(); + +// 中间件:JWT 认证 +monitors.use('*', async (c, next) => { + const jwtMiddleware = jwt({ + secret: getJwtSecret(c) + }); + return jwtMiddleware(c, next); +}); + +// 获取所有监控 +monitors.get('/', async (c) => { + try { + const payload = c.get('jwtPayload'); + + // 根据用户角色过滤监控 + let result; + if (payload.role === 'admin') { + result = await c.env.DB.prepare( + 'SELECT * FROM monitors ORDER BY created_at DESC' + ).all(); + } else { + result = await c.env.DB.prepare( + 'SELECT * FROM monitors WHERE created_by = ? ORDER BY created_at DESC' + ).bind(payload.id).all(); + } + + // 获取所有监控的历史状态数据 + if (result.results && result.results.length > 0) { + const monitorsWithHistory = await Promise.all(result.results.map(async (monitor) => { + const historyResult = await c.env.DB.prepare( + `SELECT * FROM monitor_status_history + WHERE monitor_id = ? + ORDER BY timestamp ASC` + ).bind(monitor.id).all(); + + return { + ...monitor, + history: historyResult.results || [] + }; + })); + + return c.json({ success: true, monitors: monitorsWithHistory }); + } + + return c.json({ success: true, monitors: result.results }); + } catch (error) { + console.error('获取监控列表错误:', error); + return c.json({ success: false, message: '获取监控列表失败' }, 500); + } +}); + +// 获取单个监控 +monitors.get('/:id', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + + const monitor = await c.env.DB.prepare( + 'SELECT * FROM monitors WHERE id = ?' + ).bind(id).first(); + + if (!monitor) { + return c.json({ success: false, message: '监控不存在' }, 404); + } + + // 检查权限 + if (payload.role !== 'admin' && monitor.created_by !== payload.id) { + return c.json({ success: false, message: '无权访问此监控' }, 403); + } + + // 获取历史状态数据 + const historyResult = await c.env.DB.prepare( + `SELECT * FROM monitor_status_history + WHERE monitor_id = ? + ORDER BY timestamp ASC` + ).bind(id).all(); + + // 获取最近的检查历史记录 + const checksResult = await c.env.DB.prepare( + `SELECT * FROM monitor_checks + WHERE monitor_id = ? + ORDER BY checked_at DESC + LIMIT 5` + ).bind(id).all(); + + return c.json({ + success: true, + monitor: { + ...monitor, + history: historyResult.results || [], + checks: checksResult.results || [] + } + }); + } catch (error) { + console.error('获取监控详情错误:', error); + return c.json({ success: false, message: '获取监控详情失败' }, 500); + } +}); + +// 创建监控 +monitors.post('/', async (c) => { + try { + const payload = c.get('jwtPayload'); + const data = await c.req.json(); + + // 验证必填字段 + if (!data.name || !data.url || !data.method) { + return c.json({ success: false, message: '缺少必填字段' }, 400); + } + + const now = new Date().toISOString(); + + // 将 headers 对象转换为 JSON 字符串 + const headers = data.headers ? JSON.stringify(data.headers) : '{}'; + + // 插入新监控 + const result = await c.env.DB.prepare( + `INSERT INTO monitors + (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + ).bind( + data.name, + data.url, + data.method, + data.interval || 60, + data.timeout || 30, + data.expectedStatus || 200, + headers, + data.body || '', + payload.id, + true, + 'pending', + 100.0, + 0, + null, + now, + now + ).run(); + + if (!result.success) { + throw new Error('创建监控失败'); + } + + // 获取新创建的监控 + const newMonitor = await c.env.DB.prepare( + 'SELECT * FROM monitors WHERE rowid = last_insert_rowid()' + ).first(); + + return c.json({ success: true, monitor: newMonitor }, 201); + } catch (error) { + console.error('创建监控错误:', error); + return c.json({ success: false, message: '创建监控失败' }, 500); + } +}); + +// 更新监控 +monitors.put('/:id', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + const data = await c.req.json(); + + // 检查监控是否存在 + const monitor = await c.env.DB.prepare( + 'SELECT * FROM monitors WHERE id = ?' + ).bind(id).first(); + + if (!monitor) { + return c.json({ success: false, message: '监控不存在' }, 404); + } + + // 检查权限 + if (payload.role !== 'admin' && monitor.created_by !== payload.id) { + return c.json({ success: false, message: '无权修改此监控' }, 403); + } + + // 准备更新数据 + const updates = []; + const values = []; + + if (data.name !== undefined) { + updates.push('name = ?'); + values.push(data.name); + } + + if (data.url !== undefined) { + updates.push('url = ?'); + values.push(data.url); + } + + if (data.method !== undefined) { + updates.push('method = ?'); + values.push(data.method); + } + + if (data.interval !== undefined) { + updates.push('interval = ?'); + values.push(data.interval); + } + + if (data.timeout !== undefined) { + updates.push('timeout = ?'); + values.push(data.timeout); + } + + if (data.expectedStatus !== undefined) { + updates.push('expected_status = ?'); + values.push(data.expectedStatus); + } + + if (data.headers !== undefined) { + updates.push('headers = ?'); + values.push(JSON.stringify(data.headers)); + } + + if (data.body !== undefined) { + updates.push('body = ?'); + values.push(data.body); + } + + if (data.active !== undefined) { + updates.push('active = ?'); + values.push(data.active); + } + + if (data.status !== undefined) { + updates.push('status = ?'); + values.push(data.status); + } + + if (data.uptime !== undefined) { + updates.push('uptime = ?'); + values.push(data.uptime); + } + + if (data.responseTime !== undefined) { + updates.push('response_time = ?'); + values.push(data.responseTime); + } + + if (data.lastChecked !== undefined) { + updates.push('last_checked = ?'); + values.push(data.lastChecked); + } + + updates.push('updated_at = ?'); + values.push(new Date().toISOString()); + + // 添加 ID 作为 WHERE 条件 + values.push(id); + + // 执行更新 + const result = await c.env.DB.prepare( + `UPDATE monitors SET ${updates.join(', ')} WHERE id = ?` + ).bind(...values).run(); + + if (!result.success) { + throw new Error('更新监控失败'); + } + + // 获取更新后的监控 + const updatedMonitor = await c.env.DB.prepare( + 'SELECT * FROM monitors WHERE id = ?' + ).bind(id).first(); + + return c.json({ success: true, monitor: updatedMonitor }); + } catch (error) { + console.error('更新监控错误:', error); + return c.json({ success: false, message: '更新监控失败' }, 500); + } +}); + +// 删除监控 +monitors.delete('/:id', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + + // 检查监控是否存在 + const monitor = await c.env.DB.prepare( + 'SELECT * FROM monitors WHERE id = ?' + ).bind(id).first(); + + if (!monitor) { + return c.json({ success: false, message: '监控不存在' }, 404); + } + + // 检查权限 + if (payload.role !== 'admin' && monitor.created_by !== payload.id) { + return c.json({ success: false, message: '无权删除此监控' }, 403); + } + + // 先删除关联的历史数据 + await c.env.DB.prepare( + 'DELETE FROM monitor_status_history WHERE monitor_id = ?' + ).bind(id).run(); + + await c.env.DB.prepare( + 'DELETE FROM monitor_checks WHERE monitor_id = ?' + ).bind(id).run(); + + // 执行删除 + const result = await c.env.DB.prepare( + 'DELETE FROM monitors WHERE id = ?' + ).bind(id).run(); + + if (!result.success) { + throw new Error('删除监控失败'); + } + + return c.json({ success: true, message: '监控已删除' }); + } catch (error) { + console.error('删除监控错误:', error); + return c.json({ success: false, message: '删除监控失败' }, 500); + } +}); + +// 获取监控状态历史 +monitors.get('/:id/history', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + + // 检查监控是否存在 + const monitor = await c.env.DB.prepare( + 'SELECT * FROM monitors WHERE id = ?' + ).bind(id).first(); + + if (!monitor) { + return c.json({ success: false, message: '监控不存在' }, 404); + } + + // 检查权限 + if (payload.role !== 'admin' && monitor.created_by !== payload.id) { + return c.json({ success: false, message: '无权访问此监控历史' }, 403); + } + + // 获取历史状态 + const historyResult = await c.env.DB.prepare( + `SELECT * FROM monitor_status_history + WHERE monitor_id = ? + ORDER BY timestamp ASC` + ).bind(id).all(); + + return c.json({ + success: true, + history: historyResult.results || [] + }); + } catch (error) { + console.error('获取监控历史错误:', error); + return c.json({ success: false, message: '获取监控历史失败' }, 500); + } +}); + +// 获取监控检查记录 +monitors.get('/:id/checks', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + const limit = parseInt(c.req.query('limit') || '10'); + + // 检查监控是否存在 + const monitor = await c.env.DB.prepare( + 'SELECT * FROM monitors WHERE id = ?' + ).bind(id).first(); + + if (!monitor) { + return c.json({ success: false, message: '监控不存在' }, 404); + } + + // 检查权限 + if (payload.role !== 'admin' && monitor.created_by !== payload.id) { + return c.json({ success: false, message: '无权访问此监控检查记录' }, 403); + } + + // 获取检查记录 + const checksResult = await c.env.DB.prepare( + `SELECT * FROM monitor_checks + WHERE monitor_id = ? + ORDER BY checked_at DESC + LIMIT ?` + ).bind(id, limit).all(); + + return c.json({ + success: true, + checks: checksResult.results || [] + }); + } catch (error) { + console.error('获取监控检查记录错误:', error); + return c.json({ success: false, message: '获取监控检查记录失败' }, 500); + } +}); + +// 手动检查单个监控 +monitors.post('/:id/check', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + + // 检查监控是否存在 + const monitor = await c.env.DB.prepare( + 'SELECT * FROM monitors WHERE id = ?' + ).bind(id).first(); + + if (!monitor) { + return c.json({ success: false, message: '监控不存在' }, 404); + } + + // 检查权限 + if (payload.role !== 'admin' && monitor.created_by !== payload.id) { + return c.json({ success: false, message: '无权访问此监控' }, 403); + } + + // 触发监控检查 + const startTime = Date.now(); + let response; + let error = null; + + // 解析 headers + let headers = {}; + try { + headers = JSON.parse(monitor.headers || '{}'); + } catch (e) { + console.error('解析请求头错误:', e); + } + + // 执行请求 + try { + const controller = new AbortController(); + // 设置超时 + const timeoutId = setTimeout(() => controller.abort(), monitor.timeout * 1000); + + response = await fetch(monitor.url, { + method: monitor.method, + headers: headers, + body: monitor.method !== 'GET' && monitor.method !== 'HEAD' ? monitor.body : undefined, + signal: controller.signal + }); + + clearTimeout(timeoutId); + } catch (e) { + error = e instanceof Error ? e.message : String(e); + } + + const responseTime = Date.now() - startTime; + const now = new Date().toISOString(); + let status = 'down'; + + // 判断状态 + if (response && response.status === monitor.expected_status) { + status = 'up'; + } + + // 更新监控状态 + await c.env.DB.prepare(` + UPDATE monitors + SET status = ?, response_time = ?, last_checked = ? + WHERE id = ? + `).bind( + status, + responseTime, + now, + monitor.id + ).run(); + + // 添加检查记录 + await c.env.DB.prepare(` + INSERT INTO monitor_checks + (monitor_id, status, response_time, status_code, error, checked_at) + VALUES (?, ?, ?, ?, ?, ?) + `).bind( + monitor.id, + status, + responseTime, + response ? response.status : null, + error, + now + ).run(); + + // 添加状态历史记录(仅当状态变化时) + if (monitor.status !== status) { + await c.env.DB.prepare(` + INSERT INTO monitor_status_history + (monitor_id, status, timestamp) + VALUES (?, ?, ?) + `).bind( + monitor.id, + status, + now + ).run(); + } + + return c.json({ + success: true, + message: '监控检查完成', + result: { + monitor_id: monitor.id, + name: monitor.name, + status, + previous_status: monitor.status, + response_time: responseTime, + status_code: response ? response.status : null, + error, + checked_at: now + } + }); + } catch (error) { + console.error('手动检查监控错误:', error); + return c.json({ success: false, message: '手动检查监控失败' }, 500); + } +}); + +export default monitors; \ No newline at end of file diff --git a/backend/src/routes/status.ts b/backend/src/routes/status.ts new file mode 100644 index 0000000..3399194 --- /dev/null +++ b/backend/src/routes/status.ts @@ -0,0 +1,610 @@ +import { Hono } from 'hono'; +import { jwt } from 'hono/jwt'; +import { Bindings } from '../models/db'; +import { getJwtSecret } from '../utils/jwt'; + +// 状态页配置接口定义 +interface StatusPageConfig { + title: string; + description: string; + logoUrl: string; + customCss: string; + monitors: Array<{ + id: number; + name: string; + selected: boolean; + }>; + agents: Array<{ + id: number; + name: string; + selected: boolean; + }>; +} + +// 监控项接口 +interface Monitor { + id: number; + name: string; + url: string; + method: string; + interval: number; + timeout: number; + expected_status: number; + headers: string; + body: string; + created_by: number; + active: boolean; + status: string; + uptime: number; + response_time: number; + last_checked?: string; + created_at: string; + updated_at: string; + history?: string[]; +} + +// 客户端接口 +interface Agent { + id: number; + name: string; + token: string; + created_by: number; + created_at: string; + updated_at: string; + status?: string; + cpu?: number; + memory?: number; + disk?: number; + network_rx?: number; + network_tx?: number; + hostname?: string; + ip_address?: string; + os?: string; + version?: string; +} + +// 数据库中的状态页配置记录 +interface DbStatusPageConfig { + id?: number; + user_id: number; + title: string; + description: string; + logo_url: string; + custom_css: string; +} + +// 数据库中的监控项记录 +interface DbMonitorItem { + monitor_id: number; +} + +// 数据库中的客户端记录 +interface DbAgentItem { + agent_id: number; +} + +// 创建 Hono 路由 +const app = new Hono<{ Bindings: Bindings }>(); + +// 保护管理员路由 +const adminRoutes = new Hono<{ Bindings: Bindings }>() + .use('*', async (c, next) => { + try { + const jwtMiddleware = jwt({ + secret: getJwtSecret(c) + }); + await jwtMiddleware(c, next); + + const payload = c.get('jwtPayload'); + if (!payload || !payload.id) { + return c.json({ error: '未授权' }, 401); + } + + // 这里不再调用next(),防止重复调用 + } catch (error) { + console.error('JWT认证错误:', error); + return c.json({ error: '认证失败' }, 401); + } + }); + +// 获取状态页配置 +adminRoutes.get('/config', async (c) => { + const payload = c.get('jwtPayload'); + const userId = payload.id; + + try { + // 获取用户的状态页配置 + const configResult = await c.env.DB.prepare( + 'SELECT * FROM status_page_config WHERE user_id = ?' + ).bind(userId).all(); + + let config: DbStatusPageConfig | null = null; + if (configResult.results && configResult.results.length > 0) { + config = configResult.results[0]; + } + + // 获取配置的监控项 + const monitorsResult = await c.env.DB.prepare( + 'SELECT m.id, m.name, CASE WHEN spm.monitor_id IS NOT NULL THEN 1 ELSE 0 END as selected ' + + 'FROM monitors m ' + + 'LEFT JOIN status_page_monitors spm ON m.id = spm.monitor_id AND spm.config_id = ? ' + + 'WHERE m.created_by = ?' + ).bind(config?.id || 0, userId).all<{id: number, name: string, selected: number}>(); + + // 获取配置的客户端 + const agentsResult = await c.env.DB.prepare( + 'SELECT a.id, a.name, CASE WHEN spa.agent_id IS NOT NULL THEN 1 ELSE 0 END as selected ' + + 'FROM agents a ' + + 'LEFT JOIN status_page_agents spa ON a.id = spa.agent_id AND spa.config_id = ? ' + + 'WHERE a.created_by = ?' + ).bind(config?.id || 0, userId).all<{id: number, name: string, selected: number}>(); + + // 构建响应 + const response: StatusPageConfig = { + title: config?.title || '系统状态', + description: config?.description || '当前系统运行状态', + logoUrl: config?.logo_url || '', + customCss: config?.custom_css || '', + monitors: monitorsResult.results?.map(m => ({ + id: m.id, + name: m.name, + selected: m.selected === 1 + })) || [], + agents: agentsResult.results?.map(a => ({ + id: a.id, + name: a.name, + selected: a.selected === 1 + })) || [] + }; + + return c.json(response); + } catch (error) { + console.error('获取状态页配置失败:', error); + return c.json({ error: '获取状态页配置失败' }, 500); + } +}); + +// 保存状态页配置 +adminRoutes.post('/config', async (c) => { + const payload = c.get('jwtPayload'); + const userId = payload.id; + const data = await c.req.json() as StatusPageConfig; + + console.log('接收到的配置数据:', JSON.stringify(data)); + + if (!data) { + console.log('无效的请求数据'); + return c.json({ error: '无效的请求数据' }, 400); + } + + try { + // 检查是否已存在配置 + const existingConfig = await c.env.DB.prepare( + 'SELECT id FROM status_page_config WHERE user_id = ?' + ).bind(userId).first<{id: number}>(); + + console.log('现有配置查询结果:', existingConfig); + + let configId: number; + + if (existingConfig && existingConfig.id) { + // 更新现有配置 + console.log('更新现有配置ID:', existingConfig.id); + await c.env.DB.prepare( + 'UPDATE status_page_config SET title = ?, description = ?, logo_url = ?, custom_css = ? WHERE id = ?' + ).bind( + data.title, + data.description, + data.logoUrl, + data.customCss, + existingConfig.id + ).run(); + + configId = existingConfig.id; + } else { + // 创建新配置 + console.log('创建新配置'); + const insertResult = await c.env.DB.prepare( + 'INSERT INTO status_page_config (user_id, title, description, logo_url, custom_css) VALUES (?, ?, ?, ?, ?)' + ).bind( + userId, + data.title, + data.description, + data.logoUrl, + data.customCss + ).run(); + + console.log('插入配置结果:', insertResult); + + if (!insertResult.success) { + throw new Error('创建状态页配置失败'); + } + + // 获取新插入的ID + const lastInsertId = await c.env.DB.prepare('SELECT last_insert_rowid() as id').first<{ id: number }>(); + console.log('获取的最后插入ID:', lastInsertId); + + if (!lastInsertId || typeof lastInsertId.id !== 'number') { + throw new Error('获取配置ID失败'); + } + + configId = lastInsertId.id; + } + + // 清除现有的监控项关联 + console.log('清除配置ID的现有监控关联:', configId); + const deleteMonitorsResult = await c.env.DB.prepare( + 'DELETE FROM status_page_monitors WHERE config_id = ?' + ).bind(configId).run(); + console.log('删除现有监控关联结果:', deleteMonitorsResult); + + // 清除现有的客户端关联 + console.log('清除配置ID的现有客户端关联:', configId); + const deleteAgentsResult = await c.env.DB.prepare( + 'DELETE FROM status_page_agents WHERE config_id = ?' + ).bind(configId).run(); + console.log('删除现有客户端关联结果:', deleteAgentsResult); + + // 添加选定的监控项 + console.log('接收到的监控项IDs:', data.monitors); + if (Array.isArray(data.monitors) && data.monitors.length > 0) { + console.log(`添加 ${data.monitors.length} 个监控项`); + for (const monitorId of data.monitors) { + const insertResult = await c.env.DB.prepare( + 'INSERT INTO status_page_monitors (config_id, monitor_id) VALUES (?, ?)' + ).bind(configId, monitorId).run(); + console.log(`添加监控项 ${monitorId} 结果:`, insertResult); + } + } else { + console.log('没有选中的监控项需要添加'); + } + + // 添加选定的客户端 + console.log('接收到的客户端IDs:', data.agents); + if (Array.isArray(data.agents) && data.agents.length > 0) { + console.log(`添加 ${data.agents.length} 个客户端`); + for (const agentId of data.agents) { + const insertResult = await c.env.DB.prepare( + 'INSERT INTO status_page_agents (config_id, agent_id) VALUES (?, ?)' + ).bind(configId, agentId).run(); + console.log(`添加客户端 ${agentId} 结果:`, insertResult); + } + } else { + console.log('没有选中的客户端需要添加'); + } + + return c.json({ success: true, configId }); + } catch (error) { + console.error('保存状态页配置失败:', error); + return c.json({ error: '保存状态页配置失败' }, 500); + } +}); + +// 公共路由 +// 获取状态页数据(公开访问) +app.get('/data', async (c) => { + try { + // 获取所有配置 + console.log('获取状态页配置...'); + const configsResult = await c.env.DB.prepare( + 'SELECT * FROM status_page_config' + ).all(); + + console.log('状态页配置查询结果:', JSON.stringify(configsResult)); + + if (!configsResult.results || configsResult.results.length === 0) { + console.log('未找到状态页配置,返回默认配置'); + + // 尝试创建一个默认配置 + const defaultConfig = await createDefaultConfig(c); + if (defaultConfig) { + return c.json({ + success: true, + data: defaultConfig + }); + } + + return c.json({ + success: true, + data: { + title: '系统状态', + description: '实时监控系统状态', + logoUrl: '', + customCss: '', + monitors: [], + agents: [] + } + }); + } + + // 简单处理:获取第一个配置 + const config = configsResult.results[0]; + console.log('找到状态页配置:', config); + + // 获取选中的监控项 + const selectedMonitors = await c.env.DB.prepare( + 'SELECT monitor_id FROM status_page_monitors WHERE config_id = ?' + ).bind(config.id).all<{ monitor_id: number }>(); + + console.log('选中的监控项:', JSON.stringify(selectedMonitors)); + + // 获取选中的客户端 + const selectedAgents = await c.env.DB.prepare( + 'SELECT agent_id FROM status_page_agents WHERE config_id = ?' + ).bind(config.id).all<{ agent_id: number }>(); + + console.log('选中的客户端:', JSON.stringify(selectedAgents)); + + // 获取监控项详细信息 + let monitors: Monitor[] = []; + if (selectedMonitors.results && selectedMonitors.results.length > 0) { + const monitorIds = selectedMonitors.results.map(m => m.monitor_id); + const placeholders = monitorIds.map(() => '?').join(','); + + console.log(`查询监控项详情, IDs: ${monitorIds}, SQL占位符: ${placeholders}`); + + if (monitorIds.length > 0) { + const monitorsResult = await c.env.DB.prepare( + `SELECT * FROM monitors WHERE id IN (${placeholders})` + ).bind(...monitorIds).all(); + + console.log('监控项查询结果:', JSON.stringify(monitorsResult)); + + if (monitorsResult.results) { + // 获取每个监控的历史记录 + monitors = await Promise.all(monitorsResult.results.map(async (monitor) => { + const historyResult = await c.env.DB.prepare( + `SELECT status, timestamp + FROM monitor_status_history + WHERE monitor_id = ? + ORDER BY timestamp DESC + LIMIT 24` + ).bind(monitor.id).all<{status: string, timestamp: string}>(); + + // 将历史记录转换为状态数组 + const history = historyResult.results + ? historyResult.results.map(h => h.status) + : Array(24).fill('unknown'); + + return { + ...monitor, + history + }; + })); + } + } + } else { + console.log('没有配置的监控项,返回空列表'); + } + + // 获取客户端详细信息 + let agents: Agent[] = []; + if (selectedAgents.results && selectedAgents.results.length > 0) { + const agentIds = selectedAgents.results.map(a => a.agent_id); + const placeholders = agentIds.map(() => '?').join(','); + + console.log(`查询客户端详情, IDs: ${agentIds}, SQL占位符: ${placeholders}`); + + if (agentIds.length > 0) { + const agentsResult = await c.env.DB.prepare( + `SELECT * FROM agents WHERE id IN (${placeholders})` + ).bind(...agentIds).all(); + + console.log('客户端查询结果:', JSON.stringify(agentsResult)); + + if (agentsResult.results) { + agents = agentsResult.results; + } + } + } else { + // 修改: 不再自动获取所有客户端并关联 + console.log('没有配置的客户端,返回空列表'); + // 这里不再获取所有活跃客户端和自动关联 + } + + // 构建并返回响应 + console.log('返回状态页数据:', { + monitors: monitors.length, + agents: agents.length + }); + + // 为监控项添加必要的字段 + const enrichedMonitors = monitors.map((monitor: any) => ({ + ...monitor, + status: monitor.status || 'unknown', + uptime: monitor.uptime || 0, + response_time: monitor.response_time || 0, + history: monitor.history || Array(24).fill('unknown') + })); + + // 为客户端添加资源使用情况字段 + const enrichedAgents = agents.map((agent: any) => { + // 计算内存使用百分比 (如果有总量和使用量) + const memoryPercent = agent.memory_total && agent.memory_used + ? (agent.memory_used / agent.memory_total) * 100 + : null; + + // 计算磁盘使用百分比 (如果有总量和使用量) + const diskPercent = agent.disk_total && agent.disk_used + ? (agent.disk_used / agent.disk_total) * 100 + : null; + + return { + ...agent, + // 使用数据库中的 status 字段,不重新计算 + cpu: agent.cpu_usage || 0, // 使用数据库中的CPU使用率 + memory: memoryPercent || 0, // 使用数据库中的内存使用百分比 + disk: diskPercent || 0, // 使用数据库中的磁盘使用百分比 + network_rx: agent.network_rx || 0, // 使用数据库中的网络下载速率 + network_tx: agent.network_tx || 0, // 使用数据库中的网络上传速率 + hostname: agent.hostname || "未知主机", + ip_address: agent.ip_address || "0.0.0.0", + os: agent.os || "未知系统", + version: agent.version || "未知版本" + }; + }); + + return c.json({ + success: true, + data: { + title: config.title, + description: config.description, + logoUrl: config.logo_url, + customCss: config.custom_css, + monitors: enrichedMonitors, + agents: enrichedAgents.map(agent => ({ + id: agent.id, + name: agent.name, + status: agent.status, + cpu: agent.cpu, + memory: agent.memory, + disk: agent.disk, + network_rx: agent.network_rx, + network_tx: agent.network_tx, + hostname: agent.hostname, + ip_address: agent.ip_address, + os: agent.os, + version: agent.version + })) + } + }); + } catch (error) { + console.error('获取状态页数据失败:', error); + return c.json({ success: false, message: '获取状态页数据失败', error: String(error) }, 500); + } +}); + +// 创建默认配置 +async function createDefaultConfig(c: any) { + try { + console.log('创建默认状态页配置'); + + // 查找管理员账户 + const admin = await c.env.DB.prepare( + 'SELECT id FROM users WHERE role = ?' + ).bind('admin').first(); + + if (!admin) { + console.log('未找到管理员账户'); + return null; + } + + // 创建默认配置 + const insertResult = await c.env.DB.prepare( + 'INSERT INTO status_page_config (user_id, title, description, logo_url, custom_css) VALUES (?, ?, ?, ?, ?)' + ).bind( + admin.id, + '系统状态', + '实时监控系统状态', + '', + '' + ).run(); + + if (!insertResult.success) { + console.log('创建默认配置失败'); + return null; + } + + // 获取新插入的ID + const lastInsertId = await c.env.DB.prepare('SELECT last_insert_rowid() as id').first(); + if (!lastInsertId || typeof lastInsertId.id !== 'number') { + console.log('获取配置ID失败'); + return null; + } + + const configId = lastInsertId.id; + console.log(`创建的默认配置ID: ${configId}`); + + // 获取所有监控项目 + let monitors; + try { + // 获取监控项 + monitors = await c.env.DB.prepare( + 'SELECT * FROM monitors WHERE active = 1' + ).all(); + } catch (error) { + console.error('获取监控项失败:', error); + monitors = { results: [] }; + } + + // 获取所有客户端 + let agents; + try { + // 获取所有客户端 + agents = await c.env.DB.prepare( + 'SELECT * FROM agents' + ).all(); + } catch (error) { + console.error('获取客户端失败:', error); + agents = { results: [] }; + } + + // 关联监控项 + if (monitors.results && monitors.results.length > 0) { + console.log(`找到 ${monitors.results.length} 个活跃监控项`); + for (const monitor of monitors.results) { + await c.env.DB.prepare( + 'INSERT INTO status_page_monitors (config_id, monitor_id) VALUES (?, ?)' + ).bind(configId, monitor.id).run(); + } + } + + // 关联客户端 + if (agents.results && agents.results.length > 0) { + console.log(`找到 ${agents.results.length} 个活跃客户端`); + for (const agent of agents.results) { + await c.env.DB.prepare( + 'INSERT INTO status_page_agents (config_id, agent_id) VALUES (?, ?)' + ).bind(configId, agent.id).run(); + } + } + + // 返回创建的配置 + return { + title: '系统状态', + description: '实时监控系统状态', + logoUrl: '', + customCss: '', + monitors: monitors.results ? monitors.results.map((monitor: any) => ({ + ...monitor, + status: monitor.status || 'unknown', + uptime: monitor.uptime || 0, + response_time: monitor.response_time || 0, + history: Array(24).fill('unknown') + })) : [], + agents: agents.results ? agents.results.map((agent: any) => { + // 计算内存使用百分比 (如果有总量和使用量) + const memoryPercent = agent.memory_total && agent.memory_used + ? (agent.memory_used / agent.memory_total) * 100 + : null; + + // 计算磁盘使用百分比 (如果有总量和使用量) + const diskPercent = agent.disk_total && agent.disk_used + ? (agent.disk_used / agent.disk_total) * 100 + : null; + + return { + ...agent, + // 使用数据库中的 status 字段,不重新计算 + cpu: agent.cpu_usage || 0, // 使用数据库中的CPU使用率 + memory: memoryPercent || 0, // 使用数据库中的内存使用百分比 + disk: diskPercent || 0, // 使用数据库中的磁盘使用百分比 + network_rx: agent.network_rx || 0, // 使用数据库中的网络下载速率 + network_tx: agent.network_tx || 0, // 使用数据库中的网络上传速率 + hostname: agent.hostname || "未知主机", + ip_address: agent.ip_address || "0.0.0.0", + os: agent.os || "未知系统", + version: agent.version || "未知版本" + }; + }) : [] + }; + } catch (error) { + console.error('创建默认配置失败:', error); + return null; + } +} + +// 注册管理员路由 +app.route('/', adminRoutes); + +export default app; \ No newline at end of file diff --git a/backend/src/routes/users.ts b/backend/src/routes/users.ts new file mode 100644 index 0000000..1ca3868 --- /dev/null +++ b/backend/src/routes/users.ts @@ -0,0 +1,341 @@ +import { Hono } from 'hono'; +import { jwt } from 'hono/jwt'; +import { compare, hash } from 'bcryptjs'; +import { Bindings } from '../models/db'; +import { getJwtSecret } from '../utils/jwt'; + +// 用户类型定义 +interface User { + id: number; + username: string; + password: string; + email: string | null; + role: string; + created_at: string; + updated_at: string; +} + +const users = new Hono<{ Bindings: Bindings }>(); + +// 中间件:JWT 认证 +users.use('*', async (c, next) => { + const jwtMiddleware = jwt({ + secret: getJwtSecret(c) + }); + return jwtMiddleware(c, next); +}); + +// 获取所有用户(仅管理员) +users.get('/', async (c) => { + try { + const payload = c.get('jwtPayload'); + + // 检查权限 + if (payload.role !== 'admin') { + return c.json({ success: false, message: '无权访问用户列表' }, 403); + } + + // 查询所有用户,不包括密码 + const result = await c.env.DB.prepare( + 'SELECT id, username, email, role, created_at, updated_at FROM users ORDER BY id' + ).all>(); + + return c.json({ success: true, users: result.results }); + } catch (error) { + console.error('获取用户列表错误:', error); + return c.json({ success: false, message: '获取用户列表失败' }, 500); + } +}); + +// 获取单个用户 +users.get('/:id', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + + // 检查权限(仅允许管理员或用户本人) + if (payload.role !== 'admin' && payload.id !== id) { + return c.json({ success: false, message: '无权访问此用户信息' }, 403); + } + + // 查询用户,不包括密码 + const user = await c.env.DB.prepare( + 'SELECT id, username, email, role, created_at, updated_at FROM users WHERE id = ?' + ).bind(id).first>(); + + if (!user) { + return c.json({ success: false, message: '用户不存在' }, 404); + } + + return c.json({ success: true, user }); + } catch (error) { + console.error('获取用户详情错误:', error); + return c.json({ success: false, message: '获取用户详情失败' }, 500); + } +}); + +// 创建用户(仅管理员) +users.post('/', async (c) => { + try { + const payload = c.get('jwtPayload'); + + // 检查权限 + if (payload.role !== 'admin') { + return c.json({ success: false, message: '无权创建用户' }, 403); + } + + const data = await c.req.json(); + + // 验证必填字段 + if (!data.username || !data.password || !data.role) { + return c.json({ success: false, message: '缺少必填字段' }, 400); + } + + // 检查用户名是否已存在 + const existingUser = await c.env.DB.prepare( + 'SELECT id FROM users WHERE username = ?' + ).bind(data.username).first(); + + if (existingUser) { + return c.json({ success: false, message: '用户名已存在' }, 400); + } + + // 检查角色是否有效 + const validRoles = ['admin', 'manager', 'viewer']; + if (!validRoles.includes(data.role)) { + return c.json({ success: false, message: '无效的角色' }, 400); + } + + // 哈希密码 + const hashedPassword = await hash(data.password, 10); + const now = new Date().toISOString(); + + // 插入新用户 + const result = await c.env.DB.prepare( + `INSERT INTO users (username, password, email, role, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?)` + ).bind( + data.username, + hashedPassword, + data.email || null, + data.role, + now, + now + ).run(); + + if (!result.success) { + throw new Error('创建用户失败'); + } + + // 获取新创建的用户 + const newUser = await c.env.DB.prepare( + 'SELECT id, username, email, role, created_at, updated_at FROM users WHERE rowid = last_insert_rowid()' + ).first>(); + + return c.json({ success: true, user: newUser }, 201); + } catch (error) { + console.error('创建用户错误:', error); + return c.json({ success: false, message: '创建用户失败' }, 500); + } +}); + +// 更新用户 +users.put('/:id', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + + // 检查权限(仅允许管理员或用户本人) + if (payload.role !== 'admin' && payload.id !== id) { + return c.json({ success: false, message: '无权修改此用户信息' }, 403); + } + + // 检查用户是否存在 + const user = await c.env.DB.prepare( + 'SELECT * FROM users WHERE id = ?' + ).bind(id).first(); + + if (!user) { + return c.json({ success: false, message: '用户不存在' }, 404); + } + + const data = await c.req.json(); + + // 非管理员不能修改角色 + if (payload.role !== 'admin' && data.role && data.role !== user.role) { + return c.json({ success: false, message: '无权修改用户角色' }, 403); + } + + // 准备更新数据 + const updates = []; + const values = []; + + if (data.username !== undefined) { + // 检查新用户名是否已存在 + if (data.username !== user.username) { + const existingUser = await c.env.DB.prepare( + 'SELECT id FROM users WHERE username = ? AND id != ?' + ).bind(data.username, id).first(); + + if (existingUser) { + return c.json({ success: false, message: '用户名已存在' }, 400); + } + + updates.push('username = ?'); + values.push(data.username); + } + } + + if (data.email !== undefined) { + updates.push('email = ?'); + values.push(data.email); + } + + if (data.role !== undefined) { + updates.push('role = ?'); + values.push(data.role); + } + + // 如果提供了新密码,则更新密码 + if (data.password) { + const hashedPassword = await hash(data.password, 10); + updates.push('password = ?'); + values.push(hashedPassword); + } + + updates.push('updated_at = ?'); + values.push(new Date().toISOString()); + + // 添加 ID 作为 WHERE 条件 + values.push(id); + + // 如果没有要更新的字段,直接返回 + if (updates.length <= 1) { + return c.json({ success: false, message: '没有提供要更新的字段' }, 400); + } + + // 执行更新 + const result = await c.env.DB.prepare( + `UPDATE users SET ${updates.join(', ')} WHERE id = ?` + ).bind(...values).run(); + + if (!result.success) { + throw new Error('更新用户失败'); + } + + // 获取更新后的用户 + const updatedUser = await c.env.DB.prepare( + 'SELECT id, username, email, role, created_at, updated_at FROM users WHERE id = ?' + ).bind(id).first>(); + + return c.json({ success: true, user: updatedUser }); + } catch (error) { + console.error('更新用户错误:', error); + return c.json({ success: false, message: '更新用户失败' }, 500); + } +}); + +// 删除用户(仅管理员) +users.delete('/:id', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + + // 检查权限 + if (payload.role !== 'admin') { + return c.json({ success: false, message: '无权删除用户' }, 403); + } + + // 防止删除自己 + if (payload.id === id) { + return c.json({ success: false, message: '不能删除当前登录的用户' }, 400); + } + + // 检查用户是否存在 + const user = await c.env.DB.prepare( + 'SELECT id FROM users WHERE id = ?' + ).bind(id).first(); + + if (!user) { + return c.json({ success: false, message: '用户不存在' }, 404); + } + + // 执行删除 + const result = await c.env.DB.prepare( + 'DELETE FROM users WHERE id = ?' + ).bind(id).run(); + + if (!result.success) { + throw new Error('删除用户失败'); + } + + return c.json({ success: true, message: '用户已删除' }); + } catch (error) { + console.error('删除用户错误:', error); + return c.json({ success: false, message: '删除用户失败' }, 500); + } +}); + +// 修改密码 +users.post('/:id/change-password', async (c) => { + try { + const id = parseInt(c.req.param('id')); + const payload = c.get('jwtPayload'); + + // 检查权限(仅允许用户本人或管理员) + if (payload.role !== 'admin' && payload.id !== id) { + return c.json({ success: false, message: '无权修改此用户密码' }, 403); + } + + const { currentPassword, newPassword } = await c.req.json(); + + // 管理员可以不提供当前密码 + if (payload.role !== 'admin' && !currentPassword) { + return c.json({ success: false, message: '必须提供当前密码' }, 400); + } + + if (!newPassword) { + return c.json({ success: false, message: '必须提供新密码' }, 400); + } + + // 获取用户 + const user = await c.env.DB.prepare( + 'SELECT * FROM users WHERE id = ?' + ).bind(id).first(); + + if (!user) { + return c.json({ success: false, message: '用户不存在' }, 404); + } + + // 非管理员需要验证当前密码 + if (payload.role !== 'admin') { + const isPasswordValid = await compare(currentPassword, user.password); + if (!isPasswordValid) { + return c.json({ success: false, message: '当前密码不正确' }, 400); + } + } + + // 哈希新密码 + const hashedPassword = await hash(newPassword, 10); + + // 更新密码 + const result = await c.env.DB.prepare( + 'UPDATE users SET password = ?, updated_at = ? WHERE id = ?' + ).bind( + hashedPassword, + new Date().toISOString(), + id + ).run(); + + if (!result.success) { + throw new Error('更新密码失败'); + } + + return c.json({ success: true, message: '密码已更新' }); + } catch (error) { + console.error('修改密码错误:', error); + return c.json({ success: false, message: '修改密码失败' }, 500); + } +}); + +export default users; \ No newline at end of file diff --git a/backend/src/setup/database.ts b/backend/src/setup/database.ts new file mode 100644 index 0000000..6906237 --- /dev/null +++ b/backend/src/setup/database.ts @@ -0,0 +1,300 @@ +// 初始化数据库脚本 +// 用于在 Cloudflare Workers 环境中创建数据库表和初始数据 + +import { Hono } from 'hono'; +import bcrypt from 'bcryptjs'; +import { Bindings } from '../models/db'; + +const initDb = new Hono<{ Bindings: Bindings }>(); + +// 初始化数据库路由 +initDb.get('/init-db', async (c) => { + try { + console.log('开始初始化数据库...'); + + // 先删除已有的表 + console.log('删除已有的表...'); + try { + await c.env.DB.exec("DROP TABLE IF EXISTS status_page_agents"); + await c.env.DB.exec("DROP TABLE IF EXISTS status_page_monitors"); + await c.env.DB.exec("DROP TABLE IF EXISTS status_page_config"); + await c.env.DB.exec("DROP TABLE IF EXISTS monitor_status_history"); + await c.env.DB.exec("DROP TABLE IF EXISTS monitor_checks"); + await c.env.DB.exec("DROP TABLE IF EXISTS agents"); + await c.env.DB.exec("DROP TABLE IF EXISTS monitors"); + await c.env.DB.exec("DROP TABLE IF EXISTS users"); + } catch (error) { + console.log('删除表出错,可能表不存在:', error); + } + + console.log('创建用户表...'); + // 创建用户表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password TEXT NOT NULL, email TEXT, role TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)"); + + console.log('创建监控表...'); + // 创建监控表(单行格式) + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS monitors (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, url TEXT NOT NULL, method TEXT NOT NULL, interval INTEGER NOT NULL, timeout INTEGER NOT NULL, expected_status INTEGER NOT NULL, headers TEXT NOT NULL, body TEXT, created_by INTEGER NOT NULL, active BOOLEAN NOT NULL, status TEXT DEFAULT 'pending', uptime REAL DEFAULT 100.0, response_time INTEGER DEFAULT 0, last_checked TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, FOREIGN KEY (created_by) REFERENCES users(id))"); + + console.log('创建监控历史记录表...'); + // 创建监控历史记录表(单行格式) + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS monitor_checks (id INTEGER PRIMARY KEY AUTOINCREMENT, monitor_id INTEGER NOT NULL, status TEXT NOT NULL, response_time INTEGER, status_code INTEGER, error TEXT, checked_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (monitor_id) REFERENCES monitors(id))"); + + console.log('创建监控状态历史表...'); + // 创建监控状态历史表(单行格式) + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS monitor_status_history (id INTEGER PRIMARY KEY AUTOINCREMENT, monitor_id INTEGER NOT NULL, status TEXT NOT NULL, timestamp TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (monitor_id) REFERENCES monitors(id))"); + + console.log('创建客户端表...'); + // 创建客户端表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS agents (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, token TEXT NOT NULL UNIQUE, created_by INTEGER NOT NULL, status TEXT DEFAULT 'inactive', created_at TEXT NOT NULL, updated_at TEXT NOT NULL, hostname TEXT, ip_address TEXT, os TEXT, version TEXT, cpu_usage REAL, memory_total INTEGER, memory_used INTEGER, disk_total INTEGER, disk_used INTEGER, network_rx INTEGER, network_tx INTEGER, FOREIGN KEY (created_by) REFERENCES users(id))"); + + console.log('创建状态页配置表...'); + // 创建状态页配置表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS status_page_config (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, title TEXT NOT NULL DEFAULT '系统状态', description TEXT DEFAULT '系统当前运行状态', logo_url TEXT DEFAULT '', custom_css TEXT DEFAULT '', created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id))"); + + console.log('创建状态页监控项关联表...'); + // 创建状态页监控项关联表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS status_page_monitors (config_id INTEGER NOT NULL, monitor_id INTEGER NOT NULL, PRIMARY KEY (config_id, monitor_id), FOREIGN KEY (config_id) REFERENCES status_page_config(id) ON DELETE CASCADE, FOREIGN KEY (monitor_id) REFERENCES monitors(id) ON DELETE CASCADE)"); + + console.log('创建状态页客户端关联表...'); + // 创建状态页客户端关联表 + await c.env.DB.exec("CREATE TABLE IF NOT EXISTS status_page_agents (config_id INTEGER NOT NULL, agent_id INTEGER NOT NULL, PRIMARY KEY (config_id, agent_id), FOREIGN KEY (config_id) REFERENCES status_page_config(id) ON DELETE CASCADE, FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE)"); + + console.log('检查管理员用户...'); + // 检查是否已存在管理员用户 + const adminUser = await c.env.DB.prepare( + 'SELECT id FROM users WHERE username = ?' + ).bind('admin').first(); + + // 如果不存在管理员用户,则创建一个 + if (!adminUser) { + console.log('创建管理员用户...'); + // 密码: admin123 + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash('admin123', salt); + const now = new Date().toISOString(); + + await c.env.DB.prepare( + `INSERT INTO users (username, password, email, role, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?)` + ).bind( + 'admin', + hashedPassword, + 'admin@example.com', + 'admin', + now, + now + ).run(); + } + + console.log('添加示例监控数据...'); + // 添加示例监控数据 + await addSampleMonitors(c); + + console.log('添加示例客户端数据...'); + // 添加示例客户端数据 + await addSampleAgents(c); + + return c.json({ + success: true, + message: '数据库初始化成功', + tables: ['users', 'monitors', 'monitor_checks', 'monitor_status_history', 'agents', 'status_page_config', 'status_page_monitors', 'status_page_agents'] + }); + } catch (error) { + console.error('数据库初始化错误:', error); + return c.json({ + success: false, + message: '数据库初始化失败', + error: error instanceof Error ? error.message : String(error) + }, 500); + } +}); + +// 添加示例监控数据 +async function addSampleMonitors(c: any) { + // 检查是否已有示例监控数据 + const existingMonitors = await c.env.DB.prepare('SELECT COUNT(*) as count FROM monitors').first(); + + if (existingMonitors.count === 0) { + console.log('添加示例监控...'); + const now = new Date().toISOString(); + const userId = 1; // 管理员用户ID + + // 添加API服务监控 + const apiMonitor = await c.env.DB.prepare( + `INSERT INTO monitors (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + ).bind( + 'API服务', + 'https://api.example.com/status', + 'GET', + 60, + 30, + 200, + '{}', + '', + userId, + true, + 'up', + 99.98, + 120, + now, + now, + now + ).run(); + + // 添加用户服务监控 + const userMonitor = await c.env.DB.prepare( + `INSERT INTO monitors (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + ).bind( + '用户服务', + 'https://users.example.com/health', + 'GET', + 30, + 30, + 200, + '{}', + '', + userId, + true, + 'up', + 99.95, + 150, + now, + now, + now + ).run(); + + // 添加支付服务监控 + const paymentMonitor = await c.env.DB.prepare( + `INSERT INTO monitors (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + ).bind( + '支付服务', + 'https://payments.example.com/status', + 'GET', + 60, + 30, + 200, + '{}', + '', + userId, + true, + 'up', + 99.9, + 180, + now, + now, + now + ).run(); + + // 添加数据库服务监控 + const dbMonitor = await c.env.DB.prepare( + `INSERT INTO monitors (name, url, method, interval, timeout, expected_status, headers, body, created_by, active, status, uptime, response_time, last_checked, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + ).bind( + '数据库服务', + 'https://db.example.com/health', + 'GET', + 120, + 30, + 200, + '{}', + '', + userId, + true, + 'down', + 99.5, + 300, + now, + now, + now + ).run(); + } +} + +// 添加示例客户端数据 +async function addSampleAgents(c: any) { + // 检查是否已有示例客户端数据 + const existingAgents = await c.env.DB.prepare('SELECT COUNT(*) as count FROM agents').first(); + + if (existingAgents.count === 0) { + console.log('添加示例客户端...'); + const now = new Date().toISOString(); + const userId = 1; // 管理员用户ID + + // 主服务器 + const primaryAgent = await c.env.DB.prepare( + `INSERT INTO agents (name, token, created_by, status, created_at, updated_at, cpu_usage, memory_total, memory_used, disk_total, disk_used, network_rx, network_tx, hostname, ip_address, os, version) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + ).bind( + '主服务器', + 'primary-server-token-123456', + userId, + 'active', + now, + now, + 25.5, // CPU使用率 + 16384, // 内存总量 (MB) + 8192, // 内存使用量 (MB) + 500000, // 磁盘总量 (MB) + 250000, // 磁盘使用量 (MB) + 1024, // 网络接收流量 (KB) + 512, // 网络发送流量 (KB) + 'primary-server', // 主机名 + '192.168.1.10', // IP地址 + 'Linux Ubuntu 20.04', // 操作系统 + '1.0.0' // 版本 + ).run(); + + // 备份服务器 + const backupAgent = await c.env.DB.prepare( + `INSERT INTO agents (name, token, created_by, status, created_at, updated_at, cpu_usage, memory_total, memory_used, disk_total, disk_used, network_rx, network_tx, hostname, ip_address, os, version) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + ).bind( + '备份服务器', + 'backup-server-token-123456', + userId, + 'active', + now, + now, + 15.2, // CPU使用率 + 8192, // 内存总量 (MB) + 4096, // 内存使用量 (MB) + 1000000, // 磁盘总量 (MB) + 400000, // 磁盘使用量 (MB) + 512, // 网络接收流量 (KB) + 256, // 网络发送流量 (KB) + 'backup-server', // 主机名 + '192.168.1.11', // IP地址 + 'Linux CentOS 8', // 操作系统 + '1.0.0' // 版本 + ).run(); + + // API服务器 + const apiAgent = await c.env.DB.prepare( + `INSERT INTO agents (name, token, created_by, status, created_at, updated_at, cpu_usage, memory_total, memory_used, disk_total, disk_used, network_rx, network_tx, hostname, ip_address, os, version) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + ).bind( + 'API服务器', + 'api-server-token-123456', + userId, + 'active', + now, + now, + 35.8, // CPU使用率 + 4096, // 内存总量 (MB) + 2048, // 内存使用量 (MB) + 250000, // 磁盘总量 (MB) + 125000, // 磁盘使用量 (MB) + 2048, // 网络接收流量 (KB) + 1024, // 网络发送流量 (KB) + 'api-server', // 主机名 + '192.168.1.12', // IP地址 + 'Linux Debian 11', // 操作系统 + '1.0.0' // 版本 + ).run(); + } +} + +export default initDb; \ No newline at end of file diff --git a/backend/src/tasks/agent-task.ts b/backend/src/tasks/agent-task.ts new file mode 100644 index 0000000..1a9e0dd --- /dev/null +++ b/backend/src/tasks/agent-task.ts @@ -0,0 +1,12 @@ +// 定期检查客户端状态的任务 +export const checkAgentsStatus = async (env: any) => { + try { + console.log('定时任务: 检查客户端状态...'); + + // 所有客户端默认都是inactive状态,只有当客户端明确报告活跃时才会被设置为active + // 这个任务不做任何状态变更,只打印一条日志 + console.log('定时任务: 客户端状态由客户端主动报告,无需定时任务检查'); + } catch (error) { + console.error('定时任务: 检查客户端状态出错:', error); + } +}; \ No newline at end of file diff --git a/backend/src/tasks/index.ts b/backend/src/tasks/index.ts new file mode 100644 index 0000000..34fba7c --- /dev/null +++ b/backend/src/tasks/index.ts @@ -0,0 +1,23 @@ +// 导出所有定时任务 +import monitorTask from './monitor-task'; +import { checkAgentsStatus } from './agent-task'; + +// 统一的定时任务处理函数 +export const runScheduledTasks = async (event: any, env: any, ctx: any) => { + try { + // 执行监控检查任务 + if (monitorTask.scheduled) { + await monitorTask.scheduled(event, env, ctx); + } + + // 执行客户端状态检查任务 + await checkAgentsStatus(env); + } catch (error) { + console.error('定时任务执行出错:', error); + } +}; + +export { + monitorTask, + checkAgentsStatus +}; \ No newline at end of file diff --git a/backend/src/tasks/monitor-task.ts b/backend/src/tasks/monitor-task.ts new file mode 100644 index 0000000..fd02cf8 --- /dev/null +++ b/backend/src/tasks/monitor-task.ts @@ -0,0 +1,133 @@ +import { Hono } from 'hono'; +import { Bindings } from '../models/db'; +import { Monitor } from '../models/monitor'; +import { generateToken } from '../utils/jwt'; + +const monitorTask = new Hono<{ Bindings: Bindings }>(); + +// 监控检查的主要函数 +async function checkMonitors(c: any) { + try { + console.log('开始执行监控检查...'); + + // 获取当前时间 + const now = new Date(); + + // 查询需要检查的监控 + // 条件:active=true 且 (last_checked 为 null 或 当前时间 - last_checked > interval) + const monitors = await c.env.DB.prepare(` + SELECT * FROM monitors + WHERE active = true + AND (last_checked IS NULL OR datetime('now') > datetime(last_checked, '+' || interval || ' seconds')) + `).all(); + + console.log(`找到 ${monitors?.results?.length || 0} 个需要检查的监控`); + + if (!monitors.results || monitors.results.length === 0) { + return { success: true, message: '没有需要检查的监控', checked: 0 }; + } + + // 检查每个监控 + const results = await Promise.all(monitors.results.map(async (monitor: Monitor) => { + return await checkSingleMonitor(c, monitor); + })); + + return { + success: true, + message: '监控检查完成', + checked: results.length, + results: results + }; + } catch (error) { + console.error('监控检查出错:', error); + return { success: false, message: '监控检查出错', error: String(error) }; + } +} + +// 检查单个监控的函数 +async function checkSingleMonitor(c: any, monitor: Monitor) { + try { + console.log(`开始检查监控项: ${monitor.name} (${monitor.url})`); + + const startTime = Date.now(); + const response = await fetch(monitor.url, { + method: monitor.method, + headers: { + 'User-Agent': 'Xugou-Monitor/1.0', + ...(monitor.headers ? JSON.parse(monitor.headers) : {}) + }, + body: monitor.body || undefined + }); + + const endTime = Date.now(); + const responseTime = endTime - startTime; + + // 检查响应状态 + const isUp = response.status === monitor.expected_status; + const status = isUp ? 'up' : 'down'; + + // 记录状态历史 + await c.env.DB.prepare( + `INSERT INTO monitor_status_history (monitor_id, status, timestamp) + VALUES (?, ?, datetime('now'))` + ).bind(monitor.id, status).run(); + + // 更新监控项状态 + await c.env.DB.prepare( + `UPDATE monitors + SET status = ?, + last_checked = datetime('now'), + response_time = ?, + uptime = CASE + WHEN status = 'up' THEN uptime + 1 + ELSE uptime + END + WHERE id = ?` + ).bind(status, responseTime, monitor.id).run(); + + console.log(`监控项检查完成: ${monitor.name}, 状态: ${status}, 响应时间: ${responseTime}ms`); + + return { + success: true, + status, + responseTime + }; + } catch (error: any) { + console.error(`检查监控项失败: ${monitor.name}`, error); + + // 记录错误状态 + await c.env.DB.prepare( + `INSERT INTO monitor_status_history (monitor_id, status, timestamp) + VALUES (?, ?, datetime('now'))` + ).bind(monitor.id, 'down').run(); + + // 更新监控项状态 + await c.env.DB.prepare( + `UPDATE monitors + SET status = 'down', + last_checked = datetime('now') + WHERE id = ?` + ).bind(monitor.id).run(); + + return { + success: false, + status: 'down', + error: error.message + }; + } +} + +// 定义触发器路由 - 通过HTTP请求触发监控检查 +monitorTask.get('/api/trigger-check', async (c) => { + const result = await checkMonitors(c); + return c.json(result); +}); + +// 在 Cloudflare Workers 中设置定时触发器 +export default { + async scheduled(event: any, env: any, ctx: any) { + const c = { env }; + await checkMonitors(c); + }, + fetch: monitorTask.fetch +}; \ No newline at end of file diff --git a/backend/src/utils/jwt.ts b/backend/src/utils/jwt.ts new file mode 100644 index 0000000..ce3a57e --- /dev/null +++ b/backend/src/utils/jwt.ts @@ -0,0 +1,31 @@ +/** + * JWT工具类,提供JWT相关的通用功能 + */ + +/** + * 获取JWT密钥 + * 优先从环境变量中获取JWT_SECRET,如果不存在则使用默认值 + * + * @param c Cloudflare环境上下文 + * @returns JWT密钥 + */ +export const getJwtSecret = (c: any): string => { + // 在Cloudflare Workers环境中,使用env变量 + if (typeof process === 'undefined') { + return c.env.JWT_SECRET || 'your-secret-key-change-in-production'; + } + // 在Node.js环境中,使用process.env + return process.env.JWT_SECRET || 'your-secret-key-change-in-production'; +}; + +/** + * 生成随机令牌 + * 生成用于API密钥或认证令牌的随机字符串 + * + * @returns 生成的随机令牌 + */ +export async function generateToken(): Promise { + const array = new Uint8Array(32); + crypto.getRandomValues(array); + return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); +} \ No newline at end of file diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 0000000..a0e95d9 --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "moduleResolution": "Node", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "outDir": "dist", + "sourceMap": true, + "declaration": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/backend/wrangler.toml b/backend/wrangler.toml new file mode 100644 index 0000000..5951a4a --- /dev/null +++ b/backend/wrangler.toml @@ -0,0 +1,26 @@ +name = "xugou-backend" +main = "src/index.ts" +compatibility_date = "2025-03-10" +compatibility_flags = ["nodejs_compat"] + +[build] +command = "npm run build" + +[env.production] +vars = { NODE_ENV = "production" } + +# 添加定时触发器,每分钟执行一次监控检查 +[triggers] +crons = ["* * * * *"] + +[env.development] +vars = { NODE_ENV = "development" } + +[[d1_databases]] +binding = "DB" +database_name = "xugou_db" +database_id = "32a2ca5f-3a77-4b08-965e-804612557880" +preview_database_id = "ec71b62b-0775-4026-88be-15938a9e2745" + +[vars] +JWT_SECRET = "your-secret-key-change-in-production" \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..40ede56 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,54 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default tseslint.config({ + extends: [ + // Remove ...tseslint.configs.recommended and replace with this + ...tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + ...tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + ...tseslint.configs.stylisticTypeChecked, + ], + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default tseslint.config({ + plugins: { + // Add the react-x and react-dom plugins + 'react-x': reactX, + 'react-dom': reactDom, + }, + rules: { + // other rules... + // Enable its recommended typescript rules + ...reactX.configs['recommended-typescript'].rules, + ...reactDom.configs.recommended.rules, + }, +}) +``` diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..3ddfa7a --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,17 @@ + + + + + + + + + + + XUGOU - 轻量化监控平台 + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..442d6ff --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,5663 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@radix-ui/react-avatar": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-form": "^0.1.2", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tabs": "^1.1.3", + "@radix-ui/react-toast": "^1.2.6", + "@radix-ui/themes": "^3.2.1", + "axios": "^1.8.3", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.3.0", + "recharts": "^2.15.1" + }, + "devDependencies": { + "@eslint/js": "^9.21.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.15.0", + "typescript": "~5.7.2", + "typescript-eslint": "^8.24.1", + "vite": "^6.2.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.10", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.26.10.tgz", + "integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.10", + "@babel/types": "^7.26.10", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.10", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.26.10.tgz", + "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.10", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.10.tgz", + "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.10" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.10", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.26.9", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.10", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.26.10.tgz", + "integrity": "sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.10", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.26.10", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.26.10.tgz", + "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.5.1", + "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", + "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.2", + "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.1.0.tgz", + "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", + "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.22.0", + "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.22.0.tgz", + "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.12.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmmirror.com/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@radix-ui/colors": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/@radix-ui/colors/-/colors-3.0.0.tgz", + "integrity": "sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==", + "license": "MIT" + }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.2.tgz", + "integrity": "sha512-+rnMO0SEfzkcHr93RshkQVpOA26MtGOv4pcS9QUnLg4F8+GDmCJ8c2FEPhPz5e7arf31EzbTqJxFbzg3qen14g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-visually-hidden": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-accordion/-/react-accordion-1.2.3.tgz", + "integrity": "sha512-RIQ15mrcvqIkDARJeERSuXSry2N8uYnxkdDetpfmalT/+0ntOXLkFOsh9iwlAsCv+qcmhZjbdJogIm6WBa6c4A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collapsible": "1.1.3", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.6.tgz", + "integrity": "sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dialog": "1.1.6", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz", + "integrity": "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.2.tgz", + "integrity": "sha512-TaJxYoCpxJ7vfEkv2PTNox/6zzmpKXT6ewvCuf2tTOIVN45/Jahhlld29Yw4pciOXS2Xq91/rSGEdmEnUWZCqA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-avatar/-/react-avatar-1.1.3.tgz", + "integrity": "sha512-Paen00T4P8L8gd9bNsRMw7Cbaz85oxiv+hzomsRZgFm2byltPFDtfcoqlWJ8GyZlIBWgLssJlzLCnKU0G0302g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-checkbox/-/react-checkbox-1.1.4.tgz", + "integrity": "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.3.tgz", + "integrity": "sha512-jFSerheto1X03MUC0g6R7LedNW9EEGWdg9W1+MlpkMLwGkgkbUXLPBH/KIuWKXUoeYRVY11llqbTBDzuLg7qrw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-collection/-/react-collection-1.1.2.tgz", + "integrity": "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-context-menu/-/react-context-menu-2.2.6.tgz", + "integrity": "sha512-aUP99QZ3VU84NPsHeaFt4cQUNgJqFsLLOt/RbbWXszZ6MP0DpDyjkFZORr4RpAEx3sUBk+Kc8h13yGtC5Qw8dg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-menu": "2.1.6", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz", + "integrity": "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz", + "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.6.tgz", + "integrity": "sha512-no3X7V5fD487wab/ZYSHXq3H37u4NVeLDKI/Ks724X/eEFSSEFYZxWgsIlr1UBeEyDaM29HM5x9p1Nv8DuTYPA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-menu": "2.1.6", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.2.tgz", + "integrity": "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-form": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-form/-/react-form-0.1.2.tgz", + "integrity": "sha512-Owj1MjLq6/Rp85bgzYI+zRK5APLiWDtXDM63Z39FW15bNdehrcS+FjQgLGQYswFzipYu4GAA+t5w/VqvvNZ3ag==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-label": "2.1.2", + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-hover-card/-/react-hover-card-1.1.6.tgz", + "integrity": "sha512-E4ozl35jq0VRlrdc4dhHrNSV0JqBb4Jy73WAhBEK7JoYnQ83ED5r0Rb/XdVKw89ReAJN38N492BAPBZQ57VmqQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-label/-/react-label-2.1.2.tgz", + "integrity": "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-menu/-/react-menu-2.1.6.tgz", + "integrity": "sha512-tBBb5CXDJW3t2mo9WlO7r6GTmWV0F0uzHZVFmlRmYpiSK1CDU5IKojP1pm7oknpBOrFZx/YgBRW9oorPO2S/Lg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-menubar/-/react-menubar-1.1.6.tgz", + "integrity": "sha512-FHq7+3DlXwh/7FOM4i0G4bC4vPjiq89VEEvNF4VMLchGnaUuUbE5uKXMUCjdKaOghEEMeiKa5XCa2Pk4kteWmg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-menu": "2.1.6", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.5", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.5.tgz", + "integrity": "sha512-myMHHQUZ3ZLTi8W381/Vu43Ia0NqakkQZ2vzynMmTUtQQ9kNkjzhOwkZC9TAM5R07OZUVIQyHC06f/9JZJpvvA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-popover/-/react-popover-1.1.6.tgz", + "integrity": "sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.2.2.tgz", + "integrity": "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.1.4.tgz", + "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-progress/-/react-progress-1.1.2.tgz", + "integrity": "sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-radio-group/-/react-radio-group-1.2.3.tgz", + "integrity": "sha512-xtCsqt8Rp09FK50ItqEqTJ7Sxanz8EM8dnkVIhJrc/wkMMomSmXHvYbhv3E7Zx4oXh98aaLt9W679SUYXg4IDA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.2.tgz", + "integrity": "sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.3.tgz", + "integrity": "sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-select/-/react-select-2.1.6.tgz", + "integrity": "sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-separator/-/react-separator-1.1.2.tgz", + "integrity": "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-slider/-/react-slider-1.2.3.tgz", + "integrity": "sha512-nNrLAWLjGESnhqBqcCNW4w2nn7LxudyMzeB6VgdyAnFLC6kfQgnAjSL2v6UkQTnDctJBlxrmxfplWS4iYjdUTw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-switch/-/react-switch-1.1.3.tgz", + "integrity": "sha512-1nc+vjEOQkJVsJtWPSiISGT6OKm4SiOdjMo+/icLxo2G4vxz1GntC5MzfL4v8ey9OEfw787QCD1y3mUv0NiFEQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.3.tgz", + "integrity": "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.6.tgz", + "integrity": "sha512-gN4dpuIVKEgpLn1z5FhzT9mYRUitbfZq9XqN/7kkBMUgFTzTG8x/KszWJugJXHcwxckY8xcKDZPz7kG3o6DsUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-toggle/-/react-toggle-1.1.2.tgz", + "integrity": "sha512-lntKchNWx3aCHuWKiDY+8WudiegQvBpDRAYL8dKLRvKEH8VOpl0XX6SSU/bUBqIRJbcTy4+MW06Wv8vgp10rzQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.2.tgz", + "integrity": "sha512-JBm6s6aVG/nwuY5eadhU2zDi/IwYS0sDM5ZWb4nymv/hn3hZdkw+gENn0LP4iY1yCd7+bgJaCwueMYJIU3vk4A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-toggle": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-toolbar/-/react-toolbar-1.1.2.tgz", + "integrity": "sha512-wT20eQ7ScFk+kBMDmHp+lMk18cgxhu35b2Bn5deUcPxiVwfn5vuZgi7NGcHu8ocdkinahmp4FaSZysKDyRVPWQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-separator": "1.1.2", + "@radix-ui/react-toggle-group": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz", + "integrity": "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz", + "integrity": "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", + "license": "MIT" + }, + "node_modules/@radix-ui/themes": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/themes/-/themes-3.2.1.tgz", + "integrity": "sha512-WJL2YKAGItkunwm3O4cLTFKCGJTfAfF6Hmq7f5bCo1ggqC9qJQ/wfg/25AAN72aoEM1yqXZQ+pslsw48AFR0Xg==", + "license": "MIT", + "dependencies": { + "@radix-ui/colors": "^3.0.0", + "classnames": "^2.3.2", + "radix-ui": "^1.1.3", + "react-remove-scroll-bar": "^2.3.8" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz", + "integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz", + "integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz", + "integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz", + "integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz", + "integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz", + "integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz", + "integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz", + "integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz", + "integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz", + "integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz", + "integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz", + "integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz", + "integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz", + "integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz", + "integrity": "sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz", + "integrity": "sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz", + "integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz", + "integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz", + "integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.0.10", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.4", + "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.0.4.tgz", + "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.26.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz", + "integrity": "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.26.1", + "@typescript-eslint/type-utils": "8.26.1", + "@typescript-eslint/utils": "8.26.1", + "@typescript-eslint/visitor-keys": "8.26.1", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.26.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.26.1.tgz", + "integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.26.1", + "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/typescript-estree": "8.26.1", + "@typescript-eslint/visitor-keys": "8.26.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.26.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz", + "integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/visitor-keys": "8.26.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.26.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz", + "integrity": "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.26.1", + "@typescript-eslint/utils": "8.26.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.26.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.26.1.tgz", + "integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.26.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz", + "integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/visitor-keys": "8.26.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.26.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.26.1.tgz", + "integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.26.1", + "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/typescript-estree": "8.26.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.26.1", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz", + "integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.26.1", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.3", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.8.3.tgz", + "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001705", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001705.tgz", + "integrity": "sha512-S0uyMMiYvA7CxNgomYBwwwPUnWzFD83f3B1ce5jHUfHTH//QL6hHsreI8RVC5606R4ssqravelYO5TU6t8sEyg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.119", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.119.tgz", + "integrity": "sha512-Ku4NMzUjz3e3Vweh7PhApPrZSS4fyiCIbcIrG9eKrriYVLmbMepETR/v6SU7xPm98QTqMSYiCwfO89QNjXLkbQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.1", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.22.0", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.22.0.tgz", + "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.2", + "@eslint/config-helpers": "^0.1.0", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.0", + "@eslint/js": "9.22.0", + "@eslint/plugin-kit": "^0.2.7", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.19", + "resolved": "https://registry.npmmirror.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.19.tgz", + "integrity": "sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmmirror.com/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmmirror.com/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.10", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.10.tgz", + "integrity": "sha512-vSJJTG+t/dIKAUhUDw/dLdZ9s//5OxcHqLaDWWrW4Cdq7o6tdLIczUkMXt2MBNmk6sJRZBZRXVixs7URY1CmIg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/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/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/radix-ui": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/radix-ui/-/radix-ui-1.1.3.tgz", + "integrity": "sha512-W8L6soM1vQnIXVvVa31AkQhoZBDPwVoNHhT13R3aB9Qq7ARYIUS9DLaCopRBsbTdZm1NEEPx3rnq659CiNOBDw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-accessible-icon": "1.1.2", + "@radix-ui/react-accordion": "1.2.3", + "@radix-ui/react-alert-dialog": "1.1.6", + "@radix-ui/react-aspect-ratio": "1.1.2", + "@radix-ui/react-avatar": "1.1.3", + "@radix-ui/react-checkbox": "1.1.4", + "@radix-ui/react-collapsible": "1.1.3", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-context-menu": "2.2.6", + "@radix-ui/react-dialog": "1.1.6", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-dropdown-menu": "2.1.6", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-form": "0.1.2", + "@radix-ui/react-hover-card": "1.1.6", + "@radix-ui/react-label": "2.1.2", + "@radix-ui/react-menu": "2.1.6", + "@radix-ui/react-menubar": "1.1.6", + "@radix-ui/react-navigation-menu": "1.2.5", + "@radix-ui/react-popover": "1.1.6", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-progress": "1.1.2", + "@radix-ui/react-radio-group": "1.2.3", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-scroll-area": "1.2.3", + "@radix-ui/react-select": "2.1.6", + "@radix-ui/react-separator": "1.1.2", + "@radix-ui/react-slider": "1.2.3", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-switch": "1.1.3", + "@radix-ui/react-tabs": "1.1.3", + "@radix-ui/react-toast": "1.2.6", + "@radix-ui/react-toggle": "1.1.2", + "@radix-ui/react-toggle-group": "1.1.2", + "@radix-ui/react-toolbar": "1.1.2", + "@radix-ui/react-tooltip": "1.1.8", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmmirror.com/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.6.3", + "resolved": "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", + "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.3.0.tgz", + "integrity": "sha512-466f2W7HIWaNXTKM5nHTqNxLrHTyXybm7R0eBlVSt0k/u55tTCDO194OIx/NrYD4TS5SXKTNekXfT37kMKUjgw==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.3.0.tgz", + "integrity": "sha512-z7Q5FTiHGgQfEurX/FBinkOXhWREJIAB2RiU24lvcBa82PxUpwqvs/PAXb9lJyPjTs2jrl6UkLvCZVGJPeNuuQ==", + "license": "MIT", + "dependencies": { + "react-router": "7.3.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmmirror.com/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/recharts": { + "version": "2.15.1", + "resolved": "https://registry.npmmirror.com/recharts/-/recharts-2.15.1.tgz", + "integrity": "sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmmirror.com/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.35.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.35.0.tgz", + "integrity": "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.35.0", + "@rollup/rollup-android-arm64": "4.35.0", + "@rollup/rollup-darwin-arm64": "4.35.0", + "@rollup/rollup-darwin-x64": "4.35.0", + "@rollup/rollup-freebsd-arm64": "4.35.0", + "@rollup/rollup-freebsd-x64": "4.35.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", + "@rollup/rollup-linux-arm-musleabihf": "4.35.0", + "@rollup/rollup-linux-arm64-gnu": "4.35.0", + "@rollup/rollup-linux-arm64-musl": "4.35.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", + "@rollup/rollup-linux-riscv64-gnu": "4.35.0", + "@rollup/rollup-linux-s390x-gnu": "4.35.0", + "@rollup/rollup-linux-x64-gnu": "4.35.0", + "@rollup/rollup-linux-x64-musl": "4.35.0", + "@rollup/rollup-win32-arm64-msvc": "4.35.0", + "@rollup/rollup-win32-ia32-msvc": "4.35.0", + "@rollup/rollup-win32-x64-msvc": "4.35.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.0.1.tgz", + "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.26.1", + "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.26.1.tgz", + "integrity": "sha512-t/oIs9mYyrwZGRpDv3g+3K6nZ5uhKEMt2oNmAPwaY4/ye0+EH4nXIPYNtkYFS6QHm+1DFg34DbglYBz5P9Xysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.26.1", + "@typescript-eslint/parser": "8.26.1", + "@typescript-eslint/utils": "8.26.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmmirror.com/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.2.2.tgz", + "integrity": "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..f61abad --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,45 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@radix-ui/react-avatar": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-form": "^0.1.2", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tabs": "^1.1.3", + "@radix-ui/react-toast": "^1.2.6", + "@radix-ui/themes": "^3.2.1", + "axios": "^1.8.3", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.3.0", + "recharts": "^2.15.1" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20250317.0", + "@eslint/js": "^9.21.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.15.0", + "typescript": "~5.7.2", + "typescript-eslint": "^8.24.1", + "vite": "^6.2.0" + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" +} diff --git a/frontend/public/android-chrome-192x192.png b/frontend/public/android-chrome-192x192.png new file mode 100644 index 0000000..3f6c6e9 Binary files /dev/null and b/frontend/public/android-chrome-192x192.png differ diff --git a/frontend/public/android-chrome-512x512.png b/frontend/public/android-chrome-512x512.png new file mode 100644 index 0000000..f6d01b2 Binary files /dev/null and b/frontend/public/android-chrome-512x512.png differ diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png new file mode 100644 index 0000000..46a9174 Binary files /dev/null and b/frontend/public/apple-touch-icon.png differ diff --git a/frontend/public/favicon-16x16.png b/frontend/public/favicon-16x16.png new file mode 100644 index 0000000..ec34036 Binary files /dev/null and b/frontend/public/favicon-16x16.png differ diff --git a/frontend/public/favicon-32x32.png b/frontend/public/favicon-32x32.png new file mode 100644 index 0000000..f91f8fc Binary files /dev/null and b/frontend/public/favicon-32x32.png differ diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..1924379 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/logo.svg b/frontend/public/logo.svg new file mode 100644 index 0000000..38a352d --- /dev/null +++ b/frontend/public/logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/site.webmanifest b/frontend/public/site.webmanifest new file mode 100644 index 0000000..24a46a9 --- /dev/null +++ b/frontend/public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "XUGOU", + "short_name": "XUGOU", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#4F46E5", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..fab863a --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,149 @@ +import { Routes, Route, Navigate } from 'react-router-dom'; +import '@radix-ui/themes/styles.css'; +import './styles/index.css'; +import { useAuth } from './contexts/AuthContext'; + +// 导入布局组件 +import Layout from './components/Layout'; + +// 导入页面组件 +import Home from './pages/Home'; +import Login from './pages/auth/Login'; +import Register from './pages/auth/Register'; +import Dashboard from './pages/Dashboard'; +import MonitorsList from './pages/monitors/MonitorsList'; +import MonitorDetail from './pages/monitors/MonitorDetail'; +import CreateMonitor from './pages/monitors/CreateMonitor'; +import EditMonitor from './pages/monitors/EditMonitor'; +import AgentsList from './pages/agents/AgentsList'; +import AgentDetail from './pages/agents/AgentDetail'; +import UsersList from './pages/users/UsersList'; +import UserProfile from './pages/users/UserProfile'; +import NotFound from './pages/NotFound'; +import StatusPage from './pages/status/StatusPage'; +import StatusPageConfig from './pages/status/StatusPageConfig'; +import CreateAgent from './pages/agents/CreateAgent'; +import EditAgent from './pages/agents/EditAgent'; + +// 受保护的路由组件 +const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { + const { isAuthenticated, isLoading } = useAuth(); + + if (isLoading) { + return
加载中...
; + } + + if (!isAuthenticated) { + return ; + } + + return <>{children}; +}; + +// 管理员路由组件 +const AdminRoute = ({ children }: { children: React.ReactNode }) => { + const { isAuthenticated, isLoading, user } = useAuth(); + + if (isLoading) { + return
加载中...
; + } + + if (!isAuthenticated) { + return ; + } + + if (user?.role !== 'admin') { + return ; + } + + return <>{children}; +}; + +function App() { + return ( + + {/* 公共路由 */} + } /> + } /> + } /> + + {/* 状态页 - 无需认证 */} + } /> + + {/* 受保护的路由 */} + + + + } /> + + {/* 状态页配置 - 需要认证 */} + + + + } /> + + {/* 监控路由 */} + + + + } /> + + + + } /> + + + + } /> + + + + } /> + + {/* 客户端路由 */} + + + + } /> + + + + } /> + + + + } /> + + + + } /> + + {/* 用户路由 */} + + + + } /> + + + + } /> + + {/* 404页面 */} + } /> + + ); +} + +export default App; diff --git a/frontend/src/api/agents.ts b/frontend/src/api/agents.ts new file mode 100644 index 0000000..017c51e --- /dev/null +++ b/frontend/src/api/agents.ts @@ -0,0 +1,115 @@ +import api from './index'; + +export interface Agent { + id: number; + name: string; + status?: string; + created_at: string; + updated_at: string; + cpu_usage: number; + memory_total: number; + memory_used: number; + disk_total: number; + disk_used: number; + network_rx: number; + network_tx: number; + hostname?: string; + ip_address?: string; + os?: string; + version?: string; + token?: string; + created_by?: number; +} + +export const generateToken = async () => { + try { + const response = await api.post('/agents/token/generate'); + return response.data; + } catch (error) { + console.error('生成客户端注册令牌失败:', error); + return { + success: false, + message: '生成客户端注册令牌失败' + }; + } +}; + +export const getAllAgents = async () => { + try { + const response = await api.get('/agents'); + return response.data; + } catch (error) { + console.error('获取客户端列表失败:', error); + return { + success: false, + message: '获取客户端列表失败' + }; + } +}; + +export const updateAgentStatus = async (id: number, metrics: { + cpu_usage: number; + memory_total: number; + memory_used: number; + disk_total: number; + disk_used: number; + network_rx: number; + network_tx: number; +}) => { + try { + const response = await api.post(`/agents/${id}/status`, metrics); + return response.data; + } catch (error) { + console.error('更新客户端状态失败:', error); + return { + success: false, + message: '更新客户端状态失败' + }; + } +}; + +export const getAgent = async (id: number) => { + try { + const response = await api.get(`/agents/${id}`); + return response.data; + } catch (error) { + console.error(`获取客户端 ${id} 失败:`, error); + return { + success: false, + message: '获取客户端失败' + }; + } +}; + +export const deleteAgent = async (id: number) => { + try { + const response = await api.delete(`/agents/${id}`); + return response.data; + } catch (error) { + console.error(`删除客户端 ${id} 失败:`, error); + return { + success: false, + message: '删除客户端失败' + }; + } +}; + +export const updateAgent = async (id: number, data: { + name?: string; + hostname?: string; + ip_address?: string; + os?: string; + version?: string; + status?: string; +}) => { + try { + const response = await api.put(`/agents/${id}`, data); + return response.data; + } catch (error) { + console.error(`更新客户端 ${id} 失败:`, error); + return { + success: false, + message: '更新客户端失败' + }; + } +}; \ No newline at end of file diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts new file mode 100644 index 0000000..31b6561 --- /dev/null +++ b/frontend/src/api/auth.ts @@ -0,0 +1,51 @@ +import api from './index'; + +export interface LoginRequest { + username: string; + password: string; +} + +export interface RegisterRequest { + username: string; + password: string; + email?: string; +} + +export interface User { + id: number; + username: string; + email?: string; + role: string; +} + +export interface AuthResponse { + success: boolean; + message: string; + token?: string; + user?: User; +} + +// 登录 +export const login = async (data: LoginRequest): Promise => { + const response = await api.post('/auth/login', data); + return response.data; +}; + +// 注册 +export const register = async (data: RegisterRequest): Promise => { + const response = await api.post('/auth/register', data); + return response.data; +}; + +// 获取当前用户信息 +export const getCurrentUser = async (): Promise<{ success: boolean; user?: User }> => { + const response = await api.get<{ success: boolean; user?: User }>('/auth/me'); + return response.data; +}; + +// 退出登录 +export const logout = (): void => { + localStorage.removeItem('token'); + localStorage.removeItem('user'); + window.location.href = '/login'; +}; \ No newline at end of file diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts new file mode 100644 index 0000000..4ac82af --- /dev/null +++ b/frontend/src/api/index.ts @@ -0,0 +1,45 @@ +import axios from 'axios'; + +// 创建 axios 实例 +const api = axios.create({ + baseURL: 'http://localhost:8787/api', // 本地开发环境 + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}); + +// 请求拦截器 +api.interceptors.request.use( + (config) => { + // 从 localStorage 获取 token + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +// 响应拦截器 +api.interceptors.response.use( + (response) => { + return response; + }, + (error) => { + if (error.response) { + // 处理 401 未授权错误 + if (error.response.status === 401) { + localStorage.removeItem('token'); + localStorage.removeItem('user'); + window.location.href = '/login'; + } + } + return Promise.reject(error); + } +); + +export default api; \ No newline at end of file diff --git a/frontend/src/api/monitors.ts b/frontend/src/api/monitors.ts new file mode 100644 index 0000000..c6795f9 --- /dev/null +++ b/frontend/src/api/monitors.ts @@ -0,0 +1,140 @@ +import api from './index'; + +export interface Monitor { + id: number; + name: string; + url: string; + method: string; + interval: number; + timeout: number; + expected_status: number; + headers: string; + body: string; + created_by: number; + active: boolean; + status: string; // up, down, degraded, pending + uptime: number; + response_time: number; + last_checked: string; + created_at: string; + updated_at: string; + history?: MonitorStatusHistory[]; + checks?: MonitorCheck[]; +} + +export interface MonitorStatusHistory { + id: number; + monitor_id: number; + status: string; + timestamp: string; +} + +export interface MonitorCheck { + id: number; + monitor_id: number; + status: string; + response_time: number; + status_code: number; + error: string | null; + checked_at: string; +} + +export interface MonitorResponse { + success: boolean; + message?: string; + monitor?: Monitor; + monitors?: Monitor[]; +} + +export interface HistoryResponse { + success: boolean; + message?: string; + history: MonitorStatusHistory[]; +} + +export interface ChecksResponse { + success: boolean; + message?: string; + checks: MonitorCheck[]; +} + +export interface CreateMonitorRequest { + name: string; + url: string; + method: string; + interval?: number; + timeout?: number; + expectedStatus?: number; + headers?: Record; + body?: string; +} + +export interface UpdateMonitorRequest { + name?: string; + url?: string; + method?: string; + interval?: number; + timeout?: number; + expectedStatus?: number; + headers?: Record; + body?: string; + active?: boolean; + status?: string; + uptime?: number; + responseTime?: number; + lastChecked?: string; +} + +// 获取所有监控 +export const getAllMonitors = async (): Promise => { + const response = await api.get('/monitors'); + return response.data; +}; + +// 获取单个监控 +export const getMonitor = async (id: number): Promise => { + const response = await api.get(`/monitors/${id}`); + return response.data; +}; + +// 创建监控 +export const createMonitor = async (data: CreateMonitorRequest): Promise => { + const response = await api.post('/monitors', data); + return response.data; +}; + +// 更新监控 +export const updateMonitor = async (id: number, data: UpdateMonitorRequest): Promise => { + const response = await api.put(`/monitors/${id}`, data); + return response.data; +}; + +// 删除监控 +export const deleteMonitor = async (id: number): Promise => { + const response = await api.delete(`/monitors/${id}`); + return response.data; +}; + +// 获取监控历史 +export const getMonitorHistory = async (id: number): Promise => { + const response = await api.get(`/monitors/${id}/history`); + return response.data; +}; + +// 获取监控检查记录 +export const getMonitorChecks = async (id: number, limit: number = 10): Promise => { + const response = await api.get(`/monitors/${id}/checks?limit=${limit}`); + return response.data; +}; + +// 手动检查监控 +export interface CheckResponse { + success: boolean; + message?: string; + result?: any; +} + +export const checkMonitor = async (id: number): Promise => { + const response = await api.post(`/monitors/${id}/check`); + return response.data; +}; \ No newline at end of file diff --git a/frontend/src/api/status.ts b/frontend/src/api/status.ts new file mode 100644 index 0000000..fba2e5d --- /dev/null +++ b/frontend/src/api/status.ts @@ -0,0 +1,153 @@ +import api from './index'; +import { Monitor } from './monitors'; +import { Agent } from './agents'; + +// 后端返回的监控项结构 +export interface ConfigMonitor { + id: number; + name: string; + selected: boolean; +} + +// 后端返回的客户端结构 +export interface ConfigAgent { + id: number; + name: string; + selected: boolean; +} + +// 状态页配置接口(从后端获取时使用) +export interface StatusPageConfigResponse { + title: string; + description: string; + logoUrl: string; + customCss: string; + monitors: ConfigMonitor[]; // 监控项对象数组 + agents: ConfigAgent[]; // 客户端对象数组 +} + +// 状态页配置接口(保存到后端时使用) +export interface StatusPageConfig { + title: string; + description: string; + logoUrl: string; + customCss: string; + monitors: number[]; // 选中的监控ID列表 + agents: number[]; // 选中的客户端ID列表 +} + +// 状态页中展示的客户端类型,包含资源使用信息 +export interface StatusAgent extends Omit { + status: string; + cpu: number; + memory: number; + disk: number; + network_rx: number; + network_tx: number; +} + +// 状态页数据接口 +export interface StatusPageData { + title: string; + description: string; + logoUrl: string; + customCss: string; + monitors: Monitor[]; + agents: StatusAgent[]; +} + +// 获取状态页配置 +export const getStatusPageConfig = async (): Promise<{ + success: boolean; + message?: string; + config?: StatusPageConfigResponse; +}> => { + try { + const response = await api.get<{ + success: boolean; + message?: string; + config?: StatusPageConfigResponse; + }>('/status/config'); + return response.data; + } catch (error) { + console.error('获取状态页配置失败:', error); + return { + success: false, + message: '获取状态页配置失败' + }; + } +}; + +// 保存状态页配置 +export const saveStatusPageConfig = async (config: StatusPageConfig): Promise<{ + success: boolean; + message?: string; +}> => { + try { + console.log('API请求 - 保存状态页配置:', config); + + const response = await api.post<{ + success: boolean; + message?: string; + }>('/status/config', config); + + console.log('API响应 - 保存状态页配置:', response); + + return response.data; + } catch (error) { + console.error('保存状态页配置失败:', error); + console.log('API错误详情:', { + error, + config + }); + + return { + success: false, + message: '保存状态页配置失败' + }; + } +}; + +// 获取状态页数据 +export const getStatusPageData = async (): Promise<{ + success: boolean; + message?: string; + data?: StatusPageData; +}> => { + try { + console.log('开始请求状态页数据...'); + const response = await api.get('/status/data'); + console.log('状态页数据响应:', response.data); + + // 后端返回 { success: true, data: {...} } 格式 + const { success, data, message } = response.data; + + if (success && data) { + console.log('返回成功的状态数据:', data); + + // 确保监控和客户端数据存在 + const processedData = { + ...data, + monitors: data.monitors || [], + agents: data.agents || [] + }; + + return { + success: true, + data: processedData as StatusPageData + }; + } else { + console.error('获取状态页数据失败:', message); + return { + success: false, + message: message || '获取状态页数据失败' + }; + } + } catch (error) { + console.error('获取状态页数据失败:', error); + return { + success: false, + message: '获取状态页数据失败' + }; + } +}; \ No newline at end of file diff --git a/frontend/src/api/users.ts b/frontend/src/api/users.ts new file mode 100644 index 0000000..09237aa --- /dev/null +++ b/frontend/src/api/users.ts @@ -0,0 +1,64 @@ +import api from './index'; +import { User } from './auth'; + +export interface UserResponse { + success: boolean; + message?: string; + user?: User; + users?: User[]; +} + +export interface CreateUserRequest { + username: string; + password: string; + email?: string; + role: string; +} + +export interface UpdateUserRequest { + username?: string; + email?: string; + role?: string; + password?: string; +} + +export interface ChangePasswordRequest { + currentPassword?: string; + newPassword: string; +} + +// 获取所有用户 +export const getAllUsers = async (): Promise => { + const response = await api.get('/users'); + return response.data; +}; + +// 获取单个用户 +export const getUser = async (id: number): Promise => { + const response = await api.get(`/users/${id}`); + return response.data; +}; + +// 创建用户 +export const createUser = async (data: CreateUserRequest): Promise => { + const response = await api.post('/users', data); + return response.data; +}; + +// 更新用户 +export const updateUser = async (id: number, data: UpdateUserRequest): Promise => { + const response = await api.put(`/users/${id}`, data); + return response.data; +}; + +// 删除用户 +export const deleteUser = async (id: number): Promise => { + const response = await api.delete(`/users/${id}`); + return response.data; +}; + +// 修改密码 +export const changePassword = async (id: number, data: ChangePasswordRequest): Promise => { + const response = await api.post(`/users/${id}/change-password`, data); + return response.data; +}; \ No newline at end of file diff --git a/frontend/src/components/AgentCard.tsx b/frontend/src/components/AgentCard.tsx new file mode 100644 index 0000000..6814f7b --- /dev/null +++ b/frontend/src/components/AgentCard.tsx @@ -0,0 +1,116 @@ +import { Box, Card, Flex, Text, Badge } from '@radix-ui/themes'; +import { GlobeIcon } from '@radix-ui/react-icons'; +import { Agent } from '../api/agents'; +import ClientResourceSection from './ClientResourceSection'; +import '../styles/components.css'; + +// 格式化时间间隔为"几分钟前"、"几小时前"等 +const formatTimeAgo = (date: Date) => { + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffSec = Math.floor(diffMs / 1000); + const diffMin = Math.floor(diffSec / 60); + const diffHour = Math.floor(diffMin / 60); + const diffDay = Math.floor(diffHour / 24); + + if (diffDay > 0) { + return `${diffDay}天前`; + } else if (diffHour > 0) { + return `${diffHour}小时前`; + } else if (diffMin > 0) { + return `${diffMin}分钟前`; + } else { + return '刚刚'; + } +}; + +interface AgentCardProps { + agent: Agent; +} + +/** + * 客户端状态卡片组件 + * 用于显示单个客户端的状态和资源使用情况 + */ +const AgentCard = ({ agent }: AgentCardProps) => { + // 解析获取资源使用情况 + let cpuUsage = 0; + let memoryUsage = 0; + let diskUsage = 0; + let networkRx = 0; + let networkTx = 0; + + console.log('AgentCard接收到的客户端数据:', agent); + + try { + // 直接从agent获取资源使用情况 + if (agent.cpu_usage !== null && agent.memory_total && agent.memory_used && agent.disk_total && agent.disk_used) { + cpuUsage = Math.round(agent.cpu_usage); + memoryUsage = Math.round((agent.memory_used / agent.memory_total) * 100); + diskUsage = Math.round((agent.disk_used / agent.disk_total) * 100); + + // 传递原始网络流量数据 (KB/s),由 ClientResourceSection 负责转换为 MB/s + networkRx = agent.network_rx || 0; + networkTx = agent.network_tx || 0; + + console.log('计算后的资源使用情况:', { + cpuUsage, + memoryUsage, + diskUsage, + networkRx, + networkTx + }); + } else { + console.warn('客户端没有资源指标数据:', agent.id); + } + } catch (e) { + console.error("解析客户端资源使用数据失败", e); + } + + // 根据status属性判断状态 + const agentStatus = agent.status || 'inactive'; + + // 状态颜色映射 + const statusColors: { [key: string]: string } = { + 'active': 'green', + 'inactive': 'amber', + 'connecting': 'yellow' + }; + + // 状态文本映射 + const statusText: { [key: string]: string } = { + 'active': '活跃', + 'inactive': '离线', + 'connecting': '连接中' + }; + + return ( + + + + + + + + {agent.name} + + + + {statusText[agentStatus] || agentStatus} + + + + + + + + ); +}; + +export default AgentCard; diff --git a/frontend/src/components/ClientResourceSection.tsx b/frontend/src/components/ClientResourceSection.tsx new file mode 100644 index 0000000..c0a7216 --- /dev/null +++ b/frontend/src/components/ClientResourceSection.tsx @@ -0,0 +1,103 @@ +import { Box, Flex, Text } from '@radix-ui/themes'; +import ResourceBar from './ResourceBar'; +import '../styles/components.css'; + +interface ClientResourceSectionProps { + cpuUsage: number; + memoryUsage: number; + diskUsage: number; + networkRx: number; + networkTx: number; +} + +/** + * 客户端资源使用情况展示组件 + * 用于显示CPU、内存、磁盘和网络流量等资源使用情况 + */ +const ClientResourceSection = ({ + cpuUsage = 0, + memoryUsage = 0, + diskUsage = 0, + networkRx = 0, + networkTx = 0 +}: ClientResourceSectionProps) => { + // 将 KB/s 转换为 MB/s + const networkRxMB = networkRx / 1024; + const networkTxMB = networkTx / 1024; + + return ( + + + {/* CPU使用率 */} + + + + + CPU + + {cpuUsage.toFixed(1)}% + + + + + {/* 内存使用率 */} + + + + + 内存 + + {memoryUsage.toFixed(1)}% + + + + + {/* 磁盘使用率 */} + + + + + 磁盘 + + {diskUsage.toFixed(1)}% + + + + + {/* 网络流量 */} + + + 网络流量 + + + {/* 下载速率 */} + + + + + 下载 + + {networkRxMB.toFixed(2)} MB/s + + + + + {/* 上传速率 */} + + + + + 上传 + + {networkTxMB.toFixed(2)} MB/s + + + + + + + + ); +}; + +export default ClientResourceSection; \ No newline at end of file diff --git a/frontend/src/components/HeartbeatGrid.tsx b/frontend/src/components/HeartbeatGrid.tsx new file mode 100644 index 0000000..a9b056f --- /dev/null +++ b/frontend/src/components/HeartbeatGrid.tsx @@ -0,0 +1,139 @@ +import { Box, Flex, Text } from '@radix-ui/themes'; +import { MonitorStatusHistory } from '../api/monitors'; + +// 心跳网格组件 - 类似uptime-kuma的点阵网格 +const HeartbeatGrid = ({ status, uptime, history = [] }: { status: string, uptime: number, history?: (MonitorStatusHistory | string)[] }) => { + // 根据状态确定颜色 + const getColor = (itemStatus: string) => { + switch (itemStatus) { + case 'up': + return 'var(--green-5)'; + case 'down': + return 'var(--red-5)'; + case 'unknown': + return 'var(--gray-5)'; + default: + return 'var(--gray-5)'; + } + }; + + // 根据状态确定悬停颜色 + const getHoverColor = (itemStatus: string) => { + switch (itemStatus) { + case 'up': + return 'var(--green-6)'; + case 'down': + return 'var(--red-6)'; + case 'unknown': + return 'var(--gray-6)'; + default: + return 'var(--gray-6)'; + } + }; + + // 转换timestamp为更易读的格式 + const formatTimestamp = (timestamp: string) => { + try { + const date = new Date(timestamp); + return date.toLocaleString(); + } catch (e) { + return timestamp; + } + }; + + // 最多显示72个时间点 + const maxPoints = 72; + + // 准备显示数据 + let displayHistory: { status: string, timestamp?: string }[] = []; + + // 处理不同类型的历史数据 + if (Array.isArray(history)) { + displayHistory = history.map(item => { + if (typeof item === 'string') { + return { status: item }; + } + return item; + }); + } + + // 如果记录不足,填充空白点 + let emptyBoxes: any[] = []; + if (displayHistory.length < 24) { + // 至少显示24个点,不足的用空白点补充 + emptyBoxes = Array.from({ length: 24 - displayHistory.length }, (_, i) => ({ + status: 'empty', + })); + } + + // 计算每行显示的点数 + const pointsPerRow = 24; // 每行24个点 + const rows = Math.ceil((displayHistory.length + emptyBoxes.length) / pointsPerRow); + + // 每个点的大小 + const pointSize = 12; + const gap = 2; + + return ( + + {/* 多行网格点 */} + + {Array.from({ length: rows }).map((_, rowIndex) => ( + + {displayHistory.slice(rowIndex * pointsPerRow, (rowIndex + 1) * pointsPerRow).map((item, index) => ( + { + const target = e.currentTarget; + target.style.backgroundColor = getHoverColor(item.status); + target.style.transform = 'scale(1.2)'; + target.style.boxShadow = '0 0 4px rgba(0, 0, 0, 0.2)'; + }} + onMouseLeave={(e) => { + const target = e.currentTarget; + target.style.backgroundColor = getColor(item.status); + target.style.transform = 'scale(1)'; + target.style.boxShadow = 'none'; + }} + /> + ))} + + {/* 该行补充空白点 */} + {rowIndex === rows - 1 && emptyBoxes.map((_, index) => ( + + ))} + + ))} + + + + 正常运行: {uptime}% + + + 正常 + + 故障 + + + + ); +}; + +export default HeartbeatGrid; \ No newline at end of file diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx new file mode 100644 index 0000000..1c247f6 --- /dev/null +++ b/frontend/src/components/Layout.tsx @@ -0,0 +1,38 @@ +import { ReactNode } from 'react'; +import { Box, Flex, Text, Container, Theme, Separator } from '@radix-ui/themes'; +import Navbar from './Navbar'; +import '../styles/components.css'; + +interface LayoutProps { + children: ReactNode; +} + +const Layout = ({ children }: LayoutProps) => { + const currentYear = new Date().getFullYear(); + + return ( + + + {/* 顶部导航栏 */} + + + {/* 主要内容 */} + + {children} + + + {/* 页脚 */} + + + + + © {currentYear} XUGOU. 保留所有权利。 + + + + + + ); +}; + +export default Layout; \ No newline at end of file diff --git a/frontend/src/components/MonitorCard.tsx b/frontend/src/components/MonitorCard.tsx new file mode 100644 index 0000000..360991c --- /dev/null +++ b/frontend/src/components/MonitorCard.tsx @@ -0,0 +1,70 @@ +import { Box, Card, Flex, Text, Badge } from '@radix-ui/themes'; +import { CheckCircledIcon, CrossCircledIcon, ClockIcon, QuestionMarkCircledIcon } from '@radix-ui/react-icons'; +import { Monitor, MonitorStatusHistory } from '../api/monitors'; +import HeartbeatGrid from './HeartbeatGrid'; +import '../styles/components.css'; + +interface MonitorCardProps { + monitor: Monitor; +} + +/** + * API监控卡片组件 + * 用于显示单个API监控服务的状态信息 + */ +const MonitorCard = ({ monitor }: MonitorCardProps) => { + // 状态图标组件 + const StatusIcon = ({ status }: { status: string }) => { + switch (status) { + case 'up': + return ; + case 'down': + default: + return ; + } + }; + + // 状态颜色映射 + const statusColors: { [key: string]: string } = { + 'up': 'green', + 'down': 'red' + }; + + // 状态文本映射 + const statusText: { [key: string]: string } = { + 'up': '正常', + 'down': '故障' + }; + + return ( + + + + + + {monitor.name} + + + {statusText[monitor.status === 'up' ? 'up' : 'down']} + + + + + + 响应时间: {monitor.response_time}ms + + + + + + + + + ); +}; + +export default MonitorCard; diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx new file mode 100644 index 0000000..062e863 --- /dev/null +++ b/frontend/src/components/Navbar.tsx @@ -0,0 +1,147 @@ +import { Link, useNavigate, useLocation } from 'react-router-dom'; +import { Box, Flex, Text, Button, Avatar, DropdownMenu, Separator, Container, Theme, Badge, IconButton } from '@radix-ui/themes'; +import { ExitIcon, PersonIcon, PersonIcon as UserIcon, DashboardIcon, ChevronDownIcon, ActivityLogIcon, CubeIcon, PieChartIcon } from '@radix-ui/react-icons'; +import { useAuth } from '../contexts/AuthContext'; +import '../styles/components.css'; + +const Navbar = () => { + const navigate = useNavigate(); + const location = useLocation(); + const { user, isAuthenticated, logout } = useAuth(); + + const handleLogout = () => { + logout(); + navigate('/login'); + }; + + const isActive = (path: string) => { + return location.pathname === path || location.pathname.startsWith(`${path}/`); + }; + + return ( + + + + + + {/* Logo 部分 */} + + + + + + + XUGOU + + + + + {/* 导航链接 */} + + {isAuthenticated ? ( + <> + + + + + + + + + + + + + + + + + + + {/* 用户菜单 */} + + + + + + + + + 已登录为 + {user?.username} + + + + + {user?.role === 'admin' && ( + navigate('/users')}> + + + 用户管理 + + + )} + + navigate('/profile')}> + + + 个人资料 + + + + + + + + + 退出登录 + + + + + + ) : ( + + + + + )} + + + + + + + ); +}; + +export default Navbar; \ No newline at end of file diff --git a/frontend/src/components/ResourceBar.tsx b/frontend/src/components/ResourceBar.tsx new file mode 100644 index 0000000..b6f77a8 --- /dev/null +++ b/frontend/src/components/ResourceBar.tsx @@ -0,0 +1,75 @@ +import { Box, Theme } from '@radix-ui/themes'; +import '../styles/components.css'; + +/** + * 资源使用量进度条组件 + * 用于展示CPU、内存、磁盘等资源的使用百分比 + */ +interface ResourceBarProps { + /** 资源使用百分比,0-100 */ + value: number; + /** + * 颜色方案 + * - 'dynamic': 根据值动态变化颜色(<50%绿色,50-75%琥珀色,>75%红色) + * - 'green'/'blue'/'amber'/'red'/'cyan'/'indigo': 固定颜色 + */ + color?: string; + /** 进度条高度,单位px */ + height?: number; + /** 是否启用动画效果 */ + animate?: boolean; +} + +/** + * 资源使用量可视化进度条 + * + * @example + * // 基本用法 + * + * + * // 动态颜色(随数值变化颜色) + * + * + * // 自定义高度和颜色 + * + */ +const ResourceBar = ({ + value = 0, + color = 'green', + height = 8, + animate = true +}: ResourceBarProps) => { + // 确保值在0-100之间 + const safeValue = Math.min(Math.max(value, 0), 100); + + // 动态颜色映射 + const getColorClass = () => { + if (color === 'dynamic') { + if (safeValue < 50) return 'resource-bar-green'; + if (safeValue < 75) return 'resource-bar-amber'; + return 'resource-bar-red'; + } + + return `resource-bar-${color}`; + }; + + return ( + + + + ); +}; + +export default ResourceBar; \ No newline at end of file diff --git a/frontend/src/components/StatusSummaryCard.tsx b/frontend/src/components/StatusSummaryCard.tsx new file mode 100644 index 0000000..b04ee7a --- /dev/null +++ b/frontend/src/components/StatusSummaryCard.tsx @@ -0,0 +1,52 @@ +import { Box, Card, Flex, Heading, Text } from '@radix-ui/themes'; +import { ReactNode } from 'react'; +import '../styles/components.css'; + +interface StatusItem { + icon: ReactNode; + label: string; + value: number; + bgColor?: string; + iconColor?: string; +} + +interface StatusSummaryCardProps { + title: string; + items: StatusItem[]; +} + +/** + * 状态摘要卡片组件 + * 用于显示系统状态概览,如正常/异常服务数量等 + */ +const StatusSummaryCard = ({ title, items }: StatusSummaryCardProps) => { + return ( + + + {title} + + {items.map((item, index) => ( + + + + {item.icon} + + {item.label} + + {item.value} + + ))} + + + + ); +}; + +export default StatusSummaryCard; diff --git a/frontend/src/contexts/AuthContext.tsx b/frontend/src/contexts/AuthContext.tsx new file mode 100644 index 0000000..cfe93dd --- /dev/null +++ b/frontend/src/contexts/AuthContext.tsx @@ -0,0 +1,121 @@ +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { login as apiLogin, register as apiRegister, getCurrentUser, User, LoginRequest, RegisterRequest } from '../api/auth'; + +interface AuthContextType { + user: User | null; + token: string | null; + isAuthenticated: boolean; + isLoading: boolean; + login: (data: LoginRequest) => Promise<{ success: boolean; message: string }>; + register: (data: RegisterRequest) => Promise<{ success: boolean; message: string }>; + logout: () => void; +} + +const AuthContext = createContext(undefined); + +export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [user, setUser] = useState(null); + const [token, setToken] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + // 从 localStorage 获取 token 和 user + const storedToken = localStorage.getItem('token'); + const storedUser = localStorage.getItem('user'); + + if (storedToken && storedUser) { + setToken(storedToken); + setUser(JSON.parse(storedUser)); + setIsLoading(false); + } else { + setIsLoading(false); + } + }, []); + + useEffect(() => { + // 如果有 token,但没有 user,则获取用户信息 + if (token && !user) { + fetchCurrentUser(); + } + }, [token, user]); + + const fetchCurrentUser = async () => { + try { + const response = await getCurrentUser(); + if (response.success && response.user) { + setUser(response.user); + localStorage.setItem('user', JSON.stringify(response.user)); + } else { + // 如果获取用户信息失败,清除 token 和 user + localStorage.removeItem('token'); + localStorage.removeItem('user'); + setToken(null); + setUser(null); + } + } catch (error) { + console.error('获取用户信息失败:', error); + localStorage.removeItem('token'); + localStorage.removeItem('user'); + setToken(null); + setUser(null); + } + }; + + const login = async (data: LoginRequest) => { + try { + const response = await apiLogin(data); + if (response.success && response.token && response.user) { + localStorage.setItem('token', response.token); + localStorage.setItem('user', JSON.stringify(response.user)); + setToken(response.token); + setUser(response.user); + } + return { success: response.success, message: response.message }; + } catch (error) { + console.error('登录失败:', error); + return { success: false, message: '登录失败,请稍后再试' }; + } + }; + + const register = async (data: RegisterRequest) => { + try { + const response = await apiRegister(data); + return { success: response.success, message: response.message }; + } catch (error) { + console.error('注册失败:', error); + return { success: false, message: '注册失败,请稍后再试' }; + } + }; + + const logout = () => { + localStorage.removeItem('token'); + localStorage.removeItem('user'); + setToken(null); + setUser(null); + window.location.href = '/login'; + }; + + return ( + + {children} + + ); +}; + +export const useAuth = () => { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +}; \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..6339f89 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,20 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import { BrowserRouter } from 'react-router-dom' +import { Theme } from '@radix-ui/themes' +import { AuthProvider } from './contexts/AuthContext' +import '@radix-ui/themes/styles.css' +import './styles/index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + + + + + + + , +) diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx new file mode 100644 index 0000000..1068de2 --- /dev/null +++ b/frontend/src/pages/Dashboard.tsx @@ -0,0 +1,203 @@ +import { useState, useEffect } from 'react'; +import { Box, Flex, Heading, Text, Grid, Button, Container, Theme } from '@radix-ui/themes'; +import { CheckCircledIcon, CrossCircledIcon, ClockIcon, GlobeIcon, ExclamationTriangleIcon, ArrowRightIcon } from '@radix-ui/react-icons'; +import { Link } from 'react-router-dom'; +import { getAllMonitors, Monitor } from '../api/monitors'; +import { getAllAgents, Agent } from '../api/agents'; +import StatusSummaryCard from '../components/StatusSummaryCard'; +import MonitorCard from '../components/MonitorCard'; +import AgentCard from '../components/AgentCard'; +import '../styles/components.css'; + +const Dashboard = () => { + const [monitors, setMonitors] = useState([]); + const [agents, setAgents] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // 获取所有数据 + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + + // 同时获取监控和客户端数据 + const [monitorsResponse, agentsResponse] = await Promise.all([ + getAllMonitors(), + getAllAgents() + ]); + + // 处理监控数据 + if (monitorsResponse.success && monitorsResponse.monitors) { + setMonitors(monitorsResponse.monitors); + } else { + console.error('获取监控数据失败:', monitorsResponse.message); + } + + // 处理客户端数据 + if (agentsResponse.success && agentsResponse.agents) { + console.log('获取到客户端列表:', agentsResponse.agents); + setAgents(agentsResponse.agents); + } else { + console.error('获取客户端数据失败:', agentsResponse.message); + } + } catch (err) { + console.error('获取数据错误:', err); + setError('获取数据失败'); + } finally { + setLoading(false); + } + }; + + fetchData(); + + // 设置定时器,每分钟刷新一次数据 + const intervalId = setInterval(() => { + console.log('Dashboard: 自动刷新数据...'); + fetchData(); + }, 60000); // 60000ms = 1分钟 + + // 组件卸载时清除定时器 + return () => clearInterval(intervalId); + }, []); + + // 加载中显示 + if (loading) { + return ( + + + + 加载中... + + + + ); + } + + // 错误显示 + if (error) { + return ( + + + + + {error} + + + + + + ); + } + + // 准备API监控状态摘要数据 + const apiMonitorItems = [ + { + icon: , + label: '正常运行', + value: monitors.filter(m => m.status === 'up').length, + bgColor: 'var(--green-3)', + iconColor: 'var(--green-9)' + }, + { + icon: , + label: '服务中断', + value: monitors.filter(m => m.status === 'down').length, + bgColor: 'var(--red-3)', + iconColor: 'var(--red-9)' + }, + { + icon: , + label: '总计监控', + value: monitors.length, + bgColor: 'var(--gray-3)', + iconColor: 'var(--gray-9)' + } + ]; + + // 准备客户端状态摘要数据 + const agentStatusItems = [ + { + icon: , + label: '客户端活跃', + value: agents.filter(a => a.status === 'active').length, + bgColor: 'var(--green-3)', + iconColor: 'var(--green-9)' + }, + { + icon: , + label: '客户端离线', + value: agents.filter(a => a.status === 'inactive').length, + bgColor: 'var(--amber-3)', + iconColor: 'var(--amber-9)' + }, + { + icon: , + label: '总计客户端', + value: agents.length, + bgColor: 'var(--gray-3)', + iconColor: 'var(--gray-9)' + } + ]; + + return ( + + + + + {/* 状态摘要 */} + + 系统状态摘要 + + + {/* API监控状态摘要 */} + + + + + {/* 客户端监控状态摘要 */} + + + + + + + {/* API监控状态 */} + + + API监控状态 + + + + {monitors.slice(0, 3).map(monitor => ( + + ))} + + + + {/* 客户端状态 */} + + + 客户端状态 + + + + {agents.slice(0, 3).map((agent) => ( + + ))} + + + + + + + ); +}; + +export default Dashboard; \ No newline at end of file diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx new file mode 100644 index 0000000..ff94956 --- /dev/null +++ b/frontend/src/pages/Home.tsx @@ -0,0 +1,87 @@ +import { Box, Flex, Heading, Text, Button, Card, Grid } from '@radix-ui/themes'; +import { Link } from 'react-router-dom'; +import { ActivityLogIcon, LightningBoltIcon, MixerHorizontalIcon, DesktopIcon } from '@radix-ui/react-icons'; +import { useAuth } from '../contexts/AuthContext'; + +const Home = () => { + const { isAuthenticated } = useAuth(); + + return ( + +
+ {/* 英雄区域 */} + + + 轻量化监控平台 + + + XUGOU 是一个轻量化的监控平台,基于 CloudFlare 搭建,用于跟踪API、服务和系统的可用性和性能。 + + + {isAuthenticated ? ( + + ) : ( + <> + + + + )} + + + + {/* 特性区域 */} + + + 主要功能 + + + + + + API监控 + + 监控API端点的可用性和性能,设置自定义检查间隔和警报阈值。 + + + + + + + 系统监控 + + 通过轻量级客户端收集服务器的CPU、内存、磁盘和网络指标。 + + + + + + + 实时通知 + + 当监控检测到问题时,通过多种渠道接收实时通知。 + + + + + + + 自定义状态页 + + 创建公共状态页面,展示服务的实时状态和历史可用性。 + + + + + +
+
+ ); +}; + +export default Home; \ No newline at end of file diff --git a/frontend/src/pages/NotFound.tsx b/frontend/src/pages/NotFound.tsx new file mode 100644 index 0000000..2627fb0 --- /dev/null +++ b/frontend/src/pages/NotFound.tsx @@ -0,0 +1,23 @@ +import { Box, Flex, Heading, Text, Button } from '@radix-ui/themes'; +import { Link } from 'react-router-dom'; +import { ExclamationTriangleIcon } from '@radix-ui/react-icons'; + +const NotFound = () => { + return ( +
+ + + 404 + 页面未找到 + + 您访问的页面不存在或已被移除。请检查URL是否正确,或返回首页。 + + + +
+ ); +}; + +export default NotFound; \ No newline at end of file diff --git a/frontend/src/pages/agents/AgentDetail.tsx b/frontend/src/pages/agents/AgentDetail.tsx new file mode 100644 index 0000000..889addd --- /dev/null +++ b/frontend/src/pages/agents/AgentDetail.tsx @@ -0,0 +1,398 @@ +import { useState, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { Box, Flex, Heading, Text, Button, Card, Badge, Tabs, Grid, Table, Avatar } from '@radix-ui/themes'; +import { ArrowLeftIcon, Pencil1Icon, Cross2Icon, ReloadIcon, ClockIcon, InfoCircledIcon, LapTimerIcon, DesktopIcon, GlobeIcon, LaptopIcon } from '@radix-ui/react-icons'; +import * as Toast from '@radix-ui/react-toast'; +import { getAgent, Agent, deleteAgent } from '../../api/agents'; +import ResourceBar from '../../components/ResourceBar'; +import ClientResourceSection from '../../components/ClientResourceSection'; + +// 定义客户端状态颜色映射 +const statusColors: Record = { + active: "green", + inactive: "red", + connecting: "yellow", + unknown: "gray" +}; + +// 日志级别颜色映射 +const logLevelColors: Record = { + error: "red", + warning: "yellow", + info: "blue", + debug: "gray" +}; + +// 客户端详情包含系统资源信息的扩展接口 +interface AgentWithResources extends Agent { + uptime: number; + cpuUsage?: number; + memoryUsage?: number; + diskUsage?: number; + networkRx?: number; + networkTx?: number; +} + +const AgentDetail = () => { + const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); + const [agent, setAgent] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [deleteLoading, setDeleteLoading] = useState(false); + const [toastOpen, setToastOpen] = useState(false); + const [toastMessage, setToastMessage] = useState(''); + const [toastType, setToastType] = useState<'success' | 'error'>('success'); + + const fetchAgentData = async () => { + setLoading(true); + setError(null); + + try { + console.log('AgentDetail: 正在获取客户端数据...'); + const agentResponse = await getAgent(Number(id)); + + if (!agentResponse.success || !agentResponse.agent) { + throw new Error(agentResponse.message || '获取客户端数据失败'); + } + + const agentData = agentResponse.agent; + + // 计算资源使用百分比 + const cpuUsage = agentData.cpu_usage || 0; + const memoryUsage = agentData.memory_total && agentData.memory_used ? + Math.round((agentData.memory_used / agentData.memory_total) * 100) : 0; + const diskUsage = agentData.disk_total && agentData.disk_used ? + Math.round((agentData.disk_used / agentData.disk_total) * 100) : 0; + + // 扩展客户端信息 + const agentWithDetails: AgentWithResources = { + ...agentData, + uptime: 0, // 没有真正的 uptime 数据,这里简化处理 + cpuUsage, + memoryUsage, + diskUsage, + networkRx: agentData.network_rx || 0, // 保持原始 KB/s 单位 + networkTx: agentData.network_tx || 0 // 保持原始 KB/s 单位 + }; + + setAgent(agentWithDetails); + setLoading(false); + } catch (err) { + console.error('AgentDetail: 获取客户端数据失败:', err); + setError(err instanceof Error ? err.message : '获取客户端数据失败'); + setLoading(false); + } + }; + + useEffect(() => { + // 确保id是有效的数字 + if (id && !isNaN(Number(id))) { + fetchAgentData(); + + // 设置定时器,每分钟刷新一次数据 + const intervalId = setInterval(() => { + console.log('AgentDetail: 自动刷新数据...'); + fetchAgentData(); + }, 60000); // 60000ms = 1分钟 + + // 组件卸载时清除定时器 + return () => clearInterval(intervalId); + } else if (id) { + // 如果id存在但不是有效数字 + console.error(`无效的客户端ID: ${id}`); + setError(`无效的客户端ID: ${id}`); + setLoading(false); + } + }, [id]); + + const handleRefresh = () => { + fetchAgentData(); + }; + + const formatDate = (dateString: string | null) => { + if (!dateString) return '未知'; + + const date = new Date(dateString); + return new Intl.DateTimeFormat('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }).format(date); + }; + + const formatUptime = (seconds: number, agent: AgentWithResources) => { + // 如果有最后活动时间,计算从创建到最后活动的时间差 + if (agent.updated_at) { + const lastSeenDate = new Date(agent.updated_at); + const createdDate = new Date(agent.created_at); + const diffMs = lastSeenDate.getTime() - createdDate.getTime(); + + // 转换为天、小时、分钟 + const diffSec = Math.floor(diffMs / 1000); + const days = Math.floor(diffSec / 86400); + const hours = Math.floor((diffSec % 86400) / 3600); + const minutes = Math.floor((diffSec % 3600) / 60); + + let result = ''; + if (days > 0) result += `${days}天 `; + if (hours > 0 || days > 0) result += `${hours}小时 `; + result += `${minutes}分钟`; + + return result; + } + + // 如果没有活动时间记录,显示0小时 + return '0小时'; + }; + + // 使用agent.updated_at代替last_seen作为上次活动时间 + const formatDateTime = (dateTimeStr: string) => { + if (!dateTimeStr) return '未知'; + const date = new Date(dateTimeStr); + return date.toLocaleString(); + }; + + const handleDelete = async () => { + if (!confirm('确定要删除此客户端吗?此操作无法撤销。')) { + return; + } + + try { + setDeleteLoading(true); + // 显示正在删除的Toast消息 + setToastMessage('正在删除客户端...'); + setToastType('success'); + setToastOpen(true); + + const response = await deleteAgent(Number(id)); + + if (response.success) { + // 显示删除成功的Toast消息 + setToastMessage('客户端已成功删除'); + setToastType('success'); + setToastOpen(true); + + // 延长延迟时间,确保Toast消息有足够时间显示 + setTimeout(() => { + navigate('/agents'); + }, 3000); + } else { + setToastMessage(response.message || '删除客户端失败'); + setToastType('error'); + setToastOpen(true); + } + } catch (error) { + console.error('删除客户端失败:', error); + setToastMessage('删除客户端失败,请重试'); + setToastType('error'); + setToastOpen(true); + } finally { + setDeleteLoading(false); + } + }; + + if (loading) { + return ( + + + 加载客户端详情中... + + + ); + } + + if (error) { + return ( + + + + + 加载出错 + {error} + + + + + + ); + } + + if (!agent) { + return ( + + + + + 客户端未找到 + 找不到ID为 {id} 的客户端 + + + + + + ); + } + + return ( + +
+ + + + 客户端详情 + + {agent.status === 'active' ? '在线' : '离线'} + + + + + + + + + +
+ + + + + {agent.name} + + {agent.hostname ? agent.hostname : ''} + {agent.hostname && agent.ip_address ? ` (${agent.ip_address})` : ''} + + + + + + + + 运行时间: + {formatUptime(agent.uptime, agent)} + + + + + 上次更新: + {formatDateTime(agent.updated_at)} + + + + + + + 系统状态 + + + + + + {/* 系统信息卡片 */} + + + 系统信息 + + + + + 系统: + {agent.os || '未知'} + + + + + + + 版本: + {agent.version || '未知'} + + + + + + + 主机名: + {agent.hostname || '未知'} + + + + + + + IP地址: + {agent.ip_address || '未知'} + + + + + + {/* 系统资源信息卡片 */} + + + 系统资源 + + + + + + + + +
+ + + + + {toastType === 'success' ? '成功' : '错误'} + + + {toastMessage} + + + + + + + +
+
+ ); +}; + +export default AgentDetail; \ No newline at end of file diff --git a/frontend/src/pages/agents/AgentsList.tsx b/frontend/src/pages/agents/AgentsList.tsx new file mode 100644 index 0000000..262bd49 --- /dev/null +++ b/frontend/src/pages/agents/AgentsList.tsx @@ -0,0 +1,318 @@ +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Box, Flex, Heading, Text, Button, Card, Table, Badge, IconButton, Dialog, Tooltip, Grid, Tabs } from '@radix-ui/themes'; +import { PlusIcon, Cross2Icon, Pencil1Icon, InfoCircledIcon, ReloadIcon, LayoutIcon, ViewGridIcon } from '@radix-ui/react-icons'; +import { getAllAgents, deleteAgent, Agent } from '../../api/agents'; +import AgentCard from '../../components/AgentCard'; + +// 定义客户端状态颜色映射 +const statusColors: Record = { + active: "green", + inactive: "red", + connecting: "yellow", + unknown: "gray" +}; + +interface ClientWithStatus extends Agent { + status?: 'active' | 'inactive' | 'connecting'; +} + +const AgentsList = () => { + const navigate = useNavigate(); + const [agents, setAgents] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [selectedAgentId, setSelectedAgentId] = useState(null); + const [viewMode, setViewMode] = useState<'table' | 'card'>('card'); // 默认使用卡片视图 + + // 获取客户端数据 + const fetchAgents = async () => { + setLoading(true); + setError(null); + + try { + // 获取所有客户端 + const response = await getAllAgents(); + + if (!response.success || !response.agents) { + throw new Error(response.message || '获取客户端数据失败'); + } + + // 处理客户端数据 + const clientsWithStatus: ClientWithStatus[] = response.agents.map((agent: Agent) => { + // 直接使用服务器端返回的状态 + let status: 'active' | 'inactive' | 'connecting' = 'inactive'; + + if (agent.status === 'active') { + status = 'active'; + } else if (agent.status === 'connecting') { + status = 'connecting'; + } + + return { + ...agent, + status + }; + }); + + setAgents(clientsWithStatus); + } catch (err) { + setError(err instanceof Error ? err.message : '获取客户端列表失败'); + console.error('获取客户端列表错误:', err); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchAgents(); + + // 设置定时器,每分钟刷新一次数据 + const intervalId = setInterval(() => { + console.log('AgentsList: 自动刷新数据...'); + fetchAgents(); + }, 60000); // 60000ms = 1分钟 + + // 组件卸载时清除定时器 + return () => clearInterval(intervalId); + }, []); + + // 刷新客户端列表 + const handleRefresh = () => { + fetchAgents(); + }; + + // 打开删除确认对话框 + const handleDeleteClick = (agentId: number) => { + setSelectedAgentId(agentId); + setDeleteDialogOpen(true); + }; + + // 确认删除客户端 + const handleDeleteConfirm = async () => { + if (selectedAgentId) { + setLoading(true); + try { + const response = await deleteAgent(selectedAgentId); + + if (response.success) { + // 删除成功,刷新客户端列表 + fetchAgents(); + } else { + setError(response.message || '删除客户端失败'); + } + } catch (err) { + setError(err instanceof Error ? err.message : '删除客户端时发生错误'); + console.error('删除客户端错误:', err); + } finally { + setDeleteDialogOpen(false); + setSelectedAgentId(null); + setLoading(false); + } + } + }; + + // 展示卡片视图 + const renderCardView = () => { + return ( + + {agents.map(agent => ( + + + + navigate(`/agents/${agent.id}`)} title="查看详情"> + + + navigate(`/agents/edit/${agent.id}`)} title="编辑客户端"> + + + handleDeleteClick(agent.id)} title="删除客户端"> + + + + + ))} + + ); + }; + + // 展示表格视图 + const renderTableView = () => { + return ( + + + + 名称 + 主机名 + IP地址 + 状态 + 操作系统 + 版本 + 操作 + + + + {agents.map(agent => ( + + + {agent.name} + + + {agent.hostname || '未知'} + + + {agent.ip_address || '未知'} + + + + {agent.status === 'active' + ? '在线' + : agent.status === 'connecting' + ? '连接中' + : '离线' + } + + + + {agent.os || '未知'} + + + {agent.version || '未知'} + + + + navigate(`/agents/${agent.id}`)}> + + + navigate(`/agents/edit/${agent.id}`)}> + + + handleDeleteClick(agent.id)}> + + + + + + ))} + + + ); + }; + + // 加载中显示 + if (loading) { + return ( + + + 加载中... + + + ); + } + + // 错误显示 + if (error) { + return ( + + + + {error} + + + + + ); + } + + return ( + +
+ + 客户端监控 + + + + setViewMode('card')}> + + + setViewMode('table')}> + + + + + + + + + +
+ {agents.length === 0 ? ( + + + 没有找到客户端 + + + + ) : viewMode === 'table' ? ( + // 表格视图 + renderTableView() + ) : ( + // 卡片视图 + renderCardView() + )} +
+
+ + {/* 删除确认对话框 */} + + + 确认删除 + + 您确定要删除此客户端吗?此操作无法撤销。 + + + + + + + + + +
+ ); +}; + +export default AgentsList; \ No newline at end of file diff --git a/frontend/src/pages/agents/CreateAgent.tsx b/frontend/src/pages/agents/CreateAgent.tsx new file mode 100644 index 0000000..a06c561 --- /dev/null +++ b/frontend/src/pages/agents/CreateAgent.tsx @@ -0,0 +1,281 @@ +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Box, Flex, Heading, Text, Button, Card, Code, Separator } from '@radix-ui/themes'; +import { ArrowLeftIcon, CopyIcon, CheckIcon, InfoCircledIcon } from '@radix-ui/react-icons'; +import { generateToken } from '../../api/agents'; + +const CreateAgent = () => { + const navigate = useNavigate(); + const [loading, setLoading] = useState(false); + const [token, setToken] = useState(''); + const [copied, setCopied] = useState(false); + // 获取当前浏览器访问的地址作为服务端地址 + const [serverUrl, setServerUrl] = useState(''); + + // 生成服务端验证的 token + useEffect(() => { + // 获取当前访问的URL + const origin = window.location.origin; + setServerUrl(origin); + + const fetchToken = async () => { + setLoading(true); + try { + const response = await generateToken(); + if (response.success && response.token) { + setToken(response.token); + } else { + console.error('获取 token 失败:', response.message); + } + } catch (error) { + console.error('获取 token 失败:', error); + } finally { + setLoading(false); + } + }; + + fetchToken(); + }, []); + + // 复制token + const handleCopyToken = () => { + navigator.clipboard.writeText(token); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + // 复制完整命令 + const handleCopyCommand = () => { + const command = `curl -sSL ${serverUrl}/agent/install | sudo bash -s -- --token=${token} --server=${serverUrl}`; + navigator.clipboard.writeText(command); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + // 处理服务端地址变更 + const handleServerUrlChange = (e: React.ChangeEvent) => { + setServerUrl(e.target.value); + }; + + return ( + +
+ + + + 添加客户端 + + + +
+ + + {/* 提示信息 */} + + + + + 客户端将在运行安装命令后自动添加到系统中,无需手动创建 + + + + + {/* 服务端地址 */} + + + 服务端地址 + + + + 请确保此地址可以从客户端服务器访问 + + + + {/* 客户端Token */} + + + 注册Token + + {loading ? ( + 正在生成Token... + ) : ( + <> + + + {token} + + + + + 此令牌将用于您的客户端向服务器注册身份,有效期为24小时 + + + )} + + + + + {/* 安装指南 */} + + + 安装指南 + 按照以下两个步骤安装并注册客户端 + + + + + {/* 第一步:下载客户端二进制文件 */} + + 步骤 1: 下载客户端二进制文件 + + + {`curl -sSL https://xugou-agent.r2.dev/xugou-agent-latest -o xugou-agent && chmod +x xugou-agent`} + + + + + 此命令将从 Cloudflare R2 存储下载最新版客户端二进制文件并赋予可执行权限 + + + + {/* 第二步:运行并注册客户端 */} + + 步骤 2: 注册并启动客户端 + + + {`./xugou-agent start --server ${serverUrl} --token ${token} --interval 60`} + + + + + 此命令将启动客户端,并使用生成的令牌自动注册到服务端。参数 interval 表示上报间隔(秒) + + + + {/* 设置为系统服务(可选) */} + + (可选)将客户端设置为系统服务 + + 在 Linux/Unix 系统上,您可以使用以下命令创建系统服务 + + + + {`sudo mv xugou-agent /usr/local/bin/ +sudo tee /etc/systemd/system/xugou-agent.service > /dev/null << EOF +[Unit] +Description=Xugou Agent +After=network.target + +[Service] +ExecStart=/usr/local/bin/xugou-agent start --server ${serverUrl} --token ${token} --interval 60 +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +EOF + +sudo systemctl enable xugou-agent.service +sudo systemctl start xugou-agent.service`} + + + + + + + + + {/* 返回按钮 */} + + + + + +
+
+
+ ); +}; + +export default CreateAgent; \ No newline at end of file diff --git a/frontend/src/pages/agents/EditAgent.tsx b/frontend/src/pages/agents/EditAgent.tsx new file mode 100644 index 0000000..559223e --- /dev/null +++ b/frontend/src/pages/agents/EditAgent.tsx @@ -0,0 +1,184 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { Box, Flex, Heading, Text, Button, Card, TextField } from '@radix-ui/themes'; +import { ArrowLeftIcon, Cross2Icon } from '@radix-ui/react-icons'; +import * as Toast from '@radix-ui/react-toast'; +import { getAgent, updateAgent } from '../../api/agents'; + +const EditAgent = () => { + const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); + const [loading, setLoading] = useState(false); + const [notFound, setNotFound] = useState(false); + const [toastOpen, setToastOpen] = useState(false); + const [toastMessage, setToastMessage] = useState(''); + const [toastType, setToastType] = useState<'success' | 'error'>('success'); + const initialFormData = { + name: '', + status: 'active' as 'active' | 'inactive' + }; + const [formData, setFormData] = useState(initialFormData); + + useEffect(() => { + const fetchAgentData = async () => { + try { + setLoading(true); + + const response = await getAgent(Number(id)); + + if (response.success && response.agent) { + const agent = response.agent; + + setFormData({ + name: agent.name, + status: agent.status || 'inactive' + }); + } else { + setNotFound(true); + } + } catch (error) { + console.error('获取客户端数据失败:', error); + setNotFound(true); + } finally { + setLoading(false); + } + }; + + if (id) { + fetchAgentData(); + } + }, [id]); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value + })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + try { + setLoading(true); + + // 准备提交数据 - 只包含名称 + const payload = { + name: formData.name + }; + + // 调用API更新客户端 + const response = await updateAgent(Number(id), payload); + + if (response.success) { + setToastMessage('客户端更新成功'); + setToastType('success'); + setToastOpen(true); + + // 短暂延迟后导航,让用户有时间看到提示 + setTimeout(() => { + navigate('/agents'); + }, 1500); + } else { + setToastMessage(response.message || '更新失败'); + setToastType('error'); + setToastOpen(true); + } + } catch (error) { + console.error('更新客户端失败:', error); + setToastMessage('更新客户端失败'); + setToastType('error'); + setToastOpen(true); + } finally { + setLoading(false); + } + }; + + if (notFound) { + return ( + + + + + 客户端未找到 + 找不到ID为 {id} 的客户端 + + + + + + ); + } + + return ( + +
+ + + + 编辑客户端: {formData.name} + + + +
+ +
+ + + + 客户端名称 * + + + + 为客户端设置一个能够识别的名称 + + + + + + + + +
+
+
+ + + + + {toastType === 'success' ? '成功' : '错误'} + + + {toastMessage} + + + + + + + +
+
+ ); +}; + +export default EditAgent; \ No newline at end of file diff --git a/frontend/src/pages/auth/Login.tsx b/frontend/src/pages/auth/Login.tsx new file mode 100644 index 0000000..04bea3e --- /dev/null +++ b/frontend/src/pages/auth/Login.tsx @@ -0,0 +1,107 @@ +import { useState, useEffect } from 'react'; +import { useNavigate, Link, useLocation } from 'react-router-dom'; +import { Button, Card, Flex, Heading, Text, Box } from '@radix-ui/themes'; +import { useAuth } from '../../contexts/AuthContext'; +import './auth.css'; + +const Login = () => { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [message, setMessage] = useState(''); + + const { login } = useAuth(); + const navigate = useNavigate(); + const location = useLocation(); + + // 检查是否有来自注册页面的消息 + useEffect(() => { + if (location.state?.message) { + setMessage(location.state.message); + } + }, [location.state]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + setIsLoading(true); + + try { + const result = await login({ username, password }); + if (result.success) { + navigate('/dashboard'); + } else { + setError(result.message); + } + } catch (err: any) { + setError(err.message || '登录失败,请检查您的凭据'); + } finally { + setIsLoading(false); + } + }; + + return ( +
+ + + + 登录 + + {message && ( + {message} + )} + + {error && ( + {error} + )} + +
+ +
+ setUsername(e.target.value)} + required + className="text-input" + /> +
+ +
+ setPassword(e.target.value)} + required + className="text-input" + /> +
+ + +
+
+ + + + + 忘记密码? + + + + + 注册账号 + + + +
+
+
+
+ ); +}; + +export default Login; \ No newline at end of file diff --git a/frontend/src/pages/auth/Register.tsx b/frontend/src/pages/auth/Register.tsx new file mode 100644 index 0000000..a744a23 --- /dev/null +++ b/frontend/src/pages/auth/Register.tsx @@ -0,0 +1,116 @@ +import { useState } from 'react'; +import { useNavigate, Link } from 'react-router-dom'; +import { Button, Card, Flex, Heading, Text, Box } from '@radix-ui/themes'; +import { useAuth } from '../../contexts/AuthContext'; +import './auth.css'; + +const Register = () => { + const [username, setUsername] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + const { register } = useAuth(); + const navigate = useNavigate(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + + // 验证密码 + if (password !== confirmPassword) { + setError('两次输入的密码不匹配'); + return; + } + + setIsLoading(true); + + try { + const result = await register({ username, password, email }); + if (result.success) { + navigate('/login', { state: { message: '注册成功,请登录' } }); + } else { + setError(result.message); + } + } catch (err: any) { + setError(err.message || '注册失败,请稍后重试'); + } finally { + setIsLoading(false); + } + }; + + return ( +
+ + + + 注册账号 + + {error && ( + {error} + )} + +
+ +
+ setUsername(e.target.value)} + required + className="text-input" + /> +
+ +
+ setEmail(e.target.value)} + required + className="text-input" + /> +
+ +
+ setPassword(e.target.value)} + required + className="text-input" + /> +
+ +
+ setConfirmPassword(e.target.value)} + required + className="text-input" + /> +
+ + +
+
+ + + 已有账号? 登录 + +
+
+
+
+ ); +}; + +export default Register; \ No newline at end of file diff --git a/frontend/src/pages/auth/auth.css b/frontend/src/pages/auth/auth.css new file mode 100644 index 0000000..8aa7b1f --- /dev/null +++ b/frontend/src/pages/auth/auth.css @@ -0,0 +1 @@ +/* 这个文件已经被全局样式文件替代,保留此文件以避免导入错误 */ \ No newline at end of file diff --git a/frontend/src/pages/events/EventDetail.tsx b/frontend/src/pages/events/EventDetail.tsx new file mode 100644 index 0000000..b12f726 --- /dev/null +++ b/frontend/src/pages/events/EventDetail.tsx @@ -0,0 +1,511 @@ +import { useState, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { Box, Flex, Heading, Text, Button, Card, Badge, Tabs, Grid, TextArea, Avatar } from '@radix-ui/themes'; +import { ArrowLeftIcon, CheckIcon, Cross2Icon, ReloadIcon, ClockIcon, InfoCircledIcon, PersonIcon } from '@radix-ui/react-icons'; + +// 定义事件严重性颜色映射 +const severityColors: Record = { + critical: "red", + high: "orange", + medium: "yellow", + low: "blue", + info: "gray" +}; + +// 定义事件类型接口 +interface Event { + id: string; + title: string; + description: string; + source: string; + sourceId: string; + sourceName: string; + severity: string; + status: string; + createdAt: string; + updatedAt: string; + assignedTo: string | null; + resolvedBy?: string; + resolution?: string; +} + +// 模拟事件数据 +const mockEvents: Event[] = [ + { + id: '1', + title: 'API服务不可用', + description: 'API服务连续3次检查失败,当前状态为不可用。', + source: 'monitor', + sourceId: '1', + sourceName: 'API服务', + severity: 'critical', + status: 'active', + createdAt: '2023-10-15T14:30:00Z', + updatedAt: '2023-10-15T14:30:00Z', + assignedTo: null + }, + { + id: '2', + title: '数据库服务响应缓慢', + description: '数据库服务响应时间超过阈值(500ms),当前响应时间为780ms。', + source: 'monitor', + sourceId: '3', + sourceName: '数据库服务', + severity: 'high', + status: 'active', + createdAt: '2023-10-15T13:45:00Z', + updatedAt: '2023-10-15T13:45:00Z', + assignedTo: '张三' + }, + { + id: '3', + title: '生产服务器CPU使用率高', + description: '生产服务器CPU使用率超过90%,当前为92%。', + source: 'agent', + sourceId: '1', + sourceName: '生产服务器客户端', + severity: 'medium', + status: 'active', + createdAt: '2023-10-15T12:20:00Z', + updatedAt: '2023-10-15T12:20:00Z', + assignedTo: null + }, + { + id: '4', + title: '测试服务器客户端离线', + description: '测试服务器客户端已离线超过30分钟。', + source: 'agent', + sourceId: '2', + sourceName: '测试服务器客户端', + severity: 'medium', + status: 'resolved', + createdAt: '2023-10-14T09:15:00Z', + updatedAt: '2023-10-14T10:30:00Z', + assignedTo: '李四', + resolvedBy: '李四', + resolution: '重启了客户端服务' + }, + { + id: '5', + title: '用户服务性能下降', + description: '用户服务响应时间增加了50%,从200ms增加到300ms。', + source: 'monitor', + sourceId: '2', + sourceName: '用户服务', + severity: 'low', + status: 'resolved', + createdAt: '2023-10-13T16:40:00Z', + updatedAt: '2023-10-13T17:30:00Z', + assignedTo: '王五', + resolvedBy: '王五', + resolution: '增加了服务实例数量' + }, + { + id: '6', + title: '备份服务器磁盘空间不足', + description: '备份服务器磁盘使用率达到85%,接近警告阈值。', + source: 'agent', + sourceId: '4', + sourceName: '备份服务器客户端', + severity: 'info', + status: 'active', + createdAt: '2023-10-15T11:10:00Z', + updatedAt: '2023-10-15T11:10:00Z', + assignedTo: null + } +]; + +// 模拟事件时间线数据 +const mockTimeline = [ + { + id: '1', + eventId: '1', + type: 'created', + timestamp: '2023-10-15T14:30:00Z', + user: '系统', + message: '事件已创建' + }, + { + id: '2', + eventId: '2', + type: 'created', + timestamp: '2023-10-15T13:45:00Z', + user: '系统', + message: '事件已创建' + }, + { + id: '3', + eventId: '2', + type: 'assigned', + timestamp: '2023-10-15T13:50:00Z', + user: '管理员', + message: '事件已分配给张三' + }, + { + id: '4', + eventId: '3', + type: 'created', + timestamp: '2023-10-15T12:20:00Z', + user: '系统', + message: '事件已创建' + }, + { + id: '5', + eventId: '4', + type: 'created', + timestamp: '2023-10-14T09:15:00Z', + user: '系统', + message: '事件已创建' + }, + { + id: '6', + eventId: '4', + type: 'assigned', + timestamp: '2023-10-14T09:30:00Z', + user: '管理员', + message: '事件已分配给李四' + }, + { + id: '7', + eventId: '4', + type: 'resolved', + timestamp: '2023-10-14T10:30:00Z', + user: '李四', + message: '事件已解决:重启了客户端服务' + }, + { + id: '8', + eventId: '5', + type: 'created', + timestamp: '2023-10-13T16:40:00Z', + user: '系统', + message: '事件已创建' + }, + { + id: '9', + eventId: '5', + type: 'assigned', + timestamp: '2023-10-13T16:45:00Z', + user: '管理员', + message: '事件已分配给王五' + }, + { + id: '10', + eventId: '5', + type: 'resolved', + timestamp: '2023-10-13T17:30:00Z', + user: '王五', + message: '事件已解决:增加了服务实例数量' + }, + { + id: '11', + eventId: '6', + type: 'created', + timestamp: '2023-10-15T11:10:00Z', + user: '系统', + message: '事件已创建' + } +]; + +const EventDetail = () => { + const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); + const [event, setEvent] = useState(null); + const [timeline, setTimeline] = useState([]); + const [loading, setLoading] = useState(true); + const [resolution, setResolution] = useState(''); + const [resolving, setResolving] = useState(false); + + useEffect(() => { + // 模拟API调用获取事件详情 + const fetchEventDetail = () => { + setLoading(true); + // 模拟网络延迟 + setTimeout(() => { + const foundEvent = mockEvents.find(e => e.id === id); + setEvent(foundEvent || null); + + if (foundEvent) { + const eventTimeline = mockTimeline.filter(t => t.eventId === foundEvent.id); + setTimeline(eventTimeline); + } + + setLoading(false); + }, 800); + }; + + fetchEventDetail(); + }, [id]); + + const handleRefresh = () => { + setLoading(true); + // 模拟刷新 + setTimeout(() => { + const foundEvent = mockEvents.find(e => e.id === id); + setEvent(foundEvent || null); + + if (foundEvent) { + const eventTimeline = mockTimeline.filter(t => t.eventId === foundEvent.id); + setTimeline(eventTimeline); + } + + setLoading(false); + }, 800); + }; + + const handleResolve = () => { + if (!event || !resolution.trim()) return; + + setResolving(true); + + // 模拟API调用 + setTimeout(() => { + const updatedEvent = { + ...event, + status: 'resolved', + updatedAt: new Date().toISOString(), + resolvedBy: '当前用户', + resolution: resolution + }; + + setEvent(updatedEvent); + + // 添加新的时间线项 + const newTimelineItem = { + id: String(Date.now()), + eventId: event.id, + type: 'resolved', + timestamp: new Date().toISOString(), + user: '当前用户', + message: `事件已解决:${resolution}` + }; + + setTimeline([...timeline, newTimelineItem]); + setResolving(false); + setResolution(''); + }, 800); + }; + + const formatDate = (dateString: string) => { + const date = new Date(dateString); + return new Intl.DateTimeFormat('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }).format(date); + }; + + const getTimelineIcon = (type: string) => { + switch (type) { + case 'created': + return ; + case 'assigned': + return ; + case 'resolved': + return ; + default: + return ; + } + }; + + const getTimelineColor = (type: string) => { + switch (type) { + case 'created': + return 'blue'; + case 'assigned': + return 'orange'; + case 'resolved': + return 'green'; + default: + return 'gray'; + } + }; + + if (loading) { + return ( + + + 加载事件详情中... + + + ); + } + + if (!event) { + return ( + + + + + 事件未找到 + 找不到ID为 {id} 的事件 + + + + + + ); + } + + return ( + + + + + 事件详情 + + {event.severity === 'critical' && '严重'} + {event.severity === 'high' && '高'} + {event.severity === 'medium' && '中'} + {event.severity === 'low' && '低'} + {event.severity === 'info' && '信息'} + + + {event.status === 'active' ? '活跃' : '已解决'} + + + + + {event.status === 'active' && ( + + )} + + + + + {event.title} + {event.description} + + + + + 来源: + + {event.sourceName} ({event.source === 'monitor' ? '监控' : '客户端'}) + + + + + + 创建时间: + {formatDate(event.createdAt)} + + + + + 更新时间: + {formatDate(event.updatedAt)} + + + + + 分配给: + {event.assignedTo || '未分配'} + + + {event.status === 'resolved' && ( + <> + + + 解决者: + {event.resolvedBy} + + + + + 解决方案: + {event.resolution} + + + )} + + + + + + 时间线 + {event.status === 'active' && 解决事件} + + + + + + 事件时间线 + {timeline.length === 0 ? ( + + 没有时间线记录 + + ) : ( + + {timeline.map((item, index) => ( + + + + {getTimelineIcon(item.type)} + + + + {item.user} + {formatDate(item.timestamp)} + + {item.message} + + + + ))} + + )} + + + + {event.status === 'active' && ( + + + 解决事件 + + + 解决方案 * + +