A5: Extract all route groups into Flask blueprints (app.py -1735 lines)
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>
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
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
|
||||
Reference in New Issue
Block a user