Removes standalone "// v0.X.X:" comment lines and inline trailing version annotations. Keeps version references in config field docs and compatibility notes where they describe requirements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
189 lines
5.0 KiB
Go
189 lines
5.0 KiB
Go
package pages
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"switchboard-core/store"
|
|
)
|
|
|
|
// TeamOption for template dropdowns.
|
|
type TeamOption struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description,omitempty"`
|
|
MemberCount int `json:"member_count,omitempty"`
|
|
IsActive bool `json:"is_active"`
|
|
}
|
|
|
|
// ── Page data structs ────────────────────────
|
|
|
|
// AdminPageData is what the admin surface templates receive.
|
|
type AdminPageData struct {
|
|
Section string `json:"section"`
|
|
Category string `json:"category"`
|
|
ConfigSections []ConfigSectionEntry `json:"config_sections,omitempty"`
|
|
}
|
|
|
|
// SettingsPageData is what the settings surface templates receive.
|
|
// Feature gates (BYOK, personas) moved to sw.auth.policies in v0.37.19.
|
|
type SettingsPageData struct {
|
|
Section string `json:"section"`
|
|
ConfigSections []ConfigSectionEntry `json:"config_sections,omitempty"`
|
|
}
|
|
|
|
// ── Loader registration ──────────────────────
|
|
|
|
// TeamAdminPageData is what the team-admin surface receives.
|
|
type TeamAdminPageData struct {
|
|
Section string `json:"section"`
|
|
ConfigSections []ConfigSectionEntry `json:"config_sections,omitempty"`
|
|
}
|
|
|
|
func (e *Engine) registerLoaders() {
|
|
e.RegisterLoader("admin", e.adminLoader)
|
|
e.RegisterLoader("settings", e.settingsLoader)
|
|
e.RegisterLoader("team-admin", e.teamAdminLoader)
|
|
}
|
|
|
|
// Used for manifest validation — DataRequires entries must match a key here.
|
|
func (e *Engine) ListDataProviders() []string {
|
|
keys := make([]string, 0, len(e.loaders))
|
|
for k := range e.loaders {
|
|
keys = append(keys, k)
|
|
}
|
|
return keys
|
|
}
|
|
|
|
// ── Admin loader ─────────────────────────────
|
|
// Pre-loads ALL dropdown data. Fixes bugs #1 (model roles) and #2 (team scope).
|
|
|
|
func (e *Engine) adminLoader(c *gin.Context, _ store.Stores) (any, error) {
|
|
section := c.Param("section")
|
|
if section == "" {
|
|
section = "users"
|
|
}
|
|
|
|
return &AdminPageData{
|
|
Section: section,
|
|
Category: sectionCategory(section),
|
|
}, nil
|
|
}
|
|
|
|
// ── Admin helpers ────────────────────────────
|
|
|
|
// sectionCategory maps an admin section to its category tab.
|
|
func sectionCategory(section string) string {
|
|
switch section {
|
|
case "users", "teams", "groups":
|
|
return "people"
|
|
case "workflows":
|
|
return "workflows"
|
|
case "settings", "storage", "packages", "broadcast", "backup":
|
|
return "system"
|
|
case "usage", "audit", "stats":
|
|
return "monitoring"
|
|
default:
|
|
return "system"
|
|
}
|
|
}
|
|
|
|
// ── Settings loader ──────────────────────────
|
|
// which nav links/tabs are visible (BYOK, User Personas).
|
|
|
|
func (e *Engine) teamAdminLoader(c *gin.Context, s store.Stores) (any, error) {
|
|
section := c.Param("section")
|
|
if section == "" {
|
|
section = "members"
|
|
}
|
|
return &TeamAdminPageData{
|
|
Section: section,
|
|
ConfigSections: configSectionsForSurface(context.Background(), s, "team-admin"),
|
|
}, nil
|
|
}
|
|
|
|
func (e *Engine) settingsLoader(c *gin.Context, s store.Stores) (any, error) {
|
|
section := c.Param("section")
|
|
if section == "" {
|
|
section = "general"
|
|
}
|
|
|
|
return &SettingsPageData{
|
|
Section: section,
|
|
ConfigSections: configSectionsForSurface(context.Background(), s, "settings"),
|
|
}, nil
|
|
}
|
|
|
|
// ── Config section discovery (v0.38.3) ───────
|
|
//
|
|
// Packages declare a config_section in their manifest. This helper scans
|
|
// enabled packages and returns entries whose surfaces array includes the
|
|
// target surface. No new tables or endpoints — purely manifest-driven.
|
|
|
|
func configSectionsForSurface(ctx context.Context, s store.Stores, surfaceID string) []ConfigSectionEntry {
|
|
if s.Packages == nil {
|
|
return nil
|
|
}
|
|
pkgs, err := s.Packages.List(ctx)
|
|
if err != nil {
|
|
log.Printf("[pages] config sections: failed to list packages: %v", err)
|
|
return nil
|
|
}
|
|
|
|
var sections []ConfigSectionEntry
|
|
for _, pkg := range pkgs {
|
|
if !pkg.Enabled || pkg.Manifest == nil {
|
|
continue
|
|
}
|
|
csRaw, ok := pkg.Manifest["config_section"]
|
|
if !ok {
|
|
continue
|
|
}
|
|
cs, ok := csRaw.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Check if this section targets the requested surface
|
|
surfacesRaw, _ := cs["surfaces"].([]any)
|
|
if len(surfacesRaw) == 0 {
|
|
continue
|
|
}
|
|
found := false
|
|
for _, v := range surfacesRaw {
|
|
if s, ok := v.(string); ok && s == surfaceID {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
continue
|
|
}
|
|
|
|
label, _ := cs["label"].(string)
|
|
if label == "" {
|
|
label = pkg.Title
|
|
}
|
|
icon, _ := cs["icon"].(string)
|
|
component, _ := cs["component"].(string)
|
|
if component == "" {
|
|
component = "js/config.js"
|
|
}
|
|
category, _ := cs["category"].(string)
|
|
if category == "" {
|
|
category = "system"
|
|
}
|
|
|
|
sections = append(sections, ConfigSectionEntry{
|
|
PackageID: pkg.ID,
|
|
Label: label,
|
|
Icon: icon,
|
|
Component: component,
|
|
Category: category,
|
|
})
|
|
}
|
|
return sections
|
|
}
|