package server import ( "net/http" "time" "github.com/gin-gonic/gin" "warpbox/lib/boxstore" "warpbox/lib/helpers" "warpbox/lib/metastore" ) type AccountDashboardView struct { PageTitle string WindowTitle string WindowIcon string PageScripts []string AccountNav AccountNavView CSRFToken string Stats AccountDashboardStats Statuses []accountStatusRow Alerts []accountAlertPreviewRow RecentBoxes []accountDashboardBoxRow RecentActivity []accountActivityRow ShowUsersStat bool CanManageBoxes bool CanManageUsers bool CanViewSettings bool HasAlertsPreview bool } type AccountDashboardStats struct { ActiveBoxes int StorageUsedLabel string AlertCount int TotalUsers int ActiveUsers int DisabledUsers int } type accountStatusRow struct { Label string Value string Severity string } type accountAlertPreviewRow struct { Severity string Title string Detail string } type accountDashboardBoxRow struct { ID string FileCount int TotalSizeLabel string CreatedAt string ExpiresAt string Flags string CanManage bool } type accountActivityRow struct { Time string Title string Meta string } func (app *App) handleAccountDashboard(ctx *gin.Context) { actor, ok := currentAccountUser(ctx) if !ok { ctx.Redirect(http.StatusSeeOther, "/account/login") return } view, err := app.GetAccountDashboard(ctx, actor) if err != nil { ctx.String(http.StatusInternalServerError, "Could not load account dashboard") return } ctx.HTML(http.StatusOK, "account_dashboard.html", view) } func (app *App) GetAccountDashboard(ctx *gin.Context, actor metastore.User) (AccountDashboardView, error) { perms := currentAccountPermissions(ctx) nav := app.accountNavView(ctx, "dashboard") totalSize := int64(0) activeBoxes := 0 recentBoxes := []accountDashboardBoxRow{} if perms.AdminBoxesView { summaries, err := boxstore.ListBoxSummaries() if err != nil { return AccountDashboardView{}, err } recentBoxes = make([]accountDashboardBoxRow, 0, minInt(len(summaries), 10)) for _, summary := range summaries { totalSize += summary.TotalSize if !summary.Expired { activeBoxes++ } if len(recentBoxes) < 10 { recentBoxes = append(recentBoxes, accountDashboardBoxRow{ ID: summary.ID, FileCount: summary.FileCount, TotalSizeLabel: summary.TotalSizeLabel, CreatedAt: formatAdminTime(summary.CreatedAt), ExpiresAt: formatAdminTime(summary.ExpiresAt), Flags: accountBoxFlags(summary.Expired, summary.OneTimeDownload, summary.PasswordProtected), CanManage: true, }) } } } stats := AccountDashboardStats{ ActiveBoxes: activeBoxes, StorageUsedLabel: helpers.FormatBytes(totalSize), } showUsersStat := perms.AdminUsersManage if showUsersStat { users, err := app.store.ListUsers() if err != nil { return AccountDashboardView{}, err } stats.TotalUsers = len(users) for _, user := range users { if user.Disabled { stats.DisabledUsers++ } else { stats.ActiveUsers++ } } } return AccountDashboardView{ PageTitle: "WarpBox Account", WindowTitle: "WarpBox Account Control Panel", WindowIcon: "W", AccountNav: nav, CSRFToken: app.currentCSRFToken(ctx), Stats: stats, Statuses: app.accountDashboardStatuses(), Alerts: accountPlaceholderAlerts(), RecentBoxes: recentBoxes, RecentActivity: accountPlaceholderActivity(actor, ctx), ShowUsersStat: showUsersStat, CanManageBoxes: perms.AdminBoxesView, CanManageUsers: perms.AdminUsersManage, CanViewSettings: perms.AdminSettingsManage, HasAlertsPreview: true, }, nil } func (app *App) accountDashboardStatuses() []accountStatusRow { return []accountStatusRow{ {Label: "Guest uploads", Value: enabledLabel(app.config.GuestUploadsEnabled), Severity: boolSeverity(app.config.GuestUploadsEnabled)}, {Label: "API", Value: enabledLabel(app.config.APIEnabled), Severity: boolSeverity(app.config.APIEnabled)}, {Label: "ZIP downloads", Value: enabledLabel(app.config.ZipDownloadsEnabled), Severity: boolSeverity(app.config.ZipDownloadsEnabled)}, {Label: "One-time boxes", Value: enabledLabel(app.config.OneTimeDownloadsEnabled), Severity: boolSeverity(app.config.OneTimeDownloadsEnabled)}, } } func accountPlaceholderAlerts() []accountAlertPreviewRow { return []accountAlertPreviewRow{ { Severity: "info", Title: "Alerts system pending", Detail: "Dedicated alert storage arrives in the alerts implementation pass.", }, } } func accountPlaceholderActivity(actor metastore.User, ctx *gin.Context) []accountActivityRow { now := time.Now().UTC() if value, ok := ctx.Get("accountSession"); ok { if session, ok := value.(metastore.Session); ok { now = session.CreatedAt } } return []accountActivityRow{ { Time: formatAdminTime(now), Title: "Signed in", Meta: actor.Username + " opened the account dashboard.", }, { Time: "pending", Title: "Audit log not implemented", Meta: "Recent account activity will use the audit model in a later pass.", }, } } func accountBoxFlags(expired bool, oneTime bool, passwordProtected bool) string { flags := []string{} if expired { flags = append(flags, "expired") } if oneTime { flags = append(flags, "one-time") } if passwordProtected { flags = append(flags, "password") } if len(flags) == 0 { return "normal" } out := flags[0] for _, flag := range flags[1:] { out += ", " + flag } return out } func enabledLabel(enabled bool) string { if enabled { return "enabled" } return "disabled" } func boolSeverity(enabled bool) string { if enabled { return "ok" } return "warn" } func minInt(a int, b int) int { if a < b { return a } return b }