From be6f7cfcbb5466db55d122fc9d70b091335136d8 Mon Sep 17 00:00:00 2001 From: pika Date: Sun, 30 Mar 2025 21:52:20 +0200 Subject: [PATCH] wip --- app/__init__.py | 148 ++------ app/__pycache__/__init__.cpython-313.pyc | Bin 8827 -> 3840 bytes app/core/__pycache__/auth.cpython-313.pyc | Bin 2737 -> 2626 bytes .../template_filters.cpython-313.pyc | Bin 1781 -> 3057 bytes app/core/auth.py | 10 +- app/core/template_filters.py | 34 +- app/routes/__pycache__/api.cpython-313.pyc | Bin 12302 -> 12747 bytes app/routes/__pycache__/auth.cpython-313.pyc | Bin 4387 -> 4040 bytes .../__pycache__/dashboard.cpython-313.pyc | Bin 15264 -> 15971 bytes app/routes/__pycache__/ipam.cpython-313.pyc | Bin 9043 -> 12179 bytes app/routes/api.py | 31 +- app/routes/auth.py | 11 +- app/routes/dashboard.py | 143 +++++--- app/routes/ipam.py | 67 +++- .../__pycache__/db_seed.cpython-313.pyc | Bin 2362 -> 0 bytes app/static/css/custom.css | 55 +++ app/static/css/markdown.css | 195 +++++++++++ app/templates/auth/register.html | 77 ++-- app/templates/dashboard/app_edit.html | 258 ++++++-------- app/templates/dashboard/app_form.html | 189 ++++------ app/templates/dashboard/app_view.html | 241 +++++++++++-- app/templates/dashboard/server_form.html | 139 +++++++- app/templates/dashboard/server_view.html | 328 ++++++++++-------- app/templates/dashboard/settings.html | 86 +++++ app/templates/dashboard/subnet_view.html | 64 ++++ app/templates/ipam/subnet_form.html | 99 +++++- app/templates/layout.html | 224 +++++++++++- app/templates/server.html | 6 +- config/__pycache__/settings.cpython-313.pyc | Bin 4243 -> 0 bytes instance/app.db | Bin 36864 -> 36864 bytes instance/development.db | Bin 36864 -> 0 bytes instance/production.db | Bin 36864 -> 0 bytes scripts/check_routes.py | 94 +++++ scripts/cleanup.py | 63 ++++ scripts/create_admin.py | 68 ++++ 35 files changed, 1897 insertions(+), 733 deletions(-) delete mode 100644 app/scripts/__pycache__/db_seed.cpython-313.pyc create mode 100644 app/static/css/custom.css create mode 100644 app/static/css/markdown.css create mode 100644 app/templates/dashboard/settings.html create mode 100644 app/templates/dashboard/subnet_view.html delete mode 100644 config/__pycache__/settings.cpython-313.pyc delete mode 100644 instance/development.db delete mode 100644 instance/production.db create mode 100755 scripts/check_routes.py create mode 100755 scripts/cleanup.py create mode 100644 scripts/create_admin.py 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 25aafc2cbc39c9a07a125cb0834f95ccdb4dae6f..6632ed3fba6b93785e327e18b4dced87b44ef533 100644 GIT binary patch literal 3840 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 r;B8|yE=7dS)07dU zjDaDTBbdvQX>uKtAy5ssB~uaKBxYAe?#X|cJXJwl;b0yqh9Z$*ULYwN%m*aJg86}@ zc(6c`M7p4+(Bv#;BWaGj(wv;cq@2{36+koHUN!(pO~xW2AT@a{^EV?EAY&>+G}KN8 zhEPVB{hSUQo&1WG zhrLJvD67ddiOp3~6rzq_X@SXgWz&nwrW-0Q^E+JNaG0FJ=EJBkc_Z64M(xQN>=|t8 zK$S&0ldrLxG9H-B!(n8Wa*^M;!Q%!GU%zLU=Y*u`nUgXXFi*}~5wgSeB9Bvp>kSdH z>7J83XC%$foRztPd3N50(u*RtlZ!a~+3Y^CF|s;NKEY87w8D+kmeFN$3+G087myWp bAVLmANCSym95%W6DWy57c11puL%3oAt8Hz& delta 460 zcmX>kvQd=xGcPX}0}z~Td6~X=BX2Y#o zB3(dJaB>E-ksxbkUP@}k%L<@*FB^b_+vKgx-xwn%H*hGkDKUgHnletF%OPbJ3}gUh zfgqG2hZ(|RU`S`sWb&(GEiFmYwa{cK;slBpu>y%(Y$>V9`6;QB@3I8RD*y!=81Bd_ zUzfGIBx|+9;5J&g36# z8EhIri6Y&}Y3!zq`zOy~H!?A~$nVhLaf64i-?PheLeliiNtp|nC+DpQ*&%X~$F9Nk zhKSg7&q?N0swN*ddUC) diff --git a/app/core/__pycache__/template_filters.cpython-313.pyc b/app/core/__pycache__/template_filters.cpython-313.pyc index f27479d67f728cd1e2e3ffdb8e5f973a4c086cb7..9ea9883736d04acf68920dd95c8fafbd5870b534 100644 GIT binary patch literal 3057 zcmcIm&rcgi6rSDn+QtU26No_M*8)!BO#wGmQIv`h5lFxdF%;WP6`Zz>YMf2*a-+!sVnW9S-)@Q z&3y08H?DSc_z*l_eEz&J-GNdvVyw1{#j;{xRU?)suNH}d&8)M2dmb-X4&Le= zFu)fRXX9Ml73bsbVP0dB%zQK29)cRX?nKiPLS^917+7%FLukI90|!l;e)PdW5;bAm zO-`p9_ndQS=;|8?`H_f{D1;>M>R1BHnwi&eNt{y2bU8C*l3PVZl#2=`rWhU*7y4qe ziAy&IMTKOCC6VZ2wrmo;Bu*!;&a8eknp2m>Y*9AN^Zg|mFXi+VZO9%iwtfz2I#K$^ zMva}(LqsJ-@W%i(8ZpLFXGSA1_1Nl%`;S0WWA6Y7)o7Xj=KkkbONNe#tdYH=MC;I& zSo!_OT9L7)YK6TcU8@2AYn-fHCaSLO9cQ8jY>p%GtB1$LRINf%H^RfGFG%4F@1(Sp z_DSlxwAyPs6VbUHe4_1(UY(nrPmISBJ0OnPh->5Xv1n{+SO3I#G7+7Pz3zv@$>)o5 z!IWGUuM(wXS}ay9&Mapv_vCD1d}anbE~0!)EUzIGIJ%l?d7Pf*$BI#uvr0+Ph)GYJ z2#<9kQqo9dQ7WobEA(lwRxkAo0`iiLR{%o?h=ku<|rSQL&@JG?;cVA8Q$es|ElHeCs#KIyx_ z*MVQeH#fXT);-UJ1D`G5UcOWKe)1>w=e8f)HiV0}U0WRLI|CE82iLAtVRmGB>qoOi zt9WqPT1A&|7Zf5)!SKi-gcce)l|*%2)S4A%M^#Fq2x#7WpnB(6s&*k%;3U`Tg5uNf|3NuWlk|ii&CZ^NN;La_Ov|Q!F2~ zi)s;aZt+_2tqg`z@WK3wW&+cug(S|V+wzriX+zE~$pr-;gnddZ_CsU27D=gSQo$8P zEYM$sI$=&O@e%m78(x#%P;2OKH}basDV)4dHif};*I#ZF?0qcsKN9+%vg*Cao|0V&U436o&o-c>io;%M09dqGzr{b1+q$HRN0l5@6z^uma2Xz6^KZC5&|pKv!1HVUre%L*0Sx9oES^3En|? ztJKh1=lHH?LeFDi;E^zJfAWVz8^YO#-m@>C`43eQ!*~5EbXGWM|EadafKR)+(U-mu z_YD)`Z2p|pwD3n6uxoh^I{V|VUe&uY$TS#aR|gvm+JwDIptB^0z!(%UEM#o1{ZM=$`Kc_bqdRvjPQ$q_@-1 zLR90>|1X6$GF8+wa#75ewJeo6T4J!69m`D2WB6A=>+v!^4m*zFKCn1M8Q88TJVbw_ zW|VeLQA2e~5^5CFbBuebp=xSh0p=2>rC@o}X-zIE>9oaWjG6*;f>@_t(P{f^V_Kt_ zX25F26zmPz3wVI+Gh9;$?`TQSm5a(4riwtXu=iqTo|>^eJll4ITcS53Jw1 z7vAIt*4P)W4klPRhFI5Qrh9|wzUSLy2DaFPOrXjkHdLX#N_gAdzR8@}VtJ;Ex=&VU WuM*g=`(aOblaU(3Puo3v1OEbOA?v09 delta 849 zcmZ{iJ#5oJ6vyxUkvL9l7Ya$!lGdb^s3oKYMapvc76U?u+0*`c{_j2C`LOU$l8+=w1Z4c! zIA{;p_wq#V51I9W4s^H#aQudtA#-Mlz0YAz1r4qYbhg3Q80rK7ry8k;N*pIu=V~yX z>-;3Boo!*^96EJM_Xwt^6L{tvTo@(KBxsS)g=7NUPcqkf6-H-oGIOBKY5eQb zUDtVmaG(yYYJgq_YT&B(9xXqJQi1WTi~K0Nf}9pIn=RKwq`F8LUNVLtS zXS6y-8)2T5A_zzG`1DNG=uX9Ug35E(MitWtHNLv249hj+-my%;GZd24-)vQvx0U*qQ2(BljzDOozreBZx-eamh$XsUc})ieYcB2#a~8?E z9JYnOH!hP1U!jsGLb_utFYXPG%!X-VfAM z!6cO_BASFxE7GID+3>4ax*Clep4SYJ?RAX+4U5pP#' 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 ffd4419d5919118459c668b1b0ce05696d4d120b..9091e04912cf730b074884b428d15efb697d2056 100644 GIT binary patch delta 2881 zcma)8Yitx%6rMY~-Dh_xeQeupyA17a+v$skKmtfh<&AAw+?j$j znpR_?NK#X71OW~F6aHaV<1Z5v^@kxU4Mv)Yf|y`JOiZEt!4UAAGh5oNkT}Vl+?jLk zJ?DJqanHp5O9Q@HpU)$~({=V%;-g7lIh&ZQJ7w$j1wZ@YJ9+Y4X@%PBUBPf|oiZsr{v(fAS51{=5?tt>)PjHCpJDw|F^XNjSkj_iptFvN3? z?PC?N@WQL_2Qn_*bx2D}J{UYYc-0@8@`tYYmGO>Wy@j`3Qc2~k+g@CC2UqXa&Nxvn zc{Dj)5IE_b^iCHCPxegq+;!SZ93*@jg=ZyuerQIr=Y{Uwk>Fd;110BM+N)fnWh=-M zk?~E#soe_&-Y{yPG3J&5^hg>D&X=e#!8Xnh^5n7DsCO6z#qOG3GH&}20vgVhqQT+c@1*INH7Fy9uMKV97zNS08g4s z2beoDXb=(#K0JtQPeL=ImirFg1b<4zxW|-drOZC>1Y_mmul#k5t5DK81pdYnTJkRB z&hx69a)1Yb#cPrHkQ9q9pR#e@gr{&P({!|LUg?rpI+;~!VX_|BE8*3Tij0jcc+3;p6p4p121pAd8cY_VY&k(EvoZ0eOUqH=z{+ zELM#8Mqq74nK#85$7*q;pocYQzAv~Svm&7v)vqK=vsq%N49QCmb3><~&1HoG2V`9k zmx{{A&T+sQmk|ax`B3gO~tofyMPu3cT75(|+ z2@%2zafOVTH;EvpWOHH)U;-r{E5 zfaS(}=E%q5Ot1pjUk3kRz2bC5mD&%iTwe=@V*IuE@ZnT6&9y^G?J(a1GUvqa6N+n%gt6Xy}YK9B8UZKdZ^i1M-xIFqO`i9j5Oyj z)Xd)o@k?TN_0Ql%OHCge+XO!->vIx@loI|=tOW{;op<4W(n;cI{-TcK%Z4WF`a2-; zH%g^|j0+ZlNM+KIuVvOMR@Fx;vD!>q-)P|6SDO^EV3FH!=0J(?o>H#Jm zgoEOQ`Vd??(XgB~VE68WLrLp=zs|>#j{-3racxjDG=5CnXe_QF^kn!y*9yEA+tMR| zQ6tKldaa~UM9dp>Sbb8oIRo-F98K1VpPWu{yt1-}?RCQjdioj6D00q>BKONLdc+M^ zk?0D*lp-&hQsn~M|Rt^kvC8p?UZ6J9dIglM*conFHTFAW&00*?l zYDsmtqH6;ZX>9Sm0eh*XMo*5g=x~F ztDWODOSc)dbnKuK^(w0^VRPZw6sSNs)(E8)$Cz_;jlY28Q8zPPPY(^pM+UW4eij(u zx1^tf%$cz<#;!|iu1ITslWN83=G9AY%T5-WwMlZ_46bGhGdG);$t^Q>C##()lw|w6 aWp9;T_B34_xtJI)yUbc2ZD^wvDg74}XMa%u delta 2421 zcmah~ZERCz6u$56=lVhG)^=^Tv3A|o-i?oiFi^r^$d`zi1q!HWYHcs0y0zuKw_C*M zM2t}kgn5iYBvE5DA^y?ykI{s{Z-0n@3Au?OF%rYSk%@{S;yLf_XiH|iNuPWBo^#K~ zbDsCT-{(#~@16I0T@qgDH*b!wn)OE5i?hiy;b-l8yisYG40tWvd2sfV_OEYzCR@kt ziUQwS&bi!Zw%~7mHC*gj3uD1H$3aOd%6l2ibp+tG;8XC?l90SthQp44ygLZjg55#; zFr%=N6q9xxk|a0Y543|P)ZzDSSM^wSVj`whUMQ-%1{a-y8M`4D@`g26$aBLvQOafY zLaAstE1H_m6v~>>lC9{ajF!t54Ofnhyo*2()K5=I0-}X4C0L9!L6VL4kv&F` zLO_~C3f_%>G#f&UE*>YFYh2YcW>b6_E>LLgGW^=|lgtyw^g1IEZQFIQbZK3QUi*c zx8RM71N*OS#IVTGY*rVnwaj{97A35j1Y5`Zf{Nr#(V{HxO); zW)Mf|u|D|bWc*RGif$b!Ptgp?liKAJ9rs(gEK(R&s@#-;11&Tmyc&gI3S~PQGx*c) zN^m|Nh1S3gB;~KbR@O*|S=ScyGmE2Sii^`>w&)L}0H^&eRi=HbR*2mK6F+3Ny;R-(h_d2U= zA5^=BB9z?lYL)SERns%3WT$cME4bTr$4>Pf>4W9|aJ8-bAJ$K=H*K@U#5n?M&~6ZI@e}ChWY|@R!E?P)xShI(E!|e)Y+wjYX$&f|){kuuMSdH> zu;4-Ly;dT@^rj*z4^oMLklG{3)T4CngEQob^wZs2FpfDKC8t61Ee zg)w$rT75-Y{j=16T^hb3!RX56k!#F%OZKtUyhW1z1j#uo{+;UqHWaRYu+kyBZdo0y HUu5_fkX;LS diff --git a/app/routes/__pycache__/auth.cpython-313.pyc b/app/routes/__pycache__/auth.cpython-313.pyc index 08dccd58e03e5cd6022a80c1790f48351dbd3978..8dfda970c67df1beb8d1e05e270e639118be5578 100644 GIT binary patch delta 402 zcmZ3ibV8o*GcPX}0}%Y2@iM)HYa`!EcBW@clXtS$GHOjW=cp41WzYag13@A?14Ag| zWI;}GS=L}SQznS00zR7hZ)^+#qVqiN@CaTIGTsnzg~x94 zTwagK@_d|(3Y#_gJQ!JRfQsEGm+?Q~25BthzgUUyGcPX}0}z~Rewp6My^(JvJJUYq$vfF=8K+G)=ct=($zklwYRU*w!oZNn z6w06hVKOB0F))NO!g=gK9uth0BF4ZFEeKV}5X=_LKKVX}C@V)WXE4{~pB$E~+?I?% z(vuf5OHB^s6ldl^lQ(B)4CX~q!3R+x4>rM)NrfR=6lQt^V=%uZQyz0DbCB%hyPUR@ z?{JDRGEe@<87M6fENIFEQK!HVtq#)>%52Iwxq(-9@*j4Q$$ea0+;CGhg(ffNQj)&K zQCgf@l$V&BS|kb7R3+}1lcSK9nVOSQJoz41h_)tUkr7B-EEK9tAu*>YH8G`9p(HUo zHBXbNNCGGgRx{a%d!r=_f)6&ZNCzlg%mO47fS^bZ$hgH(l$xGdT#{N;WC`S|fD~jg zPM*LPGWi0Vh{!iK1_9A|9_u{r@CaTI(q0j9g~x1i4UfmUFWvA z$Zc_*+v*~>6Pbwh|_Fx^HWN5QtgV|Cd>2Z3#u|YGk#`fU^4i?1Y&;hn>?Ri F696SciShsd diff --git a/app/routes/__pycache__/dashboard.cpython-313.pyc b/app/routes/__pycache__/dashboard.cpython-313.pyc index 28a1a0221deb5b8e128787d241bbf683c5a835ec..8db7c902d27791a413caa5178b68ee6de80abad3 100644 GIT binary patch delta 3685 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 Vd9tfWS|4k;imBa?b*L$W_rE!o8xsHk delta 2981 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 QUm^O+?2acoG*z?s2TFY)>;M1& delta 78 zcmbOnf7y-iGcPX}0}vc&ewm&qHIYw(=?>#YjXR7i>1>)(n=6>)<(V{PC$s2Yn0!xH eoGTlsk`aiD4LASS)nnxT$imC$##p2e6aoN&!4z2l 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 02a29fe84be3d508f10ddce0bd537b626582f999..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2362 zcmbVNO>7fK6rT0&dMA#Z#0ep>(i`yqa;OL=xFAL4P>%G*y%#PZq+YO+D%RAfhn~0vC5NiL^v&9vSQHhNkv;R~&3oT_ z^WK}cJF!>~cF+z@P9hpvmQvwY955xYkXcAcEUZ-uHt}F3bd^vho-LP|H0Mwc zM5LTc=tYAo1Oo#h!heUtd><_ad^8_8y2n9iUT)j8$cfgf38%kCNdE<)MIn&VLYk;a zix7h5nCI5s7>7{?O|&#Lc}lX;q8~giHXnUMnDe^-&CBf}sAw@BoNHlC$srAE9e^sl z2dWVG1*m;{sF9qYb!yQm(GI-_W~dFbKfug|w3ybVb!$CS(qcO|4%}YO&4g_69o7Z{ zwEZ`s-(R)9HrxL{qtd?i$h#_Hc;9M3 z1yu=|8x45c!F|l-!9!?K4NerKPa`RnJkql_p)#;lc8USnbMA9ZzQ`KTc=U$1=xH zvhby)CCeZS@Z=@KGAYAF$Iv~?u~~;+@tnMC=r)V$hG(sk{EFjxE>i#udP24u-3QAa zs~#Cso6SPaMj{IMq`?m6D7IcAEMk@Ox@l74x-9HA8&1r0j7o{vp6^6e zxk3sm^q%U8G4%>58cvDui?z6Kv7?vEMXPy(bV_8BZz$ZfPWkJ5H3t(D7rvNd(w7Sh zbKsE(O02UC%`IL0atIR`U{R|C;$y`fgvTl|vyBhbGGvvA- z`7}5PR&fOk`j%x<(yb!HmJ2uK@*kXs-0*y;E+Hm?lvNA^0N{I6{fu$i?wrK-e?Q-~zXh=K)EV zZ?G6A@?6WW+bn=`V0Q(g{~g*5BiwcS=B=AsN}{GDHk6_Ds~d{ih!51`hwA;q^@GVq zV))1856Oq;9*n&1kb5I*;oT_e=)4=b6S>=Ur|aJ8CLRHp-obkRNPXbLdS9ZRJojIc z-M#gA(igk@T9LXVYvOLC9njsxL%SHo;`g+Ng~t<5mTJ+j8qpru8IPx*T&qPdZOdXj zvL=I2(~m8jtl{MP#3nw(FBH99y;a>(25QQ{hLTvnyrGOVdJe2FzZgIB{A8_XZcVP^ zSWD;BCLU|x$lh%#n|OFzlI3)ZxrGxooB)p+IQF9J=&yME&qn8g9TaMl7^>kRP~#Fk zA3cgc5{^jyE|1_GKocchGvrqF@WKi9DMIsgCw 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 %} +
+
-