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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user