09138fbc18
Extract 9 route groups out of app.py into routes/ blueprints:
- routes/network.py — DNS, DHCP, NTP, network info/test (10 routes)
- routes/wireguard.py — WireGuard keys, peers, config, enforcement (18 routes)
- routes/cells.py — cell-to-cell connections (5 routes)
- routes/peers.py — peer CRUD + IP update + _next_peer_ip helper (10 routes)
- routes/routing.py — NAT, peer routes, firewall, iptables (17 routes)
- routes/vault.py — certs, trust, secrets (19 routes)
- routes/containers.py — containers, images, volumes (14 routes)
- routes/services.py — service bus, logs, services status/connectivity (18 routes)
- routes/peer_dashboard.py — peer-scoped dashboard/services (2 routes)
All blueprints use lazy `from app import X` inside route bodies to preserve
test patch compatibility (patch('app.email_manager', mock) still works).
Also included in this commit:
- A1 fix: backup/restore now includes email/calendar user files
- A2 fix: apply_config sets applying=True flag via helper container
- A3 fix: add_peer rolls back firewall on DNS failure
app.py reduced: 3011 → 1294 lines. 1021 tests passing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
208 lines
8.0 KiB
Python
208 lines
8.0 KiB
Python
import logging
|
|
from flask import Blueprint, request, jsonify
|
|
logger = logging.getLogger('picell')
|
|
bp = Blueprint('routing', __name__)
|
|
|
|
@bp.route('/api/routing/status', methods=['GET'])
|
|
def get_routing_status():
|
|
try:
|
|
from app import routing_manager
|
|
return jsonify(routing_manager.get_status())
|
|
except Exception as e:
|
|
logger.error(f"Error getting routing status: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/setup', methods=['POST'])
|
|
def setup_routing():
|
|
try:
|
|
from app import routing_manager
|
|
status = routing_manager.get_status()
|
|
return jsonify({'success': True, 'message': 'Routing managed by WireGuard PostUp rules', **status})
|
|
except Exception as e:
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/nat', methods=['GET'])
|
|
def get_nat_rules():
|
|
try:
|
|
from app import routing_manager
|
|
return jsonify({"nat_rules": routing_manager.get_nat_rules()})
|
|
except Exception as e:
|
|
logger.error(f"Error getting NAT rules: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/nat', methods=['POST'])
|
|
def add_nat_rule():
|
|
try:
|
|
from app import routing_manager
|
|
data = request.get_json(silent=True) or {}
|
|
result = routing_manager.add_nat_rule(
|
|
source_network=data.get('source_network'),
|
|
target_interface=data.get('target_interface'),
|
|
masquerade=data.get('masquerade', True),
|
|
nat_type=data.get('nat_type', 'MASQUERADE'),
|
|
protocol=data.get('protocol', 'ALL'),
|
|
external_port=data.get('external_port'),
|
|
internal_ip=data.get('internal_ip'),
|
|
internal_port=data.get('internal_port')
|
|
)
|
|
return jsonify({'success': result})
|
|
except Exception as e:
|
|
logger.error(f"Error adding NAT rule: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/nat/<rule_id>', methods=['DELETE'])
|
|
def remove_nat_rule(rule_id):
|
|
try:
|
|
from app import routing_manager
|
|
return jsonify(routing_manager.remove_nat_rule(rule_id))
|
|
except Exception as e:
|
|
logger.error(f"Error removing NAT rule: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/peers', methods=['GET'])
|
|
def get_peer_routes():
|
|
try:
|
|
from app import routing_manager
|
|
return jsonify({"peer_routes": routing_manager.get_peer_routes()})
|
|
except Exception as e:
|
|
logger.error(f"Error getting peer routes: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/peers', methods=['POST'])
|
|
def add_peer_route():
|
|
try:
|
|
from app import routing_manager
|
|
data = request.get_json(silent=True) or {}
|
|
peer_name = data.get('peer_name')
|
|
peer_ip = data.get('peer_ip')
|
|
if not peer_name or not peer_ip:
|
|
return jsonify({"error": "Missing required fields: peer_name, peer_ip"}), 400
|
|
result = routing_manager.add_peer_route(
|
|
peer_name, peer_ip,
|
|
data.get('allowed_networks', []),
|
|
data.get('route_type', 'lan')
|
|
)
|
|
return jsonify({"added": result})
|
|
except Exception as e:
|
|
logger.error(f"Error adding peer route: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/peers/<peer_name>', methods=['DELETE'])
|
|
def remove_peer_route(peer_name):
|
|
try:
|
|
from app import routing_manager
|
|
return jsonify(routing_manager.remove_peer_route(peer_name))
|
|
except Exception as e:
|
|
logger.error(f"Error removing peer route: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/exit-nodes', methods=['POST'])
|
|
def add_exit_node():
|
|
try:
|
|
from app import routing_manager
|
|
data = request.get_json(silent=True) or {}
|
|
peer_name = data.get('peer_name')
|
|
peer_ip = data.get('peer_ip')
|
|
if not peer_name or not peer_ip:
|
|
return jsonify({"error": "Missing required fields: peer_name, peer_ip"}), 400
|
|
return jsonify({"added": routing_manager.add_exit_node(peer_name, peer_ip, data.get('allowed_domains'))})
|
|
except Exception as e:
|
|
logger.error(f"Error adding exit node: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/bridge', methods=['POST'])
|
|
def add_bridge_route():
|
|
try:
|
|
from app import routing_manager
|
|
data = request.get_json(silent=True) or {}
|
|
source_peer = data.get('source_peer')
|
|
target_peer = data.get('target_peer')
|
|
if not source_peer or not target_peer:
|
|
return jsonify({"error": "Missing required fields: source_peer, target_peer"}), 400
|
|
return jsonify({"added": routing_manager.add_bridge_route(source_peer, target_peer, data.get('allowed_networks', []))})
|
|
except Exception as e:
|
|
logger.error(f"Error adding bridge route: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/split', methods=['POST'])
|
|
def add_split_route():
|
|
try:
|
|
from app import routing_manager
|
|
data = request.get_json(silent=True) or {}
|
|
network = data.get('network')
|
|
exit_peer = data.get('exit_peer')
|
|
if not network or not exit_peer:
|
|
return jsonify({"error": "Missing required fields: network, exit_peer"}), 400
|
|
return jsonify({"added": routing_manager.add_split_route(network, exit_peer, data.get('fallback_peer'))})
|
|
except Exception as e:
|
|
logger.error(f"Error adding split route: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/firewall', methods=['GET'])
|
|
def get_firewall_rules():
|
|
try:
|
|
from app import routing_manager
|
|
return jsonify({"firewall_rules": routing_manager.get_firewall_rules()})
|
|
except Exception as e:
|
|
logger.error(f"Error getting firewall rules: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/firewall', methods=['POST'])
|
|
def add_firewall_rule():
|
|
try:
|
|
from app import routing_manager
|
|
data = request.get_json(silent=True) or {}
|
|
result = routing_manager.add_firewall_rule(
|
|
rule_type=data.get('rule_type'),
|
|
source=data.get('source'),
|
|
destination=data.get('destination'),
|
|
action=data.get('action', 'ACCEPT'),
|
|
port=data.get('port'),
|
|
protocol=data.get('protocol', 'ALL'),
|
|
port_range=data.get('port_range')
|
|
)
|
|
return jsonify({'success': result})
|
|
except Exception as e:
|
|
logger.error(f"Error adding firewall rule: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/firewall/<rule_id>', methods=['DELETE'])
|
|
def remove_firewall_rule(rule_id):
|
|
try:
|
|
from app import routing_manager
|
|
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
|
|
|
|
@bp.route('/api/routing/live-iptables', methods=['GET'])
|
|
def get_live_iptables():
|
|
try:
|
|
from app import routing_manager
|
|
return jsonify(routing_manager.get_live_iptables())
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
@bp.route('/api/routing/connectivity', methods=['POST'])
|
|
def test_routing_connectivity():
|
|
try:
|
|
from app import routing_manager
|
|
data = request.get_json(silent=True) or {}
|
|
return jsonify(routing_manager.test_routing_connectivity(
|
|
data.get('target_ip', '8.8.8.8'),
|
|
data.get('via_peer')
|
|
))
|
|
except Exception as e:
|
|
logger.error(f"Error testing routing connectivity: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@bp.route('/api/routing/logs', methods=['GET'])
|
|
def get_routing_logs():
|
|
try:
|
|
from app import routing_manager
|
|
lines = request.args.get('lines', 50, type=int)
|
|
return jsonify(routing_manager.get_logs(lines))
|
|
except Exception as e:
|
|
logger.error(f"Error getting routing logs: {e}")
|
|
return jsonify({"error": str(e)}), 500
|