Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- **实时状态监控**: 实时查看 CPU、内存、硬盘使用率、系统负载、网络速度等关键指标。
- **全球地图视图**: 在交互式世界地图上直观地展示您的服务器节点分布和在线状态。
- **历史数据图表**: 查看单个服务器过去24小时的性能历史图表,帮助分析趋势和问题。
- **深度节点洞察**: 在详情面板中实时查看可用率、性能摘要、累计流量以及最近的故障记录。
- **掉线告警**: 当服务器离线时,可通过 Telegram Bot 自动发送通知,并在恢复时收到提醒。
- **多数据库支持**: 支持 SQLite, MySQL 和 PostgreSQL,安装过程简单灵活。
- **轻量级探针**: 客户端探针是一个简单的 Bash 脚本,资源占用极低,兼容性强。
Expand Down
112 changes: 33 additions & 79 deletions api.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,98 +3,52 @@
ini_set('display_errors', 0);
error_reporting(0);
header('Content-Type: application/json');

require_once __DIR__ . '/includes/bootstrap.php';

$action = $_GET['action'] ?? 'get_all';
$server_id = $_GET['id'] ?? null;
$serverId = $_GET['id'] ?? null;
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 500;

try {
$pdo = get_pdo_connection();
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

if ($action === 'get_history' && $server_id) {
// --- Action: Get detailed history for a single server (last 24h) ---
$sql_history = 'SELECT cpu_usage, mem_usage_percent, disk_usage_percent, load_avg, net_up_speed, net_down_speed, total_up, total_down, timestamp, processes, connections FROM server_stats WHERE server_id = ? ORDER BY timestamp DESC LIMIT 1440';
$stmt_history = $pdo->prepare($sql_history);
$stmt_history->execute([$server_id]);
$history = $stmt_history->fetchAll(PDO::FETCH_ASSOC);
$service = new MonitoringService($pdo, $db_config);

$typed_history = array_map(function($record) {
foreach($record as $key => $value) {
if ($value !== null && is_numeric($value)) {
$record[$key] = strpos($value, '.') === false ? (int)$value : (float)$value;
}
switch ($action) {
case 'get_history':
if (!$serverId) {
linker_json_response(['error' => '缺少服务器 ID'], 400);
}
return $record;
}, $history);

echo json_encode(['history' => array_reverse($typed_history)]);
exit;
$history = $service->getServerHistory($serverId, $limit);
linker_json_response(['history' => $history]);
break;

} elseif ($action === 'get_all') {
// --- Action: Get main dashboard data (lightweight) ---
$response = [
'nodes' => [],
'outages' => [],
'site_name' => '灵刻监控'
];

$stmt_site_name = $pdo->prepare("SELECT value FROM settings WHERE `key` = 'site_name'");
$stmt_site_name->execute();
if ($site_name = $stmt_site_name->fetchColumn()) {
$response['site_name'] = $site_name;
}
case 'get_summary':
linker_json_response(['summary' => $service->getSummaryOnly()]);
break;

$stmt_servers = $pdo->query("SELECT id, name, intro, tags, price_usd_yearly, latitude, longitude, country_code, system, arch, cpu_model, mem_total, disk_total FROM servers ORDER BY id ASC");
$servers = $stmt_servers->fetchAll(PDO::FETCH_ASSOC);
case 'get_insights':
linker_json_response(['insights' => $service->getInsights()]);
break;

$stmt_status = $pdo->query("SELECT id, is_online, last_checked FROM server_status");
$online_status_raw = $stmt_status->fetchAll(PDO::FETCH_ASSOC);
$online_status = [];
foreach ($online_status_raw as $status) {
$online_status[$status['id']] = $status;
}

if ($db_config['type'] === 'pgsql') {
$sql_stats = "SELECT DISTINCT ON (server_id) * FROM server_stats ORDER BY server_id, timestamp DESC";
} else { // Works for SQLite and MySQL
$sql_stats = "SELECT s.* FROM server_stats s JOIN (SELECT server_id, MAX(timestamp) AS max_ts FROM server_stats GROUP BY server_id) AS m ON s.server_id = m.server_id AND s.timestamp = m.max_ts";
}
$stmt_stats = $pdo->query($sql_stats);
$latest_stats_raw = $stmt_stats->fetchAll(PDO::FETCH_ASSOC);
$latest_stats = [];
foreach($latest_stats_raw as $stat) {
$latest_stats[$stat['server_id']] = $stat;
}

foreach ($servers as $node) {
$node_id = $node['id'];
$node['x'] = (float)($node['latitude'] ?? 0);
$node['y'] = (float)($node['longitude'] ?? 0);
$node['stats'] = $latest_stats[$node_id] ?? [];

$status_info = $online_status[$node_id] ?? ['is_online' => false, 'last_checked' => 0];
$node['is_online'] = (bool)$status_info['is_online'];

if (!$node['is_online'] && $status_info['last_checked'] > 0) {
$node['anomaly_msg'] = '服务器掉线';
$node['outage_duration'] = time() - $status_info['last_checked'];
case 'get_node_detail':
if (!$serverId) {
linker_json_response(['error' => '缺少服务器 ID'], 400);
}

$node['history'] = []; // History is no longer included in the main payload

$response['nodes'][] = $node;
}

$response['outages'] = $pdo->query("SELECT * FROM outages ORDER BY start_time DESC LIMIT 100")->fetchAll(PDO::FETCH_ASSOC);

echo json_encode($response);
$node = $service->getServerDetails($serverId);
linker_json_response(['node' => $node]);
break;

case 'get_all':
default:
$payload = $service->getDashboardPayload();
linker_json_response($payload);
break;
}

} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
exit;
} catch (Throwable $e) {
error_log('api.php error: ' . $e->getMessage());
linker_json_response(['error' => '服务器内部错误,请稍后重试。'], 500);
}

?>

Loading