mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-03-14 12:23:33 +00:00
feat: add update page, refactor update checking process
Some checks failed
tests / ci (push) Has been cancelled
Some checks failed
tests / ci (push) Has been cancelled
This commit is contained in:
parent
eb767fa6d0
commit
297a17d00b
17 changed files with 1212 additions and 241 deletions
|
|
@ -70,6 +70,12 @@
|
|||
<flux:menu.radio.group>
|
||||
<flux:menu.item href="{{ route('settings.preferences') }}" wire:navigate icon="cog">Settings</flux:menu.item>
|
||||
<flux:menu.item href="{{ route('settings.support') }}" wire:navigate icon="heart">Support</flux:menu.item>
|
||||
<flux:menu.item href="{{ route('settings.update') }}" wire:navigate icon="arrow-down-circle">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>Updates</span>
|
||||
<livewire:update-badge />
|
||||
</div>
|
||||
</flux:menu.item>
|
||||
</flux:menu.radio.group>
|
||||
|
||||
<flux:menu.separator/>
|
||||
|
|
|
|||
48
resources/views/livewire/update-badge.blade.php
Normal file
48
resources/views/livewire/update-badge.blade.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Livewire\Component;
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
public bool $hasUpdate = false;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->checkForUpdate();
|
||||
}
|
||||
|
||||
public function checkForUpdate(): void
|
||||
{
|
||||
$currentVersion = config('app.version');
|
||||
if (! $currentVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = Cache::get('latest_release');
|
||||
if (! $response) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle both single release object and array of releases
|
||||
if (is_array($response) && isset($response[0])) {
|
||||
// Array of releases - find the latest one
|
||||
$latestRelease = $response[0];
|
||||
$latestVersion = Arr::get($latestRelease, 'tag_name');
|
||||
} else {
|
||||
// Single release object
|
||||
$latestVersion = Arr::get($response, 'tag_name');
|
||||
}
|
||||
|
||||
if ($latestVersion && version_compare($latestVersion, $currentVersion, '>')) {
|
||||
$this->hasUpdate = true;
|
||||
}
|
||||
}
|
||||
} ?>
|
||||
|
||||
<span>
|
||||
@if($hasUpdate)
|
||||
<flux:badge color="yellow"><flux:icon name="sparkles" class="size-4"/></flux:badge>
|
||||
@endif
|
||||
</span>
|
||||
66
resources/views/livewire/update-check.blade.php
Normal file
66
resources/views/livewire/update-check.blade.php
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
use App\Jobs\CheckVersionUpdateJob;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Livewire\Component;
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
public ?string $latestVersion = null;
|
||||
|
||||
public bool $isUpdateAvailable = false;
|
||||
|
||||
public ?array $releaseData = null;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$currentVersion = config('app.version');
|
||||
if (! $currentVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check cache first - only show content if cached
|
||||
$cachedResponse = Cache::get('latest_release');
|
||||
if ($cachedResponse) {
|
||||
$this->processCachedResponse($cachedResponse, $currentVersion);
|
||||
} else {
|
||||
// Defer job in background using dispatchAfterResponse
|
||||
CheckVersionUpdateJob::dispatchAfterResponse();
|
||||
}
|
||||
}
|
||||
|
||||
private function processCachedResponse($response, string $currentVersion): void
|
||||
{
|
||||
$latestVersion = null;
|
||||
|
||||
// Handle both single release object and array of releases
|
||||
if (is_array($response) && isset($response[0])) {
|
||||
// Array of releases - find the latest one
|
||||
$latestRelease = $response[0];
|
||||
$latestVersion = Arr::get($latestRelease, 'tag_name');
|
||||
$this->releaseData = $latestRelease;
|
||||
} else {
|
||||
// Single release object
|
||||
$latestVersion = Arr::get($response, 'tag_name');
|
||||
$this->releaseData = $response;
|
||||
}
|
||||
|
||||
if ($latestVersion && version_compare($latestVersion, $currentVersion, '>')) {
|
||||
$this->latestVersion = $latestVersion;
|
||||
$this->isUpdateAvailable = true;
|
||||
}
|
||||
}
|
||||
} ?>
|
||||
|
||||
<div>
|
||||
@if(config('app.version') && $isUpdateAvailable && $latestVersion)
|
||||
<flux:callout class="text-xs mt-6" icon="arrow-down-circle">
|
||||
<flux:callout.heading>Update available</flux:callout.heading>
|
||||
<flux:callout.text>
|
||||
There is a newer version {{ $latestVersion }} available. Update to the latest version for the best experience.
|
||||
<flux:callout.link href="{{route('settings.update')}}" wire:navigate>Release notes</flux:callout.link>
|
||||
</flux:callout.text>
|
||||
</flux:callout>
|
||||
@endif
|
||||
</div>
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
<flux:navlist.item :href="route('user-password.edit')" wire:navigate>{{ __('Password') }}</flux:navlist.item>
|
||||
@endif
|
||||
<flux:navlist.item :href="route('settings.support')" wire:navigate>{{ __('Support') }}</flux:navlist.item>
|
||||
<flux:navlist.item :href="route('settings.update')" wire:navigate>{{ __('Updates') }}</flux:navlist.item>
|
||||
</flux:navlist>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
258
resources/views/pages/settings/update.blade.php
Normal file
258
resources/views/pages/settings/update.blade.php
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
<?php
|
||||
|
||||
use App\Jobs\CheckVersionUpdateJob;
|
||||
use App\Settings\UpdateSettings;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Livewire\Component;
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
public ?string $latestVersion = null;
|
||||
|
||||
public bool $isUpdateAvailable = false;
|
||||
|
||||
public ?string $releaseNotes = null;
|
||||
|
||||
public ?string $releaseUrl = null;
|
||||
|
||||
public bool $prereleases = false;
|
||||
|
||||
public bool $isChecking = false;
|
||||
|
||||
public ?string $errorMessage = null;
|
||||
|
||||
public ?int $backoffUntil = null;
|
||||
|
||||
private UpdateSettings $updateSettings;
|
||||
|
||||
public function boot(UpdateSettings $updateSettings): void
|
||||
{
|
||||
$this->updateSettings = $updateSettings;
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->prereleases = $this->updateSettings->prereleases;
|
||||
$this->loadFromCache();
|
||||
}
|
||||
|
||||
private function loadFromCache(): void
|
||||
{
|
||||
$currentVersion = config('app.version');
|
||||
if (! $currentVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load from cache without fetching
|
||||
$cachedRelease = Cache::get('latest_release');
|
||||
if ($cachedRelease) {
|
||||
$this->processCachedRelease($cachedRelease, $currentVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private function processCachedRelease($cachedRelease, string $currentVersion): void
|
||||
{
|
||||
$latestVersion = null;
|
||||
$releaseData = null;
|
||||
|
||||
// Handle both single release object and array of releases
|
||||
if (is_array($cachedRelease) && isset($cachedRelease[0])) {
|
||||
// Array of releases - find the latest one
|
||||
$releaseData = $cachedRelease[0];
|
||||
$latestVersion = Arr::get($releaseData, 'tag_name');
|
||||
} else {
|
||||
// Single release object
|
||||
$releaseData = $cachedRelease;
|
||||
$latestVersion = Arr::get($releaseData, 'tag_name');
|
||||
}
|
||||
|
||||
if ($latestVersion) {
|
||||
$this->latestVersion = $latestVersion;
|
||||
$this->isUpdateAvailable = version_compare($latestVersion, $currentVersion, '>');
|
||||
$this->releaseUrl = Arr::get($releaseData, 'html_url');
|
||||
$this->loadReleaseNotes();
|
||||
}
|
||||
}
|
||||
|
||||
public function checkForUpdates(): void
|
||||
{
|
||||
$this->isChecking = true;
|
||||
$this->errorMessage = null;
|
||||
$this->backoffUntil = null;
|
||||
|
||||
try {
|
||||
$result = CheckVersionUpdateJob::dispatchSync();
|
||||
|
||||
$this->latestVersion = $result['latest_version'];
|
||||
$this->isUpdateAvailable = $result['is_newer'];
|
||||
$this->releaseUrl = Arr::get($result['release_data'] ?? [], 'html_url');
|
||||
|
||||
// Handle errors
|
||||
if (isset($result['error'])) {
|
||||
if ($result['error'] === 'rate_limit') {
|
||||
$this->backoffUntil = $result['backoff_until'] ?? null;
|
||||
$this->errorMessage = 'GitHub API rate limit exceeded. Please try again later.';
|
||||
} elseif ($result['error'] === 'connection_failed') {
|
||||
$this->errorMessage = 'Request timed out or failed to connect to GitHub. Please check your internet connection and try again.';
|
||||
} elseif ($result['error'] === 'fetch_failed') {
|
||||
$this->errorMessage = 'Failed to fetch update information from GitHub. Please try again later.';
|
||||
} else {
|
||||
$this->errorMessage = 'An unexpected error occurred while checking for updates. Please try again later.';
|
||||
}
|
||||
} else {
|
||||
// Reload release notes if we have a new version
|
||||
if ($this->latestVersion) {
|
||||
$this->loadReleaseNotes();
|
||||
}
|
||||
}
|
||||
} catch (Illuminate\Http\Client\ConnectionException $e) {
|
||||
$this->errorMessage = 'Request timed out or failed to connect to GitHub. Please check your internet connection and try again.';
|
||||
Log::error('Update check connection failed: '.$e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
$this->errorMessage = 'Request timed out or failed. Please check your internet connection and try again.';
|
||||
Log::error('Update check failed: '.$e->getMessage());
|
||||
} finally {
|
||||
$this->isChecking = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedPrereleases(): void
|
||||
{
|
||||
$this->updateSettings->prereleases = $this->prereleases;
|
||||
$this->updateSettings->save();
|
||||
|
||||
// Clear cache and recheck for updates with new preference
|
||||
Cache::forget('latest_release');
|
||||
$this->checkForUpdates();
|
||||
}
|
||||
|
||||
public function loadReleaseNotes(): void
|
||||
{
|
||||
if (! $this->latestVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cacheKey = "release_notes_{$this->latestVersion}";
|
||||
$currentVersionKey = 'release_notes_current_version';
|
||||
|
||||
// Check if we have a previous version cached
|
||||
$previousVersion = Cache::get($currentVersionKey);
|
||||
|
||||
// Clean up old version cache if different
|
||||
if ($previousVersion && $previousVersion !== $this->latestVersion) {
|
||||
Cache::forget("release_notes_{$previousVersion}");
|
||||
}
|
||||
|
||||
// Try to get from cache first - always load from cache if available
|
||||
$cachedNotes = Cache::get($cacheKey);
|
||||
if ($cachedNotes) {
|
||||
$this->releaseNotes = $cachedNotes;
|
||||
// Update current version tracker
|
||||
Cache::put($currentVersionKey, $this->latestVersion, 86400);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch release notes if we have a version but no cache
|
||||
// This will fetch on mount if an update is available, or when explicitly checking
|
||||
$githubRepo = config('app.github_repo');
|
||||
$apiBaseUrl = "https://api.github.com/repos/{$githubRepo}";
|
||||
|
||||
try {
|
||||
// Fetch release notes for the specific version with HTML format
|
||||
$response = Http::withHeaders([
|
||||
'Accept' => 'application/vnd.github.v3.html+json',
|
||||
])->timeout(10)->get("{$apiBaseUrl}/releases/tags/{$this->latestVersion}");
|
||||
if ($response->successful()) {
|
||||
$releaseData = $response->json();
|
||||
$bodyHtml = Arr::get($releaseData, 'body_html');
|
||||
|
||||
if ($bodyHtml) {
|
||||
// Cache for 24 hours
|
||||
Cache::put($cacheKey, $bodyHtml, 86400);
|
||||
$this->releaseNotes = $bodyHtml;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::debug('Failed to fetch release notes: '.$e->getMessage());
|
||||
}
|
||||
|
||||
// Update current version tracker
|
||||
Cache::put($currentVersionKey, $this->latestVersion, 86400);
|
||||
}
|
||||
} ?>
|
||||
|
||||
<section class="w-full">
|
||||
@include('partials.settings-heading')
|
||||
|
||||
<x-pages::settings.layout heading="Updates">
|
||||
<div class="my-6 w-full space-y-6">
|
||||
@if(config('app.version'))
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:heading size="sm">Current Version</flux:heading>
|
||||
<flux:text class="text-sm">
|
||||
<a href="https://github.com/{{ config('app.github_repo') }}/releases/" target="_blank" class="text-primary-600 dark:text-primary-400 hover:underline">
|
||||
{{ config('app.version') }}
|
||||
</a>
|
||||
</flux:text>
|
||||
</div>
|
||||
<flux:button wire:click="checkForUpdates">
|
||||
Check for Updates
|
||||
</flux:button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="space-y-4">
|
||||
<flux:switch wire:model.live="prereleases" label="Include Pre-Releases"/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
@if($errorMessage)
|
||||
<flux:callout icon="exclamation-triangle" variant="danger">
|
||||
<flux:callout.heading>Error</flux:callout.heading>
|
||||
<flux:callout.text>
|
||||
{{ $errorMessage }}
|
||||
@if($backoffUntil)
|
||||
<br><small class="text-xs opacity-75">You can try again after {{ \Carbon\Carbon::createFromTimestamp($backoffUntil)->format('H:i') }}.</small>
|
||||
@endif
|
||||
</flux:callout.text>
|
||||
</flux:callout>
|
||||
@elseif($isUpdateAvailable && $latestVersion)
|
||||
<flux:callout icon="arrow-down-circle" variant="info">
|
||||
<flux:callout.heading>Update Available</flux:callout.heading>
|
||||
<flux:callout.text>
|
||||
A newer version {{ $latestVersion }} is available. Update to the latest version for the best experience.
|
||||
</flux:callout.text>
|
||||
@if($releaseNotes)
|
||||
<div class="mt-4 [&_h2]:text-sm [&_h2]:font-semibold [&_h2]:text-zinc-900 [&_h2]:dark:text-white [&_h2]:mb-2 [&_h2]:mt-4 [&_h2:first-child]:mt-0 [&_p]:text-sm [&_p]:text-zinc-500 [&_p]:dark:text-white/70 [&_p]:mb-2 [&_ul]:text-sm [&_ul]:text-zinc-500 [&_ul]:dark:text-white/70 [&_ul]:list-disc [&_ul]:ml-6 [&_ul]:mb-2 [&_ul>li]:mb-1 [&_li]:text-sm [&_li]:text-zinc-500 [&_li]:dark:text-white/70 [&_a]:text-primary-600 [&_a]:dark:text-primary-400 [&_a]:hover:underline">
|
||||
{!! $releaseNotes !!}
|
||||
</div>
|
||||
@endif
|
||||
@if($releaseUrl)
|
||||
<div class="mt-4">
|
||||
<flux:button
|
||||
href="{{ $releaseUrl }}"
|
||||
target="_blank"
|
||||
icon:trailing="arrow-up-right"
|
||||
class="w-full">
|
||||
View on GitHub
|
||||
</flux:button>
|
||||
</div>
|
||||
@endif
|
||||
</flux:callout>
|
||||
@elseif($latestVersion && !$isUpdateAvailable)
|
||||
<flux:callout icon="check-circle" variant="success">
|
||||
<flux:callout.heading>Up to Date</flux:callout.heading>
|
||||
<flux:callout.text>
|
||||
You are running the latest version {{ $latestVersion }}.
|
||||
</flux:callout.text>
|
||||
</flux:callout>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</x-pages::settings.layout>
|
||||
</section>
|
||||
|
|
@ -32,39 +32,6 @@
|
|||
@endif
|
||||
</header>
|
||||
@auth
|
||||
@if(config('app.version'))
|
||||
<flux:text class="text-xs">Version: <a href="https://github.com/usetrmnl/byos_laravel/releases/"
|
||||
target="_blank">{{ config('app.version') }}</a>
|
||||
</flux:text>
|
||||
|
||||
@php
|
||||
$response = Cache::remember('latest_release', 86400, function () {
|
||||
try {
|
||||
$response = Http::get('https://api.github.com/repos/usetrmnl/byos_laravel/releases/latest');
|
||||
if ($response->successful()) {
|
||||
return $response->json();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::debug('Failed to fetch latest release: ' . $e->getMessage());
|
||||
}
|
||||
return null;
|
||||
});
|
||||
$latestVersion = Arr::get($response, 'tag_name');
|
||||
|
||||
if ($latestVersion && version_compare($latestVersion, config('app.version'), '>')) {
|
||||
$newVersion = $latestVersion;
|
||||
}
|
||||
@endphp
|
||||
|
||||
@if(isset($newVersion))
|
||||
<flux:callout class="text-xs mt-6" icon="arrow-down-circle">
|
||||
<flux:callout.heading>Update available</flux:callout.heading>
|
||||
<flux:callout.text>
|
||||
There is a newer version {{ $newVersion }} available. Update to the latest version for the best experience.
|
||||
<flux:callout.link href="https://github.com/usetrmnl/byos_laravel/releases/" target="_blank">Release notes</flux:callout.link>
|
||||
</flux:callout.text>
|
||||
</flux:callout>
|
||||
@endif
|
||||
@endif
|
||||
<livewire:update-check />
|
||||
@endauth
|
||||
</x-layouts::auth.card>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue