infracensus-collector 1.6.2 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -15972,19 +15972,37 @@ ${tokenCss()}
15972
15972
  writeRunSiteHint();
15973
15973
  $("runTargets").value = (s.scanDefaults.targets || []).join(", ");
15974
15974
  }
15975
- renderLive(s.scanProgress || null);
15976
- // A run that has just ENDED is the moment the history gained a row, and the
15977
- // moment somebody wants to read it.
15975
+ // THE AUTHORITY ON WHAT IS RUNNING, held for the history to read.
15978
15976
  //
15979
- // It is also re-read WHILE one runs, which it deliberately was not before:
15980
- // the detail used to be a fold of a finished run, so nothing in it changed
15981
- // between two polls of a scan in flight. It now carries that run's stages
15982
- // and the addresses it has found so far, both of which land as the scan
15983
- // goes \u2014 a panel somebody opened to watch their own scan cannot be a
15984
- // snapshot of the moment they opened it. Bounded twice over: only while the
15985
- // panel is open, and only while something is actually running.
15986
- const running = !!s.scanProgress;
15987
- if ($("histWrap").open && (running || (wasRunning && !running))) loadRuns();
15977
+ // The detail used to decide "is this run live" from a boolean this paint
15978
+ // had set on a previous tick, and then say "the collector stopped before it
15979
+ // finished" on the strength of it \u2014 a strong claim about somebody's
15980
+ // machine, made from a stale client-side variable. The server knows: the
15981
+ // daemon and this console are one process, and the progress store IS the
15982
+ // running job. Matched by jobId below, so the claim is about the run being
15983
+ // looked at rather than about whatever was running most recently.
15984
+ liveProgress = s.scanProgress || null;
15985
+ renderLive(liveProgress);
15986
+
15987
+ const running = !!liveProgress;
15988
+ if ($("histWrap").open) {
15989
+ const now = Date.now();
15990
+ if (wasRunning && !running) {
15991
+ // The run just ended: the ledger gained its verdict, its device list and
15992
+ // its receipt all at once, and this is the moment somebody reads them.
15993
+ loadRuns();
15994
+ } else if (running) {
15995
+ // A LIVE run is repainted from the store above rather than re-read from
15996
+ // disk, because the two carry different things and only one of them
15997
+ // moves. The ledger records a phase when it COMPLETES \u2014 that is what
15998
+ // makes it a ledger and not a transcript \u2014 so bars built from it jump
15999
+ // from nothing to done and an operator watching a sweep sees a frozen
16000
+ // panel. The live store has every report. Repainting is free; the
16001
+ // re-read is not, so it happens on a slow beat to pick up the rest.
16002
+ if (now - lastRunsAt > 15000) loadRuns();
16003
+ else renderRuns(lastRuns);
16004
+ }
16005
+ }
15988
16006
  wasRunning = running;
15989
16007
  $("lastPollAt").textContent = fmt(s.daemon && s.daemon.lastPollAt);
15990
16008
  $("lastPushAt").textContent = fmt(s.daemon && s.daemon.lastPushAt);
@@ -16638,6 +16656,20 @@ ${tokenCss()}
16638
16656
 
16639
16657
  /** Was a scan running the last time we painted? Used to refresh history. */
16640
16658
  let wasRunning = false;
16659
+ /** The running job as the SERVER reports it, or null. The liveness authority. */
16660
+ let liveProgress = null;
16661
+ /** The last fold read from the ledger, repainted between reads. */
16662
+ let lastRuns = [];
16663
+ /** When that read happened, so a live repaint does not re-read the file. */
16664
+ let lastRunsAt = 0;
16665
+ /**
16666
+ * Which runs the operator has expanded, by start time.
16667
+ *
16668
+ * A live run repaints every poll, and a repaint that rebuilt the list would
16669
+ * shut the panel somebody is reading three seconds after they opened it \u2014
16670
+ * which is precisely the panel this whole feature exists to keep open.
16671
+ */
16672
+ const openRuns = new Set();
16641
16673
 
