feat: show local Docker images on project detail page
- Add GET /api/projects/{id}/images endpoint returning local images matching the project
- Add ListImagesByRef with tag, size, and created timestamp to Docker client
- Display images table on project page with tag, ID (truncated), size (MB), and created date
- Only shown when Docker is available and images exist locally
This commit is contained in:
@@ -1,10 +1,46 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/alexei/docker-watcher/internal/store"
|
||||
)
|
||||
|
||||
// listProjectImages handles GET /api/projects/{id}/images.
|
||||
// Returns all local Docker images matching the project's image reference.
|
||||
func (s *Server) listProjectImages(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
|
||||
project, err := s.store.GetProjectByID(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
respondNotFound(w, "project")
|
||||
return
|
||||
}
|
||||
slog.Error("failed to get project", "error", err)
|
||||
respondError(w, http.StatusInternalServerError, "internal server error")
|
||||
return
|
||||
}
|
||||
|
||||
if s.docker == nil || project.Image == "" {
|
||||
respondJSON(w, http.StatusOK, []any{})
|
||||
return
|
||||
}
|
||||
|
||||
images, err := s.docker.ListImagesByRef(r.Context(), project.Image)
|
||||
if err != nil {
|
||||
slog.Warn("list project images", "project", project.Name, "error", err)
|
||||
respondJSON(w, http.StatusOK, []any{})
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, images)
|
||||
}
|
||||
|
||||
// pruneImages handles POST /api/docker/prune-images.
|
||||
// Only removes images that belong to Docker Watcher projects (not all system images).
|
||||
func (s *Server) pruneImages(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -204,6 +204,7 @@ func (s *Server) Router() chi.Router {
|
||||
r.Get("/stages/{stage}/env", s.listStageEnv)
|
||||
r.Get("/stages/{stage}/instances", s.listInstances)
|
||||
r.Get("/stages/{stage}/instances/{iid}/stats", s.getInstanceStats)
|
||||
r.Get("/images", s.listProjectImages)
|
||||
r.Get("/volumes", s.listVolumes)
|
||||
r.Get("/volumes/{volId}/browse", s.browseVolume)
|
||||
r.Get("/volumes/{volId}/download", s.downloadVolume)
|
||||
|
||||
@@ -115,9 +115,11 @@ func EncodeRegistryAuth(username, password, serverAddress string) (string, error
|
||||
|
||||
// LocalImage represents a Docker image on the local machine.
|
||||
type LocalImage struct {
|
||||
ID string `json:"id"`
|
||||
Ref string `json:"ref"` // e.g., "registry/org/app:tag"
|
||||
Size int64 `json:"size"` // bytes
|
||||
ID string `json:"id"`
|
||||
Ref string `json:"ref"` // e.g., "registry/org/app:tag"
|
||||
Tag string `json:"tag"` // just the tag part
|
||||
Size int64 `json:"size"` // bytes
|
||||
Created int64 `json:"created"` // unix timestamp
|
||||
}
|
||||
|
||||
// ListImagesByRef returns all local images matching a given image reference prefix.
|
||||
@@ -132,10 +134,16 @@ func (c *Client) ListImagesByRef(ctx context.Context, imageBase string) ([]Local
|
||||
for _, img := range result.Items {
|
||||
for _, tag := range img.RepoTags {
|
||||
if strings.HasPrefix(tag, imageBase+":") || tag == imageBase {
|
||||
tagPart := ""
|
||||
if idx := strings.LastIndex(tag, ":"); idx != -1 {
|
||||
tagPart = tag[idx+1:]
|
||||
}
|
||||
images = append(images, LocalImage{
|
||||
ID: img.ID,
|
||||
Ref: tag,
|
||||
Size: img.Size,
|
||||
ID: img.ID,
|
||||
Ref: tag,
|
||||
Tag: tagPart,
|
||||
Size: img.Size,
|
||||
Created: img.Created,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user