detached c410e03

For each listed images add a deploy button. Pops up a basic deploy form with inputs

commit
c410e035cd84e6af35a09978d62bc1007cff56f0
author
bnema · · 2023-09-25 05:32 UTC
committer
bnema · · 2023-09-25 05:33 UTC
parent
8c01942
signature
unsigned
12 files · +248 -32
M internal/app/app.go +1 -0
@@ -56,6 +56,7 @@ type ContainerEngineConfig struct {
5656 Sock string `yaml:"dockersock"`
5757 PodmanSock string `yaml:"podmansock"`
5858 Podman bool `yaml:"podman"`
59+ Network string `yaml:"network"`
5960 }
6061
6162 func InitializeEnvironment() {
@@ -56,6 +56,7 @@ type ContainerEngineConfig struct {
56 Sock string `yaml:"dockersock"`56 Sock string `yaml:"dockersock"`
57 PodmanSock string `yaml:"podmansock"`57 PodmanSock string `yaml:"podmansock"`
58 Podman bool `yaml:"podman"`58 Podman bool `yaml:"podman"`
59+ Network string `yaml:"network"`
59 }60 }
60 61
61 func InitializeEnvironment() {62 func InitializeEnvironment() {
M internal/gotemplate/models/html/admin/manager.gohtml +1 -1
@@ -9,7 +9,7 @@
99 <div class="flex-1 flex flex-col space-y-4 p-4 md:ml-4">
1010 <div class="flex flex-col md:flex-row w-full flex-grow md:mt-24">
1111 <div class="flex-grow bg-white p-4 rounded-md shadow mb-4">
12- <h1 class="text-lg md:text-2xl font-bold">Container Manager</h1>
12+ <h1 class="text-lg md:text-2xl font-bold">Container Engine Manager</h1>
1313 <!-- List all the containers -->
1414 <div
1515 id="container-manager"
@@ -9,7 +9,7 @@
9 <div class="flex-1 flex flex-col space-y-4 p-4 md:ml-4">9 <div class="flex-1 flex flex-col space-y-4 p-4 md:ml-4">
10 <div class="flex flex-col md:flex-row w-full flex-grow md:mt-24">10 <div class="flex flex-col md:flex-row w-full flex-grow md:mt-24">
11 <div class="flex-grow bg-white p-4 rounded-md shadow mb-4">11 <div class="flex-grow bg-white p-4 rounded-md shadow mb-4">
12- <h1 class="text-lg md:text-2xl font-bold">Container Manager</h1>12+ <h1 class="text-lg md:text-2xl font-bold">Container Engine Manager</h1>
13 <!-- List all the containers -->13 <!-- List all the containers -->
14 <div14 <div
15 id="container-manager"15 id="container-manager"
M internal/gotemplate/models/html/fragments/containerlist.gohtml +2 -2
@@ -1,5 +1,5 @@
11 <div class="flex flex-col p-4">
2- <h1 class="text-lg md:text-xl font-bold">Running containers</h1>
2+ <h1 class="text-lg md:text-xl font-bold">Containers</h1>
33 <table class="p-4 border-t-2" hx-ext="response-targets">
44 <!-- <thead>
55 <tr>
@@ -73,7 +73,7 @@
7373 </tr>
7474 {{ else }}
7575 <tr>
76- <td colspan="7" class="mt-2">No Running containers.</td>
76+ <td colspan="7" class="mt-2">No available containers.</td>
7777 </tr>
7878 {{
7979 end
@@ -1,5 +1,5 @@
1 <div class="flex flex-col p-4">1 <div class="flex flex-col p-4">
2- <h1 class="text-lg md:text-xl font-bold">Running containers</h1>2+ <h1 class="text-lg md:text-xl font-bold">Containers</h1>
3 <table class="p-4 border-t-2" hx-ext="response-targets">3 <table class="p-4 border-t-2" hx-ext="response-targets">
4 <!-- <thead>4 <!-- <thead>
5 <tr>5 <tr>
@@ -73,7 +73,7 @@
73 </tr>73 </tr>
74 {{ else }}74 {{ else }}
75 <tr>75 <tr>
76- <td colspan="7" class="mt-2">No Running containers.</td>76+ <td colspan="7" class="mt-2">No available containers.</td>
77 </tr>77 </tr>
78 {{78 {{
79 end79 end
A internal/gotemplate/models/html/fragments/createcontainer.gohtml +82 -0
@@ -0,0 +1,82 @@
1+<div
2+ class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"
3+>
4+ <!-- Left Side: Docker Run Options -->
5+ <div class="flex-grow bg-slate-100 p-4 rounded-md">
6+ <h2 class="font-bold pb-4">Deploy a container based on {{.ImageName}}</h2>
7+ {{if .ErrorMessage}}
8+ <div class="text-red-500">{{.ErrorMessage}}</div>
9+ {{ end }}
10+ <form
11+ action="/create-container"
12+ method="post"
13+ hx-post="/htmx/create-container"
14+ hx-swap="innerHTML"
15+ >
16+ <!-- Container Name -->
17+ <div class="mb-4">
18+ <label class="block">Container name</label>
19+ <input
20+ type="text"
21+ name="container_name"
22+ class="w-full p-4 rounded-md"
23+ value="{{.ContainerName}}"
24+ />
25+ </div>
26+ <!-- Domain/Subdomain -->
27+ <div class="mb-4">
28+ <label class="block">Domain/Subdomain</label>
29+ <input
30+ type="text"
31+ name="container_name"
32+ class="w-full p-4 rounded-md"
33+ value="{{.ContainerDomain}}"
34+ />
35+ </div>
36+
37+ <!-- Image Name -->
38+ <div class="mb-4">
39+ <label class="block">Image name</label>
40+ <input
41+ type="text"
42+ name="image_name"
43+ class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
44+ value="{{.ImageName}}"
45+ readonly
46+ />
47+ </div>
48+
49+ <!-- Ports -->
50+ <div class="mb-4">
51+ <label class="block">Ports</label>
52+ <span class="text-gray-500"
53+ >e.g. 80:80 (left is host, right is container)</span
54+ >
55+ <input
56+ type="text"
57+ name="ports"
58+ class="w-full p-4 rounded-md"
59+ value="{{.Ports}}"
60+ />
61+ </div>
62+
63+ <!-- Volumes -->
64+ <div class="mb-4">
65+ <label class="block">Volumes</label>
66+ <span class="text-gray-500">e.g. ./data:/container/data </span>
67+ <input
68+ type="text"
69+ name="volumes"
70+ class="w-full p-4 rounded-md"
71+ value="{{.Volumes}}"
72+ />
73+ </div>
74+
75+ <input
76+ type="submit"
77+ value="Create"
78+ class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
79+ />
80+ </form>
81+ </div>
82+</div>
@@ -0,0 +1,82 @@
1+<div
2+ class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"
3+>
4+ <!-- Left Side: Docker Run Options -->
5+ <div class="flex-grow bg-slate-100 p-4 rounded-md">
6+ <h2 class="font-bold pb-4">Deploy a container based on {{.ImageName}}</h2>
7+ {{if .ErrorMessage}}
8+ <div class="text-red-500">{{.ErrorMessage}}</div>
9+ {{ end }}
10+ <form
11+ action="/create-container"
12+ method="post"
13+ hx-post="/htmx/create-container"
14+ hx-swap="innerHTML"
15+ >
16+ <!-- Container Name -->
17+ <div class="mb-4">
18+ <label class="block">Container name</label>
19+ <input
20+ type="text"
21+ name="container_name"
22+ class="w-full p-4 rounded-md"
23+ value="{{.ContainerName}}"
24+ />
25+ </div>
26+ <!-- Domain/Subdomain -->
27+ <div class="mb-4">
28+ <label class="block">Domain/Subdomain</label>
29+ <input
30+ type="text"
31+ name="container_name"
32+ class="w-full p-4 rounded-md"
33+ value="{{.ContainerDomain}}"
34+ />
35+ </div>
36+
37+ <!-- Image Name -->
38+ <div class="mb-4">
39+ <label class="block">Image name</label>
40+ <input
41+ type="text"
42+ name="image_name"
43+ class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
44+ value="{{.ImageName}}"
45+ readonly
46+ />
47+ </div>
48+
49+ <!-- Ports -->
50+ <div class="mb-4">
51+ <label class="block">Ports</label>
52+ <span class="text-gray-500"
53+ >e.g. 80:80 (left is host, right is container)</span
54+ >
55+ <input
56+ type="text"
57+ name="ports"
58+ class="w-full p-4 rounded-md"
59+ value="{{.Ports}}"
60+ />
61+ </div>
62+
63+ <!-- Volumes -->
64+ <div class="mb-4">
65+ <label class="block">Volumes</label>
66+ <span class="text-gray-500">e.g. ./data:/container/data </span>
67+ <input
68+ type="text"
69+ name="volumes"
70+ class="w-full p-4 rounded-md"
71+ value="{{.Volumes}}"
72+ />
73+ </div>
74+
75+ <input
76+ type="submit"
77+ value="Create"
78+ class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
79+ />
80+ </form>
81+ </div>
82+</div>
M internal/gotemplate/models/html/fragments/imagelist.gohtml +14 -1
@@ -35,6 +35,18 @@
3535 </td>
3636 <td>{{.SizeStr}}</td>
3737 <td align="right">
38+
39+ <button
40+ id="add-button-img-{{.ID}}"
41+ hx-get="/htmx/create-container/{{.ID}}"
42+ hx-target="#create-container"
43+ hx-target-*="#error-message-div-{{.ID}}"
44+ hx-swap="outerHTML"
45+ class="py-2 px-4"
46+ >
47+ <span title="Deploy a container using this image" class="iconoir-add-circle text-xl text-blue-800 font-bold"></span>
48+ </button>
49+
3850 <button
3951 id="remove-button-img-{{.ID}}"
4052 hx-delete="/htmx/image-manager/delete/{{.ID}}"
@@ -44,7 +56,7 @@
4456 hx-confirm="Are you sure you want to delete this container image?"
4557 class="py-2 px-4"
4658 >
47- <span class="iconoir-trash text-xl text-red-600 font-bold"></span>
59+ <span title="Remove this image" class="iconoir-trash text-xl text-red-600 font-bold"></span>
4860 </button>
4961 </td>
5062 </tr>
@@ -63,4 +75,5 @@
6375 </tbody>
6476 </table>
6577 <div id="upload-image"></div>
78+ <div id="create-container"></div>
6679 </div>
@@ -35,6 +35,18 @@
35 </td>35 </td>
36 <td>{{.SizeStr}}</td>36 <td>{{.SizeStr}}</td>
37 <td align="right">37 <td align="right">
38+
39+ <button
40+ id="add-button-img-{{.ID}}"
41+ hx-get="/htmx/create-container/{{.ID}}"
42+ hx-target="#create-container"
43+ hx-target-*="#error-message-div-{{.ID}}"
44+ hx-swap="outerHTML"
45+ class="py-2 px-4"
46+ >
47+ <span title="Deploy a container using this image" class="iconoir-add-circle text-xl text-blue-800 font-bold"></span>
48+ </button>
49+
38 <button50 <button
39 id="remove-button-img-{{.ID}}"51 id="remove-button-img-{{.ID}}"
40 hx-delete="/htmx/image-manager/delete/{{.ID}}"52 hx-delete="/htmx/image-manager/delete/{{.ID}}"
@@ -44,7 +56,7 @@
44 hx-confirm="Are you sure you want to delete this container image?"56 hx-confirm="Are you sure you want to delete this container image?"
45 class="py-2 px-4"57 class="py-2 px-4"
46 >58 >
47- <span class="iconoir-trash text-xl text-red-600 font-bold"></span>59+ <span title="Remove this image" class="iconoir-trash text-xl text-red-600 font-bold"></span>
48 </button>60 </button>
49 </td>61 </td>
50 </tr>62 </tr>
@@ -63,4 +75,5 @@
63 </tbody>75 </tbody>
64 </table>76 </table>
65 <div id="upload-image"></div>77 <div id="upload-image"></div>
78+ <div id="create-container"></div>
66 </div>79 </div>
M internal/gotemplate/models/html/fragments/uploadimage.gohtml +3 -3
@@ -1,10 +1,10 @@
1-<!-- Form to upload a container image (.tar.gz format) -->
1+<!-- Form to upload a container image (.tar format) -->
22 <div
33 class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"
44 hx-ext="response-targets"
55 hx-boost="true"
66 >
7- <div class="flex-grow bg-white p-4 rounded-md shadow mb-4">
7+ <div class="flex-grow bg-slate-100 p-4 rounded-md">
88 <h2>Upload Image Containers</h2>
99 <!-- Upload Form -->
1010 <form
@@ -17,7 +17,7 @@
1717 hx-target-*="#error-message-div"
1818 >
1919 <div
20- class="upload-zone w-full h-44 bg-gray-300 rounded-md shadow mb-4"
20+ class="upload-zone w-full h-44 bg-slate-200 rounded-md p-4"
2121 hx-drop="true"
2222 >
2323 Drag & Drop your file here or
@@ -1,10 +1,10 @@
1-<!-- Form to upload a container image (.tar.gz format) -->1+<!-- Form to upload a container image (.tar format) -->
2 <div2 <div
3 class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"3 class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"
4 hx-ext="response-targets"4 hx-ext="response-targets"
5 hx-boost="true"5 hx-boost="true"
6 >6 >
7- <div class="flex-grow bg-white p-4 rounded-md shadow mb-4">7+ <div class="flex-grow bg-slate-100 p-4 rounded-md">
8 <h2>Upload Image Containers</h2>8 <h2>Upload Image Containers</h2>
9 <!-- Upload Form -->9 <!-- Upload Form -->
10 <form10 <form
@@ -17,7 +17,7 @@
17 hx-target-*="#error-message-div"17 hx-target-*="#error-message-div"
18 >18 >
19 <div19 <div
20- class="upload-zone w-full h-44 bg-gray-300 rounded-md shadow mb-4"20+ class="upload-zone w-full h-44 bg-slate-200 rounded-md p-4"
21 hx-drop="true"21 hx-drop="true"
22 >22 >
23 Drag & Drop your file here or23 Drag & Drop your file here or
A internal/gotemplate/render/strings.go +16 -0
@@ -0,0 +1,16 @@
1+// Take some inputs and return as full strings
2+package render
3+
4+type CreateContainer struct {
5+ ContainerName string
6+ ContainerHost string
7+ Environment string
8+ ImageName string
9+ ImageID string
10+ Ports string
11+ Data string
12+ TraefikLabels []string
13+ Network string
14+}
15+
16+const dockerContainerTemplate = ""
@@ -0,0 +1,16 @@
1+// Take some inputs and return as full strings
2+package render
3+
4+type CreateContainer struct {
5+ ContainerName string
6+ ContainerHost string
7+ Environment string
8+ ImageName string
9+ ImageID string
10+ Ports string
11+ Data string
12+ TraefikLabels []string
13+ Network string
14+}
15+
16+const dockerContainerTemplate = ""
A internal/httpserve/handler/createcontainer.go +66 -0
@@ -0,0 +1,66 @@
1+package handler
2+
3+import (
4+ "net/http"
5+
6+ "github.com/bnema/gordon/internal/app"
7+ "github.com/bnema/gordon/internal/gotemplate/render"
8+ "github.com/bnema/gordon/pkg/utils/docker"
9+ "github.com/labstack/echo/v4"
10+)
11+
12+type CreateContainer struct {
13+ ContainerName string
14+ ContainerHost string
15+ Environment string
16+ ImageName string
17+ ImageID string
18+ Ports string
19+ Volumes string
20+ TraefikLabels []string
21+ Network string
22+}
23+
24+// CreateContainerRoute is the route for creating a new container
25+func CreateContainerGETHandler(c echo.Context, a *app.App) error {
26+ // Retreive the image ID from the URL
27+ ShortID := c.Param("ID")
28+ idMapMutex.Lock()
29+ // Check if the ShortImgID exists in the idMap
30+ imageID, exists := idMap[ShortID]
31+ idMapMutex.Unlock()
32+
33+ imageInfo, err := docker.GetImageInfo(imageID)
34+ if err != nil {
35+ return sendError(c, err)
36+ }
37+ // Extract image name
38+ var imageName string
39+ if len(imageInfo.RepoTags) > 0 {
40+ imageName = imageInfo.RepoTags[0] // Using the first tag as the image name
41+ }
42+
43+ if !exists {
44+ return c.String(http.StatusBadRequest, "Invalid ShortImgID")
45+ }
46+
47+ data := map[string]interface{}{
48+ "Title": "Create a new container",
49+ "ImageName": imageName,
50+ }
51+
52+ rendererData, err := render.GetHTMLRenderer("html/fragments", "createcontainer.gohtml", a.TemplateFS, a)
53+
54+ if err != nil {
55+ return sendError(c, err)
56+ }
57+
58+ renderedHTML, err := rendererData.Render(data, a)
59+
60+ if err != nil {
61+ return sendError(c, err)
62+ }
63+
64+ return c.HTML(200, renderedHTML)
65+
66+}
@@ -0,0 +1,66 @@
1+package handler
2+
3+import (
4+ "net/http"
5+
6+ "github.com/bnema/gordon/internal/app"
7+ "github.com/bnema/gordon/internal/gotemplate/render"
8+ "github.com/bnema/gordon/pkg/utils/docker"
9+ "github.com/labstack/echo/v4"
10+)
11+
12+type CreateContainer struct {
13+ ContainerName string
14+ ContainerHost string
15+ Environment string
16+ ImageName string
17+ ImageID string
18+ Ports string
19+ Volumes string
20+ TraefikLabels []string
21+ Network string
22+}
23+
24+// CreateContainerRoute is the route for creating a new container
25+func CreateContainerGETHandler(c echo.Context, a *app.App) error {
26+ // Retreive the image ID from the URL
27+ ShortID := c.Param("ID")
28+ idMapMutex.Lock()
29+ // Check if the ShortImgID exists in the idMap
30+ imageID, exists := idMap[ShortID]
31+ idMapMutex.Unlock()
32+
33+ imageInfo, err := docker.GetImageInfo(imageID)
34+ if err != nil {
35+ return sendError(c, err)
36+ }
37+ // Extract image name
38+ var imageName string
39+ if len(imageInfo.RepoTags) > 0 {
40+ imageName = imageInfo.RepoTags[0] // Using the first tag as the image name
41+ }
42+
43+ if !exists {
44+ return c.String(http.StatusBadRequest, "Invalid ShortImgID")
45+ }
46+
47+ data := map[string]interface{}{
48+ "Title": "Create a new container",
49+ "ImageName": imageName,
50+ }
51+
52+ rendererData, err := render.GetHTMLRenderer("html/fragments", "createcontainer.gohtml", a.TemplateFS, a)
53+
54+ if err != nil {
55+ return sendError(c, err)
56+ }
57+
58+ renderedHTML, err := rendererData.Render(data, a)
59+
60+ if err != nil {
61+ return sendError(c, err)
62+ }
63+
64+ return c.HTML(200, renderedHTML)
65+
66+}
M internal/httpserve/router.go +3 -0
@@ -88,4 +88,7 @@ func bindHTMXEndpoints(e *echo.Echo, a *app.App) {
8888 e.POST("/htmx/upload-image", func(c echo.Context) error {
8989 return handler.UploadImagePOSTHandler(c, a)
9090 })
91+ e.GET("htmx/create-container/:ID", func(c echo.Context) error {
92+ return handler.CreateContainerGETHandler(c, a)
93+ })
9194 }
@@ -88,4 +88,7 @@ func bindHTMXEndpoints(e *echo.Echo, a *app.App) {
88 e.POST("/htmx/upload-image", func(c echo.Context) error {88 e.POST("/htmx/upload-image", func(c echo.Context) error {
89 return handler.UploadImagePOSTHandler(c, a)89 return handler.UploadImagePOSTHandler(c, a)
90 })90 })
91+ e.GET("htmx/create-container/:ID", func(c echo.Context) error {
92+ return handler.CreateContainerGETHandler(c, a)
93+ })
91 }94 }
M internal/webui/public/assets/css/tailwind.css +39 -23
@@ -580,6 +580,10 @@ video {
580580 margin-bottom: 0.5rem;
581581 }
582582
583+.mb-1 {
584+ margin-bottom: 0.25rem;
585+}
586+
583587 .mb-4 {
584588 margin-bottom: 1rem;
585589 }
@@ -600,16 +604,8 @@ video {
600604 margin-top: 1rem;
601605 }
602606
603-.mb-1 {
604- margin-bottom: 0.25rem;
605-}
606-
607-.mt-1 {
608- margin-top: 0.25rem;
609-}
610-
611-.mt-3 {
612- margin-top: 0.75rem;
607+.block {
608+ display: block;
613609 }
614610
615611 .flex {
@@ -741,6 +737,10 @@ video {
741737 border-width: 1px;
742738 }
743739
740+.border-2 {
741+ border-width: 2px;
742+}
743+
744744 .border-t-2 {
745745 border-top-width: 2px;
746746 }
@@ -765,11 +765,26 @@ video {
765765 background-color: rgb(0 123 192 / var(--tw-bg-opacity));
766766 }
767767
768+.bg-gray-100 {
769+ --tw-bg-opacity: 1;
770+ background-color: rgb(243 244 246 / var(--tw-bg-opacity));
771+}
772+
768773 .bg-gray-300 {
769774 --tw-bg-opacity: 1;
770775 background-color: rgb(209 213 219 / var(--tw-bg-opacity));
771776 }
772777
778+.bg-slate-100 {
779+ --tw-bg-opacity: 1;
780+ background-color: rgb(241 245 249 / var(--tw-bg-opacity));
781+}
782+
783+.bg-slate-200 {
784+ --tw-bg-opacity: 1;
785+ background-color: rgb(226 232 240 / var(--tw-bg-opacity));
786+}
787+
773788 .bg-transparent {
774789 background-color: transparent;
775790 }
@@ -806,22 +821,18 @@ video {
806821 padding-right: 1rem;
807822 }
808823
809-.py-2 {
810- padding-top: 0.5rem;
811- padding-bottom: 0.5rem;
812-}
813-
814824 .py-1 {
815825 padding-top: 0.25rem;
816826 padding-bottom: 0.25rem;
817827 }
818828
819-.pt-2 {
829+.py-2 {
820830 padding-top: 0.5rem;
831+ padding-bottom: 0.5rem;
821832 }
822833
823-.align-baseline {
824- vertical-align: baseline;
834+.pb-4 {
835+ padding-bottom: 1rem;
825836 }
826837
827838 .align-middle {
@@ -870,6 +881,11 @@ video {
870881 letter-spacing: -0.05em;
871882 }
872883
884+.text-blue-800 {
885+ --tw-text-opacity: 1;
886+ color: rgb(30 64 175 / var(--tw-text-opacity));
887+}
888+
873889 .text-gordonmascot-hblue {
874890 --tw-text-opacity: 1;
875891 color: rgb(1 30 92 / var(--tw-text-opacity));
@@ -885,6 +901,11 @@ video {
885901 color: rgb(243 244 246 / var(--tw-text-opacity));
886902 }
887903
904+.text-gray-500 {
905+ --tw-text-opacity: 1;
906+ color: rgb(107 114 128 / var(--tw-text-opacity));
907+}
908+
888909 .text-gray-800 {
889910 --tw-text-opacity: 1;
890911 color: rgb(31 41 55 / var(--tw-text-opacity));
@@ -910,11 +931,6 @@ video {
910931 color: rgb(255 255 255 / var(--tw-text-opacity));
911932 }
912933
913-.text-black {
914- --tw-text-opacity: 1;
915- color: rgb(0 0 0 / var(--tw-text-opacity));
916-}
917-
918934 .underline {
919935 text-decoration-line: underline;
920936 }
@@ -580,6 +580,10 @@ video {
580 margin-bottom: 0.5rem;580 margin-bottom: 0.5rem;
581 }581 }
582 582
583+.mb-1 {
584+ margin-bottom: 0.25rem;
585+}
586+
583 .mb-4 {587 .mb-4 {
584 margin-bottom: 1rem;588 margin-bottom: 1rem;
585 }589 }
@@ -600,16 +604,8 @@ video {
600 margin-top: 1rem;604 margin-top: 1rem;
601 }605 }
602 606
603-.mb-1 {607+.block {
604- margin-bottom: 0.25rem;608+ display: block;
605-}
606-
607-.mt-1 {
608- margin-top: 0.25rem;
609-}
610-
611-.mt-3 {
612- margin-top: 0.75rem;
613 }609 }
614 610
615 .flex {611 .flex {
@@ -741,6 +737,10 @@ video {
741 border-width: 1px;737 border-width: 1px;
742 }738 }
743 739
740+.border-2 {
741+ border-width: 2px;
742+}
743+
744 .border-t-2 {744 .border-t-2 {
745 border-top-width: 2px;745 border-top-width: 2px;
746 }746 }
@@ -765,11 +765,26 @@ video {
765 background-color: rgb(0 123 192 / var(--tw-bg-opacity));765 background-color: rgb(0 123 192 / var(--tw-bg-opacity));
766 }766 }
767 767
768+.bg-gray-100 {
769+ --tw-bg-opacity: 1;
770+ background-color: rgb(243 244 246 / var(--tw-bg-opacity));
771+}
772+
768 .bg-gray-300 {773 .bg-gray-300 {
769 --tw-bg-opacity: 1;774 --tw-bg-opacity: 1;
770 background-color: rgb(209 213 219 / var(--tw-bg-opacity));775 background-color: rgb(209 213 219 / var(--tw-bg-opacity));
771 }776 }
772 777
778+.bg-slate-100 {
779+ --tw-bg-opacity: 1;
780+ background-color: rgb(241 245 249 / var(--tw-bg-opacity));
781+}
782+
783+.bg-slate-200 {
784+ --tw-bg-opacity: 1;
785+ background-color: rgb(226 232 240 / var(--tw-bg-opacity));
786+}
787+
773 .bg-transparent {788 .bg-transparent {
774 background-color: transparent;789 background-color: transparent;
775 }790 }
@@ -806,22 +821,18 @@ video {
806 padding-right: 1rem;821 padding-right: 1rem;
807 }822 }
808 823
809-.py-2 {
810- padding-top: 0.5rem;
811- padding-bottom: 0.5rem;
812-}
813-
814 .py-1 {824 .py-1 {
815 padding-top: 0.25rem;825 padding-top: 0.25rem;
816 padding-bottom: 0.25rem;826 padding-bottom: 0.25rem;
817 }827 }
818 828
819-.pt-2 {829+.py-2 {
820 padding-top: 0.5rem;830 padding-top: 0.5rem;
831+ padding-bottom: 0.5rem;
821 }832 }
822 833
823-.align-baseline {834+.pb-4 {
824- vertical-align: baseline;835+ padding-bottom: 1rem;
825 }836 }
826 837
827 .align-middle {838 .align-middle {
@@ -870,6 +881,11 @@ video {
870 letter-spacing: -0.05em;881 letter-spacing: -0.05em;
871 }882 }
872 883
884+.text-blue-800 {
885+ --tw-text-opacity: 1;
886+ color: rgb(30 64 175 / var(--tw-text-opacity));
887+}
888+
873 .text-gordonmascot-hblue {889 .text-gordonmascot-hblue {
874 --tw-text-opacity: 1;890 --tw-text-opacity: 1;
875 color: rgb(1 30 92 / var(--tw-text-opacity));891 color: rgb(1 30 92 / var(--tw-text-opacity));
@@ -885,6 +901,11 @@ video {
885 color: rgb(243 244 246 / var(--tw-text-opacity));901 color: rgb(243 244 246 / var(--tw-text-opacity));
886 }902 }
887 903
904+.text-gray-500 {
905+ --tw-text-opacity: 1;
906+ color: rgb(107 114 128 / var(--tw-text-opacity));
907+}
908+
888 .text-gray-800 {909 .text-gray-800 {
889 --tw-text-opacity: 1;910 --tw-text-opacity: 1;
890 color: rgb(31 41 55 / var(--tw-text-opacity));911 color: rgb(31 41 55 / var(--tw-text-opacity));
@@ -910,11 +931,6 @@ video {
910 color: rgb(255 255 255 / var(--tw-text-opacity));931 color: rgb(255 255 255 / var(--tw-text-opacity));
911 }932 }
912 933
913-.text-black {
914- --tw-text-opacity: 1;
915- color: rgb(0 0 0 / var(--tw-text-opacity));
916-}
917-
918 .underline {934 .underline {
919 text-decoration-line: underline;935 text-decoration-line: underline;
920 }936 }
M internal/webui/public/assets/js/custom.js +10 -0
@@ -0,0 +1,10 @@
1+document.body.addEventListener("htmx:afterOnLoad", function(event) {
2+ if (
3+ event.target.id === "image-manager" &&
4+ event.detail.xhr.responseText === "Success"
5+ ) {
6+ setTimeout(function() {
7+ location.reload();
8+ }, 3000);
9+ }
10+});
@@ -0,0 +1,10 @@
1+document.body.addEventListener("htmx:afterOnLoad", function(event) {
2+ if (
3+ event.target.id === "image-manager" &&
4+ event.detail.xhr.responseText === "Success"
5+ ) {
6+ setTimeout(function() {
7+ location.reload();
8+ }, 3000);
9+ }
10+});
M pkg/utils/docker/images.go +11 -2
@@ -33,8 +33,6 @@ func DeleteContainerImage(imageID string) error {
3333 }
3434
3535 func ImportImageToEngine(imagePath string) (string, error) {
36- fmt.Println("Importing image to Docker engine")
37- fmt.Println(imagePath)
3836 // Check if the Docker client has been initialized
3937 CheckIfInitialized()
4038
@@ -77,3 +75,14 @@ func ImportImageToEngine(imagePath string) (string, error) {
7775
7876 return imageID, nil
7977 }
78+
79+// From an ID, get the all the information about the image
80+func GetImageInfo(imageID string) (*types.ImageInspect, error) {
81+ // Get the image information using the Docker client
82+ imageInfo, _, err := dockerCli.ImageInspectWithRaw(context.Background(), imageID)
83+ if err != nil {
84+ return nil, err
85+ }
86+
87+ return &imageInfo, nil
88+}
@@ -33,8 +33,6 @@ func DeleteContainerImage(imageID string) error {
33 }33 }
34 34
35 func ImportImageToEngine(imagePath string) (string, error) {35 func ImportImageToEngine(imagePath string) (string, error) {
36- fmt.Println("Importing image to Docker engine")
37- fmt.Println(imagePath)
38 // Check if the Docker client has been initialized36 // Check if the Docker client has been initialized
39 CheckIfInitialized()37 CheckIfInitialized()
40 38
@@ -77,3 +75,14 @@ func ImportImageToEngine(imagePath string) (string, error) {
77 75
78 return imageID, nil76 return imageID, nil
79 }77 }
78+
79+// From an ID, get the all the information about the image
80+func GetImageInfo(imageID string) (*types.ImageInspect, error) {
81+ // Get the image information using the Docker client
82+ imageInfo, _, err := dockerCli.ImageInspectWithRaw(context.Background(), imageID)
83+ if err != nil {
84+ return nil, err
85+ }
86+
87+ return &imageInfo, nil
88+}

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