from flask import Blueprint, render_template, redirect, url_for, request, flash, jsonify from flask_login import login_required, current_user import markdown from app.core.models import Server, App, Subnet, Port from app.core.extensions import db, limiter from datetime import datetime bp = Blueprint('dashboard', __name__, url_prefix='/dashboard') @bp.route('/') @login_required def dashboard_home(): """Main dashboard view showing server statistics""" server_count = Server.query.count() app_count = App.query.count() subnet_count = Subnet.query.count() # Get latest added servers latest_servers = Server.query.order_by(Server.created_at.desc()).limit(5).all() # Get subnets with usage stats subnets = Subnet.query.all() for subnet in subnets: subnet.usage_percent = subnet.used_ips / 254 * 100 if subnet.cidr.endswith('/24') else 0 return render_template( 'dashboard/index.html', title='Dashboard', server_count=server_count, app_count=app_count, subnet_count=subnet_count, latest_servers=latest_servers, subnets=subnets, now=datetime.now() ) @bp.route('/servers') @login_required def server_list(): """List all servers""" servers = Server.query.order_by(Server.hostname).all() return render_template( 'dashboard/server_list.html', title='Servers', servers=servers, now=datetime.now() ) @bp.route('/server/') @login_required def server_view(server_id): """View server details""" server = Server.query.get_or_404(server_id) apps = App.query.filter_by(server_id=server_id).all() return render_template( 'dashboard/server_view.html', title=f'Server - {server.hostname}', server=server, apps=apps, now=datetime.now() ) @bp.route('/server/new', methods=['GET', 'POST']) @login_required def server_new(): """Create a new server""" subnets = Subnet.query.all() if request.method == 'POST': hostname = request.form.get('hostname') ip_address = request.form.get('ip_address') subnet_id = request.form.get('subnet_id') documentation = request.form.get('documentation', '') # Basic validation if not hostname or not ip_address or not subnet_id: flash('Please fill in all required fields', 'danger') return render_template( 'dashboard/server_form.html', title='New Server', subnets=subnets, now=datetime.now() ) # Check if hostname or IP already exists if Server.query.filter_by(hostname=hostname).first(): flash('Hostname already exists', 'danger') return render_template( 'dashboard/server_form.html', title='New Server', subnets=subnets, now=datetime.now() ) if Server.query.filter_by(ip_address=ip_address).first(): flash('IP address already exists', 'danger') return render_template( 'dashboard/server_form.html', title='New Server', subnets=subnets, now=datetime.now() ) # Create new server server = Server( hostname=hostname, ip_address=ip_address, subnet_id=subnet_id, documentation=documentation ) db.session.add(server) db.session.commit() flash('Server created successfully', 'success') return redirect(url_for('dashboard.server_view', server_id=server.id)) return render_template( 'dashboard/server_form.html', title='New Server', subnets=subnets, now=datetime.now() ) @bp.route('/server//edit', methods=['GET', 'POST']) @login_required def server_edit(server_id): """Edit an existing server""" server = Server.query.get_or_404(server_id) if request.method == 'POST': hostname = request.form.get('hostname') ip_address = request.form.get('ip_address') subnet_id = request.form.get('subnet_id') if not hostname or not ip_address or not subnet_id: flash('All fields are required', 'danger') return redirect(url_for('dashboard.server_edit', server_id=server_id)) # Check if hostname changed and already exists if hostname != server.hostname and Server.query.filter_by(hostname=hostname).first(): flash('Hostname already exists', 'danger') return redirect(url_for('dashboard.server_edit', server_id=server_id)) # Check if IP changed and already exists if ip_address != server.ip_address and Server.query.filter_by(ip_address=ip_address).first(): flash('IP address already exists', 'danger') return redirect(url_for('dashboard.server_edit', server_id=server_id)) # Update server server.hostname = hostname server.ip_address = ip_address server.subnet_id = subnet_id db.session.commit() flash('Server updated successfully', 'success') return redirect(url_for('dashboard.server_view', server_id=server.id)) # GET request - show form with current values subnets = Subnet.query.all() return render_template( 'dashboard/server_edit.html', title=f'Edit Server - {server.hostname}', server=server, subnets=subnets ) @bp.route('/server//delete', methods=['POST']) @login_required def server_delete(server_id): """Delete a server""" server = Server.query.get_or_404(server_id) # Delete all apps associated with this server App.query.filter_by(server_id=server_id).delete() # Delete the server db.session.delete(server) db.session.commit() flash('Server deleted successfully', 'success') return redirect(url_for('dashboard.dashboard_home')) @bp.route('/app/new', methods=['GET', 'POST']) @login_required def app_new(): """Create a new application""" servers = Server.query.all() if request.method == 'POST': name = request.form.get('name') server_id = request.form.get('server_id') documentation = request.form.get('documentation', '') # Get port data from form port_numbers = request.form.getlist('port_numbers[]') protocols = request.form.getlist('protocols[]') port_descriptions = request.form.getlist('port_descriptions[]') # Basic validation if not name or not server_id: flash('Please fill in all required fields', 'danger') return render_template( 'dashboard/app_form.html', title='New Application', servers=servers ) # Create new app app = App( name=name, server_id=server_id, documentation=documentation ) db.session.add(app) db.session.flush() # Get the app ID without committing # Add ports if provided for i in range(len(port_numbers)): if port_numbers[i] and port_numbers[i].strip(): try: port_num = int(port_numbers[i]) # Get protocol and description, handling index errors protocol = protocols[i] if i < len(protocols) else 'TCP' description = port_descriptions[i] if i < len(port_descriptions) else '' new_port = Port( app_id=app.id, port_number=port_num, protocol=protocol, description=description ) db.session.add(new_port) except (ValueError, IndexError): continue db.session.commit() flash('Application created successfully', 'success') return redirect(url_for('dashboard.server_view', server_id=server_id)) return render_template( 'dashboard/app_form.html', title='New Application', servers=servers ) @bp.route('/app/', methods=['GET']) @login_required def app_view(app_id): """View a specific application""" app = App.query.get_or_404(app_id) server = Server.query.get(app.server_id) return render_template( 'dashboard/app_view.html', title=f'Application - {app.name}', app=app, server=server, now=datetime.now() ) @bp.route('/app//edit', methods=['GET', 'POST']) @login_required def app_edit(app_id): """Edit an existing application""" app = App.query.get_or_404(app_id) servers = Server.query.all() if request.method == 'POST': name = request.form.get('name') server_id = request.form.get('server_id') documentation = request.form.get('documentation', '') # Get port data from form port_numbers = request.form.getlist('port_numbers[]') protocols = request.form.getlist('protocols[]') port_descriptions = request.form.getlist('port_descriptions[]') # Validate inputs if not all([name, server_id]): flash('All fields are required', 'danger') return render_template('dashboard/app_form.html', title='Edit Application', app=app, servers=servers, edit_mode=True) # Update app app.name = name app.server_id = server_id app.documentation = documentation # Delete existing ports and recreate them # This simplifies handling additions, deletions, and updates Port.query.filter_by(app_id=app.id).delete() # Add new ports for i in range(len(port_numbers)): if port_numbers[i] and port_numbers[i].strip(): try: port_num = int(port_numbers[i]) # Get protocol and description, handling index errors protocol = protocols[i] if i < len(protocols) else 'TCP' description = port_descriptions[i] if i < len(port_descriptions) else '' new_port = Port( app_id=app.id, port_number=port_num, protocol=protocol, description=description ) db.session.add(new_port) except (ValueError, IndexError): continue try: db.session.commit() flash(f'Application {name} has been updated', 'success') return redirect(url_for('dashboard.server_view', server_id=app.server_id)) except Exception as e: db.session.rollback() flash(f'Error updating application: {str(e)}', 'danger') return render_template('dashboard/app_form.html', title='Edit Application', app=app, servers=servers, edit_mode=True) @bp.route('/app//delete', methods=['POST']) @login_required def app_delete(app_id): """Delete an application""" app = App.query.get_or_404(app_id) server_id = app.server_id db.session.delete(app) db.session.commit() flash('Application deleted successfully', 'success') return redirect(url_for('dashboard.server_view', server_id=server_id))