diff --git a/app/__init__.py b/app/__init__.py index 4ce705f..a1e8af5 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -4,22 +4,16 @@ import os def create_app(config_name='development'): app = Flask(__name__, - template_folder='templates', - static_folder='static') + static_folder='static', + template_folder='templates') - # Import config - try: - from config.settings import config - app.config.from_object(config.get(config_name, 'default')) - except ImportError: - # Fallback configuration - app.config['SECRET_KEY'] = 'dev-key-placeholder' - app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db' - app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False - - # Create the app instance folder if it doesn't exist - # This is where SQLite database will be stored - os.makedirs(os.path.join(app.instance_path), exist_ok=True) + # Load configuration + if config_name == 'production': + app.config.from_object('config.ProductionConfig') + elif config_name == 'testing': + app.config.from_object('config.TestingConfig') + else: + app.config.from_object('config.DevelopmentConfig') # Initialize extensions from app.core.extensions import db, migrate, login_manager, bcrypt, limiter, csrf @@ -35,118 +29,38 @@ def create_app(config_name='development'): @login_manager.user_loader def load_user(user_id): - # Make sure we're in app context - with app.app_context(): - return User.query.get(int(user_id)) + return User.query.get(int(user_id)) - # Initialize CSRF protection - from flask_wtf.csrf import CSRFProtect - csrf = CSRFProtect() - csrf.init_app(app) + login_manager.login_view = 'auth.login' + login_manager.login_message = 'Please log in to access this page.' + login_manager.login_message_category = 'info' - # Request hooks - @app.before_request - def before_request(): - g.user = None - from flask_login import current_user - if current_user.is_authenticated: - g.user = current_user - - # Add datetime to all templates - g.now = datetime.datetime.utcnow() - - @app.context_processor - def inject_now(): - return {'now': datetime.datetime.utcnow()} - - @app.after_request - def add_security_headers(response): - # Security headers - response.headers['X-Content-Type-Options'] = 'nosniff' - response.headers['X-Frame-Options'] = 'SAMEORIGIN' - response.headers['X-XSS-Protection'] = '1; mode=block' - - # Update last_seen for the user - if hasattr(g, 'user') and g.user and g.user.is_authenticated: - g.user.last_seen = datetime.datetime.utcnow() - db.session.commit() - return response - - # Add a basic index route that redirects to login or dashboard - @app.route('/') - def index(): - from flask_login import current_user - if current_user.is_authenticated: - return redirect(url_for('dashboard.dashboard_home')) - return redirect(url_for('auth.login')) - - # Register blueprints - order matters! - # First auth blueprint - from app.routes.auth import bp as auth_bp - app.register_blueprint(auth_bp) - print("Registered Auth blueprint") - - # Then other blueprints - try: - from app.routes.dashboard import bp as dashboard_bp - app.register_blueprint(dashboard_bp) - print("Registered Dashboard blueprint") - except ImportError as e: - print(f"Could not import dashboard blueprint: {e}") - - try: - from app.routes.ipam import bp as ipam_bp - app.register_blueprint(ipam_bp) - print("Registered IPAM blueprint") - except ImportError as e: - print(f"Could not import ipam blueprint: {e}") - - try: - from app.routes.api import bp as api_bp - app.register_blueprint(api_bp) - print("Registered API blueprint") - except ImportError as e: - print(f"Could not import API blueprint: {e}") - - # Register template filters - IMPORTANT FOR MARKDOWN FILTER + # Register template filters from app.core.template_filters import bp as filters_bp app.register_blueprint(filters_bp) - # Register template filters directly if the blueprint method doesn't work - from app.core.template_filters import markdown_filter, ip_network_filter, ip_address_filter, get_ip_network - app.jinja_env.filters['markdown'] = markdown_filter - app.jinja_env.filters['ip_network'] = ip_network_filter - app.jinja_env.filters['ip_address'] = ip_address_filter - app.jinja_env.globals['get_ip_network'] = get_ip_network - - # Create database tables + # Create database tables without seeding any data with app.app_context(): try: db.create_all() print("Database tables created successfully") - - # Check if we need to seed the database - from app.core.auth import User - if User.query.count() == 0: - # Run the seed database function if we have no users - try: - from app.scripts.db_seed import seed_database - seed_database() - print("Database seeded with initial data") - - # Create an admin user - admin = User(email="admin@example.com", is_admin=True) - admin.set_password("admin") - db.session.add(admin) - db.session.commit() - print("Admin user created: admin@example.com / admin") - except Exception as e: - print(f"Error seeding database: {e}") except Exception as e: print(f"Error with database setup: {e}") - # After all blueprint registrations, add error handlers - + # Register blueprints + from app.routes.auth import bp as auth_bp + app.register_blueprint(auth_bp) + + from app.routes.dashboard import bp as dashboard_bp + app.register_blueprint(dashboard_bp) + + from app.routes.ipam import bp as ipam_bp + app.register_blueprint(ipam_bp) + + from app.routes.api import bp as api_bp + app.register_blueprint(api_bp) + + # Add error handlers @app.errorhandler(404) def page_not_found(e): return render_template('errors/404.html', title='Page Not Found'), 404 @@ -158,9 +72,5 @@ def create_app(config_name='development'): @app.errorhandler(403) def forbidden(e): return render_template('errors/403.html', title='Forbidden'), 403 - - @app.errorhandler(401) - def unauthorized(e): - return render_template('errors/401.html', title='Unauthorized'), 401 return app \ No newline at end of file diff --git a/app/__pycache__/__init__.cpython-313.pyc b/app/__pycache__/__init__.cpython-313.pyc index 25aafc2..6632ed3 100644 Binary files a/app/__pycache__/__init__.cpython-313.pyc and b/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/app/core/__pycache__/auth.cpython-313.pyc b/app/core/__pycache__/auth.cpython-313.pyc index 5a7100f..a2cbcbb 100644 Binary files a/app/core/__pycache__/auth.cpython-313.pyc and b/app/core/__pycache__/auth.cpython-313.pyc differ diff --git a/app/core/__pycache__/template_filters.cpython-313.pyc b/app/core/__pycache__/template_filters.cpython-313.pyc index f27479d..9ea9883 100644 Binary files a/app/core/__pycache__/template_filters.cpython-313.pyc and b/app/core/__pycache__/template_filters.cpython-313.pyc differ diff --git a/app/core/auth.py b/app/core/auth.py index 8bcb78c..bb92f6a 100644 --- a/app/core/auth.py +++ b/app/core/auth.py @@ -10,9 +10,9 @@ class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(64), unique=True, index=True) - email = db.Column(db.String(120), unique=True, index=True) - password_hash = db.Column(db.String(128)) + username = db.Column(db.String(64), unique=True, nullable=True) + email = db.Column(db.String(120), unique=True, nullable=False) + password_hash = db.Column(db.String(128), nullable=False) is_admin = db.Column(db.Boolean, default=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) last_seen = db.Column(db.DateTime, default=datetime.utcnow) @@ -21,10 +21,10 @@ class User(UserMixin, db.Model): return f'' def set_password(self, password): - self.password_hash = bcrypt.generate_password_hash(password).decode('utf-8') + self.password_hash = generate_password_hash(password) def check_password(self, password): - return bcrypt.check_password_hash(self.password_hash, password) + return check_password_hash(self.password_hash, password) def get_id(self): return str(self.id) diff --git a/app/core/template_filters.py b/app/core/template_filters.py index 7bcda14..3a04345 100644 --- a/app/core/template_filters.py +++ b/app/core/template_filters.py @@ -1,14 +1,44 @@ import ipaddress import markdown as md_package +import re from flask import Blueprint bp = Blueprint('filters', __name__) +def github_style_admonition(text): + """Transform GitHub-style alerts (> [!NOTE], etc.) to custom HTML""" + patterns = { + r'> \[!NOTE\](.*?)(?:\n\n|\Z)': '

