From 1c83e211e7818197a26247daecde5d78c73709ea Mon Sep 17 00:00:00 2001 From: Bryan Cerrati Date: Fri, 7 Mar 2025 10:11:36 -0500 Subject: [PATCH] Client Pages / Passwords --- app/__init__.py | 8 ++ app/models.py | 38 +++++++ app/routes/clients.py | 108 +++++++++++++++++++ app/routes/domain.py | 18 +++- app/templates/add_client.html | 33 ++++++ app/templates/add_domain.html | 42 ++++---- app/templates/add_ssl_domain.html | 42 ++++---- app/templates/base.html | 60 +++++++---- app/templates/client_detail.html | 170 ++++++++++++++++++++++++++++++ app/templates/clients.html | 26 +++++ app/templates/domains.html | 55 ++++++---- app/templates/home.html | 82 +++++++------- app/templates/ssl.html | 71 ++++++++----- app/templates/tools.html | 29 ++--- 14 files changed, 627 insertions(+), 155 deletions(-) create mode 100644 app/routes/clients.py create mode 100644 app/templates/add_client.html create mode 100644 app/templates/client_detail.html create mode 100644 app/templates/clients.html diff --git a/app/__init__.py b/app/__init__.py index 24d649d..d935939 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -8,6 +8,8 @@ def create_app(): # Create the Flask app and set the template folder (located in app/templates) app = Flask(__name__, template_folder='templates') + app.secret_key = 'your_super_secret_key_here' + # Configure the SQLite database app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(os.path.abspath(os.path.dirname(__file__)), 'domains.db') app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False @@ -27,4 +29,10 @@ def create_app(): from app.routes.ssl import ssl_bp app.register_blueprint(ssl_bp) + from app.routes.tools import tools_bp + app.register_blueprint(tools_bp) + + from app.routes.clients import clients_bp + app.register_blueprint(clients_bp) + return app diff --git a/app/models.py b/app/models.py index 60e63f1..fa2b5f8 100644 --- a/app/models.py +++ b/app/models.py @@ -1,5 +1,16 @@ from app import db +# Association tables for many-to-many relationships. +client_domains = db.Table('client_domains', + db.Column('client_id', db.Integer, db.ForeignKey('client.id')), + 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')), + db.Column('domain_id', db.Integer, db.ForeignKey('domain.id')) +) + class Domain(db.Model): __tablename__ = 'domain' __table_args__ = {'extend_existing': True} @@ -12,3 +23,30 @@ class Domain(db.Model): def __repr__(self): return f"" + +class Client(db.Model): + __tablename__ = 'client' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(255), nullable=False) + phone = db.Column(db.String(255)) + address = db.Column(db.String(255)) + + # Relationship for storing passwords + passwords = db.relationship('ClientPassword', backref='client', lazy=True) + # Many-to-many relationships for domains and SSL domains + domains = db.relationship('Domain', secondary=client_domains, backref='clients') + ssl_domains = db.relationship('Domain', secondary=client_ssl, backref='ssl_clients') + + def __repr__(self): + return f"" + +class ClientPassword(db.Model): + __tablename__ = 'client_password' + id = db.Column(db.Integer, primary_key=True) + item = db.Column(db.String(255), nullable=False) + username = db.Column(db.String(255), nullable=False) + password = db.Column(db.String(255), nullable=False) + client_id = db.Column(db.Integer, db.ForeignKey('client.id'), nullable=False) + + def __repr__(self): + return f"" diff --git a/app/routes/clients.py b/app/routes/clients.py new file mode 100644 index 0000000..7b61359 --- /dev/null +++ b/app/routes/clients.py @@ -0,0 +1,108 @@ +from flask import Blueprint, render_template, request, redirect, url_for +from app.models import Client, ClientPassword, Domain +from app import db + +clients_bp = Blueprint('clients', __name__) + +# List all clients +@clients_bp.route('/clients') +def clients(): + clients_list = Client.query.all() + return render_template('clients.html', clients=clients_list) + +# Add multiple clients at once +@clients_bp.route('/clients/add', methods=['GET', 'POST']) +def add_client(): + if request.method == 'POST': + names = request.form.getlist('name') + for name in names: + if name.strip(): + new_client = Client(name=name.strip()) + db.session.add(new_client) + db.session.commit() + return redirect(url_for('clients.clients')) + return render_template('add_client.html') + +# Client detail page with all four sections +@clients_bp.route('/clients/') +def client_detail(client_id): + client = Client.query.get_or_404(client_id) + # For the Domains dropdown: only available non-SSL-only domains. + available_domains = Domain.query.filter_by(ssl_only=False).all() + # For the SSL dropdown: only available SSL-only domains. + available_ssl = Domain.query.filter_by(ssl_only=True).all() + return render_template('client_detail.html', client=client, + available_domains=available_domains, + available_ssl=available_ssl) + +# Update client details (phone, address) +@clients_bp.route('/clients//update_details', methods=['POST']) +def update_client_details(client_id): + client = Client.query.get_or_404(client_id) + client.phone = request.form.get('phone') + client.address = request.form.get('address') + db.session.commit() + return redirect(url_for('clients.client_detail', client_id=client.id)) + +# Add a new password for the client +@clients_bp.route('/clients//add_password', methods=['POST']) +def add_client_password(client_id): + client = Client.query.get_or_404(client_id) + item = request.form.get('item') + username = request.form.get('username') + password = request.form.get('password') + if item and username and password: + new_pass = ClientPassword(item=item, username=username, password=password, client_id=client.id) + db.session.add(new_pass) + db.session.commit() + return redirect(url_for('clients.client_detail', client_id=client.id)) + +# Attach a domain (non SSL-only) to the client +@clients_bp.route('/clients//attach_domain', methods=['POST']) +def attach_domain(client_id): + client = Client.query.get_or_404(client_id) + domain_id = request.form.get('domain_id') + if domain_id: + domain = Domain.query.get(int(domain_id)) + if domain and domain not in client.domains: + client.domains.append(domain) + db.session.commit() + return redirect(url_for('clients.client_detail', client_id=client.id)) + +# Attach an SSL domain (ssl_only) to the client +@clients_bp.route('/clients//attach_ssl', methods=['POST']) +def attach_ssl(client_id): + client = Client.query.get_or_404(client_id) + domain_id = request.form.get('ssl_domain_id') + if domain_id: + domain = Domain.query.get(int(domain_id)) + if domain and domain not in client.ssl_domains: + client.ssl_domains.append(domain) + db.session.commit() + return redirect(url_for('clients.client_detail', client_id=client.id)) + + +@clients_bp.route('/clients//remove_domain/', methods=['POST']) +def remove_domain(client_id, domain_id): + client = Client.query.get_or_404(client_id) + domain = Domain.query.get_or_404(domain_id) + if domain in client.domains: + client.domains.remove(domain) + db.session.commit() + return redirect(url_for('clients.client_detail', client_id=client.id)) + +@clients_bp.route('/clients//remove_ssl/', methods=['POST']) +def remove_ssl(client_id, domain_id): + client = Client.query.get_or_404(client_id) + domain = Domain.query.get_or_404(domain_id) + if domain in client.ssl_domains: + client.ssl_domains.remove(domain) + db.session.commit() + return redirect(url_for('clients.client_detail', client_id=client.id)) + +@clients_bp.route('/clients//remove_password/', methods=['POST']) +def remove_password(client_id, password_id): + password = ClientPassword.query.get_or_404(password_id) + db.session.delete(password) + 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 3f340fa..1fc6958 100644 --- a/app/routes/domain.py +++ b/app/routes/domain.py @@ -71,13 +71,14 @@ def add_domain(): return redirect(url_for('domain.domains')) return render_template('add_domain.html') -@domain_bp.route('/delete/', methods=['GET']) +@domain_bp.route('/delete/', methods=['POST']) def delete_domain(domain_id): domain_to_delete = Domain.query.get_or_404(domain_id) db.session.delete(domain_to_delete) db.session.commit() return redirect(url_for('domain.domains')) + @domain_bp.route('/domains/refresh') def refresh_domains(): # Refresh only non-SSL-only domains @@ -88,3 +89,18 @@ def refresh_domains(): d.ssl_expiration_date = get_ssl_expiration(d.domain) db.session.commit() return redirect(url_for('domain.domains')) + +@domain_bp.route('/refresh/', methods=['GET']) +def refresh_individual(domain_id): + domain = Domain.query.get_or_404(domain_id) + from app.utils import get_whois_expiration, get_ssl_expiration + # Update WHOIS expiration date + domain.expiration_date = get_whois_expiration(domain.domain) + # Update SSL expiration date + domain.ssl_expiration_date = get_ssl_expiration(domain.domain) + db.session.commit() + # Redirect back to the appropriate page based on the domain type + if domain.ssl_only: + return redirect(url_for('ssl.ssl_page')) + else: + return redirect(url_for('domain.domains')) diff --git a/app/templates/add_client.html b/app/templates/add_client.html new file mode 100644 index 0000000..fb39bdd --- /dev/null +++ b/app/templates/add_client.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% block title %}Add Client(s){% endblock %} +{% block content %} +
+

