Backupfunktion erweitert:
- Versionsinfo im Archiv - Verzeichnis anpassbar - API-Call zum Erstellen von Verzeichnissen Typos in definitions.py angepasst write_admin_setting Funktion hinzugefügt
This commit is contained in:
parent
681ad9d3fe
commit
e59e558a6e
142
lib/admin.py
142
lib/admin.py
@ -611,26 +611,21 @@ Dies kann nicht rückgängig gemacht werden!''')
|
||||
ui.markdown("**Administrationsbenutzer:**")
|
||||
with ui.grid(columns=2):
|
||||
def save_admin_settings():
|
||||
output_dict = { }
|
||||
output_dict["admin_user"] = admin_user.value
|
||||
write_adminsetting("admin_user", admin_user.value)
|
||||
if admin_password.value != "":
|
||||
output_dict["admin_password"] = hash_password(admin_password.value)
|
||||
write_adminsetting("admin_password", hash_password(admin_password.value))
|
||||
else:
|
||||
output_dict["admin_password"] = data["admin_password"]
|
||||
output_dict["port"] = port.value
|
||||
output_dict["secret"] = secret
|
||||
output_dict["touchscreen"] = touchscreen_switch.value
|
||||
output_dict["times_on_touchscreen"] = timestamp_switch.value
|
||||
output_dict["photos_on_touchscreen"] = photo_switch.value
|
||||
output_dict["picture_height"] = picture_height_input.value
|
||||
output_dict["button_height"] = button_height_input.value
|
||||
output_dict["user_notes"] = notes_switch.value
|
||||
output_dict["holidays"] = data["holidays"]
|
||||
output_dict["version"] = app_version
|
||||
write_adminsetting("admin_password", data["admin_password"])
|
||||
write_adminsetting("port", port.value)
|
||||
write_adminsetting("secret", secret)
|
||||
write_adminsetting("touchscreen", touchscreen_switch.value)
|
||||
write_adminsetting("times_on_touchscreen", timestamp_switch.value)
|
||||
write_adminsetting("photos_on_touchscreen", photo_switch.value)
|
||||
write_adminsetting("picture_height", picture_height_input.value)
|
||||
write_adminsetting("button_height", button_height_input.value)
|
||||
write_adminsetting("user_notes", notes_switch.value)
|
||||
write_adminsetting("holidays", data["holidays"])
|
||||
|
||||
json_dict = json.dumps(output_dict, indent=4)
|
||||
with open(os.path.join(scriptpath, usersettingsfilename), "w") as outputfile:
|
||||
outputfile.write(json_dict)
|
||||
if int(old_port) != int(port.value):
|
||||
with ui.dialog() as dialog, ui.card():
|
||||
ui.markdown("Damit die Porteinstellungen wirksam werden, muss der Server neu gestartet werden.")
|
||||
@ -1247,10 +1242,49 @@ Dies kann nicht rückgängig gemacht werden!''')
|
||||
user_selection_changed()
|
||||
with ui.tab_panel(backups):
|
||||
|
||||
ui.markdown('**Backups**')
|
||||
try:
|
||||
backupfolder = load_adminsettings()["backup_folder"]
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
api_key = load_adminsettings()["backup_api_key"]
|
||||
except:
|
||||
api_key = ""
|
||||
|
||||
ui.label("Backupeinstellungen").classes('font-bold')
|
||||
with ui.grid(columns='auto auto auto'):
|
||||
ui.markdown("Backupordner:")
|
||||
backupfolder_input = ui.input(value=backupfolder).props(f"size={len(backupfolder)}")
|
||||
def save_new_folder_name():
|
||||
if os.path.exists(backupfolder_input.value):
|
||||
write_adminsetting("backup_folder", backupfolder_input.value)
|
||||
ui.notify("Neuen Pfad gespeichert")
|
||||
else:
|
||||
with ui.dialog() as dialog, ui.card():
|
||||
ui.label("Der Pfad")
|
||||
ui.label(backupfolder_input.value)
|
||||
ui.label("exisitiert nicht und kann daher nicht verwendet werden.")
|
||||
ui.button("OK", on_click=dialog.close)
|
||||
dialog.open()
|
||||
ui.button("Speichern", on_click=save_new_folder_name).tooltip("Hiermit können Sie das Backupverzeichnis ändeern")
|
||||
|
||||
ui.markdown("API-Schlüssel:")
|
||||
backup_api_key_input = ui.input(value=api_key).tooltip("Hier den API-Schlüssel eintragen, der für Backuperzeugung mittels API-Aufruf verwendet werden soll.")
|
||||
def new_backup_api_key():
|
||||
backup_api_key_input.value = hashlib.shake_256(bytes(f"{backupfolder}-{datetime.datetime.now().timestamp()}", 'utf-8')).hexdigest(20)
|
||||
def save_new_api_key():
|
||||
write_adminsetting("backup_api_key", backup_api_key_input.value)
|
||||
with ui.grid(columns=2):
|
||||
ui.button("Neu", on_click=new_backup_api_key).tooltip("Hiermit können Sie einen neuen zufälligen API-Schlüssel erstellen.")
|
||||
ui.button("Speichern", on_click=save_new_api_key).tooltip("Neuen API-Schlüssel speichern. Der alte API-Schlüssel ist damit sofort ungültig.")
|
||||
|
||||
ui.label(f"Der Adresse für den API-Aufruf lautet /api/backup/[API-Schlüssel]").classes('col-span-3')
|
||||
|
||||
ui.separator()
|
||||
|
||||
ui.markdown('**Backups**')
|
||||
date_format = '%Y-%m-%d_%H-%M'
|
||||
searchpath = os.path.join(scriptpath, backupfolder)
|
||||
searchpath = backupfolder
|
||||
|
||||
@ui.refreshable
|
||||
def backup_list():
|
||||
@ -1259,20 +1293,32 @@ Dies kann nicht rückgängig gemacht werden!''')
|
||||
os.makedirs(os.path.join(searchpath))
|
||||
|
||||
backup_files = []
|
||||
file_and_size = []
|
||||
with ui.grid(columns='auto auto auto auto auto'):
|
||||
file_info = []
|
||||
with ui.grid(columns='auto auto auto auto auto auto'):
|
||||
|
||||
ui.label("Backupzeitpunkt/Dateiname")
|
||||
ui.label("Backupgröße")
|
||||
ui.label("Programmversion")
|
||||
|
||||
for i in range(0,3):
|
||||
ui.space()
|
||||
|
||||
for file in os.listdir(searchpath):
|
||||
if file.endswith(".zip"):
|
||||
file_and_size = [file, os.path.getsize(os.path.join(searchpath, file))]
|
||||
backup_files.append(file_and_size)
|
||||
with zipfile.ZipFile(os.path.join(searchpath, file)) as current_archive:
|
||||
try:
|
||||
current_version = current_archive.read("app_version.txt").decode('utf-8')
|
||||
except KeyError:
|
||||
current_version = "-"
|
||||
file_info = [file, os.path.getsize(os.path.join(searchpath, file)), current_version]
|
||||
backup_files.append(file_info)
|
||||
backup_files.sort()
|
||||
backup_files.reverse()
|
||||
|
||||
if len(backup_files) == 0:
|
||||
ui.label("Keine Backups vorhanden")
|
||||
|
||||
for file, size in backup_files:
|
||||
for file, size, version in backup_files:
|
||||
date_string = file[0:-4]
|
||||
try:
|
||||
date_string_dt = datetime.datetime.strptime(date_string, date_format)
|
||||
@ -1281,6 +1327,7 @@ Dies kann nicht rückgängig gemacht werden!''')
|
||||
button_string = date_string
|
||||
ui.markdown(button_string)
|
||||
ui.markdown(f'{round(size/1_000_000,2)} MB')
|
||||
ui.markdown(version)
|
||||
ui.button(icon='download', on_click=lambda file=date_string: ui.download.file(
|
||||
os.path.join(scriptpath, backupfolder, f'{file}.zip'))).tooltip(
|
||||
"Backup herunterladen")
|
||||
@ -1333,8 +1380,7 @@ Dies kann nicht rückgängig gemacht werden!''')
|
||||
async def make_backup():
|
||||
n = ui.notification("Backup wird erzeugt...")
|
||||
compress = zipfile.ZIP_DEFLATED
|
||||
filename = os.path.join(scriptpath, backupfolder,
|
||||
datetime.datetime.now().strftime(date_format) + '.zip')
|
||||
filename = os.path.join(searchpath, datetime.datetime.now().strftime(date_format) + '.zip')
|
||||
folder = userfolder
|
||||
with zipfile.ZipFile(filename, 'w', compress) as target:
|
||||
for root, dirs, files in os.walk(folder):
|
||||
@ -1342,6 +1388,7 @@ Dies kann nicht rückgängig gemacht werden!''')
|
||||
add = os.path.join(root, file)
|
||||
target.write(add)
|
||||
target.write(usersettingsfilename)
|
||||
target.writestr("app_version.txt", data=app_version)
|
||||
backup_list.refresh()
|
||||
n.dismiss()
|
||||
ui.notify("Backup erstellt")
|
||||
@ -1361,16 +1408,51 @@ Dies kann nicht rückgängig gemacht werden!''')
|
||||
if upload:
|
||||
|
||||
content = e.content.read()
|
||||
with open(os.path.join(searchpath, filename), 'wb') as output:
|
||||
temp_file = os.path.join(searchpath, f"temp-{filename}")
|
||||
with open(temp_file, 'wb') as output:
|
||||
output.write(content)
|
||||
with zipfile.ZipFile(temp_file) as temporary_file:
|
||||
try:
|
||||
version_in_file = temporary_file.read("app_version.txt").decode('utf-8')
|
||||
except KeyError:
|
||||
version_in_file = ""
|
||||
if version_in_file == app_version:
|
||||
os.rename(temp_file, os.path.join(searchpath, filename))
|
||||
ui.notify("Datei hochgeladen")
|
||||
backup_list.refresh()
|
||||
uploader.reset()
|
||||
zip_upload.reset()
|
||||
else:
|
||||
with ui.dialog() as dialog, ui.card():
|
||||
if version_in_file == "":
|
||||
ui.label("Es wurden keine gültigen Versionsdaten in der Datei gefunden.")
|
||||
ui.label("Sind sie sicher, dass Sie diese Datei verwenden möchten?").classes('font-bold')
|
||||
else:
|
||||
ui.label(f"Die Versionsdaten des Backups zeigen an, dass diese mit der Version {version_in_file} erstellt wurden. Die aktuell verwendete Version ist {app_version}. Ggf. sind die Backupdaten inkompatibel.")
|
||||
ui.label("Sind Sie sicher, dass Sie diese Daten verwenden möchten?").classes('font-bold')
|
||||
go_check = ui.checkbox("Ich bin mir sicher.")
|
||||
with ui.grid(columns=2):
|
||||
def go_action():
|
||||
os.rename(temp_file, os.path.join(searchpath, filename))
|
||||
ui.notify("Datei hochgeladen")
|
||||
backup_list.refresh()
|
||||
zip_upload.reset()
|
||||
dialog.close()
|
||||
def abort_action():
|
||||
os.remove(temp_file)
|
||||
ui.notify("Temporäre Datei gelöscht")
|
||||
zip_upload.reset()
|
||||
dialog.close()
|
||||
ui.button("Ja", on_click=go_action).bind_enabled_from(go_check, 'value')
|
||||
ui.button("Nein", on_click=abort_action)
|
||||
dialog.open()
|
||||
|
||||
ui.separator()
|
||||
ui.label("Backup hochladen").classes('font-bold')
|
||||
ui.label(f"Stellen Sie sicher, dass Sie nur zur aktuellen Programmversion ({app_version}) Backups hochladen.")
|
||||
uploader = ui.upload(on_upload=handle_upload).props('accept=.zip')
|
||||
ui.label(f"Stellen Sie sicher, dass Sie zur aktuellen Programmversion ({app_version}) passende Backups hochladen.")
|
||||
zip_upload = ui.upload(on_upload=handle_upload).props('accept=.zip')
|
||||
|
||||
ui.separator()
|
||||
|
||||
|
||||
# Alternativ zur Loginseite navigieren
|
||||
else:
|
||||
|
72
lib/api.py
72
lib/api.py
@ -533,68 +533,14 @@ def json_info(api_key: str):
|
||||
if not found_key:
|
||||
return { "data": "none"}
|
||||
|
||||
@ui.page("/api/backup")
|
||||
def backup():
|
||||
try:
|
||||
admin_auth = app.storage.user['admin_authenticated']
|
||||
except:
|
||||
admin_auth = False
|
||||
|
||||
if admin_auth:
|
||||
|
||||
@app.get('/api/backup/{api_key}')
|
||||
def backup_api(api_key: str):
|
||||
date_format = '%Y-%m-%d_%H-%M'
|
||||
|
||||
pageheader("Backup herunterladen")
|
||||
ui.page_title(f"{app_title} {app_version}")
|
||||
ui.label("Vorhandene Backups:")
|
||||
|
||||
@ui.refreshable
|
||||
def backup_list():
|
||||
if not os.path.isdir(os.path.join(scriptpath, backupfolder)):
|
||||
os.makedirs(os.path.join(scriptpath, backupfolder))
|
||||
|
||||
backup_files = [ ]
|
||||
with ui.grid(columns='auto auto'):
|
||||
for file in os.listdir(os.path.join(scriptpath, backupfolder)):
|
||||
if file.endswith(".zip"):
|
||||
backup_files.append(file)
|
||||
backup_files.sort()
|
||||
backup_files.reverse()
|
||||
|
||||
if len(backup_files) == 0:
|
||||
ui.label("Keine Backups vorhanden")
|
||||
|
||||
for file in backup_files:
|
||||
date_string = file[0:-4]
|
||||
try:
|
||||
date_string_dt = datetime.strptime(date_string, date_format)
|
||||
button_string = date_string_dt.strftime('%d.%m.%Y - %H:%M')
|
||||
except ValueError:
|
||||
button_string = date_string
|
||||
ui.button(button_string, on_click=lambda file=date_string: ui.download.file(f'{file}.zip'))
|
||||
def del_backup_dialog(file):
|
||||
def del_backup():
|
||||
os.remove(os.path.join(scriptpath, backupfolder, f'{file}.zip'))
|
||||
dialog.close()
|
||||
ui.notify(f'Backupdatei {file}.zip gelöscht')
|
||||
backup_list.refresh()
|
||||
with ui.dialog() as dialog, ui.card():
|
||||
ui.label(f"Soll das Backup {file}.zip wirklich gelöscht werden?")
|
||||
ui.label("Dies kann nicht rückgänig gemacht werden!").classes('font-bold')
|
||||
with ui.grid(columns=2):
|
||||
ui.button("Löschen", on_click=del_backup)
|
||||
ui.button("Abbrechen", on_click=dialog.close)
|
||||
dialog.open()
|
||||
|
||||
ui.button(icon='delete', on_click=lambda file=date_string: del_backup_dialog(file))
|
||||
|
||||
backup_list()
|
||||
|
||||
ui.separator()
|
||||
searchpath = backupfolder
|
||||
|
||||
def make_backup():
|
||||
compress = zipfile.ZIP_DEFLATED
|
||||
filename = os.path.join(scriptpath, backupfolder, datetime.now().strftime(date_format) + '.zip')
|
||||
filename = os.path.join(searchpath, datetime.now().strftime(date_format) + '.zip')
|
||||
folder = userfolder
|
||||
with zipfile.ZipFile(filename, 'w', compress) as target:
|
||||
for root, dirs, files in os.walk(folder):
|
||||
@ -602,9 +548,9 @@ def backup():
|
||||
add = os.path.join(root, file)
|
||||
target.write(add)
|
||||
target.write(usersettingsfilename)
|
||||
backup_list.refresh()
|
||||
|
||||
ui.button("Neues Backup erstellen", on_click=make_backup)
|
||||
|
||||
target.writestr("app_version.txt", data=app_version)
|
||||
if api_key == load_adminsettings()["backup_api_key"]:
|
||||
make_backup()
|
||||
return {"backup": datetime.now().strftime(date_format), "success": True}
|
||||
else:
|
||||
login_mask()
|
||||
return {"backup": datetime.now().strftime(date_format), "success": False}
|
@ -3,6 +3,7 @@
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
|
||||
app_title = "Zeiterfassung"
|
||||
app_version = ("0.0.0")
|
||||
@ -10,7 +11,7 @@ app_version = ("0.0.0")
|
||||
# Standardpfade
|
||||
scriptpath = str(Path(os.path.dirname(os.path.abspath(__file__))).parent.absolute())
|
||||
userfolder = "users"
|
||||
backupfolder = "backup"
|
||||
backupfolder = str(os.path.join(scriptpath, "backup"))
|
||||
|
||||
# Dateinamen
|
||||
|
||||
@ -31,9 +32,11 @@ standard_adminsettings = { "admin_user": "admin",
|
||||
"times_on_touchscreen": True,
|
||||
"photos_on_touchscreen": True,
|
||||
"touchscreen": True,
|
||||
"picure_height": 200,
|
||||
"picture_height": 200,
|
||||
"button_height": 300,
|
||||
"user_notes": True,
|
||||
"backupfolder": backupfolder,
|
||||
"backup_api_key": hashlib.shake_256(bytes(backupfolder, 'utf-8')).hexdigest(20),
|
||||
"holidays": { }
|
||||
}
|
||||
|
||||
|
13
lib/users.py
13
lib/users.py
@ -504,3 +504,16 @@ def load_adminsettings():
|
||||
return data
|
||||
except:
|
||||
return -1
|
||||
|
||||
# bestimmte Admineinstellungen speichern
|
||||
def write_adminsetting(key: str, value):
|
||||
settings_filename = os.path.join(scriptpath, usersettingsfilename)
|
||||
admin_settings = load_adminsettings()
|
||||
try:
|
||||
admin_settings[key] = value
|
||||
json_data = json.dumps(admin_settings, indent=4)
|
||||
with open(settings_filename, 'w') as output_file:
|
||||
output_file.write(json_data)
|
||||
except KeyError:
|
||||
print(f"Kein Einstellungsschlüssel {key} vorhanden.")
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
from datetime import datetime
|
||||
|
||||
from nicegui import ui, app
|
||||
from nicegui import ui, app, events
|
||||
|
||||
from lib.users import *
|
||||
from lib.definitions import *
|
||||
@ -10,6 +10,10 @@ import hashlib
|
||||
import calendar
|
||||
import locale
|
||||
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
|
||||
class pageheader:
|
||||
|
@ -9,6 +9,8 @@
|
||||
"picture_height": "100",
|
||||
"button_height": "120",
|
||||
"user_notes": true,
|
||||
"backup_folder": "/home/alexander/Dokumente/Python/Zeiterfassung/backup",
|
||||
"backup_api_key": "6fed93dc4a35308b2c073a8a6f3284afe1fb9946",
|
||||
"holidays": {
|
||||
"2025-01-01": "Neujahr",
|
||||
"2025-04-18": "Karfreitag",
|
||||
|
@ -1,68 +0,0 @@
|
||||
1743966330
|
||||
1743966416
|
||||
1744018256
|
||||
1744018315
|
||||
1744018470
|
||||
1744018696
|
||||
1744100316
|
||||
1744100330
|
||||
1744194603
|
||||
1744196086
|
||||
1744196347
|
||||
1744196348
|
||||
1744196349
|
||||
1744196350
|
||||
1744196350
|
||||
1744196351
|
||||
1744197304
|
||||
1744197306
|
||||
1744197767
|
||||
1744197768
|
||||
1744210910
|
||||
1744210912
|
||||
1744210913
|
||||
1744210914
|
||||
1744211937
|
||||
1744211939
|
||||
1744221416
|
||||
1744221418
|
||||
1744221436
|
||||
1744221439
|
||||
1744221562
|
||||
1744221565
|
||||
1744221993
|
||||
1744222004
|
||||
1744222029
|
||||
1744222032
|
||||
1744259777
|
||||
1744259780
|
||||
1744260543
|
||||
1744260545
|
||||
1744266752
|
||||
1744266755
|
||||
1744266781
|
||||
1744266782
|
||||
1744268299
|
||||
1744268300
|
||||
1744269834
|
||||
1744269835
|
||||
1744269841
|
||||
1744269877
|
||||
1744269879
|
||||
1744269923
|
||||
1744269924
|
||||
1744269924
|
||||
1744269925
|
||||
1744269926
|
||||
1744269963
|
||||
1744269964
|
||||
1744269964
|
||||
1744269967
|
||||
1744269969
|
||||
1744269970
|
||||
1744269974
|
||||
1744269977
|
||||
1744269978
|
||||
1744269980
|
||||
1744270078
|
||||
1744270084
|
Binary file not shown.
Before Width: | Height: | Size: 550 KiB |
Binary file not shown.
Before Width: | Height: | Size: 48 KiB |
Loading…
x
Reference in New Issue
Block a user