diff --git a/.gitignore b/.gitignore index 894729e..dcf5c72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ __pycache__/ outputs/ +app/__pycache__/ *.db .web .states certs/ reflex* assets/external/ -*.py[cod] .aider* diff --git a/app/app.py (bleibt unverändert) b/app/app.py (bleibt unverändert) deleted file mode 100644 index 210d50a..0000000 --- a/app/app.py (bleibt unverändert) +++ /dev/null @@ -1,309 +0,0 @@ -import reflex as rx -import subprocess -import os -import glob -import datetime -import asyncio -import traceback - -# --- PFADE ABSOLUT DEFINIEREN --- -APP_DIR = os.path.dirname(os.path.abspath(__file__)) -BASE_DIR = os.path.dirname(APP_DIR) - -# Expliziter Pfad zum playbooks/ Ordner -PLAYBOOK_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "playbooks")) -print(f"Playbook directory: {PLAYBOOK_DIR}") -OUTPUT_DIR = os.path.join(BASE_DIR, "outputs") - -# Sichere Ordner-Erstellung -try: - os.makedirs(PLAYBOOK_DIR, exist_ok=True) - os.makedirs(OUTPUT_DIR, exist_ok=True) -except Exception as e: - print(f"Fehler bei der Ordner-Erstellung: {str(e)}") - - -class AppState(rx.State): - playbooks: list[str] = [] - selected_playbook: str = "" - output: str = "" - is_running: bool = False - error: str = "" - show_preview: bool = False - preview_content: str = "" - - def toggle_preview(self): - """Schaltet die Vorschau um.""" - self.show_preview = not self.show_preview - if self.show_preview and self.selected_playbook: - self.load_preview() - - def load_preview(self): - """Lädt den Inhalt des ausgewählten Playbooks für die Vorschau.""" - if not self.selected_playbook: - self.preview_content = "Wählen Sie ein Playbook" - return - try: - playbook_path = os.path.join(PLAYBOOK_DIR, self.selected_playbook) - with open(playbook_path, "r", encoding="utf-8") as f: - self.preview_content = f.read() - except Exception as e: - self.preview_content = f"Fehler beim Laden: {str(e)}" - - past_runs: list[str] = [] - selected_run: str = "" - - def set_selected_playbook(self, val: str): - self.selected_playbook = val - if self.show_preview: - self.load_preview() - - def set_selected_run(self, val: str): - """Setzt den ausgewählten alten Durchlauf und lädt dessen Inhalt.""" - self.selected_run = val - if val: - try: - with open(os.path.join(OUTPUT_DIR, val), "r", encoding="utf-8") as f: - self.output = f.read() - self.error = f"Log geladen: {val}" - except Exception as e: - self.error = f"Fehler beim Laden des Logs: {str(e)}" - - async def load_data(self): - """Triggert das Laden von Playbooks UND alten Durchläufen beim Seitenstart.""" - await self.load_playbooks() - await self.load_past_runs() - - async def load_playbooks(self): - """Lädt alle Playbooks aus dem konfigurierten Ordner.""" - try: - self.playbooks = [] - print(f"Searching for playbooks in: {PLAYBOOK_DIR}") - - if not os.path.exists(PLAYBOOK_DIR): - self.error = f"Playbook directory not found: {PLAYBOOK_DIR}" - print(self.error) - return - - playbook_files = [] - for ext in ("*.yml", "*.yaml"): - playbook_files.extend(glob.glob(os.path.join(PLAYBOOK_DIR, ext))) - - print(f"Found playbook files: {playbook_files}") - - self.playbooks = sorted([os.path.basename(p) for p in playbook_files]) - - if self.playbooks: - if not self.selected_playbook or self.selected_playbook not in self.playbooks: - self.selected_playbook = self.playbooks[0] - else: - self.selected_playbook = "" - self.error = "Keine Playbooks gefunden. Bitte legen Sie welche im playbooks/ Ordner ab." - - print(f"Playbooks: {self.playbooks}") - print(f"Selected: {self.selected_playbook}") - except Exception as e: - self.error = f"Fehler beim Laden der Playbooks: {str(e)}" - print(f"Fehler in load_playbooks: {str(e)}") - print(f"Traceback: {traceback.format_exc()}") - - async def load_past_runs(self): - """Scannt den outputs/ Ordner nach alten Logs, zeigt nur die 20 neuesten an und archiviert den Rest.""" - try: - archive_dir = os.path.join(OUTPUT_DIR, "archiv") - os.makedirs(archive_dir, exist_ok=True) - - log_files = glob.glob(os.path.join(OUTPUT_DIR, "*.txt")) - sorted_files = sorted(log_files, key=os.path.getmtime, reverse=True) - - for file in sorted_files[20:]: - try: - dest = os.path.join(archive_dir, os.path.basename(file)) - os.rename(file, dest) - except Exception as e: - print(f"Fehler beim Archivieren von {file}: {str(e)}") - - remaining_files = glob.glob(os.path.join(OUTPUT_DIR, "*.txt")) - self.past_runs = sorted([os.path.basename(l) for l in remaining_files], - key=lambda x: x.split("_")[0:2], reverse=True)[:20] - except Exception as e: - self.error = f"Fehler beim Laden der Historie: {str(e)}" - print(f"Fehler in load_past_runs: {str(e)}") - - async def run_playbook(self): - """Führt das ausgewählte Playbook asynchron aus und speichert den Output.""" - if not self.selected_playbook: - self.error = "Bitte wählen Sie ein Playbook aus." - return - - self.is_running = True - self.error = "" - self.output = f"Starte Ausführung von {self.selected_playbook}..." - print(f"Starte Playbook: {self.selected_playbook}") - yield - - playbook_path = os.path.join(PLAYBOOK_DIR, self.selected_playbook) - print(f"Playbook Pfad: {playbook_path}") - - timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") - output_filename = f"{timestamp}_{self.selected_playbook.replace('.yml', '').replace('.yaml', '')}.txt" - output_path = os.path.join(OUTPUT_DIR, output_filename) - - try: - proc = await asyncio.create_subprocess_exec( - "ansible-playbook", playbook_path, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE - ) - - try: - stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=300) - except asyncio.TimeoutError: - proc.kill() - await proc.wait() - raise asyncio.TimeoutError("Playbook hat das Timeout überschritten.") - - output_text = stdout.decode() + stderr.decode() - self.output = output_text - - try: - with open(output_path, "w", encoding="utf-8") as f: - f.write(output_text) - await self.load_past_runs() - except Exception as e: - self.error = f"Fehler beim Speichern des Outputs: {str(e)}" - - if proc.returncode != 0: - self.error = f"Playbook wurde mit Fehlercode {proc.returncode} beendet." - else: - self.error = "" - self.error = f"{self.selected_playbook} wurde erfolgreich ausgeführt!" - - except asyncio.TimeoutError as e: - self.error = str(e) - self.output = "Fehler: Timeout" - except Exception as e: - self.error = f"Ein Fehler ist aufgetreten: {str(e)}" - self.output = "Fehler bei der Ausführung" - finally: - self.is_running = False - yield - - -def index(): - return rx.container( - rx.heading("Ansible Playbook Runner", size="6"), - rx.grid( - rx.vstack( - rx.text("Playbook auswählen:", font_weight="bold"), - rx.vstack( - rx.cond( - AppState.playbooks, - rx.select( - AppState.playbooks, - placeholder="Wählen Sie ein Playbook...", - value=AppState.selected_playbook, - on_change=AppState.set_selected_playbook, - width="100%", - ), - rx.text("Keine Playbooks gefunden!", color="red") - ), - rx.cond( - AppState.error, - rx.text(AppState.error, color="red"), - ), - spacing="2" - ), - rx.button( - rx.cond( - AppState.is_running, - rx.hstack(rx.spinner(), "Wird ausgeführt..."), - "Ausführen" - ), - on_click=AppState.run_playbook, - disabled=AppState.is_running, - color_scheme="blue", - width="100%", - ), - rx.divider(margin_y="1em"), - rx.vstack( - rx.button( - "Playbook-Vorschau anzeigen", - on_click=AppState.toggle_preview, - width="100%", - font_weight="bold" - ), - rx.cond( - AppState.show_preview, - rx.code_block( - AppState.preview_content, - language="yaml", - width="100%", - max_height="300px", - ) - ), - width="100%", - spacing="2" - ), - rx.text("Alte Durchläufe ansehen:", font_weight="bold"), - rx.select( - AppState.past_runs, - placeholder="Historie wählen...", - value=AppState.selected_run, - on_change=AppState.set_selected_run, - width="100%", - ), - rx.cond( - AppState.error, - rx.text( - AppState.error, - color="red", - margin_top="1em", - font_weight="bold" - ), - ), - align_items="stretch", - spacing="3", - ), - rx.vstack( - rx.text("Ausgabe / Log-Inhalt:", font_weight="bold"), - rx.text_area( - value=AppState.output, - width="100%", - height="500px", - disabled=True, - resize="none", - ), - width="100%", - ), - columns="1", - sm_columns="1", - md_columns="2", - spacing="5", - margin_top="2em", - width="100%", - ), - padding="2em", - max_width="1200px", - margin="auto", - ) - - -app = rx.App() -app.add_page( - index, - route="/", - on_load=AppState.load_data, - meta=[] -) - -if __name__ == "__main__": - try: - # Nur die benötigten Argumente übergeben; Ports/Hosts kommen aus rxconfig.py - app.run( - host="0.0.0.0", - reload=True, - log_level="debug", - ) - except Exception as e: - print(f"Fehler bei der Ausführung: {str(e)}")