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
This commit is contained in:
2026-05-17 01:15:52 +00:00
parent 0e7090a2cb
commit 37039351be
26 changed files with 1737 additions and 848 deletions
+19 -39
View File
@@ -17,7 +17,6 @@
<tr>
<th>Zone</th>
<th style="width:120px;">Masquerade</th>
<th style="width:80px;">Action</th>
</tr>
</thead>
<tbody>
@@ -25,21 +24,23 @@
<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>
<form hx-post="/api/firewall/masquerade" hx-encoding="json" hx-vals='{"zone": "{{ zone.get('name', '') }}", "enable": JSON.stringify(this.checked)}' hx-swap="none" hx-on::after-request="if(evt.detail.successful){ showSuccessToast('Masquerade '+(this.checked?'enabled':'disabled')+' for {{ zone.get('name', '') }}') } else { this.checked=!this.checked; }">
<label class="switch">
<input type="checkbox"
{% if zone.get('masquerade') %}checked{% endif %}
id="masq-{{ zone.get('name', '') }}"
hx-trigger="change from:#masq-{{ zone.get('name', '') }}"
disabled>
<span class="slider"></span>
</label>
<button type="submit" style="display:none"></button>
</form>
</td>
</tr>
{% endfor %}
{% if not (zones or []) %}
<tr>
<td colspan="3" class="text-muted text-sm">No zones configured</td>
<td colspan="2" class="text-muted text-sm">No zones configured</td>
</tr>
{% endif %}
</tbody>
@@ -50,7 +51,7 @@
<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);">
<form hx-post="/api/firewall/forward-port" hx-swap="none" hx-on::after-request="if(evt.detail.successful){ refreshTable('/api/firewall/config', document.getElementById('forward-rows'), renderForwardsFromConfig); showSuccessToast('Forward rule added'); }">
<div class="inline-form">
<div class="form-group">
<label for="fw-zone">Zone</label>
@@ -63,7 +64,7 @@
</div>
<div class="form-group">
<label for="fw-protocol">Protocol</label>
<select id="fw-protocol" name="protocol">
<select id="fw-protocol" name="proto">
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
</select>
@@ -74,11 +75,11 @@
</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;">
<input type="text" id="fw-target" name="toaddr" 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;">
<input type="number" id="fw-target-port" name="toport" placeholder="80" min="1" max="65535" style="width:90px;">
</div>
<button type="submit" class="btn btn-primary">Add</button>
</div>
@@ -97,7 +98,7 @@
<th style="width:80px;">Action</th>
</tr>
</thead>
<tbody>
<tbody id="forward-rows">
{% set all_forwards = [] %}
{% for zone in (zones or []) %}
{% for fwd in zone.get('forward_ports', []) %}
@@ -112,8 +113,8 @@
<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 hx-delete="/api/firewall/forward-port/{{ fwd.zone }}/{{ fwd.port }}/{{ fwd['proxy-protocol'] }}" hx-swap="none" hx-on::after-request="if(evt.detail.successful){ refreshTable('/api/firewall/config', document.getElementById('forward-rows'), renderForwardsFromConfig); showSuccessToast('Rule removed'); }">
<button type="submit" class="btn btn-sm btn-danger" hx-confirm="Remove forward rule {{ fwd.port }}/{{ fwd['proxy-protocol'] }} → {{ fwd['to-addr'] }}:{{ fwd['to-port'] }}?">Remove</button>
</form>
</td>
</tr>
@@ -127,25 +128,4 @@
</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 %}