Move archive mailbox management to own menu

This commit is contained in:
DonVoo 2026-07-12 22:43:18 +02:00
parent 9a5fb89518
commit fe3ece17ba
2 changed files with 87 additions and 16 deletions

View file

@ -22,6 +22,7 @@ func RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/accounts/save", accountSaveHandler) // TODO Codex
mux.HandleFunc("/accounts/delete", accountDeleteHandler) // TODO Codex
mux.HandleFunc("/accounts/test", accountTestHandler) // TODO Codex: Quelle+Ziel-Login pruefen
mux.HandleFunc("/archives", archivesHandler)
mux.HandleFunc("/archives/create", archiveCreateHandler)
mux.HandleFunc("/archives/delete", archiveDeleteHandler)
mux.HandleFunc("/migrate/run", migrateRunHandler) // TODO Codex: RunMigration im Hintergrund
@ -54,9 +55,9 @@ func renderShell(w http.ResponseWriter, active, readingPane string) {
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-7">
<link rel="stylesheet" href="/static/style.css?v=20260712-8">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-7" defer></script></head>
<script src="/static/app.js?v=20260712-8" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -73,6 +74,7 @@ func renderShell(w http.ResponseWriter, active, readingPane string) {
<a class="backstage-item active" href="/view">Archiv-Boxen</a>
<a class="backstage-item" href="/view">Import/Export</a>
<a class="backstage-item" href="/accounts">Konten-Verwaltung</a>
<a class="backstage-item sub" href="/archives">Archiv-mbox Verwaltung</a>
<a class="backstage-item" href="/view">Mail-Transfer</a>
<a class="backstage-item" href="/accounts">Einstellungen</a>
</aside>
@ -177,6 +179,15 @@ func accountTestHandler(w http.ResponseWriter, r *http.Request) {
redirectAccounts(w, r, name, "Quelle und Ziel erfolgreich getestet.")
}
func archivesHandler(w http.ResponseWriter, r *http.Request) {
accounts, err := ListAccounts()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
renderArchivesPage(w, archiveMailboxes(accounts), accounts, r.URL.Query().Get("msg"), r.URL.Query().Get("err"))
}
func archiveCreateHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
@ -184,14 +195,14 @@ func archiveCreateHandler(w http.ResponseWriter, r *http.Request) {
}
name, err := safeArchiveName(r.FormValue("archive_name"))
if err != nil {
redirectAccounts(w, r, "", err.Error())
redirectArchives(w, r, err.Error())
return
}
if err := os.MkdirAll(filepath.Join(Cfg.MboxRoot, name), 0o700); err != nil {
redirectAccounts(w, r, "", err.Error())
redirectArchives(w, r, err.Error())
return
}
redirectAccounts(w, r, "", "Archiv-Mailbox angelegt.")
redirectArchives(w, r, "Archiv-Mailbox angelegt.")
}
func archiveDeleteHandler(w http.ResponseWriter, r *http.Request) {
@ -201,32 +212,32 @@ func archiveDeleteHandler(w http.ResponseWriter, r *http.Request) {
}
name, err := safeArchiveName(r.FormValue("archive_name"))
if err != nil {
redirectAccounts(w, r, "", err.Error())
redirectArchives(w, r, err.Error())
return
}
if archiveInUse(name) {
redirectAccounts(w, r, "", "Archiv-Mailbox ist noch einem Postfach zugeordnet.")
redirectArchives(w, r, "Archiv-Mailbox ist noch einem Postfach zugeordnet.")
return
}
path := filepath.Join(Cfg.MboxRoot, name)
entries, err := os.ReadDir(path)
if os.IsNotExist(err) {
redirectAccounts(w, r, "", "Archiv-Mailbox existiert nicht.")
redirectArchives(w, r, "Archiv-Mailbox existiert nicht.")
return
}
if err != nil {
redirectAccounts(w, r, "", err.Error())
redirectArchives(w, r, err.Error())
return
}
if len(entries) > 0 {
redirectAccounts(w, r, "", "Archiv-Mailbox ist nicht leer und wurde nicht entfernt.")
redirectArchives(w, r, "Archiv-Mailbox ist nicht leer und wurde nicht entfernt.")
return
}
if err := os.Remove(path); err != nil {
redirectAccounts(w, r, "", err.Error())
redirectArchives(w, r, err.Error())
return
}
redirectAccounts(w, r, "", "Leere Archiv-Mailbox entfernt.")
redirectArchives(w, r, "Leere Archiv-Mailbox entfernt.")
}
func migrateRunHandler(w http.ResponseWriter, r *http.Request) {
@ -284,9 +295,9 @@ func renderAccountsPage(w http.ResponseWriter, accounts []Account, edit Account,
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Konten-Verwaltung - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-7">
<link rel="stylesheet" href="/static/style.css?v=20260712-8">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-7" defer></script></head>
<script src="/static/app.js?v=20260712-8" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -303,6 +314,7 @@ func renderAccountsPage(w http.ResponseWriter, accounts []Account, edit Account,
<a class="backstage-item" href="/view">Archiv-Boxen</a>
<a class="backstage-item" href="/view">Import/Export</a>
<a class="backstage-item active" href="/accounts">Konten-Verwaltung</a>
<a class="backstage-item sub" href="/archives">Archiv-mbox Verwaltung</a>
<a class="backstage-item" href="/view">Mail-Transfer</a>
<a class="backstage-item" href="/accounts">Einstellungen</a>
</aside>
@ -316,13 +328,52 @@ func renderAccountsPage(w http.ResponseWriter, accounts []Account, edit Account,
}
fmt.Fprint(w, `<section class="account-layout"><div class="account-list"><div class="pane-head">Eingetragene Postfaecher</div>`)
renderAccountsTable(w, accounts, edit.Name)
fmt.Fprint(w, `<div class="pane-head">Archiv-mbox verwalten</div>`)
renderArchiveManager(w, archives, accounts)
fmt.Fprint(w, `</div><div class="account-form-panel"><div class="pane-head">Postfach eintragen</div>`)
renderAccountForm(w, edit, archives)
fmt.Fprint(w, `</div></section></main><footer class="statusbar"><span id="mig-status" hx-get="/migrate/status" hx-trigger="load,every 3s" hx-swap="innerHTML">bereit</span></footer></body></html>`)
}
func renderArchivesPage(w http.ResponseWriter, archives []string, accounts []Account, msg, errMsg string) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Archiv-mbox Verwaltung - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260712-8">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260712-8" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
<nav class="ribbon-tabs">
<a class="tab" href="/view">Archiv-Boxen</a>
<a class="tab" href="/view">Import/Export</a>
<a class="tab" href="/accounts">Konten-Verwaltung</a>
<a class="tab" href="/view">Mail-Transfer</a>
<a class="tab" href="/accounts">Einstellungen</a>
</nav>
<div class="ribbon-title">Mail-Graveyard</div>
</header>
<aside class="backstage-menu" aria-label="Mail-Graveyard-Menue">
<a class="backstage-item" href="/view">Archiv-Boxen</a>
<a class="backstage-item" href="/view">Import/Export</a>
<a class="backstage-item" href="/accounts">Konten-Verwaltung</a>
<a class="backstage-item sub active" href="/archives">Archiv-mbox Verwaltung</a>
<a class="backstage-item" href="/view">Mail-Transfer</a>
<a class="backstage-item" href="/accounts">Einstellungen</a>
</aside>
<button class="backstage-shade" type="button" aria-label="Menue schliessen" data-backstage-close></button>
<main class="accounts-page archive-page">`)
if msg != "" {
fmt.Fprintf(w, `<div class="notice ok">%s</div>`, html.EscapeString(msg))
}
if errMsg != "" {
fmt.Fprintf(w, `<div class="notice bad">%s</div>`, html.EscapeString(errMsg))
}
fmt.Fprint(w, `<section class="archive-page-layout"><div class="pane-head">Archiv-mbox Verwaltung</div>`)
renderArchiveManager(w, archives, accounts)
fmt.Fprint(w, `</section></main><footer class="statusbar"><span id="mig-status" hx-get="/migrate/status" hx-trigger="load,every 3s" hx-swap="innerHTML">bereit</span></footer></body></html>`)
}
func renderAccountsTable(w http.ResponseWriter, accounts []Account, editName string) {
if len(accounts) == 0 {
fmt.Fprint(w, `<div class="ef-empty">Noch keine Postfaecher eingetragen.</div>`)
@ -581,6 +632,14 @@ func redirectAccounts(w http.ResponseWriter, r *http.Request, edit, msg string)
http.Redirect(w, r, target, http.StatusSeeOther)
}
func redirectArchives(w http.ResponseWriter, r *http.Request, msg string) {
key := "msg"
if strings.Contains(strings.ToLower(msg), "fehl") || strings.Contains(strings.ToLower(msg), "ungueltig") || strings.Contains(strings.ToLower(msg), "nicht") || strings.Contains(strings.ToLower(msg), "error") {
key = "err"
}
http.Redirect(w, r, "/archives?"+key+"="+urlQuery(msg), http.StatusSeeOther)
}
func urlQuery(value string) string {
return url.QueryEscape(value)
}