mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-17 22:14:14 +00:00
feat(proxy): add Traefik version tracking with notifications and dismissible UI warnings
- Add automated Traefik version checking job running weekly on Sundays - Implement version detection from running containers and comparison with versions.json - Add notifications across all channels (Email, Discord, Slack, Telegram, Pushover, Webhook) for outdated versions - Create dismissible callout component with localStorage persistence - Display cross-branch upgrade warnings (e.g., v3.5 -> v3.6) with changelog links - Show patch update notifications within same branch - Add warning icon that appears when callouts are dismissed - Prevent duplicate notifications during proxy restart by adding restarting parameter - Fix notification spam with transition-based logic for status changes - Enable system email settings by default in development mode - Track last saved/applied proxy settings to detect configuration drift
This commit is contained in:
1 parent
a4d07ab050
commit
0bd4ffb2d7
48 files changed
+1488
-48
No files matched your search
@@ -13,7 +13,7 @@ class StartProxy
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Server $server, bool $async = true, bool $force = false): string|Activity
|
||||
public function handle(Server $server, bool $async = true, bool $force = false, bool $restarting = false): string|Activity
|
||||
{
|
||||
$proxyType = $server->proxyType();
|
||||
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
|
||||
@@ -22,7 +22,10 @@ class StartProxy
|
||||
$server->proxy->set('status', 'starting');
|
||||
$server->save();
|
||||
$server->refresh();
|
||||
ProxyStatusChangedUI::dispatch($server->team_id);
|
||||
|
||||
if (! $restarting) {
|
||||
ProxyStatusChangedUI::dispatch($server->team_id);
|
||||
}
|
||||
|
||||
$commands = collect([]);
|
||||
$proxy_path = $server->proxyPath();
|
||||
|
||||
@@ -12,13 +12,16 @@ class StopProxy
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Server $server, bool $forceStop = true, int $timeout = 30)
|
||||
public function handle(Server $server, bool $forceStop = true, int $timeout = 30, bool $restarting = false)
|
||||
{
|
||||
try {
|
||||
$containerName = $server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
|
||||
$server->proxy->status = 'stopping';
|
||||
$server->save();
|
||||
ProxyStatusChangedUI::dispatch($server->team_id);
|
||||
|
||||
if (! $restarting) {
|
||||
ProxyStatusChangedUI::dispatch($server->team_id);
|
||||
}
|
||||
|
||||
instant_remote_process(command: [
|
||||
"docker stop --time=$timeout $containerName",
|
||||
@@ -32,7 +35,10 @@ class StopProxy
|
||||
return handleError($e);
|
||||
} finally {
|
||||
ProxyDashboardCacheService::clearCache($server);
|
||||
ProxyStatusChanged::dispatch($server->id);
|
||||
|
||||
if (! $restarting) {
|
||||
ProxyStatusChanged::dispatch($server->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\CheckTraefikVersionJob;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CheckTraefikVersionCommand extends Command
|
||||
{
|
||||
protected $signature = 'traefik:check-version';
|
||||
|
||||
protected $description = 'Check Traefik proxy versions on all servers and send notifications for outdated versions';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$this->info('Checking Traefik versions on all servers...');
|
||||
|
||||
try {
|
||||
CheckTraefikVersionJob::dispatch();
|
||||
$this->info('Traefik version check job dispatched successfully.');
|
||||
$this->info('Notifications will be sent to teams with outdated Traefik versions.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
} catch (\Exception $e) {
|
||||
$this->error('Failed to dispatch Traefik version check job: '.$e->getMessage());
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace App\Console;
|
||||
use App\Jobs\CheckAndStartSentinelJob;
|
||||
use App\Jobs\CheckForUpdatesJob;
|
||||
use App\Jobs\CheckHelperImageJob;
|
||||
use App\Jobs\CheckTraefikVersionJob;
|
||||
use App\Jobs\CleanupInstanceStuffsJob;
|
||||
use App\Jobs\PullChangelog;
|
||||
use App\Jobs\PullTemplatesFromCDN;
|
||||
@@ -83,6 +84,8 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
$this->scheduleInstance->job(new RegenerateSslCertJob)->twiceDaily();
|
||||
|
||||
$this->scheduleInstance->job(new CheckTraefikVersionJob)->weekly()->sundays()->at('00:00')->timezone($this->instanceTimezone)->onOneServer();
|
||||
|
||||
$this->scheduleInstance->command('cleanup:database --yes')->daily();
|
||||
$this->scheduleInstance->command('uploads:clear')->everyTwoMinutes();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ class ServerMetadata extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public ?ProxyTypes $type,
|
||||
public ?ProxyStatus $status
|
||||
public ?ProxyStatus $status,
|
||||
public ?string $last_saved_settings = null,
|
||||
public ?string $last_applied_settings = null
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Server\TraefikVersionOutdated;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CheckTraefikVersionJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $tries = 3;
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
Log::info('CheckTraefikVersionJob: Starting Traefik version check');
|
||||
|
||||
// Load versions from versions.json
|
||||
$versionsPath = base_path('versions.json');
|
||||
if (! File::exists($versionsPath)) {
|
||||
Log::warning('CheckTraefikVersionJob: versions.json not found, skipping check');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$allVersions = json_decode(File::get($versionsPath), true);
|
||||
$traefikVersions = data_get($allVersions, 'traefik');
|
||||
|
||||
if (empty($traefikVersions) || ! is_array($traefikVersions)) {
|
||||
Log::warning('CheckTraefikVersionJob: Traefik versions not found or invalid in versions.json');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$branches = array_keys($traefikVersions);
|
||||
Log::info('CheckTraefikVersionJob: Loaded Traefik version branches', ['branches' => $branches]);
|
||||
|
||||
// Query all servers with Traefik proxy that are reachable
|
||||
$servers = Server::whereNotNull('proxy')
|
||||
->whereRelation('settings', 'is_reachable', true)
|
||||
->whereRelation('settings', 'is_usable', true)
|
||||
->get()
|
||||
->filter(fn ($server) => $server->proxyType() === ProxyTypes::TRAEFIK->value);
|
||||
|
||||
$serverCount = $servers->count();
|
||||
Log::info("CheckTraefikVersionJob: Found {$serverCount} server(s) with Traefik proxy");
|
||||
|
||||
if ($serverCount === 0) {
|
||||
Log::info('CheckTraefikVersionJob: No Traefik servers found, job completed');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$outdatedServers = collect();
|
||||
|
||||
// Phase 1: Scan servers and detect versions
|
||||
Log::info('CheckTraefikVersionJob: Phase 1 - Scanning servers and detecting versions');
|
||||
|
||||
foreach ($servers as $server) {
|
||||
$currentVersion = getTraefikVersionFromDockerCompose($server);
|
||||
|
||||
Log::info("CheckTraefikVersionJob: Server '{$server->name}' - Detected version: ".($currentVersion ?? 'unable to detect'));
|
||||
|
||||
// Update detected version in database
|
||||
$server->update(['detected_traefik_version' => $currentVersion]);
|
||||
|
||||
if (! $currentVersion) {
|
||||
Log::warning("CheckTraefikVersionJob: Server '{$server->name}' - Unable to detect version, skipping");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if image tag is 'latest' by inspecting the image
|
||||
$imageTag = instant_remote_process([
|
||||
"docker inspect coolify-proxy --format '{{.Config.Image}}' 2>/dev/null",
|
||||
], $server, false);
|
||||
|
||||
if (str_contains(strtolower(trim($imageTag)), ':latest')) {
|
||||
Log::info("CheckTraefikVersionJob: Server '{$server->name}' uses 'latest' tag, skipping notification (UI warning only)");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse current version to extract major.minor.patch
|
||||
$current = ltrim($currentVersion, 'v');
|
||||
if (! preg_match('/^(\d+\.\d+)\.(\d+)$/', $current, $matches)) {
|
||||
Log::warning("CheckTraefikVersionJob: Server '{$server->name}' - Invalid version format '{$current}', skipping");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$currentBranch = $matches[1]; // e.g., "3.6"
|
||||
$currentPatch = $matches[2]; // e.g., "0"
|
||||
|
||||
Log::debug("CheckTraefikVersionJob: Server '{$server->name}' - Parsed branch: {$currentBranch}, patch: {$currentPatch}");
|
||||
|
||||
// Find the latest version for this branch
|
||||
$latestForBranch = $traefikVersions["v{$currentBranch}"] ?? null;
|
||||
|
||||
if (! $latestForBranch) {
|
||||
// User is on a branch we don't track - check if newer branches exist
|
||||
Log::debug("CheckTraefikVersionJob: Server '{$server->name}' - Branch v{$currentBranch} not tracked, checking for newer branches");
|
||||
|
||||
$newestBranch = null;
|
||||
$newestVersion = null;
|
||||
|
||||
foreach ($traefikVersions as $branch => $version) {
|
||||
$branchNum = ltrim($branch, 'v');
|
||||
if (version_compare($branchNum, $currentBranch, '>')) {
|
||||
if (! $newestVersion || version_compare($version, $newestVersion, '>')) {
|
||||
$newestBranch = $branchNum;
|
||||
$newestVersion = $version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($newestVersion) {
|
||||
Log::info("CheckTraefikVersionJob: Server '{$server->name}' is outdated - on {$current}, newer branch {$newestBranch} with version {$newestVersion} available");
|
||||
$server->outdatedInfo = [
|
||||
'current' => $current,
|
||||
'latest' => $newestVersion,
|
||||
'type' => 'minor_upgrade',
|
||||
];
|
||||
$outdatedServers->push($server);
|
||||
} else {
|
||||
Log::info("CheckTraefikVersionJob: Server '{$server->name}' on {$current} - no newer branches available");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compare patch version within the same branch
|
||||
$latest = ltrim($latestForBranch, 'v');
|
||||
|
||||
if (version_compare($current, $latest, '<')) {
|
||||
Log::info("CheckTraefikVersionJob: Server '{$server->name}' is outdated - current: {$current}, latest for branch: {$latest}");
|
||||
$server->outdatedInfo = [
|
||||
'current' => $current,
|
||||
'latest' => $latest,
|
||||
'type' => 'patch_update',
|
||||
];
|
||||
$outdatedServers->push($server);
|
||||
} else {
|
||||
// Check if newer branches exist (user is up to date on their branch, but branch might be old)
|
||||
$newestBranch = null;
|
||||
$newestVersion = null;
|
||||
|
||||
foreach ($traefikVersions as $branch => $version) {
|
||||
$branchNum = ltrim($branch, 'v');
|
||||
if (version_compare($branchNum, $currentBranch, '>')) {
|
||||
if (! $newestVersion || version_compare($version, $newestVersion, '>')) {
|
||||
$newestBranch = $branchNum;
|
||||
$newestVersion = $version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($newestVersion) {
|
||||
Log::info("CheckTraefikVersionJob: Server '{$server->name}' up to date on branch {$currentBranch} ({$current}), but newer branch {$newestBranch} available ({$newestVersion})");
|
||||
$server->outdatedInfo = [
|
||||
'current' => $current,
|
||||
'latest' => $newestVersion,
|
||||
'type' => 'minor_upgrade',
|
||||
];
|
||||
$outdatedServers->push($server);
|
||||
} else {
|
||||
Log::info("CheckTraefikVersionJob: Server '{$server->name}' is fully up to date - version: {$current}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$outdatedCount = $outdatedServers->count();
|
||||
Log::info("CheckTraefikVersionJob: Phase 1 complete - Found {$outdatedCount} outdated server(s)");
|
||||
|
||||
if ($outdatedCount === 0) {
|
||||
Log::info('CheckTraefikVersionJob: All servers are up to date, no notifications to send');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Phase 2: Group by team and send notifications
|
||||
Log::info('CheckTraefikVersionJob: Phase 2 - Grouping by team and sending notifications');
|
||||
|
||||
$serversByTeam = $outdatedServers->groupBy('team_id');
|
||||
$teamCount = $serversByTeam->count();
|
||||
|
||||
Log::info("CheckTraefikVersionJob: Grouped outdated servers into {$teamCount} team(s)");
|
||||
|
||||
foreach ($serversByTeam as $teamId => $teamServers) {
|
||||
$team = Team::find($teamId);
|
||||
if (! $team) {
|
||||
Log::warning("CheckTraefikVersionJob: Team ID {$teamId} not found, skipping");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$serverNames = $teamServers->pluck('name')->join(', ');
|
||||
Log::info("CheckTraefikVersionJob: Sending notification to team '{$team->name}' for {$teamServers->count()} server(s): {$serverNames}");
|
||||
|
||||
// Send one notification per team with all outdated servers (with per-server info)
|
||||
$team->notify(new TraefikVersionOutdated($teamServers));
|
||||
|
||||
Log::info("CheckTraefikVersionJob: Notification sent to team '{$team->name}'");
|
||||
}
|
||||
|
||||
Log::info('CheckTraefikVersionJob: Job completed successfully');
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('CheckTraefikVersionJob: Error checking Traefik versions: '.$e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,12 +31,12 @@ class RestartProxyJob implements ShouldBeEncrypted, ShouldQueue
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
StopProxy::run($this->server);
|
||||
StopProxy::run($this->server, restarting: true);
|
||||
|
||||
$this->server->proxy->force_stop = false;
|
||||
$this->server->save();
|
||||
|
||||
StartProxy::run($this->server, force: true);
|
||||
StartProxy::run($this->server, force: true, restarting: true);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e);
|
||||
|
||||
@@ -347,6 +347,8 @@ class Index extends Component
|
||||
}
|
||||
$this->createdServer->proxy->type = $proxyType;
|
||||
$this->createdServer->proxy->status = 'exited';
|
||||
$this->createdServer->proxy->last_saved_settings = null;
|
||||
$this->createdServer->proxy->last_applied_settings = null;
|
||||
$this->createdServer->save();
|
||||
$this->getProjects();
|
||||
}
|
||||
|
||||
@@ -62,6 +62,9 @@ class Discord extends Component
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverPatchDiscordNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $traefikOutdatedDiscordNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $discordPingEnabled = true;
|
||||
|
||||
@@ -98,6 +101,7 @@ class Discord extends Component
|
||||
$this->settings->server_reachable_discord_notifications = $this->serverReachableDiscordNotifications;
|
||||
$this->settings->server_unreachable_discord_notifications = $this->serverUnreachableDiscordNotifications;
|
||||
$this->settings->server_patch_discord_notifications = $this->serverPatchDiscordNotifications;
|
||||
$this->settings->traefik_outdated_discord_notifications = $this->traefikOutdatedDiscordNotifications;
|
||||
|
||||
$this->settings->discord_ping_enabled = $this->discordPingEnabled;
|
||||
|
||||
@@ -120,6 +124,7 @@ class Discord extends Component
|
||||
$this->serverReachableDiscordNotifications = $this->settings->server_reachable_discord_notifications;
|
||||
$this->serverUnreachableDiscordNotifications = $this->settings->server_unreachable_discord_notifications;
|
||||
$this->serverPatchDiscordNotifications = $this->settings->server_patch_discord_notifications;
|
||||
$this->traefikOutdatedDiscordNotifications = $this->settings->traefik_outdated_discord_notifications;
|
||||
|
||||
$this->discordPingEnabled = $this->settings->discord_ping_enabled;
|
||||
}
|
||||
|
||||
@@ -104,6 +104,9 @@ class Email extends Component
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverPatchEmailNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $traefikOutdatedEmailNotifications = true;
|
||||
|
||||
#[Validate(['nullable', 'email'])]
|
||||
public ?string $testEmailAddress = null;
|
||||
|
||||
@@ -155,6 +158,7 @@ class Email extends Component
|
||||
$this->settings->server_reachable_email_notifications = $this->serverReachableEmailNotifications;
|
||||
$this->settings->server_unreachable_email_notifications = $this->serverUnreachableEmailNotifications;
|
||||
$this->settings->server_patch_email_notifications = $this->serverPatchEmailNotifications;
|
||||
$this->settings->traefik_outdated_email_notifications = $this->traefikOutdatedEmailNotifications;
|
||||
$this->settings->save();
|
||||
|
||||
} else {
|
||||
@@ -187,6 +191,7 @@ class Email extends Component
|
||||
$this->serverReachableEmailNotifications = $this->settings->server_reachable_email_notifications;
|
||||
$this->serverUnreachableEmailNotifications = $this->settings->server_unreachable_email_notifications;
|
||||
$this->serverPatchEmailNotifications = $this->settings->server_patch_email_notifications;
|
||||
$this->traefikOutdatedEmailNotifications = $this->settings->traefik_outdated_email_notifications;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ class Pushover extends Component
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverPatchPushoverNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $traefikOutdatedPushoverNotifications = true;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
@@ -104,6 +107,7 @@ class Pushover extends Component
|
||||
$this->settings->server_reachable_pushover_notifications = $this->serverReachablePushoverNotifications;
|
||||
$this->settings->server_unreachable_pushover_notifications = $this->serverUnreachablePushoverNotifications;
|
||||
$this->settings->server_patch_pushover_notifications = $this->serverPatchPushoverNotifications;
|
||||
$this->settings->traefik_outdated_pushover_notifications = $this->traefikOutdatedPushoverNotifications;
|
||||
|
||||
$this->settings->save();
|
||||
refreshSession();
|
||||
@@ -125,6 +129,7 @@ class Pushover extends Component
|
||||
$this->serverReachablePushoverNotifications = $this->settings->server_reachable_pushover_notifications;
|
||||
$this->serverUnreachablePushoverNotifications = $this->settings->server_unreachable_pushover_notifications;
|
||||
$this->serverPatchPushoverNotifications = $this->settings->server_patch_pushover_notifications;
|
||||
$this->traefikOutdatedPushoverNotifications = $this->settings->traefik_outdated_pushover_notifications;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,9 @@ class Slack extends Component
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverPatchSlackNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $traefikOutdatedSlackNotifications = true;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
@@ -100,6 +103,7 @@ class Slack extends Component
|
||||
$this->settings->server_reachable_slack_notifications = $this->serverReachableSlackNotifications;
|
||||
$this->settings->server_unreachable_slack_notifications = $this->serverUnreachableSlackNotifications;
|
||||
$this->settings->server_patch_slack_notifications = $this->serverPatchSlackNotifications;
|
||||
$this->settings->traefik_outdated_slack_notifications = $this->traefikOutdatedSlackNotifications;
|
||||
|
||||
$this->settings->save();
|
||||
refreshSession();
|
||||
@@ -120,6 +124,7 @@ class Slack extends Component
|
||||
$this->serverReachableSlackNotifications = $this->settings->server_reachable_slack_notifications;
|
||||
$this->serverUnreachableSlackNotifications = $this->settings->server_unreachable_slack_notifications;
|
||||
$this->serverPatchSlackNotifications = $this->settings->server_patch_slack_notifications;
|
||||
$this->traefikOutdatedSlackNotifications = $this->settings->traefik_outdated_slack_notifications;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ class Telegram extends Component
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverPatchTelegramNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $traefikOutdatedTelegramNotifications = true;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsDeploymentSuccessThreadId = null;
|
||||
|
||||
@@ -109,6 +112,9 @@ class Telegram extends Component
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsServerPatchThreadId = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $telegramNotificationsTraefikOutdatedThreadId = null;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
@@ -143,6 +149,7 @@ class Telegram extends Component
|
||||
$this->settings->server_reachable_telegram_notifications = $this->serverReachableTelegramNotifications;
|
||||
$this->settings->server_unreachable_telegram_notifications = $this->serverUnreachableTelegramNotifications;
|
||||
$this->settings->server_patch_telegram_notifications = $this->serverPatchTelegramNotifications;
|
||||
$this->settings->traefik_outdated_telegram_notifications = $this->traefikOutdatedTelegramNotifications;
|
||||
|
||||
$this->settings->telegram_notifications_deployment_success_thread_id = $this->telegramNotificationsDeploymentSuccessThreadId;
|
||||
$this->settings->telegram_notifications_deployment_failure_thread_id = $this->telegramNotificationsDeploymentFailureThreadId;
|
||||
@@ -157,6 +164,7 @@ class Telegram extends Component
|
||||
$this->settings->telegram_notifications_server_reachable_thread_id = $this->telegramNotificationsServerReachableThreadId;
|
||||
$this->settings->telegram_notifications_server_unreachable_thread_id = $this->telegramNotificationsServerUnreachableThreadId;
|
||||
$this->settings->telegram_notifications_server_patch_thread_id = $this->telegramNotificationsServerPatchThreadId;
|
||||
$this->settings->telegram_notifications_traefik_outdated_thread_id = $this->telegramNotificationsTraefikOutdatedThreadId;
|
||||
|
||||
$this->settings->save();
|
||||
} else {
|
||||
@@ -177,6 +185,7 @@ class Telegram extends Component
|
||||
$this->serverReachableTelegramNotifications = $this->settings->server_reachable_telegram_notifications;
|
||||
$this->serverUnreachableTelegramNotifications = $this->settings->server_unreachable_telegram_notifications;
|
||||
$this->serverPatchTelegramNotifications = $this->settings->server_patch_telegram_notifications;
|
||||
$this->traefikOutdatedTelegramNotifications = $this->settings->traefik_outdated_telegram_notifications;
|
||||
|
||||
$this->telegramNotificationsDeploymentSuccessThreadId = $this->settings->telegram_notifications_deployment_success_thread_id;
|
||||
$this->telegramNotificationsDeploymentFailureThreadId = $this->settings->telegram_notifications_deployment_failure_thread_id;
|
||||
@@ -191,6 +200,7 @@ class Telegram extends Component
|
||||
$this->telegramNotificationsServerReachableThreadId = $this->settings->telegram_notifications_server_reachable_thread_id;
|
||||
$this->telegramNotificationsServerUnreachableThreadId = $this->settings->telegram_notifications_server_unreachable_thread_id;
|
||||
$this->telegramNotificationsServerPatchThreadId = $this->settings->telegram_notifications_server_patch_thread_id;
|
||||
$this->telegramNotificationsTraefikOutdatedThreadId = $this->settings->telegram_notifications_traefik_outdated_thread_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ class Webhook extends Component
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverPatchWebhookNotifications = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $traefikOutdatedWebhookNotifications = true;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
@@ -95,6 +98,7 @@ class Webhook extends Component
|
||||
$this->settings->server_reachable_webhook_notifications = $this->serverReachableWebhookNotifications;
|
||||
$this->settings->server_unreachable_webhook_notifications = $this->serverUnreachableWebhookNotifications;
|
||||
$this->settings->server_patch_webhook_notifications = $this->serverPatchWebhookNotifications;
|
||||
$this->settings->traefik_outdated_webhook_notifications = $this->traefikOutdatedWebhookNotifications;
|
||||
|
||||
$this->settings->save();
|
||||
refreshSession();
|
||||
@@ -115,6 +119,7 @@ class Webhook extends Component
|
||||
$this->serverReachableWebhookNotifications = $this->settings->server_reachable_webhook_notifications;
|
||||
$this->serverUnreachableWebhookNotifications = $this->settings->server_unreachable_webhook_notifications;
|
||||
$this->serverPatchWebhookNotifications = $this->settings->server_patch_webhook_notifications;
|
||||
$this->traefikOutdatedWebhookNotifications = $this->settings->traefik_outdated_webhook_notifications;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace App\Livewire\Server;
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Actions\Proxy\StopProxy;
|
||||
use App\Jobs\RestartProxyJob;
|
||||
use App\Models\Server;
|
||||
use App\Services\ProxyDashboardCacheService;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
@@ -61,7 +60,13 @@ class Navbar extends Component
|
||||
{
|
||||
try {
|
||||
$this->authorize('manageProxy', $this->server);
|
||||
RestartProxyJob::dispatch($this->server);
|
||||
StopProxy::run($this->server, restarting: true);
|
||||
|
||||
$this->server->proxy->force_stop = false;
|
||||
$this->server->save();
|
||||
|
||||
$activity = StartProxy::run($this->server, force: true, restarting: true);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -118,19 +123,28 @@ class Navbar extends Component
|
||||
|
||||
public function showNotification()
|
||||
{
|
||||
$previousStatus = $this->proxyStatus;
|
||||
$this->server->refresh();
|
||||
$this->proxyStatus = $this->server->proxy->status ?? 'unknown';
|
||||
|
||||
switch ($this->proxyStatus) {
|
||||
case 'running':
|
||||
$this->loadProxyConfiguration();
|
||||
$this->dispatch('success', 'Proxy is running.');
|
||||
// Only show "Proxy is running" notification when transitioning from a stopped/error state
|
||||
// Don't show during normal start/restart flows (starting, restarting, stopping)
|
||||
if (in_array($previousStatus, ['exited', 'stopped', 'unknown', null])) {
|
||||
$this->dispatch('success', 'Proxy is running.');
|
||||
}
|
||||
break;
|
||||
case 'restarting':
|
||||
$this->dispatch('info', 'Initiating proxy restart.');
|
||||
break;
|
||||
case 'exited':
|
||||
$this->dispatch('info', 'Proxy has exited.');
|
||||
// Only show "Proxy has exited" notification when transitioning from running state
|
||||
// Don't show during normal stop/restart flows (stopping, restarting)
|
||||
if (in_array($previousStatus, ['running'])) {
|
||||
$this->dispatch('info', 'Proxy has exited.');
|
||||
}
|
||||
break;
|
||||
case 'stopping':
|
||||
$this->dispatch('info', 'Proxy is stopping.');
|
||||
|
||||
@@ -4,8 +4,10 @@ namespace App\Livewire\Server;
|
||||
|
||||
use App\Actions\Proxy\GetProxyConfiguration;
|
||||
use App\Actions\Proxy\SaveProxyConfiguration;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Livewire\Component;
|
||||
|
||||
class Proxy extends Component
|
||||
@@ -144,4 +146,125 @@ class Proxy extends Component
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function getLatestTraefikVersionProperty(): ?string
|
||||
{
|
||||
try {
|
||||
$versionsPath = base_path('versions.json');
|
||||
if (! File::exists($versionsPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$versions = json_decode(File::get($versionsPath), true);
|
||||
$traefikVersions = data_get($versions, 'traefik');
|
||||
|
||||
if (! $traefikVersions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle new structure (array of branches)
|
||||
if (is_array($traefikVersions)) {
|
||||
$currentVersion = $this->server->detected_traefik_version;
|
||||
|
||||
// If we have a current version, try to find matching branch
|
||||
if ($currentVersion && $currentVersion !== 'latest') {
|
||||
$current = ltrim($currentVersion, 'v');
|
||||
if (preg_match('/^(\d+\.\d+)/', $current, $matches)) {
|
||||
$branch = "v{$matches[1]}";
|
||||
if (isset($traefikVersions[$branch])) {
|
||||
$version = $traefikVersions[$branch];
|
||||
|
||||
return str_starts_with($version, 'v') ? $version : "v{$version}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the newest available version
|
||||
$newestVersion = collect($traefikVersions)
|
||||
->map(fn ($v) => ltrim($v, 'v'))
|
||||
->sortBy(fn ($v) => $v, SORT_NATURAL)
|
||||
->last();
|
||||
|
||||
return $newestVersion ? "v{$newestVersion}" : null;
|
||||
}
|
||||
|
||||
// Handle old structure (simple string) for backward compatibility
|
||||
return str_starts_with($traefikVersions, 'v') ? $traefikVersions : "v{$traefikVersions}";
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function getIsTraefikOutdatedProperty(): bool
|
||||
{
|
||||
if ($this->server->proxyType() !== ProxyTypes::TRAEFIK->value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentVersion = $this->server->detected_traefik_version;
|
||||
if (! $currentVersion || $currentVersion === 'latest') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$latestVersion = $this->latestTraefikVersion;
|
||||
if (! $latestVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare versions (strip 'v' prefix)
|
||||
$current = ltrim($currentVersion, 'v');
|
||||
$latest = ltrim($latestVersion, 'v');
|
||||
|
||||
return version_compare($current, $latest, '<');
|
||||
}
|
||||
|
||||
public function getNewerTraefikBranchAvailableProperty(): ?string
|
||||
{
|
||||
try {
|
||||
if ($this->server->proxyType() !== ProxyTypes::TRAEFIK->value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$currentVersion = $this->server->detected_traefik_version;
|
||||
if (! $currentVersion || $currentVersion === 'latest') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$versionsPath = base_path('versions.json');
|
||||
if (! File::exists($versionsPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$versions = json_decode(File::get($versionsPath), true);
|
||||
$traefikVersions = data_get($versions, 'traefik');
|
||||
|
||||
if (! is_array($traefikVersions)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract current branch (e.g., "3.5" from "3.5.6")
|
||||
$current = ltrim($currentVersion, 'v');
|
||||
if (! preg_match('/^(\d+\.\d+)/', $current, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$currentBranch = $matches[1];
|
||||
|
||||
// Find the newest branch that's greater than current
|
||||
$newestVersion = null;
|
||||
foreach ($traefikVersions as $branch => $version) {
|
||||
$branchNum = ltrim($branch, 'v');
|
||||
if (version_compare($branchNum, $currentBranch, '>')) {
|
||||
$cleanVersion = ltrim($version, 'v');
|
||||
if (! $newestVersion || version_compare($cleanVersion, $newestVersion, '>')) {
|
||||
$newestVersion = $cleanVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $newestVersion ? "v{$newestVersion}" : null;
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ class DiscordNotificationSettings extends Model
|
||||
'server_reachable_discord_notifications',
|
||||
'server_unreachable_discord_notifications',
|
||||
'server_patch_discord_notifications',
|
||||
'traefik_outdated_discord_notifications',
|
||||
'discord_ping_enabled',
|
||||
];
|
||||
|
||||
@@ -48,6 +49,7 @@ class DiscordNotificationSettings extends Model
|
||||
'server_reachable_discord_notifications' => 'boolean',
|
||||
'server_unreachable_discord_notifications' => 'boolean',
|
||||
'server_patch_discord_notifications' => 'boolean',
|
||||
'traefik_outdated_discord_notifications' => 'boolean',
|
||||
'discord_ping_enabled' => 'boolean',
|
||||
];
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ class EmailNotificationSettings extends Model
|
||||
'scheduled_task_failure_email_notifications',
|
||||
'server_disk_usage_email_notifications',
|
||||
'server_patch_email_notifications',
|
||||
'traefik_outdated_email_notifications',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -63,6 +64,7 @@ class EmailNotificationSettings extends Model
|
||||
'scheduled_task_failure_email_notifications' => 'boolean',
|
||||
'server_disk_usage_email_notifications' => 'boolean',
|
||||
'server_patch_email_notifications' => 'boolean',
|
||||
'traefik_outdated_email_notifications' => 'boolean',
|
||||
];
|
||||
|
||||
public function team()
|
||||
|
||||
@@ -30,6 +30,7 @@ class PushoverNotificationSettings extends Model
|
||||
'server_reachable_pushover_notifications',
|
||||
'server_unreachable_pushover_notifications',
|
||||
'server_patch_pushover_notifications',
|
||||
'traefik_outdated_pushover_notifications',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -49,6 +50,7 @@ class PushoverNotificationSettings extends Model
|
||||
'server_reachable_pushover_notifications' => 'boolean',
|
||||
'server_unreachable_pushover_notifications' => 'boolean',
|
||||
'server_patch_pushover_notifications' => 'boolean',
|
||||
'traefik_outdated_pushover_notifications' => 'boolean',
|
||||
];
|
||||
|
||||
public function team()
|
||||
|
||||
@@ -167,6 +167,7 @@ class Server extends BaseModel
|
||||
'hetzner_server_id',
|
||||
'hetzner_server_status',
|
||||
'is_validating',
|
||||
'detected_traefik_version',
|
||||
];
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
@@ -29,6 +29,7 @@ class SlackNotificationSettings extends Model
|
||||
'server_reachable_slack_notifications',
|
||||
'server_unreachable_slack_notifications',
|
||||
'server_patch_slack_notifications',
|
||||
'traefik_outdated_slack_notifications',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -47,6 +48,7 @@ class SlackNotificationSettings extends Model
|
||||
'server_reachable_slack_notifications' => 'boolean',
|
||||
'server_unreachable_slack_notifications' => 'boolean',
|
||||
'server_patch_slack_notifications' => 'boolean',
|
||||
'traefik_outdated_slack_notifications' => 'boolean',
|
||||
];
|
||||
|
||||
public function team()
|
||||
|
||||
+3
-1
@@ -49,7 +49,9 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function ($team) {
|
||||
$team->emailNotificationSettings()->create();
|
||||
$team->emailNotificationSettings()->create([
|
||||
'use_instance_email_settings' => isDev(),
|
||||
]);
|
||||
$team->discordNotificationSettings()->create();
|
||||
$team->slackNotificationSettings()->create();
|
||||
$team->telegramNotificationSettings()->create();
|
||||
|
||||
@@ -30,6 +30,7 @@ class TelegramNotificationSettings extends Model
|
||||
'server_reachable_telegram_notifications',
|
||||
'server_unreachable_telegram_notifications',
|
||||
'server_patch_telegram_notifications',
|
||||
'traefik_outdated_telegram_notifications',
|
||||
|
||||
'telegram_notifications_deployment_success_thread_id',
|
||||
'telegram_notifications_deployment_failure_thread_id',
|
||||
@@ -43,6 +44,7 @@ class TelegramNotificationSettings extends Model
|
||||
'telegram_notifications_server_reachable_thread_id',
|
||||
'telegram_notifications_server_unreachable_thread_id',
|
||||
'telegram_notifications_server_patch_thread_id',
|
||||
'telegram_notifications_traefik_outdated_thread_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -62,6 +64,7 @@ class TelegramNotificationSettings extends Model
|
||||
'server_reachable_telegram_notifications' => 'boolean',
|
||||
'server_unreachable_telegram_notifications' => 'boolean',
|
||||
'server_patch_telegram_notifications' => 'boolean',
|
||||
'traefik_outdated_telegram_notifications' => 'boolean',
|
||||
|
||||
'telegram_notifications_deployment_success_thread_id' => 'encrypted',
|
||||
'telegram_notifications_deployment_failure_thread_id' => 'encrypted',
|
||||
@@ -75,6 +78,7 @@ class TelegramNotificationSettings extends Model
|
||||
'telegram_notifications_server_reachable_thread_id' => 'encrypted',
|
||||
'telegram_notifications_server_unreachable_thread_id' => 'encrypted',
|
||||
'telegram_notifications_server_patch_thread_id' => 'encrypted',
|
||||
'telegram_notifications_traefik_outdated_thread_id' => 'encrypted',
|
||||
];
|
||||
|
||||
public function team()
|
||||
|
||||
@@ -29,6 +29,7 @@ class WebhookNotificationSettings extends Model
|
||||
'server_reachable_webhook_notifications',
|
||||
'server_unreachable_webhook_notifications',
|
||||
'server_patch_webhook_notifications',
|
||||
'traefik_outdated_webhook_notifications',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
@@ -49,6 +50,7 @@ class WebhookNotificationSettings extends Model
|
||||
'server_reachable_webhook_notifications' => 'boolean',
|
||||
'server_unreachable_webhook_notifications' => 'boolean',
|
||||
'server_patch_webhook_notifications' => 'boolean',
|
||||
'traefik_outdated_webhook_notifications' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Server;
|
||||
|
||||
use App\Notifications\CustomEmailNotification;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use App\Notifications\Dto\PushoverMessage;
|
||||
use App\Notifications\Dto\SlackMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class TraefikVersionOutdated extends CustomEmailNotification
|
||||
{
|
||||
public function __construct(public Collection $servers)
|
||||
{
|
||||
$this->onQueue('high');
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return $notifiable->getEnabledChannels('traefik_outdated');
|
||||
}
|
||||
|
||||
private function formatVersion(string $version): string
|
||||
{
|
||||
// Add 'v' prefix if not present for consistent display
|
||||
return str_starts_with($version, 'v') ? $version : "v{$version}";
|
||||
}
|
||||
|
||||
public function toMail($notifiable = null): MailMessage
|
||||
{
|
||||
$mail = new MailMessage;
|
||||
$count = $this->servers->count();
|
||||
|
||||
$mail->subject("Coolify: Traefik proxy outdated on {$count} server(s)");
|
||||
$mail->view('emails.traefik-version-outdated', [
|
||||
'servers' => $this->servers,
|
||||
'count' => $count,
|
||||
]);
|
||||
|
||||
return $mail;
|
||||
}
|
||||
|
||||
public function toDiscord(): DiscordMessage
|
||||
{
|
||||
$count = $this->servers->count();
|
||||
$hasUpgrades = $this->servers->contains(fn ($s) => ($s->outdatedInfo['type'] ?? 'patch_update') === 'minor_upgrade');
|
||||
|
||||
$description = "**{$count} server(s)** running outdated Traefik proxy. Update recommended for security and features.\n\n";
|
||||
$description .= "*Based on actual running container version*\n\n";
|
||||
$description .= "**Affected servers:**\n";
|
||||
|
||||
foreach ($this->servers as $server) {
|
||||
$info = $server->outdatedInfo ?? [];
|
||||
$current = $this->formatVersion($info['current'] ?? 'unknown');
|
||||
$latest = $this->formatVersion($info['latest'] ?? 'unknown');
|
||||
$type = ($info['type'] ?? 'patch_update') === 'patch_update' ? '(patch)' : '(upgrade)';
|
||||
$description .= "• {$server->name}: {$current} → {$latest} {$type}\n";
|
||||
}
|
||||
|
||||
$description .= "\n⚠️ It is recommended to test before switching the production version.";
|
||||
|
||||
if ($hasUpgrades) {
|
||||
$description .= "\n\n📖 **For major/minor upgrades**: Read the Traefik changelog before upgrading to understand breaking changes.";
|
||||
}
|
||||
|
||||
return new DiscordMessage(
|
||||
title: ':warning: Coolify: Traefik proxy outdated',
|
||||
description: $description,
|
||||
color: DiscordMessage::warningColor(),
|
||||
);
|
||||
}
|
||||
|
||||
public function toTelegram(): array
|
||||
{
|
||||
$count = $this->servers->count();
|
||||
$hasUpgrades = $this->servers->contains(fn ($s) => ($s->outdatedInfo['type'] ?? 'patch_update') === 'minor_upgrade');
|
||||
|
||||
$message = "⚠️ Coolify: Traefik proxy outdated on {$count} server(s)!\n\n";
|
||||
$message .= "Update recommended for security and features.\n";
|
||||
$message .= "ℹ️ Based on actual running container version\n\n";
|
||||
$message .= "📊 Affected servers:\n";
|
||||
|
||||
foreach ($this->servers as $server) {
|
||||
$info = $server->outdatedInfo ?? [];
|
||||
$current = $this->formatVersion($info['current'] ?? 'unknown');
|
||||
$latest = $this->formatVersion($info['latest'] ?? 'unknown');
|
||||
$type = ($info['type'] ?? 'patch_update') === 'patch_update' ? '(patch)' : '(upgrade)';
|
||||
$message .= "• {$server->name}: {$current} → {$latest} {$type}\n";
|
||||
}
|
||||
|
||||
$message .= "\n⚠️ It is recommended to test before switching the production version.";
|
||||
|
||||
if ($hasUpgrades) {
|
||||
$message .= "\n\n📖 For major/minor upgrades: Read the Traefik changelog before upgrading to understand breaking changes.";
|
||||
}
|
||||
|
||||
return [
|
||||
'message' => $message,
|
||||
'buttons' => [],
|
||||
];
|
||||
}
|
||||
|
||||
public function toPushover(): PushoverMessage
|
||||
{
|
||||
$count = $this->servers->count();
|
||||
$hasUpgrades = $this->servers->contains(fn ($s) => ($s->outdatedInfo['type'] ?? 'patch_update') === 'minor_upgrade');
|
||||
|
||||
$message = "Traefik proxy outdated on {$count} server(s)!\n";
|
||||
$message .= "Based on actual running container version\n\n";
|
||||
$message .= "Affected servers:\n";
|
||||
|
||||
foreach ($this->servers as $server) {
|
||||
$info = $server->outdatedInfo ?? [];
|
||||
$current = $this->formatVersion($info['current'] ?? 'unknown');
|
||||
$latest = $this->formatVersion($info['latest'] ?? 'unknown');
|
||||
$type = ($info['type'] ?? 'patch_update') === 'patch_update' ? '(patch)' : '(upgrade)';
|
||||
$message .= "• {$server->name}: {$current} → {$latest} {$type}\n";
|
||||
}
|
||||
|
||||
$message .= "\nIt is recommended to test before switching the production version.";
|
||||
|
||||
if ($hasUpgrades) {
|
||||
$message .= "\n\nFor major/minor upgrades: Read the Traefik changelog before upgrading.";
|
||||
}
|
||||
|
||||
return new PushoverMessage(
|
||||
title: 'Traefik proxy outdated',
|
||||
level: 'warning',
|
||||
message: $message,
|
||||
);
|
||||
}
|
||||
|
||||
public function toSlack(): SlackMessage
|
||||
{
|
||||
$count = $this->servers->count();
|
||||
$hasUpgrades = $this->servers->contains(fn ($s) => ($s->outdatedInfo['type'] ?? 'patch_update') === 'minor_upgrade');
|
||||
|
||||
$description = "Traefik proxy outdated on {$count} server(s)!\n";
|
||||
$description .= "_Based on actual running container version_\n\n";
|
||||
$description .= "*Affected servers:*\n";
|
||||
|
||||
foreach ($this->servers as $server) {
|
||||
$info = $server->outdatedInfo ?? [];
|
||||
$current = $this->formatVersion($info['current'] ?? 'unknown');
|
||||
$latest = $this->formatVersion($info['latest'] ?? 'unknown');
|
||||
$type = ($info['type'] ?? 'patch_update') === 'patch_update' ? '(patch)' : '(upgrade)';
|
||||
$description .= "• `{$server->name}`: {$current} → {$latest} {$type}\n";
|
||||
}
|
||||
|
||||
$description .= "\n:warning: It is recommended to test before switching the production version.";
|
||||
|
||||
if ($hasUpgrades) {
|
||||
$description .= "\n\n:book: For major/minor upgrades: Read the Traefik changelog before upgrading to understand breaking changes.";
|
||||
}
|
||||
|
||||
return new SlackMessage(
|
||||
title: 'Coolify: Traefik proxy outdated',
|
||||
description: $description,
|
||||
color: SlackMessage::warningColor()
|
||||
);
|
||||
}
|
||||
|
||||
public function toWebhook(): array
|
||||
{
|
||||
$servers = $this->servers->map(function ($server) {
|
||||
$info = $server->outdatedInfo ?? [];
|
||||
|
||||
return [
|
||||
'name' => $server->name,
|
||||
'uuid' => $server->uuid,
|
||||
'current_version' => $info['current'] ?? 'unknown',
|
||||
'latest_version' => $info['latest'] ?? 'unknown',
|
||||
'update_type' => $info['type'] ?? 'patch_update',
|
||||
];
|
||||
})->toArray();
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Traefik proxy outdated',
|
||||
'event' => 'traefik_version_outdated',
|
||||
'affected_servers_count' => $this->servers->count(),
|
||||
'servers' => $servers,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user