detached 7902d0a

Add user settings icon and logout on left menu

commit
7902d0a83e172af2c08f682a790bf78dd0d3e5cb
author
bnema · · 2023-09-25 08:10 UTC
parent
d925f24
signature
unsigned
5 files · +157 -33
M internal/gotemplate/models/html/fragments/createcontainer.gohtml +13 -14
@@ -8,11 +8,22 @@
88 <div class="text-red-500">{{.ErrorMessage}}</div>
99 {{ end }}
1010 <form
11- action="/create-container"
11+ action="/create-container/{{.ShortID}}"
1212 method="post"
13- hx-post="/htmx/create-container"
13+ hx-post="/htmx/create-container/{{.ShortID}}"
1414 hx-swap="innerHTML"
1515 >
16+ <!-- Image Name -->
17+ <div class="mb-4">
18+ <label class="block">Image name</label>
19+ <input
20+ type="text"
21+ name="image_name"
22+ class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
23+ value="{{.ImageName}}"
24+ readonly
25+ />
26+ </div>
1627 <!-- Container Name -->
1728 <div class="mb-4">
1829 <label class="block">Container name</label>
@@ -34,18 +45,6 @@
3445 />
3546 </div>
3647
37- <!-- Image Name -->
38- <div class="mb-4">
39- <label class="block">Image name</label>
40- <input
41- type="text"
42- name="image_name"
43- class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
44- value="{{.ImageName}}"
45- readonly
46- />
47- </div>
48-
4948 <!-- Ports -->
5049 <div class="mb-4">
5150 <label class="block">Ports</label>
@@ -8,11 +8,22 @@
8 <div class="text-red-500">{{.ErrorMessage}}</div>8 <div class="text-red-500">{{.ErrorMessage}}</div>
9 {{ end }}9 {{ end }}
10 <form10 <form
11- action="/create-container"11+ action="/create-container/{{.ShortID}}"
12 method="post"12 method="post"
13- hx-post="/htmx/create-container"13+ hx-post="/htmx/create-container/{{.ShortID}}"
14 hx-swap="innerHTML"14 hx-swap="innerHTML"
15 >15 >
16+ <!-- Image Name -->
17+ <div class="mb-4">
18+ <label class="block">Image name</label>
19+ <input
20+ type="text"
21+ name="image_name"
22+ class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
23+ value="{{.ImageName}}"
24+ readonly
25+ />
26+ </div>
16 <!-- Container Name -->27 <!-- Container Name -->
17 <div class="mb-4">28 <div class="mb-4">
18 <label class="block">Container name</label>29 <label class="block">Container name</label>
@@ -34,18 +45,6 @@
34 />45 />
35 </div>46 </div>
36 47
37- <!-- Image Name -->
38- <div class="mb-4">
39- <label class="block">Image name</label>
40- <input
41- type="text"
42- name="image_name"
43- class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
44- value="{{.ImageName}}"
45- readonly
46- />
47- </div>
48-
49 <!-- Ports -->48 <!-- Ports -->
50 <div class="mb-4">49 <div class="mb-4">
51 <label class="block">Ports</label>50 <label class="block">Ports</label>
M internal/gotemplate/models/html/fragments/leftmenu.gohtml +33 -9
@@ -1,15 +1,24 @@
1-<div class="w-full md:w-1/5 flex flex-col space-evenly p-4 md:p-0">
1+<div
2+ class="w-full md:w-1/5 flex flex-col space-evenly md:p-0 max-w-fit min-w-fit"
3+>
24 <div class="hidden md:flex justify-center p-4">
35 <img src="/assets/imgs/gordon-mascot-hq-trsp.png" alt="logo" class="h-32" />
46 </div>
5- <div class="p-4 flex flex-col bg-gordonmascot-lblue rounded-lg shadow space-y-4">
7+ <div
8+ class="p-4 xl:p-8 flex flex-col bg-gordonmascot-lblue rounded-lg shadow space-y-4"
9+ >
610 <div class="w-full flex justify-center">
7- <h1 class="gordon-title text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter">Gordon</h1>
11+ <h1
12+ class="gordon-title text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter"
13+ >
14+ Gordon
15+ </h1>
816 <span class="text-gordonmascot-mblue text-sm">{{ .BuildVersion }}</span>
917 </div>
1018 <div class="w-full">
1119 <ul
12- class="flex flex-wrap md:flex-col md:space-y-6 md:py-8 font-semibold text-gray-100 text-lg md:text-2xl md:pl-4 min-w-fit">
20+ class="flex flex-wrap md:flex-col md:space-y-6 md:py-8 font-semibold text-gray-100 text-lg md:text-2xl min-w-fit justify-center"
21+ >
1322 <li class="flex items-center w-1/2 md:w-full">
1423 <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i>
1524 <a href="/admin">Dashboard</a>
@@ -18,15 +27,30 @@
1827 <i class="iconoir-server mr-2 md:text-3xl"></i>
1928 <a href="/admin/manager">Manager</a>
2029 </li>
21- <li class="flex items-center w-1/2 md:w-full">
30+ <li class="flex items-center w-1/2 md:w-full text-gray-600">
2231 <i class="iconoir-community mr-2 md:text-3xl"></i>
23- <a href="/admin/users">Users</a>
32+ <a title="Disabled" href="#">Users</a>
2433 </li>
25- <li class="flex items-center w-1/2 md:w-full">
34+ <li class="flex items-center w-1/2 md:w-full text-gray-600">
2635 <i class="iconoir-database-settings mr-2 md:text-3xl"></i>
27- <a href="/admin/settings">Settings</a>
36+ <a title="Disabled" href="#">Settings</a>
2837 </li>
2938 </ul>
3039 </div>
40+ <!-- Settings / Logout on the same line -->
41+ <div class="w-full flex justify-center space-x-4">
42+ <a
43+ href="/admin/settings"
44+ title="User settings"
45+ class="iconoir-settings text-gordonmascot-mblue text-lg md:text-2xl"
46+ >
47+ </a>
48+ <a
49+ href="/admin/logout"
50+ title="Logout"
51+ class="iconoir-log-out text-gordonmascot-mblue text-lg md:text-2xl"
52+ >
53+ </a>
54+ </div>
3155 </div>
32-</div>⏎̸
56+</div>
@@ -1,15 +1,24 @@
1-<div class="w-full md:w-1/5 flex flex-col space-evenly p-4 md:p-0">1+<div
2+ class="w-full md:w-1/5 flex flex-col space-evenly md:p-0 max-w-fit min-w-fit"
3+>
2 <div class="hidden md:flex justify-center p-4">4 <div class="hidden md:flex justify-center p-4">
3 <img src="/assets/imgs/gordon-mascot-hq-trsp.png" alt="logo" class="h-32" />5 <img src="/assets/imgs/gordon-mascot-hq-trsp.png" alt="logo" class="h-32" />
4 </div>6 </div>
5- <div class="p-4 flex flex-col bg-gordonmascot-lblue rounded-lg shadow space-y-4">7+ <div
8+ class="p-4 xl:p-8 flex flex-col bg-gordonmascot-lblue rounded-lg shadow space-y-4"
9+ >
6 <div class="w-full flex justify-center">10 <div class="w-full flex justify-center">
7- <h1 class="gordon-title text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter">Gordon</h1>11+ <h1
12+ class="gordon-title text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter"
13+ >
14+ Gordon
15+ </h1>
8 <span class="text-gordonmascot-mblue text-sm">{{ .BuildVersion }}</span>16 <span class="text-gordonmascot-mblue text-sm">{{ .BuildVersion }}</span>
9 </div>17 </div>
10 <div class="w-full">18 <div class="w-full">
11 <ul19 <ul
12- class="flex flex-wrap md:flex-col md:space-y-6 md:py-8 font-semibold text-gray-100 text-lg md:text-2xl md:pl-4 min-w-fit">20+ class="flex flex-wrap md:flex-col md:space-y-6 md:py-8 font-semibold text-gray-100 text-lg md:text-2xl min-w-fit justify-center"
21+ >
13 <li class="flex items-center w-1/2 md:w-full">22 <li class="flex items-center w-1/2 md:w-full">
14 <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i>23 <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i>
15 <a href="/admin">Dashboard</a>24 <a href="/admin">Dashboard</a>
@@ -18,15 +27,30 @@
18 <i class="iconoir-server mr-2 md:text-3xl"></i>27 <i class="iconoir-server mr-2 md:text-3xl"></i>
19 <a href="/admin/manager">Manager</a>28 <a href="/admin/manager">Manager</a>
20 </li>29 </li>
21- <li class="flex items-center w-1/2 md:w-full">30+ <li class="flex items-center w-1/2 md:w-full text-gray-600">
22 <i class="iconoir-community mr-2 md:text-3xl"></i>31 <i class="iconoir-community mr-2 md:text-3xl"></i>
23- <a href="/admin/users">Users</a>32+ <a title="Disabled" href="#">Users</a>
24 </li>33 </li>
25- <li class="flex items-center w-1/2 md:w-full">34+ <li class="flex items-center w-1/2 md:w-full text-gray-600">
26 <i class="iconoir-database-settings mr-2 md:text-3xl"></i>35 <i class="iconoir-database-settings mr-2 md:text-3xl"></i>
27- <a href="/admin/settings">Settings</a>36+ <a title="Disabled" href="#">Settings</a>
28 </li>37 </li>
29 </ul>38 </ul>
30 </div>39 </div>
40+ <!-- Settings / Logout on the same line -->
41+ <div class="w-full flex justify-center space-x-4">
42+ <a
43+ href="/admin/settings"
44+ title="User settings"
45+ class="iconoir-settings text-gordonmascot-mblue text-lg md:text-2xl"
46+ >
47+ </a>
48+ <a
49+ href="/admin/logout"
50+ title="Logout"
51+ class="iconoir-log-out text-gordonmascot-mblue text-lg md:text-2xl"
52+ >
53+ </a>
54+ </div>
31 </div>55 </div>
32-</div>⏎̸56+</div>
M internal/httpserve/handler/createcontainer.go +44 -9
@@ -1,7 +1,7 @@
11 package handler
22
33 import (
4- "net/http"
4+ "fmt"
55
66 "github.com/bnema/gordon/internal/app"
77 "github.com/bnema/gordon/internal/gotemplate/render"
@@ -21,31 +21,46 @@ type CreateContainer struct {
2121 Network string
2222 }
2323
24-// CreateContainerRoute is the route for creating a new container
25-func CreateContainerGETHandler(c echo.Context, a *app.App) error {
26- // Retreive the image ID from the URL
27- ShortID := c.Param("ID")
24+// FromShortIDToImageID converts a short image ID to a full image ID
25+func FromShortIDToImageID(ShortID string) (string, error) {
2826 idMapMutex.Lock()
2927 // Check if the ShortImgID exists in the idMap
3028 imageID, exists := idMap[ShortID]
3129 idMapMutex.Unlock()
3230
31+ if !exists {
32+ return "", fmt.Errorf("Invalid ShortImgID")
33+ }
34+
35+ return imageID, nil
36+}
37+
38+// CreateContainerRoute is the route for creating a new container
39+func CreateContainerGET(c echo.Context, a *app.App) error {
40+ // Retreive the ShortID of the image from the URL
41+ ShortID := c.Param("ID")
42+
43+ // Convert the ShortID to a full image ID
44+ imageID, err := FromShortIDToImageID(ShortID)
45+ if err != nil {
46+ return sendError(c, err)
47+ }
48+
49+ // Get the image info
3350 imageInfo, err := docker.GetImageInfo(imageID)
3451 if err != nil {
3552 return sendError(c, err)
3653 }
54+
3755 // Extract image name
3856 var imageName string
3957 if len(imageInfo.RepoTags) > 0 {
4058 imageName = imageInfo.RepoTags[0] // Using the first tag as the image name
4159 }
4260
43- if !exists {
44- return c.String(http.StatusBadRequest, "Invalid ShortImgID")
45- }
46-
4761 data := map[string]interface{}{
4862 "Title": "Create a new container",
63+ "ShortID": ShortID,
4964 "ImageName": imageName,
5065 }
5166
@@ -64,3 +79,23 @@ func CreateContainerGETHandler(c echo.Context, a *app.App) error {
6479 return c.HTML(200, renderedHTML)
6580
6681 }
82+
83+func CreateContainerPOST(c echo.Context, a *app.App) error {
84+ // Retreive the ShortID of the image from the URL
85+ ShortID := c.Param("ID")
86+
87+ // Convert the ShortID to a full image ID
88+ imageID, err := FromShortIDToImageID(ShortID)
89+ if err != nil {
90+ return sendError(c, err)
91+ }
92+
93+ // Get the image info
94+ _, err = docker.GetImageInfo(imageID)
95+ if err != nil {
96+ return sendError(c, err)
97+ }
98+
99+ // Redirect to the container manager
100+ return c.Redirect(302, "/htmx/container-manager")
101+}
@@ -1,7 +1,7 @@
1 package handler1 package handler
2 2
3 import (3 import (
4- "net/http"4+ "fmt"
5 5
6 "github.com/bnema/gordon/internal/app"6 "github.com/bnema/gordon/internal/app"
7 "github.com/bnema/gordon/internal/gotemplate/render"7 "github.com/bnema/gordon/internal/gotemplate/render"
@@ -21,31 +21,46 @@ type CreateContainer struct {
21 Network string21 Network string
22 }22 }
23 23
24-// CreateContainerRoute is the route for creating a new container24+// FromShortIDToImageID converts a short image ID to a full image ID
25-func CreateContainerGETHandler(c echo.Context, a *app.App) error {25+func FromShortIDToImageID(ShortID string) (string, error) {
26- // Retreive the image ID from the URL
27- ShortID := c.Param("ID")
28 idMapMutex.Lock()26 idMapMutex.Lock()
29 // Check if the ShortImgID exists in the idMap27 // Check if the ShortImgID exists in the idMap
30 imageID, exists := idMap[ShortID]28 imageID, exists := idMap[ShortID]
31 idMapMutex.Unlock()29 idMapMutex.Unlock()
32 30
31+ if !exists {
32+ return "", fmt.Errorf("Invalid ShortImgID")
33+ }
34+
35+ return imageID, nil
36+}
37+
38+// CreateContainerRoute is the route for creating a new container
39+func CreateContainerGET(c echo.Context, a *app.App) error {
40+ // Retreive the ShortID of the image from the URL
41+ ShortID := c.Param("ID")
42+
43+ // Convert the ShortID to a full image ID
44+ imageID, err := FromShortIDToImageID(ShortID)
45+ if err != nil {
46+ return sendError(c, err)
47+ }
48+
49+ // Get the image info
33 imageInfo, err := docker.GetImageInfo(imageID)50 imageInfo, err := docker.GetImageInfo(imageID)
34 if err != nil {51 if err != nil {
35 return sendError(c, err)52 return sendError(c, err)
36 }53 }
54+
37 // Extract image name55 // Extract image name
38 var imageName string56 var imageName string
39 if len(imageInfo.RepoTags) > 0 {57 if len(imageInfo.RepoTags) > 0 {
40 imageName = imageInfo.RepoTags[0] // Using the first tag as the image name58 imageName = imageInfo.RepoTags[0] // Using the first tag as the image name
41 }59 }
42 60
43- if !exists {
44- return c.String(http.StatusBadRequest, "Invalid ShortImgID")
45- }
46-
47 data := map[string]interface{}{61 data := map[string]interface{}{
48 "Title": "Create a new container",62 "Title": "Create a new container",
63+ "ShortID": ShortID,
49 "ImageName": imageName,64 "ImageName": imageName,
50 }65 }
51 66
@@ -64,3 +79,23 @@ func CreateContainerGETHandler(c echo.Context, a *app.App) error {
64 return c.HTML(200, renderedHTML)79 return c.HTML(200, renderedHTML)
65 80
66 }81 }
82+
83+func CreateContainerPOST(c echo.Context, a *app.App) error {
84+ // Retreive the ShortID of the image from the URL
85+ ShortID := c.Param("ID")
86+
87+ // Convert the ShortID to a full image ID
88+ imageID, err := FromShortIDToImageID(ShortID)
89+ if err != nil {
90+ return sendError(c, err)
91+ }
92+
93+ // Get the image info
94+ _, err = docker.GetImageInfo(imageID)
95+ if err != nil {
96+ return sendError(c, err)
97+ }
98+
99+ // Redirect to the container manager
100+ return c.Redirect(302, "/htmx/container-manager")
101+}
M internal/httpserve/router.go +4 -1
@@ -89,6 +89,9 @@ func bindHTMXEndpoints(e *echo.Echo, a *app.App) {
8989 return handler.UploadImagePOSTHandler(c, a)
9090 })
9191 e.GET("htmx/create-container/:ID", func(c echo.Context) error {
92- return handler.CreateContainerGETHandler(c, a)
92+ return handler.CreateContainerGET(c, a)
93+ })
94+ e.POST("htmx/create-container/:ID", func(c echo.Context) error {
95+ return handler.CreateContainerPOST(c, a)
9396 })
9497 }
@@ -89,6 +89,9 @@ func bindHTMXEndpoints(e *echo.Echo, a *app.App) {
89 return handler.UploadImagePOSTHandler(c, a)89 return handler.UploadImagePOSTHandler(c, a)
90 })90 })
91 e.GET("htmx/create-container/:ID", func(c echo.Context) error {91 e.GET("htmx/create-container/:ID", func(c echo.Context) error {
92- return handler.CreateContainerGETHandler(c, a)92+ return handler.CreateContainerGET(c, a)
93+ })
94+ e.POST("htmx/create-container/:ID", func(c echo.Context) error {
95+ return handler.CreateContainerPOST(c, a)
93 })96 })
94 }97 }
M internal/webui/public/assets/css/tailwind.css +63 -0
@@ -580,6 +580,11 @@ video {
580580 margin-bottom: 0.5rem;
581581 }
582582
583+.mx-2 {
584+ margin-left: 0.5rem;
585+ margin-right: 0.5rem;
586+}
587+
583588 .mb-1 {
584589 margin-bottom: 0.25rem;
585590 }
@@ -665,6 +670,15 @@ video {
665670 min-width: fit-content;
666671 }
667672
673+.max-w-lg {
674+ max-width: 32rem;
675+}
676+
677+.max-w-fit {
678+ max-width: -moz-fit-content;
679+ max-width: fit-content;
680+}
681+
668682 .flex-1 {
669683 flex: 1 1 0%;
670684 }
@@ -701,6 +715,18 @@ video {
701715 justify-content: space-between;
702716 }
703717
718+.justify-around {
719+ justify-content: space-around;
720+}
721+
722+.justify-evenly {
723+ justify-content: space-evenly;
724+}
725+
726+.justify-stretch {
727+ justify-content: stretch;
728+}
729+
704730 .justify-items-center {
705731 justify-items: center;
706732 }
@@ -717,6 +743,12 @@ video {
717743 margin-bottom: calc(1rem * var(--tw-space-y-reverse));
718744 }
719745
746+.space-x-4 > :not([hidden]) ~ :not([hidden]) {
747+ --tw-space-x-reverse: 0;
748+ margin-right: calc(1rem * var(--tw-space-x-reverse));
749+ margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
750+}
751+
720752 .rounded {
721753 border-radius: 0.25rem;
722754 }
@@ -811,6 +843,10 @@ video {
811843 padding: 1rem;
812844 }
813845
846+.p-8 {
847+ padding: 2rem;
848+}
849+
814850 .px-2 {
815851 padding-left: 0.5rem;
816852 padding-right: 0.5rem;
@@ -835,6 +871,14 @@ video {
835871 padding-bottom: 1rem;
836872 }
837873
874+.pl-2 {
875+ padding-left: 0.5rem;
876+}
877+
878+.text-center {
879+ text-align: center;
880+}
881+
838882 .align-middle {
839883 vertical-align: middle;
840884 }
@@ -931,6 +975,21 @@ video {
931975 color: rgb(255 255 255 / var(--tw-text-opacity));
932976 }
933977
978+.text-gray-300 {
979+ --tw-text-opacity: 1;
980+ color: rgb(209 213 219 / var(--tw-text-opacity));
981+}
982+
983+.text-gray-400 {
984+ --tw-text-opacity: 1;
985+ color: rgb(156 163 175 / var(--tw-text-opacity));
986+}
987+
988+.text-gray-600 {
989+ --tw-text-opacity: 1;
990+ color: rgb(75 85 99 / var(--tw-text-opacity));
991+}
992+
934993 .underline {
935994 text-decoration-line: underline;
936995 }
@@ -1185,6 +1244,10 @@ video {
11851244 height: 12rem;
11861245 }
11871246
1247+ .xl\:p-8 {
1248+ padding: 2rem;
1249+ }
1250+
11881251 .xl\:text-5xl {
11891252 font-size: 3rem;
11901253 line-height: 1;
@@ -580,6 +580,11 @@ video {
580 margin-bottom: 0.5rem;580 margin-bottom: 0.5rem;
581 }581 }
582 582
583+.mx-2 {
584+ margin-left: 0.5rem;
585+ margin-right: 0.5rem;
586+}
587+
583 .mb-1 {588 .mb-1 {
584 margin-bottom: 0.25rem;589 margin-bottom: 0.25rem;
585 }590 }
@@ -665,6 +670,15 @@ video {
665 min-width: fit-content;670 min-width: fit-content;
666 }671 }
667 672
673+.max-w-lg {
674+ max-width: 32rem;
675+}
676+
677+.max-w-fit {
678+ max-width: -moz-fit-content;
679+ max-width: fit-content;
680+}
681+
668 .flex-1 {682 .flex-1 {
669 flex: 1 1 0%;683 flex: 1 1 0%;
670 }684 }
@@ -701,6 +715,18 @@ video {
701 justify-content: space-between;715 justify-content: space-between;
702 }716 }
703 717
718+.justify-around {
719+ justify-content: space-around;
720+}
721+
722+.justify-evenly {
723+ justify-content: space-evenly;
724+}
725+
726+.justify-stretch {
727+ justify-content: stretch;
728+}
729+
704 .justify-items-center {730 .justify-items-center {
705 justify-items: center;731 justify-items: center;
706 }732 }
@@ -717,6 +743,12 @@ video {
717 margin-bottom: calc(1rem * var(--tw-space-y-reverse));743 margin-bottom: calc(1rem * var(--tw-space-y-reverse));
718 }744 }
719 745
746+.space-x-4 > :not([hidden]) ~ :not([hidden]) {
747+ --tw-space-x-reverse: 0;
748+ margin-right: calc(1rem * var(--tw-space-x-reverse));
749+ margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
750+}
751+
720 .rounded {752 .rounded {
721 border-radius: 0.25rem;753 border-radius: 0.25rem;
722 }754 }
@@ -811,6 +843,10 @@ video {
811 padding: 1rem;843 padding: 1rem;
812 }844 }
813 845
846+.p-8 {
847+ padding: 2rem;
848+}
849+
814 .px-2 {850 .px-2 {
815 padding-left: 0.5rem;851 padding-left: 0.5rem;
816 padding-right: 0.5rem;852 padding-right: 0.5rem;
@@ -835,6 +871,14 @@ video {
835 padding-bottom: 1rem;871 padding-bottom: 1rem;
836 }872 }
837 873
874+.pl-2 {
875+ padding-left: 0.5rem;
876+}
877+
878+.text-center {
879+ text-align: center;
880+}
881+
838 .align-middle {882 .align-middle {
839 vertical-align: middle;883 vertical-align: middle;
840 }884 }
@@ -931,6 +975,21 @@ video {
931 color: rgb(255 255 255 / var(--tw-text-opacity));975 color: rgb(255 255 255 / var(--tw-text-opacity));
932 }976 }
933 977
978+.text-gray-300 {
979+ --tw-text-opacity: 1;
980+ color: rgb(209 213 219 / var(--tw-text-opacity));
981+}
982+
983+.text-gray-400 {
984+ --tw-text-opacity: 1;
985+ color: rgb(156 163 175 / var(--tw-text-opacity));
986+}
987+
988+.text-gray-600 {
989+ --tw-text-opacity: 1;
990+ color: rgb(75 85 99 / var(--tw-text-opacity));
991+}
992+
934 .underline {993 .underline {
935 text-decoration-line: underline;994 text-decoration-line: underline;
936 }995 }
@@ -1185,6 +1244,10 @@ video {
1185 height: 12rem;1244 height: 12rem;
1186 }1245 }
1187 1246
1247+ .xl\:p-8 {
1248+ padding: 2rem;
1249+ }
1250+
1188 .xl\:text-5xl {1251 .xl\:text-5xl {
1189 font-size: 3rem;1252 font-size: 3rem;
1190 line-height: 1;1253 line-height: 1;

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