208 lines
7.3 KiB
Python
208 lines
7.3 KiB
Python
import reflex as rx
|
|
import subprocess
|
|
import os
|
|
import glob
|
|
import datetime
|
|
import asyncio
|
|
|
|
# --- 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")
|
|
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 = ""
|
|
|
|
# --- NEU: Für die Historie ---
|
|
past_runs: list[str] = []
|
|
selected_run: str = ""
|
|
|
|
def set_selected_playbook(self, val: str):
|
|
self.selected_playbook = val
|
|
|
|
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:
|
|
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 = ""
|
|
except Exception as e:
|
|
self.error = f"Fehler beim Laden der Playbooks: {str(e)}"
|
|
|
|
async def load_past_runs(self):
|
|
"""Scannt den outputs/ Ordner nach alten Logs und sortiert sie (neueste zuerst)."""
|
|
try:
|
|
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)
|
|
except Exception as e:
|
|
self.error = f"Fehler beim Laden der Historie: {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 = "Wird ausgeführt..."
|
|
yield
|
|
|
|
playbook_path = os.path.join(PLAYBOOK_DIR, self.selected_playbook)
|
|
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
|
|
|
|
# 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)}"
|
|
|
|
if proc.returncode != 0:
|
|
self.error = f"Playbook wurde mit Fehlercode {proc.returncode} beendet."
|
|
else:
|
|
self.error = "Playbook 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"),
|
|
|
|
# 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.button(
|
|
"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.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.text(
|
|
AppState.error,
|
|
color=rx.cond(AppState.error, "red", "transparent"),
|
|
margin_top="1em",
|
|
),
|
|
align_items="stretch",
|
|
spacing="3",
|
|
),
|
|
|
|
# Rechte Spalte (Textausgabe)
|
|
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="2",
|
|
spacing="5",
|
|
margin_top="2em",
|
|
),
|
|
padding="2em",
|
|
max_width="1200px", # Etwas breiter gemacht fürs Grid-Layout
|
|
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)
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
app.run(debug=True)
|
|
except Exception as e:
|
|
print(f"Fehler bei der Ausführung: {str(e)}")
|