fixy i usprwnienia

This commit is contained in:
Mateusz Gruszczyński 2025-03-06 14:12:48 +01:00
parent c4b753d4bd
commit cae5ed787d
9 changed files with 329 additions and 35 deletions

200
app.py
View File

@ -7,6 +7,8 @@ from datetime import datetime, timezone, timedelta
from io import StringIO from io import StringIO
import socket import socket
import ipaddress import ipaddress
import difflib
from croniter import croniter from croniter import croniter
from tzlocal import get_localzone from tzlocal import get_localzone
@ -97,6 +99,13 @@ class RegexHostEntry(db.Model):
comment = db.Column(db.String(255), nullable=True) comment = db.Column(db.String(255), nullable=True)
user = db.relationship('User', backref='regex_entries') user = db.relationship('User', backref='regex_entries')
class HostFileVersion(db.Model):
id = db.Column(db.Integer, primary_key=True)
hostfile_id = db.Column(db.Integer, db.ForeignKey('host_file.id'), nullable=False)
content = db.Column(db.Text, nullable=False)
timestamp = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
hostfile = db.relationship('HostFile', backref=db.backref('versions', lazy=True))
# Funkcje pomocnicze # Funkcje pomocnicze
def ensure_local_defaults(content): def ensure_local_defaults(content):
required_lines = [ required_lines = [
@ -534,10 +543,15 @@ def edit_hosts_file(file_id):
flash('File not found or unauthorized', 'danger') flash('File not found or unauthorized', 'danger')
return redirect(url_for('list_hosts_files')) return redirect(url_for('list_hosts_files'))
if request.method == 'POST': if request.method == 'POST':
file.title = request.form['title'] new_title = request.form['title']
file.content = request.form['content'] new_content = request.form['content']
if file.content != new_content:
version = HostFileVersion(hostfile_id=file.id, content=file.content)
db.session.add(version)
file.title = new_title
file.content = new_content
db.session.commit() db.session.commit()
flash('Hosts file updated', 'success') flash('Hosts file updated and previous version saved.', 'success')
return redirect(url_for('list_hosts_files')) return redirect(url_for('list_hosts_files'))
return render_template('new_edit_hosts_file.html', file=file) return render_template('new_edit_hosts_file.html', file=file)
@ -897,23 +911,23 @@ def delete_backup(backup_id):
# ------------------- # -------------------
# EDYCJA LOKALNEGO PLIKU HOSTS # EDYCJA LOKALNEGO PLIKU HOSTS
# ------------------- # -------------------
@app.route('/edit-local-hosts', methods=['GET', 'POST'], endpoint='edit_local_hosts') # @app.route('/edit-local-hosts', methods=['GET', 'POST'], endpoint='edit_local_hosts')
def edit_local_hosts(): # def edit_local_hosts():
if 'user_id' not in session: # if 'user_id' not in session:
return redirect(url_for('login')) # return redirect(url_for('login'))
user_id = session['user_id'] # user_id = session['user_id']
hostfile = HostFile.query.filter_by(user_id=user_id, title="Default Hosts").first() # hostfile = HostFile.query.filter_by(user_id=user_id, title="Default Hosts").first()
if not hostfile: # if not hostfile:
default_content = "# This is a sample hosts file.\n127.0.0.1 localhost\n" # default_content = "# This is a sample hosts file.\n127.0.0.1 localhost\n"
hostfile = HostFile(user_id=user_id, title="Default Hosts", content=default_content) # hostfile = HostFile(user_id=user_id, title="Default Hosts", content=default_content)
db.session.add(hostfile) # db.session.add(hostfile)
db.session.commit() # db.session.commit()
if request.method == 'POST': # if request.method == 'POST':
new_content = request.form['hosts_content'] # new_content = request.form['hosts_content']
hostfile.content = new_content # hostfile.content = new_content
db.session.commit() # db.session.commit()
flash('Local hosts content updated successfully', 'success') # flash('Local hosts content updated successfully', 'success')
return render_template('edit_hosts.html', content=hostfile.content) # return render_template('edit_hosts.html', content=hostfile.content)
# ------------------- # -------------------
# DEPLOYMENT DOMYŚLNY DLA UŻYTKOWNIKA # DEPLOYMENT DOMYŚLNY DLA UŻYTKOWNIKA
@ -1177,6 +1191,152 @@ def update_host_automation(id):
flash('Ustawienia automatyzacji zostały zaktualizowane.', 'success') flash('Ustawienia automatyzacji zostały zaktualizowane.', 'success')
return redirect(url_for('server_list')) return redirect(url_for('server_list'))
@app.route('/edit-local-hosts', methods=['GET', 'POST'], endpoint='edit_local_hosts')
def edit_local_hosts():
if 'user_id' not in session:
return redirect(url_for('login'))
user_id = session['user_id']
hostfile = HostFile.query.filter_by(user_id=user_id, title="Default Hosts").first()
if not hostfile:
default_content = "# This is a sample hosts file.\n127.0.0.1 localhost\n"
hostfile = HostFile(user_id=user_id, title="Default Hosts", content=default_content)
db.session.add(hostfile)
db.session.commit()
if request.method == 'POST':
new_content = request.form['hosts_content']
# Zapisz obecną wersję do historii przed zmianą
version = HostFileVersion(hostfile_id=hostfile.id, content=hostfile.content)
db.session.add(version)
# Aktualizacja treści
hostfile.content = new_content
db.session.commit()
flash('Local hosts content updated successfully and previous version saved.', 'success')
return render_template('edit_hosts.html', content=hostfile.content, hostfile=hostfile)
@app.route('/hostfile/<int:hostfile_id>/versions', methods=['GET', 'POST'])
def hostfile_versions(hostfile_id):
if 'user_id' not in session:
return redirect(url_for('login'))
hostfile = HostFile.query.get(hostfile_id)
if not hostfile or hostfile.user_id != session['user_id']:
flash('Hostfile not found or unauthorized', 'danger')
return redirect(url_for('dashboard'))
if request.method == 'POST':
# Masowe usuwanie lista zaznaczonych wersji
selected_ids = request.form.getlist('selected_versions')
for version_id in selected_ids:
version = HostFileVersion.query.get(version_id)
if version and version.hostfile.user_id == session['user_id']:
db.session.delete(version)
db.session.commit()
flash('Wybrane wersje zostały usunięte.', 'info')
return redirect(url_for('hostfile_versions', hostfile_id=hostfile_id))
versions = HostFileVersion.query.filter_by(hostfile_id=hostfile.id)\
.order_by(HostFileVersion.timestamp.desc()).all()
return render_template('hostfile_versions.html', hostfile=hostfile, versions=versions)
@app.route('/hostfile/<int:hostfile_id>/versions/delete_old/<int:days>')
def delete_old_versions(hostfile_id, days):
if 'user_id' not in session:
return redirect(url_for('login'))
hostfile = HostFile.query.get(hostfile_id)
if not hostfile or hostfile.user_id != session['user_id']:
flash('Hostfile not found or unauthorized', 'danger')
return redirect(url_for('dashboard'))
cutoff = datetime.now(timezone.utc) - timedelta(days=days)
old_versions = HostFileVersion.query.filter(HostFileVersion.hostfile_id == hostfile_id,
HostFileVersion.timestamp < cutoff).all()
for version in old_versions:
db.session.delete(version)
db.session.commit()
flash(f'Usunięto wersje starsze niż {days} dni.', 'info')
return redirect(url_for('hostfile_versions', hostfile_id=hostfile_id))
@app.route('/hostfile/diff_current/<int:hostfile_id>')
def diff_current_hostfile(hostfile_id):
if 'user_id' not in session:
return redirect(url_for('login'))
hostfile = HostFile.query.get(hostfile_id)
if not hostfile or hostfile.user_id != session['user_id']:
flash('Hostfile not found or unauthorized.', 'danger')
return redirect(url_for('dashboard'))
# Używamy len() zamiast |length
if not hostfile.versions or len(hostfile.versions) == 0:
flash('Brak zapisanej historii wersji do porównania.', 'warning')
return redirect(url_for('hostfile_versions', hostfile_id=hostfile_id))
latest_version = hostfile.versions[0]
differ = difflib.HtmlDiff(wrapcolumn=80)
diff_html = differ.make_table(
latest_version.content.splitlines(),
hostfile.content.splitlines(),
fromdesc=f"Najnowsza wersja historii (ID: {latest_version.id}, {latest_version.timestamp.strftime('%Y-%m-%d %H:%M:%S')})",
todesc="Aktualna zawartość",
context=True,
numlines=3
)
return render_template('diff_versions.html', diff_html=diff_html, hostfile_id=hostfile.id)
@app.route('/hostfile/diff/<int:version1_id>/<int:version2_id>')
def diff_hostfile_versions(version1_id, version2_id):
if 'user_id' not in session:
return redirect(url_for('login'))
version1 = HostFileVersion.query.get(version1_id)
version2 = HostFileVersion.query.get(version2_id)
if not version1 or not version2 or version1.hostfile.user_id != session['user_id'] or version2.hostfile.user_id != session['user_id']:
flash('Wersje nie znalezione lub brak uprawnień.', 'danger')
return redirect(url_for('dashboard'))
differ = difflib.HtmlDiff(wrapcolumn=80)
diff_html = differ.make_table(
version1.content.splitlines(),
version2.content.splitlines(),
fromdesc=f"Wersja {version1.id} - {version1.timestamp.strftime('%Y-%m-%d %H:%M:%S')}",
todesc=f"Wersja {version2.id} - {version2.timestamp.strftime('%Y-%m-%d %H:%M:%S')}",
context=True,
numlines=3
)
# Przekazujemy hostfile_id, zakładając, że obie wersje należą do tego samego pliku
hostfile_id = version1.hostfile_id
return render_template('diff_versions.html', diff_html=diff_html, hostfile_id=hostfile_id)
@app.route('/hostfile/version/<int:version_id>')
def view_hostfile_version(version_id):
if 'user_id' not in session:
return redirect(url_for('login'))
version = HostFileVersion.query.get(version_id)
if not version or version.hostfile.user_id != session['user_id']:
flash('Version not found or unauthorized', 'danger')
return redirect(url_for('dashboard'))
return render_template('view_hostfile_version.html', version=version)
@app.route('/hostfile/version/<int:version_id>/restore')
def restore_hostfile_version(version_id):
if 'user_id' not in session:
return redirect(url_for('login'))
version = HostFileVersion.query.get(version_id)
if not version or version.hostfile.user_id != session['user_id']:
flash('Version not found or unauthorized', 'danger')
return redirect(url_for('dashboard'))
# Przywróć zawartość wersji do głównego hostfile
hostfile = version.hostfile
hostfile.content = version.content
db.session.commit()
flash('Version restored successfully.', 'success')
return redirect(url_for('edit_local_hosts'))
@app.route('/hostfile/versions')
def default_hostfile_versions():
if 'user_id' not in session:
return redirect(url_for('login'))
# Zakładamy, że domyślny plik hosts ma tytuł "Default Hosts"
hostfile = HostFile.query.filter_by(user_id=session['user_id'], title="Default Hosts").first()
if not hostfile:
flash("Default Hosts file not found.", "danger")
return redirect(url_for('edit_local_hosts'))
return redirect(url_for('hostfile_versions', hostfile_id=hostfile.id))
def scheduled_deployments(): def scheduled_deployments():

View File

@ -5,4 +5,4 @@ paramiko
apscheduler apscheduler
gunicorn gunicorn
waitress waitress
pytz croniter

View File

@ -60,6 +60,12 @@
.btn-logout { .btn-logout {
color: #fff; color: #fff;
} }
/* Zmniejszenie rozmiaru czcionki w navbarze */
.navbar {
font-size: 0.9rem; /* zmniejszony rozmiar czcionki */
}
</style> </style>
{% block extra_css %}{% endblock %} {% block extra_css %}{% endblock %}
</head> </head>
@ -94,9 +100,16 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('clear_server') }}">Wyczyść /etc/hosts</a> <a class="nav-link" href="{{ url_for('clear_server') }}">Wyczyść /etc/hosts</a>
</li> </li>
<!-- Edytuj /etc/hosts --> <!-- Edytuj /etc/hosts z podsekcjami -->
<li class="nav-item"> <li class="nav-item dropdown">
<a class="nav-link" href="{{ url_for('edit_local_hosts') }}">Edytuj /etc/hosts</a> <a class="nav-link dropdown-toggle" href="#" id="editHostsDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Edytj domyśny /etc/hosts
</a>
<ul class="dropdown-menu" aria-labelledby="editHostsDropdown">
<li><a class="dropdown-item" href="{{ url_for('edit_local_hosts') }}">Edytuj /etc/hosts</a></li>
<li><a class="dropdown-item" href="{{ url_for('default_hostfile_versions') }}">Historia wersji</a></li>
<!-- Opcjonalnie: dodaj inne akcje, np. diff wersji -->
</ul>
</li> </li>
<!-- Sieci CIDR / Regex --> <!-- Sieci CIDR / Regex -->
<li class="nav-item dropdown"> <li class="nav-item dropdown">

