Refactor action response handling and enhance UI toggle states
Backend: - Refactor common action responses to use a centralized function ActionSuccess. - Modify HTTP responses to use ActionSuccess in multiple handlers. Frontend: - Implement Hyperscript for toggle state management in upload and create-container buttons. - Update HMTX target to a common div for all action responses. - Remove redundant JavaScript for toggle states and element removal. - Update libraries and fix broken links in header.
- commit
- fa69cfb03df3e438738399b2919e4610af286ed5
- author
- bnema · · 2023-10-20 16:32 UTC
- parent
- d515416
- signature
- unsigned
15 files · +3907 -134
this diff is incomplete: 1 file is too large to show. the totals above count everything; clone the repository to read the rest.
M internal/httpserve/handler/containers.go +20 -8
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | 7 | "time" | |
| 8 | 8 | ||
| 9 | 9 | "github.com/bnema/gordon/internal/app" | |
| 10 | + | "github.com/bnema/gordon/internal/templating/load" | |
| 10 | 11 | "github.com/bnema/gordon/internal/templating/render" | |
| 11 | 12 | "github.com/bnema/gordon/pkg/docker" | |
| 12 | 13 | "github.com/bnema/gordon/pkg/humanize" | |
| @@ -14,8 +15,10 @@ import ( | |||
| 14 | 15 | "github.com/labstack/echo/v4" | |
| 15 | 16 | ) | |
| 16 | 17 | ||
| 17 | - | var idMap = make(map[string]string) | |
| 18 | - | var idMapMutex sync.Mutex | |
| 18 | + | var ( | |
| 19 | + | idMap = make(map[string]string) | |
| 20 | + | idMapMutex sync.Mutex | |
| 21 | + | ) | |
| 19 | 22 | ||
| 20 | 23 | type HumanReadableContainerImage struct { | |
| 21 | 24 | *types.ImageSummary | |
| @@ -36,6 +39,16 @@ type HumanReadableContainer struct { | |||
| 36 | 39 | StateColor string | |
| 37 | 40 | } | |
| 38 | 41 | ||
| 42 | + | func ActionSuccess(a *app.App) string { | |
| 43 | + | successFragment, err := load.Fragment(a, "success") | |
| 44 | + | if err != nil { | |
| 45 | + | fmt.Println(err) | |
| 46 | + | return "" | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | return successFragment | |
| 50 | + | } | |
| 51 | + | ||
| 39 | 52 | // ImageManagerComponent handles the /image-manager route (HTMX route) | |
| 40 | 53 | func ImageManagerComponent(c echo.Context, a *app.App) error { | |
| 41 | 54 | images, err := docker.ListContainerImages() | |
| @@ -103,8 +116,7 @@ func ImageManagerDelete(c echo.Context, a *app.App) error { | |||
| 103 | 116 | delete(idMap, ShortID) | |
| 104 | 117 | idMapMutex.Unlock() | |
| 105 | 118 | ||
| 106 | - | // Since it is HTMX we return a HTML div with the message "Removed" | |
| 107 | - | return c.HTML(http.StatusOK, `<div>Removed</div>`) | |
| 119 | + | return c.String(http.StatusOK, ActionSuccess(a)) | |
| 108 | 120 | } | |
| 109 | 121 | ||
| 110 | 122 | // ContainerManagerComponent handles the /container-manager route | |
| @@ -169,7 +181,7 @@ func ContainerManagerDelete(c echo.Context, a *app.App) error { | |||
| 169 | 181 | if err != nil { | |
| 170 | 182 | return sendError(c, err) | |
| 171 | 183 | } | |
| 172 | - | return c.HTML(http.StatusOK, `Success`) | |
| 184 | + | return c.HTML(http.StatusOK, ActionSuccess(a)) | |
| 173 | 185 | } | |
| 174 | 186 | ||
| 175 | 187 | // ContainerManagerStop handles the /container-manager/stop route | |
| @@ -182,7 +194,7 @@ func ContainerManagerStop(c echo.Context, a *app.App) error { | |||
| 182 | 194 | } | |
| 183 | 195 | // if stopped is true, the container was stopped gracefully | |
| 184 | 196 | if stopped { | |
| 185 | - | return c.HTML(http.StatusOK, `Success`) | |
| 197 | + | return c.HTML(http.StatusOK, ActionSuccess(a)) | |
| 186 | 198 | } | |
| 187 | 199 | // If stopped is false, the container was not stopped gracefully we force stop it | |
| 188 | 200 | err = docker.StopContainerRagefully(ID) | |
| @@ -190,7 +202,7 @@ func ContainerManagerStop(c echo.Context, a *app.App) error { | |||
| 190 | 202 | return sendError(c, err) | |
| 191 | 203 | } | |
| 192 | 204 | ||
| 193 | - | return c.HTML(http.StatusOK, `Success`) | |
| 205 | + | return c.HTML(http.StatusOK, ActionSuccess(a)) | |
| 194 | 206 | } | |
| 195 | 207 | ||
| 196 | 208 | // ContainerManagerStart handles the /container-manager/start route | |
| @@ -200,5 +212,5 @@ func ContainerManagerStart(c echo.Context, a *app.App) error { | |||
| 200 | 212 | if err != nil { | |
| 201 | 213 | return sendError(c, err) | |
| 202 | 214 | } | |
| 203 | - | return c.HTML(http.StatusOK, `Success`) | |
| 215 | + | return c.HTML(http.StatusOK, ActionSuccess(a)) | |
| 204 | 216 | } | |
| @@ -7,6 +7,7 @@ import ( | |||||
| 7 | "time" | 7 | "time" | ||
| 8 | 8 | ||||
| 9 | "github.com/bnema/gordon/internal/app" | 9 | "github.com/bnema/gordon/internal/app" | ||
| 10 | + | "github.com/bnema/gordon/internal/templating/load" | |||
| 10 | "github.com/bnema/gordon/internal/templating/render" | 11 | "github.com/bnema/gordon/internal/templating/render" | ||
| 11 | "github.com/bnema/gordon/pkg/docker" | 12 | "github.com/bnema/gordon/pkg/docker" | ||
| 12 | "github.com/bnema/gordon/pkg/humanize" | 13 | "github.com/bnema/gordon/pkg/humanize" | ||
| @@ -14,8 +15,10 @@ import ( | |||||
| 14 | "github.com/labstack/echo/v4" | 15 | "github.com/labstack/echo/v4" | ||
| 15 | ) | 16 | ) | ||
| 16 | 17 | ||||
| 17 | - | var idMap = make(map[string]string) | 18 | + | var ( |
| 18 | - | var idMapMutex sync.Mutex | 19 | + | idMap = make(map[string]string) |
| 20 | + | idMapMutex sync.Mutex | |||
| 21 | + | ) | |||
| 19 | 22 | ||||
| 20 | type HumanReadableContainerImage struct { | 23 | type HumanReadableContainerImage struct { | ||
| 21 | *types.ImageSummary | 24 | *types.ImageSummary | ||
| @@ -36,6 +39,16 @@ type HumanReadableContainer struct { | |||||
| 36 | StateColor string | 39 | StateColor string | ||
| 37 | } | 40 | } | ||
| 38 | 41 | ||||
| 42 | + | func ActionSuccess(a *app.App) string { | |||
| 43 | + | successFragment, err := load.Fragment(a, "success") | |||
| 44 | + | if err != nil { | |||
| 45 | + | fmt.Println(err) | |||
| 46 | + | return "" | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | return successFragment | |||
| 50 | + | } | |||
| 51 | + | ||||
| 39 | // ImageManagerComponent handles the /image-manager route (HTMX route) | 52 | // ImageManagerComponent handles the /image-manager route (HTMX route) | ||
| 40 | func ImageManagerComponent(c echo.Context, a *app.App) error { | 53 | func ImageManagerComponent(c echo.Context, a *app.App) error { | ||
| 41 | images, err := docker.ListContainerImages() | 54 | images, err := docker.ListContainerImages() | ||
| @@ -103,8 +116,7 @@ func ImageManagerDelete(c echo.Context, a *app.App) error { | |||||
| 103 | delete(idMap, ShortID) | 116 | delete(idMap, ShortID) | ||
| 104 | idMapMutex.Unlock() | 117 | idMapMutex.Unlock() | ||
| 105 | 118 | ||||
| 106 | - | // Since it is HTMX we return a HTML div with the message "Removed" | 119 | + | return c.String(http.StatusOK, ActionSuccess(a)) |
| 107 | - | return c.HTML(http.StatusOK, `<div>Removed</div>`) | |||
| 108 | } | 120 | } | ||
| 109 | 121 | ||||
| 110 | // ContainerManagerComponent handles the /container-manager route | 122 | // ContainerManagerComponent handles the /container-manager route | ||
| @@ -169,7 +181,7 @@ func ContainerManagerDelete(c echo.Context, a *app.App) error { | |||||
| 169 | if err != nil { | 181 | if err != nil { | ||
| 170 | return sendError(c, err) | 182 | return sendError(c, err) | ||
| 171 | } | 183 | } | ||
| 172 | - | return c.HTML(http.StatusOK, `Success`) | 184 | + | return c.HTML(http.StatusOK, ActionSuccess(a)) |
| 173 | } | 185 | } | ||
| 174 | 186 | ||||
| 175 | // ContainerManagerStop handles the /container-manager/stop route | 187 | // ContainerManagerStop handles the /container-manager/stop route | ||
| @@ -182,7 +194,7 @@ func ContainerManagerStop(c echo.Context, a *app.App) error { | |||||
| 182 | } | 194 | } | ||
| 183 | // if stopped is true, the container was stopped gracefully | 195 | // if stopped is true, the container was stopped gracefully | ||
| 184 | if stopped { | 196 | if stopped { | ||
| 185 | - | return c.HTML(http.StatusOK, `Success`) | 197 | + | return c.HTML(http.StatusOK, ActionSuccess(a)) |
| 186 | } | 198 | } | ||
| 187 | // If stopped is false, the container was not stopped gracefully we force stop it | 199 | // If stopped is false, the container was not stopped gracefully we force stop it | ||
| 188 | err = docker.StopContainerRagefully(ID) | 200 | err = docker.StopContainerRagefully(ID) | ||
| @@ -190,7 +202,7 @@ func ContainerManagerStop(c echo.Context, a *app.App) error { | |||||
| 190 | return sendError(c, err) | 202 | return sendError(c, err) | ||
| 191 | } | 203 | } | ||
| 192 | 204 | ||||
| 193 | - | return c.HTML(http.StatusOK, `Success`) | 205 | + | return c.HTML(http.StatusOK, ActionSuccess(a)) |
| 194 | } | 206 | } | ||
| 195 | 207 | ||||
| 196 | // ContainerManagerStart handles the /container-manager/start route | 208 | // ContainerManagerStart handles the /container-manager/start route | ||
| @@ -200,5 +212,5 @@ func ContainerManagerStart(c echo.Context, a *app.App) error { | |||||
| 200 | if err != nil { | 212 | if err != nil { | ||
| 201 | return sendError(c, err) | 213 | return sendError(c, err) | ||
| 202 | } | 214 | } | ||
| 203 | - | return c.HTML(http.StatusOK, `Success`) | 215 | + | return c.HTML(http.StatusOK, ActionSuccess(a)) |
| 204 | } | 216 | } | ||
M internal/httpserve/handler/uploadcontainerimage.go +1 -1
| @@ -60,5 +60,5 @@ func UploadImagePOSTHandler(c echo.Context, a *app.App) error { | |||
| 60 | 60 | if err != nil { | |
| 61 | 61 | return sendError(c, err) | |
| 62 | 62 | } | |
| 63 | - | return c.HTML(http.StatusOK, "Success") | |
| 63 | + | return c.HTML(http.StatusOK, ActionSuccess(a)) | |
| 64 | 64 | } | |
| @@ -60,5 +60,5 @@ func UploadImagePOSTHandler(c echo.Context, a *app.App) error { | |||||
| 60 | if err != nil { | 60 | if err != nil { | ||
| 61 | return sendError(c, err) | 61 | return sendError(c, err) | ||
| 62 | } | 62 | } | ||
| 63 | - | return c.HTML(http.StatusOK, "Success") | 63 | + | return c.HTML(http.StatusOK, ActionSuccess(a)) |
| 64 | } | 64 | } | ||
A internal/templating/load/loadfragment.go +28 -0
| @@ -0,0 +1,28 @@ | |||
| 1 | + | package load | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "fmt" | |
| 5 | + | "io" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/gordon/internal/app" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | // LoadFragment loads a specific HTML fragment from the app's TemplateFS | |
| 11 | + | func Fragment(a *app.App, fragmentName string) (string, error) { | |
| 12 | + | filePath := fmt.Sprintf("html/fragments/%s.gohtml", fragmentName) | |
| 13 | + | ||
| 14 | + | // Open the file | |
| 15 | + | file, err := a.TemplateFS.Open(filePath) | |
| 16 | + | if err != nil { | |
| 17 | + | return "", err | |
| 18 | + | } | |
| 19 | + | defer file.Close() | |
| 20 | + | ||
| 21 | + | // Read the file's content | |
| 22 | + | content, err := io.ReadAll(file) | |
| 23 | + | if err != nil { | |
| 24 | + | return "", err | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | return string(content), nil | |
| 28 | + | } | |
| @@ -0,0 +1,28 @@ | |||||
| 1 | + | package load | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "fmt" | |||
| 5 | + | "io" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/gordon/internal/app" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | // LoadFragment loads a specific HTML fragment from the app's TemplateFS | |||
| 11 | + | func Fragment(a *app.App, fragmentName string) (string, error) { | |||
| 12 | + | filePath := fmt.Sprintf("html/fragments/%s.gohtml", fragmentName) | |||
| 13 | + | ||||
| 14 | + | // Open the file | |||
| 15 | + | file, err := a.TemplateFS.Open(filePath) | |||
| 16 | + | if err != nil { | |||
| 17 | + | return "", err | |||
| 18 | + | } | |||
| 19 | + | defer file.Close() | |||
| 20 | + | ||||
| 21 | + | // Read the file's content | |||
| 22 | + | content, err := io.ReadAll(file) | |||
| 23 | + | if err != nil { | |||
| 24 | + | return "", err | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | return string(content), nil | |||
| 28 | + | } | |||
M internal/templating/models/html/admin/manager.gohtml +0 -3
| @@ -9,9 +9,6 @@ | |||
| 9 | 9 | <div class="flex-1 flex flex-col space-y-4 pt-16 md:pt-32 md:px-6"> | |
| 10 | 10 | <div class="flex flex-col md:flex-row w-full flex-grow"> | |
| 11 | 11 | <div class="flex-grow bg-white p-4 md:rounded-md md:shadow mb-4"> | |
| 12 | - | <h1 class="text-lg md:text-2xl font-bold text-gray-400 IBMPlexSans justify-center text-center"> | |
| 13 | - | {{index .Lang "manager" "content" "title"}} | |
| 14 | - | </h1> | |
| 15 | 12 | <!-- List all the containers --> | |
| 16 | 13 | <div | |
| 17 | 14 | id="container-manager" | |
| @@ -9,9 +9,6 @@ | |||||
| 9 | <div class="flex-1 flex flex-col space-y-4 pt-16 md:pt-32 md:px-6"> | 9 | <div class="flex-1 flex flex-col space-y-4 pt-16 md:pt-32 md:px-6"> | ||
| 10 | <div class="flex flex-col md:flex-row w-full flex-grow"> | 10 | <div class="flex flex-col md:flex-row w-full flex-grow"> | ||
| 11 | <div class="flex-grow bg-white p-4 md:rounded-md md:shadow mb-4"> | 11 | <div class="flex-grow bg-white p-4 md:rounded-md md:shadow mb-4"> | ||
| 12 | - | <h1 class="text-lg md:text-2xl font-bold text-gray-400 IBMPlexSans justify-center text-center"> | |||
| 13 | - | {{index .Lang "manager" "content" "title"}} | |||
| 14 | - | </h1> | |||
| 15 | <!-- List all the containers --> | 12 | <!-- List all the containers --> | ||
| 16 | <div | 13 | <div | ||
| 17 | id="container-manager" | 14 | id="container-manager" | ||
M internal/templating/models/html/fragments/containerlist.gohtml +6 -4
| @@ -1,3 +1,5 @@ | |||
| 1 | + | <div id="actionResponse"></div> | |
| 2 | + | ||
| 1 | 3 | <div class="flex flex-col p-4" hx-boost="true"> | |
| 2 | 4 | <h1 class="text-lg md:text-xl font-bold text-gordonmascot-hblue"> | |
| 3 | 5 | {{index .Lang "manager" "content" "h1-containers"}} | |
| @@ -33,7 +35,7 @@ | |||
| 33 | 35 | <button | |
| 34 | 36 | id="start-button-{{.ID}}" | |
| 35 | 37 | hx-post="/htmx/container-manager/start/{{.ID}}" | |
| 36 | - | hx-target="#container-{{.ID}}" | |
| 38 | + | hx-target="#actionResponse" | |
| 37 | 39 | hx-target-*="#error-message-{{.ID}}" | |
| 38 | 40 | hx-swap="outerHTML" | |
| 39 | 41 | class="py-2 px-4" | |
| @@ -44,7 +46,7 @@ | |||
| 44 | 46 | <button | |
| 45 | 47 | id="edit-button-{{.ID}}" | |
| 46 | 48 | hx-get="/htmx/container-manager/edit/{{.ID}}" | |
| 47 | - | hx-target="#edit-container-{{.ID}}" | |
| 49 | + | hx-target="#actionResponse" | |
| 48 | 50 | hx-target-*="#error-message-{{.ID}}" | |
| 49 | 51 | hx-swap="innerHTML" | |
| 50 | 52 | class="py-2 px-4" | |
| @@ -56,7 +58,7 @@ | |||
| 56 | 58 | <button | |
| 57 | 59 | id="stop-button-{{.ID}}" | |
| 58 | 60 | hx-post="/htmx/container-manager/stop/{{.ID}}" | |
| 59 | - | hx-target="#container-{{.ID}}" | |
| 61 | + | hx-target="#actionResponse" | |
| 60 | 62 | hx-target-*="#error-message-{{.ID}}" | |
| 61 | 63 | hx-swap="innerHTML" | |
| 62 | 64 | hx-confirm="{{index $Lang "manager" "content" "confirm-container-stop"}}" | |
| @@ -68,7 +70,7 @@ | |||
| 68 | 70 | <button | |
| 69 | 71 | id="remove-button-img-{{.ID}}" | |
| 70 | 72 | hx-delete="/htmx/container-manager/delete/{{.ID}}" | |
| 71 | - | hx-target="#container-{{.ID}}" | |
| 73 | + | hx-target="#actionResponse" | |
| 72 | 74 | hx-target-*="#error-message-{{.ID}}" | |
| 73 | 75 | hx-swap="innerHTML" | |
| 74 | 76 | hx-confirm="{{index $Lang "manager" "content" "confirm-delete"}}" | |
| @@ -1,3 +1,5 @@ | |||||
| 1 | + | <div id="actionResponse"></div> | |||
| 2 | + | ||||
| 1 | <div class="flex flex-col p-4" hx-boost="true"> | 3 | <div class="flex flex-col p-4" hx-boost="true"> | ||
| 2 | <h1 class="text-lg md:text-xl font-bold text-gordonmascot-hblue"> | 4 | <h1 class="text-lg md:text-xl font-bold text-gordonmascot-hblue"> | ||
| 3 | {{index .Lang "manager" "content" "h1-containers"}} | 5 | {{index .Lang "manager" "content" "h1-containers"}} | ||
| @@ -33,7 +35,7 @@ | |||||
| 33 | <button | 35 | <button | ||
| 34 | id="start-button-{{.ID}}" | 36 | id="start-button-{{.ID}}" | ||
| 35 | hx-post="/htmx/container-manager/start/{{.ID}}" | 37 | hx-post="/htmx/container-manager/start/{{.ID}}" | ||
| 36 | - | hx-target="#container-{{.ID}}" | 38 | + | hx-target="#actionResponse" |
| 37 | hx-target-*="#error-message-{{.ID}}" | 39 | hx-target-*="#error-message-{{.ID}}" | ||
| 38 | hx-swap="outerHTML" | 40 | hx-swap="outerHTML" | ||
| 39 | class="py-2 px-4" | 41 | class="py-2 px-4" | ||
| @@ -44,7 +46,7 @@ | |||||
| 44 | <button | 46 | <button | ||
| 45 | id="edit-button-{{.ID}}" | 47 | id="edit-button-{{.ID}}" | ||
| 46 | hx-get="/htmx/container-manager/edit/{{.ID}}" | 48 | hx-get="/htmx/container-manager/edit/{{.ID}}" | ||
| 47 | - | hx-target="#edit-container-{{.ID}}" | 49 | + | hx-target="#actionResponse" |
| 48 | hx-target-*="#error-message-{{.ID}}" | 50 | hx-target-*="#error-message-{{.ID}}" | ||
| 49 | hx-swap="innerHTML" | 51 | hx-swap="innerHTML" | ||
| 50 | class="py-2 px-4" | 52 | class="py-2 px-4" | ||
| @@ -56,7 +58,7 @@ | |||||
| 56 | <button | 58 | <button | ||
| 57 | id="stop-button-{{.ID}}" | 59 | id="stop-button-{{.ID}}" | ||
| 58 | hx-post="/htmx/container-manager/stop/{{.ID}}" | 60 | hx-post="/htmx/container-manager/stop/{{.ID}}" | ||
| 59 | - | hx-target="#container-{{.ID}}" | 61 | + | hx-target="#actionResponse" |
| 60 | hx-target-*="#error-message-{{.ID}}" | 62 | hx-target-*="#error-message-{{.ID}}" | ||
| 61 | hx-swap="innerHTML" | 63 | hx-swap="innerHTML" | ||
| 62 | hx-confirm="{{index $Lang "manager" "content" "confirm-container-stop"}}" | 64 | hx-confirm="{{index $Lang "manager" "content" "confirm-container-stop"}}" | ||
| @@ -68,7 +70,7 @@ | |||||
| 68 | <button | 70 | <button | ||
| 69 | id="remove-button-img-{{.ID}}" | 71 | id="remove-button-img-{{.ID}}" | ||
| 70 | hx-delete="/htmx/container-manager/delete/{{.ID}}" | 72 | hx-delete="/htmx/container-manager/delete/{{.ID}}" | ||
| 71 | - | hx-target="#container-{{.ID}}" | 73 | + | hx-target="#actionResponse" |
| 72 | hx-target-*="#error-message-{{.ID}}" | 74 | hx-target-*="#error-message-{{.ID}}" | ||
| 73 | hx-swap="innerHTML" | 75 | hx-swap="innerHTML" | ||
| 74 | hx-confirm="{{index $Lang "manager" "content" "confirm-delete"}}" | 76 | hx-confirm="{{index $Lang "manager" "content" "confirm-delete"}}" | ||
M internal/templating/models/html/fragments/header.gohtml +2 -2
| @@ -4,11 +4,11 @@ | |||
| 4 | 4 | <title>{{ .header.meta.title }}</title> | |
| 5 | 5 | <link rel="stylesheet" href="/assets/css/tailwind.css" /> | |
| 6 | 6 | ||
| 7 | - | <script src="/libs/htmx.min.js"></script> | |
| 7 | + | <script src="/libs/htmx.js"></script> | |
| 8 | 8 | <script src="/libs/htmx-response-targets.js"></script> | |
| 9 | 9 | <script src="/libs/htmx-debug.js"></script> | |
| 10 | 10 | <script src="/libs/htmx-sse.js"></script> | |
| 11 | - | <script defer src="https://unpkg.com/hyperscript.org@0.9.11"></script> | |
| 11 | + | <script defer src="/libs/hyperscript.min.js"></script> | |
| 12 | 12 | <script defer src="/assets/js/custom.js"></script> | |
| 13 | 13 | <!-- script wait for the page to be loaded and insert <link rel="stylesheet" href="/libs/iconoir.css" /> --> | |
| 14 | 14 | <script> | |
| @@ -4,11 +4,11 @@ | |||||
| 4 | <title>{{ .header.meta.title }}</title> | 4 | <title>{{ .header.meta.title }}</title> | ||
| 5 | <link rel="stylesheet" href="/assets/css/tailwind.css" /> | 5 | <link rel="stylesheet" href="/assets/css/tailwind.css" /> | ||
| 6 | 6 | ||||
| 7 | - | <script src="/libs/htmx.min.js"></script> | 7 | + | <script src="/libs/htmx.js"></script> |
| 8 | <script src="/libs/htmx-response-targets.js"></script> | 8 | <script src="/libs/htmx-response-targets.js"></script> | ||
| 9 | <script src="/libs/htmx-debug.js"></script> | 9 | <script src="/libs/htmx-debug.js"></script> | ||
| 10 | <script src="/libs/htmx-sse.js"></script> | 10 | <script src="/libs/htmx-sse.js"></script> | ||
| 11 | - | <script defer src="https://unpkg.com/hyperscript.org@0.9.11"></script> | 11 | + | <script defer src="/libs/hyperscript.min.js"></script> |
| 12 | <script defer src="/assets/js/custom.js"></script> | 12 | <script defer src="/assets/js/custom.js"></script> | ||
| 13 | <!-- script wait for the page to be loaded and insert <link rel="stylesheet" href="/libs/iconoir.css" /> --> | 13 | <!-- script wait for the page to be loaded and insert <link rel="stylesheet" href="/libs/iconoir.css" /> --> | ||
| 14 | <script> | 14 | <script> | ||
M internal/templating/models/html/fragments/imagelist.gohtml +5 -26
| @@ -3,13 +3,14 @@ | |||
| 3 | 3 | <div class="flex justify-between items-center mb-1"> | |
| 4 | 4 | <h1 class="text-lg md:text-xl font-bold text-gordonmascot-hblue">Images</h1> | |
| 5 | 5 | <button | |
| 6 | + | class="py-1 px-2 bg-blue-500 hover:bg-blue-700 text-white font-bold rounded" | |
| 6 | 7 | id="upload-button" | |
| 7 | 8 | hx-get="/htmx/upload-image" | |
| 8 | 9 | hx-target="#upload-image" | |
| 9 | 10 | hx-swap="innerHTML" | |
| 10 | - | data-active="false" | |
| 11 | - | class="py-1 px-2 bg-blue-500 hover:bg-blue-700 text-white font-bold rounded" | |
| 11 | + | _="on click toggle between .visible and .hidden on #upload-image then add .hidden to #create-container" | |
| 12 | 12 | > | |
| 13 | + | ||
| 13 | 14 | <span class="iconoir-upload text-xl align-middle"></span> | |
| 14 | 15 | Upload | |
| 15 | 16 | </button> | |
| @@ -36,6 +37,7 @@ | |||
| 36 | 37 | hx-target-*="#error-message-div-{{.ID}}" | |
| 37 | 38 | hx-swap="innerHTML" | |
| 38 | 39 | class="py-2 px-4" | |
| 40 | + | _="on click toggle between .visible and .hidden on #create-container then add .hidden to #upload-image" | |
| 39 | 41 | > | |
| 40 | 42 | <span title="Deploy a container using this image" class="iconoir-add-circle text-xl text-blue-800 font-bold"></span> | |
| 41 | 43 | </button> | |
| @@ -69,27 +71,4 @@ | |||
| 69 | 71 | </table> | |
| 70 | 72 | <div id="upload-image"></div> | |
| 71 | 73 | <div id="create-container"></div> | |
| 72 | - | </div> | |
| 73 | - | ||
| 74 | - | <script> | |
| 75 | - | // Hide upload form if add button is clicked | |
| 76 | - | document.querySelectorAll('[id^="add-button-img-"]').forEach((button) => { | |
| 77 | - | button.addEventListener("click", () => { | |
| 78 | - | document.getElementById("upload-image").innerHTML = ""; | |
| 79 | - | }); | |
| 80 | - | }); | |
| 81 | - | ||
| 82 | - | // Hide create container form if upload button is clicke | |
| 83 | - | document.getElementById("upload-button").addEventListener("click", () => { | |
| 84 | - | document.getElementById("create-container").innerHTML = ""; | |
| 85 | - | }); | |
| 86 | - | ||
| 87 | - | // Remove image from list if remove button is clicked (after confirmation) | |
| 88 | - | document.querySelectorAll('[id^="remove-button-img-"]').forEach((button) => { | |
| 89 | - | button.addEventListener("click", () => { | |
| 90 | - | const id = button.id.split("-")[3]; | |
| 91 | - | const tr = document.getElementById(`img-${id}`); | |
| 92 | - | tr.style.display = "none"; | |
| 93 | - | }); | |
| 94 | - | }); | |
| 95 | - | </script>⏎̸ | |
| 74 | + | </div>⏎̸ | |
| @@ -3,13 +3,14 @@ | |||||
| 3 | <div class="flex justify-between items-center mb-1"> | 3 | <div class="flex justify-between items-center mb-1"> | ||
| 4 | <h1 class="text-lg md:text-xl font-bold text-gordonmascot-hblue">Images</h1> | 4 | <h1 class="text-lg md:text-xl font-bold text-gordonmascot-hblue">Images</h1> | ||
| 5 | <button | 5 | <button | ||
| 6 | + | class="py-1 px-2 bg-blue-500 hover:bg-blue-700 text-white font-bold rounded" | |||
| 6 | id="upload-button" | 7 | id="upload-button" | ||
| 7 | hx-get="/htmx/upload-image" | 8 | hx-get="/htmx/upload-image" | ||
| 8 | hx-target="#upload-image" | 9 | hx-target="#upload-image" | ||
| 9 | hx-swap="innerHTML" | 10 | hx-swap="innerHTML" | ||
| 10 | - | data-active="false" | 11 | + | _="on click toggle between .visible and .hidden on #upload-image then add .hidden to #create-container" |
| 11 | - | class="py-1 px-2 bg-blue-500 hover:bg-blue-700 text-white font-bold rounded" | |||
| 12 | > | 12 | > | ||
| 13 | + | ||||
| 13 | <span class="iconoir-upload text-xl align-middle"></span> | 14 | <span class="iconoir-upload text-xl align-middle"></span> | ||
| 14 | Upload | 15 | Upload | ||
| 15 | </button> | 16 | </button> | ||
| @@ -36,6 +37,7 @@ | |||||
| 36 | hx-target-*="#error-message-div-{{.ID}}" | 37 | hx-target-*="#error-message-div-{{.ID}}" | ||
| 37 | hx-swap="innerHTML" | 38 | hx-swap="innerHTML" | ||
| 38 | class="py-2 px-4" | 39 | class="py-2 px-4" | ||
| 40 | + | _="on click toggle between .visible and .hidden on #create-container then add .hidden to #upload-image" | |||
| 39 | > | 41 | > | ||
| 40 | <span title="Deploy a container using this image" class="iconoir-add-circle text-xl text-blue-800 font-bold"></span> | 42 | <span title="Deploy a container using this image" class="iconoir-add-circle text-xl text-blue-800 font-bold"></span> | ||
| 41 | </button> | 43 | </button> | ||
| @@ -69,27 +71,4 @@ | |||||
| 69 | </table> | 71 | </table> | ||
| 70 | <div id="upload-image"></div> | 72 | <div id="upload-image"></div> | ||
| 71 | <div id="create-container"></div> | 73 | <div id="create-container"></div> | ||
| 72 | - | </div> | 74 | + | </div>⏎̸ |
| 73 | - | ||||
| 74 | - | <script> | |||
| 75 | - | // Hide upload form if add button is clicked | |||
| 76 | - | document.querySelectorAll('[id^="add-button-img-"]').forEach((button) => { | |||
| 77 | - | button.addEventListener("click", () => { | |||
| 78 | - | document.getElementById("upload-image").innerHTML = ""; | |||
| 79 | - | }); | |||
| 80 | - | }); | |||
| 81 | - | ||||
| 82 | - | // Hide create container form if upload button is clicke | |||
| 83 | - | document.getElementById("upload-button").addEventListener("click", () => { | |||
| 84 | - | document.getElementById("create-container").innerHTML = ""; | |||
| 85 | - | }); | |||
| 86 | - | ||||
| 87 | - | // Remove image from list if remove button is clicked (after confirmation) | |||
| 88 | - | document.querySelectorAll('[id^="remove-button-img-"]').forEach((button) => { | |||
| 89 | - | button.addEventListener("click", () => { | |||
| 90 | - | const id = button.id.split("-")[3]; | |||
| 91 | - | const tr = document.getElementById(`img-${id}`); | |||
| 92 | - | tr.style.display = "none"; | |||
| 93 | - | }); | |||
| 94 | - | }); | |||
| 95 | - | </script>⏎̸ | |||
A internal/templating/models/html/fragments/success.gohtml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | <div | |
| 2 | + | id="success" | |
| 3 | + | _="on load wait 1s then transition opacity to 0 then remove me then wait 1s then call window.location.reload()" | |
| 4 | + | class="flex text-blue-800 content-center justify-center items-center w-full h-full"> | |
| 5 | + | <p class="text-2xl text-center">Success!</p> | |
| 6 | + | </div> | |
| @@ -0,0 +1,6 @@ | |||||
| 1 | + | <div | |||
| 2 | + | id="success" | |||
| 3 | + | _="on load wait 1s then transition opacity to 0 then remove me then wait 1s then call window.location.reload()" | |||
| 4 | + | class="flex text-blue-800 content-center justify-center items-center w-full h-full"> | |||
| 5 | + | <p class="text-2xl text-center">Success!</p> | |||
| 6 | + | </div> | |||
M internal/templating/models/html/fragments/uploadimage.gohtml +6 -10
| @@ -2,8 +2,7 @@ | |||
| 2 | 2 | <div | |
| 3 | 3 | class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow" | |
| 4 | 4 | hx-ext="response-targets" | |
| 5 | - | hx-boost="true" | |
| 6 | - | > | |
| 5 | + | hx-boost="true"> | |
| 7 | 6 | <div class="flex-grow bg-slate-100 p-4 rounded-md"> | |
| 8 | 7 | <h2>Upload Image Containers</h2> | |
| 9 | 8 | <!-- Upload Form --> | |
| @@ -14,25 +13,22 @@ | |||
| 14 | 13 | hx-post="/htmx/upload-image" | |
| 15 | 14 | hx-swap="innerHTML" | |
| 16 | 15 | hx-target="#image-manager" | |
| 17 | - | hx-target-*="#error-message-div" | |
| 18 | - | > | |
| 16 | + | hx-target-*="#error-message-div"> | |
| 19 | 17 | <div | |
| 20 | 18 | class="upload-zone w-full h-44 bg-slate-200 rounded-md p-4" | |
| 21 | - | hx-drop="true" | |
| 22 | - | > | |
| 19 | + | hx-drop="true"> | |
| 23 | 20 | Drag & Drop your file here or | |
| 24 | 21 | <input | |
| 25 | 22 | type="file" | |
| 26 | 23 | name="file" | |
| 27 | 24 | accept=".tar" | |
| 28 | - | class="bg-transparent outline-none" | |
| 29 | - | /> | |
| 25 | + | class="bg-transparent outline-none" /> | |
| 30 | 26 | </div> | |
| 31 | 27 | <input | |
| 32 | 28 | type="submit" | |
| 33 | 29 | value="Upload" | |
| 34 | - | class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" | |
| 35 | - | /> | |
| 30 | + | _="on click toggle @disabled until htmx:afterOnLoad" | |
| 31 | + | class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" /> | |
| 36 | 32 | </form> | |
| 37 | 33 | <div id="error-message-div" class="text-red-500"></div> | |
| 38 | 34 | </div> | |
| @@ -2,8 +2,7 @@ | |||||
| 2 | <div | 2 | <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 | - | > | |||
| 7 | <div class="flex-grow bg-slate-100 p-4 rounded-md"> | 6 | <div class="flex-grow bg-slate-100 p-4 rounded-md"> | ||
| 8 | <h2>Upload Image Containers</h2> | 7 | <h2>Upload Image Containers</h2> | ||
| 9 | <!-- Upload Form --> | 8 | <!-- Upload Form --> | ||
| @@ -14,25 +13,22 @@ | |||||
| 14 | hx-post="/htmx/upload-image" | 13 | hx-post="/htmx/upload-image" | ||
| 15 | hx-swap="innerHTML" | 14 | hx-swap="innerHTML" | ||
| 16 | hx-target="#image-manager" | 15 | hx-target="#image-manager" | ||
| 17 | - | hx-target-*="#error-message-div" | 16 | + | hx-target-*="#error-message-div"> |
| 18 | - | > | |||
| 19 | <div | 17 | <div | ||
| 20 | class="upload-zone w-full h-44 bg-slate-200 rounded-md p-4" | 18 | class="upload-zone w-full h-44 bg-slate-200 rounded-md p-4" | ||
| 21 | - | hx-drop="true" | 19 | + | hx-drop="true"> |
| 22 | - | > | |||
| 23 | Drag & Drop your file here or | 20 | Drag & Drop your file here or | ||
| 24 | <input | 21 | <input | ||
| 25 | type="file" | 22 | type="file" | ||
| 26 | name="file" | 23 | name="file" | ||
| 27 | accept=".tar" | 24 | accept=".tar" | ||
| 28 | - | class="bg-transparent outline-none" | 25 | + | class="bg-transparent outline-none" /> |
| 29 | - | /> | |||
| 30 | </div> | 26 | </div> | ||
| 31 | <input | 27 | <input | ||
| 32 | type="submit" | 28 | type="submit" | ||
| 33 | value="Upload" | 29 | value="Upload" | ||
| 34 | - | class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" | 30 | + | _="on click toggle @disabled until htmx:afterOnLoad" |
| 35 | - | /> | 31 | + | class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" /> |
| 36 | </form> | 32 | </form> | ||
| 37 | <div id="error-message-div" class="text-red-500"></div> | 33 | <div id="error-message-div" class="text-red-500"></div> | ||
| 38 | </div> | 34 | </div> | ||
M internal/webui/public/assets/css/custom.css +7 -5
| @@ -37,7 +37,8 @@ | |||
| 37 | 37 | /* Import Jetbrains Mono Nerd Font Bold */ | |
| 38 | 38 | @font-face { | |
| 39 | 39 | font-family: "JetBrainsMono-Nerd-Font-Bold"; | |
| 40 | - | src: url("/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFont-Bold.woff2") format("woff2"); | |
| 40 | + | src: url("/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFont-Bold.woff2") | |
| 41 | + | format("woff2"); | |
| 41 | 42 | font-weight: bold; | |
| 42 | 43 | font-style: normal; | |
| 43 | 44 | } | |
| @@ -84,17 +85,18 @@ | |||
| 84 | 85 | } | |
| 85 | 86 | ||
| 86 | 87 | ::view-transition-old(slide-it) { | |
| 87 | - | animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out, | |
| 88 | + | animation: | |
| 89 | + | 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out, | |
| 88 | 90 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; | |
| 89 | 91 | } | |
| 90 | 92 | ::view-transition-new(slide-it) { | |
| 91 | - | animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, | |
| 93 | + | animation: | |
| 94 | + | 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, | |
| 92 | 95 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; | |
| 93 | 96 | } | |
| 94 | 97 | ||
| 95 | 98 | .gordon-title { | |
| 96 | 99 | font-family: "LilitaOne-Regular", sans-serif; | |
| 97 | - | ||
| 98 | 100 | } | |
| 99 | 101 | ||
| 100 | 102 | .IBMPlexSans { | |
| @@ -107,4 +109,4 @@ | |||
| 107 | 109 | ||
| 108 | 110 | .JetBrainsMonoBoldNF { | |
| 109 | 111 | font-family: "JetBrainsMono-Nerd-Font-Bold", sans-serif; | |
| 110 | - | }⏎̸ | |
| 112 | + | } | |
| @@ -37,7 +37,8 @@ | |||||
| 37 | /* Import Jetbrains Mono Nerd Font Bold */ | 37 | /* Import Jetbrains Mono Nerd Font Bold */ | ||
| 38 | @font-face { | 38 | @font-face { | ||
| 39 | font-family: "JetBrainsMono-Nerd-Font-Bold"; | 39 | font-family: "JetBrainsMono-Nerd-Font-Bold"; | ||
| 40 | - | src: url("/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFont-Bold.woff2") format("woff2"); | 40 | + | src: url("/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFont-Bold.woff2") |
| 41 | + | format("woff2"); | |||
| 41 | font-weight: bold; | 42 | font-weight: bold; | ||
| 42 | font-style: normal; | 43 | font-style: normal; | ||
| 43 | } | 44 | } | ||
| @@ -84,17 +85,18 @@ | |||||
| 84 | } | 85 | } | ||
| 85 | 86 | ||||
| 86 | ::view-transition-old(slide-it) { | 87 | ::view-transition-old(slide-it) { | ||
| 87 | - | animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out, | 88 | + | animation: |
| 89 | + | 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out, | |||
| 88 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; | 90 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; | ||
| 89 | } | 91 | } | ||
| 90 | ::view-transition-new(slide-it) { | 92 | ::view-transition-new(slide-it) { | ||
| 91 | - | animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, | 93 | + | animation: |
| 94 | + | 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, | |||
| 92 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; | 95 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; | ||
| 93 | } | 96 | } | ||
| 94 | 97 | ||||
| 95 | .gordon-title { | 98 | .gordon-title { | ||
| 96 | font-family: "LilitaOne-Regular", sans-serif; | 99 | font-family: "LilitaOne-Regular", sans-serif; | ||
| 97 | - | ||||
| 98 | } | 100 | } | ||
| 99 | 101 | ||||
| 100 | .IBMPlexSans { | 102 | .IBMPlexSans { | ||
| @@ -107,4 +109,4 @@ | |||||
| 107 | 109 | ||||
| 108 | .JetBrainsMonoBoldNF { | 110 | .JetBrainsMonoBoldNF { | ||
| 109 | font-family: "JetBrainsMono-Nerd-Font-Bold", sans-serif; | 111 | font-family: "JetBrainsMono-Nerd-Font-Bold", sans-serif; | ||
| 110 | - | }⏎̸ | 112 | + | } |
M internal/webui/public/assets/css/tailwind.css +31 -4
| @@ -575,6 +575,18 @@ video { | |||
| 575 | 575 | } | |
| 576 | 576 | } | |
| 577 | 577 | ||
| 578 | + | .visible { | |
| 579 | + | visibility: visible; | |
| 580 | + | } | |
| 581 | + | ||
| 582 | + | .invisible { | |
| 583 | + | visibility: hidden; | |
| 584 | + | } | |
| 585 | + | ||
| 586 | + | .collapse { | |
| 587 | + | visibility: collapse; | |
| 588 | + | } | |
| 589 | + | ||
| 578 | 590 | .fixed { | |
| 579 | 591 | position: fixed; | |
| 580 | 592 | } | |
| @@ -708,6 +720,10 @@ video { | |||
| 708 | 720 | flex-direction: column; | |
| 709 | 721 | } | |
| 710 | 722 | ||
| 723 | + | .content-center { | |
| 724 | + | align-content: center; | |
| 725 | + | } | |
| 726 | + | ||
| 711 | 727 | .items-center { | |
| 712 | 728 | align-items: center; | |
| 713 | 729 | } | |
| @@ -1027,6 +1043,14 @@ video { | |||
| 1027 | 1043 | outline-offset: 2px; | |
| 1028 | 1044 | } | |
| 1029 | 1045 | ||
| 1046 | + | .transition { | |
| 1047 | + | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; | |
| 1048 | + | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; | |
| 1049 | + | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; | |
| 1050 | + | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |
| 1051 | + | transition-duration: 150ms; | |
| 1052 | + | } | |
| 1053 | + | ||
| 1030 | 1054 | /* Import IBM Plex Fonts */ | |
| 1031 | 1055 | ||
| 1032 | 1056 | @font-face { | |
| @@ -1067,7 +1091,8 @@ video { | |||
| 1067 | 1091 | @font-face { | |
| 1068 | 1092 | font-family: "JetBrainsMono-Nerd-Font-Bold"; | |
| 1069 | 1093 | ||
| 1070 | - | src: url("/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFont-Bold.woff2") format("woff2"); | |
| 1094 | + | src: url("/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFont-Bold.woff2") | |
| 1095 | + | format("woff2"); | |
| 1071 | 1096 | ||
| 1072 | 1097 | font-weight: bold; | |
| 1073 | 1098 | ||
| @@ -1120,12 +1145,14 @@ video { | |||
| 1120 | 1145 | } | |
| 1121 | 1146 | ||
| 1122 | 1147 | ::view-transition-old(slide-it) { | |
| 1123 | - | animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out, | |
| 1148 | + | animation: | |
| 1149 | + | 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out, | |
| 1124 | 1150 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; | |
| 1125 | 1151 | } | |
| 1126 | 1152 | ||
| 1127 | 1153 | ::view-transition-new(slide-it) { | |
| 1128 | - | animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, | |
| 1154 | + | animation: | |
| 1155 | + | 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, | |
| 1129 | 1156 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; | |
| 1130 | 1157 | } | |
| 1131 | 1158 | ||
| @@ -1284,4 +1311,4 @@ video { | |||
| 1284 | 1311 | font-size: 3rem; | |
| 1285 | 1312 | line-height: 1; | |
| 1286 | 1313 | } | |
| 1287 | - | }⏎̸ | |
| 1314 | + | } | |
| @@ -575,6 +575,18 @@ video { | |||||
| 575 | } | 575 | } | ||
| 576 | } | 576 | } | ||
| 577 | 577 | ||||
| 578 | + | .visible { | |||
| 579 | + | visibility: visible; | |||
| 580 | + | } | |||
| 581 | + | ||||
| 582 | + | .invisible { | |||
| 583 | + | visibility: hidden; | |||
| 584 | + | } | |||
| 585 | + | ||||
| 586 | + | .collapse { | |||
| 587 | + | visibility: collapse; | |||
| 588 | + | } | |||
| 589 | + | ||||
| 578 | .fixed { | 590 | .fixed { | ||
| 579 | position: fixed; | 591 | position: fixed; | ||
| 580 | } | 592 | } | ||
| @@ -708,6 +720,10 @@ video { | |||||
| 708 | flex-direction: column; | 720 | flex-direction: column; | ||
| 709 | } | 721 | } | ||
| 710 | 722 | ||||
| 723 | + | .content-center { | |||
| 724 | + | align-content: center; | |||
| 725 | + | } | |||
| 726 | + | ||||
| 711 | .items-center { | 727 | .items-center { | ||
| 712 | align-items: center; | 728 | align-items: center; | ||
| 713 | } | 729 | } | ||
| @@ -1027,6 +1043,14 @@ video { | |||||
| 1027 | outline-offset: 2px; | 1043 | outline-offset: 2px; | ||
| 1028 | } | 1044 | } | ||
| 1029 | 1045 | ||||
| 1046 | + | .transition { | |||
| 1047 | + | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; | |||
| 1048 | + | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; | |||
| 1049 | + | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; | |||
| 1050 | + | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |||
| 1051 | + | transition-duration: 150ms; | |||
| 1052 | + | } | |||
| 1053 | + | ||||
| 1030 | /* Import IBM Plex Fonts */ | 1054 | /* Import IBM Plex Fonts */ | ||
| 1031 | 1055 | ||||
| 1032 | @font-face { | 1056 | @font-face { | ||
| @@ -1067,7 +1091,8 @@ video { | |||||
| 1067 | @font-face { | 1091 | @font-face { | ||
| 1068 | font-family: "JetBrainsMono-Nerd-Font-Bold"; | 1092 | font-family: "JetBrainsMono-Nerd-Font-Bold"; | ||
| 1069 | 1093 | ||||
| 1070 | - | src: url("/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFont-Bold.woff2") format("woff2"); | 1094 | + | src: url("/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFont-Bold.woff2") |
| 1095 | + | format("woff2"); | |||
| 1071 | 1096 | ||||
| 1072 | font-weight: bold; | 1097 | font-weight: bold; | ||
| 1073 | 1098 | ||||
| @@ -1120,12 +1145,14 @@ video { | |||||
| 1120 | } | 1145 | } | ||
| 1121 | 1146 | ||||
| 1122 | ::view-transition-old(slide-it) { | 1147 | ::view-transition-old(slide-it) { | ||
| 1123 | - | animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out, | 1148 | + | animation: |
| 1149 | + | 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out, | |||
| 1124 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; | 1150 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; | ||
| 1125 | } | 1151 | } | ||
| 1126 | 1152 | ||||
| 1127 | ::view-transition-new(slide-it) { | 1153 | ::view-transition-new(slide-it) { | ||
| 1128 | - | animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, | 1154 | + | animation: |
| 1155 | + | 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, | |||
| 1129 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; | 1156 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; | ||
| 1130 | } | 1157 | } | ||
| 1131 | 1158 | ||||
| @@ -1284,4 +1311,4 @@ video { | |||||
| 1284 | font-size: 3rem; | 1311 | font-size: 3rem; | ||
| 1285 | line-height: 1; | 1312 | line-height: 1; | ||
| 1286 | } | 1313 | } | ||
| 1287 | - | }⏎̸ | 1314 | + | } |
M internal/webui/public/assets/js/custom.js +1 -67
| @@ -1,67 +1,1 @@ | |||
| 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 | - | }, 2000); | |
| 9 | - | } | |
| 10 | - | }); | |
| 11 | - | ||
| 12 | - | function toggleButtonState(event) { | |
| 13 | - | const button = event.currentTarget; | |
| 14 | - | const isActive = button.getAttribute('data-active') === 'true'; | |
| 15 | - | ||
| 16 | - | // Toggle the state | |
| 17 | - | button.setAttribute('data-active', !isActive); | |
| 18 | - | ||
| 19 | - | // Get the related component based on hx-target attribute | |
| 20 | - | const hxTarget = button.getAttribute('hx-target'); | |
| 21 | - | const relatedComponent = document.querySelector(hxTarget); | |
| 22 | - | ||
| 23 | - | if (relatedComponent) { | |
| 24 | - | if (isActive) { | |
| 25 | - | relatedComponent.style.display = 'none'; // Hide | |
| 26 | - | } else { | |
| 27 | - | relatedComponent.style.display = 'block'; // Show | |
| 28 | - | ||
| 29 | - | // Adjust textarea height if it exists within the relatedComponent | |
| 30 | - | const textarea = relatedComponent.querySelector("#container_config"); | |
| 31 | - | if (textarea) { | |
| 32 | - | textarea.style.height = ""; // Reset the height | |
| 33 | - | textarea.style.height = textarea.scrollHeight + "px"; | |
| 34 | - | } | |
| 35 | - | } | |
| 36 | - | } | |
| 37 | - | } | |
| 38 | - | ||
| 39 | - | // Attach event listeners to buttons that needs to toggle state | |
| 40 | - | document.body.addEventListener("htmx:afterSwap", function (event) { | |
| 41 | - | const buttons = document.querySelectorAll('[id^="edit-button-"], [id^="add-button-img-"]', '[id^="upload-button"]'); | |
| 42 | - | buttons.forEach(button => { | |
| 43 | - | button.addEventListener('click', toggleButtonState); | |
| 44 | - | }); | |
| 45 | - | }); | |
| 46 | - | ||
| 47 | - | // // Attach event listeners to buttons that needs to set a full location reload | |
| 48 | - | // document.body.addEventListener("htmx:afterSwap", function (event) { | |
| 49 | - | // const xhr = event.detail.xhr; | |
| 50 | - | // const statusCode = xhr.status; | |
| 51 | - | ||
| 52 | - | // // Check for 500 status code | |
| 53 | - | // if (statusCode >= 500 && statusCode < 600) { | |
| 54 | - | // // Handle the error (e.g., log it or show an alert) | |
| 55 | - | // console.error("Server returned error: ", statusCode); | |
| 56 | - | // return; // Do not reload the page | |
| 57 | - | // } | |
| 58 | - | ||
| 59 | - | // const buttons = document.querySelectorAll('[id^="start-button-"], [id^="stop-button-"], [id^="remove-button-img-"], [id^="create-container-submit"], [id^="save-button-"]'); | |
| 60 | - | // buttons.forEach(button => { | |
| 61 | - | // button.addEventListener('click', function(event) { | |
| 62 | - | // setTimeout(function() { | |
| 63 | - | // location.reload(); | |
| 64 | - | // }, 5000); | |
| 65 | - | // }); | |
| 66 | - | // }); | |
| 67 | - | // }); | |
| 1 | + | // switched to hyperscript for the most part⏎̸ | |
| @@ -1,67 +1,1 @@ | |||||
| 1 | - | document.body.addEventListener("htmx:afterOnLoad", function(event) { | 1 | + | // switched to hyperscript for the most part⏎̸ |
| 2 | - | if ( | |||
| 3 | - | event.target.id === "image-manager" && | |||
| 4 | - | event.detail.xhr.responseText === "Success" | |||
| 5 | - | ) { | |||
| 6 | - | setTimeout(function() { | |||
| 7 | - | location.reload(); | |||
| 8 | - | }, 2000); | |||
| 9 | - | } | |||
| 10 | - | }); | |||
| 11 | - | ||||
| 12 | - | function toggleButtonState(event) { | |||
| 13 | - | const button = event.currentTarget; | |||
| 14 | - | const isActive = button.getAttribute('data-active') === 'true'; | |||
| 15 | - | ||||
| 16 | - | // Toggle the state | |||
| 17 | - | button.setAttribute('data-active', !isActive); | |||
| 18 | - | ||||
| 19 | - | // Get the related component based on hx-target attribute | |||
| 20 | - | const hxTarget = button.getAttribute('hx-target'); | |||
| 21 | - | const relatedComponent = document.querySelector(hxTarget); | |||
| 22 | - | ||||
| 23 | - | if (relatedComponent) { | |||
| 24 | - | if (isActive) { | |||
| 25 | - | relatedComponent.style.display = 'none'; // Hide | |||
| 26 | - | } else { | |||
| 27 | - | relatedComponent.style.display = 'block'; // Show | |||
| 28 | - | ||||
| 29 | - | // Adjust textarea height if it exists within the relatedComponent | |||
| 30 | - | const textarea = relatedComponent.querySelector("#container_config"); | |||
| 31 | - | if (textarea) { | |||
| 32 | - | textarea.style.height = ""; // Reset the height | |||
| 33 | - | textarea.style.height = textarea.scrollHeight + "px"; | |||
| 34 | - | } | |||
| 35 | - | } | |||
| 36 | - | } | |||
| 37 | - | } | |||
| 38 | - | ||||
| 39 | - | // Attach event listeners to buttons that needs to toggle state | |||
| 40 | - | document.body.addEventListener("htmx:afterSwap", function (event) { | |||
| 41 | - | const buttons = document.querySelectorAll('[id^="edit-button-"], [id^="add-button-img-"]', '[id^="upload-button"]'); | |||
| 42 | - | buttons.forEach(button => { | |||
| 43 | - | button.addEventListener('click', toggleButtonState); | |||
| 44 | - | }); | |||
| 45 | - | }); | |||
| 46 | - | ||||
| 47 | - | // // Attach event listeners to buttons that needs to set a full location reload | |||
| 48 | - | // document.body.addEventListener("htmx:afterSwap", function (event) { | |||
| 49 | - | // const xhr = event.detail.xhr; | |||
| 50 | - | // const statusCode = xhr.status; | |||
| 51 | - | ||||
| 52 | - | // // Check for 500 status code | |||
| 53 | - | // if (statusCode >= 500 && statusCode < 600) { | |||
| 54 | - | // // Handle the error (e.g., log it or show an alert) | |||
| 55 | - | // console.error("Server returned error: ", statusCode); | |||
| 56 | - | // return; // Do not reload the page | |||
| 57 | - | // } | |||
| 58 | - | ||||
| 59 | - | // const buttons = document.querySelectorAll('[id^="start-button-"], [id^="stop-button-"], [id^="remove-button-img-"], [id^="create-container-submit"], [id^="save-button-"]'); | |||
| 60 | - | // buttons.forEach(button => { | |||
| 61 | - | // button.addEventListener('click', function(event) { | |||
| 62 | - | // setTimeout(function() { | |||
| 63 | - | // location.reload(); | |||
| 64 | - | // }, 5000); | |||
| 65 | - | // }); | |||
| 66 | - | // }); | |||
| 67 | - | // }); | |||
A internal/webui/public/libs/htmx.js +3789 -0 large diff (3789 lines changed): collapsed
A internal/webui/public/libs/hyperscript.min.js +1 -0 too large to show in the browser
too large to show in the browser view raw
M tailwind.config.js +4 -4
| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | module.exports = { | |
| 3 | 3 | content: [ | |
| 4 | 4 | "./internal/webui/**/*.{gohtml,go}", | |
| 5 | - | "./internal/templates/models/html/**/*.{gohtml, go}" | |
| 5 | + | "./internal/templating/models/html/**/*.{gohtml, go}", | |
| 6 | 6 | ], | |
| 7 | 7 | theme: { | |
| 8 | 8 | extend: { | |
| @@ -10,12 +10,12 @@ module.exports = { | |||
| 10 | 10 | gordonmascot: { | |
| 11 | 11 | lblue: "#007BC0", | |
| 12 | 12 | mblue: "#005293", | |
| 13 | - | hblue: '#011E5C', | |
| 13 | + | hblue: "#011E5C", | |
| 14 | 14 | lbeige: "#FFDCB3", | |
| 15 | - | mbeige: '#D7B49F', | |
| 15 | + | mbeige: "#D7B49F", | |
| 16 | 16 | }, | |
| 17 | 17 | }, | |
| 18 | 18 | }, | |
| 19 | 19 | }, | |
| 20 | 20 | plugins: [require("@catppuccin/tailwindcss")], | |
| 21 | - | }⏎̸ | |
| 21 | + | }; | |
| @@ -2,7 +2,7 @@ | |||||
| 2 | module.exports = { | 2 | module.exports = { | ||
| 3 | content: [ | 3 | content: [ | ||
| 4 | "./internal/webui/**/*.{gohtml,go}", | 4 | "./internal/webui/**/*.{gohtml,go}", | ||
| 5 | - | "./internal/templates/models/html/**/*.{gohtml, go}" | 5 | + | "./internal/templating/models/html/**/*.{gohtml, go}", |
| 6 | ], | 6 | ], | ||
| 7 | theme: { | 7 | theme: { | ||
| 8 | extend: { | 8 | extend: { | ||
| @@ -10,12 +10,12 @@ module.exports = { | |||||
| 10 | gordonmascot: { | 10 | gordonmascot: { | ||
| 11 | lblue: "#007BC0", | 11 | lblue: "#007BC0", | ||
| 12 | mblue: "#005293", | 12 | mblue: "#005293", | ||
| 13 | - | hblue: '#011E5C', | 13 | + | hblue: "#011E5C", |
| 14 | lbeige: "#FFDCB3", | 14 | lbeige: "#FFDCB3", | ||
| 15 | - | mbeige: '#D7B49F', | 15 | + | mbeige: "#D7B49F", |
| 16 | }, | 16 | }, | ||
| 17 | }, | 17 | }, | ||
| 18 | }, | 18 | }, | ||
| 19 | }, | 19 | }, | ||
| 20 | plugins: [require("@catppuccin/tailwindcss")], | 20 | plugins: [require("@catppuccin/tailwindcss")], | ||
| 21 | - | }⏎̸ | 21 | + | }; |