Compare commits

...

15 Commits

Author SHA1 Message Date
0358ef44e4 Aufholen Master Branch 2025-10-30 14:12:34 +01:00
a602aded2c Docker Dateien angepasst 2025-10-30 14:08:15 +01:00
601fcf6b9d Merge branch 'user-management' into release 2025-10-30 13:48:42 +01:00
0720e0cbe9 Docker Settings gelöscht 2025-10-30 13:48:26 +01:00
6bdaccb46e .gitignore aktualisiert 2025-10-30 13:45:53 +01:00
e94ea84337 Vorbereitungen für nächsten Zwischenrelease 2025-10-30 13:42:37 +01:00
0b517f2b30 "No Time Users" hinzugefügt. 2025-10-27 13:47:57 +01:00
47a71ebf44 Login Seite entfernt. Ist in Hauptseite integriert
Löschsperre für Adminuser eingefügt
Kommandozeilenfunktion für Adminanlage angepasst.
2025-10-27 09:37:59 +01:00
0b478b5e2e Update auf Nicegui 3
Umstellung auf admin als User und Auswahl von Adminbenutzer
2025-10-26 21:18:23 +01:00
3035bb384c Erste TODOs definiert für die Anpassungen 2025-10-24 12:41:20 +02:00
be3e1ffb53 Merge remote-tracking branch 'origin/release' into release
# Conflicts:
#	docker-compose.yml
2025-07-09 11:13:40 +02:00
38d2d95b03 Docker Zeitzonensynchronisation hinzugefügt 2025-07-09 11:12:28 +02:00
a74a3959e4 docker-compose.yml aktualisiert 2025-06-06 12:54:52 +02:00
e7c999dccb Docker Script angepasst 2025-06-06 11:40:14 +02:00
202e157af4 Merge branch 'master' into release 2025-06-06 11:19:53 +02:00
12 changed files with 280 additions and 196 deletions

4
.gitignore vendored
View File

@ -5,6 +5,8 @@ Testplan.md
.venv
users/
backup/
settings/
Archiv/
Docker/
docker-work/
docker-work/
Wiki/

View File

@ -1,14 +1,13 @@
FROM debian:latest
FROM python:latest
RUN apt update && apt upgrade -y
RUN apt install python3 python3-pip python3.11-venv locales -y
RUN apt install locales -y
RUN mkdir /app
RUN mkdir /.venv
RUN mkdir /backup
RUN mkdir /settings
RUN python3 -m venv /.venv
RUN /.venv/bin/pip install nicegui
RUN /.venv/bin/pip install segno
RUN /.venv/bin/pip install python-dateutil
RUN pip install nicegui
RUN pip install segno
RUN pip install python-dateutil
RUN sed -i '/de_DE.UTF-8/s/^# //g' /etc/locale.gen && \
locale-gen
@ -19,4 +18,4 @@ ENV LC_ALL de_DE.UTF-8
COPY main.py /app/main.py
COPY lib /app/lib/
EXPOSE 8090
ENTRYPOINT ["/.venv/bin/python", "/app/main.py"]
ENTRYPOINT ["python", "/app/main.py"]

View File

@ -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.")

View File

@ -1,13 +1,14 @@
services:
zeiterfassung:
image: gitea.am-td.de/alexander/zeiterfassung:beta-2025.0.1
image: gitea.am-td.de/alexander/zeiterfassung:beta-2025.0.2
restart: always
ports:
- 8090:8090
environment:
- PYTHONUNBUFFERED=1
volumes:
- ./docker-work/users:/users
- ./docker-work/backup:/backup
- ./docker-work/settings:/settings
- ./users:/users
- ./backup:/backup
- ./settings:/settings
- /etc/localtime:/etc/localtime:ro

View File