Note

\\1
', + r'> \[!TIP\](.*?)(?:\n\n|\Z)': '

Tip

\\1
', + r'> \[!IMPORTANT\](.*?)(?:\n\n|\Z)': '

Important

\\1
', + r'> \[!WARNING\](.*?)(?:\n\n|\Z)': '

Warning

\\1
', + r'> \[!CAUTION\](.*?)(?:\n\n|\Z)': '

Caution

\\1
' + } + + for pattern, replacement in patterns.items(): + text = re.sub(pattern, replacement, text, flags=re.DOTALL) + + return text + @bp.app_template_filter('markdown') def markdown_filter(text): - """Convert markdown text to HTML""" + """Convert markdown text to HTML with support for GitHub-style features""" if text: - return md_package.markdown(text, extensions=['tables', 'fenced_code']) + # Pre-process GitHub-style alerts + text = github_style_admonition(text) + + # Convert to HTML with regular markdown + html = md_package.markdown( + text, + extensions=[ + 'tables', + 'fenced_code', + 'codehilite', + 'nl2br' + ] + ) + + return html return "" @bp.app_template_filter('ip_network') diff --git a/app/routes/__pycache__/api.cpython-313.pyc b/app/routes/__pycache__/api.cpython-313.pyc index ffd4419..9091e04 100644 Binary files a/app/routes/__pycache__/api.cpython-313.pyc and b/app/routes/__pycache__/api.cpython-313.pyc differ diff --git a/app/routes/__pycache__/auth.cpython-313.pyc b/app/routes/__pycache__/auth.cpython-313.pyc index 08dccd5..8dfda97 100644 Binary files a/app/routes/__pycache__/auth.cpython-313.pyc and b/app/routes/__pycache__/auth.cpython-313.pyc differ diff --git a/app/routes/__pycache__/dashboard.cpython-313.pyc b/app/routes/__pycache__/dashboard.cpython-313.pyc index 28a1a02..8db7c90 100644 Binary files a/app/routes/__pycache__/dashboard.cpython-313.pyc and b/app/routes/__pycache__/dashboard.cpython-313.pyc differ diff --git a/app/routes/__pycache__/ipam.cpython-313.pyc b/app/routes/__pycache__/ipam.cpython-313.pyc index b3316d2..3d17b67 100644 Binary files a/app/routes/__pycache__/ipam.cpython-313.pyc and b/app/routes/__pycache__/ipam.cpython-313.pyc differ diff --git a/app/routes/api.py b/app/routes/api.py index 9fd0761..15e8c8a 100644 --- a/app/routes/api.py +++ b/app/routes/api.py @@ -9,23 +9,14 @@ import ipaddress bp = Blueprint('api', __name__, url_prefix='/api') @bp.route('/subnets', methods=['GET']) -@login_required def get_subnets(): """Get all subnets""" subnets = Subnet.query.all() - result = [] - - for subnet in subnets: - result.append({ - 'id': subnet.id, - 'cidr': subnet.cidr, - 'location': subnet.location, - 'used_ips': subnet.used_ips, - 'auto_scan': subnet.auto_scan, - 'created_at': subnet.created_at.strftime('%Y-%m-%d %H:%M:%S') - }) - - return jsonify({'subnets': result}) + return jsonify([{ + 'id': subnet.id, + 'cidr': subnet.cidr, + 'location': subnet.location + } for subnet in subnets]) @bp.route('/subnets/', methods=['GET']) @login_required @@ -306,4 +297,14 @@ def delete_port(port_id): db.session.delete(port) db.session.commit() - return jsonify({'success': True}) \ No newline at end of file + return jsonify({'success': True}) + +@bp.route('/subnets//servers', methods=['GET']) +def get_subnet_servers(subnet_id): + """Get all servers for a specific subnet""" + servers = Server.query.filter_by(subnet_id=subnet_id).all() + return jsonify([{ + 'id': server.id, + 'hostname': server.hostname, + 'ip_address': server.ip_address + } for server in servers]) \ No newline at end of file diff --git a/app/routes/auth.py b/app/routes/auth.py index 2a41f8d..5330ea2 100644 --- a/app/routes/auth.py +++ b/app/routes/auth.py @@ -41,24 +41,19 @@ def register(): if request.method == 'POST': email = request.form.get('email') - username = request.form.get('username') password = request.form.get('password') # Validation - if not email or not username or not password: - flash('All fields are required', 'danger') + if not email or not password: + flash('Email and password are required', 'danger') return render_template('auth/register.html', title='Register') if User.query.filter_by(email=email).first(): flash('Email already registered', 'danger') return render_template('auth/register.html', title='Register') - - if User.query.filter_by(username=username).first(): - flash('Username already taken', 'danger') - return render_template('auth/register.html', title='Register') # Create new user - user = User(email=email, username=username) + user = User(email=email) user.set_password(password) db.session.add(user) diff --git a/app/routes/dashboard.py b/app/routes/dashboard.py index 5e6b394..5caf064 100644 --- a/app/routes/dashboard.py +++ b/app/routes/dashboard.py @@ -129,30 +129,48 @@ def server_new(): def server_edit(server_id): """Edit an existing server""" server = Server.query.get_or_404(server_id) + 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', '') 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)) + return render_template( + 'dashboard/server_form.html', + title='Edit Server', + server=server, + subnets=subnets + ) # 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)) + return render_template( + 'dashboard/server_form.html', + title='Edit Server', + server=server, + subnets=subnets + ) # 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)) + return render_template( + 'dashboard/server_form.html', + title='Edit Server', + server=server, + subnets=subnets + ) # Update server server.hostname = hostname server.ip_address = ip_address server.subnet_id = subnet_id + server.documentation = documentation db.session.commit() @@ -160,9 +178,8 @@ def server_edit(server_id): 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', + 'dashboard/server_form.html', title=f'Edit Server - {server.hostname}', server=server, subnets=subnets @@ -277,62 +294,44 @@ def app_edit(app_id): 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[]') + if not name or not server_id: + flash('All required fields must be filled', 'danger') + return render_template( + 'dashboard/app_form.html', + title='Edit Application', + app=app, + servers=servers + ) - # 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) + # Check if name changed and already exists on the same server + existing_app = App.query.filter(App.name == name, + App.server_id == server_id, + App.id != app.id).first() + if existing_app: + flash('Application with this name already exists on the selected server', 'danger') + return render_template( + 'dashboard/app_form.html', + title='Edit Application', + app=app, + servers=servers + ) - # Update app + # Update application 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() + db.session.commit() - # 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') + flash('Application updated successfully', 'success') + return redirect(url_for('dashboard.app_view', app_id=app.id)) - return render_template('dashboard/app_form.html', - title='Edit Application', - app=app, - servers=servers, - edit_mode=True) + return render_template( + 'dashboard/app_form.html', + title=f'Edit Application - {app.name}', + app=app, + servers=servers + ) @bp.route('/app//delete', methods=['POST']) @login_required @@ -345,4 +344,40 @@ def app_delete(app_id): db.session.commit() flash('Application deleted successfully', 'success') - return redirect(url_for('dashboard.server_view', server_id=server_id)) \ No newline at end of file + return redirect(url_for('dashboard.server_view', server_id=server_id)) + +@bp.route('/settings', methods=['GET', 'POST']) +@login_required +def settings(): + """User settings page""" + if request.method == 'POST': + # Handle user settings update + current_password = request.form.get('current_password') + new_password = request.form.get('new_password') + confirm_password = request.form.get('confirm_password') + + # Validate inputs + if not current_password: + flash('Current password is required', 'danger') + return redirect(url_for('dashboard.settings')) + + if new_password != confirm_password: + flash('New passwords do not match', 'danger') + return redirect(url_for('dashboard.settings')) + + # Verify current password + if not current_user.check_password(current_password): + flash('Current password is incorrect', 'danger') + return redirect(url_for('dashboard.settings')) + + # Update password + current_user.set_password(new_password) + db.session.commit() + + flash('Password updated successfully', 'success') + return redirect(url_for('dashboard.settings')) + + return render_template( + 'dashboard/settings.html', + title='User Settings' + ) \ No newline at end of file diff --git a/app/routes/ipam.py b/app/routes/ipam.py index b6ad807..fcd585a 100644 --- a/app/routes/ipam.py +++ b/app/routes/ipam.py @@ -192,4 +192,69 @@ def subnet_scan(subnet_id): db.session.rollback() flash(f'Error scanning subnet: {str(e)}', 'danger') - return redirect(url_for('ipam.subnet_view', subnet_id=subnet_id)) \ No newline at end of file + return redirect(url_for('ipam.subnet_view', subnet_id=subnet_id)) + +@bp.route('/subnet//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')) + 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)) + +@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) + + if not cidr or not location: + 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)}'}) + + # Create subnet + subnet = Subnet( + 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 + }) + except Exception as e: + db.session.rollback() + return jsonify({'success': False, 'error': str(e)}) \ No newline at end of file diff --git a/app/scripts/__pycache__/db_seed.cpython-313.pyc b/app/scripts/__pycache__/db_seed.cpython-313.pyc deleted file mode 100644 index 02a29fe..0000000 Binary files a/app/scripts/__pycache__/db_seed.cpython-313.pyc and /dev/null differ diff --git a/app/static/css/custom.css b/app/static/css/custom.css new file mode 100644 index 0000000..b7b12ff --- /dev/null +++ b/app/static/css/custom.css @@ -0,0 +1,55 @@ +/* Collapsible cards customization */ +.accordion-button:not(.collapsed) { + background-color: rgba(32, 107, 196, 0.06); + color: #206bc4; +} + +.accordion-button:focus { + box-shadow: none; +} + +.markdown-content { + overflow-wrap: break-word; +} + +/* Adjust spacing in application cards */ +.accordion-button .badge { + font-size: 0.7rem; + padding: 0.25em 0.5em; +} + +/* Ensure icons are vertically centered */ +.accordion-button .ti { + vertical-align: middle; +} + +/* Make sure markdown content has proper spacing */ +.markdown-content>*:first-child { + margin-top: 0; +} + +.markdown-content>*:last-child { + margin-bottom: 0; +} + +/* Custom styling for port badges in accordion headers */ +.accordion-button .badge { + background-color: #206bc4; + color: white; +} + +/* Add a bit of hover effect to the accordion items */ +.accordion-item:hover { + background-color: rgba(32, 107, 196, 0.03); +} + +/* Visual cue for the action buttons */ +.accordion-body .btn-outline-primary:hover { + background-color: #206bc4; + color: white; +} + +.accordion-body .btn-outline-danger:hover { + background-color: #d63939; + color: white; +} \ No newline at end of file diff --git a/app/static/css/markdown.css b/app/static/css/markdown.css new file mode 100644 index 0000000..bbfcff9 --- /dev/null +++ b/app/static/css/markdown.css @@ -0,0 +1,195 @@ +/* Enhanced Markdown Styling */ +.markdown-content { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.6; + word-wrap: break-word; + color: #24292e; +} + +.markdown-content h1, +.markdown-content h2, +.markdown-content h3, +.markdown-content h4, +.markdown-content h5, +.markdown-content h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; +} + +.markdown-content h1 { + font-size: 2em; + border-bottom: 1px solid #eaecef; + padding-bottom: .3em; +} + +.markdown-content h2 { + font-size: 1.5em; + border-bottom: 1px solid #eaecef; + padding-bottom: .3em; +} + +.markdown-content h3 { + font-size: 1.25em; +} + +.markdown-content h4 { + font-size: 1em; +} + +.markdown-content p { + margin-top: 0; + margin-bottom: 16px; +} + +.markdown-content blockquote { + padding: 0 1em; + color: #6a737d; + border-left: 0.25em solid #dfe2e5; + margin: 0 0 16px 0; +} + +.markdown-content pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: #f6f8fa; + border-radius: 3px; + margin-bottom: 16px; +} + +.markdown-content code { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + background-color: rgba(27, 31, 35, 0.05); + border-radius: 3px; +} + +.markdown-content pre code { + background-color: transparent; + padding: 0; +} + +.markdown-content table { + border-spacing: 0; + border-collapse: collapse; + margin-bottom: 16px; + width: 100%; + overflow: auto; +} + +.markdown-content table th, +.markdown-content table td { + padding: 6px 13px; + border: 1px solid #dfe2e5; +} + +.markdown-content table tr { + background-color: #fff; + border-top: 1px solid #c6cbd1; +} + +.markdown-content table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +/* GitHub-style alerts */ +.markdown-alert { + padding: 0.5rem 1rem; + margin-bottom: 16px; + border-radius: 6px; +} + +.markdown-alert p { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-alert p:first-of-type { + margin-top: 8px; +} + +.markdown-alert-title { + font-weight: bold; + margin-bottom: 8px; +} + +.markdown-alert-note { + background-color: #f1f8ff; + border-left: 4px solid #58a6ff; +} + +.markdown-alert-tip { + background-color: #dafbe1; + border-left: 4px solid #2da44e; +} + +.markdown-alert-important { + background-color: #fff8c5; + border-left: 4px solid #bf8700; +} + +.markdown-alert-warning { + background-color: #fff8c5; + border-left: 4px solid #bf8700; +} + +.markdown-alert-caution { + background-color: #ffebe9; + border-left: 4px solid #cf222e; +} + +/* Add this to ensure consistent display of markdown content */ +.markdown-body { + color: inherit; + font-family: inherit; +} + +.markdown-content { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + line-height: 1.6; + color: #24292e; + overflow-wrap: break-word; + padding: 0; +} + +.markdown-content h1:first-child, +.markdown-content h2:first-child, +.markdown-content h3:first-child, +.markdown-content h4:first-child, +.markdown-content h5:first-child, +.markdown-content h6:first-child { + margin-top: 0; +} + +/* Additional styling for consistency in all views */ +.markdown-content pre, +.markdown-body pre { + background-color: #f6f8fa; + border-radius: 3px; + padding: 16px; + overflow: auto; + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + font-size: 85%; +} + +.markdown-content code, +.markdown-body code { + background-color: rgba(27, 31, 35, 0.05); + border-radius: 3px; + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + font-size: 85%; + margin: 0; + padding: 0.2em 0.4em; +} + +/* Make sure all GitHub-style alert boxes look the same */ +.markdown-alert { + padding: 8px 16px; + margin-bottom: 16px; + border-radius: 6px; +} \ No newline at end of file diff --git a/app/templates/auth/register.html b/app/templates/auth/register.html index 3cf110c..1654a5d 100644 --- a/app/templates/auth/register.html +++ b/app/templates/auth/register.html @@ -1,47 +1,46 @@ {% extends "layout.html" %} {% block content %} -
-
-
-
-
-
-

