mirror of
https://github.com/getgrav/grav.git
synced 2026-07-13 16:02:35 +02:00
@@ -80,8 +80,7 @@ form:
|
||||
content: |
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Check if modern features are enabled
|
||||
var modernEnabled = document.querySelector('[name="data[scheduler][modern][enabled]"]:checked');
|
||||
// Check if webhook is enabled
|
||||
var webhookEnabled = document.querySelector('[name="data[scheduler][modern][webhook][enabled]"]:checked');
|
||||
var statusDiv = document.querySelector('.cronstatus-status');
|
||||
|
||||
@@ -106,7 +105,6 @@ form:
|
||||
});
|
||||
|
||||
function updateStatusDisplay(healthData) {
|
||||
var isModernEnabled = modernEnabled && modernEnabled.value == '1';
|
||||
var isWebhookEnabled = webhookEnabled && webhookEnabled.value == '1';
|
||||
var isWebhookReady = webhookPluginInstalled && isWebhookEnabled && healthData && healthData.webhook_enabled;
|
||||
|
||||
@@ -179,25 +177,23 @@ form:
|
||||
}
|
||||
|
||||
// Webhook setup
|
||||
if (isModernEnabled) {
|
||||
if (!webhookPluginInstalled) {
|
||||
installHtml += '<p><strong>Option 2: Webhook Support</strong><br>';
|
||||
installHtml += '1. Install plugin: <code>bin/gpm install scheduler-webhook</code><br>';
|
||||
installHtml += '2. Configure webhook token in Modern Features tab<br>';
|
||||
installHtml += '3. Use webhook URL in your CI/CD or cloud scheduler</p>';
|
||||
hasInstructions = true;
|
||||
} else if (!isWebhookEnabled) {
|
||||
installHtml += '<p><strong>Webhook Plugin Installed</strong><br>';
|
||||
installHtml += 'Enable webhooks in Modern Features tab and set a secure token.</p>';
|
||||
hasInstructions = true;
|
||||
} else if (isWebhookReady) {
|
||||
installHtml += '<p><strong>✅ Webhook is Active!</strong><br>';
|
||||
installHtml += 'Trigger URL: <code>' + window.location.origin + '/grav-editor-pro/scheduler/webhook</code><br>';
|
||||
installHtml += 'Use with Authorization header: <code>Bearer YOUR_TOKEN</code></p>';
|
||||
|
||||
if (!cronReady) {
|
||||
installHtml += '<p class="text-muted"><small>Note: No cron job configured. Scheduler runs only via webhook triggers.</small></p>';
|
||||
}
|
||||
if (!webhookPluginInstalled) {
|
||||
installHtml += '<p><strong>Option 2: Webhook Support</strong><br>';
|
||||
installHtml += '1. Install plugin: <code>bin/gpm install scheduler-webhook</code><br>';
|
||||
installHtml += '2. Configure webhook token in Advanced Features tab<br>';
|
||||
installHtml += '3. Use webhook URL in your CI/CD or cloud scheduler</p>';
|
||||
hasInstructions = true;
|
||||
} else if (!isWebhookEnabled) {
|
||||
installHtml += '<p><strong>Webhook Plugin Installed</strong><br>';
|
||||
installHtml += 'Enable webhooks in Advanced Features tab and set a secure token.</p>';
|
||||
hasInstructions = true;
|
||||
} else if (isWebhookReady) {
|
||||
installHtml += '<p><strong>✅ Webhook is Active!</strong><br>';
|
||||
installHtml += 'Trigger URL: <code>' + window.location.origin + '/grav-editor-pro/scheduler/webhook</code><br>';
|
||||
installHtml += 'Use with Authorization header: <code>Bearer YOUR_TOKEN</code></p>';
|
||||
|
||||
if (!cronReady) {
|
||||
installHtml += '<p class="text-muted"><small>Note: No cron job configured. Scheduler runs only via webhook triggers.</small></p>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,9 +201,6 @@ form:
|
||||
installHtml += '<p><strong>✅ Cron is configured and ready!</strong><br>';
|
||||
installHtml += 'The scheduler runs automatically every minute via system cron.</p>';
|
||||
|
||||
if (!isModernEnabled) {
|
||||
installHtml += '<p class="text-muted"><small>Enable Modern Features for webhook support and advanced options.</small></p>';
|
||||
}
|
||||
}
|
||||
|
||||
installHtml += '</div>';
|
||||
@@ -229,20 +222,15 @@ form:
|
||||
});
|
||||
</script>
|
||||
|
||||
modern_status:
|
||||
type: conditional
|
||||
condition: config.scheduler.modern.enabled
|
||||
|
||||
fields:
|
||||
modern_health:
|
||||
type: display
|
||||
label: Health Status
|
||||
content: |
|
||||
<div id="scheduler-health-status">
|
||||
<div class="text-muted">Checking health...</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
modern_health:
|
||||
type: display
|
||||
label: Health Status
|
||||
content: |
|
||||
<div id="scheduler-health-status">
|
||||
<div class="text-muted">Checking health...</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
function loadHealthStatus() {
|
||||
fetch(window.location.origin + '/grav-editor-pro/scheduler/health')
|
||||
.then(response => response.json())
|
||||
@@ -330,21 +318,21 @@ form:
|
||||
loadHealthStatus();
|
||||
}
|
||||
|
||||
// Refresh every 30 seconds
|
||||
setInterval(loadHealthStatus, 30000);
|
||||
})();
|
||||
</script>
|
||||
markdown: false
|
||||
// Refresh every 30 seconds
|
||||
setInterval(loadHealthStatus, 30000);
|
||||
})();
|
||||
</script>
|
||||
markdown: false
|
||||
|
||||
trigger_methods:
|
||||
type: display
|
||||
label: Active Triggers
|
||||
content: |
|
||||
<div id="scheduler-triggers">
|
||||
<div class="text-muted">Checking triggers...</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
trigger_methods:
|
||||
type: display
|
||||
label: Active Triggers
|
||||
content: |
|
||||
<div id="scheduler-triggers">
|
||||
<div class="text-muted">Checking triggers...</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
function loadTriggers() {
|
||||
// Check cron status from the main status field
|
||||
var cronReady = false;
|
||||
@@ -361,50 +349,47 @@ form:
|
||||
var triggersEl = document.getElementById('scheduler-triggers');
|
||||
if (!triggersEl) return;
|
||||
|
||||
var html = '<div style="display: flex; flex-direction: column; gap: 0.75rem;">';
|
||||
var html = '<div style="display: flex; flex-direction: column; gap: 0.5rem;">';
|
||||
|
||||
// Cron trigger card
|
||||
var cronBg = cronReady ? 'linear-gradient(135deg, #d4edda 0%, #fff 100%)' : 'linear-gradient(135deg, #f8f9fa 0%, #fff 100%)';
|
||||
var cronBorder = cronReady ? '#c3e6cb' : '#dee2e6';
|
||||
var cronIcon = cronReady ? '✅' : '❌';
|
||||
var cronStatus = cronReady ? 'Active' : 'Not Configured';
|
||||
var cronColor = cronReady ? '#155724' : '#6c757d';
|
||||
var cronStatusColor = cronReady ? '#28a745' : '#6c757d';
|
||||
var cardBg = cronReady ? '#f8f9fa' : '#fff';
|
||||
|
||||
html += '<div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 1rem; background: ' + cronBg + '; border: 1px solid ' + cronBorder + '; border-radius: 6px; box-shadow: 0 1px 2px rgba(0,0,0,0.03);">';
|
||||
html += '<div style="display: flex; align-items: center; gap: 0.5rem;">';
|
||||
html += '<span style="font-size: 1.1rem;">' + cronIcon + '</span>';
|
||||
html += '<span style="font-weight: 500; color: #495057;">Cron:</span>';
|
||||
html += '<div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 1rem; background: ' + cardBg + '; border: 1px solid #e9ecef; border-radius: 4px;">';
|
||||
html += '<div style="display: flex; align-items: center; gap: 0.75rem;">';
|
||||
html += '<span style="font-size: 1.25rem; line-height: 1;">' + cronIcon + '</span>';
|
||||
html += '<span style="font-weight: 500; color: #212529; font-size: 1rem;">Cron:</span>';
|
||||
html += '</div>';
|
||||
html += '<span style="background: ' + (cronReady ? '#28a745' : '#6c757d') + '; color: white; padding: 0.25rem 0.625rem; font-size: 0.8125rem; font-weight: 500; border-radius: 4px;">' + cronStatus + '</span>';
|
||||
html += '<span style="background: ' + cronStatusColor + '; color: white; padding: 0.25rem 0.75rem; font-size: 0.875rem; font-weight: 500; border-radius: 3px; text-transform: uppercase; letter-spacing: 0.025em;">' + cronStatus + '</span>';
|
||||
html += '</div>';
|
||||
|
||||
// Webhook trigger card
|
||||
if (data.webhook_enabled) {
|
||||
html += '<div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 1rem; background: linear-gradient(135deg, #d4edda 0%, #fff 100%); border: 1px solid #c3e6cb; border-radius: 6px; box-shadow: 0 1px 2px rgba(0,0,0,0.03);">';
|
||||
html += '<div style="display: flex; align-items: center; gap: 0.5rem;">';
|
||||
html += '<span style="font-size: 1.1rem;">✅</span>';
|
||||
html += '<span style="font-weight: 500; color: #495057;">Webhook:</span>';
|
||||
html += '<div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 1rem; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px;">';
|
||||
html += '<div style="display: flex; align-items: center; gap: 0.75rem;">';
|
||||
html += '<span style="font-size: 1.25rem; line-height: 1;">✅</span>';
|
||||
html += '<span style="font-weight: 500; color: #212529; font-size: 1rem;">Webhook:</span>';
|
||||
html += '</div>';
|
||||
html += '<span style="background: #28a745; color: white; padding: 0.25rem 0.625rem; font-size: 0.8125rem; font-weight: 500; border-radius: 4px;">Active</span>';
|
||||
html += '<span style="background: #28a745; color: white; padding: 0.25rem 0.75rem; font-size: 0.875rem; font-weight: 500; border-radius: 3px; text-transform: uppercase; letter-spacing: 0.025em;">ACTIVE</span>';
|
||||
html += '</div>';
|
||||
} else {
|
||||
var modernEnabled = document.querySelector('[name="data[scheduler][modern][enabled]"]:checked');
|
||||
if (modernEnabled && modernEnabled.value == '1') {
|
||||
html += '<div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 1rem; background: linear-gradient(135deg, #fff3cd 0%, #fff 100%); border: 1px solid #ffeeba; border-radius: 6px; box-shadow: 0 1px 2px rgba(0,0,0,0.03);">';
|
||||
html += '<div style="display: flex; align-items: center; gap: 0.5rem;">';
|
||||
html += '<span style="font-size: 1.1rem;">⚠️</span>';
|
||||
html += '<span style="font-weight: 500; color: #495057;">Webhook:</span>';
|
||||
html += '</div>';
|
||||
html += '<span style="background: #ffc107; color: #212529; padding: 0.25rem 0.625rem; font-size: 0.8125rem; font-weight: 500; border-radius: 4px;">Disabled</span>';
|
||||
html += '</div>';
|
||||
}
|
||||
// Show webhook as not configured/disabled
|
||||
html += '<div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 1rem; background: #fff; border: 1px solid #e9ecef; border-radius: 4px;">';
|
||||
html += '<div style="display: flex; align-items: center; gap: 0.75rem;">';
|
||||
html += '<span style="font-size: 1.25rem; line-height: 1;">⚠️</span>';
|
||||
html += '<span style="font-weight: 500; color: #212529; font-size: 1rem;">Webhook:</span>';
|
||||
html += '</div>';
|
||||
html += '<span style="background: #ffc107; color: #212529; padding: 0.25rem 0.75rem; font-size: 0.875rem; font-weight: 500; border-radius: 3px; text-transform: uppercase; letter-spacing: 0.025em;">DISABLED</span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Add warning if no triggers active
|
||||
if (!cronReady && !data.webhook_enabled) {
|
||||
html += '<div class="alert alert-warning">No triggers active! Configure cron or enable webhooks.</div>';
|
||||
html += '<div class="alert alert-warning" style="margin-top: 1rem;"><i class="fa fa-exclamation-triangle"></i> No triggers active! Configure cron or enable webhooks.</div>';
|
||||
}
|
||||
|
||||
triggersEl.innerHTML = html;
|
||||
@@ -431,10 +416,10 @@ form:
|
||||
document.addEventListener('DOMContentLoaded', loadTriggers);
|
||||
} else {
|
||||
loadTriggers();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
markdown: false
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
markdown: false
|
||||
|
||||
jobs_tab:
|
||||
type: tab
|
||||
@@ -501,233 +486,291 @@ form:
|
||||
|
||||
modern_tab:
|
||||
type: tab
|
||||
title: Modern Features
|
||||
title: Advanced Features
|
||||
|
||||
fields:
|
||||
modern.enabled:
|
||||
type: toggle
|
||||
label: Enable Modern Scheduler
|
||||
help: Enable enhanced scheduler features (job queue, retry, webhooks, monitoring)
|
||||
highlight: 0
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
modern_features:
|
||||
type: conditional
|
||||
condition: config.scheduler.modern.enabled
|
||||
workers_section:
|
||||
type: section
|
||||
title: Worker Configuration
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
workers_section:
|
||||
type: section
|
||||
title: Worker Configuration
|
||||
underline: true
|
||||
modern.workers:
|
||||
type: number
|
||||
label: Concurrent Workers
|
||||
help: Number of jobs that can run simultaneously (1 = sequential)
|
||||
default: 4
|
||||
size: x-small
|
||||
append: workers
|
||||
validate:
|
||||
type: int
|
||||
min: 1
|
||||
max: 10
|
||||
|
||||
retry_section:
|
||||
type: section
|
||||
title: Retry Configuration
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
modern.retry.enabled:
|
||||
type: toggle
|
||||
label: Enable Job Retry
|
||||
help: Automatically retry failed jobs
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
modern.retry.max_attempts:
|
||||
type: number
|
||||
label: Maximum Retry Attempts
|
||||
help: Maximum number of times to retry a failed job
|
||||
default: 3
|
||||
size: x-small
|
||||
append: retries
|
||||
validate:
|
||||
type: int
|
||||
min: 1
|
||||
max: 10
|
||||
|
||||
modern.retry.backoff:
|
||||
type: select
|
||||
label: Retry Backoff Strategy
|
||||
help: How to calculate delay between retries
|
||||
default: exponential
|
||||
options:
|
||||
linear: Linear (fixed delay)
|
||||
exponential: Exponential (increasing delay)
|
||||
|
||||
queue_section:
|
||||
type: section
|
||||
title: Queue Configuration
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
modern.queue.path:
|
||||
type: text
|
||||
label: Queue Storage Path
|
||||
help: Where to store queued jobs
|
||||
default: 'user-data://scheduler/queue'
|
||||
placeholder: 'user-data://scheduler/queue'
|
||||
|
||||
modern.queue.max_size:
|
||||
type: number
|
||||
label: Maximum Queue Size
|
||||
help: Maximum number of jobs that can be queued
|
||||
default: 1000
|
||||
size: x-small
|
||||
append: jobs
|
||||
validate:
|
||||
type: int
|
||||
min: 100
|
||||
max: 10000
|
||||
|
||||
history_section:
|
||||
type: section
|
||||
title: Job History
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
modern.history.enabled:
|
||||
type: toggle
|
||||
label: Enable Job History
|
||||
help: Track execution history for all jobs
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
modern.history.retention_days:
|
||||
type: number
|
||||
label: History Retention (days)
|
||||
help: How long to keep job history
|
||||
default: 30
|
||||
size: x-small
|
||||
append: days
|
||||
validate:
|
||||
type: int
|
||||
min: 1
|
||||
max: 365
|
||||
|
||||
webhook_section:
|
||||
type: section
|
||||
title: Webhook Configuration
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
webhook_plugin_notice:
|
||||
type: conditional
|
||||
condition: config.plugins.scheduler-webhook.enabled == false
|
||||
|
||||
fields:
|
||||
modern.workers:
|
||||
type: number
|
||||
label: Concurrent Workers
|
||||
help: Number of jobs that can run simultaneously (1 = sequential)
|
||||
default: 1
|
||||
size: x-small
|
||||
append: workers
|
||||
validate:
|
||||
type: int
|
||||
min: 1
|
||||
max: 10
|
||||
|
||||
retry_section:
|
||||
type: section
|
||||
title: Retry Configuration
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
modern.retry.enabled:
|
||||
type: toggle
|
||||
label: Enable Job Retry
|
||||
help: Automatically retry failed jobs
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
modern.retry.max_attempts:
|
||||
type: number
|
||||
label: Maximum Retry Attempts
|
||||
help: Maximum number of times to retry a failed job
|
||||
default: 3
|
||||
size: x-small
|
||||
append: retries
|
||||
validate:
|
||||
type: int
|
||||
min: 1
|
||||
max: 10
|
||||
|
||||
modern.retry.backoff:
|
||||
type: select
|
||||
label: Retry Backoff Strategy
|
||||
help: How to calculate delay between retries
|
||||
default: exponential
|
||||
options:
|
||||
linear: Linear (fixed delay)
|
||||
exponential: Exponential (increasing delay)
|
||||
|
||||
queue_section:
|
||||
type: section
|
||||
title: Queue Configuration
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
modern.queue.path:
|
||||
type: text
|
||||
label: Queue Storage Path
|
||||
help: Where to store queued jobs
|
||||
default: 'user-data://scheduler/queue'
|
||||
placeholder: 'user-data://scheduler/queue'
|
||||
|
||||
modern.queue.max_size:
|
||||
type: number
|
||||
label: Maximum Queue Size
|
||||
help: Maximum number of jobs that can be queued
|
||||
default: 1000
|
||||
size: x-small
|
||||
append: jobs
|
||||
validate:
|
||||
type: int
|
||||
min: 100
|
||||
max: 10000
|
||||
|
||||
history_section:
|
||||
type: section
|
||||
title: Job History
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
modern.history.enabled:
|
||||
type: toggle
|
||||
label: Enable Job History
|
||||
help: Track execution history for all jobs
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
modern.history.retention_days:
|
||||
type: number
|
||||
label: History Retention (days)
|
||||
help: How long to keep job history
|
||||
default: 30
|
||||
size: x-small
|
||||
append: days
|
||||
validate:
|
||||
type: int
|
||||
min: 1
|
||||
max: 365
|
||||
|
||||
webhook_section:
|
||||
type: section
|
||||
title: Webhook Configuration
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
webhook_plugin_notice:
|
||||
webhook_install:
|
||||
type: display
|
||||
label:
|
||||
content: |
|
||||
<div class="alert alert-warning">
|
||||
<strong>Plugin Required:</strong> The <code>scheduler-webhook</code> plugin must be installed and enabled for webhook functionality to work.
|
||||
<br><br>
|
||||
Install with: <code>bin/gpm install scheduler-webhook</code>
|
||||
<strong>Webhook Plugin Required</strong><br>
|
||||
The <code>scheduler-webhook</code> plugin is required for webhook functionality.<br><br>
|
||||
<a class="button button-primary" href="#"
|
||||
data-remodal-target="add-package"
|
||||
data-packages-slugs="scheduler-webhook"
|
||||
data-plugin-action="start-package-installation">
|
||||
<i class="fa fa-download"></i> Install Plugin Now
|
||||
</a>
|
||||
<span class="hint" style="margin-left: 10px;">or run: <code>bin/gpm install scheduler-webhook</code></span>
|
||||
</div>
|
||||
markdown: false
|
||||
modern.webhook.enabled:
|
||||
type: toggle
|
||||
label: Enable Webhook Triggers
|
||||
help: Allow triggering scheduler via HTTP webhook
|
||||
highlight: 0
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
modern.webhook.token:
|
||||
type: text
|
||||
label: Webhook Security Token
|
||||
help: Secret token for authenticating webhook requests. Keep this secret!
|
||||
placeholder: 'Click Generate to create a secure token'
|
||||
|
||||
webhook_token_generate:
|
||||
webhook_plugin_ready:
|
||||
type: conditional
|
||||
condition: config.plugins.scheduler-webhook.enabled == true
|
||||
|
||||
fields:
|
||||
webhook_ready:
|
||||
type: display
|
||||
label:
|
||||
content: |
|
||||
<button type="button" class="button" onclick="generateWebhookToken()">Generate Token</button>
|
||||
<div class="alert alert-success">
|
||||
<i class="fa fa-check-circle"></i> <strong>Webhook Plugin Ready!</strong><br>
|
||||
The scheduler-webhook plugin is installed and active. Configure your webhook settings below.
|
||||
</div>
|
||||
markdown: false
|
||||
modern.webhook.enabled:
|
||||
type: toggle
|
||||
label: Enable Webhook Triggers
|
||||
help: Allow triggering scheduler via HTTP webhook
|
||||
highlight: 0
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
modern.webhook.token:
|
||||
type: text
|
||||
label: Webhook Security Token
|
||||
help: Secret token for authenticating webhook requests. Keep this secret!
|
||||
placeholder: 'Click Generate to create a secure token'
|
||||
autocomplete: 'off'
|
||||
|
||||
webhook_token_generate:
|
||||
type: display
|
||||
label:
|
||||
content: |
|
||||
<div style="margin-top: -10px; margin-bottom: 15px;">
|
||||
<button type="button" class="button button-primary" onclick="generateWebhookToken()">
|
||||
<i class="fa fa-refresh"></i> Generate Token
|
||||
</button>
|
||||
</div>
|
||||
<script>
|
||||
function generateWebhookToken() {
|
||||
const array = new Uint8Array(32);
|
||||
crypto.getRandomValues(array);
|
||||
const token = Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
|
||||
const field = document.querySelector('[name="data[scheduler][modern][webhook][token]"]');
|
||||
if (field) {
|
||||
field.value = token;
|
||||
field.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
try {
|
||||
// Generate token
|
||||
const array = new Uint8Array(32);
|
||||
crypto.getRandomValues(array);
|
||||
const token = Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
// Try multiple selectors to find the field
|
||||
let field = document.querySelector('[name="data[scheduler][modern][webhook][token]"]');
|
||||
if (!field) {
|
||||
field = document.querySelector('input[name*="webhook][token"]');
|
||||
}
|
||||
if (!field) {
|
||||
field = document.getElementById('scheduler-modern-webhook-token');
|
||||
}
|
||||
if (!field) {
|
||||
// Look for any text input in the webhook section
|
||||
const webhookSection = document.querySelector('.webhook_section');
|
||||
if (webhookSection) {
|
||||
const inputs = webhookSection.querySelectorAll('input[type="text"]');
|
||||
// Find the token field by checking for the placeholder
|
||||
for (let input of inputs) {
|
||||
if (input.placeholder && input.placeholder.includes('Generate')) {
|
||||
field = input;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (field) {
|
||||
field.value = token;
|
||||
field.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
field.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
// Flash the field to show it was updated
|
||||
field.style.backgroundColor = '#d4edda';
|
||||
setTimeout(function() {
|
||||
field.style.backgroundColor = '';
|
||||
}, 500);
|
||||
// Also try to trigger Grav's form change detection
|
||||
if (window.jQuery) {
|
||||
jQuery(field).trigger('change');
|
||||
}
|
||||
} else {
|
||||
// Log more debugging info
|
||||
console.error('Token field not found. Looking for input fields...');
|
||||
console.log('All inputs:', document.querySelectorAll('input[type="text"]'));
|
||||
alert('Could not find the token field. Please ensure you are in the Advanced Features tab and the Webhook Configuration section is visible.');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error generating token:', e);
|
||||
alert('Error generating token: ' + e.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
markdown: false
|
||||
markdown: false
|
||||
|
||||
modern.webhook.path:
|
||||
type: text
|
||||
label: Webhook Path
|
||||
help: URL path for webhook endpoint
|
||||
default: '/scheduler/webhook'
|
||||
placeholder: '/scheduler/webhook'
|
||||
modern.webhook.path:
|
||||
type: text
|
||||
label: Webhook Path
|
||||
help: URL path for webhook endpoint
|
||||
default: '/scheduler/webhook'
|
||||
placeholder: '/scheduler/webhook'
|
||||
|
||||
health_section:
|
||||
type: section
|
||||
title: Health Check Configuration
|
||||
underline: true
|
||||
health_section:
|
||||
type: section
|
||||
title: Health Check Configuration
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
modern.health.enabled:
|
||||
type: toggle
|
||||
label: Enable Health Check
|
||||
help: Provide health status endpoint for monitoring
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
fields:
|
||||
modern.health.enabled:
|
||||
type: toggle
|
||||
label: Enable Health Check
|
||||
help: Provide health status endpoint for monitoring
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
modern.health.path:
|
||||
type: text
|
||||
label: Health Check Path
|
||||
help: URL path for health check endpoint
|
||||
default: '/scheduler/health'
|
||||
placeholder: '/scheduler/health'
|
||||
modern.health.path:
|
||||
type: text
|
||||
label: Health Check Path
|
||||
help: URL path for health check endpoint
|
||||
default: '/scheduler/health'
|
||||
placeholder: '/scheduler/health'
|
||||
|
||||
webhook_usage:
|
||||
type: section
|
||||
title: Usage Examples
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
webhook_examples:
|
||||
type: display
|
||||
label:
|
||||
content: |
|
||||
webhook_usage:
|
||||
type: section
|
||||
title: Usage Examples
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
webhook_examples:
|
||||
type: display
|
||||
label:
|
||||
content: |
|
||||
<div class="alert alert-info">
|
||||
<h4>How to use webhooks:</h4>
|
||||
<p><strong>Trigger all due jobs (respects schedule):</strong></p>
|
||||
@@ -747,7 +790,7 @@ form:
|
||||
curl -X POST ${{ secrets.SITE_URL }}/scheduler/webhook \
|
||||
-H "Authorization: Bearer ${{ secrets.WEBHOOK_TOKEN }}"</pre>
|
||||
</div>
|
||||
markdown: false
|
||||
markdown: false
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use function is_callable;
|
||||
use function is_string;
|
||||
|
||||
@@ -76,8 +78,8 @@ class Scheduler
|
||||
/** @var string */
|
||||
protected $historyPath;
|
||||
|
||||
/** @var JobHistory|null */
|
||||
protected $jobHistory = null;
|
||||
/** @var Logger|null */
|
||||
protected $logger = null;
|
||||
|
||||
/** @var array */
|
||||
protected $modernConfig = [];
|
||||
@@ -97,11 +99,10 @@ class Scheduler
|
||||
Folder::create($this->status_path);
|
||||
}
|
||||
|
||||
// Initialize modern features if enabled
|
||||
// Initialize modern features (always enabled now)
|
||||
$this->modernConfig = $grav['config']->get('scheduler.modern', []);
|
||||
if ($this->modernConfig['enabled'] ?? false) {
|
||||
$this->initializeModernFeatures($locator);
|
||||
}
|
||||
// Always initialize modern features - they're now part of core
|
||||
$this->initializeModernFeatures($locator);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,16 +250,31 @@ class Scheduler
|
||||
$runTime = new DateTime('now');
|
||||
}
|
||||
|
||||
// Log scheduler run
|
||||
if ($this->logger) {
|
||||
$jobCount = count($alljobs);
|
||||
$forceStr = $force ? ' (forced)' : '';
|
||||
$this->logger->debug("Scheduler run started - {$jobCount} jobs available{$forceStr}", [
|
||||
'time' => $runTime->format('Y-m-d H:i:s')
|
||||
]);
|
||||
}
|
||||
|
||||
// Process jobs based on modern features
|
||||
if ($this->jobQueue && ($this->modernConfig['queue']['enabled'] ?? false)) {
|
||||
// Queue jobs for processing
|
||||
$queuedCount = 0;
|
||||
foreach ($alljobs as $job) {
|
||||
if ($job->isDue($runTime) || $force) {
|
||||
// Add to queue for concurrent processing
|
||||
$this->jobQueue->push($job);
|
||||
$queuedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->logger && $queuedCount > 0) {
|
||||
$this->logger->debug("Queued {$queuedCount} job(s) for processing");
|
||||
}
|
||||
|
||||
// Process queue with workers
|
||||
$this->processJobsWithWorkers();
|
||||
|
||||
@@ -287,6 +303,31 @@ class Scheduler
|
||||
}
|
||||
}
|
||||
|
||||
// Log run summary
|
||||
if ($this->logger) {
|
||||
$successCount = 0;
|
||||
$failureCount = 0;
|
||||
$executedJobs = array_merge($this->executed_jobs, $this->jobs_run);
|
||||
|
||||
foreach ($executedJobs as $job) {
|
||||
if ($job->isSuccessful()) {
|
||||
$successCount++;
|
||||
} else {
|
||||
$failureCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($executedJobs) > 0) {
|
||||
if ($failureCount > 0) {
|
||||
$this->logger->warning("Scheduler completed: {$successCount} succeeded, {$failureCount} failed");
|
||||
} else {
|
||||
$this->logger->info("Scheduler completed: {$successCount} job(s) succeeded");
|
||||
}
|
||||
} else {
|
||||
$this->logger->debug('Scheduler completed: no jobs were due');
|
||||
}
|
||||
}
|
||||
|
||||
// Store run date
|
||||
file_put_contents("logs/lastcron.run", (new DateTime("now"))->format("Y-m-d H:i:s"), LOCK_EX);
|
||||
}
|
||||
@@ -474,15 +515,14 @@ class Scheduler
|
||||
Folder::create($this->historyPath);
|
||||
}
|
||||
|
||||
// Initialize job queue
|
||||
// Initialize job queue (always enabled)
|
||||
$this->jobQueue = new JobQueue($this->queuePath);
|
||||
|
||||
// Initialize job history
|
||||
$retentionDays = $this->modernConfig['history']['retention_days'] ?? 30;
|
||||
$this->jobHistory = new JobHistory($this->historyPath, $retentionDays);
|
||||
// Initialize scheduler logger
|
||||
$this->initializeLogger($locator);
|
||||
|
||||
// Configure workers
|
||||
$this->maxWorkers = $this->modernConfig['workers'] ?? 1;
|
||||
// Configure workers (default to 4 for concurrent processing)
|
||||
$this->maxWorkers = $this->modernConfig['workers'] ?? 4;
|
||||
|
||||
// Configure webhook
|
||||
$this->webhookEnabled = $this->modernConfig['webhook']['enabled'] ?? false;
|
||||
@@ -503,13 +543,28 @@ class Scheduler
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the job history
|
||||
* Initialize the scheduler logger
|
||||
*
|
||||
* @return JobHistory|null
|
||||
* @param $locator
|
||||
* @return void
|
||||
*/
|
||||
public function getHistory(): ?JobHistory
|
||||
protected function initializeLogger($locator): void
|
||||
{
|
||||
return $this->jobHistory;
|
||||
$this->logger = new Logger('scheduler');
|
||||
|
||||
// Single scheduler log file - all levels
|
||||
$logFile = $locator->findResource('log://scheduler.log', true, true);
|
||||
$this->logger->pushHandler(new StreamHandler($logFile, Logger::DEBUG));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scheduler logger
|
||||
*
|
||||
* @return Logger|null
|
||||
*/
|
||||
public function getLogger(): ?Logger
|
||||
{
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -678,13 +733,26 @@ class Scheduler
|
||||
$worker['job']->finalize();
|
||||
$this->saveJobState($worker['job']);
|
||||
|
||||
// Log background job completion to history
|
||||
if ($this->jobHistory) {
|
||||
$metadata = [
|
||||
'queue_id' => $worker['queueId'] ?? null,
|
||||
'background' => true
|
||||
];
|
||||
$this->jobHistory->logExecution($worker['job'], $metadata);
|
||||
// Log background job completion
|
||||
if ($this->logger) {
|
||||
$job = $worker['job'];
|
||||
$jobId = $job->getId();
|
||||
$command = is_string($job->getCommand()) ? $job->getCommand() : 'Closure';
|
||||
|
||||
if ($job->isSuccessful()) {
|
||||
$execTime = method_exists($job, 'getExecutionTime') ? $job->getExecutionTime() : null;
|
||||
$timeStr = $execTime ? sprintf(' (%.2fs)', $execTime) : '';
|
||||
$this->logger->info("Job '{$jobId}' completed successfully{$timeStr}", [
|
||||
'command' => $command,
|
||||
'background' => true
|
||||
]);
|
||||
} else {
|
||||
$error = trim($job->getOutput()) ?: 'Unknown error';
|
||||
$this->logger->error("Job '{$jobId}' failed: {$error}", [
|
||||
'command' => $command,
|
||||
'background' => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -775,9 +843,22 @@ class Scheduler
|
||||
}
|
||||
|
||||
// Log foreground jobs immediately
|
||||
if (!$job->runInBackground() && $this->jobHistory) {
|
||||
$metadata = ['queue_id' => $queueId];
|
||||
$this->jobHistory->logExecution($job, $metadata);
|
||||
if (!$job->runInBackground() && $this->logger) {
|
||||
$jobId = $job->getId();
|
||||
$command = is_string($job->getCommand()) ? $job->getCommand() : 'Closure';
|
||||
|
||||
if ($job->isSuccessful()) {
|
||||
$execTime = method_exists($job, 'getExecutionTime') ? $job->getExecutionTime() : null;
|
||||
$timeStr = $execTime ? sprintf(' (%.2fs)', $execTime) : '';
|
||||
$this->logger->info("Job '{$jobId}' completed successfully{$timeStr}", [
|
||||
'command' => $command
|
||||
]);
|
||||
} else {
|
||||
$error = trim($job->getOutput()) ?: 'Unknown error';
|
||||
$this->logger->error("Job '{$jobId}' failed: {$error}", [
|
||||
'command' => $command
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user