polish(ui): in-system design refinements from the design review
Surgical consistency/polish tightening within the existing "Tactical Terminal" system — no re-theme, no new tokens/fonts/colors, every change uses an existing @theme utility. From a design review (subagent-cataloged, false-positives rejected). - nav-item: badge tooltip is now a contextual `badge-title` prop instead of a hardcoded "update available" — the Threats count badge showed the wrong tooltip. Sidebar passes a real "N login attempts (24h)" title for Threats and "Update available" for Versions. - empty-state messages (sessions/users/webauthn-keys/login-protection) bumped from the faintest ink-4 to ink-3 — when a list is empty that line is the focal point, not meta. - modal actions: fail2ban-config / hardening-action / system-update submit buttons go from the softer `accent` to solid `primary` so the real action reads as primary (cancel stays secondary; the done-state close is unchanged). - servers list: CPU/RAM mini-bar labels ink-4 -> ink-3 to match their values. - edit-credential: top-level error now uses the standard bordered offline error box (border-offline/25 bg-offline/10 + alert icon), matching firewall-rule. - file-editor: title + path get a native `title=` tooltip so a truncated long path is legible. - add-ssh-key label rhythm (mb-1.5 -> mb-1); login-protection textarea vertical padding (py-2 -> py-2.5, the textarea norm); webauthn-keys add button w-full sm:w-auto so it isn't a mobile orphan; app.css font comment de-staled (fonts are self-hosted). Deliberately NOT changed (would harm the design, not help): mail from_name kept sans vs from_address mono (name = prose, address = technical token — the convention); the Memory metric tone kept dynamic/threshold-based (health signal) rather than forced cyan. Verified: vite build compiles all utilities, 617 tests green, de/en lang parity, DOM render check of the badge + button variants. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>feat/v1-foundation
parent
0943d56201
commit
930f78efbd
|
|
@ -26,6 +26,7 @@ return [
|
|||
'nav_versions' => 'Version',
|
||||
'nav_help' => 'Hilfe',
|
||||
'update_available' => 'Update verfügbar',
|
||||
'threats_badge' => ':count Login-Versuche (24h)',
|
||||
|
||||
// Sidebar — user / 2FA badge
|
||||
'twofa_on' => '2FA aktiv',
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ return [
|
|||
'nav_versions' => 'Version',
|
||||
'nav_help' => 'Help',
|
||||
'update_available' => 'Update available',
|
||||
'threats_badge' => ':count login attempts (24h)',
|
||||
|
||||
// Sidebar — user / 2FA badge
|
||||
'twofa_on' => '2FA on',
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@
|
|||
--color-line-soft: rgba(255, 255, 255, 0.055);
|
||||
--color-line-strong: rgba(255, 255, 255, 0.17);
|
||||
|
||||
/* fonts — TODO(§12): self-host the .woff2; falls back until then */
|
||||
/* fonts — self-hosted .woff2 via the @font-face blocks above (R14); system stack is the fallback */
|
||||
--font-display: 'Chakra Petch', ui-sans-serif, system-ui, sans-serif;
|
||||
--font-sans: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', monospace;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@props(['icon', 'href' => '#', 'active' => false, 'badge' => null])
|
||||
@props(['icon', 'href' => '#', 'active' => false, 'badge' => null, 'badgeTitle' => null])
|
||||
<a {{ $attributes->class([
|
||||
'group flex min-h-11 items-center gap-3 rounded-md border px-3 py-2 text-sm transition-colors',
|
||||
'border-accent/25 bg-accent/10 text-ink shadow-[inset_2px_0_0_var(--color-accent)]' => $active,
|
||||
|
|
@ -9,6 +9,6 @@
|
|||
<span class="truncate">{{ $slot }}</span>
|
||||
@if ($badge !== null && $badge !== '')
|
||||
<span class="ml-auto inline-flex h-4 min-w-4 shrink-0 items-center justify-center rounded-full bg-accent px-1 font-mono text-[10px] font-semibold tabular-nums text-void"
|
||||
title="{{ __('shell.update_available') }}">{{ $badge }}</span>
|
||||
@if ($badgeTitle) title="{{ $badgeTitle }}" @endif>{{ $badge }}</span>
|
||||
@endif
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -51,9 +51,9 @@
|
|||
<x-nav-item icon="settings" href="/settings" :active="request()->is('settings*')" data-tour="settings">{{ __('shell.nav_settings') }}</x-nav-item>
|
||||
<x-nav-item icon="shield" href="/system" :active="request()->is('system*')">{{ __('shell.nav_system') }}</x-nav-item>
|
||||
@can('manage-panel')
|
||||
<x-nav-item icon="shield-alert" href="/threats" :active="request()->is('threats*')" :badge="$threatCount > 0 ? $threatCount : null">{{ __('shell.nav_threats') }}</x-nav-item>
|
||||
<x-nav-item icon="shield-alert" href="/threats" :active="request()->is('threats*')" :badge="$threatCount > 0 ? $threatCount : null" :badge-title="$threatCount > 0 ? __('shell.threats_badge', ['count' => $threatCount]) : null">{{ __('shell.nav_threats') }}</x-nav-item>
|
||||
@endcan
|
||||
<x-nav-item icon="tag" href="/versions" :active="request()->is('versions*')" :badge="$updateAvailable ? '1' : null">{{ __('shell.nav_versions') }}</x-nav-item>
|
||||
<x-nav-item icon="tag" href="/versions" :active="request()->is('versions*')" :badge="$updateAvailable ? '1' : null" :badge-title="$updateAvailable ? __('shell.update_available') : null">{{ __('shell.nav_versions') }}</x-nav-item>
|
||||
@if (config('clusev.release_controls'))
|
||||
<x-nav-item icon="tag" href="/release" :active="request()->is('release*')">
|
||||
{{ __('release.nav') }}
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label class="mb-1.5 block font-mono text-[11px] uppercase tracking-wider text-ink-3">{{ __('modals.add_ssh_key.name_label') }}</label>
|
||||
<label class="mb-1 block font-mono text-[11px] uppercase tracking-wider text-ink-3">{{ __('modals.add_ssh_key.name_label') }}</label>
|
||||
<input wire:model="keyName" type="text" maxlength="64" placeholder="{{ __('modals.add_ssh_key.name_placeholder') }}"
|
||||
class="h-9 w-full rounded-md border border-line bg-inset px-3 font-mono text-sm text-ink placeholder:text-ink-4 focus:border-accent/40 focus:outline-none" />
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="mb-1.5 flex items-center justify-between">
|
||||
<div class="mb-1 flex items-center justify-between">
|
||||
<label class="font-mono text-[11px] uppercase tracking-wider text-ink-3">{{ __('modals.add_ssh_key.public_key_label') }}</label>
|
||||
<button type="button" wire:click="generate" wire:target="generate" wire:loading.attr="disabled" class="font-mono text-[11px] text-accent-text transition-colors hover:text-accent disabled:opacity-50">
|
||||
<span wire:loading.remove wire:target="generate">{{ __('modals.add_ssh_key.generate') }}</span>
|
||||
|
|
|
|||
|
|
@ -47,9 +47,10 @@
|
|||
@endif
|
||||
|
||||
@if ($error)
|
||||
<p class="flex items-center gap-1.5 font-mono text-[11px] text-offline">
|
||||
<x-icon name="alert" class="h-3.5 w-3.5 shrink-0" />{{ $error }}
|
||||
</p>
|
||||
<div class="flex items-center gap-2.5 rounded-md border border-offline/25 bg-offline/10 px-3.5 py-3">
|
||||
<x-icon name="alert" class="h-4 w-4 shrink-0 text-offline" />
|
||||
<p class="text-sm text-ink-2">{{ $error }}</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
<div class="mt-6 flex items-center justify-end gap-2">
|
||||
<x-btn variant="secondary" wire:click="$dispatch('closeModal')">{{ $loaded ? __('common.cancel') : __('common.close') }}</x-btn>
|
||||
@if ($loaded)
|
||||
<x-btn variant="accent" wire:click="save" wire:target="save" wire:loading.attr="disabled">
|
||||
<x-btn variant="primary" wire:click="save" wire:target="save" wire:loading.attr="disabled">
|
||||
<x-icon name="shield" class="h-3.5 w-3.5" wire:loading.remove wire:target="save" />
|
||||
<svg wire:loading wire:target="save" class="h-3.5 w-3.5 animate-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M21 12a9 9 0 1 1-6.219-8.56" stroke-linecap="round" /></svg>
|
||||
{{ __('common.save') }}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
<x-icon name="file" class="h-5 w-5" />
|
||||
</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<h2 class="truncate font-display text-base font-semibold text-ink">{{ $name }}</h2>
|
||||
<p class="mt-1 truncate font-mono text-[11px] text-ink-3">{{ $path }}</p>
|
||||
<h2 class="truncate font-display text-base font-semibold text-ink" title="{{ $name }}">{{ $name }}</h2>
|
||||
<p class="mt-1 truncate font-mono text-[11px] text-ink-3" title="{{ $path }}">{{ $path }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
@else
|
||||
<x-btn variant="secondary" wire:click="$dispatch('closeModal')">{{ __('common.cancel') }}</x-btn>
|
||||
@unless ($error)
|
||||
<x-btn variant="accent" wire:click="apply" wire:target="apply" wire:loading.attr="disabled">
|
||||
<x-btn variant="primary" wire:click="apply" wire:target="apply" wire:loading.attr="disabled">
|
||||
<x-icon name="shield" class="h-3.5 w-3.5" wire:loading.remove wire:target="apply" />
|
||||
<svg wire:loading wire:target="apply" class="h-3.5 w-3.5 animate-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M21 12a9 9 0 1 1-6.219-8.56" stroke-linecap="round" /></svg>
|
||||
{{ __('common.apply') }}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
@else
|
||||
<x-btn variant="secondary" wire:click="$dispatch('closeModal')">{{ __('common.cancel') }}</x-btn>
|
||||
@if (! $error && $supported)
|
||||
<x-btn variant="accent" wire:click="upgrade" wire:target="upgrade" wire:loading.attr="disabled">
|
||||
<x-btn variant="primary" wire:click="upgrade" wire:target="upgrade" wire:loading.attr="disabled">
|
||||
<x-icon name="rotate" class="h-3.5 w-3.5" wire:loading.remove wire:target="upgrade" />
|
||||
<svg wire:loading wire:target="upgrade" class="h-3.5 w-3.5 animate-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M21 12a9 9 0 1 1-6.219-8.56" stroke-linecap="round" /></svg>
|
||||
{{ __('modals.system_update.submit') }}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
<div class="hidden shrink-0 items-center gap-4 md:flex">
|
||||
<div class="w-20">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-mono text-[10px] uppercase text-ink-4">CPU</span>
|
||||
<span class="font-mono text-[10px] uppercase text-ink-3">CPU</span>
|
||||
<span class="tabular font-mono text-[10px] text-ink-3">{{ $server->cpu }}%</span>
|
||||
</div>
|
||||
<div class="mt-1 h-1 w-full overflow-hidden rounded-full bg-line">
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
</div>
|
||||
<div class="w-20">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-mono text-[10px] uppercase text-ink-4">RAM</span>
|
||||
<span class="font-mono text-[10px] uppercase text-ink-3">RAM</span>
|
||||
<span class="tabular font-mono text-[10px] text-ink-3">{{ $server->mem }}%</span>
|
||||
</div>
|
||||
<div class="mt-1 h-1 w-full overflow-hidden rounded-full bg-line">
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<div>
|
||||
<label class="{{ $label }}">{{ __('settings.lp_whitelist') }}</label>
|
||||
<textarea wire:model="whitelist" rows="4" class="{{ $field }} h-auto py-2 font-mono"></textarea>
|
||||
<textarea wire:model="whitelist" rows="4" class="{{ $field }} h-auto py-2.5 font-mono"></textarea>
|
||||
<p class="mt-1 font-mono text-[11px] text-ink-4">{{ __('settings.lp_whitelist_hint') }}</p>
|
||||
@error('whitelist') <p class="{{ $err }}"><x-icon name="alert" class="h-3.5 w-3.5 shrink-0" />{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
</x-modal-trigger>
|
||||
</div>
|
||||
@empty
|
||||
<p class="py-3 font-mono text-[11px] text-ink-4">{{ __('settings.lp_no_bans') }}</p>
|
||||
<p class="py-3 font-mono text-[11px] text-ink-3">{{ __('settings.lp_no_bans') }}</p>
|
||||
@endforelse
|
||||
</div>
|
||||
@if ($bans->isNotEmpty())
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="py-3 font-mono text-[11px] text-ink-4">{{ __('sessions.none') }}</p>
|
||||
<p class="py-3 font-mono text-[11px] text-ink-3">{{ __('sessions.none') }}</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="py-3 font-mono text-[11px] text-ink-4">{{ __('accounts.none') }}</p>
|
||||
<p class="py-3 font-mono text-[11px] text-ink-3">{{ __('accounts.none') }}</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</x-panel>
|
||||
|
|
|
|||
|
|
@ -24,14 +24,14 @@
|
|||
</x-modal-trigger>
|
||||
</div>
|
||||
@empty
|
||||
<p class="py-3 font-mono text-[11px] text-ink-4">{{ __('auth.webauthn_none') }}</p>
|
||||
<p class="py-3 font-mono text-[11px] text-ink-3">{{ __('auth.webauthn_none') }}</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2 sm:flex-row" x-data>
|
||||
<input wire:model="newName" type="text" maxlength="60" placeholder="{{ __('auth.webauthn_name_placeholder') }}"
|
||||
class="h-9 w-full rounded-md border border-line bg-inset px-3 text-sm text-ink placeholder:text-ink-4 focus:border-accent/40 focus:outline-none" />
|
||||
<x-btn variant="accent" class="shrink-0" x-on:click="window.clusevWebauthn?.register($wire)">
|
||||
<x-btn variant="accent" class="w-full shrink-0 sm:w-auto" x-on:click="window.clusevWebauthn?.register($wire)">
|
||||
<x-icon name="plus" class="h-3.5 w-3.5" /> {{ __('auth.webauthn_add') }}
|
||||
</x-btn>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue