From 6165103385ad66ec40f0069851e9b34ce6c07e89 Mon Sep 17 00:00:00 2001 From: Bryan Cerrati Date: Fri, 7 Mar 2025 21:13:22 -0500 Subject: [PATCH] Remote Details Section Added --- app/__init__.py | 8 ++ app/models.py | 88 ++++++++++------- app/routes/clients.py | 57 ++++++++++- app/routes/domain.py | 32 +++---- app/routes/tools.py | 28 +++++- app/templates/client_detail.html | 137 ++++++++++++++++++++++++++- app/templates/home.html | 18 ++-- app/templates/remote_categories.html | 38 ++++++++ 8 files changed, 338 insertions(+), 68 deletions(-) create mode 100644 app/templates/remote_categories.html diff --git a/app/__init__.py b/app/__init__.py index 8519896..f59218d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -31,6 +31,14 @@ def create_app(): db.session.add(default_category) db.session.commit() + # Ensure default remote categories exist + from app.models import RemoteCategory + default_remote_cats = ["RDP", "SSL-VPN", "VPN", "MGMT"] + for cat in default_remote_cats: + if not RemoteCategory.query.filter_by(name=cat).first(): + db.session.add(RemoteCategory(name=cat)) + db.session.commit() + @event.listens_for(Engine, "connect") def set_sqlite_pragma(dbapi_connection, connection_record): cursor = dbapi_connection.cursor() diff --git a/app/models.py b/app/models.py index ac38d09..79aa39b 100644 --- a/app/models.py +++ b/app/models.py @@ -1,28 +1,16 @@ from app import db -# Association tables (if not already defined) -client_domains = db.Table('client_domains', - db.Column('client_id', db.Integer, db.ForeignKey('client.id', ondelete="CASCADE")), - db.Column('domain_id', db.Integer, db.ForeignKey('domain.id')) -) - -client_ssl = db.Table('client_ssl', - db.Column('client_id', db.Integer, db.ForeignKey('client.id', ondelete="CASCADE")), - db.Column('domain_id', db.Integer, db.ForeignKey('domain.id')) -) - - +# Existing models… class Domain(db.Model): __tablename__ = 'domain' __table_args__ = {'extend_existing': True} id = db.Column(db.Integer, primary_key=True) domain = db.Column(db.String(255), unique=True, nullable=False) - expiration_date = db.Column(db.String(255)) # WHOIS expiration date - ssl_expiration_date = db.Column(db.String(255)) # SSL certificate expiration date - ssl_only = db.Column(db.Boolean, default=False) # True if added via SSL-only form - + expiration_date = db.Column(db.String(255)) + ssl_expiration_date = db.Column(db.String(255)) + ssl_only = db.Column(db.Boolean, default=False) def __repr__(self): - return f"" + return f"" class Client(db.Model): __tablename__ = 'client' @@ -30,29 +18,18 @@ class Client(db.Model): name = db.Column(db.String(255), nullable=False) phone = db.Column(db.String(255)) address = db.Column(db.String(255)) - - # Delete all passwords when the client is deleted. passwords = db.relationship('ClientPassword', backref='client', lazy=True, - cascade="all, delete-orphan") - - # Many-to-many: Only the associations will be removed (due to ondelete cascade), not the Domain records. - domains = db.relationship('Domain', secondary=client_domains, + cascade="all, delete-orphan") + domains = db.relationship('Domain', secondary='client_domains', backref=db.backref('clients', lazy='dynamic')) - ssl_domains = db.relationship('Domain', secondary=client_ssl, + ssl_domains = db.relationship('Domain', secondary='client_ssl', backref=db.backref('ssl_clients', lazy='dynamic')) - + # New relationship for remotes + remotes = db.relationship('ClientRemote', backref='client', lazy=True, + cascade="all, delete-orphan") def __repr__(self): return f"" - -class PasswordCategory(db.Model): - __tablename__ = 'password_category' - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(255), unique=True, nullable=False) - - def __repr__(self): - return f"" - class ClientPassword(db.Model): __tablename__ = 'client_password' id = db.Column(db.Integer, primary_key=True) @@ -62,6 +39,47 @@ class ClientPassword(db.Model): client_id = db.Column(db.Integer, db.ForeignKey('client.id'), nullable=False) category_id = db.Column(db.Integer, db.ForeignKey('password_category.id')) category = db.relationship('PasswordCategory') - def __repr__(self): return f"" + +class PasswordCategory(db.Model): + __tablename__ = 'password_category' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(255), unique=True, nullable=False) + def __repr__(self): + return f"" + +# New models for Remote Categories and Client Remotes +class RemoteCategory(db.Model): + __tablename__ = 'remote_category' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(255), unique=True, nullable=False) + def __repr__(self): + return f"" + +class ClientRemote(db.Model): + __tablename__ = 'client_remote' + id = db.Column(db.Integer, primary_key=True) + client_id = db.Column(db.Integer, db.ForeignKey('client.id'), nullable=False) + remote_category_id = db.Column(db.Integer, db.ForeignKey('remote_category.id'), nullable=False) + # Optional fields: depending on category, only some are used. + name = db.Column(db.String(255)) + ip_address = db.Column(db.String(255)) + port = db.Column(db.String(10)) + psk = db.Column(db.String(255)) + address = db.Column(db.String(255)) + details = db.Column(db.String(255)) + remote_category = db.relationship('RemoteCategory') + def __repr__(self): + return f"" + +# Association tables for domains remain unchanged. +client_domains = db.Table('client_domains', + db.Column('client_id', db.Integer, db.ForeignKey('client.id', ondelete="CASCADE")), + db.Column('domain_id', db.Integer, db.ForeignKey('domain.id')) +) + +client_ssl = db.Table('client_ssl', + db.Column('client_id', db.Integer, db.ForeignKey('client.id', ondelete="CASCADE")), + db.Column('domain_id', db.Integer, db.ForeignKey('domain.id')) +) diff --git a/app/routes/clients.py b/app/routes/clients.py index 56d1b22..2f5d78a 100644 --- a/app/routes/clients.py +++ b/app/routes/clients.py @@ -1,5 +1,5 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash -from app.models import Client, ClientPassword, Domain, PasswordCategory +from app.models import Client, ClientPassword, Domain, PasswordCategory, RemoteCategory from app import db clients_bp = Blueprint('clients', __name__) @@ -27,18 +27,29 @@ def client_detail(client_id): available_domains = Domain.query.filter_by(ssl_only=False).all() available_ssl = Domain.query.filter_by(ssl_only=True).all() available_categories = PasswordCategory.query.all() - # Group passwords by category (or "Uncategorized" if none) + available_remote_categories = RemoteCategory.query.all() + # Group passwords by category. passwords_by_category = {} for p in client.passwords: cat = p.category.name if p.category else "Uncategorized" if cat not in passwords_by_category: passwords_by_category[cat] = [] passwords_by_category[cat].append(p) + # Group remotes by remote category. + remotes_by_category = {} + for r in client.remotes: + cat_name = r.remote_category.name + if cat_name not in remotes_by_category: + remotes_by_category[cat_name] = [] + remotes_by_category[cat_name].append(r) return render_template('client_detail.html', client=client, available_domains=available_domains, available_ssl=available_ssl, available_categories=available_categories, - passwords_by_category=passwords_by_category) + available_remote_categories=available_remote_categories, + passwords_by_category=passwords_by_category, + remotes_by_category=remotes_by_category) + @clients_bp.route('/clients//update_details', methods=['POST']) def update_client_details(client_id): @@ -117,4 +128,42 @@ def delete_client(client_id): db.session.delete(client) db.session.commit() flash("Client deleted successfully.", "success") - return redirect(url_for('clients.clients')) \ No newline at end of file + return redirect(url_for('clients.clients')) + +@clients_bp.route('/clients//add_remote', methods=['POST']) +def add_remote(client_id): + from app.models import RemoteCategory, ClientRemote + client = Client.query.get_or_404(client_id) + remote_category_id = request.form.get('remote_category_id') + if not remote_category_id: + flash("Please select a remote category.", "warning") + return redirect(url_for('clients.client_detail', client_id=client.id)) + remote_category = RemoteCategory.query.get(remote_category_id) + new_remote = ClientRemote(client_id=client.id, remote_category_id=remote_category.id) + # Fill in fields depending on category name. + if remote_category.name == "RDP": + new_remote.name = request.form.get('remote_name') + new_remote.ip_address = request.form.get('ip_address') + new_remote.port = request.form.get('port') + elif remote_category.name == "SSL-VPN": + new_remote.ip_address = request.form.get('ip_address') + new_remote.port = request.form.get('port') + elif remote_category.name == "VPN": + new_remote.ip_address = request.form.get('ip_address') + new_remote.psk = request.form.get('psk') + elif remote_category.name == "MGMT": + new_remote.name = request.form.get('remote_name') + new_remote.address = request.form.get('address') + else: + new_remote.details = request.form.get('details') + db.session.add(new_remote) + db.session.commit() + return redirect(url_for('clients.client_detail', client_id=client.id)) + +@clients_bp.route('/clients//remove_remote/', methods=['POST']) +def remove_remote(client_id, remote_id): + from app.models import ClientRemote + remote = ClientRemote.query.get_or_404(remote_id) + db.session.delete(remote) + db.session.commit() + return redirect(url_for('clients.client_detail', client_id=client_id)) diff --git a/app/routes/domain.py b/app/routes/domain.py index 1fc6958..e2197a1 100644 --- a/app/routes/domain.py +++ b/app/routes/domain.py @@ -3,45 +3,45 @@ import whois from app.models import Domain from app import db from app.utils import get_ssl_expiration, get_whois_expiration -from datetime import datetime +from datetime import datetime, timedelta domain_bp = Blueprint('domain', __name__) @domain_bp.route('/') def home(): - current_year = datetime.now().year + now = datetime.now() + three_months_later = now + timedelta(days=90) all_domains = Domain.query.all() domains_expiring = [] - certs_expiring = [] + ssl_expiring = [] for d in all_domains: - # Filter and sort by WHOIS expiration date (expected format: mm/dd/yyyy) + # Check WHOIS expiration date (assumed format "mm/dd/yyyy") try: dt_whois = datetime.strptime(d.expiration_date, "%m/%d/%Y") - if dt_whois.year == current_year: + if now <= dt_whois <= three_months_later: domains_expiring.append((dt_whois, d)) except Exception: pass - - # Filter and sort by SSL expiration date (expected format: mm/dd/yyyy) + + # Check SSL expiration date (assumed format "mm/dd/yyyy") try: dt_ssl = datetime.strptime(d.ssl_expiration_date, "%m/%d/%Y") - if dt_ssl.year == current_year: - certs_expiring.append((dt_ssl, d)) + if now <= dt_ssl <= three_months_later: + ssl_expiring.append((dt_ssl, d)) except Exception: pass - # Sort the lists in ascending order by the date + # Sort lists in ascending order by date domains_expiring.sort(key=lambda x: x[0]) - certs_expiring.sort(key=lambda x: x[0]) - - # Extract the domain objects from the sorted tuples + ssl_expiring.sort(key=lambda x: x[0]) + # Extract just the domain objects domains_expiring = [item[1] for item in domains_expiring] - certs_expiring = [item[1] for item in certs_expiring] - + ssl_expiring = [item[1] for item in ssl_expiring] + return render_template('home.html', domains_expiring=domains_expiring, - certs_expiring=certs_expiring) + ssl_expiring=ssl_expiring) @domain_bp.route('/domains') def domains(): diff --git a/app/routes/tools.py b/app/routes/tools.py index 721d8d5..061e47c 100644 --- a/app/routes/tools.py +++ b/app/routes/tools.py @@ -201,4 +201,30 @@ def import_domains(): else: flash("No file uploaded", "warning") return redirect(url_for('tools.tools')) - return render_template('import_domains.html') \ No newline at end of file + return render_template('import_domains.html') + + +@tools_bp.route('/tools/remote_categories', methods=['GET', 'POST']) +def remote_categories(): + from app.models import RemoteCategory + if request.method == 'POST': + category_name = request.form.get('category_name') + if category_name: + # Check if it exists + if not RemoteCategory.query.filter_by(name=category_name.strip()).first(): + new_cat = RemoteCategory(name=category_name.strip()) + db.session.add(new_cat) + db.session.commit() + categories = RemoteCategory.query.all() + return render_template('remote_categories.html', categories=categories) + +@tools_bp.route('/tools/remote_categories/delete/', methods=['POST']) +def delete_remote_category(cat_id): + from app.models import RemoteCategory, ClientRemote + cat = RemoteCategory.query.get_or_404(cat_id) + # Optional: you might want to prevent deletion of defaults. + db.session.query(ClientRemote).filter_by(remote_category_id=cat.id).update({"remote_category_id": None}, synchronize_session=False) + db.session.delete(cat) + db.session.commit() + flash("Remote category deleted.", "success") + return redirect(url_for('tools.remote_categories')) diff --git a/app/templates/client_detail.html b/app/templates/client_detail.html index 3779f33..a8a79c9 100644 --- a/app/templates/client_detail.html +++ b/app/templates/client_detail.html @@ -7,7 +7,6 @@
-

Details

{% if client.phone or client.address %}

Phone: {{ client.phone or "N/A" }} | Address: {{ client.address or "N/A" }}

@@ -35,6 +34,142 @@
+
+ + +
+

Remote

+ {% for category, remotes in remotes_by_category.items() %} + {% if remotes|length > 0 %} +

{{ category }}

+ + + {% if category == 'RDP' %} + + + + + + + {% elif category == 'SSL-VPN' %} + + + + + + {% elif category == 'VPN' %} + + + + + + {% elif category == 'MGMT' %} + + + + + + {% else %} + + + + + {% endif %} + + + {% for remote in remotes %} + + {% if category == 'RDP' %} + + + + {% elif category == 'SSL-VPN' %} + + + {% elif category == 'VPN' %} + + + {% elif category == 'MGMT' %} + + + {% else %} + + {% endif %} + + + {% endfor %} + +
NameIP AddressPortAction
IP AddressPortAction
IP AddressPSKAction
NameAddressAction
DetailsAction
{{ remote.name }}{{ remote.ip_address }}{{ remote.port }}{{ remote.ip_address }}{{ remote.port }}{{ remote.ip_address }}{{ remote.psk }}{{ remote.name }}{{ remote.address }}{{ remote.details }} +
+ +
+
+ {% endif %} + {% endfor %} + + + + + + +
+ + + +
diff --git a/app/templates/home.html b/app/templates/home.html index 9865f5a..22abf15 100644 --- a/app/templates/home.html +++ b/app/templates/home.html @@ -2,17 +2,15 @@ {% block title %}Home{% endblock %} {% block content %}
-

