Initial commit: SSL proxy / firewall appliance

Flask WebUI behind nginx reverse proxy with zone-based firewall, DHCP,
WireGuard, and ACME certificate management.
This commit is contained in:
2026-05-07 22:24:24 +00:00
commit e2f56b8cc8
56 changed files with 10013 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
{% 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 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 method="POST" action="/api/firewall/zones/{{ zone.get('name', '') }}/delete" hx-post="/api/firewall/zones/{{ zone.get('name', '') }}/delete" hx-swap="none" onsubmit="return confirm('Delete zone {{ zone.name }}? This will affect traffic to its interfaces.');" class="htmx-on-success" data-success="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" class="htmx-on-success" data-success="Zone created" onsuccess="setTimeout(function(){closeModal('create-zone-modal');},500); location.reload();">
<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 %}