From 4a1f3268a7de0533e0a979b9e97a7117b028358e Mon Sep 17 00:00:00 2001 From: Gareth Date: Thu, 1 Feb 2024 01:19:16 -0800 Subject: [PATCH] feat: authentication for WebUI (#62) --- backrest.go | 17 +- gen/go/v1/authentication.pb.go | 239 ++++++++ gen/go/v1/authentication_grpc.pb.go | 147 +++++ gen/go/v1/config.pb.go | 531 +++++++++-------- gen/go/v1/service.pb.go | 559 +++++++----------- gen/go/v1/v1connect/authentication.connect.go | 143 +++++ go.mod | 5 +- go.sum | 22 +- internal/api/auth.go | 48 -- internal/api/authenticationhandler.go | 51 ++ .../api/{server.go => backresthandler.go} | 42 +- internal/auth/auth.go | 128 ++++ internal/auth/auth_test.go | 69 +++ internal/auth/bearer.go | 13 + internal/auth/middleware.go | 36 ++ internal/oplog/bigopdatastore.go | 4 +- internal/oplog/indexutil/indexutil.go | 10 + internal/oplog/oplog.go | 4 + internal/orchestrator/taskprune.go | 4 +- internal/orchestrator/taskstats.go | 8 +- proto/v1/authentication.proto | 24 + proto/v1/config.proto | 19 +- proto/v1/operations.proto | 2 +- proto/v1/service.proto | 9 - webui/.proxyrc.json | 4 + webui/gen/ts/v1/authentication_connect.ts | 36 ++ webui/gen/ts/v1/authentication_pb.ts | 90 +++ webui/gen/ts/v1/config_pb.ts | 143 +++-- webui/gen/ts/v1/service_pb.ts | 80 --- webui/package-lock.json | 307 ++++++++-- webui/package.json | 1 - webui/src/api.ts | 27 +- webui/src/components/ConfigProvider.tsx | 27 + webui/src/components/HooksFormList.tsx | 25 +- webui/src/components/OperationList.tsx | 67 ++- webui/src/components/SpinButton.tsx | 33 ++ webui/src/index.tsx | 17 +- webui/src/lib/formutil.ts | 3 + webui/src/state/config.ts | 20 - webui/src/views/AddPlanModal.tsx | 74 +-- webui/src/views/AddRepoModal.tsx | 75 +-- webui/src/views/App.tsx | 73 ++- webui/src/views/GettingStartedGuide.tsx | 23 +- webui/src/views/LoginModal.tsx | 91 +++ webui/src/views/MainContentArea.tsx | 65 +- webui/src/views/PlanView.tsx | 12 +- webui/src/views/RepoView.tsx | 19 +- webui/src/views/SettingsModal.tsx | 191 ++++++ 48 files changed, 2540 insertions(+), 1097 deletions(-) create mode 100644 gen/go/v1/authentication.pb.go create mode 100644 gen/go/v1/authentication_grpc.pb.go create mode 100644 gen/go/v1/v1connect/authentication.connect.go delete mode 100644 internal/api/auth.go create mode 100644 internal/api/authenticationhandler.go rename internal/api/{server.go => backresthandler.go} (81%) create mode 100644 internal/auth/auth.go create mode 100644 internal/auth/auth_test.go create mode 100644 internal/auth/bearer.go create mode 100644 internal/auth/middleware.go create mode 100644 proto/v1/authentication.proto create mode 100644 webui/gen/ts/v1/authentication_connect.ts create mode 100644 webui/gen/ts/v1/authentication_pb.ts create mode 100644 webui/src/components/ConfigProvider.tsx delete mode 100644 webui/src/state/config.ts create mode 100644 webui/src/views/LoginModal.tsx create mode 100644 webui/src/views/SettingsModal.tsx diff --git a/backrest.go b/backrest.go index a439c076d..f0509e83f 100644 --- a/backrest.go +++ b/backrest.go @@ -2,6 +2,7 @@ package main import ( "context" + "crypto/rand" "errors" "flag" "net/http" @@ -15,6 +16,7 @@ import ( rice "github.com/GeertJohan/go.rice" "github.com/garethgeorge/backrest/gen/go/v1/v1connect" "github.com/garethgeorge/backrest/internal/api" + "github.com/garethgeorge/backrest/internal/auth" "github.com/garethgeorge/backrest/internal/config" "github.com/garethgeorge/backrest/internal/oplog" "github.com/garethgeorge/backrest/internal/orchestrator" @@ -44,6 +46,13 @@ func main() { zap.S().Fatalf("Error loading config: %v", err) } + // Create the authenticator + secret := make([]byte, 32) + if n, err := rand.Read(secret); err != nil || n != 32 { + zap.S().Fatalf("Error generating secret: %v", err) + } + authenticator := auth.NewAuthenticator(secret, configStore) + var wg sync.WaitGroup // Create / load the operation log @@ -71,12 +80,14 @@ func main() { }() // Create and serve the HTTP gateway - apiServer := api.NewServer( + apiBackrestHandler := api.NewBackrestHandler( configStore, orchestrator, oplog, ) + apiAuthenticationHandler := api.NewAuthenticationHandler(authenticator) + mux := http.NewServeMux() if box, err := rice.FindBox("webui/dist"); err == nil { @@ -103,7 +114,9 @@ func main() { zap.S().Warnf("Error loading static assets, not serving UI: %v", err) } - mux.Handle(v1connect.NewBackrestHandler(apiServer)) + mux.Handle(v1connect.NewAuthenticationHandler(apiAuthenticationHandler)) + backrestHandlerPath, backrestHandler := v1connect.NewBackrestHandler(apiBackrestHandler) + mux.Handle(backrestHandlerPath, auth.RequireAuthentication(backrestHandler, authenticator)) // Serve the HTTP gateway server := &http.Server{ diff --git a/gen/go/v1/authentication.pb.go b/gen/go/v1/authentication.pb.go new file mode 100644 index 000000000..033644630 --- /dev/null +++ b/gen/go/v1/authentication.pb.go @@ -0,0 +1,239 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.32.0 +// protoc (unknown) +// source: v1/authentication.proto + +package v1 + +import ( + types "github.com/garethgeorge/backrest/gen/go/types" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/known/emptypb" + 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 LoginRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` +} + +func (x *LoginRequest) Reset() { + *x = LoginRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_authentication_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginRequest) ProtoMessage() {} + +func (x *LoginRequest) ProtoReflect() protoreflect.Message { + mi := &file_v1_authentication_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 LoginRequest.ProtoReflect.Descriptor instead. +func (*LoginRequest) Descriptor() ([]byte, []int) { + return file_v1_authentication_proto_rawDescGZIP(), []int{0} +} + +func (x *LoginRequest) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *LoginRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +type LoginResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` // JWT token +} + +func (x *LoginResponse) Reset() { + *x = LoginResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_authentication_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginResponse) ProtoMessage() {} + +func (x *LoginResponse) ProtoReflect() protoreflect.Message { + mi := &file_v1_authentication_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 LoginResponse.ProtoReflect.Descriptor instead. +func (*LoginResponse) Descriptor() ([]byte, []int) { + return file_v1_authentication_proto_rawDescGZIP(), []int{1} +} + +func (x *LoginResponse) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +var File_v1_authentication_proto protoreflect.FileDescriptor + +var file_v1_authentication_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 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, 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, 0x22, 0x46, 0x0a, 0x0c, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x75, 0x73, 0x65, 0x72, 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, 0x25, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0x7a, 0x0a, 0x0e, 0x41, + 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, + 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x38, 0x0a, + 0x0c, 0x48, 0x61, 0x73, 0x68, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x1a, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, + 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, + 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_v1_authentication_proto_rawDescOnce sync.Once + file_v1_authentication_proto_rawDescData = file_v1_authentication_proto_rawDesc +) + +func file_v1_authentication_proto_rawDescGZIP() []byte { + file_v1_authentication_proto_rawDescOnce.Do(func() { + file_v1_authentication_proto_rawDescData = protoimpl.X.CompressGZIP(file_v1_authentication_proto_rawDescData) + }) + return file_v1_authentication_proto_rawDescData +} + +var file_v1_authentication_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_v1_authentication_proto_goTypes = []interface{}{ + (*LoginRequest)(nil), // 0: v1.LoginRequest + (*LoginResponse)(nil), // 1: v1.LoginResponse + (*types.StringValue)(nil), // 2: types.StringValue +} +var file_v1_authentication_proto_depIdxs = []int32{ + 0, // 0: v1.Authentication.Login:input_type -> v1.LoginRequest + 2, // 1: v1.Authentication.HashPassword:input_type -> types.StringValue + 1, // 2: v1.Authentication.Login:output_type -> v1.LoginResponse + 2, // 3: v1.Authentication.HashPassword:output_type -> types.StringValue + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] 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_v1_authentication_proto_init() } +func file_v1_authentication_proto_init() { + if File_v1_authentication_proto != nil { + return + } + file_v1_config_proto_init() + if !protoimpl.UnsafeEnabled { + file_v1_authentication_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_authentication_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginResponse); 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_v1_authentication_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_v1_authentication_proto_goTypes, + DependencyIndexes: file_v1_authentication_proto_depIdxs, + MessageInfos: file_v1_authentication_proto_msgTypes, + }.Build() + File_v1_authentication_proto = out.File + file_v1_authentication_proto_rawDesc = nil + file_v1_authentication_proto_goTypes = nil + file_v1_authentication_proto_depIdxs = nil +} diff --git a/gen/go/v1/authentication_grpc.pb.go b/gen/go/v1/authentication_grpc.pb.go new file mode 100644 index 000000000..50a55d53e --- /dev/null +++ b/gen/go/v1/authentication_grpc.pb.go @@ -0,0 +1,147 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc (unknown) +// source: v1/authentication.proto + +package v1 + +import ( + context "context" + types "github.com/garethgeorge/backrest/gen/go/types" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + Authentication_Login_FullMethodName = "/v1.Authentication/Login" + Authentication_HashPassword_FullMethodName = "/v1.Authentication/HashPassword" +) + +// AuthenticationClient is the client API for Authentication service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AuthenticationClient interface { + Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) + HashPassword(ctx context.Context, in *types.StringValue, opts ...grpc.CallOption) (*types.StringValue, error) +} + +type authenticationClient struct { + cc grpc.ClientConnInterface +} + +func NewAuthenticationClient(cc grpc.ClientConnInterface) AuthenticationClient { + return &authenticationClient{cc} +} + +func (c *authenticationClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) { + out := new(LoginResponse) + err := c.cc.Invoke(ctx, Authentication_Login_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authenticationClient) HashPassword(ctx context.Context, in *types.StringValue, opts ...grpc.CallOption) (*types.StringValue, error) { + out := new(types.StringValue) + err := c.cc.Invoke(ctx, Authentication_HashPassword_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AuthenticationServer is the server API for Authentication service. +// All implementations must embed UnimplementedAuthenticationServer +// for forward compatibility +type AuthenticationServer interface { + Login(context.Context, *LoginRequest) (*LoginResponse, error) + HashPassword(context.Context, *types.StringValue) (*types.StringValue, error) + mustEmbedUnimplementedAuthenticationServer() +} + +// UnimplementedAuthenticationServer must be embedded to have forward compatible implementations. +type UnimplementedAuthenticationServer struct { +} + +func (UnimplementedAuthenticationServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Login not implemented") +} +func (UnimplementedAuthenticationServer) HashPassword(context.Context, *types.StringValue) (*types.StringValue, error) { + return nil, status.Errorf(codes.Unimplemented, "method HashPassword not implemented") +} +func (UnimplementedAuthenticationServer) mustEmbedUnimplementedAuthenticationServer() {} + +// UnsafeAuthenticationServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AuthenticationServer will +// result in compilation errors. +type UnsafeAuthenticationServer interface { + mustEmbedUnimplementedAuthenticationServer() +} + +func RegisterAuthenticationServer(s grpc.ServiceRegistrar, srv AuthenticationServer) { + s.RegisterService(&Authentication_ServiceDesc, srv) +} + +func _Authentication_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LoginRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthenticationServer).Login(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Authentication_Login_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthenticationServer).Login(ctx, req.(*LoginRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Authentication_HashPassword_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.(AuthenticationServer).HashPassword(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Authentication_HashPassword_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthenticationServer).HashPassword(ctx, req.(*types.StringValue)) + } + return interceptor(ctx, in, info, handler) +} + +// Authentication_ServiceDesc is the grpc.ServiceDesc for Authentication service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Authentication_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "v1.Authentication", + HandlerType: (*AuthenticationServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Login", + Handler: _Authentication_Login_Handler, + }, + { + MethodName: "HashPassword", + Handler: _Authentication_HashPassword_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "v1/authentication.proto", +} diff --git a/gen/go/v1/config.pb.go b/gen/go/v1/config.pb.go index c828dab88..2851999cb 100644 --- a/gen/go/v1/config.pb.go +++ b/gen/go/v1/config.pb.go @@ -72,7 +72,7 @@ func (x Hook_Condition) Number() protoreflect.EnumNumber { // Deprecated: Use Hook_Condition.Descriptor instead. func (Hook_Condition) EnumDescriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{6, 0} + return file_v1_config_proto_rawDescGZIP(), []int{5, 0} } // Config is the top level config object for restic UI. @@ -87,7 +87,7 @@ type Config struct { Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,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"` - Users []*User `protobuf:"bytes,5,rep,name=users,proto3" json:"users,omitempty"` + Auth *Auth `protobuf:"bytes,5,opt,name=auth,proto3" json:"auth,omitempty"` } func (x *Config) Reset() { @@ -150,9 +150,9 @@ func (x *Config) GetPlans() []*Plan { return nil } -func (x *Config) GetUsers() []*User { +func (x *Config) GetAuth() *Auth { if x != nil { - return x.Users + return x.Auth } return nil } @@ -514,81 +514,6 @@ func (x *PrunePolicy) GetMaxUnusedBytes() int32 { return 0 } -type User struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Types that are assignable to Password: - // - // *User_PasswordBcrypt - Password isUser_Password `protobuf_oneof:"password"` -} - -func (x *User) Reset() { - *x = User{} - if protoimpl.UnsafeEnabled { - mi := &file_v1_config_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *User) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*User) ProtoMessage() {} - -func (x *User) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[5] - 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 User.ProtoReflect.Descriptor instead. -func (*User) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{5} -} - -func (x *User) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (m *User) GetPassword() isUser_Password { - if m != nil { - return m.Password - } - return nil -} - -func (x *User) GetPasswordBcrypt() string { - if x, ok := x.GetPassword().(*User_PasswordBcrypt); ok { - return x.PasswordBcrypt - } - return "" -} - -type isUser_Password interface { - isUser_Password() -} - -type User_PasswordBcrypt struct { - PasswordBcrypt string `protobuf:"bytes,2,opt,name=password_bcrypt,json=passwordBcrypt,proto3,oneof"` -} - -func (*User_PasswordBcrypt) isUser_Password() {} - type Hook struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -607,7 +532,7 @@ type Hook struct { func (x *Hook) Reset() { *x = Hook{} if protoimpl.UnsafeEnabled { - mi := &file_v1_config_proto_msgTypes[6] + mi := &file_v1_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -620,7 +545,7 @@ func (x *Hook) String() string { func (*Hook) ProtoMessage() {} func (x *Hook) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[6] + mi := &file_v1_config_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -633,7 +558,7 @@ func (x *Hook) ProtoReflect() protoreflect.Message { // Deprecated: Use Hook.ProtoReflect.Descriptor instead. func (*Hook) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{6} + return file_v1_config_proto_rawDescGZIP(), []int{5} } func (x *Hook) GetConditions() []Hook_Condition { @@ -706,6 +631,128 @@ func (*Hook_ActionDiscord) isHook_Action() {} func (*Hook_ActionGotify) isHook_Action() {} +type Auth struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Users []*User `protobuf:"bytes,2,rep,name=users,proto3" json:"users,omitempty"` // users to allow access to the UI. +} + +func (x *Auth) Reset() { + *x = Auth{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_config_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Auth) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Auth) ProtoMessage() {} + +func (x *Auth) ProtoReflect() protoreflect.Message { + mi := &file_v1_config_proto_msgTypes[6] + 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 Auth.ProtoReflect.Descriptor instead. +func (*Auth) Descriptor() ([]byte, []int) { + return file_v1_config_proto_rawDescGZIP(), []int{6} +} + +func (x *Auth) GetUsers() []*User { + if x != nil { + return x.Users + } + return nil +} + +type User struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Types that are assignable to Password: + // + // *User_PasswordBcrypt + Password isUser_Password `protobuf_oneof:"password"` +} + +func (x *User) Reset() { + *x = User{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_config_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*User) ProtoMessage() {} + +func (x *User) ProtoReflect() protoreflect.Message { + mi := &file_v1_config_proto_msgTypes[7] + 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 User.ProtoReflect.Descriptor instead. +func (*User) Descriptor() ([]byte, []int) { + return file_v1_config_proto_rawDescGZIP(), []int{7} +} + +func (x *User) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (m *User) GetPassword() isUser_Password { + if m != nil { + return m.Password + } + return nil +} + +func (x *User) GetPasswordBcrypt() string { + if x, ok := x.GetPassword().(*User_PasswordBcrypt); ok { + return x.PasswordBcrypt + } + return "" +} + +type isUser_Password interface { + isUser_Password() +} + +type User_PasswordBcrypt struct { + PasswordBcrypt string `protobuf:"bytes,2,opt,name=password_bcrypt,json=passwordBcrypt,proto3,oneof"` +} + +func (*User_PasswordBcrypt) isUser_Password() {} + type Hook_Command struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -717,7 +764,7 @@ type Hook_Command struct { func (x *Hook_Command) Reset() { *x = Hook_Command{} if protoimpl.UnsafeEnabled { - mi := &file_v1_config_proto_msgTypes[7] + mi := &file_v1_config_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -730,7 +777,7 @@ func (x *Hook_Command) String() string { func (*Hook_Command) ProtoMessage() {} func (x *Hook_Command) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[7] + mi := &file_v1_config_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -743,7 +790,7 @@ func (x *Hook_Command) ProtoReflect() protoreflect.Message { // Deprecated: Use Hook_Command.ProtoReflect.Descriptor instead. func (*Hook_Command) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{6, 0} + return file_v1_config_proto_rawDescGZIP(), []int{5, 0} } func (x *Hook_Command) GetCommand() string { @@ -764,7 +811,7 @@ type Hook_Webhook struct { func (x *Hook_Webhook) Reset() { *x = Hook_Webhook{} if protoimpl.UnsafeEnabled { - mi := &file_v1_config_proto_msgTypes[8] + mi := &file_v1_config_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -777,7 +824,7 @@ func (x *Hook_Webhook) String() string { func (*Hook_Webhook) ProtoMessage() {} func (x *Hook_Webhook) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[8] + mi := &file_v1_config_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -790,7 +837,7 @@ func (x *Hook_Webhook) ProtoReflect() protoreflect.Message { // Deprecated: Use Hook_Webhook.ProtoReflect.Descriptor instead. func (*Hook_Webhook) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{6, 1} + return file_v1_config_proto_rawDescGZIP(), []int{5, 1} } func (x *Hook_Webhook) GetWebhookUrl() string { @@ -812,7 +859,7 @@ type Hook_Discord struct { func (x *Hook_Discord) Reset() { *x = Hook_Discord{} if protoimpl.UnsafeEnabled { - mi := &file_v1_config_proto_msgTypes[9] + mi := &file_v1_config_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -825,7 +872,7 @@ func (x *Hook_Discord) String() string { func (*Hook_Discord) ProtoMessage() {} func (x *Hook_Discord) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[9] + mi := &file_v1_config_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -838,7 +885,7 @@ func (x *Hook_Discord) ProtoReflect() protoreflect.Message { // Deprecated: Use Hook_Discord.ProtoReflect.Descriptor instead. func (*Hook_Discord) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{6, 2} + return file_v1_config_proto_rawDescGZIP(), []int{5, 2} } func (x *Hook_Discord) GetWebhookUrl() string { @@ -869,7 +916,7 @@ type Hook_Gotify struct { func (x *Hook_Gotify) Reset() { *x = Hook_Gotify{} if protoimpl.UnsafeEnabled { - mi := &file_v1_config_proto_msgTypes[10] + mi := &file_v1_config_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -882,7 +929,7 @@ func (x *Hook_Gotify) String() string { func (*Hook_Gotify) ProtoMessage() {} func (x *Hook_Gotify) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[10] + mi := &file_v1_config_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -895,7 +942,7 @@ func (x *Hook_Gotify) ProtoReflect() protoreflect.Message { // Deprecated: Use Hook_Gotify.ProtoReflect.Descriptor instead. func (*Hook_Gotify) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{6, 3} + return file_v1_config_proto_rawDescGZIP(), []int{5, 3} } func (x *Hook_Gotify) GetBaseUrl() string { @@ -930,124 +977,126 @@ 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, 0x92, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x6f, 0x12, 0x02, 0x76, 0x31, 0x22, 0x90, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x6e, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 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, 0x12, 0x1e, 0x0a, 0x05, 0x75, 0x73, - 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0xc0, 0x01, 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, 0x12, 0x32, 0x0a, 0x0c, 0x70, 0x72, 0x75, - 0x6e, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x52, 0x0b, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1e, 0x0a, - 0x05, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, - 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x05, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x22, 0xc3, 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, 0x12, 0x1e, 0x0a, 0x05, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x18, 0x08, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x05, 0x68, 0x6f, - 0x6f, 0x6b, 0x73, 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, 0x22, 0x93, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x75, - 0x6e, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, - 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x64, 0x61, 0x79, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, - 0x63, 0x79, 0x44, 0x61, 0x79, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x6e, - 0x75, 0x73, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x64, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x50, 0x65, 0x72, - 0x63, 0x65, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x6e, 0x75, 0x73, - 0x65, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x65, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, - 0x6d, 0x61, 0x78, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x51, - 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, 0x29, 0x0a, 0x0f, 0x70, 0x61, - 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x62, 0x63, 0x72, 0x79, 0x70, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x42, - 0x63, 0x72, 0x79, 0x70, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x22, 0xda, 0x05, 0x0a, 0x04, 0x48, 0x6f, 0x6f, 0x6b, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x6f, - 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x12, - 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x39, - 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, - 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x39, 0x0a, 0x0e, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x18, 0x65, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x57, 0x65, 0x62, 0x68, - 0x6f, 0x6f, 0x6b, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x65, 0x62, - 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x39, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, - 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, - 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x48, 0x00, - 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x12, - 0x36, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x67, 0x6f, 0x74, 0x69, 0x66, 0x79, - 0x18, 0x67, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, - 0x2e, 0x47, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x47, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x1a, 0x23, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, 0x2a, 0x0a, 0x07, - 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, - 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, - 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x1a, 0x46, 0x0a, 0x07, 0x44, 0x69, 0x73, 0x63, - 0x6f, 0x72, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, - 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, - 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x1a, 0x7c, 0x0a, 0x06, 0x47, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, - 0x73, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, - 0x73, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x69, 0x74, 0x6c, 0x65, - 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x65, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x22, 0x93, - 0x01, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x11, - 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, - 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, - 0x5f, 0x41, 0x4e, 0x59, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, - 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, - 0x4f, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4f, - 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, - 0x5f, 0x45, 0x4e, 0x44, 0x10, 0x03, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, - 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x10, 0x04, 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x2c, - 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, - 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, - 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x6c, 0x61, 0x6e, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x04, 0x61, 0x75, + 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, + 0x74, 0x68, 0x52, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0xc0, 0x01, 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, 0x12, 0x32, 0x0a, 0x0c, 0x70, 0x72, 0x75, 0x6e, 0x65, + 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, + 0x76, 0x31, 0x2e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, + 0x70, 0x72, 0x75, 0x6e, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1e, 0x0a, 0x05, 0x68, + 0x6f, 0x6f, 0x6b, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, + 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x05, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x22, 0xc3, 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, 0x12, 0x1e, 0x0a, 0x05, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x05, 0x68, 0x6f, 0x6f, 0x6b, + 0x73, 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, 0x22, 0x93, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x75, 0x6e, 0x65, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x64, 0x61, 0x79, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, + 0x44, 0x61, 0x79, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x6e, 0x75, 0x73, + 0x65, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x64, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x10, 0x6d, 0x61, 0x78, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x50, 0x65, 0x72, 0x63, 0x65, + 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, + 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x65, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6d, 0x61, + 0x78, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0xda, 0x05, 0x0a, + 0x04, 0x48, 0x6f, 0x6f, 0x6b, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x48, + 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63, + 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x39, 0x0a, 0x0e, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x64, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x39, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x77, + 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, + 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x48, 0x00, + 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, + 0x39, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x72, + 0x64, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, + 0x6b, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x36, 0x0a, 0x0d, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x67, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x18, 0x67, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x47, 0x6f, 0x74, 0x69, + 0x66, 0x79, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x47, 0x6f, 0x74, 0x69, + 0x66, 0x79, 0x1a, 0x23, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x18, 0x0a, + 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, 0x2a, 0x0a, 0x07, 0x57, 0x65, 0x62, 0x68, 0x6f, + 0x6f, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, + 0x55, 0x72, 0x6c, 0x1a, 0x46, 0x0a, 0x07, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1f, + 0x0a, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, + 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x1a, 0x7c, 0x0a, 0x06, 0x47, + 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x75, 0x72, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x55, 0x72, 0x6c, + 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x5f, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x18, 0x65, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x09, 0x43, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4e, 0x44, 0x49, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x17, + 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x4e, 0x59, 0x5f, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, 0x4e, 0x44, 0x49, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x53, 0x54, + 0x41, 0x52, 0x54, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, + 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x45, 0x4e, 0x44, 0x10, + 0x03, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, + 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x42, + 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x26, 0x0a, 0x04, 0x41, 0x75, 0x74, + 0x68, 0x12, 0x1e, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, + 0x73, 0x22, 0x51, 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, 0x29, 0x0a, + 0x0f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x62, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x42, 0x63, 0x72, 0x79, 0x70, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, + 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, + 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1063,7 +1112,7 @@ func file_v1_config_proto_rawDescGZIP() []byte { } var file_v1_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_v1_config_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_v1_config_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_v1_config_proto_goTypes = []interface{}{ (Hook_Condition)(0), // 0: v1.Hook.Condition (*Config)(nil), // 1: v1.Config @@ -1071,31 +1120,33 @@ var file_v1_config_proto_goTypes = []interface{}{ (*Plan)(nil), // 3: v1.Plan (*RetentionPolicy)(nil), // 4: v1.RetentionPolicy (*PrunePolicy)(nil), // 5: v1.PrunePolicy - (*User)(nil), // 6: v1.User - (*Hook)(nil), // 7: v1.Hook - (*Hook_Command)(nil), // 8: v1.Hook.Command - (*Hook_Webhook)(nil), // 9: v1.Hook.Webhook - (*Hook_Discord)(nil), // 10: v1.Hook.Discord - (*Hook_Gotify)(nil), // 11: v1.Hook.Gotify + (*Hook)(nil), // 6: v1.Hook + (*Auth)(nil), // 7: v1.Auth + (*User)(nil), // 8: v1.User + (*Hook_Command)(nil), // 9: v1.Hook.Command + (*Hook_Webhook)(nil), // 10: v1.Hook.Webhook + (*Hook_Discord)(nil), // 11: v1.Hook.Discord + (*Hook_Gotify)(nil), // 12: v1.Hook.Gotify } 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 - 6, // 2: v1.Config.users:type_name -> v1.User + 7, // 2: v1.Config.auth:type_name -> v1.Auth 5, // 3: v1.Repo.prune_policy:type_name -> v1.PrunePolicy - 7, // 4: v1.Repo.hooks:type_name -> v1.Hook + 6, // 4: v1.Repo.hooks:type_name -> v1.Hook 4, // 5: v1.Plan.retention:type_name -> v1.RetentionPolicy - 7, // 6: v1.Plan.hooks:type_name -> v1.Hook + 6, // 6: v1.Plan.hooks:type_name -> v1.Hook 0, // 7: v1.Hook.conditions:type_name -> v1.Hook.Condition - 8, // 8: v1.Hook.action_command:type_name -> v1.Hook.Command - 9, // 9: v1.Hook.action_webhook:type_name -> v1.Hook.Webhook - 10, // 10: v1.Hook.action_discord:type_name -> v1.Hook.Discord - 11, // 11: v1.Hook.action_gotify:type_name -> v1.Hook.Gotify - 12, // [12:12] is the sub-list for method output_type - 12, // [12:12] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name + 9, // 8: v1.Hook.action_command:type_name -> v1.Hook.Command + 10, // 9: v1.Hook.action_webhook:type_name -> v1.Hook.Webhook + 11, // 10: v1.Hook.action_discord:type_name -> v1.Hook.Discord + 12, // 11: v1.Hook.action_gotify:type_name -> v1.Hook.Gotify + 8, // 12: v1.Auth.users:type_name -> v1.User + 13, // [13:13] is the sub-list for method output_type + 13, // [13:13] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_v1_config_proto_init() } @@ -1165,7 +1216,7 @@ func file_v1_config_proto_init() { } } file_v1_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*User); i { + switch v := v.(*Hook); i { case 0: return &v.state case 1: @@ -1177,7 +1228,7 @@ func file_v1_config_proto_init() { } } file_v1_config_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Hook); i { + switch v := v.(*Auth); i { case 0: return &v.state case 1: @@ -1189,7 +1240,7 @@ func file_v1_config_proto_init() { } } file_v1_config_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Hook_Command); i { + switch v := v.(*User); i { case 0: return &v.state case 1: @@ -1201,7 +1252,7 @@ func file_v1_config_proto_init() { } } file_v1_config_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Hook_Webhook); i { + switch v := v.(*Hook_Command); i { case 0: return &v.state case 1: @@ -1213,7 +1264,7 @@ func file_v1_config_proto_init() { } } file_v1_config_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Hook_Discord); i { + switch v := v.(*Hook_Webhook); i { case 0: return &v.state case 1: @@ -1225,6 +1276,18 @@ func file_v1_config_proto_init() { } } file_v1_config_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Hook_Discord); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_config_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Hook_Gotify); i { case 0: return &v.state @@ -1238,21 +1301,21 @@ func file_v1_config_proto_init() { } } file_v1_config_proto_msgTypes[5].OneofWrappers = []interface{}{ - (*User_PasswordBcrypt)(nil), - } - file_v1_config_proto_msgTypes[6].OneofWrappers = []interface{}{ (*Hook_ActionCommand)(nil), (*Hook_ActionWebhook)(nil), (*Hook_ActionDiscord)(nil), (*Hook_ActionGotify)(nil), } + file_v1_config_proto_msgTypes[7].OneofWrappers = []interface{}{ + (*User_PasswordBcrypt)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_config_proto_rawDesc, NumEnums: 1, - NumMessages: 11, + NumMessages: 12, NumExtensions: 0, NumServices: 0, }, diff --git a/gen/go/v1/service.pb.go b/gen/go/v1/service.pb.go index 229007b2a..1303b6ede 100644 --- a/gen/go/v1/service.pb.go +++ b/gen/go/v1/service.pb.go @@ -23,108 +23,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -type LoginRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` - Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` -} - -func (x *LoginRequest) Reset() { - *x = LoginRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_v1_service_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *LoginRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*LoginRequest) ProtoMessage() {} - -func (x *LoginRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_service_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 LoginRequest.ProtoReflect.Descriptor instead. -func (*LoginRequest) Descriptor() ([]byte, []int) { - return file_v1_service_proto_rawDescGZIP(), []int{0} -} - -func (x *LoginRequest) GetUsername() string { - if x != nil { - return x.Username - } - return "" -} - -func (x *LoginRequest) GetPassword() string { - if x != nil { - return x.Password - } - return "" -} - -type LoginResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Jwt string `protobuf:"bytes,1,opt,name=jwt,proto3" json:"jwt,omitempty"` -} - -func (x *LoginResponse) Reset() { - *x = LoginResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_v1_service_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *LoginResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*LoginResponse) ProtoMessage() {} - -func (x *LoginResponse) 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 LoginResponse.ProtoReflect.Descriptor instead. -func (*LoginResponse) Descriptor() ([]byte, []int) { - return file_v1_service_proto_rawDescGZIP(), []int{1} -} - -func (x *LoginResponse) GetJwt() string { - if x != nil { - return x.Jwt - } - return "" -} - type ClearHistoryRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -138,7 +36,7 @@ type ClearHistoryRequest struct { func (x *ClearHistoryRequest) Reset() { *x = ClearHistoryRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_service_proto_msgTypes[2] + mi := &file_v1_service_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -151,7 +49,7 @@ func (x *ClearHistoryRequest) String() string { func (*ClearHistoryRequest) ProtoMessage() {} func (x *ClearHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_service_proto_msgTypes[2] + mi := &file_v1_service_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -164,7 +62,7 @@ func (x *ClearHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearHistoryRequest.ProtoReflect.Descriptor instead. func (*ClearHistoryRequest) Descriptor() ([]byte, []int) { - return file_v1_service_proto_rawDescGZIP(), []int{2} + return file_v1_service_proto_rawDescGZIP(), []int{0} } func (x *ClearHistoryRequest) GetRepoId() string { @@ -200,7 +98,7 @@ type ListSnapshotsRequest struct { func (x *ListSnapshotsRequest) Reset() { *x = ListSnapshotsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_service_proto_msgTypes[3] + mi := &file_v1_service_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -213,7 +111,7 @@ func (x *ListSnapshotsRequest) String() string { func (*ListSnapshotsRequest) ProtoMessage() {} func (x *ListSnapshotsRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_service_proto_msgTypes[3] + mi := &file_v1_service_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -226,7 +124,7 @@ func (x *ListSnapshotsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSnapshotsRequest.ProtoReflect.Descriptor instead. func (*ListSnapshotsRequest) Descriptor() ([]byte, []int) { - return file_v1_service_proto_rawDescGZIP(), []int{3} + return file_v1_service_proto_rawDescGZIP(), []int{1} } func (x *ListSnapshotsRequest) GetRepoId() string { @@ -258,7 +156,7 @@ type GetOperationsRequest struct { func (x *GetOperationsRequest) Reset() { *x = GetOperationsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_service_proto_msgTypes[4] + mi := &file_v1_service_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -271,7 +169,7 @@ func (x *GetOperationsRequest) String() string { func (*GetOperationsRequest) ProtoMessage() {} func (x *GetOperationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_service_proto_msgTypes[4] + mi := &file_v1_service_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -284,7 +182,7 @@ func (x *GetOperationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetOperationsRequest.ProtoReflect.Descriptor instead. func (*GetOperationsRequest) Descriptor() ([]byte, []int) { - return file_v1_service_proto_rawDescGZIP(), []int{4} + return file_v1_service_proto_rawDescGZIP(), []int{2} } func (x *GetOperationsRequest) GetRepoId() string { @@ -336,7 +234,7 @@ type RestoreSnapshotRequest struct { func (x *RestoreSnapshotRequest) Reset() { *x = RestoreSnapshotRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_service_proto_msgTypes[5] + mi := &file_v1_service_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -349,7 +247,7 @@ func (x *RestoreSnapshotRequest) String() string { func (*RestoreSnapshotRequest) ProtoMessage() {} func (x *RestoreSnapshotRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_service_proto_msgTypes[5] + mi := &file_v1_service_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -362,7 +260,7 @@ func (x *RestoreSnapshotRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreSnapshotRequest.ProtoReflect.Descriptor instead. func (*RestoreSnapshotRequest) Descriptor() ([]byte, []int) { - return file_v1_service_proto_rawDescGZIP(), []int{5} + return file_v1_service_proto_rawDescGZIP(), []int{3} } func (x *RestoreSnapshotRequest) GetPlanId() string { @@ -406,7 +304,7 @@ type ListSnapshotFilesRequest struct { func (x *ListSnapshotFilesRequest) Reset() { *x = ListSnapshotFilesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_service_proto_msgTypes[6] + mi := &file_v1_service_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -419,7 +317,7 @@ func (x *ListSnapshotFilesRequest) String() string { func (*ListSnapshotFilesRequest) ProtoMessage() {} func (x *ListSnapshotFilesRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_service_proto_msgTypes[6] + mi := &file_v1_service_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -432,7 +330,7 @@ func (x *ListSnapshotFilesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSnapshotFilesRequest.ProtoReflect.Descriptor instead. func (*ListSnapshotFilesRequest) Descriptor() ([]byte, []int) { - return file_v1_service_proto_rawDescGZIP(), []int{6} + return file_v1_service_proto_rawDescGZIP(), []int{4} } func (x *ListSnapshotFilesRequest) GetRepoId() string { @@ -468,7 +366,7 @@ type ListSnapshotFilesResponse struct { func (x *ListSnapshotFilesResponse) Reset() { *x = ListSnapshotFilesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_v1_service_proto_msgTypes[7] + mi := &file_v1_service_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -481,7 +379,7 @@ func (x *ListSnapshotFilesResponse) String() string { func (*ListSnapshotFilesResponse) ProtoMessage() {} func (x *ListSnapshotFilesResponse) ProtoReflect() protoreflect.Message { - mi := &file_v1_service_proto_msgTypes[7] + mi := &file_v1_service_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -494,7 +392,7 @@ func (x *ListSnapshotFilesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSnapshotFilesResponse.ProtoReflect.Descriptor instead. func (*ListSnapshotFilesResponse) Descriptor() ([]byte, []int) { - return file_v1_service_proto_rawDescGZIP(), []int{7} + return file_v1_service_proto_rawDescGZIP(), []int{5} } func (x *ListSnapshotFilesResponse) GetPath() string { @@ -523,7 +421,7 @@ type OperationDataRequest struct { func (x *OperationDataRequest) Reset() { *x = OperationDataRequest{} if protoimpl.UnsafeEnabled { - mi := &file_v1_service_proto_msgTypes[8] + mi := &file_v1_service_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -536,7 +434,7 @@ func (x *OperationDataRequest) String() string { func (*OperationDataRequest) ProtoMessage() {} func (x *OperationDataRequest) ProtoReflect() protoreflect.Message { - mi := &file_v1_service_proto_msgTypes[8] + mi := &file_v1_service_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -549,7 +447,7 @@ func (x *OperationDataRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use OperationDataRequest.ProtoReflect.Descriptor instead. func (*OperationDataRequest) Descriptor() ([]byte, []int) { - return file_v1_service_proto_rawDescGZIP(), []int{8} + return file_v1_service_proto_rawDescGZIP(), []int{6} } func (x *OperationDataRequest) GetId() int64 { @@ -586,7 +484,7 @@ type LsEntry struct { func (x *LsEntry) Reset() { *x = LsEntry{} if protoimpl.UnsafeEnabled { - mi := &file_v1_service_proto_msgTypes[9] + mi := &file_v1_service_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -599,7 +497,7 @@ func (x *LsEntry) String() string { func (*LsEntry) ProtoMessage() {} func (x *LsEntry) ProtoReflect() protoreflect.Message { - mi := &file_v1_service_proto_msgTypes[9] + mi := &file_v1_service_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -612,7 +510,7 @@ func (x *LsEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use LsEntry.ProtoReflect.Descriptor instead. func (*LsEntry) Descriptor() ([]byte, []int) { - return file_v1_service_proto_rawDescGZIP(), []int{9} + return file_v1_service_proto_rawDescGZIP(), []int{7} } func (x *LsEntry) GetName() string { @@ -697,143 +595,136 @@ var file_v1_service_proto_rawDesc = []byte{ 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, 0x22, 0x46, 0x0a, 0x0c, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, - 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, - 0x73, 0x65, 0x72, 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, 0x21, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6a, 0x77, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6a, 0x77, 0x74, 0x22, 0x68, 0x0a, 0x13, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, - 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x68, 0x0a, 0x13, 0x43, + 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 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, 0x1f, 0x0a, 0x0b, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x66, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6f, 0x6e, 0x6c, 0x79, 0x46, + 0x61, 0x69, 0x6c, 0x65, 0x64, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 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, - 0x1f, 0x0a, 0x0b, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6f, 0x6e, 0x6c, 0x79, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, - 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x22, + 0x92, 0x01, 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, 0x22, 0x92, 0x01, 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, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x05, 0x20, - 0x03, 0x28, 0x03, 0x52, 0x03, 0x69, 0x64, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x6c, 0x61, 0x73, 0x74, - 0x5f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x61, 0x73, 0x74, 0x4e, 0x22, - 0x7e, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, - 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, - 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, - 0x68, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, - 0x69, 0x6c, 0x65, 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, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x73, - 0x68, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x56, 0x0a, 0x19, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x25, 0x0a, 0x07, 0x65, 0x6e, - 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, - 0x73, 0x22, 0x38, 0x0a, 0x14, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, - 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xd3, 0x01, 0x0a, 0x07, - 0x4c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, - 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, - 0x61, 0x74, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x03, 0x67, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, - 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, - 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x74, 0x69, 0x6d, - 0x65, 0x32, 0xba, 0x08, 0x0a, 0x08, 0x42, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x12, 0x31, - 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, - 0x00, 0x12, 0x25, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0a, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x00, 0x12, 0x21, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x52, - 0x65, 0x70, 0x6f, 0x12, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x1a, 0x0a, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x12, 0x47, - 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x4f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x30, - 0x01, 0x12, 0x3e, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x76, - 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x22, - 0x00, 0x12, 0x43, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, - 0x74, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, - 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x69, + 0x64, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x03, 0x52, 0x03, 0x69, 0x64, 0x73, 0x12, 0x15, 0x0a, + 0x06, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, + 0x61, 0x73, 0x74, 0x4e, 0x22, 0x7e, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x53, + 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, + 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x22, 0x68, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, + 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, 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, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x56, + 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, + 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, + 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, + 0x25, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0b, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, + 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x38, 0x0a, 0x14, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x22, 0xd3, 0x01, 0x0a, 0x07, 0x4c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x69, + 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x67, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, + 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x74, 0x69, 0x6d, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x63, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x63, 0x74, 0x69, 0x6d, 0x65, 0x32, 0xba, 0x08, 0x0a, 0x08, 0x42, 0x61, 0x63, 0x6b, 0x72, + 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x22, 0x00, 0x12, 0x25, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, + 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x00, 0x12, 0x21, 0x0a, + 0x07, 0x41, 0x64, 0x64, 0x52, 0x65, 0x70, 0x6f, 0x12, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x70, 0x6f, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x00, + 0x12, 0x44, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, + 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3e, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0e, 0x49, 0x6e, - 0x64, 0x65, 0x78, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x12, 0x12, 0x2e, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x06, 0x42, 0x61, - 0x63, 0x6b, 0x75, 0x70, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x22, 0x00, 0x12, 0x35, 0x0a, 0x05, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x12, 0x2e, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x06, 0x46, 0x6f, 0x72, - 0x67, 0x65, 0x74, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, - 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, - 0x00, 0x12, 0x3f, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x1a, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x22, 0x00, 0x12, 0x36, 0x0a, 0x06, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x12, 0x2e, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x05, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, - 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, - 0x00, 0x12, 0x35, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x11, 0x2e, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x42, - 0x69, 0x67, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, - 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, - 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x41, - 0x0a, 0x0c, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x17, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, - 0x00, 0x12, 0x3b, 0x0a, 0x10, 0x50, 0x61, 0x74, 0x68, 0x41, 0x75, 0x74, 0x6f, 0x63, 0x6f, 0x6d, - 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x42, 0x2c, - 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, - 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, - 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x1c, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, + 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, + 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x3e, 0x0a, 0x0e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, + 0x73, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, + 0x36, 0x0a, 0x06, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x05, 0x50, 0x72, 0x75, 0x6e, 0x65, + 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x36, + 0x0a, 0x06, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x12, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x06, 0x55, 0x6e, 0x6c, 0x6f, 0x63, + 0x6b, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, + 0x35, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, + 0x12, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x44, 0x0a, + 0x13, 0x47, 0x65, 0x74, 0x42, 0x69, 0x67, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x44, 0x61, 0x74, 0x61, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0c, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, + 0x6f, 0x72, 0x79, 0x12, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x10, 0x50, 0x61, 0x74, 0x68, 0x41, 0x75, + 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x11, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, + 0x74, 0x22, 0x00, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, + 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -848,67 +739,65 @@ func file_v1_service_proto_rawDescGZIP() []byte { return file_v1_service_proto_rawDescData } -var file_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_v1_service_proto_goTypes = []interface{}{ - (*LoginRequest)(nil), // 0: v1.LoginRequest - (*LoginResponse)(nil), // 1: v1.LoginResponse - (*ClearHistoryRequest)(nil), // 2: v1.ClearHistoryRequest - (*ListSnapshotsRequest)(nil), // 3: v1.ListSnapshotsRequest - (*GetOperationsRequest)(nil), // 4: v1.GetOperationsRequest - (*RestoreSnapshotRequest)(nil), // 5: v1.RestoreSnapshotRequest - (*ListSnapshotFilesRequest)(nil), // 6: v1.ListSnapshotFilesRequest - (*ListSnapshotFilesResponse)(nil), // 7: v1.ListSnapshotFilesResponse - (*OperationDataRequest)(nil), // 8: v1.OperationDataRequest - (*LsEntry)(nil), // 9: v1.LsEntry - (*emptypb.Empty)(nil), // 10: google.protobuf.Empty - (*Config)(nil), // 11: v1.Config - (*Repo)(nil), // 12: v1.Repo - (*types.StringValue)(nil), // 13: types.StringValue - (*types.Int64Value)(nil), // 14: types.Int64Value - (*OperationEvent)(nil), // 15: v1.OperationEvent - (*OperationList)(nil), // 16: v1.OperationList - (*ResticSnapshotList)(nil), // 17: v1.ResticSnapshotList - (*types.BytesValue)(nil), // 18: types.BytesValue - (*types.StringList)(nil), // 19: types.StringList + (*ClearHistoryRequest)(nil), // 0: v1.ClearHistoryRequest + (*ListSnapshotsRequest)(nil), // 1: v1.ListSnapshotsRequest + (*GetOperationsRequest)(nil), // 2: v1.GetOperationsRequest + (*RestoreSnapshotRequest)(nil), // 3: v1.RestoreSnapshotRequest + (*ListSnapshotFilesRequest)(nil), // 4: v1.ListSnapshotFilesRequest + (*ListSnapshotFilesResponse)(nil), // 5: v1.ListSnapshotFilesResponse + (*OperationDataRequest)(nil), // 6: v1.OperationDataRequest + (*LsEntry)(nil), // 7: v1.LsEntry + (*emptypb.Empty)(nil), // 8: google.protobuf.Empty + (*Config)(nil), // 9: v1.Config + (*Repo)(nil), // 10: v1.Repo + (*types.StringValue)(nil), // 11: types.StringValue + (*types.Int64Value)(nil), // 12: types.Int64Value + (*OperationEvent)(nil), // 13: v1.OperationEvent + (*OperationList)(nil), // 14: v1.OperationList + (*ResticSnapshotList)(nil), // 15: v1.ResticSnapshotList + (*types.BytesValue)(nil), // 16: types.BytesValue + (*types.StringList)(nil), // 17: types.StringList } var file_v1_service_proto_depIdxs = []int32{ - 9, // 0: v1.ListSnapshotFilesResponse.entries:type_name -> v1.LsEntry - 10, // 1: v1.Backrest.GetConfig:input_type -> google.protobuf.Empty - 11, // 2: v1.Backrest.SetConfig:input_type -> v1.Config - 12, // 3: v1.Backrest.AddRepo:input_type -> v1.Repo - 10, // 4: v1.Backrest.GetOperationEvents:input_type -> google.protobuf.Empty - 4, // 5: v1.Backrest.GetOperations:input_type -> v1.GetOperationsRequest - 3, // 6: v1.Backrest.ListSnapshots:input_type -> v1.ListSnapshotsRequest - 6, // 7: v1.Backrest.ListSnapshotFiles:input_type -> v1.ListSnapshotFilesRequest - 13, // 8: v1.Backrest.IndexSnapshots:input_type -> types.StringValue - 13, // 9: v1.Backrest.Backup:input_type -> types.StringValue - 13, // 10: v1.Backrest.Prune:input_type -> types.StringValue - 13, // 11: v1.Backrest.Forget:input_type -> types.StringValue - 5, // 12: v1.Backrest.Restore:input_type -> v1.RestoreSnapshotRequest - 13, // 13: v1.Backrest.Unlock:input_type -> types.StringValue - 13, // 14: v1.Backrest.Stats:input_type -> types.StringValue - 14, // 15: v1.Backrest.Cancel:input_type -> types.Int64Value - 8, // 16: v1.Backrest.GetBigOperationData:input_type -> v1.OperationDataRequest - 2, // 17: v1.Backrest.ClearHistory:input_type -> v1.ClearHistoryRequest - 13, // 18: v1.Backrest.PathAutocomplete:input_type -> types.StringValue - 11, // 19: v1.Backrest.GetConfig:output_type -> v1.Config - 11, // 20: v1.Backrest.SetConfig:output_type -> v1.Config - 11, // 21: v1.Backrest.AddRepo:output_type -> v1.Config - 15, // 22: v1.Backrest.GetOperationEvents:output_type -> v1.OperationEvent - 16, // 23: v1.Backrest.GetOperations:output_type -> v1.OperationList - 17, // 24: v1.Backrest.ListSnapshots:output_type -> v1.ResticSnapshotList - 7, // 25: v1.Backrest.ListSnapshotFiles:output_type -> v1.ListSnapshotFilesResponse - 10, // 26: v1.Backrest.IndexSnapshots:output_type -> google.protobuf.Empty - 10, // 27: v1.Backrest.Backup:output_type -> google.protobuf.Empty - 10, // 28: v1.Backrest.Prune:output_type -> google.protobuf.Empty - 10, // 29: v1.Backrest.Forget:output_type -> google.protobuf.Empty - 10, // 30: v1.Backrest.Restore:output_type -> google.protobuf.Empty - 10, // 31: v1.Backrest.Unlock:output_type -> google.protobuf.Empty - 10, // 32: v1.Backrest.Stats:output_type -> google.protobuf.Empty - 10, // 33: v1.Backrest.Cancel:output_type -> google.protobuf.Empty - 18, // 34: v1.Backrest.GetBigOperationData:output_type -> types.BytesValue - 10, // 35: v1.Backrest.ClearHistory:output_type -> google.protobuf.Empty - 19, // 36: v1.Backrest.PathAutocomplete:output_type -> types.StringList + 7, // 0: v1.ListSnapshotFilesResponse.entries:type_name -> v1.LsEntry + 8, // 1: v1.Backrest.GetConfig:input_type -> google.protobuf.Empty + 9, // 2: v1.Backrest.SetConfig:input_type -> v1.Config + 10, // 3: v1.Backrest.AddRepo:input_type -> v1.Repo + 8, // 4: v1.Backrest.GetOperationEvents:input_type -> google.protobuf.Empty + 2, // 5: v1.Backrest.GetOperations:input_type -> v1.GetOperationsRequest + 1, // 6: v1.Backrest.ListSnapshots:input_type -> v1.ListSnapshotsRequest + 4, // 7: v1.Backrest.ListSnapshotFiles:input_type -> v1.ListSnapshotFilesRequest + 11, // 8: v1.Backrest.IndexSnapshots:input_type -> types.StringValue + 11, // 9: v1.Backrest.Backup:input_type -> types.StringValue + 11, // 10: v1.Backrest.Prune:input_type -> types.StringValue + 11, // 11: v1.Backrest.Forget:input_type -> types.StringValue + 3, // 12: v1.Backrest.Restore:input_type -> v1.RestoreSnapshotRequest + 11, // 13: v1.Backrest.Unlock:input_type -> types.StringValue + 11, // 14: v1.Backrest.Stats:input_type -> types.StringValue + 12, // 15: v1.Backrest.Cancel:input_type -> types.Int64Value + 6, // 16: v1.Backrest.GetBigOperationData:input_type -> v1.OperationDataRequest + 0, // 17: v1.Backrest.ClearHistory:input_type -> v1.ClearHistoryRequest + 11, // 18: v1.Backrest.PathAutocomplete:input_type -> types.StringValue + 9, // 19: v1.Backrest.GetConfig:output_type -> v1.Config + 9, // 20: v1.Backrest.SetConfig:output_type -> v1.Config + 9, // 21: v1.Backrest.AddRepo:output_type -> v1.Config + 13, // 22: v1.Backrest.GetOperationEvents:output_type -> v1.OperationEvent + 14, // 23: v1.Backrest.GetOperations:output_type -> v1.OperationList + 15, // 24: v1.Backrest.ListSnapshots:output_type -> v1.ResticSnapshotList + 5, // 25: v1.Backrest.ListSnapshotFiles:output_type -> v1.ListSnapshotFilesResponse + 8, // 26: v1.Backrest.IndexSnapshots:output_type -> google.protobuf.Empty + 8, // 27: v1.Backrest.Backup:output_type -> google.protobuf.Empty + 8, // 28: v1.Backrest.Prune:output_type -> google.protobuf.Empty + 8, // 29: v1.Backrest.Forget:output_type -> google.protobuf.Empty + 8, // 30: v1.Backrest.Restore:output_type -> google.protobuf.Empty + 8, // 31: v1.Backrest.Unlock:output_type -> google.protobuf.Empty + 8, // 32: v1.Backrest.Stats:output_type -> google.protobuf.Empty + 8, // 33: v1.Backrest.Cancel:output_type -> google.protobuf.Empty + 16, // 34: v1.Backrest.GetBigOperationData:output_type -> types.BytesValue + 8, // 35: v1.Backrest.ClearHistory:output_type -> google.protobuf.Empty + 17, // 36: v1.Backrest.PathAutocomplete:output_type -> types.StringList 19, // [19:37] is the sub-list for method output_type 1, // [1:19] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name @@ -926,30 +815,6 @@ func file_v1_service_proto_init() { file_v1_operations_proto_init() if !protoimpl.UnsafeEnabled { file_v1_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_v1_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_v1_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ClearHistoryRequest); i { case 0: return &v.state @@ -961,7 +826,7 @@ func file_v1_service_proto_init() { return nil } } - file_v1_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_v1_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListSnapshotsRequest); i { case 0: return &v.state @@ -973,7 +838,7 @@ func file_v1_service_proto_init() { return nil } } - file_v1_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_v1_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetOperationsRequest); i { case 0: return &v.state @@ -985,7 +850,7 @@ func file_v1_service_proto_init() { return nil } } - file_v1_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_v1_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RestoreSnapshotRequest); i { case 0: return &v.state @@ -997,7 +862,7 @@ func file_v1_service_proto_init() { return nil } } - file_v1_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_v1_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListSnapshotFilesRequest); i { case 0: return &v.state @@ -1009,7 +874,7 @@ func file_v1_service_proto_init() { return nil } } - file_v1_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_v1_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListSnapshotFilesResponse); i { case 0: return &v.state @@ -1021,7 +886,7 @@ func file_v1_service_proto_init() { return nil } } - file_v1_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_v1_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*OperationDataRequest); i { case 0: return &v.state @@ -1033,7 +898,7 @@ func file_v1_service_proto_init() { return nil } } - file_v1_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + file_v1_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*LsEntry); i { case 0: return &v.state @@ -1052,7 +917,7 @@ func file_v1_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_service_proto_rawDesc, NumEnums: 0, - NumMessages: 10, + NumMessages: 8, NumExtensions: 0, NumServices: 1, }, diff --git a/gen/go/v1/v1connect/authentication.connect.go b/gen/go/v1/v1connect/authentication.connect.go new file mode 100644 index 000000000..b8ce7ab7b --- /dev/null +++ b/gen/go/v1/v1connect/authentication.connect.go @@ -0,0 +1,143 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: v1/authentication.proto + +package v1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + types "github.com/garethgeorge/backrest/gen/go/types" + v1 "github.com/garethgeorge/backrest/gen/go/v1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // AuthenticationName is the fully-qualified name of the Authentication service. + AuthenticationName = "v1.Authentication" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // AuthenticationLoginProcedure is the fully-qualified name of the Authentication's Login RPC. + AuthenticationLoginProcedure = "/v1.Authentication/Login" + // AuthenticationHashPasswordProcedure is the fully-qualified name of the Authentication's + // HashPassword RPC. + AuthenticationHashPasswordProcedure = "/v1.Authentication/HashPassword" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + authenticationServiceDescriptor = v1.File_v1_authentication_proto.Services().ByName("Authentication") + authenticationLoginMethodDescriptor = authenticationServiceDescriptor.Methods().ByName("Login") + authenticationHashPasswordMethodDescriptor = authenticationServiceDescriptor.Methods().ByName("HashPassword") +) + +// AuthenticationClient is a client for the v1.Authentication service. +type AuthenticationClient interface { + Login(context.Context, *connect.Request[v1.LoginRequest]) (*connect.Response[v1.LoginResponse], error) + HashPassword(context.Context, *connect.Request[types.StringValue]) (*connect.Response[types.StringValue], error) +} + +// NewAuthenticationClient constructs a client for the v1.Authentication service. By default, it +// uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends +// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or +// connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewAuthenticationClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) AuthenticationClient { + baseURL = strings.TrimRight(baseURL, "/") + return &authenticationClient{ + login: connect.NewClient[v1.LoginRequest, v1.LoginResponse]( + httpClient, + baseURL+AuthenticationLoginProcedure, + connect.WithSchema(authenticationLoginMethodDescriptor), + connect.WithClientOptions(opts...), + ), + hashPassword: connect.NewClient[types.StringValue, types.StringValue]( + httpClient, + baseURL+AuthenticationHashPasswordProcedure, + connect.WithSchema(authenticationHashPasswordMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// authenticationClient implements AuthenticationClient. +type authenticationClient struct { + login *connect.Client[v1.LoginRequest, v1.LoginResponse] + hashPassword *connect.Client[types.StringValue, types.StringValue] +} + +// Login calls v1.Authentication.Login. +func (c *authenticationClient) Login(ctx context.Context, req *connect.Request[v1.LoginRequest]) (*connect.Response[v1.LoginResponse], error) { + return c.login.CallUnary(ctx, req) +} + +// HashPassword calls v1.Authentication.HashPassword. +func (c *authenticationClient) HashPassword(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[types.StringValue], error) { + return c.hashPassword.CallUnary(ctx, req) +} + +// AuthenticationHandler is an implementation of the v1.Authentication service. +type AuthenticationHandler interface { + Login(context.Context, *connect.Request[v1.LoginRequest]) (*connect.Response[v1.LoginResponse], error) + HashPassword(context.Context, *connect.Request[types.StringValue]) (*connect.Response[types.StringValue], error) +} + +// NewAuthenticationHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewAuthenticationHandler(svc AuthenticationHandler, opts ...connect.HandlerOption) (string, http.Handler) { + authenticationLoginHandler := connect.NewUnaryHandler( + AuthenticationLoginProcedure, + svc.Login, + connect.WithSchema(authenticationLoginMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + authenticationHashPasswordHandler := connect.NewUnaryHandler( + AuthenticationHashPasswordProcedure, + svc.HashPassword, + connect.WithSchema(authenticationHashPasswordMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/v1.Authentication/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case AuthenticationLoginProcedure: + authenticationLoginHandler.ServeHTTP(w, r) + case AuthenticationHashPasswordProcedure: + authenticationHashPasswordHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedAuthenticationHandler returns CodeUnimplemented from all methods. +type UnimplementedAuthenticationHandler struct{} + +func (UnimplementedAuthenticationHandler) Login(context.Context, *connect.Request[v1.LoginRequest]) (*connect.Response[v1.LoginResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1.Authentication.Login is not implemented")) +} + +func (UnimplementedAuthenticationHandler) HashPassword(context.Context, *connect.Request[types.StringValue]) (*connect.Response[types.StringValue], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1.Authentication.HashPassword is not implemented")) +} diff --git a/go.mod b/go.mod index 5c8a43747..59ca0c6ad 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,10 @@ require ( github.com/GeertJohan/go.rice v1.0.3 github.com/alessio/shellescape v1.4.2 github.com/gitploy-io/cronexpr v0.2.2 + github.com/golang-jwt/jwt/v5 v5.2.0 github.com/hashicorp/go-multierror v1.1.1 github.com/mattn/go-colorable v0.1.13 github.com/natefinch/atomic v1.0.1 - github.com/nikoksr/notify v0.41.0 go.etcd.io/bbolt v1.3.8 go.uber.org/zap v1.26.0 golang.org/x/crypto v0.18.0 @@ -23,13 +23,10 @@ require ( require ( github.com/daaku/go.zipexe v1.0.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/stretchr/testify v1.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe // indirect diff --git a/go.sum b/go.sum index 025c09387..e2aaac920 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -31,11 +33,7 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= -github.com/nikoksr/notify v0.41.0 h1:4LGE41GpWdHX5M3Xo6DlWRwS2WLDbOq1Rk7IzY4vjmQ= -github.com/nikoksr/notify v0.41.0/go.mod h1:FoE0UVPeopz1Vy5nm9vQZ+JVmYjEIjQgbFstbkw+cRE= github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= @@ -50,39 +48,23 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= -google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe h1:USL2DhxfgRchafRvt/wYyyQNzwgL7ZiURcozOE/Pkvo= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= -google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o= -google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c= google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8= google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= diff --git a/internal/api/auth.go b/internal/api/auth.go deleted file mode 100644 index 8caa7cacc..000000000 --- a/internal/api/auth.go +++ /dev/null @@ -1,48 +0,0 @@ -package api - -import ( - "encoding/base64" - - v1 "github.com/garethgeorge/backrest/gen/go/v1" - "golang.org/x/crypto/bcrypt" -) - -type User v1.User - -func (u *User) CheckPassword(password string) bool { - switch pw := u.Password.(type) { - case *v1.User_PasswordBcrypt: - pwHash, err := base64.StdEncoding.DecodeString(pw.PasswordBcrypt) - if err != nil { - return false - } - return bcrypt.CompareHashAndPassword(pwHash, []byte(password)) == nil - default: - return false - } -} - -type Authenticator struct { - users map[string]*User -} - -func NewAuthenticator(users []*v1.User) *Authenticator { - auth := &Authenticator{ - users: make(map[string]*User), - } - for _, user := range users { - auth.users[user.Name] = (*User)(user) - } - return auth -} - -func (a *Authenticator) Authenticate(username, password string) (*User, bool) { - user, ok := a.users[username] - if !ok { - return nil, false - } - if !user.CheckPassword(password) { - return nil, false - } - return user, true -} diff --git a/internal/api/authenticationhandler.go b/internal/api/authenticationhandler.go new file mode 100644 index 000000000..a29b0de16 --- /dev/null +++ b/internal/api/authenticationhandler.go @@ -0,0 +1,51 @@ +package api + +import ( + "context" + + "connectrpc.com/connect" + "github.com/garethgeorge/backrest/gen/go/types" + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/gen/go/v1/v1connect" + "github.com/garethgeorge/backrest/internal/auth" + "go.uber.org/zap" +) + +type AuthenticationHandler struct { + // v1connect.UnimplementedAuthenticationHandler + authenticator *auth.Authenticator +} + +var _ v1connect.AuthenticationHandler = &AuthenticationHandler{} + +func NewAuthenticationHandler(authenticator *auth.Authenticator) *AuthenticationHandler { + return &AuthenticationHandler{ + authenticator: authenticator, + } +} + +func (s *AuthenticationHandler) Login(ctx context.Context, req *connect.Request[v1.LoginRequest]) (*connect.Response[v1.LoginResponse], error) { + zap.L().Debug("login request", zap.String("username", req.Msg.Username)) + user, err := s.authenticator.Login(req.Msg.Username, req.Msg.Password) + if err != nil { + zap.L().Warn("failed login attempt", zap.Error(err)) + return nil, connect.NewError(connect.CodeUnauthenticated, auth.ErrInvalidPassword) + } + + token, err := s.authenticator.CreateJWT(user) + if err != nil { + return nil, err + } + + return connect.NewResponse(&v1.LoginResponse{ + Token: token, + }), nil +} + +func (s *AuthenticationHandler) HashPassword(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[types.StringValue], error) { + hash, err := auth.CreatePassword(req.Msg.Value) + if err != nil { + return nil, err + } + return connect.NewResponse(&types.StringValue{Value: hash}), nil +} diff --git a/internal/api/server.go b/internal/api/backresthandler.go similarity index 81% rename from internal/api/server.go rename to internal/api/backresthandler.go index 3fdc75e46..dfc8f130f 100644 --- a/internal/api/server.go +++ b/internal/api/backresthandler.go @@ -25,17 +25,17 @@ import ( "google.golang.org/protobuf/types/known/emptypb" ) -type Server struct { +type BackrestHandler struct { v1connect.UnimplementedBackrestHandler config config.ConfigStore orchestrator *orchestrator.Orchestrator oplog *oplog.OpLog } -var _ v1connect.BackrestHandler = &Server{} +var _ v1connect.BackrestHandler = &BackrestHandler{} -func NewServer(config config.ConfigStore, orchestrator *orchestrator.Orchestrator, oplog *oplog.OpLog) *Server { - s := &Server{ +func NewBackrestHandler(config config.ConfigStore, orchestrator *orchestrator.Orchestrator, oplog *oplog.OpLog) *BackrestHandler { + s := &BackrestHandler{ config: config, orchestrator: orchestrator, oplog: oplog, @@ -45,7 +45,7 @@ func NewServer(config config.ConfigStore, orchestrator *orchestrator.Orchestrato } // GetConfig implements GET /v1/config -func (s *Server) GetConfig(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[v1.Config], error) { +func (s *BackrestHandler) GetConfig(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[v1.Config], error) { config, err := s.config.Get() if err != nil { return nil, fmt.Errorf("failed to get config: %w", err) @@ -54,7 +54,7 @@ func (s *Server) GetConfig(ctx context.Context, req *connect.Request[emptypb.Emp } // SetConfig implements POST /v1/config -func (s *Server) SetConfig(ctx context.Context, req *connect.Request[v1.Config]) (*connect.Response[v1.Config], error) { +func (s *BackrestHandler) SetConfig(ctx context.Context, req *connect.Request[v1.Config]) (*connect.Response[v1.Config], error) { existing, err := s.config.Get() if err != nil { return nil, fmt.Errorf("failed to check current config: %w", err) @@ -81,7 +81,7 @@ func (s *Server) SetConfig(ctx context.Context, req *connect.Request[v1.Config]) } // AddRepo implements POST /v1/config/repo, it includes validation that the repo can be initialized. -func (s *Server) AddRepo(ctx context.Context, req *connect.Request[v1.Repo]) (*connect.Response[v1.Config], error) { +func (s *BackrestHandler) AddRepo(ctx context.Context, req *connect.Request[v1.Repo]) (*connect.Response[v1.Config], error) { c, err := s.config.Get() if err != nil { return nil, fmt.Errorf("failed to get config: %w", err) @@ -123,7 +123,7 @@ func (s *Server) AddRepo(ctx context.Context, req *connect.Request[v1.Repo]) (*c } // ListSnapshots implements POST /v1/snapshots -func (s *Server) ListSnapshots(ctx context.Context, req *connect.Request[v1.ListSnapshotsRequest]) (*connect.Response[v1.ResticSnapshotList], error) { +func (s *BackrestHandler) ListSnapshots(ctx context.Context, req *connect.Request[v1.ListSnapshotsRequest]) (*connect.Response[v1.ResticSnapshotList], error) { query := req.Msg repo, err := s.orchestrator.GetRepo(query.RepoId) if err != nil { @@ -157,7 +157,7 @@ func (s *Server) ListSnapshots(ctx context.Context, req *connect.Request[v1.List }), nil } -func (s *Server) ListSnapshotFiles(ctx context.Context, req *connect.Request[v1.ListSnapshotFilesRequest]) (*connect.Response[v1.ListSnapshotFilesResponse], error) { +func (s *BackrestHandler) ListSnapshotFiles(ctx context.Context, req *connect.Request[v1.ListSnapshotFilesRequest]) (*connect.Response[v1.ListSnapshotFilesResponse], error) { query := req.Msg repo, err := s.orchestrator.GetRepo(query.RepoId) if err != nil { @@ -176,7 +176,7 @@ func (s *Server) ListSnapshotFiles(ctx context.Context, req *connect.Request[v1. } // GetOperationEvents implements GET /v1/events/operations -func (s *Server) GetOperationEvents(ctx context.Context, req *connect.Request[emptypb.Empty], resp *connect.ServerStream[v1.OperationEvent]) error { +func (s *BackrestHandler) GetOperationEvents(ctx context.Context, req *connect.Request[emptypb.Empty], resp *connect.ServerStream[v1.OperationEvent]) error { errorChan := make(chan error) defer close(errorChan) callback := func(oldOp *v1.Operation, newOp *v1.Operation) { @@ -215,7 +215,7 @@ func (s *Server) GetOperationEvents(ctx context.Context, req *connect.Request[em } } -func (s *Server) GetOperations(ctx context.Context, req *connect.Request[v1.GetOperationsRequest]) (*connect.Response[v1.OperationList], error) { +func (s *BackrestHandler) GetOperations(ctx context.Context, req *connect.Request[v1.GetOperationsRequest]) (*connect.Response[v1.OperationList], error) { idCollector := indexutil.CollectAll() if req.Msg.LastN != 0 { @@ -257,7 +257,7 @@ func (s *Server) GetOperations(ctx context.Context, req *connect.Request[v1.GetO }), nil } -func (s *Server) IndexSnapshots(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { +func (s *BackrestHandler) IndexSnapshots(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { _, err := s.orchestrator.GetRepo(req.Msg.Value) if err != nil { return nil, fmt.Errorf("failed to get repo %q: %w", req.Msg.Value, err) @@ -268,7 +268,7 @@ func (s *Server) IndexSnapshots(ctx context.Context, req *connect.Request[types. return connect.NewResponse(&emptypb.Empty{}), nil } -func (s *Server) Backup(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { +func (s *BackrestHandler) Backup(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { plan, err := s.orchestrator.GetPlan(req.Msg.Value) if err != nil { return nil, fmt.Errorf("failed to get plan %q: %w", req.Msg.Value, err) @@ -283,7 +283,7 @@ func (s *Server) Backup(ctx context.Context, req *connect.Request[types.StringVa return connect.NewResponse(&emptypb.Empty{}), err } -func (s *Server) Forget(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { +func (s *BackrestHandler) Forget(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { plan, err := s.orchestrator.GetPlan(req.Msg.Value) if err != nil { return nil, fmt.Errorf("failed to get plan %q: %w", req.Msg.Value, err) @@ -301,7 +301,7 @@ func (s *Server) Forget(ctx context.Context, req *connect.Request[types.StringVa return connect.NewResponse(&emptypb.Empty{}), err } -func (s *Server) Prune(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { +func (s *BackrestHandler) Prune(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { plan, err := s.orchestrator.GetPlan(req.Msg.Value) if err != nil { return nil, fmt.Errorf("failed to get plan %q: %w", req.Msg.Value, err) @@ -319,7 +319,7 @@ func (s *Server) Prune(ctx context.Context, req *connect.Request[types.StringVal return connect.NewResponse(&emptypb.Empty{}), nil } -func (s *Server) Restore(ctx context.Context, req *connect.Request[v1.RestoreSnapshotRequest]) (*connect.Response[emptypb.Empty], error) { +func (s *BackrestHandler) Restore(ctx context.Context, req *connect.Request[v1.RestoreSnapshotRequest]) (*connect.Response[emptypb.Empty], error) { plan, err := s.orchestrator.GetPlan(req.Msg.PlanId) if err != nil { return nil, fmt.Errorf("failed to get plan %q: %w", req.Msg.PlanId, err) @@ -347,7 +347,7 @@ func (s *Server) Restore(ctx context.Context, req *connect.Request[v1.RestoreSna return connect.NewResponse(&emptypb.Empty{}), nil } -func (s *Server) Unlock(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { +func (s *BackrestHandler) Unlock(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { repo, err := s.orchestrator.GetRepo(req.Msg.Value) if err != nil { return nil, fmt.Errorf("failed to get repo %q: %w", req.Msg.Value, err) @@ -360,7 +360,7 @@ func (s *Server) Unlock(ctx context.Context, req *connect.Request[types.StringVa return connect.NewResponse(&emptypb.Empty{}), nil } -func (s *Server) Cancel(ctx context.Context, req *connect.Request[types.Int64Value]) (*connect.Response[emptypb.Empty], error) { +func (s *BackrestHandler) Cancel(ctx context.Context, req *connect.Request[types.Int64Value]) (*connect.Response[emptypb.Empty], error) { if err := s.orchestrator.CancelOperation(req.Msg.Value, v1.OperationStatus_STATUS_USER_CANCELLED); err != nil { return nil, err } @@ -368,7 +368,7 @@ func (s *Server) Cancel(ctx context.Context, req *connect.Request[types.Int64Val return connect.NewResponse(&emptypb.Empty{}), nil } -func (s *Server) ClearHistory(ctx context.Context, req *connect.Request[v1.ClearHistoryRequest]) (*connect.Response[emptypb.Empty], error) { +func (s *BackrestHandler) ClearHistory(ctx context.Context, req *connect.Request[v1.ClearHistoryRequest]) (*connect.Response[emptypb.Empty], error) { var err error var ids []int64 opCollector := func(op *v1.Operation) error { @@ -397,7 +397,7 @@ func (s *Server) ClearHistory(ctx context.Context, req *connect.Request[v1.Clear return connect.NewResponse(&emptypb.Empty{}), err } -func (s *Server) GetBigOperationData(ctx context.Context, req *connect.Request[v1.OperationDataRequest]) (*connect.Response[types.BytesValue], error) { +func (s *BackrestHandler) GetBigOperationData(ctx context.Context, req *connect.Request[v1.OperationDataRequest]) (*connect.Response[types.BytesValue], error) { data, err := s.oplog.GetBigData(req.Msg.Id, req.Msg.Key) if err != nil { return nil, fmt.Errorf("get operation data: %w", err) @@ -405,7 +405,7 @@ func (s *Server) GetBigOperationData(ctx context.Context, req *connect.Request[v return connect.NewResponse(&types.BytesValue{Value: data}), nil } -func (s *Server) PathAutocomplete(ctx context.Context, path *connect.Request[types.StringValue]) (*connect.Response[types.StringList], error) { +func (s *BackrestHandler) PathAutocomplete(ctx context.Context, path *connect.Request[types.StringValue]) (*connect.Response[types.StringList], error) { ents, err := os.ReadDir(path.Msg.Value) if errors.Is(err, os.ErrNotExist) { return connect.NewResponse(&types.StringList{}), nil diff --git a/internal/auth/auth.go b/internal/auth/auth.go new file mode 100644 index 000000000..3d4307cae --- /dev/null +++ b/internal/auth/auth.go @@ -0,0 +1,128 @@ +package auth + +import ( + "encoding/base64" + "errors" + "fmt" + "time" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/internal/config" + "github.com/golang-jwt/jwt/v5" + "golang.org/x/crypto/bcrypt" +) + +var defaultUsers = []*v1.User{ + { + Name: "default", + Password: &v1.User_PasswordBcrypt{PasswordBcrypt: "JDJhJDEwJDNCdzJoNFlhaWFZQy9TSDN3ZGxSRHVPZHdzV2lsNmtBSHdFSmtIWHk1dS8wYjZuUWJrMGFx"}, // default password is "password" + }, +} + +type Authenticator struct { + config config.ConfigStore + key []byte +} + +func NewAuthenticator(key []byte, configProvider config.ConfigStore) *Authenticator { + return &Authenticator{ + config: configProvider, + key: key, + } +} + +var ErrUserNotFound = errors.New("user not found") +var ErrInvalidPassword = errors.New("invalid password") + +func (a *Authenticator) users() []*v1.User { + config, err := a.config.Get() + if err != nil { + return nil + } + if len(config.Auth.GetUsers()) != 0 { + return config.Auth.GetUsers() + } + return defaultUsers +} + +func (a *Authenticator) Login(username, password string) (*v1.User, error) { + for _, user := range a.users() { + if user.Name != username { + continue + } + + if err := checkPassword(user, password); err != nil { + return nil, err + } + + return user, nil + } + + return nil, ErrUserNotFound +} + +func (a *Authenticator) VerifyJWT(token string) (*v1.User, error) { + t, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { + return a.key, nil + }) + + if err != nil { + return nil, fmt.Errorf("parse token: %w", err) + } + if !t.Valid { + return nil, fmt.Errorf("invalid token") + } + + subject, err := t.Claims.GetSubject() + if err != nil { + return nil, fmt.Errorf("get subject: %w", err) + } + + for _, user := range a.users() { + if user.Name == subject { + return user, nil + } + } + + return nil, ErrUserNotFound +} + +func (a *Authenticator) CreateJWT(user *v1.User) (string, error) { + claims := &jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)), + Subject: user.Name, + } + + t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + s, err := t.SignedString(a.key) + if err != nil { + return "", fmt.Errorf("sign token: %w", err) + } + + return s, nil +} + +// checkPassword returns nil if the password is correct, or an error if it is not. +func checkPassword(user *v1.User, password string) error { + switch pw := user.Password.(type) { + case *v1.User_PasswordBcrypt: + pwHash, err := base64.StdEncoding.DecodeString(pw.PasswordBcrypt) + if err != nil { + return fmt.Errorf("decode password: %w", err) + } + if err := bcrypt.CompareHashAndPassword(pwHash, []byte(password)); err != nil { + return fmt.Errorf("%w: %w", ErrInvalidPassword, err) + } + default: + return fmt.Errorf("unsupported password type: %T", pw) + } + return nil +} + +func CreatePassword(password string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", fmt.Errorf("generate password: %w", err) + } + return base64.StdEncoding.EncodeToString(hash), nil +} diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go new file mode 100644 index 000000000..e714dada4 --- /dev/null +++ b/internal/auth/auth_test.go @@ -0,0 +1,69 @@ +package auth + +import ( + "errors" + "testing" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/internal/config" +) + +func TestLogin(t *testing.T) { + pass := makePass(t, "testPass") + pass2 := makePass(t, "testPass2") + + config := &config.MemoryStore{ + Config: &v1.Config{ + Auth: &v1.Auth{ + Users: []*v1.User{ + { + Name: "test", + Password: &v1.User_PasswordBcrypt{ + PasswordBcrypt: pass, + }, + }, + { + Name: "anotheruser", + Password: &v1.User_PasswordBcrypt{ + PasswordBcrypt: pass2, + }, + }, + }, + }, + }, + } + + auth := NewAuthenticator([]byte("key"), config) + + tests := []struct { + name string + username string + password string + wantErr error + }{ + {"user 1 valid password", "test", "testPass", nil}, + {"user 2 valid password", "anotheruser", "testPass2", nil}, + {"user 1 wrong password", "test", "wrongPass", ErrInvalidPassword}, + {"invalid user", "nonexistent", "testPass", ErrUserNotFound}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + user, err := auth.Login(test.username, test.password) + if !errors.Is(err, test.wantErr) { + t.Fatalf("Expected error %v, got %v", test.wantErr, err) + } + if err == nil && user.Name != test.username { + t.Fatalf("Expected user name to be '%s', got '%s'", test.username, user.Name) + } + }) + } +} + +func makePass(t *testing.T, pass string) string { + p, err := CreatePassword(pass) + if err != nil { + t.Fatalf("Error creating password: %v", err) + } + return p +} diff --git a/internal/auth/bearer.go b/internal/auth/bearer.go new file mode 100644 index 000000000..55547ac80 --- /dev/null +++ b/internal/auth/bearer.go @@ -0,0 +1,13 @@ +package auth + +import ( + "fmt" + "strings" +) + +func ParseBearerToken(token string) (string, error) { + if !strings.HasPrefix(token, "Bearer ") { + return "", fmt.Errorf("invalid token") + } + return token[7:], nil +} diff --git a/internal/auth/middleware.go b/internal/auth/middleware.go new file mode 100644 index 000000000..84435de49 --- /dev/null +++ b/internal/auth/middleware.go @@ -0,0 +1,36 @@ +package auth + +import ( + "context" + "net/http" + + "go.uber.org/zap" +) + +type contextKey string + +func (k contextKey) String() string { + return "auth context value " + string(k) +} + +const UserContextKey contextKey = "user" + +func RequireAuthentication(h http.Handler, auth *Authenticator) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token, err := ParseBearerToken(r.Header.Get("Authorization")) + if err != nil { + http.Error(w, "Unauthorized (No Authorization Header)", http.StatusUnauthorized) + return + } + + user, err := auth.VerifyJWT(token) + if err != nil { + zap.S().Warnf("auth middleware blocked bad JWT: %v", err) + http.Error(w, "Unauthorized (Bad Token)", http.StatusUnauthorized) + return + } + + ctx := context.WithValue(r.Context(), UserContextKey, user) + h.ServeHTTP(w, r.WithContext(ctx)) + }) +} diff --git a/internal/oplog/bigopdatastore.go b/internal/oplog/bigopdatastore.go index cb99e9897..6e3166ac9 100644 --- a/internal/oplog/bigopdatastore.go +++ b/internal/oplog/bigopdatastore.go @@ -20,11 +20,11 @@ func NewBigOpDataStore(path string) *BigOpDataStore { } func (s *BigOpDataStore) resolvePath(opId int64) string { - return s.path + "/" + strconv.FormatInt(opId, 16) + return s.path + "/" + strconv.FormatInt(opId&0xFF, 16) + "/" + strconv.FormatInt(opId, 16) } func (s *BigOpDataStore) DeleteOperationData(opId int64) error { - dir := s.path + "/" + strconv.FormatInt(opId, 16) + dir := s.resolvePath(opId) files, err := os.ReadDir(dir) if err != nil { if os.IsNotExist(err) { diff --git a/internal/oplog/indexutil/indexutil.go b/internal/oplog/indexutil/indexutil.go index 110ed98d4..c00d52cfa 100644 --- a/internal/oplog/indexutil/indexutil.go +++ b/internal/oplog/indexutil/indexutil.go @@ -154,3 +154,13 @@ func CollectLastN(lastN int) Collector { return ids } } + +func Reversed(collector Collector) Collector { + return func(iter IndexIterator) []int64 { + ids := collector(iter) + for i, j := 0, len(ids)-1; i < j; i, j = i+1, j-1 { + ids[i], ids[j] = ids[j], ids[i] + } + return ids + } +} diff --git a/internal/oplog/oplog.go b/internal/oplog/oplog.go index f40ce4b24..d96255425 100644 --- a/internal/oplog/oplog.go +++ b/internal/oplog/oplog.go @@ -28,6 +28,7 @@ const ( ) var ErrNotExist = errors.New("operation does not exist") +var ErrStopIteration = errors.New("stop iteration") var ( SystemBucket = []byte("oplog.system") // system stores metadata @@ -373,6 +374,9 @@ func (o *OpLog) forOpsByIds(tx *bolt.Tx, ids []int64, do func(*v1.Operation) err return err } if err := do(op); err != nil { + if err == ErrStopIteration { + break + } return err } } diff --git a/internal/orchestrator/taskprune.go b/internal/orchestrator/taskprune.go index 38d22183e..db1d7ce6e 100644 --- a/internal/orchestrator/taskprune.go +++ b/internal/orchestrator/taskprune.go @@ -9,6 +9,7 @@ import ( v1 "github.com/garethgeorge/backrest/gen/go/v1" "github.com/garethgeorge/backrest/internal/hook" + "github.com/garethgeorge/backrest/internal/oplog" "github.com/garethgeorge/backrest/internal/oplog/indexutil" "go.uber.org/zap" ) @@ -84,9 +85,10 @@ func (t *PruneTask) shouldRun(now time.Time) (bool, error) { func (t *PruneTask) getNextPruneTime(repo *RepoOrchestrator, policy *v1.PrunePolicy) (time.Time, error) { var lastPruneTime time.Time - t.orch.OpLog.ForEachByRepo(t.plan.Repo, indexutil.CollectLastN(100), func(op *v1.Operation) error { + t.orch.OpLog.ForEachByRepo(t.plan.Repo, indexutil.Reversed(indexutil.CollectAll()), func(op *v1.Operation) error { if _, ok := op.Op.(*v1.Operation_OperationPrune); ok { lastPruneTime = time.Unix(0, op.UnixTimeStartMs*int64(time.Millisecond)) + return oplog.ErrStopIteration } return nil }) diff --git a/internal/orchestrator/taskstats.go b/internal/orchestrator/taskstats.go index 1ae9f6fa6..84cc15a48 100644 --- a/internal/orchestrator/taskstats.go +++ b/internal/orchestrator/taskstats.go @@ -8,6 +8,7 @@ import ( v1 "github.com/garethgeorge/backrest/gen/go/v1" "github.com/garethgeorge/backrest/internal/hook" + "github.com/garethgeorge/backrest/internal/oplog" "github.com/garethgeorge/backrest/internal/oplog/indexutil" "go.uber.org/zap" ) @@ -41,13 +42,10 @@ func (t *StatsTask) Name() string { func (t *StatsTask) shouldRun() (bool, error) { var bytesSinceLastStat int64 = -1 - if err := t.orch.OpLog.ForEachByRepo(t.plan.Repo, indexutil.CollectLastN(50), func(op *v1.Operation) error { + if err := t.orch.OpLog.ForEachByRepo(t.plan.Repo, indexutil.Reversed(indexutil.CollectAll()), func(op *v1.Operation) error { if _, ok := op.Op.(*v1.Operation_OperationStats); ok { - bytesSinceLastStat = 0 + return oplog.ErrStopIteration } else if backup, ok := op.Op.(*v1.Operation_OperationBackup); ok && backup.OperationBackup.LastStatus != nil { - if bytesSinceLastStat == -1 { - return nil - } if summary, ok := backup.OperationBackup.LastStatus.Entry.(*v1.BackupProgressEntry_Summary); ok { bytesSinceLastStat += summary.Summary.DataAdded } diff --git a/proto/v1/authentication.proto b/proto/v1/authentication.proto new file mode 100644 index 000000000..7951dab01 --- /dev/null +++ b/proto/v1/authentication.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package v1; + +option go_package = "github.com/garethgeorge/backrest/gen/go/v1"; + +import "v1/config.proto"; +import "types/value.proto"; +import "google/protobuf/empty.proto"; +import "google/api/annotations.proto"; + +service Authentication { + rpc Login(LoginRequest) returns (LoginResponse) {} + rpc HashPassword(types.StringValue) returns (types.StringValue) {} +} + +message LoginRequest { + string username = 1; + string password = 2; +} + +message LoginResponse { + string token = 1; // JWT token +} diff --git a/proto/v1/config.proto b/proto/v1/config.proto index 5a8353c5c..ca3faabf5 100644 --- a/proto/v1/config.proto +++ b/proto/v1/config.proto @@ -14,7 +14,7 @@ message Config { repeated Repo repos = 3 [json_name="repos"]; repeated Plan plans = 4 [json_name="plans"]; - repeated User users = 5 [json_name="users"]; + Auth auth = 5 [json_name="auth"]; } message Repo { @@ -56,13 +56,6 @@ message PrunePolicy { int32 max_unused_bytes = 101 [json_name="maxUnusedBytes"]; // max number of bytes that can be unused before prune is run. } -message User { - string name = 1; - oneof password { - string password_bcrypt = 2; - } -} - message Hook { enum Condition { CONDITION_UNKNOWN = 0; @@ -102,3 +95,13 @@ message Hook { } } +message Auth { + repeated User users = 2 [json_name="users"]; // users to allow access to the UI. +} + +message User { + string name = 1 [json_name="name"]; + oneof password { + string password_bcrypt = 2 [json_name="passwordBcrypt"]; + } +} diff --git a/proto/v1/operations.proto b/proto/v1/operations.proto index 37e060b10..2416c0bd1 100644 --- a/proto/v1/operations.proto +++ b/proto/v1/operations.proto @@ -99,4 +99,4 @@ message OperationStats { message OperationRunHook { string name = 1; // description of the hook that was run. typically repo/hook_idx or plan/hook_idx. string output_ref = 2; // reference to the output of the hook. -} \ No newline at end of file +} diff --git a/proto/v1/service.proto b/proto/v1/service.proto index 1839f28eb..9b1338a4a 100644 --- a/proto/v1/service.proto +++ b/proto/v1/service.proto @@ -60,15 +60,6 @@ service Backrest { rpc PathAutocomplete (types.StringValue) returns (types.StringList) {} } -message LoginRequest { - string username = 1; - string password = 2; -} - -message LoginResponse { - string jwt = 1; -} - message ClearHistoryRequest { string repo_id = 1; string plan_id = 2; diff --git a/webui/.proxyrc.json b/webui/.proxyrc.json index 41448cecb..24cf63ef6 100644 --- a/webui/.proxyrc.json +++ b/webui/.proxyrc.json @@ -2,5 +2,9 @@ "/v1.Backrest": { "target": "http://localhost:9898", "secure": false + }, + "/v1.Authentication": { + "target": "http://localhost:9898", + "secure": false } } diff --git a/webui/gen/ts/v1/authentication_connect.ts b/webui/gen/ts/v1/authentication_connect.ts new file mode 100644 index 000000000..3b3e83be2 --- /dev/null +++ b/webui/gen/ts/v1/authentication_connect.ts @@ -0,0 +1,36 @@ +// @generated by protoc-gen-connect-es v1.3.0 with parameter "target=ts" +// @generated from file v1/authentication.proto (package v1, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import { LoginRequest, LoginResponse } from "./authentication_pb.js"; +import { MethodKind } from "@bufbuild/protobuf"; +import { StringValue } from "../types/value_pb.js"; + +/** + * @generated from service v1.Authentication + */ +export const Authentication = { + typeName: "v1.Authentication", + methods: { + /** + * @generated from rpc v1.Authentication.Login + */ + login: { + name: "Login", + I: LoginRequest, + O: LoginResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc v1.Authentication.HashPassword + */ + hashPassword: { + name: "HashPassword", + I: StringValue, + O: StringValue, + kind: MethodKind.Unary, + }, + } +} as const; + diff --git a/webui/gen/ts/v1/authentication_pb.ts b/webui/gen/ts/v1/authentication_pb.ts new file mode 100644 index 000000000..6f6e77c63 --- /dev/null +++ b/webui/gen/ts/v1/authentication_pb.ts @@ -0,0 +1,90 @@ +// @generated by protoc-gen-es v1.6.0 with parameter "target=ts" +// @generated from file v1/authentication.proto (package v1, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; +import { Message, proto3 } from "@bufbuild/protobuf"; + +/** + * @generated from message v1.LoginRequest + */ +export class LoginRequest extends Message { + /** + * @generated from field: string username = 1; + */ + username = ""; + + /** + * @generated from field: string password = 2; + */ + password = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "v1.LoginRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "username", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "password", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): LoginRequest { + return new LoginRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): LoginRequest { + return new LoginRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): LoginRequest { + return new LoginRequest().fromJsonString(jsonString, options); + } + + static equals(a: LoginRequest | PlainMessage | undefined, b: LoginRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(LoginRequest, a, b); + } +} + +/** + * @generated from message v1.LoginResponse + */ +export class LoginResponse extends Message { + /** + * JWT token + * + * @generated from field: string token = 1; + */ + token = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "v1.LoginResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "token", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): LoginResponse { + return new LoginResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): LoginResponse { + return new LoginResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): LoginResponse { + return new LoginResponse().fromJsonString(jsonString, options); + } + + static equals(a: LoginResponse | PlainMessage | undefined, b: LoginResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(LoginResponse, a, b); + } +} + diff --git a/webui/gen/ts/v1/config_pb.ts b/webui/gen/ts/v1/config_pb.ts index 00f625d1b..12e3cc10a 100644 --- a/webui/gen/ts/v1/config_pb.ts +++ b/webui/gen/ts/v1/config_pb.ts @@ -37,9 +37,9 @@ export class Config extends Message { plans: Plan[] = []; /** - * @generated from field: repeated v1.User users = 5; + * @generated from field: v1.Auth auth = 5; */ - users: User[] = []; + auth?: Auth; constructor(data?: PartialMessage) { super(); @@ -53,7 +53,7 @@ export class Config extends Message { { no: 2, name: "host", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "repos", kind: "message", T: Repo, repeated: true }, { no: 4, name: "plans", kind: "message", T: Plan, repeated: true }, - { no: 5, name: "users", kind: "message", T: User, repeated: true }, + { no: 5, name: "auth", kind: "message", T: Auth }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Config { @@ -399,55 +399,6 @@ export class PrunePolicy extends Message { } } -/** - * @generated from message v1.User - */ -export class User extends Message { - /** - * @generated from field: string name = 1; - */ - name = ""; - - /** - * @generated from oneof v1.User.password - */ - password: { - /** - * @generated from field: string password_bcrypt = 2; - */ - value: string; - case: "passwordBcrypt"; - } | { case: undefined; value?: undefined } = { case: undefined }; - - constructor(data?: PartialMessage) { - super(); - proto3.util.initPartial(data, this); - } - - static readonly runtime: typeof proto3 = proto3; - static readonly typeName = "v1.User"; - static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - { no: 2, name: "password_bcrypt", kind: "scalar", T: 9 /* ScalarType.STRING */, oneof: "password" }, - ]); - - static fromBinary(bytes: Uint8Array, options?: Partial): User { - return new User().fromBinary(bytes, options); - } - - static fromJson(jsonValue: JsonValue, options?: Partial): User { - return new User().fromJson(jsonValue, options); - } - - static fromJsonString(jsonString: string, options?: Partial): User { - return new User().fromJsonString(jsonString, options); - } - - static equals(a: User | PlainMessage | undefined, b: User | PlainMessage | undefined): boolean { - return proto3.util.equals(User, a, b); - } -} - /** * @generated from message v1.Hook */ @@ -742,3 +693,91 @@ export class Hook_Gotify extends Message { } } +/** + * @generated from message v1.Auth + */ +export class Auth extends Message { + /** + * users to allow access to the UI. + * + * @generated from field: repeated v1.User users = 2; + */ + users: User[] = []; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "v1.Auth"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 2, name: "users", kind: "message", T: User, repeated: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): Auth { + return new Auth().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): Auth { + return new Auth().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): Auth { + return new Auth().fromJsonString(jsonString, options); + } + + static equals(a: Auth | PlainMessage | undefined, b: Auth | PlainMessage | undefined): boolean { + return proto3.util.equals(Auth, a, b); + } +} + +/** + * @generated from message v1.User + */ +export class User extends Message { + /** + * @generated from field: string name = 1; + */ + name = ""; + + /** + * @generated from oneof v1.User.password + */ + password: { + /** + * @generated from field: string password_bcrypt = 2; + */ + value: string; + case: "passwordBcrypt"; + } | { case: undefined; value?: undefined } = { case: undefined }; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "v1.User"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "password_bcrypt", kind: "scalar", T: 9 /* ScalarType.STRING */, oneof: "password" }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): User { + return new User().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): User { + return new User().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): User { + return new User().fromJsonString(jsonString, options); + } + + static equals(a: User | PlainMessage | undefined, b: User | PlainMessage | undefined): boolean { + return proto3.util.equals(User, a, b); + } +} + diff --git a/webui/gen/ts/v1/service_pb.ts b/webui/gen/ts/v1/service_pb.ts index 3b1a94dcb..de8a745c3 100644 --- a/webui/gen/ts/v1/service_pb.ts +++ b/webui/gen/ts/v1/service_pb.ts @@ -6,86 +6,6 @@ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; import { Message, proto3, protoInt64 } from "@bufbuild/protobuf"; -/** - * @generated from message v1.LoginRequest - */ -export class LoginRequest extends Message { - /** - * @generated from field: string username = 1; - */ - username = ""; - - /** - * @generated from field: string password = 2; - */ - password = ""; - - constructor(data?: PartialMessage) { - super(); - proto3.util.initPartial(data, this); - } - - static readonly runtime: typeof proto3 = proto3; - static readonly typeName = "v1.LoginRequest"; - static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: "username", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - { no: 2, name: "password", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - ]); - - static fromBinary(bytes: Uint8Array, options?: Partial): LoginRequest { - return new LoginRequest().fromBinary(bytes, options); - } - - static fromJson(jsonValue: JsonValue, options?: Partial): LoginRequest { - return new LoginRequest().fromJson(jsonValue, options); - } - - static fromJsonString(jsonString: string, options?: Partial): LoginRequest { - return new LoginRequest().fromJsonString(jsonString, options); - } - - static equals(a: LoginRequest | PlainMessage | undefined, b: LoginRequest | PlainMessage | undefined): boolean { - return proto3.util.equals(LoginRequest, a, b); - } -} - -/** - * @generated from message v1.LoginResponse - */ -export class LoginResponse extends Message { - /** - * @generated from field: string jwt = 1; - */ - jwt = ""; - - constructor(data?: PartialMessage) { - super(); - proto3.util.initPartial(data, this); - } - - static readonly runtime: typeof proto3 = proto3; - static readonly typeName = "v1.LoginResponse"; - static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: "jwt", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - ]); - - static fromBinary(bytes: Uint8Array, options?: Partial): LoginResponse { - return new LoginResponse().fromBinary(bytes, options); - } - - static fromJson(jsonValue: JsonValue, options?: Partial): LoginResponse { - return new LoginResponse().fromJson(jsonValue, options); - } - - static fromJsonString(jsonString: string, options?: Partial): LoginResponse { - return new LoginResponse().fromJsonString(jsonString, options); - } - - static equals(a: LoginResponse | PlainMessage | undefined, b: LoginResponse | PlainMessage | undefined): boolean { - return proto3.util.equals(LoginResponse, a, b); - } -} - /** * @generated from message v1.ClearHistoryRequest */ diff --git a/webui/package-lock.json b/webui/package-lock.json index acc12f9fc..bc8b7821f 100644 --- a/webui/package-lock.json +++ b/webui/package-lock.json @@ -25,7 +25,6 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-js-cron": "^5.0.1", - "recoil": "^0.7.7", "typescript": "^5.2.2" }, "devDependencies": { @@ -799,6 +798,69 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@parcel/optimizer-htmlnano/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/@parcel/optimizer-htmlnano/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@parcel/optimizer-htmlnano/node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@parcel/optimizer-htmlnano/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/@parcel/optimizer-htmlnano/node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@parcel/optimizer-image": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.11.0.tgz", @@ -841,6 +903,69 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@parcel/optimizer-svgo/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/@parcel/optimizer-svgo/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@parcel/optimizer-svgo/node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@parcel/optimizer-svgo/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/@parcel/optimizer-svgo/node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@parcel/optimizer-swc": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/@parcel/optimizer-swc/-/optimizer-swc-2.11.0.tgz", @@ -2568,30 +2693,93 @@ } }, "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "optional": true, + "peer": true, "dependencies": { "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", "nth-check": "^2.0.1" }, "funding": { "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-select/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "optional": true, + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/css-select/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "optional": true, + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/css-select/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "optional": true, + "peer": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/css-select/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "optional": true, + "peer": true, "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" }, "engines": { - "node": ">=8.0.0" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, "node_modules/css-what": { @@ -2606,16 +2794,41 @@ } }, "node_modules/csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "optional": true, + "peer": true, "dependencies": { - "css-tree": "^1.1.2" + "css-tree": "~2.2.0" }, "engines": { - "node": ">=8.0.0" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "optional": true, + "peer": true, + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "optional": true, + "peer": true + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -2822,11 +3035,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hamt_plus": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", - "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3301,9 +3509,11 @@ } }, "node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "optional": true, + "peer": true }, "node_modules/micromatch": { "version": "4.0.5", @@ -4206,25 +4416,6 @@ "node": ">=8.10.0" } }, - "node_modules/recoil": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", - "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", - "dependencies": { - "hamt_plus": "1.0.2" - }, - "peerDependencies": { - "react": ">=16.13.1" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -4321,7 +4512,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -4408,23 +4599,29 @@ } }, "node_modules/svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", + "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", + "optional": true, + "peer": true, "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" }, "bin": { "svgo": "bin/svgo" }, "engines": { - "node": ">=10.13.0" + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" } }, "node_modules/term-size": { diff --git a/webui/package.json b/webui/package.json index 63c23010a..2f6cedb84 100644 --- a/webui/package.json +++ b/webui/package.json @@ -28,7 +28,6 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-js-cron": "^5.0.1", - "recoil": "^0.7.7", "typescript": "^5.2.2" }, "devDependencies": { diff --git a/webui/src/api.ts b/webui/src/api.ts index ce6bb909d..0e7409f2a 100644 --- a/webui/src/api.ts +++ b/webui/src/api.ts @@ -1,11 +1,36 @@ import { useMemo } from "react"; import { createConnectTransport } from "@connectrpc/connect-web"; -import { createPromiseClient, PromiseClient } from "@connectrpc/connect"; +import { createPromiseClient } from "@connectrpc/connect"; import { Backrest } from "../gen/ts/v1/service_connect"; +import { Authentication } from "../gen/ts/v1/authentication_connect"; + +const tokenKey = "backrest-ui-authToken"; + +export const setAuthToken = (token: string) => { + localStorage.setItem(tokenKey, token); +}; + +const fetch = ( + input: RequestInfo | URL, + init?: RequestInit +): Promise => { + const headers = new Headers(init?.headers); + let token = localStorage.getItem(tokenKey); + if (token && token !== "") { + headers.set("Authorization", "Bearer " + token); + } + init = { ...init, headers }; + return window.fetch(input, init); +}; const transport = createConnectTransport({ baseUrl: "/", useBinaryFormat: true, + fetch: fetch as typeof globalThis.fetch, }); +export const authenticationService = createPromiseClient( + Authentication, + transport +); export const backrestService = createPromiseClient(Backrest, transport); diff --git a/webui/src/components/ConfigProvider.tsx b/webui/src/components/ConfigProvider.tsx new file mode 100644 index 000000000..3e48df065 --- /dev/null +++ b/webui/src/components/ConfigProvider.tsx @@ -0,0 +1,27 @@ +import React, { useContext, useEffect, useState } from "react"; +import { Config, Repo } from "../../gen/ts/v1/config_pb"; + +type ConfigCtx = [Config | null, (config: Config) => void]; + +const ConfigContext = React.createContext([null, () => { }]); + +export const ConfigContextProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [config, setConfig] = useState(null); + return ( + <> + + {children} + + + ); +}; + +export const useConfig = (): ConfigCtx => { + const context = useContext(ConfigContext); + return context; +}; + diff --git a/webui/src/components/HooksFormList.tsx b/webui/src/components/HooksFormList.tsx index bfb582ed5..d002c8af8 100644 --- a/webui/src/components/HooksFormList.tsx +++ b/webui/src/components/HooksFormList.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { Hook, Hook_Command, Hook_Condition, Hook_Discord, Hook_Gotify, Hook_Webhook } from '../../gen/ts/v1/config_pb'; import { Button, Card, Collapse, CollapseProps, Form, FormListFieldData, Input, Popover, Radio, Row, Select, Tooltip } from 'antd'; import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; +import { Rule } from 'antd/es/form'; export const hooksListTooltipText = <> Hooks are actions that can execute on backup lifecycle events. @@ -141,40 +142,42 @@ const HookBuilder = ({ field, hook }: { field: FormListFieldData, hook: Hook }) switch (hook.action.case) { case "actionDiscord": return <> - + Discord Webhook} /> - + Text Template: - + - + case "actionCommand": return <> Script: - + case "actionGotify": return <> - + Gotify Base URL} /> - - + + Gotify Token} /> - + Title Template} /> Text Template: - + default: return

Unknown hook {hook.action.case}

} -} \ No newline at end of file +} + +const requiredField = (message: string, extra?: Rule) => ({ required: true, message: message }); \ No newline at end of file diff --git a/webui/src/components/OperationList.tsx b/webui/src/components/OperationList.tsx index d8b911421..8e3cea14c 100644 --- a/webui/src/components/OperationList.tsx +++ b/webui/src/components/OperationList.tsx @@ -23,6 +23,7 @@ import { SaveOutlined, DeleteOutlined, DownloadOutlined, + RobotOutlined, } from "@ant-design/icons"; import { BackupProgressEntry, ResticSnapshot } from "../../gen/ts/v1/restic_pb"; import { @@ -110,9 +111,6 @@ export const OperationList = ({ }, [JSON.stringify(req)]); } else { backups = [...(useBackups || [])]; - backups.sort((a, b) => { - return b.startTimeMs - a.startTimeMs; - }); } if (backups.length === 0) { @@ -124,28 +122,22 @@ export const OperationList = ({ ); } + const operations = backups.flatMap((b) => b.operations); + operations.sort((a, b) => { + return Number(b.unixTimeStartMs - a.unixTimeStartMs) + }); + return ( { - const ops = [...backup.operations]; - ops.reverse(); - return ( - - {ops.map((op) => { - if (shouldHideOperation(op)) { - return null; - } - return - })} - - ); + dataSource={operations} + renderItem={(op) => { + return }} pagination={ - backups.length > 50 - ? { position: "both", align: "center", defaultPageSize: 50 } + operations.length > 25 + ? { position: "both", align: "center", defaultPageSize: 25 } : undefined } /> @@ -186,6 +178,9 @@ export const OperationRow = ({ case DisplayType.PRUNE: avatar = ; break; + case DisplayType.RUNHOOK: + avatar = ; + } const opName = displayTypeToString(getTypeForDisplay(operation)); @@ -483,7 +478,6 @@ const ForgetOperationDetails = ({ forgetOp }: { forgetOp: OperationForget }) => } const RunHookOperationStatus = ({ op }: { op: Operation }) => { - const [output, setOutput] = useState(undefined); if (op.op.case !== "operationRunHook") { return <>Wrong operation type; @@ -491,25 +485,36 @@ const RunHookOperationStatus = ({ op }: { op: Operation }) => { const hook = op.op.value; + return <> + + + + }, + ]} /> + +} + +// TODO: refactor this to use the provider pattern +const BigOperationDataVerbatim = ({ id, outputRef }: { id: bigint, outputRef: string }) => { + const [output, setOutput] = useState(undefined); + useEffect(() => { - if (!hook.outputRef) { + if (!outputRef) { return; } backrestService.getBigOperationData(new OperationDataRequest({ - id: op.id, - key: hook.outputRef, + id: id, + key: outputRef, })).then((resp) => { setOutput(new TextDecoder("utf-8").decode(resp.value)); }).catch((e) => { console.error("Failed to fetch hook output: ", e); }); - }, [hook.outputRef]); + }, [id, outputRef]); - return <> - Hook: {hook.name}
- Output:
-
-      {output}
-    
- + return
{output}
; } \ No newline at end of file diff --git a/webui/src/components/SpinButton.tsx b/webui/src/components/SpinButton.tsx index 0d4ae98eb..6ef5d7b4a 100644 --- a/webui/src/components/SpinButton.tsx +++ b/webui/src/components/SpinButton.tsx @@ -27,3 +27,36 @@ export const SpinButton: React.FC ); } + +export const ConfirmButton: React.FC Promise; + confirmTitle: React.ReactNode; + confirmTimeout?: number; // milliseconds +}> = ({ onClickAsync, confirmTimeout, confirmTitle, children, ...props }) => { + const [clicked, setClicked] = useState(false); + + if (confirmTimeout === undefined) { + confirmTimeout = 2000; + } + + const onClick = async () => { + if (!clicked) { + setClicked(true); + setTimeout(() => { + setClicked(false); + }, confirmTimeout); + } + + setClicked(false); + await onClickAsync(); + }; + + return ( + + {clicked ? confirmTitle : children} + + ); +} diff --git a/webui/src/index.tsx b/webui/src/index.tsx index 82ec519f0..27457f7c1 100644 --- a/webui/src/index.tsx +++ b/webui/src/index.tsx @@ -1,20 +1,23 @@ import * as React from "react"; import { createRoot } from "react-dom/client"; import { App } from "./views/App"; -import { RecoilRoot } from "recoil"; import { AlertContextProvider } from "./components/Alerts"; import { ModalContextProvider } from "./components/ModalManager"; import "react-js-cron/dist/styles.css"; -import { ConfigProvider, theme } from "antd"; +import { ConfigProvider as AntdConfigProvider, theme } from "antd"; +import { ConfigContextProvider } from "./components/ConfigProvider"; +import { MainContentProvider } from "./views/MainContentArea"; const Root = ({ children }: { children: React.ReactNode }) => { return ( - + - {children} + + {children} + - + ); }; @@ -23,7 +26,7 @@ const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)"); const el = document.querySelector("#app"); el && createRoot(el).render( - - + ); diff --git a/webui/src/lib/formutil.ts b/webui/src/lib/formutil.ts index 708fa8dae..2cfaddce2 100644 --- a/webui/src/lib/formutil.ts +++ b/webui/src/lib/formutil.ts @@ -13,3 +13,6 @@ export const validateForm = async (form: FormInstance) => { throw e; } }; + +// regex allows alphanumeric, underscore, dash, and dot +export const namePattern = /^[a-zA-Z0-9_\-\.]+$/; diff --git a/webui/src/state/config.ts b/webui/src/state/config.ts deleted file mode 100644 index a7d423e2c..000000000 --- a/webui/src/state/config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { atom, useSetRecoilState } from "recoil"; -import { Config, Repo } from "../../gen/ts/v1/config_pb"; -import { backrestService } from "../api"; - -export const configState = atom({ - key: "config", - default: new Config(), -}); - -export const fetchConfig = async (): Promise => { - return await backrestService.getConfig({}); -}; - -export const addRepo = async (repo: Repo): Promise => { - return await backrestService.addRepo(repo); -}; - -export const updateConfig = async (config: Config): Promise => { - return await backrestService.setConfig(config); -}; diff --git a/webui/src/views/AddPlanModal.tsx b/webui/src/views/AddPlanModal.tsx index 01d395d4e..348a45ebc 100644 --- a/webui/src/views/AddPlanModal.tsx +++ b/webui/src/views/AddPlanModal.tsx @@ -11,45 +11,40 @@ import { Row, Card, Col, + Collapse, } from "antd"; import React, { useState } from "react"; import { useShowModal } from "../components/ModalManager"; import { Plan, RetentionPolicy } from "../../gen/ts/v1/config_pb"; -import { useRecoilState } from "recoil"; -import { configState, fetchConfig, updateConfig } from "../state/config"; import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"; import { URIAutocomplete } from "../components/URIAutocomplete"; import { useAlertApi } from "../components/Alerts"; import { Cron } from "react-js-cron"; -import { validateForm } from "../lib/formutil"; +import { namePattern, validateForm } from "../lib/formutil"; import { HooksFormList, hooksListTooltipText } from "../components/HooksFormList"; +import { ConfirmButton, SpinButton } from "../components/SpinButton"; +import { useConfig } from "../components/ConfigProvider"; +import { backrestService } from "../api"; export const AddPlanModal = ({ template, }: { template: Partial | null; }) => { - const [config, setConfig] = useRecoilState(configState); - const [deleteConfirmed, setDeleteConfirmed] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false); const showModal = useShowModal(); const alertsApi = useAlertApi()!; + const [config, setConfig] = useConfig(); const [form] = Form.useForm(); - const handleDestroy = async () => { - if (!deleteConfirmed) { - setDeleteConfirmed(true); - setTimeout(() => { - setDeleteConfirmed(false); - }, 2000); - return; - } + if (!config) { + return null; + } + const handleDestroy = async () => { setConfirmLoading(true); try { - let config = await fetchConfig(); - if (!template) { throw new Error("template not found"); } @@ -63,7 +58,7 @@ export const AddPlanModal = ({ config.plans.splice(idx, 1); // Update config and notify success. - setConfig(await updateConfig(config)); + setConfig(await backrestService.setConfig(config)); showModal(null); alertsApi.success( @@ -83,8 +78,6 @@ export const AddPlanModal = ({ try { let plan = new Plan(await validateForm(form)); - let config = await fetchConfig(); - // Merge the new plan (or update) into the config if (template) { const idx = config.plans.findIndex((r) => r.id === template.id); @@ -97,7 +90,7 @@ export const AddPlanModal = ({ } // Update config and notify success. - setConfig(await updateConfig(config)); + setConfig(await backrestService.setConfig(config)); showModal(null); } catch (e: any) { alertsApi.error("Operation failed: " + e.message, 15); @@ -125,24 +118,23 @@ export const AddPlanModal = ({ Cancel , template != null ? ( - + Delete + ) : null, - , + , ]} >
{/* Plan.id */} hasFeedback name="id" label="Plan Name" - initialValue={template && template.id} + initialValue={template ? template.id : ""} validateTrigger={["onChange", "onBlur"]} rules={[ { @@ -172,6 +165,10 @@ export const AddPlanModal = ({ }, message: "Plan with name already exists", }, + { + pattern: namePattern, + message: "Name must be alphanumeric with dashes or underscores as separators", + } ]} > {() => ( - -
{JSON.stringify(form.getFieldsValue(), null, 2)}
-
+ +
{new Plan(form.getFieldsValue()).toJsonString({ prettySpaces: 2 })}
+ + ), + }, + ]} + /> )} diff --git a/webui/src/views/AddRepoModal.tsx b/webui/src/views/AddRepoModal.tsx index 555078e5d..e832bef1a 100644 --- a/webui/src/views/AddRepoModal.tsx +++ b/webui/src/views/AddRepoModal.tsx @@ -11,6 +11,7 @@ import { Card, InputNumber, FormInstance, + Collapse, } from "antd"; import React, { useState } from "react"; import { useShowModal } from "../components/ModalManager"; @@ -18,46 +19,34 @@ 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 { - addRepo, - configState, - fetchConfig, - updateConfig, -} from "../state/config"; -import { useRecoilState } from "recoil"; -import { validateForm } from "../lib/formutil"; +import { namePattern, validateForm } from "../lib/formutil"; import { backrestService } from "../api"; import { HooksFormList, hooksListTooltipText, } from "../components/HooksFormList"; -import { isDevBuild } from "../state/buildcfg"; +import { ConfirmButton } from "../components/SpinButton"; +import { useConfig } from "../components/ConfigProvider"; export const AddRepoModal = ({ template, }: { template: Partial | null; }) => { - const [config, setConfig] = useRecoilState(configState); - const [deleteConfirmed, setDeleteConfirmed] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false); const showModal = useShowModal(); const alertsApi = useAlertApi()!; + const [config, setConfig] = useConfig(); const [form] = Form.useForm(); - const handleDestroy = async () => { - if (!deleteConfirmed) { - setDeleteConfirmed(true); - setTimeout(() => { - setDeleteConfirmed(false); - }, 2000); - return; - } + if (!config) { + return null; + } + const handleDestroy = async () => { setConfirmLoading(true); try { - let config = await fetchConfig(); config.repos = config.repos || []; if (!template) { @@ -80,7 +69,7 @@ export const AddRepoModal = ({ config.repos.splice(idx, 1); // Update config and notify success. - setConfig(await updateConfig(config)); + setConfig(await backrestService.setConfig(config)); showModal(null); alertsApi.success( "Deleted repo " + @@ -91,7 +80,6 @@ export const AddRepoModal = ({ } catch (e: any) { alertsApi.error("Operation failed: " + e.message, 15); } finally { - setDeleteConfirmed(false); setConfirmLoading(false); } }; @@ -104,14 +92,13 @@ export const AddRepoModal = ({ if (template !== null) { // We are in the edit repo flow, update the repo in the config - let config = await fetchConfig(); const idx = config.repos!.findIndex((r) => r.id === template!.id); if (idx === -1) { alertsApi.error("Can't update repo, not found"); return; } config.repos![idx] = new Repo(repo); - setConfig(await updateConfig(config)); + setConfig(await backrestService.setConfig(config)); showModal(null); alertsApi.success("Updated repo " + repo.uri); @@ -120,7 +107,7 @@ export const AddRepoModal = ({ await backrestService.listSnapshots({ repoId: repo.id }); } else { // We are in the create repo flow, create the new repo via the service - setConfig(await addRepo(repo)); + setConfig(await backrestService.addRepo(repo)); showModal(null); alertsApi.success("Added repo " + repo.uri); } @@ -147,15 +134,15 @@ export const AddRepoModal = ({ Cancel , template != null ? ( - + Delete + ) : null, @@ -207,6 +253,15 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => { label: "Repositories", children: repos, }, + { + key: "settings", + icon: React.createElement(SettingOutlined), + label: "Settings", + onClick: async () => { + const { SettingsModal } = await import("./SettingsModal"); + showModal(); + } + }, ]; }; diff --git a/webui/src/views/GettingStartedGuide.tsx b/webui/src/views/GettingStartedGuide.tsx index 28513e742..55f760e63 100644 --- a/webui/src/views/GettingStartedGuide.tsx +++ b/webui/src/views/GettingStartedGuide.tsx @@ -1,10 +1,11 @@ -import { Collapse, Divider, Typography } from "antd"; -import React from "react"; -import { useRecoilValue } from "recoil"; -import { configState } from "../state/config"; +import { Collapse, Divider, Spin, Typography } from "antd"; +import React, { useEffect, useState } from "react"; +import { backrestService } from "../api"; +import { useConfig } from "../components/ConfigProvider"; +import { Config } from "../../gen/ts/v1/config_pb"; export const GettingStartedGuide = () => { - const config = useRecoilValue(configState); + const config = useConfig()[0]; return ( <> @@ -46,15 +47,15 @@ export const GettingStartedGuide = () => { { key: "1", label: "Config JSON hidden for security", - children: ( - -
{JSON.stringify(config, null, 2)}
-
- ), + children: config ? +
{config.toJsonString({
+                  prettySpaces: 2,
+                })}
+
: , }, ]} /> ); -}; +}; \ No newline at end of file diff --git a/webui/src/views/LoginModal.tsx b/webui/src/views/LoginModal.tsx new file mode 100644 index 000000000..8a544f9b6 --- /dev/null +++ b/webui/src/views/LoginModal.tsx @@ -0,0 +1,91 @@ +import { LockOutlined, UserOutlined } from '@ant-design/icons'; +import { Button, Col, Form, Input, Modal, Row } from 'antd'; +import React, { useEffect, useState } from 'react'; +import { authenticationService, setAuthToken } from '../api'; +import { LoginRequest } from '../../gen/ts/v1/authentication_pb'; +import { useAlertApi } from '../components/Alerts'; + +export const LoginModal = () => { + let defaultCreds = new LoginRequest(); + + const [form] = Form.useForm(); + const alertApi = useAlertApi()!; + + useEffect(() => { + authenticationService.login(new LoginRequest({ + username: "default", + password: "password", + })).then((loginResponse) => { + alertApi.success("No users configured yet, logged in with default credentials", 5); + setAuthToken(loginResponse.token); + setTimeout(() => { + window.location.reload(); + }, 500); + }).catch((e => { })); + }) + + const onFinish = async (values: any) => { + const loginReq = new LoginRequest({ + username: values.username, + password: values.password, + }); + + try { + const loginResponse = await authenticationService.login(loginReq); + setAuthToken(loginResponse.token); + alertApi.success("Logged in", 5); + setTimeout(() => { + window.location.reload(); + }, 500); + } catch (e: any) { + alertApi.error("Login failed: " + (e.message ? e.message : '' + e), 10); + } + }; + + return +
+ + + + } placeholder="Username" /> + + + + + + } + type="password" + placeholder="Password" + /> + + + + + + +
+
+} \ No newline at end of file diff --git a/webui/src/views/MainContentArea.tsx b/webui/src/views/MainContentArea.tsx index 5cb606df5..206c0df74 100644 --- a/webui/src/views/MainContentArea.tsx +++ b/webui/src/views/MainContentArea.tsx @@ -1,50 +1,59 @@ import { Breadcrumb, Layout, Spin, theme } from "antd"; import { Content } from "antd/es/layout/layout"; -import React from "react"; -import { atom, useRecoilValue, useSetRecoilState } from "recoil"; -import { GettingStartedGuide } from "./GettingStartedGuide"; +import React, { useState } from "react"; interface Breadcrumb { title: string; onClick?: () => void; } -const contentPanel = atom({ - key: "ui.content", - default: null, -}); +interface ContentAreaState { + content: React.ReactNode | null; + breadcrumbs: Breadcrumb[]; +} -const breadcrumbs = atom({ - key: "ui.breadcrumbs", - default: [], -}); +type ContentAreaCtx = [ContentAreaState, (content: React.ReactNode, breadcrumbs: Breadcrumb[]) => void]; -export const useSetContent = () => { - const setContent = useSetRecoilState(contentPanel); - const setBreadcrumbs = useSetRecoilState(breadcrumbs); +const ContentAreaContext = React.createContext([{ + content: null, + breadcrumbs: [], +}, (content, breadcrumbs) => { }]); + +export const MainContentProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [state, setState] = useState({ + content: null, + breadcrumbs: [], + }); - return (content: React.ReactNode | null, breadcrumbs: Breadcrumb[]) => { - setContent(content); - setBreadcrumbs(breadcrumbs); - }; + return ( + <> + { + setState({ content, breadcrumbs }); + }]}> + {children} + + + ); }; +export const useSetContent = () => { + const context = React.useContext(ContentAreaContext); + return context[1]; +} + export const MainContentArea = () => { + const { breadcrumbs, content } = React.useContext(ContentAreaContext)[0]; const { token: { colorBgContainer }, } = theme.useToken(); - let content = useRecoilValue(contentPanel); - let crumbs = useRecoilValue(breadcrumbs); - - if (!content) { - content = ; - crumbs = [{ title: "Getting started" }]; - } - return ( - + { ); -}; +} diff --git a/webui/src/views/PlanView.tsx b/webui/src/views/PlanView.tsx index eccd96c78..12a59e3d0 100644 --- a/webui/src/views/PlanView.tsx +++ b/webui/src/views/PlanView.tsx @@ -1,8 +1,6 @@ import React, { useEffect, useState } from "react"; import { Plan } from "../../gen/ts/v1/config_pb"; -import { Button, Flex, Tabs, Tooltip, Typography } from "antd"; -import { useRecoilValue } from "recoil"; -import { configState } from "../state/config"; +import { Flex, Tabs, Tooltip, Typography } from "antd"; import { useAlertApi } from "../components/Alerts"; import { OperationList } from "../components/OperationList"; import { OperationTree } from "../components/OperationTree"; @@ -14,14 +12,6 @@ import { SpinButton } from "../components/SpinButton"; export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => { const alertsApi = useAlertApi()!; - // 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

Plan was deleted.

; - } - plan = planInConfig; - const handleBackupNow = async () => { try { await backrestService.backup({ value: plan.id }); diff --git a/webui/src/views/RepoView.tsx b/webui/src/views/RepoView.tsx index eb5123f0e..98ef646da 100644 --- a/webui/src/views/RepoView.tsx +++ b/webui/src/views/RepoView.tsx @@ -1,9 +1,6 @@ -import React, { useEffect, useState } from "react"; +import React, { useContext, useEffect, useState } from "react"; import { Repo } from "../../gen/ts/v1/config_pb"; import { Col, Empty, Flex, Row, Spin, TabsProps, Tabs, Tooltip, Typography } from "antd"; -import { useRecoilValue } from "recoil"; -import { configState } from "../state/config"; -import { useAlertApi } from "../components/Alerts"; import { OperationList } from "../components/OperationList"; import { OperationTree } from "../components/OperationTree"; import { MAX_OPERATION_HISTORY, STATS_OPERATION_HISTORY } from "../constants"; @@ -15,11 +12,13 @@ import { Operation } from "../../gen/ts/v1/operations_pb"; import { backrestService } from "../api"; import { StringValue } from "@bufbuild/protobuf"; import { SpinButton } from "../components/SpinButton"; +import { ConfigContext } from "antd/es/config-provider"; +import { useConfig } from "../components/ConfigProvider"; export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => { - const alertsApi = useAlertApi()!; const [loading, setLoading] = useState(true); const [statsOperation, setStatsOperation] = useState(null); + const [config, setConfig] = useConfig(); useEffect(() => { setLoading(true); @@ -46,10 +45,14 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => { } // Gracefully handle deletions by checking if the plan is still in the config. - const config = useRecoilValue(configState); - let repoInConfig = config.repos?.find((p) => p.id === repo.id); + let repoInConfig = config?.repos?.find((r) => r.id === repo.id); if (!repoInConfig) { - return

Repo was deleted.

; + return <> + Repo was deleted +
+        {JSON.stringify(config, null, 2)}
+      
+ } repo = repoInConfig; diff --git a/webui/src/views/SettingsModal.tsx b/webui/src/views/SettingsModal.tsx new file mode 100644 index 000000000..a9688cab7 --- /dev/null +++ b/webui/src/views/SettingsModal.tsx @@ -0,0 +1,191 @@ +import { + Form, + Modal, + Input, + Typography, + Select, + Button, + Tooltip, + Radio, + InputNumber, + Row, + Card, + Col, + Collapse, +} from "antd"; +import React, { useEffect, useState } from "react"; +import { useShowModal } from "../components/ModalManager"; +import { Auth, Config, User } from "../../gen/ts/v1/config_pb"; +import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"; +import { useAlertApi } from "../components/Alerts"; +import { namePattern, validateForm } from "../lib/formutil"; +import { useConfig } from "../components/ConfigProvider"; +import { authenticationService, backrestService } from "../api"; + +interface FormData { + auth: { + users: ({ + name: string; + passwordBcrypt: string; + needsBcrypt?: boolean; + })[]; + } +} + +export const SettingsModal = () => { + let [config, setConfig] = useConfig(); + const configObj = JSON.parse(config!.toJsonString()); + const showModal = useShowModal(); + const alertsApi = useAlertApi()!; + const [form] = Form.useForm(); + + if (!config) { + return null; + } + + const handleOk = async () => { + try { + // Validate form + let formData = await validateForm(form); + + for (const user of formData.auth?.users) { + if (user.needsBcrypt) { + const hash = await authenticationService.hashPassword({ value: user.passwordBcrypt }); + user.passwordBcrypt = hash.value; + delete user.needsBcrypt; + } + } + + // Update configuration + let newConfig = config!.clone(); + newConfig.auth = new Auth().fromJson(formData.auth, { ignoreUnknownFields: false }); + + setConfig(await backrestService.setConfig(newConfig)); + alertsApi.success("Settings updated", 5); + setTimeout(() => { + window.location.reload(); + }, 500); + } catch (e: any) { + alertsApi.error("Operation failed: " + e.message, 15); + console.error(e); + } + }; + + const handleCancel = () => { + showModal(null); + }; + + return ( + <> + + Cancel + , + , + ]} + > +
+ + { + if (!users || users.length < 1) { + return Promise.reject(new Error("At least one user is required")); + } + }, + }, + ]} + initialValue={configObj.auth?.users || []} + > + {(fields, { add, remove }) => ( + <> + {fields.map((field, index) => { + + return ( + + + + + + + + + { + form.setFieldValue(["auth", "users", index, "needsBcrypt"], true); + form.setFieldValue(["auth", "users", index, "passwordBcrypt"], ""); + }} /> + + + + { + remove(field.name); + }} + /> + + + ) + })} + + + + + )} + + + + + {() => ( + +
{JSON.stringify(form.getFieldsValue(), null, 2)}
+ + ), + }, + ]} + /> + )} +
+
+
+ + ); +};