@@ -20,64 +20,180 @@ 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 ()})
39- }
40- rows = append (rows , []string {"VPN gateway via relay" , fmt .Sprintf ("%v" , gw .GatewayThroughRelay )})
4135 }
4236 rows = append (rows ,
4337 []string {"VPN gateway server" , formatWorkingStatus (stats .VPNGateway .ServerEnabled )},
38+ []string {"SOCKS5 Proxy" , formatServiceStatus (stats .SOCKS5 .ListenerEnabled , stats .SOCKS5 .ListenAddress )},
39+ )
40+
41+ s5 := stats .SOCKS5
42+ if exit := formatSOCKS5ExitNode (s5 ); exit != "off" {
43+ rows = append (rows , []string {"SOCKS5 exit node" , exit })
44+ if detail := formatExitDetail (s5 .UsingPeerPublicIP , s5 .UsingPeerPing , s5 .Connected , s5 .UsingPeerThroughRelay ); detail != "" {
45+ rows = append (rows , []string {"SOCKS5 exit" , detail })
46+ }
47+ }
48+
49+ rows = append (rows ,
50+ []string {"DNS" , formatServiceStatus (stats .IsAwlDNSSetAsSystem , stats .AwlDNSAddress )},
4451 []string {"Reachability" , strings .ToLower (stats .Reachability )},
45- []string {"Uptime" , stats .Uptime . Round ( time . Second ). String ( )},
52+ []string {"Uptime" , formatUptime ( stats .Uptime )},
4653 []string {"Server version" , stats .ServerVersion },
4754 )
4855
4956 table := tablewriter .NewWriter (w )
57+ table .SetAutoWrapText (false )
5058 table .AppendBulk (rows )
5159
5260 table .Render ()
5361
5462 return nil
5563}
5664
65+ const (
66+ statusWorking = "working"
67+ statusNotWorking = "not working"
68+ )
69+
5770func formatWorkingStatus (working bool ) string {
5871 if working {
59- return "working"
72+ return statusWorking
73+ }
74+ return statusNotWorking
75+ }
76+
77+ // formatEnabled renders an on/off feature toggle.
78+ func formatEnabled (enabled bool ) string {
79+ if enabled {
80+ return "enabled"
81+ }
82+ return "disabled"
83+ }
84+
85+ // formatConnected renders libp2p connectivity to a peer.
86+ func formatConnected (connected bool ) string {
87+ if connected {
88+ return "connected"
89+ }
90+ return "disconnected"
91+ }
92+
93+ // formatServiceStatus renders an enabled/disabled service and, when enabled,
94+ // appends its listen address in parentheses, e.g. "working (127.0.0.66:53)".
95+ // The address is omitted while the service is off.
96+ func formatServiceStatus (enabled bool , addr string ) string {
97+ if ! enabled {
98+ return statusNotWorking
99+ }
100+ if addr != "" {
101+ return fmt .Sprintf ("%s (%s)" , statusWorking , addr )
102+ }
103+ return statusWorking
104+ }
105+
106+ // formatVPNStatus renders the local VPN interface state, including the
107+ // interface name and assigned address when it is up, e.g.
108+ // "working (awl0, 10.66.0.1/24)".
109+ func formatVPNStatus (vpn entity.VPNInfo ) string {
110+ if ! vpn .VPNInterfaceEnabled {
111+ return statusNotWorking
60112 }
61- return "not working"
113+ var parts []string
114+ if vpn .InterfaceName != "" {
115+ parts = append (parts , vpn .InterfaceName )
116+ }
117+ if vpn .IPNet != "" {
118+ parts = append (parts , vpn .IPNet )
119+ }
120+ if len (parts ) == 0 {
121+ return statusWorking
122+ }
123+ return fmt .Sprintf ("%s (%s)" , statusWorking , strings .Join (parts , ", " ))
124+ }
125+
126+ // formatExitDetail renders a compact one-line summary of a selected exit peer
127+ // (SOCKS5 or VPN gateway): "<public IP>, ping <N>ms, direct/via relay". Each
128+ // part is included only when meaningful; the relay part appears only while
129+ // connected. Returns "" when nothing is known.
130+ func formatExitDetail (publicIP string , ping time.Duration , connected , throughRelay bool ) string {
131+ var parts []string
132+ if publicIP != "" {
133+ parts = append (parts , publicIP )
134+ }
135+ if ping > 0 {
136+ parts = append (parts , "ping " + ping .Round (time .Millisecond ).String ())
137+ }
138+ if connected {
139+ parts = append (parts , formatRelay (throughRelay ))
140+ }
141+ return strings .Join (parts , ", " )
142+ }
143+
144+ // formatRelay renders the connection path of an exit peer in human terms.
145+ func formatRelay (throughRelay bool ) string {
146+ if throughRelay {
147+ return "via relay"
148+ }
149+ return "direct"
150+ }
151+
152+ // formatUptime renders uptime with spaces between units. For 24h or more it
153+ // switches to day granularity and drops seconds (e.g. "1d 21h 13m"); below 24h
154+ // it keeps seconds and omits leading zero units (e.g. "1h 25m 5s", "5s").
155+ func formatUptime (d time.Duration ) string {
156+ d = d .Round (time .Second )
157+ days := d / (24 * time .Hour )
158+ d -= days * 24 * time .Hour
159+ hours := d / time .Hour
160+ d -= hours * time .Hour
161+ mins := d / time .Minute
162+ d -= mins * time .Minute
163+ secs := d / time .Second
164+
165+ if days > 0 {
166+ return fmt .Sprintf ("%dd %dh %dm" , int64 (days ), int64 (hours ), int64 (mins ))
167+ }
168+
169+ var parts []string
170+ if hours > 0 {
171+ parts = append (parts , fmt .Sprintf ("%dh" , int64 (hours )))
172+ }
173+ if mins > 0 || hours > 0 {
174+ parts = append (parts , fmt .Sprintf ("%dm" , int64 (mins )))
175+ }
176+ parts = append (parts , fmt .Sprintf ("%ds" , int64 (secs )))
177+ return strings .Join (parts , " " )
178+ }
179+
180+ func formatSOCKS5ExitNode (s5 entity.SOCKS5Info ) string {
181+ return formatExitClient (s5 .UsingPeerID != "" , s5 .Connected , s5 .UsingPeerName , s5 .UsingPeerID )
62182}
63183
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.
68184func formatVPNGatewayClient (gw entity.VPNGatewayInfo ) string {
69- if ! gw .ClientEnabled {
185+ return formatExitClient (gw .ClientEnabled , gw .Connected , gw .GatewayPeerName , gw .GatewayPeerID )
186+ }
187+
188+ func formatExitClient (enabled , connected bool , peerName , peerID string ) string {
189+ if ! enabled {
70190 return "off"
71191 }
72- name := gw . GatewayPeerName
192+ name := peerName
73193 if name == "" {
74- name = gw .GatewayPeerID
75- }
76- conn := "disconnected"
77- if gw .Connected {
78- conn = "connected"
194+ name = peerID
79195 }
80- return fmt .Sprintf ("via %s [%s]" , name , conn )
196+ return fmt .Sprintf ("%s [%s]" , name , formatConnected ( connected ) )
81197}
82198
83199func printPeerId (api * apiclient.Client , w io.Writer ) error {
0 commit comments