Files
vacuum-wall/webui/templates/zones.html
T
mteehan 37039351be fix htmx refactor route mismatches and remaining TODO items
- 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
2026-05-17 01:17:12 +00:00

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 %}