Skip to content

Commit 980c470

Browse files
author
Name
committed
changes
1 parent d2c4a7e commit 980c470

File tree

5 files changed

+224
-27
lines changed

5 files changed

+224
-27
lines changed

docs-es/v2.0/troubleshooting-es.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ Si el scheduler falla inmediatamente después de un reinicio con un mensaje como
117117
### El estado del scheduler en WebUI aparece no saludable
118118

119119
Versiones recientes muestran estado/readiness del scheduler en WebUI.
120+
Si el modal del scheduler indica que se agotó el tiempo al cargar detalles, las versiones actuales mantienen visible la última instantánea buena del scheduler con su antigüedad en lugar de convertir ese problema de transporte en un error del scheduler. Trate primero esa advertencia como un problema de comunicación entre WebUI y `lqosd`, y confirme la salud real del scheduler en los logs antes de asumir que falló el shaping.
120121

121122
Si aparece caído/desactualizado:
122123

docs/v2.0/troubleshooting.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ If an integration subprocess fails, current builds keep the scheduler alive, pub
184184
Recent builds expose scheduler readiness/state in the WebUI (Node Manager).
185185
If the scheduler is still starting, the sidebar now reports the current startup phase and a coarse progress ring rather than only a spinner.
186186
Current builds also treat scheduler progress, output, and error bus messages as proof that the scheduler is alive, so the sidebar should not stay stuck on `Scheduler available: false` while the scheduler is actively reporting work.
187+
If the scheduler modal says scheduler details timed out, current builds keep showing the last good scheduler snapshot with its age instead of turning that transport problem into a scheduler error. Treat that warning as a WebUI or `lqosd` communication issue first, then confirm scheduler health in the service logs before assuming shaping failed.
187188

188189
If scheduler status appears down/stale:
189190
1. Verify both services:

src/rust/lqosd/src/node_manager/js_build/src/template.js

Lines changed: 132 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ const SCHEDULER_MODAL_ACTIVE_POLL_MS = 1000;
3535
let schedulerStatusPollTimer = null;
3636
let schedulerStatusRequestInFlight = false;
3737
let schedulerStatusFirstRequestedAt = null;
38+
let cachedSchedulerStatusData = null;
3839
let schedulerModalPollTimer = null;
3940
let schedulerModalRequestInFlight = false;
4041
let schedulerModalInstance = null;
42+
let cachedSchedulerDetailsData = null;
4143
let cachedQueueModeConfig = null;
4244

