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:
@@ -0,0 +1,151 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}NAT - Vacuum Wall{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1>NAT & Port Forwarding</h1>
|
||||
<div class="subtitle">Masquerading and destination NAT rules</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-title">Masquerade (Source NAT)</div>
|
||||
|
||||
<div class="card">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Zone</th>
|
||||
<th style="width:120px;">Masquerade</th>
|
||||
<th style="width:80px;">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for zone in (zones or []) %}
|
||||
<tr>
|
||||
<td><strong>{{ zone.get('name', 'unnamed') }}</strong></td>
|
||||
<td>
|
||||
<label class="switch">
|
||||
<input type="checkbox"
|
||||
{% if zone.get('masquerade') %}checked{% endif %}
|
||||
onchange="toggleMasquerade('{{ zone.get('name', '') }}', this.checked)">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick="toggleMasquerade('{{ zone.get('name', '') }}', this.previousElementSibling.querySelector('input').checked)">Apply</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not (zones or []) %}
|
||||
<tr>
|
||||
<td colspan="3" class="text-muted text-sm">No zones configured</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="section-title">Port Forwarding (DNAT)</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<h3>Add Forward Rule</h3>
|
||||
<form hx-post="/api/firewall/nat/forward" hx-swap="none" class="htmx-on-success" data-success="Forward rule added" onsuccess="setTimeout(function(){ location.reload(); }, 300);">
|
||||
<div class="inline-form">
|
||||
<div class="form-group">
|
||||
<label for="fw-zone">Zone</label>
|
||||
<select id="fw-zone" name="zone" required>
|
||||
<option value="">— Select —</option>
|
||||
{% for zone in (zones or []) %}
|
||||
<option value="{{ zone.get('name', '') }}">{{ zone.get('name', '') }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="fw-protocol">Protocol</label>
|
||||
<select id="fw-protocol" name="protocol">
|
||||
<option value="tcp">TCP</option>
|
||||
<option value="udp">UDP</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="fw-port">Port</label>
|
||||
<input type="number" id="fw-port" name="port" placeholder="80" min="1" max="65535" required style="width:80px;">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="fw-target">Target Address</label>
|
||||
<input type="text" id="fw-target" name="target" placeholder="192.168.1.100" required style="width:160px;">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="fw-target-port">Target Port</label>
|
||||
<input type="number" id="fw-target-port" name="target_port" placeholder="80" min="1" max="65535" style="width:90px;">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Add</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Zone</th>
|
||||
<th>Proto</th>
|
||||
<th>Port</th>
|
||||
<th>Target</th>
|
||||
<th>Tgt Port</th>
|
||||
<th style="width:80px;">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% set all_forwards = [] %}
|
||||
{% for zone in (zones or []) %}
|
||||
{% for fwd in zone.get('forward_ports', []) %}
|
||||
{% set _ = all_forwards.append({'zone': zone.get('name'), 'proxy-protocol': fwd.get('proxy-protocol'), 'port': fwd.get('port'), 'to-addr': fwd.get('to-addr'), 'to-port': fwd.get('to-port')}) %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% for fwd in all_forwards %}
|
||||
<tr>
|
||||
<td><strong>{{ fwd.zone }}</strong></td>
|
||||
<td><span class="badge badge-info">{{ fwd['proxy-protocol'] }}</span></td>
|
||||
<td>{{ fwd.port }}</td>
|
||||
<td>{{ fwd['to-addr'] }}</td>
|
||||
<td>{{ fwd['to-port'] }}</td>
|
||||
<td>
|
||||
<form hx-delete="/api/firewall/nat/forward/{{ fwd.zone | urlencode }}/{{ fwd['proxy-protocol'] }}/{{ fwd['to-addr'] }}/{{ fwd['to-port'] }}" hx-swap="none" class="htmx-on-success" data-success="Rule removed" onsuccess="setTimeout(function(){ location.reload(); }, 300);">
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Remove this forward rule?')">Remove</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not all_forwards %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-muted text-sm">No port forwarding rules configured</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleMasquerade(zoneName, enabled) {
|
||||
var url = '/api/firewall/nat/masquerade/' + encodeURIComponent(zoneName);
|
||||
var body = JSON.stringify({ enable: enabled });
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: body
|
||||
})
|
||||
.then(function(res) {
|
||||
if (res.ok) {
|
||||
showToast('Masquerade ' + (enabled ? 'enabled' : 'disabled') + ' for ' + zoneName, 'success');
|
||||
} else {
|
||||
return res.text().then(function(t) { throw new Error(t); });
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
showToast('Failed: ' + err.message, 'error');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user