feat: routing page — port forwarding tab, live iptables, diagnostics, firewall delete
Backend: - routing_manager.remove_firewall_rule(): remove stored rule + iptables -D - routing_manager.get_live_iptables(): dump filter/nat tables from cell-wireguard - DELETE /api/routing/firewall/<rule_id> endpoint (was missing) - GET /api/routing/live-iptables endpoint Frontend Routing.jsx — 7 tabs: - Overview: proper routing table with destination/gateway/interface columns - Port Forwarding: clean DNAT form (protocol, ext port → internal IP:port) - NAT Rules: MASQUERADE/SNAT only, cleaner layout - Peer Routes: IP route entries through VPN peers - Firewall: custom rules with working delete button - Live iptables: read-only terminal view of actual running rules in cell-wireguard - Diagnostics: ping + traceroute test from server with output display Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+15
@@ -1619,6 +1619,21 @@ def add_firewall_rule():
|
|||||||
logger.error(f"Error adding firewall rule: {e}")
|
logger.error(f"Error adding firewall rule: {e}")
|
||||||
return jsonify({"error": str(e)}), 500
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/api/routing/firewall/<rule_id>', methods=['DELETE'])
|
||||||
|
def remove_firewall_rule(rule_id):
|
||||||
|
try:
|
||||||
|
result = routing_manager.remove_firewall_rule(rule_id)
|
||||||
|
return jsonify({'success': result}), (200 if result else 404)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/api/routing/live-iptables', methods=['GET'])
|
||||||
|
def get_live_iptables():
|
||||||
|
try:
|
||||||
|
return jsonify(routing_manager.get_live_iptables())
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
@app.route('/api/routing/connectivity', methods=['POST'])
|
@app.route('/api/routing/connectivity', methods=['POST'])
|
||||||
def test_routing_connectivity():
|
def test_routing_connectivity():
|
||||||
"""Test routing connectivity."""
|
"""Test routing connectivity."""
|
||||||
|
|||||||
@@ -485,6 +485,49 @@ class RoutingManager(BaseServiceManager):
|
|||||||
logger.error(f"Failed to get routing logs: {e}")
|
logger.error(f"Failed to get routing logs: {e}")
|
||||||
return {'error': str(e)}
|
return {'error': str(e)}
|
||||||
|
|
||||||
|
def remove_firewall_rule(self, rule_id: str) -> bool:
|
||||||
|
"""Remove a stored firewall rule and delete it from iptables."""
|
||||||
|
try:
|
||||||
|
rules = self._load_rules()
|
||||||
|
rule = next((r for r in rules['firewall_rules'] if r['id'] == rule_id), None)
|
||||||
|
if not rule:
|
||||||
|
return False
|
||||||
|
rules['firewall_rules'] = [r for r in rules['firewall_rules'] if r['id'] != rule_id]
|
||||||
|
self._save_rules(rules)
|
||||||
|
try:
|
||||||
|
cmd = ['iptables', '-D', rule['rule_type'],
|
||||||
|
'-s', rule['source'], '-d', rule['destination']]
|
||||||
|
if rule.get('protocol') and rule['protocol'] != 'ALL':
|
||||||
|
cmd += ['-p', rule['protocol'].lower()]
|
||||||
|
if rule.get('port'):
|
||||||
|
cmd += ['--dport', str(rule['port'])]
|
||||||
|
if rule.get('port_range'):
|
||||||
|
cmd += ['--dport', rule['port_range'].replace('-', ':')]
|
||||||
|
cmd += ['-j', rule['action']]
|
||||||
|
subprocess.run(cmd, capture_output=True, timeout=10)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"iptables -D failed (rule may already be gone): {e}")
|
||||||
|
logger.info(f"Removed firewall rule {rule_id}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to remove firewall rule: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_live_iptables(self) -> dict:
|
||||||
|
"""Return live iptables rules from the WireGuard container."""
|
||||||
|
out = {}
|
||||||
|
for table in ('filter', 'nat'):
|
||||||
|
try:
|
||||||
|
r = subprocess.run(
|
||||||
|
['docker', 'exec', 'cell-wireguard',
|
||||||
|
'iptables', '-t', table, '-L', '-n', '-v', '--line-numbers'],
|
||||||
|
capture_output=True, text=True, timeout=10
|
||||||
|
)
|
||||||
|
out[table] = r.stdout if r.returncode == 0 else r.stderr
|
||||||
|
except Exception as e:
|
||||||
|
out[table] = str(e)
|
||||||
|
return out
|
||||||
|
|
||||||
def get_nat_rules(self):
|
def get_nat_rules(self):
|
||||||
"""Return all NAT rules."""
|
"""Return all NAT rules."""
|
||||||
rules = self._load_rules()
|
rules = self._load_rules()
|
||||||
|
|||||||
+628
-705
File diff suppressed because it is too large
Load Diff
@@ -137,6 +137,7 @@ export const routingAPI = {
|
|||||||
getFirewallRules: () => api.get('/api/routing/firewall'),
|
getFirewallRules: () => api.get('/api/routing/firewall'),
|
||||||
addFirewallRule: (rule) => api.post('/api/routing/firewall', rule),
|
addFirewallRule: (rule) => api.post('/api/routing/firewall', rule),
|
||||||
deleteFirewallRule: (ruleId) => api.delete(`/api/routing/firewall/${ruleId}`),
|
deleteFirewallRule: (ruleId) => api.delete(`/api/routing/firewall/${ruleId}`),
|
||||||
|
getLiveIptables: () => api.get('/api/routing/live-iptables'),
|
||||||
// Other
|
// Other
|
||||||
addExitNode: (node) => api.post('/api/routing/exit-nodes', node),
|
addExitNode: (node) => api.post('/api/routing/exit-nodes', node),
|
||||||
addBridgeRoute: (route) => api.post('/api/routing/bridge', route),
|
addBridgeRoute: (route) => api.post('/api/routing/bridge', route),
|
||||||
|
|||||||
Reference in New Issue
Block a user