FIX Deployment csomagoló d2d alapértelmezés és automatikus környezet-állapot

- A célkörnyezet alapértelmezése a d2d (azt frissítjük sűrűbben). A config targets
  tömbjének sorrendje adja az alapértelmezést, ezért csak a sorrend cserélődött.
- A "Környezetek állapota" panel betöltéskor magától lekérdezi a deploy-version.json-t,
  percenkénti cache-sel (a hibás választ is cache-eljük, hogy egy nem válaszoló
  környezet ne lassítsa minden kattintásnál a felületet). A Frissítés gomb kényszerít.
- A panel tömörebb, és valóban két hasábos: a md:grid-cols-2 nincs benne a lefordított
  CSS-ben, ezért saját, media query-s stílussal oldjuk meg - így build nélkül is működik.
- A commitlistában a hash mellett jelöljük, melyik környezet áll azon a commiton:
  zöld a szervertől lekérdezett, szürke a napló szerinti állapot.

Teszt: a beforeEach Http::fake() catch-all stubja minden URL-re illeszkedik és a factory
az első találatot adja vissza, ezért a konkrét választ váró teszt új factory-t kap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
E98Developer 2026-08-16 22:48:09 +02:00
parent c8d5e01490
commit 4bb89ae6a1
4 changed files with 180 additions and 63 deletions

View File

@ -17,6 +17,7 @@
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Schema;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Livewire\Attributes\Computed;
use RuntimeException;
@ -87,6 +88,7 @@ public function mount(): void
]);
$this->preloadFromCommit($target);
$this->fetchAllLiveVersions();
}
/**
@ -307,7 +309,7 @@ public function markDeployed(int $id): void
* Szándékosan gombra fut és nem minden rendereléskor: így nem indul kimenő kérés
* magától, és nem lassítja az oldalt, ha egy környezet nem válaszol.
*/
public function fetchLiveVersion(string $targetKey): void
public function fetchLiveVersion(string $targetKey, bool $force = false): void
{
app(DeploymentGuard::class)->ensureAllowed(auth()->user());
@ -317,15 +319,67 @@ public function fetchLiveVersion(string $targetKey): void
return;
}
try {
$response = Http::connectTimeout(3)->timeout(5)->get('https://'.$target['domain'].'/deploy-version.json');
$cacheKey = 'deployment.live-version.'.$targetKey;
$this->liveVersions[$targetKey] = $response->successful()
if ($force) {
Cache::forget($cacheKey);
}
// Rövid cache: az oldal minden Livewire körben újrarenderel, kérésenkénti
// HTTP hívás nélkül. A hibás választ is cache-eljük, hogy egy nem válaszoló
// környezet ne lassítsa minden kattintásnál a felületet.
$this->liveVersions[$targetKey] = Cache::remember($cacheKey, now()->addMinute(), function () use ($target): array {
try {
$response = Http::connectTimeout(2)->timeout(3)->get('https://'.$target['domain'].'/deploy-version.json');
return $response->successful()
? ['ok' => true, 'data' => $response->json()]
: ['ok' => false, 'error' => 'HTTP '.$response->status()];
} catch (Throwable $exception) {
$this->liveVersions[$targetKey] = ['ok' => false, 'error' => $exception->getMessage()];
return ['ok' => false, 'error' => $exception->getMessage()];
}
});
}
/**
* Minden környezet állapotának lekérése (oldalbetöltéskor fut).
*/
public function fetchAllLiveVersions(bool $force = false): void
{
foreach (array_keys(app(DeploymentTargets::class)->all()) as $targetKey) {
$this->fetchLiveVersion($targetKey, $force);
}
}
/**
* Melyik commiton áll melyik környezet - a commitlista megjelöléséhez.
*
* Elsődlegesen a szerverről lekérdezett élő állapotot használjuk; ha az nem
* elérhető, a naplóban kirakottként megjelölt csomagot.
*
* @return array<string, array<int, array{key:string, label:string, live:bool}>>
*/
#[Computed]
public function commitMarkers(): array
{
$markers = [];
foreach ($this->environmentStatuses() as $status) {
$liveCommit = ($status['live']['ok'] ?? false) ? ($status['live']['data']['commit'] ?? null) : null;
$commit = $liveCommit ?: $status['package']?->to_commit;
if (! $commit) {
continue;
}
$markers[$commit][] = [
'key' => $status['target']['key'],
'label' => $status['target']['label'],
'live' => (bool) $liveCommit,
];
}
return $markers;
}
/**

View File

@ -110,19 +110,24 @@
|
*/
/*
| A sorrend számít: az első az alapértelmezett célkörnyezet a felületen
| (DeploymentTargets::defaultKey()). A d2d azért van elöl, mert azt frissítjük
| sűrűbben, tehát a gyakoribb eset legyen az alapértelmezés.
*/
'targets' => [
'e2e' => [
'label' => 'e2e — t2t éles',
'domain' => 'e2e.emegrendeles.hu',
'remote_path' => '/delirest/test.t2t.emegrendeles.hu/app/',
'expected_branch' => 'main',
],
'd2d' => [
'label' => 'd2d — fejlesztői',
'domain' => 'd2d.emegrendeles.hu',
'remote_path' => '/delirest/d2d.emegrendeles.hu/app/',
'expected_branch' => 'test',
],
'e2e' => [
'label' => 'e2e — t2t éles',
'domain' => 'e2e.emegrendeles.hu',
'remote_path' => '/delirest/test.t2t.emegrendeles.hu/app/',
'expected_branch' => 'main',
],
],
];