Add Client(s)

+
+
+
+ +
+
+ +

+ +
+
+ Back to Clients +
+ +{% endblock %} diff --git a/app/templates/add_domain.html b/app/templates/add_domain.html index e2516ff..1ecc2be 100644 --- a/app/templates/add_domain.html +++ b/app/templates/add_domain.html @@ -1,27 +1,33 @@ {% extends "base.html" %} {% block title %}Add Domain{% endblock %} {% block content %} -

Add Domain(s)

-
+
+

Add Domain(s)

+
- +
+ +
-
- +

- - -
-Back to Domains + + +
+ Back to Domains +
{% endblock %} diff --git a/app/templates/add_ssl_domain.html b/app/templates/add_ssl_domain.html index befe376..fb90e75 100644 --- a/app/templates/add_ssl_domain.html +++ b/app/templates/add_ssl_domain.html @@ -1,27 +1,33 @@ {% extends "base.html" %} {% block title %}Add SSL Domain{% endblock %} {% block content %} -

Add SSL Domain(s)

-
+
+

Add SSL Domain(s)

+
- +
+ +
-
- +

- - -
-Back to SSL Page + + +
+ Back to SSL Page +
{% endblock %} diff --git a/app/templates/base.html b/app/templates/base.html index 073f4f1..3f8c4f2 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -1,27 +1,49 @@ - - {% block title %}My Domain App{% endblock %} - + + {% block title %}My Domain App{% endblock %} + + + - + +
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} {% block content %}{% endblock %} +
+ + + + + {% block scripts %}{% endblock %} diff --git a/app/templates/client_detail.html b/app/templates/client_detail.html new file mode 100644 index 0000000..d17b679 --- /dev/null +++ b/app/templates/client_detail.html @@ -0,0 +1,170 @@ +{% extends "base.html" %} +{% block title %}{{ client.name }}{% endblock %} +{% block content %} +
+

