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:
Andras Bacsai
2025-11-13 13:38:57 +01:00
parent a4d07ab050
commit 0bd4ffb2d7
48 changed files with 1488 additions and 48 deletions

View File

@@ -334,3 +334,94 @@ function generateDefaultProxyConfiguration(Server $server, array $custom_command
return $config;
}
function getExactTraefikVersionFromContainer(Server $server): ?string
{
try {
Log::debug("getExactTraefikVersionFromContainer: Server '{$server->name}' (ID: {$server->id}) - Checking for exact version");
// Method A: Execute traefik version command (most reliable)
$versionCommand = "docker exec coolify-proxy traefik version 2>/dev/null | grep -oP 'Version:\s+\K\d+\.\d+\.\d+'";
Log::debug("getExactTraefikVersionFromContainer: Server '{$server->name}' (ID: {$server->id}) - Running: {$versionCommand}");
$output = instant_remote_process([$versionCommand], $server, false);
if (! empty(trim($output))) {
$version = trim($output);
Log::debug("getExactTraefikVersionFromContainer: Server '{$server->name}' (ID: {$server->id}) - Detected exact version from command: {$version}");
return $version;
}
// Method B: Try OCI label as fallback
$labelCommand = "docker inspect coolify-proxy --format '{{index .Config.Labels \"org.opencontainers.image.version\"}}' 2>/dev/null";
Log::debug("getExactTraefikVersionFromContainer: Server '{$server->name}' (ID: {$server->id}) - Trying OCI label");
$label = instant_remote_process([$labelCommand], $server, false);
if (! empty(trim($label))) {
// Extract version number from label (might have 'v' prefix)
if (preg_match('/(\d+\.\d+\.\d+)/', trim($label), $matches)) {
Log::debug("getExactTraefikVersionFromContainer: Server '{$server->name}' (ID: {$server->id}) - Detected from OCI label: {$matches[1]}");
return $matches[1];
}
}
Log::debug("getExactTraefikVersionFromContainer: Server '{$server->name}' (ID: {$server->id}) - Could not detect exact version");
return null;
} catch (\Exception $e) {
Log::error("getExactTraefikVersionFromContainer: Server '{$server->name}' (ID: {$server->id}) - Error: ".$e->getMessage());
return null;
}
}
function getTraefikVersionFromDockerCompose(Server $server): ?string
{
try {
Log::debug("getTraefikVersionFromDockerCompose: Server '{$server->name}' (ID: {$server->id}) - Starting version detection");
// Try to get exact version from running container (e.g., "3.6.0")
$exactVersion = getExactTraefikVersionFromContainer($server);
if ($exactVersion) {
Log::debug("getTraefikVersionFromDockerCompose: Server '{$server->name}' (ID: {$server->id}) - Using exact version: {$exactVersion}");
return $exactVersion;
}
// Fallback: Check image tag (current method)
Log::debug("getTraefikVersionFromDockerCompose: Server '{$server->name}' (ID: {$server->id}) - Falling back to image tag detection");
$containerName = 'coolify-proxy';
$inspectCommand = "docker inspect {$containerName} --format '{{.Config.Image}}' 2>/dev/null";
$image = instant_remote_process([$inspectCommand], $server, false);
if (empty(trim($image))) {
Log::debug("getTraefikVersionFromDockerCompose: Server '{$server->name}' (ID: {$server->id}) - Container '{$containerName}' not found or not running");
return null;
}
$image = trim($image);
Log::debug("getTraefikVersionFromDockerCompose: Server '{$server->name}' (ID: {$server->id}) - Running container image: {$image}");
// Extract version from image string (e.g., "traefik:v3.6" or "traefik:3.6.0" or "traefik:latest")
if (preg_match('/traefik:(v?\d+\.\d+(?:\.\d+)?|latest)/i', $image, $matches)) {
Log::debug("getTraefikVersionFromDockerCompose: Server '{$server->name}' (ID: {$server->id}) - Extracted version from image tag: {$matches[1]}");
return $matches[1];
}
Log::debug("getTraefikVersionFromDockerCompose: Server '{$server->name}' (ID: {$server->id}) - Image format doesn't match expected pattern: {$image}");
return null;
} catch (\Exception $e) {
Log::error("getTraefikVersionFromDockerCompose: Server '{$server->name}' (ID: {$server->id}) - Error: ".$e->getMessage());
ray('Error getting Traefik version from running container: '.$e->getMessage());
return null;
}
}