detached 396cee3

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
55 require (
66 github.com/PuerkitoBio/goquery v1.8.1
77 github.com/docker/docker v24.0.6+incompatible
8+ github.com/google/safehtml v0.1.0
89 github.com/gorilla/sessions v1.2.1
910 github.com/joho/godotenv v1.5.1
1011 github.com/labstack/echo-contrib v0.15.0
@@ -17,30 +18,29 @@ require (
1718 require (
1819 github.com/Microsoft/go-winio v0.6.1 // indirect
1920 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
2122 github.com/docker/distribution v2.8.2+incompatible // indirect
2223 github.com/docker/go-connections v0.4.0 // indirect
2324 github.com/docker/go-units v0.5.0 // indirect
2425 github.com/gogo/protobuf v1.3.2 // indirect
2526 github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
2627 github.com/golang/protobuf v1.5.3 // indirect
28+ github.com/google/go-cmp v0.5.9 // indirect
2729 github.com/gorilla/context v1.1.1 // indirect
30+ github.com/gorilla/css v1.0.0 // indirect
2831 github.com/gorilla/mux v1.6.2 // indirect
2932 github.com/gorilla/securecookie v1.1.1 // indirect
30- github.com/klauspost/compress v1.16.7 // indirect
3133 github.com/labstack/gommon v0.4.0 // indirect
3234 github.com/mattn/go-colorable v0.1.13 // indirect
3335 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
3637 github.com/moby/term v0.5.0 // indirect
3738 github.com/morikuni/aec v1.0.0 // indirect
3839 github.com/opencontainers/go-digest v1.0.0 // indirect
3940 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
40- github.com/opencontainers/runc v1.1.9 // indirect
4141 github.com/pkg/errors v0.9.1 // indirect
4242 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
4444 github.com/valyala/bytebufferpool v1.0.0 // indirect
4545 github.com/valyala/fasttemplate v1.2.2 // indirect
4646 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.16 github.com/PuerkitoBio/goquery v1.8.1
7 github.com/docker/docker v24.0.6+incompatible7 github.com/docker/docker v24.0.6+incompatible
8+ github.com/google/safehtml v0.1.0
8 github.com/gorilla/sessions v1.2.19 github.com/gorilla/sessions v1.2.1
9 github.com/joho/godotenv v1.5.110 github.com/joho/godotenv v1.5.1
10 github.com/labstack/echo-contrib v0.15.011 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 // indirect19 github.com/Microsoft/go-winio v0.6.1 // indirect
19 github.com/andybalholm/cascadia v1.3.1 // indirect20 github.com/andybalholm/cascadia v1.3.1 // indirect
20- github.com/containerd/containerd v1.7.6 // indirect21+ github.com/aymerick/douceur v0.2.0 // indirect
21 github.com/docker/distribution v2.8.2+incompatible // indirect22 github.com/docker/distribution v2.8.2+incompatible // indirect
22 github.com/docker/go-connections v0.4.0 // indirect23 github.com/docker/go-connections v0.4.0 // indirect
23 github.com/docker/go-units v0.5.0 // indirect24 github.com/docker/go-units v0.5.0 // indirect
24 github.com/gogo/protobuf v1.3.2 // indirect25 github.com/gogo/protobuf v1.3.2 // indirect
25 github.com/golang-jwt/jwt v3.2.2+incompatible // indirect26 github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
26 github.com/golang/protobuf v1.5.3 // indirect27 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 // indirect29 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 // indirect31 github.com/gorilla/mux v1.6.2 // indirect
29 github.com/gorilla/securecookie v1.1.1 // indirect32 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 // indirect33 github.com/labstack/gommon v0.4.0 // indirect
32 github.com/mattn/go-colorable v0.1.13 // indirect34 github.com/mattn/go-colorable v0.1.13 // indirect
33 github.com/mattn/go-isatty v0.0.19 // indirect35 github.com/mattn/go-isatty v0.0.19 // indirect
34- github.com/moby/patternmatcher v0.6.0 // indirect36+ 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 // indirect37 github.com/moby/term v0.5.0 // indirect
37 github.com/morikuni/aec v1.0.0 // indirect38 github.com/morikuni/aec v1.0.0 // indirect
38 github.com/opencontainers/go-digest v1.0.0 // indirect39 github.com/opencontainers/go-digest v1.0.0 // indirect
39 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect40 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 // indirect41 github.com/pkg/errors v0.9.1 // indirect
42 github.com/rogpeppe/go-internal v1.11.0 // indirect42 github.com/rogpeppe/go-internal v1.11.0 // indirect
43- github.com/sirupsen/logrus v1.9.3 // indirect43+ github.com/stretchr/testify v1.8.2 // indirect
44 github.com/valyala/bytebufferpool v1.0.0 // indirect44 github.com/valyala/bytebufferpool v1.0.0 // indirect
45 github.com/valyala/fasttemplate v1.2.2 // indirect45 github.com/valyala/fasttemplate v1.2.2 // indirect
46 golang.org/x/crypto v0.11.0 // indirect46 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 @@
11 <!DOCTYPE html>
22 <html lang="en">
33
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" .}}
155
166 <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>
497
8+ {{template "leftmenu.gohtml" .}}
509 <div class="flex-1 flex flex-col md:pt-10 space-y-4 p-4 md:ml-4">
5110 <div class="hidden md:flex mb-4 justify-end">
5211 <div class="w-72 h-10 bg-gray-300 rounded-lg">
@@ -58,7 +17,7 @@
5817 </div>
5918 <div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow">
6019 <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>
6221 </div>
6322 <div class="w-full md:w-2/6 flex flex-col space-y-4">
6423 <div class="bg-white p-4 rounded-md shadow mb-4">
@@ -73,6 +32,9 @@
7332 </div>
7433 </div>
7534 </div>
35+ <!-- footer -->
36+ {{template "footer.gohtml" .}}
37+
7638 </body>
7739
7840 </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 @@
11 <!-- manager.gohtml -->
2-{{template "header.gohtml" .}}
32 <!DOCTYPE html>
43 <html lang="en">
54
5+{{template "header.gohtml" .}}
6+
67 <body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-8 p-0">
78 {{template "leftmenu.gohtml" .}}
89 <!-- 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">
1115 <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+
1334 </div>
1435 </div>
36+ </div>
1537
16- <!-- footer -->
17- {{template "footer.gohtml" .}}
38+ <!-- footer -->
39+ {{template "footer.gohtml" .}}
1840 </body>
1941
2042 </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 @@
44 <title>{{ .header.meta.title }}</title>
55 <link rel="stylesheet" href="/assets/css/tailwind.css" />
66 <link rel="stylesheet" href="/libs/iconoir.css" />
7- <script src="/libs/alpine.min.js"></script>
7+ <script src="/libs/alpine.min.js" defer></script>
88 <script src="/libs/htmx.min.js"></script>
99 <script src="/libs/htmx-sse.js"></script>
10- <script src="assets/js/custom.js"></script>
10+ <script src="/assets/js/custom.js" defer></script>
1111 </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 @@
99 </div>
1010 <div class="w-full">
1111 <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">
1314 <li class="flex items-center w-1/2 md:w-full">
1415 <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i>
15- <a href="#">Dashboard</a>
16+ <a href="/admin">Dashboard</a>
1617 </li>
1718 <li class="flex items-center w-1/2 md:w-full">
1819 <i class="iconoir-server mr-2 md:text-3xl"></i>
19- <a href="#manager">Manager</a>
20+ <a href="/admin/manager">Manager</a>
2021 </li>
2122 <li class="flex items-center w-1/2 md:w-full">
2223 <i class="iconoir-community mr-2 md:text-3xl"></i>
23- <a href="#users">Users</a>
24+ <a href="/admin/users">Users</a>
2425 </li>
2526 <li class="flex items-center w-1/2 md:w-full">
2627 <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>
2829 </li>
2930 </ul>
3031 </div>
@@ -9,22 +9,23 @@
9 </div>9 </div>
10 <div class="w-full">10 <div class="w-full">
11 <ul11 <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 {
2626 bindAdminRoute(e, a, AdminPath)
2727 bindStaticRoute(e, a, "/*")
2828 bindLoginRoute(e, a, AdminPath)
29-
29+ e.GET("/image-manager", handler.ImageManagerHandler)
3030 // Protect the root path with a 403
3131 e.GET("/", func(c echo.Context) error {
3232 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 40330 // 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 {
558558 margin-right: 0.5rem;
559559 }
560560
561+.mt-20 {
562+ margin-top: 5rem;
563+}
564+
561565 .mt-4 {
562566 margin-top: 1rem;
563567 }
@@ -623,6 +627,10 @@ video {
623627 flex-grow: 1;
624628 }
625629
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+
626634 .flex-row {
627635 flex-direction: row;
628636 }
@@ -818,6 +826,14 @@ video {
818826 outline-offset: 2px;
819827 }
820828
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+
821837 /* Import IBM Plex Fonts */
822838
823839 @font-face {
@@ -949,6 +965,10 @@ video {
949965 margin-left: 1rem;
950966 }
951967
968+ .md\:mt-24 {
969+ margin-top: 6rem;
970+ }
971+
952972 .md\:flex {
953973 display: flex;
954974 }
@@ -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 @@
11 package docker
22
33 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"
513 )
614
15+type ContainerImage struct {
16+ ID string
17+ Repository string
18+ Tag string
19+ Created time.Time
20+ Size int64
21+}
22+
723 type ContainerEngineClient interface {
824 InitializeClient(*Config) error
925 // Add other methods that both Docker and Podman should implement
@@ -14,29 +30,76 @@ type Config struct {
1430 }
1531
1632 var engineClient ContainerEngineClient
33+var dockerCli *client.Client
1734
1835 // DockerClient implements the ContainerEngineClient interface for Docker
1936 type DockerClient struct{}
2037
2138 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+ }
2547
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+ }
2858
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
3261 }
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")
3867 }
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+ })
4184 }
85+
86+ return containerImages, nil
4287 }
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 docker1 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) error24 InitializeClient(*Config) error
9 // Add other methods that both Docker and Podman should implement25 // Add other methods that both Docker and Podman should implement
@@ -14,29 +30,76 @@ type Config struct {
14 }30 }
15 31
16 var engineClient ContainerEngineClient32 var engineClient ContainerEngineClient
33+var dockerCli *client.Client
17 34
18 // DockerClient implements the ContainerEngineClient interface for Docker35 // 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 here39+ // Create a custom HTTP client that uses the Docker socket
23- return nil // Return an error if something goes wrong40+ 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 Podman48+ // 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 here60+ 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+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close