"No Time Users" hinzugefügt.

This commit is contained in:
Alexander Malzkuhn 2025-10-27 13:47:57 +01:00
parent 47a71ebf44
commit 0b517f2b30
9 changed files with 170 additions and 240 deletions

View File

@ -5,9 +5,9 @@ import os
server = 'gitea.am-td.de' server = 'gitea.am-td.de'
server_user = 'alexander' server_user = 'alexander'
if os.getuid() == 0: #if os.getuid() == 0:
subprocess.run(["docker", "build", "--force-rm", "-t", f"{server}/{server_user}/{app_title.lower()}:{app_version}", "."]) subprocess.run(["docker", "build", "--force-rm", "-t", f"{server}/{server_user}/{app_title.lower()}:{app_version}", "."])
if input("docker-compose erstellen j=JA ") == "j": if input("docker-compose erstellen j=JA ") == "j":
userfolder = input("Pfad für Benutzerdaten /users:") userfolder = input("Pfad für Benutzerdaten /users:")
backupfolder = input("Pfad für Backupdaten /backup:") backupfolder = input("Pfad für Backupdaten /backup:")
settingsfolder = input("Pfad für Einstellungen /settings:") settingsfolder = input("Pfad für Einstellungen /settings:")
@ -27,5 +27,5 @@ services:
with open('docker-compose.yml', 'w') as docker_compose: with open('docker-compose.yml', 'w') as docker_compose:
docker_compose.write(docker_compose_content) docker_compose.write(docker_compose_content)
else: #else:
print("Es werden Root-Rechte benötigt.") # print("Es werden Root-Rechte benötigt.")

View File

@ -4,6 +4,7 @@ import dateutil.easter
from dateutil.easter import * from dateutil.easter import *
from nicegui import ui, app, events from nicegui import ui, app, events
from nicegui.functions.navigate import navigate
from nicegui.html import button from nicegui.html import button
from nicegui.events import KeyEventArguments from nicegui.events import KeyEventArguments
@ -69,6 +70,13 @@ def page_admin():
def update_userlist(): def update_userlist():
nonlocal userlist nonlocal userlist
userlist = list_users() 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() update_userlist()
@ -763,19 +771,27 @@ def page_admin():
with ui.tab_panel(settings): with ui.tab_panel(settings):
with ui.grid(columns='auto auto'): with ui.grid(columns='auto auto'):
with ui.card(): with ui.card():
ui.label("Administrationsbenutzer:").classes('text-bold') ui.label("Benutzereinstellungen:").classes('text-bold')
with ui.grid(columns=2).classes('items-baseline'): with ui.grid(columns=2).classes('items-center'):
ui.label("Benutzer mit Administrationsrechten:") ui.label("Benutzer mit Administrationsrechten:")
user_switch_list = [] user_switch_list = []
with ui.element(): with ui.grid(columns=2).classes('gap-y-0'):
for i in list_users(): for i in list_users():
user_switch_list.append(ui.switch(i)) user_switch_list.append(ui.switch(i))
for i in user_switch_list: for i in user_switch_list:
if i.text in get_admin_list(): if i.text in get_admin_list():
i.value = True i.value = True
secret = data["secret"] 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(): with ui.card():
ui.label("Systemeinstellungen:").classes('text-bold') ui.label("Systemeinstellungen:").classes('text-bold')
with ui.grid(columns=2).classes('items-baseline'): with ui.grid(columns=2).classes('items-baseline'):
@ -1064,7 +1080,17 @@ def page_admin():
dialog.open() dialog.open()
else: 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("admin_user", admin_users)
write_adminsetting("no_time_users", no_time_users)
write_adminsetting("port", port.value) write_adminsetting("port", port.value)
write_adminsetting("secret", secret) write_adminsetting("secret", secret)
write_adminsetting("touchscreen", touchscreen_switch.value) write_adminsetting("touchscreen", touchscreen_switch.value)
@ -1086,6 +1112,12 @@ def page_admin():
reset_visibility.value = False reset_visibility.value = False
timetable.refresh() 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.button("Speichern", on_click=save_admin_settings):
with ui.tooltip(): 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') 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: else:
delete_info.value = False delete_info.value = False
delete_binder.value = True delete_binder.value = True
print(delete_info.value)
print(delete_binder.value)
api_key_input.value = current_user.api_key api_key_input.value = current_user.api_key
api_link_column.clear() api_link_column.clear()
@ -1183,7 +1213,7 @@ def page_admin():
ui.label("Benutzername wurde geändert.").classes('text-bold') ui.label("Benutzername wurde geändert.").classes('text-bold')
ui.label(f"Benutzerdaten werden in den neuen Ordner {username_input.value} verschoben.") ui.label(f"Benutzerdaten werden in den neuen Ordner {username_input.value} verschoben.")
ui.label("Sollen die Einstellungen gespeichert werden?") 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("Speichern", on_click=save_settings)
ui.button("Abbrechen", on_click=dialog.close) ui.button("Abbrechen", on_click=dialog.close)
dialog.open() dialog.open()
@ -1236,9 +1266,9 @@ def page_admin():
with ui.dialog() as dialog, ui.card(): with ui.dialog() as dialog, ui.card():
ui.label("Sollen die Änderungen an den Arbeitsstunden und/oder Urlaubstagen gespeichert werden?") 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("Speichern", on_click=save_settings)
ui.button("Abrrechen", on_click=dialog.close) ui.button("Abbrechen", on_click=dialog.close)
dialog.open() dialog.open()
def delete_workhour_entry(): def delete_workhour_entry():
@ -1342,6 +1372,7 @@ 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.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:') ui.label('Aufruf zum Stempeln:')
global api_link_column global api_link_column
with ui.expansion("").classes('w-full'):
with ui.column().classes('gap-0') as api_link_column: with ui.column().classes('gap-0') as api_link_column:
global stamp_link global stamp_link
stamp_link = [ ] stamp_link = [ ]