@ -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
@ -44,6 +45,10 @@ def page_admin():
updates_available = ValueBinder()
updates_available.value = False
delete_binder = ValueBinder()
delete_binder.value = True
delete_info = ValueBinder()
delete_info.value = False
enabled_because_not_docker = ValueBinder
if is_docker():
@ -65,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()
@ -759,17 +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("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.label("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.")
ui.label("Benutzereinstellungen:").classes('text-bold')
with ui.grid(columns=2).classes('items-center'):
ui.label("Benutzer mit Administrationsrechten:")
user_switch_list = []
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'):
@ -1045,31 +1067,56 @@ def page_admin():
holiday_section()
def save_admin_settings():
write_adminsetting("admin_user", admin_user.value)
if admin_password.value != "":
write_adminsetting("admin_password", hash_password(admin_password.value))
else:
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"])
write_adminsetting("vacation_application", va_switch.value)
if int(old_port) != int(port.value):
admin_users = { }
admin_counter = -1
for i in user_switch_list:
if i.value == True:
admin_counter += 1
admin_users[str(admin_counter)] = i.text
if len(admin_users) == 0:
with ui.dialog() as dialog, ui.card():
ui.label(
"Damit die Porteinstellungen wirksam werden, muss der Server neu gestartet werden.")
ui.button("OK", on_click=lambda: dialog.close())
ui.label("Es wurde kein Administrationsbenutzer ausgewählt. Mindestens ein Benutzer muss Administrationsrechte haben.")
ui.button("OK", on_click=dialog.close)
dialog.open()
ui.notify("Einstellungen gespeichert")
reset_visibility.value = False
timetable.refresh()
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)
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"])
write_adminsetting("vacation_application", va_switch.value)
if int(old_port) != int(port.value):
with ui.dialog() as dialog, ui.card():
ui.label(
"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")
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():
@ -1080,7 +1127,6 @@ def page_admin():
workhours = [ ]
with ui.row():
def user_selection_changed():
try:
if user_selection.value != None:
@ -1089,7 +1135,12 @@ def page_admin():
fullname_input.value = current_user.fullname
#password_input.value = current_user.password
usersettingscard.visible = True
if current_user.username in get_admin_list():
delete_info.value = True
delete_binder.value = False
else:
delete_info.value = False
delete_binder.value = True
api_key_input.value = current_user.api_key
api_link_column.clear()
@ -1162,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()
@ -1215,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():
@ -1321,16 +1372,18 @@ 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):
ui.button("Speichern", on_click=save_user_settings).tooltip("Klicken Sie hier um die Änderungen zu speichern.")
ui.button("Löschen", on_click=del_user)
ui.button("Löschen", on_click=del_user).bind_enabled_from(delete_binder, 'value')
usersettings_card()

View File

