feat: Implement backup and restore functionality for licenses; add download and upload routes
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
LICENSES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "licenses.json"))
|
||||
|
||||
|
||||
def load_licenses() -> dict:
|
||||
"""Load licenses.json file and return its content."""
|
||||
try:
|
||||
with open(LICENSES_FILE, "r") as f:
|
||||
return json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
return []
|
||||
|
||||
|
||||
def save_licenses(data: dict) -> bool:
|
||||
"""Save licenses data to licenses.json file."""
|
||||
try:
|
||||
os.makedirs(os.path.dirname(LICENSES_FILE), exist_ok=True)
|
||||
with open(LICENSES_FILE, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error saving licenses: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def export_backup() -> dict:
|
||||
"""Export current licenses.json content."""
|
||||
return load_licenses()
|
||||
|
||||
|
||||
def import_backup(data: dict) -> bool:
|
||||
"""Import and restore licenses from backup data."""
|
||||
try:
|
||||
if not isinstance(data, list):
|
||||
return False
|
||||
save_licenses(data)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error importing backup: {e}")
|
||||
return False
|
||||
+52
-10
@@ -1,9 +1,12 @@
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from flask import Flask, render_template, request, jsonify, flash, redirect, url_for, get_flashed_messages, session
|
||||
from flask import Flask, render_template, request, jsonify, flash, redirect, url_for, get_flashed_messages, session, send_file
|
||||
import verify
|
||||
import backup
|
||||
import pyotp
|
||||
import qrcode
|
||||
import json
|
||||
from io import BytesIO
|
||||
|
||||
def _resolve_template_dir() -> Path:
|
||||
candidates = [
|
||||
@@ -21,7 +24,7 @@ def _resolve_template_dir() -> Path:
|
||||
totp_key = "Hsdfisdf4n34234dfiseLoasjfj3asnnvhxbbfgrzzuewwndcodrweokyn"
|
||||
|
||||
app = Flask(__name__, template_folder=str(_resolve_template_dir()))
|
||||
app.secret_key = "Test123"
|
||||
app.secret_key = "ASDfhbsdfseiufhgildsrfrjg874368546987s6e8468f4s"
|
||||
|
||||
def _read_app_version() -> str:
|
||||
candidates = [
|
||||
@@ -37,7 +40,7 @@ def _read_app_version() -> str:
|
||||
return "unknown"
|
||||
|
||||
def generate_totp_qrcode():
|
||||
uri = pyotp.totp.TOTP(totp_key).provisioning_uri(name='',issuer_name='Key Verification Server')
|
||||
uri = pyotp.totp.TOTP(totp_key).provisioning_uri(name='',issuer_name='Inventarsystem Lizenz Verwaltung')
|
||||
qrcode.make(uri).save("qr.png")
|
||||
|
||||
def check_totp(key):
|
||||
@@ -62,13 +65,6 @@ def validate__information():
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
"""
|
||||
User login route.
|
||||
Authenticates users and redirects to appropriate homepage based on role.
|
||||
|
||||
Returns:
|
||||
flask.Response: Rendered template or redirect
|
||||
"""
|
||||
if 'username' in session:
|
||||
return redirect(url_for('default'))
|
||||
if request.method == 'POST':
|
||||
@@ -126,6 +122,52 @@ def logout():
|
||||
return redirect(url_for('login'))
|
||||
|
||||
|
||||
@app.route('/download_backup', methods=['GET'])
|
||||
def download_backup():
|
||||
if 'username' not in session:
|
||||
return redirect(url_for('login'))
|
||||
|
||||
licenses_data = backup.export_backup()
|
||||
json_str = json.dumps(licenses_data, indent=2)
|
||||
return send_file(
|
||||
BytesIO(json_str.encode('utf-8')),
|
||||
mimetype='application/json',
|
||||
as_attachment=True,
|
||||
download_name='licenses_backup.json'
|
||||
)
|
||||
|
||||
|
||||
@app.route('/upload_backup', methods=['POST'])
|
||||
def upload_backup():
|
||||
if 'username' not in session:
|
||||
return redirect(url_for('login'))
|
||||
|
||||
if 'file' not in request.files:
|
||||
flash('No file provided', 'error')
|
||||
return redirect(url_for('default'))
|
||||
|
||||
file = request.files['file']
|
||||
|
||||
if file.filename == '':
|
||||
flash('No file selected', 'error')
|
||||
return redirect(url_for('default'))
|
||||
|
||||
try:
|
||||
content = file.read().decode('utf-8')
|
||||
data = json.loads(content)
|
||||
|
||||
if backup.import_backup(data):
|
||||
flash('Licenses backup restored successfully', 'success')
|
||||
else:
|
||||
flash('Failed to restore backup - invalid format', 'error')
|
||||
except json.JSONDecodeError:
|
||||
flash('Invalid JSON file', 'error')
|
||||
except Exception as e:
|
||||
flash(f'Error uploading backup: {str(e)}', 'error')
|
||||
|
||||
return redirect(url_for('default'))
|
||||
|
||||
|
||||
def main():
|
||||
app.run(host="0.0.0.0", port=5000, debug=False)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<title>{% block title %}Admin Inventarsystem{% endblock %}</title>
|
||||
<title>{% block title %}Inventarsystem Lizenz Verwaltung{% endblock %}</title>
|
||||
{% block head %}
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||||
@@ -95,7 +95,7 @@
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{{ url_for('default') }}">Inventarsystem</a>
|
||||
<a class="navbar-brand" href="{{ url_for('default') }}">Inventarsystem Lizenz Verwaltung</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
@@ -8,9 +8,13 @@
|
||||
<h2 class="mb-1">All Keys</h2>
|
||||
<p class="text-muted mb-0">Overview of license keys and bound HWIDs.</p>
|
||||
</div>
|
||||
<form method="POST" action="{{ url_for('generate_new') }}">
|
||||
<button type="submit" class="btn btn-success">Generate New Key</button>
|
||||
</form>
|
||||
<div class="d-flex gap-2">
|
||||
<form method="POST" action="{{ url_for('generate_new') }}" style="display: inline;">
|
||||
<button type="submit" class="btn btn-success">Generate New Key</button>
|
||||
</form>
|
||||
<a href="{{ url_for('download_backup') }}" class="btn btn-info">Download Backup</a>
|
||||
<button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#uploadModal">Upload Backup</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
@@ -50,4 +54,32 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Upload Backup Modal -->
|
||||
<div class="modal fade" id="uploadModal" tabindex="-1" aria-labelledby="uploadModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="uploadModalLabel">Upload Licenses Backup</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form method="POST" action="{{ url_for('upload_backup') }}" enctype="multipart/form-data">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="backupFile" class="form-label">Select JSON backup file</label>
|
||||
<input class="form-control" type="file" id="backupFile" name="file" accept=".json" required>
|
||||
<small class="text-muted">Select a previously downloaded licenses_backup.json file</small>
|
||||
</div>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<strong>Warning:</strong> Uploading a backup will replace all current licenses. This action cannot be undone.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure you want to restore from this backup? Current licenses will be replaced.');">Restore</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -13,5 +13,10 @@
|
||||
"user_id": "0003",
|
||||
"license_key": "H02JSHkh9qtnzU8D5yBVcyej98AU-R6lVhwyYfHA1uM",
|
||||
"hwid_uuid": ""
|
||||
},
|
||||
{
|
||||
"user_id": "0004",
|
||||
"license_key": "H02JSHkh9qtnzU8D5yBVcyej98AU-R6lVhwyYfHA1pM",
|
||||
"hwid_uuid": "Test"
|
||||
}
|
||||
]
|
||||
@@ -40,7 +40,7 @@ if settings_path.is_file():
|
||||
version_file.write_text(f"{version}\n", encoding="utf-8")
|
||||
print(f"Prepared {version_file} with version: {version}")
|
||||
PY
|
||||
python -m nuitka --standalone --follow-imports --include-data-dir=Code/templates=templates --include-data-dir=Code/static=static --include-data-file=licenses.json=licenses.json --assume-yes-for-downloads --output-dir=build --remove-output Code/main.py
|
||||
python -m nuitka --standalone --follow-imports --include-data-dir=Code/templates=templates --include-data-dir=Code/static=static --include-data-file=licenses.json=licenses.json --assume-yes-for-downloads --output-dir=Inventarsystem_Lizenz_Verwaltung --remove-output Code/main.py
|
||||
if [[ -x "./build/main.dist/main.bin" ]]; then
|
||||
./build/main.dist/main.bin
|
||||
else
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
curl -i -X POST "http://127.0.0.1:5000/validate__information" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"license":"npEDI1JVhyisQ2dg-fWTTAn5H1hg3eS80Zc-4IptWIU=","hwid":""}'
|
||||
-d '{"license":"npEDI1JVhyisQ2dg-fWTTAn5H1hg3eS80Zc-4IptWIU=","hwid":""}'
|
||||
|
||||
|
||||
|
||||
curl -i -X POST "http://127.0.0.1:5000/validate__information" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"license":"npEDI1JVhyisQ2dg-fWTTAn5H1hg3eS80Zc-4IptWIU=","hwid":"0c27aefd7107396933c7363efa38fb6c297aa3db119c1597952c05d947f4f27c"}'
|
||||
|
||||
|
||||
|
||||
curl -i -X POST "http://127.0.0.1:5000/validate__information" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"license":"npEDI1JVhyisQ2dg-fWTTAn5H1hg3eS80Zc-4IptWIU=","hwid":"0c27aefd7107396933c7363efa38fb6c297aa3db119c1597952c05d947f4f37c"}'
|
||||
Reference in New Issue
Block a user