Feat v0.2.6 admin settings audit (#10)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #10.
This commit is contained in:
26
CHANGELOG.md
26
CHANGELOG.md
@@ -2,6 +2,32 @@
|
|||||||
|
|
||||||
All notable changes to Switchboard Core are documented here.
|
All notable changes to Switchboard Core are documented here.
|
||||||
|
|
||||||
|
## [Unreleased] — v0.2.6
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Roadmap reorder**: Extension Lifecycle moved from v0.2.6 to v0.3.x
|
||||||
|
(Workflows series). Settings audit milestones renumbered: v0.2.7→v0.2.6,
|
||||||
|
v0.2.8→v0.2.7, v0.2.9→v0.2.8. Added v0.2.9 for builtin extension
|
||||||
|
retirement (chat-centric extensions dormant until chat surface ships).
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- **Dead admin section categories** in `sectionCategory()`: AI (providers,
|
||||||
|
models, personas, roles, knowledgeBases, memory), routing (health, routing,
|
||||||
|
capabilities), and channels. Default category changed from `ai` to `system`.
|
||||||
|
- **Dead PublicSettings fields**: `system_prompt`/`has_admin_prompt`,
|
||||||
|
`retention_ttl_days`, `paste_to_file_chars`, `allow_user_personas` policy
|
||||||
|
— all chat-era with no frontend consumers.
|
||||||
|
- **Dead PolicyDefaults**: `allow_raw_model_access`, `default_model`.
|
||||||
|
- **Dead policy lookups**: `allow_raw_model_access` and `kb_direct_access`
|
||||||
|
from profile bootstrap and permissions handlers.
|
||||||
|
- **Dead test seed data**: `model_roles` global setting, `allow_raw_model_access`
|
||||||
|
policy from test helper.
|
||||||
|
- **Dead CORE_IDS entry**: Removed `chat` from packages admin page.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [Unreleased] — v0.2.5
|
## [Unreleased] — v0.2.5
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
27
ROADMAP.md
27
ROADMAP.md
@@ -89,42 +89,45 @@ SDK stabilization, and the first rebuilt extension (tasks).
|
|||||||
| Default surface routing | ✅ | Resolution chain: user preference → global config → first extension → `/welcome`. Users can set personal default in Settings > General. Admin sets global default in Admin > Settings. |
|
| Default surface routing | ✅ | Resolution chain: user preference → global config → first extension → `/welcome`. Users can set personal default in Settings > General. Admin sets global default in Admin > Settings. |
|
||||||
| Admin navigation | ✅ | Replaced Back button with UserMenu in admin topbar. Eliminates back-button infinite loop. |
|
| Admin navigation | ✅ | Replaced Back button with UserMenu in admin topbar. Eliminates back-button infinite loop. |
|
||||||
|
|
||||||
### v0.2.6 — Extension Lifecycle
|
### v0.2.6 — Admin Settings Audit
|
||||||
|
|
||||||
| Step | Status | Description |
|
| Step | Status | Description |
|
||||||
|------|--------|-------------|
|
|------|--------|-------------|
|
||||||
| Extension lifecycle | ⬚ | Define permanent vs PoC extensions. Package graduation criteria. Dependency policy. What ships with core vs what's installed separately. |
|
| Admin settings E2E | ✅ | All 7 admin settings sections verified (default surface, registration, banner, message bar, footer, vault, email). Dead code removed: `sectionCategory()` pruned of AI/routing/channel vestiges, `PublicSettings()` stripped of chat-era fields (system_prompt, retention_ttl, paste_to_file, allow_user_personas), dead `PolicyDefaults` removed (allow_raw_model_access, default_model), dead policy lookups removed (kb_direct_access), test seed data cleaned. |
|
||||||
|
| Packages surface | ✅ | Package list loads (17 total/17 enabled), type filters work, enable/disable visible, settings/export buttons present, core package (admin) protected. Removed dead `chat` from CORE_IDS. |
|
||||||
|
|
||||||
### v0.2.7 — Admin Settings Audit
|
### v0.2.7 — User Settings Audit
|
||||||
|
|
||||||
| Step | Status | Description |
|
|
||||||
|------|--------|-------------|
|
|
||||||
| Admin settings E2E | ⬚ | Verify all surviving admin settings work end-to-end: default surface, registration, banner, message bar, footer, vault, email. Remove any handler/store vestiges of deleted settings (search, compaction, memory). |
|
|
||||||
| Packages surface | ⬚ | Verify packages page correctly shows installed vs available. Enable/disable works. Broken post-fork surfaces identified and either fixed or marked. |
|
|
||||||
|
|
||||||
### v0.2.8 — User Settings Audit
|
|
||||||
|
|
||||||
| Step | Status | Description |
|
| Step | Status | Description |
|
||||||
|------|--------|-------------|
|
|------|--------|-------------|
|
||||||
| User settings E2E | ⬚ | Verify all user settings sections: General (default surface), Appearance (theme, scale, font), Profile (display name, avatar, handle), Connections, Notifications. Remove dead features, fix broken states. |
|
| User settings E2E | ⬚ | Verify all user settings sections: General (default surface), Appearance (theme, scale, font), Profile (display name, avatar, handle), Connections, Notifications. Remove dead features, fix broken states. |
|
||||||
| Visibility gating | ⬚ | Ensure settings sections only show features that are actually available. Hide empty sections. Respect `user_overridable` flag from extension manifests. |
|
| Visibility gating | ⬚ | Ensure settings sections only show features that are actually available. Hide empty sections. Respect `user_overridable` flag from extension manifests. |
|
||||||
|
|
||||||
### v0.2.9 — Team Admin Settings Audit (Pass 1)
|
### v0.2.8 — Team Admin Settings Audit (Pass 1)
|
||||||
|
|
||||||
| Step | Status | Description |
|
| Step | Status | Description |
|
||||||
|------|--------|-------------|
|
|------|--------|-------------|
|
||||||
| Team admin E2E | ⬚ | Verify team member management, team settings cascade, role assignment (admin/member). Audit for dead code from pre-fork team features. First pass — validates current functionality before v0.3.x adds team roles. |
|
| Team admin E2E | ⬚ | Verify team member management, team settings cascade, role assignment (admin/member). Audit for dead code from pre-fork team features. First pass — validates current functionality before v0.3.x adds team roles. |
|
||||||
| User settings team tab | ⬚ | Verify the Teams section in user settings — team list, join/leave, team-scoped settings. |
|
| User settings team tab | ⬚ | Verify the Teams section in user settings — team list, join/leave, team-scoped settings. |
|
||||||
|
|
||||||
|
### v0.2.9 — Builtin Extension Retirement
|
||||||
|
|
||||||
|
| Step | Status | Description |
|
||||||
|
|------|--------|-------------|
|
||||||
|
| Retire builtin seeder | ⬚ | Stop auto-seeding chat-centric extensions (csv-table, diff-viewer, js-sandbox, katex-renderer, mermaid-renderer, regex-tester). Remove `SeedBuiltinPackages`, Dockerfile COPY, and `extensions/builtin/` directory. These extensions are not OBE — they're dormant until a chat surface exists to consume them. |
|
||||||
|
| Convert to regular packages | ⬚ | Repackage the 6 extensions as standard `.pkg` archives in `packages/`. Add chat dependency metadata to manifests so they can be installed when the chat extension ships post-MVP. No auto-install — explicit install only, matching the distribution model. |
|
||||||
|
|
||||||
## v0.3.x — Workflow Architecture
|
## v0.3.x — Workflow Architecture
|
||||||
|
|
||||||
Workflows are the core platform capability. This series implements the
|
Workflows are the core platform capability. This series implements the
|
||||||
full multi-step automation system with team role integration.
|
full multi-step automation system with team role integration and
|
||||||
|
finalizes the extension lifecycle model.
|
||||||
|
|
||||||
### v0.3.0 — Workflow Design + Schema
|
### v0.3.0 — Workflow Design + Schema
|
||||||
|
|
||||||
| Step | Status | Description |
|
| Step | Status | Description |
|
||||||
|------|--------|-------------|
|
|------|--------|-------------|
|
||||||
|
| Extension lifecycle | ⬚ | Define permanent vs PoC extensions. Package graduation criteria. Dependency policy. What ships with core vs what's installed separately. |
|
||||||
| Workflow design session | ⬚ | Define what "workflow" means in the extension-first model. Determine if the existing `workflows` table/handler survives, gets rebuilt, or gets removed. Document the Starlark contract for multi-step automation. See `docs/DESIGN-WORKFLOW-REDESIGN-0.2.6.md`. |
|
| Workflow design session | ⬚ | Define what "workflow" means in the extension-first model. Determine if the existing `workflows` table/handler survives, gets rebuilt, or gets removed. Document the Starlark contract for multi-step automation. See `docs/DESIGN-WORKFLOW-REDESIGN-0.2.6.md`. |
|
||||||
| Team roles | ⬚ | Different roles per team responsible for different workflow stages. Role-based stage assignment, multi-party validation (2-party sign-off at stage boundaries). |
|
| Team roles | ⬚ | Different roles per team responsible for different workflow stages. Role-based stage assignment, multi-party validation (2-party sign-off at stage boundaries). |
|
||||||
| Trigger composition model | ⬚ | How do triggers, schedules, and workflows compose? Event chains, conditional branching, error handling. Design doc before code. |
|
| Trigger composition model | ⬚ | How do triggers, schedules, and workflows compose? Event chains, conditional branching, error handling. Design doc before code. |
|
||||||
|
|||||||
@@ -335,15 +335,13 @@ func TruncateAll(t *testing.T) {
|
|||||||
INSERT INTO global_settings (key, value) VALUES
|
INSERT INTO global_settings (key, value) VALUES
|
||||||
('registration', '{"enabled": true}'),
|
('registration', '{"enabled": true}'),
|
||||||
('site', '{"name": "Switchboard Core", "tagline": "Self-hosted extension platform"}'),
|
('site', '{"name": "Switchboard Core", "tagline": "Self-hosted extension platform"}'),
|
||||||
('banner', '{"enabled": false, "text": "", "bg": "#007a33", "fg": "#ffffff"}'),
|
('banner', '{"enabled": false, "text": "", "bg": "#007a33", "fg": "#ffffff"}')
|
||||||
('model_roles', '{"utility":{"primary":null,"fallback":null},"embedding":{"primary":null,"fallback":null},"generation":{"primary":null,"fallback":null}}')
|
|
||||||
ON CONFLICT (key) DO NOTHING
|
ON CONFLICT (key) DO NOTHING
|
||||||
`)
|
`)
|
||||||
DB.Exec(`
|
DB.Exec(`
|
||||||
INSERT INTO platform_policies (key, value) VALUES
|
INSERT INTO platform_policies (key, value) VALUES
|
||||||
('allow_user_byok', 'false'),
|
('allow_user_byok', 'false'),
|
||||||
('allow_user_personas', 'false'),
|
('allow_user_personas', 'false'),
|
||||||
('allow_raw_model_access', 'true'),
|
|
||||||
('allow_registration', 'true'),
|
('allow_registration', 'true'),
|
||||||
('default_user_active', 'false'),
|
('default_user_active', 'false'),
|
||||||
('allow_team_providers', 'true')
|
('allow_team_providers', 'true')
|
||||||
@@ -354,19 +352,13 @@ func TruncateAll(t *testing.T) {
|
|||||||
INSERT INTO global_settings (key, value) VALUES
|
INSERT INTO global_settings (key, value) VALUES
|
||||||
('registration', '{"enabled": true}'::jsonb),
|
('registration', '{"enabled": true}'::jsonb),
|
||||||
('site', '{"name": "Switchboard Core", "tagline": "Self-hosted extension platform"}'::jsonb),
|
('site', '{"name": "Switchboard Core", "tagline": "Self-hosted extension platform"}'::jsonb),
|
||||||
('banner', '{"enabled": false, "text": "", "bg": "#007a33", "fg": "#ffffff"}'::jsonb),
|
('banner', '{"enabled": false, "text": "", "bg": "#007a33", "fg": "#ffffff"}'::jsonb)
|
||||||
('model_roles', '{
|
|
||||||
"utility": { "primary": null, "fallback": null },
|
|
||||||
"embedding": { "primary": null, "fallback": null },
|
|
||||||
"generation": { "primary": null, "fallback": null }
|
|
||||||
}'::jsonb)
|
|
||||||
ON CONFLICT (key) DO NOTHING
|
ON CONFLICT (key) DO NOTHING
|
||||||
`)
|
`)
|
||||||
DB.Exec(`
|
DB.Exec(`
|
||||||
INSERT INTO platform_policies (key, value) VALUES
|
INSERT INTO platform_policies (key, value) VALUES
|
||||||
('allow_user_byok', 'false'),
|
('allow_user_byok', 'false'),
|
||||||
('allow_user_personas', 'false'),
|
('allow_user_personas', 'false'),
|
||||||
('allow_raw_model_access', 'true'),
|
|
||||||
('allow_registration', 'true'),
|
('allow_registration', 'true'),
|
||||||
('default_user_active', 'false'),
|
('default_user_active', 'false'),
|
||||||
('allow_team_providers', 'true')
|
('allow_team_providers', 'true')
|
||||||
|
|||||||
@@ -361,47 +361,15 @@ func (h *AdminHandler) PublicSettings(c *gin.Context) {
|
|||||||
// Banner config, branding, etc. — safe subset for non-admin users
|
// Banner config, branding, etc. — safe subset for non-admin users
|
||||||
banner, _ := h.stores.GlobalConfig.Get(c.Request.Context(), "banner")
|
banner, _ := h.stores.GlobalConfig.Get(c.Request.Context(), "banner")
|
||||||
branding, _ := h.stores.GlobalConfig.Get(c.Request.Context(), "branding")
|
branding, _ := h.stores.GlobalConfig.Get(c.Request.Context(), "branding")
|
||||||
systemPrompt, _ := h.stores.GlobalConfig.Get(c.Request.Context(), "system_prompt")
|
|
||||||
policies, _ := h.stores.Policies.GetAll(c.Request.Context())
|
policies, _ := h.stores.Policies.GetAll(c.Request.Context())
|
||||||
|
|
||||||
// Paste-to-file threshold (admin-configurable via storage.paste_to_file_chars, default 2000)
|
|
||||||
pasteChars := 2000
|
|
||||||
if storageSettings, err := h.stores.GlobalConfig.Get(c.Request.Context(), "storage"); err == nil {
|
|
||||||
if v, ok := storageSettings["paste_to_file_chars"]; ok {
|
|
||||||
switch n := v.(type) {
|
|
||||||
case float64:
|
|
||||||
pasteChars = int(n)
|
|
||||||
case int:
|
|
||||||
pasteChars = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only tell the user whether admin prompt exists — don't expose content
|
|
||||||
hasAdminPrompt := false
|
|
||||||
if content, ok := systemPrompt["content"].(string); ok && content != "" {
|
|
||||||
hasAdminPrompt = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retention TTL (v0.37.14 — days before purge for global/team channels)
|
|
||||||
retentionTTL := 0
|
|
||||||
if ttlCfg, err := h.stores.GlobalConfig.Get(c.Request.Context(), "retention_ttl_days"); err == nil {
|
|
||||||
if v, ok := ttlCfg["value"].(float64); ok {
|
|
||||||
retentionTTL = int(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"banner": banner,
|
"banner": banner,
|
||||||
"branding": branding,
|
"branding": branding,
|
||||||
"has_admin_prompt": hasAdminPrompt,
|
"storage_configured": storageConfigured,
|
||||||
"storage_configured": storageConfigured,
|
|
||||||
"paste_to_file_chars": pasteChars,
|
|
||||||
"policies": gin.H{
|
"policies": gin.H{
|
||||||
"allow_registration": policies["allow_registration"],
|
"allow_registration": policies["allow_registration"],
|
||||||
"allow_user_byok": policies["allow_user_byok"],
|
"allow_user_byok": policies["allow_user_byok"],
|
||||||
"allow_user_personas": policies["allow_user_personas"],
|
|
||||||
"retention_ttl_days": retentionTTL,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,8 +86,6 @@ func (h *ProfileBootstrapHandler) GetBootstrap(c *gin.Context) {
|
|||||||
if ps := h.stores.Policies; ps != nil {
|
if ps := h.stores.Policies; ps != nil {
|
||||||
policies["allow_user_byok"], _ = ps.GetBool(ctx, "allow_user_byok")
|
policies["allow_user_byok"], _ = ps.GetBool(ctx, "allow_user_byok")
|
||||||
policies["allow_user_personas"], _ = ps.GetBool(ctx, "allow_user_personas")
|
policies["allow_user_personas"], _ = ps.GetBool(ctx, "allow_user_personas")
|
||||||
policies["allow_raw_model_access"], _ = ps.GetBool(ctx, "allow_raw_model_access")
|
|
||||||
policies["kb_direct_access"], _ = ps.GetBool(ctx, "kb_direct_access")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Settings ────────────────────────────
|
// ── Settings ────────────────────────────
|
||||||
|
|||||||
@@ -67,8 +67,6 @@ func (h *ProfilePermissionsHandler) GetMyPermissions(c *gin.Context) {
|
|||||||
if ps := h.stores.Policies; ps != nil {
|
if ps := h.stores.Policies; ps != nil {
|
||||||
policies["allow_user_byok"], _ = ps.GetBool(ctx, "allow_user_byok")
|
policies["allow_user_byok"], _ = ps.GetBool(ctx, "allow_user_byok")
|
||||||
policies["allow_user_personas"], _ = ps.GetBool(ctx, "allow_user_personas")
|
policies["allow_user_personas"], _ = ps.GetBool(ctx, "allow_user_personas")
|
||||||
policies["allow_raw_model_access"], _ = ps.GetBool(ctx, "allow_raw_model_access")
|
|
||||||
policies["kb_direct_access"], _ = ps.GetBool(ctx, "kb_direct_access")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
|||||||
@@ -84,13 +84,11 @@ type TeamMember struct {
|
|||||||
// PLATFORM POLICIES
|
// PLATFORM POLICIES
|
||||||
|
|
||||||
var PolicyDefaults = map[string]string{
|
var PolicyDefaults = map[string]string{
|
||||||
"allow_user_byok": "false",
|
"allow_user_byok": "false",
|
||||||
"allow_user_personas": "false",
|
"allow_user_personas": "false",
|
||||||
"allow_raw_model_access": "false",
|
"allow_registration": "true",
|
||||||
"allow_registration": "true",
|
"default_user_active": "false",
|
||||||
"default_user_active": "false",
|
"allow_team_providers": "true",
|
||||||
"allow_team_providers": "true",
|
|
||||||
"default_model": "",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleFromName generates a URL-safe @mention handle from a display name.
|
// HandleFromName generates a URL-safe @mention handle from a display name.
|
||||||
|
|||||||
@@ -79,16 +79,14 @@ func sectionCategory(section string) string {
|
|||||||
switch section {
|
switch section {
|
||||||
case "users", "teams", "groups":
|
case "users", "teams", "groups":
|
||||||
return "people"
|
return "people"
|
||||||
case "providers", "models", "personas", "roles", "knowledgeBases", "memory":
|
case "workflows":
|
||||||
return "ai"
|
return "workflows"
|
||||||
case "health", "routing", "capabilities":
|
case "settings", "storage", "packages", "broadcast":
|
||||||
return "routing"
|
|
||||||
case "settings", "storage", "packages", "channels", "broadcast":
|
|
||||||
return "system"
|
return "system"
|
||||||
case "usage", "audit", "stats":
|
case "usage", "audit", "stats":
|
||||||
return "monitoring"
|
return "monitoring"
|
||||||
default:
|
default:
|
||||||
return "ai" // default landing
|
return "system"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const { html } = window;
|
|||||||
const { useState, useEffect, useCallback } = hooks;
|
const { useState, useEffect, useCallback } = hooks;
|
||||||
|
|
||||||
const TYPE_OPTIONS = ['all', 'surface', 'extension', 'full', 'workflow'];
|
const TYPE_OPTIONS = ['all', 'surface', 'extension', 'full', 'workflow'];
|
||||||
const CORE_IDS = new Set(['chat', 'admin']);
|
const CORE_IDS = new Set(['admin']);
|
||||||
|
|
||||||
function typeBadge(type) {
|
function typeBadge(type) {
|
||||||
const cls = type === 'surface' ? 'badge-active'
|
const cls = type === 'surface' ? 'badge-active'
|
||||||
|
|||||||
Reference in New Issue
Block a user