mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-09-21 07:25:38 +00:00
feat: implement basic plan view
This commit is contained in:
+94
-29
@@ -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
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ export enum OperationStatus {
|
||||
STATUS_ERROR = "STATUS_ERROR",
|
||||
}
|
||||
|
||||
export type OperationList = {
|
||||
operations?: Operation[]
|
||||
}
|
||||
|
||||
|
||||
type BaseOperation = {
|
||||
id?: string
|
||||
|
||||
@@ -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) }
|
||||
);
|
||||
}
|
||||
}
|
||||
Generated
+58
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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> => {
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
);
|
||||
})();
|
||||
@@ -114,6 +114,7 @@ export const AddPlanModal = ({
|
||||
<>
|
||||
<Modal
|
||||
open={true}
|
||||
onCancel={handleCancel}
|
||||
title="Add Plan"
|
||||
footer={[
|
||||
<Button loading={confirmLoading} key="back" onClick={handleCancel}>
|
||||
|
||||
@@ -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
@@ -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 || "" },
|
||||
]);
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user