View File

@ -38,6 +38,7 @@ status_out = "ausgestempelt"
standard_adminsettings = { "admin_user": { standard_adminsettings = { "admin_user": {
0: "admin"}, 0: "admin"},
"no_time_users": { },
"port": "8090", "port": "8090",
"secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise", "secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise",
"times_on_touchscreen": True, "times_on_touchscreen": True,

View File

@ -41,15 +41,15 @@ def page_touchscreen():
def set_columns(width): def set_columns(width):
nonlocal number_of_columns nonlocal number_of_columns
if width > 1400: if width > 1400:
number_of_columns = 6
elif width > 1200:
number_of_columns = 5 number_of_columns = 5
elif width > 900: elif width > 1200:
number_of_columns = 4 number_of_columns = 4
elif width > 750: elif width > 900:
number_of_columns = 3 number_of_columns = 3
else: elif width > 750:
number_of_columns = 2 number_of_columns = 2
else:
number_of_columns = 1
user_buttons.refresh() user_buttons.refresh()
ui.on('resize', lambda e: set_columns(e.args['width'])) ui.on('resize', lambda e: set_columns(e.args['width']))
@ -71,11 +71,12 @@ def page_touchscreen():
</script> </script>
''') ''')
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: for name in userlist:
current_user = user(name) 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 current_button:
with ui.grid(columns='1fr 1fr').classes('w-full h-full py-5 items-start'): with ui.grid(columns='1fr 1fr').classes('w-full h-full py-5 items-start'):
@ -120,7 +121,7 @@ def page_touchscreen():
<path style="fill:#D61E1E;" d="M85.85,60.394c-9.086,7.86-17.596,16.37-25.456,25.456l349.914,349.914 <path style="fill:#D61E1E;" d="M85.85,60.394c-9.086,7.86-17.596,16.37-25.456,25.456l349.914,349.914
c9.086-7.861,17.596-16.37,25.456-25.456L85.85,60.394z"/> c9.086-7.861,17.596-16.37,25.456-25.456L85.85,60.394z"/>
</svg>''' </svg>'''
ui.html(no_photo_svg) ui.html(no_photo_svg, sanitize=False)
with ui.column().classes('' if admin_settings["photos_on_touchscreen"] else 'col-span-2'): 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') ui.label(current_user.fullname).classes('text-left text-xl text.bold')
if admin_settings["times_on_touchscreen"]: if admin_settings["times_on_touchscreen"]:

View File

@ -583,3 +583,8 @@ def get_admin_list():
admin_list = load_adminsettings()["admin_user"] admin_list = load_adminsettings()["admin_user"]
return admin_list.values() 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()

View File

@ -41,6 +41,7 @@ class login_mask:
if username.value in get_admin_list(): if username.value in get_admin_list():
current_user = user(username.value) current_user = user(username.value)
if hash_password(password.value) == current_user.password: if hash_password(password.value) == current_user.password:
if not username.value in get_no_time_users_list():
with ui.dialog() as forward_dialog, ui.card(): with ui.dialog() as forward_dialog, ui.card():
ui.label("Wollen Sie den Administrationsbereich oder den Datenbereich aufrufen?") ui.label("Wollen Sie den Administrationsbereich oder den Datenbereich aufrufen?")
def admin_area(): def admin_area():
@ -49,12 +50,14 @@ class login_mask:
def time_area(): def time_area():
app.storage.user['active_user'] = current_user.username app.storage.user['active_user'] = current_user.username
ui.navigate.to(self.target) ui.navigate.to(self.target)
with ui.grid(columns=2): with ui.grid(columns=2).classes('justify-center w-full'):
ui.button("Administrationsbereich", on_click=admin_area) ui.button("Administrationsbereich", on_click=admin_area)
ui.button("Datenbereich", on_click=time_area) ui.button("Datenbereich", on_click=time_area)
forward_dialog.open() forward_dialog.open()
#ui.navigate.to("/admin") else:
app.storage.user['admin_authenticated'] = True
ui.navigate.to("/admin")
else: else:
ui.notify("Login fehlgeschlagen") ui.notify("Login fehlgeschlagen")
else: else:

View File

@ -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

View File

@ -3,13 +3,16 @@
"0": "admin", "0": "admin",
"1": "admin2" "1": "admin2"
}, },
"no_time_users": {
"0": "admin"
},
"port": "8090", "port": "8090",
"secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise", "secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise",
"times_on_touchscreen": true, "times_on_touchscreen": true,
"photos_on_touchscreen": true, "photos_on_touchscreen": true,
"touchscreen": true, "touchscreen": true,
"picture_height": 200, "picture_height": 200,
"button_height": 300, "button_height": "300",
"user_notes": true, "user_notes": true,
"vacation_application": true, "vacation_application": true,
"backup_folder": "/home/alexander/Dokumente/Python/Zeiterfassung/backup", "backup_folder": "/home/alexander/Dokumente/Python/Zeiterfassung/backup",

17
settings/settings.json Normal file
View File

@ -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": {}
}