{{ client.name }}

+
+ + +
+

Details

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

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

+ {% else %} +

No details available.

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

Passwords

+ + + + + + + + + + + {% for p in client.passwords %} + + + + + + + {% endfor %} + +
ItemUsernamePasswordAction
{{ p.item }}{{ p.username }}{{ p.password }} +
+ +
+
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+ +
+ + +
+

Domains

+ + + + + + + + + + {% for d in client.domains %} + + + + + + {% endfor %} + +
DomainWHOIS ExpirationAction
{{ d.domain }}{{ d.expiration_date }} +
+ +
+
+
+
+ + +
+ +
+
+ +
+ + +
+

SSL

+ + + + + + + + + + {% for d in client.ssl_domains %} + + + + + + {% endfor %} + +
DomainSSL ExpirationAction
{{ d.domain }}{{ d.ssl_expiration_date }} +
+ +
+
+
+
+ + +
+ +
+
+{% endblock %} diff --git a/app/templates/clients.html b/app/templates/clients.html new file mode 100644 index 0000000..f041d81 --- /dev/null +++ b/app/templates/clients.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} +{% block title %}Clients{% endblock %} +{% block content %} +
+

Clients

+ + + + + + + + + {% for client in clients %} + + + + {% endfor %} + +
Client Name
+ {{ client.name }} +
+
+{% endblock %} diff --git a/app/templates/domains.html b/app/templates/domains.html index 2c43bcb..eceefee 100644 --- a/app/templates/domains.html +++ b/app/templates/domains.html @@ -1,28 +1,41 @@ {% extends "base.html" %} {% block title %}Domains{% endblock %} {% block content %} -

