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

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

{{-- ── The tabs ────────────────────────────────────────────────────── This was one narrow column of six cards: an operator connecting DNS scrolled past Stripe to reach it, and the raw .env editor sat at the bottom of everything. Split by what a section is FOR — services we buy from outside, our own machines, the file underneath — which is the same axis this page was rebuilt around when it stopped being split by storage mechanism. The tab lives in the query string, so a reload or a bookmark lands where the operator was. Unlocking survives a switch: the password confirmation is a session fact, not a property of a tab. --}}
@foreach ($tabs as $name) @endforeach
@if ($canSecrets && ! $usable) {{ __('secrets.no_key') }} @endif {{-- The one lock for everything write-only on this page: every vault entry below, and the .env editor further down. Plain settings never sit behind this — they were never gated by a password before, and nothing about merging the pages makes them more dangerous. --}} @if ($canSecrets && ! $unlocked)

{{ __('secrets.locked_title') }}

{{ __('secrets.locked_body') }}

{{ __('secrets.unlock') }}
@elseif ($canSecrets)

{{ __('secrets.unlocked_note') }}

@endif @if ($tab === 'services') {{-- Two columns from lg up. Each card is a form of two or three fields — stacked, they left half the window empty and pushed monitoring below the fold. --}}
{{-- Zahlungen (Stripe) — vault only, no plain setting belongs here. --}} @if ($canSecrets)

{{ __('integrations.payments_title') }}

{{ __('integrations.payments_body') }}

@if ($unlocked) {{ __('secrets.webhook_secret_note') }} @endif
@endif {{-- DNS (Hetzner) — token and the zone/Traefik settings that use it. --}} @if ($canSecrets || $canInfra)

{{ __('integrations.dns_title') }}

{{ __('integrations.dns_body') }}

@if ($canInfra)
@endif @if ($canSecrets) @if ($canInfra)
@endif @endif
@endif {{-- Das Support-Postfach — IMAP, nicht die mailcow-API: die API verwaltet Domains und Postfächer, gelesen wird ein Postfach über IMAP, egal wer den Server betreibt. --}} @if ($canSecrets || $canInfra)

{{ __('integrations.inbound_title') }}

{{ __('integrations.inbound_body') }}

@if ($canInfra)
@endif @if ($canSecrets) @if ($canInfra)
@endif @endif {{-- The one thing an operator wants after typing all of it: did any of it work. The button saves first, because testing what is on screen while the server still holds the old values would report on a mailbox nobody configured. --}} @if ($canInfra)
{{ __('integrations.inbound_test') }} {{-- Always said, including "never": a line that appears only after a success leaves an operator unable to tell a mailbox that has not been checked from one that is fine. R19: stored in UTC, read on the wall clock. --}} @if ($inboundStatus === null)

{{ __('integrations.inbound_never') }}

@else

$inboundStatus['ok'], 'text-danger' => ! $inboundStatus['ok']])> {{ __($inboundStatus['ok'] ? 'integrations.inbound_last_ok' : 'integrations.inbound_last_failed', [ 'when' => $inboundStatus['at']->local()->isoFormat('D. MMM YYYY, HH:mm'), 'ago' => $inboundStatus['at']->diffForHumans(), ]) }}

@endif
@endif
@endif {{-- Monitoring — token and where the Kuma bridge is reachable. --}} @if ($canSecrets || $canInfra)

{{ __('integrations.monitoring_title') }}

{{ __('integrations.monitoring_body') }}

@if ($canInfra) @endif @if ($canSecrets) @if ($canInfra)
@endif @endif
@endif
@endif @if ($tab === 'platform')
{{-- VPN/WireGuard — no vault entry: the private hub key stays in .env. --}} @if ($canInfra)

{{ __('integrations.vpn_title') }}

{{ __('integrations.vpn_body') }}

@endif {{-- SSH-Identität — public half a setting, private half the vault entry. --}} @if ($canSecrets || $canInfra)

{{ __('integrations.ssh_title') }}

{{ __('integrations.ssh_body') }}

@if ($canInfra)

{{ __('integrations.ssh_public_key_hint') }}

@error('sshPublicKey')

{{ $message }}

@enderror
@endif @if ($canSecrets) @if ($canInfra)
@endif @endif
@endif
@endif {{-- One button for both tabs' plain settings. Livewire keeps the values of a hidden field, so saving from either tab writes everything the operator has typed — which is what they mean by "speichern". --}} @if ($canInfra && in_array($tab, ['services', 'platform'], true))
{{ __('integrations.save_settings') }}
@endif {{-- Stripe's own answer, under the Stripe card it belongs to. --}} @if ($tab === 'services' && $canSecrets && $unlocked && $check !== null)

{{ __('secrets.check_title') }}

@if (! $check['ok']) {{ __('secrets.check_'.$check['reason']) }} @else
{{ __('secrets.check_account') }}:
{{ $check['account'] }}{{ $check['business'] ? ' · '.$check['business'] : '' }}
{{ __('secrets.check_mode') }}:
{{ $check['live'] ? __('secrets.mode_live') : __('secrets.mode_test') }}{{ $check['restricted'] ? ' · '.__('secrets.mode_restricted') : '' }}

{{ __('secrets.check_webhooks') }}

@if ($check['webhooks'] === null)

{{ __('secrets.check_webhooks_unknown') }}

@elseif ($check['webhooks'] === []) {{ __('secrets.check_webhooks_none') }} @else @endif @endif
@endif {{-- Part B: the raw .env editor. Everything above has a field; everything that does not still needs to be reachable from the console, or an operator needing it needs a shell and this whole page misses the point. Shares the lock above: secrets.manage AND a confirmed password, because it is the one place on this page that can reach every credential the vault otherwise keeps write-only. --}} @if ($tab === 'env' && $canSecrets)

{{ __('integrations.env_title') }}

{{ __('integrations.env_subtitle') }}

@if (! $unlocked)

{{ __('integrations.env_locked') }}

@else

{{ __('integrations.env_danger_title') }}

{{ __('integrations.env_danger_body') }}

{{ __('integrations.env_vault_title') }}

{{ __('integrations.env_vault_body') }}

    @foreach ($entries as $entry)
  • {{ $entry['envKey'] }} {{ $entry['source'] === 'stored' ? __('integrations.env_vault_active') : __('integrations.env_vault_inactive') }}
  • @endforeach
@error('envContent')

{{ $message }}

@enderror
{{ __('integrations.env_save') }}

{{ __('integrations.env_backup_title') }}

{{ __('integrations.env_backup_body') }}

{{-- What saving now does automatically (config:clear here, the restart via UpdateChannel) — not homework anymore, see App\Livewire\Admin\Integrations::saveEnv(). The spinner below is this component's own wire:poll, not the full-screen deployment overlay: a restart of four containers is seconds, not minutes, and `app` — the container answering this very poll — is never among them, so this page never goes offline for it. --}}

{{ __('integrations.env_restart_title') }}

{{ __('integrations.env_restart_body') }}

@if ($envRestartWatching || $restart['restarting'])

{{ __('integrations.env_restarting') }}

@elseif (! $restart['agent_seen']) {{-- Honest, not silent: told plainly, with the fallback right here — same shape as the update card's own admin_settings.update_no_agent. --}} {{ __('admin_settings.update_no_agent') }}
docker compose restart queue queue-provisioning scheduler reverb
@endif
@endif
@endif