0.1.5 b849f6c

containers.go RawHistoryFolder

1package handler
2
3import (
4	"fmt"
5	"net/http"
6	"sync"
7	"time"
8
9	"github.com/bnema/gordon/internal/server"
10	"github.com/bnema/gordon/internal/templating/load"
11	"github.com/bnema/gordon/internal/templating/render"
12	"github.com/bnema/gordon/pkg/docker"
13	"github.com/bnema/gordon/pkg/humanize"
14	"github.com/docker/docker/api/types"
15	"github.com/labstack/echo/v4"
16)
17
18type MapOperation string
19
20var (
21	idMap      = make(map[string]string)
22	idMapMutex sync.Mutex
23)
24
25const (
26	Fetch  MapOperation = "Fetch"
27	Update MapOperation = "Update"
28	Delete MapOperation = "Delete"
29)
30
31func safelyInteractWithIDMap(op MapOperation, key string, value ...string) (string, bool) {
32	idMapMutex.Lock()
33	defer idMapMutex.Unlock()
34
35	switch op {
36	case Fetch:
37		val, exists := idMap[key]
38		return val, exists
39	case Update:
40		if len(value) > 0 {
41			idMap[key] = value[0]
42		}
43		return "", true
44	case Delete:
45		delete(idMap, key)
46		return "", true
47	default:
48		return "", false
49	}
50}
51
52type HumanReadableContainerImage struct {
53	ID          string
54	Name        string
55	ShortID     string
56	CreatedStr  string
57	SizeStr     string
58	RepoDigests []string
59	RepoTags    []string
60}
61
62type HumanReadableContainer struct {
63	*types.Container
64	Name       string
65	Ports      []string
66	ShortID    string
67	CreatedStr string
68	SizeStr    string
69	UpSince    string
70	StateColor string
71}
72
73// renderHTML is a generalized function to render HTML
74func renderHTML(c echo.Context, a *server.App, path, templateName string, data map[string]interface{}) error {
75	rendererData, err := render.GetHTMLRenderer(path, templateName, a.TemplateFS, a)
76	if err != nil {
77		return err
78	}
79	renderedHTML, err := rendererData.Render(data, a)
80	if err != nil {
81		return err
82	}
83	return c.HTML(http.StatusOK, renderedHTML)
84}
85
86// ActionSuccess returns the success HTML fragment
87func ActionSuccess(a *server.App) string {
88	successFragment, err := load.Fragment(a, "success")
89	if err != nil {
90		fmt.Println(err)
91		return ""
92	}
93
94	return successFragment
95}
96
97// ImageManagerComponent handles the /image-manager route (HTMX route)
98func ImageManagerComponent(c echo.Context, a *server.App) error {
99	images, err := docker.ListContainerImages()
100	if err != nil {
101		return sendError(c, err)
102	}
103
104	var humanReadableImages []HumanReadableContainerImage
105
106	for _, img := range images {
107		shortID := img.ID[7:19]
108		safelyInteractWithIDMap(Update, shortID, img.ID)
109		createdTime := time.Unix(img.Created, 0)
110		createdStr := humanize.TimeAgo(createdTime)
111		sizeStr := humanize.BytesToReadableSize(img.Size)
112
113		for _, repoTag := range img.RepoTags {
114			humanReadableImages = append(humanReadableImages, HumanReadableContainerImage{
115				ID:          img.ID,
116				ShortID:     shortID,
117				CreatedStr:  createdStr,
118				SizeStr:     sizeStr,
119				Name:        repoTag,
120				RepoDigests: img.RepoDigests,
121				RepoTags:    img.RepoTags,
122			})
123		}
124	}
125
126	data := map[string]interface{}{
127		"Images": humanReadableImages,
128	}
129
130	return renderHTML(c, a, "html/fragments", "imagelist.gohtml", data)
131}
132
133// ImageManagerDelete handles the /image-manager/delete route
134func ImageManagerDelete(c echo.Context, a *server.App) error {
135	ShortID := c.Param("ID")
136
137	imageID, exists := safelyInteractWithIDMap(Fetch, ShortID)
138	if !exists {
139		return c.String(http.StatusBadRequest, "Invalid ShortImgID")
140	}
141
142	err := docker.DeleteContainerImage(imageID)
143	if err != nil {
144		c.Response().Header().Set("X-Error-Type", "image")
145		return c.String(http.StatusInternalServerError, err.Error())
146	}
147
148	safelyInteractWithIDMap(Delete, ShortID)
149
150	return c.String(http.StatusOK, ActionSuccess(a))
151}
152
153// ContainerManagerComponent handles the /container-manager route
154func ContainerManagerComponent(c echo.Context, a *server.App) error {
155	containers, err := docker.ListRunningContainers()
156	if err != nil {
157		return sendError(c, err)
158	}
159
160	var humanReadableContainers []HumanReadableContainer
161
162	for _, container := range containers {
163		localContainer := container // Make a local copy
164		sizeStr := humanize.BytesToReadableSize(container.SizeRw)
165		stateColor := "green"
166
167		if container.State == "exited" {
168			stateColor = "red"
169		}
170		var ports []string
171		for _, port := range container.Ports {
172			portStr := fmt.Sprintf("%d:%d", port.PublicPort, port.PrivatePort)
173			ports = append(ports, portStr)
174		}
175
176		for _, name := range container.Names {
177			name = name[1:]
178			humanReadableContainers = append(humanReadableContainers, HumanReadableContainer{
179				Container:  &localContainer,
180				SizeStr:    sizeStr,
181				UpSince:    humanize.TimeAgo(time.Unix(container.Created, 0)),
182				StateColor: stateColor,
183				Name:       name,
184				Ports:      ports,
185			})
186		}
187	}
188	yamlData, err := GetLocalizedData(c, a)
189	if err != nil {
190		return err
191	}
192	data := map[string]interface{}{
193		"Lang":       yamlData["Lang"],
194		"containers": humanReadableContainers,
195	}
196
197	return renderHTML(c, a, "html/fragments", "containerlist.gohtml", data)
198}
199
200// ContainerManagerDelete handles the /container-manager/delete route
201func ContainerManagerDelete(c echo.Context, a *server.App) error {
202	err := docker.RemoveContainer(c.Param("ID"))
203	if err != nil {
204		return sendError(c, err)
205	}
206	return c.HTML(http.StatusOK, ActionSuccess(a))
207}
208
209// ContainerManagerStop handles the /container-manager/stop route
210func ContainerManagerStop(c echo.Context, a *server.App) error {
211	// Stop the container gracefully with a timeout
212	stopped, err := docker.StopContainerGracefully(c.Param("ID"), 3*time.Second)
213	if err != nil {
214		return sendError(c, err)
215	}
216	// if stopped is true, the container was stopped gracefully
217	if stopped {
218		return c.HTML(http.StatusOK, ActionSuccess(a))
219	}
220	// If stopped is false, the container was not stopped gracefully we force stop it
221	err = docker.StopContainerRagefully(c.Param("ID"))
222	if err != nil {
223		return sendError(c, err)
224	}
225
226	return c.HTML(http.StatusOK, ActionSuccess(a))
227}
228
229// ContainerManagerStart handles the /container-manager/start route
230func ContainerManagerStart(c echo.Context, a *server.App) error {
231	err := docker.StartContainer(c.Param("ID"))
232	if err != nil {
233		return sendError(c, err)
234	}
235	return c.HTML(http.StatusOK, ActionSuccess(a))
236}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close