feat(PROJ-17): Admin Dashboard Systemauslastung immer anzeigen

- Systemauslastungs-Sektion wird immer gerendert (nicht nur bei Erfolg)
- Fehlermeldung wenn /api/admin/system/stats nicht erreichbar ist
- Feature-Status auf In Review gesetzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-14 11:43:19 +01:00
parent a893084a88
commit d360c9a5ba
68 changed files with 11938 additions and 435 deletions
+99
View File
@@ -0,0 +1,99 @@
package imap
import (
"crypto/tls"
"fmt"
"strings"
imapv2 "github.com/emersion/go-imap/v2"
"github.com/emersion/go-imap/v2/imapclient"
)
// FolderInfo describes a single IMAP folder with exclusion metadata.
type FolderInfo struct {
Name string `json:"name"`
Excluded bool `json:"excluded"`
Reason string `json:"reason,omitempty"`
}
// junkTrashNames lists well-known junk/trash folder names for fallback detection.
var junkTrashNames = []string{
"junk", "spam", "trash", "deleted items",
"deleted messages", "papierkorb", "gelöschte elemente",
}
// Connect establishes an IMAP client connection using the specified TLS mode.
func Connect(host string, port int, tlsMode string) (*imapclient.Client, error) {
addr := fmt.Sprintf("%s:%d", host, port)
switch tlsMode {
case "ssl":
c, err := imapclient.DialTLS(addr, &imapclient.Options{
TLSConfig: &tls.Config{ServerName: host},
})
if err != nil {
return nil, fmt.Errorf("imap connect ssl: %w", err)
}
return c, nil
case "starttls":
c, err := imapclient.DialStartTLS(addr, &imapclient.Options{
TLSConfig: &tls.Config{ServerName: host},
})
if err != nil {
return nil, fmt.Errorf("imap connect starttls: %w", err)
}
return c, nil
case "none":
c, err := imapclient.DialInsecure(addr, nil)
if err != nil {
return nil, fmt.Errorf("imap connect plain: %w", err)
}
return c, nil
default:
return nil, fmt.Errorf("imap: unknown tls mode %q", tlsMode)
}
}
// ListFolders retrieves all mailbox folders and detects junk/trash folders.
func ListFolders(c *imapclient.Client) ([]FolderInfo, error) {
listCmd := c.List("", "*", nil)
mailboxes, err := listCmd.Collect()
if err != nil {
return nil, fmt.Errorf("imap list folders: %w", err)
}
var folders []FolderInfo
for _, mb := range mailboxes {
fi := FolderInfo{Name: mb.Mailbox}
// Check special-use attributes (RFC 6154)
for _, attr := range mb.Attrs {
if attr == imapv2.MailboxAttrJunk {
fi.Excluded = true
fi.Reason = "special_use"
break
}
if attr == imapv2.MailboxAttrTrash {
fi.Excluded = true
fi.Reason = "special_use"
break
}
}
// Fallback: case-insensitive name matching
if !fi.Excluded {
lower := strings.ToLower(mb.Mailbox)
for _, jt := range junkTrashNames {
if lower == jt {
fi.Excluded = true
fi.Reason = "name_match"
break
}
}
}
folders = append(folders, fi)
}
return folders, nil
}