View File

@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block title %}Porównanie wersji{% endblock %}
{% block extra_css %}
{{ super() }}
<style>
/* Przykładowe style dla ciemnego motywu w diff */
table.diff {font-family: Courier; border: medium; }
.diff_header {background-color: #444; color: #fff; }
td.diff_header {text-align: center;}
.diff_next {background-color: #333; }
.diff_add {background-color: #008800; color: #fff; }
.diff_chg {background-color: #4444aa; color: #fff; }
.diff_sub {background-color: #aa0000; color: #fff; }
</style>
{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h2>Porównanie wersji</h2>
</div>
<div class="card-body">
{{ diff_html|safe }}
<hr>
<a href="{{ url_for('hostfile_versions', hostfile_id=hostfile_id) }}" class="btn btn-secondary">Powrót do historii</a>
</div>
</div>
{% endblock %}

View File

@ -1,14 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Edytuj lokalny Hosts - /etc/hosts Manager{% endblock %} {% block title %}Edytuj lokalny Hosts - /etc/hosts Manager{% endblock %}
{% block extra_css %}
{{ super() }}
<style>
.tooltip-inner {
max-width: 300px;
text-align: left;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header"> <div class="card-header">
@ -24,7 +15,26 @@
</form> </form>
</div> </div>
</div> </div>
{% if hostfile %}
<div class="card mb-4">
<div class="card-header">
<h3>Wersje /etc/hosts File</h3>
</div>
<div class="card-body">
<p>Przeglądaj historię zmian, porównuj aktualną zawartość z najnowszą wersją oraz usuwaj stare wersje.</p>
<a href="{{ url_for('hostfile_versions', hostfile_id=hostfile.id) }}" class="btn btn-info">Historia wersji</a>
<a href="{{ url_for('delete_old_versions', hostfile_id=hostfile.id, days=30) }}" class="btn btn-secondary" onclick="return confirm('Usuń wersje starsze niż 30 dni?');">Usuń wersje starsze niż 30 dni</a>
{% if hostfile.versions|length > 0 %}
<a href="{{ url_for('diff_current_hostfile', hostfile_id=hostfile.id) }}" class="btn btn-warning" onclick="return confirm('Porównaj aktualną zawartość z najnowszą wersją zapisanej historii?');">Diff aktualny vs. najnowsza</a>
{% else %}
<span class="text-muted">Brak zapisanych wersji do diff.</span>
{% endif %}
</div>
</div>
{% endif %}
<div class="mt-3 text-center"> <div class="mt-3 text-center">
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Przejdź do pulpitu</a> <a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Przejdź do dashboardu</a>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,52 @@
{% extends "base.html" %}
{% block title %}Historia wersji hosts{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h2>Historia wersji dla: {{ hostfile.title }}</h2>
</div>
<div class="card-body">
<form method="post" id="bulkDeleteForm">
<table class="table table-striped">
<thead>
<tr>
<th><input type="checkbox" id="select-all"></th>
<th>Data</th>
<th>Fragment treści</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
{% set latest_id = versions[0].id if versions|length > 0 else None %}
{% for version in versions %}
<tr>
<td><input type="checkbox" name="selected_versions" value="{{ version.id }}"></td>
<td>{{ version.timestamp.strftime("%Y-%m-%d %H:%M:%S") }}</td>
<td>{{ version.content[:50] }}{% if version.content|length > 50 %}...{% endif %}</td>
<td>
<a href="{{ url_for('view_hostfile_version', version_id=version.id) }}" class="btn btn-sm btn-info">Podgląd</a>
<a href="{{ url_for('restore_hostfile_version', version_id=version.id) }}" class="btn btn-sm btn-success" onclick="return confirm('Przywrócić tę wersję?');">Przywróć</a>
{% if latest_id and version.id != latest_id %}
<a href="{{ url_for('diff_hostfile_versions', version1_id=version.id, version2_id=latest_id) }}" class="btn btn-sm btn-warning">Diff z najnowszą</a>
{% else %}
<span class="text-muted">Brak diff</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<button type="submit" class="btn btn-danger" onclick="return confirm('Czy na pewno usunąć zaznaczone wersje?');">Usuń zaznaczone</button>
<a href="{{ url_for('delete_old_versions', hostfile_id=hostfile.id, days=30) }}" class="btn btn-secondary" onclick="return confirm('Usuń wersje starsze niż 30 dni?');">Usuń wersje starsze niż 30 dni</a>
</form>
</div>
</div>
<script>
document.getElementById('select-all').addEventListener('change', function(){
var checkboxes = document.querySelectorAll('input[name="selected_versions"]');
checkboxes.forEach(function(checkbox) {
checkbox.checked = document.getElementById('select-all').checked;
});
});
</script>
{% endblock %}

View File

@ -31,6 +31,7 @@
<a href="{{ url_for('edit_hosts_file', file_id=file.id) }}" class="btn btn-sm btn-warning">Edytuj</a> <a href="{{ url_for('edit_hosts_file', file_id=file.id) }}" class="btn btn-sm btn-warning">Edytuj</a>
<a href="{{ url_for('delete_hosts_file', file_id=file.id) }}" class="btn btn-sm btn-danger" onclick="return confirm('Czy na pewno usunąć plik?');">Usuń</a> <a href="{{ url_for('delete_hosts_file', file_id=file.id) }}" class="btn btn-sm btn-danger" onclick="return confirm('Czy na pewno usunąć plik?');">Usuń</a>
<a href="{{ url_for('deploy_hosts_file', file_id=file.id) }}" class="btn btn-sm btn-success">Deploy</a> <a href="{{ url_for('deploy_hosts_file', file_id=file.id) }}" class="btn btn-sm btn-success">Deploy</a>
<a href="{{ url_for('hostfile_versions', hostfile_id=file.id) }}" class="btn btn-sm btn-info">Historia wersji</a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -28,8 +28,25 @@
</form> </form>
</div> </div>
</div> </div>
{% if file %}
<!-- Sekcja zarządzania wersjami -->
<div class="card mb-4">
<div class="card-header">
<h3>Wersje pliku /etc/hosts</h3>
</div>
<div class="card-body">
<p>Przeglądaj historię zmian, porównaj aktualną zawartość z najnowszą wersją zapisanej historii lub usuń stare wersje.</p>
<a href="{{ url_for('hostfile_versions', hostfile_id=file.id) }}" class="btn btn-info">Historia wersji</a>
<a href="{{ url_for('delete_old_versions', hostfile_id=file.id, days=30) }}" class="btn btn-secondary" onclick="return confirm('Usuń wersje starsze niż 30 dni?');">Usuń wersje starsze niż 30 dni</a>
{% if file.versions|length > 0 %}
<a href="{{ url_for('diff_current_hostfile', hostfile_id=file.id) }}" class="btn btn-warning" onclick="return confirm('Porównaj aktualną zawartość z najnowszą wersją zapisanej historii?');">Diff aktualny vs. ostatnia kopia</a>
{% endif %}
</div>
</div>
{% endif %}
<div class="mt-3 text-center"> <div class="mt-3 text-center">
<a href="{{ url_for('list_hosts_files') }}" class="btn btn-secondary">Lista Hosts Files</a> <a href="{{ url_for('list_hosts_files') }}" class="btn btn-secondary">Lista Hosts Files</a>
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Przejdź do pulpitu</a>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block title %}Podgląd wersji{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h2>Podgląd wersji z: {{ version.timestamp.strftime("%Y-%m-%d %H:%M:%S") }}</h2>
</div>
<div class="card-body">
<pre>{{ version.content }}</pre>
<a href="{{ url_for('hostfile_versions', hostfile_id=version.hostfile_id) }}" class="btn btn-secondary">Powrót do historii</a>
<a href="{{ url_for('restore_hostfile_version', version_id=version.id) }}" class="btn btn-success" onclick="return confirm('Przywrócić tę wersję?');">Przywróć tę wersję</a>
</div>
</div>
{% endblock %}