diff --git a/create_docker.py b/create_docker.py
index e7e6170..d3266a9 100644
--- a/create_docker.py
+++ b/create_docker.py
@@ -5,13 +5,13 @@ import os
server = 'gitea.am-td.de'
server_user = 'alexander'
-if os.getuid() == 0:
- subprocess.run(["docker", "build", "--force-rm", "-t", f"{server}/{server_user}/{app_title.lower()}:{app_version}", "."])
- if input("docker-compose erstellen j=JA ") == "j":
- userfolder = input("Pfad für Benutzerdaten /users:")
- backupfolder = input("Pfad für Backupdaten /backup:")
- settingsfolder = input("Pfad für Einstellungen /settings:")
- docker_compose_content = f'''
+#if os.getuid() == 0:
+subprocess.run(["docker", "build", "--force-rm", "-t", f"{server}/{server_user}/{app_title.lower()}:{app_version}", "."])
+if input("docker-compose erstellen j=JA ") == "j":
+ userfolder = input("Pfad für Benutzerdaten /users:")
+ backupfolder = input("Pfad für Backupdaten /backup:")
+ settingsfolder = input("Pfad für Einstellungen /settings:")
+ docker_compose_content = f'''
services:
zeiterfassung:
image: {server}/{server_user}/{app_title.lower()}:{app_version.lower()}
@@ -25,7 +25,7 @@ services:
- {backupfolder}:/backup
- {settingsfolder}:/settings'''
- with open('docker-compose.yml', 'w') as docker_compose:
- docker_compose.write(docker_compose_content)
-else:
- print("Es werden Root-Rechte benötigt.")
+ with open('docker-compose.yml', 'w') as docker_compose:
+ docker_compose.write(docker_compose_content)
+#else:
+# print("Es werden Root-Rechte benötigt.")
diff --git a/lib/admin.py b/lib/admin.py
index 17c3df9..4804882 100644
--- a/lib/admin.py
+++ b/lib/admin.py
@@ -4,6 +4,7 @@ import dateutil.easter
from dateutil.easter import *
from nicegui import ui, app, events
+from nicegui.functions.navigate import navigate
from nicegui.html import button
from nicegui.events import KeyEventArguments
@@ -69,6 +70,13 @@ def page_admin():
def update_userlist():
nonlocal userlist
userlist = list_users()
+ # Benutzerliste um No Time Users bereinigen
+ users_to_remove = [ ]
+ for entry in userlist:
+ if entry in get_no_time_users_list():
+ users_to_remove.append(entry)
+ for i in users_to_remove:
+ userlist.remove(i)
update_userlist()
@@ -763,19 +771,27 @@ def page_admin():
with ui.tab_panel(settings):
with ui.grid(columns='auto auto'):
with ui.card():
- ui.label("Administrationsbenutzer:").classes('text-bold')
- with ui.grid(columns=2).classes('items-baseline'):
+ ui.label("Benutzereinstellungen:").classes('text-bold')
+ with ui.grid(columns=2).classes('items-center'):
ui.label("Benutzer mit Administrationsrechten:")
user_switch_list = []
- with ui.element():
+ with ui.grid(columns=2).classes('gap-y-0'):
for i in list_users():
user_switch_list.append(ui.switch(i))
for i in user_switch_list:
if i.text in get_admin_list():
i.value = True
secret = data["secret"]
-
+ ui.separator().classes('col-span-2')
+ ui.label("Benutzer ohne Zeiterfassung:")
+ no_time_user_switch_list = [ ]
+ with ui.grid(columns=2).classes('gap-y-0'):
+ for i in list_users():
+ no_time_user_switch_list.append(ui.switch(i))
+ for item in no_time_user_switch_list:
+ if item.text in get_no_time_users_list():
+ item.value = True
with ui.card():
ui.label("Systemeinstellungen:").classes('text-bold')
with ui.grid(columns=2).classes('items-baseline'):
@@ -1064,7 +1080,17 @@ def page_admin():
dialog.open()
else:
+ no_time_users = { }
+ no_time_users_counter = -1
+ for i in no_time_user_switch_list:
+ if i.value == True:
+ no_time_users_counter += 1
+ no_time_users[str(no_time_users_counter)] = i.text
+
+ old_no_time_users_list = get_no_time_users_list()
+
write_adminsetting("admin_user", admin_users)
+ write_adminsetting("no_time_users", no_time_users)
write_adminsetting("port", port.value)
write_adminsetting("secret", secret)
write_adminsetting("touchscreen", touchscreen_switch.value)
@@ -1086,6 +1112,12 @@ def page_admin():
reset_visibility.value = False
timetable.refresh()
+ if not set(old_no_time_users_list) == set(get_no_time_users_list()):
+ with ui.dialog() as infobox, ui.card():
+ ui.label("Benutzer ohne Zeiterfassung wurden geändert. Die Seite wird neu geladen.")
+ ui.button("OK", on_click=ui.navigate.reload)
+ infobox.open()
+
with ui.button("Speichern", on_click=save_admin_settings):
with ui.tooltip():
ui.label("Hiermit werden sämtliche oben gemachten Einstellungen gespeichert.\nGgf. müssen Sie die Seite neu laden um die Auswirkungen sichtbar zu machen.").style('white-space: pre-wrap')
@@ -1109,8 +1141,6 @@ def page_admin():
else:
delete_info.value = False
delete_binder.value = True
- print(delete_info.value)
- print(delete_binder.value)
api_key_input.value = current_user.api_key
api_link_column.clear()
@@ -1183,7 +1213,7 @@ def page_admin():
ui.label("Benutzername wurde geändert.").classes('text-bold')
ui.label(f"Benutzerdaten werden in den neuen Ordner {username_input.value} verschoben.")
ui.label("Sollen die Einstellungen gespeichert werden?")
- with ui.row():
+ with ui.row().classes('w-full justify-center'):
ui.button("Speichern", on_click=save_settings)
ui.button("Abbrechen", on_click=dialog.close)
dialog.open()
@@ -1236,9 +1266,9 @@ def page_admin():
with ui.dialog() as dialog, ui.card():
ui.label("Sollen die Änderungen an den Arbeitsstunden und/oder Urlaubstagen gespeichert werden?")
- with ui.row():
+ with ui.row().classes('justify-center w-full'):
ui.button("Speichern", on_click=save_settings)
- ui.button("Abrrechen", on_click=dialog.close)
+ ui.button("Abbrechen", on_click=dialog.close)
dialog.open()
def delete_workhour_entry():
@@ -1342,11 +1372,12 @@ def page_admin():
ui.button("Neu", on_click=new_api_key).tooltip("Neuen API-Schlüssel erzeugen. Wird erst beim Klick auf Speichern übernommen und entsprechende Links und QR-Codes aktualisiert")
ui.label('Aufruf zum Stempeln:')
global api_link_column
- with ui.column().classes('gap-0') as api_link_column:
- global stamp_link
- stamp_link = [ ]
- for i in app.urls:
- stamp_link.append(ui.link(f'{i}/api/stamp/"API-Schüssel"'))
+ with ui.expansion("").classes('w-full'):
+ with ui.column().classes('gap-0') as api_link_column:
+ global stamp_link
+ stamp_link = [ ]
+ for i in app.urls:
+ stamp_link.append(ui.link(f'{i}/api/stamp/"API-Schüssel"'))
ui.label("Administratoren können nicht gelöscht werden. Um das Konto zu löschen, müssen Sie ihm zuerst die Administrationsrechte entziehen.").bind_visibility_from(delete_info, 'value').classes('font-bold text-red')
with ui.grid(columns=2):
diff --git a/lib/definitions.py b/lib/definitions.py
index 4b86e79..f2438fb 100644
--- a/lib/definitions.py
+++ b/lib/definitions.py
@@ -38,6 +38,7 @@ status_out = "ausgestempelt"
standard_adminsettings = { "admin_user": {
0: "admin"},
+ "no_time_users": { },
"port": "8090",
"secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise",
"times_on_touchscreen": True,
diff --git a/lib/touchscreen.py b/lib/touchscreen.py
index 9557552..a06beca 100644
--- a/lib/touchscreen.py
+++ b/lib/touchscreen.py
@@ -41,15 +41,15 @@ def page_touchscreen():
def set_columns(width):
nonlocal number_of_columns
if width > 1400:
- number_of_columns = 6
- elif width > 1200:
number_of_columns = 5
- elif width > 900:
+ elif width > 1200:
number_of_columns = 4
- elif width > 750:
+ elif width > 900:
number_of_columns = 3
- else:
+ elif width > 750:
number_of_columns = 2
+ else:
+ number_of_columns = 1
user_buttons.refresh()
ui.on('resize', lambda e: set_columns(e.args['width']))
@@ -71,76 +71,77 @@ def page_touchscreen():
''')
- with ui.grid(columns=number_of_columns).classes('w-full center'):
+ with ui.grid(columns=number_of_columns).classes('w-full'):
for name in userlist:
current_user = user(name)
- current_button = ui.button(on_click=lambda name=name: button_click(name)).classes(f'w-md h-full min-h-[{admin_settings["button_height"]}px]')
+ if not current_user.username in get_no_time_users_list():
+ current_button = ui.button(on_click=lambda name=name: button_click(name)).classes(f'h-full min-h-[{admin_settings["button_height"]}px]')
- with current_button:
- with ui.grid(columns='1fr 1fr').classes('w-full h-full py-5 items-start'):
+ with current_button:
+ with ui.grid(columns='1fr 1fr').classes('w-full h-full py-5 items-start'):
- if admin_settings["photos_on_touchscreen"]:
- image_size = int(admin_settings["picture_height"])
- try:
- with open(current_user.photofile, 'r') as file:
- pass
- ui.image(current_user.photofile).classes(f'max-h-[{image_size}px]').props('fit=scale-down')
- except:
- no_photo_svg = f'''
-
- '''
- ui.html(no_photo_svg)
- with ui.column().classes('' if admin_settings["photos_on_touchscreen"] else 'col-span-2'):
- ui.label(current_user.fullname).classes('text-left text-xl text.bold')
- 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 += "\n"
- ui.label(table_string).style('white-space: pre-wrap').classes('text-left')
- if current_user.stamp_status() == status_in:
- current_button.props('color=green')
- else:
- current_button.props('color=red')
- buttons[name] = current_button
+ if admin_settings["photos_on_touchscreen"]:
+ image_size = int(admin_settings["picture_height"])
+ try:
+ with open(current_user.photofile, 'r') as file:
+ pass
+ ui.image(current_user.photofile).classes(f'max-h-[{image_size}px]').props('fit=scale-down')
+ except:
+ no_photo_svg = f'''
+
+ '''
+ ui.html(no_photo_svg, sanitize=False)
+ with ui.column().classes('' if admin_settings["photos_on_touchscreen"] else 'col-span-2'):
+ ui.label(current_user.fullname).classes('text-left text-xl text.bold')
+ 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 += "\n"
+ ui.label(table_string).style('white-space: pre-wrap').classes('text-left')
+ if current_user.stamp_status() == status_in:
+ current_button.props('color=green')
+ else:
+ current_button.props('color=red')
+ buttons[name] = current_button
user_buttons()
else:
diff --git a/lib/users.py b/lib/users.py
index ae8b92a..345b440 100644
--- a/lib/users.py
+++ b/lib/users.py
@@ -583,3 +583,8 @@ def get_admin_list():
admin_list = load_adminsettings()["admin_user"]
return admin_list.values()
+def get_no_time_users_list():
+ adnin_settings = load_adminsettings()
+ admin_list = load_adminsettings()["no_time_users"]
+ return admin_list.values()
+
diff --git a/lib/web_ui.py b/lib/web_ui.py
index 816ac23..d14864a 100644
--- a/lib/web_ui.py
+++ b/lib/web_ui.py
@@ -41,20 +41,23 @@ class login_mask:
if username.value in get_admin_list():
current_user = user(username.value)
if hash_password(password.value) == current_user.password:
- with ui.dialog() as forward_dialog, ui.card():
- ui.label("Wollen Sie den Administrationsbereich oder den Datenbereich aufrufen?")
- def admin_area():
- app.storage.user['admin_authenticated'] = True
- ui.navigate.to('/admin')
- def time_area():
- app.storage.user['active_user'] = current_user.username
- ui.navigate.to(self.target)
- with ui.grid(columns=2):
- ui.button("Administrationsbereich", on_click=admin_area)
- ui.button("Datenbereich", on_click=time_area)
+ if not username.value in get_no_time_users_list():
+ with ui.dialog() as forward_dialog, ui.card():
+ ui.label("Wollen Sie den Administrationsbereich oder den Datenbereich aufrufen?")
+ def admin_area():
+ app.storage.user['admin_authenticated'] = True
+ ui.navigate.to('/admin')
+ def time_area():
+ app.storage.user['active_user'] = current_user.username
+ ui.navigate.to(self.target)
+ with ui.grid(columns=2).classes('justify-center w-full'):
+ ui.button("Administrationsbereich", on_click=admin_area)
+ ui.button("Datenbereich", on_click=time_area)
- forward_dialog.open()
- #ui.navigate.to("/admin")
+ forward_dialog.open()
+ else:
+ app.storage.user['admin_authenticated'] = True
+ ui.navigate.to("/admin")
else:
ui.notify("Login fehlgeschlagen")
else:
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 0f30e3d..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,131 +0,0 @@
-aiofiles==24.1.0
-aiohappyeyeballs==2.6.1
-aiohttp==3.11.16
-aiosignal==1.3.2
-altgraph==0.17.4
-annotated-types==0.7.0
-anyio==4.9.0
-attrs==25.3.0
-Babel==2.10.3
-beautifulsoup4==4.11.2
-bidict==0.23.1
-blinker==1.5
-Brlapi==0.8.4
-certifi==2025.1.31
-chardet==5.1.0
-charset-normalizer==3.0.1
-click==8.1.8
-crit==3.17.1
-cryptography==38.0.4
-cupshelpers==1.0
-dbus-python==1.3.2
-distro==1.8.0
-distro-info==1.5+deb12u1
-docker==5.0.3
-docker-compose==1.29.2
-dockerpty==0.4.1
-docopt==0.6.2
-docutils==0.21.2
-easygui==0.98.1
-fastapi==0.115.12
-feedgenerator==2.0.0
-frozenlist==1.5.0
-gitdb==4.0.9
-GitPython==3.1.30
-gpg==1.18.0
-h11==0.14.0
-html5lib==1.1
-httpcore==1.0.8
-httplib2==0.20.4
-httptools==0.6.4
-httpx==0.28.1
-idna==3.3
-ifaddr==0.2.0
-imaplib2==3.5
-invoke==2.0.0
-itsdangerous==2.2.0
-Jinja2==3.1.6
-jsonpointer==2.3
-jsonschema==4.10.3
-lazr.restfulclient==0.14.5
-lazr.uri==1.0.6
-louis==3.24.0
-lxml==4.9.2
-Mako==1.2.4.dev0
-Markdown==3.4.1
-markdown-it-py==2.1.0
-markdown2==2.5.3
-MarkupSafe==2.1.2
-mdurl==0.1.2
-menulibre==2.2.2
-multidict==6.4.3
-nicegui==2.15.0
-ntpsec==1.2.2
-numpy==2.2.5
-oauthlib==3.2.2
-olefile==0.46
-opencv-python==4.11.0.86
-orjson==3.10.16
-packaging==25.0
-pelican==4.8.0
-photocollage==1.4.5
-Pillow==9.4.0
-playsound3==3.2.3
-propcache==0.3.1
-protobuf==4.21.12
-pscript==0.7.7
-psutil==5.9.4
-pycairo==1.20.1
-pycups==2.0.1
-pydantic==2.11.3
-pydantic_core==2.33.1
-pyenchant==3.2.2
-Pygments==2.19.1
-PyGObject==3.42.2
-pygtkspellcheck==4.0.5
-pyinstaller==6.13.0
-pyinstaller-hooks-contrib==2025.4
-PyJWT==2.6.0
-pyparsing==3.0.9
-pyrsistent==0.18.1
-pysmbc==1.0.23
-python-apt==2.6.0
-python-dateutil==2.8.2
-python-dotenv==1.1.0
-python-engineio==4.12.0
-python-multipart==0.0.20
-python-socketio==5.13.0
-pytz==2022.7.1
-pyxdg==0.28
-PyYAML==6.0.2
-requests==2.32.3
-rfc3987==1.3.8
-rich==13.3.1
-roman==3.3
-segno==1.6.6
-simple-websocket==1.1.0
-six==1.16.0
-smmap==5.0.0
-sniffio==1.3.1
-soupsieve==2.3.2
-starlette==0.46.2
-texttable==1.6.7
-typing-inspection==0.4.0
-typing_extensions==4.13.2
-Unidecode==1.3.6
-uritemplate==4.1.1
-urllib3==2.4.0
-uvicorn==0.34.1
-uvloop==0.21.0
-vboxapi==1.0
-vbuild==0.8.2
-wadllib==1.3.6
-watchfiles==1.0.5
-webcolors==1.11.1
-webencodings==0.5.1
-websocket-client==1.2.3
-websockets==15.0.1
-wsproto==1.2.0
-wxPython==4.2.0
-xdg==5
-yarl==1.19.0
diff --git a/settings.json b/settings.json
index 68532ee..7e32a9c 100644
--- a/settings.json
+++ b/settings.json
@@ -3,13 +3,16 @@
"0": "admin",
"1": "admin2"
},
+ "no_time_users": {
+ "0": "admin"
+ },
"port": "8090",
"secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise",
"times_on_touchscreen": true,
"photos_on_touchscreen": true,
"touchscreen": true,
"picture_height": 200,
- "button_height": 300,
+ "button_height": "300",
"user_notes": true,
"vacation_application": true,
"backup_folder": "/home/alexander/Dokumente/Python/Zeiterfassung/backup",
diff --git a/settings/settings.json b/settings/settings.json
new file mode 100644
index 0000000..f60cc13
--- /dev/null
+++ b/settings/settings.json
@@ -0,0 +1,17 @@
+{
+ "admin_user": {
+ "0": "admin"
+ },
+ "port": "8090",
+ "secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise",
+ "times_on_touchscreen": true,
+ "photos_on_touchscreen": true,
+ "touchscreen": true,
+ "picture_height": 200,
+ "button_height": 300,
+ "user_notes": true,
+ "vacation_application": true,
+ "backup_folder": "/backup",
+ "backup_api_key": "0b3b1d883b915bd2f05ee4d256d97a404df67a93",
+ "holidays": {}
+}
\ No newline at end of file