mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-08 15:41:20 +02:00
feat: Enhance notification service configuration and refactor related components
- Updated notification service models to use pointers for boolean fields, allowing for better handling of enabled states. - Introduced helper methods for getting and setting enabled states in notification and auth provider models. - Refactored notification form templates to consolidate event trigger handling, improving code maintainability. - Added SSL verification configuration to the application settings, allowing users to skip SSL verification for outgoing notifications. - Enhanced the notification sending logic to respect the SSL verification setting. - Introduced new migration to alter boolean defaults in the database schema for better compatibility with the updated models. - Added unit tests for email and Rclone service functionalities to ensure reliability.
This commit is contained in:
+1
-14
@@ -43,20 +43,7 @@ node_modules/
|
||||
Thumbs.db
|
||||
|
||||
# Ignore all Go files in the components directory
|
||||
components/*.go
|
||||
!components/components.go
|
||||
|
||||
components/providers/*.go
|
||||
!components/providers/providers.go
|
||||
|
||||
components/providers/source/*.go
|
||||
!components/providers/source/source.go
|
||||
|
||||
components/providers/destination/*.go
|
||||
!components/providers/destination/destination.go
|
||||
|
||||
components/providers/common/*.go
|
||||
!components/providers/common/common.go
|
||||
*_templ.go
|
||||
|
||||
# Ignore the data directory
|
||||
data/
|
||||
|
||||
@@ -275,7 +275,7 @@ templ formContent(provider *db.AuthProvider, isNew bool) {
|
||||
<!-- Enabled -->
|
||||
<div>
|
||||
<div class="flex items-center">
|
||||
if provider == nil || provider.Enabled {
|
||||
if provider == nil || provider.GetEnabled() {
|
||||
<input
|
||||
type="checkbox"
|
||||
id="enabled"
|
||||
|
||||
@@ -80,7 +80,7 @@ templ AuthProviders(ctx context.Context, providers []db.AuthProvider) {
|
||||
{ string(provider.Type) }
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
if provider.Enabled {
|
||||
if provider.GetEnabled() {
|
||||
<span class="px-2 py-1 text-xs rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
|
||||
Active
|
||||
</span>
|
||||
|
||||
@@ -35,7 +35,7 @@ templ AuthProviderButtons(providers []db.AuthProvider) {
|
||||
} else {
|
||||
<div class="space-y-2 w-full">
|
||||
for _, provider := range providers {
|
||||
if provider.Enabled {
|
||||
if provider.GetEnabled() {
|
||||
<a
|
||||
href={ templ.SafeURL(fmt.Sprintf("/auth/provider/%d", provider.ID)) }
|
||||
class="w-full inline-flex items-center justify-center px-4 py-2.5 bg-gray-100 border border-gray-300 rounded-lg font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-gray-600"
|
||||
|
||||
@@ -290,33 +290,34 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
// This will need coordination with your backend to ensure the IDs match the commands
|
||||
let commandName = '';
|
||||
switch(parseInt(this.commandId)) {
|
||||
// Map command IDs to command names - adjust these based on your actual command IDs
|
||||
// Correct mapping based on internal/db/migrations/009_add_rclone_tables.go
|
||||
case 1: commandName = 'copy'; break;
|
||||
case 2: commandName = 'move'; break;
|
||||
case 3: commandName = 'sync'; break;
|
||||
case 4: commandName = 'ls'; break;
|
||||
case 5: commandName = 'lsd'; break;
|
||||
case 6: commandName = 'lsl'; break;
|
||||
case 7: commandName = 'lsf'; break;
|
||||
case 8: commandName = 'lsjson'; break;
|
||||
case 9: commandName = 'md5sum'; break;
|
||||
case 10: commandName = 'sha1sum'; break;
|
||||
case 11: commandName = 'size'; break;
|
||||
case 12: commandName = 'delete'; break;
|
||||
case 13: commandName = 'purge'; break;
|
||||
case 14: commandName = 'mkdir'; break;
|
||||
case 15: commandName = 'rmdir'; break;
|
||||
case 16: commandName = 'rmdirs'; break;
|
||||
case 17: commandName = 'check'; break;
|
||||
case 18: commandName = 'cleanup'; break;
|
||||
case 19: commandName = 'dedupe'; break;
|
||||
case 20: commandName = 'version'; break;
|
||||
case 21: commandName = 'listremotes'; break;
|
||||
case 22: commandName = 'cryptcheck'; break;
|
||||
case 23: commandName = 'bisync'; break;
|
||||
case 24: commandName = 'copyto'; break;
|
||||
case 25: commandName = 'moveto'; break;
|
||||
default: commandName = 'copy'; // Default to copy
|
||||
case 2: commandName = 'sync'; break;
|
||||
case 3: commandName = 'bisync'; break;
|
||||
case 4: commandName = 'move'; break;
|
||||
case 5: commandName = 'delete'; break;
|
||||
case 6: commandName = 'purge'; break;
|
||||
case 7: commandName = 'mkdir'; break;
|
||||
case 8: commandName = 'rmdir'; break;
|
||||
case 9: commandName = 'rmdirs'; break;
|
||||
case 10: commandName = 'check'; break;
|
||||
case 11: commandName = 'ls'; break;
|
||||
case 12: commandName = 'lsd'; break;
|
||||
case 13: commandName = 'lsl'; break;
|
||||
case 14: commandName = 'lsf'; break;
|
||||
case 15: commandName = 'lsjson'; break;
|
||||
case 16: commandName = 'md5sum'; break;
|
||||
case 17: commandName = 'sha1sum'; break;
|
||||
case 18: commandName = 'size'; break;
|
||||
case 19: commandName = 'version'; break;
|
||||
case 20: commandName = 'cleanup'; break;
|
||||
case 21: commandName = 'dedupe'; break;
|
||||
case 22: commandName = 'copyto'; break;
|
||||
case 23: commandName = 'moveto'; break;
|
||||
case 24: commandName = 'listremotes'; break;
|
||||
case 25: commandName = 'obscure'; break;
|
||||
case 26: commandName = 'cryptcheck'; break;
|
||||
default: commandName = 'copy'; // Default to copy if ID is unknown
|
||||
}
|
||||
|
||||
console.log('Command ID:', this.commandId, 'Command Name:', commandName);
|
||||
|
||||
@@ -142,7 +142,7 @@ templ LayoutWithContext(title string, ctx context.Context) {
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-full bg-gray-50 dark:bg-gray-900" style="min-height: 100vh; display: flex; flex-direction: column;">
|
||||
<body class="min-h-full bg-gray-50 dark:bg-gray-900" style="min-height: 100vh; display: flex; flex-direction: column;" hx-on::after-swap="initFlowbite()">
|
||||
if isLoggedIn(ctx) {
|
||||
<!-- Application Shell -->
|
||||
<div class="flex min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
|
||||
@@ -2,7 +2,7 @@ package fields
|
||||
|
||||
import (
|
||||
"github.com/starfleetcptn/gomft/components/notifications/types"
|
||||
"github.com/starfleetcptn/gomft/components/notifications/form/utils"
|
||||
// "github.com/starfleetcptn/gomft/components/notifications/form/utils" // Removed as unused
|
||||
)
|
||||
|
||||
templ GotifyFields(data types.NotificationFormData) {
|
||||
@@ -59,35 +59,7 @@ templ GotifyFields(data types.NotificationFormData) {
|
||||
}</textarea>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Event Triggers</label>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="gotify_trigger_job_start" name="trigger_job_start" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_start")) }/>
|
||||
} else {
|
||||
<input id="gotify_trigger_job_start" name="trigger_job_start" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"/>
|
||||
}
|
||||
<label for="gotify_trigger_job_start" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Start</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="gotify_trigger_job_complete" name="trigger_job_complete" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_complete")) }/>
|
||||
} else {
|
||||
<input id="gotify_trigger_job_complete" name="trigger_job_complete" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"/>
|
||||
}
|
||||
<label for="gotify_trigger_job_complete" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Complete</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="gotify_trigger_job_error" name="trigger_job_error" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_error")) }/>
|
||||
} else {
|
||||
<input id="gotify_trigger_job_error" name="trigger_job_error" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"/>
|
||||
}
|
||||
<label for="gotify_trigger_job_error" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Error</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
// Removed duplicate Event Triggers section - now handled in form.templ
|
||||
<!-- Test notification button for Gotify -->
|
||||
<div class="mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
|
||||
@@ -9,8 +9,8 @@ import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"github.com/starfleetcptn/gomft/components/notifications/form/utils"
|
||||
"github.com/starfleetcptn/gomft/components/notifications/types"
|
||||
// "github.com/starfleetcptn/gomft/components/notifications/form/utils" // Removed as unused
|
||||
)
|
||||
|
||||
func GotifyFields(data types.NotificationFormData) templ.Component {
|
||||
@@ -143,91 +143,7 @@ func GotifyFields(data types.NotificationFormData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</textarea><p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p></div><div class=\"mb-6\"><label class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Event Triggers</label><div class=\"space-y-2\"><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<input id=\"gotify_trigger_job_start\" name=\"trigger_job_start\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_start")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/gotify.templ`, Line: 67, Col: 361}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<input id=\"gotify_trigger_job_start\" name=\"trigger_job_start\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<label for=\"gotify_trigger_job_start\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Start</label></div><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<input id=\"gotify_trigger_job_complete\" name=\"trigger_job_complete\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_complete")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/gotify.templ`, Line: 75, Col: 370}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<input id=\"gotify_trigger_job_complete\" name=\"trigger_job_complete\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<label for=\"gotify_trigger_job_complete\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Complete</label></div><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<input id=\"gotify_trigger_job_error\" name=\"trigger_job_error\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_error")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/gotify.templ`, Line: 83, Col: 361}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<input id=\"gotify_trigger_job_error\" name=\"trigger_job_error\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<label for=\"gotify_trigger_job_error\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Error</label></div></div></div><!-- Test notification button for Gotify --><div class=\"mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600\"><div class=\"flex items-center justify-between mb-2\"><h4 class=\"text-base font-medium text-gray-900 dark:text-white\">Test Configuration</h4><button type=\"button\" id=\"test-gotify-btn\" hx-post=\"/admin/settings/notifications/test\" hx-trigger=\"click\" hx-target=\"#test-notification-result\" hx-swap=\"outerHTML\" class=\"px-3 py-2 text-xs font-medium text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 rounded-lg dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800\"><i class=\"fas fa-paper-plane mr-1\"></i> Send Test Notification</button></div><p class=\"text-sm text-gray-500 dark:text-gray-400\">Send a test notification to verify your Gotify configuration works correctly before saving.</p><div id=\"test-notification-result\" class=\"mt-3 hidden\"><!-- Result will be shown here --></div></div></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</textarea><p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p></div><!-- Test notification button for Gotify --><div class=\"mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600\"><div class=\"flex items-center justify-between mb-2\"><h4 class=\"text-base font-medium text-gray-900 dark:text-white\">Test Configuration</h4><button type=\"button\" id=\"test-gotify-btn\" hx-post=\"/admin/settings/notifications/test\" hx-trigger=\"click\" hx-target=\"#test-notification-result\" hx-swap=\"outerHTML\" class=\"px-3 py-2 text-xs font-medium text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 rounded-lg dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800\"><i class=\"fas fa-paper-plane mr-1\"></i> Send Test Notification</button></div><p class=\"text-sm text-gray-500 dark:text-gray-400\">Send a test notification to verify your Gotify configuration works correctly before saving.</p><div id=\"test-notification-result\" class=\"mt-3 hidden\"><!-- Result will be shown here --></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package fields
|
||||
|
||||
import (
|
||||
"github.com/starfleetcptn/gomft/components/notifications/types"
|
||||
"github.com/starfleetcptn/gomft/components/notifications/form/utils"
|
||||
// "github.com/starfleetcptn/gomft/components/notifications/form/utils" // Removed as unused
|
||||
)
|
||||
|
||||
templ NtfyFields(data types.NotificationFormData) {
|
||||
@@ -76,35 +76,7 @@ templ NtfyFields(data types.NotificationFormData) {
|
||||
} </textarea>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Event Triggers</label>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="ntfy_trigger_job_start" name="trigger_job_start" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_start")) }/>
|
||||
} else {
|
||||
<input id="ntfy_trigger_job_start" name="trigger_job_start" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"/>
|
||||
}
|
||||
<label for="ntfy_trigger_job_start" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Start</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="ntfy_trigger_job_complete" name="trigger_job_complete" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_complete")) }/>
|
||||
} else {
|
||||
<input id="ntfy_trigger_job_complete" name="trigger_job_complete" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"/>
|
||||
}
|
||||
<label for="ntfy_trigger_job_complete" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Complete</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="ntfy_trigger_job_error" name="trigger_job_error" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_error")) }/>
|
||||
} else {
|
||||
<input id="ntfy_trigger_job_error" name="trigger_job_error" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"/>
|
||||
}
|
||||
<label for="ntfy_trigger_job_error" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Error</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
// Removed duplicate Event Triggers section - now handled in form.templ
|
||||
<!-- Test notification button for Ntfy -->
|
||||
<div class="mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
|
||||
@@ -9,8 +9,8 @@ import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"github.com/starfleetcptn/gomft/components/notifications/form/utils"
|
||||
"github.com/starfleetcptn/gomft/components/notifications/types"
|
||||
// "github.com/starfleetcptn/gomft/components/notifications/form/utils" // Removed as unused
|
||||
)
|
||||
|
||||
func NtfyFields(data types.NotificationFormData) templ.Component {
|
||||
@@ -199,91 +199,7 @@ func NtfyFields(data types.NotificationFormData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</textarea><p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p></div><div class=\"mb-6\"><label class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Event Triggers</label><div class=\"space-y-2\"><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<input id=\"ntfy_trigger_job_start\" name=\"trigger_job_start\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_start")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/ntfy.templ`, Line: 84, Col: 359}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<input id=\"ntfy_trigger_job_start\" name=\"trigger_job_start\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<label for=\"ntfy_trigger_job_start\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Start</label></div><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<input id=\"ntfy_trigger_job_complete\" name=\"trigger_job_complete\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_complete")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/ntfy.templ`, Line: 92, Col: 368}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<input id=\"ntfy_trigger_job_complete\" name=\"trigger_job_complete\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<label for=\"ntfy_trigger_job_complete\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Complete</label></div><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<input id=\"ntfy_trigger_job_error\" name=\"trigger_job_error\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_error")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/ntfy.templ`, Line: 100, Col: 359}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<input id=\"ntfy_trigger_job_error\" name=\"trigger_job_error\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<label for=\"ntfy_trigger_job_error\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Error</label></div></div></div><!-- Test notification button for Ntfy --><div class=\"mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600\"><div class=\"flex items-center justify-between mb-2\"><h4 class=\"text-base font-medium text-gray-900 dark:text-white\">Test Configuration</h4><button type=\"button\" id=\"test-ntfy-btn\" hx-post=\"/admin/settings/notifications/test\" hx-trigger=\"click\" hx-target=\"#test-notification-result\" hx-swap=\"outerHTML\" class=\"px-3 py-2 text-xs font-medium text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 rounded-lg dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800\"><i class=\"fas fa-paper-plane mr-1\"></i> Send Test Notification</button></div><p class=\"text-sm text-gray-500 dark:text-gray-400\">Send a test notification to verify your Ntfy configuration works correctly before saving.</p><div id=\"test-notification-result\" class=\"mt-3 hidden\"><!-- Result will be shown here --></div></div></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</textarea><p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p></div><!-- Test notification button for Ntfy --><div class=\"mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600\"><div class=\"flex items-center justify-between mb-2\"><h4 class=\"text-base font-medium text-gray-900 dark:text-white\">Test Configuration</h4><button type=\"button\" id=\"test-ntfy-btn\" hx-post=\"/admin/settings/notifications/test\" hx-trigger=\"click\" hx-target=\"#test-notification-result\" hx-swap=\"outerHTML\" class=\"px-3 py-2 text-xs font-medium text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 rounded-lg dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800\"><i class=\"fas fa-paper-plane mr-1\"></i> Send Test Notification</button></div><p class=\"text-sm text-gray-500 dark:text-gray-400\">Send a test notification to verify your Ntfy configuration works correctly before saving.</p><div id=\"test-notification-result\" class=\"mt-3 hidden\"><!-- Result will be shown here --></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package fields
|
||||
|
||||
import (
|
||||
"github.com/starfleetcptn/gomft/components/notifications/types"
|
||||
"github.com/starfleetcptn/gomft/components/notifications/form/utils"
|
||||
// "github.com/starfleetcptn/gomft/components/notifications/form/utils" // Removed as unused
|
||||
)
|
||||
|
||||
templ PushbulletFields(data types.NotificationFormData) {
|
||||
@@ -46,35 +46,7 @@ templ PushbulletFields(data types.NotificationFormData) {
|
||||
} </textarea>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Event Triggers</label>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="pb_trigger_job_start" name="trigger_job_start" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_start")) }/>
|
||||
} else {
|
||||
<input id="pb_trigger_job_start" name="trigger_job_start" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"/>
|
||||
}
|
||||
<label for="pb_trigger_job_start" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Start</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="pb_trigger_job_complete" name="trigger_job_complete" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_complete")) }/>
|
||||
} else {
|
||||
<input id="pb_trigger_job_complete" name="trigger_job_complete" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"/>
|
||||
}
|
||||
<label for="pb_trigger_job_complete" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Complete</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="pb_trigger_job_error" name="trigger_job_error" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_error")) }/>
|
||||
} else {
|
||||
<input id="pb_trigger_job_error" name="trigger_job_error" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"/>
|
||||
}
|
||||
<label for="pb_trigger_job_error" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Error</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
// Removed duplicate Event Triggers section - now handled in form.templ
|
||||
<!-- Test notification button for Pushbullet -->
|
||||
<div class="mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
|
||||
@@ -9,8 +9,8 @@ import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"github.com/starfleetcptn/gomft/components/notifications/form/utils"
|
||||
"github.com/starfleetcptn/gomft/components/notifications/types"
|
||||
// "github.com/starfleetcptn/gomft/components/notifications/form/utils" // Removed as unused
|
||||
)
|
||||
|
||||
func PushbulletFields(data types.NotificationFormData) templ.Component {
|
||||
@@ -128,91 +128,7 @@ func PushbulletFields(data types.NotificationFormData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</textarea><p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p></div><div class=\"mb-6\"><label class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Event Triggers</label><div class=\"space-y-2\"><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<input id=\"pb_trigger_job_start\" name=\"trigger_job_start\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_start")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/pushbullet.templ`, Line: 54, Col: 357}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<input id=\"pb_trigger_job_start\" name=\"trigger_job_start\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<label for=\"pb_trigger_job_start\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Start</label></div><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<input id=\"pb_trigger_job_complete\" name=\"trigger_job_complete\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_complete")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/pushbullet.templ`, Line: 62, Col: 366}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<input id=\"pb_trigger_job_complete\" name=\"trigger_job_complete\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<label for=\"pb_trigger_job_complete\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Complete</label></div><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<input id=\"pb_trigger_job_error\" name=\"trigger_job_error\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_error")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/pushbullet.templ`, Line: 70, Col: 357}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<input id=\"pb_trigger_job_error\" name=\"trigger_job_error\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<label for=\"pb_trigger_job_error\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Error</label></div></div></div><!-- Test notification button for Pushbullet --><div class=\"mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600\"><div class=\"flex items-center justify-between mb-2\"><h4 class=\"text-base font-medium text-gray-900 dark:text-white\">Test Configuration</h4><button type=\"button\" id=\"test-pushbullet-btn\" hx-post=\"/admin/settings/notifications/test\" hx-trigger=\"click\" hx-target=\"#test-notification-result\" hx-swap=\"outerHTML\" class=\"px-3 py-2 text-xs font-medium text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 rounded-lg dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800\"><i class=\"fas fa-paper-plane mr-1\"></i> Send Test Notification</button></div><p class=\"text-sm text-gray-500 dark:text-gray-400\">Send a test notification to verify your Pushbullet configuration works correctly before saving.</p><div id=\"test-notification-result\" class=\"mt-3 hidden\"><!-- Result will be shown here --></div></div></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</textarea><p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p></div><!-- Test notification button for Pushbullet --><div class=\"mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600\"><div class=\"flex items-center justify-between mb-2\"><h4 class=\"text-base font-medium text-gray-900 dark:text-white\">Test Configuration</h4><button type=\"button\" id=\"test-pushbullet-btn\" hx-post=\"/admin/settings/notifications/test\" hx-trigger=\"click\" hx-target=\"#test-notification-result\" hx-swap=\"outerHTML\" class=\"px-3 py-2 text-xs font-medium text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 rounded-lg dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800\"><i class=\"fas fa-paper-plane mr-1\"></i> Send Test Notification</button></div><p class=\"text-sm text-gray-500 dark:text-gray-400\">Send a test notification to verify your Pushbullet configuration works correctly before saving.</p><div id=\"test-notification-result\" class=\"mt-3 hidden\"><!-- Result will be shown here --></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package fields
|
||||
|
||||
import (
|
||||
"github.com/starfleetcptn/gomft/components/notifications/types"
|
||||
"github.com/starfleetcptn/gomft/components/notifications/form/utils"
|
||||
// "github.com/starfleetcptn/gomft/components/notifications/form/utils" // Removed as unused
|
||||
)
|
||||
|
||||
templ PushoverFields(data types.NotificationFormData) {
|
||||
@@ -97,35 +97,7 @@ templ PushoverFields(data types.NotificationFormData) {
|
||||
} </textarea>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Event Triggers</label>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="pushover_trigger_job_start" name="trigger_job_start" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_start")) }/>
|
||||
} else {
|
||||
<input id="pushover_trigger_job_start" name="trigger_job_start" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"/>
|
||||
}
|
||||
<label for="pushover_trigger_job_start" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Start</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="pushover_trigger_job_complete" name="trigger_job_complete" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_complete")) }/>
|
||||
} else {
|
||||
<input id="pushover_trigger_job_complete" name="trigger_job_complete" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"/>
|
||||
}
|
||||
<label for="pushover_trigger_job_complete" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Complete</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="pushover_trigger_job_error" name="trigger_job_error" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_error")) }/>
|
||||
} else {
|
||||
<input id="pushover_trigger_job_error" name="trigger_job_error" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"/>
|
||||
}
|
||||
<label for="pushover_trigger_job_error" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Error</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
// Removed duplicate Event Triggers section - now handled in form.templ
|
||||
<!-- Test notification button for Pushover -->
|
||||
<div class="mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
|
||||
@@ -9,8 +9,8 @@ import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"github.com/starfleetcptn/gomft/components/notifications/form/utils"
|
||||
"github.com/starfleetcptn/gomft/components/notifications/types"
|
||||
// "github.com/starfleetcptn/gomft/components/notifications/form/utils" // Removed as unused
|
||||
)
|
||||
|
||||
func PushoverFields(data types.NotificationFormData) templ.Component {
|
||||
@@ -171,91 +171,7 @@ func PushoverFields(data types.NotificationFormData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</textarea><p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p></div><div class=\"mb-6\"><label class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Event Triggers</label><div class=\"space-y-2\"><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<input id=\"pushover_trigger_job_start\" name=\"trigger_job_start\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_start")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/pushover.templ`, Line: 105, Col: 363}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<input id=\"pushover_trigger_job_start\" name=\"trigger_job_start\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<label for=\"pushover_trigger_job_start\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Start</label></div><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<input id=\"pushover_trigger_job_complete\" name=\"trigger_job_complete\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_complete")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/pushover.templ`, Line: 113, Col: 372}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<input id=\"pushover_trigger_job_complete\" name=\"trigger_job_complete\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<label for=\"pushover_trigger_job_complete\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Complete</label></div><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<input id=\"pushover_trigger_job_error\" name=\"trigger_job_error\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_error")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/pushover.templ`, Line: 121, Col: 363}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<input id=\"pushover_trigger_job_error\" name=\"trigger_job_error\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<label for=\"pushover_trigger_job_error\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Error</label></div></div></div><!-- Test notification button for Pushover --><div class=\"mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600\"><div class=\"flex items-center justify-between mb-2\"><h4 class=\"text-base font-medium text-gray-900 dark:text-white\">Test Configuration</h4><button type=\"button\" id=\"test-pushover-btn\" hx-post=\"/admin/settings/notifications/test\" hx-trigger=\"click\" hx-target=\"#test-notification-result\" hx-swap=\"outerHTML\" class=\"px-3 py-2 text-xs font-medium text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 rounded-lg dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800\"><i class=\"fas fa-paper-plane mr-1\"></i> Send Test Notification</button></div><p class=\"text-sm text-gray-500 dark:text-gray-400\">Send a test notification to verify your Pushover configuration works correctly before saving.</p><div id=\"test-notification-result\" class=\"mt-3 hidden\"><!-- Result will be shown here --></div></div></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</textarea><p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p></div><!-- Test notification button for Pushover --><div class=\"mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600\"><div class=\"flex items-center justify-between mb-2\"><h4 class=\"text-base font-medium text-gray-900 dark:text-white\">Test Configuration</h4><button type=\"button\" id=\"test-pushover-btn\" hx-post=\"/admin/settings/notifications/test\" hx-trigger=\"click\" hx-target=\"#test-notification-result\" hx-swap=\"outerHTML\" class=\"px-3 py-2 text-xs font-medium text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 rounded-lg dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800\"><i class=\"fas fa-paper-plane mr-1\"></i> Send Test Notification</button></div><p class=\"text-sm text-gray-500 dark:text-gray-400\">Send a test notification to verify your Pushover configuration works correctly before saving.</p><div id=\"test-notification-result\" class=\"mt-3 hidden\"><!-- Result will be shown here --></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package fields
|
||||
|
||||
import (
|
||||
"github.com/starfleetcptn/gomft/components/notifications/types"
|
||||
"github.com/starfleetcptn/gomft/components/notifications/form/utils"
|
||||
// "github.com/starfleetcptn/gomft/components/notifications/form/utils" // Removed as unused
|
||||
)
|
||||
|
||||
templ WebhookFields(data types.NotificationFormData) {
|
||||
@@ -80,29 +80,7 @@ templ WebhookFields(data types.NotificationFormData) {
|
||||
} </textarea>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Event Triggers</label>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="trigger_job_start" name="trigger_job_start" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_start")) }/>
|
||||
}
|
||||
<label for="trigger_job_start" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Start</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="trigger_job_complete" name="trigger_job_complete" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_complete")) }/>
|
||||
}
|
||||
<label for="trigger_job_complete" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Complete</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
<input id="trigger_job_error" name="trigger_job_error" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_error")) }/>
|
||||
}
|
||||
<label for="trigger_job_error" class="ml-2 text-sm font-medium text-gray-900 dark:text-white">Job Error</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
// Removed duplicate Event Triggers section - now handled in form.templ
|
||||
<div class="mb-6">
|
||||
<label for="secret_key" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Secret Key (for signature verification)</label>
|
||||
if data.NotificationService.SecretKey != "" {
|
||||
|
||||
@@ -9,8 +9,8 @@ import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"github.com/starfleetcptn/gomft/components/notifications/form/utils"
|
||||
"github.com/starfleetcptn/gomft/components/notifications/types"
|
||||
// "github.com/starfleetcptn/gomft/components/notifications/form/utils" // Removed as unused
|
||||
)
|
||||
|
||||
func WebhookFields(data types.NotificationFormData) templ.Component {
|
||||
@@ -137,151 +137,82 @@ func WebhookFields(data types.NotificationFormData) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</textarea><p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p></div><div class=\"mb-6\"><label class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Event Triggers</label><div class=\"space-y-2\"><div class=\"flex items-center\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</textarea><p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">Use placeholders for dynamic values. Available variables: job.*, instance.*, timestamp, notification.*</p></div><div class=\"mb-6\"><label for=\"secret_key\" class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Secret Key (for signature verification)</label> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<input id=\"trigger_job_start\" name=\"trigger_job_start\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if data.NotificationService.SecretKey != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<input type=\"text\" id=\"secret_key\" name=\"secret_key\" class=\"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\" placeholder=\"Optional signature verification key\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_start")))
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.NotificationService.SecretKey)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/webhook.templ`, Line: 88, Col: 354}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/webhook.templ`, Line: 87, Col: 417}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\"> ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<label for=\"trigger_job_start\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Start</label></div><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<input id=\"trigger_job_complete\" name=\"trigger_job_complete\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_complete")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/webhook.templ`, Line: 94, Col: 363}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<label for=\"trigger_job_complete\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Complete</label></div><div class=\"flex items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.EventTriggers != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<input id=\"trigger_job_error\" name=\"trigger_job_error\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(utils.Contains(data.NotificationService.EventTriggers, "job_error")))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/webhook.templ`, Line: 100, Col: 354}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<label for=\"trigger_job_error\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-white\">Job Error</label></div></div></div><div class=\"mb-6\"><label for=\"secret_key\" class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Secret Key (for signature verification)</label> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.SecretKey != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<input type=\"text\" id=\"secret_key\" name=\"secret_key\" class=\"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\" placeholder=\"Optional signature verification key\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.NotificationService.SecretKey)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/fields/webhook.templ`, Line: 109, Col: 417}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">If provided, all webhooks will include an X-GoMFT-Signature header</p></div><div class=\"mb-6\"><label for=\"retry_policy\" class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Retry Policy</label> <select id=\"retry_policy\" name=\"retry_policy\" class=\"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<p class=\"mt-1 text-sm text-gray-500 dark:text-gray-400\">If provided, all webhooks will include an X-GoMFT-Signature header</p></div><div class=\"mb-6\"><label for=\"retry_policy\" class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Retry Policy</label> <select id=\"retry_policy\" name=\"retry_policy\" class=\"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.RetryPolicy != "" {
|
||||
if data.NotificationService.RetryPolicy == "none" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<option value=\"none\" selected=\"selected\">No retries</option>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<option value=\"none\" selected=\"selected\">No retries</option>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<option value=\"none\">No retries</option>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<option value=\"none\">No retries</option>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, " ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, " ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.RetryPolicy == "simple" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<option value=\"simple\" selected=\"selected\">Simple (3 retries)</option>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<option value=\"simple\" selected=\"selected\">Simple (3 retries)</option>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<option value=\"simple\">Simple (3 retries)</option>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<option value=\"simple\">Simple (3 retries)</option>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.RetryPolicy == "exponential" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<option value=\"exponential\" selected=\"selected\">Exponential backoff</option>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<option value=\"exponential\" selected=\"selected\">Exponential backoff</option>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<option value=\"exponential\">Exponential backoff</option>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<option value=\"exponential\">Exponential backoff</option>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<option value=\"none\">No retries</option> <option value=\"simple\">Simple (3 retries)</option> <option value=\"exponential\">Exponential backoff</option>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<option value=\"none\">No retries</option> <option value=\"simple\">Simple (3 retries)</option> <option value=\"exponential\">Exponential backoff</option>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</select></div><!-- Test notification button --><div class=\"mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600\"><div class=\"flex items-center justify-between mb-2\"><h4 class=\"text-base font-medium text-gray-900 dark:text-white\">Test Configuration</h4><button type=\"button\" id=\"test-webhook-btn\" hx-post=\"/admin/settings/notifications/test\" hx-trigger=\"click\" hx-target=\"#test-notification-result\" hx-swap=\"outerHTML\" class=\"px-3 py-2 text-xs font-medium text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 rounded-lg dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800\"><i class=\"fas fa-paper-plane mr-1\"></i> Send Test Notification</button></div><p class=\"text-sm text-gray-500 dark:text-gray-400\">Send a test notification to verify your configuration works correctly before saving.</p><div id=\"test-notification-result\" class=\"mt-3 hidden\"><!-- Result will be shown here --></div></div></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</select></div><!-- Test notification button --><div class=\"mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600\"><div class=\"flex items-center justify-between mb-2\"><h4 class=\"text-base font-medium text-gray-900 dark:text-white\">Test Configuration</h4><button type=\"button\" id=\"test-webhook-btn\" hx-post=\"/admin/settings/notifications/test\" hx-trigger=\"click\" hx-target=\"#test-notification-result\" hx-swap=\"outerHTML\" class=\"px-3 py-2 text-xs font-medium text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 rounded-lg dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800\"><i class=\"fas fa-paper-plane mr-1\"></i> Send Test Notification</button></div><p class=\"text-sm text-gray-500 dark:text-gray-400\">Send a test notification to verify your configuration works correctly before saving.</p><div id=\"test-notification-result\" class=\"mt-3 hidden\"><!-- Result will be shown here --></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@@ -102,17 +102,45 @@ templ NotificationForm(ctx context.Context, data types.NotificationFormData) {
|
||||
@fields.GotifyFields(data)
|
||||
@fields.PushoverFields(data)
|
||||
|
||||
|
||||
<div class="mb-6 hidden common-fields">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Event Triggers</label>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Select the job events that should trigger this notification.</p>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
for _, event := range []string{"job_start", "job_complete", "job_error"} {
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id={ "trigger_" + event }
|
||||
name="event_triggers[]"
|
||||
type="checkbox"
|
||||
value={ event }
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
|
||||
if utils.IsEventTriggerSelected(data.NotificationService, event, data.IsNew) {
|
||||
checked
|
||||
}
|
||||
/>
|
||||
<label for={ "trigger_" + event } class="ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">{ utils.FormatEventTriggerName(event) }</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start mb-6 hidden common-fields">
|
||||
<div class="flex items-center h-5">
|
||||
if data.NotificationService.IsEnabled != false {
|
||||
<input id="is_enabled" name="is_enabled" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked={ utils.BoolToString(data.NotificationService.IsEnabled) }/>
|
||||
} else {
|
||||
// Default to checked=true if IsEnabled is explicitly false (or not set) when creating new
|
||||
<input id="is_enabled" name="is_enabled" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800" checked="true"/>
|
||||
}
|
||||
<input
|
||||
id="is_enabled"
|
||||
name="is_enabled"
|
||||
type="checkbox"
|
||||
value="true"
|
||||
class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800"
|
||||
if data.NotificationService != nil && data.NotificationService.IsEnabled {
|
||||
checked
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-3 text-sm">
|
||||
<label for="is_enabled" class="font-medium text-gray-900 dark:text-white">Enable this notification service</label>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">Check this box to make the service active.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden common-fields">
|
||||
|
||||
@@ -141,7 +141,7 @@ func NotificationForm(ctx context.Context, data types.NotificationFormData) temp
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " hx-target=\"body\"><div class=\"mb-6\"><label for=\"notification_type\" class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Notification Type</label> <select id=\"notification_type\" name=\"type\" class=\"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\"><option value=\"\">Select a type</option> ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " hx-target=\"#notification-form-container\"><div class=\"mb-6\"><label for=\"notification_type\" class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Notification Type</label> <select id=\"notification_type\" name=\"type\" class=\"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\"><option value=\"\">Select a type</option> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -284,50 +284,104 @@ func NotificationForm(ctx context.Context, data types.NotificationFormData) temp
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<div class=\"flex items-start mb-6 hidden common-fields\"><div class=\"flex items-center h-5\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<div class=\"mb-6 hidden common-fields\"><label class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Event Triggers</label><p class=\"text-xs text-gray-500 dark:text-gray-400 mb-2\">Select the job events that should trigger this notification.</p><div class=\"flex flex-wrap gap-4\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService.IsEnabled != false {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<input id=\"is_enabled\" name=\"is_enabled\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"")
|
||||
for _, event := range []string{"job_start", "job_complete", "job_error"} {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<div class=\"flex items-center\"><input id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(utils.BoolToString(data.NotificationService.IsEnabled))
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs("trigger_" + event)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/form.templ`, Line: 108, Col: 310}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/form.templ`, Line: 113, Col: 34}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\" name=\"event_triggers[]\" type=\"checkbox\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, " <input id=\"is_enabled\" name=\"is_enabled\" type=\"checkbox\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\" checked=\"true\">")
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(event)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/form.templ`, Line: 116, Col: 24}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\" class=\"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if utils.IsEventTriggerSelected(data.NotificationService, event, data.IsNew) {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " checked")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "> <label for=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs("trigger_" + event)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/form.templ`, Line: 122, Col: 41}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\" class=\"ml-2 text-sm font-medium text-gray-900 dark:text-gray-300\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatEventTriggerName(event))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/notifications/form/form.templ`, Line: 122, Col: 147}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</label></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</div><div class=\"ml-3 text-sm\"><label for=\"is_enabled\" class=\"font-medium text-gray-900 dark:text-white\">Enable this notification service</label></div></div><div class=\"hidden common-fields\"><button type=\"submit\" class=\"text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</div></div><div class=\"flex items-start mb-6 hidden common-fields\"><div class=\"flex items-center h-5\"><input id=\"is_enabled\" name=\"is_enabled\" type=\"checkbox\" value=\"true\" class=\"w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.NotificationService != nil && data.NotificationService.IsEnabled {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, " checked")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "></div><div class=\"ml-3 text-sm\"><label for=\"is_enabled\" class=\"font-medium text-gray-900 dark:text-white\">Enable this notification service</label><p class=\"text-xs text-gray-500 dark:text-gray-400\">Check this box to make the service active.</p></div></div><div class=\"hidden common-fields\"><button type=\"submit\" class=\"text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.IsNew {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "Add Service")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "Add Service")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "Save Changes")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "Save Changes")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</button></div></form></div><!-- Help Notice --><div class=\"mt-8 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-800 dark:border-gray-700\"><div class=\"flex\"><div class=\"flex-shrink-0\"><i class=\"fas fa-info-circle text-blue-400 dark:text-blue-400\"></i></div><div class=\"ml-3\"><p class=\"text-sm text-blue-700 dark:text-blue-400\">Configure your notification service to receive alerts for job events. Different notification types have different configuration options.</p></div></div></div></div></div><!-- Theme-specific background handled by Tailwind classes -->")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</button></div></form></div><!-- Help Notice --><div class=\"mt-8 p-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-800 dark:border-gray-700\"><div class=\"flex\"><div class=\"flex-shrink-0\"><i class=\"fas fa-info-circle text-blue-400 dark:text-blue-400\"></i></div><div class=\"ml-3\"><p class=\"text-sm text-blue-700 dark:text-blue-400\">Configure your notification service to receive alerts for job events. Different notification types have different configuration options.</p></div></div></div></div></div><!-- Theme-specific background handled by Tailwind classes -->")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@@ -1,28 +1,88 @@
|
||||
package utils
|
||||
|
||||
// Contains checks if a string is in a slice.
|
||||
func Contains(slice []string, str string) bool {
|
||||
for _, s := range slice {
|
||||
if s == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BoolToString converts bool to string for HTML attributes.
|
||||
// Returns "true" for true, "" for false (useful for checked attribute).
|
||||
func BoolToString(b bool) string {
|
||||
if b {
|
||||
return "true"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetNotificationFormTitle returns the appropriate title for the form.
|
||||
// GetNotificationFormTitle returns the title for the notification form page.
|
||||
func GetNotificationFormTitle(isNew bool) string {
|
||||
if isNew {
|
||||
return "Add Notification Service"
|
||||
}
|
||||
return "Edit Notification Service"
|
||||
}
|
||||
|
||||
// Contains checks if a string slice contains a specific string.
|
||||
func Contains(slice []string, item string) bool {
|
||||
for _, a := range slice {
|
||||
if a == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// BoolToString converts a boolean to its string representation "true" or "false".
|
||||
// Useful for setting HTML attributes that expect string values.
|
||||
func BoolToString(b bool) string {
|
||||
if b {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
// IsEventTriggerSelected checks if a specific event trigger should be pre-selected.
|
||||
// It now accepts the anonymous struct type defined in types.NotificationFormData.
|
||||
// Defaults to checking 'job_complete' and 'job_error' when creating a new service.
|
||||
func IsEventTriggerSelected(service *struct {
|
||||
ID uint
|
||||
Name string
|
||||
Description string
|
||||
Type string
|
||||
IsEnabled bool
|
||||
EventTriggers []string
|
||||
RetryPolicy string
|
||||
WebhookURL string
|
||||
Method string
|
||||
Headers string
|
||||
PayloadTemplate string
|
||||
SecretKey string
|
||||
PushbulletAPIKey string
|
||||
PushbulletDeviceID string
|
||||
PushbulletTitleTemplate string
|
||||
PushbulletBodyTemplate string
|
||||
NtfyServer string
|
||||
NtfyTopic string
|
||||
NtfyPriority string
|
||||
NtfyUsername string
|
||||
NtfyPassword string
|
||||
NtfyTitleTemplate string
|
||||
NtfyMessageTemplate string
|
||||
GotifyURL string
|
||||
GotifyToken string
|
||||
GotifyPriority string
|
||||
GotifyTitleTemplate string
|
||||
GotifyMessageTemplate string
|
||||
PushoverAPIToken string
|
||||
PushoverUserKey string
|
||||
PushoverDevice string
|
||||
PushoverPriority string
|
||||
PushoverSound string
|
||||
PushoverTitleTemplate string
|
||||
PushoverMessageTemplate string
|
||||
}, event string, isNew bool) bool {
|
||||
if !isNew && service != nil {
|
||||
// Use the Contains helper function
|
||||
return Contains(service.EventTriggers, event)
|
||||
}
|
||||
// Default for new services: check complete and error
|
||||
return isNew && (event == "job_complete" || event == "job_error")
|
||||
}
|
||||
|
||||
// FormatEventTriggerName converts event trigger keys to human-readable names.
|
||||
func FormatEventTriggerName(event string) string {
|
||||
// Replace underscores with spaces and capitalize words
|
||||
name := strings.ReplaceAll(event, "_", " ")
|
||||
name = strings.Title(name) // Use strings.Title for capitalization
|
||||
return name
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ require (
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gorm.io/gorm v1.25.12
|
||||
@@ -22,6 +23,7 @@ require (
|
||||
github.com/bytedance/sonic v1.12.9 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
@@ -43,6 +45,7 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
|
||||
@@ -16,6 +16,7 @@ type Config struct {
|
||||
Email EmailConfig `json:"email"`
|
||||
BaseURL string `json:"base_url"` // Base URL for generating links in emails
|
||||
TOTPEncryptKey string `json:"totp_encrypt_key"` // Encryption key for TOTP secrets
|
||||
SkipSSLVerify bool `json:"skip_ssl_verify"` // Skip SSL verification for outgoing webhooks/notifications
|
||||
}
|
||||
|
||||
type EmailConfig struct {
|
||||
@@ -40,6 +41,7 @@ func Load() (*Config, error) {
|
||||
JWTSecret: "change_this_to_a_secure_random_string",
|
||||
BaseURL: "http://localhost:8080",
|
||||
TOTPEncryptKey: "this-is-a-dev-key-not-for-production!", // Default development key
|
||||
SkipSSLVerify: false, // Default to verifying SSL
|
||||
Email: EmailConfig{
|
||||
Enabled: false,
|
||||
Host: "smtp.example.com",
|
||||
@@ -119,6 +121,12 @@ func Load() (*Config, error) {
|
||||
if emailRequireAuth := os.Getenv("EMAIL_REQUIRE_AUTH"); emailRequireAuth != "" {
|
||||
cfg.Email.RequireAuth = strings.ToLower(emailRequireAuth) == "true"
|
||||
}
|
||||
|
||||
// SSL Verification configuration
|
||||
if sslVerify := os.Getenv("SSL_VERIFY"); sslVerify != "" {
|
||||
// Default is true (verify), only set SkipSSLVerify to true if env var is explicitly "false"
|
||||
cfg.SkipSSLVerify = strings.ToLower(sslVerify) == "false"
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
} else {
|
||||
@@ -148,6 +156,10 @@ func Load() (*Config, error) {
|
||||
"EMAIL_REQUIRE_AUTH=" + strconv.FormatBool(cfg.Email.RequireAuth),
|
||||
"EMAIL_USERNAME=" + cfg.Email.Username,
|
||||
"EMAIL_PASSWORD=" + cfg.Email.Password,
|
||||
"",
|
||||
"# SSL Verification for outgoing notifications (webhooks, etc.)",
|
||||
"# Set to false to disable SSL certificate verification (USE WITH CAUTION)",
|
||||
"SSL_VERIFY=" + strconv.FormatBool(!cfg.SkipSSLVerify), // Default is true (verify)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(envPath, []byte(strings.Join(envContent, "\n")), 0644); err != nil {
|
||||
|
||||
@@ -28,7 +28,7 @@ type AuthProvider struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
Type ProviderType `gorm:"not null" json:"type"`
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
Enabled *bool `gorm:"default:true" json:"enabled"`
|
||||
Description string `json:"description"`
|
||||
ProviderURL string `json:"provider_url"`
|
||||
ClientID string `json:"client_id"`
|
||||
@@ -75,6 +75,21 @@ func (p *AuthProvider) SetConfig(data map[string]interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- AuthProvider Helper Methods ---
|
||||
|
||||
// GetEnabled returns the value of Enabled with a default if nil
|
||||
func (p *AuthProvider) GetEnabled() bool {
|
||||
if p.Enabled == nil {
|
||||
return true // Default to true if not set
|
||||
}
|
||||
return *p.Enabled
|
||||
}
|
||||
|
||||
// SetEnabled sets the Enabled field
|
||||
func (p *AuthProvider) SetEnabled(value bool) {
|
||||
p.Enabled = &value
|
||||
}
|
||||
|
||||
// ExternalUserIdentity represents a user identity from an external authentication provider
|
||||
type ExternalUserIdentity struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AlterBooleanDefaults changes boolean columns with default:true to pointers
|
||||
// using explicit table recreation with raw SQL for SQLite compatibility.
|
||||
func AlterBooleanDefaults() *gormigrate.Migration {
|
||||
|
||||
// --- Raw SQL CREATE TABLE statements for the target schema ---
|
||||
|
||||
const createNotificationServicesSQL = `
|
||||
CREATE TABLE notification_services (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
is_enabled INTEGER DEFAULT 1, -- Target: *bool, SQLite uses 0/1, default true
|
||||
config TEXT,
|
||||
description TEXT,
|
||||
event_triggers TEXT DEFAULT '[]',
|
||||
payload_template TEXT,
|
||||
secret_key TEXT,
|
||||
retry_policy TEXT DEFAULT 'simple',
|
||||
last_used timestamp,
|
||||
success_count INTEGER DEFAULT 0,
|
||||
failure_count INTEGER DEFAULT 0,
|
||||
created_by INTEGER,
|
||||
created_at timestamp,
|
||||
updated_at timestamp
|
||||
);`
|
||||
|
||||
const createAuthProvidersSQL = `
|
||||
CREATE TABLE auth_providers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
enabled INTEGER DEFAULT 1, -- Target: *bool, SQLite uses 0/1, default true
|
||||
description TEXT,
|
||||
provider_url TEXT,
|
||||
client_id TEXT,
|
||||
client_secret TEXT,
|
||||
redirect_url TEXT,
|
||||
scopes TEXT,
|
||||
attribute_mapping TEXT,
|
||||
config TEXT,
|
||||
icon_url TEXT,
|
||||
successful_logins INTEGER DEFAULT 0,
|
||||
last_used timestamp,
|
||||
created_at timestamp,
|
||||
updated_at timestamp
|
||||
);`
|
||||
|
||||
const createTransferConfigsSQL = `
|
||||
CREATE TABLE transfer_configs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
source_type TEXT NOT NULL,
|
||||
source_path TEXT NOT NULL,
|
||||
source_host TEXT,
|
||||
source_port INTEGER DEFAULT 22,
|
||||
source_user TEXT,
|
||||
source_key_file TEXT,
|
||||
source_bucket TEXT,
|
||||
source_region TEXT,
|
||||
source_access_key TEXT,
|
||||
source_endpoint TEXT,
|
||||
source_share TEXT,
|
||||
source_domain TEXT,
|
||||
source_passive_mode INTEGER DEFAULT 1, -- Already *bool, keep default
|
||||
source_client_id TEXT,
|
||||
source_drive_id TEXT,
|
||||
source_team_drive TEXT,
|
||||
source_read_only INTEGER,
|
||||
source_start_year INTEGER,
|
||||
source_include_archived INTEGER,
|
||||
file_pattern TEXT DEFAULT '*',
|
||||
output_pattern TEXT,
|
||||
destination_type TEXT NOT NULL,
|
||||
destination_path TEXT NOT NULL,
|
||||
dest_host TEXT,
|
||||
dest_port INTEGER DEFAULT 22,
|
||||
dest_user TEXT,
|
||||
dest_key_file TEXT,
|
||||
dest_bucket TEXT,
|
||||
dest_region TEXT,
|
||||
dest_access_key TEXT,
|
||||
dest_endpoint TEXT,
|
||||
dest_share TEXT,
|
||||
dest_domain TEXT,
|
||||
dest_passive_mode INTEGER DEFAULT 1, -- Already *bool, keep default
|
||||
dest_client_id TEXT,
|
||||
dest_drive_id TEXT,
|
||||
dest_team_drive TEXT,
|
||||
dest_read_only INTEGER,
|
||||
dest_start_year INTEGER,
|
||||
dest_include_archived INTEGER,
|
||||
use_builtin_auth_source INTEGER,
|
||||
use_builtin_auth_dest INTEGER,
|
||||
google_drive_authenticated INTEGER,
|
||||
archive_path TEXT,
|
||||
archive_enabled INTEGER DEFAULT 0,
|
||||
rclone_flags TEXT,
|
||||
command_id INTEGER DEFAULT 1,
|
||||
command_flags TEXT,
|
||||
command_flag_values TEXT,
|
||||
delete_after_transfer INTEGER DEFAULT 0,
|
||||
skip_processed_files INTEGER DEFAULT 1, -- Target: *bool, SQLite uses 0/1, default true
|
||||
max_concurrent_transfers INTEGER DEFAULT 4,
|
||||
created_by INTEGER,
|
||||
created_at timestamp,
|
||||
updated_at timestamp
|
||||
);`
|
||||
|
||||
// --- End Raw SQL ---
|
||||
|
||||
return &gormigrate.Migration{
|
||||
ID: "012_alter_boolean_defaults",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
// --- Backup Logic (copied) ---
|
||||
var count int64
|
||||
if err := tx.Raw("SELECT count(*) FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").Scan(&count).Error; err != nil {
|
||||
return fmt.Errorf("failed to check for existing tables: %v", err)
|
||||
}
|
||||
if count > 0 {
|
||||
sqlDB, err := tx.DB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get underlying database: %v", err)
|
||||
}
|
||||
var seq int
|
||||
var name, dbPath string
|
||||
if err := sqlDB.QueryRow("PRAGMA database_list").Scan(&seq, &name, &dbPath); err != nil {
|
||||
return fmt.Errorf("failed to get database path: %v", err)
|
||||
}
|
||||
backupDir := os.Getenv("BACKUP_DIR")
|
||||
if backupDir == "" {
|
||||
backupDir = "/app/backups"
|
||||
if _, err := os.Stat(backupDir); os.IsNotExist(err) {
|
||||
backupDir = "backups"
|
||||
}
|
||||
}
|
||||
if err := os.MkdirAll(backupDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create backup directory: %v", err)
|
||||
}
|
||||
dbFileName := filepath.Base(dbPath)
|
||||
backupFileName := fmt.Sprintf("%s.backup.%s", dbFileName, time.Now().Format("20060102_150405"))
|
||||
backupFile := filepath.Join(backupDir, backupFileName)
|
||||
data, err := os.ReadFile(dbPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read database for backup: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(backupFile, data, 0600); err != nil {
|
||||
return fmt.Errorf("failed to create database backup: %v", err)
|
||||
}
|
||||
fmt.Printf("Created database backup at: %s\n", backupFile)
|
||||
}
|
||||
// --- End Backup Logic ---
|
||||
|
||||
// --- Table Recreation Logic for SQLite ---
|
||||
if err := tx.Exec("PRAGMA foreign_keys = OFF").Error; err != nil {
|
||||
return fmt.Errorf("failed to disable foreign keys: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := tx.Exec("PRAGMA foreign_keys = ON").Error; err != nil {
|
||||
fmt.Printf("Warning: failed to re-enable foreign keys: %v\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Helper function for table recreation
|
||||
recreateTable := func(tableName, createSQL string) error {
|
||||
fmt.Printf("Recreating table %s...\n", tableName)
|
||||
oldTableName := fmt.Sprintf("_%s_old", tableName)
|
||||
|
||||
// Rename old table
|
||||
if err := tx.Exec(fmt.Sprintf("ALTER TABLE %s RENAME TO %s", tableName, oldTableName)).Error; err == nil {
|
||||
fmt.Printf("Renamed %s to %s.\n", tableName, oldTableName)
|
||||
|
||||
// Create new table using raw SQL
|
||||
fmt.Printf("Creating new %s table...\n", tableName)
|
||||
if err := tx.Exec(createSQL).Error; err != nil {
|
||||
return fmt.Errorf("failed to create new %s table: %w", tableName, err)
|
||||
}
|
||||
fmt.Printf("New %s table created.\n", tableName)
|
||||
|
||||
// Copy data
|
||||
fmt.Printf("Copying data to new %s table...\n", tableName)
|
||||
// IMPORTANT: Ensure column order/names match if schema changed beyond types/defaults
|
||||
if err := tx.Exec(fmt.Sprintf("INSERT INTO %s SELECT * FROM %s", tableName, oldTableName)).Error; err != nil {
|
||||
return fmt.Errorf("failed to copy data to new %s table: %w", tableName, err)
|
||||
}
|
||||
fmt.Printf("Data copied to %s.\n", tableName)
|
||||
|
||||
// Drop old table
|
||||
if err := tx.Exec(fmt.Sprintf("DROP TABLE %s", oldTableName)).Error; err != nil {
|
||||
return fmt.Errorf("failed to drop old %s table: %w", tableName, err)
|
||||
}
|
||||
fmt.Printf("Successfully recreated %s.\n", tableName)
|
||||
} else {
|
||||
// Check if rename failed because table doesn't exist (fresh install)
|
||||
var tableExists int
|
||||
tx.Raw(fmt.Sprintf("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='%s'", tableName)).Scan(&tableExists)
|
||||
if tableExists == 0 {
|
||||
fmt.Printf("%s table does not exist, creating.\n", tableName)
|
||||
if err := tx.Exec(createSQL).Error; err != nil { // Create table directly
|
||||
return fmt.Errorf("failed to create new %s table: %w", tableName, err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("failed to rename %s: %w", tableName, err) // Real rename error
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recreate tables
|
||||
if err := recreateTable("notification_services", createNotificationServicesSQL); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := recreateTable("auth_providers", createAuthProvidersSQL); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := recreateTable("transfer_configs", createTransferConfigsSQL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// --- End Table Recreation Logic ---
|
||||
|
||||
// Create audit log entry
|
||||
now := time.Now()
|
||||
details, auditErr := json.Marshal(map[string]interface{}{
|
||||
"tables_affected": []string{"notification_services", "auth_providers", "transfer_configs"},
|
||||
"columns_altered": []string{"is_enabled", "enabled", "skip_processed_files"},
|
||||
"new_type": "*bool (pointer to boolean)",
|
||||
"method": "Table recreation (SQLite - Raw SQL)",
|
||||
"message": "Changed boolean columns with default:true to pointers to handle false values correctly with GORM.",
|
||||
})
|
||||
if auditErr != nil {
|
||||
fmt.Printf("Warning: Failed to marshal audit log details: %v\n", auditErr)
|
||||
}
|
||||
|
||||
if auditErr == nil {
|
||||
if auditExecErr := tx.Exec(`
|
||||
INSERT INTO audit_logs (action, entity_type, entity_id, user_id, details, created_at, updated_at, timestamp)
|
||||
VALUES ('schema_update', 'multiple_tables', 0, 1, ?, ?, ?, ?)
|
||||
`, string(details), now, now, now).Error; auditExecErr != nil {
|
||||
fmt.Printf("Warning: Failed to insert audit log: %v\n", auditExecErr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
// Rollback is complex and risky with table recreation. Log skip.
|
||||
now := time.Now()
|
||||
details, err := json.Marshal(map[string]interface{}{
|
||||
"migration_id": "012_alter_boolean_defaults",
|
||||
"message": "Skipping rollback of boolean column type changes (via table recreation) due to complexity/potential data loss.",
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: Failed to marshal rollback audit log details: %v\n", err)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if auditExecErr := tx.Exec(`
|
||||
INSERT INTO audit_logs (action, entity_type, entity_id, user_id, details, created_at, updated_at, timestamp)
|
||||
VALUES ('migration_rollback', 'multiple_tables', 0, 1, ?, ?, ?, ?)
|
||||
`, string(details), now, now, now).Error; auditExecErr != nil {
|
||||
fmt.Printf("Warning: Failed to insert rollback audit log: %v\n", auditExecErr)
|
||||
}
|
||||
}
|
||||
fmt.Println("Rollback for migration 012_alter_boolean_defaults skipped for safety.")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ func GetMigrations(db *gorm.DB) *gormigrate.Gormigrate {
|
||||
AddRcloneTables(), // 009
|
||||
AddRcloneCommandToConfig(), // 010
|
||||
AddAuthProviders(), // 011
|
||||
AlterBooleanDefaults(), // 012
|
||||
)
|
||||
|
||||
return gormigrate.New(db, gormigrate.DefaultOptions, migrations)
|
||||
|
||||
@@ -12,7 +12,7 @@ type NotificationService struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Type string `json:"type" gorm:"not null"` // email, webhook
|
||||
IsEnabled bool `json:"is_enabled" gorm:"default:true"`
|
||||
IsEnabled *bool `json:"is_enabled" gorm:"default:true"`
|
||||
Config map[string]string `json:"config" gorm:"-"`
|
||||
ConfigJSON string `json:"-" gorm:"column:config"`
|
||||
Description string `json:"description"`
|
||||
@@ -69,6 +69,8 @@ func (db *DB) GetNotificationServices(onlyEnabled bool) ([]NotificationService,
|
||||
query := db.DB
|
||||
|
||||
if onlyEnabled {
|
||||
// When using a pointer, we need to explicitly check for true
|
||||
// GORM handles the underlying SQL correctly for different dialects
|
||||
query = query.Where("is_enabled = ?", true)
|
||||
}
|
||||
|
||||
@@ -102,3 +104,21 @@ func (db *DB) UpdateNotificationService(service *NotificationService) error {
|
||||
func (db *DB) DeleteNotificationService(id uint) error {
|
||||
return db.Delete(&NotificationService{}, id).Error
|
||||
}
|
||||
|
||||
// --- NotificationService Helper Methods ---
|
||||
|
||||
// GetIsEnabled returns the value of IsEnabled with a default if nil
|
||||
func (n *NotificationService) GetIsEnabled() bool {
|
||||
if n.IsEnabled == nil {
|
||||
// If the pointer is nil, GORM might not have set it,
|
||||
// or it was explicitly set to nil. We assume the DB default (true)
|
||||
// if it's nil, aligning with the original gorm tag default.
|
||||
return true
|
||||
}
|
||||
return *n.IsEnabled
|
||||
}
|
||||
|
||||
// SetIsEnabled sets the IsEnabled field
|
||||
func (n *NotificationService) SetIsEnabled(value bool) {
|
||||
n.IsEnabled = &value
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ type TransferConfig struct {
|
||||
SourceShare string `form:"source_share"`
|
||||
SourceDomain string `form:"source_domain"`
|
||||
// FTP source fields
|
||||
SourcePassiveMode *bool `gorm:"default:true" form:"source_passive_mode"`
|
||||
SourcePassiveMode *bool `gorm:"default:true" form:"source_passive_mode"` // Already a pointer, no change needed here
|
||||
// OneDrive and Google Drive source fields
|
||||
SourceClientID string `form:"source_client_id"`
|
||||
SourceClientSecret string `form:"source_client_secret" gorm:"-"` // Not stored in DB, only used for form
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/starfleetcptn/gomft/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func createTestConfig(enabled bool) *config.Config {
|
||||
return &config.Config{
|
||||
BaseURL: "http://localhost:8080",
|
||||
Email: config.EmailConfig{
|
||||
Enabled: enabled,
|
||||
Host: "smtp.example.com",
|
||||
Port: 587,
|
||||
Username: "user",
|
||||
Password: "password",
|
||||
FromEmail: "noreply@example.com",
|
||||
FromName: "GoMFT Test",
|
||||
RequireAuth: true,
|
||||
EnableTLS: true,
|
||||
ReplyTo: "support@example.com",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewService(t *testing.T) {
|
||||
cfg := createTestConfig(true)
|
||||
service := NewService(cfg)
|
||||
require.NotNil(t, service)
|
||||
assert.Equal(t, cfg, service.Config)
|
||||
}
|
||||
|
||||
func TestGeneratePasswordResetEmailHTML(t *testing.T) {
|
||||
cfg := createTestConfig(true)
|
||||
service := NewService(cfg)
|
||||
|
||||
testUsername := "testuser"
|
||||
testResetLink := "http://localhost:8080/reset-password?token=testtoken123"
|
||||
testAppName := "GoMFT"
|
||||
testYear := time.Now().Year()
|
||||
|
||||
data := map[string]interface{}{
|
||||
"Username": testUsername,
|
||||
"ResetLink": testResetLink,
|
||||
"AppName": testAppName,
|
||||
"Year": testYear,
|
||||
"ExpiresHours": 0.25,
|
||||
}
|
||||
|
||||
htmlContent, err := service.generatePasswordResetEmailHTML(data)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, htmlContent)
|
||||
|
||||
// Basic checks for content presence
|
||||
assert.Contains(t, htmlContent, "Reset Your Password")
|
||||
assert.Contains(t, htmlContent, fmt.Sprintf("Hello %s", testUsername))
|
||||
assert.Contains(t, htmlContent, testResetLink) // Check link appears (both in button and text)
|
||||
assert.Contains(t, htmlContent, fmt.Sprintf("href=\"%s\"", testResetLink))
|
||||
assert.Contains(t, htmlContent, fmt.Sprintf("© %d %s", testYear, testAppName))
|
||||
assert.Contains(t, htmlContent, "This link will expire in 15 minutes.")
|
||||
|
||||
// Test without username
|
||||
dataNoUser := map[string]interface{}{
|
||||
"ResetLink": testResetLink,
|
||||
"AppName": testAppName,
|
||||
"Year": testYear,
|
||||
"ExpiresHours": 0.25,
|
||||
}
|
||||
htmlContentNoUser, err := service.generatePasswordResetEmailHTML(dataNoUser)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, htmlContentNoUser, "Hello,") // Should just say Hello,
|
||||
assert.NotContains(t, htmlContentNoUser, fmt.Sprintf("Hello %s", testUsername))
|
||||
}
|
||||
|
||||
func TestGenerateTestEmailHTML(t *testing.T) {
|
||||
cfg := createTestConfig(true)
|
||||
service := NewService(cfg)
|
||||
|
||||
testSubject := "My Test Subject"
|
||||
testMessage := "This is the test message body."
|
||||
testAppName := "GoMFT"
|
||||
testYear := time.Now().Year()
|
||||
testCurrentTime := time.Now().Format(time.RFC1123Z) // Use the same format
|
||||
|
||||
data := map[string]interface{}{
|
||||
"Subject": testSubject,
|
||||
"Message": testMessage,
|
||||
"AppName": testAppName,
|
||||
"Year": testYear,
|
||||
"SMTPServer": cfg.Email.Host,
|
||||
"SMTPPort": cfg.Email.Port,
|
||||
"FromEmail": cfg.Email.FromEmail,
|
||||
"CurrentTime": testCurrentTime,
|
||||
}
|
||||
|
||||
htmlContent, err := service.generateTestEmailHTML(data)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, htmlContent)
|
||||
|
||||
// Basic checks for content presence
|
||||
assert.Contains(t, htmlContent, fmt.Sprintf("<title>%s</title>", testSubject))
|
||||
assert.Contains(t, htmlContent, fmt.Sprintf("<h1>%s</h1>", testSubject))
|
||||
assert.Contains(t, htmlContent, testMessage)
|
||||
assert.Contains(t, htmlContent, fmt.Sprintf("%s:%d", cfg.Email.Host, cfg.Email.Port))
|
||||
assert.Contains(t, htmlContent, cfg.Email.FromEmail)
|
||||
assert.Contains(t, htmlContent, testCurrentTime)
|
||||
assert.Contains(t, htmlContent, fmt.Sprintf("© %d %s", testYear, testAppName))
|
||||
}
|
||||
|
||||
func TestSendPasswordResetEmail_Disabled(t *testing.T) {
|
||||
cfg := createTestConfig(false) // Email disabled
|
||||
service := NewService(cfg)
|
||||
|
||||
toEmail := "test@example.com"
|
||||
username := "testuser"
|
||||
resetToken := "disabledtoken123"
|
||||
|
||||
err := service.SendPasswordResetEmail(toEmail, username, resetToken)
|
||||
require.Error(t, err)
|
||||
|
||||
expectedErrorSubstr := fmt.Sprintf("email service is disabled, reset link would be: %s/reset-password?token=%s",
|
||||
cfg.BaseURL, resetToken)
|
||||
assert.Contains(t, err.Error(), expectedErrorSubstr)
|
||||
}
|
||||
|
||||
func TestSendTestEmail_Disabled(t *testing.T) {
|
||||
cfg := createTestConfig(false) // Email disabled
|
||||
service := NewService(cfg)
|
||||
|
||||
toEmail := "test@example.com"
|
||||
|
||||
err := service.SendTestEmail(toEmail, "Test Subject", "Test Message")
|
||||
require.Error(t, err)
|
||||
assert.EqualError(t, err, "email service is disabled")
|
||||
}
|
||||
|
||||
// --- Placeholder/TODO for more complex tests ---
|
||||
|
||||
// TODO: TestSendPasswordResetEmail_Enabled - Requires mocking sendEmail or SMTP interactions
|
||||
// TODO: TestSendTestEmail_Enabled - Requires mocking sendEmail or SMTP interactions
|
||||
// TODO: TestSendEmail - Requires extensive mocking of net/smtp package
|
||||
|
||||
// Example structure for testing enabled path (without actual sending/mocking)
|
||||
// This verifies the function prepares the correct data before calling sendEmail
|
||||
func TestSendPasswordResetEmail_Enabled_DataPreparation(t *testing.T) {
|
||||
cfg := createTestConfig(true)
|
||||
service := NewService(cfg)
|
||||
|
||||
// We need a way to intercept the call to sendEmail or verify its inputs
|
||||
// For now, we just check that no error occurs up to that point
|
||||
// and that the HTML generation works (implicitly tested by TestGeneratePasswordResetEmailHTML)
|
||||
|
||||
toEmail := "recipient@example.com"
|
||||
username := "testuser-enabled"
|
||||
resetToken := "enabledtoken456"
|
||||
|
||||
// If generatePasswordResetEmailHTML works, this call should proceed
|
||||
// without error until the actual sendEmail call (which we aren't testing here)
|
||||
// A full test would mock sendEmail and verify the arguments passed to it.
|
||||
err := service.SendPasswordResetEmail(toEmail, username, resetToken)
|
||||
|
||||
// In a real scenario without mocking, this might fail if SMTP connection fails.
|
||||
// For this basic check, we assume HTML generation is the main potential failure point *before* sendEmail.
|
||||
// If TestGeneratePasswordResetEmailHTML passes, we expect no error *from generation*.
|
||||
// We cannot assert assert.NoError(t, err) reliably without mocking sendEmail.
|
||||
t.Logf("SendPasswordResetEmail (enabled) returned: %v (expected success or SMTP error)", err)
|
||||
// Asserting that the error, if any, is NOT related to template generation could be a weak check.
|
||||
if err != nil {
|
||||
assert.False(t, strings.Contains(err.Error(), "template"), "Error should be SMTP related, not template related")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendTestEmail_Enabled_DataPreparation(t *testing.T) {
|
||||
cfg := createTestConfig(true)
|
||||
service := NewService(cfg)
|
||||
|
||||
toEmail := "recipient@example.com"
|
||||
subject := "Specific Test Subject"
|
||||
message := "Specific test message."
|
||||
|
||||
// Test with specific subject and message
|
||||
err := service.SendTestEmail(toEmail, subject, message)
|
||||
t.Logf("SendTestEmail (enabled, specific) returned: %v (expected success or SMTP error)", err)
|
||||
if err != nil {
|
||||
assert.False(t, strings.Contains(err.Error(), "template"), "Error should be SMTP related, not template related")
|
||||
}
|
||||
|
||||
// Test with default subject and message
|
||||
errDefault := service.SendTestEmail(toEmail, "", "")
|
||||
t.Logf("SendTestEmail (enabled, default) returned: %v (expected success or SMTP error)", errDefault)
|
||||
if errDefault != nil {
|
||||
assert.False(t, strings.Contains(errDefault.Error(), "template"), "Error should be SMTP related, not template related")
|
||||
}
|
||||
// A full test would mock sendEmail and verify the subject/message passed (checking defaults).
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package rclone_service
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -14,27 +15,36 @@ import (
|
||||
"github.com/starfleetcptn/gomft/internal/db"
|
||||
)
|
||||
|
||||
// --- Mockable os/exec ---
|
||||
|
||||
// execCommandContext allows mocking exec.CommandContext during tests.
|
||||
var execCommandContext = exec.CommandContext
|
||||
|
||||
// cmdCombinedOutput allows mocking the CombinedOutput method during tests.
|
||||
var cmdCombinedOutput = (*exec.Cmd).CombinedOutput
|
||||
|
||||
// cmdRun allows mocking the Run method during tests.
|
||||
var cmdRun = (*exec.Cmd).Run
|
||||
|
||||
// --- Function Implementation ---
|
||||
|
||||
// TestRcloneConnection attempts to connect to a provider using temporary config created via `rclone config create`.
|
||||
// It returns success (bool), a message (string), and an error.
|
||||
func TestRcloneConnection(config db.TransferConfig, providerType string, dbInstance *db.DB) (bool, string, error) {
|
||||
var remoteName string
|
||||
var remotePath string
|
||||
var provider string
|
||||
// Removed bucket, share as they are not used in current rclone config create args
|
||||
var host, user, pass, keyFile, region, accessKey, secretKey, endpoint, domain, clientID, clientSecret, driveID, teamDrive string
|
||||
var port int
|
||||
// Add other necessary fields like passiveMode, readOnly etc. if needed by rclone config create for specific types
|
||||
var err error
|
||||
|
||||
// Create a temporary directory for the config file
|
||||
tempDir, err := os.MkdirTemp("", "gomft-rclone-test-")
|
||||
if err != nil {
|
||||
return false, "Failed to create temp directory for rclone config", err
|
||||
}
|
||||
defer os.RemoveAll(tempDir) // Clean up the temp directory
|
||||
defer os.RemoveAll(tempDir)
|
||||
tempConfigPath := filepath.Join(tempDir, "rclone_test.conf")
|
||||
|
||||
// Extract parameters based on providerType
|
||||
if providerType == "source" {
|
||||
remoteName = "testSource"
|
||||
remotePath = config.SourcePath
|
||||
@@ -44,18 +54,15 @@ func TestRcloneConnection(config db.TransferConfig, providerType string, dbInsta
|
||||
user = config.SourceUser
|
||||
pass = config.SourcePassword
|
||||
keyFile = config.SourceKeyFile
|
||||
// bucket = config.SourceBucket // Removed - Not used in create args yet
|
||||
region = config.SourceRegion
|
||||
accessKey = config.SourceAccessKey
|
||||
secretKey = config.SourceSecretKey
|
||||
endpoint = config.SourceEndpoint
|
||||
// share = config.SourceShare // Removed - Not used in create args yet
|
||||
domain = config.SourceDomain
|
||||
clientID = config.SourceClientID
|
||||
clientSecret = config.SourceClientSecret
|
||||
driveID = config.SourceDriveID
|
||||
teamDrive = config.SourceTeamDrive
|
||||
// Extract other source fields as needed (e.g., passiveMode, readOnly)
|
||||
} else if providerType == "destination" {
|
||||
remoteName = "testDest"
|
||||
remotePath = config.DestinationPath
|
||||
@@ -65,46 +72,38 @@ func TestRcloneConnection(config db.TransferConfig, providerType string, dbInsta
|
||||
user = config.DestUser
|
||||
pass = config.DestPassword
|
||||
keyFile = config.DestKeyFile
|
||||
// bucket = config.DestBucket // Removed - Not used in create args yet
|
||||
region = config.DestRegion
|
||||
accessKey = config.DestAccessKey
|
||||
secretKey = config.DestSecretKey
|
||||
endpoint = config.DestEndpoint
|
||||
// share = config.DestShare // Removed - Not used in create args yet
|
||||
domain = config.DestDomain
|
||||
clientID = config.DestClientID
|
||||
clientSecret = config.DestClientSecret
|
||||
driveID = config.DestDriveID
|
||||
teamDrive = config.DestTeamDrive
|
||||
// Extract other dest fields as needed (e.g., passiveMode, readOnly)
|
||||
} else {
|
||||
return false, "Invalid provider type specified", fmt.Errorf("unknown provider type: %s", providerType)
|
||||
}
|
||||
|
||||
// Get the rclone path from the environment variable or use the default path
|
||||
rclonePath := os.Getenv("RCLONE_PATH")
|
||||
if rclonePath == "" {
|
||||
rclonePath = "rclone"
|
||||
}
|
||||
|
||||
// --- Use `rclone config create` to generate the temporary config section ---
|
||||
createArgs := []string{
|
||||
"config", "create", remoteName, provider,
|
||||
"--config", tempConfigPath,
|
||||
"--non-interactive",
|
||||
"--log-level", "DEBUG", // Use DEBUG for create to see details if it fails
|
||||
"--log-level", "DEBUG",
|
||||
}
|
||||
|
||||
// --- Declare variables for lsd command *before* the switch/goto ---
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
var lsdArgs []string
|
||||
var stdout, stderr bytes.Buffer
|
||||
var lsdCmd *exec.Cmd
|
||||
var createCmd *exec.Cmd // Declare createCmd here as well
|
||||
var createCmd *exec.Cmd
|
||||
|
||||
// Add provider-specific arguments
|
||||
// Mirroring logic from db.GenerateRcloneConfig but passing args to CLI
|
||||
switch provider {
|
||||
case "sftp":
|
||||
createArgs = append(createArgs, "host", host, "user", user)
|
||||
@@ -112,20 +111,18 @@ func TestRcloneConnection(config db.TransferConfig, providerType string, dbInsta
|
||||
createArgs = append(createArgs, "port", fmt.Sprintf("%d", port))
|
||||
}
|
||||
if pass != "" {
|
||||
createArgs = append(createArgs, "pass", pass) // Pass directly
|
||||
createArgs = append(createArgs, "pass", pass)
|
||||
}
|
||||
if keyFile != "" {
|
||||
createArgs = append(createArgs, "key_file", keyFile)
|
||||
// Note: Passphrase for keyfile might need 'pass' too, rclone handles this context.
|
||||
}
|
||||
// Add other SFTP args like ssh_agent, use_insecure_cipher etc. if needed
|
||||
case "s3":
|
||||
createArgs = append(createArgs, "provider", "AWS", "env_auth", "false") // Assume AWS for generic S3
|
||||
createArgs = append(createArgs, "provider", "AWS", "env_auth", "false")
|
||||
if accessKey != "" {
|
||||
createArgs = append(createArgs, "access_key_id", accessKey)
|
||||
}
|
||||
if secretKey != "" {
|
||||
createArgs = append(createArgs, "secret_access_key", secretKey) // Pass directly
|
||||
createArgs = append(createArgs, "secret_access_key", secretKey)
|
||||
}
|
||||
if region != "" {
|
||||
createArgs = append(createArgs, "region", region)
|
||||
@@ -133,19 +130,18 @@ func TestRcloneConnection(config db.TransferConfig, providerType string, dbInsta
|
||||
if endpoint != "" {
|
||||
createArgs = append(createArgs, "endpoint", endpoint)
|
||||
}
|
||||
// Add other S3 args like acl, storage_class if needed
|
||||
case "minio":
|
||||
createArgs = append(createArgs, "provider", "Minio", "env_auth", "false")
|
||||
if accessKey != "" {
|
||||
createArgs = append(createArgs, "access_key_id", accessKey)
|
||||
}
|
||||
if secretKey != "" {
|
||||
createArgs = append(createArgs, "secret_access_key", secretKey) // Pass directly
|
||||
createArgs = append(createArgs, "secret_access_key", secretKey)
|
||||
}
|
||||
if endpoint != "" {
|
||||
createArgs = append(createArgs, "endpoint", endpoint)
|
||||
}
|
||||
if region != "" { // Minio might ignore region, but add if present
|
||||
if region != "" {
|
||||
createArgs = append(createArgs, "region", region)
|
||||
}
|
||||
case "ftp":
|
||||
@@ -156,13 +152,12 @@ func TestRcloneConnection(config db.TransferConfig, providerType string, dbInsta
|
||||
if pass != "" {
|
||||
createArgs = append(createArgs, "pass", pass)
|
||||
}
|
||||
// Add explicit_tls, passive_mode based on config boolean pointers
|
||||
if config.GetSourcePassiveMode() || config.GetDestPassiveMode() { // Check based on providerType
|
||||
if config.GetSourcePassiveMode() || config.GetDestPassiveMode() {
|
||||
createArgs = append(createArgs, "passive_mode", "true")
|
||||
} else {
|
||||
createArgs = append(createArgs, "passive_mode", "false")
|
||||
}
|
||||
createArgs = append(createArgs, "explicit_tls", "true") // Defaulting to true
|
||||
createArgs = append(createArgs, "explicit_tls", "true")
|
||||
case "smb":
|
||||
createArgs = append(createArgs, "host", host, "user", user)
|
||||
if port != 0 {
|
||||
@@ -175,7 +170,7 @@ func TestRcloneConnection(config db.TransferConfig, providerType string, dbInsta
|
||||
createArgs = append(createArgs, "domain", domain)
|
||||
}
|
||||
case "webdav":
|
||||
createArgs = append(createArgs, "url", endpoint, "vendor", "other", "user", user) // Default vendor
|
||||
createArgs = append(createArgs, "url", endpoint, "vendor", "other", "user", user)
|
||||
if pass != "" {
|
||||
createArgs = append(createArgs, "pass", pass)
|
||||
}
|
||||
@@ -184,84 +179,70 @@ func TestRcloneConnection(config db.TransferConfig, providerType string, dbInsta
|
||||
if pass != "" {
|
||||
createArgs = append(createArgs, "pass", pass)
|
||||
}
|
||||
// Add cases for gdrive, gphotos - these are complex due to token handling
|
||||
// For testing, they might require pre-existing tokens or manual auth flow outside this scope.
|
||||
// Passing client_id/secret might work for initial setup but not subsequent tests without a token.
|
||||
case "gdrive":
|
||||
createArgs = append(createArgs, "scope", "drive") // Default scope
|
||||
createArgs = append(createArgs, "scope", "drive")
|
||||
if clientID != "" {
|
||||
createArgs = append(createArgs, "client_id", clientID)
|
||||
}
|
||||
if clientSecret != "" {
|
||||
createArgs = append(createArgs, "client_secret", clientSecret)
|
||||
}
|
||||
if driveID != "" { // Use root_folder_id for specific drive/folder
|
||||
if driveID != "" {
|
||||
createArgs = append(createArgs, "root_folder_id", driveID)
|
||||
}
|
||||
if teamDrive != "" {
|
||||
createArgs = append(createArgs, "team_drive", teamDrive)
|
||||
}
|
||||
// Cannot pass token directly via 'config create' easily for testing non-interactive flow.
|
||||
log.Println("Warning: Google Drive test may require pre-existing token or manual auth.")
|
||||
|
||||
case "gphotos":
|
||||
// Similar complexity to gdrive regarding tokens
|
||||
if clientID != "" {
|
||||
createArgs = append(createArgs, "client_id", clientID)
|
||||
}
|
||||
if clientSecret != "" {
|
||||
createArgs = append(createArgs, "client_secret", clientSecret)
|
||||
}
|
||||
// Add read_only, start_year, include_archived based on config
|
||||
// Cannot pass token directly via 'config create' easily for testing non-interactive flow.
|
||||
log.Println("Warning: Google Photos test may require pre-existing token or manual auth.")
|
||||
|
||||
case "local":
|
||||
// 'rclone config create' might not be needed or work well for 'local' type.
|
||||
// Write a minimal config manually for local.
|
||||
localConfigContent := fmt.Sprintf("[%s]\ntype = local\nnounc = true\n", remoteName)
|
||||
if err := os.WriteFile(tempConfigPath, []byte(localConfigContent), 0600); err != nil {
|
||||
return false, fmt.Sprintf("Failed to write temporary local config: %v", err), err
|
||||
}
|
||||
goto RunLsd // Skip rclone config create for local
|
||||
goto RunLsd
|
||||
default:
|
||||
return false, fmt.Sprintf("Provider type '%s' not yet supported for testing via 'rclone config create'", provider), fmt.Errorf("unsupported provider")
|
||||
}
|
||||
|
||||
log.Printf("Executing rclone config create command: %s %s", rclonePath, strings.Join(createArgs, " "))
|
||||
createCmd = exec.Command(rclonePath, createArgs...) // Assign value using =
|
||||
if output, err := createCmd.CombinedOutput(); err != nil {
|
||||
// Log the config file content on error for debugging
|
||||
createCmd = execCommandContext(context.Background(), rclonePath, createArgs...)
|
||||
// Use the mockable function variable
|
||||
if output, err := cmdCombinedOutput(createCmd); err != nil {
|
||||
configContentBytes, _ := os.ReadFile(tempConfigPath)
|
||||
log.Printf("Temp config content on create error:\n---\n%s\n---", string(configContentBytes))
|
||||
return false, fmt.Sprintf("Failed to create temp config section: %v\nOutput: %s", err, string(output)), err
|
||||
}
|
||||
log.Printf("Successfully created temp config section for %s", remoteName)
|
||||
|
||||
RunLsd: // Label to jump to for local type
|
||||
RunLsd:
|
||||
|
||||
// --- Execute `rclone lsd` using the temporary config ---
|
||||
ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second) // Assign values
|
||||
ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
lsdArgs = []string{ // Assign value
|
||||
lsdArgs = []string{
|
||||
"--config", tempConfigPath,
|
||||
"lsd",
|
||||
fmt.Sprintf("%s:%s", remoteName, remotePath),
|
||||
"--low-level-retries", "1",
|
||||
"--retries", "1",
|
||||
// Add -vv for verbose logging during test if needed
|
||||
// "-vv",
|
||||
}
|
||||
|
||||
log.Printf("Executing rclone lsd command: %s %s", rclonePath, strings.Join(lsdArgs, " "))
|
||||
lsdCmd = exec.CommandContext(ctx, rclonePath, lsdArgs...) // Assign value
|
||||
lsdCmd = execCommandContext(ctx, rclonePath, lsdArgs...)
|
||||
|
||||
// var stdout, stderr bytes.Buffer // Moved declaration up
|
||||
lsdCmd.Stdout = &stdout // Assign buffer
|
||||
lsdCmd.Stderr = &stderr // Assign buffer
|
||||
lsdCmd.Stdout = &stdout
|
||||
lsdCmd.Stderr = &stderr
|
||||
|
||||
err = lsdCmd.Run() // Assign error
|
||||
// Use the mockable function variable
|
||||
err = cmdRun(lsdCmd)
|
||||
|
||||
stdoutStr := stdout.String()
|
||||
stderrStr := stderr.String()
|
||||
@@ -269,14 +250,16 @@ RunLsd: // Label to jump to for local type
|
||||
log.Printf("Rclone lsd stdout:\n%s", stdoutStr)
|
||||
log.Printf("Rclone lsd stderr:\n%s", stderrStr)
|
||||
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return false, "Connection test timed out after 30 seconds.", ctx.Err()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Try to provide a more specific error message based on lsd output
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
return false, "Connection test timed out after 30 seconds.", context.DeadlineExceeded
|
||||
}
|
||||
// Check ctx.Err() as a fallback - This check might be redundant now
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return false, "Connection test timed out after 30 seconds.", ctx.Err()
|
||||
}
|
||||
|
||||
errMsg := fmt.Sprintf("Connection test failed: %v. Stderr: %s", err, stderrStr)
|
||||
// Add specific error checks based on stderrStr if needed
|
||||
if strings.Contains(stderrStr, "connect: connection refused") {
|
||||
errMsg = "Connection test failed: Connection refused by host."
|
||||
} else if strings.Contains(stderrStr, "no such host") || strings.Contains(stderrStr, "name resolution error") {
|
||||
@@ -285,12 +268,11 @@ RunLsd: // Label to jump to for local type
|
||||
errMsg = "Connection test failed: Authentication failed (check credentials/permissions)."
|
||||
} else if strings.Contains(stderrStr, "directory not found") {
|
||||
errMsg = "Connection test failed: Directory/Path not found (check path)."
|
||||
} else if strings.Contains(stderrStr, "Couldn't find section") { // Error from rclone config create
|
||||
} else if strings.Contains(stderrStr, "Couldn't find section") {
|
||||
errMsg = "Connection test failed: Invalid parameters provided for provider type."
|
||||
}
|
||||
return false, errMsg, err
|
||||
}
|
||||
|
||||
// If lsd runs without error, the connection is likely okay
|
||||
return true, "Connection test successful!", nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
package rclone_service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/starfleetcptn/gomft/internal/db"
|
||||
)
|
||||
|
||||
// --- Mock os/exec ---
|
||||
|
||||
// Note: The package-level variable 'execCommandContext' is defined in rclone_service.go
|
||||
// This helper function replaces it for the duration of a test.
|
||||
|
||||
// MockExecCommand replaces the package-level execCommandContext variable (defined in rclone_service.go)
|
||||
// with a function provided by the test and returns a function to restore the original.
|
||||
func MockExecCommand(mockFunc func(ctx context.Context, command string, args ...string) *exec.Cmd) (restore func()) {
|
||||
original := execCommandContext
|
||||
execCommandContext = mockFunc
|
||||
return func() { execCommandContext = original }
|
||||
}
|
||||
|
||||
// Helper function to find the actual rclone command within args, skipping flags.
|
||||
func findRcloneCommand(args []string) string {
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
if strings.HasPrefix(arg, "-") {
|
||||
if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
|
||||
i++
|
||||
}
|
||||
continue
|
||||
}
|
||||
return arg
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// --- Tests ---
|
||||
|
||||
func TestTestRcloneConnection_Success_SFTP(t *testing.T) {
|
||||
config := db.TransferConfig{
|
||||
SourceType: "sftp",
|
||||
SourceHost: "testhost",
|
||||
SourceUser: "testuser",
|
||||
SourcePassword: "testpassword",
|
||||
}
|
||||
providerType := "source"
|
||||
var dbInstance *db.DB
|
||||
configCreateCalled := false
|
||||
|
||||
// Mock execCommandContext (only needed to return a basic cmd struct)
|
||||
restoreExec := MockExecCommand(func(ctx context.Context, command string, args ...string) *exec.Cmd {
|
||||
// Return a simple, non-nil command object. The actual execution is mocked below.
|
||||
return exec.Command("echo", "mocked")
|
||||
})
|
||||
defer restoreExec()
|
||||
|
||||
// Mock cmdCombinedOutput for config create
|
||||
originalCombinedOutput := cmdCombinedOutput
|
||||
cmdCombinedOutput = func(c *exec.Cmd) ([]byte, error) {
|
||||
configCreateCalled = true
|
||||
return []byte(""), nil // Simulate success
|
||||
}
|
||||
defer func() { cmdCombinedOutput = originalCombinedOutput }() // Restore original
|
||||
|
||||
// Mock cmdRun for lsd
|
||||
originalRun := cmdRun
|
||||
cmdRun = func(c *exec.Cmd) error {
|
||||
if !configCreateCalled {
|
||||
t.Fatalf("lsd (Run) called before config create")
|
||||
}
|
||||
// Simulate success by returning nil error
|
||||
// We also need to simulate writing to stdout if the main func uses it
|
||||
if stdoutWriter, ok := c.Stdout.(interface{ WriteString(string) (int, error) }); ok {
|
||||
stdoutWriter.WriteString(" -1 2023-01-01 10:00:00 -1 some_dir\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
defer func() { cmdRun = originalRun }() // Restore original
|
||||
|
||||
success, msg, err := TestRcloneConnection(config, providerType, dbInstance)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, but got: %v", err)
|
||||
}
|
||||
if !success {
|
||||
t.Errorf("Expected success=true, but got false. Message: %s", msg)
|
||||
}
|
||||
if msg != "Connection test successful!" {
|
||||
t.Errorf("Expected success message, but got: %q", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestRcloneConnection_ConfigCreateFail(t *testing.T) {
|
||||
config := db.TransferConfig{
|
||||
SourceType: "sftp",
|
||||
SourceHost: "testhost",
|
||||
SourceUser: "testuser",
|
||||
}
|
||||
providerType := "source"
|
||||
var dbInstance *db.DB
|
||||
expectedStderr := "invalid parameters"
|
||||
expectedErr := errors.New("exit status 1")
|
||||
|
||||
// Mock execCommandContext
|
||||
restoreExec := MockExecCommand(func(ctx context.Context, command string, args ...string) *exec.Cmd {
|
||||
return exec.Command("echo", "mocked")
|
||||
})
|
||||
defer restoreExec()
|
||||
|
||||
// Mock cmdCombinedOutput for config create failure
|
||||
originalCombinedOutput := cmdCombinedOutput
|
||||
cmdCombinedOutput = func(c *exec.Cmd) ([]byte, error) {
|
||||
return []byte(expectedStderr), expectedErr // Simulate failure
|
||||
}
|
||||
defer func() { cmdCombinedOutput = originalCombinedOutput }()
|
||||
|
||||
// Mock cmdRun (should not be called)
|
||||
originalRun := cmdRun
|
||||
cmdRun = func(c *exec.Cmd) error {
|
||||
t.Fatalf("lsd (Run) called after config create failure")
|
||||
return errors.New("should not be called")
|
||||
}
|
||||
defer func() { cmdRun = originalRun }()
|
||||
|
||||
success, msg, err := TestRcloneConnection(config, providerType, dbInstance)
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected an error from config create failure, but got nil")
|
||||
} else if !errors.Is(err, expectedErr) {
|
||||
t.Errorf("Expected error %v, got %v", expectedErr, err)
|
||||
}
|
||||
if success {
|
||||
t.Error("Expected success=false for config create failure, but got true")
|
||||
}
|
||||
if !strings.Contains(msg, "Failed to create temp config section") {
|
||||
t.Errorf("Expected message containing 'Failed to create temp config section', got: %q", msg)
|
||||
}
|
||||
// Note: The CombinedOutput mock returns stderr in the output byte slice
|
||||
if !strings.Contains(msg, expectedStderr) {
|
||||
t.Errorf("Expected message containing stderr %q, got: %q", expectedStderr, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestRcloneConnection_LsdTimeout(t *testing.T) {
|
||||
config := db.TransferConfig{
|
||||
SourceType: "sftp",
|
||||
SourceHost: "testhost",
|
||||
SourceUser: "testuser",
|
||||
SourcePassword: "pw",
|
||||
}
|
||||
providerType := "source"
|
||||
var dbInstance *db.DB
|
||||
|
||||
// Mock execCommandContext
|
||||
restoreExec := MockExecCommand(func(ctx context.Context, command string, args ...string) *exec.Cmd {
|
||||
return exec.Command("echo", "mocked")
|
||||
})
|
||||
defer restoreExec()
|
||||
|
||||
// Mock cmdCombinedOutput for config create success
|
||||
originalCombinedOutput := cmdCombinedOutput
|
||||
cmdCombinedOutput = func(c *exec.Cmd) ([]byte, error) {
|
||||
return []byte(""), nil
|
||||
}
|
||||
defer func() { cmdCombinedOutput = originalCombinedOutput }()
|
||||
|
||||
// Mock cmdRun for lsd timeout
|
||||
originalRun := cmdRun
|
||||
cmdRun = func(c *exec.Cmd) error {
|
||||
// Simulate timeout error
|
||||
return context.DeadlineExceeded
|
||||
}
|
||||
defer func() { cmdRun = originalRun }()
|
||||
|
||||
success, msg, err := TestRcloneConnection(config, providerType, dbInstance)
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected a timeout error, but got nil")
|
||||
} else if !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Errorf("Expected context.DeadlineExceeded error, got: %v (type: %T)", err, err)
|
||||
}
|
||||
if success {
|
||||
t.Error("Expected success=false for timeout, but got true")
|
||||
}
|
||||
if !strings.Contains(msg, "Connection test timed out") {
|
||||
t.Errorf("Expected message containing 'Connection test timed out', got: %q", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestRcloneConnection_LsdAuthFail(t *testing.T) {
|
||||
config := db.TransferConfig{
|
||||
SourceType: "sftp",
|
||||
SourceHost: "testhost",
|
||||
SourceUser: "wronguser",
|
||||
SourcePassword: "wrongpassword",
|
||||
}
|
||||
providerType := "source"
|
||||
var dbInstance *db.DB
|
||||
expectedStderr := "authentication failed"
|
||||
expectedErr := errors.New("exit status 1")
|
||||
|
||||
// Mock execCommandContext
|
||||
restoreExec := MockExecCommand(func(ctx context.Context, command string, args ...string) *exec.Cmd {
|
||||
return exec.Command("echo", "mocked")
|
||||
})
|
||||
defer restoreExec()
|
||||
|
||||
// Mock cmdCombinedOutput for config create success
|
||||
originalCombinedOutput := cmdCombinedOutput
|
||||
cmdCombinedOutput = func(c *exec.Cmd) ([]byte, error) {
|
||||
return []byte(""), nil
|
||||
}
|
||||
defer func() { cmdCombinedOutput = originalCombinedOutput }()
|
||||
|
||||
// Mock cmdRun for lsd failure
|
||||
originalRun := cmdRun
|
||||
cmdRun = func(c *exec.Cmd) error {
|
||||
// Simulate failure by returning error and writing to stderr buffer
|
||||
if stderrWriter, ok := c.Stderr.(interface{ WriteString(string) (int, error) }); ok {
|
||||
stderrWriter.WriteString(expectedStderr)
|
||||
}
|
||||
return expectedErr
|
||||
}
|
||||
defer func() { cmdRun = originalRun }()
|
||||
|
||||
success, msg, err := TestRcloneConnection(config, providerType, dbInstance)
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected an error from lsd auth failure, but got nil")
|
||||
} else if !errors.Is(err, expectedErr) {
|
||||
if !strings.Contains(err.Error(), "exit status 1") {
|
||||
t.Errorf("Expected error containing 'exit status 1', got: %v", err)
|
||||
}
|
||||
}
|
||||
if success {
|
||||
t.Error("Expected success=false for lsd auth failure, but got true")
|
||||
}
|
||||
// Check the parsed error message based on stderr
|
||||
if !strings.Contains(msg, "Authentication failed") {
|
||||
t.Errorf("Expected message containing 'Authentication failed', got: %q", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestRcloneConnection_LocalSuccess(t *testing.T) {
|
||||
tempPath := t.TempDir()
|
||||
config := db.TransferConfig{
|
||||
SourceType: "local",
|
||||
SourcePath: tempPath,
|
||||
}
|
||||
providerType := "source"
|
||||
var dbInstance *db.DB
|
||||
|
||||
// Mock execCommandContext (only lsd should be called)
|
||||
restoreExec := MockExecCommand(func(ctx context.Context, command string, args ...string) *exec.Cmd {
|
||||
rcloneCmd := findRcloneCommand(args)
|
||||
if rcloneCmd != "lsd" {
|
||||
t.Fatalf("Unexpected command call for local provider: %q", rcloneCmd)
|
||||
}
|
||||
return exec.Command("echo", "mocked for lsd")
|
||||
})
|
||||
defer restoreExec()
|
||||
|
||||
// Mock cmdCombinedOutput (should not be called)
|
||||
originalCombinedOutput := cmdCombinedOutput
|
||||
cmdCombinedOutput = func(c *exec.Cmd) ([]byte, error) {
|
||||
t.Fatalf("CombinedOutput called unexpectedly for local provider")
|
||||
return nil, errors.New("should not be called")
|
||||
}
|
||||
defer func() { cmdCombinedOutput = originalCombinedOutput }()
|
||||
|
||||
// Mock cmdRun for lsd success
|
||||
originalRun := cmdRun
|
||||
cmdRun = func(c *exec.Cmd) error {
|
||||
// Simulate success
|
||||
if stdoutWriter, ok := c.Stdout.(interface{ WriteString(string) (int, error) }); ok {
|
||||
stdoutWriter.WriteString(" -1 2023-01-01 10:00:00 -1 some_local_dir\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
defer func() { cmdRun = originalRun }()
|
||||
|
||||
success, msg, err := TestRcloneConnection(config, providerType, dbInstance)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error for local success, but got: %v", err)
|
||||
}
|
||||
if !success {
|
||||
t.Errorf("Expected success=true for local success, but got false. Message: %s", msg)
|
||||
}
|
||||
if msg != "Connection test successful!" {
|
||||
t.Errorf("Expected success message, but got: %q", msg)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add more tests for other providers (S3, FTP, WebDAV, etc.)
|
||||
// TODO: Add tests for destination providerType
|
||||
// TODO: Add tests for specific error string parsing (connection refused, dir not found)
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -30,15 +31,17 @@ type NotificationDB interface {
|
||||
|
||||
// Notifier handles sending notifications via various services.
|
||||
type Notifier struct {
|
||||
db NotificationDB // Use the interface type
|
||||
logger *Logger
|
||||
db NotificationDB // Use the interface type
|
||||
logger *Logger
|
||||
skipSSLVerify bool
|
||||
}
|
||||
|
||||
// NewNotifier creates a new Notifier.
|
||||
func NewNotifier(database NotificationDB, logger *Logger) *Notifier { // Accept the interface type
|
||||
func NewNotifier(database NotificationDB, logger *Logger, skipSSLVerify bool) *Notifier { // Accept the interface type and skip flag
|
||||
return &Notifier{
|
||||
db: database,
|
||||
logger: logger,
|
||||
db: database,
|
||||
logger: logger,
|
||||
skipSSLVerify: skipSSLVerify, // Store the flag
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,10 +141,8 @@ func (n *Notifier) sendJobWebhookNotification(job *db.Job, history *db.JobHistor
|
||||
|
||||
n.logger.LogDebug("Webhook headers: %+v", req.Header)
|
||||
|
||||
// Send the request with a timeout
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
// Send the request with a timeout and configured TLS settings
|
||||
client := createHTTPClient(10*time.Second, n.skipSSLVerify)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
n.logger.LogError("Error sending webhook for job %d: %v", job.ID, err)
|
||||
@@ -328,6 +329,26 @@ func generateEmailBody(job *db.Job, history *db.JobHistory, config *db.TransferC
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// createHTTPClient creates an HTTP client with appropriate TLS settings and timeout.
|
||||
func createHTTPClient(timeout time.Duration, skipSSLVerify bool) *http.Client {
|
||||
// Clone the default transport to avoid modifying global state
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
|
||||
if skipSSLVerify {
|
||||
// Ensure TLSClientConfig exists before modifying it
|
||||
if transport.TLSClientConfig == nil {
|
||||
transport.TLSClientConfig = &tls.Config{}
|
||||
}
|
||||
transport.TLSClientConfig.InsecureSkipVerify = true
|
||||
// TODO: Consider adding a log warning here when skipping verification
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: transport,
|
||||
}
|
||||
}
|
||||
|
||||
// sendServiceWebhookNotification sends a webhook notification using a configured notification service.
|
||||
func (n *Notifier) sendServiceWebhookNotification(service *db.NotificationService, job *db.Job, history *db.JobHistory, config *db.TransferConfig, eventType string) error {
|
||||
n.logger.LogDebug("Preparing webhook notification via service %s for job %d", service.Name, job.ID)
|
||||
@@ -413,10 +434,8 @@ func (n *Notifier) sendServiceWebhookNotification(service *db.NotificationServic
|
||||
timeout = 15 * time.Second
|
||||
}
|
||||
|
||||
// Prepare client with timeout
|
||||
client := &http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
// Prepare client with timeout and configured TLS settings
|
||||
client := createHTTPClient(timeout, n.skipSSLVerify)
|
||||
|
||||
// Attempt to send with retries
|
||||
var resp *http.Response
|
||||
@@ -834,7 +853,7 @@ func (n *Notifier) sendPushbulletNotification(service *db.NotificationService, j
|
||||
req.Header.Set("Access-Token", apiKey)
|
||||
|
||||
// Send the request
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
client := createHTTPClient(10*time.Second, n.skipSSLVerify)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send Pushbullet notification: %v", err)
|
||||
@@ -930,7 +949,7 @@ func (n *Notifier) sendNtfyNotification(service *db.NotificationService, job *db
|
||||
}
|
||||
|
||||
// Send the request
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
client := createHTTPClient(10*time.Second, n.skipSSLVerify)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send ntfy notification: %v", err)
|
||||
@@ -1016,7 +1035,7 @@ func (n *Notifier) sendGotifyNotification(service *db.NotificationService, job *
|
||||
req.Header.Set("X-Gotify-Key", token)
|
||||
|
||||
// Send the request
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
client := createHTTPClient(10*time.Second, n.skipSSLVerify)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send Gotify notification: %v", err)
|
||||
@@ -1111,7 +1130,7 @@ func (n *Notifier) sendPushoverNotification(service *db.NotificationService, job
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
// Send the request
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
client := createHTTPClient(10*time.Second, n.skipSSLVerify)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send Pushover notification: %v", err)
|
||||
|
||||
@@ -698,7 +698,7 @@ func (h *Handlers) HandleAuthProviderInit(c *gin.Context) {
|
||||
|
||||
// Get the auth provider
|
||||
provider, err := h.DB.GetAuthProviderByID(c.Request.Context(), uint(providerID))
|
||||
if err != nil || !provider.Enabled {
|
||||
if err != nil || !provider.GetEnabled() { // Use getter
|
||||
h.HandleBadRequest(c, "Provider Not Available", "The authentication provider is not available")
|
||||
return
|
||||
}
|
||||
@@ -862,7 +862,7 @@ func (h *Handlers) HandleAuthProviderCallback(c *gin.Context) {
|
||||
|
||||
// Get the auth provider
|
||||
provider, err := h.DB.GetAuthProviderByID(c.Request.Context(), uint(providerID))
|
||||
if err != nil || !provider.Enabled {
|
||||
if err != nil || !provider.GetEnabled() { // Use getter
|
||||
h.HandleBadRequest(c, "Provider Not Available", "The authentication provider is not available")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -99,8 +99,9 @@ func (h *Handlers) HandleCreateAuthProvider(c *gin.Context) {
|
||||
Scopes: c.PostForm("scopes"),
|
||||
Description: c.PostForm("description"),
|
||||
IconURL: c.PostForm("icon_url"),
|
||||
Enabled: c.PostForm("enabled") == "on",
|
||||
// Enabled will be set using the helper method below
|
||||
}
|
||||
provider.SetEnabled(c.PostForm("enabled") == "on") // Use helper method
|
||||
|
||||
// Process config values based on provider type
|
||||
config := make(map[string]interface{})
|
||||
@@ -211,7 +212,7 @@ func (h *Handlers) HandleUpdateAuthProvider(c *gin.Context) {
|
||||
existingProvider.RedirectURL = c.PostForm("redirect_url")
|
||||
existingProvider.Scopes = c.PostForm("scopes")
|
||||
existingProvider.Description = c.PostForm("description")
|
||||
existingProvider.Enabled = c.PostForm("enabled") == "on"
|
||||
existingProvider.SetEnabled(c.PostForm("enabled") == "on") // Use setter
|
||||
|
||||
// Update the type if changed
|
||||
if providerType := c.PostForm("type"); providerType != "" {
|
||||
|
||||
@@ -43,7 +43,7 @@ func (h *Handlers) HandleSettings(c *gin.Context) {
|
||||
ID: service.ID,
|
||||
Name: service.Name,
|
||||
Type: service.Type,
|
||||
IsEnabled: service.IsEnabled,
|
||||
IsEnabled: service.GetIsEnabled(), // Use getter
|
||||
Config: service.Config,
|
||||
Description: service.Description,
|
||||
EventTriggers: service.EventTriggers,
|
||||
@@ -76,6 +76,8 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
serviceType := c.PostForm("type")
|
||||
description := c.PostForm("description")
|
||||
isEnabled := c.PostForm("is_enabled") == "on"
|
||||
// Read event triggers directly from the form array
|
||||
eventTriggers := c.PostFormArray("event_triggers[]")
|
||||
|
||||
// Validate required fields
|
||||
if name == "" || serviceType == "" {
|
||||
@@ -86,7 +88,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
|
||||
// Create config map based on service type
|
||||
config := make(map[string]string)
|
||||
eventTriggers := make([]string, 0)
|
||||
// eventTriggers slice is now populated above
|
||||
fmt.Println("serviceType", serviceType)
|
||||
switch serviceType {
|
||||
case "email":
|
||||
@@ -102,16 +104,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
config["title_template"] = c.PostForm("pushbullet_title_template")
|
||||
config["body_template"] = c.PostForm("pushbullet_body_template")
|
||||
|
||||
// Build event triggers
|
||||
if c.PostForm("trigger_job_start") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_start")
|
||||
}
|
||||
if c.PostForm("trigger_job_complete") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_complete")
|
||||
}
|
||||
if c.PostForm("trigger_job_error") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_error")
|
||||
}
|
||||
// Event triggers are handled above
|
||||
|
||||
// Validate required fields
|
||||
if config["api_key"] == "" {
|
||||
@@ -121,14 +114,15 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
|
||||
// Create new notification service
|
||||
service := db.NotificationService{
|
||||
Name: name,
|
||||
Type: serviceType,
|
||||
IsEnabled: isEnabled,
|
||||
Name: name,
|
||||
Type: serviceType,
|
||||
// IsEnabled will be set using the helper method below
|
||||
Config: config,
|
||||
Description: description,
|
||||
EventTriggers: eventTriggers,
|
||||
CreatedBy: c.GetUint("userID"),
|
||||
}
|
||||
service.SetIsEnabled(isEnabled) // Use helper method
|
||||
|
||||
// Save to database
|
||||
if err := h.DB.Create(&service).Error; err != nil {
|
||||
@@ -141,7 +135,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
auditDetails := map[string]interface{}{
|
||||
"name": service.Name,
|
||||
"type": service.Type,
|
||||
"is_enabled": service.IsEnabled,
|
||||
"is_enabled": service.GetIsEnabled(), // Use getter
|
||||
"description": service.Description,
|
||||
"event_triggers": eventTriggers,
|
||||
}
|
||||
@@ -171,16 +165,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
config["title_template"] = c.PostForm("ntfy_title_template")
|
||||
config["message_template"] = c.PostForm("ntfy_message_template")
|
||||
|
||||
// Build event triggers
|
||||
if c.PostForm("trigger_job_start") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_start")
|
||||
}
|
||||
if c.PostForm("trigger_job_complete") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_complete")
|
||||
}
|
||||
if c.PostForm("trigger_job_error") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_error")
|
||||
}
|
||||
// Event triggers are handled above
|
||||
|
||||
// Validate required fields
|
||||
if config["server"] == "" || config["topic"] == "" {
|
||||
@@ -190,14 +175,15 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
|
||||
// Create new notification service
|
||||
service := db.NotificationService{
|
||||
Name: name,
|
||||
Type: serviceType,
|
||||
IsEnabled: isEnabled,
|
||||
Name: name,
|
||||
Type: serviceType,
|
||||
// IsEnabled will be set using the helper method below
|
||||
Config: config,
|
||||
Description: description,
|
||||
EventTriggers: eventTriggers,
|
||||
CreatedBy: c.GetUint("userID"),
|
||||
}
|
||||
service.SetIsEnabled(isEnabled) // Use helper method
|
||||
|
||||
// Save to database
|
||||
if err := h.DB.Create(&service).Error; err != nil {
|
||||
@@ -210,7 +196,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
auditDetails := map[string]interface{}{
|
||||
"name": service.Name,
|
||||
"type": service.Type,
|
||||
"is_enabled": service.IsEnabled,
|
||||
"is_enabled": service.GetIsEnabled(), // Use getter
|
||||
"description": service.Description,
|
||||
"event_triggers": eventTriggers,
|
||||
}
|
||||
@@ -238,16 +224,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
config["title_template"] = c.PostForm("gotify_title_template")
|
||||
config["message_template"] = c.PostForm("gotify_message_template")
|
||||
|
||||
// Build event triggers
|
||||
if c.PostForm("trigger_job_start") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_start")
|
||||
}
|
||||
if c.PostForm("trigger_job_complete") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_complete")
|
||||
}
|
||||
if c.PostForm("trigger_job_error") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_error")
|
||||
}
|
||||
// Event triggers are handled above
|
||||
|
||||
// Validate required fields
|
||||
if config["url"] == "" || config["token"] == "" {
|
||||
@@ -257,14 +234,15 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
|
||||
// Create new notification service
|
||||
service := db.NotificationService{
|
||||
Name: name,
|
||||
Type: serviceType,
|
||||
IsEnabled: isEnabled,
|
||||
Name: name,
|
||||
Type: serviceType,
|
||||
// IsEnabled will be set using the helper method below
|
||||
Config: config,
|
||||
Description: description,
|
||||
EventTriggers: eventTriggers,
|
||||
CreatedBy: c.GetUint("userID"),
|
||||
}
|
||||
service.SetIsEnabled(isEnabled) // Use helper method
|
||||
|
||||
// Save to database
|
||||
if err := h.DB.Create(&service).Error; err != nil {
|
||||
@@ -277,7 +255,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
auditDetails := map[string]interface{}{
|
||||
"name": service.Name,
|
||||
"type": service.Type,
|
||||
"is_enabled": service.IsEnabled,
|
||||
"is_enabled": service.GetIsEnabled(), // Use getter
|
||||
"description": service.Description,
|
||||
"event_triggers": eventTriggers,
|
||||
}
|
||||
@@ -307,16 +285,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
config["title_template"] = c.PostForm("pushover_title_template")
|
||||
config["message_template"] = c.PostForm("pushover_message_template")
|
||||
|
||||
// Build event triggers
|
||||
if c.PostForm("trigger_job_start") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_start")
|
||||
}
|
||||
if c.PostForm("trigger_job_complete") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_complete")
|
||||
}
|
||||
if c.PostForm("trigger_job_error") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_error")
|
||||
}
|
||||
// Event triggers are handled above
|
||||
|
||||
// Validate required fields
|
||||
if config["app_token"] == "" || config["user_key"] == "" {
|
||||
@@ -326,14 +295,15 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
|
||||
// Create new notification service
|
||||
service := db.NotificationService{
|
||||
Name: name,
|
||||
Type: serviceType,
|
||||
IsEnabled: isEnabled,
|
||||
Name: name,
|
||||
Type: serviceType,
|
||||
// IsEnabled will be set using the helper method below
|
||||
Config: config,
|
||||
Description: description,
|
||||
EventTriggers: eventTriggers,
|
||||
CreatedBy: c.GetUint("userID"),
|
||||
}
|
||||
service.SetIsEnabled(isEnabled) // Use helper method
|
||||
|
||||
// Save to database
|
||||
if err := h.DB.Create(&service).Error; err != nil {
|
||||
@@ -346,7 +316,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
auditDetails := map[string]interface{}{
|
||||
"name": service.Name,
|
||||
"type": service.Type,
|
||||
"is_enabled": service.IsEnabled,
|
||||
"is_enabled": service.GetIsEnabled(), // Use getter
|
||||
"description": service.Description,
|
||||
"event_triggers": eventTriggers,
|
||||
}
|
||||
@@ -373,26 +343,13 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
config["headers"] = c.PostForm("headers")
|
||||
|
||||
// Add the new webhook fields
|
||||
// Create event triggers array
|
||||
// print all event triggers
|
||||
log.Printf("Event triggers: %v", c.PostForm("trigger_job_start"))
|
||||
log.Printf("Event triggers: %v", c.PostForm("trigger_job_complete"))
|
||||
log.Printf("Event triggers: %v", c.PostForm("trigger_job_error"))
|
||||
if c.PostForm("trigger_job_start") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_start")
|
||||
}
|
||||
if c.PostForm("trigger_job_complete") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_complete")
|
||||
}
|
||||
if c.PostForm("trigger_job_error") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_error")
|
||||
}
|
||||
// Event triggers are handled above
|
||||
|
||||
// Create new notification service with additional fields
|
||||
service := db.NotificationService{
|
||||
Name: name,
|
||||
Type: serviceType,
|
||||
IsEnabled: isEnabled,
|
||||
Name: name,
|
||||
Type: serviceType,
|
||||
// IsEnabled will be set using the helper method below
|
||||
Config: config,
|
||||
Description: description,
|
||||
EventTriggers: eventTriggers,
|
||||
@@ -401,6 +358,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
RetryPolicy: c.PostForm("retry_policy"),
|
||||
CreatedBy: c.GetUint("userID"),
|
||||
}
|
||||
service.SetIsEnabled(isEnabled) // Use helper method
|
||||
|
||||
// Save to database
|
||||
if err := h.DB.Create(&service).Error; err != nil {
|
||||
@@ -413,7 +371,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
auditDetails := map[string]interface{}{
|
||||
"name": service.Name,
|
||||
"type": service.Type,
|
||||
"is_enabled": service.IsEnabled,
|
||||
"is_enabled": service.GetIsEnabled(), // Use getter
|
||||
"description": service.Description,
|
||||
"event_triggers": eventTriggers,
|
||||
"retry_policy": service.RetryPolicy,
|
||||
@@ -442,13 +400,14 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
|
||||
// Create new notification service
|
||||
service := db.NotificationService{
|
||||
Name: name,
|
||||
Type: serviceType,
|
||||
IsEnabled: isEnabled,
|
||||
Name: name,
|
||||
Type: serviceType,
|
||||
// IsEnabled will be set using the helper method below
|
||||
Config: config,
|
||||
Description: description,
|
||||
CreatedBy: c.GetUint("userID"),
|
||||
}
|
||||
service.SetIsEnabled(isEnabled) // Use helper method
|
||||
|
||||
// Save to database
|
||||
if err := h.DB.Create(&service).Error; err != nil {
|
||||
@@ -461,7 +420,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
||||
auditDetails := map[string]interface{}{
|
||||
"name": service.Name,
|
||||
"type": service.Type,
|
||||
"is_enabled": service.IsEnabled,
|
||||
"is_enabled": service.GetIsEnabled(), // Use getter
|
||||
"description": service.Description,
|
||||
}
|
||||
|
||||
@@ -1142,7 +1101,7 @@ func (h *Handlers) HandleNotificationsPage(c *gin.Context) {
|
||||
ID: service.ID,
|
||||
Name: service.Name,
|
||||
Type: service.Type,
|
||||
IsEnabled: service.IsEnabled,
|
||||
IsEnabled: service.GetIsEnabled(), // Use getter
|
||||
Config: service.Config,
|
||||
Description: service.Description,
|
||||
EventTriggers: service.EventTriggers,
|
||||
@@ -1281,7 +1240,7 @@ func (h *Handlers) HandleEditNotificationPage(c *gin.Context) {
|
||||
Name: service.Name,
|
||||
Description: service.Description,
|
||||
Type: service.Type,
|
||||
IsEnabled: service.IsEnabled,
|
||||
IsEnabled: service.GetIsEnabled(), // Use getter
|
||||
EventTriggers: service.EventTriggers,
|
||||
RetryPolicy: service.RetryPolicy,
|
||||
PayloadTemplate: service.PayloadTemplate,
|
||||
@@ -1353,6 +1312,7 @@ func (h *Handlers) HandleUpdateNotificationService(c *gin.Context) {
|
||||
serviceType := c.PostForm("type")
|
||||
description := c.PostForm("description")
|
||||
isEnabled := c.PostForm("is_enabled") == "on"
|
||||
eventTriggers := c.PostFormArray("event_triggers[]")
|
||||
|
||||
// Validate required fields
|
||||
if name == "" || serviceType == "" {
|
||||
@@ -1364,7 +1324,8 @@ func (h *Handlers) HandleUpdateNotificationService(c *gin.Context) {
|
||||
service.Name = name
|
||||
service.Type = serviceType
|
||||
service.Description = description
|
||||
service.IsEnabled = isEnabled
|
||||
service.EventTriggers = eventTriggers
|
||||
service.SetIsEnabled(isEnabled) // Use setter
|
||||
|
||||
// Update type-specific fields based on service type
|
||||
switch serviceType {
|
||||
@@ -1377,18 +1338,7 @@ func (h *Handlers) HandleUpdateNotificationService(c *gin.Context) {
|
||||
service.SecretKey = c.PostForm("secret_key")
|
||||
service.RetryPolicy = c.PostForm("retry_policy")
|
||||
|
||||
// Update event triggers
|
||||
eventTriggers := make([]string, 0)
|
||||
if c.PostForm("trigger_job_start") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_start")
|
||||
}
|
||||
if c.PostForm("trigger_job_complete") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_complete")
|
||||
}
|
||||
if c.PostForm("trigger_job_error") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_error")
|
||||
}
|
||||
service.EventTriggers = eventTriggers
|
||||
// Event triggers are handled above
|
||||
|
||||
case "pushbullet":
|
||||
// Update Pushbullet-specific fields
|
||||
@@ -1397,18 +1347,7 @@ func (h *Handlers) HandleUpdateNotificationService(c *gin.Context) {
|
||||
service.Config["title_template"] = c.PostForm("pushbullet_title_template")
|
||||
service.Config["body_template"] = c.PostForm("pushbullet_body_template")
|
||||
|
||||
// Update event triggers
|
||||
eventTriggers := make([]string, 0)
|
||||
if c.PostForm("trigger_job_start") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_start")
|
||||
}
|
||||
if c.PostForm("trigger_job_complete") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_complete")
|
||||
}
|
||||
if c.PostForm("trigger_job_error") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_error")
|
||||
}
|
||||
service.EventTriggers = eventTriggers
|
||||
// Event triggers are handled above
|
||||
|
||||
case "ntfy":
|
||||
// Update Ntfy-specific fields
|
||||
@@ -1420,18 +1359,7 @@ func (h *Handlers) HandleUpdateNotificationService(c *gin.Context) {
|
||||
service.Config["title_template"] = c.PostForm("ntfy_title_template")
|
||||
service.Config["message_template"] = c.PostForm("ntfy_message_template")
|
||||
|
||||
// Update event triggers
|
||||
eventTriggers := make([]string, 0)
|
||||
if c.PostForm("trigger_job_start") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_start")
|
||||
}
|
||||
if c.PostForm("trigger_job_complete") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_complete")
|
||||
}
|
||||
if c.PostForm("trigger_job_error") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_error")
|
||||
}
|
||||
service.EventTriggers = eventTriggers
|
||||
// Event triggers are handled above
|
||||
|
||||
case "gotify":
|
||||
// Update Gotify-specific fields
|
||||
@@ -1441,18 +1369,7 @@ func (h *Handlers) HandleUpdateNotificationService(c *gin.Context) {
|
||||
service.Config["title_template"] = c.PostForm("gotify_title_template")
|
||||
service.Config["message_template"] = c.PostForm("gotify_message_template")
|
||||
|
||||
// Update event triggers
|
||||
eventTriggers := make([]string, 0)
|
||||
if c.PostForm("trigger_job_start") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_start")
|
||||
}
|
||||
if c.PostForm("trigger_job_complete") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_complete")
|
||||
}
|
||||
if c.PostForm("trigger_job_error") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_error")
|
||||
}
|
||||
service.EventTriggers = eventTriggers
|
||||
// Event triggers are handled above
|
||||
|
||||
case "pushover":
|
||||
// Update Pushover-specific fields
|
||||
@@ -1464,18 +1381,7 @@ func (h *Handlers) HandleUpdateNotificationService(c *gin.Context) {
|
||||
service.Config["title_template"] = c.PostForm("pushover_title_template")
|
||||
service.Config["message_template"] = c.PostForm("pushover_message_template")
|
||||
|
||||
// Update event triggers
|
||||
eventTriggers := make([]string, 0)
|
||||
if c.PostForm("trigger_job_start") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_start")
|
||||
}
|
||||
if c.PostForm("trigger_job_complete") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_complete")
|
||||
}
|
||||
if c.PostForm("trigger_job_error") == "on" {
|
||||
eventTriggers = append(eventTriggers, "job_error")
|
||||
}
|
||||
service.EventTriggers = eventTriggers
|
||||
// Event triggers are handled above
|
||||
}
|
||||
|
||||
// Save to database
|
||||
@@ -1489,7 +1395,7 @@ func (h *Handlers) HandleUpdateNotificationService(c *gin.Context) {
|
||||
auditDetails := map[string]interface{}{
|
||||
"name": service.Name,
|
||||
"type": service.Type,
|
||||
"is_enabled": service.IsEnabled,
|
||||
"is_enabled": service.GetIsEnabled(), // Use getter
|
||||
"description": service.Description,
|
||||
"event_triggers": service.EventTriggers,
|
||||
}
|
||||
@@ -1524,7 +1430,7 @@ func (h *Handlers) handleNotificationsWithError(c *gin.Context, errorMessage str
|
||||
ID: service.ID,
|
||||
Name: service.Name,
|
||||
Type: service.Type,
|
||||
IsEnabled: service.IsEnabled,
|
||||
IsEnabled: service.GetIsEnabled(), // Use getter
|
||||
Config: service.Config,
|
||||
Description: service.Description,
|
||||
EventTriggers: service.EventTriggers,
|
||||
@@ -1559,7 +1465,7 @@ func (h *Handlers) handleNotificationsWithSuccess(c *gin.Context, successMessage
|
||||
ID: service.ID,
|
||||
Name: service.Name,
|
||||
Type: service.Type,
|
||||
IsEnabled: service.IsEnabled,
|
||||
IsEnabled: service.GetIsEnabled(), // Use getter
|
||||
Config: service.Config,
|
||||
Description: service.Description,
|
||||
EventTriggers: service.EventTriggers,
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
// "github.com/starfleetcptn/gomft/internal/api"
|
||||
"github.com/starfleetcptn/gomft/components"
|
||||
"github.com/starfleetcptn/gomft/internal/config"
|
||||
@@ -144,7 +145,7 @@ func main() {
|
||||
var dbJobExecutor scheduler.JobExecutorDB = database
|
||||
var dbScheduler scheduler.SchedulerDB = database
|
||||
|
||||
notifier := scheduler.NewNotifier(dbNotifier, schedLogger)
|
||||
notifier := scheduler.NewNotifier(dbNotifier, schedLogger, cfg.SkipSSLVerify)
|
||||
metadataHandler := scheduler.NewMetadataHandler(dbMetadata, schedLogger)
|
||||
transferExecutor := scheduler.NewTransferExecutor(dbTransfer, schedLogger, metadataHandler, notifier)
|
||||
jobExecutor := scheduler.NewJobExecutor(dbJobExecutor, schedLogger, schedCron, jobsMap, &jobMutex, transferExecutor, notifier)
|
||||
|
||||
Reference in New Issue
Block a user