Remote Details Section Added

main
Bryan Cerrati 2025-03-07 21:13:22 -05:00
parent a5d6dea801
commit 6165103385
8 changed files with 338 additions and 68 deletions

View File

@ -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()

View File

@ -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"<Domain {self.domain} | Whois: {self.expiration_date} | SSL: {self.ssl_expiration_date} | ssl_only: {self.ssl_only}>"
return f"<Domain {self.domain}>"
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,
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"<Client {self.name}>"
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"<PasswordCategory {self.name}>"
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"<ClientPassword {self.item}>"
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"<PasswordCategory {self.name}>"
# 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"<RemoteCategory {self.name}>"
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"<ClientRemote {self.id} in {self.remote_category.name}>"
# 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'))
)

View File

@ -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/<int:client_id>/update_details', methods=['POST'])
def update_client_details(client_id):
@ -118,3 +129,41 @@ def delete_client(client_id):
db.session.commit()
flash("Client deleted successfully.", "success")
return redirect(url_for('clients.clients'))
@clients_bp.route('/clients/<int:client_id>/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/<int:client_id>/remove_remote/<int:remote_id>', 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))

View File

@ -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():

View File

@ -202,3 +202,29 @@ def import_domains():
flash("No file uploaded", "warning")
return redirect(url_for('tools.tools'))
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/<int:cat_id>', 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'))

View File

@ -7,7 +7,6 @@
<!-- Section 1: Details -->
<div class="mt-4">
<h2 class="section-title text-center">Details</h2>
<div id="detailsDisplay" class="text-center">
{% if client.phone or client.address %}
<h2>Phone: {{ client.phone or "N/A" }} | Address: {{ client.address or "N/A" }}</h2>
@ -35,6 +34,142 @@
</div>
</div>
<hr>
<!-- Section: Remote -->
<div class="mt-4">
<h2 class="section-title">Remote</h2>
{% for category, remotes in remotes_by_category.items() %}
{% if remotes|length > 0 %}
<h3 class="mt-3">{{ category }}</h3>
<table class="table table-bordered table-hover">
<thead class="thead-light">
{% if category == 'RDP' %}
<tr>
<th>Name</th>
<th>IP Address</th>
<th>Port</th>
<th>Action</th>
</tr>
{% elif category == 'SSL-VPN' %}
<tr>
<th>IP Address</th>
<th>Port</th>
<th>Action</th>
</tr>
{% elif category == 'VPN' %}
<tr>
<th>IP Address</th>
<th>PSK</th>
<th>Action</th>
</tr>
{% elif category == 'MGMT' %}
<tr>
<th>Name</th>
<th>Address</th>
<th>Action</th>
</tr>
{% else %}
<tr>
<th>Details</th>
<th>Action</th>
</tr>
{% endif %}
</thead>
<tbody>
{% for remote in remotes %}
<tr>
{% if category == 'RDP' %}
<td>{{ remote.name }}</td>
<td>{{ remote.ip_address }}</td>
<td>{{ remote.port }}</td>
{% elif category == 'SSL-VPN' %}
<td>{{ remote.ip_address }}</td>
<td>{{ remote.port }}</td>
{% elif category == 'VPN' %}
<td>{{ remote.ip_address }}</td>
<td>{{ remote.psk }}</td>
{% elif category == 'MGMT' %}
<td>{{ remote.name }}</td>
<td><a href="{{ remote.address }}" target="_blank">{{ remote.address }}</a></td>
{% else %}
<td>{{ remote.details }}</td>
{% endif %}
<td>
<form action="{{ url_for('clients.remove_remote', client_id=client.id, remote_id=remote.id) }}" method="post" class="d-inline">
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to delete this remote?');">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endfor %}
<!-- Button to toggle remote form -->
<button id="toggleRemoteFormBtn" class="btn btn-primary">Add Remote</button>
<!-- Remote Form: hidden by default -->
<div id="remoteForm" style="display: none;" class="mt-4">
<h3>Add Remote</h3>
<form action="{{ url_for('clients.add_remote', client_id=client.id) }}" method="post" id="remoteFormFields">
<div class="form-group">
<label for="remote_category">Remote Category</label>
<select class="form-control" id="remote_category" name="remote_category_id" onchange="changeRemoteFormFields()">
{% for rc in available_remote_categories %}
<option value="{{ rc.id }}">{{ rc.name }}</option>
{% endfor %}
</select>
</div>
<div id="remoteFields">
<!-- Fields will be dynamically inserted by JavaScript -->
</div>
<button type="submit" class="btn btn-success">Add Remote</button>
</form>
</div>
</div>
<script>
// Toggle the remote form display when the button is clicked
document.getElementById("toggleRemoteFormBtn").addEventListener("click", function(){
var formDiv = document.getElementById("remoteForm");
if(formDiv.style.display === "none"){
formDiv.style.display = "block";
} else {
formDiv.style.display = "none";
}
});
function changeRemoteFormFields() {
var select = document.getElementById("remote_category");
var categoryName = select.options[select.selectedIndex].text;
var container = document.getElementById("remoteFields");
container.innerHTML = "";
if (categoryName === "RDP") {
container.innerHTML += '<div class="form-group"><label for="remote_name">Name</label><input type="text" class="form-control" name="remote_name" placeholder="Name"></div>';
container.innerHTML += '<div class="form-group"><label for="ip_address">IP Address</label><input type="text" class="form-control" name="ip_address" placeholder="IP Address"></div>';
container.innerHTML += '<div class="form-group"><label for="port">Port</label><input type="text" class="form-control" name="port" placeholder="Port"></div>';
} else if (categoryName === "SSL-VPN") {
container.innerHTML += '<div class="form-group"><label for="ip_address">IP Address</label><input type="text" class="form-control" name="ip_address" placeholder="IP Address"></div>';
container.innerHTML += '<div class="form-group"><label for="port">Port</label><input type="text" class="form-control" name="port" placeholder="Port"></div>';
} else if (categoryName === "VPN") {
container.innerHTML += '<div class="form-group"><label for="ip_address">IP Address</label><input type="text" class="form-control" name="ip_address" placeholder="IP Address"></div>';
container.innerHTML += '<div class="form-group"><label for="psk">PSK</label><input type="text" class="form-control" name="psk" placeholder="Pre-Shared Key"></div>';
} else if (categoryName === "MGMT") {
container.innerHTML += '<div class="form-group"><label for="remote_name">Name</label><input type="text" class="form-control" name="remote_name" placeholder="Name"></div>';
container.innerHTML += '<div class="form-group"><label for="address">Address (URL)</label><input type="text" class="form-control" name="address" placeholder="http://example.com"></div>';
} else {
container.innerHTML += '<div class="form-group"><label for="details">Details</label><input type="text" class="form-control" name="details" placeholder="Details"></div>';
}
}
document.addEventListener("DOMContentLoaded", function(){
changeRemoteFormFields();
});
</script>
<hr>
<!-- Section 2: Passwords -->