Register

-

Create a new account

-
+
+
+
+

Create New Account

- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} - - {% endfor %} - {% endif %} - {% endwith %} + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} -
- -
- - -
-
- - -
-
- - -
- -
- -
-

Already have an account? Login here

-
+
+ +
+ + +
+
+ + +
+ +
+
+
or
+ diff --git a/app/templates/dashboard/app_edit.html b/app/templates/dashboard/app_edit.html index c207dc3..beea199 100644 --- a/app/templates/dashboard/app_edit.html +++ b/app/templates/dashboard/app_edit.html @@ -44,170 +44,130 @@
- - Supports Markdown formatting + Supports Markdown formatting including GitHub-style alerts: +
+ > [!NOTE] This is a note + > [!TIP] This is a tip + > [!IMPORTANT] Important info + > [!WARNING] Warning message + > [!CAUTION] Critical caution +
- -
- {% if app.ports %} - {% for port in app.ports %} -
-
- -
-
- -
-
- -
-
- -
-
- {% endfor %} - {% else %} -
-
- -
-
- -
-
- -
-
- -
-
- {% endif %} + +
+ + + + + + + + + + + {% for port in app.ports %} + + + + + + + {% endfor %} + +
Port NumberProtocolDescription
+ + + + + + + +
-
- -
-
- - Cancel - - +
+ +{% block extra_js %} + {% endblock %} -{% block scripts %} - {% endblock %} \ No newline at end of file diff --git a/app/templates/dashboard/app_form.html b/app/templates/dashboard/app_form.html index d104542..e8ddabe 100644 --- a/app/templates/dashboard/app_form.html +++ b/app/templates/dashboard/app_form.html @@ -6,7 +6,7 @@

