detached d925f24

Fix error 500 when uploading an image for the first time

commit
d925f248fa72f4441fd4f5f0e6937c759860b161
author
bnema · · 2023-09-25 07:30 UTC
parent
339e684
signature
unsigned
5 files · +53 -37
M internal/gotemplate/models/html/fragments/imagelist.gohtml +25 -10
@@ -6,7 +6,7 @@
66 id="upload-button"
77 hx-get="/htmx/upload-image"
88 hx-target="#upload-image"
9- hx-swap="outerHTML"
9+ hx-swap="innerHTML"
1010 class="py-1 px-2 bg-blue-500 hover:bg-blue-700 text-white font-bold rounded"
1111 >
1212 <span class="iconoir-upload text-xl align-middle"></span>
@@ -14,14 +14,6 @@
1414 </button>
1515 </div>
1616 <table class="p-4 border-t-2">
17- <!-- <thead>
18- <tr>
19- <th>Name</th>
20- <th>Created</th>
21- <th>Size</th>
22- <th>Action</th>
23- </tr>
24- </thead> -->
2517 <tbody>
2618 {{
2719 range.Images
@@ -41,7 +33,7 @@
4133 hx-get="/htmx/create-container/{{.ID}}"
4234 hx-target="#create-container"
4335 hx-target-*="#error-message-div-{{.ID}}"
44- hx-swap="outerHTML"
36+ hx-swap="innerHTML"
4537 class="py-2 px-4"
4638 >
4739 <span title="Deploy a container using this image" class="iconoir-add-circle text-xl text-blue-800 font-bold"></span>
@@ -77,3 +69,26 @@
7769 <div id="upload-image"></div>
7870 <div id="create-container"></div>
7971 </div>
72+
73+<script>
74+ // Hide upload form if add button is clicked
75+ document.querySelectorAll('[id^="add-button-img-"]').forEach((button) => {
76+ button.addEventListener("click", () => {
77+ document.getElementById("upload-image").innerHTML = "";
78+ });
79+ });
80+
81+ // Hide create container form if upload button is clicke
82+ document.getElementById("upload-button").addEventListener("click", () => {
83+ document.getElementById("create-container").innerHTML = "";
84+ });
85+
86+ // Remove image from list if remove button is clicked (after confirmation)
87+ document.querySelectorAll('[id^="remove-button-img-"]').forEach((button) => {
88+ button.addEventListener("click", () => {
89+ const id = button.id.split("-")[3];
90+ const tr = document.getElementById(`img-${id}`);
91+ tr.style.display = "none";
92+ });
93+ });
94+</script>⏎̸
@@ -6,7 +6,7 @@
6 id="upload-button"6 id="upload-button"
7 hx-get="/htmx/upload-image"7 hx-get="/htmx/upload-image"
8 hx-target="#upload-image"8 hx-target="#upload-image"
9- hx-swap="outerHTML"9+ hx-swap="innerHTML"
10 class="py-1 px-2 bg-blue-500 hover:bg-blue-700 text-white font-bold rounded"10 class="py-1 px-2 bg-blue-500 hover:bg-blue-700 text-white font-bold rounded"
11 >11 >
12 <span class="iconoir-upload text-xl align-middle"></span>12 <span class="iconoir-upload text-xl align-middle"></span>
@@ -14,14 +14,6 @@
14 </button>14 </button>
15 </div>15 </div>
16 <table class="p-4 border-t-2">16 <table class="p-4 border-t-2">
17- <!-- <thead>
18- <tr>
19- <th>Name</th>
20- <th>Created</th>
21- <th>Size</th>
22- <th>Action</th>
23- </tr>
24- </thead> -->
25 <tbody>17 <tbody>
26 {{18 {{
27 range.Images19 range.Images
@@ -41,7 +33,7 @@
41 hx-get="/htmx/create-container/{{.ID}}"33 hx-get="/htmx/create-container/{{.ID}}"
42 hx-target="#create-container"34 hx-target="#create-container"
43 hx-target-*="#error-message-div-{{.ID}}"35 hx-target-*="#error-message-div-{{.ID}}"
44- hx-swap="outerHTML"36+ hx-swap="innerHTML"
45 class="py-2 px-4"37 class="py-2 px-4"
46 >38 >
47 <span title="Deploy a container using this image" class="iconoir-add-circle text-xl text-blue-800 font-bold"></span>39 <span title="Deploy a container using this image" class="iconoir-add-circle text-xl text-blue-800 font-bold"></span>
@@ -77,3 +69,26 @@
77 <div id="upload-image"></div>69 <div id="upload-image"></div>
78 <div id="create-container"></div>70 <div id="create-container"></div>
79 </div>71 </div>
72+
73+<script>
74+ // Hide upload form if add button is clicked
75+ document.querySelectorAll('[id^="add-button-img-"]').forEach((button) => {
76+ button.addEventListener("click", () => {
77+ document.getElementById("upload-image").innerHTML = "";
78+ });
79+ });
80+
81+ // Hide create container form if upload button is clicke
82+ document.getElementById("upload-button").addEventListener("click", () => {
83+ document.getElementById("create-container").innerHTML = "";
84+ });
85+
86+ // Remove image from list if remove button is clicked (after confirmation)
87+ document.querySelectorAll('[id^="remove-button-img-"]').forEach((button) => {
88+ button.addEventListener("click", () => {
89+ const id = button.id.split("-")[3];
90+ const tr = document.getElementById(`img-${id}`);
91+ tr.style.display = "none";
92+ });
93+ });
94+</script>⏎̸
M internal/httpserve/handler/uploadimage.go +11 -6
@@ -1,7 +1,8 @@
11 package handler
22
33 import (
4- "fmt"
4+ "bytes"
5+ "io"
56 "net/http"
67
78 "github.com/bnema/gordon/internal/app"
@@ -37,23 +38,27 @@ func UploadImagePOSTHandler(c echo.Context, a *app.App) error {
3738 if err := c.Request().ParseMultipartForm(MaxUploadSize); err != nil {
3839 return c.String(http.StatusRequestEntityTooLarge, "File size exceeded")
3940 }
41+
4042 // Get the file from the form
4143 file, header, err := c.Request().FormFile("file")
4244 if err != nil {
4345 return sendError(c, err)
4446 }
45- defer file.Close()
47+
48+ // Get the file size
49+ buf := new(bytes.Buffer)
50+ _, err = io.Copy(buf, file)
51+ if err != nil {
52+ return sendError(c, err)
53+ }
4654
4755 // Get the filename
4856 filename := header.Filename
49- fmt.Println("Uploaded file:", filename)
5057
5158 // Save the image to the storage directory
52- imageId, err := store.SaveImageToStorage(&a.Config, filename, file)
59+ _, err = store.SaveImageToStorage(&a.Config, filename, buf)
5360 if err != nil {
5461 return sendError(c, err)
5562 }
56- // Just print for now but add to the database later
57- fmt.Println(imageId)
5863 return c.HTML(http.StatusOK, "Success")
5964 }
@@ -1,7 +1,8 @@
1 package handler1 package handler
2 2
3 import (3 import (
4- "fmt"4+ "bytes"
5+ "io"
5 "net/http"6 "net/http"
6 7
7 "github.com/bnema/gordon/internal/app"8 "github.com/bnema/gordon/internal/app"
@@ -37,23 +38,27 @@ func UploadImagePOSTHandler(c echo.Context, a *app.App) error {
37 if err := c.Request().ParseMultipartForm(MaxUploadSize); err != nil {38 if err := c.Request().ParseMultipartForm(MaxUploadSize); err != nil {
38 return c.String(http.StatusRequestEntityTooLarge, "File size exceeded")39 return c.String(http.StatusRequestEntityTooLarge, "File size exceeded")
39 }40 }
41+
40 // Get the file from the form42 // Get the file from the form
41 file, header, err := c.Request().FormFile("file")43 file, header, err := c.Request().FormFile("file")
42 if err != nil {44 if err != nil {
43 return sendError(c, err)45 return sendError(c, err)
44 }46 }
45- defer file.Close()47+
48+ // Get the file size
49+ buf := new(bytes.Buffer)
50+ _, err = io.Copy(buf, file)
51+ if err != nil {
52+ return sendError(c, err)
53+ }
46 54
47 // Get the filename55 // Get the filename
48 filename := header.Filename56 filename := header.Filename
49- fmt.Println("Uploaded file:", filename)
50 57
51 // Save the image to the storage directory58 // Save the image to the storage directory
52- imageId, err := store.SaveImageToStorage(&a.Config, filename, file)59+ _, err = store.SaveImageToStorage(&a.Config, filename, buf)
53 if err != nil {60 if err != nil {
54 return sendError(c, err)61 return sendError(c, err)
55 }62 }
56- // Just print for now but add to the database later
57- fmt.Println(imageId)
58 return c.HTML(http.StatusOK, "Success")63 return c.HTML(http.StatusOK, "Success")
59 }64 }
M internal/webui/public/assets/js/custom.js +2 -1
@@ -5,6 +5,7 @@ document.body.addEventListener("htmx:afterOnLoad", function(event) {
55 ) {
66 setTimeout(function() {
77 location.reload();
8- }, 3000);
8+ }, 2000);
99 }
1010 });
11+
@@ -5,6 +5,7 @@ document.body.addEventListener("htmx:afterOnLoad", function(event) {
5 ) {5 ) {
6 setTimeout(function() {6 setTimeout(function() {
7 location.reload();7 location.reload();
8- }, 3000);8+ }, 2000);
9 }9 }
10 });10 });
11+
M pkg/utils/docker/images.go +7 -15
@@ -2,7 +2,6 @@ package docker
22
33 import (
44 "context"
5- "fmt"
65 "os"
76
87 "github.com/docker/docker/api/types"
@@ -20,10 +19,9 @@ func ListContainerImages() ([]types.ImageSummary, error) {
2019
2120 return images, nil
2221 }
23-func DeleteContainerImage(imageID string) error {
24- // Check if the Docker client has been initialized
2522
26- // Delete the image using the Docker client
23+// DeleteContainerImage deletes an image from the Docker engine
24+func DeleteContainerImage(imageID string) error {
2725 _, err := dockerCli.ImageRemove(context.Background(), imageID, types.ImageRemoveOptions{})
2826 if err != nil {
2927 return err
@@ -32,22 +30,20 @@ func DeleteContainerImage(imageID string) error {
3230 return nil
3331 }
3432
33+// ImportImageToEngine imports an image to the Docker engine
3534 func ImportImageToEngine(imagePath string) (string, error) {
36- // Check if the Docker client has been initialized
37- CheckIfInitialized()
38-
3935 // Open the image file
4036 imageFile, err := os.Open(imagePath)
4137 if err != nil {
4238 return "", err
4339 }
44- defer imageFile.Close()
4540
46- // Import the image into Docker
4741 _, err = dockerCli.ImageLoad(context.Background(), imageFile, false)
4842 if err != nil {
4943 return "", err
5044 }
45+ // Close the image file
46+ imageFile.Close()
5147
5248 // List all images
5349 images, err := dockerCli.ImageList(context.Background(), types.ImageListOptions{})
@@ -59,18 +55,14 @@ func ImportImageToEngine(imagePath string) (string, error) {
5955 var imageID string
6056 for _, image := range images {
6157 for _, tag := range image.RepoTags {
62- if tag == "alpine:latest" {
58+ if tag == "<none>:<none>" {
6359 imageID = image.ID
64- break
6560 }
6661 }
67- if imageID != "" {
68- break
69- }
7062 }
7163
7264 if imageID == "" {
73- return "", fmt.Errorf("could not find the loaded image")
65+ return "", err
7466 }
7567
7668 return imageID, nil
@@ -2,7 +2,6 @@ package docker
2 2
3 import (3 import (
4 "context"4 "context"
5- "fmt"
6 "os"5 "os"
7 6
8 "github.com/docker/docker/api/types"7 "github.com/docker/docker/api/types"
@@ -20,10 +19,9 @@ func ListContainerImages() ([]types.ImageSummary, error) {
20 19
21 return images, nil20 return images, nil
22 }21 }
23-func DeleteContainerImage(imageID string) error {
24- // Check if the Docker client has been initialized
25 22
26- // Delete the image using the Docker client23+// DeleteContainerImage deletes an image from the Docker engine
24+func DeleteContainerImage(imageID string) error {
27 _, err := dockerCli.ImageRemove(context.Background(), imageID, types.ImageRemoveOptions{})25 _, err := dockerCli.ImageRemove(context.Background(), imageID, types.ImageRemoveOptions{})
28 if err != nil {26 if err != nil {
29 return err27 return err
@@ -32,22 +30,20 @@ func DeleteContainerImage(imageID string) error {
32 return nil30 return nil
33 }31 }
34 32
33+// ImportImageToEngine imports an image to the Docker engine
35 func ImportImageToEngine(imagePath string) (string, error) {34 func ImportImageToEngine(imagePath string) (string, error) {
36- // Check if the Docker client has been initialized
37- CheckIfInitialized()
38-
39 // Open the image file35 // Open the image file
40 imageFile, err := os.Open(imagePath)36 imageFile, err := os.Open(imagePath)
41 if err != nil {37 if err != nil {
42 return "", err38 return "", err
43 }39 }
44- defer imageFile.Close()
45 40
46- // Import the image into Docker
47 _, err = dockerCli.ImageLoad(context.Background(), imageFile, false)41 _, err = dockerCli.ImageLoad(context.Background(), imageFile, false)
48 if err != nil {42 if err != nil {
49 return "", err43 return "", err
50 }44 }
45+ // Close the image file
46+ imageFile.Close()
51 47
52 // List all images48 // List all images
53 images, err := dockerCli.ImageList(context.Background(), types.ImageListOptions{})49 images, err := dockerCli.ImageList(context.Background(), types.ImageListOptions{})
@@ -59,18 +55,14 @@ func ImportImageToEngine(imagePath string) (string, error) {
59 var imageID string55 var imageID string
60 for _, image := range images {56 for _, image := range images {
61 for _, tag := range image.RepoTags {57 for _, tag := range image.RepoTags {
62- if tag == "alpine:latest" {58+ if tag == "<none>:<none>" {
63 imageID = image.ID59 imageID = image.ID
64- break
65 }60 }
66 }61 }
67- if imageID != "" {
68- break
69- }
70 }62 }
71 63
72 if imageID == "" {64 if imageID == "" {
73- return "", fmt.Errorf("could not find the loaded image")65+ return "", err
74 }66 }
75 67
76 return imageID, nil68 return imageID, nil
M pkg/utils/store/storeimages.go +8 -5
@@ -3,7 +3,6 @@ package store
33 import (
44 "fmt"
55 "io"
6- "mime/multipart"
76 "os"
87 "path/filepath"
98
@@ -24,7 +23,7 @@ func NewStorageConfig(config *app.AppConfig) *StorageConfig {
2423 }
2524 }
2625
27-func SaveImageToStorage(config *app.AppConfig, filename string, file multipart.File) (string, error) {
26+func SaveImageToStorage(config *app.AppConfig, filename string, buf io.Reader) (string, error) {
2827 // Check if the folder exist if not create it
2928 if _, err := os.Stat(config.General.StorageDir); os.IsNotExist(err) {
3029 err := os.MkdirAll(config.General.StorageDir, 0755)
@@ -41,18 +40,22 @@ func SaveImageToStorage(config *app.AppConfig, filename string, file multipart.F
4140 if err != nil {
4241 return "", fmt.Errorf("failed to open file: %v", err)
4342 }
44- defer outFile.Close()
4543
4644 // Write the uploaded file's content to the outFile
47- _, err = io.Copy(outFile, file)
45+ _, err = io.Copy(outFile, buf)
4846 if err != nil {
4947 return "", fmt.Errorf("failed to write file: %v", err)
5048 }
5149
50+ // Check if file exists
51+ if _, err := os.Stat(saveInPath); os.IsNotExist(err) {
52+ return "", fmt.Errorf("file does not exist: %v", err)
53+ }
54+
5255 // Import the image into Docker
5356 imageId, err := docker.ImportImageToEngine(saveInPath)
5457 if err != nil {
55- return "", fmt.Errorf("failed to save image to storage directory: %v", err)
58+ return "", fmt.Errorf("failed to import image to Docker: %v", err)
5659 }
5760
5861 return imageId, nil
@@ -3,7 +3,6 @@ package store
3 import (3 import (
4 "fmt"4 "fmt"
5 "io"5 "io"
6- "mime/multipart"
7 "os"6 "os"
8 "path/filepath"7 "path/filepath"
9 8
@@ -24,7 +23,7 @@ func NewStorageConfig(config *app.AppConfig) *StorageConfig {
24 }23 }
25 }24 }
26 25
27-func SaveImageToStorage(config *app.AppConfig, filename string, file multipart.File) (string, error) {26+func SaveImageToStorage(config *app.AppConfig, filename string, buf io.Reader) (string, error) {
28 // Check if the folder exist if not create it27 // Check if the folder exist if not create it
29 if _, err := os.Stat(config.General.StorageDir); os.IsNotExist(err) {28 if _, err := os.Stat(config.General.StorageDir); os.IsNotExist(err) {
30 err := os.MkdirAll(config.General.StorageDir, 0755)29 err := os.MkdirAll(config.General.StorageDir, 0755)
@@ -41,18 +40,22 @@ func SaveImageToStorage(config *app.AppConfig, filename string, file multipart.F
41 if err != nil {40 if err != nil {
42 return "", fmt.Errorf("failed to open file: %v", err)41 return "", fmt.Errorf("failed to open file: %v", err)
43 }42 }
44- defer outFile.Close()
45 43
46 // Write the uploaded file's content to the outFile44 // Write the uploaded file's content to the outFile
47- _, err = io.Copy(outFile, file)45+ _, err = io.Copy(outFile, buf)
48 if err != nil {46 if err != nil {
49 return "", fmt.Errorf("failed to write file: %v", err)47 return "", fmt.Errorf("failed to write file: %v", err)
50 }48 }
51 49
50+ // Check if file exists
51+ if _, err := os.Stat(saveInPath); os.IsNotExist(err) {
52+ return "", fmt.Errorf("file does not exist: %v", err)
53+ }
54+
52 // Import the image into Docker55 // Import the image into Docker
53 imageId, err := docker.ImportImageToEngine(saveInPath)56 imageId, err := docker.ImportImageToEngine(saveInPath)
54 if err != nil {57 if err != nil {
55- return "", fmt.Errorf("failed to save image to storage directory: %v", err)58+ return "", fmt.Errorf("failed to import image to Docker: %v", err)
56 }59 }
57 60
58 return imageId, nil61 return imageId, nil

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