diff --git a/.gitignore b/.gitignore
index 8904f6e..7b5fe5d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,6 @@
.venv
users/
backup/
+Archiv/
+Docker/
+docker-work/
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/Zeiterfassung.iml b/.idea/Zeiterfassung.iml
deleted file mode 100644
index 68b5ff6..0000000
--- a/.idea/Zeiterfassung.iml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2d..0000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 32d08e5..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 7c204cb..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..f97154d
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,24 @@
+FROM debian:latest
+RUN apt update && apt upgrade -y
+RUN apt install python3 python3-pip python3.11-venv locales -y
+RUN mkdir /app
+RUN mkdir /.venv
+RUN mkdir /backup
+RUN mkdir /settings
+RUN python3 -m venv /.venv
+RUN /.venv/bin/pip install nicegui
+RUN /.venv/bin/pip install segno
+RUN /.venv/bin/pip install python-dateutil
+
+RUN sed -i '/de_DE.UTF-8/s/^# //g' /etc/locale.gen && \
+ locale-gen
+ENV LANG de_DE.UTF-8
+ENV LANGUAGE de_DE:de
+ENV LC_ALL de_DE.UTF-8
+
+COPY main.py /app/main.py
+COPY favicon.svg /app/favicon.svg
+COPY lib /app/lib/
+EXPOSE 8090
+ENTRYPOINT ["/.venv/bin/python", "/app/main.py"]
+#ENTRYPOINT exec /app/.venv/bin/python /app/main.py --docker
\ No newline at end of file
diff --git a/__pycache__/zeiterfassung.cpython-38.pyc b/__pycache__/zeiterfassung.cpython-38.pyc
deleted file mode 100644
index 1dcae1d..0000000
Binary files a/__pycache__/zeiterfassung.cpython-38.pyc and /dev/null differ
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..9695725
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,11 @@
+services:
+ test:
+ image: test:0
+ restart: always
+ ports:
+ - 8090:8090
+ volumes:
+ #- /home/alexander/Dokumente/Python/Zeiterfassung:/app
+ - /home/alexander/Dokumente/Python/Zeiterfassung/docker-work/users:/users
+ - /home/alexander/Dokumente/Python/Zeiterfassung/docker-work/backup:/backup
+ - /home/alexander/Dokumente/Python/Zeiterfassung/docker-work/settings:/settings
\ No newline at end of file
diff --git a/lib/admin.py b/lib/admin.py
index 3396e10..7b1ed32 100644
--- a/lib/admin.py
+++ b/lib/admin.py
@@ -1,7 +1,6 @@
from datetime import datetime
import dateutil.easter
-from PIL.SpiderImagePlugin import isInt
from dateutil.easter import *
from nicegui import ui, app, events
@@ -45,6 +44,14 @@ def page_admin():
updates_available = ValueBinder()
updates_available.value = False
+ enabled_because_not_docker = ValueBinder
+ if is_docker():
+ enabled_because_not_docker.value = False
+ scriptpath = "/app"
+ backupfolder = "/backup"
+ else:
+ enabled_because_not_docker.value = True
+
with ui.tabs() as tabs:
time_overview = ui.tab('Zeitdaten')
@@ -739,30 +746,6 @@ Dies kann nicht rückgängig gemacht werden!''')
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")
@@ -784,7 +767,9 @@ Dies kann nicht rückgängig gemacht werden!''')
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')
+ "Portnummer zu klein": lambda value: len(value)>=2}).tooltip("Geben Sie hier die Portnummer ein, unter der die Zeiterfassung erreichbar ist.").props('size=5').bind_enabled_from(enabled_because_not_docker, 'value')
+ if is_docker():
+ port.tooltip("Diese Einstellung ist beim Einsatz von Docker deaktiviert.")
old_port = data["port"]
port.value = old_port
@@ -826,12 +811,12 @@ Dies kann nicht rückgängig gemacht werden!''')
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"])
-
+ reset_visibility = ValueBinder()
def holiday_section():
with ui.card():
ui.markdown('**Feiertage:**')
- reset_visibility = ValueBinder()
+
reset_visibility.value = False
def new_holiday_entry():
@@ -1031,6 +1016,33 @@ Dies kann nicht rückgängig gemacht werden!''')
holiday_section()
+ def save_admin_settings():
+ write_adminsetting("admin_user", admin_user.value)
+ if admin_password.value != "":
+ write_adminsetting("admin_password", hash_password(admin_password.value))
+ else:
+ write_adminsetting("admin_password", data["admin_password"])
+ write_adminsetting("port", port.value)
+ write_adminsetting("secret", secret)
+ write_adminsetting("touchscreen", touchscreen_switch.value)
+ write_adminsetting("times_on_touchscreen", timestamp_switch.value)
+ write_adminsetting("photos_on_touchscreen", photo_switch.value)
+ write_adminsetting("picture_height", picture_height_input.value)
+ write_adminsetting("button_height", button_height_input.value)
+ write_adminsetting("user_notes", notes_switch.value)
+ write_adminsetting("holidays", data["holidays"])
+ write_adminsetting("vacation_application", va_switch.value)
+
+ if int(old_port) != int(port.value):
+ 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")
+ reset_visibility.value = False
+ timetable.refresh()
+
ui.button("Speichern", on_click=save_admin_settings).tooltip("Hiermit werden sämtliche oben gemachten Einstellungen gespeichert.")
with ui.tab_panel(users):
@@ -1393,7 +1405,7 @@ Dies kann nicht rückgängig gemacht werden!''')
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)}")
+ backupfolder_input = ui.input(value=backupfolder).props(f"size={len(backupfolder)}").bind_enabled_from(enabled_because_not_docker, 'value')
def save_new_folder_name():
if os.path.exists(backupfolder_input.value):
write_adminsetting("backup_folder", backupfolder_input.value)
@@ -1405,7 +1417,9 @@ Dies kann nicht rückgängig gemacht werden!''')
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")
+ save_backup_folder_button = ui.button("Speichern", on_click=save_new_folder_name).tooltip("Hiermit können Sie das Backupverzeichnis ändeern").bind_enabled_from(enabled_because_not_docker, 'value')
+ if is_docker():
+ save_backup_folder_button.tooltip("Diese Einstellung ist beim Einsatz von Docker deaktiviert.")
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.")
@@ -1465,7 +1479,10 @@ Dies kann nicht rückgängig gemacht werden!''')
except ValueError:
button_string = date_string
ui.markdown(button_string)
- ui.markdown(f'{round(size/1_000_000,2)} MB')
+ if size > 1_000_000:
+ ui.markdown(f'{round(size/1_000_000,2)} MB')
+ else:
+ ui.markdown(f'{round(size / 1_000, 2)} kB')
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(
@@ -1526,7 +1543,7 @@ Dies kann nicht rückgängig gemacht werden!''')
for file in files:
add = os.path.join(root, file)
target.write(add)
- target.write(usersettingsfilename)
+ target.write(os.path.join(scriptpath, usersettingsfilename), arcname=usersettingsfilename)
target.writestr("app_version.txt", data=app_version)
backup_list.refresh()
n.dismiss()
diff --git a/lib/definitions.py b/lib/definitions.py
index 0196977..130b709 100644
--- a/lib/definitions.py
+++ b/lib/definitions.py
@@ -6,12 +6,21 @@ from pathlib import Path
import hashlib
app_title = "Zeiterfassung"
-app_version = ("0.0.0")
+app_version = "0.0.0"
# Standardpfade
-scriptpath = str(Path(os.path.dirname(os.path.abspath(__file__))).parent.absolute())
+
+def is_docker():
+ cgroup = Path('/proc/self/cgroup')
+ return Path('/.dockerenv').is_file() or (cgroup.is_file() and 'docker' in cgroup.read_text())
+
+if is_docker():
+ scriptpath = "/settings"
+ backupfolder = "/backup"
+else:
+ scriptpath = str(Path(os.path.dirname(os.path.abspath(__file__))).parent.absolute())
+ backupfolder = str(os.path.join(scriptpath, "backup"))
userfolder = "users"
-backupfolder = str(os.path.join(scriptpath, "backup"))
# Dateinamen
@@ -37,7 +46,7 @@ standard_adminsettings = { "admin_user": "admin",
"button_height": 300,
"user_notes": True,
"vacation_application": True,
- "backupfolder": backupfolder,
+ "backup_folder": backupfolder,
"backup_api_key": hashlib.shake_256(bytes(backupfolder, 'utf-8')).hexdigest(20),
"holidays": { }
}
diff --git a/lib/homepage.py b/lib/homepage.py
index 092bad3..71234bc 100644
--- a/lib/homepage.py
+++ b/lib/homepage.py
@@ -199,6 +199,7 @@ def homepage():
overviews = ui.tab('Übersichten')
absence = ui.tab('Urlaubsantrag')
absence.set_visibility(load_adminsettings()["vacation_application"])
+ pw_change = ui.tab("Passwort")
with ui.grid(columns='1fr auto 1fr').classes('w-full items-center'):
ui.space()
@@ -281,6 +282,31 @@ def homepage():
ui.button("Zurückziehen", on_click=retract_va).tooltip("Hiermit wird der oben gewählte Urlaubsantrag zurückgezogen.").classes('w-full')
open_vacation_applications()
+ with ui.tab_panel(pw_change):
+ ui.label("Passwort ändern").classes('font-bold')
+ with ui.grid(columns='auto auto').classes('items-end'):
+ ui.label("Altes Passwort:")
+ old_pw_input = ui.input(password=True)
+ ui.label("Neues Passwort:")
+ new_pw_input = ui.input(password=True)
+ ui.label("Neues Passwort bestätigen:")
+ new_pw_confirm_input = ui.input(password=True)
+ def revert_pw_inputs():
+ old_pw_input.value = ""
+ new_pw_input.value = ""
+ new_pw_confirm_input.value = ""
+ def save_new_password():
+ if hash_password(old_pw_input.value) == current_user.password:
+ if new_pw_input.value == new_pw_confirm_input.value:
+ current_user.password = hash_password(new_pw_input.value)
+ current_user.write_settings()
+ ui.notify("Neues Passwort gespeichert")
+ else:
+ ui.notify("Passwortbestätigung stimmt nicht überein")
+ else:
+ ui.notify("Altes Passwort nicht korrekt")
+ ui.button("Speichern", on_click=save_new_password)
+ ui.button("Zurücksetzen", on_click=revert_pw_inputs)
ui.space()
else:
diff --git a/lib/settings.json b/lib/settings.json
deleted file mode 100644
index fe1226a..0000000
--- a/lib/settings.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "admin_user": "admin",
- "admin_password": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918",
- "port": "8090",
- "secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise",
- "times_on_touchscreen": true,
- "photos_on_touchscreen": true,
- "touchscreen": true,
- "picure_height": 200,
- "button_height": 300,
- "user_notes": true,
- "holidays": {}
-}
\ No newline at end of file
diff --git a/lib/users.py b/lib/users.py
index 3d37674..16381cf 100644
--- a/lib/users.py
+++ b/lib/users.py
@@ -14,25 +14,26 @@ import shutil
import re
from lib.definitions import userfolder, scriptpath, usersettingsfilename, photofilename, status_in, status_out, \
- standard_adminsettings, standard_usersettings, va_file
-
+ standard_adminsettings, standard_usersettings, va_file, is_docker
# Benutzerklasse
class user:
def __init__(self, name):
- self.userfolder = os.path.join(scriptpath, userfolder, name)
+ if not is_docker():
+ self.userfolder = os.path.join(scriptpath, userfolder, name)
+ else:
+ self.userfolder = os.path.join("/users", name)
self.settingsfile = os.path.join(self.userfolder, usersettingsfilename)
self.photofile = os.path.join(self.userfolder, photofilename)
# Stammdaten einlesen
- try:
- with open(self.settingsfile) as json_file:
+ #try:
+ with open(self.settingsfile) as json_file:
data = json.load(json_file)
-
- except:
- print("Fehler beim Erstellen des Datenarrays.")
- #Hier muss noch Fehlerbehandlungcode hin
+ #except:
+ # print("Fehler beim Erstellen des Datenarrays.")
+ # #TODO Hier muss noch Fehlerbehandlungcode hin
self.password = data["password"]
self.workhours = data["workhours"]
@@ -301,10 +302,8 @@ class user:
return { }
def write_notes(self, year, month, day, note_dict):
- print(os.path.join(self.userfolder, f"{int(year)}-{int(month)}.json"))
with open(os.path.join(self.userfolder, f"{int(year)}-{int(month)}.json"), "r") as json_file:
json_data = json.load(json_file)
- print(json_data)
if len(note_dict) == 1:
user_info = list(note_dict)[0]
json_data["notes"][str(day)] = { }
diff --git a/zeiterfassung.py b/main.py
similarity index 94%
rename from zeiterfassung.py
rename to main.py
index f6ebcb3..89c263a 100644
--- a/zeiterfassung.py
+++ b/main.py
@@ -15,8 +15,7 @@ import argparse
from lib.web_ui import hash_password
-
-class Commandline_Header:
+def commandline_header():
message_string = f"{app_title} {app_version}"
underline = ""
for i in range(len(message_string)):
@@ -37,8 +36,7 @@ def main():
def startup_message():
- Commandline_Header()
-
+ commandline_header()
url_string = ""
for i in list(app.urls):
url_string += f"{i}, "
@@ -60,8 +58,13 @@ if __name__ in ("__main__", "__mp_main__"):
parser = argparse.ArgumentParser(description=f'{app_title} {app_version}')
parser.add_argument('--admin-access', help='Zugangsdaten für Administrator einstellen', action="store_true")
args = parser.parse_args()
+
+ if is_docker():
+ scriptpath = "/app"
+ backupfolder = "/backup"
+
if args.admin_access:
- Commandline_Header()
+ commandline_header()
print("Lade Administrationseinstellungen")
admin_settings = load_adminsettings()
print("Geben Sie den neuen Benutzernamen für den Administrationsbenutzer an:")
diff --git a/playgound.py b/playgound.py
deleted file mode 100644
index 8d8184f..0000000
--- a/playgound.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import json
-import urllib.request
-
-from nicegui import ui, app
-
-
-import segno
-
-@app.get("/data")
-async def deliver_data():
- with open("settings.json") as json_file:
- data = json.load(json_file)
- return data
-
-string = ""
-for i in range(1000):
- string += str(i)
-
-qr_code = segno.make_qr(string).svg_data_uri()
-#qr_code.save("qr_code.png", scale=5, border=0)
-ui.image(qr_code)
-
-ui.run(language="de-DE", port=9000)
\ No newline at end of file
diff --git a/qr_scanner.py b/qr_scanner.py
deleted file mode 100644
index 3f4a21e..0000000
--- a/qr_scanner.py
+++ /dev/null
@@ -1,163 +0,0 @@
-#!/usr/bin/env python3
-import base64
-import signal
-import time
-import argparse
-import requests
-
-import cv2
-import numpy as np
-from fastapi import Response
-from playsound3 import playsound
-from definitions import app_title, app_version
-
-from nicegui import Client, app, core, run, ui
-
-class Commandline_Header:
- message_string = f"{app_title} {app_version}"
- underline = ""
- for i in range(len(message_string)):
- underline += "-"
- print(message_string)
- print(underline)
-
-def visual_interface(port=9000):
- # In case you don't have a webcam, this will provide a black placeholder image.
- black_1px = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYGBg+A8AAQQBAHAgZQsAAAAASUVORK5CYII='
- placeholder = Response(content=base64.b64decode(black_1px.encode('ascii')), media_type='image/png')
-
- global convert
- def convert(frame: np.ndarray) -> bytes:
- """Converts a frame from OpenCV to a JPEG image.
-
- This is a free function (not in a class or inner-function),
- to allow run.cpu_bound to pickle it and send it to a separate process.
- """
- _, imencode_image = cv2.imencode('.jpg', frame)
- return imencode_image.tobytes()
-
- global setup
- def setup() -> None:
-
- url_string = ""
- for i in list(app.urls):
- url_string += f"{i}, "
- url_string = url_string[0:-2]
- print("Weboberfläche erreichbar unter: " + url_string)
-
- # OpenCV is used to access the webcam.
- video_capture = cv2.VideoCapture(0)
- detector = cv2.QRCodeDetector()
-
- blocker = False
- blockset = 0
-
-
- @app.get('/video/frame')
- # Thanks to FastAPI's `app.get` it is easy to create a web route which always provides the latest image from OpenCV.
- async def grab_video_frame() -> Response:
- nonlocal blocker
- if time.time() - blockset > 5:
- blocker = False
-
- if not video_capture.isOpened():
- return placeholder
- # The `video_capture.read` call is a blocking function.
- # So we run it in a separate thread (default executor) to avoid blocking the event loop.
- _, frame = await run.io_bound(video_capture.read)
- if frame is None:
- return placeholder
- # `convert` is a CPU-intensive function, so we run it in a separate process to avoid blocking the event loop and GIL.
- jpeg = await run.cpu_bound(convert, frame)
-
- # QR-Handling
-
- def function_call():
- r = requests.get(str(a))
- print(r.content())
- print("Inside Function_call")
- #b = webbrowser.open(str(a))
- if r.status_code == 200:
- print('Erkannt')
- if r.json()["stampstatus"]:
- playsound('ui-on.mp3')
- elif not r.json()["stampstatus"]:
- playsound('ui-off.mp3')
- else:
- playsound('ui-sound.mp3')
- nonlocal blocker
- nonlocal blockset
- blocker = True
- blockset = time.time()
-
- if not blocker:
- _, img = video_capture.read()
- # detect and decode
- data, bbox, _ = detector.detectAndDecode(img)
- # check if there is a QRCode in the image
- if data:
- a = data
- function_call()
- # cv2.imshow("QRCODEscanner", img)
- if cv2.waitKey(1) == ord("q"):
- function_call()
-
- return Response(content=jpeg, media_type='image/jpeg')
-
- # For non-flickering image updates and automatic bandwidth adaptation an interactive image is much better than `ui.image()`.
- video_image = ui.interactive_image().classes('w-full h-full')
- # A timer constantly updates the source of the image.
- # Because data from same paths is cached by the browser,
- # we must force an update by adding the current timestamp to the source.
-
- ui.timer(interval=0.1, callback=lambda: video_image.set_source(f'/video/frame?{time.time()}'))
-
- async def disconnect() -> None:
- """Disconnect all clients from current running server."""
- for client_id in Client.instances:
- await core.sio.disconnect(client_id)
-
- def handle_sigint(signum, frame) -> None:
- # `disconnect` is async, so it must be called from the event loop; we use `ui.timer` to do so.
- ui.timer(0.1, disconnect, once=True)
- # Delay the default handler to allow the disconnect to complete.
- ui.timer(1, lambda: signal.default_int_handler(signum, frame), once=True)
-
- async def cleanup() -> None:
- # This prevents ugly stack traces when auto-reloading on code change,
- # because otherwise disconnected clients try to reconnect to the newly started server.
- await disconnect()
- # Release the webcam hardware so it can be used by other applications again.
- video_capture.release()
-
- app.on_shutdown(cleanup)
- # We also need to disconnect clients when the app is stopped with Ctrl+C,
- # because otherwise they will keep requesting images which lead to unfinished subprocesses blocking the shutdown.
- signal.signal(signal.SIGINT, handle_sigint)
-
-
- # All the setup is only done when the server starts. This avoids the webcam being accessed
- # by the auto-reload main process (see https://github.com/zauberzeug/nicegui/discussions/2321).
- app.on_startup(setup)
- ui.run(favicon="favicon.svg", port=port, language='de-DE', show_welcome_message=False)
-
-if __name__ in ("__main__", "__mp_main__"):
- parser = argparse.ArgumentParser(description=f'{app_title}-QR-Scanner {app_version}')
- parser.add_argument('--webgui', help='Web-GUI starten', action="store_true")
- parser.add_argument('-p', help="Port, über den die Weboberfläche erreichbar ist")
- args = parser.parse_args()
-
- Commandline_Header()
- print("QR-Scanner")
-
- if args.webgui:
- try:
- port = int(args.p)
- except:
- port = False
- if not port == False:
- visual_interface(port)
- else:
- print("Ungültiger Port")
- print("Beende")
- quit()
diff --git a/qr_scanner_example.py b/qr_scanner_example.py
deleted file mode 100644
index a641a08..0000000
--- a/qr_scanner_example.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import cv2
-import webbrowser
-
-cap = cv2.VideoCapture(0)
-# initialize the cv2 QRCode detector
-detector = cv2.QRCodeDetector()
-
-while True:
- _, img = cap.read()
- # detect and decode
- data, bbox, _ = detector.detectAndDecode(img)
- # check if there is a QRCode in the image
- if data:
- a = data
- break
- cv2.imshow("QRCODEscanner", img)
- if cv2.waitKey(1) == ord("q"):
- break
-
-b = webbrowser.open(str(a))
-cap.release()
-cv2.destroyAllWindows()
\ No newline at end of file
diff --git a/settings.json b/settings.json
index 3a7dc0b..72a00b8 100644
--- a/settings.json
+++ b/settings.json
@@ -73,6 +73,7 @@
"2030-10-30": "Reformationstag",
"2030-12-25": "1. Weihnachtsfeiertag",
"2030-12-26": "2. Weihnachtsfeiertag",
- "2025-06-11": "Testeintrag"
+ "2025-06-11": "Testeintrag",
+ "2025-05-31": "Testeintrag"
}
}
\ No newline at end of file
diff --git a/settings.json_bak b/settings.json_bak
deleted file mode 100644
index b1a23bc..0000000
--- a/settings.json_bak
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "admin_user": "admin",
- "admin_password": "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92",
- "port": "8090",
- "secret": "ftgzuhjikg,mt5jn46uzer8sfi9okrmtzjhndfierko5zltjhdgise",
- "holidays": {
- "2024-05-01": "Tag der Arbeit",
- "2024-12-25": "1. Weihnachtsfeiertag",
- "2025-01-01": "Neujahr",
- "2025-05-01": "Tag der Arbeit"
- }
-}
\ No newline at end of file
diff --git a/sounds/3beeps.mp3 b/sounds/3beeps.mp3
deleted file mode 100644
index c14243e..0000000
Binary files a/sounds/3beeps.mp3 and /dev/null differ
diff --git a/sounds/beep.mp3 b/sounds/beep.mp3
deleted file mode 100644
index 0d1f255..0000000
Binary files a/sounds/beep.mp3 and /dev/null differ
diff --git a/sounds/power-on.mp3 b/sounds/power-on.mp3
deleted file mode 100644
index c659616..0000000
Binary files a/sounds/power-on.mp3 and /dev/null differ
diff --git a/sounds/store_beep.mp3 b/sounds/store_beep.mp3
deleted file mode 100644
index ac50b9c..0000000
Binary files a/sounds/store_beep.mp3 and /dev/null differ
diff --git a/sounds/success.mp3 b/sounds/success.mp3
deleted file mode 100644
index c86f6c6..0000000
Binary files a/sounds/success.mp3 and /dev/null differ
diff --git a/sounds/ui-off.mp3 b/sounds/ui-off.mp3
deleted file mode 100644
index 1b8ccb7..0000000
Binary files a/sounds/ui-off.mp3 and /dev/null differ
diff --git a/sounds/ui-on.mp3 b/sounds/ui-on.mp3
deleted file mode 100644
index 834f291..0000000
Binary files a/sounds/ui-on.mp3 and /dev/null differ
diff --git a/sounds/ui-sound.mp3 b/sounds/ui-sound.mp3
deleted file mode 100644
index 1da7ff3..0000000
Binary files a/sounds/ui-sound.mp3 and /dev/null differ
diff --git a/test.json b/test.json
deleted file mode 100644
index f304e61..0000000
--- a/test.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "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"
- }
-}
\ No newline at end of file
diff --git a/users/testuser10/2025-5.json b/users/testuser10/2025-5.json
index ece2b10..942539d 100644
--- a/users/testuser10/2025-5.json
+++ b/users/testuser10/2025-5.json
@@ -1,5 +1,6 @@
{
- "archived": 0,
+ "archived": 1,
"total_hours": 0,
- "absence": {}
+ "absence": {},
+ "overtime": -406441
}
\ No newline at end of file
diff --git a/webcam_example.py b/webcam_example.py
deleted file mode 100644
index b4fbfc4..0000000
--- a/webcam_example.py
+++ /dev/null
@@ -1,113 +0,0 @@
-#!/usr/bin/env python3
-import base64
-import signal
-import time
-import webbrowser
-
-import cv2
-import numpy as np
-from fastapi import Response
-
-from nicegui import Client, app, core, run, ui
-
-# In case you don't have a webcam, this will provide a black placeholder image.
-black_1px = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYGBg+A8AAQQBAHAgZQsAAAAASUVORK5CYII='
-placeholder = Response(content=base64.b64decode(black_1px.encode('ascii')), media_type='image/png')
-
-
-def convert(frame: np.ndarray) -> bytes:
- """Converts a frame from OpenCV to a JPEG image.
-
- This is a free function (not in a class or inner-function),
- to allow run.cpu_bound to pickle it and send it to a separate process.
- """
- _, imencode_image = cv2.imencode('.jpg', frame)
- return imencode_image.tobytes()
-
-
-def setup() -> None:
- # OpenCV is used to access the webcam.
- video_capture = cv2.VideoCapture(0)
- detector = cv2.QRCodeDetector()
-
- blocker = False
- blockset = 0
-
- @app.get('/video/frame')
- # Thanks to FastAPI's `app.get` it is easy to create a web route which always provides the latest image from OpenCV.
- async def grab_video_frame() -> Response:
- nonlocal blocker
- if time.time() - blockset > 5:
- blocker = False
-
- if not video_capture.isOpened():
- return placeholder
- # The `video_capture.read` call is a blocking function.
- # So we run it in a separate thread (default executor) to avoid blocking the event loop.
- _, frame = await run.io_bound(video_capture.read)
- if frame is None:
- return placeholder
- # `convert` is a CPU-intensive function, so we run it in a separate process to avoid blocking the event loop and GIL.
- jpeg = await run.cpu_bound(convert, frame)
-
- # QR-Handling
-
- def function_call():
- b = webbrowser.open(str(a))
- print('\a')
- nonlocal blocker
- nonlocal blockset
- blocker = True
- blockset = time.time()
-
- if not blocker:
- _, img = video_capture.read()
- # detect and decode
- data, bbox, _ = detector.detectAndDecode(img)
- # check if there is a QRCode in the image
- if data:
- a = data
- function_call()
- # cv2.imshow("QRCODEscanner", img)
- if cv2.waitKey(1) == ord("q"):
- function_call()
-
- return Response(content=jpeg, media_type='image/jpeg')
-
- # For non-flickering image updates and automatic bandwidth adaptation an interactive image is much better than `ui.image()`.
- video_image = ui.interactive_image().classes('w-full h-full')
- # A timer constantly updates the source of the image.
- # Because data from same paths is cached by the browser,
- # we must force an update by adding the current timestamp to the source.
-
- ui.timer(interval=0.1, callback=lambda: video_image.set_source(f'/video/frame?{time.time()}'))
-
- async def disconnect() -> None:
- """Disconnect all clients from current running server."""
- for client_id in Client.instances:
- await core.sio.disconnect(client_id)
-
- def handle_sigint(signum, frame) -> None:
- # `disconnect` is async, so it must be called from the event loop; we use `ui.timer` to do so.
- ui.timer(0.1, disconnect, once=True)
- # Delay the default handler to allow the disconnect to complete.
- ui.timer(1, lambda: signal.default_int_handler(signum, frame), once=True)
-
- async def cleanup() -> None:
- # This prevents ugly stack traces when auto-reloading on code change,
- # because otherwise disconnected clients try to reconnect to the newly started server.
- await disconnect()
- # Release the webcam hardware so it can be used by other applications again.
- video_capture.release()
-
- app.on_shutdown(cleanup)
- # We also need to disconnect clients when the app is stopped with Ctrl+C,
- # because otherwise they will keep requesting images which lead to unfinished subprocesses blocking the shutdown.
- signal.signal(signal.SIGINT, handle_sigint)
-
-
-# All the setup is only done when the server starts. This avoids the webcam being accessed
-# by the auto-reload main process (see https://github.com/zauberzeug/nicegui/discussions/2321).
-app.on_startup(setup)
-
-ui.run(port=9005)
\ No newline at end of file