diff --git a/admin.py b/admin.py index 8d4f955..7b9bc23 100644 --- a/admin.py +++ b/admin.py @@ -1,9 +1,11 @@ from datetime import datetime import dateutil.easter +from PIL.SpiderImagePlugin import isInt from dateutil.easter import * from nicegui import ui, app, events +from nicegui.html import button from users import * from definitions import * @@ -596,47 +598,89 @@ Dies kann nicht rückgängig gemacht werden!''') button_update.move(timetable_header) with ui.tab_panel(settings): - with ui.card(): - ui.markdown("**Administrationsbenutzer:**") - with ui.grid(columns=2): - def save_admin_settings(): - output_dict = { } - output_dict["admin_user"] = admin_user.value - if admin_password.value != "": - output_dict["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["holidays"] = data["holidays"] - json_dict = json.dumps(output_dict, indent=4) - with open(f"{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.") - ui.button("OK", on_click=lambda: dialog.close()) - dialog.open() - ui.notify("Einstellungen gespeichert") - timetable.refresh() + with ui.grid(columns='auto auto'): + with ui.card(): + ui.markdown("**Administrationsbenutzer:**") + with ui.grid(columns=2): + def save_admin_settings(): + output_dict = { } + output_dict["admin_user"] = admin_user.value + if admin_password.value != "": + output_dict["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["times_on_touchscreen"] = timestamp_switch.value + output_dict["photos_on_touchscreen"] = photo_switch.value + output_dict["holidays"] = data["holidays"] + json_dict = json.dumps(output_dict, indent=4) + with open(f"{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.") + ui.button("OK", on_click=lambda: dialog.close()) + dialog.open() + ui.notify("Einstellungen gespeichert") + timetable.refresh() + + ui.markdown("Benutzername des Adminstrators") + admin_user = ui.input().tooltip("Geben Sie hier den Benutzernamen für den Adminstationsnutzer ein") + admin_user.value = data["admin_user"] + ui.markdown("Passwort des Administrators") + admin_password = ui.input(password=True).tooltip("Geben Sie hier das Passwort für den Administationsnutzer ein. Merken Sie sich dieses Passwort gut. Es kann nicht über das Webinterface zurückgesetzt werden.") + + secret = data["secret"] + + with ui.card(): + ui.markdown("**Systemeinstellungen:**") + with ui.grid(columns=2): + def check_is_number(number): + try: + number = int(number) + return True + except: + return False + + ui.markdown("Port:") + port = ui.input(validation={"Nur ganzzahlige Portnummern erlaubt": lambda value: check_is_number(value), + "Portnummer zu klein": lambda value: len(value)>=2}).tooltip("Geben Sie hier die Portnummer ein, unter der die Zeiterfassung erreichbar ist.").props('size=5') + old_port = data["port"] + port.value = old_port - ui.markdown("Benutzername des Adminstrators") - admin_user = ui.input() - admin_user.value = data["admin_user"] - ui.markdown("Passwort des Administrators") - admin_password = ui.input(password=True) - secret = data["secret"] + with ui.card(): + ui.markdown("**Einstellungen für das Stempelterminal:**") + with ui.column(): + timestamp_switch = ui.switch("Stempelzeiten anzeigen") + photo_switch = ui.switch("Fotos anzeigen") + timestamp_switch.value = bool(data["times_on_touchscreen"]) + with ui.row(): + photo_switch.value = bool(data["photos_on_touchscreen"]) + with ui.row().bind_visibility_from(photo_switch, 'value'): + ui.markdown("Maximale Bilderöhe") + picture_height_input = ui.input(validation={"Größe muss eine Ganzzahl sein.": lambda value: check_is_number(value), + "Größe muss größer 0 sein": lambda value: int(value)>0}).props('size=5') + picture_height_input.value = data["picture_height"] + ui.markdown('px') + with ui.row(): + ui.markdown("Maximale Buttonhöhe") + def compare_button_height(height): + if not photo_switch.value: + return True + elif int(height) < int(picture_height_input.value): + return False + else: + return True - with ui.card(): - ui.markdown("**Systemeinstellungen:**") - with ui.grid(columns=2): - - ui.markdown("Port:") - port = ui.input() - old_port = data["port"] - port.value = old_port + button_height_input = ui.input(validation={"Größe muss eine Ganzzahl sein.": lambda value: check_is_number(value), + "Größe muss größer 0 sein": lambda value: int(value)>0, + "Buttons dürfen nicht kleiner als die Fotos sein": lambda value: compare_button_height(value)}).props('size=5') + button_height_input.value = data["button_height"] + photo_switch.on_value_change(button_height_input.validate) + ui.markdown('px') def holiday_section(): with ui.card(): @@ -825,9 +869,9 @@ Dies kann nicht rückgängig gemacht werden!''') with ui.grid(columns='auto auto'): ui.space() with ui.row(): - ui.button("Gesetzliche Feiertage eintragen", on_click=defined_holidays) - ui.button("Eigener Eintrag", on_click=new_holiday_entry) - ui.button("Zurücksetzen", icon="undo", on_click=reset_holidays).bind_visibility_from(reset_visibility, 'value').classes('bg-red') + ui.button("Gesetzliche Feiertage eintragen", on_click=defined_holidays).tooltip("Hier können Sie automatisiert gesetzliche Feiertage in Deutschland eintragen.") + ui.button("Eigener Eintrag", on_click=new_holiday_entry).tooltip("Hier können Sie einen eigenen Feiertag definieren.") + ui.button("Zurücksetzen", icon="undo", on_click=reset_holidays).bind_visibility_from(reset_visibility, 'value').classes('bg-red').tooltip("Hier können Sie ungespeicherte Änderungen zurücknehmen.") ui.separator().classes('col-span-2') for year_entry in year_list: @@ -840,7 +884,7 @@ Dies kann nicht rückgängig gemacht werden!''') holiday_section() - ui.button("Speichern", on_click=save_admin_settings) + ui.button("Speichern", on_click=save_admin_settings).tooltip("Hiermit werden sämtliche oben gemachten Einstellungen gespeichert.") with ui.tab_panel(users): ui.markdown("###Benutzerverwaltung") @@ -1026,7 +1070,8 @@ Dies kann nicht rückgängig gemacht werden!''') with ui.dialog() as dialog, ui.card(): ui.markdown("Geben Sie den Benutzernamen für das neue Konto an:") user_name_input = ui.input(label="Benutzername", validation={'Leerer Benutzername nicht erlaubt': lambda value: len(value) != 0, - 'Leerzeichen im Benutzername nicht erlaubt': lambda value: " " not in value}) + 'Leerzeichen im Benutzername nicht erlaubt': lambda value: " " not in value, + 'Benutzername schon vergeben': lambda value: value not in userlist}).on('keypress.enter', create_new_user) with ui.row(): ui.button("OK", on_click=create_new_user) ui.button("Abbrechen", on_click=dialog.close) diff --git a/definitions.py b/definitions.py index 83260ad..fe8c4e7 100644 --- a/definitions.py +++ b/definitions.py @@ -26,6 +26,10 @@ standard_adminsettings = { "admin_user": "admin", "admin_password": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918", "port": "8090", "secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise", + "times_on_touchscreen": True, + "photos_on_touchscreen": True, + "picure_height": 200, + "button_height": 300, "holidays": { } } diff --git a/homepage.py b/homepage.py index eb322a4..2636be5 100644 --- a/homepage.py +++ b/homepage.py @@ -1,7 +1,9 @@ # Zeiterfassung import datetime -from nicegui import ui, app +from nicegui import ui, app, Client +from nicegui.page import page + from users import * from definitions import * @@ -179,4 +181,18 @@ def homepage(): ui.space() else: - login_mask() \ No newline at end of file + login_mask() + +# 404 Fehlerseite +@app.exception_handler(404) +async def exception_handler_404(request, exception: Exception): + with Client(page(''), request=request) as client: + pageheader("Fehler 404") + ui.label("Diese Seite existiert nicht.") + ui.label("Was möchten Sie tun?") + with ui.list().props('dense'): + with ui.item(): + ui.link("zur Startseite", "/") + with ui.item(): + ui.link("zum Administratrionsbereich", "/admin") + return client.build_response(request, 404) \ No newline at end of file diff --git a/settings.json b/settings.json index 2add415..d02a2c2 100644 --- a/settings.json +++ b/settings.json @@ -3,6 +3,10 @@ "admin_password": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918", "port": "8090", "secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise", + "times_on_touchscreen": true, + "photos_on_touchscreen": true, + "button_height": 300, + "picture_height": 200, "holidays": { "2025-01-01": "Neujahr", "2025-04-18": "Karfreitag", diff --git a/touchscreen.py b/touchscreen.py index f2e73c7..7f3ee85 100644 --- a/touchscreen.py +++ b/touchscreen.py @@ -26,8 +26,10 @@ def page_touchscreen(): # ui.notify(status_out) user_buttons.refresh() - pageheader("Bitte User auswählen:") + pageheader("Stempeluhr") + ui.page_title("Stempeluhr") + admin_settings = load_adminsettings() userlist = list_users() number_of_users = len(userlist) buttons = { } @@ -39,22 +41,40 @@ def page_touchscreen(): else: number_of_columns = number_of_users - with ui.grid(columns=number_of_columns): + with ui.grid(columns=number_of_columns).classes('w-full center'): for name in userlist: current_user = user(name) - current_button = ui.button(on_click=lambda name=name: button_click(name)) + current_button = ui.button(on_click=lambda name=name: button_click(name)).classes('w-md h-full min-h-[250px]') with current_button: - try: - with open(current_user.photofile, 'r') as file: + if admin_settings["photos_on_touchscreen"]: + try: + with open(current_user.photofile, 'r') as file: + pass + file.close() + ui.image(current_user.photofile).classes('max-h-[200px]').props('fit=scale-down') + except: pass - file.close() - ui.image(current_user.photofile) - except: - pass - ui.label(current_user.fullname) - if current_user.stamp_status() == status_in: - current_button.props('color=green') - else: - current_button.props('color=red') - buttons[name] = current_button + column_classes = "w-full items-center" + if admin_settings["times_on_touchscreen"] or admin_settings["photos_on_touchscreen"]: + column_classes += " self-end" + with ui.column().classes(column_classes): + if admin_settings["times_on_touchscreen"]: + todays_timestamps = current_user.get_day_timestamps() + # Wenn wir Einträge haben + if len(todays_timestamps) > 0 and admin_settings["times_on_touchscreen"]: + table_string = "" + for i in range(0, len(todays_timestamps), 2): + try: + table_string += f"{datetime.datetime.fromtimestamp(todays_timestamps[i]).strftime('%H:%M')} - {datetime.datetime.fromtimestamp(todays_timestamps[i+1]).strftime('%H:%M')}" + except IndexError: + table_string += f"{datetime.datetime.fromtimestamp(todays_timestamps[i]).strftime('%H:%M')} -" + if i < len(todays_timestamps) - 2: + table_string += ", " + ui.markdown(table_string) + ui.label(current_user.fullname).classes('text-center') + if current_user.stamp_status() == status_in: + current_button.props('color=green') + else: + current_button.props('color=red') + buttons[name] = current_button user_buttons() \ No newline at end of file diff --git a/users.py b/users.py index 67f934f..bce4f90 100644 --- a/users.py +++ b/users.py @@ -416,10 +416,10 @@ class user: def delete_photo(self): os.remove(self.photofile) - def get_worked_time(self, year, month, day): + def get_day_timestamps(self, year=datetime.datetime.now().year, month=datetime.datetime.now().month, day=datetime.datetime.now().day): timestamps = self.get_timestamps(year, month) check_day_dt = datetime.datetime(year, month, day) - todays_timestamps = [ ] + todays_timestamps = [] for i in timestamps: i_dt = datetime.datetime.fromtimestamp(int(i)) @@ -427,6 +427,13 @@ class user: todays_timestamps.append(int(i)) todays_timestamps.sort() + + return todays_timestamps + + def get_worked_time(self, year=datetime.datetime.now().year, month=datetime.datetime.now().month, day=datetime.datetime.now().day): + + todays_timestamps = self.get_day_timestamps(year, month, day) + if len(todays_timestamps) % 2 == 0: workrange = len(todays_timestamps) in_time_stamp = -1 @@ -460,8 +467,8 @@ def list_users(): def new_user(username: str): if not os.path.exists(userfolder): os.makedirs(userfolder) - if not os.path.exists(f"{userfolder}/{username}"): - os.makedirs(f"{userfolder}/{username}") + if not os.path.exists(os.path.join(userfolder, username)): + os.makedirs(os.path.join(userfolder, username)) start_date_dt = datetime.datetime.now() start_date = start_date_dt.strftime("%Y-%m-%d") settings_to_write = standard_usersettings diff --git a/users/filler2/2025-5.txt b/users/filler2/2025-5.txt new file mode 100644 index 0000000..e69de29 diff --git a/users/filler2/settings.json b/users/filler2/settings.json new file mode 100644 index 0000000..00f7712 --- /dev/null +++ b/users/filler2/settings.json @@ -0,0 +1,18 @@ +{ + "username": "filler2", + "fullname": "filler2", + "password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f", + "api_key": "0f36286bf8c96de1922ab41e2682ba5a81793525", + "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/filler3/2025-5.json b/users/filler3/2025-5.json new file mode 100644 index 0000000..b7881be --- /dev/null +++ b/users/filler3/2025-5.json @@ -0,0 +1,4 @@ +{ + "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 new file mode 100644 index 0000000..e831471 --- /dev/null +++ b/users/filler3/2025-5.txt @@ -0,0 +1,2 @@ +1747391900 +1747391907 diff --git a/users/filler3/settings.json b/users/filler3/settings.json new file mode 100644 index 0000000..b3fb959 --- /dev/null +++ b/users/filler3/settings.json @@ -0,0 +1,18 @@ +{ + "username": "filler3", + "fullname": "filler3", + "password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f", + "api_key": "9e3f37809cd898a3db340c453df53bd0793a99fa", + "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/filler4/2025-5.txt b/users/filler4/2025-5.txt new file mode 100644 index 0000000..e69de29 diff --git a/users/filler4/settings.json b/users/filler4/settings.json new file mode 100644 index 0000000..a657bde --- /dev/null +++ b/users/filler4/settings.json @@ -0,0 +1,18 @@ +{ + "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 new file mode 100644 index 0000000..e69de29 diff --git a/users/filler5/settings.json b/users/filler5/settings.json new file mode 100644 index 0000000..3b45fe5 --- /dev/null +++ b/users/filler5/settings.json @@ -0,0 +1,18 @@ +{ + "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 new file mode 100644 index 0000000..e69de29 diff --git a/users/filler6/settings.json b/users/filler6/settings.json new file mode 100644 index 0000000..50c28c8 --- /dev/null +++ b/users/filler6/settings.json @@ -0,0 +1,18 @@ +{ + "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/testuser1/2025-5.txt b/users/testuser1/2025-5.txt index cdc85f6..4425bef 100644 --- a/users/testuser1/2025-5.txt +++ b/users/testuser1/2025-5.txt @@ -18,3 +18,15 @@ 1747302887 1747302889 1747302897 +1747386098 +1747386110 +1747387148 +1747387150 +1747387501 +1747387508 +1747387633 +1747387635 +1747387761 +1747388239 +1747388242 +1747388615 diff --git a/users/testuser10/2025-5.json b/users/testuser10/2025-5.json new file mode 100644 index 0000000..b7881be --- /dev/null +++ b/users/testuser10/2025-5.json @@ -0,0 +1,4 @@ +{ + "archived": 0, + "total_hours": 0 +} \ No newline at end of file diff --git a/users/testuser10/2025-5.txt b/users/testuser10/2025-5.txt index e69de29..2ed4771 100644 --- a/users/testuser10/2025-5.txt +++ b/users/testuser10/2025-5.txt @@ -0,0 +1,4 @@ +1747387168 +1747387171 +1747388261 +1747388617 diff --git a/users/testuser3/2025-5.txt b/users/testuser3/2025-5.txt index 4eb306a..4eff463 100644 --- a/users/testuser3/2025-5.txt +++ b/users/testuser3/2025-5.txt @@ -1,2 +1,6 @@ 1746385111 1746385118 +1747388255 +1747388619 +1747391536 +1747391567