This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/pages/pages_surfaces.go
Jeffrey Smith f0dd43144e rebrand: Switchboard Core → Armature
- Rename Go module switchboard-core → armature (155+ files)
- Rename Docker image → gobha/armature
- Rename K8s resources, secrets, deployments
- Rename Prometheus metrics switchboard_* → armature_*
- Rename env vars SWITCHBOARD_ADMIN_* → ARMATURE_ADMIN_*
- Rename DB names switchboard_core* → armature*
- Update all frontend branding, notification templates, docs
- Update CI scripts, e2e tests, Keycloak realm, nginx conf
- Rename scripts/switchboard-ca.sh → scripts/armature-ca.sh
- Rename k8s/switchboard.yaml → k8s/armature.yaml
- Rename chart alerting/dashboard files
- Fix: DockerHub push uses env: binding for secret injection
- Helm chart updated (name, labels, template functions, dashboard, alerting)
- Replace favicon/icon assets with Armature brand

No functional changes. Pure mechanical rename + CI fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 21:39:58 +00:00

186 lines
5.8 KiB
Go

package pages
import (
"context"
"log"
"net/http"
"github.com/gin-gonic/gin"
"armature/middleware"
)
// SeedSurfaces writes core surface manifests to the registry table.
// Called once at startup. Does NOT overwrite the enabled flag — admin
// toggles survive restarts.
func (e *Engine) SeedSurfaces() {
if e.stores.Packages == nil {
log.Printf("[pages] Surface registry not available — skipping seed")
return
}
ctx := context.Background()
for _, s := range e.surfaces {
manifest := map[string]any{
"route": s.Route,
"alt_routes": s.AltRoutes,
"template": s.Template,
"components": s.Components,
"data_requires": s.DataRequires,
"auth": s.Auth,
"layout": s.Layout,
}
if err := e.stores.Packages.Seed(ctx, s.ID, s.Title, s.Source, manifest); err != nil {
log.Printf("[pages] Failed to seed surface %q: %v", s.ID, err)
}
}
log.Printf("[pages] Seeded %d core surfaces into registry", len(e.surfaces))
// Editor is now an installable package — the old seed row would show a
// broken nav link until the package .pkg is uploaded via admin UI.
if sr, err := e.stores.Packages.Get(ctx, "editor"); err == nil && sr != nil && sr.Source == "extension" {
if sr.Type == "" || sr.Type == "surface" {
if delErr := e.stores.Packages.Delete(ctx, "editor"); delErr != nil {
log.Printf("[pages] Failed to clean up old editor surface: %v", delErr)
} else {
log.Printf("[pages] Cleaned up old editor core surface — install editor .pkg via admin UI")
}
}
}
}
// IsSurfaceEnabled checks if a surface is enabled in the registry.
// Admin is always enabled (system-critical).
// Returns true if the surface is not found (fail-open for backward compat).
func (e *Engine) IsSurfaceEnabled(surfaceID string) bool {
if surfaceID == "admin" {
return true
}
if e.stores.Packages == nil {
return true // No registry = all surfaces enabled (backward compat)
}
ctx := context.Background()
sr, err := e.stores.Packages.Get(ctx, surfaceID)
if err != nil || sr == nil {
return true // Not in registry = assume enabled (backward compat)
}
return sr.Enabled
}
// EnabledSurfaceIDs returns a list of enabled surface IDs for nav rendering.
func (e *Engine) EnabledSurfaceIDs() []string {
if e.stores.Packages == nil {
// No registry — return all core surface IDs
var all []string
for _, s := range e.surfaces {
all = append(all, s.ID)
}
return all
}
ctx := context.Background()
ids, err := e.stores.Packages.ListEnabled(ctx)
if err != nil {
log.Printf("[pages] Failed to load enabled surfaces: %v", err)
// Fallback: return all core surface IDs
var all []string
for _, s := range e.surfaces {
all = append(all, s.ID)
}
return all
}
return ids
}
// DefaultSurfaceRedirect returns a handler for GET / that redirects to the
// configured default surface, falling back to the first enabled extension
// surface, then /welcome.
//
// This route has no auth middleware, so we opportunistically read the JWT
// from the cookie to check user preferences. If the cookie is missing or
// invalid, user preference is skipped (falls through to global default).
func (e *Engine) DefaultSurfaceRedirect() gin.HandlerFunc {
return func(c *gin.Context) {
userID := middleware.UserIDFromCookie(c, e.cfg.JWTSecret)
target := e.resolveDefaultSurface(c.Request.Context(), userID)
c.Redirect(http.StatusTemporaryRedirect, e.cfg.BasePath+target)
}
}
// resolveDefaultSurface returns the path to redirect to (without BasePath).
// Priority: user preference → global default_surface → first enabled extension → /welcome.
func (e *Engine) resolveDefaultSurface(ctx context.Context, userID string) string {
if e.stores.GlobalConfig == nil || e.stores.Packages == nil {
return "/welcome"
}
// 1. Check user preference (default_surface in user settings)
if userID != "" && e.stores.Users != nil {
if user, err := e.stores.Users.GetByID(ctx, userID); err == nil && user != nil {
if id, _ := user.Settings["default_surface"].(string); id != "" {
if path := e.surfacePath(ctx, id); path != "" {
return path
}
// User's chosen surface is missing or disabled — fall through
}
}
}
// 2. Check admin-configured global default_surface
if raw, err := e.stores.GlobalConfig.Get(ctx, "default_surface"); err == nil && raw != nil {
if id, ok := raw["id"].(string); ok && id != "" {
if path := e.surfacePath(ctx, id); path != "" {
return path
}
// Configured surface is missing or disabled — fall through
}
}
// 3. First enabled extension surface (type "surface" or "full")
allPkgs, err := e.stores.Packages.List(ctx)
if err == nil {
for _, s := range allPkgs {
if s.Source == "core" || s.Source == "builtin" {
continue
}
if !s.Enabled {
continue
}
if s.Type == "surface" || s.Type == "full" {
return "/s/" + s.ID
}
}
}
// 4. Fallback — welcome surface (no extensions installed)
return "/welcome"
}
// surfacePath returns the URL path for a surface ID, or "" if the surface
// doesn't exist or is disabled.
func (e *Engine) surfacePath(ctx context.Context, id string) string {
pkg, err := e.stores.Packages.Get(ctx, id)
if err != nil || pkg == nil || !pkg.Enabled {
return ""
}
// Core surfaces live at /{id}, extension surfaces at /s/{slug}
if pkg.Source == "core" {
// Look up the core surface's base route
if sm := e.GetSurface(id); sm != nil {
// Use the first alt route (without params) or strip params from Route
if len(sm.AltRoutes) > 0 {
return sm.AltRoutes[0]
}
}
return "/" + id
}
return "/s/" + id
}
// disabledRedirect returns a handler that redirects to /.
// The DefaultSurfaceRedirect handler will resolve to the appropriate surface.
func (e *Engine) disabledRedirect() gin.HandlerFunc {
return func(c *gin.Context) {
c.Redirect(http.StatusTemporaryRedirect, e.cfg.BasePath+"/admin")
}
}