feat: implement basic plan view

This commit is contained in:
garethgeorge
2023-11-15 14:27:59 -08:00
parent 70b88e86eb
commit 7c73faaa6f
23 changed files with 1111 additions and 201 deletions
+94 -29
View File
@@ -125,6 +125,53 @@ func (OperationStatus) EnumDescriptor() ([]byte, []int) {
return file_v1_operations_proto_rawDescGZIP(), []int{1}
}
type OperationList struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Operations []*Operation `protobuf:"bytes,1,rep,name=operations,proto3" json:"operations,omitempty"`
}
func (x *OperationList) Reset() {
*x = OperationList{}
if protoimpl.UnsafeEnabled {
mi := &file_v1_operations_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *OperationList) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*OperationList) ProtoMessage() {}
func (x *OperationList) ProtoReflect() protoreflect.Message {
mi := &file_v1_operations_proto_msgTypes[0]
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 OperationList.ProtoReflect.Descriptor instead.
func (*OperationList) Descriptor() ([]byte, []int) {
return file_v1_operations_proto_rawDescGZIP(), []int{0}
}
func (x *OperationList) GetOperations() []*Operation {
if x != nil {
return x.Operations
}
return nil
}
type Operation struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -146,7 +193,7 @@ type Operation struct {
func (x *Operation) Reset() {
*x = Operation{}
if protoimpl.UnsafeEnabled {
mi := &file_v1_operations_proto_msgTypes[0]
mi := &file_v1_operations_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -159,7 +206,7 @@ func (x *Operation) String() string {
func (*Operation) ProtoMessage() {}
func (x *Operation) ProtoReflect() protoreflect.Message {
mi := &file_v1_operations_proto_msgTypes[0]
mi := &file_v1_operations_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -172,7 +219,7 @@ func (x *Operation) ProtoReflect() protoreflect.Message {
// Deprecated: Use Operation.ProtoReflect.Descriptor instead.
func (*Operation) Descriptor() ([]byte, []int) {
return file_v1_operations_proto_rawDescGZIP(), []int{0}
return file_v1_operations_proto_rawDescGZIP(), []int{1}
}
func (x *Operation) GetId() int64 {
@@ -261,7 +308,7 @@ type OperationEvent struct {
func (x *OperationEvent) Reset() {
*x = OperationEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_v1_operations_proto_msgTypes[1]
mi := &file_v1_operations_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -274,7 +321,7 @@ func (x *OperationEvent) String() string {
func (*OperationEvent) ProtoMessage() {}
func (x *OperationEvent) ProtoReflect() protoreflect.Message {
mi := &file_v1_operations_proto_msgTypes[1]
mi := &file_v1_operations_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -287,7 +334,7 @@ func (x *OperationEvent) ProtoReflect() protoreflect.Message {
// Deprecated: Use OperationEvent.ProtoReflect.Descriptor instead.
func (*OperationEvent) Descriptor() ([]byte, []int) {
return file_v1_operations_proto_rawDescGZIP(), []int{1}
return file_v1_operations_proto_rawDescGZIP(), []int{2}
}
func (x *OperationEvent) GetType() OperationEventType {
@@ -315,7 +362,7 @@ type OperationBackup struct {
func (x *OperationBackup) Reset() {
*x = OperationBackup{}
if protoimpl.UnsafeEnabled {
mi := &file_v1_operations_proto_msgTypes[2]
mi := &file_v1_operations_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -328,7 +375,7 @@ func (x *OperationBackup) String() string {
func (*OperationBackup) ProtoMessage() {}
func (x *OperationBackup) ProtoReflect() protoreflect.Message {
mi := &file_v1_operations_proto_msgTypes[2]
mi := &file_v1_operations_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -341,7 +388,7 @@ func (x *OperationBackup) ProtoReflect() protoreflect.Message {
// Deprecated: Use OperationBackup.ProtoReflect.Descriptor instead.
func (*OperationBackup) Descriptor() ([]byte, []int) {
return file_v1_operations_proto_rawDescGZIP(), []int{2}
return file_v1_operations_proto_rawDescGZIP(), []int{3}
}
func (x *OperationBackup) GetLastStatus() *BackupProgressEntry {
@@ -356,7 +403,11 @@ var File_v1_operations_proto protoreflect.FileDescriptor
var file_v1_operations_proto_rawDesc = []byte{
0x0a, 0x13, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x76, 0x31, 0x1a, 0x0f, 0x76, 0x31, 0x2f, 0x72, 0x65,
0x73, 0x74, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc1, 0x02, 0x0a, 0x09, 0x4f,
0x73, 0x74, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x3e, 0x0a, 0x0d, 0x4f, 0x70,
0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x0a, 0x6f,
0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x0d, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a,
0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xc1, 0x02, 0x0a, 0x09, 0x4f,
0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49,
@@ -420,26 +471,28 @@ func file_v1_operations_proto_rawDescGZIP() []byte {
}
var file_v1_operations_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_v1_operations_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_v1_operations_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_v1_operations_proto_goTypes = []interface{}{
(OperationEventType)(0), // 0: v1.OperationEventType
(OperationStatus)(0), // 1: v1.OperationStatus
(*Operation)(nil), // 2: v1.Operation
(*OperationEvent)(nil), // 3: v1.OperationEvent
(*OperationBackup)(nil), // 4: v1.OperationBackup
(*BackupProgressEntry)(nil), // 5: v1.BackupProgressEntry
(*OperationList)(nil), // 2: v1.OperationList
(*Operation)(nil), // 3: v1.Operation
(*OperationEvent)(nil), // 4: v1.OperationEvent
(*OperationBackup)(nil), // 5: v1.OperationBackup
(*BackupProgressEntry)(nil), // 6: v1.BackupProgressEntry
}
var file_v1_operations_proto_depIdxs = []int32{
1, // 0: v1.Operation.status:type_name -> v1.OperationStatus
4, // 1: v1.Operation.operation_backup:type_name -> v1.OperationBackup
0, // 2: v1.OperationEvent.type:type_name -> v1.OperationEventType
2, // 3: v1.OperationEvent.operation:type_name -> v1.Operation
5, // 4: v1.OperationBackup.last_status:type_name -> v1.BackupProgressEntry
5, // [5:5] is the sub-list for method output_type
5, // [5:5] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
3, // 0: v1.OperationList.operations:type_name -> v1.Operation
1, // 1: v1.Operation.status:type_name -> v1.OperationStatus
5, // 2: v1.Operation.operation_backup:type_name -> v1.OperationBackup
0, // 3: v1.OperationEvent.type:type_name -> v1.OperationEventType
3, // 4: v1.OperationEvent.operation:type_name -> v1.Operation
6, // 5: v1.OperationBackup.last_status:type_name -> v1.BackupProgressEntry
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
}
func init() { file_v1_operations_proto_init() }
@@ -450,7 +503,7 @@ func file_v1_operations_proto_init() {
file_v1_restic_proto_init()
if !protoimpl.UnsafeEnabled {
file_v1_operations_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Operation); i {
switch v := v.(*OperationList); i {
case 0:
return &v.state
case 1:
@@ -462,7 +515,7 @@ func file_v1_operations_proto_init() {
}
}
file_v1_operations_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OperationEvent); i {
switch v := v.(*Operation); i {
case 0:
return &v.state
case 1:
@@ -474,6 +527,18 @@ func file_v1_operations_proto_init() {
}
}
file_v1_operations_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OperationEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_v1_operations_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OperationBackup); i {
case 0:
return &v.state
@@ -486,7 +551,7 @@ func file_v1_operations_proto_init() {
}
}
}
file_v1_operations_proto_msgTypes[0].OneofWrappers = []interface{}{
file_v1_operations_proto_msgTypes[1].OneofWrappers = []interface{}{
(*Operation_OperationBackup)(nil),
}
type x struct{}
@@ -495,7 +560,7 @@ func file_v1_operations_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_v1_operations_proto_rawDesc,
NumEnums: 2,
NumMessages: 3,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
+155 -58
View File
@@ -78,6 +78,69 @@ func (x *ListSnapshotsRequest) GetPlanId() string {
return ""
}
type GetOperationsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RepoId string `protobuf:"bytes,1,opt,name=repo_id,json=repoId,proto3" json:"repo_id,omitempty"`
PlanId string `protobuf:"bytes,2,opt,name=plan_id,json=planId,proto3" json:"plan_id,omitempty"`
LastN int64 `protobuf:"varint,3,opt,name=last_n,json=lastN,proto3" json:"last_n,omitempty"` // limit to the last n operations
}
func (x *GetOperationsRequest) Reset() {
*x = GetOperationsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_v1_service_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetOperationsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetOperationsRequest) ProtoMessage() {}
func (x *GetOperationsRequest) ProtoReflect() protoreflect.Message {
mi := &file_v1_service_proto_msgTypes[1]
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 GetOperationsRequest.ProtoReflect.Descriptor instead.
func (*GetOperationsRequest) Descriptor() ([]byte, []int) {
return file_v1_service_proto_rawDescGZIP(), []int{1}
}
func (x *GetOperationsRequest) GetRepoId() string {
if x != nil {
return x.RepoId
}
return ""
}
func (x *GetOperationsRequest) GetPlanId() string {
if x != nil {
return x.PlanId
}
return ""
}
func (x *GetOperationsRequest) GetLastN() int64 {
if x != nil {
return x.LastN
}
return 0
}
var File_v1_service_proto protoreflect.FileDescriptor
var file_v1_service_proto_rawDesc = []byte{
@@ -95,41 +158,57 @@ var file_v1_service_proto_rawDesc = []byte{
0x65, 0x73, 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, 0x17, 0x0a, 0x07,
0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70,
0x6c, 0x61, 0x6e, 0x49, 0x64, 0x32, 0xe5, 0x03, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63,
0x55, 0x49, 0x12, 0x43, 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, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31,
0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3a, 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, 0x15, 0x82, 0xd3,
0xe4, 0x93, 0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x12, 0x3b, 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, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22,
0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x72, 0x65, 0x70, 0x6f,
0x12, 0x61, 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, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f,
0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x73, 0x30, 0x01, 0x12, 0x5b, 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, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01,
0x2a, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73,
0x12, 0x5b, 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, 0x20, 0x82, 0xd3, 0xe4,
0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x6f,
0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x42, 0x2e, 0x5a,
0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65,
0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x72, 0x65, 0x73, 0x74, 0x69, 0x63, 0x75,
0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6c, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x5f, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 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, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69,
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x12,
0x15, 0x0a, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
0x05, 0x6c, 0x61, 0x73, 0x74, 0x4e, 0x32, 0x8f, 0x05, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x74, 0x69,
0x63, 0x55, 0x49, 0x12, 0x43, 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, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76,
0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3a, 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, 0x15, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, 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, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a,
0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x72, 0x65, 0x70,
0x6f, 0x12, 0x61, 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, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31,
0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x30, 0x01, 0x12, 0x57, 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, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x22, 0x0e, 0x2f,
0x76, 0x31, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x5b, 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, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, 0x2f, 0x76, 0x31,
0x2f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x12, 0x4f, 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, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x76, 0x31,
0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x5b, 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, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01,
0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x6c,
0x65, 0x74, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f,
0x72, 0x67, 0x65, 0x2f, 0x72, 0x65, 0x73, 0x74, 0x69, 0x63, 0x75, 0x69, 0x2f, 0x67, 0x6f, 0x2f,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -144,32 +223,38 @@ func file_v1_service_proto_rawDescGZIP() []byte {
return file_v1_service_proto_rawDescData
}
var file_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_v1_service_proto_goTypes = []interface{}{
(*ListSnapshotsRequest)(nil), // 0: v1.ListSnapshotsRequest
(*emptypb.Empty)(nil), // 1: google.protobuf.Empty
(*Config)(nil), // 2: v1.Config
(*Repo)(nil), // 3: v1.Repo
(*types.StringValue)(nil), // 4: types.StringValue
(*OperationEvent)(nil), // 5: v1.OperationEvent
(*ResticSnapshotList)(nil), // 6: v1.ResticSnapshotList
(*types.StringList)(nil), // 7: types.StringList
(*GetOperationsRequest)(nil), // 1: v1.GetOperationsRequest
(*emptypb.Empty)(nil), // 2: google.protobuf.Empty
(*Config)(nil), // 3: v1.Config
(*Repo)(nil), // 4: v1.Repo
(*types.StringValue)(nil), // 5: types.StringValue
(*OperationEvent)(nil), // 6: v1.OperationEvent
(*OperationList)(nil), // 7: v1.OperationList
(*ResticSnapshotList)(nil), // 8: v1.ResticSnapshotList
(*types.StringList)(nil), // 9: types.StringList
}
var file_v1_service_proto_depIdxs = []int32{
1, // 0: v1.ResticUI.GetConfig:input_type -> google.protobuf.Empty
2, // 1: v1.ResticUI.SetConfig:input_type -> v1.Config
3, // 2: v1.ResticUI.AddRepo:input_type -> v1.Repo
1, // 3: v1.ResticUI.GetOperationEvents:input_type -> google.protobuf.Empty
0, // 4: v1.ResticUI.ListSnapshots:input_type -> v1.ListSnapshotsRequest
4, // 5: v1.ResticUI.PathAutocomplete:input_type -> types.StringValue
2, // 6: v1.ResticUI.GetConfig:output_type -> v1.Config
2, // 7: v1.ResticUI.SetConfig:output_type -> v1.Config
2, // 8: v1.ResticUI.AddRepo:output_type -> v1.Config
5, // 9: v1.ResticUI.GetOperationEvents:output_type -> v1.OperationEvent
6, // 10: v1.ResticUI.ListSnapshots:output_type -> v1.ResticSnapshotList
7, // 11: v1.ResticUI.PathAutocomplete:output_type -> types.StringList
6, // [6:12] is the sub-list for method output_type
0, // [0:6] is the sub-list for method input_type
2, // 0: v1.ResticUI.GetConfig:input_type -> google.protobuf.Empty
3, // 1: v1.ResticUI.SetConfig:input_type -> v1.Config
4, // 2: v1.ResticUI.AddRepo:input_type -> v1.Repo
2, // 3: v1.ResticUI.GetOperationEvents:input_type -> google.protobuf.Empty
1, // 4: v1.ResticUI.GetOperations:input_type -> v1.GetOperationsRequest
0, // 5: v1.ResticUI.ListSnapshots:input_type -> v1.ListSnapshotsRequest
5, // 6: v1.ResticUI.Backup:input_type -> types.StringValue
5, // 7: v1.ResticUI.PathAutocomplete:input_type -> types.StringValue
3, // 8: v1.ResticUI.GetConfig:output_type -> v1.Config
3, // 9: v1.ResticUI.SetConfig:output_type -> v1.Config
3, // 10: v1.ResticUI.AddRepo:output_type -> v1.Config
6, // 11: v1.ResticUI.GetOperationEvents:output_type -> v1.OperationEvent
7, // 12: v1.ResticUI.GetOperations:output_type -> v1.OperationList
8, // 13: v1.ResticUI.ListSnapshots:output_type -> v1.ResticSnapshotList
2, // 14: v1.ResticUI.Backup:output_type -> google.protobuf.Empty
9, // 15: v1.ResticUI.PathAutocomplete:output_type -> types.StringList
8, // [8:16] is the sub-list for method output_type
0, // [0:8] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
@@ -196,6 +281,18 @@ func file_v1_service_proto_init() {
return nil
}
}
file_v1_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetOperationsRequest); 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{
@@ -203,7 +300,7 @@ func file_v1_service_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_v1_service_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
+170
View File
@@ -136,6 +136,40 @@ func request_ResticUI_GetOperationEvents_0(ctx context.Context, marshaler runtim
}
func request_ResticUI_GetOperations_0(ctx context.Context, marshaler runtime.Marshaler, client ResticUIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetOperationsRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetOperations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ResticUI_GetOperations_0(ctx context.Context, marshaler runtime.Marshaler, server ResticUIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetOperationsRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetOperations(ctx, &protoReq)
return msg, metadata, err
}
func request_ResticUI_ListSnapshots_0(ctx context.Context, marshaler runtime.Marshaler, client ResticUIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListSnapshotsRequest
var metadata runtime.ServerMetadata
@@ -170,6 +204,40 @@ func local_request_ResticUI_ListSnapshots_0(ctx context.Context, marshaler runti
}
func request_ResticUI_Backup_0(ctx context.Context, marshaler runtime.Marshaler, client ResticUIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq types.StringValue
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.Backup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ResticUI_Backup_0(ctx context.Context, marshaler runtime.Marshaler, server ResticUIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq types.StringValue
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.Backup(ctx, &protoReq)
return msg, metadata, err
}
func request_ResticUI_PathAutocomplete_0(ctx context.Context, marshaler runtime.Marshaler, client ResticUIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq types.StringValue
var metadata runtime.ServerMetadata
@@ -292,6 +360,31 @@ func RegisterResticUIHandlerServer(ctx context.Context, mux *runtime.ServeMux, s
return
})
mux.Handle("POST", pattern_ResticUI_GetOperations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/v1.ResticUI/GetOperations", runtime.WithHTTPPathPattern("/v1/operations"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ResticUI_GetOperations_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ResticUI_GetOperations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ResticUI_ListSnapshots_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -317,6 +410,31 @@ func RegisterResticUIHandlerServer(ctx context.Context, mux *runtime.ServeMux, s
})
mux.Handle("POST", pattern_ResticUI_Backup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/v1.ResticUI/Backup", runtime.WithHTTPPathPattern("/v1/cmd/backup"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ResticUI_Backup_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ResticUI_Backup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ResticUI_PathAutocomplete_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -471,6 +589,28 @@ func RegisterResticUIHandlerClient(ctx context.Context, mux *runtime.ServeMux, c
})
mux.Handle("POST", pattern_ResticUI_GetOperations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/v1.ResticUI/GetOperations", runtime.WithHTTPPathPattern("/v1/operations"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ResticUI_GetOperations_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ResticUI_GetOperations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ResticUI_ListSnapshots_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -493,6 +633,28 @@ func RegisterResticUIHandlerClient(ctx context.Context, mux *runtime.ServeMux, c
})
mux.Handle("POST", pattern_ResticUI_Backup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/v1.ResticUI/Backup", runtime.WithHTTPPathPattern("/v1/cmd/backup"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ResticUI_Backup_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ResticUI_Backup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ResticUI_PathAutocomplete_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -527,8 +689,12 @@ var (
pattern_ResticUI_GetOperationEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "events", "operations"}, ""))
pattern_ResticUI_GetOperations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "operations"}, ""))
pattern_ResticUI_ListSnapshots_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "snapshots"}, ""))
pattern_ResticUI_Backup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "cmd", "backup"}, ""))
pattern_ResticUI_PathAutocomplete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "autocomplete", "path"}, ""))
)
@@ -541,7 +707,11 @@ var (
forward_ResticUI_GetOperationEvents_0 = runtime.ForwardResponseStream
forward_ResticUI_GetOperations_0 = runtime.ForwardResponseMessage
forward_ResticUI_ListSnapshots_0 = runtime.ForwardResponseMessage
forward_ResticUI_Backup_0 = runtime.ForwardResponseMessage
forward_ResticUI_PathAutocomplete_0 = runtime.ForwardResponseMessage
)
+78
View File
@@ -25,7 +25,9 @@ const (
ResticUI_SetConfig_FullMethodName = "/v1.ResticUI/SetConfig"
ResticUI_AddRepo_FullMethodName = "/v1.ResticUI/AddRepo"
ResticUI_GetOperationEvents_FullMethodName = "/v1.ResticUI/GetOperationEvents"
ResticUI_GetOperations_FullMethodName = "/v1.ResticUI/GetOperations"
ResticUI_ListSnapshots_FullMethodName = "/v1.ResticUI/ListSnapshots"
ResticUI_Backup_FullMethodName = "/v1.ResticUI/Backup"
ResticUI_PathAutocomplete_FullMethodName = "/v1.ResticUI/PathAutocomplete"
)
@@ -37,7 +39,11 @@ type ResticUIClient interface {
SetConfig(ctx context.Context, in *Config, opts ...grpc.CallOption) (*Config, error)
AddRepo(ctx context.Context, in *Repo, opts ...grpc.CallOption) (*Config, error)
GetOperationEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (ResticUI_GetOperationEventsClient, error)
GetOperations(ctx context.Context, in *GetOperationsRequest, opts ...grpc.CallOption) (*OperationList, error)
ListSnapshots(ctx context.Context, in *ListSnapshotsRequest, opts ...grpc.CallOption) (*ResticSnapshotList, error)
// Backup schedules a backup operation. It accepts a plan id and returns empty if the task is enqueued.
Backup(ctx context.Context, in *types.StringValue, 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)
}
@@ -108,6 +114,15 @@ func (x *resticUIGetOperationEventsClient) Recv() (*OperationEvent, error) {
return m, nil
}
func (c *resticUIClient) GetOperations(ctx context.Context, in *GetOperationsRequest, opts ...grpc.CallOption) (*OperationList, error) {
out := new(OperationList)
err := c.cc.Invoke(ctx, ResticUI_GetOperations_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *resticUIClient) ListSnapshots(ctx context.Context, in *ListSnapshotsRequest, opts ...grpc.CallOption) (*ResticSnapshotList, error) {
out := new(ResticSnapshotList)
err := c.cc.Invoke(ctx, ResticUI_ListSnapshots_FullMethodName, in, out, opts...)
@@ -117,6 +132,15 @@ func (c *resticUIClient) ListSnapshots(ctx context.Context, in *ListSnapshotsReq
return out, nil
}
func (c *resticUIClient) Backup(ctx context.Context, in *types.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, ResticUI_Backup_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *resticUIClient) PathAutocomplete(ctx context.Context, in *types.StringValue, opts ...grpc.CallOption) (*types.StringList, error) {
out := new(types.StringList)
err := c.cc.Invoke(ctx, ResticUI_PathAutocomplete_FullMethodName, in, out, opts...)
@@ -134,7 +158,11 @@ type ResticUIServer interface {
SetConfig(context.Context, *Config) (*Config, error)
AddRepo(context.Context, *Repo) (*Config, error)
GetOperationEvents(*emptypb.Empty, ResticUI_GetOperationEventsServer) error
GetOperations(context.Context, *GetOperationsRequest) (*OperationList, error)
ListSnapshots(context.Context, *ListSnapshotsRequest) (*ResticSnapshotList, error)
// Backup schedules a backup operation. It accepts a plan id and returns empty if the task is enqueued.
Backup(context.Context, *types.StringValue) (*emptypb.Empty, error)
// PathAutocomplete provides path autocompletion options for a given filesystem path.
PathAutocomplete(context.Context, *types.StringValue) (*types.StringList, error)
mustEmbedUnimplementedResticUIServer()
}
@@ -155,9 +183,15 @@ func (UnimplementedResticUIServer) AddRepo(context.Context, *Repo) (*Config, err
func (UnimplementedResticUIServer) GetOperationEvents(*emptypb.Empty, ResticUI_GetOperationEventsServer) error {
return status.Errorf(codes.Unimplemented, "method GetOperationEvents not implemented")
}
func (UnimplementedResticUIServer) GetOperations(context.Context, *GetOperationsRequest) (*OperationList, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetOperations not implemented")
}
func (UnimplementedResticUIServer) ListSnapshots(context.Context, *ListSnapshotsRequest) (*ResticSnapshotList, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListSnapshots not implemented")
}
func (UnimplementedResticUIServer) Backup(context.Context, *types.StringValue) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method Backup not implemented")
}
func (UnimplementedResticUIServer) PathAutocomplete(context.Context, *types.StringValue) (*types.StringList, error) {
return nil, status.Errorf(codes.Unimplemented, "method PathAutocomplete not implemented")
}
@@ -249,6 +283,24 @@ func (x *resticUIGetOperationEventsServer) Send(m *OperationEvent) error {
return x.ServerStream.SendMsg(m)
}
func _ResticUI_GetOperations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetOperationsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ResticUIServer).GetOperations(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ResticUI_GetOperations_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ResticUIServer).GetOperations(ctx, req.(*GetOperationsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ResticUI_ListSnapshots_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListSnapshotsRequest)
if err := dec(in); err != nil {
@@ -267,6 +319,24 @@ func _ResticUI_ListSnapshots_Handler(srv interface{}, ctx context.Context, dec f
return interceptor(ctx, in, info, handler)
}
func _ResticUI_Backup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(types.StringValue)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ResticUIServer).Backup(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ResticUI_Backup_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ResticUIServer).Backup(ctx, req.(*types.StringValue))
}
return interceptor(ctx, in, info, handler)
}
func _ResticUI_PathAutocomplete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(types.StringValue)
if err := dec(in); err != nil {
@@ -304,10 +374,18 @@ var ResticUI_ServiceDesc = grpc.ServiceDesc{
MethodName: "AddRepo",
Handler: _ResticUI_AddRepo_Handler,
},
{
MethodName: "GetOperations",
Handler: _ResticUI_GetOperations_Handler,
},
{
MethodName: "ListSnapshots",
Handler: _ResticUI_ListSnapshots_Handler,
},
{
MethodName: "Backup",
Handler: _ResticUI_Backup_Handler,
},
{
MethodName: "PathAutocomplete",
Handler: _ResticUI_PathAutocomplete_Handler,
+43 -1
View File
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"os"
"time"
"github.com/garethgeorge/resticui/gen/go/types"
v1 "github.com/garethgeorge/resticui/gen/go/v1"
@@ -28,6 +29,7 @@ var _ v1.ResticUIServer = &Server{}
func NewServer(orchestrator *orchestrator.Orchestrator, oplog *oplog.OpLog) *Server {
s := &Server{
orchestrator: orchestrator,
oplog: oplog,
}
return s
@@ -54,6 +56,12 @@ func (s *Server) SetConfig(ctx context.Context, c *v1.Config) (*v1.Config, error
if err := config.Default.Update(c); err != nil {
return nil, fmt.Errorf("failed to update config: %w", err)
}
newConfig, err := config.Default.Get()
if err != nil {
return nil, fmt.Errorf("failed to get newly set config: %w", err)
}
s.orchestrator.ApplyConfig(newConfig)
return config.Default.Get()
}
@@ -122,7 +130,7 @@ func (s *Server) ListSnapshots(ctx context.Context, query *v1.ListSnapshotsReque
}, nil
}
// GetEvents implements GET /v1/events
// GetOperationEvents implements GET /v1/events/operations
func (s *Server) GetOperationEvents(_ *emptypb.Empty, stream v1.ResticUI_GetOperationEventsServer) error {
errorChan := make(chan error)
defer close(errorChan)
@@ -158,6 +166,40 @@ func (s *Server) GetOperationEvents(_ *emptypb.Empty, stream v1.ResticUI_GetOper
}
}
func (s *Server) GetOperations(ctx context.Context, req *v1.GetOperationsRequest) (*v1.OperationList, error) {
filter := oplog.FilterKeepAll()
if req.LastN != 0 {
filter = oplog.FilterLastN(req.LastN)
}
var err error
var ops []*v1.Operation
if req.RepoId != "" && req.PlanId != "" {
return nil, errors.New("cannot specify both repoId and planId")
} else if req.PlanId != "" {
ops, err = s.oplog.GetByPlan(req.PlanId, filter)
} else if req.RepoId != "" {
ops, err = s.oplog.GetByRepo(req.RepoId, filter)
}
if err != nil {
return nil, fmt.Errorf("failed to get operations: %w", err)
}
return &v1.OperationList{
Operations: ops,
}, nil
}
func (s* Server) Backup(ctx context.Context, req *types.StringValue) (*emptypb.Empty, error) {
plan, err := s.orchestrator.GetPlan(req.Value)
if err != nil {
return nil, fmt.Errorf("failed to get plan %q: %w", req.Value, err)
}
s.orchestrator.EnqueueTask(orchestrator.NewOneofBackupTask(s.orchestrator, plan, time.Now()))
return &emptypb.Empty{}, nil
}
func (s *Server) PathAutocomplete(ctx context.Context, path *types.StringValue) (*types.StringList, error) {
ents, err := os.ReadDir(path.Value)
if errors.Is(err, os.ErrNotExist) {
+9
View File
@@ -18,3 +18,12 @@ func stob(v string) []byte {
b = append(b, []byte(v)...)
return b
}
func btos(b []byte) (string, int64) {
length := btoi(b[:8])
return string(b[8:8+length]), 8+length
}
func normalizeSnapshotId(id string) string {
return id[:8]
}
+11 -1
View File
@@ -10,4 +10,14 @@ func TestItoa(t *testing.T) {
t.Errorf("itob/btoi failed for %d", num)
}
}
}
}
func TestStob(t *testing.T) {
strs := []string{"", "a", "ab", "abc", "abcd", "abcde", "abcdef"}
for _, str := range strs {
b := stob(str)
if val, _ := btos(b); val != str {
t.Errorf("stob/btos failed for %s", str)
}
}
}
+77 -60
View File
@@ -26,8 +26,9 @@ type Orchestrator struct {
oplog *oplog.OpLog
repoPool *resticRepoPool
// configUpdates chan makes config changes available to Run()
configUpdates chan *v1.Config
configUpdates chan *v1.Config // configUpdates chan makes config changes available to Run()
externTasks chan Task // externTasks is a channel that externally added tasks can be added to, they will be consumed by Run()
}
func NewOrchestrator(configProvider config.ConfigStore, oplog *oplog.OpLog) (*Orchestrator, error) {
@@ -40,6 +41,7 @@ func NewOrchestrator(configProvider config.ConfigStore, oplog *oplog.OpLog) (*Or
config: cfg,
oplog: oplog,
repoPool: newResticRepoPool(&config.MemoryStore{Config: cfg}),
externTasks: make(chan Task, 2),
}, nil
}
@@ -99,84 +101,99 @@ func (o *Orchestrator) Run(mainCtx context.Context) error {
o.configUpdates = make(chan *v1.Config)
o.mu.Unlock()
for {
lock := sync.Mutex{}
ctx, cancel := context.WithCancel(mainCtx)
var wg sync.WaitGroup
for mainCtx.Err() == nil {
o.mu.Lock()
config := o.config
o.mu.Unlock()
o.runVersion(mainCtx, config)
zap.L().Info("Restarting orchestrator loop")
}
zap.L().Info("Exited orchestrator loop, context cancelled.")
var execTask func(t task, runAt time.Time)
execTask = func(t task, runAt time.Time) {
curTime := time.Now()
timer := time.NewTimer(runAt.Sub(curTime))
zap.L().Debug("Scheduling task", zap.String("task", t.Name()), zap.String("runAt", runAt.Format(time.RFC3339)))
return nil
}
wg.Add(1)
go func() {
defer wg.Done()
select {
case <-ctx.Done():
timer.Stop()
zap.L().Debug("Not running task, orchestrator context is cancelled.", zap.String("task", t.Name()))
return
case <-timer.C:
lock.Lock()
defer lock.Unlock()
zap.L().Debug("Running task", zap.String("task", t.Name()))
// Task execution runs with mainCtx meaning config changes do not interrupt it, but cancelling the orchestration loop will.
if err := t.Run(mainCtx); err != nil {
zap.L().Error("Task failed", zap.String("task", t.Name()), zap.Error(err))
} else {
zap.L().Debug("Task finished", zap.String("task", t.Name()))
}
// runImmutable is a helper function for Run() that runs the orchestration loop with a single version of the config.
func (o *Orchestrator) runVersion(mainCtx context.Context, config *v1.Config) {
lock := sync.Mutex{}
ctx, cancel := context.WithCancel(mainCtx)
var wg sync.WaitGroup
if ctx.Err() != nil {
zap.L().Debug("Not attempting to reschedule task, orchestrator context is cancelled.", zap.String("task", t.Name()))
return
}
var execTask func(t Task)
execTask = func(t Task) {
curTime := time.Now()
next := t.Next(time.Now())
if next != nil {
execTask(t, *next)
} else {
zap.L().Debug("Task has no next run, not rescheduling.", zap.String("task", t.Name()))
}
runAt := t.Next(curTime)
if runAt == nil {
zap.L().Debug("Task has no next run, not scheduling.", zap.String("task", t.Name()))
return
}
timer := time.NewTimer(runAt.Sub(curTime))
zap.L().Debug("Scheduling task", zap.String("task", t.Name()), zap.String("runAt", runAt.Format(time.RFC3339)))
wg.Add(1)
go func() {
defer wg.Done()
select {
case <-ctx.Done():
timer.Stop()
zap.L().Debug("Cancelling scheduled (but not running) task, orchestrator context is cancelled.", zap.String("task", t.Name()))
return
case <-timer.C:
lock.Lock()
defer lock.Unlock()
zap.L().Debug("Running task", zap.String("task", t.Name()))
// Task execution runs with mainCtx meaning config changes do not interrupt it, but cancelling the orchestration loop will.
if err := t.Run(mainCtx); err != nil {
zap.L().Error("Task failed", zap.String("task", t.Name()), zap.Error(err))
} else {
zap.L().Debug("Task finished", zap.String("task", t.Name()))
}
}()
if ctx.Err() != nil {
zap.L().Debug("Not attempting to reschedule task, orchestrator context is cancelled.", zap.String("task", t.Name()))
return
}
execTask(t)
}
}()
}
// Schedule all backup tasks.
for _, plan := range config.Plans {
t, err := NewScheduledBackupTask(o, plan)
if err != nil {
zap.L().Error("Failed to create backup task for plan", zap.String("plan", plan.Id), zap.Error(err))
}
// Schedule all backup tasks.
for _, plan := range o.config.Plans {
t, err := newBackupTask(o, plan)
if err != nil {
zap.L().Error("Failed to create backup task for plan", zap.String("plan", plan.Id), zap.Error(err))
}
execTask(t)
}
next := t.Next(time.Now())
if next != nil {
execTask(t, *next)
} else {
zap.L().Debug("Task has no next run, not scheduling.", zap.String("task", t.Name()))
}
}
// wait for either an error or the context to be cancelled, then wait for all tasks.
// wait for either an error or the context to be cancelled, then wait for all tasks.
for {
select {
case t := <-o.externTasks:
execTask(t)
case <-ctx.Done():
cancel()
wg.Wait()
return nil
return
case <-o.configUpdates:
zap.L().Info("Orchestrator received config change, waiting for in-progress operations")
cancel()
wg.Wait()
zap.L().Info("Restarting orchestrator loop")
continue
return
}
}
}
func (o *Orchestrator) EnqueueTask(t Task) {
o.externTasks <- t
}
// resticRepoPool caches restic repos.
type resticRepoPool struct {
+55 -19
View File
@@ -14,72 +14,108 @@ import (
)
type task interface {
type Task interface {
Name() string // huamn readable name for this task.
Next(now time.Time) *time.Time // when this task would like to be run.
Run(ctx context.Context) error // run the task.
}
type backupTask struct {
// BackupTask is a scheduled backup operation.
type ScheduledBackupTask struct {
orchestrator *Orchestrator // owning orchestrator
plan *v1.Plan
schedule *cronexpr.Schedule
}
var _ task = &backupTask{}
var _ Task = &ScheduledBackupTask{}
func newBackupTask(orchestrator *Orchestrator, plan *v1.Plan) (*backupTask, error) {
func NewScheduledBackupTask(orchestrator *Orchestrator, plan *v1.Plan) (*ScheduledBackupTask, error) {
sched, err := cronexpr.Parse(plan.Cron)
if err != nil {
return nil, fmt.Errorf("failed to parse schedule %q: %w", plan.Cron, err)
}
return &backupTask{
return &ScheduledBackupTask{
orchestrator: orchestrator,
plan: plan,
schedule: sched,
}, nil
}
func (t *backupTask) Name() string {
return fmt.Sprintf("execute backup plan %v", t.plan.Id)
func (t *ScheduledBackupTask) Name() string {
return fmt.Sprintf("backup for plan %q", t.plan.Id)
}
func (t *backupTask) Next(now time.Time) *time.Time {
func (t *ScheduledBackupTask) Next(now time.Time) *time.Time {
next := t.schedule.Next(now)
return &next
}
func (t *backupTask) Run(ctx context.Context) error {
func (t *ScheduledBackupTask) Run(ctx context.Context) error {
return backupHelper(ctx, t.orchestrator, t.plan)
}
// OnetimeBackupTask is a single backup operation.
type OnetimeBackupTask struct {
orchestrator *Orchestrator
plan *v1.Plan
time *time.Time
}
func NewOneofBackupTask(orchestrator *Orchestrator, plan *v1.Plan, at time.Time) *OnetimeBackupTask {
return &OnetimeBackupTask{
orchestrator: orchestrator,
plan: plan,
time: &at,
}
}
func (t *OnetimeBackupTask) Name() string {
return fmt.Sprintf("onetime backup for plan %q", t.plan.Id)
}
func (t *OnetimeBackupTask) Next(now time.Time) *time.Time {
ret := t.time
t.time = nil
return ret
}
func (t *OnetimeBackupTask) Run(ctx context.Context) error {
return backupHelper(ctx, t.orchestrator, t.plan)
}
// backupHelper does a backup.
func backupHelper(ctx context.Context, orchestrator *Orchestrator, plan *v1.Plan) error {
backupOp := &v1.Operation_OperationBackup{
OperationBackup: &v1.OperationBackup{},
}
op := &v1.Operation{
PlanId: t.plan.Id,
RepoId: t.plan.Repo,
PlanId: plan.Id,
RepoId: plan.Repo,
UnixTimeStartMs: time.Now().Unix(),
Status: v1.OperationStatus_STATUS_INPROGRESS,
Op: backupOp,
}
return WithOperation(t.orchestrator.oplog, op, func() error {
zap.L().Info("Starting backup", zap.String("plan", t.plan.Id))
repo, err := t.orchestrator.GetRepo(t.plan.Repo)
return WithOperation(orchestrator.oplog, op, func() error {
zap.L().Info("Starting backup", zap.String("plan", plan.Id))
repo, err := orchestrator.GetRepo(plan.Repo)
if err != nil {
return fmt.Errorf("failed to get repo %q: %w", t.plan.Repo, err)
return fmt.Errorf("failed to get repo %q: %w", plan.Repo, err)
}
if _, err := repo.Backup(ctx, t.plan, func(entry *restic.BackupProgressEntry) {
if _, err := repo.Backup(ctx, plan, func(entry *restic.BackupProgressEntry) {
backupOp.OperationBackup.LastStatus = entry.ToProto()
if err := t.orchestrator.oplog.Update(op); err != nil {
if err := orchestrator.oplog.Update(op); err != nil {
zap.S().Errorf("failed to update oplog with progress for backup: %v", err)
}
zap.L().Info("Backup progress", zap.Float64("progress", entry.PercentDone))
zap.L().Debug("Backup progress", zap.Float64("progress", entry.PercentDone))
}); err != nil {
return fmt.Errorf("failed to backup repo %q: %w", t.plan.Repo, err)
return fmt.Errorf("failed to backup repo %q: %w", plan.Repo, err)
}
zap.L().Info("Backup complete", zap.String("plan", plan.Id))
return nil
})
}
+4
View File
@@ -6,6 +6,10 @@ option go_package = "github.com/garethgeorge/resticui/go/proto/v1";
import "v1/restic.proto";
message OperationList {
repeated Operation operations = 1;
}
message Operation {
int64 id = 1;
string repo_id = 2; // repo id if associated with a repo (always true)
+23 -1
View File
@@ -38,6 +38,13 @@ service ResticUI {
};
}
rpc GetOperations (GetOperationsRequest) returns (OperationList) {
option (google.api.http) = {
post: "/v1/operations"
body: "*"
};
}
rpc ListSnapshots(ListSnapshotsRequest) returns (ResticSnapshotList) {
option (google.api.http) = {
post: "/v1/snapshots"
@@ -45,6 +52,15 @@ service ResticUI {
};
}
// Backup schedules a backup operation. It accepts a plan id and returns empty if the task is enqueued.
rpc Backup(types.StringValue) returns (google.protobuf.Empty) {
option (google.api.http) = {
post: "/v1/cmd/backup"
body: "*"
};
}
// PathAutocomplete provides path autocompletion options for a given filesystem path.
rpc PathAutocomplete (types.StringValue) returns (types.StringList) {
option (google.api.http) = {
post: "/v1/autocomplete/path"
@@ -56,4 +72,10 @@ service ResticUI {
message ListSnapshotsRequest {
string repo_id = 1;
string plan_id = 2;
}
}
message GetOperationsRequest {
string repo_id = 1;
string plan_id = 2;
int64 last_n = 3; // limit to the last n operations
}
+4
View File
@@ -29,6 +29,10 @@ export enum OperationStatus {
STATUS_ERROR = "STATUS_ERROR",
}
export type OperationList = {
operations?: Operation[]
}
type BaseOperation = {
id?: string
+79 -12
View File
@@ -15,23 +15,90 @@ export type ListSnapshotsRequest = {
planId?: string
}
export type GetOperationsRequest = {
repoId?: string;
planId?: string;
lastN?: string;
};
export class ResticUI {
static GetConfig(req: GoogleProtobufEmpty.Empty, initReq?: fm.InitReq): Promise<V1Config.Config> {
return fm.fetchReq<GoogleProtobufEmpty.Empty, V1Config.Config>(`/v1/config?${fm.renderURLSearchParams(req, [])}`, {...initReq, method: "GET"})
static GetConfig(
req: GoogleProtobufEmpty.Empty,
initReq?: fm.InitReq
): Promise<V1Config.Config> {
return fm.fetchReq<GoogleProtobufEmpty.Empty, V1Config.Config>(
`/v1/config?${fm.renderURLSearchParams(req, [])}`,
{ ...initReq, method: "GET" }
);
}
static SetConfig(req: V1Config.Config, initReq?: fm.InitReq): Promise<V1Config.Config> {
return fm.fetchReq<V1Config.Config, V1Config.Config>(`/v1/config`, {...initReq, method: "POST", body: JSON.stringify(req, fm.replacer)})
static SetConfig(
req: V1Config.Config,
initReq?: fm.InitReq
): Promise<V1Config.Config> {
return fm.fetchReq<V1Config.Config, V1Config.Config>(`/v1/config`, {
...initReq,
method: "POST",
body: JSON.stringify(req, fm.replacer),
});
}
static AddRepo(req: V1Config.Repo, initReq?: fm.InitReq): Promise<V1Config.Config> {
return fm.fetchReq<V1Config.Repo, V1Config.Config>(`/v1/config/repo`, {...initReq, method: "POST", body: JSON.stringify(req, fm.replacer)})
static AddRepo(
req: V1Config.Repo,
initReq?: fm.InitReq
): Promise<V1Config.Config> {
return fm.fetchReq<V1Config.Repo, V1Config.Config>(`/v1/config/repo`, {
...initReq,
method: "POST",
body: JSON.stringify(req, fm.replacer),
});
}
static GetOperationEvents(req: GoogleProtobufEmpty.Empty, entityNotifier?: fm.NotifyStreamEntityArrival<V1Operations.OperationEvent>, initReq?: fm.InitReq): Promise<void> {
return fm.fetchStreamingRequest<GoogleProtobufEmpty.Empty, V1Operations.OperationEvent>(`/v1/events/operations?${fm.renderURLSearchParams(req, [])}`, entityNotifier, {...initReq, method: "GET"})
static GetOperationEvents(
req: GoogleProtobufEmpty.Empty,
entityNotifier?: fm.NotifyStreamEntityArrival<V1Operations.OperationEvent>,
initReq?: fm.InitReq
): Promise<void> {
return fm.fetchStreamingRequest<
GoogleProtobufEmpty.Empty,
V1Operations.OperationEvent
>(
`/v1/events/operations?${fm.renderURLSearchParams(req, [])}`,
entityNotifier,
{ ...initReq, method: "GET" }
);
}
static ListSnapshots(req: ListSnapshotsRequest, initReq?: fm.InitReq): Promise<V1Restic.ResticSnapshotList> {
return fm.fetchReq<ListSnapshotsRequest, V1Restic.ResticSnapshotList>(`/v1/snapshots`, {...initReq, method: "POST", body: JSON.stringify(req, fm.replacer)})
static GetOperations(
req: GetOperationsRequest,
initReq?: fm.InitReq
): Promise<V1Operations.OperationList> {
return fm.fetchReq<GetOperationsRequest, V1Operations.OperationList>(
`/v1/operations`,
{ ...initReq, method: "POST", body: JSON.stringify(req, fm.replacer) }
);
}
static PathAutocomplete(req: TypesValue.StringValue, initReq?: fm.InitReq): Promise<TypesValue.StringList> {
return fm.fetchReq<TypesValue.StringValue, TypesValue.StringList>(`/v1/autocomplete/path`, {...initReq, method: "POST", body: JSON.stringify(req, fm.replacer)})
static ListSnapshots(
req: ListSnapshotsRequest,
initReq?: fm.InitReq
): Promise<V1Restic.ResticSnapshotList> {
return fm.fetchReq<ListSnapshotsRequest, V1Restic.ResticSnapshotList>(
`/v1/snapshots`,
{ ...initReq, method: "POST", body: JSON.stringify(req, fm.replacer) }
);
}
static Backup(
req: TypesValue.StringValue,
initReq?: fm.InitReq
): Promise<GoogleProtobufEmpty.Empty> {
return fm.fetchReq<TypesValue.StringValue, GoogleProtobufEmpty.Empty>(
`/v1/cmd/backup`,
{ ...initReq, method: "POST", body: JSON.stringify(req, fm.replacer) }
);
}
static PathAutocomplete(
req: TypesValue.StringValue,
initReq?: fm.InitReq
): Promise<TypesValue.StringList> {
return fm.fetchReq<TypesValue.StringValue, TypesValue.StringList>(
`/v1/autocomplete/path`,
{ ...initReq, method: "POST", body: JSON.stringify(req, fm.replacer) }
);
}
}
+58
View File
@@ -22,6 +22,10 @@
"react-js-cron": "^5.0.1",
"recoil": "^0.7.7",
"typescript": "^5.2.2"
},
"devDependencies": {
"events": "^3.3.0",
"stream-browserify": "^3.0.0"
}
},
"node_modules/@ant-design/colors": {
@@ -2806,6 +2810,15 @@
"node": ">=0.8.0"
}
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"dev": true,
"engines": {
"node": ">=0.8.x"
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -2950,6 +2963,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -4161,6 +4180,20 @@
"node": ">=0.10.0"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dev": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/recoil": {
"version": "0.7.7",
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz",
@@ -4282,6 +4315,25 @@
"integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==",
"deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility"
},
"node_modules/stream-browserify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
"integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
"dev": true,
"dependencies": {
"inherits": "~2.0.4",
"readable-stream": "^3.5.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dev": true,
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-convert": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
@@ -4430,6 +4482,12 @@
"browserslist": ">= 4.21.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
"node_modules/utility-types": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz",
+4
View File
@@ -23,5 +23,9 @@
"react-js-cron": "^5.0.1",
"recoil": "^0.7.7",
"typescript": "^5.2.2"
},
"devDependencies": {
"events": "^3.3.0",
"stream-browserify": "^3.0.0"
}
}
@@ -0,0 +1,27 @@
import React from "react";
export const GettingStartedGuide = () => {
return (
<>
<h1>Getting Started</h1>
<ul>
<li>
Repos map directly to restic repositories, start by configuring your
backup locations.
</li>
<li>
Plans are where you configure directories to backup, and backup
scheduling. Multiple plans can backup to a single restic repository.
</li>
<li>
See{" "}
<a href="https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html">
the restic docs on preparing a new repository
</a>{" "}
for details about available repository types and how they can be
configured.
</li>
</ul>
</>
);
};
+56
View File
@@ -0,0 +1,56 @@
import { Breadcrumb, Layout, Spin, theme } from "antd";
import { BreadcrumbItemType } from "antd/es/breadcrumb/Breadcrumb";
import { Content } from "antd/es/layout/layout";
import React, { useContext } from "react";
import { createContext } from "react";
import { atom, useRecoilValue, useSetRecoilState } from "recoil";
interface Breadcrumb {
title: string;
onClick?: () => void;
}
const contentPanel = atom<React.ReactNode | null>({
key: "ui.content",
default: null,
});
const breadcrumbs = atom<Breadcrumb[]>({
key: "ui.breadcrumbs",
default: [],
});
export const useSetContent = () => {
const setContent = useSetRecoilState(contentPanel);
const setBreadcrumbs = useSetRecoilState(breadcrumbs);
return (content: React.ReactNode | null, breadcrumbs: Breadcrumb[]) => {
setContent(content);
setBreadcrumbs(breadcrumbs);
};
};
export const MainContentArea = () => {
const {
token: { colorBgContainer },
} = theme.useToken();
const content = useRecoilValue(contentPanel);
const crumbs = useRecoilValue(breadcrumbs);
return (
<Layout style={{ padding: "0 24px 24px" }}>
<Breadcrumb style={{ margin: "16px 0" }} items={[...crumbs]}></Breadcrumb>
<Content
style={{
padding: 24,
margin: 0,
minHeight: 280,
background: colorBgContainer,
}}
>
{content}
</Content>
</Layout>
);
};
+3 -3
View File
@@ -2,13 +2,13 @@ import { atom, useSetRecoilState } from "recoil";
import { Config, Repo } from "../../gen/ts/v1/config.pb";
import { ResticUI } from "../../gen/ts/v1/service.pb";
export const configState = atom({
export const configState = atom<Config>({
key: "config",
default: null as Config | null,
default: {},
});
export const fetchConfig = async (): Promise<Config> => {
return await ResticUI.GetConfig({}, { pathPrefix: "/api/" });
return await ResticUI.GetConfig({}, { pathPrefix: "/api" });
};
export const addRepo = async (repo: Repo): Promise<Config> => {
+24
View File
@@ -0,0 +1,24 @@
import { atom } from "recoil";
import {
Operation,
OperationEvent,
OperationEventType,
OperationStatus,
} from "../../gen/ts/v1/operations.pb";
import { ResticUI } from "../../gen/ts/v1/service.pb";
import { EventEmitter } from "events";
export const operationEmitter = new EventEmitter();
// Start fetching and emitting operations.
(async () => {
await ResticUI.GetOperationEvents(
{},
(event: OperationEvent) => {
operationEmitter.emit("operation", event);
},
{
pathPrefix: "/api",
}
);
})();
+1
View File
@@ -114,6 +114,7 @@ export const AddPlanModal = ({
<>
<Modal
open={true}
onCancel={handleCancel}
title="Add Plan"
footer={[
<Button loading={confirmLoading} key="back" onClick={handleCancel}>
+1
View File
@@ -134,6 +134,7 @@ export const AddRepoModel = ({
<>
<Modal
open={true}
onCancel={handleCancel}
title={template ? "Edit Restic Repository" : "Add Restic Repository"}
footer={[
<Button loading={confirmLoading} key="back" onClick={handleCancel}>
+12 -17
View File
@@ -14,6 +14,9 @@ import { AlertContextProvider, useAlertApi } from "../components/Alerts";
import { useShowModal } from "../components/ModalManager";
import { AddPlanModal } from "./AddPlanModel";
import { AddRepoModel } from "./AddRepoModel";
import { MainContentArea, useSetContent } from "../components/MainContentArea";
import { GettingStartedGuide } from "../components/GettingStartedGuide";
import { PlanView } from "./PlanView";
const { Header, Content, Sider } = Layout;
@@ -22,6 +25,7 @@ export const App: React.FC = () => {
token: { colorBgContainer, colorTextLightSolid },
} = theme.useToken();
const setContent = useSetContent();
const [config, setConfig] = useRecoilState(configState);
const alertApi = useAlertApi()!;
const showModal = useShowModal();
@@ -29,6 +33,8 @@ export const App: React.FC = () => {
useEffect(() => {
showModal(<Spin spinning={true} fullscreen />);
setContent(<GettingStartedGuide />, [{ title: "Getting Started" }]);
fetchConfig()
.then((config) => {
setConfig(config);
@@ -64,22 +70,7 @@ export const App: React.FC = () => {
items={items}
/>
</Sider>
<Layout style={{ padding: "0 24px 24px" }}>
<Breadcrumb
style={{ margin: "16px 0" }}
items={[{ title: "Home" }]}
></Breadcrumb>
<Content
style={{
padding: 24,
margin: 0,
minHeight: 280,
background: colorBgContainer,
}}
>
Content
</Content>
</Layout>
<MainContentArea />
</Layout>
</Layout>
);
@@ -87,6 +78,7 @@ export const App: React.FC = () => {
const getSidenavItems = (config: Config | null): MenuProps["items"] => {
const showModal = useShowModal();
const setContent = useSetContent();
if (!config) return [];
@@ -108,7 +100,10 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => {
icon: <CheckCircleOutlined style={{ color: "green" }} />,
label: plan.id,
onClick: () => {
showModal(<AddPlanModal template={plan} />);
setContent(<PlanView plan={plan} />, [
{ title: "Plans" },
{ title: plan.id || "" },
]);
},
};
}),
+123
View File
@@ -0,0 +1,123 @@
import React, { useEffect, useState } from "react";
import { Plan } from "../../gen/ts/v1/config.pb";
import { Button, Flex } from "antd";
import { SettingOutlined } from "@ant-design/icons";
import { AddPlanModal } from "./AddPlanModel";
import { useShowModal } from "../components/ModalManager";
import { useRecoilValue } from "recoil";
import { configState } from "../state/config";
import { useAlertApi } from "../components/Alerts";
import { ResticUI } from "../../gen/ts/v1/service.pb";
import {
Operation,
OperationEvent,
OperationEventType,
} from "../../gen/ts/v1/operations.pb";
import { operationEmitter } from "../state/oplog";
export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => {
const showModal = useShowModal();
const alertsApi = useAlertApi()!;
const [operations, setOperations] = useState<Operation[]>([]);
useEffect(() => {
(async () => {
try {
const ops = await ResticUI.GetOperations(
{ planId: plan.id },
{ pathPrefix: "/api" }
);
if (!ops.operations) throw new Error("No operations returned");
setOperations(ops.operations);
} catch (e: any) {
alertsApi.error("Failed to fetch operations: " + e.message);
}
})();
const listener = (opEvent: OperationEvent) => {
setOperations((operations) => {
if (opEvent.type === OperationEventType.EVENT_CREATED) {
operations.push(opEvent.operation!);
} else if (opEvent.type === OperationEventType.EVENT_UPDATED) {
// We iterate from the back since the most recent operations are at the end and
// only recent ops receive updates.
for (let i = operations.length - 1; i >= 0; i--) {
if (operations[i].id === opEvent.operation?.id) {
operations[i] = opEvent.operation!;
break;
}
}
}
return operations;
});
};
operationEmitter.on("operation", listener);
return () => {
operationEmitter.removeListener("operation", listener);
};
}, [plan.id]);
// Gracefully handle deletions by checking if the plan is still in the config.
const config = useRecoilValue(configState);
let planInConfig = config.plans?.find((p) => p.id === plan.id);
if (!planInConfig) {
return <p>Plan was deleted.</p>;
}
plan = planInConfig;
const handleBackupNow = async () => {
try {
ResticUI.Backup({ value: plan.id }, { pathPrefix: "/api" });
alertsApi.success("Backup scheduled.");
} catch (e: any) {
alertsApi.error("Failed to schedule backup: " + e.message);
}
};
const handlePruneNow = () => {
alertsApi.warning("Not implemented yet :(");
};
return (
<>
<Flex gap="small" align="center" wrap="wrap">
<h1>{plan.id}</h1>
<Button
type="text"
size="small"
shape="circle"
icon={<SettingOutlined />}
onClick={() => {
showModal(<AddPlanModal template={plan} />);
}}
/>
</Flex>
<Flex gap="small" align="center" wrap="wrap">
<Button type="primary" onClick={handleBackupNow}>
Backup Now
</Button>
<Button type="primary" onClick={handlePruneNow}>
Prune Now
</Button>
</Flex>
<OperationsPanel operations={operations} />
</>
);
};
const OperationsPanel = ({ operations }: { operations: Operation[] }) => {
return (
<>
<h2>Operations List</h2>
{operations.map((op) => {
return (
<div key={op.id}>
<h3>{op.id}</h3>
</div>
);
})}
</>
);
};