diff --git a/app/Livewire/Modals/EditCredential.php b/app/Livewire/Modals/EditCredential.php
new file mode 100644
index 0000000..55644ef
--- /dev/null
+++ b/app/Livewire/Modals/EditCredential.php
@@ -0,0 +1,85 @@
+serverId = $serverId;
+
+ if ($cred = Server::find($serverId)?->credential) {
+ $this->username = $cred->username;
+ $this->authType = $cred->auth_type;
+ // secret/passphrase are never pre-filled (encrypted at rest)
+ }
+ }
+
+ public static function modalMaxWidth(): string
+ {
+ return 'lg';
+ }
+
+ public function save(): void
+ {
+ $this->error = null;
+
+ $server = Server::find($this->serverId);
+ if (! $server) {
+ $this->error = 'Server nicht gefunden.';
+
+ return;
+ }
+ if (trim($this->username) === '' || trim($this->secret) === '') {
+ $this->error = 'Benutzer und Passwort/Schlüssel sind erforderlich.';
+
+ return;
+ }
+
+ $server->credential()->updateOrCreate([], [
+ 'username' => trim($this->username),
+ 'auth_type' => $this->authType === 'key' ? 'key' : 'password',
+ 'secret' => $this->secret,
+ 'passphrase' => $this->passphrase !== '' ? $this->passphrase : null,
+ ]);
+
+ AuditEvent::create([
+ 'user_id' => Auth::id(),
+ 'server_id' => $server->id,
+ 'actor' => Auth::user()?->name ?? 'system',
+ 'action' => 'server.credential_updated',
+ 'target' => $server->name.' · '.trim($this->username),
+ 'ip' => request()->ip(),
+ ]);
+
+ $this->dispatch('credentialChanged');
+ $this->dispatch('notify', message: 'Zugangsdaten gespeichert.');
+ $this->closeModal();
+ }
+
+ public function render()
+ {
+ return view('livewire.modals.edit-credential');
+ }
+}
diff --git a/app/Livewire/Servers/Show.php b/app/Livewire/Servers/Show.php
index 51cedb0..ad3849a 100644
--- a/app/Livewire/Servers/Show.php
+++ b/app/Livewire/Servers/Show.php
@@ -138,6 +138,15 @@ class Show extends Component
}
}
+ /** Credential changed -> reconnect with the new login and re-pull the snapshot. */
+ #[On('credentialChanged')]
+ public function reloadAfterCredential(FleetService $fleet): void
+ {
+ $this->server->refresh();
+ $this->ready = false;
+ $this->load($fleet);
+ }
+
/** Open the file manager scoped to this server. */
public function openFiles()
{
diff --git a/resources/views/components/btn.blade.php b/resources/views/components/btn.blade.php
new file mode 100644
index 0000000..d146236
--- /dev/null
+++ b/resources/views/components/btn.blade.php
@@ -0,0 +1,17 @@
+@props(['variant' => 'secondary', 'icon' => false])
+@php
+ // One button style for the whole app — compact, consistent (R10).
+ $variants = [
+ 'primary' => 'bg-accent text-void hover:bg-accent-bright',
+ 'accent' => 'border border-accent/25 bg-accent/10 text-accent-text hover:bg-accent/15',
+ 'secondary' => 'border border-line bg-inset text-ink-2 hover:bg-raised hover:text-ink',
+ 'danger' => 'bg-offline text-void hover:bg-offline/90',
+ 'ghost' => 'text-ink-2 hover:bg-raised hover:text-ink',
+ 'ghost-danger' => 'text-ink-3 hover:bg-offline/10 hover:text-offline',
+ ];
+ $shape = $icon ? 'h-8 w-8 justify-center' : 'h-8 px-3';
+ $classes = 'inline-flex items-center gap-1.5 rounded-md text-xs font-medium transition-colors '
+ .'disabled:opacity-50 disabled:pointer-events-none '
+ .$shape.' '.($variants[$variant] ?? $variants['secondary']);
+@endphp
+
diff --git a/resources/views/livewire/files/index.blade.php b/resources/views/livewire/files/index.blade.php
index 457d690..4f96d41 100644
--- a/resources/views/livewire/files/index.blade.php
+++ b/resources/views/livewire/files/index.blade.php
@@ -49,9 +49,9 @@
{{-- Listing --}}
Hier den Zugang hinterlegen — z. B. root für volle Steuerung (systemctl, Journal).
+ SSH-Zugang
+
{{ $key['fingerprint'] }}