diff --git a/app.py b/app.py index 5893db2..8e753de 100644 --- a/app.py +++ b/app.py @@ -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//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//versions/delete_old/') +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/') +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//') +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/') +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//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(): diff --git a/requirements.txt b/requirements.txt index 4cc7be4..22eb102 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,4 @@ paramiko apscheduler gunicorn waitress -pytz \ No newline at end of file +croniter \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 41a7409..2153e54 100644 --- a/templates/base.html +++ b/templates/base.html @@ -60,6 +60,12 @@ .btn-logout { color: #fff; } + + /* Zmniejszenie rozmiaru czcionki w navbarze */ + .navbar { + font-size: 0.9rem; /* zmniejszony rozmiar czcionki */ + } + {% block extra_css %}{% endblock %} @@ -94,9 +100,16 @@ - -