V0.38.3 extension config sections (#236)
Co-authored-by: gobha <jasafpro@gmail.com> Co-committed-by: gobha <jasafpro@gmail.com>
This commit is contained in:
@@ -95,6 +95,7 @@ type AdminPageData struct {
|
||||
Users []UserRow `json:"users,omitempty"`
|
||||
Roles []RoleConfig `json:"roles"`
|
||||
Policies any `json:"policies,omitempty"`
|
||||
ConfigSections []ConfigSectionEntry `json:"config_sections,omitempty"` // v0.38.3
|
||||
}
|
||||
|
||||
// ChatPageData is what the chat surface templates receive.
|
||||
@@ -110,14 +111,16 @@ type NotesPageData struct {
|
||||
// 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"`
|
||||
Section string `json:"section"`
|
||||
ConfigSections []ConfigSectionEntry `json:"config_sections,omitempty"` // v0.38.3
|
||||
}
|
||||
|
||||
// ── Loader registration ──────────────────────
|
||||
|
||||
// TeamAdminPageData is what the team-admin surface receives.
|
||||
type TeamAdminPageData struct {
|
||||
Section string `json:"section"`
|
||||
Section string `json:"section"`
|
||||
ConfigSections []ConfigSectionEntry `json:"config_sections,omitempty"` // v0.38.3
|
||||
}
|
||||
|
||||
// ProjectsPageData is what the projects surface receives.
|
||||
@@ -246,6 +249,9 @@ func (e *Engine) adminLoader(c *gin.Context, s store.Stores) (any, error) {
|
||||
data.Users = e.loadUsers(ctx, s)
|
||||
}
|
||||
|
||||
// v0.38.3: Discover extension config sections for admin surface
|
||||
data.ConfigSections = configSectionsForSurface(ctx, s, "admin")
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -409,7 +415,10 @@ func (e *Engine) teamAdminLoader(c *gin.Context, s store.Stores) (any, error) {
|
||||
if section == "" {
|
||||
section = "members"
|
||||
}
|
||||
return &TeamAdminPageData{Section: section}, nil
|
||||
return &TeamAdminPageData{
|
||||
Section: section,
|
||||
ConfigSections: configSectionsForSurface(context.Background(), s, "team-admin"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *Engine) settingsLoader(c *gin.Context, s store.Stores) (any, error) {
|
||||
@@ -418,10 +427,84 @@ func (e *Engine) settingsLoader(c *gin.Context, s store.Stores) (any, error) {
|
||||
section = "general"
|
||||
}
|
||||
|
||||
return &SettingsPageData{Section: section}, nil
|
||||
return &SettingsPageData{
|
||||
Section: section,
|
||||
ConfigSections: configSectionsForSurface(context.Background(), s, "settings"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *Engine) projectsLoader(c *gin.Context, s store.Stores) (any, error) {
|
||||
projectID := c.Param("id")
|
||||
return &ProjectsPageData{ProjectID: projectID}, 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
|
||||
}
|
||||
|
||||
@@ -135,6 +135,17 @@ type ExtensionNavItem struct {
|
||||
Route string
|
||||
}
|
||||
|
||||
// ConfigSectionEntry describes a package-declared config section for
|
||||
// lazy-loading in Settings, Admin, or Team Admin surfaces.
|
||||
// v0.38.3: manifest-driven discovery — no new tables or endpoints.
|
||||
type ConfigSectionEntry struct {
|
||||
PackageID string `json:"package_id"` // section key & asset path prefix
|
||||
Label string `json:"label"` // nav link text
|
||||
Icon string `json:"icon"` // optional SVG path data (admin CatIcon format)
|
||||
Component string `json:"component"` // relative asset path, e.g. "js/config.js"
|
||||
Category string `json:"category"` // admin only: category tab (default "system")
|
||||
}
|
||||
|
||||
// extensionNavItems returns enabled extension surfaces for sidebar rendering.
|
||||
// Queries the DB at render time so newly installed surfaces appear immediately.
|
||||
func (e *Engine) extensionNavItems() []ExtensionNavItem {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
{{/* Inject section name for Preact routing */}}
|
||||
<script nonce="{{.CSPNonce}}">
|
||||
window.__SECTION__ = '{{.Section}}';
|
||||
window.__CONFIG_SECTIONS__ = {{.Data.ConfigSections | toJSON}};
|
||||
</script>
|
||||
|
||||
{{/* Vendor: Preact + htm → globals, then SDK boot, then admin surface */}}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
{{/* Inject section name for Preact routing */}}
|
||||
<script nonce="{{.CSPNonce}}">
|
||||
window.__SECTION__ = '{{.Section}}';
|
||||
window.__CONFIG_SECTIONS__ = {{.Data.ConfigSections | toJSON}};
|
||||
</script>
|
||||
|
||||
{{/* Bridge section scripts removed in v0.37.7 — all sections are native Preact */}}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
{{define "scripts-team-admin"}}
|
||||
<script nonce="{{.CSPNonce}}">
|
||||
window.__SECTION__ = '{{.Section}}';
|
||||
window.__CONFIG_SECTIONS__ = {{.Data.ConfigSections | toJSON}};
|
||||
</script>
|
||||
|
||||
<script type="module" nonce="{{.CSPNonce}}">
|
||||
|
||||
@@ -12350,6 +12350,28 @@ components:
|
||||
resolved_ver:
|
||||
type: string
|
||||
description: Actual library version at dependency creation time
|
||||
ConfigSection:
|
||||
type: object
|
||||
description: "v0.38.3: Manifest-declared config section for Settings/Admin/Team Admin surfaces"
|
||||
properties:
|
||||
label:
|
||||
type: string
|
||||
description: Nav link text
|
||||
icon:
|
||||
type: string
|
||||
description: Optional SVG path data (compact format)
|
||||
component:
|
||||
type: string
|
||||
description: Relative asset path within package archive (default js/config.js)
|
||||
surfaces:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum: [admin, settings, team-admin]
|
||||
description: Which surfaces show this section
|
||||
category:
|
||||
type: string
|
||||
description: Admin-only category tab (default system)
|
||||
ExtConnection:
|
||||
type: object
|
||||
description: "v0.38.1: Extension connection credential (secrets masked in list responses)"
|
||||
|
||||
Reference in New Issue
Block a user