Interne Backupfunktion hinzugefügt
Downloads für Backups hinzugefügt Wiederherstellungsfunktion hinzugefügt
This commit is contained in:
parent
c248f1eb63
commit
0d5d977a1e
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,21 +0,0 @@
|
|||||||
# Zeiterfassung
|
|
||||||
#
|
|
||||||
# statische Werte
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Quasi Konstanten:
|
|
||||||
|
|
||||||
scriptpath = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
suffix_userfolder = "users"
|
|
||||||
suffix_settingsfolder = "settings"
|
|
||||||
usersettingsfilename = "settings.json"
|
|
||||||
photofilename = "photo.png"
|
|
||||||
program_name = "Zeiterfassung"
|
|
||||||
program_version = "Development"
|
|
||||||
|
|
||||||
status_in = "eingestempelt"
|
|
||||||
status_out = "ausgestempelt"
|
|
||||||
|
|
||||||
userfolder = scriptpath + "/" + suffix_userfolder
|
|
||||||
settingsfolder = scriptpath + "/" + suffix_settingsfolder
|
|
@ -1,36 +0,0 @@
|
|||||||
# Zeiterfassung
|
|
||||||
# JSON Handling
|
|
||||||
|
|
||||||
# Imports
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
# Datenstruktur:
|
|
||||||
|
|
||||||
# user: Benutzername
|
|
||||||
# name: Vollständiger Name
|
|
||||||
# password: gehashtes Passwort
|
|
||||||
|
|
||||||
# Montatsspezifische Informationen:
|
|
||||||
# Gültigkeitsdatum, ab wann gülitg
|
|
||||||
#
|
|
||||||
# monday: Stunden
|
|
||||||
# tuesday: Stunden
|
|
||||||
# wednesday: Stunden
|
|
||||||
# thursday: Stunden
|
|
||||||
# friday: Stunden
|
|
||||||
# saturday: Stunden
|
|
||||||
# sunday: Stunden
|
|
||||||
# pto: Tage pro Jahr
|
|
||||||
|
|
||||||
def load_settings(filename):
|
|
||||||
|
|
||||||
with open(filename) as json_file:
|
|
||||||
data = json.load(json_file)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def write_settings(filename, settings):
|
|
||||||
|
|
||||||
with open(filename, "w") as json_file:
|
|
||||||
json.dump(settings, json_file, indent=4)
|
|
93
lib/admin.py
93
lib/admin.py
@ -14,6 +14,7 @@ from lib.web_ui import *
|
|||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import os
|
import os
|
||||||
|
import zipfile
|
||||||
from stat import S_IREAD, S_IRWXU
|
from stat import S_IREAD, S_IRWXU
|
||||||
import hashlib
|
import hashlib
|
||||||
import calendar
|
import calendar
|
||||||
@ -45,6 +46,7 @@ def page_admin():
|
|||||||
time_overview = ui.tab('Zeitübersichten')
|
time_overview = ui.tab('Zeitübersichten')
|
||||||
settings = ui.tab('Einstellungen')
|
settings = ui.tab('Einstellungen')
|
||||||
users = ui.tab('Benutzer')
|
users = ui.tab('Benutzer')
|
||||||
|
backups = ui.tab('Backups')
|
||||||
|
|
||||||
userlist = [ ]
|
userlist = [ ]
|
||||||
|
|
||||||
@ -1241,6 +1243,97 @@ Dies kann nicht rückgängig gemacht werden!''')
|
|||||||
ui.button("Speichern", on_click=save_workhours)
|
ui.button("Speichern", on_click=save_workhours)
|
||||||
ui.button("Löschen", on_click=delete_workhour_entry)
|
ui.button("Löschen", on_click=delete_workhour_entry)
|
||||||
user_selection_changed()
|
user_selection_changed()
|
||||||
|
with ui.tab_panel(backups):
|
||||||
|
|
||||||
|
ui.markdown('**Backups**')
|
||||||
|
|
||||||
|
date_format = '%Y-%m-%d_%H-%M'
|
||||||
|
|
||||||
|
@ui.refreshable
|
||||||
|
def backup_list():
|
||||||
|
if not os.path.isdir(os.path.join(scriptpath, backupfolder)):
|
||||||
|
os.makedirs(os.path.join(scriptpath, backupfolder))
|
||||||
|
|
||||||
|
backup_files = []
|
||||||
|
with ui.grid(columns='auto auto auto'):
|
||||||
|
for file in os.listdir(os.path.join(scriptpath, backupfolder)):
|
||||||
|
if file.endswith(".zip"):
|
||||||
|
backup_files.append(file)
|
||||||
|
backup_files.sort()
|
||||||
|
backup_files.reverse()
|
||||||
|
|
||||||
|
if len(backup_files) == 0:
|
||||||
|
ui.label("Keine Backups vorhanden")
|
||||||
|
|
||||||
|
for file 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.button(button_string,
|
||||||
|
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()
|
||||||
|
|
||||||
|
def make_backup():
|
||||||
|
compress = zipfile.ZIP_DEFLATED
|
||||||
|
filename = os.path.join(scriptpath, backupfolder,
|
||||||
|
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)
|
||||||
|
backup_list.refresh()
|
||||||
|
|
||||||
|
ui.button("Neues Backup erstellen", on_click=make_backup)
|
||||||
|
|
||||||
# Alternativ zur Loginseite navigieren
|
# Alternativ zur Loginseite navigieren
|
||||||
else:
|
else:
|
||||||
|
100
lib/api.py
100
lib/api.py
@ -1,4 +1,6 @@
|
|||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
import zipfile
|
||||||
from calendar import month_name
|
from calendar import month_name
|
||||||
from logging import exception
|
from logging import exception
|
||||||
|
|
||||||
@ -16,6 +18,12 @@ import calendar
|
|||||||
@ui.page('/api/month/{username}/{year}-{month}')
|
@ui.page('/api/month/{username}/{year}-{month}')
|
||||||
def page_overview_month(username: str, year: int, month: int):
|
def page_overview_month(username: str, year: int, month: int):
|
||||||
|
|
||||||
|
try:
|
||||||
|
admin_auth = app.storage.user['admin_authenticated']
|
||||||
|
except:
|
||||||
|
admin_auth = False
|
||||||
|
|
||||||
|
if login_is_valid(username) or admin_auth:
|
||||||
data = load_adminsettings()
|
data = load_adminsettings()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -294,11 +302,17 @@ def page_overview_month(username: str, year: int, month: int):
|
|||||||
ui.markdown('#Fehler')
|
ui.markdown('#Fehler')
|
||||||
ui.markdown(str(type(e)))
|
ui.markdown(str(type(e)))
|
||||||
ui.markdown(str(e))
|
ui.markdown(str(e))
|
||||||
|
else:
|
||||||
|
login_mask(target=f'/api/month/{username}/{year}-{month}')
|
||||||
@ui.page('/api/vacation/{username}/{year}')
|
@ui.page('/api/vacation/{username}/{year}')
|
||||||
def page_overview_vacation(username: str, year: int):
|
def page_overview_vacation(username: str, year: int):
|
||||||
|
|
||||||
if login_is_valid(username):
|
try:
|
||||||
|
admin_auth = app.storage.user['admin_authenticated']
|
||||||
|
except:
|
||||||
|
admin_auth = False
|
||||||
|
|
||||||
|
if login_is_valid(username) or admin_auth:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
current_user = user(username)
|
current_user = user(username)
|
||||||
@ -354,8 +368,12 @@ def page_overview_vacation(username: str, year: int):
|
|||||||
|
|
||||||
@ui.page('/api/absence/{username}/{year}')
|
@ui.page('/api/absence/{username}/{year}')
|
||||||
def page_overview_absence(username: str, year: int):
|
def page_overview_absence(username: str, year: int):
|
||||||
|
try:
|
||||||
|
admin_auth = app.storage.user['admin_authenticated']
|
||||||
|
except:
|
||||||
|
admin_auth = False
|
||||||
|
|
||||||
if login_is_valid(username):
|
if login_is_valid(username) or admin_auth:
|
||||||
current_user = user(username)
|
current_user = user(username)
|
||||||
ui.page_title(f"Abwesenheitsübersicht für {current_user.fullname} für {year}")
|
ui.page_title(f"Abwesenheitsübersicht für {current_user.fullname} für {year}")
|
||||||
ui.label(datetime.now().strftime('%d.%m.%Y')).classes('absolute top-5 right-5')
|
ui.label(datetime.now().strftime('%d.%m.%Y')).classes('absolute top-5 right-5')
|
||||||
@ -514,3 +532,79 @@ def json_info(api_key: str):
|
|||||||
|
|
||||||
if not found_key:
|
if not found_key:
|
||||||
return { "data": "none"}
|
return { "data": "none"}
|
||||||
|
|
||||||
|
@ui.page("/api/backup")
|
||||||
|
def backup():
|
||||||
|
try:
|
||||||
|
admin_auth = app.storage.user['admin_authenticated']
|
||||||
|
except:
|
||||||
|
admin_auth = False
|
||||||
|
|
||||||
|
if admin_auth:
|
||||||
|
|
||||||
|
date_format = '%Y-%m-%d_%H-%M'
|
||||||
|
|
||||||
|
pageheader("Backup herunterladen")
|
||||||
|
ui.page_title(f"{app_title} {app_version}")
|
||||||
|
ui.label("Vorhandene Backups:")
|
||||||
|
|
||||||
|
@ui.refreshable
|
||||||
|
def backup_list():
|
||||||
|
if not os.path.isdir(os.path.join(scriptpath, backupfolder)):
|
||||||
|
os.makedirs(os.path.join(scriptpath, backupfolder))
|
||||||
|
|
||||||
|
backup_files = [ ]
|
||||||
|
with ui.grid(columns='auto auto'):
|
||||||
|
for file in os.listdir(os.path.join(scriptpath, backupfolder)):
|
||||||
|
if file.endswith(".zip"):
|
||||||
|
backup_files.append(file)
|
||||||
|
backup_files.sort()
|
||||||
|
backup_files.reverse()
|
||||||
|
|
||||||
|
if len(backup_files) == 0:
|
||||||
|
ui.label("Keine Backups vorhanden")
|
||||||
|
|
||||||
|
for file in backup_files:
|
||||||
|
date_string = file[0:-4]
|
||||||
|
try:
|
||||||
|
date_string_dt = datetime.strptime(date_string, date_format)
|
||||||
|
button_string = date_string_dt.strftime('%d.%m.%Y - %H:%M')
|
||||||
|
except ValueError:
|
||||||
|
button_string = date_string
|
||||||
|
ui.button(button_string, on_click=lambda file=date_string: ui.download.file(f'{file}.zip'))
|
||||||
|
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')
|
||||||
|
with ui.grid(columns=2):
|
||||||
|
ui.button("Löschen", on_click=del_backup)
|
||||||
|
ui.button("Abbrechen", on_click=dialog.close)
|
||||||
|
dialog.open()
|
||||||
|
|
||||||
|
ui.button(icon='delete', on_click=lambda file=date_string: del_backup_dialog(file))
|
||||||
|
|
||||||
|
backup_list()
|
||||||
|
|
||||||
|
ui.separator()
|
||||||
|
|
||||||
|
def make_backup():
|
||||||
|
compress = zipfile.ZIP_DEFLATED
|
||||||
|
filename = os.path.join(scriptpath, backupfolder, 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)
|
||||||
|
backup_list.refresh()
|
||||||
|
|
||||||
|
ui.button("Neues Backup erstellen", on_click=make_backup)
|
||||||
|
|
||||||
|
else:
|
||||||
|
login_mask()
|
@ -10,6 +10,7 @@ app_version = ("0.0.0")
|
|||||||
# Standardpfade
|
# Standardpfade
|
||||||
scriptpath = str(Path(os.path.dirname(os.path.abspath(__file__))).parent.absolute())
|
scriptpath = str(Path(os.path.dirname(os.path.abspath(__file__))).parent.absolute())
|
||||||
userfolder = "users"
|
userfolder = "users"
|
||||||
|
backupfolder = "backup"
|
||||||
|
|
||||||
# Dateinamen
|
# Dateinamen
|
||||||
|
|
||||||
|
23
nice_gui.py
23
nice_gui.py
@ -1,23 +0,0 @@
|
|||||||
# Zeiterfassung
|
|
||||||
# Nice GUI UI
|
|
||||||
|
|
||||||
from nicegui import ui
|
|
||||||
from nicegui.events import ValueChangeEventArguments
|
|
||||||
|
|
||||||
def site_pinpad():
|
|
||||||
|
|
||||||
keys = [
|
|
||||||
[ 1, 2, 3],
|
|
||||||
[ 4, 5, 6],
|
|
||||||
[ 7, 8, 9],
|
|
||||||
[ "<-", 0, "OK"]
|
|
||||||
]
|
|
||||||
|
|
||||||
with ui.row():
|
|
||||||
for y, row in enumerate(keys, 1):
|
|
||||||
for x, key in enumerate(row):
|
|
||||||
button = ui.Button(text=keys[y][x])
|
|
||||||
|
|
||||||
ui.run(port=8090)
|
|
||||||
|
|
||||||
site_pinpad()
|
|
@ -1,28 +0,0 @@
|
|||||||
from nicegui import ui
|
|
||||||
from users import *
|
|
||||||
from definitions import *
|
|
||||||
|
|
||||||
@ui.page('/login')
|
|
||||||
def page_login():
|
|
||||||
ui.label('Loginseite')
|
|
||||||
|
|
||||||
@ui.page('/stamping')
|
|
||||||
def page_stamping():
|
|
||||||
ui.label('Stempelsteite')
|
|
||||||
|
|
||||||
@ui.page('/userlist')
|
|
||||||
def page_userlist():
|
|
||||||
|
|
||||||
def click_button(button):
|
|
||||||
ui.notify(button)
|
|
||||||
|
|
||||||
ui.label(app_title + " " + app_version)
|
|
||||||
|
|
||||||
userlist = list_users()
|
|
||||||
buttons = { }
|
|
||||||
|
|
||||||
for name in userlist:
|
|
||||||
button = ui.button(text=name, on_click=lambda name=name:click_button(name) )
|
|
||||||
buttons[name] = button
|
|
||||||
|
|
||||||
ui.run(port=8090)
|
|
@ -1,29 +0,0 @@
|
|||||||
import tkinter as tk
|
|
||||||
from tkinter import ttk
|
|
||||||
|
|
||||||
# Funktion, die die Farben des Buttons ändert
|
|
||||||
def aendere_farbe(button, bg_farbe, fg_farbe):
|
|
||||||
# Ändern der Hintergrund- und Textfarbe des Buttons
|
|
||||||
button.configure(bg=bg_farbe, fg=fg_farbe)
|
|
||||||
|
|
||||||
# Erstellen des Hauptfensters
|
|
||||||
root = tk.Tk()
|
|
||||||
root.title("Buttons mit unterschiedlichen Farben")
|
|
||||||
|
|
||||||
# Liste von Button-Beschriftungen und den gewünschten Farben
|
|
||||||
button_info = [
|
|
||||||
('Button 1', 'green', 'white'),
|
|
||||||
('Button 2', 'blue', 'yellow'),
|
|
||||||
('Button 3', 'red', 'black')
|
|
||||||
]
|
|
||||||
|
|
||||||
# Erstellen der Buttons aus der Liste
|
|
||||||
buttons = []
|
|
||||||
for text, bg, fg in button_info:
|
|
||||||
# Einen Button erstellen und die Farben ändern
|
|
||||||
button = tk.Button(root, text=text, command=lambda b=button, bg=bg, fg=fg: aendere_farbe(b, bg, fg))
|
|
||||||
button.pack(pady=10)
|
|
||||||
buttons.append(button)
|
|
||||||
|
|
||||||
# Hauptloop starten
|
|
||||||
root.mainloop()
|
|
@ -1,95 +0,0 @@
|
|||||||
# Funktionen bzgl. Timestamps
|
|
||||||
|
|
||||||
import time
|
|
||||||
import datetime
|
|
||||||
import os
|
|
||||||
from definitions import *
|
|
||||||
|
|
||||||
# Zeitstempel schreiben
|
|
||||||
def append_timestamp(filename):
|
|
||||||
# Hole den aktuellen Timestamp in Epoch-Zeit
|
|
||||||
timestamp = int(time.time())
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Öffne die Datei im Anhang-Modus ('a')
|
|
||||||
with open(filename, 'a') as file:
|
|
||||||
# Schreibe den Timestamp in die Datei und füge einen Zeilenumbruch hinzu
|
|
||||||
file.write(f"{timestamp}\n")
|
|
||||||
file.close()
|
|
||||||
except FileNotFoundError:
|
|
||||||
# Fehlende Verzeichnisse anlegen
|
|
||||||
folder_path = os.path.dirname(filename)
|
|
||||||
os.makedirs(folder_path, exist_ok=True)
|
|
||||||
append_timestamp(filename)
|
|
||||||
|
|
||||||
# Anzahl der Zeilen zählen
|
|
||||||
def len_timestamps(filename):
|
|
||||||
try:
|
|
||||||
# Öffne die Datei im Lese-Modus ('r')
|
|
||||||
with open(filename, 'r') as file:
|
|
||||||
# Zähle die Zeilen
|
|
||||||
lines = file.readlines()
|
|
||||||
file.close()
|
|
||||||
print(len(lines))
|
|
||||||
return len(lines)
|
|
||||||
except FileNotFoundError:
|
|
||||||
print(f"Die Datei {filename} wurde nicht gefunden.")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# Stempelzustand auslesen
|
|
||||||
def stempel_zustand(filename):
|
|
||||||
lines = len_timestamps(filename)
|
|
||||||
if lines == 0:
|
|
||||||
print(f"Keine Einträge")
|
|
||||||
elif lines % 2 == 0:
|
|
||||||
return(status_out)
|
|
||||||
else:
|
|
||||||
return(status_in)
|
|
||||||
|
|
||||||
# Letzten Zeitstempel auswerten
|
|
||||||
def get_last_2_timestmaps(filename):
|
|
||||||
with open(filename, 'r') as file:
|
|
||||||
lines = file.readlines()
|
|
||||||
file.close()
|
|
||||||
second_last_line = lines[-2]
|
|
||||||
last_line = lines[-1]
|
|
||||||
last_2_timestmaps = [second_last_line, last_line]
|
|
||||||
return last_2_timestmaps
|
|
||||||
|
|
||||||
# Stempelübersicht zusammenstellen
|
|
||||||
def overview(filename):
|
|
||||||
|
|
||||||
# Öffne die Datei im Lese-Modus ('r')
|
|
||||||
with open(filename, 'r') as file:
|
|
||||||
lines = file.readlines()
|
|
||||||
|
|
||||||
timelist = [[] for i in range(3)]
|
|
||||||
|
|
||||||
for i in range(0, len(lines)):
|
|
||||||
if (i + 1) % 2 == 0:
|
|
||||||
timelist[1].append(lines[i])
|
|
||||||
else:
|
|
||||||
timelist[0].append(lines[i])
|
|
||||||
if len(timelist[0]) > len(timelist[1]):
|
|
||||||
timelist[1].append("")
|
|
||||||
|
|
||||||
for i in range(0, len(timelist[0])):
|
|
||||||
timelist[2].append(int(timelist[1][i])-int(timelist[0][i]))
|
|
||||||
|
|
||||||
for i in range(0, len(timelist[0])):
|
|
||||||
print(convert_timestamp(timelist[0][i], "%d.%m.%Y %H:%M") + " - " + convert_timestamp(timelist[1][i], "%d.%m.%Y %H:%M") + " Dauer: " + convert_duration(timelist[2][i]))
|
|
||||||
|
|
||||||
# Timestamp konvertieren
|
|
||||||
def convert_timestamp(timestamp, format):
|
|
||||||
try:
|
|
||||||
return str(datetime.datetime.fromtimestamp(int(timestamp)).strftime(format))
|
|
||||||
except:
|
|
||||||
return ("...")
|
|
||||||
|
|
||||||
#Zeitdauerdarstellung berechnen
|
|
||||||
def convert_duration(duration):
|
|
||||||
hours = int(duration / 3600)
|
|
||||||
minutes = int((duration - hours * 3600) / 60)
|
|
||||||
seconds = int(duration - hours * 3600 - minutes * 60)
|
|
||||||
|
|
||||||
return(f"{hours:02d}" + ":" + f"{minutes:02d}" + ":" + f"{seconds:02d}")
|
|
@ -1,15 +0,0 @@
|
|||||||
import tkinter as tk
|
|
||||||
|
|
||||||
def aktionSF():
|
|
||||||
label3 = tk.Label(root, text="Aktion durchgeführt", bg="yellow")
|
|
||||||
label3.pack()
|
|
||||||
|
|
||||||
root = tk.Tk()
|
|
||||||
|
|
||||||
label1 = tk.Label(root, text="Hallo Welt", bg="orange")
|
|
||||||
label1.pack()
|
|
||||||
|
|
||||||
schaltf1 = tk.Button(root, text="Aktion durchführen", command=aktionSF)
|
|
||||||
schaltf1.pack()
|
|
||||||
|
|
||||||
root.mainloop()
|
|
87
ui.py
87
ui.py
@ -1,87 +0,0 @@
|
|||||||
# Zeiterfassung
|
|
||||||
# UI
|
|
||||||
from definitions import *
|
|
||||||
|
|
||||||
import tkinter as tk
|
|
||||||
from time import strftime
|
|
||||||
from timestamping import *
|
|
||||||
from users import determine_filename
|
|
||||||
import locale
|
|
||||||
|
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
|
||||||
|
|
||||||
def update_time():
|
|
||||||
string_time = strftime('%A, der %d.%m.%Y - %H:%M:%S')
|
|
||||||
global digital_clock
|
|
||||||
digital_clock.config(text=string_time)
|
|
||||||
digital_clock.after(1000, update_time)
|
|
||||||
|
|
||||||
def ui_stempeln(line_index, user):
|
|
||||||
append_timestamp(determine_filename(user))
|
|
||||||
|
|
||||||
global buttons
|
|
||||||
global in_time_labels
|
|
||||||
global out_time_labels
|
|
||||||
|
|
||||||
last_timestamps = get_last_2_timestmaps(determine_filename(user))
|
|
||||||
|
|
||||||
if stempel_zustand(determine_filename(user)) == status_out:
|
|
||||||
buttons[line_index].configure(relief="raised", bg="red", text=user + "\n" + status_out)
|
|
||||||
in_time_labels[line_index].configure(text=convert_timestamp(last_timestamps[0],"%H:%M"))
|
|
||||||
out_time_labels[line_index].configure(text=convert_timestamp(last_timestamps[-1], "%H:%M"))
|
|
||||||
elif stempel_zustand(determine_filename(user)) == status_in:
|
|
||||||
buttons[line_index].configure(relief="sunken", bg="green", text=user + "\n" + status_in)
|
|
||||||
in_time_labels[line_index].configure(text=convert_timestamp(last_timestamps[-1], "%H:%M"))
|
|
||||||
out_time_labels[line_index].configure(text="")
|
|
||||||
else:
|
|
||||||
buttons[line_index].configure(bg="yellow", text="Fehler")
|
|
||||||
|
|
||||||
def win_stempeln(userlist):
|
|
||||||
stempeln = tk.Tk()
|
|
||||||
stempeln.title(program_name + " " + program_version)
|
|
||||||
#stempeln.geometry("600x400")
|
|
||||||
stempeln.minsize(width=200, height=200)
|
|
||||||
|
|
||||||
global buttons
|
|
||||||
global in_time_labels
|
|
||||||
global out_time_labels
|
|
||||||
global digital_clock
|
|
||||||
|
|
||||||
buttons = [ ]
|
|
||||||
in_time_labels = [ ]
|
|
||||||
out_time_labels = [ ]
|
|
||||||
|
|
||||||
button_index = 0
|
|
||||||
|
|
||||||
digital_clock = tk.Label(stempeln)
|
|
||||||
digital_clock.grid(column=1, row=0)
|
|
||||||
|
|
||||||
update_time()
|
|
||||||
|
|
||||||
frame_stempeln = tk.Frame(stempeln, borderwidth=5, relief="ridge", padx=10, pady=10)
|
|
||||||
frame_stempeln.grid(row=1, column=1)
|
|
||||||
|
|
||||||
tk.Label(frame_stempeln, text="Benutzer:", anchor="w", width=10).grid(row=0, column=1, sticky="w")
|
|
||||||
tk.Label(frame_stempeln, text="Gekommen:").grid(row=0, column=2)
|
|
||||||
tk.Label(frame_stempeln, text="Gegangen:").grid(row=0, column=3)
|
|
||||||
|
|
||||||
#Schleife zur Erzeugung von Stempelzeilen für User
|
|
||||||
for i in userlist:
|
|
||||||
button = tk.Button(frame_stempeln, height=3, compound="left", command=lambda b=button_index, user=i: ui_stempeln(b, user))
|
|
||||||
in_time = tk.Label(frame_stempeln, padx=10)
|
|
||||||
out_time = tk.Label(frame_stempeln, padx=10)
|
|
||||||
if len_timestamps(determine_filename(i)) % 2 == 0:
|
|
||||||
button.configure(relief="raised", bg ="red", text=i + "\n" + status_out)
|
|
||||||
else:
|
|
||||||
button.configure(relief="sunken", bg="green", text=i + "\n" + status_in)
|
|
||||||
button.grid(row=button_index+1, column=1, sticky="ew")
|
|
||||||
in_time.grid(row=button_index+1, column=2)
|
|
||||||
out_time.grid(row=button_index+1, column=3)
|
|
||||||
buttons.append(button)
|
|
||||||
in_time_labels.append(in_time)
|
|
||||||
out_time_labels.append(out_time)
|
|
||||||
button_index+=1
|
|
||||||
|
|
||||||
stempeln.mainloop()
|
|
||||||
|
|
||||||
win_stempeln( ["testuser", "testuser2"])
|
|
27
users.py
27
users.py
@ -1,27 +0,0 @@
|
|||||||
# Zeiterfassung
|
|
||||||
# Benutzerfunktionen
|
|
||||||
|
|
||||||
import os
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from definitions import *
|
|
||||||
|
|
||||||
# Benutzer anhand Verzeichnisse auflisten
|
|
||||||
def list_users():
|
|
||||||
users = [d for d in os.listdir(userfolder) if os.path.isdir(os.path.join(userfolder, d))]
|
|
||||||
return users
|
|
||||||
|
|
||||||
# Dateinamen bestimmen
|
|
||||||
def determine_filename(user, type="stamping"):
|
|
||||||
if type == "stamping":
|
|
||||||
year = str(datetime.datetime.now().year)
|
|
||||||
month = str(datetime.datetime.now().month)
|
|
||||||
completepath = userfolder + "/" + user + "/" + year + "-" + month + ".txt"
|
|
||||||
return completepath
|
|
||||||
|
|
||||||
elif type == "settings":
|
|
||||||
completepath = userfolder +"/" + user + "/" + usersettingsfilename
|
|
||||||
return completepath
|
|
||||||
elif type == "photo":
|
|
||||||
completepath = userfolder + "/" + user + "/" + photofilename
|
|
||||||
return completepath
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"archived": 0,
|
|
||||||
"total_hours": 0
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
1747642816
|
|
||||||
1747642898
|
|
||||||
1747642972
|
|
||||||
1747642976
|
|
||||||
1747643508
|
|
||||||
1747643521
|
|
||||||
1747643564
|
|
||||||
1747643566
|
|
||||||
1747643603
|
|
||||||
1747644615
|
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "filler2",
|
|
||||||
"fullname": "filler2",
|
|
||||||
"password": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
"workhours": {
|
|
||||||
"2025-05-16": {
|
|
||||||
"1": 0,
|
|
||||||
"2": 0,
|
|
||||||
"3": 0,
|
|
||||||
"4": 0,
|
|
||||||
"5": 0,
|
|
||||||
"6": 0,
|
|
||||||
"7": 0,
|
|
||||||
"vacation": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"api_key": "43ec918e7d773cb23ab3113d18059a83fee389ac"
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"archived": 0,
|
|
||||||
"total_hours": 0
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
1747391900
|
|
||||||
1747391907
|
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "filler3",
|
|
||||||
"fullname": "filler3",
|
|
||||||
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
|
|
||||||
"workhours": {
|
|
||||||
"2025-05-16": {
|
|
||||||
"1": "6",
|
|
||||||
"2": "6",
|
|
||||||
"3": "6",
|
|
||||||
"4": "6",
|
|
||||||
"5": "6",
|
|
||||||
"6": 0,
|
|
||||||
"7": 0,
|
|
||||||
"vacation": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"api_key": "9e3f37809cd898a3db340c453df53bd0793a99fa"
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "filler4",
|
|
||||||
"fullname": "filler4",
|
|
||||||
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
|
|
||||||
"api_key": "614e31aab9fcf1373558f100cb2c7a9918349eec",
|
|
||||||
"workhours": {
|
|
||||||
"2025-05-16": {
|
|
||||||
"1": 0,
|
|
||||||
"2": 0,
|
|
||||||
"3": 0,
|
|
||||||
"4": 0,
|
|
||||||
"5": 0,
|
|
||||||
"6": 0,
|
|
||||||
"7": 0,
|
|
||||||
"vacation": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "filler5",
|
|
||||||
"fullname": "filler5",
|
|
||||||
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
|
|
||||||
"api_key": "ad32682beb4e19f78efc1bdae259aee3ccbf9883",
|
|
||||||
"workhours": {
|
|
||||||
"2025-05-16": {
|
|
||||||
"1": 0,
|
|
||||||
"2": 0,
|
|
||||||
"3": 0,
|
|
||||||
"4": 0,
|
|
||||||
"5": 0,
|
|
||||||
"6": 0,
|
|
||||||
"7": 0,
|
|
||||||
"vacation": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "filler6",
|
|
||||||
"fullname": "filler6",
|
|
||||||
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
|
|
||||||
"api_key": "68d974e4ed516795d48d5cb8b7dc8b8ca4144a9b",
|
|
||||||
"workhours": {
|
|
||||||
"2025-05-16": {
|
|
||||||
"1": 0,
|
|
||||||
"2": 0,
|
|
||||||
"3": 0,
|
|
||||||
"4": 0,
|
|
||||||
"5": 0,
|
|
||||||
"6": 0,
|
|
||||||
"7": 0,
|
|
||||||
"vacation": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,112 +0,0 @@
|
|||||||
1743965819
|
|
||||||
1743965909
|
|
||||||
1743966022
|
|
||||||
1743966045
|
|
||||||
1743966047
|
|
||||||
1743966049
|
|
||||||
1743967346
|
|
||||||
1744024713
|
|
||||||
1744024974
|
|
||||||
1744107474
|
|
||||||
1744194019
|
|
||||||
1744194047
|
|
||||||
1744194048
|
|
||||||
1744194049
|
|
||||||
1744194602
|
|
||||||
1744194612
|
|
||||||
1744194613
|
|
||||||
1744194613
|
|
||||||
1744194614
|
|
||||||
1744194709
|
|
||||||
1744196085
|
|
||||||
1744196089
|
|
||||||
1744196089
|
|
||||||
1744196120
|
|
||||||
1744196189
|
|
||||||
1744196243
|
|
||||||
1744196307
|
|
||||||
1744196315
|
|
||||||
1744196324
|
|
||||||
1744196333
|
|
||||||
1744196334
|
|
||||||
1744196335
|
|
||||||
1744196337
|
|
||||||
1744196338
|
|
||||||
1744196339
|
|
||||||
1744196340
|
|
||||||
1744196342
|
|
||||||
1744196343
|
|
||||||
1744196344
|
|
||||||
1744196345
|
|
||||||
1744196346
|
|
||||||
1744196500
|
|
||||||
1744196505
|
|
||||||
1744196506
|
|
||||||
1744196507
|
|
||||||
1744196663
|
|
||||||
1744196665
|
|
||||||
1744196667
|
|
||||||
1744196667
|
|
||||||
1744196719
|
|
||||||
1744196721
|
|
||||||
1744196991
|
|
||||||
1744197071
|
|
||||||
1744197205
|
|
||||||
1744197211
|
|
||||||
1744197213
|
|
||||||
1744197215
|
|
||||||
1744197217
|
|
||||||
1744197218
|
|
||||||
1744197219
|
|
||||||
1744197253
|
|
||||||
1744197300
|
|
||||||
1744197301
|
|
||||||
1744197302
|
|
||||||
1744197303
|
|
||||||
1744197765
|
|
||||||
1744197766
|
|
||||||
1744197769
|
|
||||||
1744197868
|
|
||||||
1744197871
|
|
||||||
1744198070
|
|
||||||
1744198071
|
|
||||||
1744198392
|
|
||||||
1744198393
|
|
||||||
1744210902
|
|
||||||
1744210904
|
|
||||||
1744221414
|
|
||||||
1744221415
|
|
||||||
1744221946
|
|
||||||
1744221947
|
|
||||||
1744222133
|
|
||||||
1744222135
|
|
||||||
1744260500
|
|
||||||
1744260507
|
|
||||||
1744266057
|
|
||||||
1744266059
|
|
||||||
1744266162
|
|
||||||
1744266164
|
|
||||||
1744266742
|
|
||||||
1744266745
|
|
||||||
1744266778
|
|
||||||
1744266779
|
|
||||||
1744266990
|
|
||||||
1744266993
|
|
||||||
1744267045
|
|
||||||
1744267050
|
|
||||||
1744269810
|
|
||||||
1744269812
|
|
||||||
1744269823
|
|
||||||
1744269829
|
|
||||||
1744269915
|
|
||||||
1744269919
|
|
||||||
1744269921
|
|
||||||
1744269922
|
|
||||||
1744269957
|
|
||||||
1744269959
|
|
||||||
1744269961
|
|
||||||
1744269971
|
|
||||||
1744269973
|
|
||||||
1744269974
|
|
||||||
1744270075
|
|
||||||
1744270081
|
|
Binary file not shown.
Before Width: | Height: | Size: 560 KiB |
Binary file not shown.
Before Width: | Height: | Size: 49 KiB |
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "testuser",
|
|
||||||
"name": "Der neue Tester",
|
|
||||||
"password": "123456789",
|
|
||||||
"2024-04-01": {
|
|
||||||
"0": "0",
|
|
||||||
"1": "8",
|
|
||||||
"2": "8",
|
|
||||||
"3": "8",
|
|
||||||
"4": "8",
|
|
||||||
"5": "8",
|
|
||||||
"6": "0",
|
|
||||||
"vacation": "30"
|
|
||||||
},
|
|
||||||
"2024-04-07": {
|
|
||||||
"0": "0",
|
|
||||||
"1": "6",
|
|
||||||
"2": "6",
|
|
||||||
"3": "6",
|
|
||||||
"4": "8",
|
|
||||||
"5": "6",
|
|
||||||
"6": "0",
|
|
||||||
"vacation": "28"
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user