commit
This commit is contained in:
787
app.py
Normal file
787
app.py
Normal file
@@ -0,0 +1,787 @@
|
||||
import os
|
||||
import ssl
|
||||
import socket
|
||||
from datetime import datetime, timedelta
|
||||
import requests
|
||||
from flask import Flask, render_template, request, redirect, url_for, session, jsonify, flash, current_app
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from flask_bcrypt import Bcrypt
|
||||
import smtplib
|
||||
import imaplib
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
# Globalna referencja do aplikacji – przydatna dla scheduler'a
|
||||
app_instance = None
|
||||
|
||||
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
||||
DATABASE_FILE = os.path.join(BASEDIR, 'ssl_monitor.db')
|
||||
|
||||
# Globalna flaga, by operację utworzenia domyślnego użytkownika wykonać tylko raz
|
||||
default_user_created = False
|
||||
|
||||
db = SQLAlchemy()
|
||||
bcrypt = Bcrypt()
|
||||
|
||||
def create_app():
|
||||
global app_instance
|
||||
app = Flask(__name__)
|
||||
# W produkcji ustaw SECRET_KEY przez zmienną środowiskową!
|
||||
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'moj-klucz-session')
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DATABASE_FILE}'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
|
||||
# Konfiguracja ciasteczek sesyjnych (zalecane przy HTTPS)
|
||||
app.config['SESSION_COOKIE_SECURE'] = False
|
||||
app.config['SESSION_COOKIE_HTTPONLY'] = True
|
||||
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
|
||||
|
||||
db.init_app(app)
|
||||
bcrypt.init_app(app)
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
app_instance = app
|
||||
|
||||
init_scheduler(app)
|
||||
register_routes(app)
|
||||
|
||||
return app
|
||||
|
||||
###############################################################################
|
||||
# MODELE
|
||||
###############################################################################
|
||||
|
||||
class User(db.Model):
|
||||
"""
|
||||
Model użytkownika – hasło przechowywane jako hash przy użyciu bcrypt.
|
||||
"""
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(50), unique=True, nullable=False)
|
||||
password = db.Column(db.String(200), nullable=False)
|
||||
|
||||
class MonitoredService(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
host = db.Column(db.String(256), nullable=False)
|
||||
port = db.Column(db.Integer, default=443)
|
||||
protocol = db.Column(db.String(50), default='https')
|
||||
region = db.Column(db.String(100), nullable=True)
|
||||
certificate_type = db.Column(db.String(50), nullable=True)
|
||||
last_check = db.Column(db.DateTime)
|
||||
expiry_date = db.Column(db.DateTime)
|
||||
status = db.Column(db.String(20))
|
||||
|
||||
class Settings(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
check_interval_minutes = db.Column(db.Integer, default=60)
|
||||
pushover_enabled = db.Column(db.Boolean, default=False)
|
||||
pushover_token = db.Column(db.String(200), nullable=True)
|
||||
pushover_userkey = db.Column(db.String(200), nullable=True)
|
||||
alert_threshold_30 = db.Column(db.Integer, default=30)
|
||||
alert_threshold_14 = db.Column(db.Integer, default=14)
|
||||
alert_threshold_7 = db.Column(db.Integer, default=7)
|
||||
alert_repeat = db.Column(db.Boolean, default=False)
|
||||
logs_retention_days = db.Column(db.Integer, default=30)
|
||||
|
||||
class History(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
service_id = db.Column(db.Integer, db.ForeignKey('monitored_service.id'), nullable=False)
|
||||
# Dodaj relację, by mieć dostęp do obiektu MonitoredService
|
||||
service = db.relationship("MonitoredService", backref=db.backref("history_entries", lazy=True))
|
||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
status = db.Column(db.String(20))
|
||||
expiry_date = db.Column(db.DateTime, nullable=True)
|
||||
message = db.Column(db.String(500))
|
||||
|
||||
class AuditLog(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
|
||||
operation = db.Column(db.String(50))
|
||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
details = db.Column(db.Text)
|
||||
|
||||
class UserPreferences(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), unique=True)
|
||||
default_grouping = db.Column(db.String(50), default='protocol') # lub "none", "host", "region", itp.
|
||||
# Możesz dodać więcej pól, np. zapisanych filtrów w formie JSON:
|
||||
filters = db.Column(db.Text, default='{}')
|
||||
|
||||
###############################################################################
|
||||
# FUNKCJE POMOCNICZE – SPRAWDZANIE CERTYFIKATU
|
||||
###############################################################################
|
||||
|
||||
def check_https_cert(host, port=443):
|
||||
"""
|
||||
Pobiera datę wygaśnięcia certyfikatu HTTPS.
|
||||
"""
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((host, port), timeout=10) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=host) as ssock:
|
||||
cert = ssock.getpeercert()
|
||||
not_after = cert["notAfter"]
|
||||
expiry_date = datetime.strptime(not_after, "%b %d %H:%M:%S %Y %Z")
|
||||
return expiry_date
|
||||
|
||||
def update_certs():
|
||||
with app_instance.app_context():
|
||||
services = MonitoredService.query.all()
|
||||
settings = Settings.query.first()
|
||||
for service in services:
|
||||
try:
|
||||
proto = service.protocol.lower().strip().replace(" ", "_")
|
||||
if proto == 'https':
|
||||
exp_date = check_https_cert(service.host, service.port)
|
||||
elif proto == 'smtp_starttls':
|
||||
exp_date = check_smtp_starttls_cert(service.host, service.port)
|
||||
elif proto == 'smtp_ssl':
|
||||
exp_date = check_smtp_ssl_cert(service.host, service.port)
|
||||
elif proto == 'imap_starttls':
|
||||
exp_date = check_imap_starttls_cert(service.host, service.port)
|
||||
elif proto == 'imap_ssl':
|
||||
exp_date = check_imap_ssl_cert(service.host, service.port)
|
||||
else:
|
||||
service.status = 'ProtocolNotImplemented'
|
||||
db.session.commit()
|
||||
continue
|
||||
|
||||
service.expiry_date = exp_date
|
||||
service.last_check = datetime.now()
|
||||
if exp_date < datetime.now():
|
||||
new_status = 'Expired'
|
||||
elif exp_date < datetime.now() + timedelta(days=7):
|
||||
new_status = 'ExpiringSoon'
|
||||
else:
|
||||
new_status = 'OK'
|
||||
old_status = service.status
|
||||
service.status = new_status
|
||||
db.session.commit()
|
||||
# Zapis do historii
|
||||
history = History(service_id=service.id, status=new_status, expiry_date=exp_date,
|
||||
message=f"Sprawdzenie. Poprzedni status: {old_status}")
|
||||
db.session.add(history)
|
||||
db.session.commit()
|
||||
|
||||
# Sprawdź progi alertów (dla certyfikatów, które nie wygasły)
|
||||
if new_status in ['ExpiringSoon', 'OK']:
|
||||
days_left = (exp_date - datetime.now()).days
|
||||
alert_sent = False # Możesz rozwinąć logikę, by zapamiętać wysłane alerty
|
||||
if days_left <= settings.alert_threshold_7:
|
||||
# Wysyłamy alert o 7-dniowym progu
|
||||
send_pushover_message("user_key_placeholder", "api_token_placeholder",
|
||||
f"Certyfikat dla {service.host} wygasa za {days_left} dni (7-dniowy próg).",
|
||||
"Alert SSL Monitor")
|
||||
alert_sent = True
|
||||
elif days_left <= settings.alert_threshold_14:
|
||||
send_pushover_message("user_key_placeholder", "api_token_placeholder",
|
||||
f"Certyfikat dla {service.host} wygasa za {days_left} dni (14-dniowy próg).",
|
||||
"Alert SSL Monitor")
|
||||
alert_sent = True
|
||||
elif days_left <= settings.alert_threshold_30:
|
||||
send_pushover_message("user_key_placeholder", "api_token_placeholder",
|
||||
f"Certyfikat dla {service.host} wygasa za {days_left} dni (30-dniowy próg).",
|
||||
"Alert SSL Monitor")
|
||||
alert_sent = True
|
||||
# Jeśli alert_repeat jest False, możesz zapisać, że alert dla tego progu został wysłany,
|
||||
# aby nie wysyłać go ponownie.
|
||||
except Exception as e:
|
||||
service.status = 'Error'
|
||||
db.session.commit()
|
||||
history = History(service_id=service.id, status="Error", expiry_date=None, message=str(e))
|
||||
db.session.add(history)
|
||||
db.session.commit()
|
||||
|
||||
def check_smtp_starttls_cert(host, port=587):
|
||||
context = ssl.create_default_context()
|
||||
server = smtplib.SMTP(host, port, timeout=10)
|
||||
server.ehlo()
|
||||
server.starttls(context=context)
|
||||
cert = server.sock.getpeercert()
|
||||
server.quit()
|
||||
not_after = cert["notAfter"]
|
||||
expiry_date = datetime.strptime(not_after, "%b %d %H:%M:%S %Y %Z")
|
||||
return expiry_date
|
||||
|
||||
def check_smtp_ssl_cert(host, port=465):
|
||||
context = ssl.create_default_context()
|
||||
server = smtplib.SMTP_SSL(host, port, timeout=10, context=context)
|
||||
cert = server.sock.getpeercert()
|
||||
server.quit()
|
||||
not_after = cert["notAfter"]
|
||||
expiry_date = datetime.strptime(not_after, "%b %d %H:%M:%S %Y %Z")
|
||||
return expiry_date
|
||||
|
||||
def check_imap_starttls_cert(host, port=143):
|
||||
context = ssl.create_default_context()
|
||||
imap = imaplib.IMAP4(host, port)
|
||||
imap.starttls(ssl_context=context)
|
||||
cert = imap.sock.getpeercert()
|
||||
imap.logout()
|
||||
not_after = cert["notAfter"]
|
||||
expiry_date = datetime.strptime(not_after, "%b %d %H:%M:%S %Y %Z")
|
||||
return expiry_date
|
||||
|
||||
def check_imap_ssl_cert(host, port=993):
|
||||
context = ssl.create_default_context()
|
||||
imap = imaplib.IMAP4_SSL(host, port, ssl_context=context)
|
||||
cert = imap.sock.getpeercert()
|
||||
imap.logout()
|
||||
not_after = cert["notAfter"]
|
||||
expiry_date = datetime.strptime(not_after, "%b %d %H:%M:%S %Y %Z")
|
||||
return expiry_date
|
||||
|
||||
def get_service_response(service):
|
||||
"""
|
||||
Nawiązuje połączenie z serwerem dla danej usługi i zwraca pierwsze 1024 bajty odpowiedzi.
|
||||
Dla HTTPS wysyła żądanie HEAD, dla pozostałych protokołów pobiera banner.
|
||||
"""
|
||||
host = service.host
|
||||
port = service.port
|
||||
proto = service.protocol.lower().strip().replace(" ", "_")
|
||||
timeout = 10
|
||||
if proto == "https":
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((host, port), timeout=timeout) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=host) as ssock:
|
||||
# Wysyłamy żądanie HEAD, aby uzyskać nagłówki
|
||||
request = f"HEAD / HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n"
|
||||
ssock.send(request.encode())
|
||||
data = ssock.recv(1024)
|
||||
return data.decode(errors='replace')
|
||||
elif proto in ["smtp_starttls", "smtp_ssl"]:
|
||||
if proto == "smtp_ssl":
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((host, port), timeout=timeout) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=host) as ssock:
|
||||
data = ssock.recv(1024)
|
||||
return data.decode(errors='replace')
|
||||
else:
|
||||
# smtp_starttls: banner jest wysyłany w wersji niezaszyfrowanej
|
||||
with socket.create_connection((host, port), timeout=timeout) as sock:
|
||||
data = sock.recv(1024)
|
||||
return data.decode(errors='replace')
|
||||
elif proto in ["imap_starttls", "imap_ssl"]:
|
||||
if proto == "imap_ssl":
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((host, port), timeout=timeout) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=host) as ssock:
|
||||
data = ssock.recv(1024)
|
||||
return data.decode(errors='replace')
|
||||
else:
|
||||
# imap_starttls: banner wysyłany jest bez szyfrowania
|
||||
with socket.create_connection((host, port), timeout=timeout) as sock:
|
||||
data = sock.recv(1024)
|
||||
return data.decode(errors='replace')
|
||||
else:
|
||||
raise Exception("Protocol not supported for response retrieval")
|
||||
|
||||
|
||||
def send_pushover_message(user_key, api_token, message, title="SSL Monitor Alert"):
|
||||
"""
|
||||
Wysyła powiadomienie przez Pushover przy użyciu requests.
|
||||
"""
|
||||
url = "https://api.pushover.net/1/messages.json"
|
||||
data = {
|
||||
"token": api_token,
|
||||
"user": user_key,
|
||||
"message": message,
|
||||
"title": title
|
||||
}
|
||||
response = requests.post(url, data=data)
|
||||
return response.json()
|
||||
|
||||
def get_cert_details(service):
|
||||
"""
|
||||
Pobiera szczegółowe informacje o certyfikacie (HTTPS).
|
||||
"""
|
||||
host = service.host
|
||||
port = service.port
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((host, port), timeout=10) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=host) as ssock:
|
||||
cert = ssock.getpeercert()
|
||||
return cert # Zwracamy cały słownik certyfikatu
|
||||
|
||||
def get_cert_details(svc):
|
||||
"""
|
||||
Pobiera szczegółowe informacje o certyfikacie dla danej usługi HTTPS.
|
||||
Zwraca certyfikat w formie słownika.
|
||||
"""
|
||||
host = svc.host
|
||||
port = svc.port
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((host, port), timeout=10) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=host) as ssock:
|
||||
cert = ssock.getpeercert()
|
||||
return cert
|
||||
|
||||
def format_cert_details_as_table(cert_details):
|
||||
def flatten_pairs(value):
|
||||
"""Rekurencyjnie przetwarza zagnieżdżone struktury i zwraca listę napisów 'klucz: wartość'."""
|
||||
result = []
|
||||
if isinstance(value, (list, tuple)):
|
||||
for item in value:
|
||||
# Jeżeli mamy krotkę o długości 2, a pierwszy element to napis – traktuj ją jako parę
|
||||
if isinstance(item, (list, tuple)) and len(item) == 2 and isinstance(item[0], str):
|
||||
result.append(f"{item[0]}: {item[1]}")
|
||||
else:
|
||||
result.extend(flatten_pairs(item))
|
||||
else:
|
||||
result.append(str(value))
|
||||
return result
|
||||
|
||||
table = '<table class="table table-sm table-dark table-bordered">'
|
||||
table += '<thead><tr><th>Klucz</th><th>Wartość</th></tr></thead><tbody>'
|
||||
for key, value in cert_details.items():
|
||||
if isinstance(value, (list, tuple)):
|
||||
flat = flatten_pairs(value)
|
||||
formatted_value = "<br>".join(flat) if flat else str(value)
|
||||
else:
|
||||
formatted_value = str(value)
|
||||
table += f'<tr><th>{key}</th><td>{formatted_value}</td></tr>'
|
||||
table += '</tbody></table>'
|
||||
return table
|
||||
|
||||
|
||||
def get_cert_chain_details(host, port=443):
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((host, port), timeout=10) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=host) as ssock:
|
||||
der_cert = ssock.getpeercert(binary_form=True)
|
||||
cert = x509.load_der_x509_certificate(der_cert, default_backend())
|
||||
# Możesz wyekstrahować informacje o certyfikacie:
|
||||
details = {
|
||||
"subject": cert.subject.rfc4514_string(),
|
||||
"issuer": cert.issuer.rfc4514_string(),
|
||||
"not_before": cert.not_valid_before.isoformat(),
|
||||
"not_after": cert.not_valid_after.isoformat(),
|
||||
"serial_number": str(cert.serial_number),
|
||||
"signature_algorithm": cert.signature_hash_algorithm.name if cert.signature_hash_algorithm else "Unknown",
|
||||
# Dla SAN: cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
|
||||
}
|
||||
return details
|
||||
|
||||
def get_cert_chain_html(host, port=443):
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((host, port), timeout=10) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=host) as ssock:
|
||||
der_cert = ssock.getpeercert(binary_form=True)
|
||||
cert = x509.load_der_x509_certificate(der_cert, default_backend())
|
||||
|
||||
# Pobranie Subject Alternative Names (SAN) jeśli są dostępne
|
||||
try:
|
||||
san_extension = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
|
||||
san = ", ".join(san_extension.value.get_values_for_type(x509.DNSName))
|
||||
except Exception:
|
||||
san = "N/A"
|
||||
|
||||
# Można dodać inne rozszerzenia, np. Key Usage, Extended Key Usage, itd.
|
||||
try:
|
||||
key_usage = cert.extensions.get_extension_for_class(x509.KeyUsage).value
|
||||
key_usage_str = ", ".join([
|
||||
"digitalSignature" if key_usage.digital_signature else "",
|
||||
"contentCommitment" if key_usage.content_commitment else "",
|
||||
"keyEncipherment" if key_usage.key_encipherment else "",
|
||||
"dataEncipherment" if key_usage.data_encipherment else "",
|
||||
"keyAgreement" if key_usage.key_agreement else "",
|
||||
"keyCertSign" if key_usage.key_cert_sign else "",
|
||||
"crlSign" if key_usage.crl_sign else "",
|
||||
"encipherOnly" if key_usage.encipher_only else "",
|
||||
"decipherOnly" if key_usage.decipher_only else ""
|
||||
]).strip(", ")
|
||||
except Exception:
|
||||
key_usage_str = "N/A"
|
||||
|
||||
details_html = f"""
|
||||
<h5 class="mb-3">Szczegółowe informacje o certyfikacie</h5>
|
||||
<table class="table table-sm table-dark">
|
||||
<tr>
|
||||
<th>Subject</th>
|
||||
<td>{cert.subject.rfc4514_string()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Issuer</th>
|
||||
<td>{cert.issuer.rfc4514_string()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Not Before</th>
|
||||
<td>{cert.not_valid_before.strftime('%Y-%m-%d %H:%M:%S')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Not After</th>
|
||||
<td>{cert.not_valid_after.strftime('%Y-%m-%d %H:%M:%S')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Serial Number</th>
|
||||
<td>{cert.serial_number}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Signature Algorithm</th>
|
||||
<td>{cert.signature_hash_algorithm.name if cert.signature_hash_algorithm else 'Unknown'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Subject Alternative Names</th>
|
||||
<td>{san}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Key Usage</th>
|
||||
<td>{key_usage_str}</td>
|
||||
</tr>
|
||||
</table>
|
||||
"""
|
||||
return details_html
|
||||
|
||||
|
||||
###############################################################################
|
||||
# KONFIGURACJA SCHEDULERA
|
||||
###############################################################################
|
||||
|
||||
def init_scheduler(flask_app):
|
||||
global scheduler
|
||||
scheduler = BackgroundScheduler()
|
||||
|
||||
with flask_app.app_context():
|
||||
settings = Settings.query.first()
|
||||
if not settings:
|
||||
settings = Settings(check_interval_minutes=60)
|
||||
db.session.add(settings)
|
||||
db.session.commit()
|
||||
interval = settings.check_interval_minutes
|
||||
|
||||
scheduler.add_job(func=update_certs, trigger="interval", minutes=interval)
|
||||
scheduler.start()
|
||||
|
||||
###############################################################################
|
||||
# TRASY I WIDOKI
|
||||
###############################################################################
|
||||
|
||||
def register_routes(app):
|
||||
# @app.before_request
|
||||
# def ensure_default_user():
|
||||
# """
|
||||
# Przy pierwszym żądaniu tworzymy domyślnego użytkownika 'admin' z zahashowanym hasłem.
|
||||
# """
|
||||
# global default_user_created
|
||||
# if not default_user_created:
|
||||
# if not User.query.filter_by(username='admin').first():
|
||||
# hashed_pw = bcrypt.generate_password_hash("admin").decode('utf-8')
|
||||
# user = User(username='admin', password=hashed_pw)
|
||||
# db.session.add(user)
|
||||
# db.session.commit()
|
||||
# default_user_created = True
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
if 'logged_in' in session and session['logged_in']:
|
||||
return redirect(url_for('dashboard'))
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user and bcrypt.check_password_hash(user.password, password):
|
||||
session['logged_in'] = True
|
||||
session['username'] = user.username
|
||||
session['user_id'] = user.id # << Dodaj tę linię
|
||||
flash("Zalogowano poprawnie.", "success")
|
||||
# Log operacji logowania
|
||||
log = AuditLog(user_id=user.id, operation="login", details="Użytkownik zalogowany.")
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
return redirect(url_for('dashboard'))
|
||||
else:
|
||||
flash("Błędne dane logowania.", "danger")
|
||||
return redirect(url_for('login'))
|
||||
return render_template('login.html')
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.clear()
|
||||
flash("Wylogowano.", "success")
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/dashboard')
|
||||
def dashboard():
|
||||
if 'logged_in' not in session or not session['logged_in']:
|
||||
flash("Musisz się zalogować.", "warning")
|
||||
return redirect(url_for('login'))
|
||||
|
||||
services = MonitoredService.query.all()
|
||||
total = len(services)
|
||||
expired = sum(1 for s in services if s.status == 'Expired')
|
||||
exp_soon = sum(1 for s in services if s.status == 'ExpiringSoon')
|
||||
ok = sum(1 for s in services if s.status == 'OK')
|
||||
error = sum(1 for s in services if s.status == 'Error')
|
||||
|
||||
# Pobranie preferencji użytkownika
|
||||
user = User.query.filter_by(username=session.get('username')).first()
|
||||
prefs = UserPreferences.query.filter_by(user_id=user.id).first() if user else None
|
||||
default_grouping = prefs.default_grouping if prefs else "protocol"
|
||||
|
||||
return render_template('dashboard.html',
|
||||
services=services,
|
||||
total=total, expired=expired,
|
||||
exp_soon=exp_soon, ok=ok, error=error,
|
||||
default_grouping=default_grouping)
|
||||
|
||||
@app.route('/api/services', methods=['GET'])
|
||||
def api_get_services():
|
||||
services = MonitoredService.query.all()
|
||||
data = []
|
||||
for s in services:
|
||||
data.append({
|
||||
'id': s.id,
|
||||
'host': s.host,
|
||||
'port': s.port,
|
||||
'protocol': s.protocol,
|
||||
'region': s.region, # Dodane pole region
|
||||
'last_check': s.last_check.strftime('%Y-%m-%d %H:%M:%S') if s.last_check else None,
|
||||
'expiry_date': s.expiry_date.strftime('%Y-%m-%d %H:%M:%S') if s.expiry_date else None,
|
||||
'status': s.status
|
||||
})
|
||||
return jsonify(data)
|
||||
|
||||
@app.route('/api/services/add', methods=['POST'])
|
||||
def api_add_service():
|
||||
req = request.json
|
||||
host = req.get('host')
|
||||
port = req.get('port', 443)
|
||||
protocol = req.get('protocol', 'https')
|
||||
region = req.get('region', '') # Pobranie regionu z żądania
|
||||
new_svc = MonitoredService(
|
||||
host=host,
|
||||
port=port,
|
||||
protocol=protocol,
|
||||
region=region, # Zapis regionu
|
||||
status='Unknown'
|
||||
)
|
||||
db.session.add(new_svc)
|
||||
db.session.commit()
|
||||
return jsonify({'message': 'Service added'}), 200
|
||||
|
||||
@app.route('/api/services/delete/<int:service_id>', methods=['DELETE'])
|
||||
def api_delete_service(service_id):
|
||||
svc = MonitoredService.query.get_or_404(service_id)
|
||||
db.session.delete(svc)
|
||||
db.session.commit()
|
||||
return jsonify({'message': 'Service deleted'}), 200
|
||||
|
||||
@app.route('/api/services/edit/<int:service_id>', methods=['POST'])
|
||||
def api_edit_service(service_id):
|
||||
req = request.json
|
||||
svc = MonitoredService.query.get_or_404(service_id)
|
||||
svc.host = req.get('host', svc.host)
|
||||
svc.port = req.get('port', svc.port)
|
||||
svc.protocol = req.get('protocol', svc.protocol)
|
||||
# Pobierz region – jeśli nie został podany, ustaw wartość domyślną (np. "default")
|
||||
region = req.get('region', '').strip()
|
||||
if not region:
|
||||
region = "default"
|
||||
svc.region = region
|
||||
db.session.commit()
|
||||
return jsonify({'message': 'Service updated'}), 200
|
||||
|
||||
@app.route('/api/services/update/<int:service_id>', methods=['POST'])
|
||||
def api_update_service(service_id):
|
||||
svc = MonitoredService.query.get_or_404(service_id)
|
||||
try:
|
||||
# Ujednolicenie protokołu
|
||||
proto = svc.protocol.lower().strip().replace(" ", "_")
|
||||
if proto == 'https':
|
||||
exp_date = check_https_cert(svc.host, svc.port)
|
||||
elif proto == 'smtp_starttls':
|
||||
exp_date = check_smtp_starttls_cert(svc.host, svc.port)
|
||||
elif proto == 'smtp_ssl':
|
||||
exp_date = check_smtp_ssl_cert(svc.host, svc.port)
|
||||
elif proto == 'imap_starttls':
|
||||
exp_date = check_imap_starttls_cert(svc.host, svc.port)
|
||||
elif proto == 'imap_ssl':
|
||||
exp_date = check_imap_ssl_cert(svc.host, svc.port)
|
||||
else:
|
||||
svc.status = 'ProtocolNotImplemented'
|
||||
db.session.commit()
|
||||
return jsonify({'message': 'Protocol not implemented'}), 200
|
||||
|
||||
svc.expiry_date = exp_date
|
||||
svc.last_check = datetime.now()
|
||||
if exp_date < datetime.now():
|
||||
svc.status = 'Expired'
|
||||
elif exp_date < datetime.now() + timedelta(days=7):
|
||||
svc.status = 'ExpiringSoon'
|
||||
else:
|
||||
svc.status = 'OK'
|
||||
db.session.commit()
|
||||
return jsonify({'message': 'Service updated'}), 200
|
||||
except Exception as e:
|
||||
svc.status = 'Error'
|
||||
db.session.commit()
|
||||
return jsonify({'message': 'Error updating service', 'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/services/bulk_update', methods=['POST'])
|
||||
def api_bulk_update():
|
||||
update_certs()
|
||||
return jsonify({'message': 'Bulk update completed'}), 200
|
||||
|
||||
@app.route('/settings', methods=['GET', 'POST'])
|
||||
def app_settings():
|
||||
if 'logged_in' not in session or not session['logged_in']:
|
||||
flash("Musisz się zalogować.", "warning")
|
||||
return redirect(url_for('login'))
|
||||
|
||||
s = Settings.query.first()
|
||||
if not s:
|
||||
s = Settings()
|
||||
db.session.add(s)
|
||||
db.session.commit()
|
||||
|
||||
if request.method == 'POST':
|
||||
s.check_interval_minutes = int(request.form.get('check_interval_minutes', 60))
|
||||
s.pushover_enabled = bool(request.form.get('pushover_enabled', False))
|
||||
s.pushover_token = request.form.get('pushover_token', '')
|
||||
s.pushover_userkey = request.form.get('pushover_userkey', '')
|
||||
s.alert_threshold_30 = int(request.form.get('alert_threshold_info', 30))
|
||||
s.alert_threshold_14 = int(request.form.get('alert_threshold_warning', 14))
|
||||
s.alert_threshold_7 = int(request.form.get('alert_threshold_critical', 7))
|
||||
s.alert_repeat = bool(request.form.get('alert_repeat', False))
|
||||
# Pobierz wartość z nowego pola, domyślnie 30 dni
|
||||
s.logs_retention_days = int(request.form.get('logs_retention_days', 30))
|
||||
db.session.commit()
|
||||
|
||||
global scheduler
|
||||
if scheduler:
|
||||
scheduler.remove_all_jobs()
|
||||
scheduler.add_job(func=update_certs,
|
||||
trigger="interval",
|
||||
minutes=s.check_interval_minutes)
|
||||
|
||||
# Usuwanie logów starszych niż podana liczba dni
|
||||
cutoff_date = datetime.utcnow() - timedelta(days=s.logs_retention_days)
|
||||
deleted_count = History.query.filter(History.timestamp < cutoff_date).delete()
|
||||
db.session.commit()
|
||||
|
||||
flash(f"Ustawienia zapisane. Usunięto {deleted_count} logów starszych niż {s.logs_retention_days} dni.", "success")
|
||||
return redirect(url_for('app_settings'))
|
||||
|
||||
return render_template('settings.html', settings=s)
|
||||
|
||||
@app.route('/api/service/response/<int:service_id>', methods=['GET'])
|
||||
def api_service_response(service_id):
|
||||
svc = MonitoredService.query.get_or_404(service_id)
|
||||
try:
|
||||
response_data = get_service_response(svc)
|
||||
return jsonify({'response': response_data})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
confirm = request.form.get('confirm')
|
||||
if password != confirm:
|
||||
flash("Hasła nie są zgodne.", "danger")
|
||||
return redirect(url_for('register'))
|
||||
if User.query.filter_by(username=username).first():
|
||||
flash("Użytkownik o tej nazwie już istnieje.", "danger")
|
||||
return redirect(url_for('register'))
|
||||
hashed_pw = bcrypt.generate_password_hash(password).decode('utf-8')
|
||||
new_user = User(username=username, password=hashed_pw)
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
# Rejestracja operacji w logu audytu
|
||||
log = AuditLog(user_id=new_user.id, operation="register", details="Nowy użytkownik zarejestrowany.")
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
flash("Rejestracja zakończona powodzeniem. Zaloguj się.", "success")
|
||||
return redirect(url_for('login'))
|
||||
return render_template('register.html')
|
||||
|
||||
@app.route('/change_password', methods=['GET', 'POST'])
|
||||
def change_password():
|
||||
if 'logged_in' not in session or not session['logged_in']:
|
||||
flash("Musisz być zalogowany.", "warning")
|
||||
return redirect(url_for('login'))
|
||||
if request.method == 'POST':
|
||||
current_password = request.form.get('current_password')
|
||||
new_password = request.form.get('new_password')
|
||||
confirm = request.form.get('confirm')
|
||||
user = User.query.filter_by(username=session.get('username')).first()
|
||||
if not user or not bcrypt.check_password_hash(user.password, current_password):
|
||||
flash("Błędne aktualne hasło.", "danger")
|
||||
return redirect(url_for('change_password'))
|
||||
if new_password != confirm:
|
||||
flash("Nowe hasła nie są zgodne.", "danger")
|
||||
return redirect(url_for('change_password'))
|
||||
user.password = bcrypt.generate_password_hash(new_password).decode('utf-8')
|
||||
db.session.commit()
|
||||
# Log operacji zmiany hasła
|
||||
log = AuditLog(user_id=user.id, operation="change_password", details="Zmiana hasła.")
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
flash("Hasło zostało zmienione.", "success")
|
||||
return redirect(url_for('dashboard'))
|
||||
return render_template('change_password.html')
|
||||
|
||||
@app.route('/api/cert_details/<int:service_id>', methods=['GET'])
|
||||
def api_cert_details(service_id):
|
||||
svc = MonitoredService.query.get_or_404(service_id)
|
||||
try:
|
||||
proto = svc.protocol.lower().strip().replace(" ", "_")
|
||||
if proto != "https":
|
||||
return jsonify({"error": "Szczegółowe informacje dostępne tylko dla HTTPS."}), 400
|
||||
details = get_cert_details(svc)
|
||||
html_table = format_cert_details_as_table(details)
|
||||
return jsonify({"html": html_table})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/history')
|
||||
def history():
|
||||
history_records = History.query.order_by(History.timestamp.desc()).all()
|
||||
return render_template('history.html', history=history_records)
|
||||
|
||||
@app.route('/preferences', methods=['GET', 'POST'])
|
||||
def preferences():
|
||||
if 'logged_in' not in session or not session['logged_in']:
|
||||
flash("Musisz się zalogować.", "warning")
|
||||
return redirect(url_for('login'))
|
||||
user = User.query.filter_by(username=session.get('username')).first()
|
||||
prefs = UserPreferences.query.filter_by(user_id=user.id).first()
|
||||
if not prefs:
|
||||
prefs = UserPreferences(user_id=user.id)
|
||||
db.session.add(prefs)
|
||||
db.session.commit()
|
||||
if request.method == 'POST':
|
||||
prefs.default_grouping = request.form.get('default_grouping', 'protocol')
|
||||
# Przyjmijmy, że filtrujemy jako JSON – np. {"region": "EU", "certificate_type": "DV"}
|
||||
prefs.filters = request.form.get('filters', '{}')
|
||||
db.session.commit()
|
||||
flash("Preferencje widoku zapisane.", "success")
|
||||
return redirect(url_for('dashboard'))
|
||||
return render_template('preferences.html', prefs=prefs)
|
||||
|
||||
@app.route('/api/cert_chain/<int:service_id>', methods=['GET'])
|
||||
def api_cert_chain(service_id):
|
||||
svc = MonitoredService.query.get_or_404(service_id)
|
||||
try:
|
||||
proto = svc.protocol.lower().strip().replace(" ", "_")
|
||||
if proto != "https":
|
||||
return jsonify({"error": "Łańcuch certyfikatów dostępny tylko dla HTTPS."}), 400
|
||||
html_details = get_cert_chain_html(svc.host, svc.port)
|
||||
return jsonify({"html": html_details})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
###############################################################################
|
||||
# URUCHAMIANIE APLIKACJI (tryb deweloperski)
|
||||
###############################################################################
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = create_app()
|
||||
app.run(debug=True)
|
Reference in New Issue
Block a user