diff --git a/cmd/boring/tunnels.go b/cmd/boring/tunnels.go index 81421be..17250b3 100644 --- a/cmd/boring/tunnels.go +++ b/cmd/boring/tunnels.go @@ -196,27 +196,53 @@ func listTunnels() { return } - tbl := table.New("Status", "Name", "Local", "", "Remote", "Via") + // Group tunnels by their group field + groupedTunnels := make(map[string][]*tunnel.Desc) visited := make(map[string]bool) + // Process configured tunnels for _, t := range conf.Tunnels { + var tunnelToAdd *tunnel.Desc if q, ok := ts[t.Name]; ok { - tbl.AddRow(status(q), q.Name, q.LocalAddress, q.Mode, q.RemoteAddress, q.Host) + tunnelToAdd = q visited[q.Name] = true - continue + } else { + tunnelToAdd = &t } - // TODO: case where tunnel is in resp but with different name - tbl.AddRow(status(&t), t.Name, t.LocalAddress, t.Mode, t.RemoteAddress, t.Host) + + group := tunnelToAdd.Group + if group == "" { + group = "default" + } + groupedTunnels[group] = append(groupedTunnels[group], tunnelToAdd) } // Add tunnels that are in resp but not in the config for _, q := range ts { if !visited[q.Name] { - tbl.AddRow(status(q), q.Name, q.LocalAddress, q.Mode, q.RemoteAddress, q.Host) + group := q.Group + if group == "" { + group = "default" + } + groupedTunnels[group] = append(groupedTunnels[group], q) } } - log.Emitf("%v", tbl) + // Display tunnels grouped by group + first := true + for groupName, tunnels := range groupedTunnels { + if !first { + log.Emitf("\n") + } + first = false + + log.Emitf("Group: %s\n", groupName) + tbl := table.New("Status", "Name", "Local", "", "Remote", "Via") + for _, t := range tunnels { + tbl.AddRow(status(t), t.Name, t.LocalAddress, t.Mode, t.RemoteAddress, t.Host) + } + log.Emitf("%v", tbl) + } } func transmitCmd(cmd daemon.Cmd, resp any) error { diff --git a/examples/config.toml b/examples/config.toml index 8a56e5a..ce717f0 100644 --- a/examples/config.toml +++ b/examples/config.toml @@ -4,6 +4,7 @@ name = "dev" local = "9000" remote = "localhost:9000" host = "dev-server" # automatically matches host against SSH config +group = "development" # optional: group tunnels for organized display # simple remote (-R) tunnel; note that we can also use IP{v4,v6} addresses [[tunnels]] @@ -12,6 +13,7 @@ local = "[::1]:9000" remote = "9000" host = "dev-server" mode = "remote" +group = "development" # example of an explicit host (doesn't use SSH config) [[tunnels]] @@ -21,6 +23,7 @@ remote = "localhost:5001" host = "prod.example.com" user = "root" identity = "~/.ssh/id_prod" # will try default ones if not set +group = "production" # example using Unix sockets, and remote (-R) mode; # note that we can freely mix unix and TCP sockets @@ -30,6 +33,7 @@ local = "/tmp/serve.sock" remote = "/tmp/listen.sock" host = "dev-server" mode = "remote" +group = "development" # example of a SOCKS5 proxy; this will setup a SOCKS5 server at # port 9000 and forward all traffic through `dev-server`. @@ -38,6 +42,7 @@ name = "dev-prox" local = "9000" host = "dev-server" mode = "socks" +group = "proxies" # reverse SOCKS5 proxy; this will setup a SOCKS5 server at # port 9000 on `dev-server` and forward all traffic through @@ -47,3 +52,4 @@ name = "dev-rev-prox" remote = "9000" host = "dev-server" mode = "socks-remote" +group = "proxies" diff --git a/internal/tunnel/tunnel.go b/internal/tunnel/tunnel.go index 25ced69..a419635 100644 --- a/internal/tunnel/tunnel.go +++ b/internal/tunnel/tunnel.go @@ -34,6 +34,7 @@ type Desc struct { Port int `toml:"port" json:"port"` KeepAlive *int `toml:"keep_alive" json:"keep_alive"` Mode Mode `toml:"mode" json:"mode"` + Group string `toml:"group" json:"group"` Status Status `toml:"-" json:"status"` LastConn time.Time `toml:"-" json:"last_conn"` }