Skip to content

Commit 0738a0f

Browse files
committed
feat: show updatable projects
1 parent 85397c7 commit 0738a0f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3118
-192
lines changed

.vscode/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@
1111
"prettier.documentSelectors": ["**/*.svelte"],
1212
"tasks.statusbar.default.hide": true,
1313
"tasks.statusbar.limit": 8,
14-
"github.copilot.chat.summarizeAgentConversationHistory.enabled": false
14+
"github.copilot.chat.summarizeAgentConversationHistory.enabled": false,
15+
"snyk.advanced.organization": "a7adead1-b200-490f-91c5-8fa0e410d62c",
16+
"snyk.advanced.autoSelectOrganization": true
1517
}

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.Project, svcs.Settings, svcs.Vulnerability)
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/containers.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type ListContainersInput struct {
4444
GroupBy string `query:"groupBy" doc:"Optional grouping mode (for example: project)"`
4545
IncludeInternal bool `query:"includeInternal" default:"false" doc:"Include internal containers"`
4646
Updates string `query:"updates" doc:"Filter by update status (has_update, up_to_date, error, unknown)"`
47+
Standalone string `query:"standalone" doc:"Filter standalone containers only (true/false)"`
4748
}
4849

4950
type ListContainersOutput struct {
@@ -244,6 +245,9 @@ func (h *ContainerHandler) ListContainers(ctx context.Context, input *ListContai
244245
if input.Updates != "" {
245246
filters["updates"] = input.Updates
246247
}
248+
if input.Standalone != "" {
249+
filters["standalone"] = input.Standalone
250+
}
247251

248252
params := pagination.QueryParams{
249253
SearchQuery: pagination.SearchQuery{Search: input.Search},

backend/internal/huma/handlers/dashboard_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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, nil, settingsSvc, nil),
115115
}
116116

117117
output, err := handler.GetDashboard(context.Background(), &GetDashboardInput{EnvironmentID: "0"})

backend/internal/huma/handlers/image_updates.go

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@ package handlers
33
import (
44
"context"
55
"net/http"
6+
"strings"
67

78
"github.com/danielgtaylor/huma/v2"
89
"github.com/getarcaneapp/arcane/backend/internal/common"
910
"github.com/getarcaneapp/arcane/backend/internal/services"
1011
"github.com/getarcaneapp/arcane/types/base"
12+
imagetypes "github.com/getarcaneapp/arcane/types/image"
1113
"github.com/getarcaneapp/arcane/types/imageupdate"
1214
)
1315

1416
type ImageUpdateHandler struct {
1517
imageUpdateService *services.ImageUpdateService
18+
imageService *services.ImageService
1619
}
1720

1821
type CheckImageUpdateInput struct {
@@ -51,6 +54,15 @@ type CheckAllImagesOutput struct {
5154
Body base.ApiResponse[imageupdate.BatchResponse]
5255
}
5356

57+
type GetUpdateInfoByRefsInput struct {
58+
EnvironmentID string `path:"id" doc:"Environment ID"`
59+
ImageRefs string `query:"imageRefs" doc:"Comma-separated image references"`
60+
}
61+
62+
type GetUpdateInfoByRefsOutput struct {
63+
Body base.ApiResponse[map[string]*imagetypes.UpdateInfo]
64+
}
65+
5466
type GetUpdateSummaryInput struct {
5567
EnvironmentID string `path:"id" doc:"Environment ID"`
5668
}
@@ -60,8 +72,11 @@ type GetUpdateSummaryOutput struct {
6072
}
6173

6274
// RegisterImageUpdates registers image update endpoints.
63-
func RegisterImageUpdates(api huma.API, imageUpdateSvc *services.ImageUpdateService) {
64-
h := &ImageUpdateHandler{imageUpdateService: imageUpdateSvc}
75+
func RegisterImageUpdates(api huma.API, imageUpdateSvc *services.ImageUpdateService, imageSvc *services.ImageService) {
76+
h := &ImageUpdateHandler{
77+
imageUpdateService: imageUpdateSvc,
78+
imageService: imageSvc,
79+
}
6580

6681
huma.Register(api, huma.Operation{
6782
OperationID: "check-image-update",
@@ -108,6 +123,15 @@ func RegisterImageUpdates(api huma.API, imageUpdateSvc *services.ImageUpdateServ
108123
Security: []map[string][]string{{"BearerAuth": {}}, {"ApiKeyAuth": {}}},
109124
}, h.CheckAllImages)
110125

126+
huma.Register(api, huma.Operation{
127+
OperationID: "get-update-info-by-refs",
128+
Method: http.MethodGet,
129+
Path: "/environments/{id}/image-updates/by-refs",
130+
Summary: "Get persisted update info for image references",
131+
Tags: []string{"Image Updates"},
132+
Security: []map[string][]string{{"BearerAuth": {}}, {"ApiKeyAuth": {}}},
133+
}, h.GetUpdateInfoByRefs)
134+
111135
huma.Register(api, huma.Operation{
112136
OperationID: "get-update-summary",
113137
Method: http.MethodGet,
@@ -192,6 +216,30 @@ func (h *ImageUpdateHandler) CheckAllImages(ctx context.Context, input *CheckAll
192216
}, nil
193217
}
194218

219+
func (h *ImageUpdateHandler) GetUpdateInfoByRefs(ctx context.Context, input *GetUpdateInfoByRefsInput) (*GetUpdateInfoByRefsOutput, error) {
220+
imageRefs := parseImageRefsQueryInternal(input.ImageRefs)
221+
if len(imageRefs) == 0 {
222+
return &GetUpdateInfoByRefsOutput{
223+
Body: base.ApiResponse[map[string]*imagetypes.UpdateInfo]{
224+
Success: true,
225+
Data: map[string]*imagetypes.UpdateInfo{},
226+
},
227+
}, nil
228+
}
229+
230+
result, err := h.imageService.GetUpdateInfoByImageRefs(ctx, imageRefs)
231+
if err != nil {
232+
return nil, huma.Error500InternalServerError((&common.BatchImageUpdateCheckError{Err: err}).Error())
233+
}
234+
235+
return &GetUpdateInfoByRefsOutput{
236+
Body: base.ApiResponse[map[string]*imagetypes.UpdateInfo]{
237+
Success: true,
238+
Data: result,
239+
},
240+
}, nil
241+
}
242+
195243
func (h *ImageUpdateHandler) GetUpdateSummary(ctx context.Context, input *GetUpdateSummaryInput) (*GetUpdateSummaryOutput, error) {
196244
summary, err := h.imageUpdateService.GetUpdateSummary(ctx)
197245
if err != nil {
@@ -205,3 +253,26 @@ func (h *ImageUpdateHandler) GetUpdateSummary(ctx context.Context, input *GetUpd
205253
},
206254
}, nil
207255
}
256+
257+
func parseImageRefsQueryInternal(raw string) []string {
258+
if strings.TrimSpace(raw) == "" {
259+
return nil
260+
}
261+
262+
parts := strings.Split(raw, ",")
263+
result := make([]string, 0, len(parts))
264+
seen := make(map[string]struct{}, len(parts))
265+
for _, part := range parts {
266+
ref := strings.TrimSpace(part)
267+
if ref == "" {
268+
continue
269+
}
270+
if _, exists := seen[ref]; exists {
271+
continue
272+
}
273+
seen[ref] = struct{}{}
274+
result = append(result, ref)
275+
}
276+
277+
return result
278+
}

backend/internal/huma/handlers/projects.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type ListProjectsInput struct {
4242
Start int `query:"start" default:"0" doc:"Start index for pagination"`
4343
Limit int `query:"limit" default:"20" doc:"Number of items per page"`
4444
Status string `query:"status" doc:"Filter by status (comma-separated: running,stopped,partially running)"`
45+
Updates string `query:"updates" doc:"Filter by update status (has_update, up_to_date, error, unknown)"`
4546
}
4647

4748
type ListProjectsOutput struct {
@@ -375,6 +376,14 @@ func (h *ProjectHandler) ListProjects(ctx context.Context, input *ListProjectsIn
375376
return nil, huma.Error500InternalServerError("service not available")
376377
}
377378

379+
filters := map[string]string{}
380+
if input.Status != "" {
381+
filters["status"] = input.Status
382+
}
383+
if input.Updates != "" {
384+
filters["updates"] = input.Updates
385+
}
386+
378387
params := pagination.QueryParams{
379388
SearchQuery: pagination.SearchQuery{
380389
Search: input.Search,
@@ -387,9 +396,7 @@ func (h *ProjectHandler) ListProjects(ctx context.Context, input *ListProjectsIn
387396
Start: input.Start,
388397
Limit: input.Limit,
389398
},
390-
Filters: map[string]string{
391-
"status": input.Status,
392-
},
399+
Filters: filters,
393400
}
394401

395402
projects, paginationResp, err := h.projectService.ListProjects(ctx, params)

backend/internal/huma/huma.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ func registerHandlers(api huma.API, svc *Services) {
394394
handlers.RegisterTemplates(api, templateSvc, environmentSvc)
395395
handlers.RegisterImages(api, dockerSvc, imageSvc, imageUpdateSvc, settingsSvc, buildSvc)
396396
handlers.RegisterBuildWorkspaces(api, buildWorkspaceSvc)
397-
handlers.RegisterImageUpdates(api, imageUpdateSvc)
397+
handlers.RegisterImageUpdates(api, imageUpdateSvc, imageSvc)
398398
handlers.RegisterSettings(api, settingsSvc, settingsSearchSvc, environmentSvc, cfg)
399399
handlers.RegisterJobSchedules(api, jobScheduleSvc, environmentSvc)
400400
handlers.RegisterVolumes(api, dockerSvc, volumeSvc)

backend/internal/services/container_service.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,20 @@ func (s *ContainerService) buildContainerFilterAccessors() []pagination.FilterAc
12871287
}
12881288
},
12891289
},
1290+
{
1291+
Key: "standalone",
1292+
Fn: func(c containertypes.Summary, filterValue string) bool {
1293+
isStandalone := strings.TrimSpace(c.Labels["com.docker.compose.project"]) == ""
1294+
switch filterValue {
1295+
case "true", "1":
1296+
return isStandalone
1297+
case "false", "0":
1298+
return !isStandalone
1299+
default:
1300+
return true
1301+
}
1302+
},
1303+
},
12901304
}
12911305
}
12921306

backend/internal/services/container_service_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,24 @@ func TestGroupContainersByProjectUsesNoProjectBucket(t *testing.T) {
8080
require.Equal(t, containerNoProjectGroup, getContainerProjectNameInternal(groups[1].Items[1]))
8181
}
8282

83+
func TestBuildContainerFilterAccessors_FiltersStandaloneContainers(t *testing.T) {
84+
service := &ContainerService{}
85+
items := []containertypes.Summary{
86+
{ID: "standalone", Labels: map[string]string{}},
87+
{ID: "compose", Labels: map[string]string{"com.docker.compose.project": "alpha"}},
88+
}
89+
90+
result := pagination.SearchOrderAndPaginate(
91+
items,
92+
pagination.QueryParams{Filters: map[string]string{"standalone": "true"}},
93+
pagination.Config[containertypes.Summary]{FilterAccessors: service.buildContainerFilterAccessors()},
94+
)
95+
96+
require.Len(t, result.Items, 1)
97+
require.Equal(t, "standalone", result.Items[0].ID)
98+
require.Equal(t, int64(1), result.TotalCount)
99+
}
100+
83101
func TestBuildCleanNetworkingConfigInternalPreservesEndpointSettings(t *testing.T) {
84102
containerInspect := container.InspectResponse{
85103
NetworkSettings: &container.NetworkSettings{

0 commit comments

Comments
 (0)