format('Y-m-d H:i:s') . "\n"; if (!empty($context)) { $fullMessage .= "\nDetails:\n"; foreach ($context as $key => $value) { $fullMessage .= "- {$key}: {$value}\n"; } } $sender = "webmaster@{$hostname}"; Mail::raw($fullMessage, function ($mail) use ($recipientList, $sender, $subject, $hostname) { $mail->from($sender, "Jabali Panel ({$hostname})"); $mail->to($recipientList); $mail->subject("[Jabali] {$subject}"); }); Log::info("AdminNotification sent: {$type} - {$subject}"); self::logNotification($type, $subject, $message, $recipientList, 'sent', $context); return true; } catch (\Exception $e) { Log::error("AdminNotification failed: {$e->getMessage()}"); self::logNotification($type, $subject, $message, $recipientList, 'failed', $context, $e->getMessage()); return false; } } /** * Log a notification to the database. */ protected static function logNotification( string $type, string $subject, string $message, array $recipients, string $status, ?array $context = null, ?string $error = null ): void { try { NotificationLog::log($type, $subject, $message, $recipients, $status, $context, $error); } catch (\Exception $e) { // Don't let logging failures break the notification system Log::error("Failed to log notification: {$e->getMessage()}"); } } public static function sslError(string $domain, string $error): bool { return self::send( 'ssl_errors', "SSL Certificate Error: {$domain}", "An SSL certificate error occurred for domain: {$domain}", ['Domain' => $domain, 'Error' => $error] ); } public static function sslExpiring(string $domain, int $daysUntilExpiry): bool { return self::send( 'ssl_errors', "SSL Certificate Expiring: {$domain}", "The SSL certificate for {$domain} will expire in {$daysUntilExpiry} days.", ['Domain' => $domain, 'Days Until Expiry' => $daysUntilExpiry] ); } public static function backupFailure(string $backupName, string $error): bool { return self::send( 'backup_failures', "Backup Failed: {$backupName}", "A scheduled backup has failed.", ['Backup Name' => $backupName, 'Error' => $error] ); } public static function backupSuccess(string $backupName, int $sizeBytes, ?string $destination = null): bool { $size = self::formatBytes($sizeBytes); $context = [ 'Backup Name' => $backupName, 'Size' => $size, ]; if ($destination) { $context['Destination'] = $destination; } return self::send( 'backup_success', "Backup Completed: {$backupName}", "A backup has completed successfully.", $context ); } protected static function formatBytes(int $bytes, int $precision = 2): string { $units = ['B', 'KB', 'MB', 'GB', 'TB']; $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= pow(1024, $pow); return round($bytes, $precision) . ' ' . $units[$pow]; } public static function diskQuotaWarning(string $username, int $usagePercent): bool { return self::send( 'disk_quota', "Disk Quota Warning: {$username}", "User {$username} has reached {$usagePercent}% of their disk quota.", ['Username' => $username, 'Usage' => "{$usagePercent}%"] ); } public static function loginFailure(string $ip, string $service, int $attempts): bool { return self::send( 'login_failures', "Login Failure Alert: {$ip}", "Multiple failed login attempts detected.", ['IP Address' => $ip, 'Service' => $service, 'Attempts' => $attempts] ); } public static function systemUpdatesAvailable(int $updateCount): bool { return self::send( 'system_updates', "System Updates Available", "{$updateCount} system update(s) are available for your Jabali Panel.", ['Available Updates' => $updateCount] ); } public static function sshLogin(string $username, string $ip, string $method = 'password'): bool { return self::send( 'ssh_logins', "SSH Login: {$username}", "Successful SSH login detected.", ['Username' => $username, 'IP Address' => $ip, 'Auth Method' => $method] ); } }