CluPilotCloud/resources/views/components/admin/secret-field.blade.php

101 lines
5.5 KiB
PHP

@props(['entry', 'unlocked'])
{{--
One vault entry outline, new-value field, test/save/forget reusable
across every integration section on App\Livewire\Admin\Integrations. Kept
as its own component so a section can sit a secret and a plain setting
side by side (R20 exception: this is the page, not a table row) without
repeating this block per section.
Locked and unlocked are two genuinely different renders, not one hidden
behind CSS: while locked, nothing about the stored value not even
whether one exists beyond the badge above this component reaches the
page beyond a "gesperrt" note.
--}}
<div wire:key="secret-{{ $entry['key'] }}" class="space-y-4">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<h3 class="text-sm font-semibold text-ink">{{ $entry['label'] }}</h3>
{{-- The NAME OF THE SERVER VARIABLE, not the internal registry key.
It used to print `stripe.secret` an identifier from
SecretVault::REGISTRY that means nothing on this page and is not
something an operator can type anywhere. The env name is: it is
what stands in the server file, it is what the .env tab of this
same page lists, and when the badge beside this says "Aus der
Serverdatei" it is the answer to "which line?". --}}
<p class="mt-0.5 font-mono text-xs text-muted">{{ $entry['envKey'] }}</p>
</div>
<span class="inline-flex items-center gap-1.5 rounded-pill border px-2.5 py-0.5 text-xs font-medium
{{ $entry['source'] === 'stored' ? 'border-success-border bg-success-bg text-success'
: ($entry['source'] === 'environment' ? 'border-info-border bg-info-bg text-info'
: 'border-warning-border bg-warning-bg text-warning') }}">
<span class="size-1.5 rounded-pill bg-current" aria-hidden="true"></span>
{{ __('secrets.source_'.$entry['source']) }}
</span>
</div>
@if (! $unlocked)
<p class="flex items-center gap-1.5 text-sm text-muted">
<x-ui.icon name="lock" class="size-4" />
{{ __('secrets.locked_title') }}
</p>
@else
@if ($entry['outline'])
<dl class="rounded-lg border border-line bg-surface-2 px-4 py-3 text-sm">
<div class="flex justify-between gap-4">
<dt class="text-muted">{{ __('secrets.stored_value') }}</dt>
<dd class="font-mono text-body">{{ $entry['outline'] }}</dd>
</div>
@if ($entry['updated_at'])
<div class="mt-1 flex justify-between gap-4">
<dt class="text-muted">{{ __('secrets.changed') }}</dt>
<dd class="text-body">{{ \Illuminate\Support\Carbon::parse($entry['updated_at'])->diffForHumans() }}</dd>
</div>
@endif
</dl>
@endif
<div>
@if ($entry['multiline'])
{{-- A multi-line PEM key: a single-line password field would
mangle it on paste. Not masked a textarea cannot mask
its content in any browser. --}}
<label for="entered-{{ $entry['field'] }}" class="block text-sm font-medium text-body">{{ __('secrets.new_value') }}</label>
<textarea id="entered-{{ $entry['field'] }}" name="entered.{{ $entry['field'] }}" rows="6" autocomplete="off" spellcheck="false"
wire:model="entered.{{ $entry['field'] }}"
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="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<p class="mt-1.5 text-xs text-muted">{{ __('secrets.ssh_private_key_hint') }}</p>
@error('entered.'.$entry['field'])<p class="mt-1.5 text-xs text-danger">{{ $message }}</p>@enderror
@else
<x-ui.input name="entered.{{ $entry['field'] }}" type="password" autocomplete="off"
:label="__('secrets.new_value')" :hint="__('secrets.new_value_hint')"
wire:model="entered.{{ $entry['field'] }}" />
@endif
</div>
<div class="flex flex-wrap gap-2">
{{-- Test first: a key that is saved and wrong fails later,
somewhere else, usually in front of a customer. --}}
@if ($entry['testable'])
<x-ui.button variant="secondary" wire:click="test('{{ $entry['key'] }}')"
wire:loading.attr="disabled" wire:target="test('{{ $entry['key'] }}')">
{{ __('secrets.test') }}
</x-ui.button>
@endif
<x-ui.button variant="primary"
x-on:click="$dispatch('openModal', { component: 'admin.confirm-save-secret', arguments: { key: '{{ $entry['key'] }}' } })">
{{ __('secrets.save') }}
</x-ui.button>
@if ($entry['source'] === 'stored')
<x-ui.button variant="ghost"
x-on:click="$dispatch('openModal', { component: 'admin.confirm-forget-secret', arguments: { key: '{{ $entry['key'] }}' } })">
{{ __('secrets.forget') }}
</x-ui.button>
@endif
</div>
@endif
</div>