Add archive import export and dedup tools
This commit is contained in:
parent
8d2838aea7
commit
d5576ddbde
4 changed files with 528 additions and 36 deletions
|
|
@ -32,6 +32,10 @@ func RegisterRoutes(mux *http.ServeMux) {
|
|||
mux.HandleFunc("/archives", archivesHandler)
|
||||
mux.HandleFunc("/archives/create", archiveCreateHandler)
|
||||
mux.HandleFunc("/archives/delete", archiveDeleteHandler)
|
||||
mux.HandleFunc("/archives/dedup", archiveDedupHandler)
|
||||
mux.HandleFunc("/import-export", importExportHandler)
|
||||
mux.HandleFunc("/import-export/upload", archiveUploadHandler)
|
||||
mux.HandleFunc("/import-export/download", archiveDownloadHandler)
|
||||
mux.HandleFunc("/transfer", transferHandler)
|
||||
mux.HandleFunc("/transfer/", transferHandler)
|
||||
mux.HandleFunc("/boxes/source", emailBoxHandler)
|
||||
|
|
@ -59,8 +63,8 @@ func renderStartPage(w http.ResponseWriter, r *http.Request) {
|
|||
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-21">
|
||||
<script src="/static/app.js?v=20260712-21" defer></script></head>
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
||||
<script src="/static/app.js?v=20260712-22" 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,7 +77,7 @@ func renderStartPage(w http.ResponseWriter, r *http.Request) {
|
|||
<aside class="backstage-menu" aria-label="Mail-Graveyard-Menue">
|
||||
<a class="backstage-item active" href="/">Start</a>
|
||||
<a class="backstage-item" href="/view">Archiv-Boxen</a>
|
||||
<a class="backstage-item" href="/view">Import/Export</a>
|
||||
<a class="backstage-item" href="/import-export">Import/Export</a>
|
||||
<a class="backstage-item" href="/accounts">Konten-Verwaltung</a>
|
||||
<a class="backstage-item" href="/archives">Archiv-mbox Verwaltung</a>
|
||||
<a class="backstage-item" href="/transfer/postfach-archiv">Mail-Transfer</a>
|
||||
|
|
@ -107,9 +111,9 @@ func renderShell(w http.ResponseWriter, r *http.Request, active, readingPane str
|
|||
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-21">
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<script src="/static/app.js?v=20260712-21" defer></script></head>
|
||||
<script src="/static/app.js?v=20260712-22" 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>
|
||||
|
|
@ -121,7 +125,7 @@ func renderShell(w http.ResponseWriter, r *http.Request, active, readingPane str
|
|||
</header>
|
||||
<aside class="backstage-menu" aria-label="Mail-Graveyard-Menue">
|
||||
<a class="backstage-item active" href="/view">Archiv-Boxen</a>
|
||||
<a class="backstage-item" href="/view">Import/Export</a>
|
||||
<a class="backstage-item" href="/import-export">Import/Export</a>
|
||||
<a class="backstage-item" href="/accounts">Konten-Verwaltung</a>
|
||||
<a class="backstage-item" href="/archives">Archiv-mbox Verwaltung</a>
|
||||
<a class="backstage-item" href="/transfer/postfach-archiv">Mail-Transfer</a>
|
||||
|
|
@ -263,7 +267,7 @@ func archivesHandler(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
renderArchivesPage(w, r, archives, accounts, r.URL.Query().Get("msg"), r.URL.Query().Get("err"))
|
||||
renderArchivesPage(w, r, archives, accounts, archiveStatsMap(archives), r.URL.Query().Get("msg"), r.URL.Query().Get("err"))
|
||||
}
|
||||
|
||||
func transferHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -578,9 +582,9 @@ func renderAccountsPage(w http.ResponseWriter, r *http.Request, accounts []Accou
|
|||
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-21">
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<script src="/static/app.js?v=20260712-21" defer></script></head>
|
||||
<script src="/static/app.js?v=20260712-22" 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>
|
||||
|
|
@ -592,7 +596,7 @@ func renderAccountsPage(w http.ResponseWriter, r *http.Request, accounts []Accou
|
|||
</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="/import-export">Import/Export</a>
|
||||
<a class="backstage-item active" href="/accounts">Konten-Verwaltung</a>
|
||||
<a class="backstage-item" href="/archives">Archiv-mbox Verwaltung</a>
|
||||
<a class="backstage-item" href="/transfer/postfach-archiv">Mail-Transfer</a>
|
||||
|
|
@ -617,15 +621,15 @@ func renderAccountsPage(w http.ResponseWriter, r *http.Request, accounts []Accou
|
|||
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, r *http.Request, archives []string, accounts []Account, msg, errMsg string) {
|
||||
func renderArchivesPage(w http.ResponseWriter, r *http.Request, archives []string, accounts []Account, stats map[string]ArchiveStats, msg, errMsg string) {
|
||||
user := CurrentUser(r)
|
||||
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-21">
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<script src="/static/app.js?v=20260712-21" defer></script></head>
|
||||
<script src="/static/app.js?v=20260712-22" 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>
|
||||
|
|
@ -637,7 +641,7 @@ func renderArchivesPage(w http.ResponseWriter, r *http.Request, archives []strin
|
|||
</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="/import-export">Import/Export</a>
|
||||
<a class="backstage-item" href="/accounts">Konten-Verwaltung</a>
|
||||
<a class="backstage-item active" href="/archives">Archiv-mbox Verwaltung</a>
|
||||
<a class="backstage-item" href="/transfer/postfach-archiv">Mail-Transfer</a>
|
||||
|
|
@ -656,7 +660,7 @@ func renderArchivesPage(w http.ResponseWriter, r *http.Request, archives []strin
|
|||
fmt.Fprintf(w, `<div class="notice bad">%s</div>`, html.EscapeString(errMsg))
|
||||
}
|
||||
fmt.Fprint(w, `<section class="archive-page-layout"><div class="archive-list-panel"><div class="pane-head">Archiv-mboxen</div>`)
|
||||
renderArchiveList(w, archives, accounts)
|
||||
renderArchiveList(w, archives, accounts, stats)
|
||||
fmt.Fprint(w, `</div><div class="archive-form-panel"><div class="pane-head">Archiv-mbox anlegen</div>`)
|
||||
renderArchiveForm(w)
|
||||
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>`)
|
||||
|
|
@ -670,9 +674,9 @@ func renderUsersPage(w http.ResponseWriter, r *http.Request, users []AppUser, ed
|
|||
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Benutzerverwaltung - Mail-Graveyard</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260712-21">
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<script src="/static/app.js?v=20260712-21" defer></script></head>
|
||||
<script src="/static/app.js?v=20260712-22" 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>
|
||||
|
|
@ -684,7 +688,7 @@ func renderUsersPage(w http.ResponseWriter, r *http.Request, users []AppUser, ed
|
|||
</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="/import-export">Import/Export</a>
|
||||
<a class="backstage-item" href="/accounts">Konten-Verwaltung</a>
|
||||
<a class="backstage-item" href="/archives">Archiv-mbox Verwaltung</a>
|
||||
<a class="backstage-item" href="/transfer/postfach-archiv">Mail-Transfer</a>
|
||||
|
|
@ -740,9 +744,9 @@ func renderTransferPage(w http.ResponseWriter, r *http.Request, mode transferMod
|
|||
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>%s - Mail-Graveyard</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260712-21">
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<script src="/static/app.js?v=20260712-21" defer></script></head>
|
||||
<script src="/static/app.js?v=20260712-22" 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>
|
||||
|
|
@ -754,7 +758,7 @@ func renderTransferPage(w http.ResponseWriter, r *http.Request, mode transferMod
|
|||
</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="/import-export">Import/Export</a>
|
||||
<a class="backstage-item" href="/accounts">Konten-Verwaltung</a>
|
||||
<a class="backstage-item" href="/archives">Archiv-mbox Verwaltung</a>
|
||||
<a class="backstage-item active" href="/transfer/postfach-archiv">Mail-Transfer</a>
|
||||
|
|
@ -781,9 +785,9 @@ func renderSourceEmailBoxPage(w http.ResponseWriter, r *http.Request, accounts [
|
|||
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Quell-Postfach - Mail-Graveyard</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260712-21">
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<script src="/static/app.js?v=20260712-21" defer></script></head>
|
||||
<script src="/static/app.js?v=20260712-22" 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>
|
||||
|
|
@ -795,7 +799,7 @@ func renderSourceEmailBoxPage(w http.ResponseWriter, r *http.Request, accounts [
|
|||
</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="/import-export">Import/Export</a>
|
||||
<a class="backstage-item" href="/accounts">Konten-Verwaltung</a>
|
||||
<a class="backstage-item" href="/archives">Archiv-mbox Verwaltung</a>
|
||||
<a class="backstage-item" href="/transfer/postfach-archiv">Mail-Transfer</a>
|
||||
|
|
@ -823,9 +827,9 @@ func renderTargetEmailBoxPage(w http.ResponseWriter, r *http.Request, accounts [
|
|||
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Ziel-Postfach - Mail-Graveyard</title>
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260712-21">
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260712-22">
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
<script src="/static/app.js?v=20260712-21" defer></script></head>
|
||||
<script src="/static/app.js?v=20260712-22" 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>
|
||||
|
|
@ -837,7 +841,7 @@ func renderTargetEmailBoxPage(w http.ResponseWriter, r *http.Request, accounts [
|
|||
</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="/import-export">Import/Export</a>
|
||||
<a class="backstage-item" href="/accounts">Konten-Verwaltung</a>
|
||||
<a class="backstage-item" href="/archives">Archiv-mbox Verwaltung</a>
|
||||
<a class="backstage-item" href="/transfer/postfach-archiv">Mail-Transfer</a>
|
||||
|
|
@ -1011,6 +1015,37 @@ func renderRibbonTransferMenu(active string) string {
|
|||
return b.String()
|
||||
}
|
||||
|
||||
func renderBackstage(active string) string {
|
||||
items := []struct {
|
||||
Key string
|
||||
Href string
|
||||
Label string
|
||||
}{
|
||||
{"home", "/", "Start"},
|
||||
{"archive", "/view", "Archiv-Boxen"},
|
||||
{"import-export", "/import-export", "Import/Export"},
|
||||
{"accounts", "/accounts", "Konten-Verwaltung"},
|
||||
{"archives", "/archives", "Archiv-mbox Verwaltung"},
|
||||
{"transfer", "/transfer/postfach-archiv", "Mail-Transfer"},
|
||||
{"users", "/users", "Benutzerverwaltung"},
|
||||
{"logout", "/logout", "Logout"},
|
||||
}
|
||||
var b strings.Builder
|
||||
b.WriteString(`<aside class="backstage-menu" aria-label="Mail-Graveyard-Menue">`)
|
||||
for _, item := range items {
|
||||
class := "backstage-item"
|
||||
if item.Key == active {
|
||||
class += " active"
|
||||
}
|
||||
fmt.Fprintf(&b, `<a class="%s" href="%s">%s</a>`, class, item.Href, html.EscapeString(item.Label))
|
||||
if item.Key == "transfer" {
|
||||
b.WriteString(transferMenuItems(""))
|
||||
}
|
||||
}
|
||||
b.WriteString(`</aside><button class="backstage-shade" type="button" aria-label="Menue schliessen" data-backstage-close></button>`)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func renderTransferPicker(kind string, accounts []Account, archives []string) string {
|
||||
var b strings.Builder
|
||||
b.WriteString(`<div class="transfer-picker">`)
|
||||
|
|
@ -1185,20 +1220,26 @@ func passwordHint(username string) string {
|
|||
return "Bearbeiten: Neues Passwort eintragen oder leer lassen, um das bestehende Passwort zu behalten."
|
||||
}
|
||||
|
||||
func renderArchiveList(w http.ResponseWriter, archives []string, accounts []Account) {
|
||||
func renderArchiveList(w http.ResponseWriter, archives []string, accounts []Account, stats map[string]ArchiveStats) {
|
||||
if len(archives) == 0 {
|
||||
fmt.Fprint(w, `<div class="ef-empty">Noch keine Archiv-Mailbox angelegt.</div>`)
|
||||
return
|
||||
}
|
||||
fmt.Fprint(w, `<table class="archive-table"><thead><tr><th>Archiv-Mailbox</th><th>Zuordnung</th><th></th></tr></thead><tbody>`)
|
||||
fmt.Fprint(w, `<table class="archive-table"><thead><tr><th>Archiv-Mailbox</th><th>Zuordnung</th><th>Speicher</th><th>Mails</th><th>Dubletten</th><th></th></tr></thead><tbody>`)
|
||||
for _, archive := range archives {
|
||||
uses := archiveUses(archive, accounts)
|
||||
action := `<span class="muted">belegt</span>`
|
||||
stat := stats[archive]
|
||||
var actions strings.Builder
|
||||
fmt.Fprintf(&actions, `<a class="btn compact secondary" href="/import-export/download?archive_name=%s">Download</a>`, urlQuery(archive))
|
||||
if stat.Duplicates > 0 {
|
||||
fmt.Fprintf(&actions, `<form method="post" action="/archives/dedup"><input type="hidden" name="archive_name" value="%s"><button class="btn compact secondary" type="submit">Dedup</button></form>`, html.EscapeString(archive))
|
||||
}
|
||||
if uses == "" {
|
||||
action = fmt.Sprintf(`<form method="post" action="/archives/delete"><input type="hidden" name="archive_name" value="%s"><button class="btn compact secondary danger" type="submit">Entfernen</button></form>`, html.EscapeString(archive))
|
||||
fmt.Fprintf(&actions, `<form method="post" action="/archives/delete"><input type="hidden" name="archive_name" value="%s"><button class="btn compact secondary danger" type="submit">Entfernen</button></form>`, html.EscapeString(archive))
|
||||
uses = "frei"
|
||||
}
|
||||
fmt.Fprintf(w, `<tr><td>%s</td><td>%s</td><td class="archive-action">%s</td></tr>`, html.EscapeString(archive), html.EscapeString(uses), action)
|
||||
fmt.Fprintf(w, `<tr><td>%s</td><td>%s</td><td>%s</td><td>%d</td><td>%d</td><td class="archive-action">%s</td></tr>`,
|
||||
html.EscapeString(archive), html.EscapeString(uses), html.EscapeString(formatBytes(stat.Bytes)), stat.Messages, stat.Duplicates, actions.String())
|
||||
}
|
||||
fmt.Fprint(w, `</tbody></table>`)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue