feat(release): live pipeline rail + test-server input on the Release page

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/v1-foundation
boban 2026-06-23 01:55:14 +02:00
parent 0ac819e4ad
commit 787e48d8fd
3 changed files with 91 additions and 28 deletions

View File

@ -35,6 +35,19 @@ return [
'pipe_sub_mirror' => 'Automatischer Push-Mirror',
'pipe_sub_ci' => 'Tests laufen',
'pipe_sub_staging' => 'Beta wird ausgerollt',
'pipe_sub_test' => 'Beta auf deinem Test-Server',
'no_beta_running' => 'Keine Beta in Arbeit.',
'step_test' => 'Test-Server',
'state_done' => 'fertig',
'state_running' => 'läuft …',
'state_failed' => 'fehlgeschlagen',
'state_pending' => 'ausstehend',
'state_unknown' => 'unbekannt',
'state_unset' => 'URL setzen',
'save' => 'Speichern',
'test_server_saved' => 'Test-Server gespeichert.',
'test_server_cleared' => 'Test-Server entfernt.',
'test_server_invalid' => 'Ungültige URL (http/https).',
// confirm modal (R5)
'confirm_title' => 'Beta auf Staging schneiden?',

View File

@ -35,6 +35,19 @@ return [
'pipe_sub_mirror' => 'Automatic push mirror',
'pipe_sub_ci' => 'Tests run',
'pipe_sub_staging' => 'Beta is deployed',
'pipe_sub_test' => 'Beta on your test server',
'no_beta_running' => 'No beta in flight.',
'step_test' => 'Test server',
'state_done' => 'done',
'state_running' => 'running …',
'state_failed' => 'failed',
'state_pending' => 'pending',
'state_unknown' => 'unknown',
'state_unset' => 'set a URL',
'save' => 'Save',
'test_server_saved' => 'Test server saved.',
'test_server_cleared' => 'Test server cleared.',
'test_server_invalid' => 'Invalid URL (http/https).',
// confirm modal (R5)
'confirm_title' => 'Cut a staging beta?',

View File

@ -8,13 +8,6 @@
$tiles[] = ['target' => $targets['patch'], 'label' => __('release.kind_patch'), 'icon' => 'tag', 'primary' => false];
$tiles[] = ['target' => $targets['minor'], 'label' => __('release.kind_minor'), 'icon' => 'tag', 'primary' => false];
$tiles[] = ['target' => $targets['major'], 'label' => __('release.kind_major'), 'icon' => 'git-branch', 'primary' => false];
$steps = [
['name' => 'Gitea', 'sub' => __('release.pipe_sub_gitea'), 'active' => $pendingId !== null],
['name' => 'GitHub-privat', 'sub' => __('release.pipe_sub_mirror'), 'active' => false],
['name' => 'CI', 'sub' => __('release.pipe_sub_ci'), 'active' => false],
['name' => 'Staging', 'sub' => __('release.pipe_sub_staging'), 'active' => false],
];
@endphp
<div class="space-y-5">
@ -123,28 +116,72 @@
@endif
</div>
{{-- Right: pipeline rail --}}
{{-- Right: live pipeline rail --}}
<x-panel :title="__('release.pipeline')">
<ol>
@foreach ($steps as $s)
<li class="flex gap-3" wire:key="step-{{ $loop->index }}">
<div class="flex flex-col items-center">
<span @class([
'grid h-6 w-6 shrink-0 place-items-center rounded-full border font-mono text-[10px]',
'border-accent/40 bg-accent/15 text-accent-text' => $s['active'],
'border-line bg-inset text-ink-3' => ! $s['active'],
])>{{ $loop->iteration }}</span>
@unless ($loop->last)
<span class="my-1 w-px flex-1 bg-line" aria-hidden="true"></span>
@endunless
</div>
<div @class(['min-w-0', 'pb-5' => ! $loop->last])>
<p class="font-mono text-xs {{ $s['active'] ? 'text-accent-text' : 'text-ink' }}">{{ $s['name'] }}</p>
<p class="mt-0.5 font-mono text-[11px] text-ink-4">{{ $s['sub'] }}</p>
</div>
</li>
@endforeach
</ol>
@if ($pipeline === null)
<p class="font-mono text-[11px] text-ink-4">{{ __('release.no_beta_running') }}</p>
@else
<ol>
@foreach ($pipeline['steps'] as $s)
@php
$names = [
'tag' => 'Gitea', 'mirror' => 'GitHub-privat', 'ci' => 'CI', 'test' => __('release.step_test'),
];
$sub = [
'tag' => __('release.pipe_sub_gitea'), 'mirror' => __('release.pipe_sub_mirror'),
'ci' => __('release.pipe_sub_ci'), 'test' => __('release.pipe_sub_test'),
];
$detail = match ($s['state']) {
'done' => $s['detail'] ?? __('release.state_done'),
'running' => __('release.state_running'),
'failed' => __('release.state_failed'),
'pending' => __('release.state_pending'),
'unset' => __('release.state_unset'),
default => __('release.state_unknown'),
};
@endphp
<li class="flex gap-3" wire:key="pstep-{{ $s['key'] }}">
<div class="flex flex-col items-center">
<span class="grid h-6 w-6 shrink-0 place-items-center rounded-full border border-line bg-inset">
@if ($s['state'] === 'running')
<x-icon name="rotate" class="h-3 w-3 animate-spin text-warning" />
@elseif ($s['state'] === 'done')
<x-status-dot status="online" />
@elseif ($s['state'] === 'failed')
<x-status-dot status="offline" />
@elseif ($s['state'] === 'pending')
<x-status-dot status="pending" />
@else
<span class="h-1.5 w-1.5 rounded-full bg-ink-4"></span>
@endif
</span>
@unless ($loop->last)
<span class="my-1 w-px flex-1 bg-line" aria-hidden="true"></span>
@endunless
</div>
<div @class(['min-w-0', 'pb-5' => ! $loop->last])>
<p class="font-mono text-xs text-ink">{{ $names[$s['key']] }}</p>
@if ($s['key'] === 'ci' && $s['url'])
<a href="{{ $s['url'] }}" target="_blank" rel="noopener" class="mt-0.5 block font-mono text-[11px] text-cyan hover:text-cyan-bright">{{ $detail }}</a>
@else
<p class="mt-0.5 font-mono text-[11px] text-ink-4">{{ $detail }}</p>
@endif
@if ($s['key'] === 'test')
<div class="mt-2 flex items-center gap-2">
<input type="url" wire:model="testServer" placeholder="https://test-server"
class="h-8 w-full rounded-md border border-line bg-inset px-2.5 font-mono text-[11px] text-ink placeholder:text-ink-4 focus:border-accent/40 focus:outline-none" />
<x-btn variant="secondary" wire:click="saveTestServer($wire.testServer)" wire:loading.attr="disabled">{{ __('release.save') }}</x-btn>
</div>
@endif
</div>
</li>
@endforeach
</ol>
@if ($pipeline['ciRunning'])
<span class="hidden" wire:poll.5s="pollPipeline"></span>
@endif
@endif
</x-panel>
</div>
</div>