diff --git a/.gitignore b/.gitignore
index 976bd51..340867e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,9 @@
.pnp.js
node_modules/
+.wrangler
+xugou.wiki
+
# --- Project Specific: Agent ---
# Build artifacts for the agent project
/agent/xugou-agent
diff --git a/.trae/rules/project_rules.md b/.trae/rules/project_rules.md
new file mode 100644
index 0000000..9dea35b
--- /dev/null
+++ b/.trae/rules/project_rules.md
@@ -0,0 +1,137 @@
+# XUGOU 项目开发规则
+
+## 项目概述
+
+XUGOU 是一个基于 CloudFlare 的轻量化系统监控平台,采用现代化的全栈架构:
+- **Frontend**: React + TypeScript + Vite + TailwindCSS + Radix UI
+- **Backend**: Hono + TypeScript + CloudFlare Workers + D1 Database
+- **Agent**: Go + Cobra CLI + gopsutil
+- **部署**: CloudFlare Workers + D1 + Pages
+
+## 代码规范
+
+### 通用规范
+
+1. **代码是写给人看的,只是机器恰好可以运行而已!**
+2. **每次修改代码以后,需要确保代码能够编译通过!**
+3. 使用 TypeScript 进行类型安全开发
+4. 遵循 ESLint 和 Prettier 配置
+5. 使用有意义的变量和函数命名
+6. 添加必要的注释,特别是复杂业务逻辑
+7. 保持代码简洁,避免过度工程化
+
+### Frontend 规范
+
+#### 文件组织
+```
+src/
+├── api/ # API 调用封装
+├── components/ # 可复用组件
+│ └── ui/ # 基础 UI 组件 (shadcn/ui)
+├── config/ # 配置文件
+├── hooks/ # 自定义 React Hooks
+├── i18n/ # 国际化配置
+├── lib/ # 工具库
+├── pages/ # 页面组件
+├── providers/ # Context Providers
+├── router/ # 路由配置
+├── styles/ # 样式文件
+├── types/ # TypeScript 类型定义
+└── utils/ # 工具函数
+```
+
+#### 组件规范
+1. 使用函数式组件和 React Hooks
+2. 组件文件使用 PascalCase 命名 (如 `MonitorCard.tsx`)
+3. 导出组件使用 default export
+4. Props 接口命名为 `ComponentNameProps`
+5. 使用 Radix UI 作为基础组件库
+6. 样式使用 TailwindCSS,避免内联样式
+7. 状态管理优先使用 React Context,复杂状态考虑 Zustand
+
+#### 示例组件结构
+```typescript
+interface MonitorCardProps {
+ monitor: Monitor;
+ onEdit?: (monitor: Monitor) => void;
+}
+
+export default function MonitorCard({ monitor, onEdit }: MonitorCardProps) {
+ // 组件逻辑
+ return (
+
+ {/* 组件内容 */}
+
+ );
+}
+```
+
+### Backend 规范
+
+#### 文件组织
+```
+src/
+├── api/ # API 路由处理
+├── config/ # 配置文件
+├── db/ # 数据库相关
+│ └── schema.ts # Drizzle ORM 模式定义
+├── jobs/ # 定时任务
+├── middlewares/ # 中间件
+├── models/ # 数据模型和类型
+├── repositories/ # 数据访问层
+├── services/ # 业务逻辑层
+├── static/ # 静态资源
+└── utils/ # 工具函数
+```
+
+#### API 规范
+1. 使用 Hono 框架构建 RESTful API
+2. 路由文件按功能模块组织 (如 `auth.ts`, `monitors.ts`)
+3. 统一的错误处理和响应格式
+4. 使用中间件处理 CORS、JWT 认证等
+5. API 路径统一使用 `/api/` 前缀
+
+#### 数据库规范
+1. 使用 Drizzle ORM 进行数据库操作
+2. 所有表结构定义在 `schema.ts` 中
+3. 使用 SQLite (CloudFlare D1) 作为数据库
+4. 表名使用复数形式,字段使用 snake_case
+5. 必须包含 `created_at` 和 `updated_at` 字段
+6. 使用 Repository 模式封装数据访问
+
+#### 示例 API 结构
+```typescript
+const monitors = new Hono<{ Bindings: Bindings }>();
+
+monitors.get('/', async (c) => {
+ try {
+ const result = await MonitorService.getAllMonitors(c.env);
+ return c.json({ success: true, data: result });
+ } catch (error) {
+ return c.json({ success: false, message: error.message }, 500);
+ }
+});
+```
+
+### Agent 规范
+
+#### 文件组织
+```
+cmd/
+└── agent/ # CLI 命令定义
+pkg/
+├── collector/ # 数据收集器
+├── config/ # 配置管理
+├── model/ # 数据模型
+├── reporter/ # 数据上报
+└── utils/ # 工具函数
+```
+
+#### Go 代码规范
+1. 遵循 Go 官方代码规范
+2. 使用 Cobra 构建 CLI 应用
+3. 使用 Viper 进行配置管理
+4. 错误处理要完整,不忽略任何错误
+5. 包名使用小写,避免下划线
+6. 导出的函数和类型使用 PascalCase
+7. 私有函数和变量使用 camelCase
diff --git a/backend/drizzle/0002_public_domino.sql b/backend/drizzle/0002_public_domino.sql
new file mode 100644
index 0000000..2b6280f
--- /dev/null
+++ b/backend/drizzle/0002_public_domino.sql
@@ -0,0 +1,3 @@
+CREATE INDEX `agent_metrics_24h_agent_timestamp_idx` ON `agent_metrics_24h` (`agent_id`,`timestamp`);--> statement-breakpoint
+CREATE INDEX `monitor_daily_stats_monitor_id_date_idx` ON `monitor_daily_stats` (`monitor_id`,`date`);--> statement-breakpoint
+CREATE INDEX `monitor_status_history_24h_monitor_timestamp_idx` ON `monitor_status_history_24h` (`monitor_id`,`timestamp`);
\ No newline at end of file
diff --git a/backend/drizzle/meta/0002_snapshot.json b/backend/drizzle/meta/0002_snapshot.json
new file mode 100644
index 0000000..0f637ce
--- /dev/null
+++ b/backend/drizzle/meta/0002_snapshot.json
@@ -0,0 +1,1314 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "8479041e-fe6e-4e45-ae21-645880bed63c",
+ "prevId": "c3674fd1-f5ba-4032-8da8-b96fc21d8666",
+ "tables": {
+ "agent_metrics_24h": {
+ "name": "agent_metrics_24h",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'CURRENT_TIMESTAMP'"
+ },
+ "cpu_usage": {
+ "name": "cpu_usage",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cpu_cores": {
+ "name": "cpu_cores",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cpu_model": {
+ "name": "cpu_model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "memory_total": {
+ "name": "memory_total",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "memory_used": {
+ "name": "memory_used",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "memory_free": {
+ "name": "memory_free",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "memory_usage_rate": {
+ "name": "memory_usage_rate",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "load_1": {
+ "name": "load_1",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "load_5": {
+ "name": "load_5",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "load_15": {
+ "name": "load_15",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "disk_metrics": {
+ "name": "disk_metrics",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "network_metrics": {
+ "name": "network_metrics",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "agent_metrics_24h_agent_timestamp_idx": {
+ "name": "agent_metrics_24h_agent_timestamp_idx",
+ "columns": [
+ "agent_id",
+ "timestamp"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "agent_metrics_24h_agent_id_agents_id_fk": {
+ "name": "agent_metrics_24h_agent_id_agents_id_fk",
+ "tableFrom": "agent_metrics_24h",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "agents": {
+ "name": "agents",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'inactive'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "hostname": {
+ "name": "hostname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ip_addresses": {
+ "name": "ip_addresses",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "os": {
+ "name": "os",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "keepalive": {
+ "name": "keepalive",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "agents_token_unique": {
+ "name": "agents_token_unique",
+ "columns": [
+ "token"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "agents_created_by_users_id_fk": {
+ "name": "agents_created_by_users_id_fk",
+ "tableFrom": "agents",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "monitor_daily_stats": {
+ "name": "monitor_daily_stats",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "date": {
+ "name": "date",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "total_checks": {
+ "name": "total_checks",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "up_checks": {
+ "name": "up_checks",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "down_checks": {
+ "name": "down_checks",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "avg_response_time": {
+ "name": "avg_response_time",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "min_response_time": {
+ "name": "min_response_time",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "max_response_time": {
+ "name": "max_response_time",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "availability": {
+ "name": "availability",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "monitor_daily_stats_monitor_id_date_idx": {
+ "name": "monitor_daily_stats_monitor_id_date_idx",
+ "columns": [
+ "monitor_id",
+ "date"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "monitor_daily_stats_monitor_id_monitors_id_fk": {
+ "name": "monitor_daily_stats_monitor_id_monitors_id_fk",
+ "tableFrom": "monitor_daily_stats",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "monitor_status_history_24h": {
+ "name": "monitor_status_history_24h",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'CURRENT_TIMESTAMP'"
+ },
+ "response_time": {
+ "name": "response_time",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status_code": {
+ "name": "status_code",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "monitor_status_history_24h_monitor_timestamp_idx": {
+ "name": "monitor_status_history_24h_monitor_timestamp_idx",
+ "columns": [
+ "monitor_id",
+ "timestamp"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "monitor_status_history_24h_monitor_id_monitors_id_fk": {
+ "name": "monitor_status_history_24h_monitor_id_monitors_id_fk",
+ "tableFrom": "monitor_status_history_24h",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "monitors": {
+ "name": "monitors",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "method": {
+ "name": "method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "interval": {
+ "name": "interval",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "timeout": {
+ "name": "timeout",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expected_status": {
+ "name": "expected_status",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "headers": {
+ "name": "headers",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "active": {
+ "name": "active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "response_time": {
+ "name": "response_time",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "last_checked": {
+ "name": "last_checked",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "monitors_created_by_users_id_fk": {
+ "name": "monitors_created_by_users_id_fk",
+ "tableFrom": "monitors",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "notification_channels": {
+ "name": "notification_channels",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "config": {
+ "name": "config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'CURRENT_TIMESTAMP'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'CURRENT_TIMESTAMP'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_channels_created_by_users_id_fk": {
+ "name": "notification_channels_created_by_users_id_fk",
+ "tableFrom": "notification_channels",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "notification_history": {
+ "name": "notification_history",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "target_id": {
+ "name": "target_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "template_id": {
+ "name": "template_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sent_at": {
+ "name": "sent_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'CURRENT_TIMESTAMP'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_history_channel_id_notification_channels_id_fk": {
+ "name": "notification_history_channel_id_notification_channels_id_fk",
+ "tableFrom": "notification_history",
+ "tableTo": "notification_channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "notification_history_template_id_notification_templates_id_fk": {
+ "name": "notification_history_template_id_notification_templates_id_fk",
+ "tableFrom": "notification_history",
+ "tableTo": "notification_templates",
+ "columnsFrom": [
+ "template_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "notification_settings": {
+ "name": "notification_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "target_type": {
+ "name": "target_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'global'"
+ },
+ "target_id": {
+ "name": "target_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "on_down": {
+ "name": "on_down",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "on_recovery": {
+ "name": "on_recovery",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "on_offline": {
+ "name": "on_offline",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "on_cpu_threshold": {
+ "name": "on_cpu_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "cpu_threshold": {
+ "name": "cpu_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 90
+ },
+ "on_memory_threshold": {
+ "name": "on_memory_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "memory_threshold": {
+ "name": "memory_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 85
+ },
+ "on_disk_threshold": {
+ "name": "on_disk_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "disk_threshold": {
+ "name": "disk_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 90
+ },
+ "channels": {
+ "name": "channels",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'[]'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'CURRENT_TIMESTAMP'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'CURRENT_TIMESTAMP'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_settings_user_id_users_id_fk": {
+ "name": "notification_settings_user_id_users_id_fk",
+ "tableFrom": "notification_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "notification_templates": {
+ "name": "notification_templates",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'CURRENT_TIMESTAMP'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'CURRENT_TIMESTAMP'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_templates_created_by_users_id_fk": {
+ "name": "notification_templates_created_by_users_id_fk",
+ "tableFrom": "notification_templates",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "status_page_agents": {
+ "name": "status_page_agents",
+ "columns": {
+ "config_id": {
+ "name": "config_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "status_page_agents_config_id_status_page_config_id_fk": {
+ "name": "status_page_agents_config_id_status_page_config_id_fk",
+ "tableFrom": "status_page_agents",
+ "tableTo": "status_page_config",
+ "columnsFrom": [
+ "config_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "status_page_agents_agent_id_agents_id_fk": {
+ "name": "status_page_agents_agent_id_agents_id_fk",
+ "tableFrom": "status_page_agents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "status_page_agents_config_id_agent_id_pk": {
+ "columns": [
+ "config_id",
+ "agent_id"
+ ],
+ "name": "status_page_agents_config_id_agent_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "status_page_config": {
+ "name": "status_page_config",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'系统状态'"
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'系统当前运行状态'"
+ },
+ "logo_url": {
+ "name": "logo_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "custom_css": {
+ "name": "custom_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'CURRENT_TIMESTAMP'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'CURRENT_TIMESTAMP'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "status_page_config_user_id_users_id_fk": {
+ "name": "status_page_config_user_id_users_id_fk",
+ "tableFrom": "status_page_config",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "status_page_monitors": {
+ "name": "status_page_monitors",
+ "columns": {
+ "config_id": {
+ "name": "config_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "status_page_monitors_config_id_status_page_config_id_fk": {
+ "name": "status_page_monitors_config_id_status_page_config_id_fk",
+ "tableFrom": "status_page_monitors",
+ "tableTo": "status_page_config",
+ "columnsFrom": [
+ "config_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "status_page_monitors_monitor_id_monitors_id_fk": {
+ "name": "status_page_monitors_monitor_id_monitors_id_fk",
+ "tableFrom": "status_page_monitors",
+ "tableTo": "monitors",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "status_page_monitors_config_id_monitor_id_pk": {
+ "columns": [
+ "config_id",
+ "monitor_id"
+ ],
+ "name": "status_page_monitors_config_id_monitor_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "users_username_unique": {
+ "name": "users_username_unique",
+ "columns": [
+ "username"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/backend/drizzle/meta/_journal.json b/backend/drizzle/meta/_journal.json
index ec97879..01d6ea2 100644
--- a/backend/drizzle/meta/_journal.json
+++ b/backend/drizzle/meta/_journal.json
@@ -15,6 +15,13 @@
"when": 1749736889133,
"tag": "0001_fluffy_pestilence",
"breakpoints": true
+ },
+ {
+ "idx": 2,
+ "version": "6",
+ "when": 1750852918387,
+ "tag": "0002_public_domino",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/backend/src/db/generated-migrations.ts b/backend/src/db/generated-migrations.ts
index 0cfadc7..e3d484e 100644
--- a/backend/src/db/generated-migrations.ts
+++ b/backend/src/db/generated-migrations.ts
@@ -198,5 +198,29 @@ CREATE TABLE IF NOT EXISTS \`users\` (
);
--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS \`users_username_unique\` ON \`users\` (\`username\`);`
+ },
+ {
+ name: "0001_fluffy_pestilence.sql",
+ sql: `ALTER TABLE \`agents\` DROP COLUMN \`cpu_usage\`;
+--> statement-breakpoint
+ALTER TABLE \`agents\` DROP COLUMN \`memory_total\`;
+--> statement-breakpoint
+ALTER TABLE \`agents\` DROP COLUMN \`memory_used\`;
+--> statement-breakpoint
+ALTER TABLE \`agents\` DROP COLUMN \`disk_total\`;
+--> statement-breakpoint
+ALTER TABLE \`agents\` DROP COLUMN \`disk_used\`;
+--> statement-breakpoint
+ALTER TABLE \`agents\` DROP COLUMN \`network_rx\`;
+--> statement-breakpoint
+ALTER TABLE \`agents\` DROP COLUMN \`network_tx\`;`
+ },
+ {
+ name: "0002_public_domino.sql",
+ sql: `CREATE INDEX IF NOT EXISTS \`agent_metrics_24h_agent_timestamp_idx\` ON \`agent_metrics_24h\` (\`agent_id\`,\`timestamp\`);
+--> statement-breakpoint
+CREATE INDEX IF NOT EXISTS \`monitor_daily_stats_monitor_id_date_idx\` ON \`monitor_daily_stats\` (\`monitor_id\`,\`date\`);
+--> statement-breakpoint
+CREATE INDEX IF NOT EXISTS \`monitor_status_history_24h_monitor_timestamp_idx\` ON \`monitor_status_history_24h\` (\`monitor_id\`,\`timestamp\`);`
}
];
diff --git a/backend/src/db/schema.ts b/backend/src/db/schema.ts
index fe00624..0c7669e 100644
--- a/backend/src/db/schema.ts
+++ b/backend/src/db/schema.ts
@@ -1,4 +1,4 @@
-import { int, sqliteTable, text, real, primaryKey } from "drizzle-orm/sqlite-core";
+import { int, sqliteTable, text, real, primaryKey, index } from "drizzle-orm/sqlite-core";
// 用户表
export const users = sqliteTable("users", {
@@ -40,7 +40,10 @@ export const monitorStatusHistory24h = sqliteTable("monitor_status_history_24h",
response_time: int("response_time"),
status_code: int("status_code"),
error: text("error")
-});
+}, (table) => ({
+ // monitor_id 和 timestamp 的联合索引,用于优化按监控项和时间查询的性能
+ monitorTimestampIdx: index("monitor_status_history_24h_monitor_timestamp_idx").on(table.monitor_id, table.timestamp)
+}));
// 监控每日统计表
export const monitorDailyStats = sqliteTable("monitor_daily_stats", {
@@ -55,7 +58,9 @@ export const monitorDailyStats = sqliteTable("monitor_daily_stats", {
max_response_time: int("max_response_time").default(0),
availability: real("availability").default(0),
created_at: text("created_at").notNull()
-});
+}, (table) => ({
+ monitorDateIdx: index("monitor_daily_stats_monitor_id_date_idx").on(table.monitor_id, table.date)
+}));
// 客户端表
export const agents = sqliteTable("agents", {
@@ -90,7 +95,10 @@ export const agentMetrics24h = sqliteTable("agent_metrics_24h", {
load_15: real("load_15"),
disk_metrics: text("disk_metrics"),
network_metrics: text("network_metrics")
-});
+}, (table) => ({
+ // agent_id 和 timestamp 的联合索引,用于优化按代理和时间查询的性能
+ agentTimestampIdx: index("agent_metrics_24h_agent_timestamp_idx").on(table.agent_id, table.timestamp)
+}));
// 状态页配置表
export const statusPageConfig = sqliteTable("status_page_config", {
diff --git a/backend/src/index.ts b/backend/src/index.ts
index 3052710..1eab24b 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -29,6 +29,47 @@ app.route("/api/users", api.users);
app.route("/api/status", api.status);
app.route("/api/notifications", api.notifications);
app.route("/api/dashboard", api.dashboard);
+
+// 静态文件路由 - 处理所有非 API 请求,返回前端应用
+app.get("*", async (c) => {
+ const url = new URL(c.req.url);
+
+ // 如果是 API 路由,跳过静态文件处理
+ if (url.pathname.startsWith("/api/")) {
+ return c.notFound();
+ }
+
+ try {
+ // 尝试获取请求的静态文件
+ const asset = await c.env.ASSETS.fetch(c.req.url);
+
+ // 如果文件存在,直接返回
+ if (asset.status === 200) {
+ return asset;
+ }
+
+ // 如果文件不存在,返回 index.html 以支持 React Router
+ const indexUrl = new URL("/index.html", c.req.url);
+ const indexAsset = await c.env.ASSETS.fetch(indexUrl.toString());
+
+ if (indexAsset.status === 200) {
+ // 设置正确的 Content-Type
+ const response = new Response(indexAsset.body, {
+ status: 200,
+ headers: {
+ "Content-Type": "text/html; charset=utf-8",
+ "Cache-Control": "no-cache"
+ }
+ });
+ return response;
+ }
+
+ return c.notFound();
+ } catch (error) {
+ console.error("静态文件服务错误:", error);
+ return c.text("Internal Server Error", 500);
+ }
+});
// 数据库状态标志,用于记录数据库初始化状态
let dbInitialized = false;
diff --git a/backend/src/jobs/monitor-task.ts b/backend/src/jobs/monitor-task.ts
index 190b2cc..adf27b7 100644
--- a/backend/src/jobs/monitor-task.ts
+++ b/backend/src/jobs/monitor-task.ts
@@ -4,7 +4,11 @@ import { Monitor } from "../models/monitor";
import { getMonitorsToCheck, checkMonitor } from "../services";
import { shouldSendNotification, sendNotification } from "../services";
import { db } from "../config";
-import { monitorDailyStats, monitorStatusHistory24h,monitors } from "../db/schema";
+import {
+ monitorDailyStats,
+ monitorStatusHistory24h,
+ monitors,
+} from "../db/schema";
import { and, gte, lte } from "drizzle-orm";
const monitorTask = new Hono<{ Bindings: Bindings }>();
@@ -25,7 +29,7 @@ async function checkMonitors(c: any) {
// 检查每个监控
const results = await Promise.all(
- monitors.map(async (monitor: Monitor) => {
+ monitors.map(async (monitor: Monitor) => {
console.log(`开始检查监控: ${monitor.name} (ID: ${monitor.id})`);
const checkResult = await checkMonitor(monitor);
// 处理通知
@@ -166,12 +170,12 @@ async function generateDailyStats(c: any) {
// 一次性获取所有监控
const monitorsResult = await db.select().from(monitors);
- if (!monitorsResult.results || monitorsResult.results.length === 0) {
+ if (!monitorsResult || monitorsResult.length === 0) {
console.log("没有找到监控");
return { success: true, message: "没有监控", processed: 0 };
}
- const allMonitors = monitorsResult.results as Monitor[];
+ const allMonitors = monitorsResult as Monitor[];
console.log(`找到 ${allMonitors.length} 个监控`);
// 创建监控ID列表
@@ -191,14 +195,13 @@ async function generateDailyStats(c: any) {
lte(monitorStatusHistory24h.timestamp, endTime)
)
);
-
- if (!historyResult.results || historyResult.results.length === 0) {
+ if (!historyResult || historyResult.length === 0) {
console.log(`在 ${dateStr} 没有找到任何监控历史记录`);
return { success: true, message: "没有历史记录", processed: 0 };
}
- console.log(`找到 ${historyResult.results.length} 条历史记录`);
+ console.log(`找到 ${historyResult.length} 条历史记录`);
// 按监控ID分组处理数据
const statsMap = new Map();
@@ -219,7 +222,7 @@ async function generateDailyStats(c: any) {
}
// 处理所有历史记录
- for (const record of historyResult.results) {
+ for (const record of historyResult) {
const monitorId = record.monitor_id;
if (!statsMap.has(monitorId)) continue;
@@ -306,12 +309,14 @@ async function generateDailyStats(c: any) {
// 从 24h 表中删除已处理的数据
console.log(`开始从24小时热表删除已处理的数据`);
- await db.delete(monitorStatusHistory24h).where(
- and(
- gte(monitorStatusHistory24h.timestamp, startTime),
- lte(monitorStatusHistory24h.timestamp, endTime)
- )
- );
+ await db
+ .delete(monitorStatusHistory24h)
+ .where(
+ and(
+ gte(monitorStatusHistory24h.timestamp, startTime),
+ lte(monitorStatusHistory24h.timestamp, endTime)
+ )
+ );
console.log(`从24小时热表删除已处理的数据完成`);
return {
@@ -341,9 +346,10 @@ export default {
const hour = now.getUTCHours();
const minute = now.getUTCMinutes();
- if (hour === 0 && minute === 5) {
+ if (hour == 0 && minute == 5) {
// 生成每日监控统计数据
const statsResult = await generateDailyStats(c);
+ console.log("生成每日监控统计测试");
if (statsResult.error) {
console.error("生成每日监控统计数据时出错:", statsResult.error);
}
diff --git a/backend/src/middlewares/auth.ts b/backend/src/middlewares/auth.ts
index 27a15b6..4f058e2 100644
--- a/backend/src/middlewares/auth.ts
+++ b/backend/src/middlewares/auth.ts
@@ -7,6 +7,12 @@ import { getJwtSecret } from "../utils/jwt";
* 验证请求中的JWT令牌并将解码的payload存入上下文
*/
export const jwtMiddleware = async (c: Context, next: Next) => {
+ // 跳过所有非 API 路径的认证检查(用于静态文件服务)
+ if (!c.req.path.startsWith("/api/")) {
+ return next();
+ }
+
+ // 跳过特定的 API 端点
if (
(c.req.path.endsWith("/status") ||
c.req.path.endsWith("/register") ||
diff --git a/backend/src/models/db.ts b/backend/src/models/db.ts
index 0dced24..b51fe4d 100644
--- a/backend/src/models/db.ts
+++ b/backend/src/models/db.ts
@@ -14,6 +14,11 @@ export interface D1PreparedStatement {
raw(): Promise;
}
+// Cloudflare Workers Fetcher 类型定义
+export interface Fetcher {
+ fetch(input: RequestInfo, init?: RequestInit): Promise;
+}
+
// 扩展D1Result meta属性的类型
export interface D1Meta {
last_row_id?: number;
@@ -39,4 +44,5 @@ export interface VersionMetadata {
export type Bindings = {
DB: D1Database;
CF_VERSION_METADATA?: VersionMetadata;
+ ASSETS: Fetcher;
};
diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts
index 41918ee..556cbf0 100644
--- a/frontend/src/api/client.ts
+++ b/frontend/src/api/client.ts
@@ -3,8 +3,8 @@ import { ENV_API_BASE_URL, ENV_API_TIMEOUT } from "../config";
// 创建 axios 实例
const api = axios.create({
- baseURL: ENV_API_BASE_URL, // 从配置中获取API基础URL
- timeout: ENV_API_TIMEOUT, // 从配置中获取超时设置
+ baseURL: ENV_API_BASE_URL || "", // 从配置中获取API基础URL
+ timeout: ENV_API_TIMEOUT || 100000, // 从配置中获取超时设置
headers: {
"Content-Type": "application/json",
},
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..863655e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "xugou",
+ "version": "1.0.0",
+ "description": "Xugou monitoring system",
+ "private": true,
+ "workspaces": [
+ "frontend",
+ "backend"
+ ],
+ "scripts": {
+ "dev:frontend": "npm run dev --workspace=frontend",
+ "dev:backend": "npm run dev --workspace=backend",
+ "build:frontend": "npm run build --workspace=frontend",
+ "build:backend": "npm run build --workspace=backend",
+ "db:generate": "npm run db:generate --workspace=backend",
+ "build": "npm run build:frontend && npm run build:backend",
+ "deploy": "wrangler deploy",
+ "preview": "npm run dev --workspace=backend"
+ }
+}
\ No newline at end of file
diff --git a/backend/wrangler.toml b/wrangler.toml
similarity index 66%
rename from backend/wrangler.toml
rename to wrangler.toml
index 1432508..6c800a5 100644
--- a/backend/wrangler.toml
+++ b/wrangler.toml
@@ -1,5 +1,5 @@
name = "xugou-backend"
-main = "src/index.ts"
+main = "backend/src/index.ts"
compatibility_date = "2025-03-10"
compatibility_flags = ["nodejs_compat"]
@@ -12,5 +12,10 @@ binding = "DB"
database_name = "xugou_db"
database_id = "32a2ca5f-3a77-4b08-965e-804612557880"
+[assets]
+binding = "ASSETS"
+directory = "frontend/dist"
+not_found_handling = "single-page-application"
+
[version_metadata]
binding = "CF_VERSION_METADATA"
\ No newline at end of file