Home

+

Expiring in the Next 3 Months

- -

Domains Expiring this Year

+

Domains:

{% if domains_expiring and domains_expiring|length > 0 %} - @@ -20,18 +18,16 @@ - {% endfor %}
Domain WHOIS ExpirationSSL Expiration
{{ d.domain }} {{ d.expiration_date }}{{ d.ssl_expiration_date }}
{% else %} -

No domains expiring this year.

+

No domains expiring in the next 3 months.

{% endif %} - -

Certs Expiring this Year

- {% if certs_expiring and certs_expiring|length > 0 %} +

SSL Certificates:

+ {% if ssl_expiring and ssl_expiring|length > 0 %} @@ -40,7 +36,7 @@ - {% for d in certs_expiring %} + {% for d in ssl_expiring %} @@ -49,7 +45,7 @@
{{ d.domain }} {{ d.ssl_expiration_date }}
{% else %} -

No certificates expiring this year.

+

No SSL certificates expiring in the next 3 months.

{% endif %}
{% endblock %} diff --git a/app/templates/remote_categories.html b/app/templates/remote_categories.html new file mode 100644 index 0000000..a3124e8 --- /dev/null +++ b/app/templates/remote_categories.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} +{% block title %}Remote Categories{% endblock %} +{% block content %} +
+

Remote Categories

+
+
+ + +
+ +
+ {% if categories %} + + + + + + + + + {% for cat in categories %} + + + + + {% endfor %} + +
Category NameAction
{{ cat.name }} +
+ +
+
+ {% else %} +

No remote categories defined.

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