design(release): rebuild the Release page in the Tactical Terminal system
Replace the bland version + four plain buttons with a composed layout: a current- version hero (mono version, channel + source), tactile bump tiles showing the current → target transition with a Beta-tag badge (continue-beta highlighted accent), styled running/last-pushed states, and a vertical pipeline rail (Gitea → GitHub-privat → CI → Staging). Tokens-only, responsive 375/768/1280, German, no emoji; the R5 confirm flow + component contract are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/v1-foundation
parent
25516ad323
commit
6a284caa44
|
|
@ -3,21 +3,45 @@
|
|||
return [
|
||||
'title' => 'Release',
|
||||
'nav' => 'Release',
|
||||
'eyebrow' => 'Release-Pipeline',
|
||||
'heading' => 'Release-Steuerung',
|
||||
'subtitle' => 'Beta auf Staging schneiden — nur auf der Dev-Instanz.',
|
||||
'dev_instance' => 'Dev-Instanz',
|
||||
|
||||
'current' => 'Aktuelle Version',
|
||||
'continue_beta' => 'Nächste Beta (:v)',
|
||||
'bump_patch' => 'Patch-Beta (:v)',
|
||||
'bump_minor' => 'Minor-Beta (:v)',
|
||||
'bump_major' => 'Major-Beta (:v)',
|
||||
'channel' => 'Kanal',
|
||||
'source' => 'Quelle',
|
||||
|
||||
// option tiles
|
||||
'choose' => 'Beta schneiden',
|
||||
'kind_continue' => 'Nächste Beta',
|
||||
'kind_patch' => 'Patch',
|
||||
'kind_minor' => 'Minor',
|
||||
'kind_major' => 'Major',
|
||||
'tile_beta' => 'Beta-Tag',
|
||||
'hint' => 'Schneidet einen Beta-Tag und pusht ihn nach Gitea → Mirror → CI → Staging.',
|
||||
'confirm_title' => 'Beta schneiden?',
|
||||
'confirm_body' => 'Beta :target wird geschnitten und nach Gitea → Mirror → CI → Staging gepusht. Das stößt einen Deploy auf der Staging-Instanz an.',
|
||||
'confirm_action' => 'Schneiden & pushen',
|
||||
'staged_label' => 'Zuletzt geschnitten:',
|
||||
|
||||
// running / result
|
||||
'running_title' => 'Release läuft',
|
||||
'running' => 'Beta :target wird geschnitten und gepusht …',
|
||||
'in_progress' => 'läuft',
|
||||
'staged' => 'Beta :tag gepusht → Mirror → CI → Staging.',
|
||||
'staged_label' => 'Zuletzt gepusht:',
|
||||
'failed' => 'Release fehlgeschlagen: :reason',
|
||||
|
||||
// pipeline rail
|
||||
'pipeline' => 'Pipeline',
|
||||
'pipe_sub_gitea' => 'Quelle · Tag wird hier erzeugt',
|
||||
'pipe_sub_mirror' => 'Automatischer Push-Mirror',
|
||||
'pipe_sub_ci' => 'Tests laufen',
|
||||
'pipe_sub_staging' => 'Beta wird ausgerollt',
|
||||
|
||||
// confirm modal (R5)
|
||||
'confirm_title' => 'Beta auf Staging schneiden?',
|
||||
'confirm_body' => 'Schneidet eine Beta von :target, taggt sie und pusht nach Gitea → Mirror → CI → Staging. Der Tag ist unwiderruflich.',
|
||||
'confirm_action' => 'Beta schneiden',
|
||||
|
||||
// errors
|
||||
'invalid_target' => 'Ungültige Ziel-Version.',
|
||||
'throttled' => 'Zu viele Versuche — in :seconds s erneut.',
|
||||
'write_failed' => 'Anfrage konnte nicht geschrieben werden (Signal-Ordner nicht beschreibbar).',
|
||||
|
|
|
|||
|
|
@ -3,21 +3,45 @@
|
|||
return [
|
||||
'title' => 'Release',
|
||||
'nav' => 'Release',
|
||||
'eyebrow' => 'Release pipeline',
|
||||
'heading' => 'Release control',
|
||||
'subtitle' => 'Cut a staging beta — dev instance only.',
|
||||
'dev_instance' => 'Dev instance',
|
||||
|
||||
'current' => 'Current version',
|
||||
'continue_beta' => 'Next beta (:v)',
|
||||
'bump_patch' => 'Patch beta (:v)',
|
||||
'bump_minor' => 'Minor beta (:v)',
|
||||
'bump_major' => 'Major beta (:v)',
|
||||
'channel' => 'Channel',
|
||||
'source' => 'Source',
|
||||
|
||||
// option tiles
|
||||
'choose' => 'Cut a beta',
|
||||
'kind_continue' => 'Next beta',
|
||||
'kind_patch' => 'Patch',
|
||||
'kind_minor' => 'Minor',
|
||||
'kind_major' => 'Major',
|
||||
'tile_beta' => 'Beta tag',
|
||||
'hint' => 'Cuts a beta tag and pushes it to Gitea → mirror → CI → staging.',
|
||||
'confirm_title' => 'Cut beta?',
|
||||
'confirm_body' => 'Beta :target will be cut and pushed to Gitea → mirror → CI → staging. This triggers a deploy on the staging instance.',
|
||||
'confirm_action' => 'Cut & push',
|
||||
'staged_label' => 'Last cut:',
|
||||
|
||||
// running / result
|
||||
'running_title' => 'Release running',
|
||||
'running' => 'Cutting and pushing beta :target …',
|
||||
'in_progress' => 'running',
|
||||
'staged' => 'Beta :tag pushed → mirror → CI → staging.',
|
||||
'staged_label' => 'Last pushed:',
|
||||
'failed' => 'Release failed: :reason',
|
||||
|
||||
// pipeline rail
|
||||
'pipeline' => 'Pipeline',
|
||||
'pipe_sub_gitea' => 'Source · tag is cut here',
|
||||
'pipe_sub_mirror' => 'Automatic push mirror',
|
||||
'pipe_sub_ci' => 'Tests run',
|
||||
'pipe_sub_staging' => 'Beta is deployed',
|
||||
|
||||
// confirm modal (R5)
|
||||
'confirm_title' => 'Cut a staging beta?',
|
||||
'confirm_body' => 'Cuts a beta of :target, tags it and pushes to Gitea → mirror → CI → staging. The tag is irreversible.',
|
||||
'confirm_action' => 'Cut beta',
|
||||
|
||||
// errors
|
||||
'invalid_target' => 'Invalid target version.',
|
||||
'throttled' => 'Too many attempts — retry in :seconds s.',
|
||||
'write_failed' => 'Could not write the request (signal directory not writable).',
|
||||
|
|
|
|||
|
|
@ -1,45 +1,150 @@
|
|||
<div class="space-y-6">
|
||||
<div class="space-y-1.5">
|
||||
<h1 class="font-display text-2xl font-bold tracking-tight text-ink">{{ __('release.heading') }}</h1>
|
||||
<p class="font-mono text-xs text-ink-3">{{ __('release.subtitle') }}</p>
|
||||
@php
|
||||
$isBeta = str_contains($current, '-beta');
|
||||
|
||||
$tiles = [];
|
||||
if (isset($targets['continueBeta'])) {
|
||||
$tiles[] = ['target' => $targets['continueBeta'], 'label' => __('release.kind_continue'), 'icon' => 'rotate', 'primary' => true];
|
||||
}
|
||||
$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">
|
||||
{{-- Header --}}
|
||||
<div class="flex flex-wrap items-end justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-mono text-[11px] uppercase tracking-[0.2em] text-accent-text">{{ __('release.eyebrow') }}</p>
|
||||
<h2 class="mt-0.5 font-display text-xl font-semibold text-ink sm:text-2xl">{{ __('release.heading') }}</h2>
|
||||
<p class="mt-1 font-mono text-xs text-ink-3">{{ __('release.subtitle') }}</p>
|
||||
</div>
|
||||
<x-badge tone="cyan">{{ __('release.dev_instance') }}</x-badge>
|
||||
</div>
|
||||
|
||||
<x-panel>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<span class="font-mono text-[11px] uppercase tracking-wider text-ink-3">{{ __('release.current') }}</span>
|
||||
<span class="font-mono text-lg text-ink">{{ $current }}</span>
|
||||
<div class="grid gap-5 xl:grid-cols-[minmax(0,1fr)_22rem]">
|
||||
{{-- Left: current version + action --}}
|
||||
<div class="space-y-5">
|
||||
{{-- Current version hero --}}
|
||||
<x-panel :padded="false">
|
||||
<div class="flex flex-col gap-5 p-5 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="grid h-12 w-12 shrink-0 place-items-center rounded-lg border border-accent/25 bg-accent/10">
|
||||
<x-icon name="tag" class="h-5 w-5 text-accent" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-mono text-[11px] uppercase tracking-wider text-ink-3">{{ __('release.current') }}</p>
|
||||
<p class="font-mono text-2xl font-semibold leading-tight text-ink">{{ $current }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<dl class="flex gap-7 font-mono text-[11px]">
|
||||
<div>
|
||||
<dt class="uppercase tracking-wider text-ink-4">{{ __('release.channel') }}</dt>
|
||||
<dd class="mt-1 {{ $isBeta ? 'text-warning' : 'text-online' }}">{{ $isBeta ? 'beta' : 'stable' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="uppercase tracking-wider text-ink-4">{{ __('release.source') }}</dt>
|
||||
<dd class="mt-1 text-ink-2">Gitea</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</x-panel>
|
||||
|
||||
@if ($pendingId !== null)
|
||||
{{-- Running --}}
|
||||
<div class="flex items-center gap-4 rounded-lg border border-warning/30 bg-warning/5 px-5 py-4" wire:poll.5s="pollResult">
|
||||
<div class="grid h-10 w-10 shrink-0 place-items-center rounded-lg border border-warning/30 bg-warning/10">
|
||||
<x-icon name="rotate" class="h-5 w-5 animate-spin text-warning" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<p class="font-display text-sm font-semibold text-ink">{{ __('release.running_title') }}</p>
|
||||
<p class="mt-0.5 truncate font-mono text-[11px] text-ink-3">{{ __('release.running', ['target' => $pendingTarget]) }}</p>
|
||||
</div>
|
||||
<x-status-pill status="pending" class="ml-auto shrink-0">{{ __('release.in_progress') }}</x-status-pill>
|
||||
</div>
|
||||
@else
|
||||
@if ($lastTag !== null)
|
||||
{{-- Last successful push --}}
|
||||
<div class="flex flex-wrap items-center gap-2.5 rounded-lg border border-online/25 bg-online/10 px-5 py-3.5">
|
||||
<x-status-dot status="online" />
|
||||
<span class="font-mono text-[11px] text-ink-3">{{ __('release.staged_label') }}</span>
|
||||
<span class="font-mono text-xs font-semibold text-online">{{ $lastTag }}</span>
|
||||
<span class="font-mono text-[11px] text-ink-4">→ Mirror → CI → Staging</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Option tiles --}}
|
||||
<div>
|
||||
<p class="mb-2.5 font-mono text-[11px] uppercase tracking-wider text-ink-3">{{ __('release.choose') }}</p>
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
@foreach ($tiles as $t)
|
||||
<button type="button"
|
||||
wire:key="tile-{{ $t['target'] }}"
|
||||
wire:click="confirmDeployStaging('{{ $t['target'] }}')"
|
||||
wire:loading.attr="disabled"
|
||||
@class([
|
||||
'group flex flex-col gap-3 rounded-lg border p-4 text-left transition-colors focus:outline-none focus-visible:border-accent/50',
|
||||
'border-accent/25 bg-accent/10 hover:bg-accent/15' => $t['primary'],
|
||||
'border-line bg-inset hover:border-line-strong hover:bg-raised' => ! $t['primary'],
|
||||
])>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span @class([
|
||||
'font-display text-sm font-semibold',
|
||||
'text-accent-text' => $t['primary'],
|
||||
'text-ink' => ! $t['primary'],
|
||||
])>{{ $t['label'] }}</span>
|
||||
<x-icon :name="$t['icon']" @class([
|
||||
'h-4 w-4 shrink-0',
|
||||
'text-accent' => $t['primary'],
|
||||
'text-ink-4 transition-colors group-hover:text-ink-2' => ! $t['primary'],
|
||||
]) />
|
||||
</div>
|
||||
<div class="flex items-center gap-2 font-mono text-xs">
|
||||
<span class="text-ink-4">{{ $current }}</span>
|
||||
<x-icon name="chevron-right" class="h-3.5 w-3.5 shrink-0 text-ink-4" />
|
||||
<span @class([
|
||||
'font-semibold',
|
||||
'text-accent-text' => $t['primary'],
|
||||
'text-ink' => ! $t['primary'],
|
||||
])>{{ $t['target'] }}</span>
|
||||
<x-badge tone="{{ $t['primary'] ? 'accent' : 'neutral' }}" class="ml-auto">{{ __('release.tile_beta') }}</x-badge>
|
||||
</div>
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
<p class="mt-3 font-mono text-[11px] leading-relaxed text-ink-4">{{ __('release.hint') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($lastTag !== null)
|
||||
<p class="mt-4 flex items-center gap-1.5 font-mono text-[11px] text-online">
|
||||
<x-icon name="tag" class="h-3.5 w-3.5 shrink-0" />
|
||||
{{ __('release.staged_label') }} <span class="font-medium">{{ $lastTag }}</span>
|
||||
</p>
|
||||
@endif
|
||||
|
||||
@if ($pendingId !== null)
|
||||
<p class="mt-5 flex items-center gap-1.5 font-mono text-[11px] text-warning" wire:poll.5s="pollResult">
|
||||
<x-icon name="rotate" class="h-3.5 w-3.5 shrink-0 animate-spin" />
|
||||
{{ __('release.running', ['target' => $pendingTarget]) }}
|
||||
</p>
|
||||
@else
|
||||
<div class="mt-5 grid gap-2.5 sm:grid-cols-2">
|
||||
@if (isset($targets['continueBeta']))
|
||||
<x-btn variant="primary" wire:click="confirmDeployStaging('{{ $targets['continueBeta'] }}')" wire:loading.attr="disabled">
|
||||
{{ __('release.continue_beta', ['v' => $targets['continueBeta']]) }}
|
||||
</x-btn>
|
||||
@endif
|
||||
<x-btn variant="secondary" wire:click="confirmDeployStaging('{{ $targets['patch'] }}')" wire:loading.attr="disabled">
|
||||
{{ __('release.bump_patch', ['v' => $targets['patch']]) }}
|
||||
</x-btn>
|
||||
<x-btn variant="secondary" wire:click="confirmDeployStaging('{{ $targets['minor'] }}')" wire:loading.attr="disabled">
|
||||
{{ __('release.bump_minor', ['v' => $targets['minor']]) }}
|
||||
</x-btn>
|
||||
<x-btn variant="secondary" wire:click="confirmDeployStaging('{{ $targets['major'] }}')" wire:loading.attr="disabled">
|
||||
{{ __('release.bump_major', ['v' => $targets['major']]) }}
|
||||
</x-btn>
|
||||
</div>
|
||||
<p class="mt-3 font-mono text-[11px] text-ink-4">{{ __('release.hint') }}</p>
|
||||
@endif
|
||||
</x-panel>
|
||||
{{-- Right: 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>
|
||||
</x-panel>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue