fix: IMAP-Konto bearbeiten + Löschen auch bei sync_running

- Store: UpdateCredentials() — Zugangsdaten + Passwort neu verschlüsseln,
  setzt status='idle', error_msg='', sync_running=false zurück
- Handler: PATCH /api/imap/{id} unterstützt nun Credential-Update
  (name/host/username vorhanden = Credential-Update, sonst sync_interval)
- Frontend: "Bearbeiten"-Button öffnet Edit-Dialog mit allen Feldern;
  Passwort-Feld leer = unverändertes Passwort
- Frontend: Löschen-Button nicht mehr durch sync_running blockiert
  (nur noch bei status=running gesperrt)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-20 00:49:37 +01:00
parent 4a4136e4a6
commit c59cad92be
4 changed files with 184 additions and 3 deletions
+108 -1
View File
@@ -12,6 +12,7 @@ import {
getImapProgress,
triggerImapSync,
updateImapInterval,
updateImapAccount,
type ImapAccount,
type ImapFolder,
} from "@/lib/api";
@@ -50,6 +51,17 @@ export default function ImapPage() {
const [dialogOpen, setDialogOpen] = useState(false);
const [deleteConfirm, setDeleteConfirm] = useState<number | null>(null);
// Edit state
const [editAccount, setEditAccount] = useState<ImapAccount | null>(null);
const [editName, setEditName] = useState("");
const [editHost, setEditHost] = useState("");
const [editPort, setEditPort] = useState("993");
const [editTls, setEditTls] = useState("ssl");
const [editUsername, setEditUsername] = useState("");
const [editPassword, setEditPassword] = useState("");
const [editSaving, setEditSaving] = useState(false);
const [editError, setEditError] = useState("");
// Form state
const [formName, setFormName] = useState("");
const [formHost, setFormHost] = useState("");
@@ -209,6 +221,43 @@ export default function ImapPage() {
setDeleteConfirm(null);
}
function openEdit(acc: ImapAccount) {
setEditAccount(acc);
setEditName(acc.name);
setEditHost(acc.host);
setEditPort(String(acc.port));
setEditTls(acc.tls);
setEditUsername(acc.username);
setEditPassword("");
setEditError("");
}
async function handleEditSave() {
if (!editAccount) return;
if (!editName || !editHost || !editUsername) {
setEditError("Name, Host und Benutzername sind Pflichtfelder.");
return;
}
setEditSaving(true);
setEditError("");
try {
const updated = await updateImapAccount(editAccount.id, {
name: editName,
host: editHost,
port: parseInt(editPort, 10),
tls: editTls,
username: editUsername,
password: editPassword || undefined,
});
setAccounts((prev) => prev.map((a) => (a.id === updated.id ? updated : a)));
setEditAccount(null);
} catch (err) {
setEditError(err instanceof Error ? err.message : String(err));
} finally {
setEditSaving(false);
}
}
async function handleSyncNow(id: number) {
try {
const updated = await triggerImapSync(id);
@@ -407,10 +456,17 @@ export default function ImapPage() {
>
Sync jetzt
</Button>
<Button
size="sm"
variant="outline"
onClick={() => openEdit(acc)}
>
Bearbeiten
</Button>
<Button
size="sm"
variant="destructive"
disabled={acc.status === "running" || acc.sync_running}
disabled={acc.status === "running"}
onClick={() => setDeleteConfirm(acc.id)}
>
Loeschen
@@ -572,6 +628,57 @@ export default function ImapPage() {
</DialogContent>
</Dialog>
{/* Edit Account Dialog */}
<Dialog open={editAccount !== null} onOpenChange={(open) => { if (!open) setEditAccount(null); }}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>IMAP-Konto bearbeiten</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-1">
<Label>Name</Label>
<Input value={editName} onChange={(e) => setEditName(e.target.value)} placeholder="z.B. Firmen-Mail" />
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label>Host</Label>
<Input value={editHost} onChange={(e) => setEditHost(e.target.value)} placeholder="imap.example.com" />
</div>
<div className="space-y-1">
<Label>Port</Label>
<Input value={editPort} onChange={(e) => setEditPort(e.target.value)} type="number" />
</div>
</div>
<div className="space-y-1">
<Label>TLS</Label>
<Select value={editTls} onValueChange={setEditTls}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="ssl">SSL/TLS</SelectItem>
<SelectItem value="starttls">STARTTLS</SelectItem>
<SelectItem value="none">Keine</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label>Benutzername</Label>
<Input value={editUsername} onChange={(e) => setEditUsername(e.target.value)} placeholder="user@example.com" />
</div>
<div className="space-y-1">
<Label>Passwort <span className="text-muted-foreground text-xs">(leer lassen = unveraendert)</span></Label>
<Input value={editPassword} onChange={(e) => setEditPassword(e.target.value)} type="password" placeholder="Neues Passwort eingeben" />
</div>
{editError && <p className="text-sm text-destructive">{editError}</p>}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setEditAccount(null)}>Abbrechen</Button>
<Button onClick={handleEditSave} disabled={editSaving}>
{editSaving ? "Speichert..." : "Speichern"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Delete Confirmation Dialog */}
<Dialog
open={deleteConfirm !== null}
+10
View File
@@ -392,6 +392,16 @@ export async function updateImapInterval(id: number, intervalMin: number): Promi
});
}
export async function updateImapAccount(
id: number,
data: { name: string; host: string; port: number; tls: string; username: string; password?: string }
): Promise<ImapAccount> {
return request<ImapAccount>(`/api/imap/${id}`, {
method: "PATCH",
body: JSON.stringify(data),
});
}
// ── POP3 ──────────────────────────────────────────────────────────────────
export interface Pop3Account {