Files
seaweedfs/weed/admin/view/app/plugin_lane.templ
T
Chris LuandGitHub f2c3bd7b77 fix(admin/view): define basePath in plugin IIFE scopes (#9298)
The plugin.templ and plugin_lane.templ components use basePath() in their IIFE
(Immediately Invoked Function Expression) scopes to handle subdirectory
deployments. However, basePath was not defined locally, causing "basePath is
not defined" errors when accessing plugin pages.

Added local basePath function definitions in both files, matching the pattern
from admin.js. This function checks window.__BASE_PATH__ (set by the layout
during page initialization) and prepends it to API paths.
2026-05-01 18:22:39 -07:00

172 lines
8.4 KiB
Templ

package app
templ PluginLane(page string, lane string) {
{{
currentPage := page
if currentPage == "" {
currentPage = "lane_workers"
}
}}
<div class="container-fluid" id="plugin-lane-page" data-plugin-page={ currentPage } data-plugin-lane={ lane }>
<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-layer-group me-2"></i>{ lane } Lane Workers</h2>
<p class="text-muted mb-0">Workers and scheduler status for the { lane } scheduling lane.</p>
</div>
<div class="btn-group">
<a href="/plugin" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>All Workers
</a>
<button type="button" class="btn btn-outline-secondary" id="plugin-lane-refresh-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">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0"><i class="fas fa-server me-2"></i>Lane Scheduler Status</h5>
</div>
<div class="card-body" id="plugin-lane-scheduler-status">
<div class="text-center text-muted py-3">
<i class="fas fa-spinner fa-spin me-1"></i>Loading scheduler status...
</div>
</div>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0"><i class="fas fa-tasks me-2"></i>Job Types</h5>
</div>
<div class="card-body" id="plugin-lane-job-types">
<div class="text-center text-muted py-3">
<i class="fas fa-spinner fa-spin me-1"></i>Loading job types...
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0"><i class="fas fa-plug me-2"></i>Connected Workers</h5>
</div>
<div class="card-body" id="plugin-lane-workers">
<div class="text-center text-muted py-3">
<i class="fas fa-spinner fa-spin me-1"></i>Loading workers...
</div>
</div>
</div>
</div>
</div>
</div>
<script>
(function() {
const page = document.getElementById('plugin-lane-page');
if (!page) return;
const lane = page.dataset.pluginLane || '';
if (!lane) return;
function basePath(path) {
return (window.__BASE_PATH__ || '') + path;
}
function fetchAndRender(url, containerId, renderFn) {
fetch(basePath(url))
.then(r => r.json())
.then(data => {
const el = document.getElementById(containerId);
if (el) el.innerHTML = renderFn(data);
})
.catch(err => {
const el = document.getElementById(containerId);
if (el) el.innerHTML = '<div class="alert alert-danger">Failed to load: ' + err.message + '</div>';
});
}
function renderSchedulerStatus(data) {
if (!data || !data.scheduler) return '<div class="text-muted">No scheduler data</div>';
const s = data.scheduler;
let html = '<div class="row">';
html += '<div class="col-md-3"><strong>Phase:</strong> ' + (s.current_phase || 'idle') + '</div>';
html += '<div class="col-md-3"><strong>Idle Sleep:</strong> ' + (s.idle_sleep_seconds || 0) + 's</div>';
html += '<div class="col-md-3"><strong>Current Job Type:</strong> ' + (s.current_job_type || '-') + '</div>';
html += '<div class="col-md-3"><strong>Next Detection:</strong> ' + (s.next_detection_at ? new Date(s.next_detection_at).toLocaleTimeString() : '-') + '</div>';
html += '</div>';
if (s.job_types && s.job_types.length > 0) {
html += '<hr><table class="table table-sm table-hover mb-0"><thead><tr><th>Job Type</th><th>Enabled</th><th>Interval</th><th>In Flight</th><th>Next Detection</th></tr></thead><tbody>';
s.job_types.forEach(jt => {
html += '<tr>';
html += '<td>' + jt.job_type + '</td>';
html += '<td>' + (jt.enabled ? '<span class="badge bg-success">Yes</span>' : '<span class="badge bg-secondary">No</span>') + '</td>';
html += '<td>' + (jt.detection_interval_seconds || '-') + 's</td>';
html += '<td>' + (jt.detection_in_flight ? '<span class="badge bg-warning">Yes</span>' : 'No') + '</td>';
html += '<td>' + (jt.next_detection_at ? new Date(jt.next_detection_at).toLocaleTimeString() : '-') + '</td>';
html += '</tr>';
});
html += '</tbody></table>';
}
return html;
}
function renderJobTypes(data) {
if (!data || data.length === 0) return '<div class="text-muted">No job types in this lane</div>';
const filtered = data.filter(s => s.lane === lane);
if (filtered.length === 0) return '<div class="text-muted">No job types in this lane</div>';
let html = '<table class="table table-sm table-hover mb-0"><thead><tr><th>Job Type</th><th>Enabled</th><th>Concurrency</th><th>Detection Interval</th><th>Status</th></tr></thead><tbody>';
filtered.forEach(s => {
html += '<tr>';
html += '<td><a href="' + basePath('/plugin/configuration?job=' + s.job_type) + '">' + s.job_type + '</a></td>';
html += '<td>' + (s.enabled ? '<span class="badge bg-success">Yes</span>' : '<span class="badge bg-secondary">No</span>') + '</td>';
html += '<td>' + (s.global_execution_concurrency || 1) + '</td>';
html += '<td>' + (s.detection_interval_seconds || '-') + 's</td>';
html += '<td>' + (s.last_run_status || '-') + '</td>';
html += '</tr>';
});
html += '</tbody></table>';
return html;
}
function renderWorkers(data) {
if (!data || data.length === 0) return '<div class="text-muted">No workers connected for this lane</div>';
let html = '<table class="table table-sm table-hover mb-0"><thead><tr><th>Worker ID</th><th>Address</th><th>Job Types</th><th>Connected</th><th>Last Seen</th></tr></thead><tbody>';
data.forEach(w => {
const jobTypes = Object.keys(w.Capabilities || {}).join(', ');
html += '<tr>';
html += '<td>' + (w.WorkerID || '-') + '</td>';
html += '<td>' + (w.Address || '-') + '</td>';
html += '<td>' + jobTypes + '</td>';
html += '<td>' + (w.ConnectedAt ? new Date(w.ConnectedAt).toLocaleString() : '-') + '</td>';
html += '<td>' + (w.LastSeenAt ? new Date(w.LastSeenAt).toLocaleString() : '-') + '</td>';
html += '</tr>';
});
html += '</tbody></table>';
return html;
}
function refresh() {
fetchAndRender('/api/plugin/scheduler-status?lane=' + lane, 'plugin-lane-scheduler-status', renderSchedulerStatus);
fetchAndRender('/api/plugin/scheduler-states?lane=' + lane, 'plugin-lane-job-types', renderJobTypes);
fetchAndRender('/api/plugin/workers?lane=' + lane, 'plugin-lane-workers', renderWorkers);
}
refresh();
const btn = document.getElementById('plugin-lane-refresh-btn');
if (btn) btn.addEventListener('click', refresh);
})();
</script>
}