Skip to content

Commit 8c21173

Browse files
committed
feat: universal environment dashboard
1 parent 6f74709 commit 8c21173

File tree

21 files changed

+1889
-288
lines changed

21 files changed

+1889
-288
lines changed

backend/internal/bootstrap/services_bootstrap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func initializeServices(ctx context.Context, db *database.DB, cfg *config.Config
8383
svcs.BuildWorkspace = services.NewBuildWorkspaceService(svcs.Settings)
8484
svcs.Project = services.NewProjectService(db, svcs.Settings, svcs.Event, svcs.Image, svcs.Docker, svcs.Build, cfg)
8585
svcs.Container = services.NewContainerService(db, svcs.Event, svcs.Docker, svcs.Image, svcs.Settings, svcs.Project)
86-
svcs.Dashboard = services.NewDashboardService(db, svcs.Docker, svcs.Container, svcs.Settings, svcs.Vulnerability)
86+
svcs.Dashboard = services.NewDashboardService(db, svcs.Docker, svcs.Container, svcs.Settings, svcs.Vulnerability, svcs.Environment)
8787
svcs.Volume = services.NewVolumeService(db, svcs.Docker, svcs.Event, svcs.Settings, svcs.Container, svcs.Image, cfg.BackupVolumeName)
8888
svcs.Network = services.NewNetworkService(db, svcs.Docker, svcs.Event)
8989
svcs.Port = services.NewPortService(svcs.Docker)

backend/internal/huma/handlers/dashboard.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ type GetDashboardActionItemsOutput struct {
3232
Body base.ApiResponse[dashboardtypes.ActionItems]
3333
}
3434

35+
type GetDashboardEnvironmentsOverviewInput struct {
36+
DebugAllGood bool `query:"debugAllGood" default:"false" doc:"Debug mode: force empty action item lists"`
37+
}
38+
39+
type GetDashboardEnvironmentsOverviewOutput struct {
40+
Body base.ApiResponse[dashboardtypes.EnvironmentsOverview]
41+
}
42+
3543
func RegisterDashboard(api huma.API, dashboardService *services.DashboardService) {
3644
h := &DashboardHandler{dashboardService: dashboardService}
3745

@@ -60,6 +68,18 @@ func RegisterDashboard(api huma.API, dashboardService *services.DashboardService
6068
{"ApiKeyAuth": {}},
6169
},
6270
}, h.GetActionItems)
71+
72+
huma.Register(api, huma.Operation{
73+
OperationID: "get-dashboard-environments-overview",
74+
Method: http.MethodGet,
75+
Path: "/dashboard/environments",
76+
Summary: "Get aggregate environments dashboard overview",
77+
Description: "Returns dashboard summary data for all visible environments",
78+
Tags: []string{"Dashboard"},
79+
Security: []map[string][]string{
80+
{"BearerAuth": {}},
81+
},
82+
}, h.GetEnvironmentsOverview)
6383
}
6484

6585
func (h *DashboardHandler) GetDashboard(ctx context.Context, input *GetDashboardInput) (*GetDashboardOutput, error) {
@@ -117,3 +137,30 @@ func (h *DashboardHandler) GetActionItems(ctx context.Context, input *GetDashboa
117137
},
118138
}, nil
119139
}
140+
141+
func (h *DashboardHandler) GetEnvironmentsOverview(
142+
ctx context.Context,
143+
input *GetDashboardEnvironmentsOverviewInput,
144+
) (*GetDashboardEnvironmentsOverviewOutput, error) {
145+
if h.dashboardService == nil {
146+
return nil, huma.Error500InternalServerError("service not available")
147+
}
148+
149+
overview, err := h.dashboardService.GetEnvironmentsOverview(ctx, services.DashboardActionItemsOptions{
150+
DebugAllGood: input.DebugAllGood,
151+
})
152+
if err != nil {
153+
return nil, huma.Error500InternalServerError(err.Error())
154+
}
155+
156+
if overview == nil {
157+
return nil, huma.Error500InternalServerError("dashboard environments overview not available")
158+
}
159+
160+
return &GetDashboardEnvironmentsOverviewOutput{
161+
Body: base.ApiResponse[dashboardtypes.EnvironmentsOverview]{
162+
Success: true,
163+
Data: *overview,
164+
},
165+
}, nil
166+
}

