Files
ecs-controller/NotificationService.php
2026-04-20 20:46:07 +08:00

584 lines
26 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
use PHPMailer\PHPMailer\PHPMailer;
class NotificationService
{
private $config;
public function setConfig($config)
{
$this->config = $config;
}
/**
* 发送定时任务通知
* @return bool|string 成功返回 true失败返回错误信息
*/
public function notifySchedule($actionType, $account, $description = "")
{
$title = "定时任务: " . $actionType;
$maskedKey = substr($account['access_key_id'], 0, 7) . '***';
$traffic = isset($account['traffic_used']) ? $this->formatTraffic((float) $account['traffic_used']) : '暂无';
$threshold = $this->config['traffic_threshold'] ?? 95;
$details = [
['label' => '账号', 'value' => $maskedKey],
['label' => '执行动作', 'value' => $actionType, 'highlight' => true],
['label' => '执行时间', 'value' => date('Y-m-d H:i:s')],
['label' => '当前流量', 'value' => $traffic],
['label' => '设定阈值', 'value' => $threshold . '%'],
['label' => '详情说明', 'value' => $description ?: '根据预设时间表自动执行。']
];
$textMsg = "【ECS 服务器管家】{$title}\n" .
"账号: {$maskedKey}\n" .
"执行动作: {$actionType}\n" .
"当前流量: {$traffic}\n" .
"设定阈值: {$threshold}%\n" .
"执行时间: " . date('Y-m-d H:i:s') . "\n" .
"详情说明: " . ($description ?: '根据预设时间表自动执行。');
return $this->dispatchNotifications($title, "您的实例已执行{$actionType}操作", $details, 'info', $textMsg, $account['access_key_id']);
}
/**
* 发送流量告警
* @return bool|string
*/
public function sendTrafficWarning($accessKeyId, $traffic, $percentage, $statusText, $threshold)
{
$title = "流量告警 - " . $statusText;
$trafficText = $this->formatTraffic((float) $traffic);
$details = [
['label' => '账号', 'value' => substr($accessKeyId, 0, 7) . '***'],
['label' => '当前流量', 'value' => $trafficText],
['label' => '使用率', 'value' => $percentage . '%', 'highlight' => true],
['label' => '设定阈值', 'value' => $threshold . '%'],
['label' => '当前状态', 'value' => $statusText]
];
$textMsg = "【ECS 服务器管家】{$title}\n" .
"账号: " . substr($accessKeyId, 0, 7) . '***' . "\n" .
"当前流量: {$trafficText}\n" .
"使用率: {$percentage}%\n" .
"设定阈值: {$threshold}%\n" .
"当前状态: {$statusText}";
return $this->dispatchNotifications($title, "检测到流量异常或达到阈值", $details, 'warning', $textMsg, $accessKeyId);
}
public function notifyCredentialInvalid($accessKeyId, $traffic, $percentage, $threshold)
{
$title = "流量告警 - 账号密钥已失效";
$trafficText = $this->formatTraffic((float) $traffic);
$maskedAccount = substr($accessKeyId, 0, 7) . '***';
$statusText = '检测到 AK 已失效,已暂停自动停机';
$details = [
['label' => '账号', 'value' => $maskedAccount],
['label' => '当前流量', 'value' => $trafficText],
['label' => '使用率', 'value' => $percentage . '%', 'highlight' => true],
['label' => '设定阈值', 'value' => $threshold . '%'],
['label' => '当前状态', 'value' => $statusText],
['label' => '处理建议', 'value' => '请更新 AK 后再恢复自动停机保护。']
];
$textMsg = "【ECS 服务器管家】{$title}\n" .
"账号: {$maskedAccount}\n" .
"当前流量: {$trafficText}\n" .
"使用率: {$percentage}%\n" .
"设定阈值: {$threshold}%\n" .
"当前状态: {$statusText}\n" .
"处理建议: 请更新 AK 后再恢复自动停机保护。";
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);
}
public function notifyPublicIpChanged($accountLabel, array $account, $oldIp, $newIp, $reason = '')
{
$instanceName = $account['instance_name'] ?? ($account['remark'] ?? '');
$instanceId = $account['instance_id'] ?? '';
$region = $account['region_id'] ?? '';
$title = '公网 IP 已更换';
$details = [
['label' => '账号', 'value' => $accountLabel],
['label' => '实例名称', 'value' => $instanceName ?: '-'],
['label' => '实例编号', 'value' => $instanceId ?: '-', 'highlight' => true],
['label' => '区域', 'value' => $region ?: '-'],
['label' => '原公网 IP', 'value' => $oldIp ?: '-'],
['label' => '新公网 IP', 'value' => $newIp ?: '-', 'highlight' => true],
['label' => '变更时间', 'value' => date('Y-m-d H:i:s')],
['label' => '说明', 'value' => $reason ?: '系统已更换托管 EIP并同步更新 DDNS 解析。']
];
$textMsg = "【ECS 服务器管家】公网 IP 已更换\n" .
"账号: {$accountLabel}\n" .
"实例名称: " . ($instanceName ?: '-') . "\n" .
"实例编号: " . ($instanceId ?: '-') . "\n" .
"区域: " . ($region ?: '-') . "\n" .
"原公网 IP: " . ($oldIp ?: '-') . "\n" .
"新公网 IP: " . ($newIp ?: '-') . "\n" .
"变更时间: " . date('Y-m-d H:i:s') . "\n" .
"说明: " . ($reason ?: '系统已更换托管 EIP并同步更新 DDNS 解析。');
return $this->dispatchNotifications($title, '公网 IP 已成功更换DDNS 解析已同步更新。', $details, 'success', $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' => '成功'],
['label' => '发送时间', 'value' => date('Y-m-d H:i:s')],
['label' => '服务器', 'value' => $_SERVER['SERVER_NAME'] ?? 'localhost']
];
$html = $this->renderEmailTemplate("测试邮件", "邮件服务配置验证成功", $details, 'success');
return $this->sendMail($to, '管理员', 'ECS 服务器管家测试邮件', $html);
}
public function sendTestTelegram($data)
{
$textMsg = "【ECS 服务器管理】测试推送\n这是一条来自 Telegram 的测试消息。\n发送时间: " . date('Y-m-d H:i:s');
return $this->sendTelegram($textMsg, $data);
}
public function sendTestWebhook($data)
{
$textMsg = "【ECS 服务器管家】测试推送\n这是一条来自接口回调的测试消息。\n发送时间: " . date('Y-m-d H:i:s');
$summary = "这是一条来自接口回调的测试消息。";
$threshold = $this->config['traffic_threshold'] ?? 95;
$details = [
['label' => '当前流量', 'value' => '0 MB'],
['label' => '设定阈值', 'value' => $threshold . '%']
];
return $this->sendWebhook($textMsg, "测试推送", $summary, $details, 'test_account_id', $data);
}
private function dispatchNotifications($title, $summary, $details, $type, $textMsg, $accountId = '')
{
$errors = [];
$successCount = 0;
$attemptCount = 0;
// 邮件通知
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'], '', "ECS 服务器管家通知 - " . $title, $html);
if ($res === true)
$successCount++;
else
$errors[] = "邮件通知: " . $res;
}
// Telegram
if (($this->config['notify_tg_enabled'] ?? '0') === '1' && !empty($this->config['notify_tg_token']) && !empty($this->config['notify_tg_chat_id'])) {
$attemptCount++;
$res = $this->sendTelegram($textMsg);
if ($res === true)
$successCount++;
else
$errors[] = "Telegram: " . $res;
}
// 接口回调
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[] = "接口回调: " . $res;
}
if ($attemptCount == 0)
return true;
if ($successCount == 0 && count($errors) > 0) {
return implode(" | ", $errors);
} else if (count($errors) > 0) {
return "部分完成: " . implode(" | ", $errors);
}
return true;
}
private function renderEmailTemplate($title, $summary, $details, $type = 'info')
{
$color = '#007AFF';
if ($type === 'warning')
$color = '#FF3B30';
if ($type === 'success')
$color = '#34C759';
$rows = '';
foreach ($details as $item) {
$valColor = isset($item['highlight']) && $item['highlight'] ? $color : '#1C1C1E';
$rows .= "
<tr style='border-bottom: 1px solid #F2F2F7;'>
<td style='padding: 12px 0; color: #8E8E93; font-size: 14px; width: 40%;'>{$item['label']}</td>
<td style='padding: 12px 0; color: {$valColor}; font-size: 14px; font-weight: 600; text-align: right;'>{$item['value']}</td>
</tr>";
}
return "
<!DOCTYPE html>
<html>
<head><meta charset='utf-8'></head>
<body style='margin: 0; padding: 0; background-color: #F2F2F7; font-family: sans-serif;'>
<table width='100%' border='0' cellspacing='0' cellpadding='0'>
<tr><td align='center' style='padding: 40px 20px;'>
<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; 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;'>&copy; " . date('Y') . " ECS 服务器管家</td></tr>
</table>
</td></tr>
</table>
</body></html>";
}
private function sendMail($to, $name, $subject, $body)
{
$mail = new PHPMailer();
$mail->CharSet = 'UTF-8';
$mail->IsSMTP();
$mail->SMTPDebug = 0;
$mail->SMTPAuth = true;
$secure = $this->config['notify_secure'] ?? 'ssl';
if (!empty($secure)) {
$mail->SMTPSecure = $secure;
} else {
$mail->SMTPSecure = '';
$mail->SMTPAutoTLS = false;
}
$mail->Host = $this->config['notify_host'] ?? '';
$mail->Port = $this->config['notify_port'] ?? 465;
$mail->Username = $this->config['notify_username'] ?? '';
$mail->Password = $this->config['notify_password'] ?? '';
$mail->SetFrom($mail->Username, 'ECS 服务器管家');
$mail->Subject = $subject;
$mail->MsgHTML($body);
$mail->AddAddress($to, $name);
// 修改:返回 true 或 错误信息字符串
if ($mail->Send()) {
return true;
} else {
return $mail->ErrorInfo;
}
}
private function sendTelegram($text, $overrideConfig = null)
{
$token = $overrideConfig['token'] ?? $this->config['notify_tg_token'] ?? '';
$chatId = $overrideConfig['chat_id'] ?? $this->config['notify_tg_chat_id'] ?? '';
$proxyType = $overrideConfig['proxy_type'] ?? $this->config['notify_tg_proxy_type'] ?? 'none';
if (empty($token) || empty($chatId))
return "Telegram 的机器人令牌或接收会话编号为空";
$url = "https://api.telegram.org/bot{$token}/sendMessage";
if ($proxyType === 'custom' && !empty($overrideConfig['proxy_url'] ?? $this->config['notify_tg_proxy_url'] ?? '')) {
$baseUrl = rtrim($overrideConfig['proxy_url'] ?? $this->config['notify_tg_proxy_url'], '/');
$url = "{$baseUrl}/bot{$token}/sendMessage";
}
$postFields = [
'chat_id' => $chatId,
'text' => $text
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postFields));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
if ($proxyType === 'socks5') {
$proxyIp = $overrideConfig['proxy_ip'] ?? $this->config['notify_tg_proxy_ip'] ?? '';
$proxyPort = $overrideConfig['proxy_port'] ?? $this->config['notify_tg_proxy_port'] ?? '';
if ($proxyIp && $proxyPort) {
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
curl_setopt($ch, CURLOPT_PROXY, "{$proxyIp}:{$proxyPort}");
$proxyUser = $overrideConfig['proxy_user'] ?? $this->config['notify_tg_proxy_user'] ?? '';
$proxyPass = $overrideConfig['proxy_pass'] ?? $this->config['notify_tg_proxy_pass'] ?? '';
if ($proxyUser || $proxyPass) {
curl_setopt($ch, CURLOPT_PROXYUSERPWD, "{$proxyUser}:{$proxyPass}");
}
}
}
$result = curl_exec($ch);
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($error)
return "网络请求错误: " . $error;
if ($httpCode != 200)
return "接口返回错误 {$httpCode}: " . $result;
return true;
}
private function sendWebhook($text, $title, $summary, $details, $accountId = '', $overrideConfig = null)
{
$url = $overrideConfig['url'] ?? $this->config['notify_wh_url'] ?? '';
$method = strtoupper($overrideConfig['method'] ?? $this->config['notify_wh_method'] ?? 'GET');
$requestType = strtoupper($overrideConfig['request_type'] ?? $this->config['notify_wh_request_type'] ?? 'JSON');
$headersStr = $overrideConfig['headers'] ?? $this->config['notify_wh_headers'] ?? '';
$bodyTemplate = $overrideConfig['body'] ?? $this->config['notify_wh_body'] ?? '';
if (empty($url))
return "接口回调地址为空";
// Parse variables
$traffic = '暂无';
$maxTraffic = '暂无';
foreach ($details as $d) {
if ($d['label'] === '当前流量')
$traffic = str_replace([' GB', ' MB', ' GB', ' MB'], '', $d['value']);
if ($d['label'] === '设定阈值')
$maxTraffic = str_replace('%', '', $d['value']);
}
$replacePairs = [
'#TITLE#' => $title,
'#MSG#' => $summary ?: $text,
'#ACCOUNT#' => $accountId,
'#TRAFFIC#' => $traffic,
'#MAX_TRAFFIC#' => $maxTraffic
];
$ch = curl_init();
$customHeaders = [];
// Parse custom headers
if (!empty($headersStr)) {
$parsedHeaders = json_decode($headersStr, true);
if (is_array($parsedHeaders)) {
foreach ($parsedHeaders as $k => $v) {
$customHeaders[] = "{$k}: {$v}";
}
}
}
if ($method === 'GET') {
$urlReplacePairs = [];
foreach ($replacePairs as $k => $v) {
$urlReplacePairs[$k] = urlencode((string) $v);
}
$finalUrl = strtr($url, $urlReplacePairs);
// 读取请求没有请求体时,将默认参数拼到地址上。
if (empty($bodyTemplate) && strpos($finalUrl, '?') === false && strpos($url, '#') === false) {
$payload = [
'title' => $title,
'text' => $text,
'time' => date('Y-m-d H:i:s')
];
$finalUrl .= '?' . http_build_query($payload);
}
curl_setopt($ch, CURLOPT_URL, $finalUrl);
} else {
// 发送请求
$urlReplacePairs = [];
foreach ($replacePairs as $k => $v) {
$urlReplacePairs[$k] = urlencode((string) $v);
}
curl_setopt($ch, CURLOPT_URL, strtr($url, $urlReplacePairs));
curl_setopt($ch, CURLOPT_POST, true);
$finalBody = '';
if (!empty($bodyTemplate)) {
$bodyReplacePairs = $replacePairs;
if ($requestType === 'JSON') {
foreach ($bodyReplacePairs as $k => $v) {
// 数据格式安全转义,避免模板变量破坏 JSON 字符串。
$bodyReplacePairs[$k] = substr(json_encode((string) $v, JSON_UNESCAPED_UNICODE), 1, -1);
}
} else if ($requestType === 'FORM') {
foreach ($bodyReplacePairs as $k => $v) {
$bodyReplacePairs[$k] = urlencode((string) $v);
}
}
$finalBody = strtr($bodyTemplate, $bodyReplacePairs);
// Content Type
if ($requestType === 'JSON') {
$customHeaders[] = 'Content-Type: application/json';
} else if ($requestType === 'FORM') {
$customHeaders[] = 'Content-Type: application/x-www-form-urlencoded';
// 用户误填 JSON 时,尽量转换为表单格式。
$decoded = json_decode($finalBody, true);
if (is_array($decoded)) {
$finalBody = http_build_query($decoded);
}
}
} else {
// 未配置请求体时发送默认内容。
$payload = ['title' => $title, 'text' => $text, 'time' => date('Y-m-d H:i:s')];
if ($requestType === 'JSON') {
$finalBody = json_encode($payload, JSON_UNESCAPED_UNICODE);
$customHeaders[] = 'Content-Type: application/json';
} else {
$finalBody = http_build_query($payload);
$customHeaders[] = 'Content-Type: application/x-www-form-urlencoded';
}
}
curl_setopt($ch, CURLOPT_POSTFIELDS, $finalBody);
}
if (!empty($customHeaders)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, array_unique($customHeaders));
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$result = curl_exec($ch);
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($error)
return "网络请求错误: " . $error;
if ($httpCode >= 400)
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';
}
}