From ceb29a80f9617c377b5df0f244b255c6c438f7ce Mon Sep 17 00:00:00 2001 From: Gareth George Date: Thu, 10 Jul 2025 00:21:50 -0700 Subject: [PATCH] progress implementing remote instance views --- gen/go/v1/syncservice.pb.go | 335 ++++++++++++++++++---------- internal/api/syncapi/peerstate.go | 34 ++- internal/api/syncapi/syncclient.go | 13 +- internal/api/syncapi/synchandler.go | 16 +- proto/v1/syncservice.proto | 17 +- webui/gen/ts/v1/syncservice_pb.ts | 89 +++++--- webui/src/views/App.tsx | 128 ++++++++--- 7 files changed, 430 insertions(+), 202 deletions(-) diff --git a/gen/go/v1/syncservice.pb.go b/gen/go/v1/syncservice.pb.go index 7b320234..335fd972 100644 --- a/gen/go/v1/syncservice.pb.go +++ b/gen/go/v1/syncservice.pb.go @@ -141,7 +141,7 @@ func (x SyncStreamItem_RepoConnectionState) Number() protoreflect.EnumNumber { // Deprecated: Use SyncStreamItem_RepoConnectionState.Descriptor instead. func (SyncStreamItem_RepoConnectionState) EnumDescriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 0} + return file_v1_syncservice_proto_rawDescGZIP(), []int{5, 0} } type TunnelMessage struct { @@ -272,14 +272,110 @@ func (x *SyncStateStreamRequest) GetSubscribe() bool { return false } +type SyncRepoMetadata struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Guid string `protobuf:"bytes,2,opt,name=guid,proto3" json:"guid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SyncRepoMetadata) Reset() { + *x = SyncRepoMetadata{} + mi := &file_v1_syncservice_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncRepoMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncRepoMetadata) ProtoMessage() {} + +func (x *SyncRepoMetadata) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncRepoMetadata.ProtoReflect.Descriptor instead. +func (*SyncRepoMetadata) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{2} +} + +func (x *SyncRepoMetadata) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *SyncRepoMetadata) GetGuid() string { + if x != nil { + return x.Guid + } + return "" +} + +type SyncPlanMetadata struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SyncPlanMetadata) Reset() { + *x = SyncPlanMetadata{} + mi := &file_v1_syncservice_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncPlanMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncPlanMetadata) ProtoMessage() {} + +func (x *SyncPlanMetadata) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncPlanMetadata.ProtoReflect.Descriptor instead. +func (*SyncPlanMetadata) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{3} +} + +func (x *SyncPlanMetadata) GetId() string { + if x != nil { + return x.Id + } + return "" +} + type PeerState struct { state protoimpl.MessageState `protogen:"open.v1"` PeerInstanceId string `protobuf:"bytes,1,opt,name=peer_instance_id,json=peerInstanceId,proto3" json:"peer_instance_id,omitempty"` PeerKeyid string `protobuf:"bytes,2,opt,name=peer_keyid,json=peerKeyid,proto3" json:"peer_keyid,omitempty"` State SyncConnectionState `protobuf:"varint,3,opt,name=state,proto3,enum=v1.SyncConnectionState" json:"state,omitempty"` StatusMessage string `protobuf:"bytes,4,opt,name=status_message,json=statusMessage,proto3" json:"status_message,omitempty"` - KnownPlans []string `protobuf:"bytes,5,rep,name=known_plans,json=knownPlans,proto3" json:"known_plans,omitempty"` // List of plan IDs that the peer has. - KnownRepos []string `protobuf:"bytes,6,rep,name=known_repos,json=knownRepos,proto3" json:"known_repos,omitempty"` // List of repo IDs that the peer has. + KnownPlans []*SyncPlanMetadata `protobuf:"bytes,5,rep,name=known_plans,json=knownPlans,proto3" json:"known_plans,omitempty"` // List of plan IDs that the peer has. + KnownRepos []*SyncRepoMetadata `protobuf:"bytes,6,rep,name=known_repos,json=knownRepos,proto3" json:"known_repos,omitempty"` // List of repo IDs that the peer has. RemoteConfig *RemoteConfig `protobuf:"bytes,7,opt,name=remote_config,json=remoteConfig,proto3" json:"remote_config,omitempty"` // The remote config of the peer, if available. LastHeartbeatMillis int64 `protobuf:"varint,8,opt,name=last_heartbeat_millis,json=lastHeartbeatMillis,proto3" json:"last_heartbeat_millis,omitempty"` // The last time the peer sent a heartbeat, in milliseconds since epoch. unknownFields protoimpl.UnknownFields @@ -288,7 +384,7 @@ type PeerState struct { func (x *PeerState) Reset() { *x = PeerState{} - mi := &file_v1_syncservice_proto_msgTypes[2] + mi := &file_v1_syncservice_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -300,7 +396,7 @@ func (x *PeerState) String() string { func (*PeerState) ProtoMessage() {} func (x *PeerState) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[2] + mi := &file_v1_syncservice_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -313,7 +409,7 @@ func (x *PeerState) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerState.ProtoReflect.Descriptor instead. func (*PeerState) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{2} + return file_v1_syncservice_proto_rawDescGZIP(), []int{4} } func (x *PeerState) GetPeerInstanceId() string { @@ -344,14 +440,14 @@ func (x *PeerState) GetStatusMessage() string { return "" } -func (x *PeerState) GetKnownPlans() []string { +func (x *PeerState) GetKnownPlans() []*SyncPlanMetadata { if x != nil { return x.KnownPlans } return nil } -func (x *PeerState) GetKnownRepos() []string { +func (x *PeerState) GetKnownRepos() []*SyncRepoMetadata { if x != nil { return x.KnownRepos } @@ -392,7 +488,7 @@ type SyncStreamItem struct { func (x *SyncStreamItem) Reset() { *x = SyncStreamItem{} - mi := &file_v1_syncservice_proto_msgTypes[3] + mi := &file_v1_syncservice_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -404,7 +500,7 @@ func (x *SyncStreamItem) String() string { func (*SyncStreamItem) ProtoMessage() {} func (x *SyncStreamItem) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[3] + mi := &file_v1_syncservice_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -417,7 +513,7 @@ func (x *SyncStreamItem) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncStreamItem.ProtoReflect.Descriptor instead. func (*SyncStreamItem) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{3} + return file_v1_syncservice_proto_rawDescGZIP(), []int{5} } func (x *SyncStreamItem) GetAction() isSyncStreamItem_Action { @@ -579,7 +675,7 @@ type RemoteConfig struct { func (x *RemoteConfig) Reset() { *x = RemoteConfig{} - mi := &file_v1_syncservice_proto_msgTypes[4] + mi := &file_v1_syncservice_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -591,7 +687,7 @@ func (x *RemoteConfig) String() string { func (*RemoteConfig) ProtoMessage() {} func (x *RemoteConfig) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[4] + mi := &file_v1_syncservice_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -604,7 +700,7 @@ func (x *RemoteConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoteConfig.ProtoReflect.Descriptor instead. func (*RemoteConfig) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{4} + return file_v1_syncservice_proto_rawDescGZIP(), []int{6} } func (x *RemoteConfig) GetModno() int32 { @@ -646,7 +742,7 @@ type SyncStreamItem_SyncActionHandshake struct { func (x *SyncStreamItem_SyncActionHandshake) Reset() { *x = SyncStreamItem_SyncActionHandshake{} - mi := &file_v1_syncservice_proto_msgTypes[5] + mi := &file_v1_syncservice_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -658,7 +754,7 @@ func (x *SyncStreamItem_SyncActionHandshake) String() string { func (*SyncStreamItem_SyncActionHandshake) ProtoMessage() {} func (x *SyncStreamItem_SyncActionHandshake) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[5] + mi := &file_v1_syncservice_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -671,7 +767,7 @@ func (x *SyncStreamItem_SyncActionHandshake) ProtoReflect() protoreflect.Message // Deprecated: Use SyncStreamItem_SyncActionHandshake.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionHandshake) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 0} + return file_v1_syncservice_proto_rawDescGZIP(), []int{5, 0} } func (x *SyncStreamItem_SyncActionHandshake) GetProtocolVersion() int64 { @@ -704,7 +800,7 @@ type SyncStreamItem_SyncActionHeartbeat struct { func (x *SyncStreamItem_SyncActionHeartbeat) Reset() { *x = SyncStreamItem_SyncActionHeartbeat{} - mi := &file_v1_syncservice_proto_msgTypes[6] + mi := &file_v1_syncservice_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -716,7 +812,7 @@ func (x *SyncStreamItem_SyncActionHeartbeat) String() string { func (*SyncStreamItem_SyncActionHeartbeat) ProtoMessage() {} func (x *SyncStreamItem_SyncActionHeartbeat) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[6] + mi := &file_v1_syncservice_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -729,7 +825,7 @@ func (x *SyncStreamItem_SyncActionHeartbeat) ProtoReflect() protoreflect.Message // Deprecated: Use SyncStreamItem_SyncActionHeartbeat.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionHeartbeat) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 1} + return file_v1_syncservice_proto_rawDescGZIP(), []int{5, 1} } type SyncStreamItem_SyncActionSendConfig struct { @@ -741,7 +837,7 @@ type SyncStreamItem_SyncActionSendConfig struct { func (x *SyncStreamItem_SyncActionSendConfig) Reset() { *x = SyncStreamItem_SyncActionSendConfig{} - mi := &file_v1_syncservice_proto_msgTypes[7] + mi := &file_v1_syncservice_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -753,7 +849,7 @@ func (x *SyncStreamItem_SyncActionSendConfig) String() string { func (*SyncStreamItem_SyncActionSendConfig) ProtoMessage() {} func (x *SyncStreamItem_SyncActionSendConfig) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[7] + mi := &file_v1_syncservice_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -766,7 +862,7 @@ func (x *SyncStreamItem_SyncActionSendConfig) ProtoReflect() protoreflect.Messag // Deprecated: Use SyncStreamItem_SyncActionSendConfig.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionSendConfig) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 2} + return file_v1_syncservice_proto_rawDescGZIP(), []int{5, 2} } func (x *SyncStreamItem_SyncActionSendConfig) GetConfig() *RemoteConfig { @@ -788,7 +884,7 @@ type SyncStreamItem_SyncActionSetConfig struct { func (x *SyncStreamItem_SyncActionSetConfig) Reset() { *x = SyncStreamItem_SyncActionSetConfig{} - mi := &file_v1_syncservice_proto_msgTypes[8] + mi := &file_v1_syncservice_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -800,7 +896,7 @@ func (x *SyncStreamItem_SyncActionSetConfig) String() string { func (*SyncStreamItem_SyncActionSetConfig) ProtoMessage() {} func (x *SyncStreamItem_SyncActionSetConfig) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[8] + mi := &file_v1_syncservice_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -813,7 +909,7 @@ func (x *SyncStreamItem_SyncActionSetConfig) ProtoReflect() protoreflect.Message // Deprecated: Use SyncStreamItem_SyncActionSetConfig.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionSetConfig) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 3} + return file_v1_syncservice_proto_rawDescGZIP(), []int{5, 3} } func (x *SyncStreamItem_SyncActionSetConfig) GetRepos() []*Repo { @@ -846,15 +942,15 @@ func (x *SyncStreamItem_SyncActionSetConfig) GetPlansToDelete() []string { type SyncStreamItem_SyncActionListResources struct { state protoimpl.MessageState `protogen:"open.v1"` - RepoIds []string `protobuf:"bytes,1,rep,name=repo_ids,json=repoIds,proto3" json:"repo_ids,omitempty"` // List of repo IDs that can be accessed. - PlanIds []string `protobuf:"bytes,2,rep,name=plan_ids,json=planIds,proto3" json:"plan_ids,omitempty"` // List of plan IDs that can be accessed. + Repos []*SyncRepoMetadata `protobuf:"bytes,1,rep,name=repos,proto3" json:"repos,omitempty"` + Plans []*SyncPlanMetadata `protobuf:"bytes,2,rep,name=plans,proto3" json:"plans,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *SyncStreamItem_SyncActionListResources) Reset() { *x = SyncStreamItem_SyncActionListResources{} - mi := &file_v1_syncservice_proto_msgTypes[9] + mi := &file_v1_syncservice_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -866,7 +962,7 @@ func (x *SyncStreamItem_SyncActionListResources) String() string { func (*SyncStreamItem_SyncActionListResources) ProtoMessage() {} func (x *SyncStreamItem_SyncActionListResources) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[9] + mi := &file_v1_syncservice_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -879,19 +975,19 @@ func (x *SyncStreamItem_SyncActionListResources) ProtoReflect() protoreflect.Mes // Deprecated: Use SyncStreamItem_SyncActionListResources.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionListResources) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 4} + return file_v1_syncservice_proto_rawDescGZIP(), []int{5, 4} } -func (x *SyncStreamItem_SyncActionListResources) GetRepoIds() []string { +func (x *SyncStreamItem_SyncActionListResources) GetRepos() []*SyncRepoMetadata { if x != nil { - return x.RepoIds + return x.Repos } return nil } -func (x *SyncStreamItem_SyncActionListResources) GetPlanIds() []string { +func (x *SyncStreamItem_SyncActionListResources) GetPlans() []*SyncPlanMetadata { if x != nil { - return x.PlanIds + return x.Plans } return nil } @@ -905,7 +1001,7 @@ type SyncStreamItem_SyncActionConnectRepo struct { func (x *SyncStreamItem_SyncActionConnectRepo) Reset() { *x = SyncStreamItem_SyncActionConnectRepo{} - mi := &file_v1_syncservice_proto_msgTypes[10] + mi := &file_v1_syncservice_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -917,7 +1013,7 @@ func (x *SyncStreamItem_SyncActionConnectRepo) String() string { func (*SyncStreamItem_SyncActionConnectRepo) ProtoMessage() {} func (x *SyncStreamItem_SyncActionConnectRepo) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[10] + mi := &file_v1_syncservice_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -930,7 +1026,7 @@ func (x *SyncStreamItem_SyncActionConnectRepo) ProtoReflect() protoreflect.Messa // Deprecated: Use SyncStreamItem_SyncActionConnectRepo.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionConnectRepo) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 5} + return file_v1_syncservice_proto_rawDescGZIP(), []int{5, 5} } func (x *SyncStreamItem_SyncActionConnectRepo) GetRepoId() string { @@ -955,7 +1051,7 @@ type SyncStreamItem_SyncActionDiffOperations struct { func (x *SyncStreamItem_SyncActionDiffOperations) Reset() { *x = SyncStreamItem_SyncActionDiffOperations{} - mi := &file_v1_syncservice_proto_msgTypes[11] + mi := &file_v1_syncservice_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -967,7 +1063,7 @@ func (x *SyncStreamItem_SyncActionDiffOperations) String() string { func (*SyncStreamItem_SyncActionDiffOperations) ProtoMessage() {} func (x *SyncStreamItem_SyncActionDiffOperations) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[11] + mi := &file_v1_syncservice_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -980,7 +1076,7 @@ func (x *SyncStreamItem_SyncActionDiffOperations) ProtoReflect() protoreflect.Me // Deprecated: Use SyncStreamItem_SyncActionDiffOperations.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionDiffOperations) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 6} + return file_v1_syncservice_proto_rawDescGZIP(), []int{5, 6} } func (x *SyncStreamItem_SyncActionDiffOperations) GetHaveOperationsSelector() *OpSelector { @@ -1020,7 +1116,7 @@ type SyncStreamItem_SyncActionSendOperations struct { func (x *SyncStreamItem_SyncActionSendOperations) Reset() { *x = SyncStreamItem_SyncActionSendOperations{} - mi := &file_v1_syncservice_proto_msgTypes[12] + mi := &file_v1_syncservice_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1032,7 +1128,7 @@ func (x *SyncStreamItem_SyncActionSendOperations) String() string { func (*SyncStreamItem_SyncActionSendOperations) ProtoMessage() {} func (x *SyncStreamItem_SyncActionSendOperations) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[12] + mi := &file_v1_syncservice_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1045,7 +1141,7 @@ func (x *SyncStreamItem_SyncActionSendOperations) ProtoReflect() protoreflect.Me // Deprecated: Use SyncStreamItem_SyncActionSendOperations.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionSendOperations) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 7} + return file_v1_syncservice_proto_rawDescGZIP(), []int{5, 7} } func (x *SyncStreamItem_SyncActionSendOperations) GetEvent() *OperationEvent { @@ -1064,7 +1160,7 @@ type SyncStreamItem_SyncActionThrottle struct { func (x *SyncStreamItem_SyncActionThrottle) Reset() { *x = SyncStreamItem_SyncActionThrottle{} - mi := &file_v1_syncservice_proto_msgTypes[13] + mi := &file_v1_syncservice_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1076,7 +1172,7 @@ func (x *SyncStreamItem_SyncActionThrottle) String() string { func (*SyncStreamItem_SyncActionThrottle) ProtoMessage() {} func (x *SyncStreamItem_SyncActionThrottle) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[13] + mi := &file_v1_syncservice_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1089,7 +1185,7 @@ func (x *SyncStreamItem_SyncActionThrottle) ProtoReflect() protoreflect.Message // Deprecated: Use SyncStreamItem_SyncActionThrottle.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncActionThrottle) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 8} + return file_v1_syncservice_proto_rawDescGZIP(), []int{5, 8} } func (x *SyncStreamItem_SyncActionThrottle) GetDelayMs() int64 { @@ -1110,7 +1206,7 @@ type SyncStreamItem_SyncEstablishSharedSecret struct { func (x *SyncStreamItem_SyncEstablishSharedSecret) Reset() { *x = SyncStreamItem_SyncEstablishSharedSecret{} - mi := &file_v1_syncservice_proto_msgTypes[14] + mi := &file_v1_syncservice_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1122,7 +1218,7 @@ func (x *SyncStreamItem_SyncEstablishSharedSecret) String() string { func (*SyncStreamItem_SyncEstablishSharedSecret) ProtoMessage() {} func (x *SyncStreamItem_SyncEstablishSharedSecret) ProtoReflect() protoreflect.Message { - mi := &file_v1_syncservice_proto_msgTypes[14] + mi := &file_v1_syncservice_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1135,7 +1231,7 @@ func (x *SyncStreamItem_SyncEstablishSharedSecret) ProtoReflect() protoreflect.M // Deprecated: Use SyncStreamItem_SyncEstablishSharedSecret.ProtoReflect.Descriptor instead. func (*SyncStreamItem_SyncEstablishSharedSecret) Descriptor() ([]byte, []int) { - return file_v1_syncservice_proto_rawDescGZIP(), []int{3, 9} + return file_v1_syncservice_proto_rawDescGZIP(), []int{5, 9} } func (x *SyncStreamItem_SyncEstablishSharedSecret) GetEd25519() string { @@ -1158,19 +1254,24 @@ const file_v1_syncservice_proto_rawDesc = "" + "\x12pubkey_ecdh_x25519\x18d \x01(\fR\x10pubkeyEcdhX25519\x12'\n" + "\x03any\x18\xe8\a \x01(\v2\x14.google.protobuf.AnyR\x03any\"6\n" + "\x16SyncStateStreamRequest\x12\x1c\n" + - "\tsubscribe\x18\x01 \x01(\bR\tsubscribe\"\xd7\x02\n" + + "\tsubscribe\x18\x01 \x01(\bR\tsubscribe\"6\n" + + "\x10SyncRepoMetadata\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04guid\x18\x02 \x01(\tR\x04guid\"\"\n" + + "\x10SyncPlanMetadata\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"\x83\x03\n" + "\tPeerState\x12(\n" + "\x10peer_instance_id\x18\x01 \x01(\tR\x0epeerInstanceId\x12\x1d\n" + "\n" + "peer_keyid\x18\x02 \x01(\tR\tpeerKeyid\x12-\n" + "\x05state\x18\x03 \x01(\x0e2\x17.v1.SyncConnectionStateR\x05state\x12%\n" + - "\x0estatus_message\x18\x04 \x01(\tR\rstatusMessage\x12\x1f\n" + - "\vknown_plans\x18\x05 \x03(\tR\n" + - "knownPlans\x12\x1f\n" + - "\vknown_repos\x18\x06 \x03(\tR\n" + + "\x0estatus_message\x18\x04 \x01(\tR\rstatusMessage\x125\n" + + "\vknown_plans\x18\x05 \x03(\v2\x14.v1.SyncPlanMetadataR\n" + + "knownPlans\x125\n" + + "\vknown_repos\x18\x06 \x03(\v2\x14.v1.SyncRepoMetadataR\n" + "knownRepos\x125\n" + "\rremote_config\x18\a \x01(\v2\x10.v1.RemoteConfigR\fremoteConfig\x122\n" + - "\x15last_heartbeat_millis\x18\b \x01(\x03R\x13lastHeartbeatMillis\"\xcf\x0e\n" + + "\x15last_heartbeat_millis\x18\b \x01(\x03R\x13lastHeartbeatMillis\"\xf1\x0e\n" + "\x0eSyncStreamItem\x12:\n" + "\x0esigned_message\x18\x01 \x01(\v2\x11.v1.SignedMessageH\x00R\rsignedMessage\x12F\n" + "\thandshake\x18\x03 \x01(\v2&.v1.SyncStreamItem.SyncActionHandshakeH\x00R\thandshake\x12F\n" + @@ -1196,10 +1297,10 @@ const file_v1_syncservice_proto_rawDesc = "" + "\x05repos\x18\x01 \x03(\v2\b.v1.RepoR\x05repos\x12\x1e\n" + "\x05plans\x18\x02 \x03(\v2\b.v1.PlanR\x05plans\x12&\n" + "\x0frepos_to_delete\x18\x03 \x03(\tR\rreposToDelete\x12&\n" + - "\x0fplans_to_delete\x18\x04 \x03(\tR\rplansToDelete\x1aO\n" + - "\x17SyncActionListResources\x12\x19\n" + - "\brepo_ids\x18\x01 \x03(\tR\arepoIds\x12\x19\n" + - "\bplan_ids\x18\x02 \x03(\tR\aplanIds\x1a0\n" + + "\x0fplans_to_delete\x18\x04 \x03(\tR\rplansToDelete\x1aq\n" + + "\x17SyncActionListResources\x12*\n" + + "\x05repos\x18\x01 \x03(\v2\x14.v1.SyncRepoMetadataR\x05repos\x12*\n" + + "\x05plans\x18\x02 \x03(\v2\x14.v1.SyncPlanMetadataR\x05plans\x1a0\n" + "\x15SyncActionConnectRepo\x12\x17\n" + "\arepo_id\x18\x01 \x01(\tR\x06repoId\x1a\xf5\x01\n" + "\x18SyncActionDiffOperations\x12H\n" + @@ -1256,66 +1357,72 @@ func file_v1_syncservice_proto_rawDescGZIP() []byte { } var file_v1_syncservice_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_v1_syncservice_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_v1_syncservice_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_v1_syncservice_proto_goTypes = []any{ (SyncConnectionState)(0), // 0: v1.SyncConnectionState (SyncStreamItem_RepoConnectionState)(0), // 1: v1.SyncStreamItem.RepoConnectionState (*TunnelMessage)(nil), // 2: v1.TunnelMessage (*SyncStateStreamRequest)(nil), // 3: v1.SyncStateStreamRequest - (*PeerState)(nil), // 4: v1.PeerState - (*SyncStreamItem)(nil), // 5: v1.SyncStreamItem - (*RemoteConfig)(nil), // 6: v1.RemoteConfig - (*SyncStreamItem_SyncActionHandshake)(nil), // 7: v1.SyncStreamItem.SyncActionHandshake - (*SyncStreamItem_SyncActionHeartbeat)(nil), // 8: v1.SyncStreamItem.SyncActionHeartbeat - (*SyncStreamItem_SyncActionSendConfig)(nil), // 9: v1.SyncStreamItem.SyncActionSendConfig - (*SyncStreamItem_SyncActionSetConfig)(nil), // 10: v1.SyncStreamItem.SyncActionSetConfig - (*SyncStreamItem_SyncActionListResources)(nil), // 11: v1.SyncStreamItem.SyncActionListResources - (*SyncStreamItem_SyncActionConnectRepo)(nil), // 12: v1.SyncStreamItem.SyncActionConnectRepo - (*SyncStreamItem_SyncActionDiffOperations)(nil), // 13: v1.SyncStreamItem.SyncActionDiffOperations - (*SyncStreamItem_SyncActionSendOperations)(nil), // 14: v1.SyncStreamItem.SyncActionSendOperations - (*SyncStreamItem_SyncActionThrottle)(nil), // 15: v1.SyncStreamItem.SyncActionThrottle - (*SyncStreamItem_SyncEstablishSharedSecret)(nil), // 16: v1.SyncStreamItem.SyncEstablishSharedSecret - (*anypb.Any)(nil), // 17: google.protobuf.Any - (*SignedMessage)(nil), // 18: v1.SignedMessage - (*Repo)(nil), // 19: v1.Repo - (*Plan)(nil), // 20: v1.Plan - (*PublicKey)(nil), // 21: v1.PublicKey - (*OpSelector)(nil), // 22: v1.OpSelector - (*OperationEvent)(nil), // 23: v1.OperationEvent + (*SyncRepoMetadata)(nil), // 4: v1.SyncRepoMetadata + (*SyncPlanMetadata)(nil), // 5: v1.SyncPlanMetadata + (*PeerState)(nil), // 6: v1.PeerState + (*SyncStreamItem)(nil), // 7: v1.SyncStreamItem + (*RemoteConfig)(nil), // 8: v1.RemoteConfig + (*SyncStreamItem_SyncActionHandshake)(nil), // 9: v1.SyncStreamItem.SyncActionHandshake + (*SyncStreamItem_SyncActionHeartbeat)(nil), // 10: v1.SyncStreamItem.SyncActionHeartbeat + (*SyncStreamItem_SyncActionSendConfig)(nil), // 11: v1.SyncStreamItem.SyncActionSendConfig + (*SyncStreamItem_SyncActionSetConfig)(nil), // 12: v1.SyncStreamItem.SyncActionSetConfig + (*SyncStreamItem_SyncActionListResources)(nil), // 13: v1.SyncStreamItem.SyncActionListResources + (*SyncStreamItem_SyncActionConnectRepo)(nil), // 14: v1.SyncStreamItem.SyncActionConnectRepo + (*SyncStreamItem_SyncActionDiffOperations)(nil), // 15: v1.SyncStreamItem.SyncActionDiffOperations + (*SyncStreamItem_SyncActionSendOperations)(nil), // 16: v1.SyncStreamItem.SyncActionSendOperations + (*SyncStreamItem_SyncActionThrottle)(nil), // 17: v1.SyncStreamItem.SyncActionThrottle + (*SyncStreamItem_SyncEstablishSharedSecret)(nil), // 18: v1.SyncStreamItem.SyncEstablishSharedSecret + (*anypb.Any)(nil), // 19: google.protobuf.Any + (*SignedMessage)(nil), // 20: v1.SignedMessage + (*Repo)(nil), // 21: v1.Repo + (*Plan)(nil), // 22: v1.Plan + (*PublicKey)(nil), // 23: v1.PublicKey + (*OpSelector)(nil), // 24: v1.OpSelector + (*OperationEvent)(nil), // 25: v1.OperationEvent } var file_v1_syncservice_proto_depIdxs = []int32{ - 17, // 0: v1.TunnelMessage.any:type_name -> google.protobuf.Any + 19, // 0: v1.TunnelMessage.any:type_name -> google.protobuf.Any 0, // 1: v1.PeerState.state:type_name -> v1.SyncConnectionState - 6, // 2: v1.PeerState.remote_config:type_name -> v1.RemoteConfig - 18, // 3: v1.SyncStreamItem.signed_message:type_name -> v1.SignedMessage - 7, // 4: v1.SyncStreamItem.handshake:type_name -> v1.SyncStreamItem.SyncActionHandshake - 8, // 5: v1.SyncStreamItem.heartbeat:type_name -> v1.SyncStreamItem.SyncActionHeartbeat - 13, // 6: v1.SyncStreamItem.diff_operations:type_name -> v1.SyncStreamItem.SyncActionDiffOperations - 14, // 7: v1.SyncStreamItem.send_operations:type_name -> v1.SyncStreamItem.SyncActionSendOperations - 9, // 8: v1.SyncStreamItem.send_config:type_name -> v1.SyncStreamItem.SyncActionSendConfig - 10, // 9: v1.SyncStreamItem.set_config:type_name -> v1.SyncStreamItem.SyncActionSetConfig - 11, // 10: v1.SyncStreamItem.list_resources:type_name -> v1.SyncStreamItem.SyncActionListResources - 15, // 11: v1.SyncStreamItem.throttle:type_name -> v1.SyncStreamItem.SyncActionThrottle - 19, // 12: v1.RemoteConfig.repos:type_name -> v1.Repo - 20, // 13: v1.RemoteConfig.plans:type_name -> v1.Plan - 21, // 14: v1.SyncStreamItem.SyncActionHandshake.public_key:type_name -> v1.PublicKey - 18, // 15: v1.SyncStreamItem.SyncActionHandshake.instance_id:type_name -> v1.SignedMessage - 6, // 16: v1.SyncStreamItem.SyncActionSendConfig.config:type_name -> v1.RemoteConfig - 19, // 17: v1.SyncStreamItem.SyncActionSetConfig.repos:type_name -> v1.Repo - 20, // 18: v1.SyncStreamItem.SyncActionSetConfig.plans:type_name -> v1.Plan - 22, // 19: v1.SyncStreamItem.SyncActionDiffOperations.have_operations_selector:type_name -> v1.OpSelector - 23, // 20: v1.SyncStreamItem.SyncActionSendOperations.event:type_name -> v1.OperationEvent - 2, // 21: v1.TunnelService.Tunnel:input_type -> v1.TunnelMessage - 5, // 22: v1.BackrestSyncService.Sync:input_type -> v1.SyncStreamItem - 3, // 23: v1.BackrestSyncStateService.GetPeerSyncStatesStream:input_type -> v1.SyncStateStreamRequest - 2, // 24: v1.TunnelService.Tunnel:output_type -> v1.TunnelMessage - 5, // 25: v1.BackrestSyncService.Sync:output_type -> v1.SyncStreamItem - 4, // 26: v1.BackrestSyncStateService.GetPeerSyncStatesStream:output_type -> v1.PeerState - 24, // [24:27] is the sub-list for method output_type - 21, // [21:24] is the sub-list for method input_type - 21, // [21:21] is the sub-list for extension type_name - 21, // [21:21] is the sub-list for extension extendee - 0, // [0:21] is the sub-list for field type_name + 5, // 2: v1.PeerState.known_plans:type_name -> v1.SyncPlanMetadata + 4, // 3: v1.PeerState.known_repos:type_name -> v1.SyncRepoMetadata + 8, // 4: v1.PeerState.remote_config:type_name -> v1.RemoteConfig + 20, // 5: v1.SyncStreamItem.signed_message:type_name -> v1.SignedMessage + 9, // 6: v1.SyncStreamItem.handshake:type_name -> v1.SyncStreamItem.SyncActionHandshake + 10, // 7: v1.SyncStreamItem.heartbeat:type_name -> v1.SyncStreamItem.SyncActionHeartbeat + 15, // 8: v1.SyncStreamItem.diff_operations:type_name -> v1.SyncStreamItem.SyncActionDiffOperations + 16, // 9: v1.SyncStreamItem.send_operations:type_name -> v1.SyncStreamItem.SyncActionSendOperations + 11, // 10: v1.SyncStreamItem.send_config:type_name -> v1.SyncStreamItem.SyncActionSendConfig + 12, // 11: v1.SyncStreamItem.set_config:type_name -> v1.SyncStreamItem.SyncActionSetConfig + 13, // 12: v1.SyncStreamItem.list_resources:type_name -> v1.SyncStreamItem.SyncActionListResources + 17, // 13: v1.SyncStreamItem.throttle:type_name -> v1.SyncStreamItem.SyncActionThrottle + 21, // 14: v1.RemoteConfig.repos:type_name -> v1.Repo + 22, // 15: v1.RemoteConfig.plans:type_name -> v1.Plan + 23, // 16: v1.SyncStreamItem.SyncActionHandshake.public_key:type_name -> v1.PublicKey + 20, // 17: v1.SyncStreamItem.SyncActionHandshake.instance_id:type_name -> v1.SignedMessage + 8, // 18: v1.SyncStreamItem.SyncActionSendConfig.config:type_name -> v1.RemoteConfig + 21, // 19: v1.SyncStreamItem.SyncActionSetConfig.repos:type_name -> v1.Repo + 22, // 20: v1.SyncStreamItem.SyncActionSetConfig.plans:type_name -> v1.Plan + 4, // 21: v1.SyncStreamItem.SyncActionListResources.repos:type_name -> v1.SyncRepoMetadata + 5, // 22: v1.SyncStreamItem.SyncActionListResources.plans:type_name -> v1.SyncPlanMetadata + 24, // 23: v1.SyncStreamItem.SyncActionDiffOperations.have_operations_selector:type_name -> v1.OpSelector + 25, // 24: v1.SyncStreamItem.SyncActionSendOperations.event:type_name -> v1.OperationEvent + 2, // 25: v1.TunnelService.Tunnel:input_type -> v1.TunnelMessage + 7, // 26: v1.BackrestSyncService.Sync:input_type -> v1.SyncStreamItem + 3, // 27: v1.BackrestSyncStateService.GetPeerSyncStatesStream:input_type -> v1.SyncStateStreamRequest + 2, // 28: v1.TunnelService.Tunnel:output_type -> v1.TunnelMessage + 7, // 29: v1.BackrestSyncService.Sync:output_type -> v1.SyncStreamItem + 6, // 30: v1.BackrestSyncStateService.GetPeerSyncStatesStream:output_type -> v1.PeerState + 28, // [28:31] is the sub-list for method output_type + 25, // [25:28] is the sub-list for method input_type + 25, // [25:25] is the sub-list for extension type_name + 25, // [25:25] is the sub-list for extension extendee + 0, // [0:25] is the sub-list for field type_name } func init() { file_v1_syncservice_proto_init() } @@ -1328,7 +1435,7 @@ func file_v1_syncservice_proto_init() { file_v1_restic_proto_init() file_v1_service_proto_init() file_v1_operations_proto_init() - file_v1_syncservice_proto_msgTypes[3].OneofWrappers = []any{ + file_v1_syncservice_proto_msgTypes[5].OneofWrappers = []any{ (*SyncStreamItem_SignedMessage)(nil), (*SyncStreamItem_Handshake)(nil), (*SyncStreamItem_Heartbeat)(nil), @@ -1345,7 +1452,7 @@ func file_v1_syncservice_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_v1_syncservice_proto_rawDesc), len(file_v1_syncservice_proto_rawDesc)), NumEnums: 2, - NumMessages: 15, + NumMessages: 17, NumExtensions: 0, NumServices: 3, }, diff --git a/internal/api/syncapi/peerstate.go b/internal/api/syncapi/peerstate.go index c5d9744b..8260afa3 100644 --- a/internal/api/syncapi/peerstate.go +++ b/internal/api/syncapi/peerstate.go @@ -25,8 +25,8 @@ type PeerState struct { ConnectionStateMessage string // Plans and repos available on this peer - KnownRepos map[string]struct{} - KnownPlans map[string]struct{} + KnownRepos map[string]*v1.SyncRepoMetadata + KnownPlans map[string]*v1.SyncPlanMetadata // Partial configuration available for this peer Config *v1.RemoteConfig @@ -39,8 +39,8 @@ func newPeerState(instanceID, keyID string) *PeerState { LastHeartbeat: time.Now(), ConnectionState: v1.SyncConnectionState_CONNECTION_STATE_DISCONNECTED, ConnectionStateMessage: "disconnected", - KnownRepos: make(map[string]struct{}), - KnownPlans: make(map[string]struct{}), + KnownRepos: make(map[string]*v1.SyncRepoMetadata), + KnownPlans: make(map[string]*v1.SyncPlanMetadata), Config: nil, // Will be set when the config is received } } @@ -68,8 +68,8 @@ func peerStateToProto(state *PeerState) *v1.PeerState { LastHeartbeatMillis: state.LastHeartbeat.UnixMilli(), State: state.ConnectionState, StatusMessage: state.ConnectionStateMessage, - KnownRepos: slices.Collect(maps.Keys(state.KnownRepos)), - KnownPlans: slices.Collect(maps.Keys(state.KnownPlans)), + KnownRepos: slices.Collect(maps.Values(state.KnownRepos)), + KnownPlans: slices.Collect(maps.Values(state.KnownPlans)), RemoteConfig: state.Config, } } @@ -78,13 +78,13 @@ func peerStateFromProto(state *v1.PeerState) *PeerState { if state.PeerInstanceId == "" || state.PeerKeyid == "" { return nil } - knownRepos := make(map[string]struct{}, len(state.KnownRepos)) + knownRepos := make(map[string]*v1.SyncRepoMetadata, len(state.KnownRepos)) for _, repo := range state.KnownRepos { - knownRepos[repo] = struct{}{} + knownRepos[repo.Id] = repo } - knownPlans := make(map[string]struct{}, len(state.KnownPlans)) + knownPlans := make(map[string]*v1.SyncPlanMetadata, len(state.KnownPlans)) for _, plan := range state.KnownPlans { - knownPlans[plan] = struct{}{} + knownPlans[plan.Id] = plan } return &PeerState{ @@ -269,20 +269,18 @@ func (m *SqlitePeerStateManager) UpdatePeerState(keyID string, instanceID string defer m.mu.Unlock() var state *PeerState - stateBytes, err := m.kvstore.Get(keyID) - if err != nil { - zap.S().Warnf("error getting peer state for key %s: %v", keyID, err) - state = newPeerState(instanceID, keyID) - } else if stateBytes == nil { - state = newPeerState(instanceID, keyID) - } else { + if stateBytes, err := m.kvstore.Get(keyID); err == nil { var stateProto v1.PeerState if err := proto.Unmarshal(stateBytes, &stateProto); err != nil { zap.S().Warnf("error unmarshalling peer state for key %s: %v", keyID, err) - state = newPeerState(instanceID, keyID) } else { state = peerStateFromProto(&stateProto) } + } else { + zap.S().Warnf("error getting peer state for key %s: %v", keyID, err) + } + if state == nil { + state = newPeerState(instanceID, keyID) } updateFn(state) diff --git a/internal/api/syncapi/syncclient.go b/internal/api/syncapi/syncclient.go index fc6a73fd..da511f7c 100644 --- a/internal/api/syncapi/syncclient.go +++ b/internal/api/syncapi/syncclient.go @@ -231,13 +231,22 @@ func (c *syncSessionHandlerClient) OnConnectionEstablished(ctx context.Context, for _, repo := range localConfig.Repos { if c.permissions.CheckPermissionForRepo(repo.Guid, v1.Multihost_Permission_PERMISSION_READ_CONFIG) { remoteConfig.Repos = append(remoteConfig.Repos, repo) - resourceList.RepoIds = append(resourceList.RepoIds, repo.Id) + } + if c.permissions.CheckPermissionForRepo(repo.Guid, v1.Multihost_Permission_PERMISSION_READ_OPERATIONS, v1.Multihost_Permission_PERMISSION_READ_CONFIG) { + resourceList.Repos = append(resourceList.Repos, &v1.SyncRepoMetadata{ + Id: repo.Id, + Guid: repo.Guid, + }) } } for _, plan := range localConfig.Plans { if c.permissions.CheckPermissionForPlan(plan.Id, v1.Multihost_Permission_PERMISSION_READ_CONFIG) { remoteConfig.Plans = append(remoteConfig.Plans, plan) - resourceList.PlanIds = append(resourceList.PlanIds, plan.Id) + } + if c.permissions.CheckPermissionForPlan(plan.Id, v1.Multihost_Permission_PERMISSION_READ_OPERATIONS, v1.Multihost_Permission_PERMISSION_READ_CONFIG) { + resourceList.Plans = append(resourceList.Plans, &v1.SyncPlanMetadata{ + Id: plan.Id, + }) } } diff --git a/internal/api/syncapi/synchandler.go b/internal/api/syncapi/synchandler.go index c159d734..045dfda9 100644 --- a/internal/api/syncapi/synchandler.go +++ b/internal/api/syncapi/synchandler.go @@ -353,20 +353,20 @@ func (h *syncSessionHandlerServer) HandleSendConfig(ctx context.Context, stream func (h *syncSessionHandlerServer) HandleListResources(ctx context.Context, stream *bidiSyncCommandStream, item *v1.SyncStreamItem_SyncActionListResources) error { zap.L().Debug("syncserver received resource list from client", zap.String("client_instance_id", h.peer.InstanceId), - zap.Any("repos", item.GetRepoIds()), - zap.Any("plans", item.GetPlanIds())) + zap.Any("repos", item.GetRepos()), + zap.Any("plans", item.GetPlans())) h.mgr.peerStateManager.UpdatePeerState(h.peer.Keyid, h.peer.InstanceId, func(peerState *PeerState) { if peerState == nil { return // this should not happen } - repos := item.GetRepoIds() - plans := item.GetPlanIds() - for _, repoID := range repos { - peerState.KnownRepos[repoID] = struct{}{} + repos := item.GetRepos() + plans := item.GetPlans() + for _, repo := range repos { + peerState.KnownRepos[repo.Id] = repo } - for _, planID := range plans { - peerState.KnownPlans[planID] = struct{}{} + for _, plan := range plans { + peerState.KnownPlans[plan.Id] = plan } }) return nil diff --git a/proto/v1/syncservice.proto b/proto/v1/syncservice.proto index ed1dbdff..41836134 100644 --- a/proto/v1/syncservice.proto +++ b/proto/v1/syncservice.proto @@ -48,14 +48,23 @@ message SyncStateStreamRequest { bool subscribe = 1; // If true, the stream will continue to send updates until cancelled. } +message SyncRepoMetadata { + string id = 1; + string guid = 2; +} + +message SyncPlanMetadata { + string id = 1; +} + message PeerState { string peer_instance_id = 1; string peer_keyid = 2; SyncConnectionState state = 3; string status_message = 4; - repeated string known_plans = 5; // List of plan IDs that the peer has. - repeated string known_repos = 6; // List of repo IDs that the peer has. + repeated SyncPlanMetadata known_plans = 5; // List of plan IDs that the peer has. + repeated SyncRepoMetadata known_repos = 6; // List of repo IDs that the peer has. RemoteConfig remote_config = 7; // The remote config of the peer, if available. int64 last_heartbeat_millis = 8; // The last time the peer sent a heartbeat, in milliseconds since epoch. @@ -108,8 +117,8 @@ message SyncStreamItem { } message SyncActionListResources { - repeated string repo_ids = 1; // List of repo IDs that can be accessed. - repeated string plan_ids = 2; // List of plan IDs that can be accessed. + repeated SyncRepoMetadata repos = 1; + repeated SyncPlanMetadata plans = 2; } message SyncActionConnectRepo { diff --git a/webui/gen/ts/v1/syncservice_pb.ts b/webui/gen/ts/v1/syncservice_pb.ts index 8748b1f7..72e8eed9 100644 --- a/webui/gen/ts/v1/syncservice_pb.ts +++ b/webui/gen/ts/v1/syncservice_pb.ts @@ -23,7 +23,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file v1/syncservice.proto. */ export const file_v1_syncservice: GenFile = /*@__PURE__*/ - fileDesc("ChR2MS9zeW5jc2VydmljZS5wcm90bxICdjEijAEKDVR1bm5lbE1lc3NhZ2USDwoHY29ubl9pZBgBIAEoAxINCgVzZXFubxgCIAEoAxIMCgRkYXRhGAMgASgMEg0KBWNsb3NlGAQgASgIEhoKEnB1YmtleV9lY2RoX3gyNTUxORhkIAEoDBIiCgNhbnkY6AcgASgLMhQuZ29vZ2xlLnByb3RvYnVmLkFueSIrChZTeW5jU3RhdGVTdHJlYW1SZXF1ZXN0EhEKCXN1YnNjcmliZRgBIAEoCCLrAQoJUGVlclN0YXRlEhgKEHBlZXJfaW5zdGFuY2VfaWQYASABKAkSEgoKcGVlcl9rZXlpZBgCIAEoCRImCgVzdGF0ZRgDIAEoDjIXLnYxLlN5bmNDb25uZWN0aW9uU3RhdGUSFgoOc3RhdHVzX21lc3NhZ2UYBCABKAkSEwoLa25vd25fcGxhbnMYBSADKAkSEwoLa25vd25fcmVwb3MYBiADKAkSJwoNcmVtb3RlX2NvbmZpZxgHIAEoCzIQLnYxLlJlbW90ZUNvbmZpZxIdChVsYXN0X2hlYXJ0YmVhdF9taWxsaXMYCCABKAMigAwKDlN5bmNTdHJlYW1JdGVtEisKDnNpZ25lZF9tZXNzYWdlGAEgASgLMhEudjEuU2lnbmVkTWVzc2FnZUgAEjsKCWhhbmRzaGFrZRgDIAEoCzImLnYxLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25IYW5kc2hha2VIABI7CgloZWFydGJlYXQYBCABKAsyJi52MS5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uSGVhcnRiZWF0SAASRgoPZGlmZl9vcGVyYXRpb25zGBQgASgLMisudjEuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvbkRpZmZPcGVyYXRpb25zSAASRgoPc2VuZF9vcGVyYXRpb25zGBUgASgLMisudjEuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvblNlbmRPcGVyYXRpb25zSAASPgoLc2VuZF9jb25maWcYFiABKAsyJy52MS5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uU2VuZENvbmZpZ0gAEjwKCnNldF9jb25maWcYGCABKAsyJi52MS5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uU2V0Q29uZmlnSAASRAoObGlzdF9yZXNvdXJjZXMYGSABKAsyKi52MS5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uTGlzdFJlc291cmNlc0gAEjoKCHRocm90dGxlGOgHIAEoCzIlLnYxLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25UaHJvdHRsZUgAGnoKE1N5bmNBY3Rpb25IYW5kc2hha2USGAoQcHJvdG9jb2xfdmVyc2lvbhgBIAEoAxIhCgpwdWJsaWNfa2V5GAIgASgLMg0udjEuUHVibGljS2V5EiYKC2luc3RhbmNlX2lkGAMgASgLMhEudjEuU2lnbmVkTWVzc2FnZRoVChNTeW5jQWN0aW9uSGVhcnRiZWF0GjgKFFN5bmNBY3Rpb25TZW5kQ29uZmlnEiAKBmNvbmZpZxgBIAEoCzIQLnYxLlJlbW90ZUNvbmZpZxp5ChNTeW5jQWN0aW9uU2V0Q29uZmlnEhcKBXJlcG9zGAEgAygLMggudjEuUmVwbxIXCgVwbGFucxgCIAMoCzIILnYxLlBsYW4SFwoPcmVwb3NfdG9fZGVsZXRlGAMgAygJEhcKD3BsYW5zX3RvX2RlbGV0ZRgEIAMoCRo9ChdTeW5jQWN0aW9uTGlzdFJlc291cmNlcxIQCghyZXBvX2lkcxgBIAMoCRIQCghwbGFuX2lkcxgCIAMoCRooChVTeW5jQWN0aW9uQ29ubmVjdFJlcG8SDwoHcmVwb19pZBgBIAEoCRqjAQoYU3luY0FjdGlvbkRpZmZPcGVyYXRpb25zEjAKGGhhdmVfb3BlcmF0aW9uc19zZWxlY3RvchgBIAEoCzIOLnYxLk9wU2VsZWN0b3ISGgoSaGF2ZV9vcGVyYXRpb25faWRzGAIgAygDEh0KFWhhdmVfb3BlcmF0aW9uX21vZG5vcxgDIAMoAxIaChJyZXF1ZXN0X29wZXJhdGlvbnMYBCADKAMaPQoYU3luY0FjdGlvblNlbmRPcGVyYXRpb25zEiEKBWV2ZW50GAEgASgLMhIudjEuT3BlcmF0aW9uRXZlbnQaJgoSU3luY0FjdGlvblRocm90dGxlEhAKCGRlbGF5X21zGAEgASgDGjgKGVN5bmNFc3RhYmxpc2hTaGFyZWRTZWNyZXQSGwoHZWQyNTUxORgCIAEoCVIKZWQyNTUxOXB1YiK0AQoTUmVwb0Nvbm5lY3Rpb25TdGF0ZRIcChhDT05ORUNUSU9OX1NUQVRFX1VOS05PV04QABIcChhDT05ORUNUSU9OX1NUQVRFX1BFTkRJTkcQARIeChpDT05ORUNUSU9OX1NUQVRFX0NPTk5FQ1RFRBACEiEKHUNPTk5FQ1RJT05fU1RBVEVfVU5BVVRIT1JJWkVEEAMSHgoaQ09OTkVDVElPTl9TVEFURV9OT1RfRk9VTkQQBEIICgZhY3Rpb24iYAoMUmVtb3RlQ29uZmlnEg0KBW1vZG5vGAEgASgFEg8KB3ZlcnNpb24YAiABKAUSFwoFcmVwb3MYAyADKAsyCC52MS5SZXBvEhcKBXBsYW5zGAQgAygLMggudjEuUGxhbiqgAgoTU3luY0Nvbm5lY3Rpb25TdGF0ZRIcChhDT05ORUNUSU9OX1NUQVRFX1VOS05PV04QABIcChhDT05ORUNUSU9OX1NUQVRFX1BFTkRJTkcQARIeChpDT05ORUNUSU9OX1NUQVRFX0NPTk5FQ1RFRBACEiEKHUNPTk5FQ1RJT05fU1RBVEVfRElTQ09OTkVDVEVEEAMSHwobQ09OTkVDVElPTl9TVEFURV9SRVRSWV9XQUlUEAQSHwobQ09OTkVDVElPTl9TVEFURV9FUlJPUl9BVVRIEAoSIwofQ09OTkVDVElPTl9TVEFURV9FUlJPUl9QUk9UT0NPTBALEiMKH0NPTk5FQ1RJT05fU1RBVEVfRVJST1JfSU5URVJOQUwQDDJFCg1UdW5uZWxTZXJ2aWNlEjQKBlR1bm5lbBIRLnYxLlR1bm5lbE1lc3NhZ2UaES52MS5UdW5uZWxNZXNzYWdlIgAoATABMksKE0JhY2tyZXN0U3luY1NlcnZpY2USNAoEU3luYxISLnYxLlN5bmNTdHJlYW1JdGVtGhIudjEuU3luY1N0cmVhbUl0ZW0iACgBMAEyZAoYQmFja3Jlc3RTeW5jU3RhdGVTZXJ2aWNlEkgKF0dldFBlZXJTeW5jU3RhdGVzU3RyZWFtEhoudjEuU3luY1N0YXRlU3RyZWFtUmVxdWVzdBoNLnYxLlBlZXJTdGF0ZSIAMAFCLFoqZ2l0aHViLmNvbS9nYXJldGhnZW9yZ2UvYmFja3Jlc3QvZ2VuL2dvL3YxYgZwcm90bzM", [file_v1_config, file_v1_crypto, file_v1_restic, file_v1_service, file_v1_operations, file_types_value, file_google_protobuf_empty, file_google_api_annotations, file_google_protobuf_any]); + fileDesc("ChR2MS9zeW5jc2VydmljZS5wcm90bxICdjEijAEKDVR1bm5lbE1lc3NhZ2USDwoHY29ubl9pZBgBIAEoAxINCgVzZXFubxgCIAEoAxIMCgRkYXRhGAMgASgMEg0KBWNsb3NlGAQgASgIEhoKEnB1YmtleV9lY2RoX3gyNTUxORhkIAEoDBIiCgNhbnkY6AcgASgLMhQuZ29vZ2xlLnByb3RvYnVmLkFueSIrChZTeW5jU3RhdGVTdHJlYW1SZXF1ZXN0EhEKCXN1YnNjcmliZRgBIAEoCCIsChBTeW5jUmVwb01ldGFkYXRhEgoKAmlkGAEgASgJEgwKBGd1aWQYAiABKAkiHgoQU3luY1BsYW5NZXRhZGF0YRIKCgJpZBgBIAEoCSKXAgoJUGVlclN0YXRlEhgKEHBlZXJfaW5zdGFuY2VfaWQYASABKAkSEgoKcGVlcl9rZXlpZBgCIAEoCRImCgVzdGF0ZRgDIAEoDjIXLnYxLlN5bmNDb25uZWN0aW9uU3RhdGUSFgoOc3RhdHVzX21lc3NhZ2UYBCABKAkSKQoLa25vd25fcGxhbnMYBSADKAsyFC52MS5TeW5jUGxhbk1ldGFkYXRhEikKC2tub3duX3JlcG9zGAYgAygLMhQudjEuU3luY1JlcG9NZXRhZGF0YRInCg1yZW1vdGVfY29uZmlnGAcgASgLMhAudjEuUmVtb3RlQ29uZmlnEh0KFWxhc3RfaGVhcnRiZWF0X21pbGxpcxgIIAEoAyKmDAoOU3luY1N0cmVhbUl0ZW0SKwoOc2lnbmVkX21lc3NhZ2UYASABKAsyES52MS5TaWduZWRNZXNzYWdlSAASOwoJaGFuZHNoYWtlGAMgASgLMiYudjEuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvbkhhbmRzaGFrZUgAEjsKCWhlYXJ0YmVhdBgEIAEoCzImLnYxLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25IZWFydGJlYXRIABJGCg9kaWZmX29wZXJhdGlvbnMYFCABKAsyKy52MS5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uRGlmZk9wZXJhdGlvbnNIABJGCg9zZW5kX29wZXJhdGlvbnMYFSABKAsyKy52MS5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uU2VuZE9wZXJhdGlvbnNIABI+CgtzZW5kX2NvbmZpZxgWIAEoCzInLnYxLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25TZW5kQ29uZmlnSAASPAoKc2V0X2NvbmZpZxgYIAEoCzImLnYxLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25TZXRDb25maWdIABJECg5saXN0X3Jlc291cmNlcxgZIAEoCzIqLnYxLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25MaXN0UmVzb3VyY2VzSAASOgoIdGhyb3R0bGUY6AcgASgLMiUudjEuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvblRocm90dGxlSAAaegoTU3luY0FjdGlvbkhhbmRzaGFrZRIYChBwcm90b2NvbF92ZXJzaW9uGAEgASgDEiEKCnB1YmxpY19rZXkYAiABKAsyDS52MS5QdWJsaWNLZXkSJgoLaW5zdGFuY2VfaWQYAyABKAsyES52MS5TaWduZWRNZXNzYWdlGhUKE1N5bmNBY3Rpb25IZWFydGJlYXQaOAoUU3luY0FjdGlvblNlbmRDb25maWcSIAoGY29uZmlnGAEgASgLMhAudjEuUmVtb3RlQ29uZmlnGnkKE1N5bmNBY3Rpb25TZXRDb25maWcSFwoFcmVwb3MYASADKAsyCC52MS5SZXBvEhcKBXBsYW5zGAIgAygLMggudjEuUGxhbhIXCg9yZXBvc190b19kZWxldGUYAyADKAkSFwoPcGxhbnNfdG9fZGVsZXRlGAQgAygJGmMKF1N5bmNBY3Rpb25MaXN0UmVzb3VyY2VzEiMKBXJlcG9zGAEgAygLMhQudjEuU3luY1JlcG9NZXRhZGF0YRIjCgVwbGFucxgCIAMoCzIULnYxLlN5bmNQbGFuTWV0YWRhdGEaKAoVU3luY0FjdGlvbkNvbm5lY3RSZXBvEg8KB3JlcG9faWQYASABKAkaowEKGFN5bmNBY3Rpb25EaWZmT3BlcmF0aW9ucxIwChhoYXZlX29wZXJhdGlvbnNfc2VsZWN0b3IYASABKAsyDi52MS5PcFNlbGVjdG9yEhoKEmhhdmVfb3BlcmF0aW9uX2lkcxgCIAMoAxIdChVoYXZlX29wZXJhdGlvbl9tb2Rub3MYAyADKAMSGgoScmVxdWVzdF9vcGVyYXRpb25zGAQgAygDGj0KGFN5bmNBY3Rpb25TZW5kT3BlcmF0aW9ucxIhCgVldmVudBgBIAEoCzISLnYxLk9wZXJhdGlvbkV2ZW50GiYKElN5bmNBY3Rpb25UaHJvdHRsZRIQCghkZWxheV9tcxgBIAEoAxo4ChlTeW5jRXN0YWJsaXNoU2hhcmVkU2VjcmV0EhsKB2VkMjU1MTkYAiABKAlSCmVkMjU1MTlwdWIitAEKE1JlcG9Db25uZWN0aW9uU3RhdGUSHAoYQ09OTkVDVElPTl9TVEFURV9VTktOT1dOEAASHAoYQ09OTkVDVElPTl9TVEFURV9QRU5ESU5HEAESHgoaQ09OTkVDVElPTl9TVEFURV9DT05ORUNURUQQAhIhCh1DT05ORUNUSU9OX1NUQVRFX1VOQVVUSE9SSVpFRBADEh4KGkNPTk5FQ1RJT05fU1RBVEVfTk9UX0ZPVU5EEARCCAoGYWN0aW9uImAKDFJlbW90ZUNvbmZpZxINCgVtb2RubxgBIAEoBRIPCgd2ZXJzaW9uGAIgASgFEhcKBXJlcG9zGAMgAygLMggudjEuUmVwbxIXCgVwbGFucxgEIAMoCzIILnYxLlBsYW4qoAIKE1N5bmNDb25uZWN0aW9uU3RhdGUSHAoYQ09OTkVDVElPTl9TVEFURV9VTktOT1dOEAASHAoYQ09OTkVDVElPTl9TVEFURV9QRU5ESU5HEAESHgoaQ09OTkVDVElPTl9TVEFURV9DT05ORUNURUQQAhIhCh1DT05ORUNUSU9OX1NUQVRFX0RJU0NPTk5FQ1RFRBADEh8KG0NPTk5FQ1RJT05fU1RBVEVfUkVUUllfV0FJVBAEEh8KG0NPTk5FQ1RJT05fU1RBVEVfRVJST1JfQVVUSBAKEiMKH0NPTk5FQ1RJT05fU1RBVEVfRVJST1JfUFJPVE9DT0wQCxIjCh9DT05ORUNUSU9OX1NUQVRFX0VSUk9SX0lOVEVSTkFMEAwyRQoNVHVubmVsU2VydmljZRI0CgZUdW5uZWwSES52MS5UdW5uZWxNZXNzYWdlGhEudjEuVHVubmVsTWVzc2FnZSIAKAEwATJLChNCYWNrcmVzdFN5bmNTZXJ2aWNlEjQKBFN5bmMSEi52MS5TeW5jU3RyZWFtSXRlbRoSLnYxLlN5bmNTdHJlYW1JdGVtIgAoATABMmQKGEJhY2tyZXN0U3luY1N0YXRlU2VydmljZRJIChdHZXRQZWVyU3luY1N0YXRlc1N0cmVhbRIaLnYxLlN5bmNTdGF0ZVN0cmVhbVJlcXVlc3QaDS52MS5QZWVyU3RhdGUiADABQixaKmdpdGh1Yi5jb20vZ2FyZXRoZ2VvcmdlL2JhY2tyZXN0L2dlbi9nby92MWIGcHJvdG8z", [file_v1_config, file_v1_crypto, file_v1_restic, file_v1_service, file_v1_operations, file_types_value, file_google_protobuf_empty, file_google_api_annotations, file_google_protobuf_any]); /** * @generated from message v1.TunnelMessage @@ -96,6 +96,45 @@ export type SyncStateStreamRequest = Message<"v1.SyncStateStreamRequest"> & { export const SyncStateStreamRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_syncservice, 1); +/** + * @generated from message v1.SyncRepoMetadata + */ +export type SyncRepoMetadata = Message<"v1.SyncRepoMetadata"> & { + /** + * @generated from field: string id = 1; + */ + id: string; + + /** + * @generated from field: string guid = 2; + */ + guid: string; +}; + +/** + * Describes the message v1.SyncRepoMetadata. + * Use `create(SyncRepoMetadataSchema)` to create a new message. + */ +export const SyncRepoMetadataSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 2); + +/** + * @generated from message v1.SyncPlanMetadata + */ +export type SyncPlanMetadata = Message<"v1.SyncPlanMetadata"> & { + /** + * @generated from field: string id = 1; + */ + id: string; +}; + +/** + * Describes the message v1.SyncPlanMetadata. + * Use `create(SyncPlanMetadataSchema)` to create a new message. + */ +export const SyncPlanMetadataSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 3); + /** * @generated from message v1.PeerState */ @@ -123,16 +162,16 @@ export type PeerState = Message<"v1.PeerState"> & { /** * List of plan IDs that the peer has. * - * @generated from field: repeated string known_plans = 5; + * @generated from field: repeated v1.SyncPlanMetadata known_plans = 5; */ - knownPlans: string[]; + knownPlans: SyncPlanMetadata[]; /** * List of repo IDs that the peer has. * - * @generated from field: repeated string known_repos = 6; + * @generated from field: repeated v1.SyncRepoMetadata known_repos = 6; */ - knownRepos: string[]; + knownRepos: SyncRepoMetadata[]; /** * The remote config of the peer, if available. @@ -154,7 +193,7 @@ export type PeerState = Message<"v1.PeerState"> & { * Use `create(PeerStateSchema)` to create a new message. */ export const PeerStateSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 2); + messageDesc(file_v1_syncservice, 4); /** * @generated from message v1.SyncStreamItem @@ -225,7 +264,7 @@ export type SyncStreamItem = Message<"v1.SyncStreamItem"> & { * Use `create(SyncStreamItemSchema)` to create a new message. */ export const SyncStreamItemSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 3); + messageDesc(file_v1_syncservice, 5); /** * @generated from message v1.SyncStreamItem.SyncActionHandshake @@ -252,7 +291,7 @@ export type SyncStreamItem_SyncActionHandshake = Message<"v1.SyncStreamItem.Sync * Use `create(SyncStreamItem_SyncActionHandshakeSchema)` to create a new message. */ export const SyncStreamItem_SyncActionHandshakeSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 3, 0); + messageDesc(file_v1_syncservice, 5, 0); /** * SyncActionHeartbeat is sent periodically to keep the connection alive. @@ -267,7 +306,7 @@ export type SyncStreamItem_SyncActionHeartbeat = Message<"v1.SyncStreamItem.Sync * Use `create(SyncStreamItem_SyncActionHeartbeatSchema)` to create a new message. */ export const SyncStreamItem_SyncActionHeartbeatSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 3, 1); + messageDesc(file_v1_syncservice, 5, 1); /** * @generated from message v1.SyncStreamItem.SyncActionSendConfig @@ -284,7 +323,7 @@ export type SyncStreamItem_SyncActionSendConfig = Message<"v1.SyncStreamItem.Syn * Use `create(SyncStreamItem_SyncActionSendConfigSchema)` to create a new message. */ export const SyncStreamItem_SyncActionSendConfigSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 3, 2); + messageDesc(file_v1_syncservice, 5, 2); /** * @generated from message v1.SyncStreamItem.SyncActionSetConfig @@ -316,25 +355,21 @@ export type SyncStreamItem_SyncActionSetConfig = Message<"v1.SyncStreamItem.Sync * Use `create(SyncStreamItem_SyncActionSetConfigSchema)` to create a new message. */ export const SyncStreamItem_SyncActionSetConfigSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 3, 3); + messageDesc(file_v1_syncservice, 5, 3); /** * @generated from message v1.SyncStreamItem.SyncActionListResources */ export type SyncStreamItem_SyncActionListResources = Message<"v1.SyncStreamItem.SyncActionListResources"> & { /** - * List of repo IDs that can be accessed. - * - * @generated from field: repeated string repo_ids = 1; + * @generated from field: repeated v1.SyncRepoMetadata repos = 1; */ - repoIds: string[]; + repos: SyncRepoMetadata[]; /** - * List of plan IDs that can be accessed. - * - * @generated from field: repeated string plan_ids = 2; + * @generated from field: repeated v1.SyncPlanMetadata plans = 2; */ - planIds: string[]; + plans: SyncPlanMetadata[]; }; /** @@ -342,7 +377,7 @@ export type SyncStreamItem_SyncActionListResources = Message<"v1.SyncStreamItem. * Use `create(SyncStreamItem_SyncActionListResourcesSchema)` to create a new message. */ export const SyncStreamItem_SyncActionListResourcesSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 3, 4); + messageDesc(file_v1_syncservice, 5, 4); /** * @generated from message v1.SyncStreamItem.SyncActionConnectRepo @@ -359,7 +394,7 @@ export type SyncStreamItem_SyncActionConnectRepo = Message<"v1.SyncStreamItem.Sy * Use `create(SyncStreamItem_SyncActionConnectRepoSchema)` to create a new message. */ export const SyncStreamItem_SyncActionConnectRepoSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 3, 5); + messageDesc(file_v1_syncservice, 5, 5); /** * @generated from message v1.SyncStreamItem.SyncActionDiffOperations @@ -396,7 +431,7 @@ export type SyncStreamItem_SyncActionDiffOperations = Message<"v1.SyncStreamItem * Use `create(SyncStreamItem_SyncActionDiffOperationsSchema)` to create a new message. */ export const SyncStreamItem_SyncActionDiffOperationsSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 3, 6); + messageDesc(file_v1_syncservice, 5, 6); /** * @generated from message v1.SyncStreamItem.SyncActionSendOperations @@ -413,7 +448,7 @@ export type SyncStreamItem_SyncActionSendOperations = Message<"v1.SyncStreamItem * Use `create(SyncStreamItem_SyncActionSendOperationsSchema)` to create a new message. */ export const SyncStreamItem_SyncActionSendOperationsSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 3, 7); + messageDesc(file_v1_syncservice, 5, 7); /** * @generated from message v1.SyncStreamItem.SyncActionThrottle @@ -430,7 +465,7 @@ export type SyncStreamItem_SyncActionThrottle = Message<"v1.SyncStreamItem.SyncA * Use `create(SyncStreamItem_SyncActionThrottleSchema)` to create a new message. */ export const SyncStreamItem_SyncActionThrottleSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 3, 8); + messageDesc(file_v1_syncservice, 5, 8); /** * @generated from message v1.SyncStreamItem.SyncEstablishSharedSecret @@ -452,7 +487,7 @@ export type SyncStreamItem_SyncEstablishSharedSecret = Message<"v1.SyncStreamIte * Use `create(SyncStreamItem_SyncEstablishSharedSecretSchema)` to create a new message. */ export const SyncStreamItem_SyncEstablishSharedSecretSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 3, 9); + messageDesc(file_v1_syncservice, 5, 9); /** * @generated from enum v1.SyncStreamItem.RepoConnectionState @@ -490,7 +525,7 @@ export enum SyncStreamItem_RepoConnectionState { * Describes the enum v1.SyncStreamItem.RepoConnectionState. */ export const SyncStreamItem_RepoConnectionStateSchema: GenEnum = /*@__PURE__*/ - enumDesc(file_v1_syncservice, 3, 0); + enumDesc(file_v1_syncservice, 5, 0); /** * RemoteConfig contains shareable properties from a remote backrest instance. @@ -528,7 +563,7 @@ export type RemoteConfig = Message<"v1.RemoteConfig"> & { * Use `create(RemoteConfigSchema)` to create a new message. */ export const RemoteConfigSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_syncservice, 4); + messageDesc(file_v1_syncservice, 6); /** * @generated from enum v1.SyncConnectionState diff --git a/webui/src/views/App.tsx b/webui/src/views/App.tsx index ba7d9999..44f2ad25 100644 --- a/webui/src/views/App.tsx +++ b/webui/src/views/App.tsx @@ -7,10 +7,11 @@ import { ExclamationOutlined, SettingOutlined, LoadingOutlined, + CloudServerOutlined, } from "@ant-design/icons"; import type { MenuProps } from "antd"; import { Button, Empty, Layout, Menu, Spin, theme } from "antd"; -import { Config } from "../../gen/ts/v1/config_pb"; +import { Config, Multihost_Peer } from "../../gen/ts/v1/config_pb"; import { useAlertApi } from "../components/Alerts"; import { useShowModal } from "../components/ModalManager"; import { uiBuildVersion } from "../state/buildcfg"; @@ -33,6 +34,11 @@ import { getStatusForSelector, matchSelector } from "../state/logstate"; import { Route, Routes, useNavigate, useParams } from "react-router-dom"; import { MainContentAreaTemplate } from "./MainContentArea"; import { create } from "@bufbuild/protobuf"; +import { PeerState } from "../../gen/ts/v1/syncservice_pb"; +import { + subscribeToPeerStates, + unsubscribeFromPeerStates, +} from "../state/peerstates"; const { Header, Sider } = Layout; @@ -114,7 +120,20 @@ export const App: React.FC = () => { const navigate = useNavigate(); const [config, setConfig] = useConfig(); - const items = getSidenavItems(config); + const [peerStates, setPeerStates] = useState([]); + + useEffect(() => { + if (!config || !config.multihost) return; + const cb = (states: PeerState[]) => { + setPeerStates(states); + }; + subscribeToPeerStates(cb); + return () => { + unsubscribeFromPeerStates(cb); + }; + }, [config]); + + const items = getSidenavItems(config, peerStates); return ( @@ -273,7 +292,10 @@ const AuthenticationBoundary = ({ return <>{children}; }; -const getSidenavItems = (config: Config | null): MenuProps["items"] => { +const getSidenavItems = ( + config: Config | null, + peerStates: PeerState[] +): MenuProps["items"] => { const showModal = useShowModal(); const navigate = useNavigate(); @@ -285,6 +307,8 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => { const configPlans = config.plans || []; const configRepos = config.repos || []; + const menu: MenuProps["items"] = []; + const plans: MenuProps["items"] = [ { key: "add-plan", @@ -380,37 +404,83 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => { }), ]; - const authorizedClientRepos: MenuProps["items"] = []; - + const authorizedClients: MenuProps["items"] = []; if (config.multihost?.authorizedClients?.length) { - const authorizedClients = config.multihost.authorizedClients; + const authorizedClientsConfigs = new Map(); + for (const client of config.multihost.authorizedClients) { + authorizedClientsConfigs.set(client.keyid, client); + } - // Display authorized client peer statuses + const createElementForPeerState = ( + peerState: PeerState, + peerConfig: Multihost_Peer + ): Required["items"][0] => { + const repos: MenuProps["items"] = peerState.knownRepos.map((repo) => { + const sel = create(OpSelectorSchema, { + originalInstanceKeyid: peerState.peerKeyid, + repoGuid: repo.guid, + }); + }); + + return { + key: `peer-${peerState.peerKeyid}`, + icon: ( + + ), + label: ( +
+ {peerState.peerInstanceId} +
+ ), + }; + }; + + for (const peerState of peerStates) { + const peerConfig = authorizedClientsConfigs.get(peerState.peerKeyid); + if (!peerConfig) { + continue; + } + authorizedClients.push(createElementForPeerState(peerState, peerConfig)); + } } - return [ - { - key: "plans", - icon: React.createElement(ScheduleOutlined), - label: "Plans", - children: plans, + menu.push({ + key: "plans", + icon: React.createElement(ScheduleOutlined), + label: "Plans", + children: plans, + }); + menu.push({ + key: "repos", + icon: React.createElement(DatabaseOutlined), + label: "Repositories", + children: repos, + }); + if (authorizedClients.length > 0) { + menu.push({ + key: "authorized-clients", + icon: React.createElement(CloudServerOutlined), + label: "Remote Instances", + children: authorizedClients, + }); + } + menu.push({ + key: "settings", + icon: React.createElement(SettingOutlined), + label: "Settings", + onClick: async () => { + const { SettingsModal } = await import("./SettingsModal"); + showModal(); }, - { - key: "repos", - icon: React.createElement(DatabaseOutlined), - label: "Repositories", - children: repos, - }, - { - key: "settings", - icon: React.createElement(SettingOutlined), - label: "Settings", - onClick: async () => { - const { SettingsModal } = await import("./SettingsModal"); - showModal(); - }, - }, - ]; + }); + return menu; }; const IconForResource = ({ selector }: { selector: OpSelector }) => {