CluPilotCloud/resources/views/livewire/admin/integrations.blade.php

184 lines
10 KiB
PHP

<div class="mx-auto max-w-3xl space-y-6">
<div class="animate-rise">
<h1 class="text-2xl font-bold tracking-tight text-ink">{{ __('integrations.title') }}</h1>
<p class="mt-1 text-sm text-muted">{{ __('integrations.subtitle') }}</p>
</div>
@if ($canSecrets && ! $usable)
<x-ui.alert variant="warning">{{ __('secrets.no_key') }}</x-ui.alert>
@endif
{{-- The one lock for everything write-only on this page: every vault
entry below. 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)
<form wire:submit="confirmPassword" class="rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise">
<h2 class="font-semibold text-ink">{{ __('secrets.locked_title') }}</h2>
<p class="mt-1.5 max-w-xl text-sm text-muted">{{ __('secrets.locked_body') }}</p>
<div class="mt-4 flex flex-wrap items-start gap-2">
<div class="min-w-56 flex-1">
<x-ui.input name="confirmablePassword" type="password" autocomplete="current-password"
:label="__('admin_settings.password_current')" wire:model="confirmablePassword" />
</div>
<x-ui.button type="submit" variant="primary" class="mt-7" wire:loading.attr="disabled" wire:target="confirmPassword">
{{ __('secrets.unlock') }}
</x-ui.button>
</div>
</form>
@elseif ($canSecrets)
<div class="flex items-center justify-between rounded-lg border border-accent-border bg-accent-subtle px-4 py-2.5 animate-rise">
<p class="text-sm text-accent-text">{{ __('secrets.unlocked_note') }}</p>
<button type="button" wire:click="forgetPasswordConfirmation"
class="text-xs font-semibold text-accent-text hover:underline">{{ __('secrets.lock_again') }}</button>
</div>
@endif
{{-- Zahlungen (Stripe) vault only, no plain setting belongs here. --}}
@if ($canSecrets)
<div class="space-y-4 rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise">
<div>
<h2 class="font-semibold text-ink">{{ __('integrations.payments_title') }}</h2>
<p class="mt-1 text-sm text-muted">{{ __('integrations.payments_body') }}</p>
</div>
<x-admin.secret-field :entry="$entries['stripe.secret']" :unlocked="$unlocked" />
@if ($unlocked)
<x-ui.alert variant="info">{{ __('secrets.webhook_secret_note') }}</x-ui.alert>
@endif
</div>
@endif
{{-- DNS (Hetzner) token and the zone/Traefik settings that use it. --}}
@if ($canSecrets || $canInfra)
<div class="space-y-4 rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise [animation-delay:30ms]">
<div>
<h2 class="font-semibold text-ink">{{ __('integrations.dns_title') }}</h2>
<p class="mt-1 text-sm text-muted">{{ __('integrations.dns_body') }}</p>
</div>
@if ($canInfra)
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<x-ui.input name="dnsZone" wire:model="dnsZone" :label="__('integrations.dns_zone')" :hint="__('integrations.dns_zone_hint')" />
<x-ui.input name="traefikDynamicPath" wire:model="traefikDynamicPath" :label="__('integrations.traefik_path')" :hint="__('integrations.traefik_path_hint')" />
</div>
@endif
@if ($canSecrets)
@if ($canInfra)<hr class="border-line" />@endif
<x-admin.secret-field :entry="$entries['dns.token']" :unlocked="$unlocked" />
@endif
</div>
@endif
{{-- Monitoring token and where the Kuma bridge is reachable. --}}
@if ($canSecrets || $canInfra)
<div class="space-y-4 rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise [animation-delay:60ms]">
<div>
<h2 class="font-semibold text-ink">{{ __('integrations.monitoring_title') }}</h2>
<p class="mt-1 text-sm text-muted">{{ __('integrations.monitoring_body') }}</p>
</div>
@if ($canInfra)
<x-ui.input name="monitoringUrl" type="url" wire:model="monitoringUrl" :label="__('integrations.monitoring_url')" :hint="__('integrations.monitoring_url_hint')" placeholder="http://kuma-bridge:8080" />
@endif
@if ($canSecrets)
@if ($canInfra)<hr class="border-line" />@endif
<x-admin.secret-field :entry="$entries['monitoring.token']" :unlocked="$unlocked" />
@endif
</div>
@endif
{{-- VPN/WireGuard no vault entry: the private hub key stays in .env. --}}
@if ($canInfra)
<div class="space-y-4 rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise [animation-delay:90ms]">
<div>
<h2 class="font-semibold text-ink">{{ __('integrations.vpn_title') }}</h2>
<p class="mt-1 text-sm text-muted">{{ __('integrations.vpn_body') }}</p>
</div>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<x-ui.input name="wgEndpoint" wire:model="wgEndpoint" :label="__('integrations.wg_endpoint')" :hint="__('integrations.wg_endpoint_hint')" placeholder="vpn.clupilot.com:51820" />
<x-ui.input name="wgHubPubkey" wire:model="wgHubPubkey" :label="__('integrations.wg_hub_pubkey')" :hint="__('integrations.wg_hub_pubkey_hint')" />
</div>
</div>
@endif
{{-- SSH-Identität public half a setting, private half the vault entry. --}}
@if ($canSecrets || $canInfra)
<div class="space-y-4 rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise [animation-delay:120ms]">
<div>
<h2 class="font-semibold text-ink">{{ __('integrations.ssh_title') }}</h2>
<p class="mt-1 text-sm text-muted">{{ __('integrations.ssh_body') }}</p>
</div>
@if ($canInfra)
<div>
<label for="sshPublicKey" class="block text-sm font-medium text-body">{{ __('integrations.ssh_public_key') }}</label>
<textarea id="sshPublicKey" name="sshPublicKey" rows="3" autocomplete="off" spellcheck="false"
wire:model="sshPublicKey"
class="mt-1.5 block w-full rounded border border-line bg-surface px-3.5 py-2.5 font-mono text-xs text-ink placeholder:text-faint transition"
placeholder="ssh-ed25519 AAAA... clupilot"></textarea>
<p class="mt-1.5 text-xs text-muted">{{ __('integrations.ssh_public_key_hint') }}</p>
@error('sshPublicKey')<p class="mt-1.5 text-xs text-danger">{{ $message }}</p>@enderror
</div>
@endif
@if ($canSecrets)
@if ($canInfra)<hr class="border-line" />@endif
<x-admin.secret-field :entry="$entries['ssh.private_key']" :unlocked="$unlocked" />
@endif
</div>
@endif
@if ($canInfra)
<div class="flex justify-end">
<x-ui.button variant="primary" wire:click="saveInfra" wire:loading.attr="disabled" wire:target="saveInfra">
{{ __('integrations.save_settings') }}
</x-ui.button>
</div>
@endif
@if ($canSecrets && $unlocked && $check !== null)
<div class="rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise">
<h2 class="font-semibold text-ink">{{ __('secrets.check_title') }}</h2>
@if (! $check['ok'])
<x-ui.alert variant="danger" class="mt-3">{{ __('secrets.check_'.$check['reason']) }}</x-ui.alert>
@else
<dl class="mt-3 space-y-1 text-sm">
<div class="flex gap-2"><dt class="text-muted">{{ __('secrets.check_account') }}:</dt>
<dd class="font-mono text-body">{{ $check['account'] }}{{ $check['business'] ? ' · '.$check['business'] : '' }}</dd></div>
<div class="flex gap-2"><dt class="text-muted">{{ __('secrets.check_mode') }}:</dt>
<dd class="{{ $check['live'] ? 'font-medium text-warning' : 'text-body' }}">
{{ $check['live'] ? __('secrets.mode_live') : __('secrets.mode_test') }}{{ $check['restricted'] ? ' · '.__('secrets.mode_restricted') : '' }}
</dd></div>
</dl>
<h3 class="mt-5 text-sm font-semibold text-ink">{{ __('secrets.check_webhooks') }}</h3>
@if ($check['webhooks'] === null)
<p class="mt-1 text-sm text-muted">{{ __('secrets.check_webhooks_unknown') }}</p>
@elseif ($check['webhooks'] === [])
<x-ui.alert variant="warning" class="mt-2">{{ __('secrets.check_webhooks_none') }}</x-ui.alert>
@else
<ul class="mt-2 space-y-3">
@foreach ($check['webhooks'] as $hook)
<li class="rounded-lg border border-line bg-surface-2 px-4 py-3">
<div class="flex flex-wrap items-center justify-between gap-2">
<span class="font-mono text-xs text-body">{{ $hook['url'] }}</span>
<span class="text-xs {{ $hook['status'] === 'enabled' ? 'text-success' : 'text-warning' }}">{{ $hook['status'] }}</span>
</div>
<div class="mt-2 flex flex-wrap gap-1.5">
@foreach ($hook['events'] as $event)
<span class="rounded border border-line px-1.5 py-0.5 font-mono text-[11px] text-muted">{{ $event }}</span>
@endforeach
</div>
</li>
@endforeach
</ul>
@endif
@endif
</div>
@endif
</div>