Compare commits
No commits in common. "12a37cc734851772d547fd6dd71fe1b507aa6cb9" and "dfd4eb0f17ec7db8578095c39c4cf228c2eae070" have entirely different histories.
12a37cc734
...
dfd4eb0f17
@ -475,11 +475,9 @@ Object.assign(TasksManager.prototype, {
|
|||||||
},
|
},
|
||||||
filterTasksByCategoryHandler(category) {
|
filterTasksByCategoryHandler(category) {
|
||||||
this.currentCategory = category;
|
this.currentCategory = category;
|
||||||
|
|
||||||
// Clear specific task filter when switching categories. Picking a
|
// Clear specific task filter when switching categories
|
||||||
// category is the user steering the view — auto-follow stands down too.
|
|
||||||
this.highlightedTaskId = null;
|
this.highlightedTaskId = null;
|
||||||
this.followRunning = false;
|
|
||||||
|
|
||||||
// Update URL
|
// Update URL
|
||||||
this.updateURL(category);
|
this.updateURL(category);
|
||||||
|
|||||||
@ -8,10 +8,6 @@ class TasksManager {
|
|||||||
this.tasks = [];
|
this.tasks = [];
|
||||||
this.currentCategory = 'all';
|
this.currentCategory = 'all';
|
||||||
this.highlightedTaskId = null;
|
this.highlightedTaskId = null;
|
||||||
// When true, the open row tracks whichever task is currently running
|
|
||||||
// (queue handoffs, setup installs). Cleared the moment the user manually
|
|
||||||
// toggles a row or switches category — their selection then wins.
|
|
||||||
this.followRunning = false;
|
|
||||||
// Multi-select: ids of tasks the user has ticked. When non-empty the
|
// Multi-select: ids of tasks the user has ticked. When non-empty the
|
||||||
// "Clear All" button morphs into "Delete Selected (N)". Both paths
|
// "Clear All" button morphs into "Delete Selected (N)". Both paths
|
||||||
// share _showClearAllModal — same UX, different filter.
|
// share _showClearAllModal — same UX, different filter.
|
||||||
@ -182,22 +178,13 @@ class TasksManager {
|
|||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
//// // console.log('🔧 Initializing TasksManager...');
|
//// // console.log('🔧 Initializing TasksManager...');
|
||||||
|
|
||||||
// Re-read the URL on every (re)mount. The SPA reuses this singleton, so a
|
|
||||||
// navigation to /tasks/<cat>?task=X must refresh the category + deep-link
|
|
||||||
// state — a constructor-only read goes stale after the first visit.
|
|
||||||
this.initializeFromURL();
|
|
||||||
|
|
||||||
// Load initial tasks and refresh sidebar counts
|
// Load initial tasks and refresh sidebar counts
|
||||||
await this.loadTasks();
|
await this.loadTasks();
|
||||||
|
|
||||||
// Force a refresh to ensure latest data
|
// Force a refresh to ensure latest data
|
||||||
await this.loadTasks();
|
await this.loadTasks();
|
||||||
|
|
||||||
// Open the row this visit is about (deep link, or the live task) now that
|
|
||||||
// the first render is in the DOM, and arm running-task auto-follow.
|
|
||||||
if (typeof this.applyInitialSelection === 'function') this.applyInitialSelection();
|
|
||||||
|
|
||||||
// Setup auto-refresh
|
// Setup auto-refresh
|
||||||
this.setupAutoRefresh();
|
this.setupAutoRefresh();
|
||||||
|
|
||||||
@ -380,7 +367,6 @@ class TasksManager {
|
|||||||
// any open dropdown's DOM. A targeted update is enough; the next
|
// any open dropdown's DOM. A targeted update is enough; the next
|
||||||
// category switch / refresh will pull the new row in.
|
// category switch / refresh will pull the new row in.
|
||||||
this.updateTaskDisplay(task);
|
this.updateTaskDisplay(task);
|
||||||
this.maybeFollowRunningTask(task);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('taskUpdated', (e) => {
|
window.addEventListener('taskUpdated', (e) => {
|
||||||
@ -390,7 +376,6 @@ class TasksManager {
|
|||||||
if (task.status === 'running') {
|
if (task.status === 'running') {
|
||||||
this.updateTaskStructure(task.id, task);
|
this.updateTaskStructure(task.id, task);
|
||||||
this.startLogStreaming(task.id, task);
|
this.startLogStreaming(task.id, task);
|
||||||
this.maybeFollowRunningTask(task);
|
|
||||||
}
|
}
|
||||||
this.updateTaskDisplay(task);
|
this.updateTaskDisplay(task);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
// Auto-extracted from tasks-manager.js (verbatim) — augments TasksManager.prototype. Loaded after the base.
|
// Auto-extracted from tasks-manager.js (verbatim) — augments TasksManager.prototype. Loaded after the base.
|
||||||
Object.assign(TasksManager.prototype, {
|
Object.assign(TasksManager.prototype, {
|
||||||
toggleTaskDetails(taskId) {
|
toggleTaskDetails(taskId) {
|
||||||
// A manual toggle means the user has taken control of the view — stop
|
|
||||||
// auto-following the running task for the rest of this visit.
|
|
||||||
this.followRunning = false;
|
|
||||||
const details = document.getElementById(`details-${taskId}`);
|
const details = document.getElementById(`details-${taskId}`);
|
||||||
const toggleBtn = document.querySelector(`.task-btn.toggle-details[onclick*="toggleTaskDetails('${taskId}')"]`);
|
const toggleBtn = document.querySelector(`.task-btn.toggle-details[onclick*="toggleTaskDetails('${taskId}')"]`);
|
||||||
|
|
||||||
@ -61,94 +58,6 @@ Object.assign(TasksManager.prototype, {
|
|||||||
this.highlightedTaskId = null;
|
this.highlightedTaskId = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Programmatically open exactly one task row: collapse any other open
|
|
||||||
// panel, expand this one, attach the right log view (live stream for
|
|
||||||
// active tasks, snapshot for terminal ones) and bring it into view.
|
|
||||||
// Used by deep links and the running-task auto-follow — unlike
|
|
||||||
// toggleTaskDetails it never closes an already-open target and never
|
|
||||||
// touches followRunning. Returns false when the row isn't in the DOM yet.
|
|
||||||
selectTask(taskId) {
|
|
||||||
const details = document.getElementById(`details-${taskId}`);
|
|
||||||
if (!details) return false;
|
|
||||||
|
|
||||||
document.querySelectorAll('.task-details.task-details-open').forEach(el => {
|
|
||||||
if (el.id !== `details-${taskId}`) {
|
|
||||||
el.style.display = 'none';
|
|
||||||
el.classList.remove('task-details-open');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
document.querySelectorAll('.task-btn.toggle-details.expanded').forEach(btn => {
|
|
||||||
if (!(btn.getAttribute('onclick') || '').includes(`'${taskId}'`)) btn.classList.remove('expanded');
|
|
||||||
});
|
|
||||||
|
|
||||||
const wasOpen = details.classList.contains('task-details-open');
|
|
||||||
details.style.display = 'block';
|
|
||||||
details.classList.add('task-details-open');
|
|
||||||
const toggleBtn = document.querySelector(`.task-btn.toggle-details[onclick*="toggleTaskDetails('${taskId}')"]`);
|
|
||||||
if (toggleBtn) toggleBtn.classList.add('expanded');
|
|
||||||
|
|
||||||
const task = this.tasks.find(t => t.id === taskId);
|
|
||||||
if (task && (task.status === 'running' || task.status === 'queued' || task.status === 'pending')) {
|
|
||||||
this.startLogStreaming(taskId, task);
|
|
||||||
} else {
|
|
||||||
this.loadTaskLogs(taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!wasOpen) {
|
|
||||||
const row = document.querySelector(`.task-item[data-task-id="${taskId}"]`);
|
|
||||||
if (row) row.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
||||||
}
|
|
||||||
this.highlightedTaskId = taskId;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
// Pick which row should open when the page (re)loads, and arm auto-follow.
|
|
||||||
//
|
|
||||||
// A `?task=` deep link normally wins. The setup-wizard handoff is the
|
|
||||||
// exception: it deep-links the FIRST task of its install queue
|
|
||||||
// (&from=setup), but by the time the page renders the processor may
|
|
||||||
// already be tasks ahead — so for setup arrivals, and for plain visits
|
|
||||||
// with no deep link, open whatever is actually running (else next in the
|
|
||||||
// queue) and keep following the queue until the user takes over.
|
|
||||||
applyInitialSelection() {
|
|
||||||
if (!document.getElementById('tasks-list')) return;
|
|
||||||
|
|
||||||
const params = new URL(window.location.href).searchParams;
|
|
||||||
const fromSetup = params.get('from') === 'setup';
|
|
||||||
const isActive = (t) => t && (t.status === 'running' || t.status === 'queued' || t.status === 'pending');
|
|
||||||
const pinned = this.highlightedTaskId
|
|
||||||
? this.tasks.find(t => t.id === this.highlightedTaskId)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
this.followRunning = fromSetup || !pinned;
|
|
||||||
|
|
||||||
let target = pinned;
|
|
||||||
if (this.followRunning) {
|
|
||||||
const running = this.tasks.find(t => t.status === 'running');
|
|
||||||
// this.tasks is sorted newest-first, so the oldest queued is next to run
|
|
||||||
const nextQueued = [...this.tasks].reverse().find(t => t.status === 'queued' || t.status === 'pending');
|
|
||||||
target = running || (isActive(pinned) ? pinned : null) || nextQueued || pinned;
|
|
||||||
}
|
|
||||||
if (!target) return;
|
|
||||||
// renderTasks restores the pre-render scroll position one frame from now;
|
|
||||||
// selecting after that frame keeps our scrollIntoView from being cancelled.
|
|
||||||
const id = target.id;
|
|
||||||
requestAnimationFrame(() => requestAnimationFrame(() => this.selectTask(id)));
|
|
||||||
},
|
|
||||||
// The queue advanced to a new running task — if the user hasn't taken over
|
|
||||||
// and we're on the tasks page, move the open panel along with it.
|
|
||||||
maybeFollowRunningTask(task) {
|
|
||||||
if (!this.followRunning || !task || task.status !== 'running') return;
|
|
||||||
if (!document.getElementById('tasks-list')) return;
|
|
||||||
if (this.highlightedTaskId === task.id) return;
|
|
||||||
const current = this.tasks.find(t => t.id === this.highlightedTaskId);
|
|
||||||
if (current && current.status === 'running') return; // never yank a live view
|
|
||||||
if (!this.selectTask(task.id)) {
|
|
||||||
// Row not rendered yet (task created since the last render) — rebuild
|
|
||||||
// the list from the upserted cache, then select.
|
|
||||||
this.renderTasks();
|
|
||||||
this.selectTask(task.id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Monitor a specific task. State changes now arrive via SSE through
|
// Monitor a specific task. State changes now arrive via SSE through
|
||||||
// TaskEventBus, so this is mostly a hook for the UI to:
|
// TaskEventBus, so this is mostly a hook for the UI to:
|
||||||
// - auto-expand the task if it's the one we just started
|
// - auto-expand the task if it's the one we just started
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user