feat: support mutiple ip addrs
This commit is contained in:
1 parent
bd74364c57
commit
182e50d893
20 files changed
+309
-133
No files matched your search
@@ -70,28 +70,28 @@ func normalizeURL(url string) string {
|
||||
|
||||
// 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"` // 操作系统版本
|
||||
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"` // 主机名
|
||||
IPAddresses []string `json:"ip_addresses"` // 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"` // 操作系统版本
|
||||
Token string `json:"token"` // API令牌
|
||||
Name string `json:"name"` // 客户端名称
|
||||
Hostname string `json:"hostname"` // 主机名
|
||||
IPAddresses []string `json:"ip_addresses"` // IP地址列表
|
||||
OS string `json:"os"` // 操作系统
|
||||
Version string `json:"version"` // 操作系统版本
|
||||
}
|
||||
|
||||
// RegisterResponse 定义注册响应结构
|
||||
@@ -111,12 +111,12 @@ func (r *HTTPReporter) ensureRegistered(ctx context.Context, info *collector.Sys
|
||||
|
||||
// 构建注册的数据结构
|
||||
payload := RegisterPayload{
|
||||
Token: r.apiToken,
|
||||
Name: info.Hostname,
|
||||
Hostname: info.Hostname,
|
||||
IPAddress: getLocalIP(),
|
||||
OS: info.OS,
|
||||
Version: info.Version,
|
||||
Token: r.apiToken,
|
||||
Name: info.Hostname,
|
||||
Hostname: info.Hostname,
|
||||
IPAddresses: getLocalIPs(),
|
||||
OS: info.OS,
|
||||
Version: info.Version,
|
||||
}
|
||||
|
||||
// 将数据转换为JSON
|
||||
@@ -233,9 +233,9 @@ func (r *HTTPReporter) reportStatus(ctx context.Context, info *collector.SystemI
|
||||
NetworkRX: uint64(networkRXRate), // 网络接收速率(KB/s)
|
||||
NetworkTX: uint64(networkTXRate), // 网络发送速率(KB/s)
|
||||
Hostname: info.Hostname, // 添加主机名
|
||||
OS: info.OS, // 添加操作系统信息
|
||||
Version: info.Version, // 添加操作系统版本
|
||||
IPAddress: getLocalIP(), // 获取本地IP地址
|
||||
IPAddresses: getLocalIPs(),
|
||||
OS: info.OS, // 添加操作系统信息
|
||||
Version: info.Version, // 添加操作系统版本
|
||||
}
|
||||
|
||||
// 将数据转换为JSON
|
||||
@@ -295,21 +295,26 @@ func (r *HTTPReporter) Report(ctx context.Context, info *collector.SystemInfo) e
|
||||
return nil
|
||||
}
|
||||
|
||||
// getLocalIP 获取本地IP地址
|
||||
func getLocalIP() string {
|
||||
// getLocalIPs 获取所有本地IPv4地址
|
||||
func getLocalIPs() []string {
|
||||
ips := []string{}
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return "未知"
|
||||
return append(ips, "unknown")
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
// 检查IP地址类型
|
||||
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
return ipnet.IP.String()
|
||||
ips = append(ips, ipnet.IP.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "未知"
|
||||
if len(ips) == 0 {
|
||||
return append(ips, "unknown")
|
||||
}
|
||||
|
||||
return ips
|
||||
}
|
||||
+2
-2
@@ -75,7 +75,7 @@ CREATE TABLE IF NOT EXISTS agents (
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
hostname TEXT,
|
||||
ip_address TEXT,
|
||||
ip_addresses TEXT, -- 存储多个IP地址的JSON字符串
|
||||
os TEXT,
|
||||
version TEXT,
|
||||
cpu_usage REAL,
|
||||
@@ -233,7 +233,7 @@ VALUES (
|
||||
|
||||
🖥️ 主机信息:
|
||||
主机名: ${hostname}
|
||||
IP地址: ${ip_address}
|
||||
IP地址: ${ip_addresses}
|
||||
操作系统: ${os}
|
||||
|
||||
❗ 错误信息: ${error}',
|
||||
|
||||
+22
-10
@@ -40,15 +40,18 @@ export async function createAgent(
|
||||
createdBy: number,
|
||||
status: string = 'inactive',
|
||||
hostname: string | null = null,
|
||||
ipAddress: string | null = null,
|
||||
os: string | null = null,
|
||||
version: string | null = null
|
||||
version: string | null = null,
|
||||
ipAddresses: string[] | null = null
|
||||
) {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// 将 ipAddresses 数组转换为 JSON 字符串
|
||||
const ipAddressesJson = ipAddresses ? JSON.stringify(ipAddresses) : null;
|
||||
|
||||
const result = await db.prepare(
|
||||
`INSERT INTO agents
|
||||
(name, token, created_by, status, created_at, updated_at, hostname, ip_address, os, version)
|
||||
(name, token, created_by, status, created_at, updated_at, hostname, ip_addresses, os, version)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).bind(
|
||||
name,
|
||||
@@ -58,7 +61,7 @@ export async function createAgent(
|
||||
now,
|
||||
now,
|
||||
hostname,
|
||||
ipAddress,
|
||||
ipAddressesJson,
|
||||
os,
|
||||
version
|
||||
).run();
|
||||
@@ -80,7 +83,7 @@ export async function updateAgent(
|
||||
fields: {
|
||||
name?: string;
|
||||
hostname?: string;
|
||||
ip_address?: string;
|
||||
ip_addresses?: string[];
|
||||
os?: string;
|
||||
version?: string;
|
||||
status?: string;
|
||||
@@ -99,8 +102,14 @@ export async function updateAgent(
|
||||
// 添加需要更新的字段
|
||||
for (const [key, value] of Object.entries(fields)) {
|
||||
if (value !== undefined) {
|
||||
fieldsToUpdate.push(`${key} = ?`);
|
||||
values.push(value);
|
||||
// 特殊处理 ip_addresses 数组,转换为 JSON 字符串
|
||||
if (key === 'ip_addresses' && Array.isArray(value)) {
|
||||
fieldsToUpdate.push(`${key} = ?`);
|
||||
values.push(JSON.stringify(value));
|
||||
} else {
|
||||
fieldsToUpdate.push(`${key} = ?`);
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,13 +156,16 @@ export async function updateAgentStatus(
|
||||
network_rx?: number;
|
||||
network_tx?: number;
|
||||
hostname?: string;
|
||||
ip_address?: string;
|
||||
ip_addresses?: string[];
|
||||
os?: string;
|
||||
version?: string;
|
||||
}
|
||||
) {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// 将 ip_addresses 数组转换为 JSON 字符串
|
||||
const ipAddressesJson = metrics.ip_addresses ? JSON.stringify(metrics.ip_addresses) : null;
|
||||
|
||||
const result = await db.prepare(
|
||||
`UPDATE agents SET
|
||||
status = ?,
|
||||
@@ -165,7 +177,7 @@ export async function updateAgentStatus(
|
||||
network_rx = ?,
|
||||
network_tx = ?,
|
||||
hostname = ?,
|
||||
ip_address = ?,
|
||||
ip_addresses = ?,
|
||||
os = ?,
|
||||
version = ?,
|
||||
updated_at = ?
|
||||
@@ -180,7 +192,7 @@ export async function updateAgentStatus(
|
||||
metrics.network_rx,
|
||||
metrics.network_tx,
|
||||
metrics.hostname,
|
||||
metrics.ip_address,
|
||||
ipAddressesJson,
|
||||
metrics.os,
|
||||
metrics.version,
|
||||
now,
|
||||
|
||||
@@ -49,7 +49,7 @@ export interface Agent {
|
||||
network_rx?: number;
|
||||
network_tx?: number;
|
||||
hostname?: string;
|
||||
ip_address?: string;
|
||||
ip_addresses?: string;
|
||||
os?: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
+11
-11
@@ -7,17 +7,17 @@ export interface Agent {
|
||||
status: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
hostname?: string;
|
||||
ip_address?: string;
|
||||
os?: string;
|
||||
version?: string;
|
||||
hostname: string | null;
|
||||
ip_addresses: string | null; // 存储多个IP地址的JSON字符串
|
||||
os: string | null;
|
||||
version: string | null;
|
||||
|
||||
// 资源指标字段
|
||||
cpu_usage?: number;
|
||||
memory_total?: number;
|
||||
memory_used?: number;
|
||||
disk_total?: number;
|
||||
disk_used?: number;
|
||||
network_rx?: number;
|
||||
network_tx?: number;
|
||||
cpu_usage: number | null;
|
||||
memory_total: number | null;
|
||||
memory_used: number | null;
|
||||
disk_total: number | null;
|
||||
disk_used: number | null;
|
||||
network_rx: number | null;
|
||||
network_tx: number | null;
|
||||
}
|
||||
@@ -215,7 +215,7 @@ agents.post('/token/generate', async (c) => {
|
||||
// 客户端自注册接口
|
||||
agents.post('/register', async (c) => {
|
||||
try {
|
||||
const { token, name, hostname, ip_address, os, version } = await c.req.json();
|
||||
const { token, name, hostname, ip_addresses, os, version } = await c.req.json();
|
||||
|
||||
if (!token) {
|
||||
return c.json({ success: false, message: '缺少注册令牌' }, 400);
|
||||
@@ -252,9 +252,9 @@ agents.post('/register', async (c) => {
|
||||
adminId,
|
||||
'active',
|
||||
hostname || null,
|
||||
ip_address || null,
|
||||
os || null,
|
||||
version || null
|
||||
version || null,
|
||||
ip_addresses // 新增 ip_addresses 字段
|
||||
);
|
||||
|
||||
return c.json({
|
||||
@@ -285,7 +285,7 @@ agents.post('/status', async (c) => {
|
||||
network_rx,
|
||||
network_tx,
|
||||
hostname,
|
||||
ip_address,
|
||||
ip_addresses,
|
||||
os,
|
||||
version
|
||||
} = await c.req.json();
|
||||
@@ -311,7 +311,7 @@ agents.post('/status', async (c) => {
|
||||
network_rx,
|
||||
network_tx,
|
||||
hostname,
|
||||
ip_address,
|
||||
ip_addresses, // 新增 ip_addresses 字段
|
||||
os,
|
||||
version
|
||||
});
|
||||
|
||||
@@ -370,7 +370,7 @@ app.get('/data', async (c) => {
|
||||
network_rx: agent.network_rx || 0, // 使用数据库中的网络下载速率
|
||||
network_tx: agent.network_tx || 0, // 使用数据库中的网络上传速率
|
||||
hostname: agent.hostname || "未知主机",
|
||||
ip_address: agent.ip_address || "0.0.0.0",
|
||||
ip_addresses: agent.ip_addresses || "0.0.0.0",
|
||||
os: agent.os || "未知系统",
|
||||
version: agent.version || "未知版本"
|
||||
};
|
||||
@@ -394,7 +394,7 @@ app.get('/data', async (c) => {
|
||||
network_rx: agent.network_rx,
|
||||
network_tx: agent.network_tx,
|
||||
hostname: agent.hostname,
|
||||
ip_address: agent.ip_address,
|
||||
ip_addresses: agent.ip_addresses,
|
||||
os: agent.os,
|
||||
version: agent.version
|
||||
}))
|
||||
@@ -480,7 +480,7 @@ async function createDefaultStatusPageData(c: any) {
|
||||
network_rx: agent.network_rx || 0,
|
||||
network_tx: agent.network_tx || 0,
|
||||
hostname: agent.hostname || "未知主机",
|
||||
ip_address: agent.ip_address || "0.0.0.0",
|
||||
ip_addresses: agent.ip_addresses || "0.0.0.0",
|
||||
os: agent.os || "未知系统",
|
||||
version: agent.version || "未知版本"
|
||||
};
|
||||
@@ -502,7 +502,7 @@ async function createDefaultStatusPageData(c: any) {
|
||||
network_rx: agent.network_rx,
|
||||
network_tx: agent.network_tx,
|
||||
hostname: agent.hostname,
|
||||
ip_address: agent.ip_address,
|
||||
ip_addresses: agent.ip_addresses,
|
||||
os: agent.os,
|
||||
version: agent.version
|
||||
}))
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function createTables(env: Bindings): Promise<void> {
|
||||
await 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 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))");
|
||||
await 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_addresses 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 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))");
|
||||
@@ -166,8 +166,8 @@ export async function addSampleAgents(env: Bindings): Promise<void> {
|
||||
|
||||
// 主服务器
|
||||
await 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
`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_addresses, os, version)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).bind(
|
||||
'主服务器',
|
||||
'primary-server-token-123456',
|
||||
@@ -183,15 +183,15 @@ export async function addSampleAgents(env: Bindings): Promise<void> {
|
||||
1024, // 网络接收流量 (KB)
|
||||
512, // 网络发送流量 (KB)
|
||||
'primary-server', // 主机名
|
||||
'192.168.1.10', // IP地址
|
||||
'192.168.1.11', // IP地址列表
|
||||
'Linux Ubuntu 20.04', // 操作系统
|
||||
'1.0.0' // 版本
|
||||
).run();
|
||||
|
||||
// 备份服务器
|
||||
await 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
`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_addresses, os, version)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).bind(
|
||||
'备份服务器',
|
||||
'backup-server-token-789012',
|
||||
@@ -207,14 +207,14 @@ export async function addSampleAgents(env: Bindings): Promise<void> {
|
||||
512, // 网络接收流量 (KB)
|
||||
256, // 网络发送流量 (KB)
|
||||
'backup-server', // 主机名
|
||||
'192.168.1.20', // IP地址
|
||||
'192.168.1.21', // IP地址列表
|
||||
'Linux Debian 11', // 操作系统
|
||||
'1.0.0' // 版本
|
||||
).run();
|
||||
|
||||
// 应用服务器
|
||||
await 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)
|
||||
`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_addresses, os, version)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).bind(
|
||||
'应用服务器',
|
||||
@@ -231,7 +231,7 @@ export async function addSampleAgents(env: Bindings): Promise<void> {
|
||||
2048, // 网络接收流量 (KB)
|
||||
1024, // 网络发送流量 (KB)
|
||||
'app-server', // 主机名
|
||||
'192.168.1.30', // IP地址
|
||||
'192.168.1.31', // IP地址列表
|
||||
'Linux CentOS 7', // 操作系统
|
||||
'1.0.0' // 版本
|
||||
).run();
|
||||
@@ -273,7 +273,7 @@ export async function createNotificationTemplates(env: Bindings): Promise<void>
|
||||
'Agent监控模板',
|
||||
'default',
|
||||
'【${status}】${name} 客户端状态变更',
|
||||
'🔔 客户端状态变更通知\n\n📊 主机: ${name}\n🔄 状态: ${status} (之前: ${previous_status})\n🕒 时间: ${time}\n\n🖥️ 主机信息:\n 主机名: ${hostname}\n IP地址: ${ip_address}\n 操作系统: ${os}\n\n❗ 错误信息: ${error}',
|
||||
'🔔 客户端状态变更通知\n\n📊 主机: ${name}\n🔄 状态: ${status} (之前: ${previous_status})\n🕒 时间: ${time}\n\n🖥️ 主机信息:\n 主机名: ${hostname}\n IP地址: ${ip_addresses}\n 操作系统: ${os}\n\n❗ 错误信息: ${error}',
|
||||
1, // is_default=1
|
||||
userId,
|
||||
now,
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
createNotificationTemplates,
|
||||
createNotificationChannelsAndSettings
|
||||
} from './database';
|
||||
import { migrateFrom003To004 } from './migrate-0.0.3-to-0.0.4';
|
||||
|
||||
// 检查表是否存在
|
||||
async function tableExists(env: Bindings, tableName: string): Promise<boolean> {
|
||||
@@ -69,36 +70,17 @@ export async function checkAndInitializeDatabase(env: Bindings): Promise<{ initi
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有表都存在且用户表中有记录,则不需要初始化
|
||||
if (missingTables.length === 0) {
|
||||
// 确认用户表存在
|
||||
const usersTableExists = await tableExists(env, 'users');
|
||||
|
||||
if (usersTableExists) {
|
||||
try {
|
||||
const userCount = await env.DB.prepare('SELECT COUNT(*) as count FROM users').first<{ count: number }>();
|
||||
if (userCount && userCount.count > 0) {
|
||||
console.log('数据库已初始化且包含用户数据,跳过初始化操作...');
|
||||
return {
|
||||
initialized: false,
|
||||
message: '数据库已经初始化,不需要重新初始化',
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果查询用户表失败,说明表可能存在但数据有问题,继续初始化
|
||||
console.log('检查用户表数据出错:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
let initialized = false;
|
||||
|
||||
// 如果有表不存在或用户表为空,则进行初始化
|
||||
console.log('开始初始化数据库...');
|
||||
console.log('缺失的表:', missingTables.length > 0 ? missingTables.join(', ') : '无');
|
||||
|
||||
// 创建表结构(只创建不存在的表)
|
||||
if (missingTables.length > 0) {
|
||||
console.log('开始初始化数据库...');
|
||||
console.log('缺失的表:', missingTables.length > 0 ? missingTables.join(', ') : '无');
|
||||
|
||||
// 创建表结构(只创建不存在的表)
|
||||
console.log('创建缺失的表结构...');
|
||||
await createTables(env);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// 检查用户表是否存在并且为空,如果为空则创建管理员用户
|
||||
@@ -107,6 +89,7 @@ export async function checkAndInitializeDatabase(env: Bindings): Promise<{ initi
|
||||
if (!userCount || userCount.count === 0) {
|
||||
console.log('用户表为空,创建管理员用户...');
|
||||
await createAdminUser(env);
|
||||
initialized = true;
|
||||
} else {
|
||||
console.log('用户表已有数据,跳过创建管理员用户...');
|
||||
}
|
||||
@@ -120,6 +103,7 @@ export async function checkAndInitializeDatabase(env: Bindings): Promise<{ initi
|
||||
if (!monitorCount || monitorCount.count === 0) {
|
||||
console.log('监控表为空,添加示例监控...');
|
||||
await addSampleMonitors(env);
|
||||
initialized = true;
|
||||
} else {
|
||||
console.log('监控表已有数据,跳过添加示例监控...');
|
||||
}
|
||||
@@ -133,6 +117,7 @@ export async function checkAndInitializeDatabase(env: Bindings): Promise<{ initi
|
||||
if (!agentCount || agentCount.count === 0) {
|
||||
console.log('客户端表为空,添加示例客户端...');
|
||||
await addSampleAgents(env);
|
||||
initialized = true;
|
||||
} else {
|
||||
console.log('客户端表已有数据,跳过添加示例客户端...');
|
||||
}
|
||||
@@ -146,6 +131,7 @@ export async function checkAndInitializeDatabase(env: Bindings): Promise<{ initi
|
||||
if (!statusPageCount || statusPageCount.count === 0) {
|
||||
console.log('状态页配置表为空,创建默认状态页...');
|
||||
await createDefaultStatusPage(env);
|
||||
initialized = true;
|
||||
} else {
|
||||
console.log('状态页配置表已有数据,跳过创建默认状态页...');
|
||||
}
|
||||
@@ -159,6 +145,7 @@ export async function checkAndInitializeDatabase(env: Bindings): Promise<{ initi
|
||||
if (!templateCount || templateCount.count === 0) {
|
||||
console.log('通知模板表为空,创建默认通知模板...');
|
||||
await createNotificationTemplates(env);
|
||||
initialized = true;
|
||||
} else {
|
||||
console.log('通知模板表已有数据,跳过创建默认通知模板...');
|
||||
}
|
||||
@@ -172,6 +159,7 @@ export async function checkAndInitializeDatabase(env: Bindings): Promise<{ initi
|
||||
if (!channelCount || channelCount.count === 0) {
|
||||
console.log('通知渠道表为空,创建默认通知渠道和设置...');
|
||||
await createNotificationChannelsAndSettings(env);
|
||||
initialized = true;
|
||||
} else {
|
||||
console.log('通知渠道表已有数据,跳过创建默认通知渠道和设置...');
|
||||
}
|
||||
@@ -179,12 +167,35 @@ export async function checkAndInitializeDatabase(env: Bindings): Promise<{ initi
|
||||
console.log('通知渠道表不存在,跳过检查通知渠道数据...');
|
||||
}
|
||||
|
||||
// 在所有初始化操作完成后执行迁移脚本,确保数据库表已存在
|
||||
console.log('检查并执行数据库迁移...');
|
||||
await runMigrations(env);
|
||||
|
||||
return {
|
||||
initialized: true,
|
||||
message: '数据库初始化成功',
|
||||
initialized,
|
||||
message: initialized ? '数据库初始化成功' : '数据库已经初始化,不需要重新初始化',
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('数据库初始化检查错误:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 执行所有迁移脚本
|
||||
async function runMigrations(env: Bindings): Promise<void> {
|
||||
try {
|
||||
console.log('开始执行数据库迁移...');
|
||||
|
||||
// 从v0.0.3迁移到v0.0.4
|
||||
const migrationResult = await migrateFrom003To004(env);
|
||||
console.log(`迁移 0.0.3 -> 0.0.4: ${migrationResult.message}`);
|
||||
|
||||
// 将来可以在这里添加更多迁移脚本
|
||||
// 例如: const migration2Result = await migrateFrom004To005(env);
|
||||
|
||||
console.log('所有迁移已完成');
|
||||
} catch (error) {
|
||||
console.error('执行迁移脚本时出错:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 迁移脚本: 从0.0.3版本迁移到0.0.4版本
|
||||
* 主要变更: 确保 agents 表中有 ip_addresses 字段
|
||||
*/
|
||||
|
||||
import { Bindings } from '../models/db';
|
||||
|
||||
/**
|
||||
* 检查表中是否存在特定字段
|
||||
*/
|
||||
async function columnExists(env: Bindings, table: string, column: string): Promise<boolean> {
|
||||
try {
|
||||
const tableInfo = await env.DB.prepare(
|
||||
`PRAGMA table_info(${table})`
|
||||
).all();
|
||||
|
||||
return tableInfo.results?.some(
|
||||
(col: any) => col.name === column
|
||||
) || false;
|
||||
} catch (error) {
|
||||
console.error(`检查字段 ${column} 是否存在时出错:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function migrateFrom003To004(env: Bindings): Promise<{ success: boolean, message: string }> {
|
||||
try {
|
||||
console.log('开始从v0.0.3迁移到v0.0.4...');
|
||||
|
||||
// 检查 agents 表是否存在
|
||||
const tableExists = await env.DB.prepare(
|
||||
`SELECT name FROM sqlite_master WHERE type='table' AND name='agents'`
|
||||
).first<{ name: string }>();
|
||||
|
||||
if (!tableExists) {
|
||||
console.log('agents 表不存在,跳过迁移');
|
||||
return {
|
||||
success: true,
|
||||
message: 'agents 表不存在,跳过迁移',
|
||||
};
|
||||
}
|
||||
|
||||
// 检查 ip_addresses 字段是否存在
|
||||
const hasIpAddressesColumn = await columnExists(env, 'agents', 'ip_addresses');
|
||||
|
||||
if (hasIpAddressesColumn) {
|
||||
console.log('ip_addresses 字段已存在,无需迁移');
|
||||
return {
|
||||
success: true,
|
||||
message: 'ip_addresses 字段已存在,无需迁移',
|
||||
};
|
||||
}
|
||||
|
||||
// 执行迁移 - 添加 ip_addresses 字段
|
||||
console.log('添加 ip_addresses 字段到 agents 表...');
|
||||
await env.DB.exec(`ALTER TABLE agents ADD COLUMN ip_addresses TEXT`);
|
||||
|
||||
console.log('迁移完成: ip_addresses 字段已添加到 agents 表');
|
||||
|
||||
// 更新现有记录的 ip_addresses 字段
|
||||
const agents = await env.DB.prepare('SELECT id, hostname FROM agents WHERE ip_addresses IS NULL').all<{id: number, hostname: string}>();
|
||||
|
||||
if (agents.results && agents.results.length > 0) {
|
||||
console.log(`更新 ${agents.results.length} 条现有记录的 ip_addresses 字段...`);
|
||||
|
||||
for (const agent of agents.results) {
|
||||
// 使用主机名作为默认的IP地址值
|
||||
const defaultIpValue = agent.hostname ? `${agent.hostname}-ip` : 'unknown-ip';
|
||||
|
||||
await env.DB.prepare(
|
||||
'UPDATE agents SET ip_addresses = ? WHERE id = ?'
|
||||
).bind(defaultIpValue, agent.id).run();
|
||||
}
|
||||
|
||||
console.log('现有记录的 ip_addresses 字段已更新');
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: '从v0.0.3到v0.0.4的迁移成功完成',
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('迁移错误:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: `迁移失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -86,10 +86,10 @@ async function handleAgentOfflineNotification(env: any, agentId: number, agentNa
|
||||
previous_status: 'online', // 添加previous_status变量
|
||||
time: new Date().toLocaleString('zh-CN'),
|
||||
hostname: agentData.hostname || '未知',
|
||||
ip_address: agentData.ip_address || '未知',
|
||||
ip_addresses: agentData.ip_addresses || '未知',
|
||||
os: agentData.os || '未知',
|
||||
error: '客户端连接超时',
|
||||
details: `主机名: ${agentData.hostname || '未知'}\nIP地址: ${agentData.ip_address || '未知'}\n操作系统: ${agentData.os || '未知'}\n最后连接时间: ${new Date(agentData.updated_at).toLocaleString('zh-CN')}`
|
||||
details: `主机名: ${agentData.hostname || '未知'}\nIP地址: ${agentData.ip_addresses || '未知'}\n操作系统: ${agentData.os || '未知'}\n最后连接时间: ${new Date(agentData.updated_at).toLocaleString('zh-CN')}`
|
||||
};
|
||||
|
||||
// 发送通知
|
||||
@@ -196,10 +196,10 @@ export async function handleAgentThresholdNotification(env: any, agentId: number
|
||||
previous_status: 'normal', // 添加previous_status变量
|
||||
time: new Date().toLocaleString('zh-CN'),
|
||||
hostname: agent.hostname || '未知',
|
||||
ip_address: agent.ip_address || '未知',
|
||||
ip_addresses: agent.ip_addresses || '未知',
|
||||
os: agent.os || '未知',
|
||||
error: `${metricName}(${value.toFixed(2)}%)超过阈值(${threshold}%)`,
|
||||
details: `${metricName}: ${value.toFixed(2)}%\n阈值: ${threshold}%\n主机名: ${agent.hostname || '未知'}\nIP地址: ${agent.ip_address || '未知'}\n操作系统: ${agent.os || '未知'}`
|
||||
details: `${metricName}: ${value.toFixed(2)}%\n阈值: ${threshold}%\n主机名: ${agent.hostname || '未知'}\nIP地址: ${agent.ip_addresses || '未知'}\n操作系统: ${agent.os || '未知'}`
|
||||
};
|
||||
|
||||
// 发送通知
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface Agent {
|
||||
network_rx: number;
|
||||
network_tx: number;
|
||||
hostname?: string;
|
||||
ip_address?: string;
|
||||
ip_addresses?: string;
|
||||
os?: string;
|
||||
version?: string;
|
||||
token?: string;
|
||||
@@ -97,7 +97,7 @@ export const deleteAgent = async (id: number) => {
|
||||
export const updateAgent = async (id: number, data: {
|
||||
name?: string;
|
||||
hostname?: string;
|
||||
ip_address?: string;
|
||||
ip_addresses?: string;
|
||||
os?: string;
|
||||
version?: string;
|
||||
status?: string;
|
||||
|
||||
@@ -258,7 +258,14 @@ const AgentDetail = () => {
|
||||
<Heading size="5">{agent.name}</Heading>
|
||||
<Text color="gray" size="2">
|
||||
{agent.hostname ? agent.hostname : ''}
|
||||
{agent.hostname && agent.ip_address ? ` (${agent.ip_address})` : ''}
|
||||
{agent.hostname && agent.ip_addresses ? (() => {
|
||||
try {
|
||||
const ipArray = JSON.parse(String(agent.ip_addresses));
|
||||
return ipArray.length > 0 ? ` (${ipArray[0]})` : '';
|
||||
} catch (e) {
|
||||
return ` (${String(agent.ip_addresses)})`;
|
||||
}
|
||||
})() : ''}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
@@ -315,13 +322,25 @@ const AgentDetail = () => {
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Flex align="center" gap="2">
|
||||
<LaptopIcon />
|
||||
<Text as="div" size="2" weight="bold">{t('agent.ipAddress')}:</Text>
|
||||
<Text as="div" size="2">{agent.ip_address || t('common.notFound')}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Flex direction="column" gap="1">
|
||||
<Text size="2" weight="bold">{t('agent.ipAddress')}:</Text>
|
||||
{agent.ip_addresses ? (
|
||||
<Flex direction="column" gap="1">
|
||||
{(() => {
|
||||
try {
|
||||
const ipArray = JSON.parse(String(agent.ip_addresses));
|
||||
return Array.isArray(ipArray)
|
||||
? ipArray.map((ip, index) => <Text key={index} size="2">{ip}</Text>)
|
||||
: <Text size="2">{String(agent.ip_addresses)}</Text>;
|
||||
} catch (e) {
|
||||
return <Text size="2">{String(agent.ip_addresses)}</Text>;
|
||||
}
|
||||
})()}
|
||||
</Flex>
|
||||
) : (
|
||||
<Text size="2" color="gray">{t('common.unknown')}</Text>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -172,7 +172,16 @@ const AgentsList = () => {
|
||||
<Text>{agent.hostname || t('common.notFound')}</Text>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Text>{agent.ip_address || t('common.notFound')}</Text>
|
||||
<Text>{agent.ip_addresses ? (() => {
|
||||
try {
|
||||
const ipArray = JSON.parse(String(agent.ip_addresses));
|
||||
return Array.isArray(ipArray) && ipArray.length > 0
|
||||
? ipArray.join(', ')
|
||||
: String(agent.ip_addresses);
|
||||
} catch (e) {
|
||||
return String(agent.ip_addresses);
|
||||
}
|
||||
})() : t('common.notFound')}</Text>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Badge
|
||||
|
||||
@@ -1141,7 +1141,7 @@ const NotificationsConfig = () => {
|
||||
<Flex justify="between" align="center">
|
||||
<Box>
|
||||
<Text weight="medium" style={{ marginRight: '4px' }}>{agent.name}</Text>
|
||||
<Text size="1" color="gray">{agent.ip_address}</Text>
|
||||
<Text size="1" color="gray">{agent.ip_addresses}</Text>
|
||||
</Box>
|
||||
<Flex align="center" gap="2">
|
||||
<Text size="2">{t('notifications.specificSettings.override')}</Text>
|
||||
|
||||
@@ -205,7 +205,14 @@ const StatusPage = () => {
|
||||
<Heading as="h3" size="3" mb="1">{agent.name}</Heading>
|
||||
<Text as="div" size="2" color="gray">
|
||||
{agent.hostname}
|
||||
{agent.ip_address && ` (${agent.ip_address})`}
|
||||
{agent.ip_addresses && (() => {
|
||||
try {
|
||||
const ipArray = JSON.parse(String(agent.ip_addresses));
|
||||
return ipArray.length > 0 ? ` (${ipArray[0]})` : '';
|
||||
} catch (e) {
|
||||
return ` (${String(agent.ip_addresses)})`;
|
||||
}
|
||||
})()}
|
||||
</Text>
|
||||
</Box>
|
||||
<Badge
|
||||
@@ -257,7 +264,16 @@ const StatusPage = () => {
|
||||
<Text size="2">{agent.hostname || t('common.unknown')}</Text>
|
||||
|
||||
<Text size="2" style={{ color: 'var(--gray-9)' }}>{t('agent.ipAddress')}:</Text>
|
||||
<Text size="2">{agent.ip_address || t('common.unknown')}</Text>
|
||||
<Text size="2">{agent.ip_addresses ? (() => {
|
||||
try {
|
||||
const ipArray = JSON.parse(String(agent.ip_addresses));
|
||||
return Array.isArray(ipArray) && ipArray.length > 0
|
||||
? ipArray.join(', ')
|
||||
: String(agent.ip_addresses);
|
||||
} catch (e) {
|
||||
return String(agent.ip_addresses);
|
||||
}
|
||||
})() : t('common.unknown')}</Text>
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface Agent {
|
||||
id: string | number;
|
||||
name: string;
|
||||
hostname?: string;
|
||||
ip_address?: string;
|
||||
ip_addresses?: string; // JSON 字符串,存储 IP 地址列表
|
||||
status: 'online' | 'offline' | 'unknown' | 'active'; // 添加'active'以兼容Dashboard
|
||||
lastSeen?: string;
|
||||
last_seen?: string;
|
||||
|
||||
@@ -302,12 +302,27 @@ const AgentDetailScreen: React.FC = () => {
|
||||
<Text style={styles.detailLabel}>{t('agents.hostname', '主机名')}:</Text>
|
||||
<Text style={styles.detailValue}>{agent.hostname || t('common.unknown', '未知')}</Text>
|
||||
</View>
|
||||
{agent.ip_address && (
|
||||
<View style={styles.detailItem}>
|
||||
<Text style={styles.detailLabel}>{t('agents.ipAddress', 'IP地址')}:</Text>
|
||||
<Text style={styles.detailValue}>{agent.ip_address}</Text>
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.detailItem}>
|
||||
<Text style={styles.detailLabel}>{t('agents.ipAddress', 'IP地址')}:</Text>
|
||||
{agent.ip_addresses ? (
|
||||
<View>
|
||||
{(() => {
|
||||
try {
|
||||
const ipArray = JSON.parse(String(agent.ip_addresses));
|
||||
return Array.isArray(ipArray) ?
|
||||
ipArray.map((ip: string, index: number) => (
|
||||
<Text key={index} style={styles.detailValue}>{ip}</Text>
|
||||
))
|
||||
: <Text style={styles.detailValue}>{String(agent.ip_addresses)}</Text>;
|
||||
} catch (e) {
|
||||
return <Text style={styles.detailValue}>{String(agent.ip_addresses)}</Text>;
|
||||
}
|
||||
})()}
|
||||
</View>
|
||||
) : (
|
||||
<Text style={styles.detailValue}>{t('common.unknown', 'unknown')}</Text>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.detailItem}>
|
||||
<Text style={styles.detailLabel}>{t('agents.os', '操作系统')}:</Text>
|
||||
<Text style={styles.detailValue}>{agent.operating_system || agent.os || t('common.unknown', '未知')}</Text>
|
||||
|
||||
@@ -163,10 +163,10 @@ const AgentsScreen: React.FC = () => {
|
||||
<Text style={styles.infoText}>{item.os || t('common.unknown', '未知')}</Text>
|
||||
</View>
|
||||
|
||||
{item.ip_address && (
|
||||
{item.ip_addresses && (
|
||||
<View style={styles.infoItem}>
|
||||
<Ionicons name="wifi-outline" size={14} color="#666" />
|
||||
<Text style={styles.infoText}>{item.ip_address}</Text>
|
||||
<Text style={styles.infoText}>{item.ip_addresses}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ interface Agent {
|
||||
id: string;
|
||||
name: string;
|
||||
hostname?: string;
|
||||
ip_address?: string;
|
||||
ip_addresses?: string; // JSON 字符串,存储 IP 地址列表
|
||||
status?: string;
|
||||
os?: string;
|
||||
cpu?: {
|
||||
@@ -145,7 +145,7 @@ const AgentNotificationsScreen: React.FC = () => {
|
||||
id: '1',
|
||||
name: t('agents.title', '应用服务器'),
|
||||
hostname: 'app-server-01',
|
||||
ip_address: '192.168.1.10',
|
||||
ip_addresses: '192.168.1.10',
|
||||
status: 'active',
|
||||
os: 'Linux',
|
||||
cpu: { usage: 32.5, cores: 4, model: 'Intel i7' },
|
||||
@@ -159,7 +159,7 @@ const AgentNotificationsScreen: React.FC = () => {
|
||||
id: '2',
|
||||
name: t('dashboard.title', '数据库服务器'),
|
||||
hostname: 'db-server-01',
|
||||
ip_address: '192.168.1.20',
|
||||
ip_addresses: '192.168.1.20',
|
||||
status: 'active',
|
||||
os: 'Ubuntu',
|
||||
cpu: { usage: 65.2, cores: 8, model: 'AMD Ryzen' },
|
||||
@@ -430,7 +430,7 @@ const AgentNotificationsScreen: React.FC = () => {
|
||||
<View style={styles.specificSettingTitleContainer}>
|
||||
<Text style={styles.specificSettingTitle}>{agent.name}</Text>
|
||||
<Text style={styles.specificSettingDescription}>
|
||||
{agent.hostname || agent.ip_address || t('common.unknown', 'Unknown')}
|
||||
{agent.hostname || t('common.unknown', 'Unknown')}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[
|
||||
|
||||
Reference in new issue
Block a user