{{ __('admin_settings.title') }}

{{ __('admin_settings.subtitle') }}

{{-- Version & update --}} @if ($canManageSite) {{-- Polls itself: an operator watching an update run should not have to reload the page to find out whether it finished. Fast while something is happening, slow when nothing is. wire:poll alone is not enough, because the thing being watched restarts the thing doing the watching. Mid-run the requests fail, and what answers afterwards is a NEW build being questioned by the old page's JavaScript — so the card sat on "läuft" until somebody reloaded. That gap is now covered by the full-screen overlay in layouts/admin.blade.php, which binds the same watcher once for every console page rather than here — this card no longer binds it itself, so the two do not double-poll while this page is open. wire:poll below is unrelated and stays: it is Livewire's own refresh of this card's own details (log tail, elapsed time) for as long as a Livewire connection exists. --}}

{{ __('admin_settings.update_title') }}

@if ($update['running']) {{ __('admin_settings.update_running') }} @elseif ($update['checking']) {{ __('admin_settings.update_checking') }} @elseif ($update['available']) {{ __('admin_settings.update_available', ['n' => $update['behind']]) }} @elseif ($update['behind'] === 0) {{ __('admin_settings.update_current') }} @else {{-- Never shown as "up to date": not knowing and being current are different answers. --}} {{ __('admin_settings.update_unknown') }} @endif
{{ __('admin_settings.update_version') }}:
{{ $update['version'] }}@if ($update['commit']) · {{ substr($update['commit'], 0, 7) }}@endif
@if ($update['source'])
{{ __('admin_settings.update_source') }}:
{{ $update['source'] }}
@endif @if ($update['deployed_at'])
{{ __('admin_settings.update_deployed') }}:
{{ $update['deployed_at']->diffForHumans() }}
@endif @if ($update['checked_at'])
{{ __('admin_settings.update_checked') }}:
{{ $update['checked_at']->diffForHumans() }}
@endif
{{-- Bound, not @disabled(): the directive compiles to inline PHP inside the component tag, and Blade then stops seeing a component. Two separate actions, always offered together: looking and applying are different intentions, and an operator who only wants to know whether anything is new must not be routed through a real deployment to find out. "Jetzt aktualisieren" is offered regardless of $available. "behind" is a READING, taken up to a minute ago — it is not the state of the world. Push a commit and for that one minute the console would otherwise insist everything is current and refuse to do anything about it, which is precisely how it was reported. The agent fetches before it decides, so asking against a stale reading costs one fetch and finds nothing; being locked out costs the deployment. Both disabled for the two cases where neither would go anywhere: a run already in flight, and no agent. Also disabled while a check is already pending, so a second click cannot collide with the first and be refused. --}}
@if ($update['running']) {{ __('admin_settings.update_running') }} @elseif (! $update['agent_seen']) {{ __('admin_settings.update_unknown') }} @else {{ $update['checking'] ? __('admin_settings.update_checking') : __('admin_settings.update_check') }} {{ __('admin_settings.update_now') }} @endif
@if (! $update['agent_seen']) {{ __('admin_settings.update_no_agent') }} @elseif ($update['last_error']) {{ $update['last_error'] }} @endif {{-- Where it is. "Läuft gerade" alone is what sends an operator to the shell: a queued update does nothing visible until the agent picks it up, and while it runs the site is in maintenance mode and this page is unreachable. Both look identical to "wedged" without a time — hence the live countdown, replacing what used to be a static "spätestens um 17:19" that read as a promise nothing then visibly kept. There is no countdown any more, for either. It was wrong in fact — the target was recomputed on every poll, and where the interval the agent reported was shorter than the timer actually installed on that host it landed in the past every time and reset to a full minute, so it ran backwards a few seconds and jumped, forever. And it was wrong in kind: a check starts nothing, and an update now starts at once. A queued run says so in a sentence; a queued check says nothing beyond its badge. --}} @if ($update['running'])
@if ($update['phase'])

{{ __('admin_settings.update_step', ['step' => $update['phase']]) }}

@if ($update['started_at'])

{{ __('admin_settings.update_since', ['when' => $update['started_at']->diffForHumans()]) }}

@endif @else

{{ $update['instant'] ? __('admin_settings.update_starting') : __('admin_settings.update_starting_queued') }}

@endif

{{ __('admin_settings.update_offline_hint') }}

@endif @if ($update['last_state'] !== null && ! $update['running'])

{{ __('admin_settings.update_last_run', [ 'state' => __('admin_settings.update_state.'.$update['last_state']), 'when' => $update['last_finished_at']?->diffForHumans() ?? '—', ]) }} @if ($update['last_started_at'] && $update['last_finished_at']) · {{ __('admin_settings.update_took', ['duration' => $update['last_started_at']->diffForHumans($update['last_finished_at'], true)]) }} @endif {{-- Only on a failure: on a run that worked the last step is "left maintenance mode", which says nothing. --}} @if ($update['last_state'] === 'failed' && $update['last_phase']) · {{ __('admin_settings.update_failed_at', ['step' => $update['last_phase']]) }} @endif

@endif {{-- Open while it runs: "läuft gerade" on its own tells an operator nothing about whether it is progressing or wedged. --}} @if ($updateLog)
{{ __('admin_settings.update_log') }}
{{ $updateLog }}
@endif
@endif {{-- My account --}} @if ($canManageSite)

{{ __('admin_settings.site_title') }}

{{ $sitePublic ? __('admin_settings.site_public') : __('admin_settings.site_hidden') }}

{{ $sitePublic ? __('admin_settings.site_public_body') : __('admin_settings.site_hidden_body') }}

{{ __('admin_settings.site_your_view', ['ip' => request()->ip()]) }} {{ $viewerOnVpn ? __('admin_settings.site_via_vpn') : __('admin_settings.site_not_vpn') }}

{{ $sitePublic ? __('admin_settings.site_hide') : __('admin_settings.site_show') }}
@endif {{-- Who may reach this console --}} @if ($canManageSite)

{{ __('admin_settings.console_title') }}

{{ $consoleRestricted ? __('admin_settings.console_locked_badge') : __('admin_settings.console_open_badge') }}

{{ $consoleRestricted ? __('admin_settings.console_locked_body') : __('admin_settings.console_open_body') }}

{{ __('admin_settings.console_your_ip', ['ip' => $viewerIp]) }}

{{ $consoleRestricted ? __('admin_settings.console_unlock') : __('admin_settings.console_lock') }}

{{ __('admin_settings.console_always') }}

@foreach ($consoleVpnRanges as $range)
{{ $range }} {{ __('admin_settings.console_vpn_note') }}
@endforeach

{{ __('admin_settings.console_extra') }}

@forelse ($consoleIps as $ip)
{{ $ip }} {{ __('admin_settings.console_remove') }}
@empty

{{ __('admin_settings.console_none') }}

@endforelse {{-- The hint sits under the row, not inside it: as a sibling of the field it made the flex row as tall as field-plus-hint, and the button stretched to match. --}}
{{ __('admin_settings.console_add') }}

{{ __('admin_settings.console_ip_hint') }}

@endif {{-- Two-factor policy — voluntary by default, the Owner can make it compulsory. The switch refuses to turn on while the operator flipping it has no confirmed two-factor themselves: the page that would turn it back off sits behind the switch. --}} @if ($canManageSite)

{{ __('admin_settings.two_factor_title') }}

{{ __('admin_settings.two_factor_body') }}

{{ __('admin_settings.save') }}
@error('requireTwoFactor')

{{ $message }}

@enderror
@endif {{-- Own password. There was no way to change one at all: an account created with a generated password kept it until someone opened a shell. --}}

{{ __('admin_settings.password_title') }}

{{ __('admin_settings.password_sub') }}

{{ __('admin_settings.password_save') }}

{{ __('admin_settings.account_title') }}

{{ __('admin_settings.save') }}
{{-- Staff & access (Owner-only) --}} @if ($canManageStaff)

{{ __('admin_settings.staff_title') }}

{{ __('admin_settings.staff_sub') }}

{{-- Invite --}}
@error('staffName')

{{ $message }}

@enderror
@error('staffEmail')

{{ $message }}

@enderror
@error('staffRole')

{{ $message }}

@enderror
{{ __('admin_settings.invite') }}

{{ __('admin_settings.invite_hint') }}

@if ($invitedPassword)

{{ __('admin_settings.temp_title') }}

{{ __('admin_settings.temp_hint') }}

{{ __('admin_settings.email') }}:
{{ $invitedEmail }}
{{ __('admin_settings.temp_password') }}:
{{ $invitedPassword }}
@endif {{-- Staff list --}}
@foreach ($staff as $s) @endforeach
{{ __('admin_settings.col_person') }} {{ __('admin_settings.role') }} {{ __('admin_settings.col_actions') }}

{{ $s['name'] }} @if ($s['self'])({{ __('admin_settings.you') }})@endif

{{ $s['email'] }}

@if ($s['self']) {{ __('admin_settings.role_'.\Illuminate\Support\Str::slug($s['role'])) }} @else @endif {{-- Your own row has no actions — you cannot revoke yourself — and an empty cell reads as a bug rather than as a rule. --}} @if ($s['self']) @endif @unless ($s['self']) @endunless
@endif