- Add New Application + {% if app %}Edit Application{% else %}Add New Application{% endif %}

@@ -25,156 +25,85 @@ {% endif %} {% endwith %} -
+
- +
- - Supports Markdown formatting + +
Markdown is supported
- - -
- -
-
-
- -
-
- -
-
- -
-
- -
-
-
-
- - -
-
-
+ +{% block extra_js %} + {% endblock %} -{% block scripts %} - {% endblock %} \ No newline at end of file diff --git a/app/templates/dashboard/app_view.html b/app/templates/dashboard/app_view.html index f3db2ad..ed7b910 100644 --- a/app/templates/dashboard/app_view.html +++ b/app/templates/dashboard/app_view.html @@ -1,26 +1,223 @@ - -
-
-

Documentation

-
-
- {% if app.documentation %} - {{ app.documentation|markdown|safe }} - {% else %} -
-
- +{% extends "layout.html" %} + +{% block content %} +
+ \ No newline at end of file + +
+
+ +
+
+

Basic Information

+
+
+
+
Server
+
+ + {{ server.hostname }} + + ({{ server.ip_address }}) +
+
+
+
Created
+
{{ app.created_at.strftime('%Y-%m-%d %H:%M') }}
+
+
+
Last Updated
+
{{ app.updated_at.strftime('%Y-%m-%d %H:%M') }}
+
+
+
+ + +
+
+

