cmd/tailscale/cli: use tabwriter for tailscale status (#16596)

Fixes #17238

Signed-off-by: Mahyar Mirrashed <mah.mirr@gmail.com>
This commit is contained in:
Mahyar Mirrashed 2025-09-26 15:42:16 -05:00 committed by GitHub
parent c95fdb0f8a
commit eaecc0be54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -4,7 +4,6 @@
package cli package cli
import ( import (
"bytes"
"cmp" "cmp"
"context" "context"
"encoding/json" "encoding/json"
@ -16,6 +15,7 @@ import (
"net/netip" "net/netip"
"os" "os"
"strings" "strings"
"text/tabwriter"
"github.com/peterbourgon/ff/v3/ffcli" "github.com/peterbourgon/ff/v3/ffcli"
"github.com/toqueteos/webbrowser" "github.com/toqueteos/webbrowser"
@ -56,6 +56,7 @@ https://github.com/tailscale/tailscale/blob/main/ipn/ipnstate/ipnstate.go
fs.BoolVar(&statusArgs.peers, "peers", true, "show status of peers") fs.BoolVar(&statusArgs.peers, "peers", true, "show status of peers")
fs.StringVar(&statusArgs.listen, "listen", "127.0.0.1:8384", "listen address for web mode; use port 0 for automatic") fs.StringVar(&statusArgs.listen, "listen", "127.0.0.1:8384", "listen address for web mode; use port 0 for automatic")
fs.BoolVar(&statusArgs.browser, "browser", true, "Open a browser in web mode") fs.BoolVar(&statusArgs.browser, "browser", true, "Open a browser in web mode")
fs.BoolVar(&statusArgs.header, "header", false, "show column headers in table format")
return fs return fs
})(), })(),
} }
@ -68,6 +69,7 @@ var statusArgs struct {
active bool // in CLI mode, filter output to only peers with active sessions active bool // in CLI mode, filter output to only peers with active sessions
self bool // in CLI mode, show status of local machine self bool // in CLI mode, show status of local machine
peers bool // in CLI mode, show status of peer machines peers bool // in CLI mode, show status of peer machines
header bool // in CLI mode, show column headers in table format
} }
const mullvadTCD = "mullvad.ts.net." const mullvadTCD = "mullvad.ts.net."
@ -151,10 +153,15 @@ func runStatus(ctx context.Context, args []string) error {
os.Exit(1) os.Exit(1)
} }
var buf bytes.Buffer w := tabwriter.NewWriter(Stdout, 0, 0, 2, ' ', 0)
f := func(format string, a ...any) { fmt.Fprintf(&buf, format, a...) } f := func(format string, a ...any) { fmt.Fprintf(w, format, a...) }
if statusArgs.header {
fmt.Fprintln(w, "IP\tHostname\tOwner\tOS\tStatus\t")
fmt.Fprintln(w, "--\t--------\t-----\t--\t------\t")
}
printPS := func(ps *ipnstate.PeerStatus) { printPS := func(ps *ipnstate.PeerStatus) {
f("%-15s %-20s %-12s %-7s ", f("%s\t%s\t%s\t%s\t",
firstIPString(ps.TailscaleIPs), firstIPString(ps.TailscaleIPs),
dnsOrQuoteHostname(st, ps), dnsOrQuoteHostname(st, ps),
ownerLogin(st, ps), ownerLogin(st, ps),
@ -199,7 +206,7 @@ func runStatus(ctx context.Context, args []string) error {
if anyTraffic { if anyTraffic {
f(", tx %d rx %d", ps.TxBytes, ps.RxBytes) f(", tx %d rx %d", ps.TxBytes, ps.RxBytes)
} }
f("\n") f("\t\n")
} }
if statusArgs.self && st.Self != nil { if statusArgs.self && st.Self != nil {
@ -229,7 +236,8 @@ func runStatus(ctx context.Context, args []string) error {
printPS(ps) printPS(ps)
} }
} }
Stdout.Write(buf.Bytes()) w.Flush()
if locBasedExitNode { if locBasedExitNode {
outln() outln()
printf("# To see the full list of exit nodes, including location-based exit nodes, run `tailscale exit-node list` \n") printf("# To see the full list of exit nodes, including location-based exit nodes, run `tailscale exit-node list` \n")