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()