Skip to content

Commit 22d5e1c

Browse files
authored
Merge pull request #117 from ethpandaops/feat/xatu-cc
feat: implement xatu cc
2 parents 67aa180 + dc79b5c commit 22d5e1c

33 files changed

+2005
-1027
lines changed

pkg/cc/api.go

Lines changed: 23 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ import (
1414
"time"
1515

1616
"github.com/ethpandaops/xcli/pkg/ai"
17-
"github.com/ethpandaops/xcli/pkg/config"
1817
"github.com/ethpandaops/xcli/pkg/git"
19-
"github.com/ethpandaops/xcli/pkg/orchestrator"
2018
"github.com/ethpandaops/xcli/pkg/tui"
2119
"github.com/sirupsen/logrus"
2220
)
@@ -39,7 +37,7 @@ type stackProgressEvent struct {
3937

4038
// stackState tracks background stack operations to prevent concurrent boots/stops.
4139
type stackState struct {
42-
status string // "idle", "starting", "stopping"
40+
status string // "idle", "starting", "running", "stopping"
4341
lastError string // last boot/stop error, cleared on next operation
4442
cancelBoot context.CancelFunc // cancels the in-progress boot context; nil when not booting
4543
progressEvents []stackProgressEvent // accumulated progress events for the current operation
@@ -49,12 +47,8 @@ type stackState struct {
4947
// apiHandler holds dependencies for REST API handlers.
5048
type apiHandler struct {
5149
log logrus.FieldLogger
52-
wrapper *tui.OrchestratorWrapper
53-
health *tui.HealthMonitor
54-
orch *orchestrator.Orchestrator
50+
backend StackBackend
5551
redis *RedisAdmin
56-
labCfg *config.LabConfig
57-
cfgPath string
5852
gitChk *git.Checker
5953
aiDefaultProvider ai.ProviderID
6054
diagnoseSessions map[string]*diagnoseSession
@@ -68,10 +62,9 @@ type apiHandler struct {
6862

6963
// statusResponse is the full dashboard snapshot.
7064
type statusResponse struct {
71-
Services []serviceResponse `json:"services"`
72-
Infrastructure []infraResponse `json:"infrastructure"`
73-
Config configResponse `json:"config"`
74-
Timestamp time.Time `json:"timestamp"`
65+
Services []serviceResponse `json:"services"`
66+
Config any `json:"config"`
67+
Timestamp time.Time `json:"timestamp"`
7568
}
7669

7770
// serviceResponse represents a service with merged health info.
@@ -86,13 +79,6 @@ type serviceResponse struct {
8679
LogFile string `json:"logFile"`
8780
}
8881

89-
// infraResponse represents infrastructure status.
90-
type infraResponse struct {
91-
Name string `json:"name"`
92-
Status string `json:"status"`
93-
Type string `json:"type"`
94-
}
95-
9682
// configResponse is a sanitized view of the lab configuration.
9783
type configResponse struct {
9884
Mode string `json:"mode"`
@@ -139,41 +125,22 @@ type repoInfo struct {
139125
Error string `json:"error,omitempty"`
140126
}
141127

142-
// recreateOrchestrator rebuilds the orchestrator with the current config.
143-
// This is needed after config changes (e.g. mode switch) so the orchestrator
144-
// picks up the new mode, ports, etc. Must be called with a.mu held for writing.
145-
func (a *apiHandler) recreateOrchestrator() error {
146-
newOrch, err := orchestrator.NewOrchestrator(a.log, a.labCfg, a.cfgPath)
147-
if err != nil {
148-
return fmt.Errorf("failed to recreate orchestrator: %w", err)
149-
}
150-
151-
a.orch = newOrch
152-
a.wrapper.SetOrchestrator(newOrch)
153-
154-
return nil
155-
}
156-
157128
// handleGetStatus returns the full dashboard snapshot.
158-
func (a *apiHandler) handleGetStatus(w http.ResponseWriter, _ *http.Request) {
129+
func (a *apiHandler) handleGetStatus(w http.ResponseWriter, r *http.Request) {
130+
ctx := r.Context()
131+
159132
resp := statusResponse{
160-
Services: a.getServicesData(),
161-
Infrastructure: a.getInfraData(),
162-
Config: a.getConfigData(),
163-
Timestamp: time.Now(),
133+
Services: a.backend.GetServices(ctx),
134+
Config: a.backend.GetConfigSummary(),
135+
Timestamp: time.Now(),
164136
}
165137

166138
writeJSON(w, http.StatusOK, resp)
167139
}
168140

169141
// handleGetServices returns all services with health info.
170-
func (a *apiHandler) handleGetServices(w http.ResponseWriter, _ *http.Request) {
171-
writeJSON(w, http.StatusOK, a.getServicesData())
172-
}
173-
174-
// handleGetInfrastructure returns infrastructure status.
175-
func (a *apiHandler) handleGetInfrastructure(w http.ResponseWriter, _ *http.Request) {
176-
writeJSON(w, http.StatusOK, a.getInfraData())
142+
func (a *apiHandler) handleGetServices(w http.ResponseWriter, r *http.Request) {
143+
writeJSON(w, http.StatusOK, a.backend.GetServices(r.Context()))
177144
}
178145

179146
// handleGetGit returns git status for all repos, caching successful
@@ -192,12 +159,11 @@ func (a *apiHandler) handleGetGit(w http.ResponseWriter, r *http.Request) {
192159

193160
a.gitCache.mu.RUnlock()
194161

195-
repos := map[string]string{
196-
"cbt": a.labCfg.Repos.CBT,
197-
"xatu-cbt": a.labCfg.Repos.XatuCBT,
198-
"cbt-api": a.labCfg.Repos.CBTAPI,
199-
"lab-backend": a.labCfg.Repos.LabBackend,
200-
"lab": a.labCfg.Repos.Lab,
162+
repos := a.backend.GitRepos()
163+
if len(repos) == 0 {
164+
writeJSON(w, http.StatusOK, gitResponse{Repos: []repoInfo{}})
165+
166+
return
201167
}
202168

203169
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
@@ -277,13 +243,13 @@ func (a *apiHandler) handlePostServiceAction(
277243

278244
switch action {
279245
case "start":
280-
err = a.wrapper.StartService(ctx, name)
246+
err = a.backend.StartService(ctx, name)
281247
case "stop":
282-
err = a.wrapper.StopService(ctx, name)
248+
err = a.backend.StopService(ctx, name)
283249
case "restart":
284-
err = a.wrapper.RestartService(ctx, name)
250+
err = a.backend.RestartService(ctx, name)
285251
case "rebuild":
286-
err = a.wrapper.RebuildService(ctx, name)
252+
err = a.backend.RebuildService(ctx, name)
287253
default:
288254
writeJSON(w, http.StatusBadRequest, map[string]string{
289255
"error": "unknown action: " + action,
@@ -326,7 +292,7 @@ func (a *apiHandler) handleGetServiceLogs(
326292
return
327293
}
328294

329-
logPath := filepath.Clean(a.orch.LogFilePath(name))
295+
logPath := filepath.Clean(a.backend.LogFilePath(name))
330296

331297
f, err := os.Open(logPath) //nolint:gosec // path is constructed by LogFilePath from internal config
332298
if err != nil {
@@ -355,78 +321,6 @@ func (a *apiHandler) handleGetServiceLogs(
355321
writeJSON(w, http.StatusOK, lines)
356322
}
357323

358-
// getServicesData builds the services response slice.
359-
func (a *apiHandler) getServicesData() []serviceResponse {
360-
services := a.wrapper.GetServices()
361-
result := make([]serviceResponse, 0, len(services))
362-
363-
for _, svc := range services {
364-
resp := serviceResponse{
365-
Name: svc.Name,
366-
Status: svc.Status,
367-
PID: svc.PID,
368-
URL: svc.URL,
369-
Ports: svc.Ports,
370-
Health: svc.Health,
371-
LogFile: svc.LogFile,
372-
}
373-
374-
if svc.Uptime > 0 {
375-
resp.Uptime = formatDuration(svc.Uptime)
376-
}
377-
378-
result = append(result, resp)
379-
}
380-
381-
return result
382-
}
383-
384-
// getInfraData builds the infrastructure response slice.
385-
func (a *apiHandler) getInfraData() []infraResponse {
386-
infra := a.wrapper.GetInfrastructure()
387-
result := make([]infraResponse, 0, len(infra))
388-
389-
for _, i := range infra {
390-
result = append(result, infraResponse{
391-
Name: i.Name,
392-
Status: i.Status,
393-
Type: i.Type,
394-
})
395-
}
396-
397-
return result
398-
}
399-
400-
// getConfigData builds the sanitized config response.
401-
func (a *apiHandler) getConfigData() configResponse {
402-
networks := make([]networkInfo, 0, len(a.labCfg.Networks))
403-
for _, n := range a.labCfg.Networks {
404-
networks = append(networks, networkInfo{
405-
Name: n.Name,
406-
Enabled: n.Enabled,
407-
PortOffset: n.PortOffset,
408-
})
409-
}
410-
411-
return configResponse{
412-
Mode: a.labCfg.Mode,
413-
Networks: networks,
414-
Ports: portsInfo{
415-
LabBackend: a.labCfg.Ports.LabBackend,
416-
LabFrontend: a.labCfg.Ports.LabFrontend,
417-
CBTBase: a.labCfg.Ports.CBTBase,
418-
CBTAPIBase: a.labCfg.Ports.CBTAPIBase,
419-
CBTFrontendBase: a.labCfg.Ports.CBTFrontendBase,
420-
ClickHouseCBT: a.labCfg.Infrastructure.ClickHouseCBTPort,
421-
ClickHouseXatu: a.labCfg.Infrastructure.ClickHouseXatuPort,
422-
Redis: a.labCfg.Infrastructure.RedisPort,
423-
Prometheus: a.labCfg.Infrastructure.Observability.PrometheusPort,
424-
Grafana: a.labCfg.Infrastructure.Observability.GrafanaPort,
425-
},
426-
CfgPath: a.cfgPath,
427-
}
428-
}
429-
430324
// formatDuration formats a duration into a human-readable string.
431325
func formatDuration(d time.Duration) string {
432326
d = d.Truncate(time.Second)

0 commit comments

Comments
 (0)