From 610e7e8ca5230150dfb85186e643a6cebfd2dccf Mon Sep 17 00:00:00 2001 From: Sebastian Serfling Date: Wed, 29 Oct 2025 09:56:52 +0100 Subject: [PATCH] Add Monitor --- Monitor.py | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 Monitor.py diff --git a/Monitor.py b/Monitor.py new file mode 100644 index 0000000..8751847 --- /dev/null +++ b/Monitor.py @@ -0,0 +1,168 @@ +import customtkinter as ctk +import os +import threading +import datetime +from pystray import Icon as TrayIcon, Menu, MenuItem +from PIL import Image, ImageDraw +import sys +import subprocess +import webbrowser +import psutil + +LOG_REFRESH_SECONDS = 10 +MAIN_DIR = os.getcwd() +LOG_DIR = os.path.join(MAIN_DIR, 'Logs') +EXPORTER_LOG = os.path.join(LOG_DIR, f"MSSQL_exporter_log_{datetime.datetime.now().strftime('%Y-%m-%d')}.txt") +IMPORTER_LOG = os.path.join(LOG_DIR, f"SDF_importer_log_{datetime.datetime.now().strftime('%Y-%m-%d')}.txt") +MAIN_EXE = os.path.join(MAIN_DIR, 'main.exe') +ENV_FILE = os.path.join(MAIN_DIR, '.env') + + +def resource_path(relative_path): + """Korrekt aufgelöst für PyInstaller und normale Ausführung""" + try: + base_path = sys._MEIPASS + except AttributeError: + base_path = os.path.abspath('.') + return os.path.join(base_path, relative_path) + + +class LogMonitorApp(ctk.CTk): + + def __init__(self): + super().__init__() + self.title('SDF-Dienstüberwachung') + self.geometry('500x350') + ctk.set_appearance_mode('dark') + ctk.set_default_color_theme('blue') + + self.title_label = ctk.CTkLabel(self, text='SDF Monitor', font=ctk.CTkFont(size=24, weight='bold')) + self.title_label.pack(pady=(10, 0)) + + self.status_label = ctk.CTkLabel(self, text='Letzte Ausführungen laden...') + self.status_label.pack(pady=(20, 0)) + + self.seconds_remaining = LOG_REFRESH_SECONDS + self.countdown_label = ctk.CTkLabel(self, text=f'Nächster Check in: {self.seconds_remaining}s') + self.countdown_label.pack(pady=5) + + self.update_logs() + self.update_countdown() + + self.folder_button = ctk.CTkButton(self, text='Logs-Ordner öffnen', command=self.open_log_folder) + self.folder_button.pack(pady=10) + + self.env_button = ctk.CTkButton(self, text='.env Datei öffnen', command=self.open_env_file) + self.env_button.pack(pady=5) + + self.restart_button = ctk.CTkButton(self, text='Services neu starten', command=self.restart_main_exe) + self.restart_button.pack(pady=10) + + self.stop_button = ctk.CTkButton(self, text='Services beenden', command=self.stop_main_exe) + self.stop_button.pack(pady=5) + + logo_path = resource_path('logo.png') + if os.path.exists(logo_path): + from PIL import Image as PILImage + logo_img = PILImage.open(logo_path).resize((120, 40)) + self.logo_image = ctk.CTkImage(light_image=logo_img, dark_image=logo_img, size=(120, 40)) + self.logo_label = ctk.CTkLabel(self, image=self.logo_image, text='', cursor='hand2') + else: + self.logo_label = ctk.CTkLabel(self, text='www.deine-website.de', text_color='skyblue', cursor='hand2') + self.logo_label.pack(pady=5) + self.logo_label.bind('', lambda e: webbrowser.open('https://www.deine-website.de')) + + self.protocol('WM_DELETE_WINDOW', self.hide_to_tray) + self.bind('', self.on_minimize) + + self.create_tray_icon() + + def format_time(self, path): + if os.path.exists(path): + t = datetime.datetime.fromtimestamp(os.path.getmtime(path)) + return t.strftime('%Y-%m-%d %H:%M:%S') + return 'Nie' + + def update_logs(self): + exporter_time = self.format_time(EXPORTER_LOG) + importer_time = self.format_time(IMPORTER_LOG) + self.status_label.configure(text=f'Letzter Export: {exporter_time}\nLetzter Import: {importer_time}') + + def update_countdown(self): + self.countdown_label.configure(text=f'Nächster Check in: {self.seconds_remaining}s') + self.seconds_remaining -= 1 + if self.seconds_remaining <= 0: + self.update_logs() + self.seconds_remaining = LOG_REFRESH_SECONDS + self.after(1000, self.update_countdown) + + def restart_main_exe(self): + try: + if os.path.exists(MAIN_EXE): + subprocess.Popen([MAIN_EXE]) + self.write_status('main.exe wurde neu gestartet.') + else: + self.write_status('main.exe nicht gefunden.') + except Exception as e: + self.write_status(f'Fehler beim Starten von main.exe: {e}') + + def write_status(self, msg): + now = datetime.datetime.now().strftime('%H:%M:%S') + self.status_label.configure(text=f'[{now}] {msg}') + + def open_log_folder(self): + os.startfile(LOG_DIR) + + def open_env_file(self): + if os.path.exists(ENV_FILE): + os.startfile(ENV_FILE) + else: + self.write_status('.env Datei nicht gefunden.') + + def hide_to_tray(self): + self.withdraw() + self.tray_icon.visible = True + + def show_from_tray(self, icon=None, item=None): + self.deiconify() + self.tray_icon.visible = False + + def quit_app(self, icon, item): + self.tray_icon.stop() + self.destroy() + sys.exit() + + def on_minimize(self, event): + if self.state() == 'iconic': + self.hide_to_tray() + + def create_tray_icon(self): + icon_path = resource_path('icon.ico') + if os.path.exists(icon_path): + image = Image.open(icon_path).resize((64, 64)) + else: + image = Image.new('RGB', (64, 64), 'black') + draw = ImageDraw.Draw(image) + draw.rectangle((16, 16, 48, 48), fill='white') + menu = Menu(MenuItem('Öffnen', self.show_from_tray), MenuItem('Beenden', self.quit_app)) + self.tray_icon = TrayIcon('SDF-Monitor', image, 'SDF-Monitor', menu) + threading.Thread(target=self.tray_icon.run, daemon=True).start() + + def stop_main_exe(self): + found = False + for proc in psutil.process_iter(['pid', 'name']): + try: + if proc.info['name'].lower() == 'main.exe': + proc.kill() + found = True + self.write_status('main.exe wurde beendet.') + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + + if not found: + self.write_status('main.exe läuft nicht.') + + +if __name__ == '__main__': + app = LogMonitorApp() + app.mainloop()