wip
This commit is contained in:
parent
3b2f1db4ce
commit
5c16964b76
47 changed files with 2080 additions and 1053 deletions
|
@ -1,260 +1,267 @@
|
|||
from flask import Blueprint, render_template, redirect, url_for, request, flash, jsonify
|
||||
from flask_login import login_required
|
||||
from app.core.models import Subnet, Server
|
||||
from app.core.models import Subnet, Server, App
|
||||
from app.core.extensions import db
|
||||
from app.scripts.ip_scanner import scan
|
||||
import ipaddress
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
bp = Blueprint('ipam', __name__, url_prefix='/ipam')
|
||||
bp = Blueprint("ipam", __name__, url_prefix="/ipam")
|
||||
|
||||
@bp.route('/')
|
||||
|
||||
@bp.route("/")
|
||||
@login_required
|
||||
def ipam_home():
|
||||
"""Main IPAM dashboard"""
|
||||
subnets = Subnet.query.all()
|
||||
|
||||
|
||||
# Calculate usage for each subnet
|
||||
for subnet in subnets:
|
||||
network = ipaddress.ip_network(subnet.cidr, strict=False)
|
||||
max_hosts = network.num_addresses - 2 if network.prefixlen < 31 else network.num_addresses
|
||||
max_hosts = (
|
||||
network.num_addresses - 2
|
||||
if network.prefixlen < 31
|
||||
else network.num_addresses
|
||||
)
|
||||
used_count = Server.query.filter_by(subnet_id=subnet.id).count()
|
||||
subnet.usage_percent = (used_count / max_hosts) * 100 if max_hosts > 0 else 0
|
||||
|
||||
|
||||
return render_template(
|
||||
'ipam/index.html',
|
||||
title='IPAM Dashboard',
|
||||
subnets=subnets,
|
||||
now=datetime.now()
|
||||
"ipam/index.html", title="IPAM Dashboard", subnets=subnets, now=datetime.now()
|
||||
)
|
||||
|
||||
@bp.route('/subnet/new', methods=['GET', 'POST'])
|
||||
|
||||
@bp.route("/subnet/new", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def subnet_new():
|
||||
"""Create a new subnet"""
|
||||
if request.method == 'POST':
|
||||
cidr = request.form.get('cidr')
|
||||
location = request.form.get('location')
|
||||
auto_scan = request.form.get('auto_scan') == 'on'
|
||||
|
||||
if request.method == "POST":
|
||||
cidr = request.form.get("cidr")
|
||||
location = request.form.get("location")
|
||||
auto_scan = request.form.get("auto_scan") == "on"
|
||||
|
||||
# Basic validation
|
||||
if not cidr or not location:
|
||||
flash('Please fill in all required fields', 'danger')
|
||||
return render_template(
|
||||
'ipam/subnet_form.html',
|
||||
title='New Subnet'
|
||||
)
|
||||
|
||||
flash("Please fill in all required fields", "danger")
|
||||
return render_template("ipam/subnet_form.html", title="New Subnet")
|
||||
|
||||
# Validate CIDR format
|
||||
try:
|
||||
ipaddress.ip_network(cidr, strict=False)
|
||||
except ValueError:
|
||||
flash('Invalid CIDR format', 'danger')
|
||||
return render_template(
|
||||
'ipam/subnet_form.html',
|
||||
title='New Subnet'
|
||||
)
|
||||
|
||||
flash("Invalid CIDR format", "danger")
|
||||
return render_template("ipam/subnet_form.html", title="New Subnet")
|
||||
|
||||
# Check if CIDR already exists
|
||||
if Subnet.query.filter_by(cidr=cidr).first():
|
||||
flash('Subnet already exists', 'danger')
|
||||
return render_template(
|
||||
'ipam/subnet_form.html',
|
||||
title='New Subnet'
|
||||
)
|
||||
|
||||
flash("Subnet already exists", "danger")
|
||||
return render_template("ipam/subnet_form.html", title="New Subnet")
|
||||
|
||||
# Create new subnet with JSON string for active_hosts, not a Python list
|
||||
subnet = Subnet(
|
||||
cidr=cidr,
|
||||
location=location,
|
||||
active_hosts=json.dumps([]), # Convert empty list to JSON string
|
||||
last_scanned=None,
|
||||
auto_scan=auto_scan
|
||||
auto_scan=auto_scan,
|
||||
)
|
||||
|
||||
|
||||
db.session.add(subnet)
|
||||
db.session.commit()
|
||||
|
||||
flash('Subnet created successfully', 'success')
|
||||
return redirect(url_for('ipam.subnet_view', subnet_id=subnet.id))
|
||||
|
||||
return render_template(
|
||||
'ipam/subnet_form.html',
|
||||
title='New Subnet'
|
||||
)
|
||||
|
||||
@bp.route('/subnet/<int:subnet_id>')
|
||||
flash("Subnet created successfully", "success")
|
||||
return redirect(url_for("ipam.subnet_view", subnet_id=subnet.id))
|
||||
|
||||
return render_template("ipam/subnet_form.html", title="New Subnet")
|
||||
|
||||
|
||||
@bp.route("/subnet/<int:subnet_id>")
|
||||
@login_required
|
||||
def subnet_view(subnet_id):
|
||||
"""View a specific subnet"""
|
||||
"""View a subnet and all its hosts"""
|
||||
subnet = Subnet.query.get_or_404(subnet_id)
|
||||
|
||||
# Get all servers in this subnet
|
||||
|
||||
# Get servers in this subnet
|
||||
servers = Server.query.filter_by(subnet_id=subnet_id).all()
|
||||
|
||||
# Parse CIDR for display
|
||||
|
||||
# Get applications in this subnet
|
||||
subnet_apps = []
|
||||
for server in servers:
|
||||
apps = App.query.filter_by(server_id=server.id).all()
|
||||
subnet_apps.extend(apps)
|
||||
|
||||
# Calculate usage statistics
|
||||
network = ipaddress.ip_network(subnet.cidr, strict=False)
|
||||
subnet_info = {
|
||||
'network_address': str(network.network_address),
|
||||
'broadcast_address': str(network.broadcast_address),
|
||||
'netmask': str(network.netmask),
|
||||
'num_addresses': network.num_addresses,
|
||||
'host_range': f"{str(network.network_address + 1)} - {str(network.broadcast_address - 1)}" if network.prefixlen < 31 else subnet.cidr
|
||||
}
|
||||
|
||||
total_ips = network.num_addresses - 2 # Subtract network and broadcast addresses
|
||||
used_ips = Server.query.filter_by(subnet_id=subnet_id).count()
|
||||
|
||||
return render_template(
|
||||
'ipam/subnet_view.html',
|
||||
title=subnet.cidr,
|
||||
"ipam/subnet_view.html",
|
||||
title=f"Subnet {subnet.cidr}",
|
||||
subnet=subnet,
|
||||
subnet_info=subnet_info,
|
||||
servers=servers,
|
||||
now=datetime.now()
|
||||
subnet_apps=subnet_apps,
|
||||
total_ips=total_ips,
|
||||
used_ips=used_ips,
|
||||
)
|
||||
|
||||
@bp.route('/subnet/<int:subnet_id>/edit', methods=['GET', 'POST'])
|
||||
|
||||
@bp.route("/subnet/<int:subnet_id>/edit", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def subnet_edit(subnet_id):
|
||||
"""Edit a subnet"""
|
||||
subnet = Subnet.query.get_or_404(subnet_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
cidr = request.form.get('cidr')
|
||||
location = request.form.get('location')
|
||||
auto_scan = request.form.get('auto_scan') == 'on'
|
||||
|
||||
|
||||
if request.method == "POST":
|
||||
cidr = request.form.get("cidr")
|
||||
location = request.form.get("location")
|
||||
auto_scan = request.form.get("auto_scan") == "on"
|
||||
|
||||
# Validate inputs
|
||||
if not all([cidr, location]):
|
||||
flash('All fields are required', 'danger')
|
||||
return render_template('ipam/subnet_form.html',
|
||||
title='Edit Subnet',
|
||||
subnet=subnet,
|
||||
edit_mode=True)
|
||||
|
||||
flash("All fields are required", "danger")
|
||||
return render_template(
|
||||
"ipam/subnet_form.html",
|
||||
title="Edit Subnet",
|
||||
subnet=subnet,
|
||||
edit_mode=True,
|
||||
)
|
||||
|
||||
# Validate CIDR format
|
||||
try:
|
||||
ipaddress.ip_network(cidr, strict=False)
|
||||
except ValueError:
|
||||
flash('Invalid CIDR format', 'danger')
|
||||
return render_template('ipam/subnet_form.html',
|
||||
title='Edit Subnet',
|
||||
subnet=subnet,
|
||||
edit_mode=True)
|
||||
|
||||
flash("Invalid CIDR format", "danger")
|
||||
return render_template(
|
||||
"ipam/subnet_form.html",
|
||||
title="Edit Subnet",
|
||||
subnet=subnet,
|
||||
edit_mode=True,
|
||||
)
|
||||
|
||||
# Update subnet
|
||||
subnet.cidr = cidr
|
||||
subnet.location = location
|
||||
subnet.auto_scan = auto_scan
|
||||
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
flash(f'Subnet {cidr} has been updated', 'success')
|
||||
return redirect(url_for('ipam.subnet_view', subnet_id=subnet.id))
|
||||
flash(f"Subnet {cidr} has been updated", "success")
|
||||
return redirect(url_for("ipam.subnet_view", subnet_id=subnet.id))
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f'Error updating subnet: {str(e)}', 'danger')
|
||||
|
||||
return render_template('ipam/subnet_form.html',
|
||||
title='Edit Subnet',
|
||||
subnet=subnet,
|
||||
edit_mode=True)
|
||||
flash(f"Error updating subnet: {str(e)}", "danger")
|
||||
|
||||
@bp.route('/subnet/<int:subnet_id>/delete', methods=['POST'])
|
||||
return render_template(
|
||||
"ipam/subnet_form.html", title="Edit Subnet", subnet=subnet, edit_mode=True
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/subnet/<int:subnet_id>/delete", methods=["POST"])
|
||||
@login_required
|
||||
def subnet_delete(subnet_id):
|
||||
"""Delete a subnet"""
|
||||
subnet = Subnet.query.get_or_404(subnet_id)
|
||||
|
||||
|
||||
# Check if subnet has servers
|
||||
servers_count = Server.query.filter_by(subnet_id=subnet_id).count()
|
||||
if servers_count > 0:
|
||||
flash(f'Cannot delete subnet {subnet.cidr}. It has {servers_count} servers assigned.', 'danger')
|
||||
return redirect(url_for('ipam.subnet_view', subnet_id=subnet_id))
|
||||
|
||||
flash(
|
||||
f"Cannot delete subnet {subnet.cidr}. It has {servers_count} servers assigned.",
|
||||
"danger",
|
||||
)
|
||||
return redirect(url_for("ipam.subnet_view", subnet_id=subnet_id))
|
||||
|
||||
db.session.delete(subnet)
|
||||
db.session.commit()
|
||||
|
||||
flash(f'Subnet {subnet.cidr} has been deleted', 'success')
|
||||
return redirect(url_for('ipam.ipam_home'))
|
||||
|
||||
@bp.route('/subnet/<int:subnet_id>/scan', methods=['POST'])
|
||||
flash(f"Subnet {subnet.cidr} has been deleted", "success")
|
||||
return redirect(url_for("ipam.ipam_home"))
|
||||
|
||||
|
||||
@bp.route("/subnet/<int:subnet_id>/scan", methods=["POST"])
|
||||
@login_required
|
||||
def subnet_scan(subnet_id):
|
||||
"""Manually scan a subnet"""
|
||||
subnet = Subnet.query.get_or_404(subnet_id)
|
||||
|
||||
|
||||
try:
|
||||
# Call the scan function with manual_trigger=True
|
||||
scan(subnet, manual_trigger=True)
|
||||
db.session.commit()
|
||||
flash(f'Scan completed for subnet {subnet.cidr}', 'success')
|
||||
flash(f"Scan completed for subnet {subnet.cidr}", "success")
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f'Error scanning subnet: {str(e)}', 'danger')
|
||||
|
||||
return redirect(url_for('ipam.subnet_view', subnet_id=subnet_id))
|
||||
flash(f"Error scanning subnet: {str(e)}", "danger")
|
||||
|
||||
@bp.route('/subnet/<int:subnet_id>/force-delete', methods=['POST'])
|
||||
return redirect(url_for("ipam.subnet_view", subnet_id=subnet_id))
|
||||
|
||||
|
||||
@bp.route("/subnet/<int:subnet_id>/force-delete", methods=["POST"])
|
||||
@login_required
|
||||
def subnet_force_delete(subnet_id):
|
||||
"""Force delete a subnet and all its related servers and applications"""
|
||||
subnet = Subnet.query.get_or_404(subnet_id)
|
||||
|
||||
|
||||
try:
|
||||
# Get all servers to be deleted for reporting
|
||||
servers = Server.query.filter_by(subnet_id=subnet_id).all()
|
||||
server_count = len(servers)
|
||||
|
||||
|
||||
# This will cascade delete all related servers and their applications
|
||||
db.session.delete(subnet)
|
||||
db.session.commit()
|
||||
|
||||
flash(f'Subnet {subnet.cidr} and {server_count} related servers were deleted successfully', 'success')
|
||||
return redirect(url_for('dashboard.ipam_home'))
|
||||
|
||||
flash(
|
||||
f"Subnet {subnet.cidr} and {server_count} related servers were deleted successfully",
|
||||
"success",
|
||||
)
|
||||
return redirect(url_for("dashboard.ipam_home"))
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f'Error deleting subnet: {str(e)}', 'danger')
|
||||
return redirect(url_for('dashboard.subnet_view', subnet_id=subnet_id))
|
||||
flash(f"Error deleting subnet: {str(e)}", "danger")
|
||||
return redirect(url_for("dashboard.subnet_view", subnet_id=subnet_id))
|
||||
|
||||
@bp.route('/subnet/create-ajax', methods=['POST'])
|
||||
|
||||
@bp.route("/subnet/create-ajax", methods=["POST"])
|
||||
@login_required
|
||||
def subnet_create_ajax():
|
||||
"""Create a subnet via AJAX"""
|
||||
data = request.json
|
||||
if not data:
|
||||
return jsonify({'success': False, 'error': 'No data provided'})
|
||||
|
||||
cidr = data.get('cidr')
|
||||
location = data.get('location')
|
||||
auto_scan = data.get('auto_scan', False)
|
||||
|
||||
return jsonify({"success": False, "error": "No data provided"})
|
||||
|
||||
cidr = data.get("cidr")
|
||||
location = data.get("location")
|
||||
auto_scan = data.get("auto_scan", False)
|
||||
|
||||
if not cidr or not location:
|
||||
return jsonify({'success': False, 'error': 'CIDR and location are required'})
|
||||
|
||||
return jsonify({"success": False, "error": "CIDR and location are required"})
|
||||
|
||||
# Validate CIDR
|
||||
try:
|
||||
network = ipaddress.ip_network(cidr, strict=False)
|
||||
except ValueError as e:
|
||||
return jsonify({'success': False, 'error': f'Invalid CIDR: {str(e)}'})
|
||||
|
||||
return jsonify({"success": False, "error": f"Invalid CIDR: {str(e)}"})
|
||||
|
||||
# Create subnet
|
||||
subnet = Subnet(
|
||||
cidr=cidr,
|
||||
location=location,
|
||||
auto_scan=auto_scan,
|
||||
active_hosts=json.dumps([])
|
||||
cidr=cidr, location=location, auto_scan=auto_scan, active_hosts=json.dumps([])
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
db.session.add(subnet)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'subnet_id': subnet.id,
|
||||
'cidr': subnet.cidr,
|
||||
'location': subnet.location
|
||||
})
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"success": True,
|
||||
"subnet_id": subnet.id,
|
||||
"cidr": subnet.cidr,
|
||||
"location": subnet.location,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'success': False, 'error': str(e)})
|
||||
return jsonify({"success": False, "error": str(e)})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue