// Code generated by templ - DO NOT EDIT.

// templ: version: v0.3.1001
package app

//lint:file-ignore SA4006 This context is only used if a nested component is present.

import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"

func Plugin(page string, initialJob string, lane string) templ.Component {
	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
			return templ_7745c5c3_CtxErr
		}
		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
		if !templ_7745c5c3_IsBuffer {
			defer func() {
				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
				if templ_7745c5c3_Err == nil {
					templ_7745c5c3_Err = templ_7745c5c3_BufErr
				}
			}()
		}
		ctx = templ.InitializeContext(ctx)
		templ_7745c5c3_Var1 := templ.GetChildren(ctx)
		if templ_7745c5c3_Var1 == nil {
			templ_7745c5c3_Var1 = templ.NopComponent
		}
		ctx = templ.ClearChildren(ctx)
		currentPage := page
		if currentPage == "" {
			currentPage = "overview"
		}
		currentLane := lane
		if currentLane == "" {
			currentLane = "default"
		}
		laneTitle := pluginLaneTitle(currentLane)
		laneDescription := pluginLaneDescription(currentLane)
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"container-fluid\" id=\"plugin-page\" data-plugin-page=\"")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		var templ_7745c5c3_Var2 string
		templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(currentPage)
		if templ_7745c5c3_Err != nil {
			return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/plugin.templ`, Line: 16, Col: 80}
		}
		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" data-plugin-job=\"")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		var templ_7745c5c3_Var3 string
		templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(initialJob)
		if templ_7745c5c3_Err != nil {
			return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/plugin.templ`, Line: 16, Col: 111}
		}
		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" data-plugin-lane=\"")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		var templ_7745c5c3_Var4 string
		templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(currentLane)
		if templ_7745c5c3_Err != nil {
			return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/plugin.templ`, Line: 16, Col: 144}
		}
		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"d-flex justify-content-between align-items-center flex-wrap gap-2\"><div><h2 class=\"mb-0\"><i class=\"fas fa-plug me-2\"></i>")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		var templ_7745c5c3_Var5 string
		templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(laneTitle)
		if templ_7745c5c3_Err != nil {
			return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/plugin.templ`, Line: 21, Col: 84}
		}
		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</h2><p class=\"text-muted mb-0\">")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		var templ_7745c5c3_Var6 string
		templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(laneDescription)
		if templ_7745c5c3_Err != nil {
			return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/plugin.templ`, Line: 22, Col: 68}
		}
		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</p></div><div class=\"btn-group\"><button type=\"button\" class=\"btn btn-outline-secondary\" id=\"plugin-refresh-all-btn\"><i class=\"fas fa-sync-alt me-1\"></i>Refresh</button></div></div></div></div><div class=\"row mb-3\"><div class=\"col-12\"><ul class=\"nav nav-tabs\" id=\"plugin-top-tabs\"><li class=\"nav-item\"><button type=\"button\" class=\"nav-link active\" data-plugin-top-tab=\"overview\"><i class=\"fas fa-chart-line me-1\"></i>Overview</button></li></ul></div></div><div class=\"row mb-3 d-none\" id=\"plugin-subtab-row\"><div class=\"col-12\"><ul class=\"nav nav-pills gap-2\" id=\"plugin-subtabs\"><li class=\"nav-item\"><button type=\"button\" class=\"nav-link active\" data-plugin-subtab=\"configuration\"><i class=\"fas fa-sliders-h me-1\"></i>Configuration</button></li><li class=\"nav-item\"><button type=\"button\" class=\"nav-link\" data-plugin-subtab=\"detection\"><i class=\"fas fa-search me-1\"></i>Job Detection</button></li><li class=\"nav-item\"><button type=\"button\" class=\"nav-link\" data-plugin-subtab=\"queue\"><i class=\"fas fa-list me-1\"></i>Job Queue</button></li><li class=\"nav-item\"><button type=\"button\" class=\"nav-link\" data-plugin-subtab=\"execution\"><i class=\"fas fa-tasks me-1\"></i>Job Execution</button></li></ul></div></div><div class=\"plugin-section plugin-section-overview\"><div class=\"row mb-4\"><div class=\"col-md-3 mb-3\"><div class=\"card border-info h-100\"><div class=\"card-body text-center\"><div class=\"text-uppercase text-muted small mb-1\">Workers</div><h4 class=\"mb-0\" id=\"plugin-status-workers\">0</h4></div></div></div><div class=\"col-md-3 mb-3\"><div class=\"card border-warning h-100\"><div class=\"card-body text-center\"><div class=\"text-uppercase text-muted small mb-1\">Active Jobs</div><h4 class=\"mb-0\" id=\"plugin-status-active-jobs\">0</h4></div></div></div><div class=\"col-md-3 mb-3\"><div class=\"card border-success h-100\"><div class=\"card-body text-center\"><div class=\"text-uppercase text-muted small mb-1\">Activities (recent)</div><h4 class=\"mb-0\" id=\"plugin-status-activities\">0</h4></div></div></div><div class=\"col-md-3 mb-3\"><div class=\"card border-secondary h-100\"><div class=\"card-body text-center\"><div class=\"text-uppercase text-muted small mb-1\">Next Run</div><h4 class=\"mb-0 plugin-scheduler-next-run\">-</h4><div class=\"text-muted small plugin-scheduler-next-run-meta\"></div></div></div></div></div><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"card shadow-sm\"><div class=\"card-header\"><h5 class=\"mb-0\"><i class=\"fas fa-chart-bar me-2\"></i>Per Job Type Summary</h5></div><div class=\"card-body p-0\"><div class=\"table-responsive\"><table class=\"table table-sm table-hover mb-0\"><thead class=\"table-light\"><tr><th>Job Type</th><th>Active Jobs</th><th>Recent Activities</th></tr></thead> <tbody id=\"plugin-jobtype-summary-body\"><tr><td colspan=\"3\" class=\"text-muted text-center py-3\">Loading...</td></tr></tbody></table></div></div></div></div></div><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"card shadow-sm\"><div class=\"card-header d-flex justify-content-between align-items-center flex-wrap gap-2\"><h5 class=\"mb-0\"><i class=\"fas fa-clock me-2\"></i>Scheduler State</h5><small class=\"text-muted\">Sequential scheduler with per-job runtime limits</small></div><div class=\"card-body p-0\"><div class=\"table-responsive\"><table class=\"table table-sm table-hover mb-0\"><thead class=\"table-light\"><tr><th>Job Type</th><th>Enabled</th><th>Detector</th><th>In Flight</th><th>Max Runtime</th><th>Exec Global</th><th>Exec/Worker</th><th>Executor Workers</th><th>Effective Exec</th><th>Last Run</th></tr></thead> <tbody id=\"plugin-scheduler-table-body\"><tr><td colspan=\"10\" class=\"text-muted text-center py-3\">Loading...</td></tr></tbody></table></div></div></div></div></div><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"card shadow-sm\"><div class=\"card-header\"><h5 class=\"mb-0\"><i class=\"fas fa-users me-2\"></i>Workers</h5></div><div class=\"card-body p-0\"><div class=\"table-responsive\" style=\"max-height: 560px; overflow-y: auto;\"><table class=\"table table-sm table-hover mb-0\"><thead class=\"table-light sticky-top\"><tr><th>Worker</th><th>Address</th><th>Capabilities</th><th>Load</th></tr></thead> <tbody id=\"plugin-workers-table-body\"><tr><td colspan=\"4\" class=\"text-muted text-center py-3\">Loading...</td></tr></tbody></table></div></div></div></div></div></div><div class=\"plugin-section plugin-section-configuration\"><div class=\"row mb-4\"><div class=\"col-lg-8 mb-3\"><div class=\"card shadow-sm h-100\"><div class=\"card-header d-flex justify-content-between align-items-center\"><h5 class=\"mb-0\"><i class=\"fas fa-sliders-h me-2\"></i>Job Type Configuration</h5><small class=\"text-muted\" id=\"plugin-config-updated-at\">Not loaded</small></div><div class=\"card-body\"><div class=\"mb-3\"><div class=\"small text-muted\">Selected Job Type</div><div class=\"fw-semibold\" id=\"plugin-selected-job-type\">-</div></div><div class=\"mb-3\"><div class=\"d-flex justify-content-between align-items-center mb-2\"><h6 class=\"mb-0\">Descriptor</h6><button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" id=\"plugin-refresh-schema-btn\"><i class=\"fas fa-cloud-download-alt me-1\"></i>Refresh Schema</button></div><div class=\"alert alert-light border\" id=\"plugin-descriptor-summary\">Select a job type to load schema and config.</div></div><div class=\"mb-3\"><h6>Admin Config Form</h6><div id=\"plugin-admin-config-form\" class=\"plugin-form-root\"><div class=\"text-muted\">No admin form loaded.</div></div></div><div class=\"mb-3\"><h6>Worker Config Form</h6><div id=\"plugin-worker-config-form\" class=\"plugin-form-root\"><div class=\"text-muted\">No worker form loaded.</div></div></div><div class=\"d-flex flex-wrap gap-2\"><button type=\"button\" class=\"btn btn-primary\" id=\"plugin-save-config-btn\"><i class=\"fas fa-save me-1\"></i>Save Config</button> <button type=\"button\" class=\"btn btn-outline-primary\" id=\"plugin-trigger-detection-btn\"><i class=\"fas fa-search me-1\"></i>Run Detection</button> <button type=\"button\" class=\"btn btn-outline-success\" id=\"plugin-run-workflow-btn\"><i class=\"fas fa-play me-1\"></i>Run Detect + Execute</button></div></div></div></div><div class=\"col-lg-4 mb-3\"><div class=\"card shadow-sm\"><div class=\"card-header\"><h5 class=\"mb-0\"><i class=\"fas fa-cogs me-2\"></i>Job Scheduling Settings</h5></div><div class=\"card-body\"><div class=\"row g-2\" id=\"plugin-admin-settings-form\"><div class=\"col-12\"><label class=\"form-label\" for=\"plugin-admin-enabled\">Enabled</label><div class=\"form-check form-switch mt-1\"><input class=\"form-check-input\" type=\"checkbox\" id=\"plugin-admin-enabled\"></div></div><div class=\"col-12\"><label class=\"form-label\" for=\"plugin-admin-detection-interval\">Detection Interval (min)</label> <input type=\"number\" class=\"form-control\" id=\"plugin-admin-detection-interval\" min=\"0\"><div class=\"form-text\">How often to check for new work.</div></div><div class=\"col-12\"><label class=\"form-label\" for=\"plugin-admin-detection-timeout\">Detection Timeout (s)</label> <input type=\"number\" class=\"form-control\" id=\"plugin-admin-detection-timeout\" min=\"0\"></div><div class=\"col-12\"><label class=\"form-label\" for=\"plugin-admin-execution-timeout\">Execution Timeout (s)</label> <input type=\"number\" class=\"form-control\" id=\"plugin-admin-execution-timeout\" min=\"0\"><div class=\"form-text\">Per-attempt deadline for one job. Size-aware tasks may extend this automatically.</div></div><div class=\"col-12\"><label class=\"form-label\" for=\"plugin-admin-max-runtime\">Job Type Max Runtime (s)</label> <input type=\"number\" class=\"form-control\" id=\"plugin-admin-max-runtime\" min=\"0\"></div><div class=\"col-12\"><label class=\"form-label\" for=\"plugin-admin-max-results\">Max Jobs / Detection</label> <input type=\"number\" class=\"form-control\" id=\"plugin-admin-max-results\" min=\"0\"></div><div class=\"col-12\"><label class=\"form-label\" for=\"plugin-admin-global-exec\">Global Execution Concurrency</label> <input type=\"number\" class=\"form-control\" id=\"plugin-admin-global-exec\" min=\"0\"></div><div class=\"col-12\"><label class=\"form-label\" for=\"plugin-admin-per-worker-exec\">Per Worker Concurrency</label> <input type=\"number\" class=\"form-control\" id=\"plugin-admin-per-worker-exec\" min=\"0\"></div><div class=\"col-12\"><label class=\"form-label\" for=\"plugin-admin-retry-limit\">Retry Limit</label> <input type=\"number\" class=\"form-control\" id=\"plugin-admin-retry-limit\" min=\"0\"></div><div class=\"col-12\"><label class=\"form-label\" for=\"plugin-admin-retry-backoff\">Retry Backoff (s)</label> <input type=\"number\" class=\"form-control\" id=\"plugin-admin-retry-backoff\" min=\"0\"></div></div></div></div><div class=\"card shadow-sm mt-3\"><div class=\"card-header d-flex justify-content-between align-items-center\"><h5 class=\"mb-0\"><i class=\"fas fa-hourglass-half me-2\"></i>Next Run</h5><small class=\"text-muted\">Scheduler</small></div><div class=\"card-body\"><div class=\"h5 mb-1 plugin-scheduler-next-run\">-</div><div class=\"text-muted small plugin-scheduler-next-run-meta\">Not scheduled</div></div></div></div></div><div class=\"row mb-4\"><div class=\"col-lg-6 mb-3\"><div class=\"card shadow-sm h-100\"><div class=\"card-header d-flex justify-content-between align-items-center\"><h5 class=\"mb-0\"><i class=\"fas fa-clipboard-check me-2\"></i>Run History</h5><small class=\"text-muted\">Keep last 10 success + last 10 errors</small></div><div class=\"card-body\"><div class=\"row\"><div class=\"col-12 mb-3\"><h6 class=\"text-success\">Successful Runs</h6><div class=\"table-responsive\"><table class=\"table table-sm table-striped\"><thead><tr><th>Time</th><th>Job ID</th><th>Worker</th><th>Duration</th></tr></thead> <tbody id=\"plugin-runs-success-body\"><tr><td colspan=\"4\" class=\"text-muted\">No data</td></tr></tbody></table></div></div><div class=\"col-12\"><h6 class=\"text-danger\">Error Runs</h6><div class=\"table-responsive\"><table class=\"table table-sm table-striped\"><thead><tr><th>Time</th><th>Job ID</th><th>Worker</th><th>Error</th></tr></thead> <tbody id=\"plugin-runs-error-body\"><tr><td colspan=\"4\" class=\"text-muted\">No data</td></tr></tbody></table></div></div></div></div></div></div><div class=\"col-lg-6 mb-3\"><div class=\"card shadow-sm h-100\"><div class=\"card-header\"><h5 class=\"mb-0\"><i class=\"fas fa-lightbulb me-2\"></i>Detection Results</h5></div><div class=\"card-body\" id=\"plugin-detection-results\"><div class=\"text-muted\">Run detection to see proposals.</div></div></div></div></div></div><div class=\"plugin-section plugin-section-queue\"><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"card shadow-sm\"><div class=\"card-header d-flex justify-content-between align-items-center flex-wrap gap-2\"><h5 class=\"mb-0\"><i class=\"fas fa-list me-2\"></i>Job Queue</h5><div class=\"d-flex gap-2 align-items-center flex-wrap\"><small class=\"text-muted\">States: pending/assigned/running</small></div></div><div class=\"card-body p-0\"><div class=\"table-responsive\" style=\"max-height: 560px; overflow-y: auto;\"><table class=\"table table-sm table-hover mb-0\"><thead class=\"table-light sticky-top\"><tr><th>Job ID</th><th>Type</th><th>State</th><th>Progress</th><th>Worker</th><th>Updated</th><th>Message</th></tr></thead> <tbody id=\"plugin-queue-jobs-table-body\"><tr><td colspan=\"7\" class=\"text-muted text-center py-3\">Loading...</td></tr></tbody></table></div></div></div></div></div></div><div class=\"plugin-section plugin-section-detection\"><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"card shadow-sm\"><div class=\"card-header d-flex justify-content-between align-items-center flex-wrap gap-2\"><h5 class=\"mb-0\"><i class=\"fas fa-search me-2\"></i>Detection Jobs</h5><div class=\"small text-muted\">Detection activities for selected job type</div></div><div class=\"card-body p-0\"><div class=\"table-responsive\" style=\"max-height: 420px; overflow-y: auto;\"><table class=\"table table-sm table-hover mb-0\"><thead class=\"table-light sticky-top\"><tr><th>Time</th><th>Job Type</th><th>Request ID</th><th>Worker</th><th>Stage</th><th>Source</th><th>Message</th></tr></thead> <tbody id=\"plugin-detection-table-body\"><tr><td colspan=\"7\" class=\"text-muted text-center py-3\">Loading...</td></tr></tbody></table></div></div></div></div></div></div><div class=\"plugin-section plugin-section-execution\"><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"card shadow-sm\"><div class=\"card-header d-flex justify-content-between align-items-center flex-wrap gap-2\"><h5 class=\"mb-0\"><i class=\"fas fa-tasks me-2\"></i>Execution Jobs</h5><div class=\"d-flex gap-2 align-items-center flex-wrap\"><select class=\"form-select form-select-sm\" id=\"plugin-monitor-job-state-filter\" style=\"min-width: 180px;\"><option value=\"\">All States</option> <option value=\"assigned\">Assigned</option> <option value=\"running\">Running</option> <option value=\"succeeded\">Succeeded</option> <option value=\"failed\">Failed</option> <option value=\"canceled\">Canceled</option></select></div></div><div class=\"card-body p-0\"><div class=\"table-responsive\"><table class=\"table table-hover mb-0\"><thead class=\"table-light\"><tr><th>Job ID</th><th>Type</th><th>State</th><th>Progress</th><th>Worker</th><th>Updated</th><th>Message</th></tr></thead> <tbody id=\"plugin-execution-jobs-table-body\"><tr><td colspan=\"7\" class=\"text-muted text-center py-3\">Loading...</td></tr></tbody></table></div></div></div></div></div><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"card shadow-sm\"><div class=\"card-header d-flex justify-content-between align-items-center flex-wrap gap-2\"><h5 class=\"mb-0\"><i class=\"fas fa-stream me-2\"></i>Execution Activities</h5><small class=\"text-muted\">Non-detection events only</small></div><div class=\"card-body p-0\"><div class=\"table-responsive\" style=\"max-height: 420px; overflow-y: auto;\"><table class=\"table table-sm table-hover mb-0\"><thead class=\"table-light sticky-top\"><tr><th>Time</th><th>Job Type</th><th>Job ID</th><th>Source</th><th>Stage</th><th>Message</th></tr></thead> <tbody id=\"plugin-execution-activities-table-body\"><tr><td colspan=\"6\" class=\"text-muted text-center py-3\">Loading...</td></tr></tbody></table></div></div></div></div></div></div></div><div class=\"modal fade\" id=\"plugin-job-detail-modal\" tabindex=\"-1\" aria-labelledby=\"plugin-job-detail-modal-label\" aria-hidden=\"true\"><div class=\"modal-dialog modal-xl modal-dialog-scrollable\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"plugin-job-detail-modal-label\"><i class=\"fas fa-file-alt me-2\"></i>Job Detail</h5><div class=\"ms-auto me-2\"><button type=\"button\" class=\"btn btn-outline-danger btn-sm\" id=\"plugin-expire-job-btn\" disabled><i class=\"fas fa-stop-circle me-1\"></i>Expire Job</button></div><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><div class=\"modal-body\" id=\"plugin-job-detail-content\"><div class=\"text-muted\">Select a job to view details.</div></div></div></div></div><style>\n        .plugin-section {\n            display: none;\n        }\n\n        .plugin-section.plugin-section-active {\n            display: block;\n        }\n\n        .plugin-form-root .card {\n            border: 1px solid #dee2e6;\n        }\n        .plugin-form-root textarea {\n            min-height: 12rem;\n        }\n\n        .plugin-field-hidden {\n            display: none;\n        }\n\n        .plugin-capability-badge {\n            font-size: 0.72rem;\n            margin-right: 0.25rem;\n        }\n\n        .plugin-job-progress {\n            min-width: 120px;\n        }\n\n        .plugin-detection-item {\n            border: 1px solid #dee2e6;\n            border-radius: 6px;\n            padding: 0.5rem 0.75rem;\n            margin-bottom: 0.5rem;\n            background: #fbfcfd;\n        }\n\n        .plugin-job-link {\n            text-decoration: none;\n        }\n\n        .plugin-job-detail-json {\n            max-height: 220px;\n            overflow: auto;\n            background: #f8f9fa;\n            border: 1px solid #dee2e6;\n            border-radius: 6px;\n            padding: 0.5rem;\n            margin-bottom: 0;\n            font-size: 0.82rem;\n        }\n        </style><script>\n        (function() {\n            var page = document.getElementById('plugin-page');\n            if (!page) {\n                return;\n            }\n\n            var pluginLane = String(page.getAttribute('data-plugin-lane') || '').trim();\n\n            function basePath(path) {\n                return (window.__BASE_PATH__ || '') + path;\n            }\n\n            var state = {\n                selectedJobType: '',\n                jobTypes: [],\n                lastDetectionByJobType: {},\n                initialPage: String(page.getAttribute('data-plugin-page') || 'overview').trim().toLowerCase(),\n                initialJob: String(page.getAttribute('data-plugin-job') || '').trim(),\n                activeTopTab: 'overview',\n                activeSubTab: 'configuration',\n                initialNavigationApplied: false,\n                monitorExecutionJobState: '',\n                workers: [],\n                jobs: [],\n                activities: [],\n                schedulerStates: [],\n                schedulerStatus: null,\n                allJobs: [],\n                allActivities: [],\n                loadedJobType: '',\n                descriptor: null,\n                config: null,\n                refreshTimer: null,\n            };\n\n            // appendLaneParam appends ?lane= or &lane= to a URL if a lane filter is active.\n            function appendLaneParam(url) {\n                if (!pluginLane) return url;\n                var sep = url.indexOf('?') === -1 ? '?' : '&';\n                return url + sep + 'lane=' + encodeURIComponent(pluginLane);\n            }\n\n            function getCSRFToken() {\n                var meta = document.querySelector('meta[name=\"csrf-token\"]');\n                if (!meta) {\n                    return '';\n                }\n                return meta.getAttribute('content') || '';\n            }\n\n            function notify(message, level) {\n                if (typeof window.showAlert === 'function') {\n                    window.showAlert(message, level || 'info');\n                    return;\n                }\n                if (level === 'error') {\n                    console.error(message);\n                }\n                alert(message);\n            }\n\n            async function readResponseJSON(response) {\n                var bodyText = await response.text();\n                if (!bodyText) {\n                    return {};\n                }\n                try {\n                    return JSON.parse(bodyText);\n                } catch (e) {\n                    return { error: bodyText };\n                }\n            }\n\n            async function pluginRequest(method, url, payload) {\n                var headers = {};\n                var requestOptions = {\n                    method: method,\n                    headers: headers,\n                };\n\n                if (payload !== undefined && payload !== null) {\n                    headers['Content-Type'] = 'application/json';\n                    requestOptions.body = JSON.stringify(payload);\n                }\n\n                if (method !== 'GET') {\n                    var csrf = getCSRFToken();\n                    if (csrf) {\n                        headers['X-CSRF-Token'] = csrf;\n                    }\n                }\n\n                var response = await fetch(basePath(url), requestOptions);\n                var data = await readResponseJSON(response);\n                if (!response.ok) {\n                    var message = (data && (data.error || data.message)) ? (data.error || data.message) : ('Request failed: ' + response.status);\n                    var requestError = new Error(message);\n                    requestError.responseData = data;\n                    requestError.responseStatus = response.status;\n                    throw requestError;\n                }\n                return data;\n            }\n\n            function textOrDash(value) {\n                if (value === null || value === undefined || value === '') {\n                    return '-';\n                }\n                return String(value);\n            }\n\n            function escapeHtml(value) {\n                if (value === null || value === undefined) {\n                    return '';\n                }\n                return String(value)\n                    .replace(/&/g, '&amp;')\n                    .replace(/</g, '&lt;')\n                    .replace(/>/g, '&gt;')\n                    .replace(/\"/g, '&quot;')\n                    .replace(/'/g, '&#039;');\n            }\n\n            function encodePath(value) {\n                return encodeURIComponent(value || '');\n            }\n\n            function normalizeEnum(value) {\n                if (value === undefined || value === null) {\n                    return '';\n                }\n                return String(value).toUpperCase();\n            }\n\n            function parseTime(value) {\n                if (!value) {\n                    return '';\n                }\n                var date = new Date(value);\n                if (Number.isNaN(date.getTime())) {\n                    return '';\n                }\n                return date.toLocaleString();\n            }\n\n            function normalizeSubTab(value) {\n                var normalized = String(value || '').toLowerCase();\n                if (normalized === 'configuration' || normalized === 'detection' || normalized === 'queue' || normalized === 'execution') {\n                    return normalized;\n                }\n                return 'configuration';\n            }\n\n            function buildPluginURL(topTab, subTab, jobType) {\n                var laneName = pluginLane || 'default';\n                var laneBase = '/plugin/lanes/' + encodeURIComponent(laneName);\n                if (topTab === 'overview') {\n                    return laneBase;\n                }\n                var effectiveSubTab = normalizeSubTab(subTab);\n                var path = laneBase + '/' + encodeURIComponent(effectiveSubTab);\n                var jt = String(jobType || '').trim();\n                if (jt) {\n                    path += '?job=' + encodeURIComponent(jt);\n                }\n                return path;\n            }\n\n            function updateURL(replace) {\n                var url = basePath(buildPluginURL(state.activeTopTab, state.activeSubTab, state.selectedJobType));\n                if (replace) {\n                    history.replaceState({ pluginTopTab: state.activeTopTab, pluginSubTab: state.activeSubTab, pluginJob: state.selectedJobType }, '', url);\n                } else {\n                    history.pushState({ pluginTopTab: state.activeTopTab, pluginSubTab: state.activeSubTab, pluginJob: state.selectedJobType }, '', url);\n                }\n            }\n\n            // Parse a job type item (string or object) into a safe object shape\n            function parseJobTypeItem(item) {\n                var jobType = '';\n                var displayName = '';\n                \n                if (typeof item === 'string') {\n                    jobType = String(item || '').trim();\n                    displayName = jobType;\n                } else if (item && typeof item === 'object') {\n                    jobType = String(item.job_type || item.jobType || '').trim();\n                    displayName = String(item.display_name || item.displayName || jobType || '').trim();\n                    if (!displayName) {\n                        displayName = jobType;\n                    }\n                }\n                \n                return { jobType: jobType, displayName: displayName };\n            }\n\n            function topTabKeyForJobType(jobType) {\n                var normalized = String(jobType || '').trim();\n                if (!normalized) {\n                    return '';\n                }\n                return 'job:' + normalized;\n            }\n\n            function parseJobTypeFromTopTab(tabKey) {\n                var normalized = String(tabKey || '').trim();\n                if (normalized.indexOf('job:') !== 0) {\n                    return '';\n                }\n                return normalized.slice(4);\n            }\n\n            function hasJobType(jobType) {\n                var normalized = String(jobType || '').trim();\n                if (!normalized) {\n                    return false;\n                }\n                var jobTypes = Array.isArray(state.jobTypes) ? state.jobTypes : [];\n                for (var i = 0; i < jobTypes.length; i++) {\n                    var item = parseJobTypeItem(jobTypes[i]);\n                    if (item.jobType === normalized) {\n                        return true;\n                    }\n                }\n                return false;\n            }\n\n            function applyInitialNavigation() {\n                if (state.initialNavigationApplied) {\n                    return;\n                }\n                state.initialNavigationApplied = true;\n                state.activeSubTab = normalizeSubTab(state.initialPage);\n\n                if (state.initialPage === 'overview') {\n                    state.activeTopTab = 'overview';\n                    return;\n                }\n                if (!Array.isArray(state.jobTypes) || state.jobTypes.length === 0) {\n                    state.activeTopTab = 'overview';\n                    return;\n                }\n                // If a specific job type was provided in the URL, use it\n                if (state.initialJob && hasJobType(state.initialJob)) {\n                    state.selectedJobType = state.initialJob;\n                } else {\n                    var firstItem = parseJobTypeItem(state.jobTypes[0]);\n                    state.selectedJobType = firstItem.jobType;\n                }\n                state.activeTopTab = topTabKeyForJobType(state.selectedJobType);\n            }\n\n            function ensureActiveNavigation() {\n                state.activeSubTab = normalizeSubTab(state.activeSubTab);\n\n                if (state.selectedJobType && !hasJobType(state.selectedJobType)) {\n                    state.selectedJobType = '';\n                    state.descriptor = null;\n                    state.config = null;\n                }\n\n                var topTabJobType = parseJobTypeFromTopTab(state.activeTopTab);\n                if (topTabJobType && !hasJobType(topTabJobType)) {\n                    state.activeTopTab = 'overview';\n                    topTabJobType = '';\n                }\n\n                if (state.activeTopTab === 'overview') {\n                    if (!state.selectedJobType && Array.isArray(state.jobTypes) && state.jobTypes.length > 0) {\n                        var item = parseJobTypeItem(state.jobTypes[0]);\n                        state.selectedJobType = item.jobType;\n                    }\n                    return;\n                }\n\n                if (!topTabJobType && state.selectedJobType) {\n                    topTabJobType = state.selectedJobType;\n                }\n                if (!topTabJobType && Array.isArray(state.jobTypes) && state.jobTypes.length > 0) {\n                    var item = parseJobTypeItem(state.jobTypes[0]);\n                    topTabJobType = item.jobType;\n                }\n                if (!topTabJobType) {\n                    state.activeTopTab = 'overview';\n                    return;\n                }\n\n                state.selectedJobType = topTabJobType;\n                state.activeTopTab = topTabKeyForJobType(topTabJobType);\n            }\n\n            function renderTopTabs() {\n                var root = document.getElementById('plugin-top-tabs');\n                if (!root) {\n                    return;\n                }\n\n                var html = '' +\n                    '<li class=\"nav-item\">' +\n                    '<button type=\"button\" class=\"nav-link\" data-plugin-top-tab=\"overview\">' +\n                    '<i class=\"fas fa-chart-line me-1\"></i>Overview' +\n                    '</button>' +\n                    '</li>';\n\n                var jobTypes = Array.isArray(state.jobTypes) ? state.jobTypes : [];\n                for (var i = 0; i < jobTypes.length; i++) {\n                    var item = parseJobTypeItem(jobTypes[i]);\n                    var jobType = item.jobType;\n                    var displayName = item.displayName;\n                    \n                    if (!jobType) {\n                        continue;\n                    }\n                    html += '' +\n                        '<li class=\"nav-item\">' +\n                        '<button type=\"button\" class=\"nav-link\" data-plugin-top-tab=\"' + escapeHtml(topTabKeyForJobType(jobType)) + '\">' +\n                        escapeHtml(displayName) +\n                        '</button>' +\n                        '</li>';\n                }\n                root.innerHTML = html;\n            }\n\n            function renderNavigationState() {\n                var selectedJobTypeLabel = document.getElementById('plugin-selected-job-type');\n                if (selectedJobTypeLabel) {\n                    selectedJobTypeLabel.textContent = state.selectedJobType || '-';\n                }\n\n                var isOverview = state.activeTopTab === 'overview';\n                var subtabRow = document.getElementById('plugin-subtab-row');\n                if (subtabRow) {\n                    subtabRow.classList.toggle('d-none', isOverview);\n                }\n\n                var topTabs = document.querySelectorAll('#plugin-top-tabs [data-plugin-top-tab]');\n                for (var i = 0; i < topTabs.length; i++) {\n                    var topTab = topTabs[i];\n                    var topKey = String(topTab.getAttribute('data-plugin-top-tab') || '');\n                    topTab.classList.toggle('active', topKey === state.activeTopTab);\n                }\n\n                var subTabs = document.querySelectorAll('#plugin-subtabs [data-plugin-subtab]');\n                for (var j = 0; j < subTabs.length; j++) {\n                    var subTab = subTabs[j];\n                    var subKey = String(subTab.getAttribute('data-plugin-subtab') || '');\n                    subTab.classList.toggle('active', !isOverview && subKey === state.activeSubTab);\n                }\n\n                function toggleSection(sectionName, visible) {\n                    var section = document.querySelector('.plugin-section-' + sectionName);\n                    if (!section) {\n                        return;\n                    }\n                    section.classList.toggle('plugin-section-active', !!visible);\n                }\n\n                toggleSection('overview', isOverview);\n                toggleSection('configuration', !isOverview && state.activeSubTab === 'configuration');\n                toggleSection('detection', !isOverview && state.activeSubTab === 'detection');\n                toggleSection('queue', !isOverview && state.activeSubTab === 'queue');\n                toggleSection('execution', !isOverview && state.activeSubTab === 'execution');\n            }\n\n            function renderJobLink(jobID) {\n                var normalized = String(jobID || '').trim();\n                if (!normalized) {\n                    return '<small>-</small>';\n                }\n                return '<button type=\"button\" class=\"btn btn-link btn-sm p-0 align-baseline plugin-job-link\" data-job-id=\"' + escapeHtml(normalized) + '\">' + escapeHtml(normalized) + '</button>';\n            }\n\n            function toPrettyJson(value) {\n                if (value === null || value === undefined || value === '') {\n                    return '';\n                }\n                if (typeof value === 'string') {\n                    return value;\n                }\n                try {\n                    return JSON.stringify(value, null, 2);\n                } catch (e) {\n                    return String(value);\n                }\n            }\n\n            function clonePlainObject(value) {\n                if (!value || typeof value !== 'object') {\n                    return null;\n                }\n                try {\n                    return JSON.parse(JSON.stringify(value));\n                } catch (e) {\n                    return null;\n                }\n            }\n\n            function extractExecutionPlan(job) {\n                if (!job || !job.parameters) {\n                    return null;\n                }\n                var plan = job.parameters.execution_plan;\n                if (!plan) {\n                    return null;\n                }\n                if (typeof plan === 'string') {\n                    try {\n                        plan = JSON.parse(plan);\n                    } catch (e) {\n                        return null;\n                    }\n                }\n                if (typeof plan !== 'object' || Array.isArray(plan)) {\n                    return null;\n                }\n                return plan;\n            }\n\n            function removeExecutionPlanForDisplay(parameters) {\n                var copy = clonePlainObject(parameters);\n                if (!copy) {\n                    return parameters;\n                }\n                if (copy.execution_plan !== undefined) {\n                    delete copy.execution_plan;\n                }\n                if (Object.keys(copy).length === 0) {\n                    return null;\n                }\n                return copy;\n            }\n\n            function parseIntOrDefault(value, fallback) {\n                var parsed = parseInt(value, 10);\n                if (Number.isNaN(parsed)) {\n                    return fallback;\n                }\n                return parsed;\n            }\n\n            function normalizeShardIDs(value) {\n                if (!Array.isArray(value)) {\n                    return [];\n                }\n                var out = [];\n                for (var i = 0; i < value.length; i++) {\n                    var parsed = parseIntOrDefault(value[i], -1);\n                    if (parsed >= 0) {\n                        out.push(parsed);\n                    }\n                }\n                out.sort(function(a, b) { return a - b; });\n                return out;\n            }\n\n            function classifyShardLabel(shardID, dataShardCount) {\n                if (shardID < dataShardCount) {\n                    return {\n                        kind: 'data',\n                        label: String(shardID),\n                        className: 'bg-primary',\n                        title: 'Data shard ' + shardID,\n                    };\n                }\n                var parityIndex = shardID - dataShardCount;\n                return {\n                    kind: 'parity',\n                    label: String(shardID),\n                    className: 'bg-warning text-dark',\n                    title: 'Parity shard ' + shardID + ' (index ' + parityIndex + ')',\n                };\n            }\n\n            function renderShardBadges(shardIDs, dataShardCount) {\n                if (!Array.isArray(shardIDs) || shardIDs.length === 0) {\n                    return '<span class=\"text-muted\">-</span>';\n                }\n                var html = '';\n                for (var i = 0; i < shardIDs.length; i++) {\n                    var shard = classifyShardLabel(shardIDs[i], dataShardCount);\n                    html += '<span class=\"badge me-1 ' + shard.className + '\" title=\"' + escapeHtml(shard.title) + '\">' + escapeHtml(shard.label) + '</span>';\n                }\n                return html;\n            }\n\n            function renderExecutionPlan(plan) {\n                if (!plan || typeof plan !== 'object') {\n                    return '';\n                }\n\n                var jobType = String(plan.job_type || '').trim().toLowerCase();\n                if (jobType === 'volume_balance') {\n                    return renderBalanceExecutionPlan(plan);\n                }\n                if (jobType !== 'erasure_coding') {\n                    var fallbackText = toPrettyJson(plan);\n                    if (!fallbackText) {\n                        return '';\n                    }\n                    return '<div class=\"mb-3\"><h6>Execution Plan</h6><pre class=\"plugin-job-detail-json\">' + escapeHtml(fallbackText) + '</pre></div>';\n                }\n\n                var dataShards = parseIntOrDefault(plan.data_shards, 10);\n                var parityShards = parseIntOrDefault(plan.parity_shards, 4);\n                var totalShards = parseIntOrDefault(plan.total_shards, dataShards + parityShards);\n                var sources = Array.isArray(plan.sources) ? plan.sources : [];\n                var targets = Array.isArray(plan.targets) ? plan.targets : [];\n                var assignments = Array.isArray(plan.shard_assignments) ? plan.shard_assignments : [];\n\n                var html = '';\n                html += '<div class=\"mb-3\"><h6>Execution Plan</h6>';\n                html += '<div class=\"row g-2 mb-2\">' +\n                    '<div class=\"col-md-3\"><small><strong>Volume:</strong> ' + escapeHtml(textOrDash(plan.volume_id)) + '</small></div>' +\n                    '<div class=\"col-md-3\"><small><strong>Collection:</strong> ' + escapeHtml(textOrDash(plan.collection)) + '</small></div>' +\n                    '<div class=\"col-md-3\"><small><strong>Shards:</strong> ' + escapeHtml(String(dataShards)) + '+' + escapeHtml(String(parityShards)) + '</small></div>' +\n                    '<div class=\"col-md-3\"><small><strong>Total:</strong> ' + escapeHtml(String(totalShards)) + '</small></div>' +\n                    '</div>';\n\n                html += '<div class=\"small text-muted mb-2\">' +\n                    '<span class=\"badge bg-primary me-1\">Data</span>' +\n                    '<span class=\"badge bg-warning text-dark me-2\">Parity</span>' +\n                    'Source and target placement per shard.' +\n                    '</div>';\n\n                function renderEndpointsTable(title, endpoints) {\n                    if (!endpoints.length) {\n                        return '<div class=\"mb-2\"><strong>' + escapeHtml(title) + ':</strong> <span class=\"text-muted\">none</span></div>';\n                    }\n                    var table = '<div class=\"table-responsive mb-2\"><table class=\"table table-sm table-striped mb-0\">' +\n                        '<thead><tr><th>#</th><th>Node</th><th>DC</th><th>Rack</th><th>Volume</th><th>Shards</th></tr></thead><tbody>';\n                    for (var i = 0; i < endpoints.length; i++) {\n                        var endpoint = endpoints[i] || {};\n                        var endpointShards = normalizeShardIDs(endpoint.shard_ids);\n                        table += '<tr>' +\n                            '<td>' + escapeHtml(String(i + 1)) + '</td>' +\n                            '<td><code>' + escapeHtml(textOrDash(endpoint.node)) + '</code></td>' +\n                            '<td>' + escapeHtml(textOrDash(endpoint.data_center)) + '</td>' +\n                            '<td>' + escapeHtml(textOrDash(endpoint.rack)) + '</td>' +\n                            '<td>' + escapeHtml(textOrDash(endpoint.volume_id)) + '</td>' +\n                            '<td>' + renderShardBadges(endpointShards, dataShards) + '</td>' +\n                            '</tr>';\n                    }\n                    table += '</tbody></table></div>';\n                    return '<div class=\"mb-2\"><strong>' + escapeHtml(title) + '</strong></div>' + table;\n                }\n\n                html += renderEndpointsTable('Sources', sources);\n                html += renderEndpointsTable('Targets', targets);\n\n                if (assignments.length) {\n                    html += '<div class=\"mb-2\"><strong>Shard Assignments</strong></div>';\n                    html += '<div class=\"table-responsive\"><table class=\"table table-sm table-striped mb-0\">' +\n                        '<thead><tr><th>Shard</th><th>Kind</th><th>Target</th><th>DC</th><th>Rack</th></tr></thead><tbody>';\n                    for (var j = 0; j < assignments.length; j++) {\n                        var assignment = assignments[j] || {};\n                        var shardID = parseIntOrDefault(assignment.shard_id, -1);\n                        if (shardID < 0) {\n                            continue;\n                        }\n                        var shard = classifyShardLabel(shardID, dataShards);\n                        html += '<tr>' +\n                            '<td><span class=\"badge ' + shard.className + '\">' + escapeHtml(shard.label) + '</span></td>' +\n                            '<td>' + escapeHtml(textOrDash(assignment.kind || shard.kind)) + '</td>' +\n                            '<td><code>' + escapeHtml(textOrDash(assignment.target_node)) + '</code></td>' +\n                            '<td>' + escapeHtml(textOrDash(assignment.target_data_center)) + '</td>' +\n                            '<td>' + escapeHtml(textOrDash(assignment.target_rack)) + '</td>' +\n                            '</tr>';\n                    }\n                    html += '</tbody></table></div>';\n                }\n\n                html += '</div>';\n                return html;\n            }\n\n            function renderBalanceExecutionPlan(plan) {\n                var html = '<div class=\"mb-3\"><h6>Execution Plan</h6>';\n                var moves = Array.isArray(plan.moves) ? plan.moves : [];\n\n                if (moves.length === 0) {\n                    // Single-move balance job\n                    var src = textOrDash(plan.source_node || plan.source_server);\n                    var dst = textOrDash(plan.target_node || plan.target_server);\n                    var vid = textOrDash(plan.volume_id);\n                    var col = textOrDash(plan.collection);\n                    html += `<div class=\"row g-2 mb-2\">\n                        <div class=\"col-md-3\"><small><strong>Volume:</strong> ${escapeHtml(vid)}</small></div>\n                        <div class=\"col-md-3\"><small><strong>Collection:</strong> ${escapeHtml(col)}</small></div>\n                        <div class=\"col-md-3\"><small><strong>Source:</strong> <code>${escapeHtml(src)}</code></small></div>\n                        <div class=\"col-md-3\"><small><strong>Target:</strong> <code>${escapeHtml(dst)}</code></small></div>\n                        </div>`;\n                } else {\n                    // Batch balance job\n                    html += '<div class=\"mb-2\"><span class=\"badge bg-info\">' + escapeHtml(String(moves.length)) + ' moves</span></div>';\n                    html += '<div class=\"table-responsive\"><table class=\"table table-sm table-striped mb-0\">' +\n                        '<thead><tr><th>#</th><th>Volume</th><th>Source</th><th>Target</th><th>Collection</th></tr></thead><tbody>';\n                    for (var i = 0; i < moves.length; i++) {\n                        var move = moves[i] || {};\n                        html += `<tr>\n                            <td>${escapeHtml(String(i + 1))}</td>\n                            <td>${escapeHtml(textOrDash(move.volume_id))}</td>\n                            <td><code>${escapeHtml(textOrDash(move.source_node))}</code></td>\n                            <td><code>${escapeHtml(textOrDash(move.target_node))}</code></td>\n                            <td>${escapeHtml(textOrDash(move.collection))}</td>\n                        </tr>`;\n                    }\n                    html += '</tbody></table></div>';\n                }\n\n                html += '</div>';\n                return html;\n            }\n\n            function isActiveJobState(candidateState) {\n                var jobState = candidateState;\n                if (candidateState && typeof candidateState === 'object' && candidateState.state !== undefined) {\n                    jobState = candidateState.state;\n                }\n                var st = String(jobState || '').toLowerCase();\n                return st === 'job_state_pending' || st === 'job_state_assigned' || st === 'job_state_running' ||\n                    st === 'pending' || st === 'assigned' || st === 'running' || st === 'in_progress';\n            }\n\n            function setExpireButtonState(job) {\n                var expireBtn = document.getElementById('plugin-expire-job-btn');\n                if (!expireBtn) {\n                    return;\n                }\n                var jobID = job && job.job_id ? String(job.job_id) : '';\n                var active = isActiveJobState(job);\n                expireBtn.setAttribute('data-job-id', jobID);\n                expireBtn.disabled = !jobID || !active;\n                if (!jobID) {\n                    expireBtn.title = 'Select a job to expire.';\n                } else if (!active) {\n                    expireBtn.title = 'Job is not active.';\n                } else {\n                    expireBtn.title = 'Expire job to unblock scheduling.';\n                }\n            }\n\n            async function expireJob(jobID) {\n                var normalizedJobID = String(jobID || '').trim();\n                if (!normalizedJobID) {\n                    return;\n                }\n\n                var reason = window.prompt('Expire job ' + normalizedJobID + '? Optional reason:', 'job expired by admin request');\n                if (reason === null) {\n                    return;\n                }\n\n                var expireBtn = document.getElementById('plugin-expire-job-btn');\n                if (expireBtn) {\n                    expireBtn.disabled = true;\n                }\n\n                try {\n                    var response = await pluginRequest('POST', '/api/plugin/jobs/' + encodePath(normalizedJobID) + '/expire', {\n                        reason: reason,\n                    });\n                    if (response && response.expired === false) {\n                        notify(response.message || 'Job is not active.', 'info');\n                    } else {\n                        notify('Job expired: ' + normalizedJobID, 'success');\n                    }\n                    await refreshJobsAndActivities();\n                    await openJobDetail(normalizedJobID);\n                } catch (e) {\n                    notify('Failed to expire job: ' + e.message, 'error');\n                }\n            }\n\n            async function openJobDetail(jobID) {\n                var normalizedJobID = String(jobID || '').trim();\n                if (!normalizedJobID) {\n                    return;\n                }\n\n                var modalElement = document.getElementById('plugin-job-detail-modal');\n                var contentRoot = document.getElementById('plugin-job-detail-content');\n                if (!modalElement || !contentRoot) {\n                    return;\n                }\n\n                var modal;\n                if (window.bootstrap && window.bootstrap.Modal) {\n                    modal = window.bootstrap.Modal.getOrCreateInstance(modalElement);\n                }\n                if (modal) {\n                    modal.show();\n                }\n\n                setExpireButtonState(null);\n                contentRoot.innerHTML = '<div class=\"text-muted\">Loading job detail...</div>';\n                try {\n                    var detail = await pluginRequest('GET', '/api/plugin/jobs/' + encodePath(normalizedJobID) + '/detail?activity_limit=500&related_limit=20');\n                    var job = (detail && detail.job) ? detail.job : {};\n                    setExpireButtonState(job);\n                    var runRecord = detail && detail.run_record ? detail.run_record : null;\n                    var activities = (detail && Array.isArray(detail.activities)) ? detail.activities : [];\n                    var relatedJobs = (detail && Array.isArray(detail.related_jobs)) ? detail.related_jobs : [];\n                    var html = '';\n\n                    html += '<div class=\"row g-3 mb-3\">' +\n                        '<div class=\"col-lg-8\">' +\n                        '<div><strong>Job ID:</strong> ' + escapeHtml(textOrDash(job.job_id || normalizedJobID)) + '</div>' +\n                        '<div><strong>Type:</strong> ' + escapeHtml(textOrDash(job.job_type)) + '</div>' +\n                        '<div><strong>State:</strong> ' + escapeHtml(textOrDash(job.state)) + '</div>' +\n                        '<div><strong>Worker:</strong> ' + escapeHtml(textOrDash(job.worker_id)) + '</div>' +\n                        '<div><strong>Updated:</strong> ' + escapeHtml(parseTime(job.updated_at) || '-') + '</div>' +\n                        '</div>' +\n                        '<div class=\"col-lg-4\">' +\n                        '<div><strong>Attempt:</strong> ' + escapeHtml(textOrDash(job.attempt)) + '</div>' +\n                        '<div><strong>Progress:</strong> ' + escapeHtml(String(Math.round(Number(job.progress || 0)))) + '%</div>' +\n                        '<div><strong>Created:</strong> ' + escapeHtml(parseTime(job.created_at) || '-') + '</div>' +\n                        '<div><strong>Completed:</strong> ' + escapeHtml(parseTime(job.completed_at) || '-') + '</div>' +\n                        '</div>' +\n                        '</div>';\n\n                    if (job.summary) {\n                        html += '<div class=\"mb-2\"><strong>Summary:</strong> ' + escapeHtml(String(job.summary)) + '</div>';\n                    }\n                    if (job.detail) {\n                        html += '<div class=\"mb-2\"><strong>Detail:</strong> ' + escapeHtml(String(job.detail)) + '</div>';\n                    }\n                    if (job.message) {\n                        html += '<div class=\"mb-2\"><strong>Message:</strong> ' + escapeHtml(String(job.message)) + '</div>';\n                    }\n                    if (job.error_message) {\n                        html += '<div class=\"mb-2 text-danger\"><strong>Error:</strong> ' + escapeHtml(String(job.error_message)) + '</div>';\n                    }\n                    if (job.result_summary) {\n                        html += '<div class=\"mb-2\"><strong>Result:</strong> ' + escapeHtml(String(job.result_summary)) + '</div>';\n                    }\n\n                    var executionPlan = extractExecutionPlan(job);\n                    if (executionPlan) {\n                        html += renderExecutionPlan(executionPlan);\n                    }\n\n                    var parametersText = toPrettyJson(removeExecutionPlanForDisplay(job.parameters));\n                    if (parametersText) {\n                        html += '<div class=\"mb-3\"><h6>Parameters</h6><pre class=\"plugin-job-detail-json\">' + escapeHtml(parametersText) + '</pre></div>';\n                    }\n                    var labelsText = toPrettyJson(job.labels);\n                    if (labelsText) {\n                        html += '<div class=\"mb-3\"><h6>Labels</h6><pre class=\"plugin-job-detail-json\">' + escapeHtml(labelsText) + '</pre></div>';\n                    }\n                    var outputsText = toPrettyJson(job.result_output_values);\n                    if (outputsText) {\n                        html += '<div class=\"mb-3\"><h6>Result Output Values</h6><pre class=\"plugin-job-detail-json\">' + escapeHtml(outputsText) + '</pre></div>';\n                    }\n\n                    if (runRecord) {\n                        html += '<div class=\"mb-3\"><h6>Run Record</h6>' +\n                            '<div><strong>Outcome:</strong> ' + escapeHtml(textOrDash(runRecord.outcome)) + '</div>' +\n                            '<div><strong>Duration:</strong> ' + escapeHtml(formatDurationMs(runRecord.duration_ms)) + '</div>' +\n                            '<div><strong>Completed:</strong> ' + escapeHtml(parseTime(runRecord.completed_at) || '-') + '</div>' +\n                            '<div><strong>Message:</strong> ' + escapeHtml(textOrDash(runRecord.message)) + '</div>' +\n                            '</div>';\n                    }\n\n                    html += '<h6 class=\"mt-3\">Activity Timeline</h6>';\n                    if (!activities.length) {\n                        html += '<div class=\"text-muted mb-3\">No activity records.</div>';\n                    } else {\n                        html += '<div class=\"table-responsive mb-3\"><table class=\"table table-sm table-striped\">' +\n                            '<thead><tr><th>Time</th><th>Stage</th><th>Source</th><th>Worker</th><th>Message</th></tr></thead><tbody>';\n                        for (var i = 0; i < activities.length; i++) {\n                            var activity = activities[i] || {};\n                            html += '<tr>' +\n                                '<td><small>' + escapeHtml(parseTime(activity.occurred_at) || '-') + '</small></td>' +\n                                '<td>' + escapeHtml(textOrDash(activity.stage)) + '</td>' +\n                                '<td>' + escapeHtml(textOrDash(activity.source)) + '</td>' +\n                                '<td>' + escapeHtml(textOrDash(activity.worker_id)) + '</td>' +\n                                '<td><small>' + escapeHtml(textOrDash(activity.message)) + '</small></td>' +\n                                '</tr>';\n                        }\n                        html += '</tbody></table></div>';\n                    }\n\n                    if (relatedJobs.length) {\n                        html += '<h6>Related Jobs</h6><div class=\"table-responsive\"><table class=\"table table-sm table-hover\">' +\n                            '<thead><tr><th>Job ID</th><th>State</th><th>Worker</th><th>Updated</th></tr></thead><tbody>';\n                        for (var j = 0; j < relatedJobs.length; j++) {\n                            var related = relatedJobs[j] || {};\n                            html += '<tr>' +\n                                '<td><small>' + escapeHtml(textOrDash(related.job_id)) + '</small></td>' +\n                                '<td>' + escapeHtml(textOrDash(related.state)) + '</td>' +\n                                '<td>' + escapeHtml(textOrDash(related.worker_id)) + '</td>' +\n                                '<td><small>' + escapeHtml(parseTime(related.updated_at) || '-') + '</small></td>' +\n                                '</tr>';\n                        }\n                        html += '</tbody></table></div>';\n                    }\n\n                    contentRoot.innerHTML = html;\n                } catch (e) {\n                    setExpireButtonState(null);\n                    contentRoot.innerHTML = '<div class=\"alert alert-danger mb-0\">Failed to load job detail: ' + escapeHtml(e.message) + '</div>';\n                }\n            }\n\n            function formatDurationMs(value) {\n                var ms = Number(value || 0);\n                if (!Number.isFinite(ms) || ms <= 0) {\n                    return '-';\n                }\n                if (ms < 1000) {\n                    return ms + 'ms';\n                }\n                var sec = Math.floor(ms / 1000);\n                var remainMs = ms % 1000;\n                if (sec < 60) {\n                    return sec + '.' + String(Math.floor(remainMs / 100)) + 's';\n                }\n                var min = Math.floor(sec / 60);\n                var remainSec = sec % 60;\n                return min + 'm ' + remainSec + 's';\n            }\n\n            function getWorkerField(worker, name) {\n                if (!worker) {\n                    return null;\n                }\n                if (worker[name] !== undefined) {\n                    return worker[name];\n                }\n                var camel = name.charAt(0).toUpperCase() + name.slice(1);\n                if (worker[camel] !== undefined) {\n                    return worker[camel];\n                }\n                return null;\n            }\n\n            function renderStatus() {\n                var allJobs = Array.isArray(state.allJobs) ? state.allJobs : [];\n                var allActivities = Array.isArray(state.allActivities) ? state.allActivities : [];\n\n                var activeCount = allJobs.filter(function(job) {\n                    return isActiveJobState(job);\n                }).length;\n\n                document.getElementById('plugin-status-workers').textContent = String(state.workers.length);\n                document.getElementById('plugin-status-active-jobs').textContent = String(activeCount);\n                document.getElementById('plugin-status-activities').textContent = String(allActivities.length);\n            }\n\n            function renderJobTypeSummary() {\n                var tbody = document.getElementById('plugin-jobtype-summary-body');\n                if (!tbody) {\n                    return;\n                }\n\n                var activeByType = {};\n                var activityByType = {};\n\n                var allJobs = Array.isArray(state.allJobs) ? state.allJobs : [];\n                var allActivities = Array.isArray(state.allActivities) ? state.allActivities : [];\n\n                for (var i = 0; i < allJobs.length; i++) {\n                    var job = allJobs[i] || {};\n                    var jobType = String(job.job_type || '').trim();\n                    if (!jobType) {\n                        continue;\n                    }\n                    var isActive = isActiveJobState(job);\n                    if (!isActive) {\n                        continue;\n                    }\n                    if (!activeByType[jobType]) {\n                        activeByType[jobType] = 0;\n                    }\n                    activeByType[jobType]++;\n                }\n\n                for (var j = 0; j < allActivities.length; j++) {\n                    var activity = allActivities[j] || {};\n                    var activityJobType = String(activity.job_type || '').trim();\n                    if (!activityJobType) {\n                        continue;\n                    }\n                    if (!activityByType[activityJobType]) {\n                        activityByType[activityJobType] = 0;\n                    }\n                    activityByType[activityJobType]++;\n                }\n\n                var known = Array.isArray(state.jobTypes) ? state.jobTypes.slice() : [];\n                var seen = {};\n                var jobTypes = [];\n\n                for (var k = 0; k < known.length; k++) {\n                    var item = parseJobTypeItem(known[k]);\n                    var knownType = item && item.jobType ? String(item.jobType).trim() : '';\n                    if (!knownType || seen[knownType]) {\n                        continue;\n                    }\n                    seen[knownType] = true;\n                    jobTypes.push(knownType);\n                }\n\n                Object.keys(activeByType).forEach(function(jobType) {\n                    if (!seen[jobType]) {\n                        seen[jobType] = true;\n                        jobTypes.push(jobType);\n                    }\n                });\n                Object.keys(activityByType).forEach(function(jobType) {\n                    if (!seen[jobType]) {\n                        seen[jobType] = true;\n                        jobTypes.push(jobType);\n                    }\n                });\n\n                jobTypes.sort();\n\n                if (jobTypes.length === 0) {\n                    tbody.innerHTML = '<tr><td colspan=\\\"3\\\" class=\\\"text-muted text-center py-3\\\">No job types discovered</td></tr>';\n                    return;\n                }\n\n                var rows = '';\n                for (var m = 0; m < jobTypes.length; m++) {\n                    var rowType = jobTypes[m];\n                    rows += '<tr>' +\n                        '<td>' + escapeHtml(rowType) + '</td>' +\n                        '<td><span class=\\\"badge bg-warning text-dark\\\">' + escapeHtml(String(activeByType[rowType] || 0)) + '</span></td>' +\n                        '<td><span class=\\\"badge bg-info\\\">' + escapeHtml(String(activityByType[rowType] || 0)) + '</span></td>' +\n                        '</tr>';\n                }\n                tbody.innerHTML = rows;\n            }\n\n            function computeEffectiveExecutionCapacity(globalExec, perWorkerExec, executorWorkers) {\n                var capacities = [];\n                if (globalExec > 0) {\n                    capacities.push(globalExec);\n                }\n                var workerCapacity = perWorkerExec > 0 && executorWorkers > 0 ? (perWorkerExec * executorWorkers) : 0;\n                if (workerCapacity > 0) {\n                    capacities.push(workerCapacity);\n                }\n                if (!capacities.length) {\n                    return 0;\n                }\n                var effective = capacities[0];\n                for (var i = 1; i < capacities.length; i++) {\n                    if (capacities[i] < effective) {\n                        effective = capacities[i];\n                    }\n                }\n                return effective;\n            }\n\n            function renderSchedulerStates() {\n                var tbody = document.getElementById('plugin-scheduler-table-body');\n                if (!tbody) {\n                    return;\n                }\n\n                var states = Array.isArray(state.schedulerStates) ? state.schedulerStates : [];\n                if (!states.length) {\n                    tbody.innerHTML = '<tr><td colspan=\"10\" class=\"text-muted text-center py-3\">No scheduler state available</td></tr>';\n                    return;\n                }\n\n                var rows = '';\n                for (var i = 0; i < states.length; i++) {\n                    var item = states[i] || {};\n                    var enabled = !!item.enabled;\n                    var inFlight = !!item.detection_in_flight;\n                    var detector = item.detector_available ? textOrDash(item.detector_worker_id) : 'No detector';\n                    var maxRuntimeSeconds = Number(item.job_type_max_runtime_seconds || 0);\n                    var maxRuntimeText = maxRuntimeSeconds > 0 ? (String(maxRuntimeSeconds) + 's') : '-';\n                    var globalExec = Number(item.global_execution_concurrency || 0);\n                    var perWorkerExec = Number(item.per_worker_execution_concurrency || 0);\n                    var executorWorkers = Number(item.executor_worker_count || 0);\n                    var effectiveExec = computeEffectiveExecutionCapacity(globalExec, perWorkerExec, executorWorkers);\n                    var globalExecText = enabled ? String(globalExec) : '-';\n                    var perWorkerExecText = enabled ? String(perWorkerExec) : '-';\n                    var executorWorkersText = enabled ? String(executorWorkers) : '-';\n                    var effectiveExecText = enabled ? String(effectiveExec) : '-';\n                    var lastRunStatus = textOrDash(item.last_run_status);\n                    var lastRunTime = parseTime(item.last_run_completed_at);\n                    var lastRunText = '-';\n                    if (lastRunStatus !== '-' || lastRunTime) {\n                        var statusLabel = lastRunStatus !== '-' ? lastRunStatus : 'run';\n                        lastRunText = lastRunTime ? (statusLabel + ' @ ' + lastRunTime) : statusLabel;\n                    }\n\n                    var enabledBadge = enabled ? '<span class=\"badge bg-success\">Enabled</span>' : '<span class=\"badge bg-secondary\">Disabled</span>';\n                    var inFlightBadge = inFlight ? '<span class=\"badge bg-warning text-dark\">Yes</span>' : '<span class=\"badge bg-light text-dark\">No</span>';\n                    var policyErrorHtml = '';\n                    if (item.policy_error) {\n                        policyErrorHtml = '<div><small class=\"text-danger\">' + escapeHtml(String(item.policy_error)) + '</small></div>';\n                    }\n\n                    rows += '<tr>' +\n                        '<td>' + escapeHtml(textOrDash(item.job_type)) + policyErrorHtml + '</td>' +\n                        '<td>' + enabledBadge + '</td>' +\n                        '<td><small>' + escapeHtml(detector) + '</small></td>' +\n                        '<td>' + inFlightBadge + '</td>' +\n                        '<td><small>' + escapeHtml(maxRuntimeText) + '</small></td>' +\n                        '<td><small>' + escapeHtml(globalExecText) + '</small></td>' +\n                        '<td><small>' + escapeHtml(perWorkerExecText) + '</small></td>' +\n                        '<td><small>' + escapeHtml(executorWorkersText) + '</small></td>' +\n                        '<td><small>' + escapeHtml(effectiveExecText) + '</small></td>' +\n                        '<td><small>' + escapeHtml(lastRunText) + '</small></td>' +\n                        '</tr>';\n                }\n\n                tbody.innerHTML = rows;\n            }\n\n            function renderSchedulerStatus() {\n                var valueNodes = document.querySelectorAll('.plugin-scheduler-next-run');\n                if (!valueNodes.length) {\n                    return;\n                }\n                var metaNodes = document.querySelectorAll('.plugin-scheduler-next-run-meta');\n                var status = state.schedulerStatus || {};\n                var nextRun = parseTime(status.next_detection_at);\n                var display = nextRun || '-';\n                valueNodes.forEach(function(node) {\n                    node.textContent = display;\n                });\n                var metaText = nextRun ? 'Local time' : 'Not scheduled';\n                if (metaNodes.length) {\n                    metaNodes.forEach(function(node) {\n                        node.textContent = metaText;\n                    });\n                }\n            }\n\n            function renderWorkers() {\n                var tbody = document.getElementById('plugin-workers-table-body');\n                if (!state.workers.length) {\n                    tbody.innerHTML = '<tr><td colspan=\"4\" class=\"text-muted text-center py-3\">No connected workers</td></tr>';\n                    return;\n                }\n\n                var rows = '';\n                for (var i = 0; i < state.workers.length; i++) {\n                    var worker = state.workers[i];\n                    var workerID = textOrDash(getWorkerField(worker, 'workerID'));\n                    var address = textOrDash(getWorkerField(worker, 'address'));\n                    var caps = getWorkerField(worker, 'capabilities') || {};\n                    var capBadges = [];\n\n                    Object.keys(caps).sort().forEach(function(jobType) {\n                        var cap = caps[jobType] || {};\n                        var labels = [];\n                        if (cap.can_detect) {\n                            labels.push('D');\n                        }\n                        if (cap.can_execute) {\n                            labels.push('E');\n                        }\n                        var text = jobType;\n                        if (labels.length) {\n                            text += ' (' + labels.join('/') + ')';\n                        }\n                        capBadges.push('<span class=\"badge bg-secondary plugin-capability-badge\">' + escapeHtml(text) + '</span>');\n                    });\n\n                    if (!capBadges.length) {\n                        capBadges.push('<span class=\"text-muted\">-</span>');\n                    }\n\n                    var heartbeat = getWorkerField(worker, 'heartbeat') || {};\n                    var detUsed = Number(heartbeat.detection_slots_used || heartbeat.DetectionSlotsUsed || 0);\n                    var detTotal = Number(heartbeat.detection_slots_total || heartbeat.DetectionSlotsTotal || 0);\n                    var execUsed = Number(heartbeat.execution_slots_used || heartbeat.ExecutionSlotsUsed || 0);\n                    var execTotal = Number(heartbeat.execution_slots_total || heartbeat.ExecutionSlotsTotal || 0);\n                    var runningWork = heartbeat.running_work || heartbeat.RunningWork || [];\n                    var queuedByType = heartbeat.queued_jobs_by_type || heartbeat.QueuedJobsByType || {};\n                    var queuedTotal = 0;\n                    Object.keys(queuedByType || {}).forEach(function(jobType) {\n                        var queuedCount = Number(queuedByType[jobType] || 0);\n                        if (Number.isFinite(queuedCount) && queuedCount > 0) {\n                            queuedTotal += queuedCount;\n                        }\n                    });\n                    var loadLines = [];\n                    loadLines.push('Detect: ' + String(detUsed) + '/' + String(detTotal));\n                    loadLines.push('Execute: ' + String(execUsed) + '/' + String(execTotal));\n                    loadLines.push('Running: ' + String(Array.isArray(runningWork) ? runningWork.length : 0));\n                    loadLines.push('Queued: ' + String(queuedTotal));\n\n                    rows += '<tr>' +\n                        '<td><div class=\"fw-semibold\">' + escapeHtml(workerID) + '</div></td>' +\n                        '<td><small>' + escapeHtml(address) + '</small></td>' +\n                        '<td>' + capBadges.join('') + '</td>' +\n                        '<td><small>' + escapeHtml(loadLines.join(' | ')) + '</small></td>' +\n                        '</tr>';\n                }\n\n                tbody.innerHTML = rows;\n            }\n\n            function normalizeJobStateForFilter(value) {\n                var normalized = String(value || '').toLowerCase();\n                if (normalized.indexOf('job_state_') === 0) {\n                    normalized = normalized.slice('job_state_'.length);\n                }\n                return normalized;\n            }\n\n            function matchesExecutionStateFilter(jobState, selectedFilter) {\n                var target = normalizeJobStateForFilter(selectedFilter);\n                if (!target) {\n                    return true;\n                }\n                var actual = normalizeJobStateForFilter(jobState);\n                if (actual === target) {\n                    return true;\n                }\n                if (target === 'running' && actual === 'in_progress') {\n                    return true;\n                }\n                return false;\n            }\n\n            function isDetectionActivitySource(source) {\n                var normalized = String(source || '').toLowerCase();\n                if (!normalized) {\n                    return false;\n                }\n                if (normalized === 'worker_detection') {\n                    return true;\n                }\n                return normalized.indexOf('detector') >= 0;\n            }\n\n            function renderExecutionJobs() {\n                var tbody = document.getElementById('plugin-execution-jobs-table-body');\n                if (!tbody) {\n                    return;\n                }\n\n                var filteredJobs = [];\n                var allJobs = Array.isArray(state.allJobs) ? state.allJobs : [];\n                for (var i = 0; i < allJobs.length; i++) {\n                    var job = allJobs[i] || {};\n                    var jobType = String(job.job_type || '').trim();\n                    if (state.selectedJobType && jobType !== state.selectedJobType) {\n                        continue;\n                    }\n                    if (!matchesExecutionStateFilter(job.state, state.monitorExecutionJobState)) {\n                        continue;\n                    }\n                    filteredJobs.push(job);\n                }\n\n                if (!filteredJobs.length) {\n                    tbody.innerHTML = '<tr><td colspan=\"7\" class=\"text-muted text-center py-3\">No execution jobs tracked yet</td></tr>';\n                    return;\n                }\n\n                var rows = '';\n                for (var j = 0; j < filteredJobs.length; j++) {\n                    var executionJob = filteredJobs[j] || {};\n                    var progress = Number(executionJob.progress || 0);\n                    if (!Number.isFinite(progress) || progress < 0) {\n                        progress = 0;\n                    }\n                    if (progress > 100) {\n                        progress = 100;\n                    }\n\n                    var barClass = 'bg-info';\n                    var stateText = String(executionJob.state || 'unknown').toLowerCase();\n                    if (stateText.indexOf('failed') >= 0) {\n                        barClass = 'bg-danger';\n                    } else if (stateText.indexOf('succeed') >= 0 || stateText.indexOf('complete') >= 0) {\n                        barClass = 'bg-success';\n                    } else if (stateText.indexOf('run') >= 0 || stateText.indexOf('progress') >= 0) {\n                        barClass = 'bg-warning';\n                    }\n\n                    var jobTypeCell = escapeHtml(textOrDash(executionJob.job_type));\n                    var execLabels = executionJob.labels || {};\n                    if (execLabels.batch === 'true' && execLabels.batch_size) {\n                        jobTypeCell += ' <span class=\"badge bg-info\">' + escapeHtml(execLabels.batch_size) + ' moves</span>';\n                    }\n\n                    rows += '<tr>' +\n                        '<td>' + renderJobLink(executionJob.job_id) + '</td>' +\n                        '<td>' + jobTypeCell + '</td>' +\n                        '<td><span class=\"badge bg-light text-dark\">' + escapeHtml(textOrDash(executionJob.state)) + '</span></td>' +\n                        '<td class=\"plugin-job-progress\"><div class=\"progress\" style=\"height: 14px;\"><div class=\"progress-bar ' + barClass + '\" role=\"progressbar\" style=\"width:' + progress + '%\">' + Math.round(progress) + '%</div></div></td>' +\n                        '<td><small>' + escapeHtml(textOrDash(executionJob.worker_id)) + '</small></td>' +\n                        '<td><small>' + escapeHtml(parseTime(executionJob.updated_at) || '-') + '</small></td>' +\n                        '<td><small>' + escapeHtml(textOrDash(executionJob.message)) + '</small></td>' +\n                        '</tr>';\n                }\n\n                tbody.innerHTML = rows;\n            }\n\n            function renderQueueJobs() {\n                var tbody = document.getElementById('plugin-queue-jobs-table-body');\n                if (!tbody) {\n                    return;\n                }\n\n                var filteredJobs = [];\n                var allJobs = Array.isArray(state.allJobs) ? state.allJobs : [];\n                for (var i = 0; i < allJobs.length; i++) {\n                    var queueJob = allJobs[i] || {};\n                    var queueJobType = String(queueJob.job_type || '').trim();\n                    if (state.selectedJobType && queueJobType !== state.selectedJobType) {\n                        continue;\n                    }\n                    var normalizedState = normalizeJobStateForFilter(queueJob.state);\n                    if (normalizedState !== 'pending' && normalizedState !== 'assigned' && normalizedState !== 'running' && normalizedState !== 'in_progress') {\n                        continue;\n                    }\n                    filteredJobs.push(queueJob);\n                }\n\n                if (!filteredJobs.length) {\n                    tbody.innerHTML = '<tr><td colspan=\"7\" class=\"text-muted text-center py-3\">No queued/active jobs</td></tr>';\n                    return;\n                }\n\n                var rows = '';\n                for (var j = 0; j < filteredJobs.length; j++) {\n                    var queueItem = filteredJobs[j] || {};\n                    var progress = Number(queueItem.progress || 0);\n                    if (!Number.isFinite(progress) || progress < 0) {\n                        progress = 0;\n                    }\n                    if (progress > 100) {\n                        progress = 100;\n                    }\n\n                    var barClass = 'bg-warning';\n                    var queueStateText = String(queueItem.state || 'unknown').toLowerCase();\n                    if (queueStateText.indexOf('run') >= 0 || queueStateText.indexOf('progress') >= 0) {\n                        barClass = 'bg-info';\n                    }\n\n                    rows += '<tr>' +\n                        '<td>' + renderJobLink(queueItem.job_id) + '</td>' +\n                        '<td>' + escapeHtml(textOrDash(queueItem.job_type)) + '</td>' +\n                        '<td><span class=\"badge bg-light text-dark\">' + escapeHtml(textOrDash(queueItem.state)) + '</span></td>' +\n                        '<td class=\"plugin-job-progress\"><div class=\"progress\" style=\"height: 14px;\"><div class=\"progress-bar ' + barClass + '\" role=\"progressbar\" style=\"width:' + progress + '%\">' + Math.round(progress) + '%</div></div></td>' +\n                        '<td><small>' + escapeHtml(textOrDash(queueItem.worker_id)) + '</small></td>' +\n                        '<td><small>' + escapeHtml(parseTime(queueItem.updated_at) || '-') + '</small></td>' +\n                        '<td><small>' + escapeHtml(textOrDash(queueItem.message)) + '</small></td>' +\n                        '</tr>';\n                }\n\n                tbody.innerHTML = rows;\n            }\n\n            function renderDetectionJobs() {\n                var tbody = document.getElementById('plugin-detection-table-body');\n                if (!tbody) {\n                    return;\n                }\n\n                var filteredActivities = [];\n                var allActivities = Array.isArray(state.allActivities) ? state.allActivities : [];\n                for (var i = 0; i < allActivities.length; i++) {\n                    var activity = allActivities[i] || {};\n                    var jobType = String(activity.job_type || '').trim();\n                    if (state.selectedJobType && jobType !== state.selectedJobType) {\n                        continue;\n                    }\n                    if (!isDetectionActivitySource(activity.source)) {\n                        continue;\n                    }\n                    filteredActivities.push(activity);\n                }\n\n                if (!filteredActivities.length) {\n                    tbody.innerHTML = '<tr><td colspan=\"7\" class=\"text-muted text-center py-3\">No detection jobs tracked yet</td></tr>';\n                    return;\n                }\n\n                var rows = '';\n                for (var j = 0; j < filteredActivities.length; j++) {\n                    var detectionActivity = filteredActivities[j] || {};\n                    rows += '<tr>' +\n                        '<td><small>' + escapeHtml(parseTime(detectionActivity.occurred_at) || '-') + '</small></td>' +\n                        '<td>' + escapeHtml(textOrDash(detectionActivity.job_type)) + '</td>' +\n                        '<td><small>' + escapeHtml(textOrDash(detectionActivity.request_id)) + '</small></td>' +\n                        '<td><small>' + escapeHtml(textOrDash(detectionActivity.worker_id)) + '</small></td>' +\n                        '<td>' + escapeHtml(textOrDash(detectionActivity.stage)) + '</td>' +\n                        '<td><span class=\"badge bg-light text-dark\">' + escapeHtml(textOrDash(detectionActivity.source)) + '</span></td>' +\n                        '<td><small>' + escapeHtml(textOrDash(detectionActivity.message)) + '</small></td>' +\n                        '</tr>';\n                }\n\n                tbody.innerHTML = rows;\n            }\n\n            function renderExecutionActivities() {\n                var tbody = document.getElementById('plugin-execution-activities-table-body');\n                if (!tbody) {\n                    return;\n                }\n\n                var filteredActivities = [];\n                var allActivities = Array.isArray(state.allActivities) ? state.allActivities : [];\n                for (var i = 0; i < allActivities.length; i++) {\n                    var activity = allActivities[i] || {};\n                    if (isDetectionActivitySource(activity.source)) {\n                        continue;\n                    }\n                    var jobType = String(activity.job_type || '').trim();\n                    if (state.selectedJobType && jobType !== state.selectedJobType) {\n                        continue;\n                    }\n                    filteredActivities.push(activity);\n                }\n\n                if (!filteredActivities.length) {\n                    tbody.innerHTML = '<tr><td colspan=\"6\" class=\"text-muted text-center py-3\">No execution activities yet</td></tr>';\n                    return;\n                }\n\n                var rows = '';\n                for (var j = 0; j < filteredActivities.length; j++) {\n                    var executionActivity = filteredActivities[j] || {};\n                    rows += '<tr>' +\n                        '<td><small>' + escapeHtml(parseTime(executionActivity.occurred_at) || '-') + '</small></td>' +\n                        '<td>' + escapeHtml(textOrDash(executionActivity.job_type)) + '</td>' +\n                        '<td>' + renderJobLink(executionActivity.job_id) + '</td>' +\n                        '<td><span class=\"badge bg-light text-dark\">' + escapeHtml(textOrDash(executionActivity.source)) + '</span></td>' +\n                        '<td>' + escapeHtml(textOrDash(executionActivity.stage)) + '</td>' +\n                        '<td><small>' + escapeHtml(textOrDash(executionActivity.message)) + '</small></td>' +\n                        '</tr>';\n                }\n\n                tbody.innerHTML = rows;\n            }\n\n            function getSelectValueArray(selectEl) {\n                if (!selectEl) {\n                    return [];\n                }\n                var values = [];\n                for (var i = 0; i < selectEl.options.length; i++) {\n                    if (selectEl.options[i].selected) {\n                        values.push(selectEl.options[i].value);\n                    }\n                }\n                return values;\n            }\n\n            function configValueToNative(configValue) {\n                if (!configValue || typeof configValue !== 'object') {\n                    return null;\n                }\n                if (Object.prototype.hasOwnProperty.call(configValue, 'bool_value')) {\n                    return !!configValue.bool_value;\n                }\n                if (Object.prototype.hasOwnProperty.call(configValue, 'int64_value')) {\n                    return configValue.int64_value;\n                }\n                if (Object.prototype.hasOwnProperty.call(configValue, 'double_value')) {\n                    return configValue.double_value;\n                }\n                if (Object.prototype.hasOwnProperty.call(configValue, 'string_value')) {\n                    return configValue.string_value;\n                }\n                if (Object.prototype.hasOwnProperty.call(configValue, 'bytes_value')) {\n                    return configValue.bytes_value;\n                }\n                if (Object.prototype.hasOwnProperty.call(configValue, 'duration_value')) {\n                    return configValue.duration_value;\n                }\n                if (configValue.string_list && Array.isArray(configValue.string_list.values)) {\n                    return configValue.string_list.values.slice();\n                }\n                if (configValue.int64_list && Array.isArray(configValue.int64_list.values)) {\n                    return configValue.int64_list.values.slice();\n                }\n                if (configValue.double_list && Array.isArray(configValue.double_list.values)) {\n                    return configValue.double_list.values.slice();\n                }\n                if (configValue.bool_list && Array.isArray(configValue.bool_list.values)) {\n                    return configValue.bool_list.values.slice();\n                }\n                if (configValue.list_value && Array.isArray(configValue.list_value.values)) {\n                    return configValue.list_value.values.map(function(v) {\n                        return configValueToNative(v);\n                    });\n                }\n                if (configValue.map_value && configValue.map_value.fields) {\n                    var out = {};\n                    Object.keys(configValue.map_value.fields).forEach(function(k) {\n                        out[k] = configValueToNative(configValue.map_value.fields[k]);\n                    });\n                    return out;\n                }\n                return null;\n            }\n\n            function inferConfigValue(value) {\n                if (value === null || value === undefined) {\n                    return { string_value: '' };\n                }\n                if (typeof value === 'boolean') {\n                    return { bool_value: value };\n                }\n                if (typeof value === 'number') {\n                    if (Number.isInteger(value)) {\n                        return { int64_value: String(value) };\n                    }\n                    return { double_value: value };\n                }\n                if (typeof value === 'string') {\n                    return { string_value: value };\n                }\n                if (Array.isArray(value)) {\n                    var listValues = value.map(function(item) {\n                        return inferConfigValue(item);\n                    });\n                    return { list_value: { values: listValues } };\n                }\n                if (typeof value === 'object') {\n                    var fields = {};\n                    Object.keys(value).forEach(function(key) {\n                        fields[key] = inferConfigValue(value[key]);\n                    });\n                    return { map_value: { fields: fields } };\n                }\n                return { string_value: String(value) };\n            }\n\n            function nativeToConfigValue(value, fieldType, widget, field) {\n                var normalizedFieldType = normalizeEnum(fieldType);\n                var normalizedWidget = normalizeEnum(widget);\n\n                if (normalizedFieldType === 'CONFIG_FIELD_TYPE_BOOL') {\n                    return { bool_value: !!value };\n                }\n\n                if (normalizedFieldType === 'CONFIG_FIELD_TYPE_INT64') {\n                    if (value === '' || value === null || value === undefined) {\n                        return null;\n                    }\n                    var intValue = parseInt(value, 10);\n                    if (Number.isNaN(intValue)) {\n                        throw new Error('Invalid int64 value for field ' + field.name);\n                    }\n                    return { int64_value: String(intValue) };\n                }\n\n                if (normalizedFieldType === 'CONFIG_FIELD_TYPE_DOUBLE') {\n                    if (value === '' || value === null || value === undefined) {\n                        return null;\n                    }\n                    var doubleValue = parseFloat(value);\n                    if (Number.isNaN(doubleValue)) {\n                        throw new Error('Invalid number for field ' + field.name);\n                    }\n                    return { double_value: doubleValue };\n                }\n\n                if (normalizedFieldType === 'CONFIG_FIELD_TYPE_DURATION') {\n                    if (value === '' || value === null || value === undefined) {\n                        return null;\n                    }\n                    var text = String(value).trim();\n                    if (!text) {\n                        return null;\n                    }\n                    if (/^\\d+$/.test(text)) {\n                        text += 's';\n                    }\n                    return { duration_value: text };\n                }\n\n                if (normalizedFieldType === 'CONFIG_FIELD_TYPE_LIST') {\n                    if (value === '' || value === null || value === undefined) {\n                        return null;\n                    }\n\n                    if (normalizedWidget === 'CONFIG_WIDGET_MULTI_SELECT' && Array.isArray(value)) {\n                        return { string_list: { values: value } };\n                    }\n\n                    if (typeof value === 'string') {\n                        try {\n                            var parsed = JSON.parse(value);\n                            if (!Array.isArray(parsed)) {\n                                throw new Error('Expected JSON array');\n                            }\n                            return inferConfigValue(parsed);\n                        } catch (e) {\n                            throw new Error('Invalid JSON list for field ' + field.name + ': ' + e.message);\n                        }\n                    }\n                    return inferConfigValue(value);\n                }\n\n                if (normalizedFieldType === 'CONFIG_FIELD_TYPE_OBJECT') {\n                    if (value === '' || value === null || value === undefined) {\n                        return null;\n                    }\n                    if (typeof value === 'string') {\n                        try {\n                            var parsedObject = JSON.parse(value);\n                            if (!parsedObject || typeof parsedObject !== 'object' || Array.isArray(parsedObject)) {\n                                throw new Error('Expected JSON object');\n                            }\n                            return inferConfigValue(parsedObject);\n                        } catch (e) {\n                            throw new Error('Invalid JSON object for field ' + field.name + ': ' + e.message);\n                        }\n                    }\n                    return inferConfigValue(value);\n                }\n\n                if (normalizedFieldType === 'CONFIG_FIELD_TYPE_ENUM') {\n                    if (normalizedWidget === 'CONFIG_WIDGET_MULTI_SELECT') {\n                        if (!value || !value.length) {\n                            return null;\n                        }\n                        return { string_list: { values: value } };\n                    }\n                    if (value === '' || value === null || value === undefined) {\n                        return null;\n                    }\n                    return { string_value: String(value) };\n                }\n\n                if (value === '' || value === null || value === undefined) {\n                    return null;\n                }\n\n                return { string_value: String(value) };\n            }\n\n            function toInputID(scope, fieldName) {\n                return 'plugin-' + scope + '-field-' + String(fieldName || '').replace(/[^a-zA-Z0-9_-]/g, '_');\n            }\n\n            function resolveFieldConfigValue(scope, field) {\n                if (!field || !field.name) {\n                    return null;\n                }\n                var fromConfig = null;\n                if (state.config) {\n                    if (scope === 'admin' && state.config.admin_config_values) {\n                        fromConfig = state.config.admin_config_values[field.name];\n                    }\n                    if (scope === 'worker' && state.config.worker_config_values) {\n                        fromConfig = state.config.worker_config_values[field.name];\n                    }\n                }\n                if (fromConfig) {\n                    return fromConfig;\n                }\n\n                var formDefaults = null;\n                if (scope === 'admin' && state.descriptor && state.descriptor.admin_config_form && state.descriptor.admin_config_form.default_values) {\n                    formDefaults = state.descriptor.admin_config_form.default_values[field.name];\n                }\n                if (scope === 'worker' && state.descriptor && state.descriptor.worker_config_form && state.descriptor.worker_config_form.default_values) {\n                    formDefaults = state.descriptor.worker_config_form.default_values[field.name];\n                }\n                if (formDefaults) {\n                    return formDefaults;\n                }\n\n                if (scope === 'worker' && state.descriptor && state.descriptor.worker_default_values) {\n                    return state.descriptor.worker_default_values[field.name] || null;\n                }\n\n                return null;\n            }\n\n            function renderFieldInput(scope, field, value) {\n                var inputID = toInputID(scope, field.name);\n                var normalizedType = normalizeEnum(field.field_type);\n                var normalizedWidget = normalizeEnum(field.widget);\n                var input;\n\n                if (normalizedType === 'CONFIG_FIELD_TYPE_BOOL' || normalizedWidget === 'CONFIG_WIDGET_TOGGLE') {\n                    input = document.createElement('input');\n                    input.type = 'checkbox';\n                    input.className = 'form-check-input';\n                    input.id = inputID;\n                    input.checked = !!value;\n                } else if ((normalizedType === 'CONFIG_FIELD_TYPE_ENUM' && normalizedWidget === 'CONFIG_WIDGET_MULTI_SELECT') || normalizedWidget === 'CONFIG_WIDGET_MULTI_SELECT') {\n                    input = document.createElement('select');\n                    input.className = 'form-select';\n                    input.id = inputID;\n                    input.multiple = true;\n                    var selected = Array.isArray(value) ? value : [];\n                    var options = Array.isArray(field.options) ? field.options : [];\n                    for (var i = 0; i < options.length; i++) {\n                        var option = document.createElement('option');\n                        option.value = options[i].value || '';\n                        option.textContent = options[i].label || options[i].value || '';\n                        if (selected.indexOf(option.value) >= 0) {\n                            option.selected = true;\n                        }\n                        input.appendChild(option);\n                    }\n                } else if (normalizedType === 'CONFIG_FIELD_TYPE_ENUM' || normalizedWidget === 'CONFIG_WIDGET_SELECT') {\n                    input = document.createElement('select');\n                    input.className = 'form-select';\n                    input.id = inputID;\n                    var blankOption = document.createElement('option');\n                    blankOption.value = '';\n                    blankOption.textContent = '-- select --';\n                    input.appendChild(blankOption);\n                    var enumOptions = Array.isArray(field.options) ? field.options : [];\n                    for (var j = 0; j < enumOptions.length; j++) {\n                        var enumOption = document.createElement('option');\n                        enumOption.value = enumOptions[j].value || '';\n                        enumOption.textContent = enumOptions[j].label || enumOptions[j].value || '';\n                        if (String(value) === enumOption.value) {\n                            enumOption.selected = true;\n                        }\n                        input.appendChild(enumOption);\n                    }\n                } else if (normalizedType === 'CONFIG_FIELD_TYPE_INT64' || normalizedType === 'CONFIG_FIELD_TYPE_DOUBLE' || normalizedWidget === 'CONFIG_WIDGET_NUMBER') {\n                    input = document.createElement('input');\n                    input.type = 'number';\n                    if (normalizedType === 'CONFIG_FIELD_TYPE_DOUBLE') {\n                        input.step = 'any';\n                    }\n                    input.className = 'form-control';\n                    input.id = inputID;\n                    if (value !== null && value !== undefined && value !== '') {\n                        input.value = String(value);\n                    }\n                } else if (normalizedType === 'CONFIG_FIELD_TYPE_LIST' || normalizedType === 'CONFIG_FIELD_TYPE_OBJECT' || normalizedWidget === 'CONFIG_WIDGET_TEXTAREA') {\n                    input = document.createElement('textarea');\n                    input.className = 'form-control';\n                    input.id = inputID;\n                    input.rows = 4;\n                    if (normalizedType === 'CONFIG_FIELD_TYPE_LIST' || normalizedType === 'CONFIG_FIELD_TYPE_OBJECT') {\n                        input.placeholder = normalizedType === 'CONFIG_FIELD_TYPE_LIST' ? '[...]' : '{...}';\n                    }\n                    if (value !== null && value !== undefined && value !== '') {\n                        if (typeof value === 'object') {\n                            input.value = JSON.stringify(value, null, 2);\n                        } else {\n                            input.value = String(value);\n                        }\n                    }\n                } else {\n                    input = document.createElement('input');\n                    input.className = 'form-control';\n                    input.id = inputID;\n                    if (normalizedWidget === 'CONFIG_WIDGET_PASSWORD' || field.sensitive) {\n                        input.type = 'password';\n                    } else {\n                        input.type = 'text';\n                    }\n                    if (value !== null && value !== undefined && value !== '') {\n                        input.value = String(value);\n                    }\n                }\n\n                if (field.placeholder && input.tagName === 'INPUT') {\n                    input.placeholder = field.placeholder;\n                }\n                if (field.placeholder && input.tagName === 'TEXTAREA') {\n                    input.placeholder = field.placeholder;\n                }\n\n                if (field.required) {\n                    input.required = true;\n                }\n                if (field.read_only) {\n                    input.disabled = true;\n                }\n\n                return input;\n            }\n\n            function renderConfigForm(scope, rootID, form) {\n                var root = document.getElementById(rootID);\n                if (!root) {\n                    return;\n                }\n\n                root.innerHTML = '';\n                if (!form || !Array.isArray(form.sections) || form.sections.length === 0) {\n                    root.innerHTML = '<div class=\"text-muted\">No fields provided by descriptor.</div>';\n                    return;\n                }\n\n                for (var s = 0; s < form.sections.length; s++) {\n                    var section = form.sections[s] || {};\n                    var sectionCard = document.createElement('div');\n                    sectionCard.className = 'card mb-3';\n\n                    var sectionHeader = document.createElement('div');\n                    sectionHeader.className = 'card-header py-2';\n                    sectionHeader.innerHTML = '<strong>' + escapeHtml(textOrDash(section.title || section.section_id)) + '</strong>';\n                    sectionCard.appendChild(sectionHeader);\n\n                    var sectionBody = document.createElement('div');\n                    sectionBody.className = 'card-body';\n\n                    if (section.description) {\n                        var sectionDescription = document.createElement('p');\n                        sectionDescription.className = 'text-muted small';\n                        sectionDescription.textContent = section.description;\n                        sectionBody.appendChild(sectionDescription);\n                    }\n\n                    var fields = Array.isArray(section.fields) ? section.fields : [];\n                    if (!fields.length) {\n                        var noFields = document.createElement('div');\n                        noFields.className = 'text-muted';\n                        noFields.textContent = 'No fields in this section.';\n                        sectionBody.appendChild(noFields);\n                    }\n\n                    for (var f = 0; f < fields.length; f++) {\n                        var field = fields[f] || {};\n                        if (!field.name) {\n                            continue;\n                        }\n\n                        var configValue = resolveFieldConfigValue(scope, field);\n                        var nativeValue = configValueToNative(configValue);\n\n                        var wrapper = document.createElement('div');\n                        wrapper.className = 'mb-3 plugin-field';\n                        wrapper.dataset.scope = scope;\n                        wrapper.dataset.fieldName = field.name;\n                        wrapper.dataset.fieldType = normalizeEnum(field.field_type);\n                        wrapper.dataset.widget = normalizeEnum(field.widget);\n                        wrapper.dataset.visibleWhenField = field.visible_when_field || '';\n                        wrapper.dataset.visibleWhenEquals = JSON.stringify(field.visible_when_equals || null);\n\n                        var label = document.createElement('label');\n                        label.className = 'form-label';\n                        label.setAttribute('for', toInputID(scope, field.name));\n                        label.innerHTML = escapeHtml(field.label || field.name) + (field.required ? ' <span class=\"text-danger\">*</span>' : '');\n                        wrapper.appendChild(label);\n\n                        var input = renderFieldInput(scope, field, nativeValue);\n                        wrapper.appendChild(input);\n\n                        if (field.description || field.help_text) {\n                            var help = document.createElement('div');\n                            help.className = 'form-text';\n                            help.textContent = field.help_text || field.description || '';\n                            wrapper.appendChild(help);\n                        }\n\n                        sectionBody.appendChild(wrapper);\n                    }\n\n                    sectionCard.appendChild(sectionBody);\n                    root.appendChild(sectionCard);\n                }\n\n                root.addEventListener('change', function() {\n                    applyVisibility(scope);\n                });\n                root.addEventListener('input', function() {\n                    applyVisibility(scope);\n                });\n\n                applyVisibility(scope);\n            }\n\n            function readRawFieldValue(scope, fieldName) {\n                var input = document.getElementById(toInputID(scope, fieldName));\n                if (!input) {\n                    return null;\n                }\n                if (input.tagName === 'SELECT' && input.multiple) {\n                    return getSelectValueArray(input);\n                }\n                if (input.type === 'checkbox') {\n                    return !!input.checked;\n                }\n                return input.value;\n            }\n\n            function compareVisibleValue(currentValue, expectedValueObject) {\n                if (!expectedValueObject) {\n                    return true;\n                }\n                var expectedNative = configValueToNative(expectedValueObject);\n                if (Array.isArray(expectedNative)) {\n                    if (!Array.isArray(currentValue)) {\n                        return false;\n                    }\n                    if (expectedNative.length !== currentValue.length) {\n                        return false;\n                    }\n                    for (var i = 0; i < expectedNative.length; i++) {\n                        if (String(expectedNative[i]) !== String(currentValue[i])) {\n                            return false;\n                        }\n                    }\n                    return true;\n                }\n                return String(currentValue) === String(expectedNative);\n            }\n\n            function applyVisibility(scope) {\n                var fields = document.querySelectorAll('.plugin-field[data-scope=\"' + scope + '\"]');\n                for (var i = 0; i < fields.length; i++) {\n                    var wrapper = fields[i];\n                    var dependsOn = wrapper.dataset.visibleWhenField;\n                    if (!dependsOn) {\n                        wrapper.classList.remove('plugin-field-hidden');\n                        continue;\n                    }\n\n                    var expectedRaw = null;\n                    try {\n                        expectedRaw = JSON.parse(wrapper.dataset.visibleWhenEquals || 'null');\n                    } catch (e) {\n                        expectedRaw = null;\n                    }\n                    var current = readRawFieldValue(scope, dependsOn);\n                    var visible = compareVisibleValue(current, expectedRaw);\n                    if (visible) {\n                        wrapper.classList.remove('plugin-field-hidden');\n                    } else {\n                        wrapper.classList.add('plugin-field-hidden');\n                    }\n                }\n            }\n\n            function collectConfigValues(scope, form) {\n                var out = {};\n                if (!form || !Array.isArray(form.sections)) {\n                    return out;\n                }\n\n                for (var s = 0; s < form.sections.length; s++) {\n                    var section = form.sections[s] || {};\n                    var fields = Array.isArray(section.fields) ? section.fields : [];\n                    for (var f = 0; f < fields.length; f++) {\n                        var field = fields[f] || {};\n                        if (!field.name) {\n                            continue;\n                        }\n\n                        var wrapper = document.querySelector('.plugin-field[data-scope=\"' + scope + '\"][data-field-name=\"' + field.name + '\"]');\n                        if (wrapper && wrapper.classList.contains('plugin-field-hidden')) {\n                            continue;\n                        }\n\n                        var input = document.getElementById(toInputID(scope, field.name));\n                        if (!input) {\n                            continue;\n                        }\n\n                        var rawValue;\n                        if (input.tagName === 'SELECT' && input.multiple) {\n                            rawValue = getSelectValueArray(input);\n                        } else if (input.type === 'checkbox') {\n                            rawValue = !!input.checked;\n                        } else {\n                            rawValue = input.value;\n                        }\n\n                        if (field.required) {\n                            var missing = false;\n                            if (input.type === 'checkbox') {\n                                missing = false;\n                            } else if (Array.isArray(rawValue)) {\n                                missing = rawValue.length === 0;\n                            } else {\n                                missing = String(rawValue || '').trim() === '';\n                            }\n                            if (missing) {\n                                throw new Error('Missing required field: ' + (field.label || field.name));\n                            }\n                        }\n\n                        var converted = nativeToConfigValue(rawValue, field.field_type, field.widget, field);\n                        if (converted !== null) {\n                            out[field.name] = converted;\n                        }\n                    }\n                }\n\n                return out;\n            }\n\n            function renderDescriptorSummary() {\n                var summary = document.getElementById('plugin-descriptor-summary');\n                if (!state.descriptor) {\n                    summary.innerHTML = 'No descriptor loaded.';\n                    return;\n                }\n\n                var descriptor = state.descriptor;\n                var html = '';\n                html += '<div><strong>' + escapeHtml(textOrDash(descriptor.display_name || descriptor.job_type)) + '</strong></div>';\n                html += '<div><small>Job Type: ' + escapeHtml(textOrDash(descriptor.job_type)) + '</small></div>';\n                html += '<div><small>Descriptor Version: ' + escapeHtml(textOrDash(descriptor.descriptor_version)) + '</small></div>';\n                if (descriptor.description) {\n                    html += '<div class=\"mt-1\">' + escapeHtml(descriptor.description) + '</div>';\n                }\n                summary.innerHTML = html;\n            }\n\n            function fillAdminSettings() {\n                var runtimeConfig = (state.config && state.config.admin_runtime) ? state.config.admin_runtime : {};\n                var defaults = (state.descriptor && state.descriptor.admin_runtime_defaults) ? state.descriptor.admin_runtime_defaults : {};\n\n                function pickNumber(key) {\n                    if (runtimeConfig && runtimeConfig[key] !== undefined && runtimeConfig[key] !== null) {\n                        return runtimeConfig[key];\n                    }\n                    if (defaults && defaults[key] !== undefined && defaults[key] !== null) {\n                        return defaults[key];\n                    }\n                    return 0;\n                }\n\n                function pickBool(key) {\n                    if (runtimeConfig && runtimeConfig[key] !== undefined && runtimeConfig[key] !== null) {\n                        return !!runtimeConfig[key];\n                    }\n                    if (defaults && defaults[key] !== undefined && defaults[key] !== null) {\n                        return !!defaults[key];\n                    }\n                    return true;\n                }\n\n                document.getElementById('plugin-admin-enabled').checked = pickBool('enabled');\n                document.getElementById('plugin-admin-detection-interval').value = String(pickNumber('detection_interval_minutes'));\n                document.getElementById('plugin-admin-detection-timeout').value = String(pickNumber('detection_timeout_seconds'));\n                document.getElementById('plugin-admin-execution-timeout').value = String(pickNumber('execution_timeout_seconds'));\n                document.getElementById('plugin-admin-max-runtime').value = String(pickNumber('job_type_max_runtime_seconds'));\n                document.getElementById('plugin-admin-max-results').value = String(pickNumber('max_jobs_per_detection'));\n                document.getElementById('plugin-admin-global-exec').value = String(pickNumber('global_execution_concurrency'));\n                document.getElementById('plugin-admin-per-worker-exec').value = String(pickNumber('per_worker_execution_concurrency'));\n                document.getElementById('plugin-admin-retry-limit').value = String(pickNumber('retry_limit'));\n                document.getElementById('plugin-admin-retry-backoff').value = String(pickNumber('retry_backoff_seconds'));\n            }\n\n            function collectAdminSettings() {\n                function getInt(id) {\n                    var raw = String(document.getElementById(id).value || '').trim();\n                    if (!raw) {\n                        return 0;\n                    }\n                    var parsed = parseInt(raw, 10);\n                    if (Number.isNaN(parsed) || parsed < 0) {\n                        throw new Error('Invalid number for ' + id);\n                    }\n                    return parsed;\n                }\n\n                return {\n                    enabled: !!document.getElementById('plugin-admin-enabled').checked,\n                    detection_interval_minutes: getInt('plugin-admin-detection-interval'),\n                    detection_timeout_seconds: getInt('plugin-admin-detection-timeout'),\n                    execution_timeout_seconds: getInt('plugin-admin-execution-timeout'),\n                    job_type_max_runtime_seconds: getInt('plugin-admin-max-runtime'),\n                    max_jobs_per_detection: getInt('plugin-admin-max-results'),\n                    global_execution_concurrency: getInt('plugin-admin-global-exec'),\n                    per_worker_execution_concurrency: getInt('plugin-admin-per-worker-exec'),\n                    retry_limit: getInt('plugin-admin-retry-limit'),\n                    retry_backoff_seconds: getInt('plugin-admin-retry-backoff'),\n                };\n            }\n\n            function renderRunHistory(history) {\n                var successBody = document.getElementById('plugin-runs-success-body');\n                var errorBody = document.getElementById('plugin-runs-error-body');\n\n                var successRuns = (history && Array.isArray(history.successful_runs)) ? history.successful_runs : [];\n                var errorRuns = (history && Array.isArray(history.error_runs)) ? history.error_runs : [];\n\n                if (!successRuns.length) {\n                    successBody.innerHTML = '<tr><td colspan=\"4\" class=\"text-muted\">No successful runs</td></tr>';\n                } else {\n                    var successRows = '';\n                    for (var i = 0; i < successRuns.length; i++) {\n                        var run = successRuns[i] || {};\n                        successRows += '<tr>' +\n                            '<td><small>' + escapeHtml(parseTime(run.completed_at) || '-') + '</small></td>' +\n                            '<td>' + renderJobLink(run.job_id) + '</td>' +\n                            '<td><small>' + escapeHtml(textOrDash(run.worker_id)) + '</small></td>' +\n                            '<td><small>' + escapeHtml(formatDurationMs(run.duration_ms)) + '</small></td>' +\n                            '</tr>';\n                    }\n                    successBody.innerHTML = successRows;\n                }\n\n                if (!errorRuns.length) {\n                    errorBody.innerHTML = '<tr><td colspan=\"4\" class=\"text-muted\">No error runs</td></tr>';\n                } else {\n                    var errorRows = '';\n                    for (var j = 0; j < errorRuns.length; j++) {\n                        var errorRun = errorRuns[j] || {};\n                        errorRows += '<tr>' +\n                            '<td><small>' + escapeHtml(parseTime(errorRun.completed_at) || '-') + '</small></td>' +\n                            '<td>' + renderJobLink(errorRun.job_id) + '</td>' +\n                            '<td><small>' + escapeHtml(textOrDash(errorRun.worker_id)) + '</small></td>' +\n                            '<td><small class=\"text-danger\">' + escapeHtml(textOrDash(errorRun.message)) + '</small></td>' +\n                            '</tr>';\n                    }\n                    errorBody.innerHTML = errorRows;\n                }\n            }\n\n            function renderDetectionResult(data) {\n                var root = document.getElementById('plugin-detection-results');\n                if (!root) {\n                    return;\n                }\n\n                function normalizeProposals(payload) {\n                    if (!payload || typeof payload !== 'object') {\n                        return [];\n                    }\n                    if (Array.isArray(payload.proposals)) {\n                        return payload.proposals;\n                    }\n                    if (payload.proposals && typeof payload.proposals === 'object') {\n                        return Object.keys(payload.proposals).map(function(key) {\n                            return payload.proposals[key];\n                        }).filter(function(item) {\n                            return !!item;\n                        });\n                    }\n                    return [];\n                }\n\n                function proposalID(proposal) {\n                    return proposal.proposal_id || proposal.proposalId || proposal.id || '';\n                }\n\n                function proposalSummary(proposal) {\n                    return proposal.summary || proposal.title || proposalID(proposal) || 'Proposal';\n                }\n\n                function proposalDetail(proposal) {\n                    return proposal.detail || proposal.reason || proposal.message || '-';\n                }\n\n                function normalizeActivities(payload) {\n                    if (!payload || typeof payload !== 'object') {\n                        return [];\n                    }\n                    if (Array.isArray(payload.activities)) {\n                        return payload.activities;\n                    }\n                    return [];\n                }\n\n                function encodeDetails(details) {\n                    if (!details || typeof details !== 'object') {\n                        return '';\n                    }\n                    var text = '';\n                    try {\n                        text = JSON.stringify(details);\n                    } catch (e) {\n                        text = '';\n                    }\n                    if (!text) {\n                        return '';\n                    }\n                    if (text.length > 360) {\n                        text = text.slice(0, 360) + '...';\n                    }\n                    return text;\n                }\n\n                if (!data) {\n                    root.innerHTML = '<div class=\"text-muted\">No results.</div>';\n                    return;\n                }\n\n                var proposals = normalizeProposals(data);\n                var activities = normalizeActivities(data);\n                var responseCount = Number(data.count || 0);\n                if (!Number.isFinite(responseCount) || responseCount < 0) {\n                    responseCount = proposals.length;\n                }\n                var requestID = data.request_id || data.requestId || '';\n                var detectorWorkerID = data.detector_worker_id || data.detectorWorkerId || '';\n                var totalProposals = Number(data.total_proposals || 0);\n                if (!Number.isFinite(totalProposals) || totalProposals < 0) {\n                    totalProposals = responseCount;\n                }\n\n                var html = '';\n                html += '<div class=\"mb-2\"><strong>Detection Run</strong></div>';\n                if (requestID) {\n                    html += '<div><small>request_id: ' + escapeHtml(String(requestID)) + '</small></div>';\n                }\n                if (detectorWorkerID) {\n                    html += '<div><small>detector: ' + escapeHtml(String(detectorWorkerID)) + '</small></div>';\n                }\n                html += '<div><small>proposals: ' + escapeHtml(String(responseCount)) + ' / total: ' + escapeHtml(String(totalProposals)) + '</small></div>';\n                if (data.error) {\n                    html += '<div class=\"mt-1 text-danger\"><small>' + escapeHtml(String(data.error)) + '</small></div>';\n                }\n\n                if (!proposals.length) {\n                    html += '<div class=\"mt-2 text-muted\">No proposals returned.</div>';\n                } else {\n                    html += '<div class=\"mt-2 mb-1\"><strong>' + escapeHtml(String(proposals.length)) + '</strong> proposal(s)</div>';\n                    for (var i = 0; i < proposals.length; i++) {\n                        var proposal = proposals[i] || {};\n                        var pID = proposalID(proposal);\n                        var pSummary = proposalSummary(proposal);\n                        var pDetail = proposalDetail(proposal);\n                        var pPriority = proposal.priority;\n                        if (pPriority === null || pPriority === undefined || pPriority === '') {\n                            pPriority = '-';\n                        }\n                        html += '<div class=\"plugin-detection-item\">' +\n                            '<div class=\"fw-semibold\">' + escapeHtml(textOrDash(pSummary)) + '</div>' +\n                            '<div><small>proposal_id: ' + escapeHtml(textOrDash(pID)) + '</small></div>' +\n                            '<div><small>priority: ' + escapeHtml(textOrDash(pPriority)) + '</small></div>' +\n                            '<div><small>detail: ' + escapeHtml(textOrDash(pDetail)) + '</small></div>' +\n                            '</div>';\n                    }\n                }\n\n                if (activities.length) {\n                    html += '<hr class=\"my-2\"/>';\n                    html += '<div class=\"mb-2\"><strong>Detection Process</strong></div>';\n                    for (var j = 0; j < activities.length; j++) {\n                        var activity = activities[j] || {};\n                        var activityStage = activity.stage || '-';\n                        var activitySource = activity.source || '-';\n                        var activityMessage = activity.message || '-';\n                        var activityTime = parseTime(activity.occurred_at) || '-';\n                        var detailsText = encodeDetails(activity.details);\n                        html += '<div class=\"plugin-detection-item\">' +\n                            '<div class=\"fw-semibold\">' + escapeHtml(textOrDash(activityStage)) + '</div>' +\n                            '<div><small>source: ' + escapeHtml(textOrDash(activitySource)) + ' | at: ' + escapeHtml(activityTime) + '</small></div>' +\n                            '<div><small>' + escapeHtml(textOrDash(activityMessage)) + '</small></div>';\n                        if (detailsText) {\n                            html += '<div><small class=\"text-muted\">details: ' + escapeHtml(detailsText) + '</small></div>';\n                        }\n                        html += '</div>';\n                    }\n                }\n\n                root.innerHTML = html;\n            }\n\n            function getSelectedJobType() {\n                return String(state.selectedJobType || '').trim();\n            }\n\n            function resetJobTypePanels() {\n                state.loadedJobType = '';\n                state.descriptor = null;\n                state.config = null;\n                document.getElementById('plugin-descriptor-summary').textContent = 'No descriptor loaded.';\n                document.getElementById('plugin-admin-config-form').innerHTML = '<div class=\"text-muted\">No admin form loaded.</div>';\n                document.getElementById('plugin-worker-config-form').innerHTML = '<div class=\"text-muted\">No worker form loaded.</div>';\n                document.getElementById('plugin-config-updated-at').textContent = 'Not loaded';\n                renderRunHistory(null);\n                renderDetectionResult(null);\n            }\n\n            async function loadDescriptorAndConfig(jobType, forceRefresh) {\n                if (!jobType) {\n                    return;\n                }\n\n                state.selectedJobType = jobType;\n                renderNavigationState();\n\n                var summary = document.getElementById('plugin-descriptor-summary');\n                summary.textContent = 'Loading descriptor and config...';\n\n                try {\n                    if (forceRefresh) {\n                        var refreshURL = '/api/plugin/job-types/' + encodePath(jobType) + '/schema?force_refresh=true';\n                        state.descriptor = await pluginRequest('POST', refreshURL, null);\n                    } else {\n                        try {\n                            state.descriptor = await pluginRequest('GET', '/api/plugin/job-types/' + encodePath(jobType) + '/descriptor');\n                        } catch (descriptorLoadError) {\n                            state.descriptor = await pluginRequest('POST', '/api/plugin/job-types/' + encodePath(jobType) + '/schema', null);\n                        }\n                    }\n                } catch (schemaError) {\n                    state.loadedJobType = '';\n                    state.descriptor = null;\n                    summary.innerHTML = '<span class=\"text-danger\">Failed to load descriptor: ' + escapeHtml(schemaError.message) + '</span>';\n                    document.getElementById('plugin-admin-config-form').innerHTML = '<div class=\"text-muted\">No admin form loaded.</div>';\n                    document.getElementById('plugin-worker-config-form').innerHTML = '<div class=\"text-muted\">No worker form loaded.</div>';\n                    throw schemaError;\n                }\n\n                try {\n                    state.config = await pluginRequest('GET', '/api/plugin/job-types/' + encodePath(jobType) + '/config');\n                } catch (configError) {\n                    state.config = null;\n                    notify('Failed to load config: ' + configError.message, 'error');\n                }\n\n                renderDescriptorSummary();\n                fillAdminSettings();\n\n                if (state.descriptor) {\n                    renderConfigForm('admin', 'plugin-admin-config-form', state.descriptor.admin_config_form || {});\n                    renderConfigForm('worker', 'plugin-worker-config-form', state.descriptor.worker_config_form || {});\n                }\n\n                if (state.config && state.config.updated_at) {\n                    document.getElementById('plugin-config-updated-at').textContent = 'Updated: ' + (parseTime(state.config.updated_at) || state.config.updated_at);\n                } else {\n                    document.getElementById('plugin-config-updated-at').textContent = 'Not saved yet';\n                }\n                state.loadedJobType = jobType;\n\n                await loadRunHistory(jobType);\n\n                if (state.lastDetectionByJobType && state.lastDetectionByJobType[jobType]) {\n                    renderDetectionResult(state.lastDetectionByJobType[jobType]);\n                } else {\n                    renderDetectionResult(null);\n                }\n            }\n\n            async function loadRunHistory(jobType) {\n                if (!jobType) {\n                    renderRunHistory(null);\n                    return;\n                }\n                try {\n                    var history = await pluginRequest('GET', '/api/plugin/job-types/' + encodePath(jobType) + '/runs');\n                    renderRunHistory(history);\n                } catch (e) {\n                    renderRunHistory(null);\n                    notify('Failed to load run history: ' + e.message, 'error');\n                }\n            }\n\n            async function saveConfig() {\n                var jobType = state.selectedJobType || getSelectedJobType();\n                if (!jobType) {\n                    notify('Select a job type first.', 'error');\n                    return;\n                }\n                if (!state.descriptor) {\n                    notify('Load descriptor before saving config.', 'error');\n                    return;\n                }\n\n                var payload;\n                try {\n                    payload = {\n                        job_type: jobType,\n                        descriptor_version: state.descriptor.descriptor_version || 0,\n                        admin_runtime: collectAdminSettings(),\n                        admin_config_values: collectConfigValues('admin', state.descriptor.admin_config_form || {}),\n                        worker_config_values: collectConfigValues('worker', state.descriptor.worker_config_form || {}),\n                    };\n                } catch (err) {\n                    notify(err.message, 'error');\n                    return;\n                }\n\n                try {\n                    state.config = await pluginRequest('PUT', '/api/plugin/job-types/' + encodePath(jobType) + '/config', payload);\n                    document.getElementById('plugin-config-updated-at').textContent = 'Updated: ' + (parseTime(state.config.updated_at) || state.config.updated_at || 'just now');\n                    notify('Config saved for ' + jobType, 'success');\n                } catch (e) {\n                    notify('Failed to save config: ' + e.message, 'error');\n                }\n            }\n\n            function getMaxResults() {\n                var raw = String(document.getElementById('plugin-admin-max-results').value || '').trim();\n                if (!raw) {\n                    return 0;\n                }\n                var parsed = parseInt(raw, 10);\n                if (Number.isNaN(parsed) || parsed < 0) {\n                    return 0;\n                }\n                return parsed;\n            }\n\n            async function runDetection() {\n                var jobType = state.selectedJobType || getSelectedJobType();\n                if (!jobType) {\n                    notify('Select a job type first.', 'error');\n                    return;\n                }\n\n                try {\n                    var data = await pluginRequest('POST', '/api/plugin/job-types/' + encodePath(jobType) + '/detect', {\n                        max_results: getMaxResults(),\n                    });\n                    state.lastDetectionByJobType[jobType] = data;\n                    renderDetectionResult(data);\n                } catch (e) {\n                    if (e && e.responseData) {\n                        state.lastDetectionByJobType[jobType] = e.responseData;\n                        renderDetectionResult(e.responseData);\n                    }\n                    notify('Detection failed: ' + e.message, 'error');\n                    return;\n                }\n\n                try {\n                    await refreshJobsAndActivities();\n                } catch (refreshErr) {\n                    notify('Detection succeeded, but monitoring refresh failed: ' + refreshErr.message, 'error');\n                }\n            }\n\n            async function runWorkflow() {\n                var jobType = state.selectedJobType || getSelectedJobType();\n                if (!jobType) {\n                    notify('Select a job type first.', 'error');\n                    return;\n                }\n\n                try {\n                    var data = await pluginRequest('POST', '/api/plugin/job-types/' + encodePath(jobType) + '/run', {\n                        max_results: getMaxResults(),\n                    });\n                    var resultRoot = document.getElementById('plugin-detection-results');\n                    resultRoot.innerHTML = '<div class=\"mb-2\"><strong>Workflow completed</strong></div>' +\n                        '<div>Detected: <strong>' + escapeHtml(String(data.detected_count || 0)) + '</strong></div>' +\n                        '<div>Ready to Execute: <strong>' + escapeHtml(String(data.ready_to_execute_count || 0)) + '</strong></div>' +\n                        '<div>Skipped (Already Active): <strong>' + escapeHtml(String(data.skipped_active_count || 0)) + '</strong></div>' +\n                        '<div>Executed: <strong>' + escapeHtml(String(data.executed_count || 0)) + '</strong></div>' +\n                        '<div class=\"text-success\">Success: ' + escapeHtml(String(data.success_count || 0)) + '</div>' +\n                        '<div class=\"text-danger\">Errors: ' + escapeHtml(String(data.error_count || 0)) + '</div>';\n                    await loadRunHistory(jobType);\n                    await refreshJobsAndActivities();\n                } catch (e) {\n                    notify('Workflow run failed: ' + e.message, 'error');\n                }\n            }\n\n            async function refreshJobsAndActivities() {\n                var executionStateFilter = document.getElementById('plugin-monitor-job-state-filter');\n\n                state.monitorExecutionJobState = executionStateFilter ? String(executionStateFilter.value || '').trim() : '';\n\n                var allJobsPromise = pluginRequest('GET', appendLaneParam('/api/plugin/jobs?limit=500'));\n                var allActivitiesPromise = pluginRequest('GET', appendLaneParam('/api/plugin/activities?limit=500'));\n                var schedulerPromise = pluginRequest('GET', appendLaneParam('/api/plugin/scheduler-states'));\n                var schedulerStatusPromise = pluginRequest('GET', appendLaneParam('/api/plugin/scheduler-status'));\n\n                var allJobs = await allJobsPromise;\n                var allActivities = await allActivitiesPromise;\n                var schedulerStates = await schedulerPromise;\n                var schedulerStatus = null;\n                try {\n                    schedulerStatus = await schedulerStatusPromise;\n                } catch (e) {\n                    schedulerStatus = null;\n                }\n\n                state.jobs = Array.isArray(allJobs) ? allJobs : [];\n                state.activities = Array.isArray(allActivities) ? allActivities : [];\n                state.allJobs = state.jobs;\n                state.allActivities = state.activities;\n                state.schedulerStates = Array.isArray(schedulerStates) ? schedulerStates : [];\n                state.schedulerStatus = schedulerStatus && schedulerStatus.scheduler ? schedulerStatus.scheduler : null;\n                renderQueueJobs();\n                renderDetectionJobs();\n                renderExecutionJobs();\n                renderExecutionActivities();\n                renderSchedulerStates();\n                renderSchedulerStatus();\n                renderStatus();\n                renderJobTypeSummary();\n            }\n\n            async function refreshAll() {\n                try {\n                    var previousSelectedJobType = state.selectedJobType;\n\n                    var workers = await pluginRequest('GET', appendLaneParam('/api/plugin/workers'));\n                    state.workers = Array.isArray(workers) ? workers : [];\n                    renderWorkers();\n\n                    var jobTypes = await pluginRequest('GET', appendLaneParam('/api/plugin/job-types'));\n                    state.jobTypes = Array.isArray(jobTypes) ? jobTypes : [];\n                    applyInitialNavigation();\n                    ensureActiveNavigation();\n                    renderTopTabs();\n                    renderNavigationState();\n\n                    if (!state.selectedJobType) {\n                        resetJobTypePanels();\n                    } else if (state.loadedJobType !== state.selectedJobType || previousSelectedJobType !== state.selectedJobType) {\n                        try {\n                            await loadDescriptorAndConfig(state.selectedJobType, false);\n                        } catch (loadError) {\n                            // Already rendered in UI.\n                        }\n                    }\n\n                    await refreshJobsAndActivities();\n                } catch (e) {\n                    notify('Refresh failed: ' + e.message, 'error');\n                }\n            }\n\n            function bindActions() {\n                var monitorStateFilter = document.getElementById('plugin-monitor-job-state-filter');\n                if (monitorStateFilter) {\n                    monitorStateFilter.addEventListener('change', function() {\n                        state.monitorExecutionJobState = String(monitorStateFilter.value || '').trim();\n                        renderExecutionJobs();\n                    });\n                }\n\n                var expireBtn = document.getElementById('plugin-expire-job-btn');\n                if (expireBtn) {\n                    expireBtn.addEventListener('click', function() {\n                        var jobID = String(expireBtn.getAttribute('data-job-id') || '').trim();\n                        if (!jobID) {\n                            return;\n                        }\n                        expireJob(jobID);\n                    });\n                }\n\n                document.getElementById('plugin-refresh-all-btn').addEventListener('click', function() {\n                    refreshAll();\n                });\n\n                document.getElementById('plugin-refresh-schema-btn').addEventListener('click', async function() {\n                    var jobType = getSelectedJobType();\n                    if (!jobType) {\n                        notify('Select a job type first.', 'error');\n                        return;\n                    }\n                    try {\n                        await loadDescriptorAndConfig(jobType, true);\n                        notify('Schema refreshed for ' + jobType, 'success');\n                    } catch (e) {\n                        notify('Schema refresh failed: ' + e.message, 'error');\n                    }\n                });\n\n                document.getElementById('plugin-save-config-btn').addEventListener('click', function() {\n                    saveConfig();\n                });\n\n                document.getElementById('plugin-trigger-detection-btn').addEventListener('click', function() {\n                    runDetection();\n                });\n\n                document.getElementById('plugin-run-workflow-btn').addEventListener('click', function() {\n                    runWorkflow();\n                });\n\n                page.addEventListener('click', async function(event) {\n                    var target = event.target;\n                    if (!target) {\n                        return;\n                    }\n\n                    var topTab = target.closest('[data-plugin-top-tab]');\n                    if (topTab) {\n                        event.preventDefault();\n                        var topTabKey = String(topTab.getAttribute('data-plugin-top-tab') || '').trim();\n                        if (!topTabKey) {\n                            return;\n                        }\n                        if (topTabKey === 'overview') {\n                            state.activeTopTab = 'overview';\n                            renderNavigationState();\n                            updateURL(false);\n                            return;\n                        }\n\n                        var jobType = parseJobTypeFromTopTab(topTabKey);\n                        if (!jobType) {\n                            return;\n                        }\n                        state.activeTopTab = topTabKey;\n                        state.selectedJobType = jobType;\n                        ensureActiveNavigation();\n                        renderNavigationState();\n                        updateURL(false);\n                        if (state.loadedJobType !== jobType) {\n                            try {\n                                await loadDescriptorAndConfig(jobType, false);\n                            } catch (e) {\n                                // Already rendered in UI.\n                            }\n                        } else {\n                            if (state.lastDetectionByJobType && state.lastDetectionByJobType[jobType]) {\n                                renderDetectionResult(state.lastDetectionByJobType[jobType]);\n                            } else {\n                                renderDetectionResult(null);\n                            }\n                            await loadRunHistory(jobType);\n                        }\n                        renderQueueJobs();\n                        renderDetectionJobs();\n                        renderExecutionJobs();\n                        renderExecutionActivities();\n                        return;\n                    }\n\n                    var subTab = target.closest('[data-plugin-subtab]');\n                    if (subTab) {\n                        event.preventDefault();\n                        var subTabKey = normalizeSubTab(subTab.getAttribute('data-plugin-subtab'));\n                        state.activeSubTab = subTabKey;\n                        renderNavigationState();\n                        updateURL(false);\n                        return;\n                    }\n\n                    var trigger = target.closest('.plugin-job-link');\n                    if (!trigger) {\n                        return;\n                    }\n                    event.preventDefault();\n                    var jobID = trigger.getAttribute('data-job-id');\n                    if (!jobID) {\n                        return;\n                    }\n                    openJobDetail(jobID);\n                });\n            }\n\n            async function init() {\n                bindActions();\n                renderTopTabs();\n                ensureActiveNavigation();\n                renderNavigationState();\n                await refreshAll();\n\n                // Set the initial browser history state to match the current view\n                updateURL(true);\n\n                state.refreshTimer = setInterval(function() {\n                    refreshAll();\n                }, 3000);\n            }\n\n            window.addEventListener('popstate', async function(event) {\n                var s = event.state;\n                if (!s || !s.pluginTopTab) {\n                    // No plugin state — treat as overview\n                    state.activeTopTab = 'overview';\n                    renderNavigationState();\n                    return;\n                }\n                state.activeTopTab = String(s.pluginTopTab || 'overview');\n                state.activeSubTab = normalizeSubTab(s.pluginSubTab);\n                var jobType = String(s.pluginJob || '').trim();\n                if (jobType) {\n                    state.selectedJobType = jobType;\n                }\n                ensureActiveNavigation();\n                renderNavigationState();\n                // If a job type is selected and not loaded yet, load it\n                if (state.selectedJobType && state.loadedJobType !== state.selectedJobType) {\n                    try {\n                        await loadDescriptorAndConfig(state.selectedJobType, false);\n                    } catch (e) {\n                        console.error('Failed to load data on popstate navigation:', e);\n                    }\n                }\n                renderQueueJobs();\n                renderDetectionJobs();\n                renderExecutionJobs();\n                renderExecutionActivities();\n            });\n\n            window.addEventListener('beforeunload', function() {\n                if (state.refreshTimer) {\n                    clearInterval(state.refreshTimer);\n                    state.refreshTimer = null;\n                }\n            });\n\n            init();\n        })();\n        </script>")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		return nil
	})
}

var _ = templruntime.GeneratedTemplate
