feat: admin area with customers, users, settings, audit log, service status (phase 5)
- customer CRUD wizard backend: per-system credential shapes validated via
connector construction, AES-256-GCM at rest, write-only over the API
- ticketing connectors (jira/azure_devops/youtrack/demo) with test-connection
- user management: roles, disable (revokes sessions), force password reset,
delete; self-demotion/disable/delete guards
- admin-editable runtime settings ({_id:app}) incl. atomizer URL override
- audit log written on every privileged mutation + filterable list API
- service status panel: mongo/atomizer/work-performer health + latency with
late-bound breaker, sync and jobs status providers
- tabbed admin UI (customers wizard dialog, users table, settings, status, audit)
- compose: mongo nofile ulimit 64000 (1024 default crashed WiredTiger)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
package httpx
|
||||
|
||||
import "context"
|
||||
|
||||
// Late-bound providers: these are wired by later subsystems (atomizer
|
||||
// client, work-performer client, sync manager, job runner) after the server
|
||||
// is constructed. All accessors are nil-safe so the admin status panel
|
||||
// degrades gracefully while subsystems are absent (e.g. in tests).
|
||||
|
||||
// BreakerInfo exposes a circuit breaker's state for the status panel.
|
||||
type BreakerInfo interface {
|
||||
BreakerState() string // closed | open | half-open
|
||||
}
|
||||
|
||||
// StatusProvider returns arbitrary JSON-marshalable status payloads.
|
||||
type StatusProvider interface {
|
||||
Statuses() any
|
||||
}
|
||||
|
||||
func (s *Server) SetAtomizerInfo(b BreakerInfo) { s.atomizer = b }
|
||||
func (s *Server) SetPerformerInfo(b BreakerInfo) { s.performer = b }
|
||||
|
||||
// SetSyncTrigger wires the sync manager's "sync now" entry point.
|
||||
func (s *Server) SetSyncTrigger(f func(customerID string)) { s.syncTrigger = f }
|
||||
func (s *Server) SetSyncStatusProvider(p StatusProvider) {
|
||||
s.syncProvider = p
|
||||
}
|
||||
func (s *Server) SetJobsStatusProvider(p StatusProvider) {
|
||||
s.jobsProvider = p
|
||||
}
|
||||
|
||||
func (s *Server) syncStatuses() any {
|
||||
if s.syncProvider == nil {
|
||||
return []any{}
|
||||
}
|
||||
return s.syncProvider.Statuses()
|
||||
}
|
||||
|
||||
func (s *Server) jobStatuses() any {
|
||||
if s.jobsProvider == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
return s.jobsProvider.Statuses()
|
||||
}
|
||||
|
||||
// effectiveAtomizerBaseURL honors the admin settings override (§3) over the
|
||||
// env default.
|
||||
func (s *Server) effectiveAtomizerBaseURL(ctx context.Context) string {
|
||||
if s.store != nil {
|
||||
if st, err := s.store.GetSettings(ctx); err == nil && st.AtomizerBaseURLOverride != "" {
|
||||
return st.AtomizerBaseURLOverride
|
||||
}
|
||||
}
|
||||
return s.cfg.AtomizerBaseURL
|
||||
}
|
||||
Reference in New Issue
Block a user