mirror of
https://github.com/Kori1c/ecs-controller.git
synced 2026-06-02 06:19:43 +08:00
Rebrand to ecs-controller: modernize UI, optimize async release engine, and update documentation for MJJs
This commit is contained in:
@@ -22,22 +22,22 @@ class NotificationService
|
||||
|
||||
$title = "定时任务: " . $actionType;
|
||||
$maskedKey = substr($account['access_key_id'], 0, 7) . '***';
|
||||
$traffic = isset($account['traffic_used']) ? round($account['traffic_used'], 2) : 'N/A';
|
||||
$traffic = isset($account['traffic_used']) ? $this->formatTraffic((float) $account['traffic_used']) : '暂无';
|
||||
$threshold = $this->config['traffic_threshold'] ?? 95;
|
||||
|
||||
$details = [
|
||||
['label' => '账号 ID', 'value' => $maskedKey],
|
||||
['label' => '账号', 'value' => $maskedKey],
|
||||
['label' => '执行动作', 'value' => $actionType, 'highlight' => true],
|
||||
['label' => '执行时间', 'value' => date('Y-m-d H:i:s')],
|
||||
['label' => '当前流量', 'value' => $traffic . ' GB'],
|
||||
['label' => '当前流量', 'value' => $traffic],
|
||||
['label' => '设定阈值', 'value' => $threshold . '%'],
|
||||
['label' => '详情说明', 'value' => $description ?: '根据预设时间表自动执行。']
|
||||
];
|
||||
|
||||
$textMsg = "【CDT Monitor】{$title}\n" .
|
||||
"账号 ID: {$maskedKey}\n" .
|
||||
$textMsg = "【ECS 服务器管家】{$title}\n" .
|
||||
"账号: {$maskedKey}\n" .
|
||||
"执行动作: {$actionType}\n" .
|
||||
"当前流量: {$traffic} GB\n" .
|
||||
"当前流量: {$traffic}\n" .
|
||||
"设定阈值: {$threshold}%\n" .
|
||||
"执行时间: " . date('Y-m-d H:i:s') . "\n" .
|
||||
"详情说明: " . ($description ?: '根据预设时间表自动执行。');
|
||||
@@ -52,17 +52,18 @@ class NotificationService
|
||||
public function sendTrafficWarning($accessKeyId, $traffic, $percentage, $statusText, $threshold)
|
||||
{
|
||||
$title = "流量告警 - " . $statusText;
|
||||
$trafficText = $this->formatTraffic((float) $traffic);
|
||||
$details = [
|
||||
['label' => '账号 ID', 'value' => substr($accessKeyId, 0, 7) . '***'],
|
||||
['label' => '当前流量', 'value' => $traffic . ' GB'],
|
||||
['label' => '账号', 'value' => substr($accessKeyId, 0, 7) . '***'],
|
||||
['label' => '当前流量', 'value' => $trafficText],
|
||||
['label' => '使用率', 'value' => $percentage . '%', 'highlight' => true],
|
||||
['label' => '设定阈值', 'value' => $threshold . '%'],
|
||||
['label' => '当前状态', 'value' => $statusText]
|
||||
];
|
||||
|
||||
$textMsg = "【CDT Monitor】{$title}\n" .
|
||||
"账号 ID: " . substr($accessKeyId, 0, 7) . '***' . "\n" .
|
||||
"当前流量: {$traffic} GB\n" .
|
||||
$textMsg = "【ECS 服务器管家】{$title}\n" .
|
||||
"账号: " . substr($accessKeyId, 0, 7) . '***' . "\n" .
|
||||
"当前流量: {$trafficText}\n" .
|
||||
"使用率: {$percentage}%\n" .
|
||||
"设定阈值: {$threshold}%\n" .
|
||||
"当前状态: {$statusText}";
|
||||
@@ -70,30 +71,144 @@ class NotificationService
|
||||
return $this->dispatchNotifications($title, "检测到流量异常或达到阈值", $details, 'warning', $textMsg, $accessKeyId);
|
||||
}
|
||||
|
||||
public function notifyEcsCreated($accountLabel, array $result, array $preview = [])
|
||||
{
|
||||
$title = 'ECS 创建并启动成功';
|
||||
$instanceId = $result['instanceId'] ?? '';
|
||||
$publicIp = $result['publicIp'] ?? '';
|
||||
$loginUser = $result['loginUser'] ?? '';
|
||||
$loginPassword = $result['loginPassword'] ?? '';
|
||||
$regionId = $preview['regionId'] ?? '';
|
||||
$instanceType = $preview['instanceType'] ?? ($result['instanceType'] ?? '');
|
||||
$instanceName = $preview['instanceName'] ?? ($result['instanceName'] ?? '');
|
||||
|
||||
$details = [
|
||||
['label' => '账号', 'value' => $accountLabel],
|
||||
['label' => '实例名称', 'value' => $instanceName ?: '-'],
|
||||
['label' => '实例编号', 'value' => $instanceId ?: '-', 'highlight' => true],
|
||||
['label' => '区域', 'value' => $regionId ?: '-'],
|
||||
['label' => '实例规格', 'value' => $instanceType ?: '-'],
|
||||
['label' => '公网地址', 'value' => $publicIp ?: '等待阿里云分配'],
|
||||
['label' => '登录用户', 'value' => $loginUser ?: '-'],
|
||||
['label' => '初始密码', 'value' => $loginPassword ?: '-', 'highlight' => true],
|
||||
['label' => '安全提醒', 'value' => '初始密码仅在本次创建完成通知和控制台弹窗展示,请立即保存。']
|
||||
];
|
||||
|
||||
$textMsg = "【ECS 服务器管家】ECS 创建并启动成功\n" .
|
||||
"账号: {$accountLabel}\n" .
|
||||
"实例名称: " . ($instanceName ?: '-') . "\n" .
|
||||
"实例编号: " . ($instanceId ?: '-') . "\n" .
|
||||
"区域: " . ($regionId ?: '-') . "\n" .
|
||||
"规格: " . ($instanceType ?: '-') . "\n" .
|
||||
"公网地址: " . ($publicIp ?: '等待阿里云分配') . "\n" .
|
||||
"登录用户: " . ($loginUser ?: '-') . "\n" .
|
||||
"初始密码: " . ($loginPassword ?: '-') . "\n" .
|
||||
"安全提醒: 初始密码仅在本次创建完成通知和控制台弹窗展示,请立即保存。";
|
||||
|
||||
return $this->dispatchNotifications($title, '实例已创建并启动,请立即保存一次性登录密码。', $details, 'success', $textMsg, $accountLabel);
|
||||
}
|
||||
|
||||
public function notifyInstanceStatusChanged($accountLabel, array $account, $fromStatus, $toStatus, $reason = '')
|
||||
{
|
||||
$fromLabel = $this->statusLabel($fromStatus);
|
||||
$toLabel = $this->statusLabel($toStatus);
|
||||
$instanceName = $account['instance_name'] ?? ($account['remark'] ?? '');
|
||||
$instanceId = $account['instance_id'] ?? '';
|
||||
$region = $account['region_id'] ?? '';
|
||||
$title = $toStatus === 'Running' ? '实例已启动' : ($toStatus === 'Stopped' ? '实例已停机' : "实例状态变化 - {$toLabel}");
|
||||
|
||||
$details = [
|
||||
['label' => '账号', 'value' => $accountLabel],
|
||||
['label' => '实例名称', 'value' => $instanceName ?: '-'],
|
||||
['label' => '实例编号', 'value' => $instanceId ?: '-', 'highlight' => true],
|
||||
['label' => '区域', 'value' => $region ?: '-'],
|
||||
['label' => '原状态', 'value' => $fromLabel],
|
||||
['label' => '新状态', 'value' => $toLabel, 'highlight' => true],
|
||||
['label' => '变化时间', 'value' => date('Y-m-d H:i:s')],
|
||||
['label' => '说明', 'value' => $reason ?: '系统检测到实例运行状态发生变化。']
|
||||
];
|
||||
|
||||
$textMsg = "【ECS 服务器管家】{$title}\n" .
|
||||
"账号: {$accountLabel}\n" .
|
||||
"实例名称: " . ($instanceName ?: '-') . "\n" .
|
||||
"实例编号: " . ($instanceId ?: '-') . "\n" .
|
||||
"区域: " . ($region ?: '-') . "\n" .
|
||||
"原状态: {$fromLabel}\n" .
|
||||
"新状态: {$toLabel}\n" .
|
||||
"变化时间: " . date('Y-m-d H:i:s') . "\n" .
|
||||
"说明: " . ($reason ?: '系统检测到实例运行状态发生变化。');
|
||||
|
||||
return $this->dispatchNotifications($title, '实例已进入最终状态', $details, 'success', $textMsg, $accountLabel);
|
||||
}
|
||||
|
||||
public function notifyInstanceReleased($accountLabel, array $account, $reason = '')
|
||||
{
|
||||
$instanceName = $account['instance_name'] ?? ($account['remark'] ?? '');
|
||||
$instanceId = $account['instance_id'] ?? '';
|
||||
$region = $account['region_id'] ?? '';
|
||||
$publicIp = $account['public_ip'] ?? '';
|
||||
$title = '实例已释放';
|
||||
|
||||
$details = [
|
||||
['label' => '账号', 'value' => $accountLabel],
|
||||
['label' => '实例名称', 'value' => $instanceName ?: '-'],
|
||||
['label' => '实例编号', 'value' => $instanceId ?: '-', 'highlight' => true],
|
||||
['label' => '区域', 'value' => $region ?: '-'],
|
||||
['label' => '公网地址', 'value' => $publicIp ?: '-'],
|
||||
['label' => '释放时间', 'value' => date('Y-m-d H:i:s')],
|
||||
['label' => '说明', 'value' => $reason ?: '实例已从 ECS 控制台释放,本地记录和 DDNS 解析将同步清理。']
|
||||
];
|
||||
|
||||
$textMsg = "【ECS 服务器管家】实例已释放\n" .
|
||||
"账号: {$accountLabel}\n" .
|
||||
"实例名称: " . ($instanceName ?: '-') . "\n" .
|
||||
"实例编号: " . ($instanceId ?: '-') . "\n" .
|
||||
"区域: " . ($region ?: '-') . "\n" .
|
||||
"公网地址: " . ($publicIp ?: '-') . "\n" .
|
||||
"释放时间: " . date('Y-m-d H:i:s') . "\n" .
|
||||
"说明: " . ($reason ?: '实例已从 ECS 控制台释放,本地记录和 DDNS 解析将同步清理。');
|
||||
|
||||
return $this->dispatchNotifications($title, '实例已释放,本地记录和 DDNS 解析将同步清理。', $details, 'warning', $textMsg, $accountLabel);
|
||||
}
|
||||
|
||||
private function statusLabel($status)
|
||||
{
|
||||
$map = [
|
||||
'Running' => '已启动',
|
||||
'Starting' => '启动中',
|
||||
'Stopping' => '停机中',
|
||||
'Stopped' => '已停机',
|
||||
'Pending' => '创建中',
|
||||
'Released' => '已释放',
|
||||
'Unknown' => '未知'
|
||||
];
|
||||
return $map[$status] ?? ($status ?: '未知');
|
||||
}
|
||||
|
||||
public function sendTestEmail($to)
|
||||
{
|
||||
$details = [
|
||||
['label' => '测试结果', 'value' => '成功 (Success)'],
|
||||
['label' => '测试结果', 'value' => '成功'],
|
||||
['label' => '发送时间', 'value' => date('Y-m-d H:i:s')],
|
||||
['label' => '服务器', 'value' => $_SERVER['SERVER_NAME'] ?? 'localhost']
|
||||
];
|
||||
$html = $this->renderEmailTemplate("测试邮件", "SMTP 配置验证成功", $details, 'success');
|
||||
return $this->sendMail($to, 'Admin', 'CDT Monitor Test', $html);
|
||||
$html = $this->renderEmailTemplate("测试邮件", "邮件服务配置验证成功", $details, 'success');
|
||||
return $this->sendMail($to, '管理员', 'ECS 服务器管家测试邮件', $html);
|
||||
}
|
||||
|
||||
public function sendTestTelegram($data)
|
||||
{
|
||||
$textMsg = "【CDT Monitor】测试推送\n这是一条来自 Telegram 的测试消息。\n发送时间: " . date('Y-m-d H:i:s');
|
||||
$textMsg = "【ECS 服务器管理】测试推送\n这是一条来自 Telegram 的测试消息。\n发送时间: " . date('Y-m-d H:i:s');
|
||||
return $this->sendTelegram($textMsg, $data);
|
||||
}
|
||||
|
||||
public function sendTestWebhook($data)
|
||||
{
|
||||
$textMsg = "【CDT Monitor】测试推送\n这是一条来自 Webhook 的测试消息。\n发送时间: " . date('Y-m-d H:i:s');
|
||||
$summary = "这是一条来自 Webhook 的测试消息。";
|
||||
$textMsg = "【ECS 服务器管家】测试推送\n这是一条来自接口回调的测试消息。\n发送时间: " . date('Y-m-d H:i:s');
|
||||
$summary = "这是一条来自接口回调的测试消息。";
|
||||
$threshold = $this->config['traffic_threshold'] ?? 95;
|
||||
$details = [
|
||||
['label' => '当前流量', 'value' => '0 GB'],
|
||||
['label' => '当前流量', 'value' => '0 MB'],
|
||||
['label' => '设定阈值', 'value' => $threshold . '%']
|
||||
];
|
||||
return $this->sendWebhook($textMsg, "测试推送", $summary, $details, 'test_account_id', $data);
|
||||
@@ -105,15 +220,15 @@ class NotificationService
|
||||
$successCount = 0;
|
||||
$attemptCount = 0;
|
||||
|
||||
// Email
|
||||
// 邮件通知
|
||||
if (($this->config['notify_email_enabled'] ?? '1') === '1' && !empty($this->config['notify_email'])) {
|
||||
$attemptCount++;
|
||||
$html = $this->renderEmailTemplate($title, $summary, $details, $type);
|
||||
$res = $this->sendMail($this->config['notify_email'], '', "CDT通知 - " . $title, $html);
|
||||
$res = $this->sendMail($this->config['notify_email'], '', "ECS 服务器管家通知 - " . $title, $html);
|
||||
if ($res === true)
|
||||
$successCount++;
|
||||
else
|
||||
$errors[] = "Email: " . $res;
|
||||
$errors[] = "邮件通知: " . $res;
|
||||
}
|
||||
|
||||
// Telegram
|
||||
@@ -123,21 +238,21 @@ class NotificationService
|
||||
if ($res === true)
|
||||
$successCount++;
|
||||
else
|
||||
$errors[] = "TG: " . $res;
|
||||
$errors[] = "Telegram: " . $res;
|
||||
}
|
||||
|
||||
// Webhook
|
||||
// 接口回调
|
||||
if (($this->config['notify_wh_enabled'] ?? '0') === '1' && !empty($this->config['notify_wh_url'])) {
|
||||
$attemptCount++;
|
||||
$res = $this->sendWebhook($textMsg, $title, $summary, $details, $accountId);
|
||||
if ($res === true)
|
||||
$successCount++;
|
||||
else
|
||||
$errors[] = "WH: " . $res;
|
||||
$errors[] = "接口回调: " . $res;
|
||||
}
|
||||
|
||||
if ($attemptCount == 0)
|
||||
return true; // No notifications enabled
|
||||
return true;
|
||||
|
||||
if ($successCount == 0 && count($errors) > 0) {
|
||||
return implode(" | ", $errors);
|
||||
@@ -175,12 +290,12 @@ class NotificationService
|
||||
<table width='100%' border='0' cellspacing='0' cellpadding='0' style='max-width: 500px; background-color: #FFFFFF; border-radius: 24px; overflow: hidden;'>
|
||||
<tr><td style='height: 6px; background-color: {$color};'></td></tr>
|
||||
<tr><td style='padding: 40px 30px;'>
|
||||
<div style='color: {$color}; font-size: 12px; font-weight: 700; text-transform: uppercase; margin-bottom: 8px;'>CDT MONITOR</div>
|
||||
<div style='color: {$color}; font-size: 12px; font-weight: 700; margin-bottom: 8px;'>ECS 服务器管家</div>
|
||||
<h1 style='margin: 0 0 10px 0; font-size: 24px; color: #1C1C1E;'>{$title}</h1>
|
||||
<p style='margin: 0 0 30px 0; font-size: 15px; color: #8E8E93;'>{$summary}</p>
|
||||
<table width='100%' border='0' cellspacing='0' cellpadding='0' style='border-top: 1px solid #F2F2F7;'>{$rows}</table>
|
||||
</td></tr>
|
||||
<tr><td style='background-color: #FAFAFC; padding: 20px; text-align: center; color: #AEAEB2; font-size: 12px;'>© " . date('Y') . " CDT Monitor</td></tr>
|
||||
<tr><td style='background-color: #FAFAFC; padding: 20px; text-align: center; color: #AEAEB2; font-size: 12px;'>© " . date('Y') . " ECS 服务器管家</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
</table>
|
||||
@@ -208,7 +323,7 @@ class NotificationService
|
||||
$mail->Username = $this->config['notify_username'] ?? '';
|
||||
$mail->Password = $this->config['notify_password'] ?? '';
|
||||
|
||||
$mail->SetFrom($mail->Username, '阿里云CDT监控');
|
||||
$mail->SetFrom($mail->Username, 'ECS 服务器管家');
|
||||
$mail->Subject = $subject;
|
||||
$mail->MsgHTML($body);
|
||||
$mail->AddAddress($to, $name);
|
||||
@@ -228,7 +343,7 @@ class NotificationService
|
||||
$proxyType = $overrideConfig['proxy_type'] ?? $this->config['notify_tg_proxy_type'] ?? 'none';
|
||||
|
||||
if (empty($token) || empty($chatId))
|
||||
return "Telegram Token 或 Chat ID 为空";
|
||||
return "Telegram 的机器人令牌或接收会话编号为空";
|
||||
|
||||
$url = "https://api.telegram.org/bot{$token}/sendMessage";
|
||||
if ($proxyType === 'custom' && !empty($overrideConfig['proxy_url'] ?? $this->config['notify_tg_proxy_url'] ?? '')) {
|
||||
@@ -268,9 +383,9 @@ class NotificationService
|
||||
curl_close($ch);
|
||||
|
||||
if ($error)
|
||||
return "Curl Error: " . $error;
|
||||
return "网络请求错误: " . $error;
|
||||
if ($httpCode != 200)
|
||||
return "HTTP Error {$httpCode}: " . $result;
|
||||
return "接口返回错误 {$httpCode}: " . $result;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -283,14 +398,14 @@ class NotificationService
|
||||
$bodyTemplate = $overrideConfig['body'] ?? $this->config['notify_wh_body'] ?? '';
|
||||
|
||||
if (empty($url))
|
||||
return "Webhook URL为空";
|
||||
return "接口回调地址为空";
|
||||
|
||||
// Parse variables
|
||||
$traffic = 'N/A';
|
||||
$maxTraffic = 'N/A';
|
||||
$traffic = '暂无';
|
||||
$maxTraffic = '暂无';
|
||||
foreach ($details as $d) {
|
||||
if ($d['label'] === '当前流量')
|
||||
$traffic = str_replace(' GB', '', $d['value']);
|
||||
$traffic = str_replace([' GB', ' MB', ' GB', ' MB'], '', $d['value']);
|
||||
if ($d['label'] === '设定阈值')
|
||||
$maxTraffic = str_replace('%', '', $d['value']);
|
||||
}
|
||||
@@ -322,10 +437,8 @@ class NotificationService
|
||||
}
|
||||
$finalUrl = strtr($url, $urlReplacePairs);
|
||||
|
||||
// If body template exists, replace vars and append it to URL query string if no URL vars were found.
|
||||
// Fallback for simple GET without body:
|
||||
// 读取请求没有请求体时,将默认参数拼到地址上。
|
||||
if (empty($bodyTemplate) && strpos($finalUrl, '?') === false && strpos($url, '#') === false) {
|
||||
// Default fallback if no variables exist in URL or body
|
||||
$payload = [
|
||||
'title' => $title,
|
||||
'text' => $text,
|
||||
@@ -335,7 +448,7 @@ class NotificationService
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_URL, $finalUrl);
|
||||
} else {
|
||||
// POST request
|
||||
// 发送请求
|
||||
$urlReplacePairs = [];
|
||||
foreach ($replacePairs as $k => $v) {
|
||||
$urlReplacePairs[$k] = urlencode((string) $v);
|
||||
@@ -348,7 +461,7 @@ class NotificationService
|
||||
$bodyReplacePairs = $replacePairs;
|
||||
if ($requestType === 'JSON') {
|
||||
foreach ($bodyReplacePairs as $k => $v) {
|
||||
// Safe JSON encoding for values injected into string literals
|
||||
// 数据格式安全转义,避免模板变量破坏 JSON 字符串。
|
||||
$bodyReplacePairs[$k] = substr(json_encode((string) $v, JSON_UNESCAPED_UNICODE), 1, -1);
|
||||
}
|
||||
} else if ($requestType === 'FORM') {
|
||||
@@ -363,14 +476,14 @@ class NotificationService
|
||||
$customHeaders[] = 'Content-Type: application/json';
|
||||
} else if ($requestType === 'FORM') {
|
||||
$customHeaders[] = 'Content-Type: application/x-www-form-urlencoded';
|
||||
// Test if user provided JSON instead of form data, attempt conversion
|
||||
// 用户误填 JSON 时,尽量转换为表单格式。
|
||||
$decoded = json_decode($finalBody, true);
|
||||
if (is_array($decoded)) {
|
||||
$finalBody = http_build_query($decoded);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback default payload if no body is configured
|
||||
// 未配置请求体时发送默认内容。
|
||||
$payload = ['title' => $title, 'text' => $text, 'time' => date('Y-m-d H:i:s')];
|
||||
if ($requestType === 'JSON') {
|
||||
$finalBody = json_encode($payload, JSON_UNESCAPED_UNICODE);
|
||||
@@ -396,9 +509,21 @@ class NotificationService
|
||||
curl_close($ch);
|
||||
|
||||
if ($error)
|
||||
return "Curl Error: " . $error;
|
||||
return "网络请求错误: " . $error;
|
||||
if ($httpCode >= 400)
|
||||
return "HTTP Error {$httpCode}: " . $result;
|
||||
return "接口返回错误 {$httpCode}: " . $result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private function formatTraffic($trafficGb)
|
||||
{
|
||||
$trafficGb = (float) $trafficGb;
|
||||
if ($trafficGb <= 0) {
|
||||
return '0 MB';
|
||||
}
|
||||
if ($trafficGb < 1) {
|
||||
return round($trafficGb * 1024, 2) . ' MB';
|
||||
}
|
||||
return round($trafficGb, 2) . ' GB';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user