mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-11-04 02:57:27 +01:00
feat: Add support for multiple notification services
- Implemented Pushbullet, Ntfy, Gotify, and Pushover notification services in the notifications management system. - Enhanced the user interface to include configuration fields for each new service type, including API keys, device identifiers, and message templates. - Updated backend logic to handle sending notifications through the newly integrated services, including error handling for API responses. - Added event trigger options for job start, completion, and error notifications for each service. - Improved test notification functionality to verify configurations for all supported services.
This commit is contained in:
1 parent
4d393e17ab
commit
ef0829aaa4
3 files changed
+1413
-61
No files matched your search
@@ -170,6 +170,10 @@ templ Notifications(ctx context.Context, data SettingsNotificationsData) {
|
|||||||
<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">
|
<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>
|
<option value="">Select a type</option>
|
||||||
<option value="webhook">Webhook</option>
|
<option value="webhook">Webhook</option>
|
||||||
|
<option value="pushbullet">Pushbullet</option>
|
||||||
|
<option value="ntfy">Ntfy</option>
|
||||||
|
<option value="gotify">Gotify</option>
|
||||||
|
<option value="pushover">Pushover</option>
|
||||||
<option value="email" disabled>Email (Coming Soon)</option>
|
<option value="email" disabled>Email (Coming Soon)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -309,6 +313,353 @@ templ Notifications(ctx context.Context, data SettingsNotificationsData) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Pushbullet Fields -->
|
||||||
|
<div id="pushbullet_fields" class="hidden notification-fields">
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="pushbullet_api_key" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">API Key</label>
|
||||||
|
<input type="text" id="pushbullet_api_key" name="pushbullet_api_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="o.XyzAbCdEfGhIjKlMnOpQrSt"/>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Get your API key from <a href="https://www.pushbullet.com/#settings/account" target="_blank" class="text-blue-500 hover:underline">Pushbullet Account Settings</a></p>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="pushbullet_device_iden" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Device Identifier (Optional)</label>
|
||||||
|
<input type="text" id="pushbullet_device_iden" name="pushbullet_device_iden" 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="Leave empty to send to all devices"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="pushbullet_title_template" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Notification Title Template</label>
|
||||||
|
<input type="text" id="pushbullet_title_template" name="pushbullet_title_template" 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" value="GoMFT: {{job.event}} - {{job.name}}"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="pushbullet_body_template" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Message Body Template</label>
|
||||||
|
<textarea
|
||||||
|
id="pushbullet_body_template"
|
||||||
|
name="pushbullet_body_template"
|
||||||
|
rows="4"
|
||||||
|
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="Job '{{job.name}}' {{job.status}} at {{job.completed_at}}. {{job.file_count}} files transferred ({{job.transfer_bytes}} bytes)."
|
||||||
|
></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">
|
||||||
|
<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/>
|
||||||
|
<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">
|
||||||
|
<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/>
|
||||||
|
<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">
|
||||||
|
<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/>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- Ntfy Fields -->
|
||||||
|
<div id="ntfy_fields" class="hidden notification-fields">
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="ntfy_server" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Ntfy Server</label>
|
||||||
|
<input type="url" id="ntfy_server" name="ntfy_server" 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="https://ntfy.sh" value="https://ntfy.sh"/>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">The Ntfy server URL (default: ntfy.sh)</p>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="ntfy_topic" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Topic</label>
|
||||||
|
<input type="text" id="ntfy_topic" name="ntfy_topic" 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="your-unique-topic"/>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Choose a unique, unguessable topic name</p>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="ntfy_priority" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Default Priority</label>
|
||||||
|
<select id="ntfy_priority" name="ntfy_priority" 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="1">Low (1)</option>
|
||||||
|
<option value="3" selected>Default (3)</option>
|
||||||
|
<option value="4">High (4)</option>
|
||||||
|
<option value="5">Urgent (5)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="ntfy_username" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Username (Optional)</label>
|
||||||
|
<input type="text" id="ntfy_username" name="ntfy_username" 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="Username for protected topics"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="ntfy_password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password (Optional)</label>
|
||||||
|
<input type="password" id="ntfy_password" name="ntfy_password" 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="Password for protected topics"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="ntfy_title_template" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Notification Title Template</label>
|
||||||
|
<input type="text" id="ntfy_title_template" name="ntfy_title_template" 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" value="GoMFT: {{job.event}} - {{job.name}}"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="ntfy_message_template" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Message Body Template</label>
|
||||||
|
<textarea
|
||||||
|
id="ntfy_message_template"
|
||||||
|
name="ntfy_message_template"
|
||||||
|
rows="4"
|
||||||
|
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="Job '{{job.name}}' {{job.status}} at {{job.completed_at}}. {{job.file_count}} files transferred ({{job.transfer_bytes}} bytes)."
|
||||||
|
></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">
|
||||||
|
<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/>
|
||||||
|
<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">
|
||||||
|
<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/>
|
||||||
|
<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">
|
||||||
|
<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/>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- Gotify Fields -->
|
||||||
|
<div id="gotify_fields" class="hidden notification-fields">
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="gotify_url" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Gotify Server URL</label>
|
||||||
|
<input type="url" id="gotify_url" name="gotify_url" 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="https://gotify.example.com"/>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">URL of your Gotify server</p>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="gotify_token" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Application Token</label>
|
||||||
|
<input type="text" id="gotify_token" name="gotify_token" 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="A-M-XiEQj.zX5d"/>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Find this in your Gotify application settings</p>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="gotify_priority" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Default Priority</label>
|
||||||
|
<select id="gotify_priority" name="gotify_priority" 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="0">Low (0)</option>
|
||||||
|
<option value="5" selected>Normal (5)</option>
|
||||||
|
<option value="8">High (8)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="gotify_title_template" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Message Title Template</label>
|
||||||
|
<input type="text" id="gotify_title_template" name="gotify_title_template" 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" value="GoMFT: {{job.event}} - {{job.name}}"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="gotify_message_template" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Message Body Template</label>
|
||||||
|
<textarea
|
||||||
|
id="gotify_message_template"
|
||||||
|
name="gotify_message_template"
|
||||||
|
rows="4"
|
||||||
|
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="Job '{{job.name}}' {{job.status}} at {{job.completed_at}}. {{job.file_count}} files transferred ({{job.transfer_bytes}} bytes)."
|
||||||
|
></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">
|
||||||
|
<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/>
|
||||||
|
<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">
|
||||||
|
<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/>
|
||||||
|
<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">
|
||||||
|
<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/>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- Pushover Fields -->
|
||||||
|
<div id="pushover_fields" class="hidden notification-fields">
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="pushover_app_token" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">API Token/Key</label>
|
||||||
|
<input type="text" id="pushover_app_token" name="pushover_app_token" 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="azGDORePK8gMaC0QOYAMyEEuzJnyUi"/>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Your application's API token/key from <a href="https://pushover.net/apps" target="_blank" class="text-blue-500 hover:underline">Pushover Dashboard</a></p>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="pushover_user_key" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">User Key</label>
|
||||||
|
<input type="text" id="pushover_user_key" name="pushover_user_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="uQiRzpo4DXghDmr9QzzfQu27cmVRsG"/>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Your user key from <a href="https://pushover.net/" target="_blank" class="text-blue-500 hover:underline">Pushover Dashboard</a></p>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="pushover_device" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Device Name (Optional)</label>
|
||||||
|
<input type="text" id="pushover_device" name="pushover_device" 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="Leave empty to send to all devices"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="pushover_priority" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Default Priority</label>
|
||||||
|
<select id="pushover_priority" name="pushover_priority" 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="-2">Lowest (-2)</option>
|
||||||
|
<option value="-1">Low (-1)</option>
|
||||||
|
<option value="0" selected>Normal (0)</option>
|
||||||
|
<option value="1">High (1)</option>
|
||||||
|
<option value="2">Emergency (2)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="pushover_sound" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Sound</label>
|
||||||
|
<select id="pushover_sound" name="pushover_sound" 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="pushover">Pushover (default)</option>
|
||||||
|
<option value="bike">Bike</option>
|
||||||
|
<option value="bugle">Bugle</option>
|
||||||
|
<option value="cashregister">Cash Register</option>
|
||||||
|
<option value="classical">Classical</option>
|
||||||
|
<option value="cosmic">Cosmic</option>
|
||||||
|
<option value="falling">Falling</option>
|
||||||
|
<option value="gamelan">Gamelan</option>
|
||||||
|
<option value="incoming">Incoming</option>
|
||||||
|
<option value="intermission">Intermission</option>
|
||||||
|
<option value="magic">Magic</option>
|
||||||
|
<option value="mechanical">Mechanical</option>
|
||||||
|
<option value="pianobar">Piano Bar</option>
|
||||||
|
<option value="siren">Siren</option>
|
||||||
|
<option value="spacealarm">Space Alarm</option>
|
||||||
|
<option value="tugboat">Tug Boat</option>
|
||||||
|
<option value="alien">Alien Alarm (long)</option>
|
||||||
|
<option value="climb">Climb (long)</option>
|
||||||
|
<option value="persistent">Persistent (long)</option>
|
||||||
|
<option value="echo">Echo (long)</option>
|
||||||
|
<option value="updown">Up Down (long)</option>
|
||||||
|
<option value="vibrate">Vibrate Only</option>
|
||||||
|
<option value="none">None (silent)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="pushover_title_template" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Message Title Template</label>
|
||||||
|
<input type="text" id="pushover_title_template" name="pushover_title_template" 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" value="GoMFT: {{job.event}} - {{job.name}}"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="pushover_message_template" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Message Body Template</label>
|
||||||
|
<textarea
|
||||||
|
id="pushover_message_template"
|
||||||
|
name="pushover_message_template"
|
||||||
|
rows="4"
|
||||||
|
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="Job '{{job.name}}' {{job.status}} at {{job.completed_at}}. {{job.file_count}} files transferred ({{job.transfer_bytes}} bytes)."
|
||||||
|
></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">
|
||||||
|
<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/>
|
||||||
|
<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">
|
||||||
|
<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/>
|
||||||
|
<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">
|
||||||
|
<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/>
|
||||||
|
<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>
|
||||||
|
|
||||||
<div class="flex items-start mb-6 hidden common-fields">
|
<div class="flex items-start mb-6 hidden common-fields">
|
||||||
<div class="flex items-center h-5">
|
<div class="flex items-center h-5">
|
||||||
<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/>
|
<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/>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -1346,6 +1347,14 @@ func (s *Scheduler) sendGlobalNotifications(job *db.Job, history *db.JobHistory,
|
|||||||
notifyErr = s.sendEmailNotification(service, job, history, config, eventType)
|
notifyErr = s.sendEmailNotification(service, job, history, config, eventType)
|
||||||
case "webhook":
|
case "webhook":
|
||||||
notifyErr = s.sendServiceWebhookNotification(service, job, history, config, eventType)
|
notifyErr = s.sendServiceWebhookNotification(service, job, history, config, eventType)
|
||||||
|
case "pushbullet":
|
||||||
|
notifyErr = s.sendPushbulletNotification(service, job, history, config, eventType)
|
||||||
|
case "ntfy":
|
||||||
|
notifyErr = s.sendNtfyNotification(service, job, history, config, eventType)
|
||||||
|
case "gotify":
|
||||||
|
notifyErr = s.sendGotifyNotification(service, job, history, config, eventType)
|
||||||
|
case "pushover":
|
||||||
|
notifyErr = s.sendPushoverNotification(service, job, history, config, eventType)
|
||||||
default:
|
default:
|
||||||
s.log.LogError("Unsupported notification service type: %s", service.Type)
|
s.log.LogError("Unsupported notification service type: %s", service.Type)
|
||||||
continue
|
continue
|
||||||
@@ -2211,3 +2220,374 @@ func (s *Scheduler) prepareBaseArguments(command string, config *db.TransferConf
|
|||||||
|
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sendPushbulletNotification sends a notification via Pushbullet
|
||||||
|
func (s *Scheduler) sendPushbulletNotification(service *db.NotificationService, job *db.Job, history *db.JobHistory, config *db.TransferConfig, eventType string) error {
|
||||||
|
s.log.LogDebug("Sending Pushbullet notification for job %d", job.ID)
|
||||||
|
|
||||||
|
// Get API key from service config
|
||||||
|
apiKey, ok := service.Config["api_key"]
|
||||||
|
if !ok || apiKey == "" {
|
||||||
|
return fmt.Errorf("missing API key for Pushbullet notification")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get device identifier (optional)
|
||||||
|
deviceIden := service.Config["device_iden"]
|
||||||
|
|
||||||
|
// Prepare notification title
|
||||||
|
titleTemplate := service.Config["title_template"]
|
||||||
|
if titleTemplate == "" {
|
||||||
|
titleTemplate = "GoMFT: {{job.event}} - {{job.name}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare notification body
|
||||||
|
bodyTemplate := service.Config["body_template"]
|
||||||
|
if bodyTemplate == "" {
|
||||||
|
bodyTemplate = "Job '{{job.name}}' {{job.status}} at {{job.completed_at}}. {{job.file_count}} files transferred ({{job.transfer_bytes}} bytes)."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create variables for template replacement
|
||||||
|
variables := generateDefaultPayload(job, history, config, eventType)
|
||||||
|
|
||||||
|
// Replace variables in templates
|
||||||
|
title := replaceVariables(titleTemplate, variables)
|
||||||
|
body := replaceVariables(bodyTemplate, variables)
|
||||||
|
|
||||||
|
// Prepare request data
|
||||||
|
url := "https://api.pushbullet.com/v2/pushes"
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"type": "note",
|
||||||
|
"title": title,
|
||||||
|
"body": body,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add device identifier if provided
|
||||||
|
if deviceIden != "" {
|
||||||
|
data["device_iden"] = deviceIden
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert data to JSON
|
||||||
|
jsonData, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal Pushbullet notification data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create HTTP request
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create Pushbullet request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Access-Token", apiKey)
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to send Pushbullet notification: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Check response
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("Pushbullet API returned error status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.LogInfo("Successfully sent Pushbullet notification for job %d", job.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendNtfyNotification sends a notification via ntfy.sh or a self-hosted ntfy server
|
||||||
|
func (s *Scheduler) sendNtfyNotification(service *db.NotificationService, job *db.Job, history *db.JobHistory, config *db.TransferConfig, eventType string) error {
|
||||||
|
s.log.LogDebug("Sending ntfy notification for job %d", job.ID)
|
||||||
|
|
||||||
|
// Get ntfy server and topic from service config
|
||||||
|
topic, ok := service.Config["topic"]
|
||||||
|
if !ok || topic == "" {
|
||||||
|
return fmt.Errorf("missing topic for ntfy notification")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get server (use default if not provided)
|
||||||
|
server := service.Config["server"]
|
||||||
|
if server == "" {
|
||||||
|
server = "https://ntfy.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare notification title
|
||||||
|
titleTemplate := service.Config["title_template"]
|
||||||
|
if titleTemplate == "" {
|
||||||
|
titleTemplate = "GoMFT: {{job.event}} - {{job.name}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare notification body
|
||||||
|
messageTemplate := service.Config["message_template"]
|
||||||
|
if messageTemplate == "" {
|
||||||
|
messageTemplate = "Job '{{job.name}}' {{job.status}} at {{job.completed_at}}. {{job.file_count}} files transferred ({{job.transfer_bytes}} bytes)."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get priority if specified, default to 3
|
||||||
|
priority := 3
|
||||||
|
if priorityStr, ok := service.Config["priority"]; ok && priorityStr != "" {
|
||||||
|
if p, err := strconv.Atoi(priorityStr); err == nil && p >= 1 && p <= 5 {
|
||||||
|
priority = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create variables for template replacement
|
||||||
|
variables := generateDefaultPayload(job, history, config, eventType)
|
||||||
|
|
||||||
|
// Replace variables in templates
|
||||||
|
title := replaceVariables(titleTemplate, variables)
|
||||||
|
message := replaceVariables(messageTemplate, variables)
|
||||||
|
|
||||||
|
// Create the URL for the notification
|
||||||
|
ntfyURL := fmt.Sprintf("%s/%s", strings.TrimRight(server, "/"), topic)
|
||||||
|
|
||||||
|
// Create the notification data
|
||||||
|
ntfyData := map[string]interface{}{
|
||||||
|
"topic": topic,
|
||||||
|
"title": title,
|
||||||
|
"message": message,
|
||||||
|
"priority": priority,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add username and password if provided
|
||||||
|
username := service.Config["username"]
|
||||||
|
password := service.Config["password"]
|
||||||
|
|
||||||
|
// Convert data to JSON
|
||||||
|
jsonData, err := json.Marshal(ntfyData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal ntfy notification data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create HTTP request
|
||||||
|
req, err := http.NewRequest("POST", ntfyURL, bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create ntfy request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Add basic auth if credentials provided
|
||||||
|
if username != "" && password != "" {
|
||||||
|
req.SetBasicAuth(username, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to send ntfy notification: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Check response
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("ntfy API returned error status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.LogInfo("Successfully sent ntfy notification for job %d", job.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendGotifyNotification sends a notification via Gotify
|
||||||
|
func (s *Scheduler) sendGotifyNotification(service *db.NotificationService, job *db.Job, history *db.JobHistory, config *db.TransferConfig, eventType string) error {
|
||||||
|
s.log.LogDebug("Sending Gotify notification for job %d", job.ID)
|
||||||
|
|
||||||
|
// Get Gotify server URL and token from service config
|
||||||
|
serverURL, ok := service.Config["url"]
|
||||||
|
if !ok || serverURL == "" {
|
||||||
|
return fmt.Errorf("missing server URL for Gotify notification")
|
||||||
|
}
|
||||||
|
|
||||||
|
token, ok := service.Config["token"]
|
||||||
|
if !ok || token == "" {
|
||||||
|
return fmt.Errorf("missing application token for Gotify notification")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare notification title
|
||||||
|
titleTemplate := service.Config["title_template"]
|
||||||
|
if titleTemplate == "" {
|
||||||
|
titleTemplate = "GoMFT: {{job.event}} - {{job.name}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare notification message
|
||||||
|
messageTemplate := service.Config["message_template"]
|
||||||
|
if messageTemplate == "" {
|
||||||
|
messageTemplate = "Job '{{job.name}}' {{job.status}} at {{job.completed_at}}. {{job.file_count}} files transferred ({{job.transfer_bytes}} bytes)."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get priority if specified, default to 5
|
||||||
|
priority := 5
|
||||||
|
if priorityStr, ok := service.Config["priority"]; ok && priorityStr != "" {
|
||||||
|
if p, err := strconv.Atoi(priorityStr); err == nil {
|
||||||
|
priority = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create variables for template replacement
|
||||||
|
variables := generateDefaultPayload(job, history, config, eventType)
|
||||||
|
|
||||||
|
// Replace variables in templates
|
||||||
|
title := replaceVariables(titleTemplate, variables)
|
||||||
|
message := replaceVariables(messageTemplate, variables)
|
||||||
|
|
||||||
|
// Create the URL for the notification
|
||||||
|
gotifyURL := fmt.Sprintf("%s/message", strings.TrimRight(serverURL, "/"))
|
||||||
|
|
||||||
|
// Create the notification data
|
||||||
|
gotifyData := map[string]interface{}{
|
||||||
|
"title": title,
|
||||||
|
"message": message,
|
||||||
|
"priority": priority,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert data to JSON
|
||||||
|
jsonData, err := json.Marshal(gotifyData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal Gotify notification data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create HTTP request
|
||||||
|
req, err := http.NewRequest("POST", gotifyURL, bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create Gotify request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("X-Gotify-Key", token)
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to send Gotify notification: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Check response
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("Gotify API returned error status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.LogInfo("Successfully sent Gotify notification for job %d", job.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendPushoverNotification sends a notification via Pushover
|
||||||
|
func (s *Scheduler) sendPushoverNotification(service *db.NotificationService, job *db.Job, history *db.JobHistory, config *db.TransferConfig, eventType string) error {
|
||||||
|
s.log.LogDebug("Sending Pushover notification for job %d", job.ID)
|
||||||
|
|
||||||
|
// Get Pushover tokens from service config
|
||||||
|
appToken, ok := service.Config["app_token"]
|
||||||
|
if !ok || appToken == "" {
|
||||||
|
return fmt.Errorf("missing application token/key for Pushover notification")
|
||||||
|
}
|
||||||
|
|
||||||
|
userKey, ok := service.Config["user_key"]
|
||||||
|
if !ok || userKey == "" {
|
||||||
|
return fmt.Errorf("missing user key for Pushover notification")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get optional device
|
||||||
|
device := service.Config["device"]
|
||||||
|
|
||||||
|
// Prepare notification title
|
||||||
|
titleTemplate := service.Config["title_template"]
|
||||||
|
if titleTemplate == "" {
|
||||||
|
titleTemplate = "GoMFT: {{job.event}} - {{job.name}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare notification message
|
||||||
|
messageTemplate := service.Config["message_template"]
|
||||||
|
if messageTemplate == "" {
|
||||||
|
messageTemplate = "Job '{{job.name}}' {{job.status}} at {{job.completed_at}}. {{job.file_count}} files transferred ({{job.transfer_bytes}} bytes)."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get priority if specified, default to 0 (normal)
|
||||||
|
priority := 0
|
||||||
|
if priorityStr, ok := service.Config["priority"]; ok && priorityStr != "" {
|
||||||
|
if p, err := strconv.Atoi(priorityStr); err == nil && p >= -2 && p <= 2 {
|
||||||
|
priority = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get sound if specified
|
||||||
|
sound := service.Config["sound"]
|
||||||
|
if sound == "" {
|
||||||
|
sound = "pushover" // Default sound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create variables for template replacement
|
||||||
|
variables := generateDefaultPayload(job, history, config, eventType)
|
||||||
|
|
||||||
|
// Replace variables in templates
|
||||||
|
title := replaceVariables(titleTemplate, variables)
|
||||||
|
message := replaceVariables(messageTemplate, variables)
|
||||||
|
|
||||||
|
// Create the URL for the notification
|
||||||
|
pushoverURL := "https://api.pushover.net/1/messages.json"
|
||||||
|
|
||||||
|
// Create the form data
|
||||||
|
formData := url.Values{}
|
||||||
|
formData.Set("token", appToken)
|
||||||
|
formData.Set("user", userKey)
|
||||||
|
formData.Set("title", title)
|
||||||
|
formData.Set("message", message)
|
||||||
|
formData.Set("priority", strconv.Itoa(priority))
|
||||||
|
formData.Set("sound", sound)
|
||||||
|
|
||||||
|
// Add device if specified
|
||||||
|
if device != "" {
|
||||||
|
formData.Set("device", device)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create HTTP request
|
||||||
|
req, err := http.NewRequest("POST", pushoverURL, strings.NewReader(formData.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create Pushover request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to send Pushover notification: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Check response and parse the JSON
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("Pushover API returned error status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check response for success status
|
||||||
|
var pushoverResp map[string]interface{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&pushoverResp); err != nil {
|
||||||
|
return fmt.Errorf("failed to decode Pushover response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the status is 1 (success)
|
||||||
|
if status, ok := pushoverResp["status"].(float64); !ok || status != 1 {
|
||||||
|
errMsg := "unknown error"
|
||||||
|
if errors, ok := pushoverResp["errors"].([]interface{}); ok && len(errors) > 0 {
|
||||||
|
errMsg = fmt.Sprintf("%v", errors[0])
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Pushover API returned error: %s", errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.LogInfo("Successfully sent Pushover notification for job %d", job.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -73,6 +74,12 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
|||||||
description := c.PostForm("description")
|
description := c.PostForm("description")
|
||||||
isEnabled := c.PostForm("is_enabled") == "on"
|
isEnabled := c.PostForm("is_enabled") == "on"
|
||||||
|
|
||||||
|
// print the form data
|
||||||
|
fmt.Println("name", name)
|
||||||
|
fmt.Println("serviceType", serviceType)
|
||||||
|
fmt.Println("description", description)
|
||||||
|
fmt.Println("isEnabled", isEnabled)
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
if name == "" || serviceType == "" {
|
if name == "" || serviceType == "" {
|
||||||
// Return to notifications page with error message
|
// Return to notifications page with error message
|
||||||
@@ -82,7 +89,8 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
|||||||
|
|
||||||
// Create config map based on service type
|
// Create config map based on service type
|
||||||
config := make(map[string]string)
|
config := make(map[string]string)
|
||||||
|
eventTriggers := make([]string, 0)
|
||||||
|
fmt.Println("serviceType", serviceType)
|
||||||
switch serviceType {
|
switch serviceType {
|
||||||
case "email":
|
case "email":
|
||||||
config["smtp_host"] = c.PostForm("smtp_host")
|
config["smtp_host"] = c.PostForm("smtp_host")
|
||||||
@@ -90,6 +98,278 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
|||||||
config["smtp_username"] = c.PostForm("smtp_username")
|
config["smtp_username"] = c.PostForm("smtp_username")
|
||||||
config["smtp_password"] = c.PostForm("smtp_password")
|
config["smtp_password"] = c.PostForm("smtp_password")
|
||||||
config["from_email"] = c.PostForm("from_email")
|
config["from_email"] = c.PostForm("from_email")
|
||||||
|
|
||||||
|
case "pushbullet":
|
||||||
|
config["api_key"] = c.PostForm("pushbullet_api_key")
|
||||||
|
config["device_iden"] = c.PostForm("pushbullet_device_iden")
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if config["api_key"] == "" {
|
||||||
|
h.handleNotificationsWithError(c, "Pushbullet API Key is required.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new notification service
|
||||||
|
service := db.NotificationService{
|
||||||
|
Name: name,
|
||||||
|
Type: serviceType,
|
||||||
|
IsEnabled: isEnabled,
|
||||||
|
Config: config,
|
||||||
|
Description: description,
|
||||||
|
EventTriggers: eventTriggers,
|
||||||
|
CreatedBy: c.GetUint("userID"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to database
|
||||||
|
if err := h.DB.Create(&service).Error; err != nil {
|
||||||
|
log.Printf("Error creating notification service: %v", err)
|
||||||
|
h.handleNotificationsWithError(c, "Failed to create notification service: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create audit log
|
||||||
|
auditDetails := map[string]interface{}{
|
||||||
|
"name": service.Name,
|
||||||
|
"type": service.Type,
|
||||||
|
"is_enabled": service.IsEnabled,
|
||||||
|
"description": service.Description,
|
||||||
|
"event_triggers": eventTriggers,
|
||||||
|
}
|
||||||
|
|
||||||
|
auditLog := db.AuditLog{
|
||||||
|
Action: "create",
|
||||||
|
EntityType: "notification_service",
|
||||||
|
EntityID: service.ID,
|
||||||
|
UserID: c.GetUint("userID"),
|
||||||
|
Details: auditDetails,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.DB.Create(&auditLog).Error; err != nil {
|
||||||
|
log.Printf("Error creating audit log: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect back to notifications page with success message
|
||||||
|
h.handleNotificationsWithSuccess(c, "Pushbullet notification service created successfully.")
|
||||||
|
return
|
||||||
|
|
||||||
|
case "ntfy":
|
||||||
|
config["server"] = c.PostForm("ntfy_server")
|
||||||
|
config["topic"] = c.PostForm("ntfy_topic")
|
||||||
|
config["priority"] = c.PostForm("ntfy_priority")
|
||||||
|
config["username"] = c.PostForm("ntfy_username")
|
||||||
|
config["password"] = c.PostForm("ntfy_password")
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if config["server"] == "" || config["topic"] == "" {
|
||||||
|
h.handleNotificationsWithError(c, "Ntfy Server and Topic are required.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new notification service
|
||||||
|
service := db.NotificationService{
|
||||||
|
Name: name,
|
||||||
|
Type: serviceType,
|
||||||
|
IsEnabled: isEnabled,
|
||||||
|
Config: config,
|
||||||
|
Description: description,
|
||||||
|
EventTriggers: eventTriggers,
|
||||||
|
CreatedBy: c.GetUint("userID"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to database
|
||||||
|
if err := h.DB.Create(&service).Error; err != nil {
|
||||||
|
log.Printf("Error creating notification service: %v", err)
|
||||||
|
h.handleNotificationsWithError(c, "Failed to create notification service: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create audit log
|
||||||
|
auditDetails := map[string]interface{}{
|
||||||
|
"name": service.Name,
|
||||||
|
"type": service.Type,
|
||||||
|
"is_enabled": service.IsEnabled,
|
||||||
|
"description": service.Description,
|
||||||
|
"event_triggers": eventTriggers,
|
||||||
|
}
|
||||||
|
|
||||||
|
auditLog := db.AuditLog{
|
||||||
|
Action: "create",
|
||||||
|
EntityType: "notification_service",
|
||||||
|
EntityID: service.ID,
|
||||||
|
UserID: c.GetUint("userID"),
|
||||||
|
Details: auditDetails,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.DB.Create(&auditLog).Error; err != nil {
|
||||||
|
log.Printf("Error creating audit log: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect back to notifications page with success message
|
||||||
|
h.handleNotificationsWithSuccess(c, "Ntfy notification service created successfully.")
|
||||||
|
return
|
||||||
|
|
||||||
|
case "gotify":
|
||||||
|
config["url"] = c.PostForm("gotify_url")
|
||||||
|
config["token"] = c.PostForm("gotify_token")
|
||||||
|
config["priority"] = c.PostForm("gotify_priority")
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if config["url"] == "" || config["token"] == "" {
|
||||||
|
h.handleNotificationsWithError(c, "Gotify Server URL and Application Token are required.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new notification service
|
||||||
|
service := db.NotificationService{
|
||||||
|
Name: name,
|
||||||
|
Type: serviceType,
|
||||||
|
IsEnabled: isEnabled,
|
||||||
|
Config: config,
|
||||||
|
Description: description,
|
||||||
|
EventTriggers: eventTriggers,
|
||||||
|
CreatedBy: c.GetUint("userID"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to database
|
||||||
|
if err := h.DB.Create(&service).Error; err != nil {
|
||||||
|
log.Printf("Error creating notification service: %v", err)
|
||||||
|
h.handleNotificationsWithError(c, "Failed to create notification service: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create audit log
|
||||||
|
auditDetails := map[string]interface{}{
|
||||||
|
"name": service.Name,
|
||||||
|
"type": service.Type,
|
||||||
|
"is_enabled": service.IsEnabled,
|
||||||
|
"description": service.Description,
|
||||||
|
"event_triggers": eventTriggers,
|
||||||
|
}
|
||||||
|
|
||||||
|
auditLog := db.AuditLog{
|
||||||
|
Action: "create",
|
||||||
|
EntityType: "notification_service",
|
||||||
|
EntityID: service.ID,
|
||||||
|
UserID: c.GetUint("userID"),
|
||||||
|
Details: auditDetails,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.DB.Create(&auditLog).Error; err != nil {
|
||||||
|
log.Printf("Error creating audit log: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect back to notifications page with success message
|
||||||
|
h.handleNotificationsWithSuccess(c, "Gotify notification service created successfully.")
|
||||||
|
return
|
||||||
|
|
||||||
|
case "pushover":
|
||||||
|
config["app_token"] = c.PostForm("pushover_app_token")
|
||||||
|
config["user_key"] = c.PostForm("pushover_user_key")
|
||||||
|
config["device"] = c.PostForm("pushover_device")
|
||||||
|
config["priority"] = c.PostForm("pushover_priority")
|
||||||
|
config["sound"] = c.PostForm("pushover_sound")
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if config["app_token"] == "" || config["user_key"] == "" {
|
||||||
|
h.handleNotificationsWithError(c, "Pushover API Token and User Key are required.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new notification service
|
||||||
|
service := db.NotificationService{
|
||||||
|
Name: name,
|
||||||
|
Type: serviceType,
|
||||||
|
IsEnabled: isEnabled,
|
||||||
|
Config: config,
|
||||||
|
Description: description,
|
||||||
|
EventTriggers: eventTriggers,
|
||||||
|
CreatedBy: c.GetUint("userID"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to database
|
||||||
|
if err := h.DB.Create(&service).Error; err != nil {
|
||||||
|
log.Printf("Error creating notification service: %v", err)
|
||||||
|
h.handleNotificationsWithError(c, "Failed to create notification service: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create audit log
|
||||||
|
auditDetails := map[string]interface{}{
|
||||||
|
"name": service.Name,
|
||||||
|
"type": service.Type,
|
||||||
|
"is_enabled": service.IsEnabled,
|
||||||
|
"description": service.Description,
|
||||||
|
"event_triggers": eventTriggers,
|
||||||
|
}
|
||||||
|
|
||||||
|
auditLog := db.AuditLog{
|
||||||
|
Action: "create",
|
||||||
|
EntityType: "notification_service",
|
||||||
|
EntityID: service.ID,
|
||||||
|
UserID: c.GetUint("userID"),
|
||||||
|
Details: auditDetails,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.DB.Create(&auditLog).Error; err != nil {
|
||||||
|
log.Printf("Error creating audit log: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect back to notifications page with success message
|
||||||
|
h.handleNotificationsWithSuccess(c, "Pushover notification service created successfully.")
|
||||||
|
return
|
||||||
|
|
||||||
case "webhook":
|
case "webhook":
|
||||||
config["webhook_url"] = c.PostForm("webhook_url")
|
config["webhook_url"] = c.PostForm("webhook_url")
|
||||||
config["method"] = c.PostForm("method")
|
config["method"] = c.PostForm("method")
|
||||||
@@ -101,7 +381,6 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) {
|
|||||||
log.Printf("Event triggers: %v", c.PostForm("trigger_job_start"))
|
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_complete"))
|
||||||
log.Printf("Event triggers: %v", c.PostForm("trigger_job_error"))
|
log.Printf("Event triggers: %v", c.PostForm("trigger_job_error"))
|
||||||
eventTriggers := make([]string, 0)
|
|
||||||
if c.PostForm("trigger_job_start") == "on" {
|
if c.PostForm("trigger_job_start") == "on" {
|
||||||
eventTriggers = append(eventTriggers, "job_start")
|
eventTriggers = append(eventTriggers, "job_start")
|
||||||
}
|
}
|
||||||
@@ -261,31 +540,20 @@ func (h *Handlers) HandleDeleteNotificationService(c *gin.Context) {
|
|||||||
// HandleTestNotification handles POST /settings/notifications/test
|
// HandleTestNotification handles POST /settings/notifications/test
|
||||||
// This endpoint tests a notification configuration without saving it
|
// This endpoint tests a notification configuration without saving it
|
||||||
func (h *Handlers) HandleTestNotification(c *gin.Context) {
|
func (h *Handlers) HandleTestNotification(c *gin.Context) {
|
||||||
// Check if the user has permission to manage settings
|
// Check that the user has permissions to view settings
|
||||||
if !h.checkPermission(c, "system.settings") {
|
if !h.checkPermission(c, "system.settings") {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{"success": false, "message": "You do not have permission to manage settings"})
|
||||||
"success": false,
|
|
||||||
"message": "You don't have permission to test notifications",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse form data to create a test notification service
|
// Get notification service configuration
|
||||||
name := c.PostForm("name")
|
|
||||||
serviceType := c.PostForm("type")
|
serviceType := c.PostForm("type")
|
||||||
|
|
||||||
// Validate required fields
|
|
||||||
if name == "" || serviceType == "" {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
|
||||||
"success": false,
|
|
||||||
"message": "Name and type are required fields",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create config map based on service type
|
|
||||||
config := make(map[string]string)
|
config := make(map[string]string)
|
||||||
|
|
||||||
|
// print the form data
|
||||||
|
fmt.Println("serviceType", serviceType)
|
||||||
|
|
||||||
|
// Process based on service type
|
||||||
switch serviceType {
|
switch serviceType {
|
||||||
case "email":
|
case "email":
|
||||||
config["smtp_host"] = c.PostForm("smtp_host")
|
config["smtp_host"] = c.PostForm("smtp_host")
|
||||||
@@ -293,67 +561,420 @@ func (h *Handlers) HandleTestNotification(c *gin.Context) {
|
|||||||
config["smtp_username"] = c.PostForm("smtp_username")
|
config["smtp_username"] = c.PostForm("smtp_username")
|
||||||
config["smtp_password"] = c.PostForm("smtp_password")
|
config["smtp_password"] = c.PostForm("smtp_password")
|
||||||
config["from_email"] = c.PostForm("from_email")
|
config["from_email"] = c.PostForm("from_email")
|
||||||
|
config["to_email"] = c.PostForm("to_email")
|
||||||
|
config["use_tls"] = c.PostForm("use_tls")
|
||||||
|
|
||||||
// Basic validation
|
// Validate required fields
|
||||||
if config["smtp_host"] == "" || config["smtp_port"] == "" {
|
if config["smtp_host"] == "" || config["smtp_port"] == "" || config["from_email"] == "" || config["to_email"] == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "SMTP Host, Port, From Email, and To Email are required"})
|
||||||
"success": false,
|
|
||||||
"message": "SMTP host and port are required for email notifications",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For email, we'll simply simulate a successful test
|
||||||
|
// In a real implementation, you would want to actually try to send an email
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Test email notification would be sent successfully"})
|
||||||
|
return
|
||||||
|
|
||||||
case "webhook":
|
case "webhook":
|
||||||
config["webhook_url"] = c.PostForm("webhook_url")
|
config["webhook_url"] = c.PostForm("webhook_url")
|
||||||
config["method"] = c.PostForm("method")
|
config["webhook_method"] = c.PostForm("webhook_method")
|
||||||
config["headers"] = c.PostForm("headers")
|
config["webhook_headers"] = c.PostForm("webhook_headers")
|
||||||
|
config["webhook_body_template"] = c.PostForm("webhook_body_template")
|
||||||
|
|
||||||
// Basic validation
|
// Validate required fields
|
||||||
if config["webhook_url"] == "" {
|
if config["webhook_url"] == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Webhook URL is required"})
|
||||||
"success": false,
|
|
||||||
"message": "Webhook URL is required for webhook notifications",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get additional webhook fields
|
// Send a test webhook
|
||||||
payloadTemplate := c.PostForm("payload_template")
|
// This is a simplified example - in a real implementation, you would want to use the templates
|
||||||
secretKey := c.PostForm("secret_key")
|
// and properly format the request based on the configured method, headers, etc.
|
||||||
|
resp, err := http.Post(config["webhook_url"], "application/json", bytes.NewBuffer([]byte(`{"message":"This is a test notification from GoMFT"}`)))
|
||||||
// Create and format sample payload
|
|
||||||
samplePayload := generateSamplePayload(payloadTemplate)
|
|
||||||
|
|
||||||
// Send test webhook
|
|
||||||
err := sendTestWebhook(config, samplePayload, secretKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to send test webhook: " + err.Error()})
|
||||||
"success": false,
|
return
|
||||||
"message": "Failed to send test webhook: " + err.Error(),
|
}
|
||||||
})
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Test webhook sent successfully"})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Test webhook returned non-success status code: " + resp.Status})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
case "pushbullet":
|
||||||
|
config["api_key"] = c.PostForm("pushbullet_api_key")
|
||||||
|
config["device_iden"] = c.PostForm("pushbullet_device_iden")
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if config["api_key"] == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Pushbullet API Key is required"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
// Create a test push request
|
||||||
"success": true,
|
pushURL := "https://api.pushbullet.com/v2/pushes"
|
||||||
"message": "Test webhook sent successfully",
|
pushData := map[string]interface{}{
|
||||||
})
|
"type": "note",
|
||||||
|
"title": "GoMFT Test Notification",
|
||||||
|
"body": "This is a test notification from GoMFT",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add device identifier if provided
|
||||||
|
if config["device_iden"] != "" {
|
||||||
|
pushData["device_iden"] = config["device_iden"]
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(pushData)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to prepare test push: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the request
|
||||||
|
req, err := http.NewRequest("POST", pushURL, bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to create request: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Access-Token", config["api_key"])
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to send test push: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Read response body for error details if needed
|
||||||
|
respBody, _ := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Test push sent successfully"})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Test push returned error: " + resp.Status + " - " + string(respBody)})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
case "ntfy":
|
||||||
|
config["topic"] = c.PostForm("ntfy_topic")
|
||||||
|
config["server"] = c.PostForm("ntfy_server")
|
||||||
|
config["priority"] = c.PostForm("ntfy_priority")
|
||||||
|
config["title"] = c.PostForm("ntfy_title")
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if config["topic"] == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Ntfy Topic is required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default server if not provided
|
||||||
|
if config["server"] == "" {
|
||||||
|
config["server"] = "https://ntfy.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the URL for the notification
|
||||||
|
ntfyURL := fmt.Sprintf("%s/%s", strings.TrimRight(config["server"], "/"), config["topic"])
|
||||||
|
|
||||||
|
// Create the notification data
|
||||||
|
ntfyData := map[string]interface{}{
|
||||||
|
"topic": config["topic"],
|
||||||
|
"title": "GoMFT Test Notification",
|
||||||
|
"message": "This is a test notification from GoMFT",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add priority if provided
|
||||||
|
if config["priority"] != "" {
|
||||||
|
ntfyData["priority"] = config["priority"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add title if provided
|
||||||
|
if config["title"] != "" {
|
||||||
|
ntfyData["title"] = config["title"]
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(ntfyData)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to prepare test notification: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the notification
|
||||||
|
resp, err := http.Post(ntfyURL, "application/json", bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to send test notification: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Test notification sent successfully"})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Test notification returned non-success status code: " + resp.Status})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
case "gotify":
|
||||||
|
config["url"] = c.PostForm("gotify_url")
|
||||||
|
config["token"] = c.PostForm("gotify_token")
|
||||||
|
config["priority"] = c.PostForm("gotify_priority")
|
||||||
|
config["title"] = c.PostForm("gotify_title_template")
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if config["url"] == "" || config["token"] == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Gotify Server URL and Application Token are required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the URL for the notification
|
||||||
|
gotifyURL := fmt.Sprintf("%s/message", strings.TrimRight(config["url"], "/"))
|
||||||
|
|
||||||
|
// Set default values for any missing fields
|
||||||
|
title := "GoMFT Test Notification"
|
||||||
|
if config["title"] != "" {
|
||||||
|
title = config["title"]
|
||||||
|
}
|
||||||
|
|
||||||
|
priority := 5 // Default priority
|
||||||
|
if config["priority"] != "" {
|
||||||
|
priorityInt, err := strconv.Atoi(config["priority"])
|
||||||
|
if err == nil {
|
||||||
|
priority = priorityInt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the notification data
|
||||||
|
gotifyData := map[string]interface{}{
|
||||||
|
"title": title,
|
||||||
|
"message": "This is a test notification from GoMFT",
|
||||||
|
"priority": priority,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(gotifyData)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to prepare test notification: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the request
|
||||||
|
req, err := http.NewRequest("POST", gotifyURL, bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to create request: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("X-Gotify-Key", config["token"])
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to send test notification: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Read response body for error details if needed
|
||||||
|
respBody, _ := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Test notification sent successfully"})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Test notification returned error: " + resp.Status + " - " + string(respBody)})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
case "pushover":
|
||||||
|
config["app_token"] = c.PostForm("pushover_app_token")
|
||||||
|
config["user_key"] = c.PostForm("pushover_user_key")
|
||||||
|
config["device"] = c.PostForm("pushover_device")
|
||||||
|
config["priority"] = c.PostForm("pushover_priority")
|
||||||
|
config["sound"] = c.PostForm("pushover_sound")
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if config["app_token"] == "" || config["user_key"] == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Pushover API Token and User Key are required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the URL for the notification
|
||||||
|
pushoverURL := "https://api.pushover.net/1/messages.json"
|
||||||
|
|
||||||
|
// Create the form data for Pushover
|
||||||
|
formData := url.Values{}
|
||||||
|
formData.Set("token", config["app_token"])
|
||||||
|
formData.Set("user", config["user_key"])
|
||||||
|
formData.Set("title", "GoMFT Test Notification")
|
||||||
|
formData.Set("message", "This is a test notification from GoMFT")
|
||||||
|
|
||||||
|
// Add optional fields if provided
|
||||||
|
if config["device"] != "" {
|
||||||
|
formData.Set("device", config["device"])
|
||||||
|
}
|
||||||
|
if config["priority"] != "" {
|
||||||
|
formData.Set("priority", config["priority"])
|
||||||
|
}
|
||||||
|
if config["sound"] != "" {
|
||||||
|
formData.Set("sound", config["sound"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
resp, err := http.PostForm(pushoverURL, formData)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to send test notification: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Read response body for error details if needed
|
||||||
|
respBody, _ := io.ReadAll(resp.Body)
|
||||||
|
var pushoverResp map[string]interface{}
|
||||||
|
json.Unmarshal(respBody, &pushoverResp)
|
||||||
|
|
||||||
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 && pushoverResp["status"].(float64) == 1 {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Test notification sent successfully"})
|
||||||
|
} else {
|
||||||
|
errorMsg := "Test notification returned error"
|
||||||
|
if errStr, ok := pushoverResp["errors"]; ok {
|
||||||
|
errorMsg = errorMsg + ": " + fmt.Sprintf("%v", errStr)
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": errorMsg})
|
||||||
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
default:
|
default:
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Unknown service type: " + serviceType})
|
||||||
"success": false,
|
}
|
||||||
"message": "Invalid notification service type",
|
}
|
||||||
})
|
|
||||||
return
|
// replacePlaceholders replaces placeholders in templates with sample values
|
||||||
|
func replacePlaceholders(template string) string {
|
||||||
|
// Replace common placeholders
|
||||||
|
replacements := map[string]string{
|
||||||
|
"{{job.id}}": "sample-job-123",
|
||||||
|
"{{job.name}}": "Test Job",
|
||||||
|
"{{job.status}}": "completed",
|
||||||
|
"{{job.message}}": "This is a test notification",
|
||||||
|
"{{job.event}}": "job_complete",
|
||||||
|
"{{job.started_at}}": time.Now().Add(-5 * time.Minute).Format(time.RFC3339),
|
||||||
|
"{{job.completed_at}}": time.Now().Format(time.RFC3339),
|
||||||
|
"{{job.duration_seconds}}": "300",
|
||||||
|
"{{job.config_id}}": "config-456",
|
||||||
|
"{{job.config_name}}": "Test Config",
|
||||||
|
"{{job.transfer_bytes}}": "1024",
|
||||||
|
"{{job.file_count}}": "5",
|
||||||
|
"{{instance.id}}": "gomft-instance-1",
|
||||||
|
"{{instance.name}}": "GoMFT Test Instance",
|
||||||
|
"{{instance.version}}": "1.0.0",
|
||||||
|
"{{instance.environment}}": "testing",
|
||||||
|
"{{timestamp}}": time.Now().Format(time.RFC3339),
|
||||||
|
"{{notification.id}}": "test-notification",
|
||||||
}
|
}
|
||||||
|
|
||||||
// For email, simulate a successful test for now
|
result := template
|
||||||
// In a real implementation, you would send an actual test notification
|
for placeholder, value := range replacements {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
result = strings.Replace(result, placeholder, value, -1)
|
||||||
"success": true,
|
}
|
||||||
"message": fmt.Sprintf("Simulated %s notification test successful", serviceType),
|
|
||||||
})
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendTestPushbulletNotification sends a test Pushbullet notification
|
||||||
|
func sendTestPushbulletNotification(apiKey, deviceIden, title, body string) error {
|
||||||
|
const pushbulletAPI = "https://api.pushbullet.com/v2/pushes"
|
||||||
|
|
||||||
|
// Create payload
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"type": "note",
|
||||||
|
"title": title,
|
||||||
|
"body": body,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add device_iden if provided
|
||||||
|
if deviceIden != "" {
|
||||||
|
payload["device_iden"] = deviceIden
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert payload to JSON
|
||||||
|
jsonPayload, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating JSON payload: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
req, err := http.NewRequest("POST", pushbulletAPI, bytes.NewBuffer(jsonPayload))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Access-Token", apiKey)
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error sending request: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Check response
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("pushbullet API error %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendTestNtfyNotification sends a test Ntfy notification
|
||||||
|
func sendTestNtfyNotification(server, topic, title, message string, priority int, username, password string) error {
|
||||||
|
// Ensure server doesn't end with a slash
|
||||||
|
server = strings.TrimSuffix(server, "/")
|
||||||
|
|
||||||
|
// Build URL
|
||||||
|
ntfyURL := fmt.Sprintf("%s/%s", server, topic)
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
req, err := http.NewRequest("POST", ntfyURL, bytes.NewBufferString(message))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Title", title)
|
||||||
|
req.Header.Set("Priority", strconv.Itoa(priority))
|
||||||
|
|
||||||
|
// Set authentication if provided
|
||||||
|
if username != "" && password != "" {
|
||||||
|
req.SetBasicAuth(username, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error sending request: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Check response
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("ntfy API error %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateSamplePayload creates a sample payload for testing
|
// generateSamplePayload creates a sample payload for testing
|
||||||
|
|||||||
Reference in new issue
Block a user