View File

@ -2,17 +2,15 @@
{% block title %}Home{% endblock %}
{% block content %}
<div class="container">
<h1 class="my-4">Home</h1>
<h1 class="my-4">Expiring in the Next 3 Months</h1>
<!-- Domains Expiring this Year -->
<h2 class="mb-3">Domains Expiring this Year</h2>
<h2>Domains:</h2>
{% if domains_expiring and domains_expiring|length > 0 %}
<table class="table table-bordered table-hover">
<thead class="thead-light">
<tr>
<th>Domain</th>
<th>WHOIS Expiration</th>
<th>SSL Expiration</th>
</tr>
</thead>
<tbody>
@ -20,18 +18,16 @@
<tr>
<td>{{ d.domain }}</td>
<td>{{ d.expiration_date }}</td>
<td>{{ d.ssl_expiration_date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No domains expiring this year.</p>
<p>No domains expiring in the next 3 months.</p>
{% endif %}
<!-- Certs Expiring this Year -->
<h2 class="mt-5 mb-3">Certs Expiring this Year</h2>
{% if certs_expiring and certs_expiring|length > 0 %}
<h2 class="mt-5">SSL Certificates:</h2>
{% if ssl_expiring and ssl_expiring|length > 0 %}
<table class="table table-bordered table-hover">
<thead class="thead-light">
<tr>
@ -40,7 +36,7 @@
</tr>
</thead>
<tbody>
{% for d in certs_expiring %}
{% for d in ssl_expiring %}
<tr>
<td>{{ d.domain }}</td>
<td>{{ d.ssl_expiration_date }}</td>
@ -49,7 +45,7 @@
</tbody>
</table>
{% else %}
<p>No certificates expiring this year.</p>
<p>No SSL certificates expiring in the next 3 months.</p>
{% endif %}
</div>
{% endblock %}

View File

@ -0,0 +1,38 @@
{% extends "base.html" %}
{% block title %}Remote Categories{% endblock %}
{% block content %}
<div class="container">
<h1 class="my-4">Remote Categories</h1>
<form method="post" class="form-inline mb-4">
<div class="form-group">
<label for="category_name" class="sr-only">Category Name:</label>
<input type="text" name="category_name" class="form-control" placeholder="Enter remote category">
</div>
<button type="submit" class="btn btn-primary ml-2">Add Category</button>
</form>
{% if categories %}
<table class="table table-bordered table-hover">
<thead class="thead-light">
<tr>
<th>Category Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for cat in categories %}
<tr>
<td>{{ cat.name }}</td>
<td>
<form action="{{ url_for('tools.delete_remote_category', cat_id=cat.id) }}" method="post" class="d-inline">
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Delete remote category?');">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No remote categories defined.</p>
{% endif %}
</div>
{% endblock %}