0.1.1 96c369e

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	*types.ImageSummary
54	Name       string
55	ShortID    string
56	CreatedStr string
57	SizeStr    string
58}
59
60type HumanReadableContainer struct {
61	*types.Container
62	Name       string
63	Ports      []string
64	ShortID    string
65	CreatedStr string
66	SizeStr    string
67	UpSince    string
68	StateColor string
69}
70
71// renderHTML is a generalized function to render HTML
72func renderHTML(c echo.Context, a *server.App, path, templateName string, data map[string]interface{}) error {
73	rendererData, err := render.GetHTMLRenderer(path, templateName, a.TemplateFS, a)
74	if err != nil {
75		return err
76	}
77	renderedHTML, err := rendererData.Render(data, a)
78	if err != nil {
79		return err
80	}
81	return c.HTML(http.StatusOK, renderedHTML)
82}
83
84// ActionSuccess returns the success HTML fragment
85func ActionSuccess(a *server.App) string {
86	successFragment, err := load.Fragment(a, "success")
87	if err != nil {
88		fmt.Println(err)
89		return ""
90	}
91
92	return successFragment
93}
94
95// ImageManagerComponent handles the /image-manager route (HTMX route)
96func ImageManagerComponent(c echo.Context, a *server.App) error {
97	images, err := docker.ListContainerImages()
98	if err != nil {
99		return sendError(c, err)
100	}
101	var humanReadableImages []HumanReadableContainerImage
102
103	for _, image := range images {
104		// We need to shorten the ID because it breaks the HTML
105		ShortID := image.ID[7:19]
106		// And we store both the ShortID and the full ID in a map so we can retrieve the full ID later
107		safelyInteractWithIDMap(Update, ShortID, image.ID)
108		createdTime := time.Unix(image.Created, 0)
109		createdStr := humanize.TimeAgo(createdTime)
110		sizeStr := humanize.BytesToReadableSize(image.Size)
111		for _, repoTag := range image.RepoTags {
112			humanReadableImages = append(humanReadableImages, HumanReadableContainerImage{
113				ImageSummary: &types.ImageSummary{
114					ID: ShortID, // Set the ID to the ShortID
115				},
116				CreatedStr: createdStr,
117				SizeStr:    sizeStr,
118				Name:       repoTag,
119			})
120		}
121	}
122	data := map[string]interface{}{
123		"Images": humanReadableImages,
124	}
125
126	return renderHTML(c, a, "html/fragments", "imagelist.gohtml", data)
127}
128
129// ImageManagerDelete handles the /image-manager/delete route
130func ImageManagerDelete(c echo.Context, a *server.App) error {
131	//
132	ShortID := c.Param("ID")
133
134	imageID, exists := safelyInteractWithIDMap(Fetch, ShortID)
135
136	if !exists {
137		return c.String(http.StatusBadRequest, "Invalid ShortImgID")
138	}
139
140	err := docker.DeleteContainerImage(imageID)
141	if err != nil {
142		c.Response().Header().Set("X-Error-Type", "image")
143		return c.String(http.StatusInternalServerError, err.Error())
144	}
145
146	safelyInteractWithIDMap(Delete, ShortID)
147
148	return c.String(http.StatusOK, ActionSuccess(a))
149}
150
151// ContainerManagerComponent handles the /container-manager route
152func ContainerManagerComponent(c echo.Context, a *server.App) error {
153	containers, err := docker.ListRunningContainers()
154	if err != nil {
155		return sendError(c, err)
156	}
157
158	var humanReadableContainers []HumanReadableContainer
159
160	for _, container := range containers {
161		localContainer := container // Make a local copy
162		sizeStr := humanize.BytesToReadableSize(container.SizeRw)
163		stateColor := "green"
164
165		if container.State == "exited" {
166			stateColor = "red"
167		}
168		var ports []string
169		for _, port := range container.Ports {
170			portStr := fmt.Sprintf("%d:%d", port.PublicPort, port.PrivatePort)
171			ports = append(ports, portStr)
172		}
173
174		for _, name := range container.Names {
175			name = name[1:]
176			humanReadableContainers = append(humanReadableContainers, HumanReadableContainer{
177				Container:  &localContainer,
178				SizeStr:    sizeStr,
179				UpSince:    humanize.TimeAgo(time.Unix(container.Created, 0)),
180				StateColor: stateColor,
181				Name:       name,
182				Ports:      ports,
183			})
184		}
185	}
186	yamlData, err := GetLocalizedData(c, a)
187	if err != nil {
188		return err
189	}
190	data := map[string]interface{}{
191		"Lang":       yamlData["Lang"],
192		"containers": humanReadableContainers,
193	}
194
195	return renderHTML(c, a, "html/fragments", "containerlist.gohtml", data)
196}
197
198// ContainerManagerDelete handles the /container-manager/delete route
199func ContainerManagerDelete(c echo.Context, a *server.App) error {
200	err := docker.RemoveContainer(c.Param("ID"))
201	if err != nil {
202		return sendError(c, err)
203	}
204	return c.HTML(http.StatusOK, ActionSuccess(a))
205}
206
207// ContainerManagerStop handles the /container-manager/stop route
208func ContainerManagerStop(c echo.Context, a *server.App) error {
209	// Stop the container gracefully with a timeout
210	stopped, err := docker.StopContainerGracefully(c.Param("ID"), 3*time.Second)
211	if err != nil {
212		return sendError(c, err)
213	}
214	// if stopped is true, the container was stopped gracefully
215	if stopped {
216		return c.HTML(http.StatusOK, ActionSuccess(a))
217	}
218	// If stopped is false, the container was not stopped gracefully we force stop it
219	err = docker.StopContainerRagefully(c.Param("ID"))
220	if err != nil {
221		return sendError(c, err)
222	}
223
224	return c.HTML(http.StatusOK, ActionSuccess(a))
225}
226
227// ContainerManagerStart handles the /container-manager/start route
228func ContainerManagerStart(c echo.Context, a *server.App) error {
229	err := docker.StartContainer(c.Param("ID"))
230	if err != nil {
231		return sendError(c, err)
232	}
233	return c.HTML(http.StatusOK, ActionSuccess(a))
234}

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