fix: update firewall toggle functionality to use wire:click and prevent direct changes

This commit is contained in:
2025-12-18 07:32:32 -05:00
parent 901cc56238
commit 7e0d73b393

View File

@@ -274,12 +274,12 @@
<span class="fw-toggle-label {{ $firewallEnabled ? 'fw-toggle-label-enabled' : 'fw-toggle-label-disabled' }}">
{{ $firewallEnabled ? 'Enabled' : 'Disabled' }}
</span>
<label class="fw-toggle" wire:loading.class="pointer-events-none">
<label class="fw-toggle" wire:loading.class="pointer-events-none" wire:click="toggleFirewall">
<input
type="checkbox"
wire:change="toggleFirewall"
wire:loading.attr="disabled"
@checked($firewallEnabled)
{{ $firewallEnabled ? 'checked' : '' }}
readonly
>
<span class="fw-toggle-slider"></span>
</label>
@@ -365,13 +365,13 @@
<span class="fw-toggle-label {{ ($adapterFirewallStates[$adapter['name']] ?? false) ? 'fw-toggle-label-enabled' : 'fw-toggle-label-disabled' }}">
{{ ($adapterFirewallStates[$adapter['name']] ?? false) ? 'On' : 'Off' }}
</span>
<label class="fw-toggle-sm" wire:loading.class="pointer-events-none">
<label class="fw-toggle-sm" wire:loading.class="pointer-events-none" wire:click="toggleAdapterFirewall('{{ $adapter['name'] }}')">
<input
type="checkbox"
wire:change="toggleAdapterFirewall('{{ $adapter['name'] }}')"
wire:loading.attr="disabled"
wire:key="adapter-{{ $adapter['name'] }}"
@checked($adapterFirewallStates[$adapter['name']] ?? false)
{{ ($adapterFirewallStates[$adapter['name']] ?? false) ? 'checked' : '' }}
readonly
>
<span class="fw-toggle-slider-sm"></span>
</label>
@@ -872,6 +872,478 @@
</div>
@endif
{{-- Rule Form Modal --}}
@if($showRuleForm)
<div class="fixed inset-0 z-[60] bg-black/50 backdrop-blur-sm flex items-center justify-center p-4"
x-data="{
macroDefinitions: @js($this->macroDefinitions),
currentMacro: @entangle('ruleMacro'),
handleMacroChange() {
if (this.currentMacro && this.currentMacro !== '' && this.macroDefinitions[this.currentMacro]) {
const macroDef = this.macroDefinitions[this.currentMacro];
if (macroDef.proto) {
$wire.set('ruleProto', macroDef.proto);
}
if (macroDef.dport) {
$wire.set('ruleDport', macroDef.dport);
}
}
}
}"
x-init="$watch('currentMacro', () => handleMacroChange())">
<div class="relative w-full max-w-4xl bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-2xl max-h-[90vh] overflow-y-auto">
<button wire:click="cancelRuleForm" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors z-10">
<x-ri-close-line class="size-5" />
</button>
<div class="p-6">
<h3 class="text-xl font-semibold mb-6">{{ $editingRuleIndex !== null ? 'Edit' : 'Create' }} Firewall Rule</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{{-- Action --}}
<div>
<label class="block text-sm font-medium mb-2">Action</label>
<select wire:model="ruleAction" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
<option value="ACCEPT">Accept</option>
<option value="REJECT">Reject</option>
<option value="DROP">Drop</option>
</select>
</div>
{{-- Type --}}
<div>
<label class="block text-sm font-medium mb-2">Direction</label>
<select wire:model="ruleType" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
<option value="in">Incoming</option>
<option value="out">Outgoing</option>
</select>
</div>
{{-- Macro --}}
<div>
<label class="block text-sm font-medium mb-2">Macro (Pre-defined Service)</label>
<select wire:model="ruleMacro" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
@foreach($availableMacros as $value => $label)
<option value="{{ $value }}">{{ $label }}</option>
@endforeach
</select>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
<x-ri-lightbulb-line class="size-3 inline mr-0.5" />
Selecting a macro will automatically set the protocol and port(s)
</p>
</div>
{{-- Interface --}}
<div>
<label class="block text-sm font-medium mb-2">Network Interface</label>
<select wire:model="ruleIface" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
<option value="">Any</option>
@foreach($networkAdapters as $adapter)
<option value="{{ $adapter['name'] }}">{{ $adapter['name'] }} ({{ ucfirst($adapter['model'] ?? 'Unknown') }})</option>
@endforeach
</select>
</div>
{{-- Source --}}
<div>
<label class="block text-sm font-medium mb-2">Source IP/CIDR</label>
<input type="text" wire:model="ruleSource" placeholder="e.g., 192.168.1.0/24 or leave empty for any" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Can use alias names or leave empty for any source</p>
</div>
{{-- Destination --}}
<div>
<label class="block text-sm font-medium mb-2">Destination IP/CIDR</label>
<select wire:model="ruleDest" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
<option value="">Any</option>
@foreach($availableIps as $ipData)
<option value="{{ $ipData['ip'] }}">{{ $ipData['ip'] }} ({{ $ipData['label'] }})</option>
@endforeach
@foreach($aliases as $alias)
<option value="{{ $alias['name'] }}">{{ $alias['name'] }} (Alias)</option>
@endforeach
</select>
</div>
{{-- Protocol --}}
<div>
<label class="block text-sm font-medium mb-2">
Protocol
<span class="text-xs font-normal text-gray-500">(auto-filled by macro)</span>
</label>
<select wire:model="ruleProto" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
<option value="">Any</option>
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
<option value="icmp">ICMP</option>
<option value="igmp">IGMP</option>
<option value="ah">AH (IPsec Authentication Header)</option>
<option value="esp">ESP (IPsec Encapsulating Security Payload)</option>
<option value="ospf">OSPF</option>
</select>
</div>
{{-- Destination Port --}}
<div>
<label class="block text-sm font-medium mb-2">
Destination Port
<span class="text-xs font-normal text-gray-500">(auto-filled by macro)</span>
</label>
<input type="text" wire:model="ruleDport" placeholder="e.g., 80, 443, 1000:2000" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
</div>
{{-- Source Port --}}
<div>
<label class="block text-sm font-medium mb-2">Source Port (Optional)</label>
<input type="text" wire:model="ruleSport" placeholder="e.g., 80, 443" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
</div>
{{-- Log Level --}}
<div>
<label class="block text-sm font-medium mb-2">Logging</label>
<select wire:model="ruleLog" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
<option value="nolog">No Logging</option>
<option value="emerg">Emergency</option>
<option value="alert">Alert</option>
<option value="crit">Critical</option>
<option value="err">Error</option>
<option value="warning">Warning</option>
<option value="notice">Notice</option>
<option value="info">Info</option>
<option value="debug">Debug</option>
</select>
</div>
{{-- Comment --}}
<div class="md:col-span-2">
<label class="block text-sm font-medium mb-2">Comment</label>
<input type="text" wire:model="ruleComment" placeholder="Optional description" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
</div>
{{-- Enable --}}
<div class="md:col-span-2 flex items-center gap-2">
<input type="checkbox" wire:model="ruleEnable" id="ruleEnable" class="rounded border-gray-300 dark:border-gray-600">
<label for="ruleEnable" class="text-sm font-medium">Enable this rule immediately</label>
</div>
</div>
<div class="flex gap-3 justify-end mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
<button type="button" wire:click="cancelRuleForm" class="px-4 py-2 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors text-sm font-medium">
Cancel
</button>
<button type="button" wire:click="saveRule" wire:loading.attr="disabled" class="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors text-sm font-medium">
<span wire:loading.remove wire:target="saveRule">Save Rule</span>
<span wire:loading wire:target="saveRule">Saving...</span>
</button>
</div>
</div>
</div>
</div>
@endif
{{-- Delete Rule Modal --}}
@if($showDeleteRuleModal && $deletingRuleIndex !== null && isset($rules[$deletingRuleIndex]))
@php $ruleToDelete = $rules[$deletingRuleIndex]; @endphp
<div class="fixed inset-0 z-[60] bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
<div class="relative w-full max-w-lg bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6">
<button wire:click="cancelDeleteRule" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<x-ri-close-line class="size-5" />
</button>
<div class="flex items-start gap-4 mb-4">
<div class="w-12 h-12 rounded-full bg-error/10 flex items-center justify-center flex-shrink-0">
<x-ri-delete-bin-fill class="size-6 text-error" />
</div>
<div class="flex-1">
<h3 class="text-xl font-bold mb-2">Delete Firewall Rule</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">Are you sure you want to delete this rule? This action cannot be undone.</p>
<div class="p-3 bg-gray-50 dark:bg-gray-800 border border-gray-100 dark:border-gray-700 rounded-lg text-sm space-y-1">
<div><span class="text-gray-500">Action:</span> <span class="font-semibold">{{ $ruleToDelete['action'] ?? '-' }}</span></div>
<div><span class="text-gray-500">Source:</span> <span class="font-mono text-xs">{{ $ruleToDelete['source'] ?? 'Any' }}</span></div>
<div><span class="text-gray-500">Destination:</span> <span class="font-mono text-xs">{{ $ruleToDelete['dest'] ?? 'Any' }}</span></div>
</div>
</div>
</div>
<div class="flex gap-3 justify-end">
<x-proxmox::ui.button wire:click="cancelDeleteRule" variant="secondary">
Cancel
</x-proxmox::ui.button>
<x-proxmox::ui.button wire:click="deleteRule" wire:loading.attr="disabled" variant="danger">
<span wire:loading.remove wire:target="deleteRule">Delete Rule</span>
<span wire:loading wire:target="deleteRule">Deleting...</span>
</x-proxmox::ui.button>
</div>
</div>
</div>
@endif
{{-- Alias Form Modal --}}
@if($showAliasForm)
<div class="fixed inset-0 z-[60] bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
<div class="relative w-full max-w-lg bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6">
<button wire:click="cancelAliasForm" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<x-ri-close-line class="size-5" />
</button>
<h3 class="text-xl font-semibold mb-6">{{ $editingAliasName ? 'Edit' : 'Create' }} IP Alias</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-2">Alias Name</label>
<input type="text" wire:model="aliasName" placeholder="e.g., web-servers" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm" {{ $editingAliasName ? 'readonly' : '' }}>
@error('aliasName') <span class="text-error text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-medium mb-2">IP Address / CIDR</label>
<input type="text" wire:model="aliasCidr" placeholder="e.g., 192.168.1.100 or 10.0.0.0/24" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
@error('aliasCidr') <span class="text-error text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-medium mb-2">Comment (Optional)</label>
<input type="text" wire:model="aliasComment" placeholder="Description of this alias" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
</div>
</div>
<div class="flex gap-3 justify-end mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
<button wire:click="cancelAliasForm" class="px-4 py-2 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors text-sm font-medium">
Cancel
</button>
<button wire:click="saveAlias" wire:loading.attr="disabled" class="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors text-sm font-medium">
<span wire:loading.remove wire:target="saveAlias">Save Alias</span>
<span wire:loading wire:target="saveAlias">Saving...</span>
</button>
</div>
</div>
</div>
@endif
{{-- Delete Alias Modal --}}
@if($showDeleteAliasModal && $deletingAliasName)
<div class="fixed inset-0 z-[60] bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
<div class="relative w-full max-w-md bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6">
<button wire:click="cancelDeleteAlias" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<x-ri-close-line class="size-5" />
</button>
<div class="flex items-start gap-4 mb-4">
<div class="w-12 h-12 rounded-full bg-error/10 flex items-center justify-center flex-shrink-0">
<x-ri-delete-bin-fill class="size-6 text-error" />
</div>
<div>
<h3 class="text-xl font-bold mb-2">Delete IP Alias</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Delete alias "<span class="font-semibold">{{ $deletingAliasName }}</span>"?</p>
<p class="text-xs text-gray-500">This may affect firewall rules using this alias.</p>
</div>
</div>
<div class="flex gap-3 justify-end">
<x-proxmox::ui.button wire:click="cancelDeleteAlias" variant="secondary">
Cancel
</x-proxmox::ui.button>
<x-proxmox::ui.button wire:click="deleteAlias" wire:loading.attr="disabled" variant="danger">
<span wire:loading.remove wire:target="deleteAlias">Delete</span>
<span wire:loading wire:target="deleteAlias">Deleting...</span>
</x-proxmox::ui.button>
</div>
</div>
</div>
@endif
{{-- IPSet Form Modal --}}
@if($showIPSetForm)
<div class="fixed inset-0 z-[60] bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
<div class="relative w-full max-w-lg bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6">
<button wire:click="cancelIPSetForm" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<x-ri-close-line class="size-5" />
</button>
<h3 class="text-xl font-semibold mb-6">Create IP Set</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-2">IPSet Name</label>
<input type="text" wire:model="ipsetName" placeholder="e.g., trusted-networks" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
@error('ipsetName') <span class="text-error text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-medium mb-2">Comment (Optional)</label>
<input type="text" wire:model="ipsetComment" placeholder="Description of this IPSet" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
</div>
<div class="p-3 bg-info/10 border border-info/30 rounded-lg">
<p class="text-xs text-info">After creating the IPSet, you can add IP addresses and CIDR ranges to it.</p>
</div>
</div>
<div class="flex gap-3 justify-end mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
<button wire:click="cancelIPSetForm" class="px-4 py-2 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors text-sm font-medium">
Cancel
</button>
<button wire:click="saveIPSet" wire:loading.attr="disabled" class="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors text-sm font-medium">
<span wire:loading.remove wire:target="saveIPSet">Create IPSet</span>
<span wire:loading wire:target="saveIPSet">Creating...</span>
</button>
</div>
</div>
</div>
@endif
{{-- Delete IPSet Modal --}}
@if($showDeleteIPSetModal && $deletingIPSetName)
<div class="fixed inset-0 z-[60] bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
<div class="relative w-full max-w-md bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6">
<button wire:click="cancelDeleteIPSet" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<x-ri-close-line class="size-5" />
</button>
<div class="flex items-start gap-4 mb-4">
<div class="w-12 h-12 rounded-full bg-error/10 flex items-center justify-center flex-shrink-0">
<x-ri-delete-bin-fill class="size-6 text-error" />
</div>
<div>
<h3 class="text-xl font-bold mb-2">Delete IP Set</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Delete IPSet "<span class="font-semibold">{{ $deletingIPSetName }}</span>"?</p>
<p class="text-xs text-gray-500">All members will be removed and rules using this IPSet may be affected.</p>
</div>
</div>
<div class="flex gap-3 justify-end">
<x-proxmox::ui.button wire:click="cancelDeleteIPSet" variant="secondary">
Cancel
</x-proxmox::ui.button>
<x-proxmox::ui.button wire:click="deleteIPSet" wire:loading.attr="disabled" variant="danger">
<span wire:loading.remove wire:target="deleteIPSet">Delete</span>
<span wire:loading wire:target="deleteIPSet">Deleting...</span>
</x-proxmox::ui.button>
</div>
</div>
</div>
@endif
{{-- IPSet Member Form Modal --}}
@if($showIPSetMemberForm && $selectedIPSetName)
<div class="fixed inset-0 z-[60] bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
<div class="relative w-full max-w-lg bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6">
<button wire:click="cancelIPSetMemberForm" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<x-ri-close-line class="size-5" />
</button>
<h3 class="text-xl font-semibold mb-6">Add IP/CIDR to {{ $selectedIPSetName }}</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-2">IP Address / CIDR</label>
<input type="text" wire:model="memberCidr" placeholder="e.g., 192.168.1.100 or 10.0.0.0/16" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
@error('memberCidr') <span class="text-error text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-medium mb-2">Comment (Optional)</label>
<input type="text" wire:model="memberComment" placeholder="Description" class="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-sm">
</div>
<div class="flex items-center gap-2">
<input type="checkbox" wire:model="memberNomatch" id="memberNomatch" class="rounded border-gray-300 dark:border-gray-600">
<label for="memberNomatch" class="text-sm">
<span class="font-medium">No Match</span>
<span class="text-gray-500">(Exclude this IP from the set)</span>
</label>
</div>
</div>
<div class="flex gap-3 justify-end mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
<button wire:click="cancelIPSetMemberForm" class="px-4 py-2 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors text-sm font-medium">
Cancel
</button>
<button wire:click="saveIPSetMember" wire:loading.attr="disabled" class="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors text-sm font-medium">
<span wire:loading.remove wire:target="saveIPSetMember">Add Member</span>
<span wire:loading wire:target="saveIPSetMember">Adding...</span>
</button>
</div>
</div>
</div>
@endif
{{-- Delete IPSet Member Modal --}}
@if($showDeleteIPSetMemberModal && $deletingMemberCidr)
<div class="fixed inset-0 z-[60] bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
<div class="relative w-full max-w-md bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6">
<button wire:click="cancelDeleteIPSetMember" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<x-ri-close-line class="size-5" />
</button>
<div class="flex items-start gap-4 mb-4">
<div class="w-12 h-12 rounded-full bg-error/10 flex items-center justify-center flex-shrink-0">
<x-ri-close-circle-fill class="size-6 text-error" />
</div>
<div>
<h3 class="text-xl font-bold mb-2">Remove Member</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Remove "<span class="font-mono font-semibold">{{ $deletingMemberCidr }}</span>" from {{ $selectedIPSetName }}?</p>
</div>
</div>
<div class="flex gap-3 justify-end">
<x-proxmox::ui.button wire:click="cancelDeleteIPSetMember" variant="secondary">
Cancel
</x-proxmox::ui.button>
<x-proxmox::ui.button wire:click="deleteIPSetMember" wire:loading.attr="disabled" variant="danger">
<span wire:loading.remove wire:target="deleteIPSetMember">Remove</span>
<span wire:loading wire:target="deleteIPSetMember">Removing...</span>
</x-proxmox::ui.button>
</div>
</div>
</div>
@endif
{{-- Save Configuration Modal --}}
@if($showSaveConfigModal)
<div class="fixed inset-0 z-[60] bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
<div class="relative w-full max-w-lg bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6">
<button wire:click="closeSaveConfigModal" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<x-ri-close-line class="size-5" />
</button>
<div class="flex items-start gap-4 mb-6">
<div class="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0">
<x-ri-save-3-line class="size-6 text-primary" />
</div>
<div>
<h3 class="text-xl font-bold mb-1">Save Current Configuration</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">Save your current firewall rules and policies as a reusable configuration</p>
</div>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-2">Configuration Name</label>
<input type="text" wire:model="newConfigName" placeholder="e.g., Web Server Rules"
class="w-full px-4 py-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:border-primary focus:outline-none transition-colors">
@error('newConfigName')
<p class="text-error text-xs mt-1">{{ $message }}</p>
@enderror
</div>
<div class="bg-gray-50 dark:bg-gray-800 border border-gray-100 dark:border-gray-700 rounded-lg p-4">
<h4 class="text-sm font-semibold mb-3">Configuration Summary</h4>
<div class="grid grid-cols-2 gap-3 text-sm">
<div class="flex items-center justify-between p-2 bg-white dark:bg-gray-900 rounded">
<span class="text-gray-500">Rules</span>
<span class="font-semibold">{{ count($rules) }}</span>
</div>
<div class="flex items-center justify-between p-2 bg-white dark:bg-gray-900 rounded">
<span class="text-gray-500">Incoming Policy</span>
<span class="font-semibold {{ $policyIn === 'DROP' ? 'text-error' : 'text-success' }}">{{ $policyIn }}</span>
</div>
<div class="flex items-center justify-between p-2 bg-white dark:bg-gray-900 rounded">
<span class="text-gray-500">Outgoing Policy</span>
<span class="font-semibold {{ $policyOut === 'DROP' ? 'text-error' : 'text-success' }}">{{ $policyOut }}</span>
</div>
</div>
</div>
</div>
<div class="flex gap-3 justify-end mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
<button wire:click="closeSaveConfigModal" class="px-4 py-2 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors text-sm font-medium">
Cancel
</button>
<button wire:click="saveConfiguration" wire:loading.attr="disabled" class="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors text-sm font-medium">
<span wire:loading.remove wire:target="saveConfiguration">Save Configuration</span>
<span wire:loading wire:target="saveConfiguration">Saving...</span>
</button>
</div>
</div>
</div>
@endif
</div>