@@ -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.
4139type 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.
5048type 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.
7064type 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.
9783type 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.
431325func formatDuration (d time.Duration ) string {
432326 d = d .Truncate (time .Second )
0 commit comments