View File

@ -27,9 +27,29 @@
];
$cardClass = 'p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700';
$commitMarkers = $this->commitMarkers;
@endphp
<x-filament-panels::page>
{{--
Saját stílus, nem Tailwind utility: egy új blade-ben használt osztály csak
npm run build után kerül a bundle-be (a md:grid-cols-2 pl. jelenleg nincs benne),
így a kétoszlopos elrendezés build nélkül is működik.
--}}
<style>
.fi-deployment-env-grid {
display: grid;
gap: 0.75rem;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.fi-deployment-env-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>
<div class="space-y-6">
<div class="{{ $cardClass }}">
@ -44,60 +64,48 @@
</div>
<div class="{{ $cardClass }}">
<h6 class="mb-4 text-lg font-semibold text-gray-900 dark:text-white">Környezetek állapota</h6>
<div class="grid gap-4 md:grid-cols-2">
@foreach ($environments as $environment)
<div class="p-4 border rounded-lg {{ $environment['mismatch'] ? $warningStyles['danger'] : 'border-gray-200 dark:border-gray-700' }}">
<div class="flex items-start justify-between gap-3">
<div>
<p class="font-semibold text-gray-900 dark:text-white">{{ $environment['target']['label'] }}</p>
<p class="text-xs font-mono text-gray-500 dark:text-gray-400">{{ $environment['target']['remote_path'] }}</p>
</div>
<x-filament::button size="xs" color="gray"
wire:click="fetchLiveVersion('{{ $environment['target']['key'] }}')">
Élő állapot
<div class="flex items-center justify-between gap-3 mb-3">
<h6 class="text-lg font-semibold text-gray-900 dark:text-white">Környezetek állapota</h6>
<x-filament::button size="xs" color="gray" wire:click="fetchAllLiveVersions(true)">
Frissítés
</x-filament::button>
</div>
<div class="mt-3 space-y-1 text-sm text-gray-600 dark:text-gray-300">
@if ($environment['package'])
<p>
Utoljára kirakva:
<code>{{ substr($environment['package']->to_commit, 0, 7) }}</code>
· {{ $environment['package']->deployed_at?->format('Y.m.d H:i') }}
</p>
@if ($environment['behind'] !== null)
<p>
Lemaradás a kiválasztott branchhez képest:
<strong>{{ $environment['behind'] }}</strong> commit
</p>
@endif
@else
<p class="text-gray-400">Még nincs kirakottként megjelölt csomag.</p>
@endif
<div class="fi-deployment-env-grid">
@foreach ($environments as $environment)
<div class="p-3 border rounded-lg {{ $environment['mismatch'] ? $warningStyles['danger'] : 'border-gray-200 dark:border-gray-700' }}">
<div class="flex items-center justify-between gap-2">
<span class="font-semibold text-gray-900 dark:text-white">{{ $environment['target']['label'] }}</span>
@if ($environment['live'])
@if ($environment['live']['ok'])
<p>
A szerver szerint:
<code>{{ substr($environment['live']['data']['commit'] ?? '?', 0, 7) }}</code>
@if (($environment['live']['data']['packaged_at'] ?? null))
· {{ \Illuminate\Support\Carbon::parse($environment['live']['data']['packaged_at'])->format('Y.m.d H:i') }}
@if ($environment['live'] && $environment['live']['ok'])
<span class="font-mono text-xs text-gray-600 dark:text-gray-300">
élő: {{ substr($environment['live']['data']['commit'] ?? '?', 0, 7) }}
</span>
@elseif ($environment['live'])
<span class="text-xs text-gray-400">élő: nem elérhető</span>
@endif
</div>
<div class="mt-1 text-sm text-gray-600 dark:text-gray-300">
@if ($environment['package'])
<span>napló: <code>{{ substr($environment['package']->to_commit, 0, 7) }}</code></span>
<span class="text-gray-400">·</span>
<span>{{ $environment['package']->deployed_at?->format('Y.m.d H:i') }}</span>
@if ($environment['behind'] !== null)
<span class="text-gray-400">·</span>
<span>{{ $environment['behind'] }} commit lemaradás</span>
@endif
</p>
@else
<p class="text-gray-400">Élő állapot nem lekérdezhető: {{ $environment['live']['error'] }}</p>
@endif
<span class="text-gray-400">Még nincs kirakottként megjelölt csomag.</span>
@endif
</div>
@if ($environment['mismatch'])
<p class="font-semibold">
<p class="mt-1 text-sm font-semibold">
A szerveren futó commit eltér a naplózottól elmaradt vagy félbemaradt feltöltés.
</p>
@endif
</div>
</div>
@endforeach
</div>
</div>
@ -188,10 +196,14 @@
</div>
<div class="{{ $cardClass }}">
<h6 class="mb-4 text-lg font-semibold text-gray-900 dark:text-white">
<h6 class="mb-1 text-lg font-semibold text-gray-900 dark:text-white">
Commitok
<span class="ml-1 text-sm font-normal text-gray-500 dark:text-gray-400">({{ count($commits) }} db)</span>
</h6>
<p class="mb-4 text-xs text-gray-500 dark:text-gray-400">
A hash melletti jelölés mutatja, melyik környezet áll azon a commiton
zöld: a szervertől lekérdezve, szürke: a napló szerint.
</p>
@if ($commits === [])
<p class="text-sm text-gray-500 dark:text-gray-400">Nincs megjeleníthető commit.</p>
@ -229,7 +241,18 @@ class="border-b border-gray-100 dark:border-gray-700 {{ $inRange ? 'bg-primary-5
</x-filament::button>
</div>
</td>
<td class="py-2 pr-4 font-mono text-xs whitespace-nowrap">{{ $commit['short'] }}</td>
<td class="py-2 pr-4 whitespace-nowrap">
<div class="flex items-center gap-1">
<span class="font-mono text-xs">{{ $commit['short'] }}</span>
{{-- Melyik környezet áll ezen a commiton (élő állapot, vagy napló szerint). --}}
@foreach ($commitMarkers[$commit['hash']] ?? [] as $marker)
<x-filament::badge size="xs" :color="$marker['live'] ? 'success' : 'gray'">
{{ $marker['key'] }}
</x-filament::badge>
@endforeach
</div>
</td>
<td class="py-2 pr-4 whitespace-nowrap text-gray-600 dark:text-gray-300">
{{ \Illuminate\Support\Carbon::parse($commit['date'])->format('Y.m.d H:i') }}
</td>

View File

@ -11,14 +11,22 @@
use App\Services\Deployment\GitRepository;
use App\Services\FeatureFlagRegistrar;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Client\Factory;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Livewire\Livewire;
use Symfony\Component\Process\Process;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
// Az oldal betöltéskor lekérdezi a környezetek deploy-version.json-ját - a tesztek
// nem indíthatnak valódi kimenő kérést.
beforeEach(function () {
Http::fake();
});
/**
* Fejlesztői gépet szimuláló környezet: az .env kapcsoló és a stage whitelist rendben.
*/
@ -418,8 +426,9 @@ function deploymentCleanup(string $path): void
$package = $component->get('lastPackage');
// Alapértelmezett célkörnyezet a d2d: azt frissítjük sűrűbben.
expect($package)->not->toBeNull()
->and($package['name'])->toStartWith('deploy_e2e_')
->and($package['name'])->toStartWith('deploy_d2d_')
->and(File::isDirectory($package['path']))->toBeTrue()
->and($package['skipped'])->toBe(['app/Elso.php']);
@ -441,7 +450,7 @@ function deploymentCleanup(string $path): void
$record = DeploymentPackageRecord::query()->latest('id')->first();
expect($record)->not->toBeNull()
->and($record->target)->toBe('e2e')
->and($record->target)->toBe('d2d')
->and($record->to_commit)->toBe($third)
->and($record->deployed_at)->toBeNull()
->and(File::exists($record->zip_path))->toBeTrue();
@ -460,9 +469,10 @@ function deploymentCleanup(string $path): void
$this->actingAs(deploymentDeveloper());
// Az alapértelmezett célkörnyezetre készül, hogy a második oldalbetöltés is ezt töltse elő.
$record = DeploymentPackageRecord::create([
'name' => 'deploy_e2e_teszt',
'target' => 'e2e',
'name' => 'deploy_d2d_teszt',
'target' => 'd2d',
'branch' => 'main',
'from_commit' => $first,
'to_commit' => $second,
@ -524,6 +534,31 @@ function deploymentCleanup(string $path): void
deploymentCleanup($repository['path']);
});
test('az oldal alapból a d2d környezetet ajánlja és betöltéskor lekérdezi az élő állapotot', function () {
deploymentAllowEnvironment();
$repository = deploymentTestRepository();
[, , $third] = $repository['commits'];
// A beforeEach-ben regisztrált catch-all stub minden URL-re illeszkedik, és a
// Http factory az ELSŐ találatot adja vissza - ezért itt új factory kell, különben
// az üres törzsű alapértelmezett válasz nyerne a konkrét stub helyett.
Http::swap(new Factory);
Http::fake([
'*deploy-version.json' => Http::response(['target' => 'd2d', 'commit' => $third, 'branch' => 'main']),
]);
$this->actingAs(deploymentDeveloper());
$component = Livewire::test(DeploymentPackage::class);
expect($component->get('data')['target'])->toBe('d2d')
// A lekérdezés gomb nélkül, magától megtörténik.
->and($component->get('liveVersions')['d2d']['ok'])->toBeTrue()
->and($component->get('liveVersions')['d2d']['data']['commit'])->toBe($third);
deploymentCleanup($repository['path']);
});
test('az oldal nem fogad el kamu commit azonosítót', function () {
deploymentAllowEnvironment();
$repository = deploymentTestRepository();