detached e9b201c

Refactor: Enhance container management and UI components

- Add TraefikEntryPoint field to ContainerCommandParams struct
- Update fetch command to handle Traefik entry point
- Refactor container list and creation UI components
- Improve error handling and state management in hyperscript
- Update _hyperscript and htmx libraries to latest versions
commit
e9b201ce14aef662a38096a3f5e46431b713009f
author
Brice · · 2024-09-16 10:46 UTC
parent
11bec3a
signature
unsigned
13 files · +184 -160
M internal/cli/cmd/deploy.go +1 -1
@@ -67,7 +67,7 @@ func NewDeployCommand(a *cli.App) *cobra.Command {
6767 },
6868 }
6969
70- deployCmd.Flags().StringVarP(&port, "port", "p", "", "Port mapping for the container")
70+ deployCmd.Flags().StringVarP(&port, "port", "p", "", "Port mapping for the container and Traefik entry point")
7171 deployCmd.Flags().StringVarP(&targetDomain, "target", "t", "", "Target domain for Traefik")
7272
7373 return deployCmd
@@ -67,7 +67,7 @@ func NewDeployCommand(a *cli.App) *cobra.Command {
67 },67 },
68 }68 }
69 69
70- deployCmd.Flags().StringVarP(&port, "port", "p", "", "Port mapping for the container")70+ deployCmd.Flags().StringVarP(&port, "port", "p", "", "Port mapping for the container and Traefik entry point")
71 deployCmd.Flags().StringVarP(&targetDomain, "target", "t", "", "Target domain for Traefik")71 deployCmd.Flags().StringVarP(&targetDomain, "target", "t", "", "Target domain for Traefik")
72 72
73 return deployCmd73 return deployCmd
M internal/common/initconfig.go +56 -9
@@ -7,6 +7,7 @@ import (
77 "os"
88 "path/filepath"
99 "runtime"
10+ "strings"
1011
1112 "github.com/bnema/gordon/pkg/docker"
1213 "github.com/bnema/gordon/pkg/parser"
@@ -68,21 +69,50 @@ var (
6869 )
6970
7071 func getConfigDir() (string, error) {
72+ var configDir string
73+
74+ if isWSL() {
75+ // Use XDG_CONFIG_HOME for WSL
76+ if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
77+ configDir = filepath.Join(xdgConfigHome, "Gordon")
78+ } else {
79+ // If XDG_CONFIG_HOME is not set, fall back to default locations
80+ homeDir, err := os.UserHomeDir()
81+ if err != nil {
82+ // If we can't get the home directory, use a fallback
83+ homeDir = os.TempDir() // Use the system's temp directory as a fallback
84+ fmt.Printf("Warning: Unable to determine home directory. Using temp directory: %s\n", homeDir)
85+ }
86+
87+ configDir = filepath.Join(homeDir, ".config", "Gordon")
88+
89+ }
90+
91+ return configDir, nil
92+ }
7193
7294 if docker.IsRunningInContainer() {
7395 return "/.", nil
7496 }
7597
76- // Get the user's home directory for non-container environments
77- homeDir, err := os.UserHomeDir()
78- if err != nil {
79- return "", fmt.Errorf("error getting user's home directory: %w", err)
80- }
98+ // Check for XDG_CONFIG_HOME first
99+ if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
100+ configDir = filepath.Join(xdgConfigHome, "Gordon")
101+ } else {
102+ // If XDG_CONFIG_HOME is not set, fall back to default locations
103+ homeDir, err := os.UserHomeDir()
104+ if err != nil {
105+ // If we can't get the home directory, use a fallback
106+ homeDir = os.TempDir() // Use the system's temp directory as a fallback
107+ fmt.Printf("Warning: Unable to determine home directory. Using temp directory: %s\n", homeDir)
108+ }
81109
82- // Select the configuration directory based on the OS
83- configDir := filepath.Join(homeDir, ".config", "Gordon")
84- if runtime.GOOS == "windows" {
85- configDir = filepath.Join(homeDir, "AppData", "Roaming", "Gordon")
110+ // Select the configuration directory based on the OS
111+ if runtime.GOOS == "windows" {
112+ configDir = filepath.Join(homeDir, "AppData", "Roaming", "Gordon")
113+ } else {
114+ configDir = filepath.Join(homeDir, ".config", "Gordon")
115+ }
86116 }
87117
88118 return configDir, nil
@@ -201,3 +231,20 @@ func (c *Config) GetBackendURL() string {
201231 func (c *Config) SetToken(token string) {
202232 c.General.Token = token
203233 }
234+
235+func isWSL() bool {
236+ // Check for /proc/version file
237+ if _, err := os.Stat("/proc/version"); err == nil {
238+ content, err := os.ReadFile("/proc/version")
239+ if err == nil && strings.Contains(strings.ToLower(string(content)), "microsoft") {
240+ return true
241+ }
242+ }
243+
244+ // Check for WSL-specific environment variable
245+ if os.Getenv("WSL_DISTRO_NAME") != "" {
246+ return true
247+ }
248+
249+ return false
250+}
@@ -7,6 +7,7 @@ import (
7 "os"7 "os"
8 "path/filepath"8 "path/filepath"
9 "runtime"9 "runtime"
10+ "strings"
10 11
11 "github.com/bnema/gordon/pkg/docker"12 "github.com/bnema/gordon/pkg/docker"
12 "github.com/bnema/gordon/pkg/parser"13 "github.com/bnema/gordon/pkg/parser"
@@ -68,21 +69,50 @@ var (
68 )69 )
69 70
70 func getConfigDir() (string, error) {71 func getConfigDir() (string, error) {
72+ var configDir string
73+
74+ if isWSL() {
75+ // Use XDG_CONFIG_HOME for WSL
76+ if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
77+ configDir = filepath.Join(xdgConfigHome, "Gordon")
78+ } else {
79+ // If XDG_CONFIG_HOME is not set, fall back to default locations
80+ homeDir, err := os.UserHomeDir()
81+ if err != nil {
82+ // If we can't get the home directory, use a fallback
83+ homeDir = os.TempDir() // Use the system's temp directory as a fallback
84+ fmt.Printf("Warning: Unable to determine home directory. Using temp directory: %s\n", homeDir)
85+ }
86+
87+ configDir = filepath.Join(homeDir, ".config", "Gordon")
88+
89+ }
90+
91+ return configDir, nil
92+ }
71 93
72 if docker.IsRunningInContainer() {94 if docker.IsRunningInContainer() {
73 return "/.", nil95 return "/.", nil
74 }96 }
75 97
76- // Get the user's home directory for non-container environments98+ // Check for XDG_CONFIG_HOME first
77- homeDir, err := os.UserHomeDir()99+ if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
78- if err != nil {100+ configDir = filepath.Join(xdgConfigHome, "Gordon")
79- return "", fmt.Errorf("error getting user's home directory: %w", err)101+ } else {
80- }102+ // If XDG_CONFIG_HOME is not set, fall back to default locations
103+ homeDir, err := os.UserHomeDir()
104+ if err != nil {
105+ // If we can't get the home directory, use a fallback
106+ homeDir = os.TempDir() // Use the system's temp directory as a fallback
107+ fmt.Printf("Warning: Unable to determine home directory. Using temp directory: %s\n", homeDir)
108+ }
81 109
82- // Select the configuration directory based on the OS110+ // Select the configuration directory based on the OS
83- configDir := filepath.Join(homeDir, ".config", "Gordon")111+ if runtime.GOOS == "windows" {
84- if runtime.GOOS == "windows" {112+ configDir = filepath.Join(homeDir, "AppData", "Roaming", "Gordon")
85- configDir = filepath.Join(homeDir, "AppData", "Roaming", "Gordon")113+ } else {
114+ configDir = filepath.Join(homeDir, ".config", "Gordon")
115+ }
86 }116 }
87 117
88 return configDir, nil118 return configDir, nil
@@ -201,3 +231,20 @@ func (c *Config) GetBackendURL() string {
201 func (c *Config) SetToken(token string) {231 func (c *Config) SetToken(token string) {
202 c.General.Token = token232 c.General.Token = token
203 }233 }
234+
235+func isWSL() bool {
236+ // Check for /proc/version file
237+ if _, err := os.Stat("/proc/version"); err == nil {
238+ content, err := os.ReadFile("/proc/version")
239+ if err == nil && strings.Contains(strings.ToLower(string(content)), "microsoft") {
240+ return true
241+ }
242+ }
243+
244+ // Check for WSL-specific environment variable
245+ if os.Getenv("WSL_DISTRO_NAME") != "" {
246+ return true
247+ }
248+
249+ return false
250+}
M internal/httpserve/handler/containers.go +30 -15
@@ -3,6 +3,7 @@ package handler
33 import (
44 "fmt"
55 "net/http"
6+ "strings"
67 "sync"
78 "time"
89
@@ -61,13 +62,14 @@ type HumanReadableContainerImage struct {
6162
6263 type HumanReadableContainer struct {
6364 *types.Container
64- Name string
65- Ports []string
66- ShortID string
67- CreatedStr string
68- SizeStr string
69- UpSince string
70- StateColor string
65+ Name string
66+ Ports []string
67+ ShortID string
68+ CreatedStr string
69+ SizeStr string
70+ UpSince string
71+ StateColor string
72+ TraefikEntryPoint string
7173 }
7274
7375 // renderHTML is a generalized function to render HTML
@@ -162,9 +164,11 @@ func ContainerManagerComponent(c echo.Context, a *server.App) error {
162164 for _, container := range containers {
163165 localContainer := container // Make a local copy
164166 sizeStr := humanize.BytesToReadableSize(container.SizeRw)
165- stateColor := "green"
167+ stateColor := "blue"
166168
167- if container.State == "exited" {
169+ if container.State == "running" {
170+ stateColor = "green"
171+ } else if container.State == "exited" {
168172 stateColor = "red"
169173 }
170174 var ports []string
@@ -176,12 +180,14 @@ func ContainerManagerComponent(c echo.Context, a *server.App) error {
176180 for _, name := range container.Names {
177181 name = name[1:]
178182 humanReadableContainers = append(humanReadableContainers, HumanReadableContainer{
179- Container: &localContainer,
180- SizeStr: sizeStr,
181- UpSince: humanize.TimeAgo(time.Unix(container.Created, 0)),
182- StateColor: stateColor,
183- Name: name,
184- Ports: ports,
183+ Container: &localContainer,
184+ SizeStr: sizeStr,
185+ UpSince: humanize.TimeAgo(time.Unix(container.Created, 0)),
186+ StateColor: stateColor,
187+ Name: name,
188+ Ports: ports,
189+ TraefikEntryPoint: extractTraefikEntryPoint(container.Labels),
190+ CreatedStr: humanize.TimeAgo(time.Unix(container.Created, 0)),
185191 })
186192 }
187193 }
@@ -197,6 +203,15 @@ func ContainerManagerComponent(c echo.Context, a *server.App) error {
197203 return renderHTML(c, a, "html/fragments", "containerlist.gohtml", data)
198204 }
199205
206+func extractTraefikEntryPoint(labels map[string]string) string {
207+ for key, value := range labels {
208+ if strings.HasSuffix(key, ".loadbalancer.server.port") {
209+ return value
210+ }
211+ }
212+ return ""
213+}
214+
200215 // ContainerManagerDelete handles the /container-manager/delete route
201216 func ContainerManagerDelete(c echo.Context, a *server.App) error {
202217 err := docker.RemoveContainer(c.Param("ID"))
@@ -3,6 +3,7 @@ package handler
3 import (3 import (
4 "fmt"4 "fmt"
5 "net/http"5 "net/http"
6+ "strings"
6 "sync"7 "sync"
7 "time"8 "time"
8 9
@@ -61,13 +62,14 @@ type HumanReadableContainerImage struct {
61 62
62 type HumanReadableContainer struct {63 type HumanReadableContainer struct {
63 *types.Container64 *types.Container
64- Name string65+ Name string
65- Ports []string66+ Ports []string
66- ShortID string67+ ShortID string
67- CreatedStr string68+ CreatedStr string
68- SizeStr string69+ SizeStr string
69- UpSince string70+ UpSince string
70- StateColor string71+ StateColor string
72+ TraefikEntryPoint string
71 }73 }
72 74
73 // renderHTML is a generalized function to render HTML75 // renderHTML is a generalized function to render HTML
@@ -162,9 +164,11 @@ func ContainerManagerComponent(c echo.Context, a *server.App) error {
162 for _, container := range containers {164 for _, container := range containers {
163 localContainer := container // Make a local copy165 localContainer := container // Make a local copy
164 sizeStr := humanize.BytesToReadableSize(container.SizeRw)166 sizeStr := humanize.BytesToReadableSize(container.SizeRw)
165- stateColor := "green"167+ stateColor := "blue"
166 168
167- if container.State == "exited" {169+ if container.State == "running" {
170+ stateColor = "green"
171+ } else if container.State == "exited" {
168 stateColor = "red"172 stateColor = "red"
169 }173 }
170 var ports []string174 var ports []string
@@ -176,12 +180,14 @@ func ContainerManagerComponent(c echo.Context, a *server.App) error {
176 for _, name := range container.Names {180 for _, name := range container.Names {
177 name = name[1:]181 name = name[1:]
178 humanReadableContainers = append(humanReadableContainers, HumanReadableContainer{182 humanReadableContainers = append(humanReadableContainers, HumanReadableContainer{
179- Container: &localContainer,183+ Container: &localContainer,
180- SizeStr: sizeStr,184+ SizeStr: sizeStr,
181- UpSince: humanize.TimeAgo(time.Unix(container.Created, 0)),185+ UpSince: humanize.TimeAgo(time.Unix(container.Created, 0)),
182- StateColor: stateColor,186+ StateColor: stateColor,
183- Name: name,187+ Name: name,
184- Ports: ports,188+ Ports: ports,
189+ TraefikEntryPoint: extractTraefikEntryPoint(container.Labels),
190+ CreatedStr: humanize.TimeAgo(time.Unix(container.Created, 0)),
185 })191 })
186 }192 }
187 }193 }
@@ -197,6 +203,15 @@ func ContainerManagerComponent(c echo.Context, a *server.App) error {
197 return renderHTML(c, a, "html/fragments", "containerlist.gohtml", data)203 return renderHTML(c, a, "html/fragments", "containerlist.gohtml", data)
198 }204 }
199 205
206+func extractTraefikEntryPoint(labels map[string]string) string {
207+ for key, value := range labels {
208+ if strings.HasSuffix(key, ".loadbalancer.server.port") {
209+ return value
210+ }
211+ }
212+ return ""
213+}
214+
200 // ContainerManagerDelete handles the /container-manager/delete route215 // ContainerManagerDelete handles the /container-manager/delete route
201 func ContainerManagerDelete(c echo.Context, a *server.App) error {216 func ContainerManagerDelete(c echo.Context, a *server.App) error {
202 err := docker.RemoveContainer(c.Param("ID"))217 err := docker.RemoveContainer(c.Param("ID"))
M internal/templating/cmdparams/fromcli.go +12 -11
@@ -37,19 +37,20 @@ func FromPayloadStructToCmdParams(ppl *common.DeployPayload, a *server.App, imag
3737 containerDomain := strings.Join(hostParts[1:], ".")
3838
3939 params := docker.ContainerCommandParams{
40- IsSSL: protocol == "https",
41- ContainerName: containerSubdomain,
42- ServiceName: containerSubdomain,
43- Domain: containerDomain,
44- ImageName: ppl.ImageName,
45- ImageID: imageID,
46- Restart: "always",
47- Volumes: volumeSlice,
48- Environment: environmentSlice,
49- Network: a.Config.ContainerEngine.Network,
40+ IsSSL: protocol == "https",
41+ ContainerName: containerSubdomain,
42+ ServiceName: containerSubdomain,
43+ Domain: containerDomain,
44+ ImageName: ppl.ImageName,
45+ ImageID: imageID,
46+ Restart: "always",
47+ Volumes: volumeSlice,
48+ Environment: environmentSlice,
49+ Network: a.Config.ContainerEngine.Network,
50+ TraefikEntryPoint: ppl.Port,
5051 }
5152
52- err = CreateTraefikLabels(&params, ppl.Port, a)
53+ err = CreateTraefikLabels(&params, params.TraefikEntryPoint, a)
5354 if err != nil {
5455 return docker.ContainerCommandParams{}, fmt.Errorf("error creating Traefik labels: %w", err)
5556 }
@@ -37,19 +37,20 @@ func FromPayloadStructToCmdParams(ppl *common.DeployPayload, a *server.App, imag
37 containerDomain := strings.Join(hostParts[1:], ".")37 containerDomain := strings.Join(hostParts[1:], ".")
38 38
39 params := docker.ContainerCommandParams{39 params := docker.ContainerCommandParams{
40- IsSSL: protocol == "https",40+ IsSSL: protocol == "https",
41- ContainerName: containerSubdomain,41+ ContainerName: containerSubdomain,
42- ServiceName: containerSubdomain,42+ ServiceName: containerSubdomain,
43- Domain: containerDomain,43+ Domain: containerDomain,
44- ImageName: ppl.ImageName,44+ ImageName: ppl.ImageName,
45- ImageID: imageID,45+ ImageID: imageID,
46- Restart: "always",46+ Restart: "always",
47- Volumes: volumeSlice,47+ Volumes: volumeSlice,
48- Environment: environmentSlice,48+ Environment: environmentSlice,
49- Network: a.Config.ContainerEngine.Network,49+ Network: a.Config.ContainerEngine.Network,
50+ TraefikEntryPoint: ppl.Port,
50 }51 }
51 52
52- err = CreateTraefikLabels(&params, ppl.Port, a)53+ err = CreateTraefikLabels(&params, params.TraefikEntryPoint, a)
53 if err != nil {54 if err != nil {
54 return docker.ContainerCommandParams{}, fmt.Errorf("error creating Traefik labels: %w", err)55 return docker.ContainerCommandParams{}, fmt.Errorf("error creating Traefik labels: %w", err)
55 }56 }
M internal/templating/cmdparams/frominputs.go +21 -17
@@ -11,30 +11,34 @@ import (
1111 func FromInputsToCmdParams(inputs map[string]string, a *server.App) (docker.ContainerCommandParams, error) {
1212 volumeSlice := ParseVolumeSlice(inputs["volumes"])
1313 environmentSlice := ParseEnvironmentSlice(inputs["environment_variables"])
14- portMappings, err := ParsePortMappingsSlice(inputs["ports"])
1514
16- // Extract the container port (right side of the mapping)
17- containerPort := extractContainerPort(portMappings)
15+ var portMappings []docker.PortMapping
16+ var err error
1817
19- if err != nil {
20- return docker.ContainerCommandParams{}, err
18+ // Parse port mappings if provided
19+ if inputs["ports"] != "" {
20+ portMappings, err = ParsePortMappingsSlice(inputs["ports"])
21+ if err != nil {
22+ return docker.ContainerCommandParams{}, err
23+ }
2124 }
2225
2326 params := docker.ContainerCommandParams{
24- IsSSL: inputs["container_protocol"] == "https",
25- ContainerName: inputs["container_name"],
26- ServiceName: inputs["container_subdomain"],
27- Domain: inputs["container_domain"],
28- ImageName: inputs["image_name"],
29- ImageID: inputs["image_id"],
30- Restart: inputs["restart"],
31- Volumes: volumeSlice,
32- Environment: environmentSlice,
33- Network: a.Config.ContainerEngine.Network,
34- PortMappings: portMappings,
27+ IsSSL: inputs["container_protocol"] == "https",
28+ ContainerName: inputs["container_name"],
29+ ServiceName: inputs["container_subdomain"],
30+ Domain: inputs["container_domain"],
31+ ImageName: inputs["image_name"],
32+ ImageID: inputs["image_id"],
33+ Restart: inputs["restart"],
34+ TraefikEntryPoint: inputs["traefik_entry_point"],
35+ Volumes: volumeSlice,
36+ Environment: environmentSlice,
37+ Network: a.Config.ContainerEngine.Network,
38+ PortMappings: portMappings,
3539 }
3640
37- err = CreateTraefikLabels(&params, containerPort, a)
41+ err = CreateTraefikLabels(&params, params.TraefikEntryPoint, a)
3842 if err != nil {
3943 return docker.ContainerCommandParams{}, fmt.Errorf("error creating Traefik labels: %w", err)
4044 }
@@ -11,30 +11,34 @@ import (
11 func FromInputsToCmdParams(inputs map[string]string, a *server.App) (docker.ContainerCommandParams, error) {11 func FromInputsToCmdParams(inputs map[string]string, a *server.App) (docker.ContainerCommandParams, error) {
12 volumeSlice := ParseVolumeSlice(inputs["volumes"])12 volumeSlice := ParseVolumeSlice(inputs["volumes"])
13 environmentSlice := ParseEnvironmentSlice(inputs["environment_variables"])13 environmentSlice := ParseEnvironmentSlice(inputs["environment_variables"])
14- portMappings, err := ParsePortMappingsSlice(inputs["ports"])
15 14
16- // Extract the container port (right side of the mapping)15+ var portMappings []docker.PortMapping
17- containerPort := extractContainerPort(portMappings)16+ var err error
18 17
19- if err != nil {18+ // Parse port mappings if provided
20- return docker.ContainerCommandParams{}, err19+ if inputs["ports"] != "" {
20+ portMappings, err = ParsePortMappingsSlice(inputs["ports"])
21+ if err != nil {
22+ return docker.ContainerCommandParams{}, err
23+ }
21 }24 }
22 25
23 params := docker.ContainerCommandParams{26 params := docker.ContainerCommandParams{
24- IsSSL: inputs["container_protocol"] == "https",27+ IsSSL: inputs["container_protocol"] == "https",
25- ContainerName: inputs["container_name"],28+ ContainerName: inputs["container_name"],
26- ServiceName: inputs["container_subdomain"],29+ ServiceName: inputs["container_subdomain"],
27- Domain: inputs["container_domain"],30+ Domain: inputs["container_domain"],
28- ImageName: inputs["image_name"],31+ ImageName: inputs["image_name"],
29- ImageID: inputs["image_id"],32+ ImageID: inputs["image_id"],
30- Restart: inputs["restart"],33+ Restart: inputs["restart"],
31- Volumes: volumeSlice,34+ TraefikEntryPoint: inputs["traefik_entry_point"],
32- Environment: environmentSlice,35+ Volumes: volumeSlice,
33- Network: a.Config.ContainerEngine.Network,36+ Environment: environmentSlice,
34- PortMappings: portMappings,37+ Network: a.Config.ContainerEngine.Network,
38+ PortMappings: portMappings,
35 }39 }
36 40
37- err = CreateTraefikLabels(&params, containerPort, a)41+ err = CreateTraefikLabels(&params, params.TraefikEntryPoint, a)
38 if err != nil {42 if err != nil {
39 return docker.ContainerCommandParams{}, fmt.Errorf("error creating Traefik labels: %w", err)43 return docker.ContainerCommandParams{}, fmt.Errorf("error creating Traefik labels: %w", err)
40 }44 }
M internal/templating/cmdparams/parsing.go +4 -0
@@ -26,6 +26,10 @@ func ParseEnvironmentSlice(environmentStr string) []string {
2626
2727 // ParsePortMappingsSlice parses the port mappings string into a slice and a struct.
2828 func ParsePortMappingsSlice(portMappingsStr string) ([]docker.PortMapping, error) {
29+ if portMappingsStr == "" {
30+ return []docker.PortMapping{}, nil
31+ }
32+
2933 portMappingsSliceRaw := strings.Split(portMappingsStr, ",")
3034 portMappingsSlice := make([]string, len(portMappingsSliceRaw))
3135 for i, spec := range portMappingsSliceRaw {
@@ -26,6 +26,10 @@ func ParseEnvironmentSlice(environmentStr string) []string {
26 26
27 // ParsePortMappingsSlice parses the port mappings string into a slice and a struct.27 // ParsePortMappingsSlice parses the port mappings string into a slice and a struct.
28 func ParsePortMappingsSlice(portMappingsStr string) ([]docker.PortMapping, error) {28 func ParsePortMappingsSlice(portMappingsStr string) ([]docker.PortMapping, error) {
29+ if portMappingsStr == "" {
30+ return []docker.PortMapping{}, nil
31+ }
32+
29 portMappingsSliceRaw := strings.Split(portMappingsStr, ",")33 portMappingsSliceRaw := strings.Split(portMappingsStr, ",")
30 portMappingsSlice := make([]string, len(portMappingsSliceRaw))34 portMappingsSlice := make([]string, len(portMappingsSliceRaw))
31 for i, spec := range portMappingsSliceRaw {35 for i, spec := range portMappingsSliceRaw {
M internal/templating/cmdparams/traefik.go +3 -3
@@ -8,7 +8,7 @@ import (
88 )
99
1010 // CreateTraefikLabels creates Traefik labels based on the provided parameters.
11-func CreateTraefikLabels(params *docker.ContainerCommandParams, portEntryPoint string, a *server.App) error {
11+func CreateTraefikLabels(params *docker.ContainerCommandParams, containerPort string, a *server.App) error {
1212 entryPoint := a.Config.Traefik.EntryPoint
1313 if params.IsSSL {
1414 entryPoint = a.Config.Traefik.SecureEntryPoint
@@ -21,12 +21,12 @@ func CreateTraefikLabels(params *docker.ContainerCommandParams, portEntryPoint s
2121 "traefik.enable=true",
2222 fmt.Sprintf("%s.rule=Host(`%s.%s`)", baseRouter, params.ServiceName, params.Domain),
2323 fmt.Sprintf("%s.entrypoints=%s", baseRouter, entryPoint),
24- fmt.Sprintf("%s.loadbalancer.server.port=%s", baseService, portEntryPoint),
24+ fmt.Sprintf("%s.loadbalancer.server.port=%s", baseService, containerPort),
2525 }
2626
2727 resolver := a.Config.Traefik.Resolver
2828 if resolver == "" {
29- fmt.Errorf("resolver not set in config")
29+ return fmt.Errorf("resolver not set in config")
3030 }
3131
3232 if params.IsSSL {
@@ -8,7 +8,7 @@ import (
8 )8 )
9 9
10 // CreateTraefikLabels creates Traefik labels based on the provided parameters.10 // CreateTraefikLabels creates Traefik labels based on the provided parameters.
11-func CreateTraefikLabels(params *docker.ContainerCommandParams, portEntryPoint string, a *server.App) error {11+func CreateTraefikLabels(params *docker.ContainerCommandParams, containerPort string, a *server.App) error {
12 entryPoint := a.Config.Traefik.EntryPoint12 entryPoint := a.Config.Traefik.EntryPoint
13 if params.IsSSL {13 if params.IsSSL {
14 entryPoint = a.Config.Traefik.SecureEntryPoint14 entryPoint = a.Config.Traefik.SecureEntryPoint
@@ -21,12 +21,12 @@ func CreateTraefikLabels(params *docker.ContainerCommandParams, portEntryPoint s
21 "traefik.enable=true",21 "traefik.enable=true",
22 fmt.Sprintf("%s.rule=Host(`%s.%s`)", baseRouter, params.ServiceName, params.Domain),22 fmt.Sprintf("%s.rule=Host(`%s.%s`)", baseRouter, params.ServiceName, params.Domain),
23 fmt.Sprintf("%s.entrypoints=%s", baseRouter, entryPoint),23 fmt.Sprintf("%s.entrypoints=%s", baseRouter, entryPoint),
24- fmt.Sprintf("%s.loadbalancer.server.port=%s", baseService, portEntryPoint),24+ fmt.Sprintf("%s.loadbalancer.server.port=%s", baseService, containerPort),
25 }25 }
26 26
27 resolver := a.Config.Traefik.Resolver27 resolver := a.Config.Traefik.Resolver
28 if resolver == "" {28 if resolver == "" {
29- fmt.Errorf("resolver not set in config")29+ return fmt.Errorf("resolver not set in config")
30 }30 }
31 31
32 if params.IsSSL {32 if params.IsSSL {
M internal/templating/models/html/fragments/containerlist.gohtml +2 -0
@@ -7,6 +7,7 @@
77 <tr class="text-center text-gray-400">
88 <th>{{index .Lang "manager" "content" "thName"}}</th>
99 <th>{{index .Lang "manager" "content" "thPorts"}}</th>
10+ <th>{{index .Lang "manager" "content" "thEntryPoint"}}</th>
1011 <th>{{index .Lang "manager" "content" "thStatus"}}</th>
1112 <th>{{index .Lang "manager" "content" "thState"}}</th>
1213 <th>{{index .Lang "manager" "content" "thActions"}}</th>
@@ -26,6 +27,7 @@
2627 >
2728 </td>
2829 <td class="port-container">{{ .Ports }}</td>
30+ <td class="entrypoint-container">{{.TraefikEntryPoint}}</td>
2931 <td class="status-container">{{.Status}} (Up since {{.UpSince}})</td>
3032 <td style="color: {{.StateColor}};">{{.State}}</td>
3133 <td>
@@ -7,6 +7,7 @@
7 <tr class="text-center text-gray-400">7 <tr class="text-center text-gray-400">
8 <th>{{index .Lang "manager" "content" "thName"}}</th>8 <th>{{index .Lang "manager" "content" "thName"}}</th>
9 <th>{{index .Lang "manager" "content" "thPorts"}}</th>9 <th>{{index .Lang "manager" "content" "thPorts"}}</th>
10+ <th>{{index .Lang "manager" "content" "thEntryPoint"}}</th>
10 <th>{{index .Lang "manager" "content" "thStatus"}}</th>11 <th>{{index .Lang "manager" "content" "thStatus"}}</th>
11 <th>{{index .Lang "manager" "content" "thState"}}</th>12 <th>{{index .Lang "manager" "content" "thState"}}</th>
12 <th>{{index .Lang "manager" "content" "thActions"}}</th>13 <th>{{index .Lang "manager" "content" "thActions"}}</th>
@@ -26,6 +27,7 @@
26 >27 >
27 </td>28 </td>
28 <td class="port-container">{{ .Ports }}</td>29 <td class="port-container">{{ .Ports }}</td>
30+ <td class="entrypoint-container">{{.TraefikEntryPoint}}</td>
29 <td class="status-container">{{.Status}} (Up since {{.UpSince}})</td>31 <td class="status-container">{{.Status}} (Up since {{.UpSince}})</td>
30 <td style="color: {{.StateColor}};">{{.State}}</td>32 <td style="color: {{.StateColor}};">{{.State}}</td>
31 <td>33 <td>
M internal/templating/models/html/fragments/createcontainer.gohtml +34 -86
@@ -1,64 +1,38 @@
1-<div
2- class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"
3- hx-ext="response-targets"
4->
1+<div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow" hx-ext="response-targets">
52 <!-- Left Side: Docker Run Options -->
63 <div class="flex-grow bg-slate-100 p-4 rounded-md">
74 <h2 class="font-bold pb-4">Deploy a container based on {{.ImageName}}</h2>
85 {{if .ErrorMessage}}
96 <div class="text-red-500">{{.ErrorMessage}}</div>
107 {{ end }}
11- <form
12- action="/create-container/{{.ShortID}}"
13- method="post"
14- hx-post="/htmx/create-container/{{.ShortID}}"
15- hx-swap="innerHTML"
16- hx-target-*="#error-message-create-container-{{.ShortID}}"
17- >
8+ <form action="/create-container/{{.ShortID}}" method="post" hx-post="/htmx/create-container/{{.ShortID}}"
9+ hx-swap="innerHTML" hx-target-*="#error-message-create-container-{{.ShortID}}">
1810 <!-- Image Name -->
1911 <div class="flex space-x-4 pb-4">
2012 <div class="w-1/3">
2113 <label class="block">Image name</label>
22- <input
23- type="text"
24- name="image_name"
25- class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
26- value="{{.ImageName}}"
27- readonly
28- />
14+ <input type="text" name="image_name" class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
15+ value="{{.ImageName}}" readonly />
2916 </div>
3017
3118 <div class="w-2/3">
3219 <label class="block">Image ID</label>
33- <input
34- type="text"
35- name="image_id"
36- class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
37- value="{{.ImageID}}"
38- readonly
39- />
20+ <input type="text" name="image_id" class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
21+ value="{{.ImageID}}" readonly />
4022 </div>
4123 </div>
4224
4325 <!-- Container Name -->
4426 <div class="pb-4">
4527 <label class="block">Container name</label>
46- <input
47- type="text"
48- name="container_name"
49- class="w-full p-4 rounded-md"
50- value="{{.ContainerName}}"
51- />
28+ <input type="text" name="container_name" class="w-full p-4 rounded-md" value="{{.ContainerName}}" />
5229 </div>
5330 <!-- Http(s) / Subdomain / Domain -->
5431 <div class="flex pb-4 space-x-4">
5532 <!-- Protocol -->
5633 <div class="w-1/4">
5734 <label class="block">Protocol</label>
58- <select
59- name="container_protocol"
60- class="w-full p-4 rounded-md border-2 bg-slate-200 border-gray-300"
61- >
35+ <select name="container_protocol" class="w-full p-4 rounded-md border-2 bg-slate-200 border-gray-300">
6236 <option value="https">https</option>
6337 <option value="http">http</option>
6438 </select>
@@ -67,69 +41,50 @@
6741 <!-- Subdomain -->
6842 <div class="w-1/3">
6943 <label class="block">Subdomain</label>
70- <input
71- type="text"
72- name="container_subdomain"
73- class="w-full p-4 rounded-md"
74- value="{{.ContainerSubdomain}}"
75- />
44+ <input type="text" name="container_subdomain" class="w-full p-4 rounded-md" value="{{.ContainerSubdomain}}" />
7645 </div>
7746
7847 <!-- Domain -->
7948 <div class="w-5/12">
8049 <label class="block">Domain</label>
81- <input
82- type="text"
83- name="container_domain"
84- class="w-full p-4 rounded-md"
85- value="{{.ContainerDomain}}"
86- />
50+ <input type="text" name="container_domain" class="w-full p-4 rounded-md" value="{{.ContainerDomain}}" />
8751 </div>
8852 </div>
89- <!-- Ports -->
53+ <!-- Exposed Ports -->
9054 <div class="pb-4">
91- <label class="block">Ports</label>
92- <span class="text-gray-500"
93- >eg: 80:80/tcp (You can specify multiple ports by separating them with
94- a comma)</span
95- >
96- <input
97- type="text"
98- name="ports"
99- class="w-full p-4 rounded-md"
100- value="{{.Ports}}"
101- />
55+ <label class="block">Exposed Ports (Optional)</label>
56+ <span class="text-gray-500">
57+ eg: 8080:1887/tcp (You can specify multiple ports by separating them with a comma)
58+ </span>
59+ <input type="text" name="ports" class="w-full p-4 rounded-md" value="{{.Ports}}" />
10260 </div>
10361
62+ <!-- Traefik Entry Point -->
63+ <div class="pb-4">
64+ <label class="block">Traefik Entry Point (Required)</label>
65+ <span class="text-gray-500">
66+ Specify the internal container port used in Traefik's loadbalancer labels
67+ </span>
68+ <input type="text" name="traefik_entry_point" class="w-full p-4 rounded-md" value="{{.TraefikEntryPoint}}" required />
69+ </div>
70+
10471 <!-- Volumes -->
10572 <div class="pb-4">
10673 <label class="block">Volumes</label>
10774 <span class="text-gray-500">e.g. ./data:/container/data </span>
108- <input
109- type="text"
110- name="volumes"
111- class="w-full p-4 rounded-md"
112- value="{{.Volumes}}"
113- />
75+ <input type="text" name="volumes" class="w-full p-4 rounded-md" value="{{.Volumes}}" />
11476 </div>
11577
11678 <div class="pb-4">
11779 <label class="block">Environment variables</label>
11880 <span class="text-gray-500">e.g. KEY=VALUE </span>
119- <textarea
120- name="environment_variables"
121- class="w-full p-4 rounded-md"
122- rows="4"
123- >{{.EnvironmentVariables}}</textarea
124- >
81+ <textarea name="environment_variables" class="w-full p-4 rounded-md"
82+ rows="4">{{.EnvironmentVariables}}</textarea>
12583 </div>
12684 <!-- Restart: always/on-failure/only when stopped/never -->
12785 <div class="pb-4">
12886 <label class="block">Restart</label>
129- <select
130- name="restart"
131- class="w-full p-4 rounded-md border-2 bg-gray-100 border-gray-300"
132- >
87+ <select name="restart" class="w-full p-4 rounded-md border-2 bg-gray-100 border-gray-300">
13388 <option value="always">Always</option>
13489 <option value="on-failure">On failure</option>
13590 <option value="unless-stopped">Unless stopped</option>
@@ -137,16 +92,9 @@
13792 </select>
13893 </div>
13994
140- <input
141- type="submit"
142- value="Create"
143- id="create-container-submit"
144- class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
145- />
95+ <input type="submit" value="Create" id="create-container-submit"
96+ class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" />
14697 </form>
147- <div
148- id="error-message-create-container-{{.ShortID}}"
149- class="text-red-500"
150- ></div>
98+ <div id="error-message-create-container-{{.ShortID}}" class="text-red-500"></div>
15199 </div>
152-</div>
100+</div>⏎̸
@@ -1,64 +1,38 @@
1-<div1+<div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow" hx-ext="response-targets">
2- class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"
3- hx-ext="response-targets"
4->
5 <!-- Left Side: Docker Run Options -->2 <!-- Left Side: Docker Run Options -->
6 <div class="flex-grow bg-slate-100 p-4 rounded-md">3 <div class="flex-grow bg-slate-100 p-4 rounded-md">
7 <h2 class="font-bold pb-4">Deploy a container based on {{.ImageName}}</h2>4 <h2 class="font-bold pb-4">Deploy a container based on {{.ImageName}}</h2>
8 {{if .ErrorMessage}}5 {{if .ErrorMessage}}
9 <div class="text-red-500">{{.ErrorMessage}}</div>6 <div class="text-red-500">{{.ErrorMessage}}</div>
10 {{ end }}7 {{ end }}
11- <form8+ <form action="/create-container/{{.ShortID}}" method="post" hx-post="/htmx/create-container/{{.ShortID}}"
12- action="/create-container/{{.ShortID}}"9+ hx-swap="innerHTML" hx-target-*="#error-message-create-container-{{.ShortID}}">
13- method="post"
14- hx-post="/htmx/create-container/{{.ShortID}}"
15- hx-swap="innerHTML"
16- hx-target-*="#error-message-create-container-{{.ShortID}}"
17- >
18 <!-- Image Name -->10 <!-- Image Name -->
19 <div class="flex space-x-4 pb-4">11 <div class="flex space-x-4 pb-4">
20 <div class="w-1/3">12 <div class="w-1/3">
21 <label class="block">Image name</label>13 <label class="block">Image name</label>
22- <input14+ <input type="text" name="image_name" class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
23- type="text"15+ value="{{.ImageName}}" readonly />
24- name="image_name"
25- class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
26- value="{{.ImageName}}"
27- readonly
28- />
29 </div>16 </div>
30 17
31 <div class="w-2/3">18 <div class="w-2/3">
32 <label class="block">Image ID</label>19 <label class="block">Image ID</label>
33- <input20+ <input type="text" name="image_id" class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
34- type="text"21+ value="{{.ImageID}}" readonly />
35- name="image_id"
36- class="w-full p-4 rounded-md bg-gray-100 border-gray-300 border-2"
37- value="{{.ImageID}}"
38- readonly
39- />
40 </div>22 </div>
41 </div>23 </div>
42 24
43 <!-- Container Name -->25 <!-- Container Name -->
44 <div class="pb-4">26 <div class="pb-4">
45 <label class="block">Container name</label>27 <label class="block">Container name</label>
46- <input28+ <input type="text" name="container_name" class="w-full p-4 rounded-md" value="{{.ContainerName}}" />
47- type="text"
48- name="container_name"
49- class="w-full p-4 rounded-md"
50- value="{{.ContainerName}}"
51- />
52 </div>29 </div>
53 <!-- Http(s) / Subdomain / Domain -->30 <!-- Http(s) / Subdomain / Domain -->
54 <div class="flex pb-4 space-x-4">31 <div class="flex pb-4 space-x-4">
55 <!-- Protocol -->32 <!-- Protocol -->
56 <div class="w-1/4">33 <div class="w-1/4">
57 <label class="block">Protocol</label>34 <label class="block">Protocol</label>
58- <select35+ <select name="container_protocol" class="w-full p-4 rounded-md border-2 bg-slate-200 border-gray-300">
59- name="container_protocol"
60- class="w-full p-4 rounded-md border-2 bg-slate-200 border-gray-300"
61- >
62 <option value="https">https</option>36 <option value="https">https</option>
63 <option value="http">http</option>37 <option value="http">http</option>
64 </select>38 </select>
@@ -67,69 +41,50 @@
67 <!-- Subdomain -->41 <!-- Subdomain -->
68 <div class="w-1/3">42 <div class="w-1/3">
69 <label class="block">Subdomain</label>43 <label class="block">Subdomain</label>
70- <input44+ <input type="text" name="container_subdomain" class="w-full p-4 rounded-md" value="{{.ContainerSubdomain}}" />
71- type="text"
72- name="container_subdomain"
73- class="w-full p-4 rounded-md"
74- value="{{.ContainerSubdomain}}"
75- />
76 </div>45 </div>
77 46
78 <!-- Domain -->47 <!-- Domain -->
79 <div class="w-5/12">48 <div class="w-5/12">
80 <label class="block">Domain</label>49 <label class="block">Domain</label>
81- <input50+ <input type="text" name="container_domain" class="w-full p-4 rounded-md" value="{{.ContainerDomain}}" />
82- type="text"
83- name="container_domain"
84- class="w-full p-4 rounded-md"
85- value="{{.ContainerDomain}}"
86- />
87 </div>51 </div>
88 </div>52 </div>
89- <!-- Ports -->53+ <!-- Exposed Ports -->
90 <div class="pb-4">54 <div class="pb-4">
91- <label class="block">Ports</label>55+ <label class="block">Exposed Ports (Optional)</label>
92- <span class="text-gray-500"56+ <span class="text-gray-500">
93- >eg: 80:80/tcp (You can specify multiple ports by separating them with57+ eg: 8080:1887/tcp (You can specify multiple ports by separating them with a comma)
94- a comma)</span58+ </span>
95- >59+ <input type="text" name="ports" class="w-full p-4 rounded-md" value="{{.Ports}}" />
96- <input
97- type="text"
98- name="ports"
99- class="w-full p-4 rounded-md"
100- value="{{.Ports}}"
101- />
102 </div>60 </div>
103 61
62+ <!-- Traefik Entry Point -->
63+ <div class="pb-4">
64+ <label class="block">Traefik Entry Point (Required)</label>
65+ <span class="text-gray-500">
66+ Specify the internal container port used in Traefik's loadbalancer labels
67+ </span>
68+ <input type="text" name="traefik_entry_point" class="w-full p-4 rounded-md" value="{{.TraefikEntryPoint}}" required />
69+ </div>
70+
104 <!-- Volumes -->71 <!-- Volumes -->
105 <div class="pb-4">72 <div class="pb-4">
106 <label class="block">Volumes</label>73 <label class="block">Volumes</label>
107 <span class="text-gray-500">e.g. ./data:/container/data </span>74 <span class="text-gray-500">e.g. ./data:/container/data </span>
108- <input75+ <input type="text" name="volumes" class="w-full p-4 rounded-md" value="{{.Volumes}}" />
109- type="text"
110- name="volumes"
111- class="w-full p-4 rounded-md"
112- value="{{.Volumes}}"
113- />
114 </div>76 </div>
115 77
116 <div class="pb-4">78 <div class="pb-4">
117 <label class="block">Environment variables</label>79 <label class="block">Environment variables</label>
118 <span class="text-gray-500">e.g. KEY=VALUE </span>80 <span class="text-gray-500">e.g. KEY=VALUE </span>
119- <textarea81+ <textarea name="environment_variables" class="w-full p-4 rounded-md"
120- name="environment_variables"82+ rows="4">{{.EnvironmentVariables}}</textarea>
121- class="w-full p-4 rounded-md"
122- rows="4"
123- >{{.EnvironmentVariables}}</textarea
124- >
125 </div>83 </div>
126 <!-- Restart: always/on-failure/only when stopped/never -->84 <!-- Restart: always/on-failure/only when stopped/never -->
127 <div class="pb-4">85 <div class="pb-4">
128 <label class="block">Restart</label>86 <label class="block">Restart</label>
129- <select87+ <select name="restart" class="w-full p-4 rounded-md border-2 bg-gray-100 border-gray-300">
130- name="restart"
131- class="w-full p-4 rounded-md border-2 bg-gray-100 border-gray-300"
132- >
133 <option value="always">Always</option>88 <option value="always">Always</option>
134 <option value="on-failure">On failure</option>89 <option value="on-failure">On failure</option>
135 <option value="unless-stopped">Unless stopped</option>90 <option value="unless-stopped">Unless stopped</option>
@@ -137,16 +92,9 @@
137 </select>92 </select>
138 </div>93 </div>
139 94
140- <input95+ <input type="submit" value="Create" id="create-container-submit"
141- type="submit"96+ class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" />
142- value="Create"
143- id="create-container-submit"
144- class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
145- />
146 </form>97 </form>
147- <div98+ <div id="error-message-create-container-{{.ShortID}}" class="text-red-500"></div>
148- id="error-message-create-container-{{.ShortID}}"
149- class="text-red-500"
150- ></div>
151 </div>99 </div>
152-</div>100+</div>⏎̸
M internal/templating/models/txt/locstrings.yml +4 -2
@@ -27,10 +27,11 @@ en:
2727 confirm-container-stop: Are you sure you want to stop this container?
2828 no-containers: No available containers.
2929 thName: Name
30- thPorts: Ports
30+ thPorts: Exposed Ports
3131 thStatus: Status
3232 thState: State
3333 thActions: Actions
34+ thEntryPoint: "Entry Point"
3435 settings:
3536 meta:
3637 title: Gordon - Settings
@@ -66,10 +67,11 @@ fr:
6667 confirm-container-stop: Êtes-vous sûr de vouloir arrêter ce conteneur?
6768 no-containers: Aucun conteneur disponible.
6869 thName: Nom
69- thPorts: Ports
70+ thPorts: Ports exposés
7071 thStatus: Statut
7172 thState: État
7273 thActions: Actions
74+ thEntryPoint: "Point d'entrée"
7375 settings:
7476 meta:
7577 title: Gordon - Paramètres
@@ -27,10 +27,11 @@ en:
27 confirm-container-stop: Are you sure you want to stop this container?27 confirm-container-stop: Are you sure you want to stop this container?
28 no-containers: No available containers.28 no-containers: No available containers.
29 thName: Name29 thName: Name
30- thPorts: Ports30+ thPorts: Exposed Ports
31 thStatus: Status31 thStatus: Status
32 thState: State32 thState: State
33 thActions: Actions33 thActions: Actions
34+ thEntryPoint: "Entry Point"
34 settings:35 settings:
35 meta:36 meta:
36 title: Gordon - Settings37 title: Gordon - Settings
@@ -66,10 +67,11 @@ fr:
66 confirm-container-stop: Êtes-vous sûr de vouloir arrêter ce conteneur?67 confirm-container-stop: Êtes-vous sûr de vouloir arrêter ce conteneur?
67 no-containers: Aucun conteneur disponible.68 no-containers: Aucun conteneur disponible.
68 thName: Nom69 thName: Nom
69- thPorts: Ports70+ thPorts: Ports exposés
70 thStatus: Statut71 thStatus: Statut
71 thState: État72 thState: État
72 thActions: Actions73 thActions: Actions
74+ thEntryPoint: "Point d'entrée"
73 settings:75 settings:
74 meta:76 meta:
75 title: Gordon - Paramètres77 title: Gordon - Paramètres
M internal/webui/public/libs/htmx.min.js +1 -1 generated file: collapsed
M internal/webui/public/libs/hyperscript.min.js +1 -1 generated file: collapsed
M pkg/docker/containers.go +15 -14
@@ -15,20 +15,21 @@ import (
1515 )
1616
1717 type ContainerCommandParams struct {
18- ContainerName string
19- ContainerHost string
20- Domain string
21- ServiceName string
22- IsSSL bool
23- EnvVar string
24- ImageName string
25- ImageID string
26- PortMappings []PortMapping
27- Volumes []string
28- Labels []string
29- Network string
30- Restart string
31- Environment []string
18+ ContainerName string
19+ ContainerHost string
20+ Domain string
21+ ServiceName string
22+ IsSSL bool
23+ EnvVar string
24+ ImageName string
25+ ImageID string
26+ PortMappings []PortMapping
27+ Volumes []string
28+ Labels []string
29+ Network string
30+ Restart string
31+ Environment []string
32+ TraefikEntryPoint string
3233 }
3334
3435 type PortMapping struct {
@@ -15,20 +15,21 @@ import (
15 )15 )
16 16
17 type ContainerCommandParams struct {17 type ContainerCommandParams struct {
18- ContainerName string18+ ContainerName string
19- ContainerHost string19+ ContainerHost string
20- Domain string20+ Domain string
21- ServiceName string21+ ServiceName string
22- IsSSL bool22+ IsSSL bool
23- EnvVar string23+ EnvVar string
24- ImageName string24+ ImageName string
25- ImageID string25+ ImageID string
26- PortMappings []PortMapping26+ PortMappings []PortMapping
27- Volumes []string27+ Volumes []string
28- Labels []string28+ Labels []string
29- Network string29+ Network string
30- Restart string30+ Restart string
31- Environment []string31+ Environment []string
32+ TraefikEntryPoint string
32 }33 }
33 34
34 type PortMapping struct {35 type PortMapping struct {

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