feat: support mutiple ip addrs

This commit is contained in:
zaunist committed 2025-04-15 10:14:10 +08:00
1 parent bd74364c57
commit 182e50d893
20 files changed
+309 -133

No files matched your search

+37 -32
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+1 -1
View File
@@ -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
View File
@@ -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;
}
+5 -5
View File
@@ -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
});
+4 -4
View File
@@ -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
}))
+10 -10
View File
@@ -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,
+38 -27
View File
@@ -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)}`,
};
}
}
+4 -4
View File
@@ -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 || '未知'}`
};
// 发送通知
+2 -2
View File
@@ -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;
+27 -8
View File
@@ -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>
+10 -1
View File
@@ -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>
+18 -2
View File
@@ -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>
)}
+1 -1
View File
@@ -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>
+2 -2
View File
@@ -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={[