fix: уведомления сервисов и исправление порогов

- Уведомления сервисов: разные цвета и тексты для остановки (🛑) и запуска ()
- Фикс порядка действий: SELECT статуса ДО UPDATE для корректного отслеживания изменений
- Фильтр алертов: только для сервисов в списке мониторинга
- NotificationService: разделение на sendThresholdNotification/sendRecoveryNotification/sendServiceNotification
- Исправление сохранения порогов (приведение типов для duration/warning/critical)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
mirivlad 2026-04-13 22:49:16 +08:00
parent c6e400ad32
commit d35ce3a022
3 changed files with 103 additions and 30 deletions

View File

@ -100,6 +100,12 @@ class MetricsController extends Model
continue;
}
// Сначала читаем старый статус (ДО обновления)
$stmtOld = $this->pdo->prepare("SELECT status FROM service_status WHERE server_id = :server_id AND service_name = :service_name");
$stmtOld->execute([':server_id' => $serverId, ':service_name' => $serviceName]);
$oldStatusRow = $stmtOld->fetch();
$oldStatus = $oldStatusRow ? $oldStatusRow['status'] : null;
// Обновляем статус сервиса (INSERT OR UPDATE)
$stmt = $this->pdo->prepare("
INSERT INTO service_status (server_id, service_name, status, load_state, active_state, sub_state, updated_at)
@ -121,9 +127,19 @@ class MetricsController extends Model
':sub_state' => $subState
]);
// Если сервис остановлен - создаем алерт
if ($serviceStatus === 'stopped') {
$this->createServiceAlert($serverId, $serviceName, $serviceStatus, $serverName);
// Если статус изменился И сервис в мониторинге - шлем алерт
if ($oldStatus !== null && $oldStatus !== $serviceStatus) {
$stmtMon = $this->pdo->prepare("SELECT monitor_services FROM agent_configs WHERE server_id = :server_id");
$stmtMon->execute([':server_id' => $serverId]);
$monConfig = $stmtMon->fetch();
$monitoredServices = [];
if ($monConfig && !empty($monConfig['monitor_services'])) {
$monitoredServices = json_decode($monConfig['monitor_services'], true) ?? [];
}
if (in_array($serviceName, $monitoredServices)) {
$this->notificationService->sendServiceNotification($serverName, $serviceName, $serviceStatus);
$this->createServiceAlert($serverId, $serviceName, $serviceStatus, $serverName);
}
}
}
@ -228,7 +244,7 @@ class MetricsController extends Model
':severity' => $severity
]);
$this->notificationService->sendAlertNotification(
$this->notificationService->sendThresholdNotification(
$serverName,
$metricName,
$value,
@ -270,12 +286,10 @@ class MetricsController extends Model
");
$stmt->execute([':id' => $existingAlert['id']]);
$this->notificationService->sendAlertNotification(
$this->notificationService->sendRecoveryNotification(
$serverName,
$metricName,
$value,
'resolved',
'Порог более не превышен'
$value
);
}
}

View File

@ -300,14 +300,14 @@ class ServerDetailController extends Model
foreach ($metricTypes as $metricType) {
$warning = $params[$metricType['name'] . '_warning'] ?? '';
$critical = $params[$metricType['name'] . '_critical'] ?? '';
$duration = $params[$metricType['name'] . '_duration'] ?? 0;
$duration = (int)($params[$metricType['name'] . '_duration'] ?? 0);
if ($warning !== '' && $critical !== '') {
$insertStmt->execute([
':server_id' => $id,
':metric_name_id' => $metricType['id'],
':warning_threshold' => $warning,
':critical_threshold' => $critical,
':warning_threshold' => (float)$warning,
':critical_threshold' => (float)$critical,
':duration' => $duration
]);
}

View File

@ -24,31 +24,77 @@ class NotificationService
}
/**
* Отправить уведомление о алерте
* Отправить уведомление о превышении порога
*/
public function sendAlertNotification($serverName, $metricName, $value, $severity, $threshold)
public function sendThresholdNotification($serverName, $metricName, $value, $severity, $threshold)
{
if ($severity === 'resolved') {
$severityText = 'ВОССТАНОВЛЕНИЕ';
$emoji = '✅';
$subject = "{$emoji} {$severityText}: {$metricName} в норме";
$message = "Сервер: {$serverName}\n";
$message .= "Метрика: {$metricName}\n";
$message .= "Текущее значение: {$value}\n";
$message .= "Статус: Порог более не превышен\n";
$message .= "Время: " . date('d.m.Y H:i:s');
if ($severity === 'warning') {
$severityText = 'ПРЕДУПРЕЖДЕНИЕ';
$emoji = '⚠️';
} else {
$severityText = $severity === 'critical' ? 'КРИТИЧЕСКИЙ' : 'ПРЕДУПРЕЖДЕНИЕ';
$severityText = 'КРИТИЧЕСКИЙ';
$emoji = '🚨';
$subject = "{$emoji} {$severityText}: Превышение порога {$metricName}";
$message = "Сервер: {$serverName}\n";
$message .= "Метрика: {$metricName}\n";
$message .= "Значение: {$value}\n";
$message .= "Порог: {$threshold}\n";
$message .= "Время: " . date('d.m.Y H:i:s') . "\n";
$message .= "Серьёзность: {$severityText}";
}
$subject = "{$emoji} {$severityText}: {$metricName}";
$message = "🖥 Сервер: {$serverName}\n";
$message .= "📊 Метрика: {$metricName}\n";
$message .= "📈 Значение: {$value}\n";
$message .= "📏 Порог: {$threshold}\n";
$message .= "🕒 Время: " . date('d.m.Y H:i:s') . "\n";
$message .= "🏷 Серьёзность: {$severityText}";
$this->sendNotification($subject, $message);
}
/**
* Отправить уведомление о восстановлении (порог в норме)
*/
public function sendRecoveryNotification($serverName, $metricName, $value)
{
$subject = "✅ Восстановление: {$metricName} в норме";
$message = "🖥 Сервер: {$serverName}\n";
$message .= "📊 Метрика: {$metricName}\n";
$message .= "📈 Текущее значение: {$value}\n";
$message .= "🟢 Статус: Порог более не превышен\n";
$message .= "🕒 Время: " . date('d.m.Y H:i:s');
$this->sendNotification($subject, $message);
}
/**
* Отправить уведомление о сервисе (остановка/запуск)
*/
public function sendServiceNotification($serverName, $serviceName, $action)
{
// $action: 'stopped' или 'running'
if ($action === 'running') {
$emoji = '✅';
$actionText = 'Запуск сервиса';
$descText = "Сервис {$serviceName} запущен";
$severityText = 'ВОССТАНОВЛЕНИЕ';
} else {
$emoji = '🛑';
$actionText = 'Остановка сервиса';
$descText = "Сервис {$serviceName} остановлен";
$severityText = 'ОСТАНОВЛЕН';
}
$subject = "{$emoji} {$actionText}: {$serviceName}";
$message = "🖥 Сервер: {$serverName}\n";
$message .= "⚙️ Сервис: {$serviceName}\n";
$message .= "📝 Действие: {$descText}\n";
$message .= "🏷 Статус: {$severityText}\n";
$message .= "🕒 Время: " . date('d.m.Y H:i:s');
$this->sendNotification($subject, $message);
}
/**
* Внутренний метод отправки (Email + Telegram)
*/
private function sendNotification($subject, $message)
{
// Отправка Email
if (!empty($this->settings['email_enabled']) && !empty($this->settings['smtp_host'])) {
$this->sendEmail($subject, $message);
@ -60,6 +106,19 @@ class NotificationService
}
}
/**
* Отправить уведомление о алерте (для обратной совместимости)
* @deprecated Используйте sendThresholdNotification или sendServiceNotification
*/
public function sendAlertNotification($serverName, $metricName, $value, $severity, $threshold)
{
if ($severity === 'resolved') {
$this->sendRecoveryNotification($serverName, $metricName, $value);
} else {
$this->sendThresholdNotification($serverName, $metricName, $value, $severity, $threshold);
}
}
/**
* Отправить тестовое уведомление
*/