4345
function listenOnceWithTimeout(eventName, timeoutMs, handler, onTimeout) {
@@ -125,6 +127,42 @@ function schedulerErrorText(data) {
125127
return String(error).trim();
126128
}
127129

130+
function schedulerSnapshotUpdatedUnix(data) {
131+
const progressUpdatedUnix = Number(data?.progress?.updated_unix);
132+
if (Number.isFinite(progressUpdatedUnix) && progressUpdatedUnix > 0) {
133+
return progressUpdatedUnix;
134+
}
135+
const statusUpdatedUnix = Number(data?.updated_unix);
136+
if (Number.isFinite(statusUpdatedUnix) && statusUpdatedUnix > 0) {
137+
return statusUpdatedUnix;
138+
}
139+
return null;
140+
}
141+
142+
function schedulerUpdatedText(data) {
143+
const updatedUnix = schedulerSnapshotUpdatedUnix(data);
144+
if (!updatedUnix) {
145+
return "Update time unavailable";
146+
}
147+
return schedulerRelativeTime(updatedUnix);
148+
}
149+
150+
function schedulerTransportWarningText(message, data) {
151+
const updatedText = schedulerUpdatedText(data);
152+
if (updatedText === "Update time unavailable") {
153+
return `${message} Retrying in the background.`;
154+
}
155+
return `${message} Showing cached scheduler data. ${updatedText}.`;
156+
}
157+
158+
function schedulerStaleStatusLabel(data) {
159+
const updatedText = schedulerUpdatedText(data);
160+
if (updatedText === "Update time unavailable") {
161+
return "Scheduler status is stale";
162+
}
163+
return `Scheduler status is stale. ${updatedText}.`;
164+
}
165+
128166
function schedulerLooksRecovered(data) {
129167
const progress = data?.progress || null;
130168
if (!data?.available || !progress || progress.active) {
@@ -170,6 +208,17 @@ function schedulerStateDescriptor(data) {
170208
icon: "fa-list-check",
171209
};
172210
}
211+
if (data?.stale) {
212+
return {
213+
tone: "warning",
214+
badgeClass: "text-bg-warning",
215+
label: "Stale",
216+
title: "Scheduler status is stale",
217+
subtitle: "Showing the last scheduler snapshot older than five minutes",
218+
ringTone: "tone-warning",
219+
icon: "fa-clock",
220+
};
221+
}
173222
if (active) {
174223
return {
175224
tone: "info",
@@ -250,7 +299,7 @@ function schedulerActivityItems(output, error, data) {
250299
.reverse();
251300
}
252301

253-
function renderSchedulerDetails(data) {
302+
function renderSchedulerDetails(data, options = {}) {
254303
const progress = data?.progress || null;
255304
const descriptor = schedulerStateDescriptor(data);
256305
const liveError = schedulerHasLiveError(data) ? schedulerErrorText(data) : "";
@@ -262,22 +311,30 @@ function renderSchedulerDetails(data) {
262311
const stepText = Number.isFinite(stepIndex) && Number.isFinite(stepCount) && stepCount > 0
263312
? `Step ${stepIndex} of ${stepCount}`
264313
: "No active step reported";
265-
const updatedText = progress?.updated_unix
266-
? schedulerRelativeTime(progress.updated_unix)
267-
: "Update time unavailable";
268-
const availabilityText = data?.setup_required ? "Setup Required" : (data?.available ? "Healthy" : "Unavailable");
314+
const updatedText = schedulerUpdatedText(data);
315+
const availabilityText = data?.setup_required
316+
? "Setup Required"
317+
: data?.stale
318+
? "Stale"
319+
: (data?.available ? "Healthy" : "Unavailable");
269320
const recentResult = schedulerSetupMessage(data) || summarizeSchedulerOutput(data?.output, historicalError || liveError);
270321
const activity = schedulerActivityItems(data?.output, historicalError || liveError, data);
271-
const progressMeta = data?.setup_required
272-
? "Complete runtime setup to enable scheduler work"
273-
: progress?.active
274-
? `${percent}% complete`
275-
: descriptor.label === "Idle"
276-
? "Last run complete"
277-
: "Waiting for activity";
322+
let progressMeta = "Waiting for activity";
323+
if (options.transportWarning) {
324+
progressMeta = "Showing cached scheduler details while the UI reconnects";
325+
} else if (data?.setup_required) {
326+
progressMeta = "Complete runtime setup to enable scheduler work";
327+
} else if (progress?.active) {
328+
progressMeta = `${percent}% complete`;
329+
} else if (descriptor.label === "Idle") {
330+
progressMeta = "Last run complete";
331+
}
278332
const setupAlert = data?.setup_required
279333
? `<div class="alert alert-warning mt-3 mb-0" role="alert"><i class="fa fa-list-check me-2"></i>${escapeHtml(schedulerSetupMessage(data) || "Choose a topology source in Complete Setup before expecting scheduler activity.")}</div>`
280334
: "";
335+
const transportWarningMarkup = options.transportWarning
336+
? `<div class="alert alert-warning mt-3 mb-0" role="alert"><i class="fa fa-wifi me-2"></i>${escapeHtml(options.transportWarning)}</div>`
337+
: "";
281338
const alertMarkup = liveError
282339
? `<div class="alert alert-danger mt-3 mb-0" role="alert"><i class="fa fa-triangle-exclamation me-2"></i>${escapeHtml(liveError)}</div>`
283340
: "";
@@ -304,6 +361,7 @@ function renderSchedulerDetails(data) {
304361
</div>
305362
</div>
306363
${setupAlert}
364+
${transportWarningMarkup}
307365
${alertMarkup}
308366
<div class="lqos-scheduler-progress-card">
309367
<div class="lqos-scheduler-progress-topline">
@@ -360,7 +418,7 @@ function updateSchedulerModalBody(contentHtml) {
360418
}
361419
}
362420

363-
function renderSchedulerStatus(container, state, progress) {
421+
function renderSchedulerStatus(container, state, progress, labelOverride = null) {
364422
if (!container) return;
365423

366424
let color = "text-secondary";
@@ -388,11 +446,18 @@ function renderSchedulerStatus(container, state, progress) {
388446
color = "text-danger";
389447
label = "Scheduler has an internal error";
390448
indicator = schedulerRingMarkup(100, "tone-danger", "fa-triangle-exclamation");
449+
} else if (state === "stale") {
450+
color = "text-warning";
451+
label = "Scheduler status is stale";
452+
indicator = schedulerRingMarkup(100, "tone-warning", "fa-clock");
391453
} else if (state === "setup") {
392454
color = "text-warning";
393455
label = "Scheduler needs topology setup";
394456
indicator = schedulerRingMarkup(100, "tone-warning", "fa-list-check");
395457
}
458+
if (labelOverride) {
459+
label = labelOverride;
460+
}
396461

397462
container.innerHTML = `
398463
<button class="nav-link btn btn-link text-start w-100 p-0 border-0 ${color} lqos-scheduler-link" type="button" id="schedulerStatusLink" aria-label="${escapeAttr(label)}" title="${escapeAttr(label)}">
@@ -432,16 +497,27 @@ function loadSchedulerStatus(force = false) {
432497
listenOnceWithTimeout("SchedulerStatus", SCHEDULER_STATUS_TIMEOUT_MS, (msg) => {
433498
schedulerStatusRequestInFlight = false;
434499
if (!msg || !msg.data) {
435-
renderSchedulerStatus(container, "unavailable", null);
500+
if (cachedSchedulerStatusData) {
501+
renderSchedulerStatus(
502+
container,
503+
"stale",
504+
cachedSchedulerStatusData.progress || null,
505+
schedulerTransportWarningText("Scheduler status response was empty.", cachedSchedulerStatusData)
506+
);
507+
} else {
508+
renderSchedulerStatus(container, "unavailable", null);
509+
}
436510
scheduleNextSchedulerStatusPoll();
437511
return;
438512
}
439513
const data = msg.data;
514+
cachedSchedulerStatusData = data;
440515
const hasError = schedulerHasLiveError(data);
441516
const isHealthy = !!data.available && !hasError;
442517
const progress = data.progress || null;
443518
const progressActive = !!(progress && progress.active);
444519
const setupRequired = !!data.setup_required;
520+
const stale = !!data.stale;
445521
const elapsed = schedulerStatusFirstRequestedAt === null ? 0 : (Date.now() - schedulerStatusFirstRequestedAt);
446522

447523
if (hasError) {
@@ -456,6 +532,12 @@ function loadSchedulerStatus(force = false) {
456532
return;
457533
}
458534

535+
if (stale) {
536+
renderSchedulerStatus(container, "stale", progress, schedulerStaleStatusLabel(data));
537+
scheduleNextSchedulerStatusPoll();
538+
return;
539+
}
540+
459541
if (progressActive) {
460542
renderSchedulerStatus(container, "progress", progress);
461543
scheduleNextSchedulerStatusPoll();
@@ -475,7 +557,14 @@ function loadSchedulerStatus(force = false) {
475557
}, () => {
476558
schedulerStatusRequestInFlight = false;
477559
const elapsed = schedulerStatusFirstRequestedAt === null ? 0 : (Date.now() - schedulerStatusFirstRequestedAt);
478-
if (elapsed < SCHEDULER_STATUS_STARTING_GRACE_MS) {
560+
if (cachedSchedulerStatusData) {
561+
renderSchedulerStatus(
562+
container,
563+
"stale",
564+
cachedSchedulerStatusData.progress || null,
565+
schedulerTransportWarningText("Scheduler status refresh timed out.", cachedSchedulerStatusData)
566+
);
567+
} else if (elapsed < SCHEDULER_STATUS_STARTING_GRACE_MS) {
479568
renderSchedulerStatus(container, "loading", null);
480569
} else {
481570
renderSchedulerStatus(container, "unavailable", null);
@@ -534,13 +623,6 @@ function renderSchedulerModalLoading() {
534623
</div>`);
535624
}
536625

537-
function renderSchedulerModalError(message) {
538-
updateSchedulerModalBody(`
539-
<div class="alert alert-danger mb-0" role="alert">
540-
<i class="fa fa-triangle-exclamation me-2"></i>${escapeHtml(message)}
541-
</div>`);
542-
}
543-
544626
function ensureSchedulerModalLifecycle() {
545627
const modalEl = document.getElementById('schedulerModal');
546628
if (!modalEl || modalEl.dataset.schedulerLiveBound === "1") {
@@ -567,10 +649,24 @@ function refreshSchedulerModalDetails(showLoading = false) {
567649
return;
568650
}
569651
if (!msg || !msg.data) {
570-
renderSchedulerModalError("Failed to load scheduler details");
652+
if (cachedSchedulerDetailsData) {
653+
updateSchedulerModalBody(renderSchedulerDetails(cachedSchedulerDetailsData, {
654+
transportWarning: schedulerTransportWarningText(
655+
"Scheduler details response was empty.",
656+
cachedSchedulerDetailsData
657+
),
658+
}));
659+
} else {
660+
updateSchedulerModalBody(`
661+
<div class="alert alert-warning mb-0" role="alert">
662+
<i class="fa fa-wifi me-2"></i>${escapeHtml("Failed to load scheduler details. Retrying in the background.")}
663+
</div>`);
664+
}
571665
scheduleNextSchedulerModalPoll(SCHEDULER_MODAL_IDLE_POLL_MS);
572666
return;
573667
}
668+
cachedSchedulerDetailsData = msg.data;
669+
cachedSchedulerStatusData = msg.data;
574670
updateSchedulerModalBody(renderSchedulerDetails(msg.data));
575671
scheduleNextSchedulerModalPoll(schedulerModalPollDelay(msg.data));
576672
}, () => {
@@ -579,7 +675,19 @@ function refreshSchedulerModalDetails(showLoading = false) {
579675
stopSchedulerModalPolling();
580676
return;
581677
}
582-
renderSchedulerModalError("Timed out while loading scheduler details");
678+
if (cachedSchedulerDetailsData) {
679+
updateSchedulerModalBody(renderSchedulerDetails(cachedSchedulerDetailsData, {
680+
transportWarning: schedulerTransportWarningText(
681+
"Scheduler details refresh timed out.",
682+
cachedSchedulerDetailsData
683+
),
684+
}));
685+
} else {
686+
updateSchedulerModalBody(`
687+
<div class="alert alert-warning mb-0" role="alert">
688+
<i class="fa fa-wifi me-2"></i>${escapeHtml("Timed out while loading scheduler details. Retrying in the background.")}
689+
</div>`);
690+
}
583691
scheduleNextSchedulerModalPoll(SCHEDULER_MODAL_IDLE_POLL_MS);
584692
});
585693
wsClient.send({ SchedulerDetails: {} });

0 commit comments

Comments
 (0)