diff --git a/cmd/backrest/backrest.go b/cmd/backrest/backrest.go index 28e91302..94916bc0 100644 --- a/cmd/backrest/backrest.go +++ b/cmd/backrest/backrest.go @@ -250,59 +250,43 @@ func installLoggers() { // operations directly to the sqlite logstore. func migrateBboltOplog(logstore oplog.OpStore) { oldBboltOplogFile := path.Join(env.DataDir(), "oplog.boltdb") - if _, err := os.Stat(oldBboltOplogFile); err == nil { - zap.S().Warnf("found old bbolt oplog file %q, migrating to sqlite", oldBboltOplogFile) - oldOpstore, err := bboltstore.NewBboltStore(oldBboltOplogFile) - if err != nil { - zap.S().Fatalf("error opening old bbolt oplog: %v", err) - } - - oldOplog, err := oplog.NewOpLog(oldOpstore) - if err != nil { - zap.S().Fatalf("error creating old bbolt oplog: %v", err) - } - - batch := make([]*v1.Operation, 0, 32) - - var errs []error - if err := oldOplog.Query(oplog.Query{}, func(op *v1.Operation) error { - batch = append(batch, op) - if len(batch) == 256 { - if err := logstore.Add(batch...); err != nil { - errs = append(errs, err) - zap.S().Warnf("error migrating %d operations: %v", len(batch), err) - } else { - zap.S().Debugf("migrated %d oplog operations from bbolt to sqlite store", len(batch)) - } - batch = batch[:0] - } - return nil - }); err != nil { - zap.S().Warnf("couldn't migrate all operations from the old bbolt oplog, if this recurs delete the file %q and restart", oldBboltOplogFile) - zap.S().Fatalf("error migrating old bbolt oplog: %v", err) - } - - if len(batch) > 0 { - if err := logstore.Add(batch...); err != nil { - errs = append(errs, err) - zap.S().Warnf("error migrating %d operations: %v", len(batch), err) - } else { - zap.S().Debugf("migrated %d oplog operations from bbolt to sqlite store", len(batch)) - } - zap.S().Debugf("migrated %d oplog operations from bbolt to sqlite store", len(batch)) - } + if _, err := os.Stat(oldBboltOplogFile); err != nil { + return + } - if len(errs) > 0 { - zap.S().Fatalf("encountered %d errors migrating old bbolt oplog, see logs for details. If this probelem recurs delete the file %q and restart", len(errs), oldBboltOplogFile) - } + zap.S().Warnf("found old bbolt oplog file %q, migrating to sqlite", oldBboltOplogFile) + oldOpstore, err := bboltstore.NewBboltStore(oldBboltOplogFile) + if err != nil { + zap.S().Fatalf("error opening old bolt opstore: %v", oldBboltOplogFile, err) + } + oldOplog, err := oplog.NewOpLog(oldOpstore) + if err != nil { + zap.S().Fatalf("error opening old bolt oplog: %v", oldBboltOplogFile, err) + } - if err := oldOpstore.Close(); err != nil { - zap.S().Warnf("error closing old bbolt oplog: %v", err) - } - if err := os.Rename(oldBboltOplogFile, oldBboltOplogFile+".deprecated"); err != nil { - zap.S().Warnf("error removing old bbolt oplog: %v", err) + var errs []error + var count int + if err := oldOplog.Query(oplog.Query{}, func(op *v1.Operation) error { + if err := logstore.Add(op); err != nil { + errs = append(errs, err) + zap.L().Warn("failed to migrate operation", zap.Error(err), zap.Any("operation", op)) + } else { + count++ } + return nil + }); err != nil { + zap.S().Warnf("couldn't migrate all operations from the old bbolt oplog, if this recurs delete the file %q and restart", oldBboltOplogFile) + zap.S().Fatalf("error migrating old bbolt oplog: %v", err) + } - zap.S().Info("migrated old bbolt oplog to sqlite") + if len(errs) > 0 { + zap.S().Errorf("encountered %d errors migrating old bbolt oplog, see logs for details.", len(errs), oldBboltOplogFile) + } + if err := oldOpstore.Close(); err != nil { + zap.S().Warnf("error closing old bbolt oplog: %v", err) + } + if err := os.Rename(oldBboltOplogFile, oldBboltOplogFile+".deprecated"); err != nil { + zap.S().Warnf("error removing old bbolt oplog: %v", err) } + zap.S().Infof("migrated %d operations from old bbolt oplog to sqlite", count) } diff --git a/gen/go/v1/operations.pb.go b/gen/go/v1/operations.pb.go index 93ef6c52..c2d6f748 100644 --- a/gen/go/v1/operations.pb.go +++ b/gen/go/v1/operations.pb.go @@ -860,7 +860,7 @@ type OperationRunCommand struct { Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"` OutputLogref string `protobuf:"bytes,2,opt,name=output_logref,json=outputLogref,proto3" json:"output_logref,omitempty"` - OutputSizeBytes int64 `protobuf:"varint,3,opt,name=output_size_bytes,json=outputSizeBytes,proto3" json:"output_size_bytes,omitempty"` + OutputSizeBytes int64 `protobuf:"varint,3,opt,name=output_size_bytes,json=outputSizeBytes,proto3" json:"output_size_bytes,omitempty"` // not necessarily authoritative, tracked as an optimization to allow clients to avoid fetching very large outputs. } func (x *OperationRunCommand) Reset() { diff --git a/gen/go/v1/service.pb.go b/gen/go/v1/service.pb.go index 0dfe4324..fb9210f1 100644 --- a/gen/go/v1/service.pb.go +++ b/gen/go/v1/service.pb.go @@ -862,6 +862,284 @@ func (x *RunCommandRequest) GetCommand() string { return "" } +type SummaryDashboardResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RepoSummaries []*SummaryDashboardResponse_Summary `protobuf:"bytes,1,rep,name=repo_summaries,json=repoSummaries,proto3" json:"repo_summaries,omitempty"` + PlanSummaries []*SummaryDashboardResponse_Summary `protobuf:"bytes,2,rep,name=plan_summaries,json=planSummaries,proto3" json:"plan_summaries,omitempty"` + ConfigPath string `protobuf:"bytes,10,opt,name=config_path,json=configPath,proto3" json:"config_path,omitempty"` + DataPath string `protobuf:"bytes,11,opt,name=data_path,json=dataPath,proto3" json:"data_path,omitempty"` +} + +func (x *SummaryDashboardResponse) Reset() { + *x = SummaryDashboardResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_service_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SummaryDashboardResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SummaryDashboardResponse) ProtoMessage() {} + +func (x *SummaryDashboardResponse) ProtoReflect() protoreflect.Message { + mi := &file_v1_service_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SummaryDashboardResponse.ProtoReflect.Descriptor instead. +func (*SummaryDashboardResponse) Descriptor() ([]byte, []int) { + return file_v1_service_proto_rawDescGZIP(), []int{12} +} + +func (x *SummaryDashboardResponse) GetRepoSummaries() []*SummaryDashboardResponse_Summary { + if x != nil { + return x.RepoSummaries + } + return nil +} + +func (x *SummaryDashboardResponse) GetPlanSummaries() []*SummaryDashboardResponse_Summary { + if x != nil { + return x.PlanSummaries + } + return nil +} + +func (x *SummaryDashboardResponse) GetConfigPath() string { + if x != nil { + return x.ConfigPath + } + return "" +} + +func (x *SummaryDashboardResponse) GetDataPath() string { + if x != nil { + return x.DataPath + } + return "" +} + +type SummaryDashboardResponse_Summary struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + BackupsFailed_30Days int64 `protobuf:"varint,2,opt,name=backups_failed_30days,json=backupsFailed30days,proto3" json:"backups_failed_30days,omitempty"` + BackupsWarningLast_30Days int64 `protobuf:"varint,3,opt,name=backups_warning_last_30days,json=backupsWarningLast30days,proto3" json:"backups_warning_last_30days,omitempty"` + BackupsSuccessLast_30Days int64 `protobuf:"varint,4,opt,name=backups_success_last_30days,json=backupsSuccessLast30days,proto3" json:"backups_success_last_30days,omitempty"` + BytesScannedLast_30Days int64 `protobuf:"varint,5,opt,name=bytes_scanned_last_30days,json=bytesScannedLast30days,proto3" json:"bytes_scanned_last_30days,omitempty"` + BytesAddedLast_30Days int64 `protobuf:"varint,6,opt,name=bytes_added_last_30days,json=bytesAddedLast30days,proto3" json:"bytes_added_last_30days,omitempty"` + TotalSnapshots int64 `protobuf:"varint,7,opt,name=total_snapshots,json=totalSnapshots,proto3" json:"total_snapshots,omitempty"` + BytesScannedAvg int64 `protobuf:"varint,8,opt,name=bytes_scanned_avg,json=bytesScannedAvg,proto3" json:"bytes_scanned_avg,omitempty"` + BytesAddedAvg int64 `protobuf:"varint,9,opt,name=bytes_added_avg,json=bytesAddedAvg,proto3" json:"bytes_added_avg,omitempty"` + NextBackupTimeMs int64 `protobuf:"varint,10,opt,name=next_backup_time_ms,json=nextBackupTimeMs,proto3" json:"next_backup_time_ms,omitempty"` + // Charts + RecentBackups *SummaryDashboardResponse_BackupChart `protobuf:"bytes,11,opt,name=recent_backups,json=recentBackups,proto3" json:"recent_backups,omitempty"` // recent backups +} + +func (x *SummaryDashboardResponse_Summary) Reset() { + *x = SummaryDashboardResponse_Summary{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_service_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SummaryDashboardResponse_Summary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SummaryDashboardResponse_Summary) ProtoMessage() {} + +func (x *SummaryDashboardResponse_Summary) ProtoReflect() protoreflect.Message { + mi := &file_v1_service_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SummaryDashboardResponse_Summary.ProtoReflect.Descriptor instead. +func (*SummaryDashboardResponse_Summary) Descriptor() ([]byte, []int) { + return file_v1_service_proto_rawDescGZIP(), []int{12, 0} +} + +func (x *SummaryDashboardResponse_Summary) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *SummaryDashboardResponse_Summary) GetBackupsFailed_30Days() int64 { + if x != nil { + return x.BackupsFailed_30Days + } + return 0 +} + +func (x *SummaryDashboardResponse_Summary) GetBackupsWarningLast_30Days() int64 { + if x != nil { + return x.BackupsWarningLast_30Days + } + return 0 +} + +func (x *SummaryDashboardResponse_Summary) GetBackupsSuccessLast_30Days() int64 { + if x != nil { + return x.BackupsSuccessLast_30Days + } + return 0 +} + +func (x *SummaryDashboardResponse_Summary) GetBytesScannedLast_30Days() int64 { + if x != nil { + return x.BytesScannedLast_30Days + } + return 0 +} + +func (x *SummaryDashboardResponse_Summary) GetBytesAddedLast_30Days() int64 { + if x != nil { + return x.BytesAddedLast_30Days + } + return 0 +} + +func (x *SummaryDashboardResponse_Summary) GetTotalSnapshots() int64 { + if x != nil { + return x.TotalSnapshots + } + return 0 +} + +func (x *SummaryDashboardResponse_Summary) GetBytesScannedAvg() int64 { + if x != nil { + return x.BytesScannedAvg + } + return 0 +} + +func (x *SummaryDashboardResponse_Summary) GetBytesAddedAvg() int64 { + if x != nil { + return x.BytesAddedAvg + } + return 0 +} + +func (x *SummaryDashboardResponse_Summary) GetNextBackupTimeMs() int64 { + if x != nil { + return x.NextBackupTimeMs + } + return 0 +} + +func (x *SummaryDashboardResponse_Summary) GetRecentBackups() *SummaryDashboardResponse_BackupChart { + if x != nil { + return x.RecentBackups + } + return nil +} + +type SummaryDashboardResponse_BackupChart struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FlowId []int64 `protobuf:"varint,1,rep,packed,name=flow_id,json=flowId,proto3" json:"flow_id,omitempty"` + TimestampMs []int64 `protobuf:"varint,2,rep,packed,name=timestamp_ms,json=timestampMs,proto3" json:"timestamp_ms,omitempty"` + DurationMs []int64 `protobuf:"varint,3,rep,packed,name=duration_ms,json=durationMs,proto3" json:"duration_ms,omitempty"` + Status []OperationStatus `protobuf:"varint,4,rep,packed,name=status,proto3,enum=v1.OperationStatus" json:"status,omitempty"` + BytesAdded []int64 `protobuf:"varint,5,rep,packed,name=bytes_added,json=bytesAdded,proto3" json:"bytes_added,omitempty"` +} + +func (x *SummaryDashboardResponse_BackupChart) Reset() { + *x = SummaryDashboardResponse_BackupChart{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_service_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SummaryDashboardResponse_BackupChart) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SummaryDashboardResponse_BackupChart) ProtoMessage() {} + +func (x *SummaryDashboardResponse_BackupChart) ProtoReflect() protoreflect.Message { + mi := &file_v1_service_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SummaryDashboardResponse_BackupChart.ProtoReflect.Descriptor instead. +func (*SummaryDashboardResponse_BackupChart) Descriptor() ([]byte, []int) { + return file_v1_service_proto_rawDescGZIP(), []int{12, 1} +} + +func (x *SummaryDashboardResponse_BackupChart) GetFlowId() []int64 { + if x != nil { + return x.FlowId + } + return nil +} + +func (x *SummaryDashboardResponse_BackupChart) GetTimestampMs() []int64 { + if x != nil { + return x.TimestampMs + } + return nil +} + +func (x *SummaryDashboardResponse_BackupChart) GetDurationMs() []int64 { + if x != nil { + return x.DurationMs + } + return nil +} + +func (x *SummaryDashboardResponse_BackupChart) GetStatus() []OperationStatus { + if x != nil { + return x.Status + } + return nil +} + +func (x *SummaryDashboardResponse_BackupChart) GetBytesAdded() []int64 { + if x != nil { + return x.BytesAdded + } + return nil +} + var File_v1_service_proto protoreflect.FileDescriptor var file_v1_service_proto_rawDesc = []byte{ @@ -961,74 +1239,141 @@ var file_v1_service_proto_rawDesc = []byte{ 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x32, 0xf7, 0x07, 0x0a, 0x08, 0x42, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, - 0x74, 0x12, 0x31, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x22, 0x00, 0x12, 0x25, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x0a, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x00, 0x12, 0x21, 0x0a, 0x07, 0x41, - 0x64, 0x64, 0x52, 0x65, 0x70, 0x6f, 0x12, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, - 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x00, 0x12, 0x44, - 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x76, - 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x22, 0x00, 0x30, 0x01, 0x12, 0x3e, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, - 0x73, 0x74, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, 0x73, - 0x68, 0x6f, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x11, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x1c, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, - 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, - 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x36, 0x0a, - 0x06, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, - 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6d, 0x61, 0x6e, 0x64, 0x22, 0xea, 0x07, 0x0a, 0x18, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x4b, 0x0a, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, + 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, + 0x0d, 0x72, 0x65, 0x70, 0x6f, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, 0x12, 0x4b, + 0x0a, 0x0e, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x0d, 0x70, 0x6c, + 0x61, 0x6e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1b, 0x0a, 0x09, + 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x64, 0x61, 0x74, 0x61, 0x50, 0x61, 0x74, 0x68, 0x1a, 0xba, 0x04, 0x0a, 0x07, 0x53, 0x75, + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, + 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x46, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x12, 0x3d, 0x0a, 0x1b, 0x62, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x73, 0x5f, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x61, 0x73, + 0x74, 0x5f, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, + 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x4c, 0x61, + 0x73, 0x74, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x12, 0x3d, 0x0a, 0x1b, 0x62, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x73, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x61, 0x73, 0x74, + 0x5f, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x62, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x61, 0x73, + 0x74, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x12, 0x39, 0x0a, 0x19, 0x62, 0x79, 0x74, 0x65, 0x73, + 0x5f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x33, 0x30, + 0x64, 0x61, 0x79, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x62, 0x79, 0x74, 0x65, + 0x73, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x4c, 0x61, 0x73, 0x74, 0x33, 0x30, 0x64, 0x61, + 0x79, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x65, + 0x64, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x14, 0x62, 0x79, 0x74, 0x65, 0x73, 0x41, 0x64, 0x64, 0x65, 0x64, 0x4c, + 0x61, 0x73, 0x74, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x63, 0x61, 0x6e, + 0x6e, 0x65, 0x64, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x62, + 0x79, 0x74, 0x65, 0x73, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x41, 0x76, 0x67, 0x12, 0x26, + 0x0a, 0x0f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x76, + 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x62, 0x79, 0x74, 0x65, 0x73, 0x41, 0x64, + 0x64, 0x65, 0x64, 0x41, 0x76, 0x67, 0x12, 0x2d, 0x0a, 0x13, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x62, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x10, 0x6e, 0x65, 0x78, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x54, + 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x4f, 0x0a, 0x0e, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x5f, + 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, + 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x43, 0x68, 0x61, 0x72, 0x74, 0x52, 0x0d, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x1a, 0xb8, 0x01, 0x0a, 0x0b, 0x42, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x43, 0x68, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x06, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x12, + 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6d, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x4d, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x41, 0x64, 0x64, 0x65, + 0x64, 0x32, 0xc6, 0x08, 0x0a, 0x08, 0x42, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x12, 0x31, + 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0a, 0x44, 0x6f, 0x52, 0x65, 0x70, 0x6f, 0x54, - 0x61, 0x73, 0x6b, 0x12, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x6f, 0x52, 0x65, 0x70, 0x6f, 0x54, - 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x06, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x12, 0x11, - 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x07, 0x52, - 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, - 0x6f, 0x72, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x06, - 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, - 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x12, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x0a, 0x52, 0x75, 0x6e, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x75, 0x6e, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, - 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, - 0x61, 0x64, 0x55, 0x52, 0x4c, 0x12, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, - 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, - 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x41, - 0x0a, 0x0c, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x17, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, + 0x70, 0x74, 0x79, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, + 0x00, 0x12, 0x25, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0a, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x00, 0x12, 0x21, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x52, + 0x65, 0x70, 0x6f, 0x12, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x1a, 0x0a, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x12, 0x47, + 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x30, + 0x01, 0x12, 0x3e, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x76, + 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x22, + 0x00, 0x12, 0x43, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, + 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, + 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x06, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0a, 0x44, 0x6f, 0x52, 0x65, 0x70, 0x6f, 0x54, 0x61, 0x73, 0x6b, + 0x12, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x6f, 0x52, 0x65, 0x70, 0x6f, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, - 0x00, 0x12, 0x3b, 0x0a, 0x10, 0x50, 0x61, 0x74, 0x68, 0x41, 0x75, 0x74, 0x6f, 0x63, 0x6f, 0x6d, - 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x42, 0x2c, - 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, - 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, - 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x00, 0x12, 0x35, 0x0a, 0x06, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x12, 0x11, 0x2e, 0x76, 0x31, + 0x2e, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x12, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x06, 0x43, 0x61, 0x6e, + 0x63, 0x65, 0x6c, 0x12, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, + 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, + 0x12, 0x34, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x12, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x6f, 0x67, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x0a, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, + 0x12, 0x39, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x55, + 0x52, 0x4c, 0x12, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0c, 0x43, + 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x17, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, + 0x0a, 0x10, 0x50, 0x61, 0x74, 0x68, 0x41, 0x75, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, + 0x74, 0x65, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x13, 0x47, + 0x65, 0x74, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, + 0x72, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, + 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x2f, 0x67, + 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1044,76 +1389,86 @@ func file_v1_service_proto_rawDescGZIP() []byte { } var file_v1_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_v1_service_proto_goTypes = []interface{}{ - (DoRepoTaskRequest_Task)(0), // 0: v1.DoRepoTaskRequest.Task - (*OpSelector)(nil), // 1: v1.OpSelector - (*DoRepoTaskRequest)(nil), // 2: v1.DoRepoTaskRequest - (*ClearHistoryRequest)(nil), // 3: v1.ClearHistoryRequest - (*ForgetRequest)(nil), // 4: v1.ForgetRequest - (*ListSnapshotsRequest)(nil), // 5: v1.ListSnapshotsRequest - (*GetOperationsRequest)(nil), // 6: v1.GetOperationsRequest - (*RestoreSnapshotRequest)(nil), // 7: v1.RestoreSnapshotRequest - (*ListSnapshotFilesRequest)(nil), // 8: v1.ListSnapshotFilesRequest - (*ListSnapshotFilesResponse)(nil), // 9: v1.ListSnapshotFilesResponse - (*LogDataRequest)(nil), // 10: v1.LogDataRequest - (*LsEntry)(nil), // 11: v1.LsEntry - (*RunCommandRequest)(nil), // 12: v1.RunCommandRequest - (*emptypb.Empty)(nil), // 13: google.protobuf.Empty - (*Config)(nil), // 14: v1.Config - (*Repo)(nil), // 15: v1.Repo - (*types.StringValue)(nil), // 16: types.StringValue - (*types.Int64Value)(nil), // 17: types.Int64Value - (*OperationEvent)(nil), // 18: v1.OperationEvent - (*OperationList)(nil), // 19: v1.OperationList - (*ResticSnapshotList)(nil), // 20: v1.ResticSnapshotList - (*types.BytesValue)(nil), // 21: types.BytesValue - (*types.StringList)(nil), // 22: types.StringList + (DoRepoTaskRequest_Task)(0), // 0: v1.DoRepoTaskRequest.Task + (*OpSelector)(nil), // 1: v1.OpSelector + (*DoRepoTaskRequest)(nil), // 2: v1.DoRepoTaskRequest + (*ClearHistoryRequest)(nil), // 3: v1.ClearHistoryRequest + (*ForgetRequest)(nil), // 4: v1.ForgetRequest + (*ListSnapshotsRequest)(nil), // 5: v1.ListSnapshotsRequest + (*GetOperationsRequest)(nil), // 6: v1.GetOperationsRequest + (*RestoreSnapshotRequest)(nil), // 7: v1.RestoreSnapshotRequest + (*ListSnapshotFilesRequest)(nil), // 8: v1.ListSnapshotFilesRequest + (*ListSnapshotFilesResponse)(nil), // 9: v1.ListSnapshotFilesResponse + (*LogDataRequest)(nil), // 10: v1.LogDataRequest + (*LsEntry)(nil), // 11: v1.LsEntry + (*RunCommandRequest)(nil), // 12: v1.RunCommandRequest + (*SummaryDashboardResponse)(nil), // 13: v1.SummaryDashboardResponse + (*SummaryDashboardResponse_Summary)(nil), // 14: v1.SummaryDashboardResponse.Summary + (*SummaryDashboardResponse_BackupChart)(nil), // 15: v1.SummaryDashboardResponse.BackupChart + (OperationStatus)(0), // 16: v1.OperationStatus + (*emptypb.Empty)(nil), // 17: google.protobuf.Empty + (*Config)(nil), // 18: v1.Config + (*Repo)(nil), // 19: v1.Repo + (*types.StringValue)(nil), // 20: types.StringValue + (*types.Int64Value)(nil), // 21: types.Int64Value + (*OperationEvent)(nil), // 22: v1.OperationEvent + (*OperationList)(nil), // 23: v1.OperationList + (*ResticSnapshotList)(nil), // 24: v1.ResticSnapshotList + (*types.BytesValue)(nil), // 25: types.BytesValue + (*types.StringList)(nil), // 26: types.StringList } var file_v1_service_proto_depIdxs = []int32{ 0, // 0: v1.DoRepoTaskRequest.task:type_name -> v1.DoRepoTaskRequest.Task 1, // 1: v1.ClearHistoryRequest.selector:type_name -> v1.OpSelector 1, // 2: v1.GetOperationsRequest.selector:type_name -> v1.OpSelector 11, // 3: v1.ListSnapshotFilesResponse.entries:type_name -> v1.LsEntry - 13, // 4: v1.Backrest.GetConfig:input_type -> google.protobuf.Empty - 14, // 5: v1.Backrest.SetConfig:input_type -> v1.Config - 15, // 6: v1.Backrest.AddRepo:input_type -> v1.Repo - 13, // 7: v1.Backrest.GetOperationEvents:input_type -> google.protobuf.Empty - 6, // 8: v1.Backrest.GetOperations:input_type -> v1.GetOperationsRequest - 5, // 9: v1.Backrest.ListSnapshots:input_type -> v1.ListSnapshotsRequest - 8, // 10: v1.Backrest.ListSnapshotFiles:input_type -> v1.ListSnapshotFilesRequest - 16, // 11: v1.Backrest.Backup:input_type -> types.StringValue - 2, // 12: v1.Backrest.DoRepoTask:input_type -> v1.DoRepoTaskRequest - 4, // 13: v1.Backrest.Forget:input_type -> v1.ForgetRequest - 7, // 14: v1.Backrest.Restore:input_type -> v1.RestoreSnapshotRequest - 17, // 15: v1.Backrest.Cancel:input_type -> types.Int64Value - 10, // 16: v1.Backrest.GetLogs:input_type -> v1.LogDataRequest - 12, // 17: v1.Backrest.RunCommand:input_type -> v1.RunCommandRequest - 17, // 18: v1.Backrest.GetDownloadURL:input_type -> types.Int64Value - 3, // 19: v1.Backrest.ClearHistory:input_type -> v1.ClearHistoryRequest - 16, // 20: v1.Backrest.PathAutocomplete:input_type -> types.StringValue - 14, // 21: v1.Backrest.GetConfig:output_type -> v1.Config - 14, // 22: v1.Backrest.SetConfig:output_type -> v1.Config - 14, // 23: v1.Backrest.AddRepo:output_type -> v1.Config - 18, // 24: v1.Backrest.GetOperationEvents:output_type -> v1.OperationEvent - 19, // 25: v1.Backrest.GetOperations:output_type -> v1.OperationList - 20, // 26: v1.Backrest.ListSnapshots:output_type -> v1.ResticSnapshotList - 9, // 27: v1.Backrest.ListSnapshotFiles:output_type -> v1.ListSnapshotFilesResponse - 13, // 28: v1.Backrest.Backup:output_type -> google.protobuf.Empty - 13, // 29: v1.Backrest.DoRepoTask:output_type -> google.protobuf.Empty - 13, // 30: v1.Backrest.Forget:output_type -> google.protobuf.Empty - 13, // 31: v1.Backrest.Restore:output_type -> google.protobuf.Empty - 13, // 32: v1.Backrest.Cancel:output_type -> google.protobuf.Empty - 21, // 33: v1.Backrest.GetLogs:output_type -> types.BytesValue - 17, // 34: v1.Backrest.RunCommand:output_type -> types.Int64Value - 16, // 35: v1.Backrest.GetDownloadURL:output_type -> types.StringValue - 13, // 36: v1.Backrest.ClearHistory:output_type -> google.protobuf.Empty - 22, // 37: v1.Backrest.PathAutocomplete:output_type -> types.StringList - 21, // [21:38] is the sub-list for method output_type - 4, // [4:21] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 14, // 4: v1.SummaryDashboardResponse.repo_summaries:type_name -> v1.SummaryDashboardResponse.Summary + 14, // 5: v1.SummaryDashboardResponse.plan_summaries:type_name -> v1.SummaryDashboardResponse.Summary + 15, // 6: v1.SummaryDashboardResponse.Summary.recent_backups:type_name -> v1.SummaryDashboardResponse.BackupChart + 16, // 7: v1.SummaryDashboardResponse.BackupChart.status:type_name -> v1.OperationStatus + 17, // 8: v1.Backrest.GetConfig:input_type -> google.protobuf.Empty + 18, // 9: v1.Backrest.SetConfig:input_type -> v1.Config + 19, // 10: v1.Backrest.AddRepo:input_type -> v1.Repo + 17, // 11: v1.Backrest.GetOperationEvents:input_type -> google.protobuf.Empty + 6, // 12: v1.Backrest.GetOperations:input_type -> v1.GetOperationsRequest + 5, // 13: v1.Backrest.ListSnapshots:input_type -> v1.ListSnapshotsRequest + 8, // 14: v1.Backrest.ListSnapshotFiles:input_type -> v1.ListSnapshotFilesRequest + 20, // 15: v1.Backrest.Backup:input_type -> types.StringValue + 2, // 16: v1.Backrest.DoRepoTask:input_type -> v1.DoRepoTaskRequest + 4, // 17: v1.Backrest.Forget:input_type -> v1.ForgetRequest + 7, // 18: v1.Backrest.Restore:input_type -> v1.RestoreSnapshotRequest + 21, // 19: v1.Backrest.Cancel:input_type -> types.Int64Value + 10, // 20: v1.Backrest.GetLogs:input_type -> v1.LogDataRequest + 12, // 21: v1.Backrest.RunCommand:input_type -> v1.RunCommandRequest + 21, // 22: v1.Backrest.GetDownloadURL:input_type -> types.Int64Value + 3, // 23: v1.Backrest.ClearHistory:input_type -> v1.ClearHistoryRequest + 20, // 24: v1.Backrest.PathAutocomplete:input_type -> types.StringValue + 17, // 25: v1.Backrest.GetSummaryDashboard:input_type -> google.protobuf.Empty + 18, // 26: v1.Backrest.GetConfig:output_type -> v1.Config + 18, // 27: v1.Backrest.SetConfig:output_type -> v1.Config + 18, // 28: v1.Backrest.AddRepo:output_type -> v1.Config + 22, // 29: v1.Backrest.GetOperationEvents:output_type -> v1.OperationEvent + 23, // 30: v1.Backrest.GetOperations:output_type -> v1.OperationList + 24, // 31: v1.Backrest.ListSnapshots:output_type -> v1.ResticSnapshotList + 9, // 32: v1.Backrest.ListSnapshotFiles:output_type -> v1.ListSnapshotFilesResponse + 17, // 33: v1.Backrest.Backup:output_type -> google.protobuf.Empty + 17, // 34: v1.Backrest.DoRepoTask:output_type -> google.protobuf.Empty + 17, // 35: v1.Backrest.Forget:output_type -> google.protobuf.Empty + 17, // 36: v1.Backrest.Restore:output_type -> google.protobuf.Empty + 17, // 37: v1.Backrest.Cancel:output_type -> google.protobuf.Empty + 25, // 38: v1.Backrest.GetLogs:output_type -> types.BytesValue + 21, // 39: v1.Backrest.RunCommand:output_type -> types.Int64Value + 20, // 40: v1.Backrest.GetDownloadURL:output_type -> types.StringValue + 17, // 41: v1.Backrest.ClearHistory:output_type -> google.protobuf.Empty + 26, // 42: v1.Backrest.PathAutocomplete:output_type -> types.StringList + 13, // 43: v1.Backrest.GetSummaryDashboard:output_type -> v1.SummaryDashboardResponse + 26, // [26:44] is the sub-list for method output_type + 8, // [8:26] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_v1_service_proto_init() } @@ -1269,6 +1624,42 @@ func file_v1_service_proto_init() { return nil } } + file_v1_service_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SummaryDashboardResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_service_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SummaryDashboardResponse_Summary); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_service_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SummaryDashboardResponse_BackupChart); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1276,7 +1667,7 @@ func file_v1_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_service_proto_rawDesc, NumEnums: 1, - NumMessages: 12, + NumMessages: 15, NumExtensions: 0, NumServices: 1, }, diff --git a/gen/go/v1/service_grpc.pb.go b/gen/go/v1/service_grpc.pb.go index 617160ba..5f4f6196 100644 --- a/gen/go/v1/service_grpc.pb.go +++ b/gen/go/v1/service_grpc.pb.go @@ -21,23 +21,24 @@ import ( const _ = grpc.SupportPackageIsVersion7 const ( - Backrest_GetConfig_FullMethodName = "/v1.Backrest/GetConfig" - Backrest_SetConfig_FullMethodName = "/v1.Backrest/SetConfig" - Backrest_AddRepo_FullMethodName = "/v1.Backrest/AddRepo" - Backrest_GetOperationEvents_FullMethodName = "/v1.Backrest/GetOperationEvents" - Backrest_GetOperations_FullMethodName = "/v1.Backrest/GetOperations" - Backrest_ListSnapshots_FullMethodName = "/v1.Backrest/ListSnapshots" - Backrest_ListSnapshotFiles_FullMethodName = "/v1.Backrest/ListSnapshotFiles" - Backrest_Backup_FullMethodName = "/v1.Backrest/Backup" - Backrest_DoRepoTask_FullMethodName = "/v1.Backrest/DoRepoTask" - Backrest_Forget_FullMethodName = "/v1.Backrest/Forget" - Backrest_Restore_FullMethodName = "/v1.Backrest/Restore" - Backrest_Cancel_FullMethodName = "/v1.Backrest/Cancel" - Backrest_GetLogs_FullMethodName = "/v1.Backrest/GetLogs" - Backrest_RunCommand_FullMethodName = "/v1.Backrest/RunCommand" - Backrest_GetDownloadURL_FullMethodName = "/v1.Backrest/GetDownloadURL" - Backrest_ClearHistory_FullMethodName = "/v1.Backrest/ClearHistory" - Backrest_PathAutocomplete_FullMethodName = "/v1.Backrest/PathAutocomplete" + Backrest_GetConfig_FullMethodName = "/v1.Backrest/GetConfig" + Backrest_SetConfig_FullMethodName = "/v1.Backrest/SetConfig" + Backrest_AddRepo_FullMethodName = "/v1.Backrest/AddRepo" + Backrest_GetOperationEvents_FullMethodName = "/v1.Backrest/GetOperationEvents" + Backrest_GetOperations_FullMethodName = "/v1.Backrest/GetOperations" + Backrest_ListSnapshots_FullMethodName = "/v1.Backrest/ListSnapshots" + Backrest_ListSnapshotFiles_FullMethodName = "/v1.Backrest/ListSnapshotFiles" + Backrest_Backup_FullMethodName = "/v1.Backrest/Backup" + Backrest_DoRepoTask_FullMethodName = "/v1.Backrest/DoRepoTask" + Backrest_Forget_FullMethodName = "/v1.Backrest/Forget" + Backrest_Restore_FullMethodName = "/v1.Backrest/Restore" + Backrest_Cancel_FullMethodName = "/v1.Backrest/Cancel" + Backrest_GetLogs_FullMethodName = "/v1.Backrest/GetLogs" + Backrest_RunCommand_FullMethodName = "/v1.Backrest/RunCommand" + Backrest_GetDownloadURL_FullMethodName = "/v1.Backrest/GetDownloadURL" + Backrest_ClearHistory_FullMethodName = "/v1.Backrest/ClearHistory" + Backrest_PathAutocomplete_FullMethodName = "/v1.Backrest/PathAutocomplete" + Backrest_GetSummaryDashboard_FullMethodName = "/v1.Backrest/GetSummaryDashboard" ) // BackrestClient is the client API for Backrest service. @@ -71,6 +72,8 @@ type BackrestClient interface { ClearHistory(ctx context.Context, in *ClearHistoryRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) // PathAutocomplete provides path autocompletion options for a given filesystem path. PathAutocomplete(ctx context.Context, in *types.StringValue, opts ...grpc.CallOption) (*types.StringList, error) + // GetSummaryDashboard returns data for the dashboard view. + GetSummaryDashboard(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SummaryDashboardResponse, error) } type backrestClient struct { @@ -280,6 +283,15 @@ func (c *backrestClient) PathAutocomplete(ctx context.Context, in *types.StringV return out, nil } +func (c *backrestClient) GetSummaryDashboard(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SummaryDashboardResponse, error) { + out := new(SummaryDashboardResponse) + err := c.cc.Invoke(ctx, Backrest_GetSummaryDashboard_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // BackrestServer is the server API for Backrest service. // All implementations must embed UnimplementedBackrestServer // for forward compatibility @@ -311,6 +323,8 @@ type BackrestServer interface { ClearHistory(context.Context, *ClearHistoryRequest) (*emptypb.Empty, error) // PathAutocomplete provides path autocompletion options for a given filesystem path. PathAutocomplete(context.Context, *types.StringValue) (*types.StringList, error) + // GetSummaryDashboard returns data for the dashboard view. + GetSummaryDashboard(context.Context, *emptypb.Empty) (*SummaryDashboardResponse, error) mustEmbedUnimplementedBackrestServer() } @@ -369,6 +383,9 @@ func (UnimplementedBackrestServer) ClearHistory(context.Context, *ClearHistoryRe func (UnimplementedBackrestServer) PathAutocomplete(context.Context, *types.StringValue) (*types.StringList, error) { return nil, status.Errorf(codes.Unimplemented, "method PathAutocomplete not implemented") } +func (UnimplementedBackrestServer) GetSummaryDashboard(context.Context, *emptypb.Empty) (*SummaryDashboardResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetSummaryDashboard not implemented") +} func (UnimplementedBackrestServer) mustEmbedUnimplementedBackrestServer() {} // UnsafeBackrestServer may be embedded to opt out of forward compatibility for this service. @@ -694,6 +711,24 @@ func _Backrest_PathAutocomplete_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _Backrest_GetSummaryDashboard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackrestServer).GetSummaryDashboard(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Backrest_GetSummaryDashboard_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackrestServer).GetSummaryDashboard(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + // Backrest_ServiceDesc is the grpc.ServiceDesc for Backrest service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -761,6 +796,10 @@ var Backrest_ServiceDesc = grpc.ServiceDesc{ MethodName: "PathAutocomplete", Handler: _Backrest_PathAutocomplete_Handler, }, + { + MethodName: "GetSummaryDashboard", + Handler: _Backrest_GetSummaryDashboard_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/gen/go/v1/v1connect/service.connect.go b/gen/go/v1/v1connect/service.connect.go index c5d26790..c2320107 100644 --- a/gen/go/v1/v1connect/service.connect.go +++ b/gen/go/v1/v1connect/service.connect.go @@ -72,28 +72,32 @@ const ( // BackrestPathAutocompleteProcedure is the fully-qualified name of the Backrest's PathAutocomplete // RPC. BackrestPathAutocompleteProcedure = "/v1.Backrest/PathAutocomplete" + // BackrestGetSummaryDashboardProcedure is the fully-qualified name of the Backrest's + // GetSummaryDashboard RPC. + BackrestGetSummaryDashboardProcedure = "/v1.Backrest/GetSummaryDashboard" ) // These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. var ( - backrestServiceDescriptor = v1.File_v1_service_proto.Services().ByName("Backrest") - backrestGetConfigMethodDescriptor = backrestServiceDescriptor.Methods().ByName("GetConfig") - backrestSetConfigMethodDescriptor = backrestServiceDescriptor.Methods().ByName("SetConfig") - backrestAddRepoMethodDescriptor = backrestServiceDescriptor.Methods().ByName("AddRepo") - backrestGetOperationEventsMethodDescriptor = backrestServiceDescriptor.Methods().ByName("GetOperationEvents") - backrestGetOperationsMethodDescriptor = backrestServiceDescriptor.Methods().ByName("GetOperations") - backrestListSnapshotsMethodDescriptor = backrestServiceDescriptor.Methods().ByName("ListSnapshots") - backrestListSnapshotFilesMethodDescriptor = backrestServiceDescriptor.Methods().ByName("ListSnapshotFiles") - backrestBackupMethodDescriptor = backrestServiceDescriptor.Methods().ByName("Backup") - backrestDoRepoTaskMethodDescriptor = backrestServiceDescriptor.Methods().ByName("DoRepoTask") - backrestForgetMethodDescriptor = backrestServiceDescriptor.Methods().ByName("Forget") - backrestRestoreMethodDescriptor = backrestServiceDescriptor.Methods().ByName("Restore") - backrestCancelMethodDescriptor = backrestServiceDescriptor.Methods().ByName("Cancel") - backrestGetLogsMethodDescriptor = backrestServiceDescriptor.Methods().ByName("GetLogs") - backrestRunCommandMethodDescriptor = backrestServiceDescriptor.Methods().ByName("RunCommand") - backrestGetDownloadURLMethodDescriptor = backrestServiceDescriptor.Methods().ByName("GetDownloadURL") - backrestClearHistoryMethodDescriptor = backrestServiceDescriptor.Methods().ByName("ClearHistory") - backrestPathAutocompleteMethodDescriptor = backrestServiceDescriptor.Methods().ByName("PathAutocomplete") + backrestServiceDescriptor = v1.File_v1_service_proto.Services().ByName("Backrest") + backrestGetConfigMethodDescriptor = backrestServiceDescriptor.Methods().ByName("GetConfig") + backrestSetConfigMethodDescriptor = backrestServiceDescriptor.Methods().ByName("SetConfig") + backrestAddRepoMethodDescriptor = backrestServiceDescriptor.Methods().ByName("AddRepo") + backrestGetOperationEventsMethodDescriptor = backrestServiceDescriptor.Methods().ByName("GetOperationEvents") + backrestGetOperationsMethodDescriptor = backrestServiceDescriptor.Methods().ByName("GetOperations") + backrestListSnapshotsMethodDescriptor = backrestServiceDescriptor.Methods().ByName("ListSnapshots") + backrestListSnapshotFilesMethodDescriptor = backrestServiceDescriptor.Methods().ByName("ListSnapshotFiles") + backrestBackupMethodDescriptor = backrestServiceDescriptor.Methods().ByName("Backup") + backrestDoRepoTaskMethodDescriptor = backrestServiceDescriptor.Methods().ByName("DoRepoTask") + backrestForgetMethodDescriptor = backrestServiceDescriptor.Methods().ByName("Forget") + backrestRestoreMethodDescriptor = backrestServiceDescriptor.Methods().ByName("Restore") + backrestCancelMethodDescriptor = backrestServiceDescriptor.Methods().ByName("Cancel") + backrestGetLogsMethodDescriptor = backrestServiceDescriptor.Methods().ByName("GetLogs") + backrestRunCommandMethodDescriptor = backrestServiceDescriptor.Methods().ByName("RunCommand") + backrestGetDownloadURLMethodDescriptor = backrestServiceDescriptor.Methods().ByName("GetDownloadURL") + backrestClearHistoryMethodDescriptor = backrestServiceDescriptor.Methods().ByName("ClearHistory") + backrestPathAutocompleteMethodDescriptor = backrestServiceDescriptor.Methods().ByName("PathAutocomplete") + backrestGetSummaryDashboardMethodDescriptor = backrestServiceDescriptor.Methods().ByName("GetSummaryDashboard") ) // BackrestClient is a client for the v1.Backrest service. @@ -125,6 +129,8 @@ type BackrestClient interface { ClearHistory(context.Context, *connect.Request[v1.ClearHistoryRequest]) (*connect.Response[emptypb.Empty], error) // PathAutocomplete provides path autocompletion options for a given filesystem path. PathAutocomplete(context.Context, *connect.Request[types.StringValue]) (*connect.Response[types.StringList], error) + // GetSummaryDashboard returns data for the dashboard view. + GetSummaryDashboard(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.SummaryDashboardResponse], error) } // NewBackrestClient constructs a client for the v1.Backrest service. By default, it uses the @@ -239,28 +245,35 @@ func NewBackrestClient(httpClient connect.HTTPClient, baseURL string, opts ...co connect.WithSchema(backrestPathAutocompleteMethodDescriptor), connect.WithClientOptions(opts...), ), + getSummaryDashboard: connect.NewClient[emptypb.Empty, v1.SummaryDashboardResponse]( + httpClient, + baseURL+BackrestGetSummaryDashboardProcedure, + connect.WithSchema(backrestGetSummaryDashboardMethodDescriptor), + connect.WithClientOptions(opts...), + ), } } // backrestClient implements BackrestClient. type backrestClient struct { - getConfig *connect.Client[emptypb.Empty, v1.Config] - setConfig *connect.Client[v1.Config, v1.Config] - addRepo *connect.Client[v1.Repo, v1.Config] - getOperationEvents *connect.Client[emptypb.Empty, v1.OperationEvent] - getOperations *connect.Client[v1.GetOperationsRequest, v1.OperationList] - listSnapshots *connect.Client[v1.ListSnapshotsRequest, v1.ResticSnapshotList] - listSnapshotFiles *connect.Client[v1.ListSnapshotFilesRequest, v1.ListSnapshotFilesResponse] - backup *connect.Client[types.StringValue, emptypb.Empty] - doRepoTask *connect.Client[v1.DoRepoTaskRequest, emptypb.Empty] - forget *connect.Client[v1.ForgetRequest, emptypb.Empty] - restore *connect.Client[v1.RestoreSnapshotRequest, emptypb.Empty] - cancel *connect.Client[types.Int64Value, emptypb.Empty] - getLogs *connect.Client[v1.LogDataRequest, types.BytesValue] - runCommand *connect.Client[v1.RunCommandRequest, types.Int64Value] - getDownloadURL *connect.Client[types.Int64Value, types.StringValue] - clearHistory *connect.Client[v1.ClearHistoryRequest, emptypb.Empty] - pathAutocomplete *connect.Client[types.StringValue, types.StringList] + getConfig *connect.Client[emptypb.Empty, v1.Config] + setConfig *connect.Client[v1.Config, v1.Config] + addRepo *connect.Client[v1.Repo, v1.Config] + getOperationEvents *connect.Client[emptypb.Empty, v1.OperationEvent] + getOperations *connect.Client[v1.GetOperationsRequest, v1.OperationList] + listSnapshots *connect.Client[v1.ListSnapshotsRequest, v1.ResticSnapshotList] + listSnapshotFiles *connect.Client[v1.ListSnapshotFilesRequest, v1.ListSnapshotFilesResponse] + backup *connect.Client[types.StringValue, emptypb.Empty] + doRepoTask *connect.Client[v1.DoRepoTaskRequest, emptypb.Empty] + forget *connect.Client[v1.ForgetRequest, emptypb.Empty] + restore *connect.Client[v1.RestoreSnapshotRequest, emptypb.Empty] + cancel *connect.Client[types.Int64Value, emptypb.Empty] + getLogs *connect.Client[v1.LogDataRequest, types.BytesValue] + runCommand *connect.Client[v1.RunCommandRequest, types.Int64Value] + getDownloadURL *connect.Client[types.Int64Value, types.StringValue] + clearHistory *connect.Client[v1.ClearHistoryRequest, emptypb.Empty] + pathAutocomplete *connect.Client[types.StringValue, types.StringList] + getSummaryDashboard *connect.Client[emptypb.Empty, v1.SummaryDashboardResponse] } // GetConfig calls v1.Backrest.GetConfig. @@ -348,6 +361,11 @@ func (c *backrestClient) PathAutocomplete(ctx context.Context, req *connect.Requ return c.pathAutocomplete.CallUnary(ctx, req) } +// GetSummaryDashboard calls v1.Backrest.GetSummaryDashboard. +func (c *backrestClient) GetSummaryDashboard(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[v1.SummaryDashboardResponse], error) { + return c.getSummaryDashboard.CallUnary(ctx, req) +} + // BackrestHandler is an implementation of the v1.Backrest service. type BackrestHandler interface { GetConfig(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.Config], error) @@ -377,6 +395,8 @@ type BackrestHandler interface { ClearHistory(context.Context, *connect.Request[v1.ClearHistoryRequest]) (*connect.Response[emptypb.Empty], error) // PathAutocomplete provides path autocompletion options for a given filesystem path. PathAutocomplete(context.Context, *connect.Request[types.StringValue]) (*connect.Response[types.StringList], error) + // GetSummaryDashboard returns data for the dashboard view. + GetSummaryDashboard(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.SummaryDashboardResponse], error) } // NewBackrestHandler builds an HTTP handler from the service implementation. It returns the path on @@ -487,6 +507,12 @@ func NewBackrestHandler(svc BackrestHandler, opts ...connect.HandlerOption) (str connect.WithSchema(backrestPathAutocompleteMethodDescriptor), connect.WithHandlerOptions(opts...), ) + backrestGetSummaryDashboardHandler := connect.NewUnaryHandler( + BackrestGetSummaryDashboardProcedure, + svc.GetSummaryDashboard, + connect.WithSchema(backrestGetSummaryDashboardMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) return "/v1.Backrest/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case BackrestGetConfigProcedure: @@ -523,6 +549,8 @@ func NewBackrestHandler(svc BackrestHandler, opts ...connect.HandlerOption) (str backrestClearHistoryHandler.ServeHTTP(w, r) case BackrestPathAutocompleteProcedure: backrestPathAutocompleteHandler.ServeHTTP(w, r) + case BackrestGetSummaryDashboardProcedure: + backrestGetSummaryDashboardHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -599,3 +627,7 @@ func (UnimplementedBackrestHandler) ClearHistory(context.Context, *connect.Reque func (UnimplementedBackrestHandler) PathAutocomplete(context.Context, *connect.Request[types.StringValue]) (*connect.Response[types.StringList], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1.Backrest.PathAutocomplete is not implemented")) } + +func (UnimplementedBackrestHandler) GetSummaryDashboard(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.SummaryDashboardResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1.Backrest.GetSummaryDashboard is not implemented")) +} diff --git a/internal/api/backresthandler.go b/internal/api/backresthandler.go index 25c3d28c..e72da216 100644 --- a/internal/api/backresthandler.go +++ b/internal/api/backresthandler.go @@ -19,6 +19,7 @@ import ( v1 "github.com/garethgeorge/backrest/gen/go/v1" "github.com/garethgeorge/backrest/gen/go/v1/v1connect" "github.com/garethgeorge/backrest/internal/config" + "github.com/garethgeorge/backrest/internal/env" "github.com/garethgeorge/backrest/internal/logstore" "github.com/garethgeorge/backrest/internal/oplog" "github.com/garethgeorge/backrest/internal/orchestrator" @@ -590,6 +591,109 @@ func (s *BackrestHandler) PathAutocomplete(ctx context.Context, path *connect.Re return connect.NewResponse(&types.StringList{Values: paths}), nil } +func (s *BackrestHandler) GetSummaryDashboard(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[v1.SummaryDashboardResponse], error) { + config, err := s.config.Get() + if err != nil { + return nil, fmt.Errorf("failed to get config: %w", err) + } + + generateSummaryHelper := func(id string, q oplog.Query) (*v1.SummaryDashboardResponse_Summary, error) { + var backupsExamined int64 + var bytesScanned30 int64 + var bytesAdded30 int64 + var backupsFailed30 int64 + var backupsSuccess30 int64 + var backupsWarning30 int64 + var nextBackupTime int64 + backupChart := &v1.SummaryDashboardResponse_BackupChart{} + + s.oplog.Query(q, func(op *v1.Operation) error { + t := time.UnixMilli(op.UnixTimeStartMs) + + if backupOp := op.GetOperationBackup(); backupOp != nil { + if time.Since(t) > 30*24*time.Hour { + return oplog.ErrStopIteration + } else if op.GetStatus() == v1.OperationStatus_STATUS_PENDING { + nextBackupTime = op.UnixTimeStartMs + return nil + } + backupsExamined++ + + if op.Status == v1.OperationStatus_STATUS_SUCCESS { + backupsSuccess30++ + } else if op.Status == v1.OperationStatus_STATUS_ERROR { + backupsFailed30++ + } else if op.Status == v1.OperationStatus_STATUS_WARNING { + backupsWarning30++ + } + + if summary := backupOp.GetLastStatus().GetSummary(); summary != nil { + bytesScanned30 += summary.TotalBytesProcessed + bytesAdded30 += summary.DataAdded + } + + // recent backups chart + if len(backupChart.TimestampMs) < 60 { // only include the latest 90 backups in the chart + duration := op.UnixTimeEndMs - op.UnixTimeStartMs + if duration <= 1000 { + duration = 1000 + } + + backupChart.FlowId = append(backupChart.FlowId, op.FlowId) + backupChart.TimestampMs = append(backupChart.TimestampMs, op.UnixTimeStartMs) + backupChart.DurationMs = append(backupChart.DurationMs, duration) + backupChart.Status = append(backupChart.Status, op.Status) + backupChart.BytesAdded = append(backupChart.BytesAdded, backupOp.GetLastStatus().GetSummary().GetDataAdded()) + } + } + + return nil + }) + + if backupsExamined == 0 { + backupsExamined = 1 // prevent division by zero for avg calculations + } + + return &v1.SummaryDashboardResponse_Summary{ + Id: id, + BytesScannedLast_30Days: bytesScanned30, + BytesAddedLast_30Days: bytesAdded30, + BackupsFailed_30Days: backupsFailed30, + BackupsWarningLast_30Days: backupsWarning30, + BackupsSuccessLast_30Days: backupsSuccess30, + BytesScannedAvg: bytesScanned30 / backupsExamined, + BytesAddedAvg: bytesAdded30 / backupsExamined, + NextBackupTimeMs: nextBackupTime, + RecentBackups: backupChart, + }, nil + } + + response := &v1.SummaryDashboardResponse{ + ConfigPath: env.ConfigFilePath(), + DataPath: env.DataDir(), + } + + for _, repo := range config.Repos { + resp, err := generateSummaryHelper(repo.Id, oplog.Query{RepoID: repo.Id, Reversed: true, Limit: 1000}) + if err != nil { + return nil, fmt.Errorf("summary for repo %q: %w", repo.Id, err) + } + + response.RepoSummaries = append(response.RepoSummaries, resp) + } + + for _, plan := range config.Plans { + resp, err := generateSummaryHelper(plan.Id, oplog.Query{PlanID: plan.Id, Reversed: true, Limit: 1000}) + if err != nil { + return nil, fmt.Errorf("summary for plan %q: %w", plan.Id, err) + } + + response.PlanSummaries = append(response.PlanSummaries, resp) + } + + return connect.NewResponse(response), nil +} + func opSelectorToQuery(sel *v1.OpSelector) (oplog.Query, error) { if sel == nil { return oplog.Query{}, errors.New("empty selector") diff --git a/internal/api/backresthandler_test.go b/internal/api/backresthandler_test.go index 192ceea5..d3044645 100644 --- a/internal/api/backresthandler_test.go +++ b/internal/api/backresthandler_test.go @@ -619,17 +619,16 @@ func TestCancelBackup(t *testing.T) { var errgroup errgroup.Group errgroup.Go(func() error { backupReq := connect.NewRequest(&types.StringValue{Value: "test"}) - sut.handler.Backup(context.Background(), backupReq) - return nil + _, err := sut.handler.Backup(context.Background(), backupReq) + return err }) // Find the backup operation ID in the oplog var backupOpId int64 - if err := retry(t, 100, 10*time.Millisecond, func() error { + if err := retry(t, 100, 100*time.Millisecond, func() error { operations := getOperations(t, sut.oplog) for _, op := range operations { - _, ok := op.GetOp().(*v1.Operation_OperationBackup) - if ok { + if op.GetOperationBackup() != nil { backupOpId = op.Id return nil } @@ -639,23 +638,20 @@ func TestCancelBackup(t *testing.T) { t.Fatalf("Couldn't find backup operation in oplog") } - errgroup.Go(func() error { - if _, err := sut.handler.Cancel(context.Background(), connect.NewRequest(&types.Int64Value{Value: backupOpId})); err != nil { - return fmt.Errorf("Cancel() error = %v, wantErr nil", err) - } - return nil - }) - - if err := errgroup.Wait(); err != nil { - t.Fatal(err.Error()) + if _, err := sut.handler.Cancel(context.Background(), connect.NewRequest(&types.Int64Value{Value: backupOpId})); err != nil { + t.Errorf("Cancel() error = %v, wantErr nil", err) } - // Assert that the backup operation was cancelled - if slices.IndexFunc(getOperations(t, sut.oplog), func(op *v1.Operation) bool { - _, ok := op.GetOp().(*v1.Operation_OperationBackup) - return op.Status == v1.OperationStatus_STATUS_ERROR && ok - }) == -1 { - t.Fatalf("Expected a failed backup operation in the log") + if err := retry(t, 10, 1*time.Second, func() error { + if slices.IndexFunc(getOperations(t, sut.oplog), func(op *v1.Operation) bool { + _, ok := op.GetOp().(*v1.Operation_OperationBackup) + return op.Status == v1.OperationStatus_STATUS_ERROR && ok + }) == -1 { + return errors.New("backup operation not found") + } + return nil + }); err != nil { + t.Fatalf("Couldn't find failed canceled backup operation in oplog") } } diff --git a/internal/oplog/sqlitestore/sqlitestore.go b/internal/oplog/sqlitestore/sqlitestore.go index ef1574e1..d51f6e80 100644 --- a/internal/oplog/sqlitestore/sqlitestore.go +++ b/internal/oplog/sqlitestore/sqlitestore.go @@ -68,6 +68,7 @@ PRAGMA journal_mode=WAL; PRAGMA page_size=4096; CREATE TABLE IF NOT EXISTS operations ( id INTEGER PRIMARY KEY, + start_time_ms INTEGER NOT NULL, flow_id INTEGER NOT NULL, instance_id STRING NOT NULL, plan_id STRING NOT NULL, @@ -81,6 +82,7 @@ CREATE TABLE IF NOT EXISTS system_info ( CREATE INDEX IF NOT EXISTS operations_repo_id_plan_id_instance_id ON operations (repo_id, plan_id, instance_id); CREATE INDEX IF NOT EXISTS operations_snapshot_id ON operations (snapshot_id); CREATE INDEX IF NOT EXISTS operations_flow_id ON operations (flow_id); +CREATE INDEX IF NOT EXISTS operations_start_time_ms ON operations (start_time_ms); INSERT INTO system_info (version) SELECT 0 WHERE NOT EXISTS (SELECT 1 FROM system_info); @@ -179,9 +181,9 @@ func (m *SqliteStore) buildQuery(q oplog.Query, includeSelectClauses bool) (stri if includeSelectClauses { if q.Reversed { - query = append(query, " ORDER BY id DESC") + query = append(query, " ORDER BY start_time_ms DESC, id DESC") } else { - query = append(query, " ORDER BY id ASC") + query = append(query, " ORDER BY start_time_ms ASC, id ASC") } if q.Limit > 0 { @@ -195,7 +197,6 @@ func (m *SqliteStore) buildQuery(q oplog.Query, includeSelectClauses bool) (stri query = append(query, " OFFSET ?") args = append(args, q.Offset) } - } return strings.Join(query, ""), args @@ -281,7 +282,7 @@ func (m *SqliteStore) Add(op ...*v1.Operation) error { return err } - query := "INSERT INTO operations (id, flow_id, instance_id, plan_id, repo_id, snapshot_id, operation) VALUES (?, ?, ?, ?, ?, ?, ?)" + query := "INSERT INTO operations (id, start_time_ms, flow_id, instance_id, plan_id, repo_id, snapshot_id, operation) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" bytes, err := proto.Marshal(o) if err != nil { @@ -289,7 +290,7 @@ func (m *SqliteStore) Add(op ...*v1.Operation) error { } if err := sqlitex.Execute(conn, query, &sqlitex.ExecOptions{ - Args: []any{o.Id, o.FlowId, o.InstanceId, o.PlanId, o.RepoId, o.SnapshotId, bytes}, + Args: []any{o.Id, o.UnixTimeStartMs, o.FlowId, o.InstanceId, o.PlanId, o.RepoId, o.SnapshotId, bytes}, }); err != nil { if sqlite.ErrCode(err) == sqlite.ResultConstraintUnique { return fmt.Errorf("operation already exists %v: %w", o.Id, oplog.ErrExist) @@ -322,8 +323,8 @@ func (m *SqliteStore) updateInternal(conn *sqlite.Conn, op ...*v1.Operation) err if err != nil { return fmt.Errorf("marshal operation: %v", err) } - if err := sqlitex.Execute(conn, "UPDATE operations SET operation = ?, flow_id = ?, instance_id = ?, plan_id = ?, repo_id = ?, snapshot_id = ? WHERE id = ?", &sqlitex.ExecOptions{ - Args: []any{bytes, o.FlowId, o.InstanceId, o.PlanId, o.RepoId, o.SnapshotId, o.Id}, + if err := sqlitex.Execute(conn, "UPDATE operations SET operation = ?, start_time_ms = ?, flow_id = ?, instance_id = ?, plan_id = ?, repo_id = ?, snapshot_id = ? WHERE id = ?", &sqlitex.ExecOptions{ + Args: []any{bytes, o.UnixTimeStartMs, o.FlowId, o.InstanceId, o.PlanId, o.RepoId, o.SnapshotId, o.Id}, }); err != nil { return fmt.Errorf("update operation: %v", err) } diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 958fb01c..cc3768f6 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -390,6 +390,8 @@ func (o *Orchestrator) RunTask(ctx context.Context, st tasks.ScheduledTask) erro zap.S().Errorf("failed to add operation to oplog: %w", err) } } + } else { + ctx = logging.ContextWithWriter(ctx, io.Discard) // discard logs if no operation. } start := time.Now() diff --git a/internal/orchestrator/tasks/scheduling_test.go b/internal/orchestrator/tasks/scheduling_test.go index 21858178..832d7970 100644 --- a/internal/orchestrator/tasks/scheduling_test.go +++ b/internal/orchestrator/tasks/scheduling_test.go @@ -149,7 +149,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, }, wantTime: now.Add(time.Hour * 24), @@ -165,7 +166,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, }, wantTime: farFuture.Add(time.Hour * 24), @@ -181,7 +183,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, }, wantTime: now.Add(time.Hour), @@ -197,7 +200,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, }, wantTime: farFuture.Add(time.Hour), @@ -213,7 +217,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, }, wantTime: mustParseTime(t, "1970-01-02T00:00:00-08:00"), @@ -229,7 +234,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, }, wantTime: mustParseTime(t, "1970-01-02T08:00:00Z"), @@ -245,7 +251,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, }, wantTime: mustParseTime(t, "1970-01-13T00:00:00-08:00"), @@ -261,7 +268,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationCheck{ OperationCheck: &v1.OperationCheck{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, }, wantTime: now.Add(time.Hour), @@ -277,7 +285,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationCheck{ OperationCheck: &v1.OperationCheck{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, }, wantTime: now.Add(time.Hour), @@ -293,7 +302,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationCheck{ OperationCheck: &v1.OperationCheck{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, { InstanceId: "instance1", @@ -302,7 +312,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, }, wantTime: farFuture.Add(time.Hour), @@ -318,7 +329,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationPrune{ OperationPrune: &v1.OperationPrune{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, }, wantTime: now.Add(time.Hour), @@ -334,7 +346,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationPrune{ OperationPrune: &v1.OperationPrune{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, }, wantTime: now.Add(time.Hour), @@ -350,7 +363,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationPrune{ OperationPrune: &v1.OperationPrune{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, { InstanceId: "instance1", @@ -359,7 +373,8 @@ func TestScheduling(t *testing.T) { Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, }, - UnixTimeEndMs: farFuture.UnixMilli(), + UnixTimeStartMs: 1000, + UnixTimeEndMs: farFuture.UnixMilli(), }, }, wantTime: farFuture.Add(time.Hour), diff --git a/internal/protoutil/validation.go b/internal/protoutil/validation.go index 2dc639c0..0979770b 100644 --- a/internal/protoutil/validation.go +++ b/internal/protoutil/validation.go @@ -8,22 +8,34 @@ import ( "github.com/garethgeorge/backrest/pkg/restic" ) +var ( + errIDRequired = errors.New("id is required") + errFlowIDRequired = errors.New("flow_id is required") + errRepoIDRequired = errors.New("repo_id is required") + errPlanIDRequired = errors.New("plan_id is required") + errInstanceIDRequired = errors.New("instance_id is required") + errUnixTimeStartMsRequired = errors.New("unix_time_start_ms must be non-zero") +) + // ValidateOperation verifies critical properties of the operation proto. func ValidateOperation(op *v1.Operation) error { if op.Id == 0 { - return errors.New("operation.id is required") + return errIDRequired } if op.FlowId == 0 { - return errors.New("operation.flow_id is required") + return errFlowIDRequired } if op.RepoId == "" { - return errors.New("operation.repo_id is required") + return errRepoIDRequired } if op.PlanId == "" { - return errors.New("operation.plan_id is required") + return errPlanIDRequired } if op.InstanceId == "" { - return errors.New("operation.instance_id is required") + return errInstanceIDRequired + } + if op.UnixTimeStartMs == 0 { + return errUnixTimeStartMsRequired } if op.SnapshotId != "" { if err := restic.ValidateSnapshotId(op.SnapshotId); err != nil { diff --git a/proto/package-lock.json b/proto/package-lock.json new file mode 100644 index 00000000..b54b013a --- /dev/null +++ b/proto/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "proto", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/proto/v1/service.proto b/proto/v1/service.proto index 5ef79f4c..2a47bccb 100644 --- a/proto/v1/service.proto +++ b/proto/v1/service.proto @@ -55,6 +55,9 @@ service Backrest { // PathAutocomplete provides path autocompletion options for a given filesystem path. rpc PathAutocomplete (types.StringValue) returns (types.StringList) {} + + // GetSummaryDashboard returns data for the dashboard view. + rpc GetSummaryDashboard(google.protobuf.Empty) returns (SummaryDashboardResponse) {} } // OpSelector is a message that can be used to select operations e.g. by query. @@ -139,4 +142,36 @@ message LsEntry { message RunCommandRequest { string repo_id = 1; string command = 2; +} + +message SummaryDashboardResponse { + repeated Summary repo_summaries = 1; + repeated Summary plan_summaries = 2; + + string config_path = 10; + string data_path = 11; + + message Summary { + string id = 1; + int64 backups_failed_30days = 2; + int64 backups_warning_last_30days = 3; + int64 backups_success_last_30days = 4; + int64 bytes_scanned_last_30days = 5; + int64 bytes_added_last_30days = 6; + int64 total_snapshots = 7; + int64 bytes_scanned_avg = 8; + int64 bytes_added_avg = 9; + int64 next_backup_time_ms = 10; + + // Charts + BackupChart recent_backups = 11; // recent backups + } + + message BackupChart { + repeated int64 flow_id = 1; + repeated int64 timestamp_ms = 2; + repeated int64 duration_ms = 3; + repeated OperationStatus status = 4; + repeated int64 bytes_added = 5; + } } \ No newline at end of file diff --git a/webui/gen/ts/v1/operations_pb.ts b/webui/gen/ts/v1/operations_pb.ts index 3072203b..82a3aad9 100644 --- a/webui/gen/ts/v1/operations_pb.ts +++ b/webui/gen/ts/v1/operations_pb.ts @@ -653,6 +653,8 @@ export class OperationRunCommand extends Message { outputLogref = ""; /** + * not necessarily authoritative, tracked as an optimization to allow clients to avoid fetching very large outputs. + * * @generated from field: int64 output_size_bytes = 3; */ outputSizeBytes = protoInt64.zero; diff --git a/webui/gen/ts/v1/service_connect.ts b/webui/gen/ts/v1/service_connect.ts index 2a65f649..adb3f0c1 100644 --- a/webui/gen/ts/v1/service_connect.ts +++ b/webui/gen/ts/v1/service_connect.ts @@ -6,7 +6,7 @@ import { Empty, MethodKind } from "@bufbuild/protobuf"; import { Config, Repo } from "./config_pb.js"; import { OperationEvent, OperationList } from "./operations_pb.js"; -import { ClearHistoryRequest, DoRepoTaskRequest, ForgetRequest, GetOperationsRequest, ListSnapshotFilesRequest, ListSnapshotFilesResponse, ListSnapshotsRequest, LogDataRequest, RestoreSnapshotRequest, RunCommandRequest } from "./service_pb.js"; +import { ClearHistoryRequest, DoRepoTaskRequest, ForgetRequest, GetOperationsRequest, ListSnapshotFilesRequest, ListSnapshotFilesResponse, ListSnapshotsRequest, LogDataRequest, RestoreSnapshotRequest, RunCommandRequest, SummaryDashboardResponse } from "./service_pb.js"; import { ResticSnapshotList } from "./restic_pb.js"; import { BytesValue, Int64Value, StringList, StringValue } from "../types/value_pb.js"; @@ -189,6 +189,17 @@ export const Backrest = { O: StringList, kind: MethodKind.Unary, }, + /** + * GetSummaryDashboard returns data for the dashboard view. + * + * @generated from rpc v1.Backrest.GetSummaryDashboard + */ + getSummaryDashboard: { + name: "GetSummaryDashboard", + I: Empty, + O: SummaryDashboardResponse, + kind: MethodKind.Unary, + }, } } as const; diff --git a/webui/gen/ts/v1/service_pb.ts b/webui/gen/ts/v1/service_pb.ts index 487e7d05..489c1edd 100644 --- a/webui/gen/ts/v1/service_pb.ts +++ b/webui/gen/ts/v1/service_pb.ts @@ -5,6 +5,7 @@ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; import { Message, proto3, protoInt64 } from "@bufbuild/protobuf"; +import { OperationStatus } from "./operations_pb.js"; /** * OpSelector is a message that can be used to select operations e.g. by query. @@ -660,3 +661,220 @@ export class RunCommandRequest extends Message { } } +/** + * @generated from message v1.SummaryDashboardResponse + */ +export class SummaryDashboardResponse extends Message { + /** + * @generated from field: repeated v1.SummaryDashboardResponse.Summary repo_summaries = 1; + */ + repoSummaries: SummaryDashboardResponse_Summary[] = []; + + /** + * @generated from field: repeated v1.SummaryDashboardResponse.Summary plan_summaries = 2; + */ + planSummaries: SummaryDashboardResponse_Summary[] = []; + + /** + * @generated from field: string config_path = 10; + */ + configPath = ""; + + /** + * @generated from field: string data_path = 11; + */ + dataPath = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "v1.SummaryDashboardResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "repo_summaries", kind: "message", T: SummaryDashboardResponse_Summary, repeated: true }, + { no: 2, name: "plan_summaries", kind: "message", T: SummaryDashboardResponse_Summary, repeated: true }, + { no: 10, name: "config_path", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 11, name: "data_path", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): SummaryDashboardResponse { + return new SummaryDashboardResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): SummaryDashboardResponse { + return new SummaryDashboardResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): SummaryDashboardResponse { + return new SummaryDashboardResponse().fromJsonString(jsonString, options); + } + + static equals(a: SummaryDashboardResponse | PlainMessage | undefined, b: SummaryDashboardResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(SummaryDashboardResponse, a, b); + } +} + +/** + * @generated from message v1.SummaryDashboardResponse.Summary + */ +export class SummaryDashboardResponse_Summary extends Message { + /** + * @generated from field: string id = 1; + */ + id = ""; + + /** + * @generated from field: int64 backups_failed_30days = 2; + */ + backupsFailed30days = protoInt64.zero; + + /** + * @generated from field: int64 backups_warning_last_30days = 3; + */ + backupsWarningLast30days = protoInt64.zero; + + /** + * @generated from field: int64 backups_success_last_30days = 4; + */ + backupsSuccessLast30days = protoInt64.zero; + + /** + * @generated from field: int64 bytes_scanned_last_30days = 5; + */ + bytesScannedLast30days = protoInt64.zero; + + /** + * @generated from field: int64 bytes_added_last_30days = 6; + */ + bytesAddedLast30days = protoInt64.zero; + + /** + * @generated from field: int64 total_snapshots = 7; + */ + totalSnapshots = protoInt64.zero; + + /** + * @generated from field: int64 bytes_scanned_avg = 8; + */ + bytesScannedAvg = protoInt64.zero; + + /** + * @generated from field: int64 bytes_added_avg = 9; + */ + bytesAddedAvg = protoInt64.zero; + + /** + * @generated from field: int64 next_backup_time_ms = 10; + */ + nextBackupTimeMs = protoInt64.zero; + + /** + * Charts + * + * recent backups + * + * @generated from field: v1.SummaryDashboardResponse.BackupChart recent_backups = 11; + */ + recentBackups?: SummaryDashboardResponse_BackupChart; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "v1.SummaryDashboardResponse.Summary"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "backups_failed_30days", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, + { no: 3, name: "backups_warning_last_30days", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, + { no: 4, name: "backups_success_last_30days", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, + { no: 5, name: "bytes_scanned_last_30days", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, + { no: 6, name: "bytes_added_last_30days", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, + { no: 7, name: "total_snapshots", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, + { no: 8, name: "bytes_scanned_avg", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, + { no: 9, name: "bytes_added_avg", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, + { no: 10, name: "next_backup_time_ms", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, + { no: 11, name: "recent_backups", kind: "message", T: SummaryDashboardResponse_BackupChart }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): SummaryDashboardResponse_Summary { + return new SummaryDashboardResponse_Summary().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): SummaryDashboardResponse_Summary { + return new SummaryDashboardResponse_Summary().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): SummaryDashboardResponse_Summary { + return new SummaryDashboardResponse_Summary().fromJsonString(jsonString, options); + } + + static equals(a: SummaryDashboardResponse_Summary | PlainMessage | undefined, b: SummaryDashboardResponse_Summary | PlainMessage | undefined): boolean { + return proto3.util.equals(SummaryDashboardResponse_Summary, a, b); + } +} + +/** + * @generated from message v1.SummaryDashboardResponse.BackupChart + */ +export class SummaryDashboardResponse_BackupChart extends Message { + /** + * @generated from field: repeated int64 flow_id = 1; + */ + flowId: bigint[] = []; + + /** + * @generated from field: repeated int64 timestamp_ms = 2; + */ + timestampMs: bigint[] = []; + + /** + * @generated from field: repeated int64 duration_ms = 3; + */ + durationMs: bigint[] = []; + + /** + * @generated from field: repeated v1.OperationStatus status = 4; + */ + status: OperationStatus[] = []; + + /** + * @generated from field: repeated int64 bytes_added = 5; + */ + bytesAdded: bigint[] = []; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "v1.SummaryDashboardResponse.BackupChart"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "flow_id", kind: "scalar", T: 3 /* ScalarType.INT64 */, repeated: true }, + { no: 2, name: "timestamp_ms", kind: "scalar", T: 3 /* ScalarType.INT64 */, repeated: true }, + { no: 3, name: "duration_ms", kind: "scalar", T: 3 /* ScalarType.INT64 */, repeated: true }, + { no: 4, name: "status", kind: "enum", T: proto3.getEnumType(OperationStatus), repeated: true }, + { no: 5, name: "bytes_added", kind: "scalar", T: 3 /* ScalarType.INT64 */, repeated: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): SummaryDashboardResponse_BackupChart { + return new SummaryDashboardResponse_BackupChart().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): SummaryDashboardResponse_BackupChart { + return new SummaryDashboardResponse_BackupChart().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): SummaryDashboardResponse_BackupChart { + return new SummaryDashboardResponse_BackupChart().fromJsonString(jsonString, options); + } + + static equals(a: SummaryDashboardResponse_BackupChart | PlainMessage | undefined, b: SummaryDashboardResponse_BackupChart | PlainMessage | undefined): boolean { + return proto3.util.equals(SummaryDashboardResponse_BackupChart, a, b); + } +} + diff --git a/webui/package-lock.json b/webui/package-lock.json index cf3d239c..e41d02e1 100644 --- a/webui/package-lock.json +++ b/webui/package-lock.json @@ -17,7 +17,7 @@ "@types/node": "^20.9.0", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", - "antd": "^5.11.1", + "antd": "^5.21.3", "buffer": "^6.0.3", "lodash": "^4.17.21", "parcel": "^2.10.2", @@ -38,17 +38,17 @@ } }, "node_modules/@ant-design/colors": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.0.2.tgz", - "integrity": "sha512-7KJkhTiPiLHSu+LmMJnehfJ6242OCxSlR3xHVBecYxnMW8MS/878NXct1GqYARyL59fyeFdKRxXTfvR9SnDgJg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.1.0.tgz", + "integrity": "sha512-MMoDGWn1y9LdQJQSHiCC20x3uZ3CwQnv9QMz6pCmJOrqdgM9YxsoVVY0wtrdXbmfSgnV0KNk6zi09NAhMR2jvg==", "dependencies": { "@ctrl/tinycolor": "^3.6.1" } }, "node_modules/@ant-design/cssinjs": { - "version": "1.18.4", - "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.18.4.tgz", - "integrity": "sha512-IrUAOj5TYuMG556C9gdbFuOrigyhzhU5ZYpWb3gYTxAwymVqRbvLzFCZg6OsjLBR6GhzcxYF3AhxKmjB+rA2xA==", + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.21.1.tgz", + "integrity": "sha512-tyWnlK+XH7Bumd0byfbCiZNK43HEubMoCcu9VxwsAwiHdHTgWa+tMN0/yvxa+e8EzuFP1WdUNNPclRpVtD33lg==", "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", @@ -56,21 +56,46 @@ "classnames": "^2.3.1", "csstype": "^3.1.3", "rc-util": "^5.35.0", - "stylis": "^4.0.13" + "stylis": "^4.3.3" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.1.tgz", + "integrity": "sha512-2HAiyGGGnM0es40SxdszeQAU5iWp41wBIInq+ONTCKjlSKOrzQfnw4JDtB8IBmqE6tQaEKwmzTP2LGdt5DSwYQ==", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, "node_modules/@ant-design/icons": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.3.0.tgz", - "integrity": "sha512-69FgBsIkeCjw72ZU3fJpqjhmLCPrzKGEllbrAZK7MUdt1BrKsyG6A8YDCBPKea27UQ0tRXi33PcjR4tp/tEXMg==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.5.1.tgz", + "integrity": "sha512-0UrM02MA2iDIgvLatWrj6YTCYe0F/cwXvVE0E2SqGrL7PZireQwgEKTKBisWpZyal5eXZLvuM98kju6YtYne8w==", "dependencies": { "@ant-design/colors": "^7.0.0", "@ant-design/icons-svg": "^4.4.0", - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.24.8", "classnames": "^2.2.6", "rc-util": "^5.31.1" }, @@ -88,9 +113,9 @@ "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==" }, "node_modules/@ant-design/react-slick": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.0.2.tgz", - "integrity": "sha512-Wj8onxL/T8KQLFFiCA4t8eIRGpRR+UPgOdac2sYzonv+i0n3kXHmvHLLiOYL655DQx2Umii9Y9nNgL7ssu5haQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", "dependencies": { "@babel/runtime": "^7.10.4", "classnames": "^2.2.5", @@ -248,9 +273,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2021,13 +2046,24 @@ "node": ">=14" } }, + "node_modules/@rc-component/async-validator": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", + "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, "node_modules/@rc-component/color-picker": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-1.5.2.tgz", - "integrity": "sha512-YJXujYzYFAEtlXJXy0yJUhwzUWPTcniBZto+wZ/vnACmFnUTNR7dH+NOeqSwMMsssh74e9H5Jfpr5LAH2PYqUw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", "dependencies": { + "@ant-design/fast-color": "^2.0.6", "@babel/runtime": "^7.23.6", - "@ctrl/tinycolor": "^3.6.1", "classnames": "^2.2.6", "rc-util": "^5.38.1" }, @@ -2094,14 +2130,31 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@rc-component/qrcode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.0.0.tgz", + "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, "node_modules/@rc-component/tour": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.12.3.tgz", - "integrity": "sha512-U4mf1FiUxGCwrX4ed8op77Y8VKur+8Y/61ylxtqGbcSoh1EBC7bWd/DkLu0ClTUrKZInqEi1FL7YgFtnT90vHA==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", "dependencies": { "@babel/runtime": "^7.18.0", "@rc-component/portal": "^1.0.0-9", - "@rc-component/trigger": "^1.3.6", + "@rc-component/trigger": "^2.0.0", "classnames": "^2.3.2", "rc-util": "^5.24.4" }, @@ -2114,9 +2167,9 @@ } }, "node_modules/@rc-component/trigger": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-1.18.3.tgz", - "integrity": "sha512-Ksr25pXreYe1gX6ayZ1jLrOrl9OAUHUqnuhEx6MeHnNa1zVM5Y2Aj3Q35UrER0ns8D2cJYtmJtVli+i+4eKrvA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.3.tgz", + "integrity": "sha512-X1oFIpKoXAMXNDYCviOmTfuNuYxE4h5laBsyCqVAVMjNHxoF3/uiyA7XdegK1XbCvBbCZ6P6byWrEoDRpKL8+A==", "dependencies": { "@babel/runtime": "^7.23.2", "@rc-component/portal": "^1.1.0", @@ -2479,57 +2532,59 @@ } }, "node_modules/antd": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/antd/-/antd-5.14.1.tgz", - "integrity": "sha512-P0Bwt9NKSZqnEJ0QAyAb13ay34FjOKsz+KEp/ts+feYsynhUxF7/Ay6d1jS6ZcNpcs+JWTlLKO59YFZ3tX07wQ==", - "dependencies": { - "@ant-design/colors": "^7.0.2", - "@ant-design/cssinjs": "^1.18.4", - "@ant-design/icons": "^5.3.0", - "@ant-design/react-slick": "~1.0.2", + "version": "5.21.3", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.21.3.tgz", + "integrity": "sha512-Yby3gU6jfuvhNFRPsrHB4Yc/G3LHLNHHy0kShwNmmZf1QTCiW5TmqP3DT5m/NHbJsTgEwJpwo3AaOWo+KQyEjw==", + "dependencies": { + "@ant-design/colors": "^7.1.0", + "@ant-design/cssinjs": "^1.21.1", + "@ant-design/cssinjs-utils": "^1.1.0", + "@ant-design/icons": "^5.5.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.25.6", "@ctrl/tinycolor": "^3.6.1", - "@rc-component/color-picker": "~1.5.1", + "@rc-component/color-picker": "~2.0.1", "@rc-component/mutate-observer": "^1.1.0", - "@rc-component/tour": "~1.12.3", - "@rc-component/trigger": "^1.18.3", + "@rc-component/qrcode": "~1.0.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.2.3", "classnames": "^2.5.1", "copy-to-clipboard": "^3.3.3", - "dayjs": "^1.11.10", - "qrcode.react": "^3.1.0", - "rc-cascader": "~3.21.2", - "rc-checkbox": "~3.1.0", - "rc-collapse": "~3.7.2", - "rc-dialog": "~9.3.4", - "rc-drawer": "~7.0.0", - "rc-dropdown": "~4.1.0", - "rc-field-form": "~1.41.0", - "rc-image": "~7.5.1", - "rc-input": "~1.4.3", - "rc-input-number": "~9.0.0", - "rc-mentions": "~2.10.1", - "rc-menu": "~9.12.4", - "rc-motion": "^2.9.0", - "rc-notification": "~5.3.0", - "rc-pagination": "~4.0.4", - "rc-picker": "~4.1.1", - "rc-progress": "~3.5.1", - "rc-rate": "~2.12.0", + "dayjs": "^1.11.11", + "rc-cascader": "~3.28.1", + "rc-checkbox": "~3.3.0", + "rc-collapse": "~3.8.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.2.0", + "rc-dropdown": "~4.2.0", + "rc-field-form": "~2.4.0", + "rc-image": "~7.11.0", + "rc-input": "~1.6.3", + "rc-input-number": "~9.2.0", + "rc-mentions": "~2.16.1", + "rc-menu": "~9.15.1", + "rc-motion": "^2.9.3", + "rc-notification": "~5.6.2", + "rc-pagination": "~4.3.0", + "rc-picker": "~4.6.15", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.0", "rc-resize-observer": "^1.4.0", - "rc-segmented": "~2.3.0", - "rc-select": "~14.11.0", - "rc-slider": "~10.5.0", + "rc-segmented": "~2.5.0", + "rc-select": "~14.15.2", + "rc-slider": "~11.1.7", "rc-steps": "~6.0.1", "rc-switch": "~4.1.0", - "rc-table": "~7.39.0", - "rc-tabs": "~14.0.0", - "rc-textarea": "~1.6.3", - "rc-tooltip": "~6.1.3", - "rc-tree": "~5.8.5", - "rc-tree-select": "~5.17.0", - "rc-upload": "~4.5.2", - "rc-util": "^5.38.1", + "rc-table": "~7.47.5", + "rc-tabs": "~15.3.0", + "rc-textarea": "~1.8.2", + "rc-tooltip": "~6.2.1", + "rc-tree": "~5.9.0", + "rc-tree-select": "~5.23.0", + "rc-upload": "~4.8.1", + "rc-util": "^5.43.0", "scroll-into-view-if-needed": "^3.1.0", - "throttle-debounce": "^5.0.0" + "throttle-debounce": "^5.0.2" }, "funding": { "type": "opencollective", @@ -2563,11 +2618,6 @@ "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==" }, - "node_modules/async-validator": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", - "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3142,9 +3192,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" }, "node_modules/decimal.js-light": { "version": "2.5.1", @@ -3955,16 +4005,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "optional": true, - "peer": true, - "engines": { - "node": "*" - } - }, "node_modules/msgpackr": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.1.tgz", @@ -4256,24 +4296,16 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/qrcode.react": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-3.1.0.tgz", - "integrity": "sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/rc-cascader": { - "version": "3.21.2", - "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.21.2.tgz", - "integrity": "sha512-J7GozpgsLaOtzfIHFJFuh4oFY0ePb1w10twqK6is3pAkqHkca/PsokbDr822KIRZ8/CK8CqevxohuPDVZ1RO/A==", + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.28.1.tgz", + "integrity": "sha512-9+8oHIMWVLHxuaapDiqFNmD9KSyKN/P4bo9x/MBuDbyTqP8f2/POmmZxdXWBO3yq/uE3pKyQCXYNUxrNfHRv2A==", "dependencies": { "@babel/runtime": "^7.12.5", "array-tree-filter": "^2.1.0", "classnames": "^2.3.1", - "rc-select": "~14.11.0", - "rc-tree": "~5.8.1", + "rc-select": "~14.15.0", + "rc-tree": "~5.9.0", "rc-util": "^5.37.0" }, "peerDependencies": { @@ -4282,9 +4314,9 @@ } }, "node_modules/rc-checkbox": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.1.0.tgz", - "integrity": "sha512-PAwpJFnBa3Ei+5pyqMMXdcKYKNBMS+TvSDiLdDnARnMJHC8ESxwPfm4Ao1gJiKtWLdmGfigascnCpwrHFgoOBQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.3.0.tgz", + "integrity": "sha512-Ih3ZaAcoAiFKJjifzwsGiT/f/quIkxJoklW4yKGho14Olulwn8gN7hOBve0/WGDg5o/l/5mL0w7ff7/YGvefVw==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.3.2", @@ -4296,9 +4328,9 @@ } }, "node_modules/rc-collapse": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.7.2.tgz", - "integrity": "sha512-ZRw6ipDyOnfLFySxAiCMdbHtb5ePAsB9mT17PA6y1mRD/W6KHRaZeb5qK/X9xDV1CqgyxMpzw0VdS74PCcUk4A==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.8.0.tgz", + "integrity": "sha512-YVBkssrKPBG09TGfcWWGj8zJBYD9G3XuTy89t5iUmSXrIXEAnO1M+qjUxRW6b4Qi0+wNWG6MHJF/+US+nmIlzA==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -4311,9 +4343,9 @@ } }, "node_modules/rc-dialog": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.3.4.tgz", - "integrity": "sha512-975X3018GhR+EjZFbxA2Z57SX5rnu0G0/OxFgMMvZK4/hQWEm3MHaNvP4wXpxYDoJsp+xUvVW+GB9CMMCm81jA==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/portal": "^1.0.0-8", @@ -4327,15 +4359,15 @@ } }, "node_modules/rc-drawer": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.0.0.tgz", - "integrity": "sha512-ePcS4KtQnn57bCbVXazHN2iC8nTPCXlWEIA/Pft87Pd9U7ZeDkdRzG47jWG2/TAFXFlFltRAMcslqmUM8NPCGA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.2.0.tgz", + "integrity": "sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==", "dependencies": { - "@babel/runtime": "^7.10.1", + "@babel/runtime": "^7.23.9", "@rc-component/portal": "^1.1.1", "classnames": "^2.2.6", "rc-motion": "^2.6.1", - "rc-util": "^5.36.0" + "rc-util": "^5.38.1" }, "peerDependencies": { "react": ">=16.9.0", @@ -4343,12 +4375,12 @@ } }, "node_modules/rc-dropdown": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.1.0.tgz", - "integrity": "sha512-VZjMunpBdlVzYpEdJSaV7WM7O0jf8uyDjirxXLZRNZ+tAC+NzD3PXPEtliFwGzVwBBdCmGuSqiS9DWcOLxQ9tw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.0.tgz", + "integrity": "sha512-odM8Ove+gSh0zU27DUj5cG1gNKg7mLWBYzB5E4nNLrLwBmYEgYP43vHKDGOVZcJSVElQBI0+jTQgjnq0NfLjng==", "dependencies": { "@babel/runtime": "^7.18.3", - "@rc-component/trigger": "^1.7.0", + "@rc-component/trigger": "^2.0.0", "classnames": "^2.2.6", "rc-util": "^5.17.0" }, @@ -4358,12 +4390,12 @@ } }, "node_modules/rc-field-form": { - "version": "1.41.0", - "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.41.0.tgz", - "integrity": "sha512-k9AS0wmxfJfusWDP/YXWTpteDNaQ4isJx9UKxx4/e8Dub4spFeZ54/EuN2sYrMRID/+hUznPgVZeg+Gf7XSYCw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.4.0.tgz", + "integrity": "sha512-XZ/lF9iqf9HXApIHQHqzJK5v2w4mkUMsVqAzOyWVzoiwwXEavY6Tpuw7HavgzIoD+huVff4JghSGcgEfX6eycg==", "dependencies": { "@babel/runtime": "^7.18.0", - "async-validator": "^4.1.0", + "@rc-component/async-validator": "^5.0.3", "rc-util": "^5.32.2" }, "engines": { @@ -4375,14 +4407,14 @@ } }, "node_modules/rc-image": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.5.1.tgz", - "integrity": "sha512-Z9loECh92SQp0nSipc0MBuf5+yVC05H/pzC+Nf8xw1BKDFUJzUeehYBjaWlxly8VGBZJcTHYri61Fz9ng1G3Ag==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.11.0.tgz", + "integrity": "sha512-aZkTEZXqeqfPZtnSdNUnKQA0N/3MbgR7nUnZ+/4MfSFWPFHZau4p5r5ShaI0KPEMnNjv4kijSCFq/9wtJpwykw==", "dependencies": { "@babel/runtime": "^7.11.2", "@rc-component/portal": "^1.0.2", "classnames": "^2.2.6", - "rc-dialog": "~9.3.4", + "rc-dialog": "~9.6.0", "rc-motion": "^2.6.2", "rc-util": "^5.34.1" }, @@ -4392,9 +4424,9 @@ } }, "node_modules/rc-input": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.4.3.tgz", - "integrity": "sha512-aHyQUAIRmTlOnvk5EcNqEpJ+XMtfMpYRAJayIlJfsvvH9cAKUWboh4egm23vgMA7E+c/qm4BZcnrDcA960GC1w==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.6.3.tgz", + "integrity": "sha512-wI4NzuqBS8vvKr8cljsvnTUqItMfG1QbJoxovCgL+DX4eVUcHIjVwharwevIxyy7H/jbLryh+K7ysnJr23aWIA==", "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", @@ -4406,15 +4438,15 @@ } }, "node_modules/rc-input-number": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.0.0.tgz", - "integrity": "sha512-RfcDBDdWFFetouWFXBA+WPEC8LzBXyngr9b+yTLVIygfFu7HiLRGn/s/v9wwno94X7KFvnb28FNynMGj9XJlDQ==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.2.0.tgz", + "integrity": "sha512-5XZFhBCV5f9UQ62AZ2hFbEY8iZT/dm23Q1kAg0H8EvOgD3UDbYYJAayoVIkM3lQaCqYAW5gV0yV3vjw1XtzWHg==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/mini-decimal": "^1.0.1", "classnames": "^2.2.5", - "rc-input": "~1.4.0", - "rc-util": "^5.28.0" + "rc-input": "~1.6.0", + "rc-util": "^5.40.1" }, "peerDependencies": { "react": ">=16.9.0", @@ -4422,16 +4454,16 @@ } }, "node_modules/rc-mentions": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.10.1.tgz", - "integrity": "sha512-72qsEcr/7su+a07ndJ1j8rI9n0Ka/ngWOLYnWMMv0p2mi/5zPwPrEDTt6Uqpe8FWjWhueDJx/vzunL6IdKDYMg==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.16.1.tgz", + "integrity": "sha512-GnhSTGP9Mtv6pqFFGQze44LlrtWOjHNrUUAcsdo9DnNAhN4pwVPEWy4z+2jpjkiGlJ3VoXdvMHcNDQdfI9fEaw==", "dependencies": { "@babel/runtime": "^7.22.5", - "@rc-component/trigger": "^1.5.0", + "@rc-component/trigger": "^2.0.0", "classnames": "^2.2.6", - "rc-input": "~1.4.0", - "rc-menu": "~9.12.0", - "rc-textarea": "~1.6.1", + "rc-input": "~1.6.0", + "rc-menu": "~9.15.1", + "rc-textarea": "~1.8.0", "rc-util": "^5.34.1" }, "peerDependencies": { @@ -4440,12 +4472,12 @@ } }, "node_modules/rc-menu": { - "version": "9.12.4", - "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.12.4.tgz", - "integrity": "sha512-t2NcvPLV1mFJzw4F21ojOoRVofK2rWhpKPx69q2raUsiHPDP6DDevsBILEYdsIegqBeSXoWs2bf6CueBKg3BFg==", + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.15.1.tgz", + "integrity": "sha512-UKporqU6LPfHnpPmtP6hdEK4iO5Q+b7BRv/uRpxdIyDGplZy9jwUjsnpev5bs3PQKB0H0n34WAPDfjAfn3kAPA==", "dependencies": { "@babel/runtime": "^7.10.1", - "@rc-component/trigger": "^1.17.0", + "@rc-component/trigger": "^2.0.0", "classnames": "2.x", "rc-motion": "^2.4.3", "rc-overflow": "^1.3.1", @@ -4457,13 +4489,13 @@ } }, "node_modules/rc-motion": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.0.tgz", - "integrity": "sha512-XIU2+xLkdIr1/h6ohPZXyPBMvOmuyFZQ/T0xnawz+Rh+gh4FINcnZmMT5UTIj6hgI0VLDjTaPeRd+smJeSPqiQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.3.tgz", + "integrity": "sha512-rkW47ABVkic7WEB0EKJqzySpvDqwl60/tdkY7hWP7dYnh5pm0SzJpo54oW3TDUGXV5wfxXFmMkxrzRRbotQ0+w==", "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", - "rc-util": "^5.21.0" + "rc-util": "^5.43.0" }, "peerDependencies": { "react": ">=16.9.0", @@ -4471,9 +4503,9 @@ } }, "node_modules/rc-notification": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.3.0.tgz", - "integrity": "sha512-WCf0uCOkZ3HGfF0p1H4Sgt7aWfipxORWTPp7o6prA3vxwtWhtug3GfpYls1pnBp4WA+j8vGIi5c2/hQRpGzPcQ==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.2.tgz", + "integrity": "sha512-Id4IYMoii3zzrG0lB0gD6dPgJx4Iu95Xu0BQrhHIbp7ZnAZbLqdqQ73aIWH0d0UFcElxwaKjnzNovTjo7kXz7g==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -4504,9 +4536,9 @@ } }, "node_modules/rc-pagination": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-4.0.4.tgz", - "integrity": "sha512-GGrLT4NgG6wgJpT/hHIpL9nELv27A1XbSZzECIuQBQTVSf4xGKxWr6I/jhpRPauYEWEbWVw22ObG6tJQqwJqWQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-4.3.0.tgz", + "integrity": "sha512-UubEWA0ShnroQ1tDa291Fzw6kj0iOeF26IsUObxYTpimgj4/qPCWVFl18RLZE+0Up1IZg0IK4pMn6nB3mjvB7g==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.3.2", @@ -4518,16 +4550,16 @@ } }, "node_modules/rc-picker": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.1.3.tgz", - "integrity": "sha512-zmS64uYgiuWNmaWAxbVoAvSMuyNzGL9iO0Z8SIZzzm8U03taHHP0/jncWuM9v+O/F7Ghm7+IrFL0dDyk7aAqIw==", + "version": "4.6.15", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.6.15.tgz", + "integrity": "sha512-OWZ1yrMie+KN2uEUfYCfS4b2Vu6RC1FWwNI0s+qypsc3wRt7g+peuZKVIzXCTaJwyyZruo80+akPg2+GmyiJjw==", "dependencies": { - "@babel/runtime": "^7.10.1", - "@rc-component/trigger": "^1.5.0", + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", "classnames": "^2.2.1", "rc-overflow": "^1.3.2", "rc-resize-observer": "^1.4.0", - "rc-util": "^5.38.1" + "rc-util": "^5.43.0" }, "engines": { "node": ">=8.x" @@ -4556,9 +4588,9 @@ } }, "node_modules/rc-progress": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.5.1.tgz", - "integrity": "sha512-V6Amx6SbLRwPin/oD+k1vbPrO8+9Qf8zW1T8A7o83HdNafEVvAxPV5YsgtKFP+Ud5HghLj33zKOcEHrcrUGkfw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.6", @@ -4570,9 +4602,9 @@ } }, "node_modules/rc-rate": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.12.0.tgz", - "integrity": "sha512-g092v5iZCdVzbjdn28FzvWebK2IutoVoiTeqoLTj9WM7SjA/gOJIw5/JFZMRyJYYVe1jLAU2UhAfstIpCNRozg==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.0.tgz", + "integrity": "sha512-oxvx1Q5k5wD30sjN5tqAyWTvJfLNNJn7Oq3IeS4HxWfAiC4BOXMITNAsw7u/fzdtO4MS8Ki8uRLOzcnEuoQiAw==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.5", @@ -4602,9 +4634,9 @@ } }, "node_modules/rc-segmented": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.3.0.tgz", - "integrity": "sha512-I3FtM5Smua/ESXutFfb8gJ8ZPcvFR+qUgeeGFQHBOvRiRKyAk4aBE5nfqrxXx+h8/vn60DQjOt6i4RNtrbOobg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.5.0.tgz", + "integrity": "sha512-B28Fe3J9iUFOhFJET3RoXAPFJ2u47QvLSYcZWC4tFYNGPEjug5LAxEasZlA/PpAxhdOPqGWsGbSj7ftneukJnw==", "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", @@ -4617,12 +4649,12 @@ } }, "node_modules/rc-select": { - "version": "14.11.0", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.11.0.tgz", - "integrity": "sha512-8J8G/7duaGjFiTXCBLWfh5P+KDWyA3KTlZDfV3xj/asMPqB2cmxfM+lH50wRiPIRsCQ6EbkCFBccPuaje3DHIg==", + "version": "14.15.2", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.15.2.tgz", + "integrity": "sha512-oNoXlaFmpqXYcQDzcPVLrEqS2J9c+/+oJuGrlXeVVX/gVgrbHa5YcyiRUXRydFjyuA7GP3elRuLF7Y3Tfwltlw==", "dependencies": { "@babel/runtime": "^7.10.1", - "@rc-component/trigger": "^1.5.0", + "@rc-component/trigger": "^2.1.1", "classnames": "2.x", "rc-motion": "^2.0.1", "rc-overflow": "^1.3.1", @@ -4638,13 +4670,13 @@ } }, "node_modules/rc-slider": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-10.5.0.tgz", - "integrity": "sha512-xiYght50cvoODZYI43v3Ylsqiw14+D7ELsgzR40boDZaya1HFa1Etnv9MDkQE8X/UrXAffwv2AcNAhslgYuDTw==", + "version": "11.1.7", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.7.tgz", + "integrity": "sha512-ytYbZei81TX7otdC0QvoYD72XSlxvTihNth5OeZ6PMXyEDq/vHdWFulQmfDGyXK1NwKwSlKgpvINOa88uT5g2A==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.5", - "rc-util": "^5.27.0" + "rc-util": "^5.36.0" }, "engines": { "node": ">=8.x" @@ -4686,16 +4718,16 @@ } }, "node_modules/rc-table": { - "version": "7.39.0", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.39.0.tgz", - "integrity": "sha512-7fHLMNsm/2DlGwyIMkdH2xIeRzb5I69bLsFaEVtX+gqmGhByy0wtOAgHkiOew3PtXozSJyh+iXifjLgQzWdczw==", + "version": "7.47.5", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.47.5.tgz", + "integrity": "sha512-fzq+V9j/atbPIcvs3emuclaEoXulwQpIiJA6/7ey52j8+9cJ4P8DGmp4YzfUVDrb3qhgedcVeD6eRgUrokwVEQ==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/context": "^1.4.0", "classnames": "^2.2.5", "rc-resize-observer": "^1.1.0", - "rc-util": "^5.37.0", - "rc-virtual-list": "^3.11.1" + "rc-util": "^5.41.0", + "rc-virtual-list": "^3.14.2" }, "engines": { "node": ">=8.x" @@ -4706,14 +4738,14 @@ } }, "node_modules/rc-tabs": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-14.0.0.tgz", - "integrity": "sha512-lp1YWkaPnjlyhOZCPrAWxK6/P6nMGX/BAZcAC3nuVwKz0Byfp+vNnQKK8BRCP2g/fzu+SeB5dm9aUigRu3tRkQ==", + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.3.0.tgz", + "integrity": "sha512-lzE18r+zppT/jZWOAWS6ntdkDUKHOLJzqMi5UAij1LeKwOaQaupupAoI9Srn73GRzVpmGznkECMRrzkRusC40A==", "dependencies": { "@babel/runtime": "^7.11.2", "classnames": "2.x", - "rc-dropdown": "~4.1.0", - "rc-menu": "~9.12.0", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.15.1", "rc-motion": "^2.6.2", "rc-resize-observer": "^1.0.0", "rc-util": "^5.34.1" @@ -4727,13 +4759,13 @@ } }, "node_modules/rc-textarea": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.6.3.tgz", - "integrity": "sha512-8k7+8Y2GJ/cQLiClFMg8kUXOOdvcFQrnGeSchOvI2ZMIVvX5a3zQpLxoODL0HTrvU63fPkRmMuqaEcOF9dQemA==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.8.2.tgz", + "integrity": "sha512-UFAezAqltyR00a8Lf0IPAyTd29Jj9ee8wt8DqXyDMal7r/Cg/nDt3e1OOv3Th4W6mKaZijjgwuPXhAfVNTN8sw==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.1", - "rc-input": "~1.4.0", + "rc-input": "~1.6.0", "rc-resize-observer": "^1.0.0", "rc-util": "^5.27.0" }, @@ -4743,12 +4775,12 @@ } }, "node_modules/rc-tooltip": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.1.3.tgz", - "integrity": "sha512-HMSbSs5oieZ7XddtINUddBLSVgsnlaSb3bZrzzGWjXa7/B7nNedmsuz72s7EWFEro9mNa7RyF3gOXKYqvJiTcQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.2.1.tgz", + "integrity": "sha512-rws0duD/3sHHsD905Nex7FvoUGy2UBQRhTkKxeEvr2FB+r21HsOxcDJI0TzyO8NHhnAA8ILr8pfbSBg5Jj5KBg==", "dependencies": { "@babel/runtime": "^7.11.2", - "@rc-component/trigger": "^1.18.0", + "@rc-component/trigger": "^2.0.0", "classnames": "^2.3.1" }, "peerDependencies": { @@ -4757,9 +4789,9 @@ } }, "node_modules/rc-tree": { - "version": "5.8.5", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.8.5.tgz", - "integrity": "sha512-PRfcZtVDNkR7oh26RuNe1hpw11c1wfgzwmPFL0lnxGnYefe9lDAO6cg5wJKIAwyXFVt5zHgpjYmaz0CPy1ZtKg==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.9.0.tgz", + "integrity": "sha512-CPrgOvm9d/9E+izTONKSngNzQdIEjMox2PBufWjS1wf7vxtvmCWzK1SlpHbRY6IaBfJIeZ+88RkcIevf729cRg==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -4776,14 +4808,14 @@ } }, "node_modules/rc-tree-select": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.17.0.tgz", - "integrity": "sha512-7sRGafswBhf7n6IuHyCEFCildwQIgyKiV8zfYyUoWfZEFdhuk7lCH+DN0aHt+oJrdiY9+6Io/LDXloGe01O8XQ==", + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.23.0.tgz", + "integrity": "sha512-aQGi2tFSRw1WbXv0UVXPzHm09E0cSvUVZMLxQtMv3rnZZpNmdRXWrnd9QkLNlVH31F+X5rgghmdSFF3yZW0N9A==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", - "rc-select": "~14.11.0-0", - "rc-tree": "~5.8.1", + "rc-select": "~14.15.0", + "rc-tree": "~5.9.0", "rc-util": "^5.16.1" }, "peerDependencies": { @@ -4792,9 +4824,9 @@ } }, "node_modules/rc-upload": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.5.2.tgz", - "integrity": "sha512-QO3ne77DwnAPKFn0bA5qJM81QBjQi0e0NHdkvpFyY73Bea2NfITiotqJqVjHgeYPOJu5lLVR32TNGP084aSoXA==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.8.1.tgz", + "integrity": "sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==", "dependencies": { "@babel/runtime": "^7.18.3", "classnames": "^2.2.5", @@ -4806,9 +4838,9 @@ } }, "node_modules/rc-util": { - "version": "5.38.2", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.38.2.tgz", - "integrity": "sha512-yRGRPKyi84H7NkRSP6FzEIYBdUt4ufdsmXUZ7qM2H5qoByPax70NnGPkfo36N+UKUnUBj2f2Q2eUbwYMuAsIOQ==", + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.43.0.tgz", + "integrity": "sha512-AzC7KKOXFqAdIBqdGWepL9Xn7cm3vnAmjlHqUnoQaTMZYhM4VlXGLkkHHxj/BZ7Td0+SOPKB4RGPboBVKT9htw==", "dependencies": { "@babel/runtime": "^7.18.3", "react-is": "^18.2.0" @@ -4819,9 +4851,9 @@ } }, "node_modules/rc-virtual-list": { - "version": "3.11.4", - "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.11.4.tgz", - "integrity": "sha512-NbBi0fvyIu26gP69nQBiWgUMTPX3mr4FcuBQiVqagU0BnuX8WQkiivnMs105JROeuUIFczLrlgUhLQwTWV1XDA==", + "version": "3.14.8", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.14.8.tgz", + "integrity": "sha512-8D0KfzpRYi6YZvlOWIxiOm9BGt4Wf2hQyEaM6RXlDDiY2NhLheuYI+RA+7ZaZj1lq+XQqy3KHlaeeXQfzI5fGg==", "dependencies": { "@babel/runtime": "^7.20.0", "classnames": "^2.2.6", @@ -5214,9 +5246,9 @@ } }, "node_modules/stylis": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", - "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.4.tgz", + "integrity": "sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==" }, "node_modules/supports-color": { "version": "7.2.0", @@ -5267,9 +5299,9 @@ } }, "node_modules/throttle-debounce": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.0.tgz", - "integrity": "sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", "engines": { "node": ">=12.22" } diff --git a/webui/package.json b/webui/package.json index a0ddb73e..2687f167 100644 --- a/webui/package.json +++ b/webui/package.json @@ -21,7 +21,7 @@ "@types/node": "^20.9.0", "@types/react": "^18.2.37", "@types/react-dom": "^18.2.15", - "antd": "^5.11.1", + "antd": "^5.21.3", "buffer": "^6.0.3", "lodash": "^4.17.21", "parcel": "^2.10.2", diff --git a/webui/src/components/OperationTree.tsx b/webui/src/components/OperationTree.tsx index 5b2117af..17156847 100644 --- a/webui/src/components/OperationTree.tsx +++ b/webui/src/components/OperationTree.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; -import { Col, Empty, Modal, Row, Tooltip, Tree } from "antd"; +import { Col, Empty, Flex, Modal, Row, Splitter, Tooltip, Tree } from "antd"; import _ from "lodash"; import { DataNode } from "antd/es/tree"; import { formatDate, formatTime, localISOTime } from "../lib/formatting"; @@ -174,18 +174,22 @@ export const OperationTree = ({ } return ( - - {backupTree} - - - {selectedBackupId ? ( - b.flowID === selectedBackupId)} - /> - ) : null} - - - + + + + {backupTree} + + + + {selectedBackupId ? ( + b.flowID === selectedBackupId)} + /> + ) : null} + {" "} + + + ); }; diff --git a/webui/src/views/AddRepoModal.tsx b/webui/src/views/AddRepoModal.tsx index d623c1a3..a91cf98a 100644 --- a/webui/src/views/AddRepoModal.tsx +++ b/webui/src/views/AddRepoModal.tsx @@ -687,10 +687,7 @@ const expectedEnvVars: { [scheme: string]: string[][] } = { ], }; -const envVarSetValidator = ( - form: FormInstance, - envVars: string[] -) => { +const envVarSetValidator = (form: FormInstance, envVars: string[]) => { if (!envVars) { return Promise.resolve(); } diff --git a/webui/src/views/App.tsx b/webui/src/views/App.tsx index e354f8f9..df60f84e 100644 --- a/webui/src/views/App.tsx +++ b/webui/src/views/App.tsx @@ -85,19 +85,25 @@ export const App: React.FC = () => { }); }, []); - const showGettingStarted = () => { - setContent(, [ - { - title: "Getting Started", - }, - ]); + const showSummaryDashboard = async () => { + const { SummaryDashboard } = await import("./SummaryDashboard"); + setContent( + }> + + , + [ + { + title: "Summary Dashboard", + }, + ] + ); }; useEffect(() => { if (config === null) { setContent(

Loading...

, []); } else { - showGettingStarted(); + showSummaryDashboard(); } }, [config === null]); @@ -114,7 +120,10 @@ export const App: React.FC = () => { backgroundColor: "#1b232c", }} > - + { + const config = useConfig()[0]; + const setContent = useSetContent(); + const alertApi = useAlertApi()!; + + const [summaryData, setSummaryData] = + useState(); + + const showGettingStarted = async () => { + const { GettingStartedGuide } = await import("./GettingStartedGuide"); + setContent( + }> + + , + [ + { + title: "Getting Started", + }, + ] + ); + }; + + useEffect(() => { + // Fetch summary data + const fetchData = async () => { + // check if the tab is in the foreground + if (document.hidden) { + return; + } + + try { + const data = await backrestService.getSummaryDashboard({}); + setSummaryData(data); + } catch (e) { + alertApi.error("Failed to fetch summary data", e); + } + }; + + fetchData(); + + const interval = setInterval(fetchData, 60000); + return () => clearInterval(interval); + }, []); + + useEffect(() => { + if (!config) { + return; + } + + if (config.repos.length === 0 && config.plans.length === 0) { + showGettingStarted(); + } + }, [config]); + + if (!summaryData) { + return ; + } + + return ( + <> + + Repos + {summaryData && summaryData.repoSummaries.length > 0 ? ( + summaryData.repoSummaries.map((summary) => ( + + )) + ) : ( + + )} + Plans + {summaryData && summaryData.planSummaries.length > 0 ? ( + summaryData.planSummaries.map((summary) => ( + + )) + ) : ( + + )} + + System Info + + + + ); +}; + +const SummaryPanel = ({ + summary, +}: { + summary: SummaryDashboardResponse_Summary; +}) => { + const recentBackupsChart: { + idx: number; + time: number; + durationMs: number; + color: string; + bytesAdded: number; + }[] = []; + const recentBackups = summary.recentBackups!; + for (let i = 0; i < recentBackups.timestampMs.length; i++) { + const color = colorForStatus(recentBackups.status[i]); + recentBackupsChart.push({ + idx: i, + time: Number(recentBackups.timestampMs[i]), + durationMs: Number(recentBackups.durationMs[i]), + color: color, + bytesAdded: Number(recentBackups.bytesAdded[i]), + }); + } + while (recentBackupsChart.length < 60) { + recentBackupsChart.push({ + idx: recentBackupsChart.length, + time: 0, + durationMs: 0, + color: "white", + bytesAdded: 0, + }); + } + + const BackupChartTooltip = ({ active, payload, label }: any) => { + const idx = Number(label); + + const entry = recentBackupsChart[idx]; + if (!entry || entry.idx > recentBackups.timestampMs.length) { + return null; + } + + const isPending = + recentBackups.status[idx] === OperationStatus.STATUS_PENDING; + + return ( + + Backup at {formatTime(entry.time)}{" "} +
+ {isPending ? ( + + Scheduled, waiting. + + ) : ( + + Took {formatDuration(entry.durationMs)}, added{" "} + {formatBytes(entry.bytesAdded)} + + )} +
+ ); + }; + + const cardInfo: { key: number; label: string; children: React.ReactNode }[] = + []; + + cardInfo.push( + { + key: 1, + label: "Backups (30d)", + children: ( + <> + {summary.backupsSuccessLast30days && ( + + {summary.backupsSuccessLast30days + ""} ok + + )} + {summary.backupsFailed30days && ( + + {summary.backupsFailed30days + ""} failed + + )} + {summary.backupsWarningLast30days && ( + + {summary.backupsWarningLast30days + ""} warning + + )} + + ), + }, + { + key: 2, + label: "Bytes Scanned (30d)", + children: formatBytes(Number(summary.bytesScannedLast30days)), + }, + { + key: 3, + label: "Bytes Added (30d)", + children: formatBytes(Number(summary.bytesAddedLast30days)), + } + ); + + // check if mobile layout + if (!isMobile()) { + cardInfo.push( + { + key: 4, + label: "Next Scheduled Backup", + children: summary.nextBackupTimeMs + ? formatTime(Number(summary.nextBackupTimeMs)) + : "None Scheduled", + }, + { + key: 5, + label: "Bytes Scanned Avg", + children: formatBytes(Number(summary.bytesScannedAvg)), + }, + { + key: 6, + label: "Bytes Added Avg", + children: formatBytes(Number(summary.bytesAddedAvg)), + } + ); + } + + return ( + + + + + + + + + + {recentBackupsChart.map((entry, index) => ( + + ))} + + + + } cursor={false} /> + + + + + + ); +};