Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Assisted-by: GLM 5.3
183 lines
4.3 KiB
Go
183 lines
4.3 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
|
||
|
||
package admin
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
"sourcedock.dev/petrbalvin/volumen/internal/store"
|
||
"sourcedock.dev/petrbalvin/volumen/internal/tokens"
|
||
"sourcedock.dev/petrbalvin/volumen/internal/users"
|
||
"sourcedock.dev/petrbalvin/volumen/internal/webhooks"
|
||
)
|
||
|
||
// roleOption is one <option> of a role select.
|
||
type roleOption struct {
|
||
Value string
|
||
Selected bool
|
||
}
|
||
|
||
// userRow is one entry of the users panel.
|
||
type userRow struct {
|
||
Username string
|
||
Display string
|
||
Initial string
|
||
Role string
|
||
Photo string
|
||
IsSelf bool
|
||
RoleOptions []roleOption
|
||
}
|
||
|
||
// hookRow is one configured webhook endpoint.
|
||
type hookRow struct {
|
||
Index string
|
||
URL string
|
||
// Managed is false for a hook declared in config.toml, which the
|
||
// settings forms may test but not change.
|
||
Managed bool
|
||
Enabled bool
|
||
Signed bool
|
||
EventsText string
|
||
}
|
||
|
||
// deliveryRow is one webhook delivery log entry.
|
||
type deliveryRow struct {
|
||
Timestamp string
|
||
Event string
|
||
HookURL string
|
||
OK bool
|
||
StatusCode int
|
||
Error string
|
||
Attempts int
|
||
|
||
// Result is the pre-formatted failure text ("failed (N attempts)"),
|
||
// translated by the caller that knows the request's language.
|
||
Result string
|
||
}
|
||
|
||
// tokenRow is one API token table row.
|
||
type tokenRow struct {
|
||
Name string
|
||
CreatedDay string
|
||
LastUsedDay string
|
||
}
|
||
|
||
// mediaRow is one media library tile.
|
||
type mediaRow struct {
|
||
Name string
|
||
URL string
|
||
SizeKB string
|
||
// Dimensions is the header-carried pixel size ("1920 × 1080"), or
|
||
// "" when the container did not yield one.
|
||
Dimensions string
|
||
}
|
||
|
||
func roleOptions(current string) []roleOption {
|
||
out := make([]roleOption, 0, len(users.Roles))
|
||
for _, role := range users.Roles {
|
||
out = append(out, roleOption{Value: role, Selected: role == current})
|
||
}
|
||
return out
|
||
}
|
||
|
||
func userRows(current string, list []*users.User) []userRow {
|
||
out := make([]userRow, 0, len(list))
|
||
for _, user := range list {
|
||
display := user.Name
|
||
if display == "" {
|
||
display = user.Username
|
||
}
|
||
out = append(out, userRow{
|
||
Username: user.Username,
|
||
Display: display,
|
||
Initial: firstUpper(display, "?"),
|
||
Role: user.Role,
|
||
Photo: user.Photo,
|
||
IsSelf: user.Username == current,
|
||
RoleOptions: roleOptions(user.Role),
|
||
})
|
||
}
|
||
return out
|
||
}
|
||
|
||
// hookRows renders the manager's merged hook list; the first static
|
||
// count came from config.toml and the rest are the admin's to manage.
|
||
func hookRows(hooks []webhooks.Webhook, staticCount int) []hookRow {
|
||
out := make([]hookRow, 0, len(hooks))
|
||
for i, hook := range hooks {
|
||
eventsText := "all"
|
||
if len(hook.Events) > 0 {
|
||
eventsText = strings.Join(hook.Events, ", ")
|
||
}
|
||
out = append(out, hookRow{
|
||
Index: fmt.Sprintf("%d", i),
|
||
URL: hook.URL,
|
||
Managed: i >= staticCount,
|
||
Enabled: hook.Enabled,
|
||
Signed: hook.Secret != "",
|
||
EventsText: eventsText,
|
||
})
|
||
}
|
||
return out
|
||
}
|
||
|
||
func deliveryRows(list []webhooks.Delivery) []deliveryRow {
|
||
out := make([]deliveryRow, 0, len(list))
|
||
for _, d := range list {
|
||
out = append(out, deliveryRow{
|
||
Timestamp: d.Timestamp,
|
||
Event: d.Event,
|
||
HookURL: d.HookURL,
|
||
OK: d.Status == "ok",
|
||
StatusCode: d.StatusCode,
|
||
Error: d.Error,
|
||
Attempts: d.Attempts,
|
||
})
|
||
}
|
||
return out
|
||
}
|
||
|
||
func tokenRows(list []tokens.Token) []tokenRow {
|
||
out := make([]tokenRow, 0, len(list))
|
||
for _, token := range list {
|
||
lastUsed := "never"
|
||
if len(token.LastUsed) >= 10 {
|
||
lastUsed = token.LastUsed[:10]
|
||
}
|
||
created := token.Created
|
||
if len(created) >= 10 {
|
||
created = created[:10]
|
||
}
|
||
out = append(out, tokenRow{
|
||
Name: token.Name,
|
||
CreatedDay: created,
|
||
LastUsedDay: lastUsed,
|
||
})
|
||
}
|
||
return out
|
||
}
|
||
|
||
func mediaRows(list []store.Media) []mediaRow {
|
||
out := make([]mediaRow, 0, len(list))
|
||
for _, item := range list {
|
||
out = append(out, mediaRow{
|
||
Name: item.Name,
|
||
URL: item.URL,
|
||
SizeKB: fmt.Sprintf("%.1f", float64(item.Size)/1024),
|
||
Dimensions: pixelSize(item.Width, item.Height),
|
||
})
|
||
}
|
||
return out
|
||
}
|
||
|
||
// pixelSize renders the header-carried pixel size, empty when the
|
||
// container did not yield one.
|
||
func pixelSize(width, height int) string {
|
||
if width <= 0 || height <= 0 {
|
||
return ""
|
||
}
|
||
return fmt.Sprintf("%d × %d", width, height)
|
||
}
|