e2f56b8cc8
Flask WebUI behind nginx reverse proxy with zone-based firewall, DHCP, WireGuard, and ACME certificate management.
89 lines
3.3 KiB
HTML
89 lines
3.3 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Interfaces - Vacuum Wall{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<div>
|
|
<h1>Interfaces</h1>
|
|
<div class="subtitle">Network interface to zone bindings</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Interface</th>
|
|
<th>MAC Address</th>
|
|
<th>IP Address</th>
|
|
<th>State</th>
|
|
<th>Zone</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for iface in (interfaces or []) %}
|
|
<tr>
|
|
<td><strong>{{ iface.get('name', 'unknown') }}</strong></td>
|
|
<td class="text-muted">{{ iface.get('mac', 'N/A') }}</td>
|
|
<td>
|
|
{% for ip in iface.get('ips', []) %}
|
|
{{ ip }}{% if not loop.last %}, {% endif %}
|
|
{% endfor %}
|
|
{% if not iface.get('ips') %}N/A{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="status-dot {{ 'status-up' if iface.get('state') == 'up' else 'status-down' }}"></span>
|
|
{{ 'Up' if iface.get('state') == 'up' else 'Down' }}
|
|
</td>
|
|
<td>
|
|
{% if zones %}
|
|
<select
|
|
class="htmx-on-success"
|
|
data-success="Zone updated for {{ iface.name }}"
|
|
hx-post="/api/firewall/zones/__ZONE__/interfaces/{{ iface.name }}"
|
|
hx-swap="none"
|
|
hx-select-oob="#toast-container *"
|
|
onchange="assignInterfaceToZone(this, '{{ iface.name }}', '{{ iface.get('zone', '') }}')"
|
|
>
|
|
{% for zone in zones %}
|
|
<option value="{{ zone.get('name', '') }}" {% if zone.get('name') == iface.get('zone') %}selected{% endif %}>{{ zone.get('name', '') }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
{% else %}
|
|
<span class="text-muted">No zones configured</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline" onclick="assignInterfaceToZone(this.previousElementSibling, '{{ iface.name }}', null)">Apply</button>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% if not (interfaces or []) %}
|
|
<tr>
|
|
<td colspan="6" class="text-muted text-sm">No interfaces found</td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<script>
|
|
function assignInterfaceToZone(selectEl, ifaceName, currentZone) {
|
|
var zoneName = selectEl.value;
|
|
var url = '/api/firewall/zones/' + encodeURIComponent(zoneName) + '/interfaces/' + encodeURIComponent(ifaceName);
|
|
fetch(url, { method: 'POST' })
|
|
.then(function(res) {
|
|
if (res.ok) {
|
|
showToast('Assigned ' + ifaceName + ' to zone ' + zoneName, 'success');
|
|
} else {
|
|
return res.text().then(function(txt) { throw new Error(txt); });
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
showToast('Failed to assign: ' + err.message, 'error');
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %}
|