v0.3.7-pre.012
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-backfill-desk/frontend/ts/main.ts
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
import "bootstrap";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
@@ -14,6 +14,7 @@ import type { BackfillStartRequestDto } from "./bindings/ksp_app_backfill_desk/d
|
||||
import type { BackfillStartResponseDto } from "./bindings/ksp_app_backfill_desk/dto_backfill/BackfillStartResponseDto.ts";
|
||||
import type { BackfillDeskOptionsDto } from "./bindings/ksp_app_backfill_desk/dto_common/BackfillDeskOptionsDto.ts";
|
||||
import type { BackfillHttpRouteOptionDto } from "./bindings/ksp_app_backfill_desk/dto_common/BackfillHttpRouteOptionDto.ts";
|
||||
import type { ProgramIdAutocompleteOptionDto } from "./bindings/ksp_app_backfill_desk/dto_common/ProgramIdAutocompleteOptionDto.ts";
|
||||
import type { ShellStatusDto } from "./bindings/ksp_app_backfill_desk/dto_common/ShellStatusDto.ts";
|
||||
import { frontendDebug, frontendInfo, frontendTrace, frontendWarn, installFrontendConsoleBridge } from "./frontend_log";
|
||||
import { invokeKsp } from "./invoke";
|
||||
@@ -141,7 +142,56 @@ function refreshStartButton(): void {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function renderProgramIdAutocomplete(options: ProgramIdAutocompleteOptionDto[]): void {
|
||||
const datalist = document.querySelector<HTMLDataListElement>("#backfillProgramIds");
|
||||
if (!datalist) {
|
||||
return;
|
||||
}
|
||||
datalist.replaceChildren();
|
||||
for (const program of options) {
|
||||
const option = document.createElement("option");
|
||||
option.value = program.programId;
|
||||
option.label = `${program.name} — ${program.code}`;
|
||||
option.dataset.code = program.code;
|
||||
option.dataset.domain = program.domain;
|
||||
option.dataset.family = program.family;
|
||||
option.dataset.name = program.name;
|
||||
option.dataset.protocol = program.protocol;
|
||||
datalist.append(option);
|
||||
}
|
||||
const help = document.querySelector<HTMLElement>("#backfillProgramIdAutocompleteHelp");
|
||||
if (help) {
|
||||
help.textContent = `${options.length} Program ID KSP disponibles comme suggestions ; la saisie reste libre.`;
|
||||
}
|
||||
frontendTrace("main", "Backfill Desk Program ID autocomplete dataset rendered", { optionCount: options.length });
|
||||
}
|
||||
|
||||
function selectedProgramAutocompleteEntry(address: string): HTMLOptionElement | null {
|
||||
const datalist = document.querySelector<HTMLDataListElement>("#backfillProgramIds");
|
||||
if (!datalist) {
|
||||
return null;
|
||||
}
|
||||
for (const option of Array.from(datalist.options)) {
|
||||
if (option.value === address) {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function safeCommandErrorText(caughtError: unknown, fallback: string): string {
|
||||
if (typeof caughtError === "object" && caughtError !== null) {
|
||||
const candidate = caughtError as { code?: unknown; domain?: unknown; message?: unknown };
|
||||
if (typeof candidate.domain === "string" && typeof candidate.code === "string" && typeof candidate.message === "string") {
|
||||
return `${candidate.domain}/${candidate.code}: ${candidate.message}`;
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function renderCampaignContract(options: BackfillDeskOptionsDto): void {
|
||||
renderProgramIdAutocomplete(options.programIdOptions);
|
||||
populateCodeSelect("backfillScopeKind", options.scopeKinds, scopeLabels);
|
||||
populateCodeSelect("backfillCommitment", options.commitments, commitmentLabels);
|
||||
applyNumberInputContract("backfillPageSize", options.limits.maxPageSize, options.limits.defaultPageSize);
|
||||
@@ -296,13 +346,7 @@ function renderRequestValidationError(caughtError: unknown): void {
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
let message = "Requête refusée par le backend.";
|
||||
if (typeof caughtError === "object" && caughtError !== null) {
|
||||
const candidate = caughtError as { code?: unknown; domain?: unknown; message?: unknown };
|
||||
if (typeof candidate.domain === "string" && typeof candidate.code === "string" && typeof candidate.message === "string") {
|
||||
message = `${candidate.domain}/${candidate.code}: ${candidate.message}`;
|
||||
}
|
||||
}
|
||||
const message = safeCommandErrorText(caughtError, "Requête refusée par le backend.");
|
||||
status.hidden = false;
|
||||
status.classList.remove("alert-secondary", "alert-success");
|
||||
status.classList.add("alert-danger");
|
||||
@@ -373,6 +417,16 @@ function bindCampaignForm(): void {
|
||||
void validateBackfillRequest();
|
||||
});
|
||||
}
|
||||
const address = document.querySelector<HTMLInputElement>("#backfillAddress");
|
||||
if (address) {
|
||||
address.addEventListener("change", () => {
|
||||
const selected = selectedProgramAutocompleteEntry(address.value);
|
||||
frontendTrace("main", "Backfill Desk address autocomplete selection evaluated", {
|
||||
programCode: selected?.dataset.code ?? null,
|
||||
registryMatch: selected !== null,
|
||||
});
|
||||
});
|
||||
}
|
||||
const startButton = document.querySelector<HTMLButtonElement>("#startBackfillRequest");
|
||||
if (startButton) {
|
||||
startButton.addEventListener("click", () => {
|
||||
@@ -472,6 +526,11 @@ function renderBackfillRunStatus(status: BackfillRunStatusDto | null, source: "c
|
||||
if (card) {
|
||||
card.hidden = true;
|
||||
}
|
||||
const terminalSummary = document.querySelector<HTMLElement>("#backfillTerminalSummary");
|
||||
if (terminalSummary) {
|
||||
terminalSummary.hidden = true;
|
||||
terminalSummary.textContent = "";
|
||||
}
|
||||
frontendTrace("main", "Backfill Desk latest-value status cleared", { source });
|
||||
return;
|
||||
}
|
||||
@@ -489,6 +548,27 @@ function renderBackfillRunStatus(status: BackfillRunStatusDto | null, source: "c
|
||||
if (card) {
|
||||
card.hidden = false;
|
||||
}
|
||||
const terminalSummary = document.querySelector<HTMLElement>("#backfillTerminalSummary");
|
||||
if (terminalSummary) {
|
||||
terminalSummary.hidden = !status.terminal;
|
||||
terminalSummary.classList.remove("alert-success", "alert-warning", "alert-danger", "alert-secondary");
|
||||
if (status.terminal) {
|
||||
const failure = status.failureDomain === null || status.failureCode === null ? null : `${status.failureDomain}/${status.failureCode}`;
|
||||
if (status.state === "completed") {
|
||||
terminalSummary.classList.add("alert-success");
|
||||
terminalSummary.textContent = `Campagne terminée — ${status.contiguousCompleted} candidats contigus durablement traités.`;
|
||||
} else if (status.state === "cancelled") {
|
||||
terminalSummary.classList.add("alert-warning");
|
||||
terminalSummary.textContent = `Campagne annulée coopérativement — checkpoint ${status.checkpointPresent ? "disponible" : "absent"}.`;
|
||||
} else {
|
||||
terminalSummary.classList.add("alert-danger");
|
||||
terminalSummary.textContent = `Campagne terminée en échec${failure === null ? "" : ` — ${failure}`}.`;
|
||||
}
|
||||
} else {
|
||||
terminalSummary.classList.add("alert-secondary");
|
||||
terminalSummary.textContent = "";
|
||||
}
|
||||
}
|
||||
const values: Record<string, string> = {
|
||||
runStatusJobId: status.jobId,
|
||||
runStatusLifecycle: status.completion === null ? status.state : `${status.state} / ${status.completion}`,
|
||||
@@ -552,11 +632,18 @@ async function resumeBackfillRun(): Promise<void> {
|
||||
state: response.state,
|
||||
});
|
||||
await syncBackfillStatus("resume");
|
||||
} catch (_caughtError) {
|
||||
} catch (caughtError) {
|
||||
resumeInFlight = false;
|
||||
if (resumeButton && currentRunStatus?.terminal && currentRunStatus.checkpointPresent) {
|
||||
resumeButton.disabled = false;
|
||||
}
|
||||
const feedback = document.querySelector<HTMLElement>("#backfillResumeFeedback");
|
||||
if (feedback) {
|
||||
feedback.hidden = false;
|
||||
feedback.classList.remove("alert-secondary", "alert-success");
|
||||
feedback.classList.add("alert-danger");
|
||||
feedback.textContent = safeCommandErrorText(caughtError, "La reprise du checkpoint a été refusée.");
|
||||
}
|
||||
frontendWarn("main", "Backfill Desk in-session Resume failed");
|
||||
}
|
||||
}
|
||||
@@ -597,7 +684,14 @@ async function bindBackfillStatusMonitoring(): Promise<void> {
|
||||
});
|
||||
return syncBackfillStatus("cancel");
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(caughtError => {
|
||||
const feedback = document.querySelector<HTMLElement>("#backfillCancelFeedback");
|
||||
if (feedback) {
|
||||
feedback.hidden = false;
|
||||
feedback.classList.remove("alert-secondary", "alert-success");
|
||||
feedback.classList.add("alert-danger");
|
||||
feedback.textContent = safeCommandErrorText(caughtError, "La demande d'annulation a été refusée.");
|
||||
}
|
||||
frontendWarn("main", "Backfill Desk cooperative cancellation failed");
|
||||
if (currentRunStatus?.active) {
|
||||
cancelButton.disabled = false;
|
||||
|
||||
Reference in New Issue
Block a user