diff --git a/gen/go/v1/config.pb.go b/gen/go/v1/config.pb.go index 773c4a80..c828dab8 100644 --- a/gen/go/v1/config.pb.go +++ b/gen/go/v1/config.pb.go @@ -600,6 +600,7 @@ type Hook struct { // *Hook_ActionCommand // *Hook_ActionWebhook // *Hook_ActionDiscord + // *Hook_ActionGotify Action isHook_Action `protobuf_oneof:"action"` } @@ -670,6 +671,13 @@ func (x *Hook) GetActionDiscord() *Hook_Discord { return nil } +func (x *Hook) GetActionGotify() *Hook_Gotify { + if x, ok := x.GetAction().(*Hook_ActionGotify); ok { + return x.ActionGotify + } + return nil +} + type isHook_Action interface { isHook_Action() } @@ -686,12 +694,18 @@ type Hook_ActionDiscord struct { ActionDiscord *Hook_Discord `protobuf:"bytes,102,opt,name=action_discord,json=actionDiscord,proto3,oneof"` } +type Hook_ActionGotify struct { + ActionGotify *Hook_Gotify `protobuf:"bytes,103,opt,name=action_gotify,json=actionGotify,proto3,oneof"` +} + func (*Hook_ActionCommand) isHook_Action() {} func (*Hook_ActionWebhook) isHook_Action() {} func (*Hook_ActionDiscord) isHook_Action() {} +func (*Hook_ActionGotify) isHook_Action() {} + type Hook_Command struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -841,6 +855,77 @@ func (x *Hook_Discord) GetTemplate() string { return "" } +type Hook_Gotify struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BaseUrl string `protobuf:"bytes,1,opt,name=base_url,json=baseUrl,proto3" json:"base_url,omitempty"` + Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty"` + Template string `protobuf:"bytes,100,opt,name=template,proto3" json:"template,omitempty"` // template for the webhook payload. + TitleTemplate string `protobuf:"bytes,101,opt,name=title_template,json=titleTemplate,proto3" json:"title_template,omitempty"` // template for the webhook title. +} + +func (x *Hook_Gotify) Reset() { + *x = Hook_Gotify{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_config_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Hook_Gotify) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Hook_Gotify) ProtoMessage() {} + +func (x *Hook_Gotify) ProtoReflect() protoreflect.Message { + mi := &file_v1_config_proto_msgTypes[10] + 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 Hook_Gotify.ProtoReflect.Descriptor instead. +func (*Hook_Gotify) Descriptor() ([]byte, []int) { + return file_v1_config_proto_rawDescGZIP(), []int{6, 3} +} + +func (x *Hook_Gotify) GetBaseUrl() string { + if x != nil { + return x.BaseUrl + } + return "" +} + +func (x *Hook_Gotify) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *Hook_Gotify) GetTemplate() string { + if x != nil { + return x.Template + } + return "" +} + +func (x *Hook_Gotify) GetTitleTemplate() string { + if x != nil { + return x.TitleTemplate + } + return "" +} + var File_v1_config_proto protoreflect.FileDescriptor var file_v1_config_proto_rawDesc = []byte{ @@ -913,7 +998,7 @@ var file_v1_config_proto_rawDesc = []byte{ 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, 0xa4, 0x04, 0x0a, 0x04, 0x48, 0x6f, 0x6f, 0x6b, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x6f, + 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, @@ -927,30 +1012,42 @@ var file_v1_config_proto_rawDesc = []byte{ 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, 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, 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, + 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, } var ( @@ -966,7 +1063,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, 10) +var file_v1_config_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_v1_config_proto_goTypes = []interface{}{ (Hook_Condition)(0), // 0: v1.Hook.Condition (*Config)(nil), // 1: v1.Config @@ -979,6 +1076,7 @@ var file_v1_config_proto_goTypes = []interface{}{ (*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 } var file_v1_config_proto_depIdxs = []int32{ 2, // 0: v1.Config.repos:type_name -> v1.Repo @@ -992,11 +1090,12 @@ var file_v1_config_proto_depIdxs = []int32{ 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:11] is the sub-list for method output_type - 11, // [11:11] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 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 } func init() { file_v1_config_proto_init() } @@ -1125,6 +1224,18 @@ func file_v1_config_proto_init() { return nil } } + file_v1_config_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Hook_Gotify); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_v1_config_proto_msgTypes[5].OneofWrappers = []interface{}{ (*User_PasswordBcrypt)(nil), @@ -1133,6 +1244,7 @@ func file_v1_config_proto_init() { (*Hook_ActionCommand)(nil), (*Hook_ActionWebhook)(nil), (*Hook_ActionDiscord)(nil), + (*Hook_ActionGotify)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -1140,7 +1252,7 @@ func file_v1_config_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_config_proto_rawDesc, NumEnums: 1, - NumMessages: 10, + NumMessages: 11, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/hook/gotifyhook.go b/internal/hook/gotifyhook.go new file mode 100644 index 00000000..d962c146 --- /dev/null +++ b/internal/hook/gotifyhook.go @@ -0,0 +1,60 @@ +package hook + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/url" + "strings" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" +) + +func (h *Hook) doGotify(cmd *v1.Hook_ActionGotify, vars HookVars, output io.Writer) error { + payload, err := h.renderTemplateOrDefault(cmd.ActionGotify.GetTemplate(), defaultTemplate, vars) + if err != nil { + return fmt.Errorf("template rendering: %w", err) + } + + title, err := h.renderTemplateOrDefault(cmd.ActionGotify.GetTitleTemplate(), "Backrest Event", vars) + if err != nil { + return fmt.Errorf("title template rendering: %w", err) + } + + message := struct { + Message string `json:"message"` + Title string `json:"title"` + Priority int `json:"priority"` + }{ + Title: title, + Priority: 5, + Message: payload, + } + + b, err := json.Marshal(message) + if err != nil { + return fmt.Errorf("json marshal: %w", err) + } + + baseUrl := strings.Trim(cmd.ActionGotify.GetBaseUrl(), "/") + + postUrl := fmt.Sprintf( + "%s/message?token=%s", + baseUrl, + url.QueryEscape(cmd.ActionGotify.GetToken())) + + output.Write([]byte(fmt.Sprintf("Sending gotify message to %s\n", postUrl))) + + body, err := post(postUrl, "application/json", bytes.NewReader(b)) + + if err != nil { + return fmt.Errorf("send gotify message: %w", err) + } + + if body != "" { + output.Write([]byte(body)) + } + + return nil +} diff --git a/internal/hook/hook.go b/internal/hook/hook.go index 95b664eb..9de6b7a5 100644 --- a/internal/hook/hook.go +++ b/internal/hook/hook.go @@ -17,7 +17,7 @@ import ( ) var ( - defaultTemplate = `{{.Summary}}` + defaultTemplate = `{{ .Summary }}` ) // ExecuteHooks schedules tasks for the hooks subscribed to the given event. The vars map is used to substitute variables @@ -138,6 +138,8 @@ func (h *Hook) Do(event v1.Hook_Condition, vars HookVars, output io.Writer) erro return h.doCommand(action, vars, output) case *v1.Hook_ActionDiscord: return h.doDiscord(action, vars, output) + case *v1.Hook_ActionGotify: + return h.doGotify(action, vars, output) default: return fmt.Errorf("unknown hook action: %v", action) } diff --git a/internal/hook/httputil.go b/internal/hook/httputil.go index 72296b58..0ee68850 100644 --- a/internal/hook/httputil.go +++ b/internal/hook/httputil.go @@ -9,7 +9,7 @@ import ( func post(url string, contentType string, body io.Reader) (string, error) { r, err := http.Post(url, contentType, body) if err != nil { - return "", fmt.Errorf("send request: %w", url, err) + return "", fmt.Errorf("send request %v: %w", url, err) } if r.StatusCode == 204 { return "", nil diff --git a/internal/orchestrator/taskcollectgarbage.go b/internal/orchestrator/taskcollectgarbage.go index 7741af59..792a0b7c 100644 --- a/internal/orchestrator/taskcollectgarbage.go +++ b/internal/orchestrator/taskcollectgarbage.go @@ -17,7 +17,7 @@ const ( // - it has no snapshot associated with it // - it has a forgotten snapshot associated with it gcHistoryAge = 30 * 24 * time.Hour - gcHistoryMaxCount = 200 + gcHistoryMaxCount = 1000 ) type CollectGarbageTask struct { diff --git a/proto/v1/config.proto b/proto/v1/config.proto index 76c12274..5a8353c5 100644 --- a/proto/v1/config.proto +++ b/proto/v1/config.proto @@ -78,6 +78,7 @@ message Hook { Command action_command = 100 [json_name="actionCommand"]; Webhook action_webhook = 101 [json_name="actionWebhook"]; Discord action_discord = 102 [json_name="actionDiscord"]; + Gotify action_gotify = 103 [json_name="actionGotify"]; } message Command { @@ -90,7 +91,14 @@ message Hook { message Discord { string webhook_url = 1 [json_name="webhookUrl"]; - string template = 2; // template for the webhook payload. + string template = 2 [json_name="template"]; // template for the webhook payload. + } + + message Gotify { + string base_url = 1 [json_name="baseUrl"]; + string token = 3 [json_name="token"]; + string template = 100 [json_name="template"]; // template for the webhook payload. + string title_template = 101 [json_name="titleTemplate"]; // template for the webhook title. } } diff --git a/webui/gen/ts/v1/config_pb.ts b/webui/gen/ts/v1/config_pb.ts index 1f4ba83b..00f625d1 100644 --- a/webui/gen/ts/v1/config_pb.ts +++ b/webui/gen/ts/v1/config_pb.ts @@ -478,6 +478,12 @@ export class Hook extends Message { */ value: Hook_Discord; case: "actionDiscord"; + } | { + /** + * @generated from field: v1.Hook.Gotify action_gotify = 103; + */ + value: Hook_Gotify; + case: "actionGotify"; } | { case: undefined; value?: undefined } = { case: undefined }; constructor(data?: PartialMessage) { @@ -492,6 +498,7 @@ export class Hook extends Message { { no: 100, name: "action_command", kind: "message", T: Hook_Command, oneof: "action" }, { no: 101, name: "action_webhook", kind: "message", T: Hook_Webhook, oneof: "action" }, { no: 102, name: "action_discord", kind: "message", T: Hook_Discord, oneof: "action" }, + { no: 103, name: "action_gotify", kind: "message", T: Hook_Gotify, oneof: "action" }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Hook { @@ -676,3 +683,62 @@ export class Hook_Discord extends Message { } } +/** + * @generated from message v1.Hook.Gotify + */ +export class Hook_Gotify extends Message { + /** + * @generated from field: string base_url = 1; + */ + baseUrl = ""; + + /** + * @generated from field: string token = 3; + */ + token = ""; + + /** + * template for the webhook payload. + * + * @generated from field: string template = 100; + */ + template = ""; + + /** + * template for the webhook title. + * + * @generated from field: string title_template = 101; + */ + titleTemplate = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "v1.Hook.Gotify"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "base_url", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "token", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 100, name: "template", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 101, name: "title_template", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): Hook_Gotify { + return new Hook_Gotify().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): Hook_Gotify { + return new Hook_Gotify().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): Hook_Gotify { + return new Hook_Gotify().fromJsonString(jsonString, options); + } + + static equals(a: Hook_Gotify | PlainMessage | undefined, b: Hook_Gotify | PlainMessage | undefined): boolean { + return proto3.util.equals(Hook_Gotify, a, b); + } +} + diff --git a/webui/src/components/HooksFormList.tsx b/webui/src/components/HooksFormList.tsx index f38f3cf8..bfb582ed 100644 --- a/webui/src/components/HooksFormList.tsx +++ b/webui/src/components/HooksFormList.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Hook, Hook_Command, Hook_Condition, Hook_Discord, Hook_Webhook } from '../../gen/ts/v1/config_pb'; +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'; @@ -123,6 +123,17 @@ const hookTypes: { }), } }, + { + name: "Gotify", action: { + case: "actionGotify", + value: new Hook_Gotify({ + baseUrl: "", + token: "", + template: "{{ .Summary }}", + titleTemplate: "Backrest {{ .EventName .Event }} in plan {{ .Plan.Id }}", + }), + } + } ]; const HookBuilder = ({ field, hook }: { field: FormListFieldData, hook: Hook }) => { @@ -133,7 +144,7 @@ const HookBuilder = ({ field, hook }: { field: FormListFieldData, hook: Hook }) Discord Webhook} /> - Text: + Text Template: @@ -147,6 +158,22 @@ const HookBuilder = ({ field, hook }: { field: FormListFieldData, hook: Hook }) + case "actionGotify": + return <> + + Gotify Base URL} /> + + + Gotify Token} /> + + + Title Template} /> + + Text Template: + + + + default: return

Unknown hook {hook.action.case}

}