Ports

+
+ +
+
+
+ {% if app.ports %} +
+ + + + + + + + + + + {% for port in app.ports %} + + + + + + + {% endfor %} + +
PortProtocolDescription
{{ port.port_number }}{{ port.protocol }}{{ port.description or 'No description' }} + + + +
+
+ {% else %} +
+
+ +
+

No ports configured

+

+ Add port information to track which ports this application uses. +

+
+ +
+
+ {% endif %} +
+
+
+ +
+ +
+
+

Documentation

+
+
+ {% if app.documentation %} + {{ app.documentation|markdown|safe }} + {% else %} +
+
+ +
+

No documentation available

+

+ Add documentation to this application to keep track of important information. +

+ +
+ {% endif %} +
+
+
+
+
+ + + + + + + + +{% for port in app.ports %} + +{% endfor %} +{% endblock %} \ No newline at end of file diff --git a/app/templates/dashboard/server_form.html b/app/templates/dashboard/server_form.html index 075c6ba..73d8fcf 100644 --- a/app/templates/dashboard/server_form.html +++ b/app/templates/dashboard/server_form.html @@ -6,7 +6,7 @@

- Add New Server + {% if server %}Edit Server{% else %}Add New Server{% endif %}

@@ -25,37 +25,144 @@ {% endif %} {% endwith %} -
+
- +
- +
- +
+ + +
- - Supports Markdown formatting + +
Markdown is supported
-
- Cancel - +
+ + + + + + {% endblock %} \ No newline at end of file diff --git a/app/templates/dashboard/server_view.html b/app/templates/dashboard/server_view.html index 5629817..596d0ca 100644 --- a/app/templates/dashboard/server_view.html +++ b/app/templates/dashboard/server_view.html @@ -12,13 +12,13 @@ {{ server.hostname }}
-
+
- Edit + Edit Server -
@@ -27,28 +27,29 @@
+
-

