This commit is contained in:
jonnybravo
2026-07-20 10:27:09 +02:00
commit 23ef289075
25 changed files with 1511 additions and 0 deletions

0
app/__init__.py Normal file
View File

207
app/app.py Normal file
View File

@@ -0,0 +1,207 @@
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)}")

13
app/playbooks/hello_world.yml Executable file
View File

@@ -0,0 +1,13 @@
---
- 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 }}"

167
app/sicherung_1.py Normal file
View File

@@ -0,0 +1,167 @@
import reflex as rx
import subprocess
import os
import glob
import datetime
import asyncio
# --- PFADE ABSOLUT DEFINIEREN ---
# Holt das Verzeichnis, in dem die app.py liegt (test_ansible/app/)
APP_DIR = os.path.dirname(os.path.abspath(__file__))
# Geht eine Ebene höher in das Hauptverzeichnis (test_ansible/)
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 = ""
# Füge diesen Event-Handler manuell hinzu:
def set_selected_playbook(self, val: str):
self.selected_playbook = val
async def load_playbooks(self):
"""Lädt alle Playbooks aus dem konfigurierten Ordner."""
try:
# --- DIESE PRINTS HIER HINZUFÜGEN ---
print(f"[DEBUG] Suche in Ordner: {PLAYBOOK_DIR}")
print(f"[DEBUG] Existiert der Ordner? {os.path.exists(PLAYBOOK_DIR)}")
playbook_files = glob.glob(os.path.join(PLAYBOOK_DIR, "*.yml")) + \
glob.glob(os.path.join(PLAYBOOK_DIR, "*.yaml"))
print(f"[DEBUG] Gefundene Dateien: {playbook_files}")
# ------------------------------------
self.playbooks = [os.path.basename(p) for p in playbook_files]
if self.playbooks:
self.selected_playbook = self.playbooks[0]
else:
self.selected_playbook = ""
self.error = ""
except Exception as e:
self.error = f"Fehler beim Laden der Playbooks: {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 # State aktualisieren, bevor die lange Aufgabe startet
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:
# ansible-playbook Befehl asynchron ausführen
proc = await asyncio.create_subprocess_exec(
"ansible-playbook", playbook_path,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
# Timeout von 300 Sekunden (5 Minuten)
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)
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 # State nach Abschluss aktualisieren
def index():
return rx.container(
rx.heading("Ansible Playbook Runner", size="6"),
# Flexiblerer und reaktiverer Aufbau des Dropdowns:
rx.select.root(
rx.select.trigger(placeholder="Wählen Sie ein Playbook..."),
rx.select.content(
rx.select.group(
rx.foreach(
AppState.playbooks,
lambda item: rx.select.item(item, value=item)
)
)
),
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",
margin_top="1em",
),
rx.text(
AppState.error,
color=rx.cond(AppState.error, "red", "transparent"),
margin_top="1em",
),
rx.text_area(
value=AppState.output,
width="100%",
height="400px",
margin_top="1em",
disabled=True,
resize="none",
),
padding="2em",
max_width="800px",
margin="auto",
)
app = rx.App()
app.add_page(index, route="/", on_load=AppState.load_playbooks)
if __name__ == "__main__":
try:
app.run(debug=True)
except Exception as e:
print(f"Fehler bei der Ausführung: {str(e)}")