77 "fmt"
88 "log"
99 "net/http"
10+ "net/http/httputil"
11+ "net/url"
1012 "os"
1113 "os/exec"
1214 "path/filepath"
@@ -70,6 +72,8 @@ type Server struct {
7072 auditMiddleware * audit.Middleware
7173 powerDNSManager * dns.PowerDNSManager
7274 clusterManager * cluster.Manager
75+ dashboardServer * http.Server
76+ cachedIP string
7377}
7478
7579func New (cfg * config.Config , configPath string ) * Server {
@@ -232,6 +236,9 @@ func New(cfg *config.Config, configPath string) *Server {
232236
233237 s .setupRoutes ()
234238
239+ // Start dashboard server on port 8080
240+ go s .StartDashboard ()
241+
235242 return s
236243}
237244
@@ -246,6 +253,24 @@ func (s *Server) setupRoutes() {
246253 // WebSocket endpoint handles its own auth via first-message
247254 api .GET ("/containers/:id/exec" , s .containerExec )
248255
256+ // Setup endpoints (public, permissive CORS, gated by setup state)
257+ setup := api .Group ("/setup" )
258+ setup .Use (s .setupCORS ())
259+ {
260+ setup .GET ("/status" , s .getSetupStatus )
261+
262+ guarded := setup .Group ("" )
263+ guarded .GET ("/info" , s .getSetupInfo )
264+ guarded .Use (s .setupGuard ())
265+ {
266+ guarded .POST ("/validate" , s .validateSystem )
267+ guarded .GET ("/verify-dns" , s .verifyDNS )
268+ guarded .POST ("/settings" , s .configureSettings )
269+ guarded .POST ("/authentication" , s .configureAuthentication )
270+ guarded .POST ("/complete" , s .completeSetup )
271+ }
272+ }
273+
249274 protected := api .Group ("" )
250275 protected .Use (s .authMiddleware .RequireAuth ())
251276 if s .auditMiddleware != nil {
@@ -564,10 +589,58 @@ func (s *Server) Start() error {
564589 return s .server .ListenAndServe ()
565590}
566591
592+ func (s * Server ) StartDashboard () {
593+ installDir := os .Getenv ("INSTALL_DIR" )
594+ if installDir == "" {
595+ installDir = "/opt/flatrun"
596+ }
597+ distDir := filepath .Join (installDir , "ui" , "dist" )
598+
599+ apiTarget , _ := url .Parse (fmt .Sprintf ("http://127.0.0.1:%d" , s .config .API .Port ))
600+ proxy := httputil .NewSingleHostReverseProxy (apiTarget )
601+
602+ mux := http .NewServeMux ()
603+
604+ mux .HandleFunc ("/api/" , func (w http.ResponseWriter , r * http.Request ) {
605+ proxy .ServeHTTP (w , r )
606+ })
607+
608+ mux .HandleFunc ("/" , func (w http.ResponseWriter , r * http.Request ) {
609+ filePath := filepath .Join (distDir , filepath .Clean (r .URL .Path ))
610+
611+ if info , err := os .Stat (filePath ); err == nil && ! info .IsDir () {
612+ http .ServeFile (w , r , filePath )
613+ return
614+ }
615+
616+ indexPath := filepath .Join (distDir , "index.html" )
617+ if _ , err := os .Stat (indexPath ); err != nil {
618+ http .Error (w , "Dashboard UI not installed" , http .StatusNotFound )
619+ return
620+ }
621+ http .ServeFile (w , r , indexPath )
622+ })
623+
624+ s .dashboardServer = & http.Server {
625+ Addr : ":8080" ,
626+ Handler : mux ,
627+ }
628+
629+ log .Printf ("Dashboard server starting on :8080 (serving %s)" , distDir )
630+ if err := s .dashboardServer .ListenAndServe (); err != nil && err != http .ErrServerClosed {
631+ log .Printf ("Dashboard server error: %v" , err )
632+ }
633+ }
634+
567635func (s * Server ) Stop () error {
568636 if s .clusterManager != nil {
569637 s .clusterManager .Stop ()
570638 }
639+ if s .dashboardServer != nil {
640+ dCtx , dCancel := context .WithTimeout (context .Background (), 5 * time .Second )
641+ defer dCancel ()
642+ _ = s .dashboardServer .Shutdown (dCtx )
643+ }
571644 ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Second )
572645 defer cancel ()
573646 return s .server .Shutdown (ctx )
@@ -3769,14 +3842,41 @@ func (s *Server) getSystemStats(c *gin.Context) {
37693842
37703843 systemStats , _ := system .GetSystemStats ()
37713844
3845+ var systemPortCount int
3846+ if ports , err := s .networksManager .ListPorts (); err == nil {
3847+ systemPortCount = len (ports )
3848+ }
3849+
3850+ var systemServiceCount int
3851+ if services , err := s .servicesManager .ListServices (); err == nil {
3852+ systemServiceCount = len (services )
3853+ }
3854+
3855+ var infraCount int
3856+ if services , err := s .infraManager .ListServices (); err == nil {
3857+ infraCount = len (services )
3858+ }
3859+
3860+ var certCount int
3861+ if certs , err := s .proxyOrchestrator .ListCertificates (); err == nil {
3862+ certCount = len (certs )
3863+ }
3864+
3865+ appCount := len (s .pluginRegistry .List ())
3866+
37723867 c .JSON (http .StatusOK , gin.H {
3773- "deployments" : depStats ,
3774- "containers" : containerStats ,
3775- "images" : imageStats ,
3776- "volumes" : volumeStats ,
3777- "networks" : gin.H {"total" : networkCount },
3778- "ports" : gin.H {"total" : portCount },
3779- "system" : systemStats ,
3868+ "deployments" : depStats ,
3869+ "containers" : containerStats ,
3870+ "images" : imageStats ,
3871+ "volumes" : volumeStats ,
3872+ "networks" : gin.H {"total" : networkCount },
3873+ "ports" : gin.H {"total" : portCount },
3874+ "system" : systemStats ,
3875+ "system_ports" : gin.H {"total" : systemPortCount },
3876+ "services" : gin.H {"total" : systemServiceCount },
3877+ "infrastructure" : gin.H {"total" : infraCount },
3878+ "certificates" : gin.H {"total" : certCount },
3879+ "apps" : gin.H {"total" : appCount },
37803880 })
37813881}
37823882
0 commit comments