add manual/api/ssh Plesk-Backends, Minimal-PDF, --pdf-only und GUI-Verbesserungen

- PLESK_BACKEND={manual,api,ssh}: manual als Default für Shared Hosts,
  api unverändert, ssh ruft `plesk bin mail` per paramiko auf.
- POP3_PORT default 995, POP3_SSL mit Auto-Erkennung anhand Port.
- Kerio: User wird mit mayChangePassword=False angelegt.
- Zusätzliche Minimal-PDF (nur Email + Cloud) pro Konto + Sammel-Variante,
  IMAP-Port konfigurierbar.
- CLI-Flag --pdf-only und entsprechender GUI-Button "📄 Nur PDF".
- GUI: Lösch-Button "✕ löschen" sichtbarer, letzte Zeile löschbar.
- PDFs sind kunden-tauglich (kein Status-Block, kein ACHTUNG-Hinweis);
  Anlage-Status separat in _admin_report_<ts>.txt.
- README dokumentiert die Skip-Logik pro Dienst und ihre Caveats.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 12:15:39 +02:00
parent e951c839ca
commit dda5c746ce
6 changed files with 216 additions and 34 deletions
+28 -9
View File
@@ -6,7 +6,7 @@ import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext, ttk
from config import Config
from deploy import parse_csv, run_deploy
from deploy import parse_csv, pdf_only, run_deploy
from models import Account
@@ -33,7 +33,8 @@ class AccountRow(ttk.Frame):
e = ttk.Entry(self, width=width)
e.grid(row=0, column=col, padx=2, pady=2, sticky="ew")
self.entries[key] = e
ttk.Button(self, text="", width=3,
ttk.Button(self, text=" löschen", width=10,
style="Remove.TButton",
command=lambda: on_remove(self)).grid(
row=0, column=len(FIELDS), padx=4)
@@ -54,6 +55,12 @@ class App(tk.Tk):
self.geometry("1500x800")
self.output_dir = Path("./output").resolve()
self.rows: list[AccountRow] = []
# Roter Lösch-Button-Style
style = ttk.Style(self)
try:
style.configure("Remove.TButton", foreground="#b00020")
except tk.TclError:
pass
self._build()
self.add_row()
@@ -68,6 +75,8 @@ class App(tk.Tk):
self.output_lbl.pack(side="left", padx=8)
self.run_btn = ttk.Button(toolbar, text="Ausführen ▶", command=self.run)
self.run_btn.pack(side="right", padx=2)
self.pdf_btn = ttk.Button(toolbar, text="📄 Nur PDF", command=self.run_pdf_only)
self.pdf_btn.pack(side="right", padx=2)
# Header über die Eingabezeilen
header = ttk.Frame(self)
@@ -112,11 +121,10 @@ class App(tk.Tk):
self.rows.append(row)
def remove_row(self, row):
if len(self.rows) <= 1:
row.from_dict({}) # leere statt entfernen
return
self.rows.remove(row)
row.destroy()
if not self.rows:
self.add_row() # immer mindestens eine leere Zeile
# ----- Buttons -----
def load_csv(self):
@@ -189,7 +197,7 @@ class App(tk.Tk):
self.log.insert("end", msg + "\n")
self.log.see("end")
def run(self):
def _start_worker(self, fn, success_title: str):
try:
accounts = self._collect_accounts()
except ValueError as e:
@@ -201,21 +209,32 @@ class App(tk.Tk):
cfg = Config()
self.log.delete("1.0", "end")
self.run_btn.config(state="disabled")
self.pdf_btn.config(state="disabled")
def worker():
try:
_, combined = run_deploy(accounts, cfg, self.output_dir,
log=lambda m: self.after(0, self._log, m))
combined = fn(accounts, cfg, self.output_dir,
log=lambda m: self.after(0, self._log, m))
# run_deploy gibt (results, combined) zurück, pdf_only nur combined
if isinstance(combined, tuple):
combined = combined[1]
self.after(0, lambda: messagebox.showinfo(
"Fertig", f"Verarbeitung abgeschlossen.\n\nGesamt-PDF:\n{combined}"))
success_title, f"Fertig.\n\nGesamt-PDF:\n{combined}"))
except Exception as e:
self.after(0, lambda: self._log(f"FEHLER: {e}"))
self.after(0, lambda: messagebox.showerror("Fehler", str(e)))
finally:
self.after(0, lambda: self.run_btn.config(state="normal"))
self.after(0, lambda: self.pdf_btn.config(state="normal"))
threading.Thread(target=worker, daemon=True).start()
def run(self):
self._start_worker(run_deploy, "Verarbeitung abgeschlossen")
def run_pdf_only(self):
self._start_worker(pdf_only, "Nur PDFs erzeugt")
def launch():
App().mainloop()