first commit

This commit is contained in:
zaunist committed 2025-03-20 22:31:18 +08:00
commit ac4cc3e02d
149 files changed
+22830

No files matched your search

+78
View File
@@ -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
*~
+21
View File
@@ -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.
+99
View File
@@ -0,0 +1,99 @@
# XUGOU - 基于CloudFlare搭建的轻量化监控平台
<div align="center">
![XUGOU Logo](frontend/public/logo.svg)
XUGOU 是一个基于 CloudFlare 的轻量化系统监控平台,提供系统监控和状态页面功能。
[English](./README_EN.md) | 简体中文
</div>
## ✨ 核心特性
- 🖥️ **系统监控**
- 实时监控 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) 文件。
+128
View File
@@ -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
+77
View File
@@ -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:]
}
+73
View File
@@ -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)
}
}
}
+111
View File
@@ -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"))
}
+32
View File
@@ -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)
}
+33
View File
@@ -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
)
+77
View File
@@ -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=
+15
View File
@@ -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)
}
}
+191
View File
@@ -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
}
+277
View File
@@ -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 "未知"
}
BIN
View File
Binary file not shown.
+3
View File
@@ -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";
+42
View File
@@ -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
+1
View File
@@ -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"}
+15
View File
@@ -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<Response>;
scheduled(event: any, env: any, ctx: any): Promise<void>;
};
export default _default;
+68
View File
@@ -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
+1
View File
@@ -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"}
+6
View File
@@ -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;
+188
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"init-db.js","sourceRoot":"","sources":["../src/init-db.ts"],"names":[],"mappings":"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 truncated
+20
View File
@@ -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;
}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=agent.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/models/agent.ts"],"names":[],"mappings":""}
+23
View File
@@ -0,0 +1,23 @@
export interface D1Database {
prepare(query: string): D1PreparedStatement;
dump(): Promise<ArrayBuffer>;
batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
exec<T = unknown>(query: string): Promise<D1Result<T>>;
}
export interface D1PreparedStatement {
bind(...values: any[]): D1PreparedStatement;
first<T = unknown>(colName?: string): Promise<T>;
run<T = unknown>(): Promise<D1Result<T>>;
all<T = unknown>(): Promise<D1Result<T>>;
raw<T = unknown>(): Promise<T[]>;
}
export interface D1Result<T = unknown> {
results?: T[];
success: boolean;
error?: string;
meta?: object;
}
export type Bindings = {
DB: D1Database;
JWT_SECRET: string;
};
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=db.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/models/db.ts"],"names":[],"mappings":""}
+34
View File
@@ -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;
}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=monitor.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"monitor.js","sourceRoot":"","sources":["../../src/models/monitor.ts"],"names":[],"mappings":""}
+6
View File
@@ -0,0 +1,6 @@
import { Bindings } from './models/db';
declare const _default: {
scheduled(event: any, env: any, ctx: any): Promise<void>;
fetch: (request: Request, Env?: {} | Bindings | undefined, executionCtx?: import("hono").ExecutionContext) => Response | Promise<Response>;
};
export default _default;
+164
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"monitor-checker.js","sourceRoot":"","sources":["../src/monitor-checker.ts"],"names":[],"mappings":"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 truncated
+11
View File
@@ -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;
+384
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../../src/routes/agents.ts"],"names":[],"mappings":"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 truncated
+28
View File
@@ -0,0 +1,28 @@
import { Hono } from 'hono';
type Bindings = {
DB: D1Database;
JWT_SECRET: string;
};
interface D1Database {
prepare(query: string): D1PreparedStatement;
dump(): Promise<ArrayBuffer>;
batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
exec<T = unknown>(query: string): Promise<D1Result<T>>;
}
interface D1PreparedStatement {
bind(...values: any[]): D1PreparedStatement;
first<T = unknown>(colName?: string): Promise<T>;
run<T = unknown>(): Promise<D1Result<T>>;
all<T = unknown>(): Promise<D1Result<T>>;
raw<T = unknown>(): Promise<T[]>;
}
interface D1Result<T = unknown> {
results?: T[];
success: boolean;
error?: string;
meta?: object;
}
declare const auth: Hono<{
Bindings: Bindings;
}, import("hono/types").BlankSchema, "/">;
export default auth;
+101
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":"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"}
+6
View File
@@ -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;
+375
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"monitors.js","sourceRoot":"","sources":["../../src/routes/monitors.ts"],"names":[],"mappings":"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 truncated
+6
View File
@@ -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;
+409
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/routes/status.ts"],"names":[],"mappings":"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 truncated
+6
View File
@@ -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;
+237
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/routes/users.ts"],"names":[],"mappings":"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 truncated
+1
View File
@@ -0,0 +1 @@
export declare const checkAgentsStatus: (env: any) => Promise<void>;
+24
View File
@@ -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
+1
View File
@@ -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"}
+6
View File
@@ -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;
+163
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/setup/database.ts"],"names":[],"mappings":"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 truncated
+1
View File
@@ -0,0 +1 @@
export declare const checkAgentsStatus: (env: any) => Promise<void>;
+17
View File
@@ -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
+1
View File
@@ -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"}
+4
View File
@@ -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<void>;
export { monitorTask, checkAgentsStatus };
+27
View File
@@ -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
+1
View File
@@ -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"}
+6
View File
@@ -0,0 +1,6 @@
import { Bindings } from '../models/db';
declare const _default: {
scheduled(event: any, env: any, ctx: any): Promise<void>;
fetch: (request: Request, Env?: {} | Bindings | undefined, executionCtx?: import("hono").ExecutionContext) => Response | Promise<Response>;
};
export default _default;
+106
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"monitor-task.js","sourceRoot":"","sources":["../../src/tasks/monitor-task.ts"],"names":[],"mappings":"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"}
+18
View File
@@ -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<string>;
+35
View File
@@ -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
+1
View File
@@ -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"}
+2294
View File
File diff suppressed because it is too large. Load diff
+36
View File
@@ -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"
}
+120
View File
@@ -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');
+40
View File
@@ -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
)
`;
+106
View File
@@ -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<ArrayBuffer>;
batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
exec<T = unknown>(query: string): Promise<D1Result<T>>;
}
interface D1PreparedStatement {
bind(...values: any[]): D1PreparedStatement;
first<T = unknown>(colName?: string): Promise<T>;
run<T = unknown>(): Promise<D1Result<T>>;
all<T = unknown>(): Promise<D1Result<T>>;
raw<T = unknown>(): Promise<T[]>;
}
interface D1Result<T = unknown> {
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);
}
}
};
+23
View File
@@ -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;
}
+28
View File
@@ -0,0 +1,28 @@
// 通用数据库类型定义
export interface D1Database {
prepare(query: string): D1PreparedStatement;
dump(): Promise<ArrayBuffer>;
batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
exec<T = unknown>(query: string): Promise<D1Result<T>>;
}
export interface D1PreparedStatement {
bind(...values: any[]): D1PreparedStatement;
first<T = unknown>(colName?: string): Promise<T>;
run<T = unknown>(): Promise<D1Result<T>>;
all<T = unknown>(): Promise<D1Result<T>>;
raw<T = unknown>(): Promise<T[]>;
}
export interface D1Result<T = unknown> {
results?: T[];
success: boolean;
error?: string;
meta?: object;
}
// 通用绑定类型
export type Bindings = {
DB: D1Database;
JWT_SECRET: string;
};
+39
View File
@@ -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;
}
+564
View File
@@ -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<Agent>();
} else {
result = await c.env.DB.prepare(
'SELECT * FROM agents WHERE created_by = ? ORDER BY created_at DESC'
).bind(payload.id).all<Agent>();
}
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<Agent>();
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<Agent>();
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<Agent>();
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<Agent>();
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<Agent>();
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<Agent>();
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;
+170
View File
@@ -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<ArrayBuffer>;
batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
exec<T = unknown>(query: string): Promise<D1Result<T>>;
}
interface D1PreparedStatement {
bind(...values: any[]): D1PreparedStatement;
first<T = unknown>(colName?: string): Promise<T>;
run<T = unknown>(): Promise<D1Result<T>>;
all<T = unknown>(): Promise<D1Result<T>>;
raw<T = unknown>(): Promise<T[]>;
}
interface D1Result<T = unknown> {
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<User | null>();
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<User>();
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;
+520
View File
@@ -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<Monitor>();
} else {
result = await c.env.DB.prepare(
'SELECT * FROM monitors WHERE created_by = ? ORDER BY created_at DESC'
).bind(payload.id).all<Monitor>();
}
// 获取所有监控的历史状态数据
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<MonitorStatusHistory>();
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<Monitor>();
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<MonitorStatusHistory>();
// 获取最近的检查历史记录
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<Monitor>();
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<Monitor>();
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<Monitor>();
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<Monitor>();
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<Monitor>();
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<MonitorStatusHistory>();
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<Monitor>();
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<Monitor>();
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;
+610
View File
@@ -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<DbStatusPageConfig>();
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<DbStatusPageConfig>();
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<Monitor>();
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<Agent>();
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;
+341
View File
@@ -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<Omit<User, 'password'>>();
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<Omit<User, 'password'>>();
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<Omit<User, 'password'>>();
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<User>();
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<Omit<User, 'password'>>();
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<User>();
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;
+300
View File
@@ -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;
+12
View File
@@ -0,0 +1,12 @@
// 定期检查客户端状态的任务
export const checkAgentsStatus = async (env: any) => {
try {
console.log('定时任务: 检查客户端状态...');
// 所有客户端默认都是inactive状态,只有当客户端明确报告活跃时才会被设置为active
// 这个任务不做任何状态变更,只打印一条日志
console.log('定时任务: 客户端状态由客户端主动报告,无需定时任务检查');
} catch (error) {
console.error('定时任务: 检查客户端状态出错:', error);
}
};
+23
View File
@@ -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
};
+133
View File
@@ -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
};
+31
View File
@@ -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<string> {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
+16
View File
@@ -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"]
}
+26
View File
@@ -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"
+24
View File
@@ -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?
+54
View File
@@ -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,
},
})
```
+28
View File
@@ -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 },
],
},
},
)
+17
View File
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>XUGOU - 轻量化监控平台</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+5663
View File
File diff suppressed because it is too large. Load diff
+45
View File
@@ -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"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- 背景圆形 -->
<circle cx="16" cy="16" r="14" fill="#4F46E5" opacity="0.1"/>
<!-- X 字母 -->
<path d="M10 10L22 22M22 10L10 22" stroke="#4F46E5" stroke-width="2" stroke-linecap="round"/>
<!-- 中心点 -->
<circle cx="16" cy="16" r="1" fill="#4F46E5"/>
</svg>

After

Width:  |  Height:  |  Size: 422 B

Loaded 100 of 149 files, more files were not shown because too many files have changed in this diff. Show more