Add user settings icon and logout on left menu
- commit
- 860e07b9600e4d1936564ba6826d84f536929838
- author
- bnema · · 2023-09-25 08:10 UTC
- parent
- c39be40
- signature
- unsigned
5 files · +157 -33
M internal/gotemplate/models/html/fragments/createcontainer.gohtml +13 -14
| @@ -8,11 +8,22 @@ | |||
| 8 | 8 | <div class="text-red-500">{{.ErrorMessage}}</div> | |
| 9 | 9 | {{ end }} | |
| 10 | 10 | <form | |
| 11 | - | action="/create-container" | |
| 11 | + | action="/create-container/{{.ShortID}}" | |
| 12 | 12 | method="post" | |
| 13 | - | hx-post="/htmx/create-container" | |
| 13 | + | hx-post="/htmx/create-container/{{.ShortID}}" | |
| 14 | 14 | hx-swap="innerHTML" | |
| 15 | 15 | > | |
| 16 | + | <!-- Image Name --> | |
| 17 | + | <div class="mb-4"> | |
| 18 | + | <label class="block">Image name</label> | |
| 19 | + | <input | |
| 20 | + | type="text" | |
| 21 | + | name="image_name" | |
| 22 | + | class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2" | |
| 23 | + | value="{{.ImageName}}" | |
| 24 | + | readonly | |
| 25 | + | /> | |
| 26 | + | </div> | |
| 16 | 27 | <!-- Container Name --> | |
| 17 | 28 | <div class="mb-4"> | |
| 18 | 29 | <label class="block">Container name</label> | |
| @@ -34,18 +45,6 @@ | |||
| 34 | 45 | /> | |
| 35 | 46 | </div> | |
| 36 | 47 | ||
| 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 | 48 | <!-- Ports --> | |
| 50 | 49 | <div class="mb-4"> | |
| 51 | 50 | <label class="block">Ports</label> | |
| @@ -8,11 +8,22 @@ | |||||
| 8 | <div class="text-red-500">{{.ErrorMessage}}</div> | 8 | <div class="text-red-500">{{.ErrorMessage}}</div> | ||
| 9 | {{ end }} | 9 | {{ end }} | ||
| 10 | <form | 10 | <form | ||
| 11 | - | action="/create-container" | 11 | + | action="/create-container/{{.ShortID}}" |
| 12 | method="post" | 12 | method="post" | ||
| 13 | - | hx-post="/htmx/create-container" | 13 | + | hx-post="/htmx/create-container/{{.ShortID}}" |
| 14 | hx-swap="innerHTML" | 14 | hx-swap="innerHTML" | ||
| 15 | > | 15 | > | ||
| 16 | + | <!-- Image Name --> | |||
| 17 | + | <div class="mb-4"> | |||
| 18 | + | <label class="block">Image name</label> | |||
| 19 | + | <input | |||
| 20 | + | type="text" | |||
| 21 | + | name="image_name" | |||
| 22 | + | class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2" | |||
| 23 | + | value="{{.ImageName}}" | |||
| 24 | + | readonly | |||
| 25 | + | /> | |||
| 26 | + | </div> | |||
| 16 | <!-- Container Name --> | 27 | <!-- Container Name --> | ||
| 17 | <div class="mb-4"> | 28 | <div class="mb-4"> | ||
| 18 | <label class="block">Container name</label> | 29 | <label class="block">Container name</label> | ||
| @@ -34,18 +45,6 @@ | |||||
| 34 | /> | 45 | /> | ||
| 35 | </div> | 46 | </div> | ||
| 36 | 47 | ||||
| 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 --> | 48 | <!-- Ports --> | ||
| 50 | <div class="mb-4"> | 49 | <div class="mb-4"> | ||
| 51 | <label class="block">Ports</label> | 50 | <label class="block">Ports</label> | ||
M internal/gotemplate/models/html/fragments/leftmenu.gohtml +33 -9
| @@ -1,15 +1,24 @@ | |||
| 1 | - | <div class="w-full md:w-1/5 flex flex-col space-evenly p-4 md:p-0"> | |
| 1 | + | <div | |
| 2 | + | class="w-full md:w-1/5 flex flex-col space-evenly md:p-0 max-w-fit min-w-fit" | |
| 3 | + | > | |
| 2 | 4 | <div class="hidden md:flex justify-center p-4"> | |
| 3 | 5 | <img src="/assets/imgs/gordon-mascot-hq-trsp.png" alt="logo" class="h-32" /> | |
| 4 | 6 | </div> | |
| 5 | - | <div class="p-4 flex flex-col bg-gordonmascot-lblue rounded-lg shadow space-y-4"> | |
| 7 | + | <div | |
| 8 | + | class="p-4 xl:p-8 flex flex-col bg-gordonmascot-lblue rounded-lg shadow space-y-4" | |
| 9 | + | > | |
| 6 | 10 | <div class="w-full flex justify-center"> | |
| 7 | - | <h1 class="gordon-title text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter">Gordon</h1> | |
| 11 | + | <h1 | |
| 12 | + | class="gordon-title text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter" | |
| 13 | + | > | |
| 14 | + | Gordon | |
| 15 | + | </h1> | |
| 8 | 16 | <span class="text-gordonmascot-mblue text-sm">{{ .BuildVersion }}</span> | |
| 9 | 17 | </div> | |
| 10 | 18 | <div class="w-full"> | |
| 11 | 19 | <ul | |
| 12 | - | class="flex flex-wrap md:flex-col md:space-y-6 md:py-8 font-semibold text-gray-100 text-lg md:text-2xl md:pl-4 min-w-fit"> | |
| 20 | + | class="flex flex-wrap md:flex-col md:space-y-6 md:py-8 font-semibold text-gray-100 text-lg md:text-2xl min-w-fit justify-center" | |
| 21 | + | > | |
| 13 | 22 | <li class="flex items-center w-1/2 md:w-full"> | |
| 14 | 23 | <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i> | |
| 15 | 24 | <a href="/admin">Dashboard</a> | |
| @@ -18,15 +27,30 @@ | |||
| 18 | 27 | <i class="iconoir-server mr-2 md:text-3xl"></i> | |
| 19 | 28 | <a href="/admin/manager">Manager</a> | |
| 20 | 29 | </li> | |
| 21 | - | <li class="flex items-center w-1/2 md:w-full"> | |
| 30 | + | <li class="flex items-center w-1/2 md:w-full text-gray-600"> | |
| 22 | 31 | <i class="iconoir-community mr-2 md:text-3xl"></i> | |
| 23 | - | <a href="/admin/users">Users</a> | |
| 32 | + | <a title="Disabled" href="#">Users</a> | |
| 24 | 33 | </li> | |
| 25 | - | <li class="flex items-center w-1/2 md:w-full"> | |
| 34 | + | <li class="flex items-center w-1/2 md:w-full text-gray-600"> | |
| 26 | 35 | <i class="iconoir-database-settings mr-2 md:text-3xl"></i> | |
| 27 | - | <a href="/admin/settings">Settings</a> | |
| 36 | + | <a title="Disabled" href="#">Settings</a> | |
| 28 | 37 | </li> | |
| 29 | 38 | </ul> | |
| 30 | 39 | </div> | |
| 40 | + | <!-- Settings / Logout on the same line --> | |
| 41 | + | <div class="w-full flex justify-center space-x-4"> | |
| 42 | + | <a | |
| 43 | + | href="/admin/settings" | |
| 44 | + | title="User settings" | |
| 45 | + | class="iconoir-settings text-gordonmascot-mblue text-lg md:text-2xl" | |
| 46 | + | > | |
| 47 | + | </a> | |
| 48 | + | <a | |
| 49 | + | href="/admin/logout" | |
| 50 | + | title="Logout" | |
| 51 | + | class="iconoir-log-out text-gordonmascot-mblue text-lg md:text-2xl" | |
| 52 | + | > | |
| 53 | + | </a> | |
| 54 | + | </div> | |
| 31 | 55 | </div> | |
| 32 | - | </div>⏎̸ | |
| 56 | + | </div> | |
| @@ -1,15 +1,24 @@ | |||||
| 1 | - | <div class="w-full md:w-1/5 flex flex-col space-evenly p-4 md:p-0"> | 1 | + | <div |
| 2 | + | class="w-full md:w-1/5 flex flex-col space-evenly md:p-0 max-w-fit min-w-fit" | |||
| 3 | + | > | |||
| 2 | <div class="hidden md:flex justify-center p-4"> | 4 | <div class="hidden md:flex justify-center p-4"> | ||
| 3 | <img src="/assets/imgs/gordon-mascot-hq-trsp.png" alt="logo" class="h-32" /> | 5 | <img src="/assets/imgs/gordon-mascot-hq-trsp.png" alt="logo" class="h-32" /> | ||
| 4 | </div> | 6 | </div> | ||
| 5 | - | <div class="p-4 flex flex-col bg-gordonmascot-lblue rounded-lg shadow space-y-4"> | 7 | + | <div |
| 8 | + | class="p-4 xl:p-8 flex flex-col bg-gordonmascot-lblue rounded-lg shadow space-y-4" | |||
| 9 | + | > | |||
| 6 | <div class="w-full flex justify-center"> | 10 | <div class="w-full flex justify-center"> | ||
| 7 | - | <h1 class="gordon-title text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter">Gordon</h1> | 11 | + | <h1 |
| 12 | + | class="gordon-title text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter" | |||
| 13 | + | > | |||
| 14 | + | Gordon | |||
| 15 | + | </h1> | |||
| 8 | <span class="text-gordonmascot-mblue text-sm">{{ .BuildVersion }}</span> | 16 | <span class="text-gordonmascot-mblue text-sm">{{ .BuildVersion }}</span> | ||
| 9 | </div> | 17 | </div> | ||
| 10 | <div class="w-full"> | 18 | <div class="w-full"> | ||
| 11 | <ul | 19 | <ul | ||
| 12 | - | class="flex flex-wrap md:flex-col md:space-y-6 md:py-8 font-semibold text-gray-100 text-lg md:text-2xl md:pl-4 min-w-fit"> | 20 | + | class="flex flex-wrap md:flex-col md:space-y-6 md:py-8 font-semibold text-gray-100 text-lg md:text-2xl min-w-fit justify-center" |
| 21 | + | > | |||
| 13 | <li class="flex items-center w-1/2 md:w-full"> | 22 | <li class="flex items-center w-1/2 md:w-full"> | ||
| 14 | <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i> | 23 | <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i> | ||
| 15 | <a href="/admin">Dashboard</a> | 24 | <a href="/admin">Dashboard</a> | ||
| @@ -18,15 +27,30 @@ | |||||
| 18 | <i class="iconoir-server mr-2 md:text-3xl"></i> | 27 | <i class="iconoir-server mr-2 md:text-3xl"></i> | ||
| 19 | <a href="/admin/manager">Manager</a> | 28 | <a href="/admin/manager">Manager</a> | ||
| 20 | </li> | 29 | </li> | ||
| 21 | - | <li class="flex items-center w-1/2 md:w-full"> | 30 | + | <li class="flex items-center w-1/2 md:w-full text-gray-600"> |
| 22 | <i class="iconoir-community mr-2 md:text-3xl"></i> | 31 | <i class="iconoir-community mr-2 md:text-3xl"></i> | ||
| 23 | - | <a href="/admin/users">Users</a> | 32 | + | <a title="Disabled" href="#">Users</a> |
| 24 | </li> | 33 | </li> | ||
| 25 | - | <li class="flex items-center w-1/2 md:w-full"> | 34 | + | <li class="flex items-center w-1/2 md:w-full text-gray-600"> |
| 26 | <i class="iconoir-database-settings mr-2 md:text-3xl"></i> | 35 | <i class="iconoir-database-settings mr-2 md:text-3xl"></i> | ||
| 27 | - | <a href="/admin/settings">Settings</a> | 36 | + | <a title="Disabled" href="#">Settings</a> |
| 28 | </li> | 37 | </li> | ||
| 29 | </ul> | 38 | </ul> | ||
| 30 | </div> | 39 | </div> | ||
| 40 | + | <!-- Settings / Logout on the same line --> | |||
| 41 | + | <div class="w-full flex justify-center space-x-4"> | |||
| 42 | + | <a | |||
| 43 | + | href="/admin/settings" | |||
| 44 | + | title="User settings" | |||
| 45 | + | class="iconoir-settings text-gordonmascot-mblue text-lg md:text-2xl" | |||
| 46 | + | > | |||
| 47 | + | </a> | |||
| 48 | + | <a | |||
| 49 | + | href="/admin/logout" | |||
| 50 | + | title="Logout" | |||
| 51 | + | class="iconoir-log-out text-gordonmascot-mblue text-lg md:text-2xl" | |||
| 52 | + | > | |||
| 53 | + | </a> | |||
| 54 | + | </div> | |||
| 31 | </div> | 55 | </div> | ||
| 32 | - | </div>⏎̸ | 56 | + | </div> |
M internal/httpserve/handler/createcontainer.go +44 -9
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | package handler | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "net/http" | |
| 4 | + | "fmt" | |
| 5 | 5 | ||
| 6 | 6 | "github.com/bnema/gordon/internal/app" | |
| 7 | 7 | "github.com/bnema/gordon/internal/gotemplate/render" | |
| @@ -21,31 +21,46 @@ type CreateContainer struct { | |||
| 21 | 21 | Network string | |
| 22 | 22 | } | |
| 23 | 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") | |
| 24 | + | // FromShortIDToImageID converts a short image ID to a full image ID | |
| 25 | + | func FromShortIDToImageID(ShortID string) (string, error) { | |
| 28 | 26 | idMapMutex.Lock() | |
| 29 | 27 | // Check if the ShortImgID exists in the idMap | |
| 30 | 28 | imageID, exists := idMap[ShortID] | |
| 31 | 29 | idMapMutex.Unlock() | |
| 32 | 30 | ||
| 31 | + | if !exists { | |
| 32 | + | return "", fmt.Errorf("Invalid ShortImgID") | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | return imageID, nil | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | // CreateContainerRoute is the route for creating a new container | |
| 39 | + | func CreateContainerGET(c echo.Context, a *app.App) error { | |
| 40 | + | // Retreive the ShortID of the image from the URL | |
| 41 | + | ShortID := c.Param("ID") | |
| 42 | + | ||
| 43 | + | // Convert the ShortID to a full image ID | |
| 44 | + | imageID, err := FromShortIDToImageID(ShortID) | |
| 45 | + | if err != nil { | |
| 46 | + | return sendError(c, err) | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | // Get the image info | |
| 33 | 50 | imageInfo, err := docker.GetImageInfo(imageID) | |
| 34 | 51 | if err != nil { | |
| 35 | 52 | return sendError(c, err) | |
| 36 | 53 | } | |
| 54 | + | ||
| 37 | 55 | // Extract image name | |
| 38 | 56 | var imageName string | |
| 39 | 57 | if len(imageInfo.RepoTags) > 0 { | |
| 40 | 58 | imageName = imageInfo.RepoTags[0] // Using the first tag as the image name | |
| 41 | 59 | } | |
| 42 | 60 | ||
| 43 | - | if !exists { | |
| 44 | - | return c.String(http.StatusBadRequest, "Invalid ShortImgID") | |
| 45 | - | } | |
| 46 | - | ||
| 47 | 61 | data := map[string]interface{}{ | |
| 48 | 62 | "Title": "Create a new container", | |
| 63 | + | "ShortID": ShortID, | |
| 49 | 64 | "ImageName": imageName, | |
| 50 | 65 | } | |
| 51 | 66 | ||
| @@ -64,3 +79,23 @@ func CreateContainerGETHandler(c echo.Context, a *app.App) error { | |||
| 64 | 79 | return c.HTML(200, renderedHTML) | |
| 65 | 80 | ||
| 66 | 81 | } | |
| 82 | + | ||
| 83 | + | func CreateContainerPOST(c echo.Context, a *app.App) error { | |
| 84 | + | // Retreive the ShortID of the image from the URL | |
| 85 | + | ShortID := c.Param("ID") | |
| 86 | + | ||
| 87 | + | // Convert the ShortID to a full image ID | |
| 88 | + | imageID, err := FromShortIDToImageID(ShortID) | |
| 89 | + | if err != nil { | |
| 90 | + | return sendError(c, err) | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | // Get the image info | |
| 94 | + | _, err = docker.GetImageInfo(imageID) | |
| 95 | + | if err != nil { | |
| 96 | + | return sendError(c, err) | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | // Redirect to the container manager | |
| 100 | + | return c.Redirect(302, "/htmx/container-manager") | |
| 101 | + | } | |
| @@ -1,7 +1,7 @@ | |||||
| 1 | package handler | 1 | package handler | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | - | "net/http" | 4 | + | "fmt" |
| 5 | 5 | ||||
| 6 | "github.com/bnema/gordon/internal/app" | 6 | "github.com/bnema/gordon/internal/app" | ||
| 7 | "github.com/bnema/gordon/internal/gotemplate/render" | 7 | "github.com/bnema/gordon/internal/gotemplate/render" | ||
| @@ -21,31 +21,46 @@ type CreateContainer struct { | |||||
| 21 | Network string | 21 | Network string | ||
| 22 | } | 22 | } | ||
| 23 | 23 | ||||
| 24 | - | // CreateContainerRoute is the route for creating a new container | 24 | + | // FromShortIDToImageID converts a short image ID to a full image ID |
| 25 | - | func CreateContainerGETHandler(c echo.Context, a *app.App) error { | 25 | + | func FromShortIDToImageID(ShortID string) (string, error) { |
| 26 | - | // Retreive the image ID from the URL | |||
| 27 | - | ShortID := c.Param("ID") | |||
| 28 | idMapMutex.Lock() | 26 | idMapMutex.Lock() | ||
| 29 | // Check if the ShortImgID exists in the idMap | 27 | // Check if the ShortImgID exists in the idMap | ||
| 30 | imageID, exists := idMap[ShortID] | 28 | imageID, exists := idMap[ShortID] | ||
| 31 | idMapMutex.Unlock() | 29 | idMapMutex.Unlock() | ||
| 32 | 30 | ||||
| 31 | + | if !exists { | |||
| 32 | + | return "", fmt.Errorf("Invalid ShortImgID") | |||
| 33 | + | } | |||
| 34 | + | ||||
| 35 | + | return imageID, nil | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | // CreateContainerRoute is the route for creating a new container | |||
| 39 | + | func CreateContainerGET(c echo.Context, a *app.App) error { | |||
| 40 | + | // Retreive the ShortID of the image from the URL | |||
| 41 | + | ShortID := c.Param("ID") | |||
| 42 | + | ||||
| 43 | + | // Convert the ShortID to a full image ID | |||
| 44 | + | imageID, err := FromShortIDToImageID(ShortID) | |||
| 45 | + | if err != nil { | |||
| 46 | + | return sendError(c, err) | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | // Get the image info | |||
| 33 | imageInfo, err := docker.GetImageInfo(imageID) | 50 | imageInfo, err := docker.GetImageInfo(imageID) | ||
| 34 | if err != nil { | 51 | if err != nil { | ||
| 35 | return sendError(c, err) | 52 | return sendError(c, err) | ||
| 36 | } | 53 | } | ||
| 54 | + | ||||
| 37 | // Extract image name | 55 | // Extract image name | ||
| 38 | var imageName string | 56 | var imageName string | ||
| 39 | if len(imageInfo.RepoTags) > 0 { | 57 | if len(imageInfo.RepoTags) > 0 { | ||
| 40 | imageName = imageInfo.RepoTags[0] // Using the first tag as the image name | 58 | imageName = imageInfo.RepoTags[0] // Using the first tag as the image name | ||
| 41 | } | 59 | } | ||
| 42 | 60 | ||||
| 43 | - | if !exists { | |||
| 44 | - | return c.String(http.StatusBadRequest, "Invalid ShortImgID") | |||
| 45 | - | } | |||
| 46 | - | ||||
| 47 | data := map[string]interface{}{ | 61 | data := map[string]interface{}{ | ||
| 48 | "Title": "Create a new container", | 62 | "Title": "Create a new container", | ||
| 63 | + | "ShortID": ShortID, | |||
| 49 | "ImageName": imageName, | 64 | "ImageName": imageName, | ||
| 50 | } | 65 | } | ||
| 51 | 66 | ||||
| @@ -64,3 +79,23 @@ func CreateContainerGETHandler(c echo.Context, a *app.App) error { | |||||
| 64 | return c.HTML(200, renderedHTML) | 79 | return c.HTML(200, renderedHTML) | ||
| 65 | 80 | ||||
| 66 | } | 81 | } | ||
| 82 | + | ||||
| 83 | + | func CreateContainerPOST(c echo.Context, a *app.App) error { | |||
| 84 | + | // Retreive the ShortID of the image from the URL | |||
| 85 | + | ShortID := c.Param("ID") | |||
| 86 | + | ||||
| 87 | + | // Convert the ShortID to a full image ID | |||
| 88 | + | imageID, err := FromShortIDToImageID(ShortID) | |||
| 89 | + | if err != nil { | |||
| 90 | + | return sendError(c, err) | |||
| 91 | + | } | |||
| 92 | + | ||||
| 93 | + | // Get the image info | |||
| 94 | + | _, err = docker.GetImageInfo(imageID) | |||
| 95 | + | if err != nil { | |||
| 96 | + | return sendError(c, err) | |||
| 97 | + | } | |||
| 98 | + | ||||
| 99 | + | // Redirect to the container manager | |||
| 100 | + | return c.Redirect(302, "/htmx/container-manager") | |||
| 101 | + | } | |||
M internal/httpserve/router.go +4 -1
| @@ -89,6 +89,9 @@ func bindHTMXEndpoints(e *echo.Echo, a *app.App) { | |||
| 89 | 89 | return handler.UploadImagePOSTHandler(c, a) | |
| 90 | 90 | }) | |
| 91 | 91 | e.GET("htmx/create-container/:ID", func(c echo.Context) error { | |
| 92 | - | return handler.CreateContainerGETHandler(c, a) | |
| 92 | + | return handler.CreateContainerGET(c, a) | |
| 93 | + | }) | |
| 94 | + | e.POST("htmx/create-container/:ID", func(c echo.Context) error { | |
| 95 | + | return handler.CreateContainerPOST(c, a) | |
| 93 | 96 | }) | |
| 94 | 97 | } | |
| @@ -89,6 +89,9 @@ func bindHTMXEndpoints(e *echo.Echo, a *app.App) { | |||||
| 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 { | 91 | e.GET("htmx/create-container/:ID", func(c echo.Context) error { | ||
| 92 | - | return handler.CreateContainerGETHandler(c, a) | 92 | + | return handler.CreateContainerGET(c, a) |
| 93 | + | }) | |||
| 94 | + | e.POST("htmx/create-container/:ID", func(c echo.Context) error { | |||
| 95 | + | return handler.CreateContainerPOST(c, a) | |||
| 93 | }) | 96 | }) | ||
| 94 | } | 97 | } | ||
M internal/webui/public/assets/css/tailwind.css +63 -0
| @@ -580,6 +580,11 @@ video { | |||
| 580 | 580 | margin-bottom: 0.5rem; | |
| 581 | 581 | } | |
| 582 | 582 | ||
| 583 | + | .mx-2 { | |
| 584 | + | margin-left: 0.5rem; | |
| 585 | + | margin-right: 0.5rem; | |
| 586 | + | } | |
| 587 | + | ||
| 583 | 588 | .mb-1 { | |
| 584 | 589 | margin-bottom: 0.25rem; | |
| 585 | 590 | } | |
| @@ -665,6 +670,15 @@ video { | |||
| 665 | 670 | min-width: fit-content; | |
| 666 | 671 | } | |
| 667 | 672 | ||
| 673 | + | .max-w-lg { | |
| 674 | + | max-width: 32rem; | |
| 675 | + | } | |
| 676 | + | ||
| 677 | + | .max-w-fit { | |
| 678 | + | max-width: -moz-fit-content; | |
| 679 | + | max-width: fit-content; | |
| 680 | + | } | |
| 681 | + | ||
| 668 | 682 | .flex-1 { | |
| 669 | 683 | flex: 1 1 0%; | |
| 670 | 684 | } | |
| @@ -701,6 +715,18 @@ video { | |||
| 701 | 715 | justify-content: space-between; | |
| 702 | 716 | } | |
| 703 | 717 | ||
| 718 | + | .justify-around { | |
| 719 | + | justify-content: space-around; | |
| 720 | + | } | |
| 721 | + | ||
| 722 | + | .justify-evenly { | |
| 723 | + | justify-content: space-evenly; | |
| 724 | + | } | |
| 725 | + | ||
| 726 | + | .justify-stretch { | |
| 727 | + | justify-content: stretch; | |
| 728 | + | } | |
| 729 | + | ||
| 704 | 730 | .justify-items-center { | |
| 705 | 731 | justify-items: center; | |
| 706 | 732 | } | |
| @@ -717,6 +743,12 @@ video { | |||
| 717 | 743 | margin-bottom: calc(1rem * var(--tw-space-y-reverse)); | |
| 718 | 744 | } | |
| 719 | 745 | ||
| 746 | + | .space-x-4 > :not([hidden]) ~ :not([hidden]) { | |
| 747 | + | --tw-space-x-reverse: 0; | |
| 748 | + | margin-right: calc(1rem * var(--tw-space-x-reverse)); | |
| 749 | + | margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); | |
| 750 | + | } | |
| 751 | + | ||
| 720 | 752 | .rounded { | |
| 721 | 753 | border-radius: 0.25rem; | |
| 722 | 754 | } | |
| @@ -811,6 +843,10 @@ video { | |||
| 811 | 843 | padding: 1rem; | |
| 812 | 844 | } | |
| 813 | 845 | ||
| 846 | + | .p-8 { | |
| 847 | + | padding: 2rem; | |
| 848 | + | } | |
| 849 | + | ||
| 814 | 850 | .px-2 { | |
| 815 | 851 | padding-left: 0.5rem; | |
| 816 | 852 | padding-right: 0.5rem; | |
| @@ -835,6 +871,14 @@ video { | |||
| 835 | 871 | padding-bottom: 1rem; | |
| 836 | 872 | } | |
| 837 | 873 | ||
| 874 | + | .pl-2 { | |
| 875 | + | padding-left: 0.5rem; | |
| 876 | + | } | |
| 877 | + | ||
| 878 | + | .text-center { | |
| 879 | + | text-align: center; | |
| 880 | + | } | |
| 881 | + | ||
| 838 | 882 | .align-middle { | |
| 839 | 883 | vertical-align: middle; | |
| 840 | 884 | } | |
| @@ -931,6 +975,21 @@ video { | |||
| 931 | 975 | color: rgb(255 255 255 / var(--tw-text-opacity)); | |
| 932 | 976 | } | |
| 933 | 977 | ||
| 978 | + | .text-gray-300 { | |
| 979 | + | --tw-text-opacity: 1; | |
| 980 | + | color: rgb(209 213 219 / var(--tw-text-opacity)); | |
| 981 | + | } | |
| 982 | + | ||
| 983 | + | .text-gray-400 { | |
| 984 | + | --tw-text-opacity: 1; | |
| 985 | + | color: rgb(156 163 175 / var(--tw-text-opacity)); | |
| 986 | + | } | |
| 987 | + | ||
| 988 | + | .text-gray-600 { | |
| 989 | + | --tw-text-opacity: 1; | |
| 990 | + | color: rgb(75 85 99 / var(--tw-text-opacity)); | |
| 991 | + | } | |
| 992 | + | ||
| 934 | 993 | .underline { | |
| 935 | 994 | text-decoration-line: underline; | |
| 936 | 995 | } | |
| @@ -1185,6 +1244,10 @@ video { | |||
| 1185 | 1244 | height: 12rem; | |
| 1186 | 1245 | } | |
| 1187 | 1246 | ||
| 1247 | + | .xl\:p-8 { | |
| 1248 | + | padding: 2rem; | |
| 1249 | + | } | |
| 1250 | + | ||
| 1188 | 1251 | .xl\:text-5xl { | |
| 1189 | 1252 | font-size: 3rem; | |
| 1190 | 1253 | line-height: 1; | |
| @@ -580,6 +580,11 @@ video { | |||||
| 580 | margin-bottom: 0.5rem; | 580 | margin-bottom: 0.5rem; | ||
| 581 | } | 581 | } | ||
| 582 | 582 | ||||
| 583 | + | .mx-2 { | |||
| 584 | + | margin-left: 0.5rem; | |||
| 585 | + | margin-right: 0.5rem; | |||
| 586 | + | } | |||
| 587 | + | ||||
| 583 | .mb-1 { | 588 | .mb-1 { | ||
| 584 | margin-bottom: 0.25rem; | 589 | margin-bottom: 0.25rem; | ||
| 585 | } | 590 | } | ||
| @@ -665,6 +670,15 @@ video { | |||||
| 665 | min-width: fit-content; | 670 | min-width: fit-content; | ||
| 666 | } | 671 | } | ||
| 667 | 672 | ||||
| 673 | + | .max-w-lg { | |||
| 674 | + | max-width: 32rem; | |||
| 675 | + | } | |||
| 676 | + | ||||
| 677 | + | .max-w-fit { | |||
| 678 | + | max-width: -moz-fit-content; | |||
| 679 | + | max-width: fit-content; | |||
| 680 | + | } | |||
| 681 | + | ||||
| 668 | .flex-1 { | 682 | .flex-1 { | ||
| 669 | flex: 1 1 0%; | 683 | flex: 1 1 0%; | ||
| 670 | } | 684 | } | ||
| @@ -701,6 +715,18 @@ video { | |||||
| 701 | justify-content: space-between; | 715 | justify-content: space-between; | ||
| 702 | } | 716 | } | ||
| 703 | 717 | ||||
| 718 | + | .justify-around { | |||
| 719 | + | justify-content: space-around; | |||
| 720 | + | } | |||
| 721 | + | ||||
| 722 | + | .justify-evenly { | |||
| 723 | + | justify-content: space-evenly; | |||
| 724 | + | } | |||
| 725 | + | ||||
| 726 | + | .justify-stretch { | |||
| 727 | + | justify-content: stretch; | |||
| 728 | + | } | |||
| 729 | + | ||||
| 704 | .justify-items-center { | 730 | .justify-items-center { | ||
| 705 | justify-items: center; | 731 | justify-items: center; | ||
| 706 | } | 732 | } | ||
| @@ -717,6 +743,12 @@ video { | |||||
| 717 | margin-bottom: calc(1rem * var(--tw-space-y-reverse)); | 743 | margin-bottom: calc(1rem * var(--tw-space-y-reverse)); | ||
| 718 | } | 744 | } | ||
| 719 | 745 | ||||
| 746 | + | .space-x-4 > :not([hidden]) ~ :not([hidden]) { | |||
| 747 | + | --tw-space-x-reverse: 0; | |||
| 748 | + | margin-right: calc(1rem * var(--tw-space-x-reverse)); | |||
| 749 | + | margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); | |||
| 750 | + | } | |||
| 751 | + | ||||
| 720 | .rounded { | 752 | .rounded { | ||
| 721 | border-radius: 0.25rem; | 753 | border-radius: 0.25rem; | ||
| 722 | } | 754 | } | ||
| @@ -811,6 +843,10 @@ video { | |||||
| 811 | padding: 1rem; | 843 | padding: 1rem; | ||
| 812 | } | 844 | } | ||
| 813 | 845 | ||||
| 846 | + | .p-8 { | |||
| 847 | + | padding: 2rem; | |||
| 848 | + | } | |||
| 849 | + | ||||
| 814 | .px-2 { | 850 | .px-2 { | ||
| 815 | padding-left: 0.5rem; | 851 | padding-left: 0.5rem; | ||
| 816 | padding-right: 0.5rem; | 852 | padding-right: 0.5rem; | ||
| @@ -835,6 +871,14 @@ video { | |||||
| 835 | padding-bottom: 1rem; | 871 | padding-bottom: 1rem; | ||
| 836 | } | 872 | } | ||
| 837 | 873 | ||||
| 874 | + | .pl-2 { | |||
| 875 | + | padding-left: 0.5rem; | |||
| 876 | + | } | |||
| 877 | + | ||||
| 878 | + | .text-center { | |||
| 879 | + | text-align: center; | |||
| 880 | + | } | |||
| 881 | + | ||||
| 838 | .align-middle { | 882 | .align-middle { | ||
| 839 | vertical-align: middle; | 883 | vertical-align: middle; | ||
| 840 | } | 884 | } | ||
| @@ -931,6 +975,21 @@ video { | |||||
| 931 | color: rgb(255 255 255 / var(--tw-text-opacity)); | 975 | color: rgb(255 255 255 / var(--tw-text-opacity)); | ||
| 932 | } | 976 | } | ||
| 933 | 977 | ||||
| 978 | + | .text-gray-300 { | |||
| 979 | + | --tw-text-opacity: 1; | |||
| 980 | + | color: rgb(209 213 219 / var(--tw-text-opacity)); | |||
| 981 | + | } | |||
| 982 | + | ||||
| 983 | + | .text-gray-400 { | |||
| 984 | + | --tw-text-opacity: 1; | |||
| 985 | + | color: rgb(156 163 175 / var(--tw-text-opacity)); | |||
| 986 | + | } | |||
| 987 | + | ||||
| 988 | + | .text-gray-600 { | |||
| 989 | + | --tw-text-opacity: 1; | |||
| 990 | + | color: rgb(75 85 99 / var(--tw-text-opacity)); | |||
| 991 | + | } | |||
| 992 | + | ||||
| 934 | .underline { | 993 | .underline { | ||
| 935 | text-decoration-line: underline; | 994 | text-decoration-line: underline; | ||
| 936 | } | 995 | } | ||
| @@ -1185,6 +1244,10 @@ video { | |||||
| 1185 | height: 12rem; | 1244 | height: 12rem; | ||
| 1186 | } | 1245 | } | ||
| 1187 | 1246 | ||||
| 1247 | + | .xl\:p-8 { | |||
| 1248 | + | padding: 2rem; | |||
| 1249 | + | } | |||
| 1250 | + | ||||
| 1188 | .xl\:text-5xl { | 1251 | .xl\:text-5xl { | ||
| 1189 | font-size: 3rem; | 1252 | font-size: 3rem; | ||
| 1190 | line-height: 1; | 1253 | line-height: 1; | ||