fix(proxmox): harden console sessions and firewall rollout safety
Reduce security and reliability risk across the Proxmox extension by removing password exposure from Livewire state, hardening console auth/cookie handling, and improving sanitized error paths. Improve firewall and router performance by adding safer lazy loading, bounded pagination, cross-server compatibility guards, and lower-overhead API workflows. Made-with: Cursor
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<x-proxmox::layout :server="$server" view="cloud-init" :status="$status" :status-polling="true" :status-polling-interval="'2s'">
|
||||
<x-proxmox::layout :server="$server" view="cloud-init" :status="$status" :status-polling="true" :status-polling-interval="'30s'">
|
||||
<style>
|
||||
.formRight select {
|
||||
background-color: hsl(var(--color-background));
|
||||
@@ -200,25 +200,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2" x-data="{ showPasswordViewModal: false, showPasswordEditModal: @entangle('isEditingPassword') }">
|
||||
<div class="space-y-2"
|
||||
x-data="{
|
||||
showPasswordEditModal: @entangle('isEditingPassword'),
|
||||
passwordInput: '',
|
||||
passwordConfirmationInput: '',
|
||||
resetPasswordFields() {
|
||||
this.passwordInput = ''
|
||||
this.passwordConfirmationInput = ''
|
||||
}
|
||||
}">
|
||||
<label class="text-sm font-semibold text-base/80">Root Password</label>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center gap-2 border border-neutral rounded-md bg-background px-3 py-2 text-sm text-base/80">
|
||||
<span class="truncate flex-1">
|
||||
@if(isset($server->password) && $server->password)
|
||||
@if($server->hasStoredPassword())
|
||||
••••••••••••
|
||||
@else
|
||||
Password is sent via email on install.
|
||||
@endif
|
||||
</span>
|
||||
@if(isset($server->password) && $server->password)
|
||||
<button type="button"
|
||||
class="text-base/70 hover:text-base/60 ml-2"
|
||||
@click="showPasswordViewModal = true"
|
||||
aria-label="View Root Password">
|
||||
<x-ri-eye-line class="inline size-4" />
|
||||
</button>
|
||||
@endif
|
||||
<button type="button"
|
||||
class="text-base/70 hover:text-base/60 ml-2"
|
||||
wire:click="startEditingPassword"
|
||||
@@ -228,54 +229,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-base/60">Matches the Overview tab control. Use Save for database only, Save & Apply to push via cloud-init.</p>
|
||||
|
||||
@if(isset($server->password) && $server->password)
|
||||
<div x-cloak x-show="showPasswordViewModal" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="showPasswordViewModal = false"></div>
|
||||
<div class="relative bg-background-secondary border border-neutral rounded-lg shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
|
||||
x-on:keydown.escape.window="showPasswordViewModal = false"
|
||||
x-data="{
|
||||
copied: false,
|
||||
copyText: {{ json_encode($server->password) }},
|
||||
copyToClipboard() {
|
||||
navigator.clipboard.writeText(this.copyText);
|
||||
this.copied = true;
|
||||
setTimeout(() => this.copied = false, 2000);
|
||||
}
|
||||
}">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-base font-semibold text-base/90">Root Password</h4>
|
||||
<button type="button"
|
||||
class="bg-background border border-neutral p-1.5 rounded-md hover:bg-background/80"
|
||||
@click="showPasswordViewModal = false"
|
||||
aria-label="Close password view modal">
|
||||
<x-ri-close-line class="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="bg-background border border-neutral rounded-md p-3 text-sm text-base/80 overflow-auto">
|
||||
<code class="whitespace-pre-wrap break-all block">{{ $server->password }}</code>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button type="button"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 rounded-md bg-primary text-white text-xs font-semibold hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary"
|
||||
@click="copyToClipboard()">
|
||||
<span x-show="!copied">Copy</span>
|
||||
<span x-show="copied">Copied!</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div x-cloak x-show="showPasswordEditModal" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="showPasswordEditModal = false; $wire.cancelEditingPassword()"></div>
|
||||
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="showPasswordEditModal = false; resetPasswordFields(); $wire.cancelEditingPassword()"></div>
|
||||
<div class="relative bg-background-secondary border border-neutral rounded-lg shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
|
||||
x-on:keydown.escape.window="showPasswordEditModal = false; $wire.cancelEditingPassword()">
|
||||
x-on:keydown.escape.window="showPasswordEditModal = false; resetPasswordFields(); $wire.cancelEditingPassword()">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-base font-semibold text-base/90">Edit Root Password</h4>
|
||||
<button type="button"
|
||||
class="bg-background border border-neutral p-1.5 rounded-md hover:bg-background/80"
|
||||
@click="showPasswordEditModal = false; $wire.cancelEditingPassword()"
|
||||
@click="showPasswordEditModal = false; resetPasswordFields(); $wire.cancelEditingPassword()"
|
||||
aria-label="Close password edit modal">
|
||||
<x-ri-close-line class="size-4" />
|
||||
</button>
|
||||
@@ -284,14 +246,14 @@
|
||||
<div>
|
||||
<label class="text-xs font-semibold text-base/70">New Password</label>
|
||||
<input type="password"
|
||||
wire:model="password"
|
||||
x-model="passwordInput"
|
||||
class="block w-full mt-1 px-2.5 py-2.5 text-sm text-base bg-background-secondary/50 border border-neutral rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition-all duration-300 ease-in-out"
|
||||
placeholder="Enter new password" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs font-semibold text-base/70">Confirm Password</label>
|
||||
<input type="password"
|
||||
wire:model="password_confirmation"
|
||||
x-model="passwordConfirmationInput"
|
||||
class="block w-full mt-1 px-2.5 py-2.5 text-sm text-base bg-background-secondary/50 border border-neutral rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition-all duration-300 ease-in-out"
|
||||
placeholder="Confirm password" />
|
||||
</div>
|
||||
@@ -303,13 +265,15 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<x-button.secondary type="button" @click="showPasswordEditModal = false; $wire.cancelEditingPassword()">
|
||||
<x-button.secondary type="button" @click="showPasswordEditModal = false; resetPasswordFields(); $wire.cancelEditingPassword()">
|
||||
Cancel
|
||||
</x-button.secondary>
|
||||
<x-button.primary type="button" wire:click="updatePassword">
|
||||
<x-button.primary type="button"
|
||||
@click="$wire.updatePassword(passwordInput, passwordConfirmationInput).then(() => { if (!$wire.isEditingPassword) { showPasswordEditModal = false; resetPasswordFields() } })">
|
||||
Save
|
||||
</x-button.primary>
|
||||
<x-button.primary type="button" wire:click="applyPassword">
|
||||
<x-button.primary type="button"
|
||||
@click="$wire.applyPassword(passwordInput, passwordConfirmationInput).then(() => { if (!$wire.isEditingPassword) { showPasswordEditModal = false; resetPasswordFields() } })">
|
||||
Save & Apply
|
||||
</x-button.primary>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user