{{ __('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') {{-- The switch — above every vault entry, because it governs them: which slot of each credential below is in force. Same lock as the vault itself (secrets.manage + confirmed password): the mode a stranger could type into a form field is only ever applied through switchMode()'s own guard, but the button that offers it in the first place stays behind the same unlock as everything else on this page. --}} @if ($canSecrets)

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

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

{{ __('readiness.mode.'.$mode->value) }} @if ($unlocked) {{ $mode->isTest() ? __('integrations.mode_switch_to_live') : __('integrations.mode_switch_to_test') }} @endif
@endif {{-- 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