@@ -20,33 +20,37 @@ func printStatus(api *apiclient.Client, w io.Writer) error {
2020 }
2121
2222 rows := [][]string {
23+ {"Name" , stats .Name },
2324 {"Download rate" , fmt .Sprintf ("%s (%s)" , stats .NetworkStatsInIECUnits .RateIn , stats .NetworkStatsInIECUnits .TotalIn )},
2425 {"Upload rate" , fmt .Sprintf ("%s (%s)" , stats .NetworkStatsInIECUnits .RateOut , stats .NetworkStatsInIECUnits .TotalOut )},
2526 {"Bootstrap peers" , fmt .Sprintf ("%d/%d" , stats .ConnectedBootstrapPeers , stats .TotalBootstrapPeers )},
26- {"DNS" , formatWorkingStatus (stats .IsAwlDNSSetAsSystem )},
27- {"SOCKS5 Proxy" , formatWorkingStatus (stats .SOCKS5 .ListenerEnabled )},
28- {"SOCKS5 Proxy address" , stats .SOCKS5 .ListenAddress },
29- {"SOCKS5 Proxy exit node" , stats .SOCKS5 .UsingPeerName },
27+ {"VPN" , formatVPNStatus (stats .VPN )},
3028 {"VPN gateway client" , formatVPNGatewayClient (stats .VPNGateway )},
3129 }
3230 gw := stats .VPNGateway
3331 if gw .ClientEnabled {
34- if gw .GatewayPublicIP != "" {
35- rows = append (rows , []string {"VPN gateway public IP " , gw . GatewayPublicIP })
32+ if detail := formatExitDetail ( gw .GatewayPublicIP , gw . GatewayPing , gw . Connected , gw . GatewayThroughRelay ); detail != "" {
33+ rows = append (rows , []string {"VPN gateway exit " , detail })
3634 }
37- if gw .GatewayPing > 0 {
38- rows = append (rows , []string {"VPN gateway ping" , gw .GatewayPing .Round (time .Millisecond ).String ()})
35+ }
36+ rows = append (rows , []string {"VPN gateway server" , formatWorkingStatus (stats .VPNGateway .ServerEnabled )})
37+ rows = append (rows , []string {"SOCKS5 Proxy" , formatServiceStatus (stats .SOCKS5 .ListenerEnabled , stats .SOCKS5 .ListenAddress )})
38+ s5 := stats .SOCKS5
39+ if exit := formatSOCKS5ExitNode (s5 ); exit != "off" {
40+ rows = append (rows , []string {"SOCKS5 exit node" , exit })
41+ if detail := formatExitDetail (s5 .UsingPeerPublicIP , s5 .UsingPeerPing , s5 .Connected , s5 .UsingPeerThroughRelay ); detail != "" {
42+ rows = append (rows , []string {"SOCKS5 exit" , detail })
3943 }
40- rows = append (rows , []string {"VPN gateway via relay" , fmt .Sprintf ("%v" , gw .GatewayThroughRelay )})
4144 }
4245 rows = append (rows ,
43- []string {"VPN gateway server " , formatWorkingStatus (stats .VPNGateway . ServerEnabled )},
46+ []string {"DNS " , formatServiceStatus (stats .IsAwlDNSSetAsSystem , stats . AwlDNSAddress )},
4447 []string {"Reachability" , strings .ToLower (stats .Reachability )},
45- []string {"Uptime" , stats .Uptime . Round ( time . Second ). String ( )},
48+ []string {"Uptime" , formatUptime ( stats .Uptime )},
4649 []string {"Server version" , stats .ServerVersion },
4750 )
4851
4952 table := tablewriter .NewWriter (w )
53+ table .SetAutoWrapText (false )
5054 table .AppendBulk (rows )
5155
5256 table .Render ()
@@ -61,23 +65,126 @@ func formatWorkingStatus(working bool) string {
6165 return "not working"
6266}
6367
64- // formatVPNGatewayClient renders the client-side VPN gateway state in a
65- // single line: either "off", or the gateway peer's name + connectivity.
66- // Mirrors how SOCKS5 status splits across two rows but compresses to one
67- // since VPN gateway has fewer knobs to surface.
68+ // formatEnabled renders an on/off feature toggle.
69+ func formatEnabled (enabled bool ) string {
70+ if enabled {
71+ return "enabled"
72+ }
73+ return "disabled"
74+ }
75+
76+ // formatConnected renders libp2p connectivity to a peer.
77+ func formatConnected (connected bool ) string {
78+ if connected {
79+ return "connected"
80+ }
81+ return "disconnected"
82+ }
83+
84+ // formatServiceStatus renders an enabled/disabled service and, when enabled,
85+ // appends its listen address in parentheses, e.g. "working (127.0.0.66:53)".
86+ // The address is omitted while the service is off.
87+ func formatServiceStatus (enabled bool , addr string ) string {
88+ if ! enabled {
89+ return "not working"
90+ }
91+ if addr != "" {
92+ return fmt .Sprintf ("working (%s)" , addr )
93+ }
94+ return "working"
95+ }
96+
97+ // formatVPNStatus renders the local VPN interface state, including the
98+ // interface name and assigned address when it is up, e.g.
99+ // "working (awl0, 10.66.0.1/24)".
100+ func formatVPNStatus (vpn entity.VPNInfo ) string {
101+ if ! vpn .VPNInterfaceEnabled {
102+ return "not working"
103+ }
104+ var parts []string
105+ if vpn .InterfaceName != "" {
106+ parts = append (parts , vpn .InterfaceName )
107+ }
108+ if vpn .IPNet != "" {
109+ parts = append (parts , vpn .IPNet )
110+ }
111+ if len (parts ) == 0 {
112+ return "working"
113+ }
114+ return fmt .Sprintf ("working (%s)" , strings .Join (parts , ", " ))
115+ }
116+
117+ // formatExitDetail renders a compact one-line summary of a selected exit peer
118+ // (SOCKS5 or VPN gateway): "<public IP>, ping <N>ms, direct/via relay". Each
119+ // part is included only when meaningful; the relay part appears only while
120+ // connected. Returns "" when nothing is known.
121+ func formatExitDetail (publicIP string , ping time.Duration , connected , throughRelay bool ) string {
122+ var parts []string
123+ if publicIP != "" {
124+ parts = append (parts , publicIP )
125+ }
126+ if ping > 0 {
127+ parts = append (parts , "ping " + ping .Round (time .Millisecond ).String ())
128+ }
129+ if connected {
130+ parts = append (parts , formatRelay (throughRelay ))
131+ }
132+ return strings .Join (parts , ", " )
133+ }
134+
135+ // formatRelay renders the connection path of an exit peer in human terms.
136+ func formatRelay (throughRelay bool ) string {
137+ if throughRelay {
138+ return "via relay"
139+ }
140+ return "direct"
141+ }
142+
143+ // formatUptime renders uptime with spaces between units. For 24h or more it
144+ // switches to day granularity and drops seconds (e.g. "1d 21h 13m"); below 24h
145+ // it keeps seconds and omits leading zero units (e.g. "1h 25m 5s", "5s").
146+ func formatUptime (d time.Duration ) string {
147+ d = d .Round (time .Second )
148+ days := d / (24 * time .Hour )
149+ d -= days * 24 * time .Hour
150+ hours := d / time .Hour
151+ d -= hours * time .Hour
152+ mins := d / time .Minute
153+ d -= mins * time .Minute
154+ secs := d / time .Second
155+
156+ if days > 0 {
157+ return fmt .Sprintf ("%dd %dh %dm" , int64 (days ), int64 (hours ), int64 (mins ))
158+ }
159+
160+ var parts []string
161+ if hours > 0 {
162+ parts = append (parts , fmt .Sprintf ("%dh" , int64 (hours )))
163+ }
164+ if mins > 0 || hours > 0 {
165+ parts = append (parts , fmt .Sprintf ("%dm" , int64 (mins )))
166+ }
167+ parts = append (parts , fmt .Sprintf ("%ds" , int64 (secs )))
168+ return strings .Join (parts , " " )
169+ }
170+
171+ func formatSOCKS5ExitNode (s5 entity.SOCKS5Info ) string {
172+ return formatExitClient (s5 .UsingPeerID != "" , s5 .Connected , s5 .UsingPeerName , s5 .UsingPeerID )
173+ }
174+
68175func formatVPNGatewayClient (gw entity.VPNGatewayInfo ) string {
69- if ! gw .ClientEnabled {
176+ return formatExitClient (gw .ClientEnabled , gw .Connected , gw .GatewayPeerName , gw .GatewayPeerID )
177+ }
178+
179+ func formatExitClient (enabled , connected bool , peerName , peerID string ) string {
180+ if ! enabled {
70181 return "off"
71182 }
72- name := gw . GatewayPeerName
183+ name := peerName
73184 if name == "" {
74- name = gw .GatewayPeerID
75- }
76- conn := "disconnected"
77- if gw .Connected {
78- conn = "connected"
185+ name = peerID
79186 }
80- return fmt .Sprintf ("via %s [%s]" , name , conn )
187+ return fmt .Sprintf ("%s [%s]" , name , formatConnected ( connected ) )
81188}
82189
83190func printPeerId (api * apiclient.Client , w io.Writer ) error {
0 commit comments