16642
16674
  /**
16643
16675
  * The last runs, folded from the ledger by the server.
@@ -16653,6 +16685,7 @@ ${tokenCss()}
16653
16685
  const res = await fetch("/ui/api/runs", { cache: "no-store" });
16654
16686
  if (res.status === 401) { showLogin(); return; }
16655
16687
  const data = await res.json().catch(() => ({ runs: [] }));
16688
+ lastRunsAt = Date.now();
16656
16689
  renderRuns(data.runs || []);
16657
16690
  $("histNote").textContent = "";
16658
16691
  } catch (e) {
@@ -16662,6 +16695,7 @@ ${tokenCss()}
16662
16695
  }
16663
16696
 
16664
16697
  function renderRuns(runs) {
16698
+ lastRuns = runs;
16665
16699
  const host = $("histBody");
16666
16700
  host.innerHTML = "";
16667
16701
  if (!runs.length) {
@@ -16671,16 +16705,33 @@ ${tokenCss()}
16671
16705
  }
16672
16706
  host.className = "";
16673
16707
  for (const run of runs) {
16708
+ // IS THIS RUN THE ONE RUNNING? Answered by the server's own record of
16709
+ // the job it is executing, not by a flag about whatever ran most
16710
+ // recently. A run with no job id is the one-shot CLI path, which cannot be
16711
+ // the daemon's live job and so is matched on nothing.
16712
+ const live = run.outcome === "running" && !!liveProgress
16713
+ && (run.jobId ? liveProgress.jobId === run.jobId : false);
16714
+ const state = run.outcome === "running" && !live ? "stopped" : run.outcome;
16715
+
16716
+ // A LIVE run's stages come from the progress store, which reports every
16717
+ // tick; the ledger only records a phase once it has finished, so bars
16718
+ // folded from it cannot move while the work is happening.
16719
+ const phases = live && liveProgress.phases.length
16720
+ ? liveProgress.phases.map((ph) => ({
16721
+ phase: ph.phase, done: ph.done, total: ph.total, note: ph.message,
16722
+ }))
16723
+ : (run.phases || []);
16724
+
16674
16725
  const row = el("details", { class: "runrow" });
16726
+ // Reopened across repaints \u2014 see openRuns.
16727
+ if (openRuns.has(run.startedAt)) row.setAttribute("open", "open");
16728
+ row.addEventListener("toggle", () => {
16729
+ if (row.open) openRuns.add(run.startedAt);
16730
+ else openRuns.delete(run.startedAt);
16731
+ });
16675
16732
  const sum = el("summary");
16676
16733
  sum.appendChild(el("span", { class: "when", text: fmt(run.startedAt) }));
16677
16734
  sum.appendChild(el("span", { text: run.mode }));
16678
- // A run the LEDGER calls running is only reported as running if a scan is
16679
- // actually in flight. The ledger cannot tell a live daemon from one killed
16680
- // between two polls; the status poll can, and it is the half of the answer
16681
- // this side of the page holds.
16682
- const live = run.outcome === "running" && wasRunning;
16683
- const state = run.outcome === "running" && !live ? "stopped" : run.outcome;
16684
16735
  sum.appendChild(el("span", { class: "outcome-" + (live ? "running" : state), text:
16685
16736
  state === "finished" ? "finished"
16686
16737
  : state === "failed" ? "failed"
@@ -16710,7 +16761,7 @@ ${tokenCss()}
16710
16761
  // The last stage the run reported is what it is DOING, which is the
16711
16762
  // sentence somebody watching their own scan is looking for. A finished run
16712
16763
  // has no use for it; a live one has nothing else.
16713
- const latest = run.phases && run.phases.length ? run.phases[run.phases.length - 1] : null;
16764
+ const latest = phases.length ? phases[phases.length - 1] : null;
16714
16765
  block("Current stage", [
16715
16766
  el("div", {
16716
16767
  class: state === "failed" ? "bad" : state === "incomplete" || state === "stopped" ? "warntext" : "",
@@ -16729,9 +16780,9 @@ ${tokenCss()}
16729
16780
  // \u2500\u2500 STAGES, AS BARS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
16730
16781
  // The same bars the live panel draws, so a finished run and a running one
16731
16782
  // look like the same thing at two moments.
16732
- if (run.phases && run.phases.length) {
16783
+ if (phases.length) {
16733
16784
  const nodes = [];
16734
- for (const ph of run.phases) {
16785
+ for (const ph of phases) {
16735
16786
  for (const n of phaseBar(ph.phase, ph.done, ph.total, ph.note)) nodes.push(n);
16736
16787
  }
16737
16788
  block("Stages", nodes);