Server Information

+

Basic Information

-
-
IP Address:
-
{{ server.ip_address }}
- -
Subnet:
-
- +
+
IP Address
+
{{ server.ip_address }}
+
+
- -
Location:
-
{{ server.subnet.location }}
- -
Created:
-
{{ server.created_at.strftime('%Y-%m-%d') }}
-
+ ({{ server.subnet.location }}) +
+
+
+
Scan Status
+
{{ server.last_scan or 'Not scanned yet' }}
+
@@ -101,70 +102,8 @@
- -
-
-

Applications

- -
-
- {% if server.apps %} -
- {% for app in server.apps %} -
-
-
-

- {{ app.name }} -

- {% if app.ports %} -
- Ports: - {% for port in app.ports %} - {{ port.port_number }}/{{ port.protocol }} - {% endfor %} -
- {% endif %} - {% if app.documentation %} -
-
Documentation
-
- {{ app.documentation|markdown|safe }} -
-
- {% else %} -
No documentation available
- {% endif %} -
-
-
- {% endfor %} -
- {% else %} -
-
- -
-

No applications found

-

- This server doesn't have any applications yet. -

- -
- {% endif %} -
-
- -
+

Documentation

@@ -189,25 +128,103 @@ {% endif %}
+ + +
+
+

Applications

+
+ + Add Application + + +
+
+
+ {% if server.apps %} +
+ {% for app in server.apps %} +
+

+ +

+
+
+
+ + View + + + Edit + + +
+ + {% if app.documentation %} +
+ {{ app.documentation|markdown|safe }} +
+ {% else %} +
No documentation available
+ {% endif %} +
+
+
+ {% endfor %} +
+ {% else %} +
+
+ +
+

No applications found

+

+ This server doesn't have any applications yet. +

+ +
+ {% endif %} +
+
-