37039351be
- wireguard: POST /peers with JSON encoding (was /add-peer) - rules: delete by rule_id in URL path (was JSON body); pass rule objects with id from server; add hx-disable to initial render - nat: port forward delete uses URL path params to match blueprint - nat: masquerade toggle uses native hx-post/hx-vals (was inline fetch) - app.js renderers updated to use URL path deletes for rules and forwards - remove TODO.md
91 lines
4.1 KiB
HTML
91 lines
4.1 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Zones - Vacuum Wall{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<div>
|
|
<h1>Zones</h1>
|
|
<div class="subtitle">Firewalld zone management</div>
|
|
</div>
|
|
<button class="btn btn-primary" onclick="openModal('create-zone-modal')">+ Create Zone</button>
|
|
</div>
|
|
|
|
<div id="zone-grid" class="card-grid">
|
|
{% for zone in (zones or []) %}
|
|
<div class="card" style="position:relative;">
|
|
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
|
|
<div>
|
|
<h3 style="font-size:16px;color:var(--accent);">{{ zone.get('name', 'unnamed') }}</h3>
|
|
<div class="text-muted text-sm" style="margin-bottom:10px;">
|
|
{% if zone.get('target') %}Target: {{ zone.target }}{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-sm mb-4">
|
|
<div class="text-muted" style="margin-bottom:4px;">Interfaces</div>
|
|
{% if zone.get('interfaces') %}
|
|
{% for iface in zone.interfaces %}
|
|
<span class="badge badge-info">{{ iface }}</span>
|
|
{% endfor %}
|
|
{% else %}
|
|
<span class="text-muted">None</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="text-sm mb-4">
|
|
<div class="text-muted" style="margin-bottom:4px;">Services</div>
|
|
{% if zone.get('services') %}
|
|
{% for svc in zone.services %}
|
|
<span class="badge badge-success">{{ svc }}</span>
|
|
{% endfor %}
|
|
{% else %}
|
|
<span class="text-muted">None</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div style="display:flex;justify-content:flex-end;gap:6px;margin-top:12px;">
|
|
<form hx-delete="/api/firewall/zones/{{ zone.get('name', '') }}" hx-swap="none" hx-confirm="Delete zone {{ zone.name }}? This will affect traffic to its interfaces." hx-on::after-request="if(evt.detail.successful){ refreshTable('/api/firewall/zones', document.getElementById('zone-grid'), renderZones); showSuccessToast('Zone deleted'); }">
|
|
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% if not (zones or []) %}
|
|
<div class="card">
|
|
<div class="text-muted text-sm">No zones configured. Create a zone to get started.</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Create Zone Modal -->
|
|
<div class="modal-overlay" id="create-zone-modal" onclick="if(event.target===this) closeModal('create-zone-modal')">
|
|
<div class="modal">
|
|
<h2>Create Zone</h2>
|
|
<form hx-post="/api/firewall/zones" hx-swap="none" hx-on::after-request="if(evt.detail.successful){ closeModal('create-zone-modal'); refreshTable('/api/firewall/zones', document.getElementById('zone-grid'), renderZones); showSuccessToast('Zone created'); }">
|
|
<div class="form-group">
|
|
<label for="zone-name">Zone Name</label>
|
|
<input type="text" id="zone-name" name="name" placeholder="e.g., trusted, dmz, external" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="zone-target">Target</label>
|
|
<select id="zone-target" name="target">
|
|
<option value="default">default</option>
|
|
<option value="%%REJECT%%">%REJECT%</option>
|
|
<option value="%%DROP%%">%DROP%</option>
|
|
<option value="%%ACCEPT%%">%ACCEPT%</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="zone-services">Default Services (comma-separated)</label>
|
|
<input type="text" id="zone-services" name="services" placeholder="e.g., dhcp, dns, ssh">
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button type="button" class="btn btn-outline" onclick="closeModal('create-zone-modal')">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">Create</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|