1582 lines
106 KiB
Python
1582 lines
106 KiB
Python
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 lib.users import *
|
|
from lib.definitions import *
|
|
from calendar import monthrange
|
|
from lib.web_ui import *
|
|
|
|
import os.path
|
|
import os
|
|
import zipfile
|
|
from stat import S_IREAD, S_IRWXU
|
|
import hashlib
|
|
import calendar
|
|
import locale
|
|
import segno
|
|
|
|
@ui.page('/admin')
|
|
def page_admin():
|
|
ui.page_title(f"{app_title} {app_version}")
|
|
data = load_adminsettings()
|
|
|
|
try:
|
|
browser_cookie = app.storage.user['admin_authenticated']
|
|
except:
|
|
browser_cookie = False
|
|
|
|
# Adminseite
|
|
if browser_cookie:
|
|
pageheader("Administration")
|
|
|
|
def admin_logout():
|
|
app.storage.user.pop("admin_authenticated", None)
|
|
ui.navigate.to("/")
|
|
|
|
ui.button("Logout", on_click=admin_logout)
|
|
|
|
updates_available = ValueBinder()
|
|
updates_available.value = False
|
|
|
|
with ui.tabs() as tabs:
|
|
|
|
time_overview = ui.tab('Zeitdaten')
|
|
settings = ui.tab('Einstellungen')
|
|
users = ui.tab('Benutzer')
|
|
backups = ui.tab('Backups')
|
|
|
|
userlist = [ ]
|
|
|
|
def update_userlist():
|
|
nonlocal userlist
|
|
userlist = list_users()
|
|
|
|
update_userlist()
|
|
|
|
with ui.tab_panels(tabs, value=time_overview):
|
|
|
|
with ui.tab_panel(time_overview):
|
|
with ui.tabs() as overview_tabs:
|
|
user_month_overview = ui.tab('Monatsansicht')
|
|
user_summary = ui.tab("Zusammenfassung")
|
|
vacation_applications = ui.tab("Urlaubsanträge")
|
|
vacation_applications.set_visibility(load_adminsettings()["vacation_application"])
|
|
|
|
with ui.tab_panels(overview_tabs, value = user_month_overview):
|
|
with ui.tab_panel(user_month_overview).classes('w-full'):
|
|
ui.markdown("##Übersichten")
|
|
|
|
# Tabelle konstruieren
|
|
with ui.card().classes('w-full'):
|
|
|
|
with ui.row() as timetable_header:
|
|
year_binder = ValueBinder()
|
|
month_binder = ValueBinder()
|
|
|
|
def update_months():
|
|
current_user = user(time_user.value)
|
|
available_months = current_user.get_months(year_binder.value)
|
|
available_months_dict = { }
|
|
|
|
for element in available_months:
|
|
available_months_dict[element] = calendar.month_name[int(element)]
|
|
|
|
select_month.clear()
|
|
select_month.set_options(available_months_dict)
|
|
select_month.value = list(available_months)[0]
|
|
|
|
def update_user():
|
|
current_user = user(time_user.value)
|
|
available_years = current_user.get_years()
|
|
|
|
try:
|
|
select_year.clear()
|
|
select_year.set_options(available_years)
|
|
|
|
try:
|
|
select_year.value = str(datetime.datetime.now().year)
|
|
except:
|
|
select_year.value = list(available_years)[0]
|
|
update_months()
|
|
try:
|
|
select_month.value = datetime.datetime.now().month
|
|
except:
|
|
select_month.value = list(available_months)[0]
|
|
except NameError:
|
|
pass
|
|
|
|
ui.markdown("Benutzer:")
|
|
|
|
time_user = ui.select(options=userlist, on_change=update_user)
|
|
time_user.value = userlist[0]
|
|
user_to_select_for_start = userlist[0]
|
|
|
|
current_year = datetime.datetime.now().year
|
|
current_month = datetime.datetime.now().month
|
|
current_user = user(user_to_select_for_start)
|
|
available_years = current_user.get_years()
|
|
available_months = current_user.get_months(current_year)
|
|
available_months_dict = { }
|
|
|
|
for element in available_months:
|
|
available_months_dict[element] = calendar.month_name[int(element)]
|
|
|
|
if current_month in available_months:
|
|
set_month = current_month
|
|
else:
|
|
set_month = available_months[0]
|
|
|
|
if str(current_year) in available_years:
|
|
set_year = str(current_year)
|
|
else:
|
|
set_year = (available_years[0])
|
|
|
|
select_month = ui.select(options=available_months_dict, value=set_month).bind_value_to(month_binder, 'value')
|
|
select_year = ui.select(options=available_years, value=set_year, on_change=update_months).bind_value_to(year_binder, 'value')
|
|
|
|
month_header = ui.markdown(f"###Buchungen für **{current_user.fullname}** für **{calendar.month_name[int(select_month.value)]} {select_year.value}**")
|
|
|
|
# Tabelle aufbauen
|
|
@ui.refreshable
|
|
def timetable():
|
|
current_user = user(time_user.value)
|
|
with ui.card().classes('w-full') as calendar_card:
|
|
def update_month_and_year():
|
|
#current_user = user(time_user.value)
|
|
# Archivstatus
|
|
days_with_errors = current_user.archiving_validity_check(int(select_year.value), int(select_month.value))
|
|
with ui.grid(columns='auto auto auto 1fr 1fr 1fr 1fr').classes('w-full md:min-w-[600px] lg:min-w-[800px]') as table_grid:
|
|
if int(select_month.value) > 1:
|
|
archive_status = current_user.get_archive_status(int(select_year.value),
|
|
int(select_month.value))
|
|
else:
|
|
archive_status = current_user.get_archive_status(int(select_year.value) - 1, 12)
|
|
|
|
def revoke_archive_status():
|
|
def revoke_status():
|
|
filestring = f"{current_user.userfolder}/{int(select_year.value)}-{int(select_month.value)}"
|
|
filename = f"{filestring}.txt"
|
|
os.chmod(filename, S_IRWXU)
|
|
filename = f"{filestring}.json"
|
|
os.chmod(filename, S_IRWXU)
|
|
with open(filename, 'r') as json_file:
|
|
data = json.load(json_file)
|
|
data["archived"] = 0
|
|
|
|
json_dict = json.dumps(data)
|
|
|
|
with open(filename, "w") as outputfile:
|
|
outputfile.write(json_dict)
|
|
timetable.refresh()
|
|
dialog.close()
|
|
|
|
with ui.dialog() as dialog, ui.card():
|
|
ui.label("Soll der Archivstatus für den aktuellen Monat aufgehoben werden, damit Änderungen vorgenommen werden können?")
|
|
with ui.grid(columns=2):
|
|
ui.button("Ja", on_click=revoke_status)
|
|
ui.button("Nein", on_click=dialog.close)
|
|
dialog.open()
|
|
|
|
if archive_status == True:
|
|
with ui.row().classes('text-right col-span-7 justify-center'):
|
|
ui.button("Archiviert", on_click=revoke_archive_status).classes('bg-transparent text-black')
|
|
ui.separator()
|
|
calendar_card.classes('bg-yellow')
|
|
else:
|
|
calendar_card.classes('bg-white')
|
|
# Überschriften
|
|
ui.markdown("**Datum**")
|
|
ui.markdown("**Buchungen**")
|
|
ui.space()
|
|
ui.markdown("**Ist**")
|
|
ui.markdown("**Soll**")
|
|
ui.markdown("**Saldo**")
|
|
ui.space()
|
|
|
|
timestamps = current_user.get_timestamps(year=select_year.value, month=select_month.value)
|
|
user_absent = current_user.get_absence(year=select_year.value, month=select_month.value)
|
|
# Dictionary für sortierte Timestamps
|
|
timestamps_dict = { }
|
|
# Dictionary mit zunächst leeren Tageinträgen befüllen
|
|
for day in range(1, monthrange(int(select_year.value), int(select_month.value))[1] + 1):
|
|
# Jeder Tag bekommt eine leere Liste
|
|
timestamps_dict[day] = [ ]
|
|
|
|
# Alle Timestamps durchgehen und sie den Dictionaryeinträgen zuordnen:
|
|
for stamp in timestamps:
|
|
day_of_month_of_timestamp = int(datetime.datetime.fromtimestamp(int(stamp)).day)
|
|
timestamps_dict[day_of_month_of_timestamp].append(int(stamp))
|
|
|
|
general_saldo = 0
|
|
|
|
for day in list(timestamps_dict):
|
|
# Datum für Tabelle konstruieren
|
|
day_in_list = datetime.datetime(int(select_year.value), int(select_month.value), day)
|
|
class_content = ""
|
|
if day_in_list.date() == datetime.datetime.now().date():
|
|
class_content = 'font-bold text-red-700 uppercase'
|
|
ui.markdown(f"{day_in_list.strftime('%a')}., {day}. {calendar.month_name[int(select_month.value)]}").classes(class_content)
|
|
|
|
# Buchungen
|
|
|
|
with ui.row():
|
|
def delete_absence(day, absence_type):
|
|
def execute_deletion():
|
|
current_user.del_absence(select_year.value, select_month.value, day)
|
|
calendar_card.clear()
|
|
update_month_and_year()
|
|
dialog.close()
|
|
ui.notify("Abwesenheitseintrag gelöscht")
|
|
with ui.dialog() as dialog, ui.card():
|
|
ui.markdown(f'''Soll der Eintrag **{absence_type}** für den **{day}. {calendar.month_name[int(select_month.value)]} {select_year.value}** gelöscht werden?
|
|
|
|
Dies kann nicht rückgängig gemacht werden!''')
|
|
with ui.grid(columns=3):
|
|
ui.button("Ja", on_click=execute_deletion)
|
|
ui.space()
|
|
ui.button("Nein", on_click=dialog.close)
|
|
dialog.open()
|
|
|
|
try:
|
|
for i in list(user_absent):
|
|
if int(i) == day:
|
|
absence_button = ui.button(absence_entries[user_absent[i]]["name"], on_click=lambda i=i, day=day: delete_absence(day, absence_entries[user_absent[i]]["name"])).props(f'color={absence_entries[user_absent[i]]["color"]}')
|
|
if archive_status:
|
|
absence_button.disable()
|
|
except:
|
|
pass
|
|
|
|
day_type = ui.markdown("Kein Arbeitstag")
|
|
day_type.set_visibility(False)
|
|
|
|
# Hier werden nur die Tage mit Timestamps behandelt
|
|
if len(timestamps_dict[day]) > 0:
|
|
timestamps_dict[day].sort()
|
|
|
|
def edit_entry(t_stamp, day):
|
|
|
|
with ui.dialog() as edit_dialog, ui.card():
|
|
ui.markdown("**Eintrag bearbeiten**")
|
|
timestamp = datetime.datetime.fromtimestamp(int(t_stamp))
|
|
input_time = ui.time().props('format24h now-btn').classes('w-full justify-center')
|
|
|
|
input_time.value = timestamp.strftime('%H:%M')
|
|
|
|
def save_entry(day):
|
|
nonlocal t_stamp
|
|
t_stamp = f"{t_stamp}\n"
|
|
position = timestamps.index(t_stamp)
|
|
new_time_stamp = datetime.datetime(int(select_year.value),
|
|
int(select_month.value), day,
|
|
int(input_time.value[:2]),
|
|
int(input_time.value[-2:]))
|
|
timestamps[position] = str(
|
|
int(new_time_stamp.timestamp())) + "\n"
|
|
|
|
current_user = user(time_user.value)
|
|
current_user.write_edited_timestamps(timestamps,
|
|
select_year.value,
|
|
select_month.value)
|
|
edit_dialog.close()
|
|
calendar_card.clear()
|
|
update_month_and_year()
|
|
month_header.set_content(
|
|
f"###Buchungen für {calendar.month_name[int(select_month.value)]} {select_year.value}")
|
|
ui.notify("Eintrag gespeichert")
|
|
|
|
def del_entry():
|
|
nonlocal t_stamp
|
|
t_stamp = f"{t_stamp}\n"
|
|
timestamps.remove(t_stamp)
|
|
timestamps.sort()
|
|
current_user = user(time_user.value)
|
|
current_user.write_edited_timestamps(timestamps,
|
|
select_year.value,
|
|
select_month.value)
|
|
edit_dialog.close()
|
|
calendar_card.clear()
|
|
update_month_and_year()
|
|
month_header.set_content(
|
|
f"###Buchungen für {calendar.month_name[int(select_month.value)]} {select_year.value}")
|
|
ui.notify("Eintrag gelöscht")
|
|
|
|
with ui.row():
|
|
ui.button("Speichern",
|
|
on_click=lambda day=day: save_entry(day))
|
|
ui.button("Löschen", on_click=del_entry)
|
|
ui.button("Abbrechen", on_click=edit_dialog.close)
|
|
|
|
edit_dialog.open()
|
|
for i in range(0, len(timestamps_dict[day]), 2):
|
|
try:
|
|
temp_pair = [ timestamps_dict[day][i] , timestamps_dict[day][i+1] ]
|
|
with ui.card().classes('bg-inherit'):
|
|
with ui.row():
|
|
for j in temp_pair:
|
|
timestamp_button = ui.button(datetime.datetime.fromtimestamp(int(j)).strftime('%H:%M'), on_click=lambda t_stamp=j, day=day: edit_entry(t_stamp, day))
|
|
if archive_status:
|
|
timestamp_button.disable()
|
|
except Exception as e:
|
|
if len(timestamps_dict[day]) % 2 != 0:
|
|
with ui.card().classes('bg-inherit'):
|
|
timestamp_button = ui.button(datetime.datetime.fromtimestamp(int(timestamps_dict[day][i])).strftime('%H:%M'), on_click=lambda t_stamp=timestamps_dict[day][i], day=day: edit_entry(t_stamp, day))
|
|
if archive_status:
|
|
timestamp_button.disable()
|
|
with ui.row():
|
|
# Fehlerhinweis
|
|
if day in days_with_errors:
|
|
ui.icon('warning', color='red').tooltip("Keine Schlussbuchung").classes('text-2xl')
|
|
|
|
# Notizen anzeigen
|
|
days_notes = current_user.get_day_notes(select_year.value, select_month.value, day)
|
|
if days_notes != { }:
|
|
with ui.icon('o_description').classes('text-2xl'):
|
|
with ui.tooltip():
|
|
with ui.grid(columns='auto auto'):
|
|
for username, text in days_notes.items():
|
|
admins_name = load_adminsettings()["admin_user"]
|
|
if username == admins_name:
|
|
ui.markdown('Administrator:')
|
|
else:
|
|
ui.markdown(current_user.fullname)
|
|
ui.markdown(text)
|
|
else:
|
|
ui.space()
|
|
|
|
# Arbeitszeit Ist bestimmen
|
|
|
|
timestamps_of_this_day = []
|
|
|
|
# Suche mir alle timestamps für diesen Tag
|
|
for i in timestamps:
|
|
actual_timestamp = datetime.datetime.fromtimestamp(int(i))
|
|
timestamp_day = actual_timestamp.day
|
|
|
|
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
|
|
|
|
ui.markdown(convert_seconds_to_hours(time_sum)).classes('text-right')
|
|
else:
|
|
ui.markdown("Kein")
|
|
|
|
# Arbeitszeitsoll bestimmen
|
|
|
|
hours_to_work = int(current_user.get_day_workhours(select_year.value, select_month.value, day))
|
|
if hours_to_work < 0:
|
|
ui.space()
|
|
day_type.content="Kein Arbeitsverhältnis"
|
|
day_type.set_visibility(True)
|
|
else:
|
|
ui.markdown(f"{convert_seconds_to_hours(int(hours_to_work) * 3600)}").classes('text-right')
|
|
if int(hours_to_work) == 0:
|
|
day_type.content = "**Kein Arbeitstag**"
|
|
day_type.set_visibility(True)
|
|
|
|
if day_in_list.strftime("%Y-%m-%d") in data["holidays"]:
|
|
day_type.content = f'**{data["holidays"][day_in_list.strftime("%Y-%m-%d")]}**'
|
|
|
|
# Saldo für den Tag berechnen
|
|
|
|
if time.time() > day_in_list.timestamp():
|
|
|
|
time_duty = int(current_user.get_day_workhours(select_year.value, select_month.value, day)) * 3600
|
|
if time_duty < 0:
|
|
ui.space()
|
|
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
|
|
|
|
general_saldo = general_saldo + saldo
|
|
ui.markdown(convert_seconds_to_hours(saldo)).classes('text-right')
|
|
else:
|
|
ui.markdown("-").classes('text-center')
|
|
|
|
def add_entry(day):
|
|
with ui.dialog() as add_dialog, ui.card():
|
|
ui.markdown("###Eintrag hinzufügen")
|
|
input_time = ui.time().classes('w-full justify-center')
|
|
|
|
def add_entry_save():
|
|
if input_time.value == None:
|
|
ui.notify("Bitte eine Uhrzeit auswählen.")
|
|
return
|
|
|
|
new_time_stamp = datetime.datetime(int(year_binder.value),
|
|
int(month_binder.value), day,
|
|
int(input_time.value[:2]),
|
|
int(input_time.value[-2:])).timestamp()
|
|
current_user = user(time_user.value)
|
|
current_user.timestamp(stamptime=int(new_time_stamp))
|
|
calendar_card.clear()
|
|
update_month_and_year()
|
|
add_dialog.close()
|
|
ui.notify("Eintrag hinzugefügt")
|
|
with ui.grid(columns=3):
|
|
ui.button("Speichern", on_click=add_entry_save)
|
|
ui.space()
|
|
ui.button("Abbrechen", on_click=add_dialog.close)
|
|
add_dialog.open()
|
|
add_dialog.move(calendar_card)
|
|
|
|
def add_absence(absence_type, day):
|
|
with ui.dialog() as dialog, ui.card().classes('w-[350px]'):
|
|
ui.markdown(f'Für welchen Zeitraum soll *{absence_entries[absence_type]["name"]}* eingetragen werden?').classes('w-full')
|
|
absence_dates = ui.date().props('range today-btn').classes('w-full justify-center')
|
|
absence_dates._props['locale'] = {'daysShort': ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'],
|
|
'months': ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
|
|
'monthsShort': ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']}
|
|
absence_dates._props['title'] = absence_entries[absence_type]["name"]
|
|
absence_dates._props['minimal'] = True
|
|
|
|
if day < 10:
|
|
day = f"0{str(day)}"
|
|
else:
|
|
day = str(day)
|
|
if int(select_month.value) < 10:
|
|
month = f"0{select_month.value}"
|
|
else:
|
|
month = select_month.value
|
|
absence_dates.value = f"{select_year.value}-{month}-{day}"
|
|
|
|
def add_absence_save():
|
|
# Bei nur einem Datum, direkt schreiben
|
|
if isinstance(absence_dates.value, str):
|
|
absence_date = absence_dates.value.split("-")
|
|
current_user.update_absence(absence_date[0], absence_date[1], absence_date[2], absence_type)
|
|
current_sel_month = select_month.value
|
|
current_sel_year = select_year.value
|
|
update_user()
|
|
update_months()
|
|
select_year.value = current_sel_year
|
|
select_month.value = current_sel_month
|
|
calendar_card.clear()
|
|
update_month_and_year()
|
|
|
|
# Bei Zeitbereich, aufteilen
|
|
elif isinstance(absence_dates.value, dict):
|
|
start_date = absence_dates.value["from"]
|
|
end_date = absence_dates.value["to"]
|
|
start_date = start_date.split("-")
|
|
end_date = end_date.split("-")
|
|
|
|
start_year = int(start_date[0])
|
|
end_year = int(end_date[0])
|
|
start_month = int(start_date[1])
|
|
end_month = int(end_date[1])
|
|
start_day = int(start_date[2])
|
|
end_day = int(end_date[2])
|
|
|
|
start_date = datetime.datetime(start_year, start_month, start_day)
|
|
end_date = datetime.datetime(end_year, end_month, end_day)
|
|
actual_date = start_date
|
|
|
|
while actual_date <= end_date:
|
|
absences = current_user.get_absence(actual_date.year, actual_date.month)
|
|
|
|
if str(actual_date.day) in list(absences):
|
|
current_user.del_absence(actual_date.year, actual_date.month, actual_date.day)
|
|
ui.notify(f"Eintrag {absence_entries[absences[str(actual_date.day)]]['name']} am {actual_date.day}.{actual_date.month}.{actual_date.year} überschrieben.")
|
|
current_user.update_absence(actual_date.year, actual_date.month, actual_date.day, absence_type)
|
|
|
|
actual_date = actual_date + datetime.timedelta(days=1)
|
|
clear_card()
|
|
ui.notify("Einträge vorgenomomen")
|
|
dialog.close()
|
|
|
|
|
|
with ui.grid(columns=3).classes('w-full justify-center'):
|
|
ui.button("Speichern", on_click=add_absence_save)
|
|
ui.space()
|
|
ui.button("Abbrechen", on_click=dialog.close)
|
|
|
|
dialog.open()
|
|
dialog.move(calendar_card)
|
|
|
|
def edit_notes(day):
|
|
notes = current_user.get_day_notes(select_year.value, select_month.value, day)
|
|
def del_note_entry(user):
|
|
try:
|
|
del(notes[user])
|
|
username_labels[user].delete()
|
|
note_labels[user].delete()
|
|
del_buttons[user].delete()
|
|
except KeyError:
|
|
ui.notify("Kann nicht gelöscht werden. Eintrag wurde noch nicht gespeichert.")
|
|
|
|
def save_notes():
|
|
if not note_labels["admin"].is_deleted:
|
|
notes["admin"] = note_labels["admin"].value
|
|
current_user.write_notes(select_year.value, select_month.value, day, notes)
|
|
timetable.refresh()
|
|
dialog.close()
|
|
|
|
with ui.dialog() as dialog, ui.card():
|
|
# Notizen
|
|
username_labels = { }
|
|
note_labels = { }
|
|
del_buttons = { }
|
|
|
|
ui.markdown(f'**Notizen für {day}.{current_month}.{current_year}**')
|
|
with ui.grid(columns='auto auto auto'):
|
|
admin_settings = load_adminsettings()
|
|
# Beschreibungsfeld für Admin
|
|
username_labels["admin"] = ui.markdown("Administrator:")
|
|
# Textarea für Admin
|
|
note_labels["admin"] = ui.textarea()
|
|
del_buttons["admin"] = ui.button(icon='remove', on_click=lambda user="admin": del_note_entry(user))
|
|
|
|
for name, text in notes.items():
|
|
if name != "admin":
|
|
username_labels["user"] = ui.markdown(current_user.fullname)
|
|
note_labels["user"] = ui.markdown(text)
|
|
del_buttons["user"] = ui.button(icon='remove', on_click=lambda user="user": del_note_entry(user))
|
|
elif name == "admin":
|
|
note_labels["admin"].value = text
|
|
|
|
with ui.row():
|
|
ui.button("OK", on_click=save_notes)
|
|
ui.button("Abbrechen", on_click=dialog.close)
|
|
dialog.open()
|
|
dialog.move(calendar_card)
|
|
|
|
with ui.button(icon='menu') as menu_button:
|
|
with ui.menu() as menu:
|
|
menu_item = ui.menu_item("Zeiteintrag hinzufügen", lambda day=day: add_entry(day))
|
|
if archive_status:
|
|
menu_item.disable()
|
|
if datetime.datetime.now().day < day:
|
|
menu_item.disable()
|
|
menu_item.tooltip("Kann keine Zeiteinträge für die Zukunft vornehmen.")
|
|
ui.separator()
|
|
menu_item = ui.menu_item("Notizen bearbeiten", lambda day=day: edit_notes(day))
|
|
if archive_status:
|
|
menu_item.disable()
|
|
ui.separator()
|
|
for i in list(absence_entries):
|
|
menu_item = ui.menu_item(f"{absence_entries[i]['name']} eintragen", lambda absence_type=i, day=day: add_absence(absence_type, day))
|
|
if archive_status:
|
|
menu_item.disable()
|
|
if str(day) in list(user_absent):
|
|
menu_item.disable()
|
|
if archive_status:
|
|
menu_button.disable()
|
|
|
|
#ui.button("Eintrag hinzufügen", on_click=lambda day=day: add_entry(day))
|
|
|
|
#4x leer und dann Gesamtsaldo
|
|
ui.space().classes('col-span-5')
|
|
ui.markdown(f"{convert_seconds_to_hours(general_saldo)}").classes('text-right')
|
|
ui.markdown("Stunden aus Vormonat").classes('col-span-5 text-right')
|
|
last_months_overtime = current_user.get_last_months_overtime(select_year.value, select_month.value)
|
|
ui.markdown(f"{convert_seconds_to_hours(last_months_overtime)}").classes('text-right')
|
|
ui.markdown("Gesamtsaldo").classes('col-span-5 text-right')
|
|
ui.markdown(f"**<ins>{convert_seconds_to_hours(general_saldo + last_months_overtime)}</ins>**").classes('text-right')
|
|
|
|
table_grid.move(calendar_card)
|
|
|
|
update_month_and_year()
|
|
|
|
def clear_card():
|
|
calendar_card.clear()
|
|
button_update.delete()
|
|
update_month_and_year()
|
|
current_user = user(time_user.value)
|
|
month_header.set_content(f"###Buchungen für **{current_user.fullname}** für **{calendar.month_name[int(select_month.value)]} {select_year.value}**")
|
|
|
|
month_header.set_content(f"###Buchungen für **{current_user.fullname}** für **{calendar.month_name[int(select_month.value)]} {select_year.value}**")
|
|
|
|
timetable()
|
|
button_update = ui.button("Aktualisieren", on_click=timetable.refresh)
|
|
button_update.move(timetable_header)
|
|
with ui.tab_panel(user_summary):
|
|
|
|
global overview_table
|
|
@ui.refreshable
|
|
def overview_table():
|
|
ov_columns = [ {'label': 'Benutzername', 'name': 'username', 'field': 'username'},
|
|
{'label': 'Name', 'name': 'name', 'field': 'name'},
|
|
{'label': 'Zeitsaldo', 'name': 'time', 'field': 'time'},
|
|
{'label': 'Urlaub', 'name': 'vacation', 'field': 'vacation'},
|
|
{'label': 'Resturlaub', 'name': 'vacation_remaining', 'field': 'vacation_remaining'},
|
|
{'label': 'Krankheit', 'name': 'illness', 'field': 'illness'}]
|
|
ov_rows = [ ]
|
|
for username in userlist:
|
|
actual_user = user(username)
|
|
ov_rows.append(
|
|
{'username': username, 'name': actual_user.fullname, 'time': f'{convert_seconds_to_hours(actual_user.get_last_months_overtime() + actual_user.get_worked_time()[0])} h', 'vacation': f'{actual_user.count_absence_days("U")} Tage',
|
|
'vacation_remaining': f'{actual_user.get_vacation_claim() - actual_user.count_absence_days("U")} Tage', 'illness': f'{actual_user.count_absence_days("K")} Tage'})
|
|
ui.table(columns=ov_columns, rows=ov_rows, row_key='username', column_defaults={'align': 'left', 'sortable': True})
|
|
|
|
overview_table()
|
|
ui.button("Aktualisieren", on_click=overview_table.refresh)
|
|
|
|
with ui.tab_panel(vacation_applications):
|
|
date_string = '%d.%m.%Y'
|
|
|
|
@ui.refreshable
|
|
def va_table():
|
|
va_columns = [ {'label': 'Benutzername', 'name': 'username', 'field': 'username'},
|
|
{'label': 'Name', 'name': 'name', 'field': 'name'},
|
|
{'label': 'key', 'name': 'key', 'field': 'key', 'classes': 'hidden', 'headerClasses': 'hidden'},
|
|
{'label': 'Anfang', 'name': 'start', 'field': 'start'},
|
|
{'label': 'Ende', 'name': 'end', 'field': 'end'},
|
|
{'label': 'Resturlaub', 'name': 'vacation_remaining', 'field': 'vacation_remaining'},
|
|
{'label': 'Resturlaub nach Genehmigung', 'name': 'vacation_remaining_after_submission', 'field': 'vacation_remaining_after_submission'}
|
|
]
|
|
va_rows = [ ]
|
|
|
|
for username in userlist:
|
|
actual_user = user(username)
|
|
open_va = actual_user.get_open_vacation_applications()
|
|
for i, dates in open_va.items():
|
|
startdate_dt = datetime.datetime.strptime(dates[0], '%Y-%m-%d')
|
|
enddate_dt = datetime.datetime.strptime(dates[1], '%Y-%m-%d')
|
|
vacation_start_to_end = (enddate_dt-startdate_dt).days
|
|
vacation_duration = 0
|
|
for day_counter in range(0, vacation_start_to_end):
|
|
new_date = startdate_dt + datetime.timedelta(days=day_counter)
|
|
if int(actual_user.get_day_workhours(new_date.year, new_date.month, new_date.day)) > 0:
|
|
if not datetime.datetime(new_date.year, new_date.month, new_date.day).strftime('%Y-%m-%d') in list(load_adminsettings()["holidays"]):
|
|
vacation_duration += 1
|
|
|
|
va_rows.append({'username': username,
|
|
'name': actual_user.fullname,
|
|
'key': f'{username}-{i}',
|
|
'start': startdate_dt.strftime(date_string),
|
|
'end': enddate_dt.strftime(date_string),
|
|
'vacation_remaining': f'{actual_user.get_vacation_claim() - actual_user.count_absence_days("U", startdate_dt.year)} Tage',
|
|
'vacation_remaining_after_submission': f'{actual_user.get_vacation_claim() - actual_user.count_absence_days("U", startdate_dt.year) - vacation_duration } Tage'})
|
|
global vacation_table
|
|
vacation_table = ui.table(columns=va_columns, rows=va_rows, row_key='key', selection='single', column_defaults={'align': 'left', 'sortable': True})
|
|
va_table()
|
|
|
|
def approve_vacation():
|
|
global vacation_table
|
|
for selection in vacation_table.selected:
|
|
key = selection["key"]
|
|
username = key.split("-")[0]
|
|
index = key.split("-")[1]
|
|
actual_user = user(username)
|
|
startdate_dt = datetime.datetime.strptime(selection["start"], date_string)
|
|
enddate_dt = datetime.datetime.strptime(selection["end"], date_string)
|
|
delta = (enddate_dt - startdate_dt).days
|
|
for i in range(0, delta):
|
|
new_date = startdate_dt + datetime.timedelta(days=i)
|
|
if int(actual_user.get_day_workhours(new_date.year, new_date.month, new_date.day)) > 0:
|
|
if not datetime.datetime(new_date.year, new_date.month, new_date.day).strftime('%Y-%m-%d') in list(load_adminsettings()["holidays"]):
|
|
actual_user.update_absence(new_date.year, new_date.month, new_date.day, "U")
|
|
ui.notify(f"Urlaub vom {selection['start']} bis {selection['end']} für {actual_user.fullname} eingetragen")
|
|
try:
|
|
retract_result = actual_user.revoke_vacation_application(index)
|
|
except IndexError:
|
|
ui.notify("Fehler beim Entfernen des Urlaubsantrages nach dem Eintragen.")
|
|
va_table.refresh()
|
|
timetable.refresh()
|
|
overview_table.refresh()
|
|
|
|
def deny_vacation():
|
|
for selection in vacation_table.selected:
|
|
key = selection["key"]
|
|
username = key.split("-")[0]
|
|
index = key.split("-")[1]
|
|
actual_user = user(username)
|
|
try:
|
|
retract_result = actual_user.revoke_vacation_application(index)
|
|
ui.notify(f"Urlaubsantrag vom {selection['start']} bis {selection['end']} für {actual_user.fullname} entfernt.")
|
|
except IndexError:
|
|
ui.notify("Fehler beim Entfernen des Urlaubsantrages. Ggf. wurde dieser zurückgezogen.")
|
|
va_table.refresh()
|
|
overview_table.refresh()
|
|
|
|
ui.button("Aktualisieren", on_click=va_table.refresh)
|
|
ui.separator()
|
|
with ui.grid(columns=2):
|
|
ui.button("Genehmigen", on_click=approve_vacation)
|
|
ui.button("Ablehnen", on_click=deny_vacation)
|
|
|
|
with ui.tab_panel(settings):
|
|
with ui.grid(columns='auto auto'):
|
|
with ui.card():
|
|
ui.markdown("**Administrationsbenutzer:**")
|
|
with ui.grid(columns=2):
|
|
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):
|
|
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
|
|
|
|
with ui.card():
|
|
ui.markdown("**Einstellungen für das Touchscreenterminal:**")
|
|
with ui.column():
|
|
touchscreen_switch = ui.switch("Touchscreenterminal aktiviert")
|
|
touchscreen_switch.value = data["touchscreen"]
|
|
timestamp_switch = ui.switch("Stempelzeiten anzeigen").bind_visibility_from(touchscreen_switch, 'value')
|
|
photo_switch = ui.switch("Fotos anzeigen").bind_visibility_from(touchscreen_switch, 'value')
|
|
timestamp_switch.value = bool(data["times_on_touchscreen"])
|
|
with ui.row().bind_visibility_from(touchscreen_switch, 'value'):
|
|
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().bind_visibility_from(touchscreen_switch, 'value'):
|
|
ui.markdown("Minimale 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
|
|
|
|
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)
|
|
picture_height_input.on_value_change(button_height_input.validate)
|
|
ui.markdown('px')
|
|
|
|
with ui.card():
|
|
ui.markdown("**Einstellungen für Benutzerfrontend**")
|
|
notes_switch = ui.switch("Notizfunktion aktiviert", value=data["user_notes"])
|
|
va_switch = ui.switch("Urlaubsanträge", value=data["vacation_application"])
|
|
|
|
def holiday_section():
|
|
with ui.card():
|
|
ui.markdown('**Feiertage:**')
|
|
|
|
reset_visibility = ValueBinder()
|
|
reset_visibility.value = False
|
|
|
|
def new_holiday_entry():
|
|
def add_holiday_to_dict():
|
|
year_from_picker = int(datepicker.value.split("-")[0])
|
|
month_from_picker = int(datepicker.value.split("-")[1])
|
|
day_from_picker = int(datepicker.value.split("-")[2])
|
|
for i in range(0, int(repetition.value)):
|
|
repetition_date_dt = datetime.datetime(year_from_picker + i, month_from_picker, day_from_picker)
|
|
date_entry = repetition_date_dt.strftime('%Y-%m-%d')
|
|
data["holidays"][date_entry] = description.value
|
|
holiday_buttons_grid.refresh()
|
|
reset_visibility.value = True
|
|
dialog.close()
|
|
|
|
with ui.dialog() as dialog, ui.card():
|
|
with ui.grid(columns='auto auto'):
|
|
ui.markdown('Geben Sie den neuen Feiertag ein:').classes('col-span-2')
|
|
datepicker = ui.date(value=datetime.datetime.now().strftime('%Y-%m-%d')).classes('col-span-2')
|
|
description = ui.input('Beschreibung').classes('col-span-2')
|
|
repetition = ui.number('Für Jahre wiederholen', value=1, min=1, precision=0).classes('col-span-2')
|
|
ui.button("OK", on_click=add_holiday_to_dict)
|
|
ui.button('Abbrechen', on_click=dialog.close)
|
|
dialog.open()
|
|
|
|
@ui.refreshable
|
|
def holiday_buttons_grid():
|
|
|
|
holidays = list(data["holidays"])
|
|
holidays.sort()
|
|
|
|
year_list = []
|
|
|
|
# Jahresliste erzeugen
|
|
for i in holidays:
|
|
i_split = i.split("-")
|
|
year = int(i_split[0])
|
|
year_list.append(year)
|
|
|
|
year_list = list(set(year_list))
|
|
year_dict = {}
|
|
|
|
# Jahresdictionary konstruieren
|
|
for i in year_list:
|
|
year_dict[i] = []
|
|
|
|
for i in holidays:
|
|
i_split = i.split("-")
|
|
year = int(i_split[0])
|
|
month = int(i_split[1])
|
|
day = int(i_split[2])
|
|
date_dt = datetime.datetime(year, month, day)
|
|
year_dict[year].append(date_dt)
|
|
|
|
def del_holiday_entry(entry):
|
|
del(data['holidays'][entry.strftime('%Y-%m-%d')])
|
|
reset_visibility.value = True
|
|
holiday_buttons_grid.refresh()
|
|
|
|
def defined_holidays():
|
|
with ui.dialog() as dialog, ui.card():
|
|
ui.markdown("Bitte wählen Sie aus, welche Feiertage eingetragen werden sollen. Vom Osterdatum abhängige Feiertage werden für die verschiedenen Jahre berechnet.:")
|
|
with ui.grid(columns='auto auto'):
|
|
with ui.column().classes('gap-0'): # Auswahlen für Feiertage
|
|
|
|
checkbox_classes = 'py-0'
|
|
|
|
new_year = ui.checkbox("Neujahr (1. Januar)").classes(checkbox_classes)
|
|
heilige_drei_koenige = ui.checkbox("Heilige Drei Könige (6. Januar)").classes(checkbox_classes)
|
|
womens_day = ui.checkbox("Internationaler Frauentag (8. März)").classes(checkbox_classes)
|
|
gruendonnerstag = ui.checkbox("Gründonnerstag (berechnet)").classes(checkbox_classes)
|
|
karfreitag = ui.checkbox("Karfreitag (berechnet").classes(checkbox_classes)
|
|
easter_sunday = ui.checkbox("Ostersonntag (berechnet)").classes(checkbox_classes)
|
|
easter_monday = ui.checkbox("Ostermontag (berechnet)").classes(checkbox_classes)
|
|
first_of_may = ui.checkbox("Tag der Arbeit (1. Mai)").classes(checkbox_classes)
|
|
liberation_day = ui.checkbox("Tag der Befreiung (8. Mai)").classes(checkbox_classes)
|
|
ascension_day = ui.checkbox("Christi Himmelfahrt (berechnet)").classes(checkbox_classes)
|
|
whitsun_sunday = ui.checkbox("Pfingssonntag (berechnet)").classes(checkbox_classes)
|
|
whitsun_monday = ui.checkbox("Pfingsmontag (berechnet)").classes(checkbox_classes)
|
|
fronleichnam = ui.checkbox("Fronleichnam (berechnet)").classes(checkbox_classes)
|
|
peace_party = ui.checkbox("Friedensfest (Augsburg - 8. August)").classes(checkbox_classes)
|
|
mary_ascension = ui.checkbox("Mariä Himmelfahrt (15. August)").classes(checkbox_classes)
|
|
childrens_day = ui.checkbox("Weltkindertag (20. September)").classes(checkbox_classes)
|
|
unity_day = ui.checkbox("Tag der deutschen Einheit (3. Oktober)").classes(checkbox_classes)
|
|
reformation_day = ui.checkbox("Reformationstag (30. Oktober)").classes(checkbox_classes)
|
|
all_hallows = ui.checkbox("Allerheiligen (1. November)").classes(checkbox_classes)
|
|
praying_day = ui.checkbox("Buß- und Bettag (berechnet)").classes(checkbox_classes)
|
|
christmas_day = ui.checkbox("1. Weihnachtsfeiertag (25. Dezember)").classes(checkbox_classes)
|
|
boxing_day = ui.checkbox("2. Weihnachtsfeiertag (26. Dezember)").classes(checkbox_classes)
|
|
|
|
def enter_holidays():
|
|
|
|
for year in range (int(starting_year.value), int(end_year.value) + 1):
|
|
ostersonntag = dateutil.easter.easter(year)
|
|
if new_year.value:
|
|
data["holidays"][f"{year}-01-01"] = f"Neujahr"
|
|
if heilige_drei_koenige.value:
|
|
data["holidays"][f"{year}-01-06"] = f"Hl. Drei Könige"
|
|
if womens_day.value:
|
|
data["holidays"][f"{year}-03-08"] = f"Intern. Frauentag"
|
|
if gruendonnerstag.value:
|
|
datum_dt = ostersonntag - datetime.timedelta(days=3)
|
|
datum = datum_dt.strftime("%Y-%m-%d")
|
|
data["holidays"][f"{datum}"] = f"Gründonnerstag"
|
|
if karfreitag.value:
|
|
datum_dt = ostersonntag - datetime.timedelta(days=2)
|
|
datum = datum_dt.strftime("%Y-%m-%d")
|
|
data["holidays"][f"{datum}"] = f"Karfreitag"
|
|
if easter_sunday.value:
|
|
datum_dt = ostersonntag
|
|
datum = datum_dt.strftime("%Y-%m-%d")
|
|
data["holidays"][f"{datum}"] = "Ostersonntag"
|
|
if easter_monday.value:
|
|
datum_dt = ostersonntag + datetime.timedelta(days=1)
|
|
datum = datum_dt.strftime("%Y-%m-%d")
|
|
data["holidays"][f"{datum}"] = "Ostermontag"
|
|
if first_of_may.value:
|
|
data["holidays"][f"{year}-05-01"] = f"Tag der Arbeit"
|
|
if liberation_day.value:
|
|
data["holidays"][f"{year}-05-08"] = f"Tag der Befreiung"
|
|
if ascension_day.value:
|
|
datum_dt = ostersonntag + datetime.timedelta(days=39)
|
|
datum = datum_dt.strftime("%Y-%m-%d")
|
|
data["holidays"][f"{datum}"] = f"Christi Himmelfahrt"
|
|
if whitsun_sunday.value:
|
|
datum_dt = ostersonntag + datetime.timedelta(days=49)
|
|
datum = datum_dt.strftime("%Y-%m-%d")
|
|
data["holidays"][f"{datum}"] = f"Pfingssonntag"
|
|
if whitsun_monday.value:
|
|
datum_dt = ostersonntag + datetime.timedelta(days=49)
|
|
datum = datum_dt.strftime("%Y-%m-%d")
|
|
data["holidays"][f"{datum}"] = f"Pfingstmontag"
|
|
if fronleichnam.value:
|
|
datum_dt = ostersonntag + datetime.timedelta(days=60)
|
|
datum = datum_dt.strftime("%Y-%m-%d")
|
|
data["holidays"][f"{datum}"] = f"Fronleichnam"
|
|
if peace_party.value:
|
|
data["holidays"][f"{year}-08-08"] = f"Friedensfest"
|
|
if mary_ascension.value:
|
|
data["holidays"][f"{year}-08-15"] = f"Mariä Himmelfahrt"
|
|
if childrens_day.value:
|
|
data["holidays"][f"{year}-09-20"] = f"Intern. Kindertag"
|
|
if unity_day.value:
|
|
data["holidays"][f"{year}-10-03"] = f"Tag der deutschen Einheit"
|
|
if reformation_day.value:
|
|
data["holidays"][f"{year}-10-30"] = f"Reformationstag"
|
|
if all_hallows.value:
|
|
data["holidays"][f"{year}-11-01"] = f"Allerheiligen"
|
|
if praying_day.value:
|
|
starting_day = datetime.datetime(year, 11 ,23)
|
|
for i in range(1, 8):
|
|
test_day = starting_day - datetime.timedelta(days=-i)
|
|
if test_day.weekday() == 2:
|
|
datum_dt = test_day
|
|
break
|
|
datum = datum_dt.strftime("%Y-%m-%d")
|
|
data["holidays"][f"{datum}"] = f"Bu0- und Bettag"
|
|
if christmas_day.value:
|
|
data["holidays"][f"{year}-12-25"] = f"1. Weihnachtsfeiertag"
|
|
if boxing_day.value:
|
|
data["holidays"][f"{year}-12-26"] = f"2. Weihnachtsfeiertag"
|
|
reset_visibility.value = True
|
|
dialog.close()
|
|
holiday_buttons_grid.refresh()
|
|
|
|
with ui.column():
|
|
starting_year = ui.number(value=datetime.datetime.now().year, label="Startjahr")
|
|
end_year = ui.number(value=starting_year.value, label="Endjahr")
|
|
with ui.row():
|
|
ui.button("Anwenden", on_click=enter_holidays)
|
|
ui.button("Abbrechen", on_click=dialog.close)
|
|
dialog.open()
|
|
|
|
def reset_holidays():
|
|
old_data = load_adminsettings()
|
|
data["holidays"] = old_data["holidays"]
|
|
reset_visibility.value = False
|
|
holiday_buttons_grid.refresh()
|
|
|
|
with ui.row():
|
|
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()
|
|
|
|
for year_entry in year_list:
|
|
with ui.expansion(year_entry):
|
|
with ui.column():
|
|
for entry in year_dict[year_entry]:
|
|
date_label = entry.strftime("%d.%m.%y")
|
|
with ui.button(on_click=lambda entry=entry: del_holiday_entry(entry)).classes('w-full').props('color=light-blue-8').tooltip(f"Klicken Sie hier, um den Feiertag \"{data['holidays'][entry.strftime('%Y-%m-%d')]}\" zu löschen."):
|
|
with ui.grid(columns="auto auto").classes('w-full'):
|
|
ui.label(f"{data['holidays'][entry.strftime('%Y-%m-%d')]}").props('align="left"')
|
|
ui.label(f"{date_label}").props('align="right"')
|
|
holiday_buttons_grid()
|
|
|
|
holiday_section()
|
|
|
|
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")
|
|
workhours = [ ]
|
|
|
|
with ui.row():
|
|
|
|
def user_selection_changed():
|
|
try:
|
|
if user_selection.value != None:
|
|
current_user = user(user_selection.value)
|
|
username_input.value = current_user.username
|
|
fullname_input.value = current_user.fullname
|
|
#password_input.value = current_user.password
|
|
usersettingscard.visible = True
|
|
|
|
api_key_input.value = current_user.api_key
|
|
|
|
api_link_column.clear()
|
|
for i in app.urls:
|
|
with ui.row() as link_row:
|
|
link_string = f'{i}/api/stamp/{api_key_input.value}'
|
|
link = ui.link(f'{i}/api/stamp/"API-Schlüssel"', link_string).tooltip(("ACHTUNG: Klick auf den Link löst Stempelaktion aus!"))
|
|
qr = segno.make_qr(link_string).svg_data_uri()
|
|
with ui.image(qr).classes('w-1/3'):
|
|
with ui.tooltip().classes('bg-white border'):
|
|
ui.image(qr).classes('w-64')
|
|
|
|
link_row.move(api_link_column)
|
|
|
|
workhours_select.clear()
|
|
workhour_list = list(current_user.workhours)
|
|
workhour_list.sort()
|
|
workhours_select.set_options(workhour_list)
|
|
workhours_select.value = workhour_list[0]
|
|
workinghourscard.visible = True
|
|
|
|
user_photo.set_source(current_user.photofile)
|
|
user_photo.force_reload()
|
|
|
|
user_photo.set_visibility(os.path.exists(current_user.photofile))
|
|
delete_button.set_visibility(os.path.exists(current_user.photofile))
|
|
|
|
except:
|
|
pass
|
|
|
|
# workhours_selection_changed(list(current_user.workhours)[0])
|
|
|
|
def workhours_selection_changed():
|
|
if workhours_select.value != None:
|
|
current_user = user(user_selection.value)
|
|
selected_workhours = current_user.workhours[workhours_select.value]
|
|
|
|
for key, hours in selected_workhours.items():
|
|
try:
|
|
days[int(key)-1].value = hours
|
|
except:
|
|
if key == 0:
|
|
days[6].value = hours
|
|
elif key == "vacation":
|
|
vacation_input.value = hours
|
|
|
|
def save_user_settings():
|
|
def save_settings():
|
|
current_user = user(user_selection.value)
|
|
current_user.username = username_input.value
|
|
current_user.fullname = fullname_input.value
|
|
current_user.password = hash_password(password_input.value)
|
|
current_user.api_key = api_key_input.value
|
|
current_user.write_settings()
|
|
password_input.value = ""
|
|
userlist = list_users()
|
|
userlist.sort()
|
|
user_selection.clear()
|
|
user_selection.set_options(userlist)
|
|
user_selection.value = current_user.username
|
|
user_selection_changed()
|
|
dialog.close()
|
|
ui.notify("Einstellungen gespeichert")
|
|
|
|
with ui.dialog() as dialog, ui.card():
|
|
if user_selection.value != username_input.value:
|
|
ui.markdown("**Benutzername wurde geändert.**")
|
|
ui.markdown(f"Benutzerdaten werden in den neuen Ordner {username_input.value}")
|
|
ui.markdown("Sollen die Einstellungen gespeichert werden?")
|
|
with ui.row():
|
|
ui.button("Speichern", on_click=save_settings)
|
|
ui.button("Abbrechen", on_click=dialog.close)
|
|
dialog.open()
|
|
|
|
def del_user():
|
|
current_user = user(user_selection.value)
|
|
|
|
def del_definitely():
|
|
if current_user.username == time_user.value:
|
|
if userlist.index(current_user.username) == 0:
|
|
time_user.value = userlist[1]
|
|
else:
|
|
time_user.value = userlist[0]
|
|
current_user.del_user()
|
|
#userlist = list_users()
|
|
#userlist.sort()
|
|
#user_selection.clear()
|
|
#user_selection.set_options(userlist)
|
|
#user_selection.value = userlist[0]
|
|
update_userlist()
|
|
time_user.set_options(userlist)
|
|
user_ui.refresh()
|
|
dialog.close()
|
|
ui.notify("Benutzer gelöscht")
|
|
|
|
with ui.dialog() as dialog, ui.card():
|
|
ui.markdown(f"Soll der Benutzer *{current_user.username}* gelöscht werden?")
|
|
ui.markdown("**Dies kann nicht rückgängig gemacht werden?**")
|
|
with ui.row():
|
|
ui.button("Löschen", on_click=del_definitely)
|
|
ui.button("Abbrechen", on_click=dialog.close)
|
|
|
|
dialog.open()
|
|
|
|
def save_workhours():
|
|
def save_settings():
|
|
current_user = user(user_selection.value)
|
|
construct_dict = { }
|
|
for i in range(7):
|
|
if i < 7:
|
|
construct_dict[i+1] = days[i].value
|
|
elif i == 7:
|
|
construct_dict[0] = days[i].value
|
|
|
|
construct_dict["vacation"] = vacation_input.value
|
|
current_user.workhours[workhours_select.value] = construct_dict
|
|
current_user.write_settings()
|
|
dialog.close()
|
|
ui.notify("Einstellungen gespeichert")
|
|
|
|
with ui.dialog() as dialog, ui.card():
|
|
ui.markdown("Sollen die Änderungen an den Arbeitsstunden und/oder Urlaubstagen gespeichert werden?")
|
|
with ui.row():
|
|
ui.button("Speichern", on_click=save_settings)
|
|
ui.button("Abrrechen", on_click=dialog.close)
|
|
dialog.open()
|
|
|
|
def delete_workhour_entry():
|
|
def delete_entry():
|
|
current_user = user(user_selection.value)
|
|
del current_user.workhours[workhours_select.value]
|
|
current_user.write_settings()
|
|
workhour_list = list(current_user.workhours)
|
|
workhours_select.clear()
|
|
workhours_select.set_options(workhour_list)
|
|
workhours_select.set_value(workhour_list[-1])
|
|
|
|
#workhours_selection_changed(current_user.workhours[0])
|
|
dialog.close()
|
|
ui.notify("Eintrag gelöscht"
|
|
"")
|
|
with ui.dialog() as dialog, ui.card():
|
|
current_user = user(user_selection.value)
|
|
if len(current_user.workhours) > 1:
|
|
ui.markdown(f"Soll der Eintrag *{workhours_select.value}* wirklich gelöscht werden?")
|
|
ui.markdown("**Dies kann nicht rückgängig gemacht werden.**")
|
|
with ui.row():
|
|
ui.button("Löschen", on_click=delete_entry)
|
|
ui.button("Abbrechen", on_click=dialog.close)
|
|
else:
|
|
ui.markdown("Es gibt nur einen Eintrag. Dieser kann nicht gelöscht werden.")
|
|
ui.button("OK", on_click=dialog.close)
|
|
dialog.open()
|
|
|
|
def dialog_new_user():
|
|
def create_new_user():
|
|
if user_name_input.validate():
|
|
|
|
new_user(user_name_input.value)
|
|
update_userlist()
|
|
time_user.set_options(userlist)
|
|
user_ui.refresh()
|
|
dialog.close()
|
|
else:
|
|
ui.notify("Ungültiger Benutzername")
|
|
|
|
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,
|
|
'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)
|
|
dialog.open()
|
|
|
|
@ui.refreshable
|
|
def user_ui():
|
|
userlist = list_users()
|
|
userlist.sort()
|
|
|
|
with ui.column():
|
|
global user_selection
|
|
user_selection = ui.select(options=userlist, with_input=True, on_change=user_selection_changed)
|
|
user_selection.value = userlist[0]
|
|
ui.button("Neu", on_click=dialog_new_user)
|
|
user_ui()
|
|
|
|
with ui.column():
|
|
@ui.refreshable
|
|
def usersettings_card():
|
|
global usersettingscard
|
|
with ui.card() as usersettingscard:
|
|
ui.markdown("**Benutzereinstellungen**")
|
|
with ui.grid(columns="auto 1fr") as usersettingsgrid:
|
|
|
|
ui.markdown("Benutzername:")
|
|
global username_input
|
|
username_input = ui.input()
|
|
ui.markdown("Voller Name:")
|
|
global fullname_input
|
|
fullname_input = ui.input()
|
|
ui.markdown("Passwort")
|
|
global password_input
|
|
password_input = ui.input(password=True)
|
|
password_input.value = ""
|
|
ui.markdown("API-Schlüssel:")
|
|
with ui.row():
|
|
global api_key_input
|
|
api_key_input = ui.input().props('size=37')
|
|
def new_api_key():
|
|
api_key_input.value = hashlib.shake_256(bytes(f'{username_input.value}_{datetime.datetime.now().timestamp()}', 'utf-8')).hexdigest(20)
|
|
|
|
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.markdown('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.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)
|
|
|
|
usersettings_card()
|
|
|
|
with ui.card() as photocard:
|
|
ui.markdown('**Foto**')
|
|
current_user = user(user_selection.value)
|
|
user_photo = ui.image(current_user.photofile)
|
|
|
|
def handle_upload(e: events.UploadEventArguments):
|
|
picture = e.content.read()
|
|
current_user = user(user_selection.value)
|
|
with open(current_user.photofile, 'wb') as outoutfile:
|
|
outoutfile.write(picture)
|
|
uploader.reset()
|
|
user_selection_changed()
|
|
|
|
|
|
def del_photo():
|
|
current_user = user(user_selection.value)
|
|
current_user.delete_photo()
|
|
user_selection_changed()
|
|
|
|
uploader = ui.upload(label="Foto hochladen", on_upload=handle_upload).props('accept=.jpg|.jpeg').classes('max-w-full')
|
|
delete_button = ui.button("Löschen", on_click=del_photo)
|
|
|
|
|
|
with ui.card() as workinghourscard:
|
|
workhours = []
|
|
ui.markdown("**Arbeitszeiten**")
|
|
|
|
with ui.card():
|
|
|
|
def calculate_weekhours():
|
|
sum = 0
|
|
for i in range(7):
|
|
try:
|
|
sum = float(days[i].value) + sum
|
|
except:
|
|
pass
|
|
workhours_sum.set_content(str(sum))
|
|
|
|
with ui.grid(columns='auto auto auto'):
|
|
ui.markdown("gültig ab:")
|
|
workhours_select = ui.select(options=workhours, on_change=workhours_selection_changed).classes('col-span-2')
|
|
|
|
days = [ ]
|
|
weekdays = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"]
|
|
counter = 0
|
|
for day in weekdays:
|
|
ui.markdown(f"{day}:")
|
|
days.append(ui.number(on_change=calculate_weekhours).props('size=3'))
|
|
ui.markdown('Stunden')
|
|
counter = counter + 1
|
|
ui.separator().classes('col-span-full')
|
|
ui.markdown("**Summe:**")
|
|
workhours_sum = ui.markdown()
|
|
ui.markdown("Stunden")
|
|
|
|
with ui.card():
|
|
with ui.grid(columns='auto auto auto'):
|
|
ui.markdown("Urlaubstage")
|
|
vacation_input = ui.number().props('size=3')
|
|
ui.markdown("Tage")
|
|
|
|
def new_workhours_entry():
|
|
current_user = user(user_selection.value)
|
|
|
|
def add_workhours_entry():
|
|
workhours_dict = { }
|
|
for i in range(7):
|
|
workhours_dict[i] = 0
|
|
workhours_dict["vacation"] = 0
|
|
current_user.workhours[date_picker.value] = workhours_dict
|
|
current_user.write_settings()
|
|
|
|
workhours_select.clear()
|
|
workhours_list = list(current_user.workhours)
|
|
workhours_list.sort()
|
|
workhours_select.set_options(workhours_list)
|
|
workhours_select.value = date_picker.value
|
|
|
|
dialog.close()
|
|
ui.notify("Eintrag angelegt")
|
|
|
|
with ui.dialog() as dialog, ui.card():
|
|
ui.markdown("Geben Sie das Gültigkeitsdatum an, ab wann die Einträge gültig sein sollen.")
|
|
date_picker = ui.date()
|
|
|
|
with ui.row():
|
|
ui.button("OK", on_click=add_workhours_entry)
|
|
ui.button("Abbrechen", on_click=dialog.close)
|
|
dialog.open()
|
|
ui.button("Neu", on_click=new_workhours_entry)
|
|
with ui.row():
|
|
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:
|
|
login = login_mask()
|
|
#ui.navigate.to("/login") |