[client] Push-based status stream for the Wails UI

Adds a SubscribeStatus gRPC RPC that pushes a fresh FullStatus snapshot
on every peer-recorder state change, replacing the Wails UI's 2-second
Status poll. The daemon's notifier already triggers on Connected /
Disconnected / Connecting / management or signal flip / address
change / peers-list change; we now coalesce those into ticks on a
buffered chan and stream the resulting snapshots over gRPC.

- Status recorder gains SubscribeToStateChanges /
  UnsubscribeFromStateChanges + a non-blocking notifyStateChange that
  drops ticks when a subscriber's 1-slot buffer is full (next snapshot
  the consumer pulls already reflects everything).
- Server.Status handler split: the snapshot composition is shared
  with the new SubscribeStatus stream handler so unary and stream
  paths return identical bytes.
- UI peers service: pollLoop replaced by statusStreamLoop. The local
  name of the existing SubscribeEvents loop is now toastStreamLoop so
  the two streams are easy to tell apart — the underlying RPC name is
  unchanged.
- Tray applyStatus skips the icon refresh when connected/lastStatus
  hasn't changed; rapid SubscribeStatus bursts during health probes
  no longer churn Shell_NotifyIcon or the log.
This commit is contained in:
Zoltán Papp
2026-04-30 11:45:43 +02:00
parent 0148d926d5
commit 88a2bf582d
8 changed files with 359 additions and 113 deletions

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.6
// protoc v6.33.1
// protoc v7.34.1
// source: daemon.proto
package proto
@@ -6566,12 +6566,13 @@ const file_daemon_proto_rawDesc = "" +
"\n" +
"EXPOSE_UDP\x10\x03\x12\x0e\n" +
"\n" +
"EXPOSE_TLS\x10\x042\xfc\x15\n" +
"EXPOSE_TLS\x10\x042\xc2\x16\n" +
"\rDaemonService\x126\n" +
"\x05Login\x12\x14.daemon.LoginRequest\x1a\x15.daemon.LoginResponse\"\x00\x12K\n" +
"\fWaitSSOLogin\x12\x1b.daemon.WaitSSOLoginRequest\x1a\x1c.daemon.WaitSSOLoginResponse\"\x00\x12-\n" +
"\x02Up\x12\x11.daemon.UpRequest\x1a\x12.daemon.UpResponse\"\x00\x129\n" +
"\x06Status\x12\x15.daemon.StatusRequest\x1a\x16.daemon.StatusResponse\"\x00\x123\n" +
"\x06Status\x12\x15.daemon.StatusRequest\x1a\x16.daemon.StatusResponse\"\x00\x12D\n" +
"\x0fSubscribeStatus\x12\x15.daemon.StatusRequest\x1a\x16.daemon.StatusResponse\"\x000\x01\x123\n" +
"\x04Down\x12\x13.daemon.DownRequest\x1a\x14.daemon.DownResponse\"\x00\x12B\n" +
"\tGetConfig\x12\x18.daemon.GetConfigRequest\x1a\x19.daemon.GetConfigResponse\"\x00\x12K\n" +
"\fListNetworks\x12\x1b.daemon.ListNetworksRequest\x1a\x1c.daemon.ListNetworksResponse\"\x00\x12Q\n" +
@@ -6766,78 +6767,80 @@ var file_daemon_proto_depIdxs = []int32{
10, // 37: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
12, // 38: daemon.DaemonService.Up:input_type -> daemon.UpRequest
14, // 39: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
16, // 40: daemon.DaemonService.Down:input_type -> daemon.DownRequest
18, // 41: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest
29, // 42: daemon.DaemonService.ListNetworks:input_type -> daemon.ListNetworksRequest
31, // 43: daemon.DaemonService.SelectNetworks:input_type -> daemon.SelectNetworksRequest
31, // 44: daemon.DaemonService.DeselectNetworks:input_type -> daemon.SelectNetworksRequest
5, // 45: daemon.DaemonService.ForwardingRules:input_type -> daemon.EmptyRequest
38, // 46: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest
40, // 47: daemon.DaemonService.GetLogLevel:input_type -> daemon.GetLogLevelRequest
42, // 48: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest
45, // 49: daemon.DaemonService.ListStates:input_type -> daemon.ListStatesRequest
47, // 50: daemon.DaemonService.CleanState:input_type -> daemon.CleanStateRequest
49, // 51: daemon.DaemonService.DeleteState:input_type -> daemon.DeleteStateRequest
51, // 52: daemon.DaemonService.SetSyncResponsePersistence:input_type -> daemon.SetSyncResponsePersistenceRequest
54, // 53: daemon.DaemonService.TracePacket:input_type -> daemon.TracePacketRequest
57, // 54: daemon.DaemonService.SubscribeEvents:input_type -> daemon.SubscribeRequest
59, // 55: daemon.DaemonService.GetEvents:input_type -> daemon.GetEventsRequest
61, // 56: daemon.DaemonService.SwitchProfile:input_type -> daemon.SwitchProfileRequest
63, // 57: daemon.DaemonService.SetConfig:input_type -> daemon.SetConfigRequest
65, // 58: daemon.DaemonService.AddProfile:input_type -> daemon.AddProfileRequest
67, // 59: daemon.DaemonService.RemoveProfile:input_type -> daemon.RemoveProfileRequest
69, // 60: daemon.DaemonService.ListProfiles:input_type -> daemon.ListProfilesRequest
72, // 61: daemon.DaemonService.GetActiveProfile:input_type -> daemon.GetActiveProfileRequest
74, // 62: daemon.DaemonService.Logout:input_type -> daemon.LogoutRequest
76, // 63: daemon.DaemonService.GetFeatures:input_type -> daemon.GetFeaturesRequest
78, // 64: daemon.DaemonService.TriggerUpdate:input_type -> daemon.TriggerUpdateRequest
80, // 65: daemon.DaemonService.GetPeerSSHHostKey:input_type -> daemon.GetPeerSSHHostKeyRequest
82, // 66: daemon.DaemonService.RequestJWTAuth:input_type -> daemon.RequestJWTAuthRequest
84, // 67: daemon.DaemonService.WaitJWTToken:input_type -> daemon.WaitJWTTokenRequest
86, // 68: daemon.DaemonService.StartCPUProfile:input_type -> daemon.StartCPUProfileRequest
88, // 69: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest
6, // 70: daemon.DaemonService.NotifyOSLifecycle:input_type -> daemon.OSLifecycleRequest
90, // 71: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest
92, // 72: daemon.DaemonService.ExposeService:input_type -> daemon.ExposeServiceRequest
9, // 73: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
11, // 74: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
13, // 75: daemon.DaemonService.Up:output_type -> daemon.UpResponse
15, // 76: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
17, // 77: daemon.DaemonService.Down:output_type -> daemon.DownResponse
19, // 78: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
30, // 79: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse
32, // 80: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse
32, // 81: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse
37, // 82: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse
39, // 83: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse
41, // 84: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse
43, // 85: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse
46, // 86: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse
48, // 87: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse
50, // 88: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse
52, // 89: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse
56, // 90: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse
58, // 91: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent
60, // 92: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse
62, // 93: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse
64, // 94: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse
66, // 95: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse
68, // 96: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse
70, // 97: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse
73, // 98: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse
75, // 99: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse
77, // 100: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse
79, // 101: daemon.DaemonService.TriggerUpdate:output_type -> daemon.TriggerUpdateResponse
81, // 102: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse
83, // 103: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse
85, // 104: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse
87, // 105: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse
89, // 106: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse
7, // 107: daemon.DaemonService.NotifyOSLifecycle:output_type -> daemon.OSLifecycleResponse
91, // 108: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse
93, // 109: daemon.DaemonService.ExposeService:output_type -> daemon.ExposeServiceEvent
73, // [73:110] is the sub-list for method output_type
36, // [36:73] is the sub-list for method input_type
14, // 40: daemon.DaemonService.SubscribeStatus:input_type -> daemon.StatusRequest
16, // 41: daemon.DaemonService.Down:input_type -> daemon.DownRequest
18, // 42: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest
29, // 43: daemon.DaemonService.ListNetworks:input_type -> daemon.ListNetworksRequest
31, // 44: daemon.DaemonService.SelectNetworks:input_type -> daemon.SelectNetworksRequest
31, // 45: daemon.DaemonService.DeselectNetworks:input_type -> daemon.SelectNetworksRequest
5, // 46: daemon.DaemonService.ForwardingRules:input_type -> daemon.EmptyRequest
38, // 47: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest
40, // 48: daemon.DaemonService.GetLogLevel:input_type -> daemon.GetLogLevelRequest
42, // 49: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest
45, // 50: daemon.DaemonService.ListStates:input_type -> daemon.ListStatesRequest
47, // 51: daemon.DaemonService.CleanState:input_type -> daemon.CleanStateRequest
49, // 52: daemon.DaemonService.DeleteState:input_type -> daemon.DeleteStateRequest
51, // 53: daemon.DaemonService.SetSyncResponsePersistence:input_type -> daemon.SetSyncResponsePersistenceRequest
54, // 54: daemon.DaemonService.TracePacket:input_type -> daemon.TracePacketRequest
57, // 55: daemon.DaemonService.SubscribeEvents:input_type -> daemon.SubscribeRequest
59, // 56: daemon.DaemonService.GetEvents:input_type -> daemon.GetEventsRequest
61, // 57: daemon.DaemonService.SwitchProfile:input_type -> daemon.SwitchProfileRequest
63, // 58: daemon.DaemonService.SetConfig:input_type -> daemon.SetConfigRequest
65, // 59: daemon.DaemonService.AddProfile:input_type -> daemon.AddProfileRequest
67, // 60: daemon.DaemonService.RemoveProfile:input_type -> daemon.RemoveProfileRequest
69, // 61: daemon.DaemonService.ListProfiles:input_type -> daemon.ListProfilesRequest
72, // 62: daemon.DaemonService.GetActiveProfile:input_type -> daemon.GetActiveProfileRequest
74, // 63: daemon.DaemonService.Logout:input_type -> daemon.LogoutRequest
76, // 64: daemon.DaemonService.GetFeatures:input_type -> daemon.GetFeaturesRequest
78, // 65: daemon.DaemonService.TriggerUpdate:input_type -> daemon.TriggerUpdateRequest
80, // 66: daemon.DaemonService.GetPeerSSHHostKey:input_type -> daemon.GetPeerSSHHostKeyRequest
82, // 67: daemon.DaemonService.RequestJWTAuth:input_type -> daemon.RequestJWTAuthRequest
84, // 68: daemon.DaemonService.WaitJWTToken:input_type -> daemon.WaitJWTTokenRequest
86, // 69: daemon.DaemonService.StartCPUProfile:input_type -> daemon.StartCPUProfileRequest
88, // 70: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest
6, // 71: daemon.DaemonService.NotifyOSLifecycle:input_type -> daemon.OSLifecycleRequest
90, // 72: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest
92, // 73: daemon.DaemonService.ExposeService:input_type -> daemon.ExposeServiceRequest
9, // 74: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
11, // 75: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
13, // 76: daemon.DaemonService.Up:output_type -> daemon.UpResponse
15, // 77: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
15, // 78: daemon.DaemonService.SubscribeStatus:output_type -> daemon.StatusResponse
17, // 79: daemon.DaemonService.Down:output_type -> daemon.DownResponse
19, // 80: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
30, // 81: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse
32, // 82: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse
32, // 83: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse
37, // 84: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse
39, // 85: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse
41, // 86: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse
43, // 87: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse
46, // 88: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse
48, // 89: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse
50, // 90: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse
52, // 91: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse
56, // 92: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse
58, // 93: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent
60, // 94: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse
62, // 95: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse
64, // 96: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse
66, // 97: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse
68, // 98: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse
70, // 99: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse
73, // 100: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse
75, // 101: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse
77, // 102: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse
79, // 103: daemon.DaemonService.TriggerUpdate:output_type -> daemon.TriggerUpdateResponse
81, // 104: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse
83, // 105: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse
85, // 106: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse
87, // 107: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse
89, // 108: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse
7, // 109: daemon.DaemonService.NotifyOSLifecycle:output_type -> daemon.OSLifecycleResponse
91, // 110: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse
93, // 111: daemon.DaemonService.ExposeService:output_type -> daemon.ExposeServiceEvent
74, // [74:112] is the sub-list for method output_type
36, // [36:74] is the sub-list for method input_type
36, // [36:36] is the sub-list for extension type_name
36, // [36:36] is the sub-list for extension extendee
0, // [0:36] is the sub-list for field type_name