feat: customer config management — CRUD, API retrieval, per-customer auth (v0.2.0)
New "Configurations" section lets operators pre-configure customer settings
in the Hub, then docker-setup.sh can download a ready-made controller.yaml
using just a customer ID and retrieval password.
- Store: customer_configs table with CRUD + per-customer API key lookup
- API: GET /api/v1/config/{id} with X-Retrieval-Password auth
- Auth: per-customer API keys alongside existing global key (backward compatible)
- Web UI: /configs list, create, edit, delete, YAML preview, copy-to-clipboard
- YAML gen: deep-merge controller.yaml.example template with customer overrides
- Template fetcher: background goroutine refreshing template from Gitea repo
- Navigation: Dashboard / Configurations tabs on all pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,13 +18,14 @@ import (
|
||||
|
||||
// Server handles the dashboard web UI.
|
||||
type Server struct {
|
||||
store *store.Store
|
||||
passwordHash string
|
||||
apiKey string // report API key — used for controller callbacks
|
||||
logger *log.Logger
|
||||
templates *template.Template
|
||||
staleThreshold time.Duration
|
||||
versionChecker *VersionChecker
|
||||
store *store.Store
|
||||
passwordHash string
|
||||
apiKey string // report API key — used for controller callbacks
|
||||
logger *log.Logger
|
||||
templates *template.Template
|
||||
staleThreshold time.Duration
|
||||
versionChecker *VersionChecker
|
||||
templateFetcher *TemplateFetcher
|
||||
}
|
||||
|
||||
// New creates a new web server.
|
||||
@@ -58,6 +59,11 @@ func (s *Server) SetVersionChecker(vc *VersionChecker) {
|
||||
s.versionChecker = vc
|
||||
}
|
||||
|
||||
// SetTemplateFetcher sets the template fetcher for config generation (optional).
|
||||
func (s *Server) SetTemplateFetcher(tf *TemplateFetcher) {
|
||||
s.templateFetcher = tf
|
||||
}
|
||||
|
||||
// ServeHTTP routes web requests.
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
@@ -80,6 +86,46 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
case strings.HasPrefix(path, "/customers/"):
|
||||
customerID := strings.TrimPrefix(path, "/customers/")
|
||||
s.handleCustomerDetail(w, r, customerID)
|
||||
// Config management routes — exact matches first, then prefix matches
|
||||
case path == "/configs":
|
||||
s.handleConfigList(w, r)
|
||||
case path == "/configs/new":
|
||||
if r.Method == http.MethodPost {
|
||||
s.handleConfigCreate(w, r)
|
||||
} else {
|
||||
s.handleConfigNewForm(w, r)
|
||||
}
|
||||
case strings.HasPrefix(path, "/configs/") && strings.HasSuffix(path, "/delete"):
|
||||
customerID := strings.TrimPrefix(path, "/configs/")
|
||||
customerID = strings.TrimSuffix(customerID, "/delete")
|
||||
if r.Method == http.MethodPost {
|
||||
s.handleConfigDelete(w, r, customerID)
|
||||
} else {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
case strings.HasPrefix(path, "/configs/") && strings.HasSuffix(path, "/edit"):
|
||||
customerID := strings.TrimPrefix(path, "/configs/")
|
||||
customerID = strings.TrimSuffix(customerID, "/edit")
|
||||
if r.Method == http.MethodPost {
|
||||
s.handleConfigUpdate(w, r, customerID)
|
||||
} else {
|
||||
s.handleConfigEditForm(w, r, customerID)
|
||||
}
|
||||
case strings.HasPrefix(path, "/configs/") && strings.HasSuffix(path, "/preview"):
|
||||
customerID := strings.TrimPrefix(path, "/configs/")
|
||||
customerID = strings.TrimSuffix(customerID, "/preview")
|
||||
s.handleConfigPreview(w, r, customerID)
|
||||
case strings.HasPrefix(path, "/configs/") && strings.HasSuffix(path, "/regen-password"):
|
||||
customerID := strings.TrimPrefix(path, "/configs/")
|
||||
customerID = strings.TrimSuffix(customerID, "/regen-password")
|
||||
if r.Method == http.MethodPost {
|
||||
s.handleConfigRegenPassword(w, r, customerID)
|
||||
} else {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
case strings.HasPrefix(path, "/configs/"):
|
||||
customerID := strings.TrimPrefix(path, "/configs/")
|
||||
s.handleConfigDetail(w, r, customerID)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user