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
import socket
import ipaddress
import difflib
from croniter import croniter
from tzlocal import get_localzone
@ -97,6 +99,13 @@ class RegexHostEntry(db.Model):
comment = db.Column(db.String(255), nullable=True)
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
def ensure_local_defaults(content):
required_lines = [
@ -534,10 +543,15 @@ def edit_hosts_file(file_id):
flash('File not found or unauthorized', 'danger')
return redirect(url_for('list_hosts_files'))
if request.method == 'POST':
file.title = request.form['title']
file.content = request.form['content']
new_title = request.form['title']
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()
flash('Hosts file updated', 'success')
flash('Hosts file updated and previous version saved.', 'success')
return redirect(url_for('list_hosts_files'))
return render_template('new_edit_hosts_file.html', file=file)
@ -897,23 +911,23 @@ def delete_backup(backup_id):
# -------------------
# EDYCJA LOKALNEGO PLIKU HOSTS
# -------------------
@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']
hostfile.content = new_content
db.session.commit()
flash('Local hosts content updated successfully', 'success')
return render_template('edit_hosts.html', content=hostfile.content)
# @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']
# hostfile.content = new_content
# db.session.commit()
# flash('Local hosts content updated successfully', 'success')
# return render_template('edit_hosts.html', content=hostfile.content)
# -------------------
# DEPLOYMENT DOMYŚLNY DLA UŻYTKOWNIKA
@ -1177,6 +1191,152 @@ def update_host_automation(id):
flash('Ustawienia automatyzacji zostały zaktualizowane.', 'success')
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():

View File

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

View File

@ -60,6 +60,12 @@
.btn-logout {
color: #fff;
}
/* Zmniejszenie rozmiaru czcionki w navbarze */
.navbar {
font-size: 0.9rem; /* zmniejszony rozmiar czcionki */
}
</style>
{% block extra_css %}{% endblock %}
</head>
@ -94,9 +100,16 @@
<li class="nav-item">
<a class="nav-link" href="{{ url_for('clear_server') }}">Wyczyść /etc/hosts</a>
</li>
<!-- Edytuj /etc/hosts -->
<li class="nav-item">
<a class="nav-link" href="{{ url_for('edit_local_hosts') }}">Edytuj /etc/hosts</a>
<!-- Edytuj /etc/hosts z podsekcjami -->
<li class="nav-item dropdown">
<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>
<!-- Sieci CIDR / Regex -->
<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" %}
{% 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 %}
<div class="card mb-4">
<div class="card-header">
@ -24,7 +15,26 @@
</form>
</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">
<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>
{% 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('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('hostfile_versions', hostfile_id=file.id) }}" class="btn btn-sm btn-info">Historia wersji</a>
</td>
</tr>
{% endfor %}

View File

@ -28,8 +28,25 @@
</form>
</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">
<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>
{% 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 %}