Ordnerstruktur sortiert
imports angespasst Stylinganpassungen im Adminbereich (Responsive, Farben für die Feiertagsbuttons)
This commit is contained in:
parent
e71f423c81
commit
9e8fa9ad62
@ -7,10 +7,10 @@ from dateutil.easter import *
|
|||||||
from nicegui import ui, app, events
|
from nicegui import ui, app, events
|
||||||
from nicegui.html import button
|
from nicegui.html import button
|
||||||
|
|
||||||
from users import *
|
from lib.users import *
|
||||||
from definitions import *
|
from lib.definitions import *
|
||||||
from calendar import monthrange
|
from calendar import monthrange
|
||||||
from web_ui import *
|
from lib.web_ui import *
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import os
|
import os
|
||||||
@ -60,7 +60,7 @@ def page_admin():
|
|||||||
ui.markdown("##Übersichten")
|
ui.markdown("##Übersichten")
|
||||||
|
|
||||||
# Tabelle konstruieren
|
# Tabelle konstruieren
|
||||||
with ui.card():
|
with ui.card().classes('w-full'):
|
||||||
|
|
||||||
with ui.row() as timetable_header:
|
with ui.row() as timetable_header:
|
||||||
year_binder = ValueBinder()
|
year_binder = ValueBinder()
|
||||||
@ -131,12 +131,12 @@ def page_admin():
|
|||||||
@ui.refreshable
|
@ui.refreshable
|
||||||
def timetable():
|
def timetable():
|
||||||
current_user = user(time_user.value)
|
current_user = user(time_user.value)
|
||||||
with ui.card() as calendar_card:
|
with ui.card().classes('w-full') as calendar_card:
|
||||||
def update_month_and_year():
|
def update_month_and_year():
|
||||||
#current_user = user(time_user.value)
|
#current_user = user(time_user.value)
|
||||||
# Archivstatus
|
# Archivstatus
|
||||||
days_with_errors = current_user.archiving_validity_check(int(select_year.value), int(select_month.value))
|
days_with_errors = current_user.archiving_validity_check(int(select_year.value), int(select_month.value))
|
||||||
with ui.grid(columns='auto auto auto 1fr 1fr 1fr 1fr') as table_grid:
|
with ui.grid(columns='auto auto auto 1fr 1fr 1fr 1fr').classes('w-full md:min-w-[600px] lg:min-w-[800px]') as table_grid:
|
||||||
if int(select_month.value) > 1:
|
if int(select_month.value) > 1:
|
||||||
archive_status = current_user.get_archive_status(int(select_year.value),
|
archive_status = current_user.get_archive_status(int(select_year.value),
|
||||||
int(select_month.value))
|
int(select_month.value))
|
||||||
@ -894,7 +894,7 @@ Dies kann nicht rückgängig gemacht werden!''')
|
|||||||
with ui.row():
|
with ui.row():
|
||||||
for entry in year_dict[year_entry]:
|
for entry in year_dict[year_entry]:
|
||||||
date_label = entry.strftime("%d.%m.")
|
date_label = entry.strftime("%d.%m.")
|
||||||
ui.button(f"{data['holidays'][entry.strftime('%Y-%m-%d')]} ({date_label})", on_click=lambda entry=entry: del_holiday_entry(entry)).classes('text-sm')
|
ui.button(f"{data['holidays'][entry.strftime('%Y-%m-%d')]} ({date_label})", color='cyan-300', on_click=lambda entry=entry: del_holiday_entry(entry)).classes('text-sm')
|
||||||
holiday_buttons_grid()
|
holiday_buttons_grid()
|
||||||
|
|
||||||
holiday_section()
|
holiday_section()
|
@ -4,10 +4,9 @@ from logging import exception
|
|||||||
|
|
||||||
from nicegui import *
|
from nicegui import *
|
||||||
|
|
||||||
import ui
|
from lib.definitions import *
|
||||||
from definitions import *
|
from lib.web_ui import *
|
||||||
from web_ui import *
|
from lib.users import *
|
||||||
from users import *
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import calendar
|
import calendar
|
@ -2,12 +2,13 @@
|
|||||||
# Quasi-Konstanten
|
# Quasi-Konstanten
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
app_title = "Zeiterfassung"
|
app_title = "Zeiterfassung"
|
||||||
app_version = ("0.0.0")
|
app_version = ("0.0.0")
|
||||||
|
|
||||||
# Standardpfade
|
# Standardpfade
|
||||||
scriptpath = os.path.dirname(os.path.abspath(__file__))
|
scriptpath = str(Path(os.path.dirname(os.path.abspath(__file__))).parent.absolute())
|
||||||
userfolder = "users"
|
userfolder = "users"
|
||||||
|
|
||||||
# Dateinamen
|
# Dateinamen
|
@ -5,15 +5,15 @@ from nicegui import ui, app, Client
|
|||||||
from nicegui.page import page
|
from nicegui.page import page
|
||||||
|
|
||||||
|
|
||||||
from users import *
|
from lib.users import *
|
||||||
from definitions import *
|
from lib.definitions import *
|
||||||
from calendar import monthrange, month_name
|
from calendar import monthrange, month_name
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import calendar
|
import calendar
|
||||||
import locale
|
import locale
|
||||||
|
|
||||||
from web_ui import *
|
from lib.web_ui import *
|
||||||
|
|
||||||
@ui.page('/')
|
@ui.page('/')
|
||||||
def homepage():
|
def homepage():
|
||||||
@ -24,7 +24,7 @@ def homepage():
|
|||||||
current_user = user(app.storage.user["active_user"])
|
current_user = user(app.storage.user["active_user"])
|
||||||
except:
|
except:
|
||||||
del(app.storage.user["active_user"])
|
del(app.storage.user["active_user"])
|
||||||
ui.navigate.to('/')
|
ui.navigate.reload()
|
||||||
pageheader(f"Willkommen, {current_user.fullname}")
|
pageheader(f"Willkommen, {current_user.fullname}")
|
||||||
|
|
||||||
today = datetime.datetime.now()
|
today = datetime.datetime.now()
|
@ -1,10 +1,10 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from nicegui import ui, app
|
from nicegui import ui, app
|
||||||
from web_ui import *
|
from lib.web_ui import *
|
||||||
|
|
||||||
from users import *
|
from lib.users import *
|
||||||
from definitions import *
|
from lib.definitions import *
|
||||||
from calendar import monthrange
|
from calendar import monthrange
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
13
lib/settings.json
Normal file
13
lib/settings.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"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": {}
|
||||||
|
}
|
@ -2,9 +2,9 @@ from datetime import datetime
|
|||||||
|
|
||||||
from nicegui import ui, app
|
from nicegui import ui, app
|
||||||
|
|
||||||
from users import *
|
from lib.users import *
|
||||||
from definitions import *
|
from lib.definitions import *
|
||||||
from web_ui import *
|
from lib.web_ui import *
|
||||||
from calendar import monthrange
|
from calendar import monthrange
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
@ -11,7 +11,7 @@ import json
|
|||||||
import shutil
|
import shutil
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from definitions import userfolder, scriptpath, usersettingsfilename, photofilename, status_in, status_out, standard_adminsettings, standard_usersettings
|
from lib.definitions import userfolder, scriptpath, usersettingsfilename, photofilename, status_in, status_out, standard_adminsettings, standard_usersettings
|
||||||
|
|
||||||
# Benutzerklasse
|
# Benutzerklasse
|
||||||
|
|
@ -2,8 +2,8 @@ from datetime import datetime
|
|||||||
|
|
||||||
from nicegui import ui, app
|
from nicegui import ui, app
|
||||||
|
|
||||||
from users import *
|
from lib.users import *
|
||||||
from definitions import *
|
from lib.definitions import *
|
||||||
from calendar import monthrange
|
from calendar import monthrange
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
22
qr_scanner_example.py
Normal file
22
qr_scanner_example.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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()
|
BIN
sounds/3beeps.mp3
Normal file
BIN
sounds/3beeps.mp3
Normal file
Binary file not shown.
BIN
sounds/beep.mp3
Normal file
BIN
sounds/beep.mp3
Normal file
Binary file not shown.
BIN
sounds/power-on.mp3
Normal file
BIN
sounds/power-on.mp3
Normal file
Binary file not shown.
BIN
sounds/store_beep.mp3
Normal file
BIN
sounds/store_beep.mp3
Normal file
Binary file not shown.
BIN
sounds/success.mp3
Normal file
BIN
sounds/success.mp3
Normal file
Binary file not shown.
BIN
sounds/ui-off.mp3
Normal file
BIN
sounds/ui-off.mp3
Normal file
Binary file not shown.
BIN
sounds/ui-on.mp3
Normal file
BIN
sounds/ui-on.mp3
Normal file
Binary file not shown.
BIN
sounds/ui-sound.mp3
Normal file
BIN
sounds/ui-sound.mp3
Normal file
Binary file not shown.
217
ui.py
217
ui.py
@ -1,217 +0,0 @@
|
|||||||
# Zeiterfassung
|
|
||||||
# UI Definitionen
|
|
||||||
|
|
||||||
import tkinter as tk
|
|
||||||
import locale
|
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
|
||||||
|
|
||||||
from time import strftime
|
|
||||||
from definitions import app_title, app_version, status_in
|
|
||||||
from users import user as uo
|
|
||||||
from users import list_users
|
|
||||||
|
|
||||||
# Pinpad
|
|
||||||
|
|
||||||
class win_pinpad(tk.Toplevel):
|
|
||||||
def __init__(self, parent):
|
|
||||||
super().__init__(parent)
|
|
||||||
|
|
||||||
def update_time():
|
|
||||||
string_time = strftime('%A, der %d.%m.%Y - %H:%M:%S')
|
|
||||||
nonlocal digital_clock
|
|
||||||
digital_clock.config(text=string_time)
|
|
||||||
digital_clock.after(1000, update_time)
|
|
||||||
|
|
||||||
self.title(app_title + " " + app_version)
|
|
||||||
|
|
||||||
# Digital clock label configuration
|
|
||||||
digital_clock = tk.Label(self)
|
|
||||||
digital_clock.grid(row=0, column=0, columnspan=3, padx=10, pady=10)
|
|
||||||
# Initial call to update_time function
|
|
||||||
update_time()
|
|
||||||
|
|
||||||
# Benutzernummer
|
|
||||||
def usernr_changed(UserNr):
|
|
||||||
nonlocal usernr
|
|
||||||
if len(str(usernr.get())) > 0:
|
|
||||||
buttons["OK"].configure(state="active")
|
|
||||||
else:
|
|
||||||
buttons["OK"].configure(state="disabled")
|
|
||||||
|
|
||||||
|
|
||||||
tk.Label(self, text="Benutzernummer:").grid(row=1, column=0)
|
|
||||||
UserNr = tk.StringVar()
|
|
||||||
UserNr.trace("w", lambda name, index, mode, UserNr=UserNr: usernr_changed(UserNr))
|
|
||||||
usernr = tk.Entry(self, width=10, textvariable=UserNr)
|
|
||||||
usernr.grid(row=1,column=1)
|
|
||||||
|
|
||||||
# Pinpad
|
|
||||||
|
|
||||||
def buttonPress(key):
|
|
||||||
|
|
||||||
nonlocal usernr
|
|
||||||
if type(key) is int:
|
|
||||||
if key < 10:
|
|
||||||
usernr.insert('end', str(key))
|
|
||||||
if key =="OK":
|
|
||||||
print("OK pressed")
|
|
||||||
if key == "<-":
|
|
||||||
usernr.delete(usernr.index("end") - 1 )
|
|
||||||
if len(usernr.get()) > 0:
|
|
||||||
buttons["OK"].configure(state="active")
|
|
||||||
else:
|
|
||||||
buttons["OK"].configure(state="disabled")
|
|
||||||
|
|
||||||
# Buttons definieren
|
|
||||||
button_width = 7
|
|
||||||
button_height = 3
|
|
||||||
pinframe = tk.Frame(self)
|
|
||||||
pinframe.grid(row=2, column=0, columnspan=3, padx=10, pady=10)
|
|
||||||
buttons = { }
|
|
||||||
|
|
||||||
keys = [
|
|
||||||
[ 1, 2, 3],
|
|
||||||
[ 4, 5, 6],
|
|
||||||
[ 7, 8, 9],
|
|
||||||
[ "<-", 0, "OK"]
|
|
||||||
]
|
|
||||||
|
|
||||||
for y, row in enumerate(keys, 1):
|
|
||||||
for x, key in enumerate(row):
|
|
||||||
button = tk.Button(pinframe, width=button_width, height=button_height, text=key, command=lambda key=key: buttonPress(key))
|
|
||||||
button.grid(row=y, column=x)
|
|
||||||
buttons[key] = button
|
|
||||||
|
|
||||||
buttons["OK"].configure(state="disabled")
|
|
||||||
|
|
||||||
usernr.focus_set()
|
|
||||||
|
|
||||||
class win_userlist(tk.Toplevel):
|
|
||||||
def __init__(self, parent):
|
|
||||||
super().__init__(parent)
|
|
||||||
|
|
||||||
def update_time():
|
|
||||||
string_time = strftime('%A, der %d.%m.%Y - %H:%M:%S')
|
|
||||||
nonlocal digital_clock
|
|
||||||
digital_clock.config(text=string_time)
|
|
||||||
digital_clock.after(1000, update_time)
|
|
||||||
|
|
||||||
self.title(app_title + " " + app_version)
|
|
||||||
|
|
||||||
# Digital clock label configuration
|
|
||||||
digital_clock = tk.Label(self)
|
|
||||||
digital_clock.grid(row=0, column=0, columnspan=3, padx=10, pady=10)
|
|
||||||
# Initial call to update_time function
|
|
||||||
update_time()
|
|
||||||
|
|
||||||
tk.Label(self, text="Benutzer auswählen").grid(row=1, column=0, columnspan=2)
|
|
||||||
|
|
||||||
# Button Frame
|
|
||||||
button_frame = tk.Frame(self)
|
|
||||||
button_frame.grid(row=2, column=0, columnspan=2, padx=0, pady=10)
|
|
||||||
userlist = list_users()
|
|
||||||
|
|
||||||
|
|
||||||
# Button Dictionary
|
|
||||||
buttons = { }
|
|
||||||
button_row_index = 0
|
|
||||||
|
|
||||||
for name in userlist:
|
|
||||||
button = tk.Button(button_frame, text=name)
|
|
||||||
button.grid(row=button_row_index, column=0, pady=5, sticky="ew")
|
|
||||||
buttons[name] = button
|
|
||||||
button_row_index = button_row_index + 1
|
|
||||||
|
|
||||||
class win_stamping(tk.Toplevel):
|
|
||||||
def __init__(self, parent, user):
|
|
||||||
super().__init__(parent)
|
|
||||||
def update_time():
|
|
||||||
string_time = strftime('%A, der %d.%m.%Y - %H:%M:%S')
|
|
||||||
nonlocal digital_clock
|
|
||||||
digital_clock.config(text=string_time)
|
|
||||||
digital_clock.after(1000, update_time)
|
|
||||||
|
|
||||||
self.title(app_title + " " + app_version)
|
|
||||||
|
|
||||||
# Benutzer feststellen
|
|
||||||
|
|
||||||
current_user = uo(user)
|
|
||||||
|
|
||||||
# Digital clock label configuration
|
|
||||||
digital_clock = tk.Label(self)
|
|
||||||
digital_clock.grid(row=0, column=0, columnspan=3, padx=10, pady=10)
|
|
||||||
# Initial call to update_time function
|
|
||||||
update_time()
|
|
||||||
|
|
||||||
# Benutzer anzeigen
|
|
||||||
tk.Label(self, text=current_user.fullname).grid(row=1, column=0, pady=10, columnspan=3)
|
|
||||||
|
|
||||||
todays_hours = tk.Label(self, text="Arbeitsstunden erscheinen hier")
|
|
||||||
todays_hours.grid(row=2, column=0, pady=10, columnspan=3)
|
|
||||||
|
|
||||||
in_button = tk.Button(self, text="Einstempeln", bg="green")
|
|
||||||
out_button = tk.Button(self, text="Ausstempeln", bg="red")
|
|
||||||
|
|
||||||
if current_user.stamp_status() == status_in:
|
|
||||||
in_button.configure(state="disabled")
|
|
||||||
out_button.configure(state="active")
|
|
||||||
out_button.focus_set()
|
|
||||||
else:
|
|
||||||
in_button.configure(state="active")
|
|
||||||
out_button.configure(state="disabled")
|
|
||||||
in_button.focus_set()
|
|
||||||
in_button.grid(row=3, column = 0)
|
|
||||||
out_button.grid(row=3, column=2)
|
|
||||||
|
|
||||||
button_frame = tk.Frame(self, relief="groove")
|
|
||||||
button_frame.grid(row=4, column=0, columnspan=3, pady=10)
|
|
||||||
|
|
||||||
overview_workinghours = tk.Button(button_frame, text="Übersicht Arbeitszeiten")
|
|
||||||
overview_missed = tk.Button(button_frame, text="Übersicht Fehlzeiten")
|
|
||||||
overview_data = tk.Button(button_frame, text="Stammdaten")
|
|
||||||
|
|
||||||
overview_workinghours.grid(row=0, column=0, sticky="ew")
|
|
||||||
overview_missed.grid(row=1, column=0, sticky="ew")
|
|
||||||
overview_data.grid(row=2, column=0, sticky="ew")
|
|
||||||
|
|
||||||
button_close = tk.Button(self, text="Schließen")
|
|
||||||
button_close.grid(row=5, column=1)
|
|
||||||
|
|
||||||
#========================================================
|
|
||||||
|
|
||||||
class mainwindow(tk.Tk):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.geometry('300x200')
|
|
||||||
self.title('Main Window')
|
|
||||||
|
|
||||||
# place a button on the root window
|
|
||||||
tk.Button(self,
|
|
||||||
text='PinPad Window',
|
|
||||||
command=self.open_pinpad).pack(expand=True)
|
|
||||||
tk.Button(self,
|
|
||||||
text='Userlist Window',
|
|
||||||
command=self.open_userlist).pack(expand=True)
|
|
||||||
tk.Button(self,
|
|
||||||
text='Stamping Window',
|
|
||||||
command=self.open_stamping).pack(expand=True)
|
|
||||||
|
|
||||||
def open_pinpad(self):
|
|
||||||
window = win_pinpad(self)
|
|
||||||
window.grab_set()
|
|
||||||
|
|
||||||
def open_userlist(self):
|
|
||||||
window = win_userlist(self)
|
|
||||||
window.grab_set()
|
|
||||||
|
|
||||||
def open_stamping(self):
|
|
||||||
window = win_stamping(self, user="testuser")
|
|
||||||
window.grab_set()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app = mainwindow()
|
|
||||||
app.mainloop()
|
|
||||||
|
|
||||||
|
|
4
users/filler2/2025-5.json
Normal file
4
users/filler2/2025-5.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"archived": 0,
|
||||||
|
"total_hours": 0
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
1747642816
|
||||||
|
1747642898
|
||||||
|
1747642972
|
||||||
|
1747642976
|
||||||
|
1747643508
|
||||||
|
1747643521
|
||||||
|
1747643564
|
||||||
|
1747643566
|
||||||
|
1747643603
|
||||||
|
1747644615
|
@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"username": "filler2",
|
"username": "filler2",
|
||||||
"fullname": "filler2",
|
"fullname": "filler2",
|
||||||
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
|
"password": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
"api_key": "0f36286bf8c96de1922ab41e2682ba5a81793525",
|
|
||||||
"workhours": {
|
"workhours": {
|
||||||
"2025-05-16": {
|
"2025-05-16": {
|
||||||
"1": 0,
|
"1": 0,
|
||||||
@ -14,5 +13,6 @@
|
|||||||
"7": 0,
|
"7": 0,
|
||||||
"vacation": 0
|
"vacation": 0
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"api_key": "43ec918e7d773cb23ab3113d18059a83fee389ac"
|
||||||
}
|
}
|
@ -2,17 +2,17 @@
|
|||||||
"username": "filler3",
|
"username": "filler3",
|
||||||
"fullname": "filler3",
|
"fullname": "filler3",
|
||||||
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
|
"password": "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f",
|
||||||
"api_key": "9e3f37809cd898a3db340c453df53bd0793a99fa",
|
|
||||||
"workhours": {
|
"workhours": {
|
||||||
"2025-05-16": {
|
"2025-05-16": {
|
||||||
"1": 0,
|
"1": "6",
|
||||||
"2": 0,
|
"2": "6",
|
||||||
"3": 0,
|
"3": "6",
|
||||||
"4": 0,
|
"4": "6",
|
||||||
"5": 0,
|
"5": "6",
|
||||||
"6": 0,
|
"6": 0,
|
||||||
"7": 0,
|
"7": 0,
|
||||||
"vacation": 0
|
"vacation": 0
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"api_key": "9e3f37809cd898a3db340c453df53bd0793a99fa"
|
||||||
}
|
}
|
@ -11,22 +11,13 @@
|
|||||||
"13": "U"
|
"13": "U"
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"5": {
|
"5": {},
|
||||||
"user": "Jo, das ging echt ab.",
|
"4": {},
|
||||||
"admin": "Streik\n\nUnd anderes"
|
|
||||||
},
|
|
||||||
"4": {
|
|
||||||
"admin": "Testeintrag\n\nZusatzeintrag"
|
|
||||||
},
|
|
||||||
"2": {},
|
"2": {},
|
||||||
"1": {},
|
"1": {},
|
||||||
"9": {
|
"9": {},
|
||||||
"user": "Dieses ist ein Testeintrag.",
|
"12": {},
|
||||||
"admin": "Das sollte der Testuser nicht sehen"
|
"14": {},
|
||||||
},
|
"22": {}
|
||||||
"12": {
|
|
||||||
"user": "Testtext"
|
|
||||||
},
|
|
||||||
"14": {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
113
webcam_example.py
Normal file
113
webcam_example.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#!/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)
|
@ -1,19 +1,19 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# Zeiterfassung
|
# Zeiterfassung
|
||||||
|
|
||||||
from web_ui import *
|
from lib.web_ui import *
|
||||||
from admin import *
|
from lib.admin import *
|
||||||
from login import *
|
from lib.login import *
|
||||||
from users import *
|
from lib.users import *
|
||||||
from touchscreen import *
|
from lib.touchscreen import *
|
||||||
from definitions import *
|
from lib.definitions import *
|
||||||
from api import *
|
from lib.api import *
|
||||||
from homepage import *
|
from lib.homepage import *
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from web_ui import hash_password
|
from lib.web_ui import hash_password
|
||||||
|
|
||||||
|
|
||||||
class Commandline_Header:
|
class Commandline_Header:
|
Loading…
x
Reference in New Issue
Block a user