diff --git a/scripts/src/hwlab-node-impl.ts b/scripts/src/hwlab-node-impl.ts index 5c24b371..4d1a21a4 100644 --- a/scripts/src/hwlab-node-impl.ts +++ b/scripts/src/hwlab-node-impl.ts @@ -72,6 +72,7 @@ interface NodeWebProbeObserveOptions { viewport: string; sampleIntervalMs: number; screenshotIntervalMs: number; + observerRefreshIntervalMs: number; maxSamples: number; commandTimeoutSeconds: number; waitMs: number; @@ -6900,6 +6901,7 @@ function parseNodeWebProbeObserveOptions( "--viewport", "--sample-interval-ms", "--screenshot-interval-ms", + "--observer-refresh-interval-ms", "--max-samples", "--command-timeout-seconds", "--wait-ms", @@ -6940,9 +6942,10 @@ function parseNodeWebProbeObserveOptions( viewport: optionValue(args, "--viewport") ?? "1440x900", sampleIntervalMs: positiveIntegerOption(args, "--sample-interval-ms", 5000, 600000), screenshotIntervalMs: positiveIntegerOption(args, "--screenshot-interval-ms", 300000, 86_400_000), + observerRefreshIntervalMs: positiveIntegerOption(args, "--observer-refresh-interval-ms", 180000, 86_400_000), maxSamples: positiveIntegerOption(args, "--max-samples", 0, 10_000_000), commandTimeoutSeconds: positiveIntegerOption(args, "--command-timeout-seconds", 55, 3600), - waitMs: positiveIntegerOption(args, "--wait-ms", observeActionRaw === "command" || observeActionRaw === "stop" ? 30000 : 0, 600000), + waitMs: positiveIntegerOption(args, "--wait-ms", 0, 600000), tailLines: positiveIntegerOption(args, "--tail-lines", 5, 200), maxFiles: positiveIntegerOption(args, "--max-files", 80, 5000), collectFile, @@ -7405,6 +7408,20 @@ function runNodeWebProbeObserveStart( const stateDir = options.stateDir ?? defaultStateDir; const runnerB64 = Buffer.from(nodeWebObserveRunnerSource(), "utf8").toString("base64"); const webProbeProxy = nodeWebProbeHostProxyEnv(spec); + const runnerEnvAssignments = [ + ...webProbeProxy.envAssignments, + `HWLAB_WEB_BASE_URL=${shellQuote(options.url)}`, + `HWLAB_WEB_USER=${shellQuote(secretSpec.bootstrapAdminUsername)}`, + `HWLAB_WEB_PASS=${shellQuote(material.password)}`, + `UNIDESK_WEB_OBSERVE_STATE_DIR=${shellQuote(stateDir)}`, + `UNIDESK_WEB_OBSERVE_JOB_ID=${shellQuote(jobId)}`, + `UNIDESK_WEB_OBSERVE_TARGET_PATH=${shellQuote(options.targetPath)}`, + `UNIDESK_WEB_OBSERVE_SAMPLE_INTERVAL_MS=${shellQuote(String(options.sampleIntervalMs))}`, + `UNIDESK_WEB_OBSERVE_SCREENSHOT_INTERVAL_MS=${shellQuote(String(options.screenshotIntervalMs))}`, + `UNIDESK_WEB_OBSERVE_OBSERVER_REFRESH_INTERVAL_MS=${shellQuote(String(options.observerRefreshIntervalMs))}`, + `UNIDESK_WEB_OBSERVE_MAX_SAMPLES=${shellQuote(String(options.maxSamples))}`, + `UNIDESK_WEB_OBSERVE_VIEWPORT=${shellQuote(options.viewport)}`, + ].join(" "); const script = [ "set -eu", `state_dir=${shellQuote(stateDir)}`, @@ -7413,20 +7430,7 @@ function runNodeWebProbeObserveStart( "runner=\"$state_dir/observer-runner.mjs\"", `node -e "require('fs').writeFileSync(process.argv[1], Buffer.from(process.argv[2], 'base64'))" "$runner" ${shellQuote(runnerB64)}`, "chmod 700 \"$runner\"", - [ - ...webProbeProxy.envAssignments, - `HWLAB_WEB_BASE_URL=${shellQuote(options.url)}`, - `HWLAB_WEB_USER=${shellQuote(secretSpec.bootstrapAdminUsername)}`, - `HWLAB_WEB_PASS=${shellQuote(material.password)}`, - `UNIDESK_WEB_OBSERVE_STATE_DIR=${shellQuote(stateDir)}`, - `UNIDESK_WEB_OBSERVE_JOB_ID=${shellQuote(jobId)}`, - `UNIDESK_WEB_OBSERVE_TARGET_PATH=${shellQuote(options.targetPath)}`, - `UNIDESK_WEB_OBSERVE_SAMPLE_INTERVAL_MS=${shellQuote(String(options.sampleIntervalMs))}`, - `UNIDESK_WEB_OBSERVE_SCREENSHOT_INTERVAL_MS=${shellQuote(String(options.screenshotIntervalMs))}`, - `UNIDESK_WEB_OBSERVE_MAX_SAMPLES=${shellQuote(String(options.maxSamples))}`, - `UNIDESK_WEB_OBSERVE_VIEWPORT=${shellQuote(options.viewport)}`, - "nohup node \"$runner\" >\"$state_dir/stdout.log\" 2>\"$state_dir/stderr.log\" /dev/null 2>&1; then setsid env ${runnerEnvAssignments} node "$runner" >"$state_dir/stdout.log" 2>"$state_dir/stderr.log" "$state_dir/stdout.log" 2>"$state_dir/stderr.log" \"$state_dir/pid\"", "sleep 1", @@ -7527,6 +7531,12 @@ function renderWebObserveStatusTable(value: Record): string { const result = record(value.result); const samples = Array.isArray(tails?.samples) ? tails.samples.map(record).filter((item): item is Record => item !== null).slice(-5) : []; const controls = Array.isArray(tails?.control) ? tails.control.map(record).filter((item): item is Record => item !== null).slice(-5) : []; + const completedControlIds = new Set(controls + .filter((item) => item.phase === "completed" || item.phase === "failed") + .map((item) => webObserveText(item.commandId)) + .filter((item) => item !== "-")); + const activeControl = controls.slice().reverse().find((item) => item.phase === "started" && !completedControlIds.has(webObserveText(item.commandId))) ?? null; + const activeControlAgeSeconds = activeControl === null ? null : Math.max(0, Math.round((Date.now() - Date.parse(webObserveText(activeControl.ts))) / 1000)); const network = Array.isArray(tails?.network) ? tails.network.map(record).filter((item): item is Record => item !== null).filter((item) => { const status = typeof item.status === "number" ? item.status : null; @@ -7575,6 +7585,17 @@ function renderWebObserveStatusTable(value: Record): string { ]]), "", ] : []), + ...(activeControl !== null ? [ + "Active command:", + webObserveTable(["TYPE", "COMMAND", "AGE_S", "STARTED_AT", "DETAIL"], [[ + webObserveText(activeControl.type), + webObserveShort(webObserveText(activeControl.commandId), 28), + webObserveText(activeControlAgeSeconds), + webObserveShort(webObserveText(activeControl.ts), 24), + webObserveShort(webObserveText(activeControl.detail), 80), + ]]), + "", + ] : []), "Recent samples:", webObserveTable(["SEQ", "TS", "PATH", "ROUTE_SESSION", "ACTIVE_SESSION", "MSG", "TRACE"], samples.length > 0 ? samples.map((sample) => [ webObserveText(sample.seq),