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

@@ -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;
}
}
}