332748a1创建于 2025年9月3日历史提交
title: PLUGIN_ADMIN.SCHEDULER

form:
    validation: loose

    fields:
        scheduler_tabs:
            type: tabs
            active: 1

            fields:
                status_tab:
                    type: tab
                    title: PLUGIN_ADMIN.SCHEDULER_STATUS

                    fields:
                        status_title:
                            type: section
                            title: PLUGIN_ADMIN.SCHEDULER_STATUS
                            underline: true

                        status:
                            type: cronstatus
                            validate:
                                type: commalist
                                
                        webhook_status_override:
                            type: display
                            label:
                            content: |
                                <script>
                                (function() {
                                    function updateSchedulerStatus() {
                                        // Find all notice bars
                                        var notices = document.querySelectorAll('.notice');
                                        var webhookStatusChecked = false;
                                        
                                        // Check for modern scheduler and webhook settings
                                        fetch(window.location.origin + '/grav-editor-pro/scheduler/health')
                                            .then(response => response.json())
                                            .then(data => {
                                                if (data.webhook_enabled) {
                                                    notices.forEach(function(notice) {
                                                        if (notice.textContent.includes('Not Enabled for user:')) {
                                                            // This is the cron status notice - replace it
                                                            notice.className = 'notice info';
                                                            notice.innerHTML = '<i class="fa fa-fw fa-check-circle"></i> <strong>Webhook Active</strong> - Scheduler can be triggered via webhook. Cron is not configured.';
                                                        }
                                                    });
                                                    
                                                    // Also update the main status if it exists
                                                    var statusDiv = document.querySelector('.cronstatus-status');
                                                    if (statusDiv && statusDiv.textContent.includes('Not Enabled')) {
                                                        statusDiv.className = 'cronstatus-status success';
                                                        statusDiv.innerHTML = '<i class="fa fa-fw fa-check"></i> Webhook Ready';
                                                    }
                                                }
                                            })
                                            .catch(error => {
                                                console.log('Webhook status check failed:', error);
                                            });
                                    }
                                    
                                    // Run on page load
                                    if (document.readyState === 'loading') {
                                        document.addEventListener('DOMContentLoaded', updateSchedulerStatus);
                                    } else {
                                        updateSchedulerStatus();
                                    }
                                    
                                    // Also run after a short delay to catch any late-rendered elements
                                    setTimeout(updateSchedulerStatus, 500);
                                })();
                                </script>
                            markdown: false
                                
                        status_enhanced:
                            type: display
                            label:
                            content: |
                                <script>
                                document.addEventListener('DOMContentLoaded', function() {
                                    // Check if webhook is enabled
                                    var webhookEnabled = document.querySelector('[name="data[scheduler][modern][webhook][enabled]"]:checked');
                                    var statusDiv = document.querySelector('.cronstatus-status');
                                    
                                    // Also find the parent notice bar
                                    var noticeBar = document.querySelector('.notice.alert');
                                    
                                    if (statusDiv) {
                                        var currentStatus = statusDiv.textContent || statusDiv.innerText;
                                        var cronReady = currentStatus.includes('Ready');
                                        var cronNotEnabled = currentStatus.includes('Not Enabled');
                                        
                                        // Check if scheduler-webhook plugin exists
                                        var webhookPluginInstalled = false;
                                        fetch(window.location.origin + '/grav-editor-pro/scheduler/health')
                                            .then(response => response.json())
                                            .then(data => {
                                                webhookPluginInstalled = true;
                                                updateStatusDisplay(data);
                                            })
                                            .catch(error => {
                                                updateStatusDisplay(null);
                                            });
                                        
                                        function updateStatusDisplay(healthData) {
                                            var isWebhookEnabled = webhookEnabled && webhookEnabled.value == '1';
                                            var isWebhookReady = webhookPluginInstalled && isWebhookEnabled && healthData && healthData.webhook_enabled;
                                            
                                            // Update the main status text
                                            var mainStatusText = '';
                                            var mainStatusClass = '';
                                            
                                            if (cronReady && isWebhookReady) {
                                                mainStatusText = 'Cron and Webhook Ready';
                                                mainStatusClass = 'success';
                                            } else if (cronReady) {
                                                mainStatusText = 'Cron Ready';
                                                mainStatusClass = 'success';
                                            } else if (isWebhookReady) {
                                                mainStatusText = 'Webhook Ready (No Cron)';
                                                mainStatusClass = 'success'; // Changed from warning to success
                                            } else if (cronNotEnabled && !isWebhookReady) {
                                                mainStatusText = 'Not Configured';
                                                mainStatusClass = 'error';
                                            } else {
                                                mainStatusText = 'Configuration Pending';
                                                mainStatusClass = 'warning';
                                            }
                                            
                                            // Update the notice bar if webhooks are ready
                                            if (noticeBar && isWebhookReady) {
                                                // Change from error (red) to success (green) or info (blue)
                                                noticeBar.classList.remove('alert');
                                                noticeBar.classList.add('info');
                                                
                                                var noticeIcon = noticeBar.querySelector('i.fa');
                                                if (noticeIcon) {
                                                    noticeIcon.classList.remove('fa-times-circle');
                                                    noticeIcon.classList.add('fa-check-circle');
                                                }
                                                
                                                var noticeText = noticeBar.querySelector('strong') || noticeBar;
                                                var username = noticeText.textContent.match(/user:\s*(\w+)/);
                                                if (username) {
                                                    noticeText.innerHTML = 'Webhook Ready for user: <b>' + username[1] + '</b> (Cron not configured)';
                                                } else {
                                                    noticeText.innerHTML = mainStatusText;
                                                }
                                            }
                                            
                                            // Update the main status div
                                            if (statusDiv) {
                                                statusDiv.innerHTML = '<i class="fa fa-fw fa-' + 
                                                    (mainStatusClass === 'success' ? 'check' : mainStatusClass === 'warning' ? 'exclamation' : 'times') + 
                                                    '"></i> ' + mainStatusText;
                                                statusDiv.className = 'cronstatus-status ' + mainStatusClass;
                                            }
                                            
                                            // Update install instructions button/content
                                            var installButton = document.querySelector('.cronstatus-install-button');
                                            var installDiv = document.querySelector('.cronstatus-install');
                                            
                                            if (installDiv) {
                                                var installHtml = '<div class="alert alert-info">';
                                                installHtml += '<h4>Setup Instructions:</h4>';
                                                
                                                var hasInstructions = false;
                                                
                                                // Cron setup
                                                if (!cronReady) {
                                                    installHtml += '<p><strong>Option 1: Traditional Cron</strong><br>';
                                                    installHtml += 'Run: <code>bin/grav scheduler --install</code><br>';
                                                    installHtml += 'This will add a cron job that runs every minute.</p>';
                                                    hasInstructions = true;
                                                }
                                                
                                                // Webhook setup
                                                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>';
                                                    }
                                                }
                                                
                                                if (!hasInstructions && cronReady) {
                                                    installHtml += '<p><strong>✅ Cron is configured and ready!</strong><br>';
                                                    installHtml += 'The scheduler runs automatically every minute via system cron.</p>';
                                                    
                                                }
                                                
                                                installHtml += '</div>';
                                                installDiv.innerHTML = installHtml;
                                                
                                                // Update button text based on status
                                                if (installButton) {
                                                    if (cronReady && isWebhookReady) {
                                                        installButton.innerHTML = '<i class="fa fa-info-circle"></i> Configuration Details';
                                                    } else if (cronReady || isWebhookReady) {
                                                        installButton.innerHTML = '<i class="fa fa-plus-circle"></i> Add More Triggers';
                                                    } else {
                                                        installButton.innerHTML = '<i class="fa fa-exclamation-triangle"></i> Install Instructions';
                                                    }
                                                }
                                            }
                                        }
                                    }
                                });
                                </script>

                        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())
                                                    .then(data => {
                                                        var statusEl = document.getElementById('scheduler-health-status');
                                                        if (!statusEl) return;
                                                        
                                                        // Modern card-based layout
                                                        var statusColor = '#6c757d';
                                                        var statusLabel = data.status || 'unknown';
                                                        if (data.status === 'healthy') statusColor = '#28a745';
                                                        else if (data.status === 'warning') statusColor = '#ffc107';
                                                        else if (data.status === 'critical') statusColor = '#dc3545';
                                                        
                                                        var html = '<div style="display: flex; flex-direction: column; gap: 1rem;">';
                                                        
                                                        // Status card
                                                        html += '<div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 1rem; background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%); border-radius: 6px; border: 1px solid #e9ecef; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">';
                                                        html += '<span style="font-weight: 500; color: #495057;">Status:</span>';
                                                        html += '<span style="background: ' + statusColor + '; color: white; padding: 0.375rem 0.75rem; font-size: 0.875rem; font-weight: 500; border-radius: 4px; text-transform: uppercase; letter-spacing: 0.025em;">' + statusLabel + '</span>';
                                                        html += '</div>';
                                                        
                                                        // Info grid
                                                        html += '<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem;">';
                                                        
                                                        // Last run card
                                                        html += '<div style="background: white; border: 1px solid #e9ecef; border-radius: 6px; padding: 0.75rem; box-shadow: 0 1px 2px rgba(0,0,0,0.03);">';
                                                        html += '<div style="color: #6c757d; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.25rem;">Last Run</div>';
                                                        if (data.last_run) {
                                                            var age = data.last_run_age;
                                                            var ageText = 'just now';
                                                            if (age > 86400) {
                                                                ageText = Math.floor(age / 86400) + ' day(s) ago';
                                                            } else if (age > 3600) {
                                                                ageText = Math.floor(age / 3600) + ' hour(s) ago';
                                                            } else if (age > 60) {
                                                                ageText = Math.floor(age / 60) + ' minute(s) ago';
                                                            } else if (age > 0) {
                                                                ageText = age + ' second(s) ago';
                                                            }
                                                            html += '<div style="font-size: 1rem; color: #212529; font-weight: 500;">' + ageText + '</div>';
                                                        } else {
                                                            html += '<div style="font-size: 1rem; color: #6c757d;">Never</div>';
                                                        }
                                                        html += '</div>';
                                                        
                                                        // Jobs count card
                                                        html += '<div style="background: white; border: 1px solid #e9ecef; border-radius: 6px; padding: 0.75rem; box-shadow: 0 1px 2px rgba(0,0,0,0.03);">';
                                                        html += '<div style="color: #6c757d; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.25rem;">Scheduled Jobs</div>';
                                                        html += '<div style="font-size: 1rem; color: #212529; font-weight: 500;">' + (data.scheduled_jobs || 0) + '</div>';
                                                        html += '</div>';
                                                        
                                                        html += '</div>'; // Close grid
                                                        
                                                        // Additional info if available
                                                        if (data.modern_features && data.queue_size !== undefined) {
                                                            html += '<div style="background: white; border: 1px solid #e9ecef; border-radius: 6px; padding: 0.75rem; box-shadow: 0 1px 2px rgba(0,0,0,0.03);">';
                                                            html += '<span style="color: #6c757d; font-size: 0.875rem;">Queue Size: </span>';
                                                            html += '<span style="font-weight: 500;">' + data.queue_size + '</span>';
                                                            html += '</div>';
                                                        }
                                                        
                                                        // Failed jobs warning
                                                        if (data.failed_jobs_24h > 0) {
                                                            html += '<div style="background: #fff5f5; border: 1px solid #feb2b2; border-radius: 6px; padding: 0.75rem; color: #c53030;">';
                                                            html += '<strong>⚠️ Failed Jobs (24h):</strong> ' + data.failed_jobs_24h;
                                                            html += '</div>';
                                                        }
                                                        
                                                        html += '</div>'; // Close main container
                                                        statusEl.innerHTML = html;
                                                    })
                                                    .catch(error => {
                                                        var statusEl = document.getElementById('scheduler-health-status');
                                                        if (statusEl) {
                                                            statusEl.innerHTML = '<div class="alert alert-warning">Unable to fetch health status. Ensure scheduler-webhook plugin is installed.</div>';
                                                        }
                                                    });
                                            }
                                            
                                            // Load on page ready
                                            if (document.readyState === 'loading') {
                                                document.addEventListener('DOMContentLoaded', loadHealthStatus);
                                            } else {
                                                loadHealthStatus();
                                            }
                                            
                                    // 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() {
                                            function loadTriggers() {
                                                // Check cron status from the main status field
                                                var cronReady = false;
                                                var statusDiv = document.querySelector('.cronstatus-status');
                                                if (statusDiv) {
                                                    var statusText = statusDiv.textContent || statusDiv.innerText;
                                                    cronReady = statusText.includes('Ready');
                                                }
                                                
                                                // Check webhook status
                                                fetch(window.location.origin + '/grav-editor-pro/scheduler/health')
                                                    .then(response => response.json())
                                                    .then(data => {
                                                        var triggersEl = document.getElementById('scheduler-triggers');
                                                        if (!triggersEl) return;
                                                        
                                                        var html = '<div style="display: flex; flex-direction: column; gap: 0.5rem;">';
                                                        
                                                        // Cron trigger card
                                                        var cronIcon = cronReady ? '✅' : '❌';
                                                        var cronStatus = cronReady ? 'Active' : 'Not Configured';
                                                        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: ' + 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: ' + 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: #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.75rem; font-size: 0.875rem; font-weight: 500; border-radius: 3px; text-transform: uppercase; letter-spacing: 0.025em;">ACTIVE</span>';
                                                            html += '</div>';
                                                        } else {
                                                            // 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" style="margin-top: 1rem;"><i class="fa fa-exclamation-triangle"></i> No triggers active! Configure cron or enable webhooks.</div>';
                                                        }
                                                        
                                                        triggersEl.innerHTML = html;
                                                    })
                                                    .catch(error => {
                                                        var triggersEl = document.getElementById('scheduler-triggers');
                                                        if (triggersEl) {
                                                            // Show just cron status if health endpoint not available
                                                            var html = '<ul class="list-unstyled">';
                                                            if (cronReady) {
                                                                html += '<li>✅ <strong>Cron:</strong> <span class="badge badge-success">Active</span></li>';
                                                            } else {
                                                                html += '<li>❌ <strong>Cron:</strong> <span class="badge badge-secondary">Not Configured</span></li>';
                                                            }
                                                            html += '<li>⚠️ <strong>Webhook:</strong> <span class="badge badge-secondary">Plugin Not Installed</span></li>';
                                                            html += '</ul>';
                                                            triggersEl.innerHTML = html;
                                                        }
                                                    });
                                            }
                                            
                                            // Load on page ready
                                            if (document.readyState === 'loading') {
                                                document.addEventListener('DOMContentLoaded', loadTriggers);
                                            } else {
                                                loadTriggers();
                                    }
                                })();
                                </script>
                            markdown: false

                jobs_tab:
                    type: tab
                    title: PLUGIN_ADMIN.SCHEDULER_JOBS

                    fields:
                        jobs_title:
                            type: section
                            title: PLUGIN_ADMIN.SCHEDULER_JOBS
                            underline: true

                        custom_jobs:
                            type: list
                            style: vertical
                            label:
                            classes: cron-job-list compact
                            key: id
                            fields:
                                .id:
                                    type: key
                                    label: ID
                                    placeholder: 'process-name'
                                    validate:
                                        required: true
                                        pattern: '[a-zа-я0-9_\-]+'
                                        max: 20
                                        message: 'ID must be lowercase with dashes/underscores only and less than 20 characters'
                                .command:
                                    type: text
                                    label: PLUGIN_ADMIN.COMMAND
                                    placeholder: 'ls'
                                    validate:
                                        required: true
                                .args:
                                    type: text
                                    label: PLUGIN_ADMIN.EXTRA_ARGUMENTS
                                    placeholder: '-lah'
                                .at:
                                    type: text
                                    wrapper_classes: cron-selector
                                    label: PLUGIN_ADMIN.SCHEDULER_RUNAT
                                    help: PLUGIN_ADMIN.SCHEDULER_RUNAT_HELP
                                    placeholder: '* * * * *'
                                    validate:
                                        required: true
                                .output:
                                    type: text
                                    label: PLUGIN_ADMIN.SCHEDULER_OUTPUT
                                    help: PLUGIN_ADMIN.SCHEDULER_OUTPUT_HELP
                                    placeholder: 'logs/ls-cron.out'
                                .output_mode:
                                    type: select
                                    label: PLUGIN_ADMIN.SCHEDULER_OUTPUT_TYPE
                                    help: PLUGIN_ADMIN.SCHEDULER_OUTPUT_TYPE_HELP
                                    default: append
                                    options:
                                        append: Append
                                        overwrite: Overwrite
                                .email:
                                    type: text
                                    label: PLUGIN_ADMIN.SCHEDULER_EMAIL
                                    help: PLUGIN_ADMIN.SCHEDULER_EMAIL_HELP
                                    placeholder: 'notifications@yoursite.com'

                modern_tab:
                    type: tab
                    title: Advanced Features

                    fields:
                        workers_section:
                            type: section
                            title: Worker Configuration
                            underline: true

                            fields:
                                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_status:
                                    type: webhook-status
                                    label:
                                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() {
                                                    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

                                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

                            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'
                                            
                        webhook_usage:
                            type: section
                            title: Usage Examples
                            underline: true
                            
                            fields:
                                webhook_examples:
                                    type: display
                                    label:
                                    content: |
                                                <script src="{{ url('plugin://admin/themes/grav/js/clipboard-helper.js') }}"></script>
                                                <div class="webhook-examples">
                                                    <script>
                                                    // Initialize webhook commands when page loads
                                                    document.addEventListener('DOMContentLoaded', function() {
                                                        if (typeof GravClipboard !== 'undefined') {
                                                            GravClipboard.initWebhookCommands();
                                                        }
                                                    });
                                                    </script>
                                                    
                                                    <div class="alert alert-info">
                                                        <h4>How to use webhooks:</h4>
                                                        
                                                        <div style="margin-bottom: 1rem;">
                                                            <label style="display: block; margin-bottom: 0.25rem; font-weight: 500;">Trigger all due jobs (respects schedule):</label>
                                                            <div class="form-input-wrapper form-input-addon-wrapper">
                                                                <textarea id="webhook-all-cmd" readonly rows="2" style="font-family: monospace; background: #f5f5f5; resize: none;">Loading...</textarea>
                                                                <div class="form-input-addon form-input-append" style="cursor: pointer;" onclick="GravClipboard.copy(this)"><i class="fa fa-copy"></i> Copy</div>
                                                            </div>
                                                        </div>
                                                        
                                                        <div style="margin-bottom: 1rem;">
                                                            <label style="display: block; margin-bottom: 0.25rem; font-weight: 500;">Force-run specific job (ignores schedule):</label>
                                                            <div class="form-input-wrapper form-input-addon-wrapper">
                                                                <textarea id="webhook-job-cmd" readonly rows="2" style="font-family: monospace; background: #f5f5f5; resize: none;">Loading...</textarea>
                                                                <div class="form-input-addon form-input-append" style="cursor: pointer;" onclick="GravClipboard.copy(this)"><i class="fa fa-copy"></i> Copy</div>
                                                            </div>
                                                        </div>
                                                        
                                                        <div style="margin-bottom: 1rem;">
                                                            <label style="display: block; margin-bottom: 0.25rem; font-weight: 500;">Check health status:</label>
                                                            <div class="form-input-wrapper form-input-addon-wrapper">
                                                                <input type="text" id="webhook-health-cmd" readonly value="Loading..." style="font-family: monospace; background: #f5f5f5;">
                                                                <div class="form-input-addon form-input-append" style="cursor: pointer;" onclick="GravClipboard.copy(this)"><i class="fa fa-copy"></i> Copy</div>
                                                            </div>
                                                        </div>
                                                        
                                                        <div style="margin-top: 1rem;">
                                                            <p><strong>GitHub Actions example:</strong></p>
                                                            <pre>- name: Trigger Scheduler
                                                  run: |
                                                    curl -X POST ${{ secrets.SITE_URL }}/scheduler/webhook \
                                                      -H "Authorization: Bearer ${{ secrets.WEBHOOK_TOKEN }}"</pre>
                                                        </div>
                                                    </div>
                                                </div>
                                    markdown: false