Merge branch 'backup'
This commit is contained in:
commit
269e6985d0
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)
|
244
lib/admin.py
244
lib/admin.py
@ -14,6 +14,7 @@ from lib.web_ui import *
|
||||
|
||||
import os.path
|
||||
import os
|
||||
import zipfile
|
||||
from stat import S_IREAD, S_IRWXU
|
||||
import hashlib
|
||||
import calendar
|
||||
@ -45,6 +46,7 @@ def page_admin():
|
||||
time_overview = ui.tab('Zeitübersichten')
|
||||
settings = ui.tab('Einstellungen')
|
||||
users = ui.tab('Benutzer')
|
||||
backups = ui.tab('Backups')
|
||||
|
||||
userlist = [ ]
|
||||
|
||||
@ -609,24 +611,21 @@ Dies kann nicht rückgängig gemacht werden!''')
|
||||
ui.markdown("**Administrationsbenutzer:**")
|
||||
with ui.grid(columns=2):
|
||||
def save_admin_settings():
|
||||
output_dict = { }
|
||||
output_dict["admin_user"] = admin_user.value
|
||||
write_adminsetting("admin_user", admin_user.value)
|
||||
if admin_password.value != "":
|
||||
output_dict["admin_password"] = hash_password(admin_password.value)
|
||||
write_adminsetting("admin_password", hash_password(admin_password.value))
|
||||
else:
|
||||
output_dict["admin_password"] = data["admin_password"]
|
||||
output_dict["port"] = port.value
|
||||
output_dict["secret"] = secret
|
||||
output_dict["touchscreen"] = touchscreen_switch.value
|
||||
output_dict["times_on_touchscreen"] = timestamp_switch.value
|
||||
output_dict["photos_on_touchscreen"] = photo_switch.value
|
||||
output_dict["picture_height"] = picture_height_input.value
|
||||
output_dict["button_height"] = button_height_input.value
|
||||
output_dict["user_notes"] = notes_switch.value
|
||||
output_dict["holidays"] = data["holidays"]
|
||||
json_dict = json.dumps(output_dict, indent=4)
|
||||
with open(os.path.join(scriptpath, usersettingsfilename), "w") as outputfile:
|
||||
outputfile.write(json_dict)
|
||||
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"])
|
||||
|
||||
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.")
|
||||
@ -1241,6 +1240,219 @@ Dies kann nicht rückgängig gemacht werden!''')
|
||||
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:
|
||||
|
46
lib/api.py
46
lib/api.py
@ -1,4 +1,6 @@
|
||||
import sys
|
||||
import os
|
||||
import zipfile
|
||||
from calendar import month_name
|
||||
from logging import exception
|
||||
|
||||
@ -16,6 +18,12 @@ import calendar
|
||||
@ui.page('/api/month/{username}/{year}-{month}')
|
||||
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()
|
||||
|
||||
try:
|
||||
@ -294,11 +302,17 @@ def page_overview_month(username: str, year: int, month: int):
|
||||
ui.markdown('#Fehler')
|
||||
ui.markdown(str(type(e)))
|
||||
ui.markdown(str(e))
|
||||
|
||||
else:
|
||||
login_mask(target=f'/api/month/{username}/{year}-{month}')
|
||||
@ui.page('/api/vacation/{username}/{year}')
|
||||
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:
|
||||
current_user = user(username)
|
||||
@ -354,8 +368,12 @@ def page_overview_vacation(username: str, year: int):
|
||||
|
||||
@ui.page('/api/absence/{username}/{year}')
|
||||
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)
|
||||
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')
|
||||
@ -514,3 +532,25 @@ def json_info(api_key: str):
|
||||
|
||||
if not found_key:
|
||||
return { "data": "none"}
|
||||
|
||||
@app.get('/api/backup/{api_key}')
|
||||
def backup_api(api_key: str):
|
||||
date_format = '%Y-%m-%d_%H-%M'
|
||||
searchpath = backupfolder
|
||||
|
||||
def make_backup():
|
||||
compress = zipfile.ZIP_DEFLATED
|
||||
filename = os.path.join(searchpath, 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)
|
||||
if api_key == load_adminsettings()["backup_api_key"]:
|
||||
make_backup()
|
||||
return {"backup": datetime.now().strftime(date_format), "success": True}
|
||||
else:
|
||||
return {"backup": datetime.now().strftime(date_format), "success": False}
|
@ -3,6 +3,7 @@
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
|
||||
app_title = "Zeiterfassung"
|
||||
app_version = ("0.0.0")
|
||||
@ -10,6 +11,7 @@ app_version = ("0.0.0")
|
||||
# Standardpfade
|
||||
scriptpath = str(Path(os.path.dirname(os.path.abspath(__file__))).parent.absolute())
|
||||
userfolder = "users"
|
||||
backupfolder = str(os.path.join(scriptpath, "backup"))
|
||||
|
||||
# Dateinamen
|
||||
|
||||
@ -30,9 +32,11 @@ standard_adminsettings = { "admin_user": "admin",
|
||||
"times_on_touchscreen": True,
|
||||
"photos_on_touchscreen": True,
|
||||
"touchscreen": True,
|
||||
"picure_height": 200,
|
||||
"picture_height": 200,
|
||||
"button_height": 300,
|
||||
"user_notes": True,
|
||||
"backupfolder": backupfolder,
|
||||
"backup_api_key": hashlib.shake_256(bytes(backupfolder, 'utf-8')).hexdigest(20),
|
||||
"holidays": { }
|
||||
}
|
||||
|
||||
|
13
lib/users.py
13
lib/users.py
@ -504,3 +504,16 @@ def load_adminsettings():
|
||||
return data
|
||||
except:
|
||||
return -1
|
||||
|
||||
# bestimmte Admineinstellungen speichern
|
||||
def write_adminsetting(key: str, value):
|
||||
settings_filename = os.path.join(scriptpath, usersettingsfilename)
|
||||
admin_settings = load_adminsettings()
|
||||
try:
|
||||
admin_settings[key] = value
|
||||
json_data = json.dumps(admin_settings, indent=4)
|
||||
with open(settings_filename, 'w') as output_file:
|
||||
output_file.write(json_data)
|
||||
except KeyError:
|
||||
print(f"Kein Einstellungsschlüssel {key} vorhanden.")
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
from datetime import datetime
|
||||
|
||||
from nicegui import ui, app
|
||||
from nicegui import ui, app, events
|
||||
|
||||
from lib.users import *
|
||||
from lib.definitions import *
|
||||
@ -10,6 +10,10 @@ import hashlib
|
||||
import calendar
|
||||
import locale
|
||||
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
|
||||
class pageheader:
|
||||
|
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)
|
@ -9,6 +9,8 @@
|
||||
"picture_height": "100",
|
||||
"button_height": "120",
|
||||
"user_notes": true,
|
||||
"backup_folder": "/home/alexander/Dokumente/Python/Zeiterfassung/backup",
|
||||
"backup_api_key": "6fed93dc4a35308b2c073a8a6f3284afe1fb9946",
|
||||
"holidays": {
|
||||
"2025-01-01": "Neujahr",
|
||||
"2025-04-18": "Karfreitag",
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
1743966330
|
||||
1743966416
|
||||
1744018256
|
||||
1744018315
|
||||
1744018470
|
||||
1744018696
|
||||
1744100316
|
||||
1744100330
|
||||
1744194603
|
||||
1744196086
|
||||
1744196347
|
||||
1744196348
|
||||
1744196349
|
||||
1744196350
|
||||
1744196350
|
||||
1744196351
|
||||
1744197304
|
||||
1744197306
|
||||
1744197767
|
||||
1744197768
|
||||
1744210910
|
||||
1744210912
|
||||
1744210913
|
||||
1744210914
|
||||
1744211937
|
||||
1744211939
|
||||
1744221416
|
||||
1744221418
|
||||
1744221436
|
||||
1744221439
|
||||
1744221562
|
||||
1744221565
|
||||
1744221993
|
||||
1744222004
|
||||
1744222029
|
||||
1744222032
|
||||
1744259777
|
||||
1744259780
|
||||
1744260543
|
||||
1744260545
|
||||
1744266752
|
||||
1744266755
|
||||
1744266781
|
||||
1744266782
|
||||
1744268299
|
||||
1744268300
|
||||
1744269834
|
||||
1744269835
|
||||
1744269841
|
||||
1744269877
|
||||
1744269879
|
||||
1744269923
|
||||
1744269924
|
||||
1744269924
|
||||
1744269925
|
||||
1744269926
|
||||
1744269963
|
||||
1744269964
|
||||
1744269964
|
||||
1744269967
|
||||
1744269969
|
||||
1744269970
|
||||
1744269974
|
||||
1744269977
|
||||
1744269978
|
||||
1744269980
|
||||
1744270078
|
||||
1744270084
|
Binary file not shown.
Before Width: | Height: | Size: 550 KiB |
Binary file not shown.
Before Width: | Height: | Size: 48 KiB |
Loading…
x
Reference in New Issue
Block a user