204 lines
13 KiB
PHP
204 lines
13 KiB
PHP
<div class="mx-auto max-w-3xl space-y-6">
|
|
<div class="animate-rise">
|
|
<h1 class="text-2xl font-semibold tracking-tight text-ink">{{ __('admin_settings.title') }}</h1>
|
|
<p class="mt-1 text-sm text-muted">{{ __('admin_settings.subtitle') }}</p>
|
|
</div>
|
|
|
|
{{-- My account --}}
|
|
@if ($canManageSite)
|
|
<div class="rounded-xl border border-line bg-surface p-6 shadow-xs animate-rise">
|
|
<div class="flex flex-wrap items-start justify-between gap-4">
|
|
<div class="min-w-0">
|
|
<div class="flex items-center gap-2">
|
|
<h2 class="font-semibold text-ink">{{ __('admin_settings.site_title') }}</h2>
|
|
<span class="inline-flex items-center gap-1.5 rounded-pill border px-2.5 py-0.5 text-xs font-medium
|
|
{{ $sitePublic ? 'border-success-border bg-success-bg text-success' : 'border-warning-border bg-warning-bg text-warning' }}">
|
|
<span class="size-1.5 rounded-pill bg-current" aria-hidden="true"></span>
|
|
{{ $sitePublic ? __('admin_settings.site_public') : __('admin_settings.site_hidden') }}
|
|
</span>
|
|
</div>
|
|
<p class="mt-1.5 max-w-xl text-sm text-muted">
|
|
{{ $sitePublic ? __('admin_settings.site_public_body') : __('admin_settings.site_hidden_body') }}
|
|
</p>
|
|
<p class="mt-2 text-xs text-faint">
|
|
{{ __('admin_settings.site_your_view', ['ip' => request()->ip()]) }}
|
|
<span class="{{ $viewerOnVpn ? 'text-success' : '' }}">
|
|
{{ $viewerOnVpn ? __('admin_settings.site_via_vpn') : __('admin_settings.site_not_vpn') }}
|
|
</span>
|
|
</p>
|
|
</div>
|
|
<x-ui.button :variant="$sitePublic ? 'secondary' : 'primary'" wire:click="toggleSiteVisibility" wire:loading.attr="disabled">
|
|
<x-ui.icon :name="$sitePublic ? 'lock' : 'unlock'" class="size-4" />
|
|
{{ $sitePublic ? __('admin_settings.site_hide') : __('admin_settings.site_show') }}
|
|
</x-ui.button>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Who may reach this console --}}
|
|
@if ($canManageSite)
|
|
<div class="rounded-xl border border-line bg-surface p-6 shadow-xs animate-rise [animation-delay:30ms]">
|
|
<div class="flex flex-wrap items-start justify-between gap-4">
|
|
<div class="min-w-0">
|
|
<div class="flex items-center gap-2">
|
|
<h2 class="font-semibold text-ink">{{ __('admin_settings.console_title') }}</h2>
|
|
<span class="inline-flex items-center gap-1.5 rounded-pill border px-2.5 py-0.5 text-xs font-medium
|
|
{{ $consoleRestricted ? 'border-success-border bg-success-bg text-success' : 'border-warning-border bg-warning-bg text-warning' }}">
|
|
<span class="size-1.5 rounded-pill bg-current" aria-hidden="true"></span>
|
|
{{ $consoleRestricted ? __('admin_settings.console_locked_badge') : __('admin_settings.console_open_badge') }}
|
|
</span>
|
|
</div>
|
|
<p class="mt-1 max-w-xl text-sm text-muted">
|
|
{{ $consoleRestricted ? __('admin_settings.console_locked_body') : __('admin_settings.console_open_body') }}
|
|
</p>
|
|
<p class="mt-2 font-mono text-xs text-faint">
|
|
{{ __('admin_settings.console_your_ip', ['ip' => $viewerIp]) }}
|
|
</p>
|
|
</div>
|
|
<x-ui.button :variant="$consoleRestricted ? 'secondary' : 'primary'"
|
|
wire:click="toggleConsoleRestriction" wire:loading.attr="disabled">
|
|
<x-ui.icon :name="$consoleRestricted ? 'unlock' : 'lock'" class="size-4" />
|
|
{{ $consoleRestricted ? __('admin_settings.console_unlock') : __('admin_settings.console_lock') }}
|
|
</x-ui.button>
|
|
</div>
|
|
|
|
<div class="mt-5 space-y-2 border-t border-line pt-4">
|
|
<p class="text-xs font-semibold uppercase tracking-wide text-faint">{{ __('admin_settings.console_always') }}</p>
|
|
@foreach ($consoleVpnRanges as $range)
|
|
<div class="flex items-center justify-between rounded-lg border border-line-strong bg-surface-2 px-3 py-2">
|
|
<span class="font-mono text-sm text-body">{{ $range }}</span>
|
|
<span class="text-xs text-faint">{{ __('admin_settings.console_vpn_note') }}</span>
|
|
</div>
|
|
@endforeach
|
|
|
|
<p class="pt-3 text-xs font-semibold uppercase tracking-wide text-faint">{{ __('admin_settings.console_extra') }}</p>
|
|
@forelse ($consoleIps as $ip)
|
|
<div wire:key="cip-{{ $ip }}" class="flex items-center justify-between rounded-lg border border-line-strong bg-surface-2 px-3 py-2">
|
|
<span class="font-mono text-sm text-body">{{ $ip }}</span>
|
|
<x-ui.button variant="ghost" size="sm" wire:click="removeConsoleIp('{{ $ip }}')">
|
|
{{ __('admin_settings.console_remove') }}
|
|
</x-ui.button>
|
|
</div>
|
|
@empty
|
|
<p class="text-sm text-muted">{{ __('admin_settings.console_none') }}</p>
|
|
@endforelse
|
|
|
|
<form wire:submit="addConsoleIp" class="flex gap-2 pt-2">
|
|
<div class="flex-1">
|
|
<x-ui.input name="consoleIp" wire:model="consoleIp"
|
|
:hint="__('admin_settings.console_ip_hint')" placeholder="203.0.113.7" />
|
|
</div>
|
|
<x-ui.button type="submit" wire:loading.attr="disabled">{{ __('admin_settings.console_add') }}</x-ui.button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<form wire:submit="saveAccount" class="space-y-4 rounded-xl border border-line bg-surface p-6 shadow-xs animate-rise [animation-delay:60ms]">
|
|
<h2 class="font-semibold text-ink">{{ __('admin_settings.account_title') }}</h2>
|
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
<x-ui.input name="name" wire:model="name" :label="__('admin_settings.name')" />
|
|
<x-ui.input name="email" wire:model="email" :label="__('admin_settings.email')" type="email" />
|
|
</div>
|
|
<p class="text-xs text-faint">{{ __('admin_settings.twofa_hint') }}</p>
|
|
<div class="flex justify-end">
|
|
<x-ui.button variant="primary" type="submit" wire:loading.attr="disabled" wire:target="saveAccount">{{ __('admin_settings.save') }}</x-ui.button>
|
|
</div>
|
|
</form>
|
|
|
|
{{-- Staff & access (Owner-only) --}}
|
|
@if ($canManageStaff)
|
|
<div class="space-y-4 rounded-xl border border-line bg-surface p-6 shadow-xs animate-rise [animation-delay:120ms]">
|
|
<div>
|
|
<h2 class="font-semibold text-ink">{{ __('admin_settings.staff_title') }}</h2>
|
|
<p class="mt-1 text-sm text-muted">{{ __('admin_settings.staff_sub') }}</p>
|
|
</div>
|
|
|
|
{{-- Invite --}}
|
|
<form wire:submit="inviteStaff" class="grid grid-cols-1 gap-3 sm:grid-cols-[1fr_1fr_auto_auto] sm:items-end">
|
|
<div>
|
|
<label class="text-sm font-medium text-body" for="staffName">{{ __('admin_settings.name') }}</label>
|
|
<input id="staffName" type="text" wire:model="staffName" class="mt-1.5 w-full rounded-md border border-line-strong bg-surface px-3 py-2 text-sm text-ink" />
|
|
@error('staffName')<p class="mt-1 text-xs text-danger">{{ $message }}</p>@enderror
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-medium text-body" for="staffEmail">{{ __('admin_settings.email') }}</label>
|
|
<input id="staffEmail" type="email" wire:model="staffEmail" class="mt-1.5 w-full rounded-md border border-line-strong bg-surface px-3 py-2 text-sm text-ink" />
|
|
@error('staffEmail')<p class="mt-1 text-xs text-danger">{{ $message }}</p>@enderror
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-medium text-body" for="staffRole">{{ __('admin_settings.role') }}</label>
|
|
<select id="staffRole" wire:model="staffRole" class="mt-1.5 rounded-md border border-line-strong bg-surface px-3 py-2 text-sm text-body">
|
|
@foreach ($roles as $r)
|
|
@if ($r !== 'Owner' || $isOwner)
|
|
<option value="{{ $r }}">{{ __('admin_settings.role_'.\Illuminate\Support\Str::slug($r)) }}</option>
|
|
@endif
|
|
@endforeach
|
|
</select>
|
|
@error('staffRole')<p class="mt-1 text-xs text-danger">{{ $message }}</p>@enderror
|
|
</div>
|
|
<x-ui.button variant="primary" type="submit" wire:loading.attr="disabled" wire:target="inviteStaff">
|
|
<x-ui.icon name="plus" class="size-4" />{{ __('admin_settings.invite') }}
|
|
</x-ui.button>
|
|
</form>
|
|
<p class="text-xs text-faint">{{ __('admin_settings.invite_hint') }}</p>
|
|
|
|
@if ($invitedPassword)
|
|
<div class="rounded-lg border border-warning-border bg-warning-bg p-4">
|
|
<p class="text-sm font-semibold text-ink">{{ __('admin_settings.temp_title') }}</p>
|
|
<p class="mt-1 text-xs text-body">{{ __('admin_settings.temp_hint') }}</p>
|
|
<dl class="mt-2 space-y-1 font-mono text-sm text-ink">
|
|
<div class="flex gap-2"><dt class="text-muted">{{ __('admin_settings.email') }}:</dt><dd>{{ $invitedEmail }}</dd></div>
|
|
<div class="flex gap-2"><dt class="text-muted">{{ __('admin_settings.temp_password') }}:</dt><dd class="select-all">{{ $invitedPassword }}</dd></div>
|
|
</dl>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Staff list --}}
|
|
<div class="overflow-hidden rounded-xl border border-line">
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm">
|
|
<thead>
|
|
<tr class="border-b border-line bg-surface-2 text-left text-xs uppercase tracking-wide text-faint">
|
|
<th class="px-4 py-3 font-semibold">{{ __('admin_settings.col_person') }}</th>
|
|
<th class="px-4 py-3 font-semibold">{{ __('admin_settings.role') }}</th>
|
|
<th class="px-4 py-3 text-right font-semibold">{{ __('admin_settings.col_actions') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach ($staff as $s)
|
|
<tr wire:key="staff-{{ $s['id'] }}" class="border-b border-line last:border-0 hover:bg-surface-hover">
|
|
<td class="px-4 py-3">
|
|
<p class="font-medium text-ink">{{ $s['name'] }}
|
|
@if ($s['self'])<span class="ml-1 text-xs text-faint">({{ __('admin_settings.you') }})</span>@endif
|
|
</p>
|
|
<p class="text-xs text-muted">{{ $s['email'] }}</p>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
@if ($s['self'])
|
|
<span class="text-body">{{ __('admin_settings.role_'.\Illuminate\Support\Str::slug($s['role'])) }}</span>
|
|
@else
|
|
<select wire:change="setStaffRole({{ $s['id'] }}, $event.target.value)" class="rounded-md border border-line-strong bg-surface px-2 py-1 text-xs text-body">
|
|
@foreach ($roles as $r)
|
|
@if ($r !== 'Owner' || $isOwner)
|
|
<option value="{{ $r }}" @selected($s['role'] === $r)>{{ __('admin_settings.role_'.\Illuminate\Support\Str::slug($r)) }}</option>
|
|
@endif
|
|
@endforeach
|
|
</select>
|
|
@endif
|
|
</td>
|
|
<td class="px-4 py-3 text-right">
|
|
@unless ($s['self'])
|
|
<button type="button" wire:click="revokeStaff({{ $s['id'] }})" class="rounded-md border border-line px-2.5 py-1.5 text-xs font-semibold text-muted hover:border-danger hover:text-danger">{{ __('admin_settings.revoke') }}</button>
|
|
@endunless
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|