@ -6,7 +6,7 @@ from pathlib import Path
import hashlib
app_title = "Zeiterfassung"
app_version = "beta-2025.0.1"
app_version = "beta-2025.0.2"
# Standardpfade
@ -36,8 +36,9 @@ status_out = "ausgestempelt"
# Standardadmin Settings:
standard_adminsettings = { "admin_user": "admin",
"admin_password": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918",
standard_adminsettings = { "admin_user": {
0: "admin"},
"no_time_users": { },
"port": "8090",
"secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise",
"times_on_touchscreen": True,
@ -55,9 +56,9 @@ standard_adminsettings = { "admin_user": "admin",
# Standard User Settings:
standard_usersettings = {
"username": "default",
"fullname": "Standardbenutzer",
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
"username": "admin",
"fullname": "Administrator",
"password": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918",
"api_key": "1234567890",
"workhours": { }
}

View File

@ -1,41 +0,0 @@
from datetime import datetime
from nicegui import ui, app
from lib.web_ui import *
from lib.users import *
from lib.definitions import *
from calendar import monthrange
import hashlib
import calendar
import locale
@ui.page('/login')
def page_login():
# Settingsdatei einlesen
data = load_adminsettings()
def login():
nonlocal data
if username.value == data["admin_user"]:
print(f"Input Hash: {hash_password(password.value)} gespeichert: {data['admin_password']}")
if hash_password(password.value) == data["admin_password"]:
app.storage.user['authenticated'] = True
ui.navigate.to("/admin")
else:
ui.notify("Login fehlgeschlagen")
#ui.markdown(f"## {app_title} {app_version}")
#ui.markdown("Bitte einloggen")
pageheader("Bitte einloggen:")
with ui.grid(columns=2):
ui.markdown("Benutzer:")
username = ui.input('Benutzername')
ui.markdown("Passwort:")
password = ui.input('Passwort', password=True)
ui.button(text="Login", on_click=lambda: login())

View File

@ -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():
</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:
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'''<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="{image_size/2}px" width="{image_size/2}px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 496.158 496.158" xml:space="preserve">
<path style="fill:#D61E1E;" d="M248.082,0.003C111.07,0.003,0,111.063,0,248.085c0,137.001,111.07,248.07,248.082,248.07
c137.006,0,248.076-111.069,248.076-248.07C496.158,111.062,385.088,0.003,248.082,0.003z"/>
<path style="fill:#F4EDED;" d="M248.082,39.002C132.609,39.002,39,132.602,39,248.084c0,115.463,93.609,209.072,209.082,209.072
c115.467,0,209.076-93.609,209.076-209.072C457.158,132.602,363.549,39.002,248.082,39.002z"/>
<g>
<path style="fill:#5B5147;" d="M145.23,144.237h-24.44c-3.21,0-5.819,4.741-5.819,10.605s2.609,10.611,5.819,10.611h24.44
c3.217,0,5.826-4.747,5.826-10.611C151.057,148.978,148.447,144.237,145.23,144.237z"/>
<path style="fill:#5B5147;" d="M380.289,172.06H226.545c-2.025-9.851-9.416-17.176-18.244-17.176h-92.199
c-10.403,0-18.818,10.125-18.818,22.592V328.9c0,10.254,8.314,18.581,18.58,18.581h264.425c10.262,0,18.586-8.327,18.586-18.581
V190.655C398.875,180.38,390.551,172.06,380.289,172.06z"/>
</g>
<path style="fill:#F4EDED;" d="M248.076,166.711c-51.133,0-92.604,41.462-92.604,92.602c0,51.146,41.471,92.608,92.604,92.608
c51.139,0,92.6-41.462,92.6-92.608C340.676,208.174,299.215,166.711,248.076,166.711z"/>
<path style="fill:#5B5147;" d="M248.086,171.416c-48.547,0-87.909,39.355-87.909,87.909c0,48.537,39.362,87.898,87.909,87.898
c48.543,0,87.896-39.361,87.896-87.898C335.981,210.771,296.629,171.416,248.086,171.416z"/>
<path style="fill:#F4EDED;" d="M248.611,205.005c-29.992,0-54.312,24.31-54.312,54.308c0,29.991,24.319,54.321,54.312,54.321
s54.318-24.33,54.318-54.321C302.93,229.315,278.603,205.005,248.611,205.005z"/>
<path style="fill:#5B5147;" d="M248.611,209.528c-27.494,0-49.789,22.286-49.789,49.786c0,27.494,22.295,49.798,49.789,49.798
c27.496,0,49.795-22.304,49.795-49.798C298.406,231.814,276.107,209.528,248.611,209.528z"/>
<g>
<path style="fill:#F4EDED;" d="M230.224,215.002c-14.401,0-26.065,11.674-26.065,26.067c0,14.399,11.664,26.073,26.065,26.073
c14.391,0,26.065-11.674,26.065-26.073C256.289,226.676,244.614,215.002,230.224,215.002z"/>
<path style="fill:#F4EDED;" d="M159.698,165.453h-45.712c-3.756,0-6.805,3.045-6.805,6.792v25.594c0,3.04,2.004,5.575,4.756,6.448
c0.65,0.209,1.328,0.35,2.049,0.35h45.712c3.76,0,6.793-3.04,6.793-6.798v-25.594C166.491,168.498,163.458,165.453,159.698,165.453
z"/>
</g>
<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"/>
</svg>'''
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'''<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="{image_size/2}px" width="{image_size/2}px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 496.158 496.158" xml:space="preserve">
<path style="fill:#D61E1E;" d="M248.082,0.003C111.07,0.003,0,111.063,0,248.085c0,137.001,111.07,248.07,248.082,248.07
c137.006,0,248.076-111.069,248.076-248.07C496.158,111.062,385.088,0.003,248.082,0.003z"/>
<path style="fill:#F4EDED;" d="M248.082,39.002C132.609,39.002,39,132.602,39,248.084c0,115.463,93.609,209.072,209.082,209.072
c115.467,0,209.076-93.609,209.076-209.072C457.158,132.602,363.549,39.002,248.082,39.002z"/>
<g>
<path style="fill:#5B5147;" d="M145.23,144.237h-24.44c-3.21,0-5.819,4.741-5.819,10.605s2.609,10.611,5.819,10.611h24.44
c3.217,0,5.826-4.747,5.826-10.611C151.057,148.978,148.447,144.237,145.23,144.237z"/>
<path style="fill:#5B5147;" d="M380.289,172.06H226.545c-2.025-9.851-9.416-17.176-18.244-17.176h-92.199
c-10.403,0-18.818,10.125-18.818,22.592V328.9c0,10.254,8.314,18.581,18.58,18.581h264.425c10.262,0,18.586-8.327,18.586-18.581
V190.655C398.875,180.38,390.551,172.06,380.289,172.06z"/>
</g>
<path style="fill:#F4EDED;" d="M248.076,166.711c-51.133,0-92.604,41.462-92.604,92.602c0,51.146,41.471,92.608,92.604,92.608
c51.139,0,92.6-41.462,92.6-92.608C340.676,208.174,299.215,166.711,248.076,166.711z"/>
<path style="fill:#5B5147;" d="M248.086,171.416c-48.547,0-87.909,39.355-87.909,87.909c0,48.537,39.362,87.898,87.909,87.898
c48.543,0,87.896-39.361,87.896-87.898C335.981,210.771,296.629,171.416,248.086,171.416z"/>
<path style="fill:#F4EDED;" d="M248.611,205.005c-29.992,0-54.312,24.31-54.312,54.308c0,29.991,24.319,54.321,54.312,54.321
s54.318-24.33,54.318-54.321C302.93,229.315,278.603,205.005,248.611,205.005z"/>
<path style="fill:#5B5147;" d="M248.611,209.528c-27.494,0-49.789,22.286-49.789,49.786c0,27.494,22.295,49.798,49.789,49.798
c27.496,0,49.795-22.304,49.795-49.798C298.406,231.814,276.107,209.528,248.611,209.528z"/>
<g>
<path style="fill:#F4EDED;" d="M230.224,215.002c-14.401,0-26.065,11.674-26.065,26.067c0,14.399,11.664,26.073,26.065,26.073
c14.391,0,26.065-11.674,26.065-26.073C256.289,226.676,244.614,215.002,230.224,215.002z"/>
<path style="fill:#F4EDED;" d="M159.698,165.453h-45.712c-3.756,0-6.805,3.045-6.805,6.792v25.594c0,3.04,2.004,5.575,4.756,6.448
c0.65,0.209,1.328,0.35,2.049,0.35h45.712c3.76,0,6.793-3.04,6.793-6.798v-25.594C166.491,168.498,163.458,165.453,159.698,165.453
z"/>
</g>
<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"/>
</svg>'''
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:

View File

@ -578,3 +578,13 @@ def write_adminsetting(key: str, value):
except KeyError:
print(f"Kein Einstellungsschlüssel {key} vorhanden.")
def get_admin_list():
adnin_settings = load_adminsettings()
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()

View File

@ -38,10 +38,26 @@ class login_mask:
def login():
nonlocal data
if username.value == data["admin_user"]:
if hash_password(password.value) == data["admin_password"]:
app.storage.user['admin_authenticated'] = True
ui.navigate.to("/admin")
if username.value in get_admin_list():
current_user = user(username.value)
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():
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()
else:
app.storage.user['admin_authenticated'] = True
ui.navigate.to("/admin")
else:
ui.notify("Login fehlgeschlagen")
else:

38
main.py
View File

@ -4,7 +4,6 @@ import os.path
from lib.web_ui import *
from lib.admin import *
from lib.login import *
from lib.users import *
from lib.touchscreen import *
from lib.definitions import *
@ -33,7 +32,7 @@ def main():
list_users()
homepage()
#homepage()
def startup_message():
@ -54,7 +53,7 @@ def main():
ui.toggle.default_props('rounded')
ui.row.default_classes('items-baseline')
ui.run(favicon='', port=port, storage_secret=secret, language='de-DE', show_welcome_message=False)
ui.run(root=homepage, favicon='', port=port, storage_secret=secret, language='de-DE', show_welcome_message=False)
if __name__ in ("__main__", "__mp_main__"):
parser = argparse.ArgumentParser(description=f'{app_title} {app_version}')
@ -85,11 +84,34 @@ if __name__ in ("__main__", "__mp_main__"):
print("Sollen diese Einstellungen übernommen werden? j=Ja")
question = input()
if question == "j":
admin_settings["admin_user"] = admin_user
admin_settings["admin_password"] = hash_password(admin_password)
json_dict = json.dumps(admin_settings, indent=4)
with open(os.path.join(scriptpath, usersettingsfilename), "w") as outputfile:
outputfile.write(json_dict)
if not os.path.exists(userfolder):
os.makedirs(userfolder)
print("Kein Ordner mit Benutzerdaten gefunden. Lege ihn an.")
if not os.path.exists(os.path.join(userfolder, admin_user)):
print("Benutzer existiert noch nicht. Lege ihn an.")
os.makedirs(os.path.join(userfolder, admin_user))
start_date_dt = datetime.datetime.now()
start_date = start_date_dt.strftime("%Y-%m-%d")
settings_to_write = standard_usersettings
settings_to_write["workhours"][start_date] = {}
settings_to_write["fullname"] = "Administrator"
settings_to_write["username"] = admin_user
# API-Key erzeugen
string_to_hash = f'{admin_user}_{datetime.datetime.now().timestamp()}'
hash_string = hashlib.shake_256(bytes(string_to_hash, 'utf-8')).hexdigest(20)
settings_to_write["api_key"] = hash_string
for i in range(1, 8):
settings_to_write["workhours"][start_date][str(i)] = 0
settings_to_write["workhours"][start_date]["vacation"] = 0
with open(f"{userfolder}/{admin_user}/{usersettingsfilename}", 'w') as json_file:
json_dict = json.dumps(standard_usersettings, indent=4)
json_file.write(json_dict)
current_user = user(admin_user)
current_user.password = hash_password(admin_password)
current_user.write_settings()
admin_users_list = load_adminsettings()["admin_user"]
admin_users_list[str(len(admin_users_list))] = admin_user
write_adminsetting("admin_user", admin_users_list)
print("Daten geschrieben")
quit()
else:

20
settings.json Normal file
View File

@ -0,0 +1,20 @@
{
"admin_user": {
"0": "admin"
},
"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",
"user_notes": true,
"vacation_application": true,
"backup_folder": "/home/alexander/Dokumente/Python/Zeiterfassung/backup",
"backup_api_key": "6fed93dc4a35308b2c073a8a6f3284afe1fb9946",
"holidays": {}
}