Domain List

- - - - - - - - +
+

Domains

+ +
DomainDomain ExpirationAction
+ + + + + + + + - {% for domain in domains %} - - - - - - {% endfor %} + {% for domain in domains %} + + + + + + + + {% endfor %} -
DomainWHOIS ExpirationSSL ExpirationRefreshAction
{{ domain.domain }}{{ domain.expiration_date }} - X -
{{ domain.domain }}{{ domain.expiration_date }}{{ domain.ssl_expiration_date }} + Refresh + +
+ +
+
-
- + + + {% endblock %} diff --git a/app/templates/home.html b/app/templates/home.html index 1feaf01..9865f5a 100644 --- a/app/templates/home.html +++ b/app/templates/home.html @@ -1,47 +1,55 @@ {% extends "base.html" %} {% block title %}Home{% endblock %} {% block content %} -

Domains Expiring this Year

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

Home

+ + +

Domains Expiring this Year

+ {% if domains_expiring and domains_expiring|length > 0 %} +
DomainWHOIS Expiration
+ + + + + + - {% for d in domains_expiring %} - - - - - {% endfor %} + {% for d in domains_expiring %} + + + + + + {% endfor %} -
DomainWHOIS ExpirationSSL Expiration
{{ d.domain }}{{ d.expiration_date }}
{{ d.domain }}{{ d.expiration_date }}{{ d.ssl_expiration_date }}
-{% else %} -

No domains expiring this year.

-{% endif %} - -

Certs Expiring this Year

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

No domains expiring this year.

+ {% endif %} + + +

Certs Expiring this Year

+ {% if certs_expiring and certs_expiring|length > 0 %} + + + + + + - {% for d in certs_expiring %} - - - - - {% endfor %} + {% for d in certs_expiring %} + + + + + {% endfor %} -
DomainSSL Expiration
{{ d.domain }}{{ d.ssl_expiration_date }}
{{ d.domain }}{{ d.ssl_expiration_date }}
-{% else %} -

No certificates expiring this year.

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

No certificates expiring this year.

+ {% endif %} + {% endblock %} diff --git a/app/templates/ssl.html b/app/templates/ssl.html index 38ac142..654a31d 100644 --- a/app/templates/ssl.html +++ b/app/templates/ssl.html @@ -1,36 +1,49 @@ {% extends "base.html" %} {% block title %}SSL Certificates{% endblock %} {% block content %} -

SSL Certificates

- - - - - - - - - +
+

SSL Certificates

+ +
DomainSSL ExpirationTypeAction
+ + + + + + + + + - {% for d in domains %} - - - - - - - {% endfor %} + {% for d in domains %} + + + + + + + + + {% endfor %} -
DomainWHOIS ExpirationSSL ExpirationTypeRefreshAction
{{ d.domain }}{{ d.ssl_expiration_date }} - {% if d.ssl_only %} - Email - {% else %} - Domain - {% endif %} - - X -
{{ d.domain }}{{ d.expiration_date }}{{ d.ssl_expiration_date }} + {% if d.ssl_only %} + SSL Only + {% else %} + Normal + {% endif %} + + Refresh + +
+ +
+
-
- + + + {% endblock %} diff --git a/app/templates/tools.html b/app/templates/tools.html index 5347ab5..7e18f90 100644 --- a/app/templates/tools.html +++ b/app/templates/tools.html @@ -1,21 +1,26 @@ {% extends "base.html" %} {% block title %}Tools{% endblock %} {% block content %} -

Tools

- -
-

Import / Export Data

-
+
+

Tools

+
+
+ Import / Export Data +
+
+
+
- - + +
+
+
+
- +
{% endblock %}