Manager WIP on listing all containers images from the specified .sock. Also catch response errors and display it in the DOM
- commit
- 396cee3c997144314c62c6dc4f9f93f7c2f2ac51
- author
- bnema · · 2023-09-20 13:33 UTC
- parent
- 64163ef
- signature
- unsigned
12 files · +227 -97
M go.mod +6 -6
| @@ -5,6 +5,7 @@ go 1.21.1 | |||
| 5 | 5 | require ( | |
| 6 | 6 | github.com/PuerkitoBio/goquery v1.8.1 | |
| 7 | 7 | github.com/docker/docker v24.0.6+incompatible | |
| 8 | + | github.com/google/safehtml v0.1.0 | |
| 8 | 9 | github.com/gorilla/sessions v1.2.1 | |
| 9 | 10 | github.com/joho/godotenv v1.5.1 | |
| 10 | 11 | github.com/labstack/echo-contrib v0.15.0 | |
| @@ -17,30 +18,29 @@ require ( | |||
| 17 | 18 | require ( | |
| 18 | 19 | github.com/Microsoft/go-winio v0.6.1 // indirect | |
| 19 | 20 | github.com/andybalholm/cascadia v1.3.1 // indirect | |
| 20 | - | github.com/containerd/containerd v1.7.6 // indirect | |
| 21 | + | github.com/aymerick/douceur v0.2.0 // indirect | |
| 21 | 22 | github.com/docker/distribution v2.8.2+incompatible // indirect | |
| 22 | 23 | github.com/docker/go-connections v0.4.0 // indirect | |
| 23 | 24 | github.com/docker/go-units v0.5.0 // indirect | |
| 24 | 25 | github.com/gogo/protobuf v1.3.2 // indirect | |
| 25 | 26 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | |
| 26 | 27 | github.com/golang/protobuf v1.5.3 // indirect | |
| 28 | + | github.com/google/go-cmp v0.5.9 // indirect | |
| 27 | 29 | github.com/gorilla/context v1.1.1 // indirect | |
| 30 | + | github.com/gorilla/css v1.0.0 // indirect | |
| 28 | 31 | github.com/gorilla/mux v1.6.2 // indirect | |
| 29 | 32 | github.com/gorilla/securecookie v1.1.1 // indirect | |
| 30 | - | github.com/klauspost/compress v1.16.7 // indirect | |
| 31 | 33 | github.com/labstack/gommon v0.4.0 // indirect | |
| 32 | 34 | github.com/mattn/go-colorable v0.1.13 // indirect | |
| 33 | 35 | github.com/mattn/go-isatty v0.0.19 // indirect | |
| 34 | - | github.com/moby/patternmatcher v0.6.0 // indirect | |
| 35 | - | github.com/moby/sys/sequential v0.5.0 // indirect | |
| 36 | + | github.com/microcosm-cc/bluemonday v1.0.25 // indirect | |
| 36 | 37 | github.com/moby/term v0.5.0 // indirect | |
| 37 | 38 | github.com/morikuni/aec v1.0.0 // indirect | |
| 38 | 39 | github.com/opencontainers/go-digest v1.0.0 // indirect | |
| 39 | 40 | github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect | |
| 40 | - | github.com/opencontainers/runc v1.1.9 // indirect | |
| 41 | 41 | github.com/pkg/errors v0.9.1 // indirect | |
| 42 | 42 | github.com/rogpeppe/go-internal v1.11.0 // indirect | |
| 43 | - | github.com/sirupsen/logrus v1.9.3 // indirect | |
| 43 | + | github.com/stretchr/testify v1.8.2 // indirect | |
| 44 | 44 | github.com/valyala/bytebufferpool v1.0.0 // indirect | |
| 45 | 45 | github.com/valyala/fasttemplate v1.2.2 // indirect | |
| 46 | 46 | golang.org/x/crypto v0.11.0 // indirect | |
| @@ -5,6 +5,7 @@ go 1.21.1 | |||||
| 5 | require ( | 5 | require ( | ||
| 6 | github.com/PuerkitoBio/goquery v1.8.1 | 6 | github.com/PuerkitoBio/goquery v1.8.1 | ||
| 7 | github.com/docker/docker v24.0.6+incompatible | 7 | github.com/docker/docker v24.0.6+incompatible | ||
| 8 | + | github.com/google/safehtml v0.1.0 | |||
| 8 | github.com/gorilla/sessions v1.2.1 | 9 | github.com/gorilla/sessions v1.2.1 | ||
| 9 | github.com/joho/godotenv v1.5.1 | 10 | github.com/joho/godotenv v1.5.1 | ||
| 10 | github.com/labstack/echo-contrib v0.15.0 | 11 | github.com/labstack/echo-contrib v0.15.0 | ||
| @@ -17,30 +18,29 @@ require ( | |||||
| 17 | require ( | 18 | require ( | ||
| 18 | github.com/Microsoft/go-winio v0.6.1 // indirect | 19 | github.com/Microsoft/go-winio v0.6.1 // indirect | ||
| 19 | github.com/andybalholm/cascadia v1.3.1 // indirect | 20 | github.com/andybalholm/cascadia v1.3.1 // indirect | ||
| 20 | - | github.com/containerd/containerd v1.7.6 // indirect | 21 | + | github.com/aymerick/douceur v0.2.0 // indirect |
| 21 | github.com/docker/distribution v2.8.2+incompatible // indirect | 22 | github.com/docker/distribution v2.8.2+incompatible // indirect | ||
| 22 | github.com/docker/go-connections v0.4.0 // indirect | 23 | github.com/docker/go-connections v0.4.0 // indirect | ||
| 23 | github.com/docker/go-units v0.5.0 // indirect | 24 | github.com/docker/go-units v0.5.0 // indirect | ||
| 24 | github.com/gogo/protobuf v1.3.2 // indirect | 25 | github.com/gogo/protobuf v1.3.2 // indirect | ||
| 25 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | 26 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | ||
| 26 | github.com/golang/protobuf v1.5.3 // indirect | 27 | github.com/golang/protobuf v1.5.3 // indirect | ||
| 28 | + | github.com/google/go-cmp v0.5.9 // indirect | |||
| 27 | github.com/gorilla/context v1.1.1 // indirect | 29 | github.com/gorilla/context v1.1.1 // indirect | ||
| 30 | + | github.com/gorilla/css v1.0.0 // indirect | |||
| 28 | github.com/gorilla/mux v1.6.2 // indirect | 31 | github.com/gorilla/mux v1.6.2 // indirect | ||
| 29 | github.com/gorilla/securecookie v1.1.1 // indirect | 32 | github.com/gorilla/securecookie v1.1.1 // indirect | ||
| 30 | - | github.com/klauspost/compress v1.16.7 // indirect | |||
| 31 | github.com/labstack/gommon v0.4.0 // indirect | 33 | github.com/labstack/gommon v0.4.0 // indirect | ||
| 32 | github.com/mattn/go-colorable v0.1.13 // indirect | 34 | github.com/mattn/go-colorable v0.1.13 // indirect | ||
| 33 | github.com/mattn/go-isatty v0.0.19 // indirect | 35 | github.com/mattn/go-isatty v0.0.19 // indirect | ||
| 34 | - | github.com/moby/patternmatcher v0.6.0 // indirect | 36 | + | github.com/microcosm-cc/bluemonday v1.0.25 // indirect |
| 35 | - | github.com/moby/sys/sequential v0.5.0 // indirect | |||
| 36 | github.com/moby/term v0.5.0 // indirect | 37 | github.com/moby/term v0.5.0 // indirect | ||
| 37 | github.com/morikuni/aec v1.0.0 // indirect | 38 | github.com/morikuni/aec v1.0.0 // indirect | ||
| 38 | github.com/opencontainers/go-digest v1.0.0 // indirect | 39 | github.com/opencontainers/go-digest v1.0.0 // indirect | ||
| 39 | github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect | 40 | github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect | ||
| 40 | - | github.com/opencontainers/runc v1.1.9 // indirect | |||
| 41 | github.com/pkg/errors v0.9.1 // indirect | 41 | github.com/pkg/errors v0.9.1 // indirect | ||
| 42 | github.com/rogpeppe/go-internal v1.11.0 // indirect | 42 | github.com/rogpeppe/go-internal v1.11.0 // indirect | ||
| 43 | - | github.com/sirupsen/logrus v1.9.3 // indirect | 43 | + | github.com/stretchr/testify v1.8.2 // indirect |
| 44 | github.com/valyala/bytebufferpool v1.0.0 // indirect | 44 | github.com/valyala/bytebufferpool v1.0.0 // indirect | ||
| 45 | github.com/valyala/fasttemplate v1.2.2 // indirect | 45 | github.com/valyala/fasttemplate v1.2.2 // indirect | ||
| 46 | golang.org/x/crypto v0.11.0 // indirect | 46 | golang.org/x/crypto v0.11.0 // indirect | ||
M go.sum +11 -17 generated file: collapsed
M internal/gotemplate/models/html/admin/index.gohtml +6 -44
| @@ -1,52 +1,11 @@ | |||
| 1 | 1 | <!DOCTYPE html> | |
| 2 | 2 | <html lang="en"> | |
| 3 | 3 | ||
| 4 | - | <head> | |
| 5 | - | <meta charset="UTF-8" /> | |
| 6 | - | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| 7 | - | <title>{{ .header.meta.title }}</title> | |
| 8 | - | <link rel="stylesheet" href="/assets/css/tailwind.css" /> | |
| 9 | - | <link rel="stylesheet" href="/libs/iconoir.css" /> | |
| 10 | - | <script src="/libs/alpine.min.js"></script> | |
| 11 | - | <script src="/libs/htmx.min.js"></script> | |
| 12 | - | <script src="/libs/htmx-sse.js"></script> | |
| 13 | - | <script src="assets/js/custom.js"></script> | |
| 14 | - | </head> | |
| 4 | + | {{template "header.gohtml" .}} | |
| 15 | 5 | ||
| 16 | 6 | <body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-8 p-0"> | |
| 17 | - | <div class="w-full md:w-1/5 flex flex-col space-evenly p-4 md:p-0"> | |
| 18 | - | <div class="hidden md:flex justify-center p-4"> | |
| 19 | - | <img src="/assets/imgs/gordon-mascot-hq-trsp.png" alt="logo" class="h-32" /> | |
| 20 | - | </div> | |
| 21 | - | <div class="p-4 flex flex-col bg-gordonmascot-lblue rounded-lg shadow space-y-4"> | |
| 22 | - | <div class="w-full flex justify-center"> | |
| 23 | - | <h1 class="gordon-title text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter">Gordon</h1> | |
| 24 | - | <span class="text-gordonmascot-mblue text-sm">{{ .BuildVersion }}</span> | |
| 25 | - | </div> | |
| 26 | - | <div class="w-full"> | |
| 27 | - | <ul | |
| 28 | - | 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"> | |
| 29 | - | <li class="flex items-center w-1/2 md:w-full"> | |
| 30 | - | <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i> | |
| 31 | - | <a href="#">Dashboard</a> | |
| 32 | - | </li> | |
| 33 | - | <li class="flex items-center w-1/2 md:w-full"> | |
| 34 | - | <i class="iconoir-server mr-2 md:text-3xl"></i> | |
| 35 | - | <a href="#manager">Manager</a> | |
| 36 | - | </li> | |
| 37 | - | <li class="flex items-center w-1/2 md:w-full"> | |
| 38 | - | <i class="iconoir-community mr-2 md:text-3xl"></i> | |
| 39 | - | <a href="#users">Users</a> | |
| 40 | - | </li> | |
| 41 | - | <li class="flex items-center w-1/2 md:w-full"> | |
| 42 | - | <i class="iconoir-database-settings mr-2 md:text-3xl"></i> | |
| 43 | - | <a href="#gordon-settings">Settings</a> | |
| 44 | - | </li> | |
| 45 | - | </ul> | |
| 46 | - | </div> | |
| 47 | - | </div> | |
| 48 | - | </div> | |
| 49 | 7 | ||
| 8 | + | {{template "leftmenu.gohtml" .}} | |
| 50 | 9 | <div class="flex-1 flex flex-col md:pt-10 space-y-4 p-4 md:ml-4"> | |
| 51 | 10 | <div class="hidden md:flex mb-4 justify-end"> | |
| 52 | 11 | <div class="w-72 h-10 bg-gray-300 rounded-lg"> | |
| @@ -58,7 +17,7 @@ | |||
| 58 | 17 | </div> | |
| 59 | 18 | <div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"> | |
| 60 | 19 | <div class="flex-grow bg-white p-4 rounded-md shadow mb-4"> | |
| 61 | - | <div class="w-full rounded-sm bg-gray-300"></div> | |
| 20 | + | <h2>Dashboard</h2> | |
| 62 | 21 | </div> | |
| 63 | 22 | <div class="w-full md:w-2/6 flex flex-col space-y-4"> | |
| 64 | 23 | <div class="bg-white p-4 rounded-md shadow mb-4"> | |
| @@ -73,6 +32,9 @@ | |||
| 73 | 32 | </div> | |
| 74 | 33 | </div> | |
| 75 | 34 | </div> | |
| 35 | + | <!-- footer --> | |
| 36 | + | {{template "footer.gohtml" .}} | |
| 37 | + | ||
| 76 | 38 | </body> | |
| 77 | 39 | ||
| 78 | 40 | </html>⏎̸ | |
| @@ -1,52 +1,11 @@ | |||||
| 1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> | ||
| 2 | <html lang="en"> | 2 | <html lang="en"> | ||
| 3 | 3 | ||||
| 4 | - | <head> | 4 | + | {{template "header.gohtml" .}} |
| 5 | - | <meta charset="UTF-8" /> | |||
| 6 | - | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||
| 7 | - | <title>{{ .header.meta.title }}</title> | |||
| 8 | - | <link rel="stylesheet" href="/assets/css/tailwind.css" /> | |||
| 9 | - | <link rel="stylesheet" href="/libs/iconoir.css" /> | |||
| 10 | - | <script src="/libs/alpine.min.js"></script> | |||
| 11 | - | <script src="/libs/htmx.min.js"></script> | |||
| 12 | - | <script src="/libs/htmx-sse.js"></script> | |||
| 13 | - | <script src="assets/js/custom.js"></script> | |||
| 14 | - | </head> | |||
| 15 | 5 | ||||
| 16 | <body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-8 p-0"> | 6 | <body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-8 p-0"> | ||
| 17 | - | <div class="w-full md:w-1/5 flex flex-col space-evenly p-4 md:p-0"> | |||
| 18 | - | <div class="hidden md:flex justify-center p-4"> | |||
| 19 | - | <img src="/assets/imgs/gordon-mascot-hq-trsp.png" alt="logo" class="h-32" /> | |||
| 20 | - | </div> | |||
| 21 | - | <div class="p-4 flex flex-col bg-gordonmascot-lblue rounded-lg shadow space-y-4"> | |||
| 22 | - | <div class="w-full flex justify-center"> | |||
| 23 | - | <h1 class="gordon-title text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter">Gordon</h1> | |||
| 24 | - | <span class="text-gordonmascot-mblue text-sm">{{ .BuildVersion }}</span> | |||
| 25 | - | </div> | |||
| 26 | - | <div class="w-full"> | |||
| 27 | - | <ul | |||
| 28 | - | 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"> | |||
| 29 | - | <li class="flex items-center w-1/2 md:w-full"> | |||
| 30 | - | <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i> | |||
| 31 | - | <a href="#">Dashboard</a> | |||
| 32 | - | </li> | |||
| 33 | - | <li class="flex items-center w-1/2 md:w-full"> | |||
| 34 | - | <i class="iconoir-server mr-2 md:text-3xl"></i> | |||
| 35 | - | <a href="#manager">Manager</a> | |||
| 36 | - | </li> | |||
| 37 | - | <li class="flex items-center w-1/2 md:w-full"> | |||
| 38 | - | <i class="iconoir-community mr-2 md:text-3xl"></i> | |||
| 39 | - | <a href="#users">Users</a> | |||
| 40 | - | </li> | |||
| 41 | - | <li class="flex items-center w-1/2 md:w-full"> | |||
| 42 | - | <i class="iconoir-database-settings mr-2 md:text-3xl"></i> | |||
| 43 | - | <a href="#gordon-settings">Settings</a> | |||
| 44 | - | </li> | |||
| 45 | - | </ul> | |||
| 46 | - | </div> | |||
| 47 | - | </div> | |||
| 48 | - | </div> | |||
| 49 | 7 | ||||
| 8 | + | {{template "leftmenu.gohtml" .}} | |||
| 50 | <div class="flex-1 flex flex-col md:pt-10 space-y-4 p-4 md:ml-4"> | 9 | <div class="flex-1 flex flex-col md:pt-10 space-y-4 p-4 md:ml-4"> | ||
| 51 | <div class="hidden md:flex mb-4 justify-end"> | 10 | <div class="hidden md:flex mb-4 justify-end"> | ||
| 52 | <div class="w-72 h-10 bg-gray-300 rounded-lg"> | 11 | <div class="w-72 h-10 bg-gray-300 rounded-lg"> | ||
| @@ -58,7 +17,7 @@ | |||||
| 58 | </div> | 17 | </div> | ||
| 59 | <div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"> | 18 | <div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"> | ||
| 60 | <div class="flex-grow bg-white p-4 rounded-md shadow mb-4"> | 19 | <div class="flex-grow bg-white p-4 rounded-md shadow mb-4"> | ||
| 61 | - | <div class="w-full rounded-sm bg-gray-300"></div> | 20 | + | <h2>Dashboard</h2> |
| 62 | </div> | 21 | </div> | ||
| 63 | <div class="w-full md:w-2/6 flex flex-col space-y-4"> | 22 | <div class="w-full md:w-2/6 flex flex-col space-y-4"> | ||
| 64 | <div class="bg-white p-4 rounded-md shadow mb-4"> | 23 | <div class="bg-white p-4 rounded-md shadow mb-4"> | ||
| @@ -73,6 +32,9 @@ | |||||
| 73 | </div> | 32 | </div> | ||
| 74 | </div> | 33 | </div> | ||
| 75 | </div> | 34 | </div> | ||
| 35 | + | <!-- footer --> | |||
| 36 | + | {{template "footer.gohtml" .}} | |||
| 37 | + | ||||
| 76 | </body> | 38 | </body> | ||
| 77 | 39 | ||||
| 78 | </html>⏎̸ | 40 | </html>⏎̸ | ||
M internal/gotemplate/models/html/admin/manager.gohtml +28 -6
| @@ -1,20 +1,42 @@ | |||
| 1 | 1 | <!-- manager.gohtml --> | |
| 2 | - | {{template "header.gohtml" .}} | |
| 3 | 2 | <!DOCTYPE html> | |
| 4 | 3 | <html lang="en"> | |
| 5 | 4 | ||
| 5 | + | {{template "header.gohtml" .}} | |
| 6 | + | ||
| 6 | 7 | <body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-8 p-0"> | |
| 7 | 8 | {{template "leftmenu.gohtml" .}} | |
| 8 | 9 | <!-- Main content here --> | |
| 9 | - | <div class="flex-1 flex flex-col md:pt-10 space-y-4 p-4 md:ml-4"> | |
| 10 | - | <div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"> | |
| 10 | + | <!-- This div will contain the error message when something goes wrong --> | |
| 11 | + | <div id="container-images-error" style="display:none;"> | |
| 12 | + | Something went wrong. </div> | |
| 13 | + | <div class="flex-1 flex flex-col space-y-4 p-4 md:ml-4"> | |
| 14 | + | <div class="flex flex-col md:flex-row w-full flex-grow md:mt-24"> | |
| 11 | 15 | <div class="flex-grow bg-white p-4 rounded-md shadow mb-4"> | |
| 12 | - | <h1>hello world</h1> | |
| 16 | + | <h1>Container Manager</h1> | |
| 17 | + | <!-- List all the images on load with HTMX --> | |
| 18 | + | <div id="container-images" hx-get="/image-manager" hx-trigger="load" hx-swap="innerHTML" hx-swap-oob="true" | |
| 19 | + | hx-target-error="container-images-error"> | |
| 20 | + | {{range .Images}} | |
| 21 | + | <div class="image-info"> | |
| 22 | + | <p>ID: {{.ID}}</p> | |
| 23 | + | <p>Repository: {{.Repository}}</p> | |
| 24 | + | <p>Tag: {{.Tag}}</p> | |
| 25 | + | <p>Created: {{.Created}}</p> | |
| 26 | + | <p>Size: {{.Size}}</p> | |
| 27 | + | </div> | |
| 28 | + | {{else}} | |
| 29 | + | <p>No images found.</p> | |
| 30 | + | {{end}} | |
| 31 | + | </div> | |
| 32 | + | ||
| 33 | + | ||
| 13 | 34 | </div> | |
| 14 | 35 | </div> | |
| 36 | + | </div> | |
| 15 | 37 | ||
| 16 | - | <!-- footer --> | |
| 17 | - | {{template "footer.gohtml" .}} | |
| 38 | + | <!-- footer --> | |
| 39 | + | {{template "footer.gohtml" .}} | |
| 18 | 40 | </body> | |
| 19 | 41 | ||
| 20 | 42 | </html>⏎̸ | |
| @@ -1,20 +1,42 @@ | |||||
| 1 | <!-- manager.gohtml --> | 1 | <!-- manager.gohtml --> | ||
| 2 | - | {{template "header.gohtml" .}} | |||
| 3 | <!DOCTYPE html> | 2 | <!DOCTYPE html> | ||
| 4 | <html lang="en"> | 3 | <html lang="en"> | ||
| 5 | 4 | ||||
| 5 | + | {{template "header.gohtml" .}} | |||
| 6 | + | ||||
| 6 | <body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-8 p-0"> | 7 | <body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-8 p-0"> | ||
| 7 | {{template "leftmenu.gohtml" .}} | 8 | {{template "leftmenu.gohtml" .}} | ||
| 8 | <!-- Main content here --> | 9 | <!-- Main content here --> | ||
| 9 | - | <div class="flex-1 flex flex-col md:pt-10 space-y-4 p-4 md:ml-4"> | 10 | + | <!-- This div will contain the error message when something goes wrong --> |
| 10 | - | <div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"> | 11 | + | <div id="container-images-error" style="display:none;"> |
| 12 | + | Something went wrong. </div> | |||
| 13 | + | <div class="flex-1 flex flex-col space-y-4 p-4 md:ml-4"> | |||
| 14 | + | <div class="flex flex-col md:flex-row w-full flex-grow md:mt-24"> | |||
| 11 | <div class="flex-grow bg-white p-4 rounded-md shadow mb-4"> | 15 | <div class="flex-grow bg-white p-4 rounded-md shadow mb-4"> | ||
| 12 | - | <h1>hello world</h1> | 16 | + | <h1>Container Manager</h1> |
| 17 | + | <!-- List all the images on load with HTMX --> | |||
| 18 | + | <div id="container-images" hx-get="/image-manager" hx-trigger="load" hx-swap="innerHTML" hx-swap-oob="true" | |||
| 19 | + | hx-target-error="container-images-error"> | |||
| 20 | + | {{range .Images}} | |||
| 21 | + | <div class="image-info"> | |||
| 22 | + | <p>ID: {{.ID}}</p> | |||
| 23 | + | <p>Repository: {{.Repository}}</p> | |||
| 24 | + | <p>Tag: {{.Tag}}</p> | |||
| 25 | + | <p>Created: {{.Created}}</p> | |||
| 26 | + | <p>Size: {{.Size}}</p> | |||
| 27 | + | </div> | |||
| 28 | + | {{else}} | |||
| 29 | + | <p>No images found.</p> | |||
| 30 | + | {{end}} | |||
| 31 | + | </div> | |||
| 32 | + | ||||
| 33 | + | ||||
| 13 | </div> | 34 | </div> | ||
| 14 | </div> | 35 | </div> | ||
| 36 | + | </div> | |||
| 15 | 37 | ||||
| 16 | - | <!-- footer --> | 38 | + | <!-- footer --> |
| 17 | - | {{template "footer.gohtml" .}} | 39 | + | {{template "footer.gohtml" .}} |
| 18 | </body> | 40 | </body> | ||
| 19 | 41 | ||||
| 20 | </html>⏎̸ | 42 | </html>⏎̸ | ||
M internal/gotemplate/models/html/fragments/header.gohtml +2 -2
| @@ -4,8 +4,8 @@ | |||
| 4 | 4 | <title>{{ .header.meta.title }}</title> | |
| 5 | 5 | <link rel="stylesheet" href="/assets/css/tailwind.css" /> | |
| 6 | 6 | <link rel="stylesheet" href="/libs/iconoir.css" /> | |
| 7 | - | <script src="/libs/alpine.min.js"></script> | |
| 7 | + | <script src="/libs/alpine.min.js" defer></script> | |
| 8 | 8 | <script src="/libs/htmx.min.js"></script> | |
| 9 | 9 | <script src="/libs/htmx-sse.js"></script> | |
| 10 | - | <script src="assets/js/custom.js"></script> | |
| 10 | + | <script src="/assets/js/custom.js" defer></script> | |
| 11 | 11 | </head>⏎̸ | |
| @@ -4,8 +4,8 @@ | |||||
| 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 | <link rel="stylesheet" href="/libs/iconoir.css" /> | 6 | <link rel="stylesheet" href="/libs/iconoir.css" /> | ||
| 7 | - | <script src="/libs/alpine.min.js"></script> | 7 | + | <script src="/libs/alpine.min.js" defer></script> |
| 8 | <script src="/libs/htmx.min.js"></script> | 8 | <script src="/libs/htmx.min.js"></script> | ||
| 9 | <script src="/libs/htmx-sse.js"></script> | 9 | <script src="/libs/htmx-sse.js"></script> | ||
| 10 | - | <script src="assets/js/custom.js"></script> | 10 | + | <script src="/assets/js/custom.js" defer></script> |
| 11 | </head>⏎̸ | 11 | </head>⏎̸ | ||
M internal/gotemplate/models/html/fragments/leftmenu.gohtml +6 -5
| @@ -9,22 +9,23 @@ | |||
| 9 | 9 | </div> | |
| 10 | 10 | <div class="w-full"> | |
| 11 | 11 | <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"> | |
| 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" | |
| 13 | + | hx-boost="true"> | |
| 13 | 14 | <li class="flex items-center w-1/2 md:w-full"> | |
| 14 | 15 | <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i> | |
| 15 | - | <a href="#">Dashboard</a> | |
| 16 | + | <a href="/admin">Dashboard</a> | |
| 16 | 17 | </li> | |
| 17 | 18 | <li class="flex items-center w-1/2 md:w-full"> | |
| 18 | 19 | <i class="iconoir-server mr-2 md:text-3xl"></i> | |
| 19 | - | <a href="#manager">Manager</a> | |
| 20 | + | <a href="/admin/manager">Manager</a> | |
| 20 | 21 | </li> | |
| 21 | 22 | <li class="flex items-center w-1/2 md:w-full"> | |
| 22 | 23 | <i class="iconoir-community mr-2 md:text-3xl"></i> | |
| 23 | - | <a href="#users">Users</a> | |
| 24 | + | <a href="/admin/users">Users</a> | |
| 24 | 25 | </li> | |
| 25 | 26 | <li class="flex items-center w-1/2 md:w-full"> | |
| 26 | 27 | <i class="iconoir-database-settings mr-2 md:text-3xl"></i> | |
| 27 | - | <a href="#gordon-settings">Settings</a> | |
| 28 | + | <a href="/admin/settings">Settings</a> | |
| 28 | 29 | </li> | |
| 29 | 30 | </ul> | |
| 30 | 31 | </div> | |
| @@ -9,22 +9,23 @@ | |||||
| 9 | </div> | 9 | </div> | ||
| 10 | <div class="w-full"> | 10 | <div class="w-full"> | ||
| 11 | <ul | 11 | <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"> | 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" |
| 13 | + | hx-boost="true"> | |||
| 13 | <li class="flex items-center w-1/2 md:w-full"> | 14 | <li class="flex items-center w-1/2 md:w-full"> | ||
| 14 | <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i> | 15 | <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i> | ||
| 15 | - | <a href="#">Dashboard</a> | 16 | + | <a href="/admin">Dashboard</a> |
| 16 | </li> | 17 | </li> | ||
| 17 | <li class="flex items-center w-1/2 md:w-full"> | 18 | <li class="flex items-center w-1/2 md:w-full"> | ||
| 18 | <i class="iconoir-server mr-2 md:text-3xl"></i> | 19 | <i class="iconoir-server mr-2 md:text-3xl"></i> | ||
| 19 | - | <a href="#manager">Manager</a> | 20 | + | <a href="/admin/manager">Manager</a> |
| 20 | </li> | 21 | </li> | ||
| 21 | <li class="flex items-center w-1/2 md:w-full"> | 22 | <li class="flex items-center w-1/2 md:w-full"> | ||
| 22 | <i class="iconoir-community mr-2 md:text-3xl"></i> | 23 | <i class="iconoir-community mr-2 md:text-3xl"></i> | ||
| 23 | - | <a href="#users">Users</a> | 24 | + | <a href="/admin/users">Users</a> |
| 24 | </li> | 25 | </li> | ||
| 25 | <li class="flex items-center w-1/2 md:w-full"> | 26 | <li class="flex items-center w-1/2 md:w-full"> | ||
| 26 | <i class="iconoir-database-settings mr-2 md:text-3xl"></i> | 27 | <i class="iconoir-database-settings mr-2 md:text-3xl"></i> | ||
| 27 | - | <a href="#gordon-settings">Settings</a> | 28 | + | <a href="/admin/settings">Settings</a> |
| 28 | </li> | 29 | </li> | ||
| 29 | </ul> | 30 | </ul> | ||
| 30 | </div> | 31 | </div> | ||
A internal/httpserve/handler/docker.go +31 -0
| @@ -0,0 +1,31 @@ | |||
| 1 | + | package handler | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "net/http" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/gordon/pkg/utils/docker" | |
| 7 | + | "github.com/bnema/gordon/pkg/utils/sanitize" | |
| 8 | + | "github.com/labstack/echo/v4" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | func ImageManagerHandler(c echo.Context) error { | |
| 12 | + | // Retrieve the list of container images. | |
| 13 | + | images, err := docker.ListContainerImages() | |
| 14 | + | if err != nil { | |
| 15 | + | rawErrHTML := `<div id="container-images">Error: ` + err.Error() + `</div>` | |
| 16 | + | sanitizedHTML, err := sanitize.SanitizeHTML(rawErrHTML) | |
| 17 | + | if err != nil { | |
| 18 | + | // Handle the sanitization error | |
| 19 | + | return c.String(http.StatusInternalServerError, "An error occurred during sanitization") | |
| 20 | + | } | |
| 21 | + | return c.HTML(http.StatusInternalServerError, sanitizedHTML) | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | // Create a map to hold the data you want to pass to the template. | |
| 25 | + | data := map[string]interface{}{ | |
| 26 | + | "Images": images, | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | // Render the template with the data. | |
| 30 | + | return c.Render(http.StatusOK, "manager", data) | |
| 31 | + | } | |
| @@ -0,0 +1,31 @@ | |||||
| 1 | + | package handler | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "net/http" | |||
| 5 | + | ||||
| 6 | + | "github.com/bnema/gordon/pkg/utils/docker" | |||
| 7 | + | "github.com/bnema/gordon/pkg/utils/sanitize" | |||
| 8 | + | "github.com/labstack/echo/v4" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | func ImageManagerHandler(c echo.Context) error { | |||
| 12 | + | // Retrieve the list of container images. | |||
| 13 | + | images, err := docker.ListContainerImages() | |||
| 14 | + | if err != nil { | |||
| 15 | + | rawErrHTML := `<div id="container-images">Error: ` + err.Error() + `</div>` | |||
| 16 | + | sanitizedHTML, err := sanitize.SanitizeHTML(rawErrHTML) | |||
| 17 | + | if err != nil { | |||
| 18 | + | // Handle the sanitization error | |||
| 19 | + | return c.String(http.StatusInternalServerError, "An error occurred during sanitization") | |||
| 20 | + | } | |||
| 21 | + | return c.HTML(http.StatusInternalServerError, sanitizedHTML) | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | // Create a map to hold the data you want to pass to the template. | |||
| 25 | + | data := map[string]interface{}{ | |||
| 26 | + | "Images": images, | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | // Render the template with the data. | |||
| 30 | + | return c.Render(http.StatusOK, "manager", data) | |||
| 31 | + | } | |||
M internal/httpserve/router.go +1 -1
| @@ -26,7 +26,7 @@ func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo { | |||
| 26 | 26 | bindAdminRoute(e, a, AdminPath) | |
| 27 | 27 | bindStaticRoute(e, a, "/*") | |
| 28 | 28 | bindLoginRoute(e, a, AdminPath) | |
| 29 | - | ||
| 29 | + | e.GET("/image-manager", handler.ImageManagerHandler) | |
| 30 | 30 | // Protect the root path with a 403 | |
| 31 | 31 | e.GET("/", func(c echo.Context) error { | |
| 32 | 32 | return c.String(403, "403 Forbidden") | |
| @@ -26,7 +26,7 @@ func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo { | |||||
| 26 | bindAdminRoute(e, a, AdminPath) | 26 | bindAdminRoute(e, a, AdminPath) | ||
| 27 | bindStaticRoute(e, a, "/*") | 27 | bindStaticRoute(e, a, "/*") | ||
| 28 | bindLoginRoute(e, a, AdminPath) | 28 | bindLoginRoute(e, a, AdminPath) | ||
| 29 | - | 29 | + | e.GET("/image-manager", handler.ImageManagerHandler) | |
| 30 | // Protect the root path with a 403 | 30 | // Protect the root path with a 403 | ||
| 31 | e.GET("/", func(c echo.Context) error { | 31 | e.GET("/", func(c echo.Context) error { | ||
| 32 | return c.String(403, "403 Forbidden") | 32 | return c.String(403, "403 Forbidden") | ||
M internal/webui/public/assets/css/tailwind.css +20 -0
| @@ -558,6 +558,10 @@ video { | |||
| 558 | 558 | margin-right: 0.5rem; | |
| 559 | 559 | } | |
| 560 | 560 | ||
| 561 | + | .mt-20 { | |
| 562 | + | margin-top: 5rem; | |
| 563 | + | } | |
| 564 | + | ||
| 561 | 565 | .mt-4 { | |
| 562 | 566 | margin-top: 1rem; | |
| 563 | 567 | } | |
| @@ -623,6 +627,10 @@ video { | |||
| 623 | 627 | flex-grow: 1; | |
| 624 | 628 | } | |
| 625 | 629 | ||
| 630 | + | .transform { | |
| 631 | + | transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); | |
| 632 | + | } | |
| 633 | + | ||
| 626 | 634 | .flex-row { | |
| 627 | 635 | flex-direction: row; | |
| 628 | 636 | } | |
| @@ -818,6 +826,14 @@ video { | |||
| 818 | 826 | outline-offset: 2px; | |
| 819 | 827 | } | |
| 820 | 828 | ||
| 829 | + | .transition { | |
| 830 | + | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; | |
| 831 | + | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; | |
| 832 | + | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; | |
| 833 | + | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |
| 834 | + | transition-duration: 150ms; | |
| 835 | + | } | |
| 836 | + | ||
| 821 | 837 | /* Import IBM Plex Fonts */ | |
| 822 | 838 | ||
| 823 | 839 | @font-face { | |
| @@ -949,6 +965,10 @@ video { | |||
| 949 | 965 | margin-left: 1rem; | |
| 950 | 966 | } | |
| 951 | 967 | ||
| 968 | + | .md\:mt-24 { | |
| 969 | + | margin-top: 6rem; | |
| 970 | + | } | |
| 971 | + | ||
| 952 | 972 | .md\:flex { | |
| 953 | 973 | display: flex; | |
| 954 | 974 | } | |
| @@ -558,6 +558,10 @@ video { | |||||
| 558 | margin-right: 0.5rem; | 558 | margin-right: 0.5rem; | ||
| 559 | } | 559 | } | ||
| 560 | 560 | ||||
| 561 | + | .mt-20 { | |||
| 562 | + | margin-top: 5rem; | |||
| 563 | + | } | |||
| 564 | + | ||||
| 561 | .mt-4 { | 565 | .mt-4 { | ||
| 562 | margin-top: 1rem; | 566 | margin-top: 1rem; | ||
| 563 | } | 567 | } | ||
| @@ -623,6 +627,10 @@ video { | |||||
| 623 | flex-grow: 1; | 627 | flex-grow: 1; | ||
| 624 | } | 628 | } | ||
| 625 | 629 | ||||
| 630 | + | .transform { | |||
| 631 | + | transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); | |||
| 632 | + | } | |||
| 633 | + | ||||
| 626 | .flex-row { | 634 | .flex-row { | ||
| 627 | flex-direction: row; | 635 | flex-direction: row; | ||
| 628 | } | 636 | } | ||
| @@ -818,6 +826,14 @@ video { | |||||
| 818 | outline-offset: 2px; | 826 | outline-offset: 2px; | ||
| 819 | } | 827 | } | ||
| 820 | 828 | ||||
| 829 | + | .transition { | |||
| 830 | + | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; | |||
| 831 | + | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; | |||
| 832 | + | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; | |||
| 833 | + | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |||
| 834 | + | transition-duration: 150ms; | |||
| 835 | + | } | |||
| 836 | + | ||||
| 821 | /* Import IBM Plex Fonts */ | 837 | /* Import IBM Plex Fonts */ | ||
| 822 | 838 | ||||
| 823 | @font-face { | 839 | @font-face { | ||
| @@ -949,6 +965,10 @@ video { | |||||
| 949 | margin-left: 1rem; | 965 | margin-left: 1rem; | ||
| 950 | } | 966 | } | ||
| 951 | 967 | ||||
| 968 | + | .md\:mt-24 { | |||
| 969 | + | margin-top: 6rem; | |||
| 970 | + | } | |||
| 971 | + | ||||
| 952 | .md\:flex { | 972 | .md\:flex { | ||
| 953 | display: flex; | 973 | display: flex; | ||
| 954 | } | 974 | } | ||
A internal/webui/public/assets/js/custom.js +15 -0
| @@ -0,0 +1,15 @@ | |||
| 1 | + | document.body.addEventListener('htmx:afterRequest', function (evt) { | |
| 2 | + | const targetError = evt.target.attributes.getNamedItem('hx-target-error'); | |
| 3 | + | if (evt.detail.failed && targetError) { | |
| 4 | + | const errorElement = document.getElementById(targetError.value); | |
| 5 | + | errorElement.innerHTML = evt.detail.xhr.responseText; // Set the response text as content | |
| 6 | + | errorElement.style.display = "inline"; | |
| 7 | + | } | |
| 8 | + | }); | |
| 9 | + | ||
| 10 | + | document.body.addEventListener('htmx:beforeRequest', function (evt) { | |
| 11 | + | const targetError = evt.target.attributes.getNamedItem('hx-target-error'); | |
| 12 | + | if (targetError) { | |
| 13 | + | document.getElementById(targetError.value).style.display = "none"; | |
| 14 | + | } | |
| 15 | + | }); | |
| @@ -0,0 +1,15 @@ | |||||
| 1 | + | document.body.addEventListener('htmx:afterRequest', function (evt) { | |||
| 2 | + | const targetError = evt.target.attributes.getNamedItem('hx-target-error'); | |||
| 3 | + | if (evt.detail.failed && targetError) { | |||
| 4 | + | const errorElement = document.getElementById(targetError.value); | |||
| 5 | + | errorElement.innerHTML = evt.detail.xhr.responseText; // Set the response text as content | |||
| 6 | + | errorElement.style.display = "inline"; | |||
| 7 | + | } | |||
| 8 | + | }); | |||
| 9 | + | ||||
| 10 | + | document.body.addEventListener('htmx:beforeRequest', function (evt) { | |||
| 11 | + | const targetError = evt.target.attributes.getNamedItem('hx-target-error'); | |||
| 12 | + | if (targetError) { | |||
| 13 | + | document.getElementById(targetError.value).style.display = "none"; | |||
| 14 | + | } | |||
| 15 | + | }); | |||
M pkg/utils/docker/initclient.go +79 -16
| @@ -1,9 +1,25 @@ | |||
| 1 | 1 | package docker | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "log" | |
| 4 | + | "context" | |
| 5 | + | "fmt" | |
| 6 | + | "net" | |
| 7 | + | "net/http" | |
| 8 | + | "net/url" | |
| 9 | + | "time" | |
| 10 | + | ||
| 11 | + | "github.com/docker/docker/api/types" | |
| 12 | + | "github.com/docker/docker/client" | |
| 5 | 13 | ) | |
| 6 | 14 | ||
| 15 | + | type ContainerImage struct { | |
| 16 | + | ID string | |
| 17 | + | Repository string | |
| 18 | + | Tag string | |
| 19 | + | Created time.Time | |
| 20 | + | Size int64 | |
| 21 | + | } | |
| 22 | + | ||
| 7 | 23 | type ContainerEngineClient interface { | |
| 8 | 24 | InitializeClient(*Config) error | |
| 9 | 25 | // Add other methods that both Docker and Podman should implement | |
| @@ -14,29 +30,76 @@ type Config struct { | |||
| 14 | 30 | } | |
| 15 | 31 | ||
| 16 | 32 | var engineClient ContainerEngineClient | |
| 33 | + | var dockerCli *client.Client | |
| 17 | 34 | ||
| 18 | 35 | // DockerClient implements the ContainerEngineClient interface for Docker | |
| 19 | 36 | type DockerClient struct{} | |
| 20 | 37 | ||
| 21 | 38 | func (d *DockerClient) InitializeClient(config *Config) error { | |
| 22 | - | // Initialize Docker client here | |
| 23 | - | return nil // Return an error if something goes wrong | |
| 24 | - | } | |
| 39 | + | // Create a custom HTTP client that uses the Docker socket | |
| 40 | + | httpClient := &http.Client{ | |
| 41 | + | Transport: &http.Transport{ | |
| 42 | + | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { | |
| 43 | + | return net.Dial("unix", config.Sock) | |
| 44 | + | }, | |
| 45 | + | }, | |
| 46 | + | } | |
| 25 | 47 | ||
| 26 | - | // PodmanClient implements the ContainerEngineClient interface for Podman | |
| 27 | - | type PodmanClient struct{} | |
| 48 | + | // Initialize Docker client | |
| 49 | + | cli, err := client.NewClientWithOpts( | |
| 50 | + | client.WithHTTPClient(httpClient), | |
| 51 | + | client.WithAPIVersionNegotiation(), | |
| 52 | + | client.WithScheme("unix"), | |
| 53 | + | client.WithHost(url.PathEscape(config.Sock)), | |
| 54 | + | ) | |
| 55 | + | if err != nil { | |
| 56 | + | return err | |
| 57 | + | } | |
| 28 | 58 | ||
| 29 | - | func (p *PodmanClient) InitializeClient(config *Config) error { | |
| 30 | - | // Initialize Podman client here | |
| 31 | - | return nil // Return an error if something goes wrong | |
| 59 | + | dockerCli = cli | |
| 60 | + | return nil | |
| 32 | 61 | } | |
| 33 | - | func InitializeEngineClient(config *Config) { | |
| 34 | - | if config.PodmanEnable { | |
| 35 | - | engineClient = &PodmanClient{} | |
| 36 | - | } else { | |
| 37 | - | engineClient = &DockerClient{} | |
| 62 | + | ||
| 63 | + | func ListContainerImages() ([]ContainerImage, error) { | |
| 64 | + | // Check if the Docker client has been initialized | |
| 65 | + | if dockerCli == nil { | |
| 66 | + | return nil, fmt.Errorf("Docker client has not been initialized") | |
| 38 | 67 | } | |
| 39 | - | if err := engineClient.InitializeClient(config); err != nil { | |
| 40 | - | log.Fatalf("Error initializing container engine client: %v", err) | |
| 68 | + | ||
| 69 | + | // List images using the Docker client | |
| 70 | + | images, err := dockerCli.ImageList(context.Background(), types.ImageListOptions{}) | |
| 71 | + | if err != nil { | |
| 72 | + | return nil, err | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | // Populate the ContainerImage slice | |
| 76 | + | var containerImages []ContainerImage | |
| 77 | + | for _, image := range images { | |
| 78 | + | containerImages = append(containerImages, ContainerImage{ | |
| 79 | + | ID: image.ID, | |
| 80 | + | // Repository and Tag information may require additional processing | |
| 81 | + | Created: time.Unix(image.Created, 0), | |
| 82 | + | Size: image.Size, | |
| 83 | + | }) | |
| 41 | 84 | } | |
| 85 | + | ||
| 86 | + | return containerImages, nil | |
| 42 | 87 | } | |
| 88 | + | ||
| 89 | + | // // PodmanClient implements the ContainerEngineClient interface for Podman | |
| 90 | + | // type PodmanClient struct{} | |
| 91 | + | ||
| 92 | + | // func (p *PodmanClient) InitializeClient(config *Config) error { | |
| 93 | + | // // Initialize Podman client here | |
| 94 | + | // return nil // Return an error if something goes wrong | |
| 95 | + | // } | |
| 96 | + | // func InitializeEngineClient(config *Config) { | |
| 97 | + | // if config.PodmanEnable { | |
| 98 | + | // engineClient = &PodmanClient{} | |
| 99 | + | // } else { | |
| 100 | + | // engineClient = &DockerClient{} | |
| 101 | + | // } | |
| 102 | + | // if err := engineClient.InitializeClient(config); err != nil { | |
| 103 | + | // log.Fatalf("Error initializing container engine client: %v", err) | |
| 104 | + | // } | |
| 105 | + | // } | |
| @@ -1,9 +1,25 @@ | |||||
| 1 | package docker | 1 | package docker | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | - | "log" | 4 | + | "context" |
| 5 | + | "fmt" | |||
| 6 | + | "net" | |||
| 7 | + | "net/http" | |||
| 8 | + | "net/url" | |||
| 9 | + | "time" | |||
| 10 | + | ||||
| 11 | + | "github.com/docker/docker/api/types" | |||
| 12 | + | "github.com/docker/docker/client" | |||
| 5 | ) | 13 | ) | ||
| 6 | 14 | ||||
| 15 | + | type ContainerImage struct { | |||
| 16 | + | ID string | |||
| 17 | + | Repository string | |||
| 18 | + | Tag string | |||
| 19 | + | Created time.Time | |||
| 20 | + | Size int64 | |||
| 21 | + | } | |||
| 22 | + | ||||
| 7 | type ContainerEngineClient interface { | 23 | type ContainerEngineClient interface { | ||
| 8 | InitializeClient(*Config) error | 24 | InitializeClient(*Config) error | ||
| 9 | // Add other methods that both Docker and Podman should implement | 25 | // Add other methods that both Docker and Podman should implement | ||
| @@ -14,29 +30,76 @@ type Config struct { | |||||
| 14 | } | 30 | } | ||
| 15 | 31 | ||||
| 16 | var engineClient ContainerEngineClient | 32 | var engineClient ContainerEngineClient | ||
| 33 | + | var dockerCli *client.Client | |||
| 17 | 34 | ||||
| 18 | // DockerClient implements the ContainerEngineClient interface for Docker | 35 | // DockerClient implements the ContainerEngineClient interface for Docker | ||
| 19 | type DockerClient struct{} | 36 | type DockerClient struct{} | ||
| 20 | 37 | ||||
| 21 | func (d *DockerClient) InitializeClient(config *Config) error { | 38 | func (d *DockerClient) InitializeClient(config *Config) error { | ||
| 22 | - | // Initialize Docker client here | 39 | + | // Create a custom HTTP client that uses the Docker socket |
| 23 | - | return nil // Return an error if something goes wrong | 40 | + | httpClient := &http.Client{ |
| 24 | - | } | 41 | + | Transport: &http.Transport{ |
| 42 | + | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { | |||
| 43 | + | return net.Dial("unix", config.Sock) | |||
| 44 | + | }, | |||
| 45 | + | }, | |||
| 46 | + | } | |||
| 25 | 47 | ||||
| 26 | - | // PodmanClient implements the ContainerEngineClient interface for Podman | 48 | + | // Initialize Docker client |
| 27 | - | type PodmanClient struct{} | 49 | + | cli, err := client.NewClientWithOpts( |
| 50 | + | client.WithHTTPClient(httpClient), | |||
| 51 | + | client.WithAPIVersionNegotiation(), | |||
| 52 | + | client.WithScheme("unix"), | |||
| 53 | + | client.WithHost(url.PathEscape(config.Sock)), | |||
| 54 | + | ) | |||
| 55 | + | if err != nil { | |||
| 56 | + | return err | |||
| 57 | + | } | |||
| 28 | 58 | ||||
| 29 | - | func (p *PodmanClient) InitializeClient(config *Config) error { | 59 | + | dockerCli = cli |
| 30 | - | // Initialize Podman client here | 60 | + | return nil |
| 31 | - | return nil // Return an error if something goes wrong | |||
| 32 | } | 61 | } | ||
| 33 | - | func InitializeEngineClient(config *Config) { | 62 | + | |
| 34 | - | if config.PodmanEnable { | 63 | + | func ListContainerImages() ([]ContainerImage, error) { |
| 35 | - | engineClient = &PodmanClient{} | 64 | + | // Check if the Docker client has been initialized |
| 36 | - | } else { | 65 | + | if dockerCli == nil { |
| 37 | - | engineClient = &DockerClient{} | 66 | + | return nil, fmt.Errorf("Docker client has not been initialized") |
| 38 | } | 67 | } | ||
| 39 | - | if err := engineClient.InitializeClient(config); err != nil { | 68 | + | |
| 40 | - | log.Fatalf("Error initializing container engine client: %v", err) | 69 | + | // List images using the Docker client |
| 70 | + | images, err := dockerCli.ImageList(context.Background(), types.ImageListOptions{}) | |||
| 71 | + | if err != nil { | |||
| 72 | + | return nil, err | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | // Populate the ContainerImage slice | |||
| 76 | + | var containerImages []ContainerImage | |||
| 77 | + | for _, image := range images { | |||
| 78 | + | containerImages = append(containerImages, ContainerImage{ | |||
| 79 | + | ID: image.ID, | |||
| 80 | + | // Repository and Tag information may require additional processing | |||
| 81 | + | Created: time.Unix(image.Created, 0), | |||
| 82 | + | Size: image.Size, | |||
| 83 | + | }) | |||
| 41 | } | 84 | } | ||
| 85 | + | ||||
| 86 | + | return containerImages, nil | |||
| 42 | } | 87 | } | ||
| 88 | + | ||||
| 89 | + | // // PodmanClient implements the ContainerEngineClient interface for Podman | |||
| 90 | + | // type PodmanClient struct{} | |||
| 91 | + | ||||
| 92 | + | // func (p *PodmanClient) InitializeClient(config *Config) error { | |||
| 93 | + | // // Initialize Podman client here | |||
| 94 | + | // return nil // Return an error if something goes wrong | |||
| 95 | + | // } | |||
| 96 | + | // func InitializeEngineClient(config *Config) { | |||
| 97 | + | // if config.PodmanEnable { | |||
| 98 | + | // engineClient = &PodmanClient{} | |||
| 99 | + | // } else { | |||
| 100 | + | // engineClient = &DockerClient{} | |||
| 101 | + | // } | |||
| 102 | + | // if err := engineClient.InitializeClient(config); err != nil { | |||
| 103 | + | // log.Fatalf("Error initializing container engine client: %v", err) | |||
| 104 | + | // } | |||
| 105 | + | // } | |||
A pkg/utils/sanitize/htmlsanitizer.go +22 -0
| @@ -0,0 +1,22 @@ | |||
| 1 | + | package sanitize | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "errors" | |
| 5 | + | ||
| 6 | + | "github.com/microcosm-cc/bluemonday" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // SanitizeHTML takes a string containing raw HTML and returns a sanitized string. | |
| 10 | + | func SanitizeHTML(rawHTML string) (string, error) { | |
| 11 | + | // Use bluemonday's UGCPolicy for user generated content | |
| 12 | + | p := bluemonday.UGCPolicy() | |
| 13 | + | ||
| 14 | + | // Sanitize the raw HTML string | |
| 15 | + | sanitized := p.Sanitize(rawHTML) | |
| 16 | + | ||
| 17 | + | if sanitized == "" && rawHTML != "" { | |
| 18 | + | return "", errors.New("Sanitization removed all content") | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | return sanitized, nil | |
| 22 | + | } | |
| @@ -0,0 +1,22 @@ | |||||
| 1 | + | package sanitize | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "errors" | |||
| 5 | + | ||||
| 6 | + | "github.com/microcosm-cc/bluemonday" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | // SanitizeHTML takes a string containing raw HTML and returns a sanitized string. | |||
| 10 | + | func SanitizeHTML(rawHTML string) (string, error) { | |||
| 11 | + | // Use bluemonday's UGCPolicy for user generated content | |||
| 12 | + | p := bluemonday.UGCPolicy() | |||
| 13 | + | ||||
| 14 | + | // Sanitize the raw HTML string | |||
| 15 | + | sanitized := p.Sanitize(rawHTML) | |||
| 16 | + | ||||
| 17 | + | if sanitized == "" && rawHTML != "" { | |||
| 18 | + | return "", errors.New("Sanitization removed all content") | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | return sanitized, nil | |||
| 22 | + | } | |||