backend/internal/huma/handlers/dashboard_test.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func setupDashboardHandlerTestDB(t *testing.T) (*database.DB, *services.Settings
2626

2727
db, err := gorm.Open(glsqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
2828
require.NoError(t, err)
29-
require.NoError(t, db.AutoMigrate(&models.ApiKey{}, &models.ImageUpdateRecord{}, &models.Project{}, &models.SettingVariable{}))
29+
require.NoError(t, db.AutoMigrate(&models.ApiKey{}, &models.Environment{}, &models.ImageUpdateRecord{}, &models.Project{}, &models.SettingVariable{}))
3030

3131
databaseDB := &database.DB{DB: db}
3232
settingsSvc, err := services.NewSettingsService(context.Background(), databaseDB)
@@ -111,7 +111,7 @@ func TestDashboardHandlerGetDashboardReturnsSnapshot(t *testing.T) {
111111

112112
dockerSvc := newDashboardHandlerTestDockerService(t, settingsSvc, containers, images)
113113
handler := &DashboardHandler{
114-
dashboardService: services.NewDashboardService(db, dockerSvc, nil, settingsSvc, nil),
114+
dashboardService: services.NewDashboardService(db, dockerSvc, nil, settingsSvc, nil, nil),
115115
}
116116

117117
output, err := handler.GetDashboard(context.Background(), &GetDashboardInput{EnvironmentID: "0"})
@@ -131,3 +131,28 @@ func TestDashboardHandlerGetDashboardReturnsSnapshot(t *testing.T) {
131131
{Kind: dashboardtypes.ActionItemKindExpiringKeys, Count: 1, Severity: dashboardtypes.ActionItemSeverityWarning},
132132
}, snapshot.ActionItems.Items)
133133
}
134+
135+
func TestDashboardHandlerGetEnvironmentsOverviewReturnsAggregateSummary(t *testing.T) {
136+
db, settingsSvc := setupDashboardHandlerTestDB(t)
137+
138+
require.NoError(t, db.WithContext(context.Background()).Create(&models.Environment{
139+
BaseModel: models.BaseModel{ID: "0", CreatedAt: time.Now()},
140+
Name: "Local Docker",
141+
ApiUrl: "http://local.test",
142+
Status: string(models.EnvironmentStatusOffline),
143+
Enabled: true,
144+
}).Error)
145+
146+
handler := &DashboardHandler{
147+
dashboardService: services.NewDashboardService(db, nil, nil, settingsSvc, nil, services.NewEnvironmentService(db, http.DefaultClient, nil, nil, settingsSvc, nil)),
148+
}
149+
150+
output, err := handler.GetEnvironmentsOverview(context.Background(), &GetDashboardEnvironmentsOverviewInput{})
151+
require.NoError(t, err)
152+
require.NotNil(t, output)
153+
require.True(t, output.Body.Success)
154+
require.Equal(t, 1, output.Body.Data.Summary.TotalEnvironments)
155+
require.Len(t, output.Body.Data.Environments, 1)
156+
require.Equal(t, "0", output.Body.Data.Environments[0].Environment.ID)
157+
require.Equal(t, dashboardtypes.EnvironmentSnapshotStateSkipped, output.Body.Data.Environments[0].SnapshotState)
158+
}

backend/internal/huma/handlers/environments.go

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -618,43 +618,7 @@ func (h *EnvironmentHandler) UpdateEnvironment(ctx context.Context, input *Updat
618618
}
619619

620620
func (h *EnvironmentHandler) applyEdgeRuntimeState(env *environment.Environment) {
621-
if env == nil || !env.IsEdge {
622-
return
623-
}
624-
625-
connected := false
626-
env.Connected = &connected
627-
env.ConnectedAt = nil
628-
env.LastHeartbeat = nil
629-
env.LastPollAt = nil
630-
env.EdgeTransport = nil
631-
632-
if pollState, ok := edge.GetPollRuntimeRegistry().Get(env.ID, time.Now()); ok {
633-
env.LastPollAt = pollState.LastPollAt
634-
}
635-
636-
if runtimeState, ok := edge.GetTunnelRuntimeState(env.ID); ok {
637-
connected = true
638-
env.Connected = &connected
639-
env.Status = string(models.EnvironmentStatusOnline)
640-
env.ConnectedAt = runtimeState.ConnectedAt
641-
env.LastHeartbeat = runtimeState.LastHeartbeat
642-
if transport, ok := edge.GetActiveTunnelTransport(env.ID); ok {
643-
env.EdgeTransport = &transport
644-
} else if runtimeState.Transport != "" {
645-
env.EdgeTransport = &runtimeState.Transport
646-
}
647-
return
648-
}
649-
650-
if env.LastPollAt != nil {
651-
env.Status = string(models.EnvironmentStatusStandby)
652-
return
653-
}
654-
655-
if env.Status != string(models.EnvironmentStatusPending) {
656-
env.Status = string(models.EnvironmentStatusOffline)
657-
}
621+
services.ApplyEnvironmentRuntimeState(env)
658622
}
659623

660624
// DeleteEnvironment deletes an environment.

0 commit comments

Comments
 (0)