mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-07-08 11:10:43 +00:00
feat: repo can be created through UI
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc (unknown)
|
||||
// source: types/value.proto
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type StringValue struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (x *StringValue) Reset() {
|
||||
*x = StringValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_types_value_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *StringValue) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*StringValue) ProtoMessage() {}
|
||||
|
||||
func (x *StringValue) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_value_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 StringValue.ProtoReflect.Descriptor instead.
|
||||
func (*StringValue) Descriptor() ([]byte, []int) {
|
||||
return file_types_value_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *StringValue) GetValue() string {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type StringList struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
}
|
||||
|
||||
func (x *StringList) Reset() {
|
||||
*x = StringList{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_types_value_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *StringList) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*StringList) ProtoMessage() {}
|
||||
|
||||
func (x *StringList) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_value_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 StringList.ProtoReflect.Descriptor instead.
|
||||
func (*StringList) Descriptor() ([]byte, []int) {
|
||||
return file_types_value_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *StringList) GetValues() []string {
|
||||
if x != nil {
|
||||
return x.Values
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_types_value_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_types_value_proto_rawDesc = []byte{
|
||||
0x0a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0x23, 0x0a, 0x0b, 0x53, 0x74,
|
||||
0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22,
|
||||
0x24, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x0a,
|
||||
0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x73, 0x42, 0x2f, 0x5a, 0x2d, 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, 0x65, 0x6e, 0x2f, 0x67, 0x6f,
|
||||
0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_types_value_proto_rawDescOnce sync.Once
|
||||
file_types_value_proto_rawDescData = file_types_value_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_types_value_proto_rawDescGZIP() []byte {
|
||||
file_types_value_proto_rawDescOnce.Do(func() {
|
||||
file_types_value_proto_rawDescData = protoimpl.X.CompressGZIP(file_types_value_proto_rawDescData)
|
||||
})
|
||||
return file_types_value_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_types_value_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_types_value_proto_goTypes = []interface{}{
|
||||
(*StringValue)(nil), // 0: types.StringValue
|
||||
(*StringList)(nil), // 1: types.StringList
|
||||
}
|
||||
var file_types_value_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] 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
|
||||
}
|
||||
|
||||
func init() { file_types_value_proto_init() }
|
||||
func file_types_value_proto_init() {
|
||||
if File_types_value_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_types_value_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*StringValue); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_types_value_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*StringList); 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{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_types_value_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_types_value_proto_goTypes,
|
||||
DependencyIndexes: file_types_value_proto_depIdxs,
|
||||
MessageInfos: file_types_value_proto_msgTypes,
|
||||
}.Build()
|
||||
File_types_value_proto = out.File
|
||||
file_types_value_proto_rawDesc = nil
|
||||
file_types_value_proto_goTypes = nil
|
||||
file_types_value_proto_depIdxs = nil
|
||||
}
|
||||
+199
-50
@@ -26,7 +26,6 @@ type Config struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Version int32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
|
||||
LogDir string `protobuf:"bytes,2,opt,name=log_dir,proto3" json:"log_dir,omitempty"`
|
||||
Repos []*Repo `protobuf:"bytes,3,rep,name=repos,proto3" json:"repos,omitempty"`
|
||||
Plans []*Plan `protobuf:"bytes,4,rep,name=plans,proto3" json:"plans,omitempty"`
|
||||
}
|
||||
@@ -70,13 +69,6 @@ func (x *Config) GetVersion() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetLogDir() string {
|
||||
if x != nil {
|
||||
return x.LogDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetRepos() []*Repo {
|
||||
if x != nil {
|
||||
return x.Repos
|
||||
@@ -230,10 +222,12 @@ type Plan struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // unique but human readable ID for this plan.
|
||||
Repo string `protobuf:"bytes,2,opt,name=repo,proto3" json:"repo,omitempty"` // ID of the repo to use.
|
||||
Paths []string `protobuf:"bytes,4,rep,name=paths,proto3" json:"paths,omitempty"` // paths to include in the backup.
|
||||
Excludes []string `protobuf:"bytes,5,rep,name=excludes,proto3" json:"excludes,omitempty"` // glob patterns to exclude.
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // unique but human readable ID for this plan.
|
||||
Repo string `protobuf:"bytes,2,opt,name=repo,proto3" json:"repo,omitempty"` // ID of the repo to use.
|
||||
Paths []string `protobuf:"bytes,4,rep,name=paths,proto3" json:"paths,omitempty"` // paths to include in the backup.
|
||||
Excludes []string `protobuf:"bytes,5,rep,name=excludes,proto3" json:"excludes,omitempty"` // glob patterns to exclude.
|
||||
Cron string `protobuf:"bytes,6,opt,name=cron,proto3" json:"cron,omitempty"`
|
||||
Retention *RetentionPolicy `protobuf:"bytes,7,opt,name=retention,proto3" json:"retention,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Plan) Reset() {
|
||||
@@ -296,38 +290,179 @@ func (x *Plan) GetExcludes() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Plan) GetCron() string {
|
||||
if x != nil {
|
||||
return x.Cron
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Plan) GetRetention() *RetentionPolicy {
|
||||
if x != nil {
|
||||
return x.Retention
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RetentionPolicy struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// max_unused_limit is used to decide when forget should be run.
|
||||
MaxUnusedLimit string `protobuf:"bytes,1,opt,name=max_unused_limit,json=maxUnusedLimit,proto3" json:"max_unused_limit,omitempty"` // e.g. a percentage i.e. 25% or a number of megabytes.
|
||||
KeepLastN int32 `protobuf:"varint,2,opt,name=keep_last_n,json=keepLastN,proto3" json:"keep_last_n,omitempty"` // keep the last n snapshots.
|
||||
KeepHourly int32 `protobuf:"varint,3,opt,name=keep_hourly,json=keepHourly,proto3" json:"keep_hourly,omitempty"` // keep the last n hourly snapshots.
|
||||
KeepDaily int32 `protobuf:"varint,4,opt,name=keep_daily,json=keepDaily,proto3" json:"keep_daily,omitempty"` // keep the last n daily snapshots.
|
||||
KeepWeekly int32 `protobuf:"varint,5,opt,name=keep_weekly,json=keepWeekly,proto3" json:"keep_weekly,omitempty"` // keep the last n weekly snapshots.
|
||||
KeepMonthly int32 `protobuf:"varint,6,opt,name=keep_monthly,json=keepMonthly,proto3" json:"keep_monthly,omitempty"` // keep the last n monthly snapshots.
|
||||
KeepYearly int32 `protobuf:"varint,7,opt,name=keep_yearly,json=keepYearly,proto3" json:"keep_yearly,omitempty"` // keep the last n yearly snapshots.
|
||||
KeepWithinDuration string `protobuf:"bytes,8,opt,name=keep_within_duration,json=keepWithinDuration,proto3" json:"keep_within_duration,omitempty"` // keep snapshots within a duration e.g. 1y2m3d4h5m6s
|
||||
}
|
||||
|
||||
func (x *RetentionPolicy) Reset() {
|
||||
*x = RetentionPolicy{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_v1_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *RetentionPolicy) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RetentionPolicy) ProtoMessage() {}
|
||||
|
||||
func (x *RetentionPolicy) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_v1_config_proto_msgTypes[4]
|
||||
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 RetentionPolicy.ProtoReflect.Descriptor instead.
|
||||
func (*RetentionPolicy) Descriptor() ([]byte, []int) {
|
||||
return file_v1_config_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *RetentionPolicy) GetMaxUnusedLimit() string {
|
||||
if x != nil {
|
||||
return x.MaxUnusedLimit
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RetentionPolicy) GetKeepLastN() int32 {
|
||||
if x != nil {
|
||||
return x.KeepLastN
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RetentionPolicy) GetKeepHourly() int32 {
|
||||
if x != nil {
|
||||
return x.KeepHourly
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RetentionPolicy) GetKeepDaily() int32 {
|
||||
if x != nil {
|
||||
return x.KeepDaily
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RetentionPolicy) GetKeepWeekly() int32 {
|
||||
if x != nil {
|
||||
return x.KeepWeekly
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RetentionPolicy) GetKeepMonthly() int32 {
|
||||
if x != nil {
|
||||
return x.KeepMonthly
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RetentionPolicy) GetKeepYearly() int32 {
|
||||
if x != nil {
|
||||
return x.KeepYearly
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RetentionPolicy) GetKeepWithinDuration() string {
|
||||
if x != nil {
|
||||
return x.KeepWithinDuration
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_v1_config_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_v1_config_proto_rawDesc = []byte{
|
||||
0x0a, 0x0f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x12, 0x02, 0x76, 0x31, 0x22, 0x7c, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
|
||||
0x6f, 0x12, 0x02, 0x76, 0x31, 0x22, 0x62, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
|
||||
0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67,
|
||||
0x5f, 0x64, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x5f,
|
||||
0x64, 0x69, 0x72, 0x12, 0x1e, 0x0a, 0x05, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03,
|
||||
0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x52, 0x05, 0x72, 0x65,
|
||||
0x70, 0x6f, 0x73, 0x12, 0x1e, 0x0a, 0x05, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03,
|
||||
0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x05, 0x70, 0x6c,
|
||||
0x61, 0x6e, 0x73, 0x22, 0x36, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
|
||||
0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x6c, 0x0a, 0x04, 0x52,
|
||||
0x65, 0x70, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
|
||||
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
|
||||
0x64, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03,
|
||||
0x65, 0x6e, 0x76, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x03,
|
||||
0x28, 0x09, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x22, 0x5c, 0x0a, 0x04, 0x50, 0x6c, 0x61,
|
||||
0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
|
||||
0x64, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x70, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x04, 0x72, 0x65, 0x70, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x04,
|
||||
0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65,
|
||||
0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x65,
|
||||
0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 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,
|
||||
0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x05, 0x72, 0x65, 0x70,
|
||||
0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65,
|
||||
0x70, 0x6f, 0x52, 0x05, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x1e, 0x0a, 0x05, 0x70, 0x6c, 0x61,
|
||||
0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c,
|
||||
0x61, 0x6e, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x22, 0x36, 0x0a, 0x04, 0x55, 0x73, 0x65,
|
||||
0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
|
||||
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
|
||||
0x64, 0x22, 0x6c, 0x0a, 0x04, 0x52, 0x65, 0x70, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x1a, 0x0a, 0x08, 0x70,
|
||||
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
|
||||
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x04,
|
||||
0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x65, 0x6e, 0x76, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61,
|
||||
0x67, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x22,
|
||||
0xa3, 0x01, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x70, 0x6f,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x65, 0x70, 0x6f, 0x12, 0x14, 0x0a, 0x05,
|
||||
0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x74,
|
||||
0x68, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0x18, 0x05,
|
||||
0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x72,
|
||||
0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x09, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x18,
|
||||
0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x65, 0x6e,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x09, 0x72, 0x65, 0x74, 0x65,
|
||||
0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb2, 0x02, 0x0a, 0x0f, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x78,
|
||||
0x5f, 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x4c, 0x69,
|
||||
0x6d, 0x69, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x6c, 0x61, 0x73, 0x74,
|
||||
0x5f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x4c, 0x61,
|
||||
0x73, 0x74, 0x4e, 0x12, 0x1f, 0x0a, 0x0b, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x68, 0x6f, 0x75, 0x72,
|
||||
0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x48, 0x6f,
|
||||
0x75, 0x72, 0x6c, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x64, 0x61, 0x69,
|
||||
0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x44, 0x61,
|
||||
0x69, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x77, 0x65, 0x65, 0x6b,
|
||||
0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x57, 0x65,
|
||||
0x65, 0x6b, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x6d, 0x6f, 0x6e,
|
||||
0x74, 0x68, 0x6c, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6b, 0x65, 0x65, 0x70,
|
||||
0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x6b, 0x65, 0x65, 0x70, 0x5f,
|
||||
0x79, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6b, 0x65,
|
||||
0x65, 0x70, 0x59, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x6b, 0x65, 0x65, 0x70,
|
||||
0x5f, 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6b, 0x65, 0x65, 0x70, 0x57, 0x69, 0x74, 0x68,
|
||||
0x69, 0x6e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 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 (
|
||||
@@ -342,21 +477,23 @@ func file_v1_config_proto_rawDescGZIP() []byte {
|
||||
return file_v1_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_v1_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_v1_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_v1_config_proto_goTypes = []interface{}{
|
||||
(*Config)(nil), // 0: v1.Config
|
||||
(*User)(nil), // 1: v1.User
|
||||
(*Repo)(nil), // 2: v1.Repo
|
||||
(*Plan)(nil), // 3: v1.Plan
|
||||
(*Config)(nil), // 0: v1.Config
|
||||
(*User)(nil), // 1: v1.User
|
||||
(*Repo)(nil), // 2: v1.Repo
|
||||
(*Plan)(nil), // 3: v1.Plan
|
||||
(*RetentionPolicy)(nil), // 4: v1.RetentionPolicy
|
||||
}
|
||||
var file_v1_config_proto_depIdxs = []int32{
|
||||
2, // 0: v1.Config.repos:type_name -> v1.Repo
|
||||
3, // 1: v1.Config.plans:type_name -> v1.Plan
|
||||
2, // [2:2] is the sub-list for method output_type
|
||||
2, // [2:2] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
2, // [2:2] is the sub-list for extension extendee
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
4, // 2: v1.Plan.retention:type_name -> v1.RetentionPolicy
|
||||
3, // [3:3] is the sub-list for method output_type
|
||||
3, // [3:3] is the sub-list for method input_type
|
||||
3, // [3:3] is the sub-list for extension type_name
|
||||
3, // [3:3] is the sub-list for extension extendee
|
||||
0, // [0:3] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_v1_config_proto_init() }
|
||||
@@ -413,6 +550,18 @@ func file_v1_config_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_v1_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RetentionPolicy); 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{
|
||||
@@ -420,7 +569,7 @@ func file_v1_config_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_v1_config_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 4,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
+44
-32
@@ -7,6 +7,7 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
types "github.com/garethgeorge/resticui/gen/go/types"
|
||||
_ "google.golang.org/genproto/googleapis/api/annotations"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
@@ -27,50 +28,61 @@ var file_v1_service_proto_rawDesc = []byte{
|
||||
0x0a, 0x10, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x12, 0x02, 0x76, 0x31, 0x1a, 0x0f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
|
||||
0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e,
|
||||
0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70,
|
||||
0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x32, 0x8e, 0x02, 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, 0x44,
|
||||
0x0a, 0x09, 0x47, 0x65, 0x74, 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, 0x09, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x12,
|
||||
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e,
|
||||
0x74, 0x73, 0x30, 0x01, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f,
|
||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70,
|
||||
0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xeb, 0x02, 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, 0x44, 0x0a, 0x09, 0x47, 0x65, 0x74, 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, 0x09, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x76, 0x65, 0x6e,
|
||||
0x74, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x65,
|
||||
0x76, 0x65, 0x6e, 0x74, 0x73, 0x30, 0x01, 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 file_v1_service_proto_goTypes = []interface{}{
|
||||
(*emptypb.Empty)(nil), // 0: google.protobuf.Empty
|
||||
(*Config)(nil), // 1: v1.Config
|
||||
(*Repo)(nil), // 2: v1.Repo
|
||||
(*Event)(nil), // 3: v1.Event
|
||||
(*emptypb.Empty)(nil), // 0: google.protobuf.Empty
|
||||
(*Config)(nil), // 1: v1.Config
|
||||
(*Repo)(nil), // 2: v1.Repo
|
||||
(*types.StringValue)(nil), // 3: types.StringValue
|
||||
(*Event)(nil), // 4: v1.Event
|
||||
(*types.StringList)(nil), // 5: types.StringList
|
||||
}
|
||||
var file_v1_service_proto_depIdxs = []int32{
|
||||
0, // 0: v1.ResticUI.GetConfig:input_type -> google.protobuf.Empty
|
||||
1, // 1: v1.ResticUI.SetConfig:input_type -> v1.Config
|
||||
2, // 2: v1.ResticUI.AddRepo:input_type -> v1.Repo
|
||||
0, // 3: v1.ResticUI.GetEvents:input_type -> google.protobuf.Empty
|
||||
1, // 4: v1.ResticUI.GetConfig:output_type -> v1.Config
|
||||
1, // 5: v1.ResticUI.SetConfig:output_type -> v1.Config
|
||||
1, // 6: v1.ResticUI.AddRepo:output_type -> v1.Config
|
||||
3, // 7: v1.ResticUI.GetEvents:output_type -> v1.Event
|
||||
4, // [4:8] is the sub-list for method output_type
|
||||
0, // [0:4] is the sub-list for method input_type
|
||||
3, // 4: v1.ResticUI.PathAutocomplete:input_type -> types.StringValue
|
||||
1, // 5: v1.ResticUI.GetConfig:output_type -> v1.Config
|
||||
1, // 6: v1.ResticUI.SetConfig:output_type -> v1.Config
|
||||
1, // 7: v1.ResticUI.AddRepo:output_type -> v1.Config
|
||||
4, // 8: v1.ResticUI.GetEvents:output_type -> v1.Event
|
||||
5, // 9: v1.ResticUI.PathAutocomplete:output_type -> types.StringList
|
||||
5, // [5:10] is the sub-list for method output_type
|
||||
0, // [0:5] 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
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/garethgeorge/resticui/gen/go/types"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
|
||||
"google.golang.org/grpc"
|
||||
@@ -135,6 +136,40 @@ func request_ResticUI_GetEvents_0(ctx context.Context, marshaler runtime.Marshal
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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.PathAutocomplete(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_ResticUI_PathAutocomplete_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.PathAutocomplete(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterResticUIHandlerServer registers the http handlers for service ResticUI to "mux".
|
||||
// UnaryRPC :call ResticUIServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
@@ -223,6 +258,31 @@ func RegisterResticUIHandlerServer(ctx context.Context, mux *runtime.ServeMux, s
|
||||
return
|
||||
})
|
||||
|
||||
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()
|
||||
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/PathAutocomplete", runtime.WithHTTPPathPattern("/v1/autocomplete/path"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_ResticUI_PathAutocomplete_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_PathAutocomplete_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -352,6 +412,28 @@ func RegisterResticUIHandlerClient(ctx context.Context, mux *runtime.ServeMux, c
|
||||
|
||||
})
|
||||
|
||||
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()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
var annotatedContext context.Context
|
||||
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/v1.ResticUI/PathAutocomplete", runtime.WithHTTPPathPattern("/v1/autocomplete/path"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_ResticUI_PathAutocomplete_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_PathAutocomplete_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -363,6 +445,8 @@ var (
|
||||
pattern_ResticUI_AddRepo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "config", "repo"}, ""))
|
||||
|
||||
pattern_ResticUI_GetEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "events"}, ""))
|
||||
|
||||
pattern_ResticUI_PathAutocomplete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "autocomplete", "path"}, ""))
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -373,4 +457,6 @@ var (
|
||||
forward_ResticUI_AddRepo_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_ResticUI_GetEvents_0 = runtime.ForwardResponseStream
|
||||
|
||||
forward_ResticUI_PathAutocomplete_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ package v1
|
||||
|
||||
import (
|
||||
context "context"
|
||||
types "github.com/garethgeorge/resticui/gen/go/types"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
@@ -20,10 +21,11 @@ import (
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
const (
|
||||
ResticUI_GetConfig_FullMethodName = "/v1.ResticUI/GetConfig"
|
||||
ResticUI_SetConfig_FullMethodName = "/v1.ResticUI/SetConfig"
|
||||
ResticUI_AddRepo_FullMethodName = "/v1.ResticUI/AddRepo"
|
||||
ResticUI_GetEvents_FullMethodName = "/v1.ResticUI/GetEvents"
|
||||
ResticUI_GetConfig_FullMethodName = "/v1.ResticUI/GetConfig"
|
||||
ResticUI_SetConfig_FullMethodName = "/v1.ResticUI/SetConfig"
|
||||
ResticUI_AddRepo_FullMethodName = "/v1.ResticUI/AddRepo"
|
||||
ResticUI_GetEvents_FullMethodName = "/v1.ResticUI/GetEvents"
|
||||
ResticUI_PathAutocomplete_FullMethodName = "/v1.ResticUI/PathAutocomplete"
|
||||
)
|
||||
|
||||
// ResticUIClient is the client API for ResticUI service.
|
||||
@@ -34,6 +36,7 @@ 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)
|
||||
GetEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (ResticUI_GetEventsClient, error)
|
||||
PathAutocomplete(ctx context.Context, in *types.StringValue, opts ...grpc.CallOption) (*types.StringList, error)
|
||||
}
|
||||
|
||||
type resticUIClient struct {
|
||||
@@ -103,6 +106,15 @@ func (x *resticUIGetEventsClient) Recv() (*Event, error) {
|
||||
return m, 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...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ResticUIServer is the server API for ResticUI service.
|
||||
// All implementations must embed UnimplementedResticUIServer
|
||||
// for forward compatibility
|
||||
@@ -111,6 +123,7 @@ type ResticUIServer interface {
|
||||
SetConfig(context.Context, *Config) (*Config, error)
|
||||
AddRepo(context.Context, *Repo) (*Config, error)
|
||||
GetEvents(*emptypb.Empty, ResticUI_GetEventsServer) error
|
||||
PathAutocomplete(context.Context, *types.StringValue) (*types.StringList, error)
|
||||
mustEmbedUnimplementedResticUIServer()
|
||||
}
|
||||
|
||||
@@ -130,6 +143,9 @@ func (UnimplementedResticUIServer) AddRepo(context.Context, *Repo) (*Config, err
|
||||
func (UnimplementedResticUIServer) GetEvents(*emptypb.Empty, ResticUI_GetEventsServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method GetEvents not implemented")
|
||||
}
|
||||
func (UnimplementedResticUIServer) PathAutocomplete(context.Context, *types.StringValue) (*types.StringList, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method PathAutocomplete not implemented")
|
||||
}
|
||||
func (UnimplementedResticUIServer) mustEmbedUnimplementedResticUIServer() {}
|
||||
|
||||
// UnsafeResticUIServer may be embedded to opt out of forward compatibility for this service.
|
||||
@@ -218,6 +234,24 @@ func (x *resticUIGetEventsServer) Send(m *Event) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ResticUIServer).PathAutocomplete(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: ResticUI_PathAutocomplete_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ResticUIServer).PathAutocomplete(ctx, req.(*types.StringValue))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// ResticUI_ServiceDesc is the grpc.ServiceDesc for ResticUI service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -237,6 +271,10 @@ var ResticUI_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "AddRepo",
|
||||
Handler: _ResticUI_AddRepo_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "PathAutocomplete",
|
||||
Handler: _ResticUI_PathAutocomplete_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
|
||||
@@ -3,11 +3,11 @@ module github.com/garethgeorge/resticui
|
||||
go 1.21.3
|
||||
|
||||
require (
|
||||
github.com/gitploy-io/cronexpr v0.2.2
|
||||
github.com/google/renameio v1.0.1
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
go.uber.org/zap v1.26.0
|
||||
golang.org/x/sync v0.5.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17
|
||||
google.golang.org/grpc v1.59.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
@@ -22,6 +22,7 @@ require (
|
||||
github.com/vine-io/vine v1.6.16 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
|
||||
@@ -10,6 +10,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gitploy-io/cronexpr v0.2.2 h1:Au+wK6FqmOLAF7AkW6q4gnrNXTe3rEW97XFZ4chy0xs=
|
||||
github.com/gitploy-io/cronexpr v0.2.2/go.mod h1:Uep5sbzUSocMZvJ1s0lNI9zi37s5iUI1llkw3vRGK9M=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
|
||||
@@ -2,11 +2,14 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/garethgeorge/resticui/gen/go/types"
|
||||
v1 "github.com/garethgeorge/resticui/gen/go/v1"
|
||||
"github.com/garethgeorge/resticui/internal/config"
|
||||
"github.com/garethgeorge/resticui/pkg/restic"
|
||||
@@ -103,6 +106,21 @@ func (s *Server) GetEvents(_ *emptypb.Empty, stream v1.ResticUI_GetEventsServer)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return &types.StringList{}, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var paths []string
|
||||
for _, ent := range ents {
|
||||
paths = append(paths, ent.Name())
|
||||
}
|
||||
|
||||
return &types.StringList{Values: paths}, nil
|
||||
}
|
||||
|
||||
|
||||
// PublishEvent publishes an event to all GetEvents streams. It is effectively a multicast.
|
||||
|
||||
@@ -22,7 +22,6 @@ type ConfigStore interface {
|
||||
|
||||
func NewDefaultConfig() *v1.Config {
|
||||
return &v1.Config{
|
||||
LogDir: "/var/log/resticui",
|
||||
Repos: []*v1.Repo{},
|
||||
Plans: []*v1.Plan{},
|
||||
}
|
||||
|
||||
+55
-14
@@ -3,17 +3,15 @@ package config
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
v1 "github.com/garethgeorge/resticui/gen/go/v1"
|
||||
"github.com/gitploy-io/cronexpr"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
)
|
||||
|
||||
func validateConfig(c *v1.Config) error {
|
||||
if c.LogDir == "" {
|
||||
return errors.New("log_dir is required")
|
||||
}
|
||||
|
||||
var err error
|
||||
repos := make(map[string]*v1.Repo)
|
||||
if c.Repos != nil {
|
||||
@@ -27,16 +25,9 @@ func validateConfig(c *v1.Config) error {
|
||||
|
||||
if c.Plans != nil {
|
||||
for _, plan := range c.Plans {
|
||||
if plan.Paths == nil || len(plan.Paths) == 0 {
|
||||
err = multierror.Append(err, fmt.Errorf("plan %s: path is required", plan.GetId()))
|
||||
}
|
||||
|
||||
if plan.Repo == "" {
|
||||
err = multierror.Append(err,fmt.Errorf("plan %s: repo is required", plan.GetId()))
|
||||
}
|
||||
|
||||
if _, ok := repos[plan.Repo]; !ok {
|
||||
err = multierror.Append(err, fmt.Errorf("plan %s: repo %s not found", plan.GetId(), plan.Repo))
|
||||
err := validatePlan(plan, repos);
|
||||
if err != nil {
|
||||
err = multierror.Append(err, fmt.Errorf("plan %s: %w", plan.GetId(), err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,5 +55,55 @@ func validateRepo(repo *v1.Repo) error {
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func validatePlan(plan *v1.Plan, repos map[string]*v1.Repo) error {
|
||||
var err error
|
||||
if plan.Paths == nil || len(plan.Paths) == 0 {
|
||||
err = multierror.Append(err, fmt.Errorf("path is required"))
|
||||
}
|
||||
|
||||
if plan.Repo == "" {
|
||||
err = multierror.Append(err,fmt.Errorf("repo is required"))
|
||||
}
|
||||
|
||||
if _, ok := repos[plan.Repo]; !ok {
|
||||
err = multierror.Append(err, fmt.Errorf("repo %q not found", plan.Repo))
|
||||
}
|
||||
|
||||
|
||||
if _, e := cronexpr.Parse(plan.GetCron()); err != nil {
|
||||
err = multierror.Append(err, fmt.Errorf("invalid cron %q: %w", plan.GetCron(), e))
|
||||
}
|
||||
|
||||
if plan.GetRetention() != nil {
|
||||
if e := validateRetention(plan.GetRetention()); e != nil {
|
||||
err = multierror.Append(err, fmt.Errorf("invalid retention policy: %w", e))
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func validateRetention(policy *v1.RetentionPolicy) error {
|
||||
var err error
|
||||
if policy.GetKeepWithinDuration() != "" {
|
||||
match, e := regexp.Match(`(\d+h)?(\d+m)?(\d+s)?`, []byte(policy.GetKeepWithinDuration()))
|
||||
if e != nil {
|
||||
panic(e) // regex error
|
||||
}
|
||||
if !match {
|
||||
err = multierror.Append(err, fmt.Errorf("invalid keep_within_duration %q", policy.GetKeepWithinDuration()))
|
||||
}
|
||||
|
||||
if policy.GetKeepLastN() != 0 || policy.GetKeepHourly() != 0 || policy.GetKeepDaily() != 0 || policy.GetKeepWeekly() != 0 || policy.GetKeepMonthly() != 0 || policy.GetKeepYearly() != 0 {
|
||||
err = multierror.Append(err, fmt.Errorf("keep_within_duration cannot be used with other retention settings"))
|
||||
}
|
||||
} else {
|
||||
if policy.GetKeepLastN() == 0 && policy.GetKeepHourly() == 0 && policy.GetKeepDaily() == 0 && policy.GetKeepWeekly() == 0 && policy.GetKeepMonthly() == 0 && policy.GetKeepYearly() == 0 {
|
||||
err = multierror.Append(err, fmt.Errorf("at least one retention policy must be set"))
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package types;
|
||||
|
||||
option go_package = "github.com/garethgeorge/resticui/gen/go/types";
|
||||
|
||||
message StringValue {
|
||||
string value = 1;
|
||||
}
|
||||
|
||||
message StringList {
|
||||
repeated string values = 1;
|
||||
}
|
||||
+15
-1
@@ -6,7 +6,6 @@ option go_package = "github.com/garethgeorge/resticui/go/proto/v1";
|
||||
|
||||
message Config {
|
||||
int32 version = 1;
|
||||
string log_dir = 2 [json_name="log_dir"];
|
||||
repeated Repo repos = 3 [json_name="repos"];
|
||||
repeated Plan plans = 4 [json_name="plans"];
|
||||
}
|
||||
@@ -29,4 +28,19 @@ message Plan {
|
||||
string repo = 2 [json_name="repo"]; // ID of the repo to use.
|
||||
repeated string paths = 4 [json_name="paths"]; // paths to include in the backup.
|
||||
repeated string excludes = 5 [json_name="excludes"]; // glob patterns to exclude.
|
||||
string cron = 6 [json_name="cron"];
|
||||
RetentionPolicy retention = 7 [json_name="retention"];
|
||||
}
|
||||
|
||||
message RetentionPolicy {
|
||||
// max_unused_limit is used to decide when forget should be run.
|
||||
string max_unused_limit = 1; // e.g. a percentage i.e. 25% or a number of megabytes.
|
||||
|
||||
int32 keep_last_n = 2; // keep the last n snapshots.
|
||||
int32 keep_hourly = 3; // keep the last n hourly snapshots.
|
||||
int32 keep_daily = 4; // keep the last n daily snapshots.
|
||||
int32 keep_weekly = 5; // keep the last n weekly snapshots.
|
||||
int32 keep_monthly = 6; // keep the last n monthly snapshots.
|
||||
int32 keep_yearly = 7; // keep the last n yearly snapshots.
|
||||
string keep_within_duration = 8; // keep snapshots within a duration e.g. 1y2m3d4h5m6s
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ option go_package = "github.com/garethgeorge/resticui/go/proto/v1";
|
||||
|
||||
import "v1/config.proto";
|
||||
import "v1/events.proto";
|
||||
import "types/value.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/api/annotations.proto";
|
||||
|
||||
@@ -35,4 +36,11 @@ service ResticUI {
|
||||
get: "/v1/events"
|
||||
};
|
||||
}
|
||||
|
||||
rpc PathAutocomplete (types.StringValue) returns (types.StringList) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/autocomplete/path"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY
|
||||
*/
|
||||
export type StringValue = {
|
||||
value?: string
|
||||
}
|
||||
|
||||
export type StringList = {
|
||||
values?: string[]
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
export type Config = {
|
||||
version?: number
|
||||
logDir?: string
|
||||
repos?: Repo[]
|
||||
plans?: Plan[]
|
||||
}
|
||||
@@ -28,4 +27,17 @@ export type Plan = {
|
||||
repo?: string
|
||||
paths?: string[]
|
||||
excludes?: string[]
|
||||
cron?: string
|
||||
retention?: RetentionPolicy
|
||||
}
|
||||
|
||||
export type RetentionPolicy = {
|
||||
maxUnusedLimit?: string
|
||||
keepLastN?: number
|
||||
keepHourly?: number
|
||||
keepDaily?: number
|
||||
keepWeekly?: number
|
||||
keepMonthly?: number
|
||||
keepYearly?: number
|
||||
keepWithinDuration?: string
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import * as fm from "../fetch.pb"
|
||||
import * as GoogleProtobufEmpty from "../google/protobuf/empty.pb"
|
||||
import * as TypesValue from "../types/value.pb"
|
||||
import * as V1Config from "./config.pb"
|
||||
import * as V1Events from "./events.pb"
|
||||
export class ResticUI {
|
||||
@@ -21,4 +22,7 @@ export class ResticUI {
|
||||
static GetEvents(req: GoogleProtobufEmpty.Empty, entityNotifier?: fm.NotifyStreamEntityArrival<V1Events.Event>, initReq?: fm.InitReq): Promise<void> {
|
||||
return fm.fetchStreamingRequest<GoogleProtobufEmpty.Empty, V1Events.Event>(`/v1/events?${fm.renderURLSearchParams(req, [])}`, entityNotifier, {...initReq, method: "GET"})
|
||||
}
|
||||
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)})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Spin } from "antd";
|
||||
import React, { useContext } from "react";
|
||||
import { createContext } from "react";
|
||||
|
||||
const ModalContext = createContext<{
|
||||
model: React.ReactNode | null;
|
||||
setModel: (model: React.ReactNode | null) => void;
|
||||
}>({
|
||||
model: null,
|
||||
setModel: () => {
|
||||
throw new Error("add a ModelContextProvider to your hierarchy");
|
||||
},
|
||||
});
|
||||
|
||||
export const ModalContextProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const [modal, setModals] = React.useState<React.ReactNode | null>([]);
|
||||
|
||||
return (
|
||||
<ModalContext.Provider
|
||||
value={{
|
||||
model: modal,
|
||||
setModel: setModals,
|
||||
}}
|
||||
>
|
||||
{modal}
|
||||
{children}
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useShowModal = () => {
|
||||
const context = useContext(ModalContext);
|
||||
return context.setModel;
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import { AutoComplete } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ResticUI } from "../../gen/ts/v1/service.pb";
|
||||
import { StringList } from "../../gen/ts/types/value.pb";
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined;
|
||||
|
||||
export const URIAutocomplete = (props: React.PropsWithChildren) => {
|
||||
const [value, setValue] = useState("");
|
||||
const [options, setOptions] = useState<{ value: string }[]>([]);
|
||||
const [showOptions, setShowOptions] = useState<{ value: string }[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setShowOptions(options.filter((o) => o.value.indexOf(value) !== -1));
|
||||
}, [options]);
|
||||
|
||||
const onChange = (value: string) => {
|
||||
setValue(value);
|
||||
|
||||
const lastSlash = value.lastIndexOf("/");
|
||||
if (lastSlash !== -1) {
|
||||
value = value.substring(0, lastSlash);
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
ResticUI.PathAutocomplete({ value: value + "/" }, { pathPrefix: "/api" })
|
||||
.then((res: StringList) => {
|
||||
if (!res.values) {
|
||||
return;
|
||||
}
|
||||
const vals = res.values.map((v) => {
|
||||
return {
|
||||
value: value + "/" + v,
|
||||
};
|
||||
});
|
||||
setOptions(vals);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log("Path autocomplete error: ", e);
|
||||
});
|
||||
}, 100);
|
||||
};
|
||||
|
||||
return <AutoComplete options={showOptions} onSearch={onChange} {...props} />;
|
||||
};
|
||||
+4
-1
@@ -4,11 +4,14 @@ import { App } from "./views/App";
|
||||
import { RecoilRoot } from "recoil";
|
||||
import ErrorBoundary from "antd/es/alert/ErrorBoundary";
|
||||
import { AlertContextProvider } from "./components/Alerts";
|
||||
import { ModalContextProvider } from "./components/ModalManager";
|
||||
|
||||
const Root = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<AlertContextProvider>{children}</AlertContextProvider>
|
||||
<AlertContextProvider>
|
||||
<ModalContextProvider>{children}</ModalContextProvider>
|
||||
</AlertContextProvider>
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { atom, useSetRecoilState } from "recoil";
|
||||
import { Config } from "../../gen/ts/v1/config.pb";
|
||||
import { Config, Repo } from "../../gen/ts/v1/config.pb";
|
||||
import { ResticUI } from "../../gen/ts/v1/service.pb";
|
||||
|
||||
export const configState = atom({
|
||||
@@ -8,10 +8,17 @@ export const configState = atom({
|
||||
});
|
||||
|
||||
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> => {
|
||||
return await ResticUI.AddRepo(repo, {
|
||||
pathPrefix: "/api/",
|
||||
});
|
||||
};
|
||||
|
||||
export const setConfig = async (config: Config): Promise<Config> => {
|
||||
return await ResticUI.SetConfig(config, {
|
||||
pathPrefix: "/api/",
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
import { Form, Modal, Input, Typography } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useShowModal } from "../components/ModalManager";
|
||||
import { Plan } from "../../gen/ts/v1/config.pb";
|
||||
|
||||
const nameRegex = /^[a-zA-Z0-9\-_ ]+$/;
|
||||
|
||||
export const AddPlanModal = ({
|
||||
template,
|
||||
}: {
|
||||
template: Partial<Plan> | null;
|
||||
}) => {
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
const showModal = useShowModal();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
template = template || {};
|
||||
|
||||
const handleOk = () => {
|
||||
setConfirmLoading(true);
|
||||
setTimeout(() => {
|
||||
showModal(null);
|
||||
setConfirmLoading(false);
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
showModal(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
open={true}
|
||||
title="Add Plan"
|
||||
onOk={handleOk}
|
||||
confirmLoading={confirmLoading}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<Form layout={"vertical"} autoComplete="off" form={form}>
|
||||
{/* Plan.id */}
|
||||
<Form.Item<Plan>
|
||||
hasFeedback
|
||||
name="id"
|
||||
label="Plan Name"
|
||||
initialValue={template.id}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "Please input plan name",
|
||||
},
|
||||
{
|
||||
pattern: nameRegex,
|
||||
message: "Invalid symbol",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
{/* Plan.repo */}
|
||||
<Form.Item<Plan>
|
||||
hasFeedback
|
||||
name="repo"
|
||||
label="Repo Name"
|
||||
initialValue={template.repo}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "Please input repo name",
|
||||
},
|
||||
{
|
||||
pattern: nameRegex,
|
||||
message: "Invalid symbol",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
{/* Plan.paths */}
|
||||
|
||||
{/* Plan.excludes */}
|
||||
|
||||
{/* Plan.cron */}
|
||||
|
||||
{/* Plan.retention */}
|
||||
|
||||
<Form.Item shouldUpdate label="Preview">
|
||||
{() => (
|
||||
<Typography>
|
||||
<pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>
|
||||
</Typography>
|
||||
)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
import {
|
||||
Form,
|
||||
Modal,
|
||||
Input,
|
||||
Typography,
|
||||
AutoComplete,
|
||||
Tooltip,
|
||||
Button,
|
||||
} from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useShowModal } from "../components/ModalManager";
|
||||
import { Repo } from "../../gen/ts/v1/config.pb";
|
||||
import { URIAutocomplete } from "../components/URIAutocomplete";
|
||||
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
|
||||
import { useAlertApi } from "../components/Alerts";
|
||||
import { ResticUI } from "../../gen/ts/v1/service.pb";
|
||||
|
||||
export const AddRepoModel = ({
|
||||
template,
|
||||
}: {
|
||||
template: Partial<Repo> | null;
|
||||
}) => {
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
const showModal = useShowModal();
|
||||
const alertsApi = useAlertApi()!;
|
||||
const [form] = Form.useForm<Repo>();
|
||||
|
||||
template = template || {};
|
||||
|
||||
const handleOk = () => {
|
||||
const errors = form
|
||||
.getFieldsError()
|
||||
.map((e) => e.errors)
|
||||
.flat();
|
||||
if (errors.length > 0) {
|
||||
alertsApi.warning("Please fix form errors " + errors.join(", "));
|
||||
return;
|
||||
}
|
||||
setConfirmLoading(true);
|
||||
|
||||
const repo = form.getFieldsValue() as Repo;
|
||||
|
||||
if (template === null) {
|
||||
// We are in the create repo flow, create the new repo via the service
|
||||
ResticUI.AddRepo(repo, {
|
||||
pathPrefix: "/api",
|
||||
})
|
||||
.then((res) => {
|
||||
showModal(null);
|
||||
alertsApi.success("Added repo " + repo.uri);
|
||||
})
|
||||
.catch((e) => {
|
||||
alertsApi.error("Error adding repo: " + e.message, 15);
|
||||
})
|
||||
.finally(() => {
|
||||
setConfirmLoading(false);
|
||||
});
|
||||
} else {
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
showModal(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
open={true}
|
||||
title={template ? "Add Restic Repository" : "Edit Restic Repository"}
|
||||
onOk={handleOk}
|
||||
confirmLoading={confirmLoading}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<Form layout={"vertical"} autoComplete="off" form={form}>
|
||||
{/* Repo.id */}
|
||||
<Form.Item<Repo>
|
||||
hasFeedback
|
||||
name="id"
|
||||
label="Repo Name"
|
||||
initialValue={template.id}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "Please input plan name",
|
||||
},
|
||||
{
|
||||
pattern: nameRegex,
|
||||
message: "Invalid symbol",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
{/* Repo.uri */}
|
||||
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
Valid Repo URIs are:
|
||||
<ul>
|
||||
<li>Local filesystem path</li>
|
||||
<li>S3 e.g. s3:// ...</li>
|
||||
<li>SFTP e.g. sftp://user@host:/repo-path</li>
|
||||
<li>
|
||||
See{" "}
|
||||
<a href="https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html#preparing-a-new-repository">
|
||||
restic docs
|
||||
</a>{" "}
|
||||
for more info.
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Form.Item<Repo>
|
||||
hasFeedback
|
||||
name="uri"
|
||||
label="Repo URI"
|
||||
initialValue={template.id}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "Please input repo URI",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Tooltip>
|
||||
|
||||
{/* Repo.password */}
|
||||
<Form.Item<Repo>
|
||||
hasFeedback
|
||||
name="password"
|
||||
label="Password"
|
||||
initialValue={template.password}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "Please input repo name",
|
||||
},
|
||||
{
|
||||
pattern: nameRegex,
|
||||
message: "Invalid symbol",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
{/* Repo.env */}
|
||||
<Form.List
|
||||
name="env"
|
||||
rules={[
|
||||
{
|
||||
validator: async (_, envVars) => {
|
||||
let uri = form.getFieldValue("uri");
|
||||
return await envVarSetValidator(uri, envVars);
|
||||
},
|
||||
},
|
||||
]}
|
||||
initialValue={[]}
|
||||
>
|
||||
{(fields, { add, remove }, { errors }) => (
|
||||
<>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item
|
||||
label={index === 0 ? "Environment Variables" : ""}
|
||||
required={false}
|
||||
key={field.key}
|
||||
>
|
||||
<Form.Item
|
||||
{...field}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
initialValue={""}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
whitespace: true,
|
||||
pattern: /^[\w-]+=.*$/,
|
||||
message:
|
||||
"Environment variable must be in format KEY=VALUE",
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
>
|
||||
<Input placeholder="KEY=VALUE" style={{ width: "60%" }} />
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
className="dynamic-delete-button"
|
||||
onClick={() => remove(field.name)}
|
||||
style={{ paddingLeft: "5px" }}
|
||||
/>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item
|
||||
label={fields.length === 0 ? "Environment Variables" : ""}
|
||||
>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add()}
|
||||
style={{ width: "60%" }}
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
Set Environment Variable
|
||||
</Button>
|
||||
<Form.ErrorList errors={errors} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
|
||||
{/* Repo.flags */}
|
||||
<Form.List name="flags" initialValue={[]}>
|
||||
{(fields, { add, remove }, { errors }) => (
|
||||
<>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item
|
||||
label={index === 0 ? "(Advanced) Flag Overrides" : ""}
|
||||
required={false}
|
||||
key={field.key}
|
||||
>
|
||||
<Form.Item
|
||||
{...field}
|
||||
validateTrigger={["onChange", "onBlur"]}
|
||||
initialValue={""}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
whitespace: true,
|
||||
pattern: /^\-\-[A-Za-z0-9_\-]*$/,
|
||||
message:
|
||||
"Value should be a CLI flag e.g. see restic --help",
|
||||
},
|
||||
]}
|
||||
noStyle
|
||||
>
|
||||
<Input placeholder="--flag" style={{ width: "60%" }} />
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
className="dynamic-delete-button"
|
||||
onClick={() => remove(field.name)}
|
||||
style={{ paddingLeft: "5px" }}
|
||||
/>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item
|
||||
label={fields.length === 0 ? "(Advanced) Flag Overrides" : ""}
|
||||
>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add()}
|
||||
style={{ width: "60%" }}
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
Set Environment Variable
|
||||
</Button>
|
||||
<Form.ErrorList errors={errors} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
|
||||
<Form.Item shouldUpdate label="Preview">
|
||||
{() => (
|
||||
<Typography>
|
||||
<pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>
|
||||
</Typography>
|
||||
)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const nameRegex = /^[a-zA-Z0-9\-_ ]+$/;
|
||||
|
||||
const expectedEnvVars: { [scheme: string]: string[] } = {
|
||||
s3: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"],
|
||||
b2: ["B2_ACCOUNT_ID", "B2_ACCOUNT_KEY"],
|
||||
};
|
||||
|
||||
const envVarSetValidator = (uri: string | undefined, envVars: string[]) => {
|
||||
if (!uri) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
let schemeIdx = uri.indexOf(":");
|
||||
if (schemeIdx === -1) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let scheme = uri.substring(0, schemeIdx);
|
||||
let expected = expectedEnvVars[scheme];
|
||||
if (!expected) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let missing: string[] = [];
|
||||
for (let e of expected) {
|
||||
if (!envVars.includes(e)) {
|
||||
missing.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
"Missing env vars " + missing.join(", ") + " for scheme " + scheme
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
+28
-4
@@ -11,6 +11,9 @@ import { configState, fetchConfig } from "../state/config";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { Config } from "../../gen/ts/v1/config.pb";
|
||||
import { AlertContextProvider, useAlertApi } from "../components/Alerts";
|
||||
import { useShowModal, useShowSpinner } from "../components/ModalManager";
|
||||
import { AddPlanModal } from "./AddPlanModel";
|
||||
import { AddRepoModel } from "./AddRepoModel";
|
||||
|
||||
const { Header, Content, Sider } = Layout;
|
||||
|
||||
@@ -21,14 +24,20 @@ export const App: React.FC = () => {
|
||||
|
||||
const [config, setConfig] = useRecoilState(configState);
|
||||
const alertApi = useAlertApi()!;
|
||||
const showModal = useShowModal();
|
||||
|
||||
useEffect(() => {
|
||||
showModal(<Spin spinning={true} fullscreen />);
|
||||
|
||||
fetchConfig()
|
||||
.then((config) => {
|
||||
setConfig(config);
|
||||
})
|
||||
.catch((err) => {
|
||||
alertApi.error(err.message, 60);
|
||||
alertApi.error(err.message, 0);
|
||||
})
|
||||
.finally(() => {
|
||||
showModal(null);
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -50,9 +59,10 @@ export const App: React.FC = () => {
|
||||
/>
|
||||
</Sider>
|
||||
<Layout style={{ padding: "0 24px 24px" }}>
|
||||
<Breadcrumb style={{ margin: "16px 0" }}>
|
||||
<Breadcrumb.Item>Home</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<Breadcrumb
|
||||
style={{ margin: "16px 0" }}
|
||||
items={[{ title: "Home" }]}
|
||||
></Breadcrumb>
|
||||
<Content
|
||||
style={{
|
||||
padding: 24,
|
||||
@@ -70,6 +80,8 @@ export const App: React.FC = () => {
|
||||
};
|
||||
|
||||
const getSidenavItems = (config: Config | null): MenuProps["items"] => {
|
||||
const showModal = useShowModal();
|
||||
|
||||
if (!config) return [];
|
||||
|
||||
const configPlans = config.plans || [];
|
||||
@@ -80,12 +92,18 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => {
|
||||
key: "add-plan",
|
||||
icon: <PlusOutlined />,
|
||||
label: "Add Plan",
|
||||
onClick: () => {
|
||||
showModal(<AddPlanModal template={null} />);
|
||||
},
|
||||
},
|
||||
...configPlans.map((plan) => {
|
||||
return {
|
||||
key: "p-" + plan.id,
|
||||
icon: <CheckCircleOutlined style={{ color: "green" }} />,
|
||||
label: plan.id,
|
||||
onClick: () => {
|
||||
showModal(<AddPlanModal template={plan} />);
|
||||
},
|
||||
};
|
||||
}),
|
||||
];
|
||||
@@ -95,12 +113,18 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => {
|
||||
key: "add-repo",
|
||||
icon: <PlusOutlined />,
|
||||
label: "Add Repo",
|
||||
onClick: () => {
|
||||
showModal(<AddRepoModel template={null} />);
|
||||
},
|
||||
},
|
||||
...configRepos.map((repo) => {
|
||||
return {
|
||||
key: "r-" + repo.id,
|
||||
icon: <CheckCircleOutlined style={{ color: "green" }} />,
|
||||
label: repo.id,
|
||||
onClick: () => {
|
||||
showModal(<AddRepoModel template={repo} />);
|
||||
},
|
||||
};
|
||||
}),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user