diff --git a/__pycache__/definitions.cpython-311.pyc b/__pycache__/definitions.cpython-311.pyc
deleted file mode 100644
index 88d0a83..0000000
Binary files a/__pycache__/definitions.cpython-311.pyc and /dev/null differ
diff --git a/__pycache__/definitions.cpython-38.pyc b/__pycache__/definitions.cpython-38.pyc
deleted file mode 100644
index 57beecb..0000000
Binary files a/__pycache__/definitions.cpython-38.pyc and /dev/null differ
diff --git a/__pycache__/jsonhandler.cpython-311.pyc b/__pycache__/jsonhandler.cpython-311.pyc
deleted file mode 100644
index 148655f..0000000
Binary files a/__pycache__/jsonhandler.cpython-311.pyc and /dev/null differ
diff --git a/__pycache__/jsonhandler.cpython-38.pyc b/__pycache__/jsonhandler.cpython-38.pyc
deleted file mode 100644
index 25ef269..0000000
Binary files a/__pycache__/jsonhandler.cpython-38.pyc and /dev/null differ
diff --git a/__pycache__/timestamping.cpython-311.pyc b/__pycache__/timestamping.cpython-311.pyc
deleted file mode 100644
index 0c0f11a..0000000
Binary files a/__pycache__/timestamping.cpython-311.pyc and /dev/null differ
diff --git a/__pycache__/timestamping.cpython-38.pyc b/__pycache__/timestamping.cpython-38.pyc
deleted file mode 100644
index a66c5e0..0000000
Binary files a/__pycache__/timestamping.cpython-38.pyc and /dev/null differ
diff --git a/__pycache__/ui.cpython-311.pyc b/__pycache__/ui.cpython-311.pyc
deleted file mode 100644
index 0746131..0000000
Binary files a/__pycache__/ui.cpython-311.pyc and /dev/null differ
diff --git a/__pycache__/users.cpython-311.pyc b/__pycache__/users.cpython-311.pyc
deleted file mode 100644
index 8aaa98e..0000000
Binary files a/__pycache__/users.cpython-311.pyc and /dev/null differ
diff --git a/__pycache__/users.cpython-38.pyc b/__pycache__/users.cpython-38.pyc
deleted file mode 100644
index 8cb26f0..0000000
Binary files a/__pycache__/users.cpython-38.pyc and /dev/null differ
diff --git a/definitions.py b/definitions.py
deleted file mode 100644
index 7a26b5f..0000000
--- a/definitions.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Zeiterfassung
-#
-# statische Werte
-
-import os
-
-# Quasi Konstanten:
-
-scriptpath = os.path.dirname(os.path.abspath(__file__))
-suffix_userfolder = "users"
-suffix_settingsfolder = "settings"
-usersettingsfilename = "settings.json"
-photofilename = "photo.png"
-program_name = "Zeiterfassung"
-program_version = "Development"
-
-status_in = "eingestempelt"
-status_out = "ausgestempelt"
-
-userfolder = scriptpath + "/" + suffix_userfolder
-settingsfolder = scriptpath + "/" + suffix_settingsfolder
\ No newline at end of file
diff --git a/jsonhandler.py b/jsonhandler.py
deleted file mode 100644
index 09fba28..0000000
--- a/jsonhandler.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Zeiterfassung
-# JSON Handling
-
-# Imports
-
-import json
-
-# Datenstruktur:
-
-# user: Benutzername
-# name: Vollständiger Name
-# password: gehashtes Passwort
-
-# Montatsspezifische Informationen:
-# Gültigkeitsdatum, ab wann gülitg
-#
-# monday: Stunden
-# tuesday: Stunden
-# wednesday: Stunden
-# thursday: Stunden
-# friday: Stunden
-# saturday: Stunden
-# sunday: Stunden
-# pto: Tage pro Jahr
-
-def load_settings(filename):
-
- with open(filename) as json_file:
- data = json.load(json_file)
-
- return data
-
-def write_settings(filename, settings):
-
- with open(filename, "w") as json_file:
- json.dump(settings, json_file, indent=4)
\ No newline at end of file
diff --git a/lib/admin.py b/lib/admin.py
index 857e9c1..eb1e955 100644
--- a/lib/admin.py
+++ b/lib/admin.py
@@ -14,6 +14,7 @@ from lib.web_ui import *
import os.path
import os
+import zipfile
from stat import S_IREAD, S_IRWXU
import hashlib
import calendar
@@ -45,6 +46,7 @@ def page_admin():
time_overview = ui.tab('Zeitübersichten')
settings = ui.tab('Einstellungen')
users = ui.tab('Benutzer')
+ backups = ui.tab('Backups')
userlist = [ ]
@@ -609,24 +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"]
- json_dict = json.dumps(output_dict, indent=4)
- with open(os.path.join(scriptpath, usersettingsfilename), "w") as outputfile:
- outputfile.write(json_dict)
+ 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"])
+
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.")
@@ -1241,6 +1240,219 @@ Dies kann nicht rückgängig gemacht werden!''')
ui.button("Speichern", on_click=save_workhours)
ui.button("Löschen", on_click=delete_workhour_entry)
user_selection_changed()
+ with ui.tab_panel(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 = backupfolder
+
+ @ui.refreshable
+ def backup_list():
+
+ if not os.path.isdir(searchpath):
+ os.makedirs(os.path.join(searchpath))
+
+ backup_files = []
+ 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"):
+ 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, version in backup_files:
+ date_string = file[0:-4]
+ try:
+ date_string_dt = datetime.datetime.strptime(date_string, date_format)
+ button_string = date_string_dt.strftime('%d.%m.%Y - %H:%M')
+ except ValueError:
+ 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")
+
+ 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')
+ check = ui.checkbox("Ich habe verstanden.")
+ with ui.grid(columns=2):
+ ui.button("Löschen", on_click=del_backup).bind_enabled_from(check, 'value')
+ ui.button("Abbrechen", on_click=dialog.close)
+ dialog.open()
+
+ def restore_backup_dialog(file):
+ def restore_backup():
+ with zipfile.ZipFile(os.path.join(scriptpath, backupfolder, f'{file}.zip'), 'r') as source:
+ source.extractall(scriptpath)
+ with ui.dialog() as confirm_dialog, ui.card():
+ ui.label("Das Backup wurde wiederhergestellt. Um Änderungen anzuzeigen, muss die Seite neu geladen werden.")
+ with ui.grid(columns=2):
+ ui.button("Neu laden", on_click=ui.navigate.reload)
+ ui.button("Später selbst neu laden", on_click=dialog.close)
+ confirm_dialog.open()
+
+ with ui.dialog() as dialog, ui.card():
+ ui.label(f"Soll das Backup {file}.zip wiederhergestellt werden?")
+ ui.label("Es werden dabei alle Dateien überschrieben!").classes('font-bold')
+ ui.label("Dies kann nicht rückgängig gemacht werden!").classes('font-bold fg-red')
+ check = ui.checkbox("Ich habe verstanden.")
+ with ui.grid(columns=2):
+ ui.button("Wiederherstellen", on_click=restore_backup).bind_enabled_from(check, 'value')
+ ui.button("Abbrechen", on_click=dialog.close)
+
+ dialog.open()
+
+ ui.button(icon='delete', on_click=lambda file=date_string: del_backup_dialog(file)).tooltip("Backup löschen")
+ ui.button(icon='undo', on_click=lambda file=date_string: restore_backup_dialog(file)).tooltip("Backup wiederherstellen")
+
+ backup_list()
+
+ ui.separator()
+
+ async def make_backup():
+ n = ui.notification("Backup wird erzeugt...")
+ compress = zipfile.ZIP_DEFLATED
+ 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):
+ for file in files:
+ 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")
+
+ ui.button("Neues Backup erstellen", on_click=make_backup)
+
+ def handle_upload(e: events.UploadEventArguments):
+ filename = e.name
+ upload = True
+ if os.path.exists(os.path.join(searchpath, filename )):
+ with ui.dialog() as dialog, ui.card():
+ ui.label("Datei mit diesem Namen existiert bereits. Die Datei kann nicht hochgeladen werden.")
+ ui.button("OK", on_click=dialog.close)
+ dialog.open()
+ upload = False
+
+ if upload:
+
+ content = e.content.read()
+ 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()
+ 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 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:
diff --git a/lib/api.py b/lib/api.py
index 5996283..e047134 100644
--- a/lib/api.py
+++ b/lib/api.py
@@ -1,4 +1,6 @@
import sys
+import os
+import zipfile
from calendar import month_name
from logging import exception
@@ -16,289 +18,301 @@ import calendar
@ui.page('/api/month/{username}/{year}-{month}')
def page_overview_month(username: str, year: int, month: int):
- data = load_adminsettings()
-
try:
- current_user = user(username)
- days_with_errors = current_user.archiving_validity_check(year, month)
- ui.page_title(f"Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}")
- if current_user.get_archive_status(year, month):
- with ui.column().classes('w-full items-end gap-0'):
- ui.label(f"Bericht erstellt am {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}")
- ui.label('Archiviert').classes('italic').classes('text-red text-bold text-xl')
- #ui.add_head_html('')
- else:
- with ui.column().classes('w-full items-end gap-0'):
- ui.label(f"Bericht erstellt am {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}")
- ui.markdown(f'#Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}')
+ admin_auth = app.storage.user['admin_authenticated']
+ except:
+ admin_auth = False
- pad_x = 4
- pad_y = 0
+ if login_is_valid(username) or admin_auth:
+ data = load_adminsettings()
- color_weekend = "gray-100"
- color_holiday = "gray-100"
-
- def overview_table():
- # Timestamp in ein Array schreiben
- timestamps = current_user.get_timestamps(year, month)
- timestamps.sort()
-
- # Abwesenheitsdaten in ein Dict schreiben
- user_absent = current_user.get_absence(year, month)
-
- # Dictionary für sortierte Timestamps
- timestamps_dict = { }
-
- # Dictionary mit zunächst leeren Tageinträgen befüllen
- for day in range(1, monthrange(year, month)[1] + 1):
- # Jeder Tag bekommt eine leere Liste
- timestamps_dict[day] = [ ]
-
- # Timestamps den Monatstagen zuordnen
- for stamp in timestamps:
- day_of_month_of_timestamp = datetime.fromtimestamp(int(stamp)).day
- timestamps_dict[day_of_month_of_timestamp].append(int(stamp))
-
- general_saldo = 0
-
- bg_color = ''
+ try:
+ current_user = user(username)
+ days_with_errors = current_user.archiving_validity_check(year, month)
+ ui.page_title(f"Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}")
if current_user.get_archive_status(year, month):
- bg_color = ' bg-yellow-100'
+ with ui.column().classes('w-full items-end gap-0'):
+ ui.label(f"Bericht erstellt am {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}")
+ ui.label('Archiviert').classes('italic').classes('text-red text-bold text-xl')
+ #ui.add_head_html('')
+ else:
+ with ui.column().classes('w-full items-end gap-0'):
+ ui.label(f"Bericht erstellt am {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}")
+ ui.markdown(f'#Bericht für {current_user.fullname} für {calendar.month_name[month]} {year}')
- with ui.grid(columns='auto auto 1fr 1fr 1fr').classes(f'gap-0 border px-0 py-0{bg_color}'):
- ui.markdown("**Datum**").classes(f'border px-{pad_x} py-{pad_y}')
- ui.markdown("**Buchungen**").classes(f'border px-{pad_x} py-{pad_y}')
- ui.markdown("**Ist**").classes(f'border px-{pad_x} py-{pad_y}')
- ui.markdown("**Soll**").classes(f'border px-{pad_x} py-{pad_y}')
- ui.markdown("**Saldo**").classes(f'border px-{pad_x} py-{pad_y}')
+ pad_x = 4
+ pad_y = 0
- # Gehe jeden einzelnen Tag des Dictionaries für die Timestamps durch
- for day in list(timestamps_dict):
- booking_text = ""
- color_day = 'inherit'
- if datetime(year, month, day).strftime('%w') in ["0", "6"]:
- color_day = color_weekend
+ color_weekend = "gray-100"
+ color_holiday = "gray-100"
- current_day_date = f"{datetime(year, month, day).strftime('%a')}, {day}.{month}.{year}"
- with ui.link_target(day).classes(f'border px-{pad_x} py-{pad_y} bg-{color_day}'):
- ui.markdown(current_day_date)
+ def overview_table():
+ # Timestamp in ein Array schreiben
+ timestamps = current_user.get_timestamps(year, month)
+ timestamps.sort()
- # Abwesenheitseinträge
- booking_color = "inherit"
- booking_text_color = "inherit"
- try:
- # Abwesenheitszeiten behandeln
- for i in list(user_absent):
- if int(i) == day:
- booking_text += absence_entries[user_absent[i]]["name"] + "
"
- booking_color = absence_entries[user_absent[i]]["color"]
- booking_text_color = absence_entries[user_absent[i]]["text-color"]
- except:
- pass
+ # Abwesenheitsdaten in ein Dict schreiben
+ user_absent = current_user.get_absence(year, month)
- # Buchungen behandeln
- for i in range(0, len(timestamps_dict[day]), 2):
+ # Dictionary für sortierte Timestamps
+ timestamps_dict = { }
+
+ # Dictionary mit zunächst leeren Tageinträgen befüllen
+ for day in range(1, monthrange(year, month)[1] + 1):
+ # Jeder Tag bekommt eine leere Liste
+ timestamps_dict[day] = [ ]
+
+ # Timestamps den Monatstagen zuordnen
+ for stamp in timestamps:
+ day_of_month_of_timestamp = datetime.fromtimestamp(int(stamp)).day
+ timestamps_dict[day_of_month_of_timestamp].append(int(stamp))
+
+ general_saldo = 0
+
+ bg_color = ''
+ if current_user.get_archive_status(year, month):
+ bg_color = ' bg-yellow-100'
+
+ with ui.grid(columns='auto auto 1fr 1fr 1fr').classes(f'gap-0 border px-0 py-0{bg_color}'):
+ ui.markdown("**Datum**").classes(f'border px-{pad_x} py-{pad_y}')
+ ui.markdown("**Buchungen**").classes(f'border px-{pad_x} py-{pad_y}')
+ ui.markdown("**Ist**").classes(f'border px-{pad_x} py-{pad_y}')
+ ui.markdown("**Soll**").classes(f'border px-{pad_x} py-{pad_y}')
+ ui.markdown("**Saldo**").classes(f'border px-{pad_x} py-{pad_y}')
+
+ # Gehe jeden einzelnen Tag des Dictionaries für die Timestamps durch
+ for day in list(timestamps_dict):
+ booking_text = ""
+ color_day = 'inherit'
+ if datetime(year, month, day).strftime('%w') in ["0", "6"]:
+ color_day = color_weekend
+
+ current_day_date = f"{datetime(year, month, day).strftime('%a')}, {day}.{month}.{year}"
+ with ui.link_target(day).classes(f'border px-{pad_x} py-{pad_y} bg-{color_day}'):
+ ui.markdown(current_day_date)
+
+ # Abwesenheitseinträge
+ booking_color = "inherit"
+ booking_text_color = "inherit"
try:
- temp_pair = [timestamps_dict[day][i], timestamps_dict[day][i + 1]]
- booking_text = booking_text + str(datetime.fromtimestamp(temp_pair[0]).strftime('%H:%M')) + " - " + str(datetime.fromtimestamp(temp_pair[1]).strftime('%H:%M')) + "
"
-
+ # Abwesenheitszeiten behandeln
+ for i in list(user_absent):
+ if int(i) == day:
+ booking_text += absence_entries[user_absent[i]]["name"] + "
"
+ booking_color = absence_entries[user_absent[i]]["color"]
+ booking_text_color = absence_entries[user_absent[i]]["text-color"]
except:
- if len(timestamps_dict[day]) % 2 != 0:
- booking_text += datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M') + " - ***Buchung fehlt!***"
+ pass
- day_notes = current_user.get_day_notes(year, month, day)
- just_once = True
-
- with ui.column().classes(f'border px-{pad_x} py-{pad_y} bg-{booking_color} text-{booking_text_color}'):
- booking_text_element = ui.markdown(booking_text)
- if len(day_notes) > 0:
- if len(timestamps_dict[day]) > 0 or day in list(map(int, list(user_absent))):
- ui.separator()
- for user_key, notes in day_notes.items():
- if user_key == "admin":
- ui.markdown(f"Administrator:
{notes}")
- else:
- with ui.element():
- ui.markdown(f"{current_user.fullname}:
{notes}")
- if len(day_notes) > 1 and just_once:
- ui.separator()
- just_once = False
- # Ist-Zeiten berechnen
- timestamps_of_this_day = []
-
- # Suche mir alle timestamps für diesen Tag
- for i in timestamps:
- actual_timestamp = datetime.fromtimestamp(int(i))
- timestamp_day = actual_timestamp.strftime('%-d')
-
- if int(timestamp_day) == int(day):
- timestamps_of_this_day.append(i)
-
- timestamps_of_this_day.sort()
- time_sum = 0
- if len(timestamps_of_this_day) > 1:
-
- if len(timestamps_of_this_day) % 2 == 0:
- for i in range(0, len(timestamps_of_this_day), 2):
- time_delta = int(
- timestamps_of_this_day[i + 1]) - int(
- timestamps_of_this_day[i])
- time_sum = time_sum + time_delta
- else:
- for i in range(0, len(timestamps_of_this_day) - 1, 2):
- time_delta = int(
- timestamps_of_this_day[i + 1]) - int(
- timestamps_of_this_day[i])
- time_sum = time_sum + time_delta
-
- is_time = convert_seconds_to_hours(time_sum) + " h"
- else:
- is_time = "Kein"
-
- ui.markdown(is_time).classes(f'border px-{pad_x} py-{pad_y} text-center')
- # Sollzeit bestimmen
-
- hours_to_work = int(current_user.get_day_workhours(year, month, day))
-
- if hours_to_work < 0:
- target_time = ""
- else:
- target_time = f"{convert_seconds_to_hours(int(hours_to_work) * 3600)} h"
- if int(hours_to_work) == 0:
- booking_text = "Kein Arbeitstag"
- date_dt = datetime(year, month, day)
- if date_dt.strftime("%Y-%m-%d") in data["holidays"]:
- booking_text = f'**{data["holidays"][date_dt.strftime("%Y-%m-%d")]}**'
- booking_text_element.set_content(booking_text)
-
- ui.markdown(target_time).classes(f'border px-{pad_x} py-{pad_y} text-center')
-
- # Saldo für den Tag berechnen
- day_in_list = datetime(year, month, day)
- if time.time() > day_in_list.timestamp():
-
- time_duty = int(current_user.get_day_workhours(year, month, day)) * 3600
- if time_duty < 0:
- saldo = 0
- total = ""
- booking_text = "Kein Arbeitsverhältnis"
- booking_text_element.set_content(booking_text)
- else:
- saldo = int(time_sum) - int(time_duty)
- # Nach Abwesenheitseinträgen suchen
+ # Buchungen behandeln
+ for i in range(0, len(timestamps_dict[day]), 2):
try:
- for i in list(user_absent):
- if int(i) == day and user_absent[i] != "UU":
- saldo = 0
+ temp_pair = [timestamps_dict[day][i], timestamps_dict[day][i + 1]]
+ booking_text = booking_text + str(datetime.fromtimestamp(temp_pair[0]).strftime('%H:%M')) + " - " + str(datetime.fromtimestamp(temp_pair[1]).strftime('%H:%M')) + "
"
+
except:
- pass
+ if len(timestamps_dict[day]) % 2 != 0:
+ booking_text += datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M') + " - ***Buchung fehlt!***"
- general_saldo = general_saldo + saldo
- total = f"{convert_seconds_to_hours(saldo)} h"
+ day_notes = current_user.get_day_notes(year, month, day)
+ just_once = True
- else:
- total = "-"
- if total == "-":
- total_class = 'text-center'
- else:
- total_class = 'text-right'
- ui.markdown(total).classes(total_class).classes(f'border px-{pad_x} py-{pad_y}')
+ with ui.column().classes(f'border px-{pad_x} py-{pad_y} bg-{booking_color} text-{booking_text_color}'):
+ booking_text_element = ui.markdown(booking_text)
+ if len(day_notes) > 0:
+ if len(timestamps_dict[day]) > 0 or day in list(map(int, list(user_absent))):
+ ui.separator()
+ for user_key, notes in day_notes.items():
+ if user_key == "admin":
+ ui.markdown(f"Administrator:
{notes}")
+ else:
+ with ui.element():
+ ui.markdown(f"{current_user.fullname}:
{notes}")
+ if len(day_notes) > 1 and just_once:
+ ui.separator()
+ just_once = False
+ # Ist-Zeiten berechnen
+ timestamps_of_this_day = []
- # Überstundenzusammenfassung
- ui.markdown("Überstunden aus Vormonat:").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}')
- last_months_overtime = current_user.get_last_months_overtime(year, month)
- ui.markdown(f"{convert_seconds_to_hours(last_months_overtime)} h").classes(f'text-right border px-{pad_x} py-{pad_y}')
- ui.markdown("Überstunden diesen Monat:").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}')
- ui.markdown(f"{convert_seconds_to_hours(general_saldo)} h").classes(f'text-right border px-{pad_x} py-{pad_y}')
- ui.markdown("**Überstunden Gesamt:**").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}')
- global overtime_overall
- overtime_overall = last_months_overtime + general_saldo
- ui.markdown(f"**{convert_seconds_to_hours(overtime_overall)} h**").classes(f'text-right border px-{pad_x} py-{pad_y}')
+ # Suche mir alle timestamps für diesen Tag
+ for i in timestamps:
+ actual_timestamp = datetime.fromtimestamp(int(i))
+ timestamp_day = actual_timestamp.strftime('%-d')
- overview_table()
+ if int(timestamp_day) == int(day):
+ timestamps_of_this_day.append(i)
- def absence_table():
- absences_this_month = current_user.get_absence(year, month)
- absence_dict = { }
+ timestamps_of_this_day.sort()
+ time_sum = 0
+ if len(timestamps_of_this_day) > 1:
- for abbr in list(absence_entries):
- absence_dict[abbr] = 0
+ if len(timestamps_of_this_day) % 2 == 0:
+ for i in range(0, len(timestamps_of_this_day), 2):
+ time_delta = int(
+ timestamps_of_this_day[i + 1]) - int(
+ timestamps_of_this_day[i])
+ time_sum = time_sum + time_delta
+ else:
+ for i in range(0, len(timestamps_of_this_day) - 1, 2):
+ time_delta = int(
+ timestamps_of_this_day[i + 1]) - int(
+ timestamps_of_this_day[i])
+ time_sum = time_sum + time_delta
- for key, value in absences_this_month.items():
- if value in list(absence_dict):
- absence_dict[value] += 1
+ is_time = convert_seconds_to_hours(time_sum) + " h"
+ else:
+ is_time = "Kein"
- total_absence_days = 0
- for key, value in absence_dict.items():
- total_absence_days += absence_dict[key]
+ ui.markdown(is_time).classes(f'border px-{pad_x} py-{pad_y} text-center')
+ # Sollzeit bestimmen
- if total_absence_days > 0:
- ui.markdown("###Abwesenheitstage diesen Monat:")
+ hours_to_work = int(current_user.get_day_workhours(year, month, day))
- with ui.grid(columns='auto 25%').classes(f'gap-0 border px-0 py-0'):
+ if hours_to_work < 0:
+ target_time = ""
+ else:
+ target_time = f"{convert_seconds_to_hours(int(hours_to_work) * 3600)} h"
+ if int(hours_to_work) == 0:
+ booking_text = "Kein Arbeitstag"
+ date_dt = datetime(year, month, day)
+ if date_dt.strftime("%Y-%m-%d") in data["holidays"]:
+ booking_text = f'**{data["holidays"][date_dt.strftime("%Y-%m-%d")]}**'
+ booking_text_element.set_content(booking_text)
- for key, value in absence_dict.items():
- if value > 0:
- ui.markdown(absence_entries[key]['name']).classes(f"border px-{pad_x} py-{pad_y}")
- ui.markdown(str(value)).classes(f'border px-{pad_x} py-{pad_y} text-center')
+ ui.markdown(target_time).classes(f'border px-{pad_x} py-{pad_y} text-center')
- absence_table()
+ # Saldo für den Tag berechnen
+ day_in_list = datetime(year, month, day)
+ if time.time() > day_in_list.timestamp():
- def archive():
- current_year = datetime.now().year
- current_month = datetime.now().month
- archivable = False
+ time_duty = int(current_user.get_day_workhours(year, month, day)) * 3600
+ if time_duty < 0:
+ saldo = 0
+ total = ""
+ booking_text = "Kein Arbeitsverhältnis"
+ booking_text_element.set_content(booking_text)
+ else:
+ saldo = int(time_sum) - int(time_duty)
+ # Nach Abwesenheitseinträgen suchen
+ try:
+ for i in list(user_absent):
+ if int(i) == day and user_absent[i] != "UU":
+ saldo = 0
+ except:
+ pass
- if current_year > year:
- if current_user.get_archive_status(year, month) == False:
- archivable = True
- if current_year == year:
- if current_month > month:
+ general_saldo = general_saldo + saldo
+ total = f"{convert_seconds_to_hours(saldo)} h"
+
+ else:
+ total = "-"
+ if total == "-":
+ total_class = 'text-center'
+ else:
+ total_class = 'text-right'
+ ui.markdown(total).classes(total_class).classes(f'border px-{pad_x} py-{pad_y}')
+
+ # Überstundenzusammenfassung
+ ui.markdown("Überstunden aus Vormonat:").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}')
+ last_months_overtime = current_user.get_last_months_overtime(year, month)
+ ui.markdown(f"{convert_seconds_to_hours(last_months_overtime)} h").classes(f'text-right border px-{pad_x} py-{pad_y}')
+ ui.markdown("Überstunden diesen Monat:").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}')
+ ui.markdown(f"{convert_seconds_to_hours(general_saldo)} h").classes(f'text-right border px-{pad_x} py-{pad_y}')
+ ui.markdown("**Überstunden Gesamt:**").classes(f'col-span-4 text-right border px-{pad_x} py-{pad_y}')
+ global overtime_overall
+ overtime_overall = last_months_overtime + general_saldo
+ ui.markdown(f"**{convert_seconds_to_hours(overtime_overall)} h**").classes(f'text-right border px-{pad_x} py-{pad_y}')
+
+ overview_table()
+
+ def absence_table():
+ absences_this_month = current_user.get_absence(year, month)
+ absence_dict = { }
+
+ for abbr in list(absence_entries):
+ absence_dict[abbr] = 0
+
+ for key, value in absences_this_month.items():
+ if value in list(absence_dict):
+ absence_dict[value] += 1
+
+ total_absence_days = 0
+ for key, value in absence_dict.items():
+ total_absence_days += absence_dict[key]
+
+ if total_absence_days > 0:
+ ui.markdown("###Abwesenheitstage diesen Monat:")
+
+ with ui.grid(columns='auto 25%').classes(f'gap-0 border px-0 py-0'):
+
+ for key, value in absence_dict.items():
+ if value > 0:
+ ui.markdown(absence_entries[key]['name']).classes(f"border px-{pad_x} py-{pad_y}")
+ ui.markdown(str(value)).classes(f'border px-{pad_x} py-{pad_y} text-center')
+
+ absence_table()
+
+ def archive():
+ current_year = datetime.now().year
+ current_month = datetime.now().month
+ archivable = False
+
+ if current_year > year:
if current_user.get_archive_status(year, month) == False:
archivable = True
+ if current_year == year:
+ if current_month > month:
+ if current_user.get_archive_status(year, month) == False:
+ archivable = True
- def archive_dialog():
- def do_archiving():
- global overtime_overall
- current_user.archive_hours(year, month, overtime_overall)
- dialog.close()
- ui.navigate.to(f'/api/month/{username}/{year}-{month}')
+ def archive_dialog():
+ def do_archiving():
+ global overtime_overall
+ current_user.archive_hours(year, month, overtime_overall)
+ dialog.close()
+ ui.navigate.to(f'/api/month/{username}/{year}-{month}')
- with ui.dialog() as dialog, ui.card():
- with ui.grid(columns='1fr 1fr'):
- ui.markdown("Hiermit bestätigen Sie, dass die Zeitbuchungen im Montagsjournal korrekt sind.
Sollte dies nicht der Fall sein, wenden Sie sich für eine Korrektur an den Administrator.").classes('col-span-2')
- ui.button("Archivieren", on_click=do_archiving)
- ui.button("Abbrechen", on_click=dialog.close)
+ with ui.dialog() as dialog, ui.card():
+ with ui.grid(columns='1fr 1fr'):
+ ui.markdown("Hiermit bestätigen Sie, dass die Zeitbuchungen im Montagsjournal korrekt sind.
Sollte dies nicht der Fall sein, wenden Sie sich für eine Korrektur an den Administrator.").classes('col-span-2')
+ ui.button("Archivieren", on_click=do_archiving)
+ ui.button("Abbrechen", on_click=dialog.close)
- dialog.open()
+ dialog.open()
- if archivable == True:
- if len(days_with_errors) > 0:
- ui.label("Es gibt Inkonsistenzen in den Buchungen. Folgende Tage müssen überprüft werden:")
- with ui.grid(columns=len(days_with_errors)):
- for i in days_with_errors:
- ui.link(f"{i}.", f'#{i}')
+ if archivable == True:
+ if len(days_with_errors) > 0:
+ ui.label("Es gibt Inkonsistenzen in den Buchungen. Folgende Tage müssen überprüft werden:")
+ with ui.grid(columns=len(days_with_errors)):
+ for i in days_with_errors:
+ ui.link(f"{i}.", f'#{i}')
- archive_button = ui.button("Archivieren", on_click=archive_dialog)
- if len(days_with_errors) > 0:
- archive_button.disable()
+ archive_button = ui.button("Archivieren", on_click=archive_dialog)
+ if len(days_with_errors) > 0:
+ archive_button.disable()
- archive()
-
- except Exception as e:
- print(str(type(e).__name__) + " " + str(e))
- if type(e) == UnboundLocalError:
- ui.markdown('#Fehler')
- ui.markdown('Benutzer existiert nicht')
- else:
- ui.markdown('#Fehler')
- ui.markdown(str(type(e)))
- ui.markdown(str(e))
+ archive()
+ except Exception as e:
+ print(str(type(e).__name__) + " " + str(e))
+ if type(e) == UnboundLocalError:
+ ui.markdown('#Fehler')
+ ui.markdown('Benutzer existiert nicht')
+ else:
+ ui.markdown('#Fehler')
+ ui.markdown(str(type(e)))
+ ui.markdown(str(e))
+ else:
+ login_mask(target=f'/api/month/{username}/{year}-{month}')
@ui.page('/api/vacation/{username}/{year}')
def page_overview_vacation(username: str, year: int):
- if login_is_valid(username):
+ try:
+ admin_auth = app.storage.user['admin_authenticated']
+ except:
+ admin_auth = False
+
+ if login_is_valid(username) or admin_auth:
try:
current_user = user(username)
@@ -354,8 +368,12 @@ def page_overview_vacation(username: str, year: int):
@ui.page('/api/absence/{username}/{year}')
def page_overview_absence(username: str, year: int):
+ try:
+ admin_auth = app.storage.user['admin_authenticated']
+ except:
+ admin_auth = False
- if login_is_valid(username):
+ if login_is_valid(username) or admin_auth:
current_user = user(username)
ui.page_title(f"Abwesenheitsübersicht für {current_user.fullname} für {year}")
ui.label(datetime.now().strftime('%d.%m.%Y')).classes('absolute top-5 right-5')
@@ -513,4 +531,26 @@ def json_info(api_key: str):
break
if not found_key:
- return { "data": "none"}
\ No newline at end of file
+ return { "data": "none"}
+
+@app.get('/api/backup/{api_key}')
+def backup_api(api_key: str):
+ date_format = '%Y-%m-%d_%H-%M'
+ searchpath = backupfolder
+
+ def make_backup():
+ compress = zipfile.ZIP_DEFLATED
+ 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):
+ for file in files:
+ add = os.path.join(root, file)
+ target.write(add)
+ target.write(usersettingsfilename)
+ 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:
+ return {"backup": datetime.now().strftime(date_format), "success": False}
\ No newline at end of file
diff --git a/lib/definitions.py b/lib/definitions.py
index 7296d9f..2c54691 100644
--- a/lib/definitions.py
+++ b/lib/definitions.py
@@ -3,6 +3,7 @@
import os
from pathlib import Path
+import hashlib
app_title = "Zeiterfassung"
app_version = ("0.0.0")
@@ -10,6 +11,7 @@ app_version = ("0.0.0")
# Standardpfade
scriptpath = str(Path(os.path.dirname(os.path.abspath(__file__))).parent.absolute())
userfolder = "users"
+backupfolder = str(os.path.join(scriptpath, "backup"))
# Dateinamen
@@ -30,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": { }
}
diff --git a/lib/users.py b/lib/users.py
index 7c7982e..a8a0f98 100644
--- a/lib/users.py
+++ b/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.")
+
diff --git a/lib/web_ui.py b/lib/web_ui.py
index deb9af7..5297d2a 100644
--- a/lib/web_ui.py
+++ b/lib/web_ui.py
@@ -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:
diff --git a/nice_gui.py b/nice_gui.py
deleted file mode 100644
index 5cc9439..0000000
--- a/nice_gui.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Zeiterfassung
-# Nice GUI UI
-
-from nicegui import ui
-from nicegui.events import ValueChangeEventArguments
-
-def site_pinpad():
-
- keys = [
- [ 1, 2, 3],
- [ 4, 5, 6],
- [ 7, 8, 9],
- [ "<-", 0, "OK"]
- ]
-
- with ui.row():
- for y, row in enumerate(keys, 1):
- for x, key in enumerate(row):
- button = ui.Button(text=keys[y][x])
-
- ui.run(port=8090)
-
-site_pinpad()
\ No newline at end of file
diff --git a/nicegui_test.py b/nicegui_test.py
deleted file mode 100644
index 2dbcbbd..0000000
--- a/nicegui_test.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from nicegui import ui
-from users import *
-from definitions import *
-
-@ui.page('/login')
-def page_login():
- ui.label('Loginseite')
-
-@ui.page('/stamping')
-def page_stamping():
- ui.label('Stempelsteite')
-
-@ui.page('/userlist')
-def page_userlist():
-
- def click_button(button):
- ui.notify(button)
-
- ui.label(app_title + " " + app_version)
-
- userlist = list_users()
- buttons = { }
-
- for name in userlist:
- button = ui.button(text=name, on_click=lambda name=name:click_button(name) )
- buttons[name] = button
-
-ui.run(port=8090)
diff --git a/settings.json b/settings.json
index 2e00471..a900a48 100644
--- a/settings.json
+++ b/settings.json
@@ -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",
diff --git a/testing ttk.py b/testing ttk.py
deleted file mode 100644
index 1431c0d..0000000
--- a/testing ttk.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import tkinter as tk
-from tkinter import ttk
-
-# Funktion, die die Farben des Buttons ändert
-def aendere_farbe(button, bg_farbe, fg_farbe):
- # Ändern der Hintergrund- und Textfarbe des Buttons
- button.configure(bg=bg_farbe, fg=fg_farbe)
-
-# Erstellen des Hauptfensters
-root = tk.Tk()
-root.title("Buttons mit unterschiedlichen Farben")
-
-# Liste von Button-Beschriftungen und den gewünschten Farben
-button_info = [
- ('Button 1', 'green', 'white'),
- ('Button 2', 'blue', 'yellow'),
- ('Button 3', 'red', 'black')
-]
-
-# Erstellen der Buttons aus der Liste
-buttons = []
-for text, bg, fg in button_info:
- # Einen Button erstellen und die Farben ändern
- button = tk.Button(root, text=text, command=lambda b=button, bg=bg, fg=fg: aendere_farbe(b, bg, fg))
- button.pack(pady=10)
- buttons.append(button)
-
-# Hauptloop starten
-root.mainloop()
diff --git a/timestamping.py b/timestamping.py
deleted file mode 100644
index 8d184ca..0000000
--- a/timestamping.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# Funktionen bzgl. Timestamps
-
-import time
-import datetime
-import os
-from definitions import *
-
-# Zeitstempel schreiben
-def append_timestamp(filename):
- # Hole den aktuellen Timestamp in Epoch-Zeit
- timestamp = int(time.time())
-
- try:
- # Öffne die Datei im Anhang-Modus ('a')
- with open(filename, 'a') as file:
- # Schreibe den Timestamp in die Datei und füge einen Zeilenumbruch hinzu
- file.write(f"{timestamp}\n")
- file.close()
- except FileNotFoundError:
- # Fehlende Verzeichnisse anlegen
- folder_path = os.path.dirname(filename)
- os.makedirs(folder_path, exist_ok=True)
- append_timestamp(filename)
-
-# Anzahl der Zeilen zählen
-def len_timestamps(filename):
- try:
- # Öffne die Datei im Lese-Modus ('r')
- with open(filename, 'r') as file:
- # Zähle die Zeilen
- lines = file.readlines()
- file.close()
- print(len(lines))
- return len(lines)
- except FileNotFoundError:
- print(f"Die Datei {filename} wurde nicht gefunden.")
- return 0
-
-# Stempelzustand auslesen
-def stempel_zustand(filename):
- lines = len_timestamps(filename)
- if lines == 0:
- print(f"Keine Einträge")
- elif lines % 2 == 0:
- return(status_out)
- else:
- return(status_in)
-
-# Letzten Zeitstempel auswerten
-def get_last_2_timestmaps(filename):
- with open(filename, 'r') as file:
- lines = file.readlines()
- file.close()
- second_last_line = lines[-2]
- last_line = lines[-1]
- last_2_timestmaps = [second_last_line, last_line]
- return last_2_timestmaps
-
-# Stempelübersicht zusammenstellen
-def overview(filename):
-
- # Öffne die Datei im Lese-Modus ('r')
- with open(filename, 'r') as file:
- lines = file.readlines()
-
- timelist = [[] for i in range(3)]
-
- for i in range(0, len(lines)):
- if (i + 1) % 2 == 0:
- timelist[1].append(lines[i])
- else:
- timelist[0].append(lines[i])
- if len(timelist[0]) > len(timelist[1]):
- timelist[1].append("")
-
- for i in range(0, len(timelist[0])):
- timelist[2].append(int(timelist[1][i])-int(timelist[0][i]))
-
- for i in range(0, len(timelist[0])):
- print(convert_timestamp(timelist[0][i], "%d.%m.%Y %H:%M") + " - " + convert_timestamp(timelist[1][i], "%d.%m.%Y %H:%M") + " Dauer: " + convert_duration(timelist[2][i]))
-
-# Timestamp konvertieren
-def convert_timestamp(timestamp, format):
- try:
- return str(datetime.datetime.fromtimestamp(int(timestamp)).strftime(format))
- except:
- return ("...")
-
-#Zeitdauerdarstellung berechnen
-def convert_duration(duration):
- hours = int(duration / 3600)
- minutes = int((duration - hours * 3600) / 60)
- seconds = int(duration - hours * 3600 - minutes * 60)
-
- return(f"{hours:02d}" + ":" + f"{minutes:02d}" + ":" + f"{seconds:02d}")
\ No newline at end of file
diff --git a/tkinter Demo.py b/tkinter Demo.py
deleted file mode 100644
index 2597100..0000000
--- a/tkinter Demo.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import tkinter as tk
-
-def aktionSF():
- label3 = tk.Label(root, text="Aktion durchgeführt", bg="yellow")
- label3.pack()
-
-root = tk.Tk()
-
-label1 = tk.Label(root, text="Hallo Welt", bg="orange")
-label1.pack()
-
-schaltf1 = tk.Button(root, text="Aktion durchführen", command=aktionSF)
-schaltf1.pack()
-
-root.mainloop()
diff --git a/ui.py b/ui.py
deleted file mode 100644
index 809ffdb..0000000
--- a/ui.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# Zeiterfassung
-# UI
-from definitions import *
-
-import tkinter as tk
-from time import strftime
-from timestamping import *
-from users import determine_filename
-import locale
-
-locale.setlocale(locale.LC_ALL, '')
-
-def update_time():
- string_time = strftime('%A, der %d.%m.%Y - %H:%M:%S')
- global digital_clock
- digital_clock.config(text=string_time)
- digital_clock.after(1000, update_time)
-
-def ui_stempeln(line_index, user):
- append_timestamp(determine_filename(user))
-
- global buttons
- global in_time_labels
- global out_time_labels
-
- last_timestamps = get_last_2_timestmaps(determine_filename(user))
-
- if stempel_zustand(determine_filename(user)) == status_out:
- buttons[line_index].configure(relief="raised", bg="red", text=user + "\n" + status_out)
- in_time_labels[line_index].configure(text=convert_timestamp(last_timestamps[0],"%H:%M"))
- out_time_labels[line_index].configure(text=convert_timestamp(last_timestamps[-1], "%H:%M"))
- elif stempel_zustand(determine_filename(user)) == status_in:
- buttons[line_index].configure(relief="sunken", bg="green", text=user + "\n" + status_in)
- in_time_labels[line_index].configure(text=convert_timestamp(last_timestamps[-1], "%H:%M"))
- out_time_labels[line_index].configure(text="")
- else:
- buttons[line_index].configure(bg="yellow", text="Fehler")
-
-def win_stempeln(userlist):
- stempeln = tk.Tk()
- stempeln.title(program_name + " " + program_version)
- #stempeln.geometry("600x400")
- stempeln.minsize(width=200, height=200)
-
- global buttons
- global in_time_labels
- global out_time_labels
- global digital_clock
-
- buttons = [ ]
- in_time_labels = [ ]
- out_time_labels = [ ]
-
- button_index = 0
-
- digital_clock = tk.Label(stempeln)
- digital_clock.grid(column=1, row=0)
-
- update_time()
-
- frame_stempeln = tk.Frame(stempeln, borderwidth=5, relief="ridge", padx=10, pady=10)
- frame_stempeln.grid(row=1, column=1)
-
- tk.Label(frame_stempeln, text="Benutzer:", anchor="w", width=10).grid(row=0, column=1, sticky="w")
- tk.Label(frame_stempeln, text="Gekommen:").grid(row=0, column=2)
- tk.Label(frame_stempeln, text="Gegangen:").grid(row=0, column=3)
-
- #Schleife zur Erzeugung von Stempelzeilen für User
- for i in userlist:
- button = tk.Button(frame_stempeln, height=3, compound="left", command=lambda b=button_index, user=i: ui_stempeln(b, user))
- in_time = tk.Label(frame_stempeln, padx=10)
- out_time = tk.Label(frame_stempeln, padx=10)
- if len_timestamps(determine_filename(i)) % 2 == 0:
- button.configure(relief="raised", bg ="red", text=i + "\n" + status_out)
- else:
- button.configure(relief="sunken", bg="green", text=i + "\n" + status_in)
- button.grid(row=button_index+1, column=1, sticky="ew")
- in_time.grid(row=button_index+1, column=2)
- out_time.grid(row=button_index+1, column=3)
- buttons.append(button)
- in_time_labels.append(in_time)
- out_time_labels.append(out_time)
- button_index+=1
-
- stempeln.mainloop()
-
-win_stempeln( ["testuser", "testuser2"])
\ No newline at end of file
diff --git a/users.py b/users.py
deleted file mode 100644
index e0c5f94..0000000
--- a/users.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# Zeiterfassung
-# Benutzerfunktionen
-
-import os
-import datetime
-
-from definitions import *
-
-# Benutzer anhand Verzeichnisse auflisten
-def list_users():
- users = [d for d in os.listdir(userfolder) if os.path.isdir(os.path.join(userfolder, d))]
- return users
-
-# Dateinamen bestimmen
-def determine_filename(user, type="stamping"):
- if type == "stamping":
- year = str(datetime.datetime.now().year)
- month = str(datetime.datetime.now().month)
- completepath = userfolder + "/" + user + "/" + year + "-" + month + ".txt"
- return completepath
-
- elif type == "settings":
- completepath = userfolder +"/" + user + "/" + usersettingsfilename
- return completepath
- elif type == "photo":
- completepath = userfolder + "/" + user + "/" + photofilename
- return completepath
diff --git a/users/filler2/2025-5.json b/users/filler2/2025-5.json
deleted file mode 100644
index b7881be..0000000
--- a/users/filler2/2025-5.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "archived": 0,
- "total_hours": 0
-}
\ No newline at end of file
diff --git a/users/filler2/2025-5.txt b/users/filler2/2025-5.txt
deleted file mode 100644
index 4d67d1a..0000000
--- a/users/filler2/2025-5.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-1747642816
-1747642898
-1747642972
-1747642976
-1747643508
-1747643521
-1747643564
-1747643566
-1747643603
-1747644615
diff --git a/users/filler2/settings.json b/users/filler2/settings.json
deleted file mode 100644
index 9bf84fb..0000000
--- a/users/filler2/settings.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "username": "filler2",
- "fullname": "filler2",
- "password": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "workhours": {
- "2025-05-16": {
- "1": 0,
- "2": 0,
- "3": 0,
- "4": 0,
- "5": 0,
- "6": 0,
- "7": 0,
- "vacation": 0
- }
- },
- "api_key": "43ec918e7d773cb23ab3113d18059a83fee389ac"
-}
\ No newline at end of file
diff --git a/users/filler3/2025-5.json b/users/filler3/2025-5.json
deleted file mode 100644
index b7881be..0000000
--- a/users/filler3/2025-5.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "archived": 0,
- "total_hours": 0
-}
\ No newline at end of file
diff --git a/users/filler3/2025-5.txt b/users/filler3/2025-5.txt
deleted file mode 100644
index e831471..0000000
--- a/users/filler3/2025-5.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-1747391900
-1747391907
diff --git a/users/filler3/settings.json b/users/filler3/settings.json
deleted file mode 100644
index 07e5ee7..0000000
--- a/users/filler3/settings.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "username": "filler3",
- "fullname": "filler3",
- "password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
- "workhours": {
- "2025-05-16": {
- "1": "6",
- "2": "6",
- "3": "6",
- "4": "6",
- "5": "6",
- "6": 0,
- "7": 0,
- "vacation": 0
- }
- },
- "api_key": "9e3f37809cd898a3db340c453df53bd0793a99fa"
-}
\ No newline at end of file
diff --git a/users/filler4/2025-5.txt b/users/filler4/2025-5.txt
deleted file mode 100644
index e69de29..0000000
diff --git a/users/filler4/settings.json b/users/filler4/settings.json
deleted file mode 100644
index a657bde..0000000
--- a/users/filler4/settings.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "username": "filler4",
- "fullname": "filler4",
- "password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
- "api_key": "614e31aab9fcf1373558f100cb2c7a9918349eec",
- "workhours": {
- "2025-05-16": {
- "1": 0,
- "2": 0,
- "3": 0,
- "4": 0,
- "5": 0,
- "6": 0,
- "7": 0,
- "vacation": 0
- }
- }
-}
\ No newline at end of file
diff --git a/users/filler5/2025-5.txt b/users/filler5/2025-5.txt
deleted file mode 100644
index e69de29..0000000
diff --git a/users/filler5/settings.json b/users/filler5/settings.json
deleted file mode 100644
index 3b45fe5..0000000
--- a/users/filler5/settings.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "username": "filler5",
- "fullname": "filler5",
- "password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
- "api_key": "ad32682beb4e19f78efc1bdae259aee3ccbf9883",
- "workhours": {
- "2025-05-16": {
- "1": 0,
- "2": 0,
- "3": 0,
- "4": 0,
- "5": 0,
- "6": 0,
- "7": 0,
- "vacation": 0
- }
- }
-}
\ No newline at end of file
diff --git a/users/filler6/2025-5.txt b/users/filler6/2025-5.txt
deleted file mode 100644
index e69de29..0000000
diff --git a/users/filler6/settings.json b/users/filler6/settings.json
deleted file mode 100644
index 50c28c8..0000000
--- a/users/filler6/settings.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "username": "filler6",
- "fullname": "filler6",
- "password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
- "api_key": "68d974e4ed516795d48d5cb8b7dc8b8ca4144a9b",
- "workhours": {
- "2025-05-16": {
- "1": 0,
- "2": 0,
- "3": 0,
- "4": 0,
- "5": 0,
- "6": 0,
- "7": 0,
- "vacation": 0
- }
- }
-}
\ No newline at end of file
diff --git a/users/testuser/2025-4.txt b/users/testuser/2025-4.txt
deleted file mode 100644
index b26b981..0000000
--- a/users/testuser/2025-4.txt
+++ /dev/null
@@ -1,112 +0,0 @@
-1743965819
-1743965909
-1743966022
-1743966045
-1743966047
-1743966049
-1743967346
-1744024713
-1744024974
-1744107474
-1744194019
-1744194047
-1744194048
-1744194049
-1744194602
-1744194612
-1744194613
-1744194613
-1744194614
-1744194709
-1744196085
-1744196089
-1744196089
-1744196120
-1744196189
-1744196243
-1744196307
-1744196315
-1744196324
-1744196333
-1744196334
-1744196335
-1744196337
-1744196338
-1744196339
-1744196340
-1744196342
-1744196343
-1744196344
-1744196345
-1744196346
-1744196500
-1744196505
-1744196506
-1744196507
-1744196663
-1744196665
-1744196667
-1744196667
-1744196719
-1744196721
-1744196991
-1744197071
-1744197205
-1744197211
-1744197213
-1744197215
-1744197217
-1744197218
-1744197219
-1744197253
-1744197300
-1744197301
-1744197302
-1744197303
-1744197765
-1744197766
-1744197769
-1744197868
-1744197871
-1744198070
-1744198071
-1744198392
-1744198393
-1744210902
-1744210904
-1744221414
-1744221415
-1744221946
-1744221947
-1744222133
-1744222135
-1744260500
-1744260507
-1744266057
-1744266059
-1744266162
-1744266164
-1744266742
-1744266745
-1744266778
-1744266779
-1744266990
-1744266993
-1744267045
-1744267050
-1744269810
-1744269812
-1744269823
-1744269829
-1744269915
-1744269919
-1744269921
-1744269922
-1744269957
-1744269959
-1744269961
-1744269971
-1744269973
-1744269974
-1744270075
-1744270081
diff --git a/users/testuser/photo.jpg b/users/testuser/photo.jpg
deleted file mode 100644
index dcaa401..0000000
Binary files a/users/testuser/photo.jpg and /dev/null differ
diff --git a/users/testuser/photo.png b/users/testuser/photo.png
deleted file mode 100644
index 3f3fc3c..0000000
Binary files a/users/testuser/photo.png and /dev/null differ
diff --git a/users/testuser/settings.json b/users/testuser/settings.json
deleted file mode 100644
index b6c1df5..0000000
--- a/users/testuser/settings.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "username": "testuser",
- "name": "Der neue Tester",
- "password": "123456789",
- "2024-04-01": {
- "0": "0",
- "1": "8",
- "2": "8",
- "3": "8",
- "4": "8",
- "5": "8",
- "6": "0",
- "vacation": "30"
- },
- "2024-04-07": {
- "0": "0",
- "1": "6",
- "2": "6",
- "3": "6",
- "4": "8",
- "5": "6",
- "6": "0",
- "vacation": "28"
- }
-}
\ No newline at end of file
diff --git a/users/testuser2/2025-4.txt b/users/testuser2/2025-4.txt
deleted file mode 100644
index 8a73420..0000000
--- a/users/testuser2/2025-4.txt
+++ /dev/null
@@ -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
diff --git a/users/testuser2/photo.jpg b/users/testuser2/photo.jpg
deleted file mode 100644
index 57c5a04..0000000
Binary files a/users/testuser2/photo.jpg and /dev/null differ
diff --git a/users/testuser2/photo.png b/users/testuser2/photo.png
deleted file mode 100644
index bc5a185..0000000
Binary files a/users/testuser2/photo.png and /dev/null differ