This commit is contained in:
jonnybravo
2026-07-20 16:20:15 +02:00
parent 23ef289075
commit aeb7626194
30 changed files with 585 additions and 98 deletions

View File

@@ -4,12 +4,15 @@ 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)
PLAYBOOK_DIR = os.path.join(BASE_DIR, "playbooks")
# 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
@@ -26,13 +29,34 @@ class AppState(rx.State):
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)}"
# --- NEU: Für die Historie ---
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."""
@@ -53,23 +77,58 @@ class AppState(rx.State):
async def load_playbooks(self):
"""Lädt alle Playbooks aus dem konfigurierten Ordner."""
try:
playbook_files = glob.glob(os.path.join(PLAYBOOK_DIR, "*.yml")) + \
glob.glob(os.path.join(PLAYBOOK_DIR, "*.yaml"))
self.playbooks = [os.path.basename(p) for p in playbook_files]
if self.playbooks and not self.selected_playbook:
self.selected_playbook = self.playbooks[0]
self.error = ""
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 und sortiert sie (neueste zuerst)."""
"""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"))
# Sortiert nach Dateiname absteigend (da Zeitstempel vorne steht, ist das neueste oben)
self.past_runs = sorted([os.path.basename(l) for l in log_files], reverse=True)
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."""
@@ -79,10 +138,13 @@ class AppState(rx.State):
self.is_running = True
self.error = ""
self.output = "Wird ausgeführt..."
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)
@@ -104,11 +166,9 @@ class AppState(rx.State):
output_text = stdout.decode() + stderr.decode()
self.output = output_text
# Output speichern
try:
with open(output_path, "w", encoding="utf-8") as f:
f.write(output_text)
# Direkt die Historie aktualisieren, damit das neue Log auftaucht
await self.load_past_runs()
except Exception as e:
self.error = f"Fehler beim Speichern des Outputs: {str(e)}"
@@ -116,7 +176,8 @@ class AppState(rx.State):
if proc.returncode != 0:
self.error = f"Playbook wurde mit Fehlercode {proc.returncode} beendet."
else:
self.error = "Playbook erfolgreich ausgeführt."
self.error = ""
self.error = f"{self.selected_playbook} wurde erfolgreich ausgeführt!"
except asyncio.TimeoutError as e:
self.error = str(e)
@@ -132,30 +193,58 @@ class AppState(rx.State):
def index():
return rx.container(
rx.heading("Ansible Playbook Runner", size="6"),
# Ein Grid layout: Links Steuerung & Historie, rechts das große Log-Fenster
rx.grid(
# Linke Spalte (Bedienung)
rx.vstack(
rx.text("Playbook auswählen:", font_weight="bold"),
rx.select(
AppState.playbooks,
placeholder="Wählen Sie ein Playbook...",
value=AppState.selected_playbook,
on_change=AppState.set_selected_playbook,
width="100%",
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(
"Ausführen",
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"),
# --- NEU: Historie Dropdown ---
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,
@@ -164,17 +253,18 @@ def index():
on_change=AppState.set_selected_run,
width="100%",
),
rx.text(
rx.cond(
AppState.error,
color=rx.cond(AppState.error, "red", "transparent"),
margin_top="1em",
rx.text(
AppState.error,
color="red",
margin_top="1em",
font_weight="bold"
),
),
align_items="stretch",
spacing="3",
),
# Rechte Spalte (Textausgabe)
rx.vstack(
rx.text("Ausgabe / Log-Inhalt:", font_weight="bold"),
rx.text_area(
@@ -186,22 +276,34 @@ def index():
),
width="100%",
),
columns="2",
columns="1",
sm_columns="1",
md_columns="2",
spacing="5",
margin_top="2em",
width="100%",
),
padding="2em",
max_width="1200px", # Etwas breiter gemacht fürs Grid-Layout
max_width="1200px",
margin="auto",
)
app = rx.App()
# Wichtig: Wir rufen beim Laden jetzt "load_data" auf, um beides zu laden
app.add_page(index, route="/", on_load=AppState.load_data)
app.add_page(
index,
route="/",
on_load=AppState.load_data,
meta=[]
)
if __name__ == "__main__":
try:
app.run(debug=True)
# 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)}")

View File

@@ -1,13 +0,0 @@
---
- name: Hallo Welt ausgeben
hosts: localhost
become: no
tasks:
- name: Hallo Welt ausgeben
debug:
msg: "Hallo {{ ansible_facts.hostname }}! Die Uhrzeit ist {{ ansible_facts.date_time.iso8601 }}"
- name: Uhrzeit ausgeben
debug:
msg: "Die Uhrzeit ist {{ ansible_facts.date_time.iso8601 }}"

View File

@@ -102,7 +102,14 @@ class AppState(rx.State):
if proc.returncode != 0:
self.error = f"Playbook wurde mit Fehlercode {proc.returncode} beendet."
else:
self.error = "Playbook erfolgreich ausgeführt."
duration = (datetime.datetime.now() - start_time).total_seconds()
self.error = ""
rx.notify(
title="Erfolgreich ausgeführt",
description=f"{self.selected_playbook} in {duration:.2f} Sekunden abgeschlossen",
status="success",
duration=3000
)
except asyncio.TimeoutError as e:
self.error = str(e)