// file: crates/ksp-app-backfill-desk/frontend/ts/main.ts // version: 8 import "bootstrap"; import ResizeObserver from "resize-observer-polyfill"; import "simplebar"; import { listen } from "@tauri-apps/api/event"; import { getCurrentWindow } from "@tauri-apps/api/window"; import type { BackfillRunStatusDto } from "./bindings/ksp_app_backfill_desk/backfill_status/BackfillRunStatusDto.ts"; import type { BackfillCancelResponseDto } from "./bindings/ksp_app_backfill_desk/dto_backfill/BackfillCancelResponseDto.ts"; import type { BackfillRequestPreviewDto } from "./bindings/ksp_app_backfill_desk/dto_backfill/BackfillRequestPreviewDto.ts"; import type { BackfillResumeResponseDto } from "./bindings/ksp_app_backfill_desk/dto_backfill/BackfillResumeResponseDto.ts"; import type { BackfillStartRequestDto } from "./bindings/ksp_app_backfill_desk/dto_backfill/BackfillStartRequestDto.ts"; 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"; (window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; installFrontendConsoleBridge("main"); type ViewId = "backfill" | "diagnostics"; const viewTitles: Record = { backfill: "Backfill", diagnostics: "Diagnostics", }; const commitmentLabels: Record = { confirmed: "confirmed", finalized: "finalized", }; const scopeLabels: Record = { latest_address: "Adresse — historique le plus récent", before_address: "Adresse — avant une signature", after_address: "Adresse — après une signature", explicit_signatures: "Signatures explicites", }; let compositionReadyForStart = false; let currentRunStatus: BackfillRunStatusDto | null = null; let runStartAccepted = false; let resumeInFlight = false; function isViewId(value: string | undefined): value is ViewId { return value === "backfill" || value === "diagnostics"; } function activateView(viewId: ViewId, source: "startup" | "user"): void { document.querySelectorAll("[data-view-panel]").forEach(panel => { panel.hidden = panel.dataset.viewPanel !== viewId; }); document.querySelectorAll("[data-view]").forEach(button => { const active = button.dataset.view === viewId; button.classList.toggle("active", active); button.setAttribute("aria-current", active ? "page" : "false"); }); const header = document.querySelector("#headerViewTitle"); if (header) { header.textContent = viewTitles[viewId]; } document.title = `Backfill Desk — ${viewTitles[viewId]}`; frontendTrace("main", "Backfill Desk view DOM updated", { viewId, source }); } function bindFrontendInteractions(): void { document.addEventListener( "click", event => { const source = event.target; if (!(source instanceof Element)) { return; } const control = source.closest('button, a, input, select, textarea, [role="button"], [role="tab"], [data-view]'); if (!control) { return; } frontendTrace("main", "Backfill Desk frontend control clicked", { controlId: control.id || null, role: control.getAttribute("role"), tagName: control.tagName.toLowerCase(), viewId: control.dataset.view ?? null, }); }, true, ); frontendTrace("main", "Backfill Desk frontend control interaction logger installed"); } function bindNavigation(): void { document.querySelectorAll("[data-view]").forEach(button => { button.addEventListener("click", () => { const viewId = button.dataset.view; frontendDebug("main", "Backfill Desk navigation tab clicked", { viewId: viewId ?? "unknown" }); if (isViewId(viewId)) { activateView(viewId, "user"); } }); }); frontendTrace("main", "Backfill Desk navigation handlers installed"); } function populateCodeSelect(selectId: string, values: string[], labels: Record): void { const select = document.querySelector(`#${selectId}`); if (!select) { return; } const previous = select.value; select.replaceChildren(); for (const value of values) { const option = document.createElement("option"); option.value = value; option.textContent = labels[value] ?? value; select.append(option); } select.disabled = values.length === 0; if (values.includes(previous)) { select.value = previous; } } function applyNumberInputContract(inputId: string, maximum: number, defaultValue: number): void { const input = document.querySelector(`#${inputId}`); if (!input) { return; } input.min = "1"; input.max = maximum.toString(); if (input.value === "") { input.value = defaultValue.toString(); } } function refreshStartButton(): void { const startButton = document.querySelector("#startBackfillRequest"); if (startButton) { startButton.disabled = !compositionReadyForStart || runStartAccepted; } } function renderProgramIdAutocomplete(options: ProgramIdAutocompleteOptionDto[]): void { const datalist = document.querySelector("#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("#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("#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); applyNumberInputContract("backfillMaxPages", options.limits.maxPages, options.limits.defaultMaxPages); applyNumberInputContract("backfillMaxCandidates", options.limits.maxCandidates, options.limits.defaultMaxCandidates); applyNumberInputContract("backfillHydrationConcurrency", options.limits.maxHydrationConcurrency, options.limits.defaultHydrationConcurrency); const signatureHelp = document.querySelector("#backfillExplicitSignaturesHelp"); if (signatureHelp) { signatureHelp.textContent = `Une signature par ligne, ${options.limits.minSignatureTextBytes}–${options.limits.maxSignatureTextBytes} octets encodés ; maximum ${options.limits.maxCandidates}.`; } const validateButton = document.querySelector("#validateBackfillRequest"); if (validateButton) { validateButton.disabled = !options.compositionReady || options.httpRoutes.length === 0; } updateCampaignScopeFields(); frontendTrace("main", "Backfill Desk campaign contract rendered", { commitmentCount: options.commitments.length, compositionReady: options.compositionReady, scopeCount: options.scopeKinds.length, }); } function currentScopeKind(): string { const select = document.querySelector("#backfillScopeKind"); return select?.value ?? ""; } function updateCampaignScopeFields(): void { const scopeKind = currentScopeKind(); const addressGroup = document.querySelector("#backfillAddressGroup"); const anchorGroup = document.querySelector("#backfillAnchorGroup"); const signaturesGroup = document.querySelector("#backfillExplicitSignaturesGroup"); const minContextSlotGroup = document.querySelector("#backfillMinContextSlotGroup"); const addressScope = scopeKind === "latest_address" || scopeKind === "before_address" || scopeKind === "after_address"; const anchoredScope = scopeKind === "before_address" || scopeKind === "after_address"; const explicitScope = scopeKind === "explicit_signatures"; if (addressGroup) { addressGroup.hidden = !addressScope; } if (anchorGroup) { anchorGroup.hidden = !anchoredScope; } if (signaturesGroup) { signaturesGroup.hidden = !explicitScope; } if (minContextSlotGroup) { minContextSlotGroup.hidden = explicitScope; } frontendTrace("main", "Backfill Desk campaign scope fields updated", { scopeKind: scopeKind || "none" }); } function invalidateCampaignValidation(source: "input" | "scope" | "route"): void { const status = document.querySelector("#backfillRequestValidation"); if (status) { status.hidden = true; status.textContent = ""; status.classList.remove("alert-success", "alert-danger"); status.classList.add("alert-secondary"); } const summary = document.querySelector("#backfillRequestPreview"); if (summary) { summary.hidden = true; } frontendTrace("main", "Backfill Desk campaign validation invalidated", { source }); } function numericInputValue(inputId: string): number { const input = document.querySelector(`#${inputId}`); if (!input || !Number.isSafeInteger(input.valueAsNumber) || input.valueAsNumber < 0) { return 0; } return input.valueAsNumber; } function rawOptionalInput(inputId: string, enabled: boolean): string | null { if (!enabled) { return null; } const input = document.querySelector(`#${inputId}`); if (!input || input.value === "") { return null; } return input.value; } function explicitSignatureLines(): string[] { const textarea = document.querySelector("#backfillExplicitSignatures"); if (!textarea || currentScopeKind() !== "explicit_signatures") { return []; } return textarea.value.split(/\r?\n/).filter(line => line.length > 0); } function buildBackfillStartRequest(): BackfillStartRequestDto { const scopeKind = currentScopeKind(); const role = document.querySelector("#backfillHttpRoute")?.value ?? ""; const commitment = document.querySelector("#backfillCommitment")?.value ?? ""; const addressScope = scopeKind === "latest_address" || scopeKind === "before_address" || scopeKind === "after_address"; const anchoredScope = scopeKind === "before_address" || scopeKind === "after_address"; const minContextSlotEnabled = scopeKind !== "explicit_signatures"; return { address: rawOptionalInput("backfillAddress", addressScope), anchorSignature: rawOptionalInput("backfillAnchorSignature", anchoredScope), commitment, explicitSignatures: explicitSignatureLines(), hydrationConcurrency: numericInputValue("backfillHydrationConcurrency"), httpRole: role, maxCandidates: numericInputValue("backfillMaxCandidates"), maxPages: numericInputValue("backfillMaxPages"), minContextSlot: rawOptionalInput("backfillMinContextSlot", minContextSlotEnabled), pageSize: numericInputValue("backfillPageSize"), scopeKind, }; } function renderRequestPreview(preview: BackfillRequestPreviewDto): void { const values: Record = { validatedNetwork: preview.network, validatedHttpRole: preview.httpRole, validatedCommitment: preview.commitment, validatedScopeKind: preview.scopeKind, validatedExplicitSignatureCount: preview.explicitSignatureCount.toString(), validatedBounds: `${preview.pageSize} / ${preview.maxPages} / ${preview.maxCandidates} / ${preview.hydrationConcurrency}`, validatedMinContextSlot: preview.minContextSlotPresent ? "présent" : "absent", }; for (const [id, value] of Object.entries(values)) { const element = document.querySelector(`#${id}`); if (element) { element.textContent = value; } } const summary = document.querySelector("#backfillRequestPreview"); if (summary) { summary.hidden = false; } const status = document.querySelector("#backfillRequestValidation"); if (status) { status.hidden = false; status.classList.remove("alert-secondary", "alert-danger"); status.classList.add("alert-success"); status.textContent = "Requête valide. Aucun job n'a été démarré."; } frontendTrace("main", "Backfill Desk validated request preview rendered", { commitment: preview.commitment, httpRole: preview.httpRole, scopeKind: preview.scopeKind, }); } function renderRequestValidationError(caughtError: unknown): void { const status = document.querySelector("#backfillRequestValidation"); if (!status) { return; } 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"); status.textContent = message; } async function startBackfillRequest(): Promise { const request = buildBackfillStartRequest(); frontendDebug("main", "Backfill Desk campaign Start requested", { commitment: request.commitment, httpRole: request.httpRole, scopeKind: request.scopeKind, }); try { const started = await invokeKsp("main", "backfill_start", { request }); runStartAccepted = true; refreshStartButton(); const status = document.querySelector("#backfillRequestValidation"); if (status) { status.hidden = false; status.classList.remove("alert-secondary", "alert-danger"); status.classList.add("alert-success"); status.textContent = `Job ${started.jobId} démarré (${started.state}).`; } await syncBackfillStatus("start"); frontendInfo("main", "Backfill Desk campaign Start accepted", { jobId: started.jobId, state: started.state }); } catch (caughtError) { renderRequestValidationError(caughtError); frontendWarn("main", "Backfill Desk campaign Start failed"); } } async function validateBackfillRequest(): Promise { const request = buildBackfillStartRequest(); frontendDebug("main", "Backfill Desk campaign validation requested", { commitment: request.commitment, httpRole: request.httpRole, scopeKind: request.scopeKind, }); try { const preview = await invokeKsp("main", "backfill_validate_request", { request }); renderRequestPreview(preview); frontendDebug("main", "Backfill Desk campaign validation completed", { commitment: preview.commitment, httpRole: preview.httpRole, scopeKind: preview.scopeKind, }); } catch (caughtError) { renderRequestValidationError(caughtError); frontendWarn("main", "Backfill Desk campaign validation failed"); } } function bindCampaignForm(): void { const scope = document.querySelector("#backfillScopeKind"); if (scope) { scope.addEventListener("change", () => { frontendDebug("main", "Backfill Desk campaign scope changed", { scopeKind: scope.value || "none" }); updateCampaignScopeFields(); invalidateCampaignValidation("scope"); }); } const form = document.querySelector("#backfillCampaignForm"); if (form) { form.addEventListener("input", () => invalidateCampaignValidation("input")); form.addEventListener("submit", event => { event.preventDefault(); void validateBackfillRequest(); }); } const address = document.querySelector("#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("#startBackfillRequest"); if (startButton) { startButton.addEventListener("click", () => { void startBackfillRequest(); }); } frontendTrace("main", "Backfill Desk campaign form handlers installed"); } function routeLabel(route: BackfillHttpRouteOptionDto): string { if (route.pooled) { return `Pool multi-provider — ${route.providers.join(" + ")}`; } return route.providers.length === 1 ? `Provider — ${route.providers[0]}` : route.role; } function renderBackfillOptions(options: BackfillDeskOptionsDto): void { compositionReadyForStart = options.compositionReady; renderCampaignContract(options); refreshStartButton(); const select = document.querySelector("#backfillHttpRoute"); if (select) { const previous = select.value; select.replaceChildren(); for (const route of options.httpRoutes) { const option = document.createElement("option"); option.value = route.role; option.textContent = routeLabel(route); select.append(option); } select.disabled = !options.transportReady || options.httpRoutes.length === 0; const reusable = options.httpRoutes.some(route => route.role === previous); if (reusable) { select.value = previous; } } const values: Record = { backfillNetwork: options.configuredNetworks.length === 1 ? options.configuredNetworks[0] : options.configuredNetworks.join(", ") || "indisponible", backfillTransportReady: options.transportReady ? "ready" : "non ready", backfillStoreReady: options.storeReady ? "ready" : "non ready", backfillCompositionReady: options.compositionReady ? "ready" : "non ready", }; for (const [id, value] of Object.entries(values)) { const element = document.querySelector(`#${id}`); if (element) { element.textContent = value; } } const diagnostic = document.querySelector("#backfillOptionsDiagnostic"); if (diagnostic) { const error = options.transportDiagnostic ?? options.storeDiagnostic; diagnostic.hidden = error === null; diagnostic.textContent = error === null ? "" : `${error.domain}/${error.code}: ${error.message}`; } frontendTrace("main", "Backfill Desk HTTP route options rendered", { compositionReady: options.compositionReady, routeCount: options.httpRoutes.length, transportReady: options.transportReady, }); } async function loadBackfillOptions(source: "startup" | "user"): Promise { frontendDebug("main", "Backfill Desk HTTP route options load started", { source }); const options = await invokeKsp("main", "backfill_options"); renderBackfillOptions(options); frontendDebug("main", "Backfill Desk HTTP route options load completed", { source, routeCount: options.httpRoutes.length }); } function bindBackfillRouteSelection(): void { const select = document.querySelector("#backfillHttpRoute"); if (!select) { return; } select.addEventListener("change", () => { frontendDebug("main", "Backfill Desk HTTP route selection changed", { role: select.value || "none" }); invalidateCampaignValidation("route"); }); frontendTrace("main", "Backfill Desk HTTP route selection handler installed"); } function renderBackfillRunStatus(status: BackfillRunStatusDto | null, source: "cancel" | "event" | "resume" | "startup" | "start" | "user"): void { const card = document.querySelector("#backfillRunStatusCard"); const cancelButton = document.querySelector("#cancelBackfillRun"); const resumeButton = document.querySelector("#resumeBackfillRun"); currentRunStatus = status; if (!status) { runStartAccepted = false; if (cancelButton) { cancelButton.disabled = true; } if (resumeButton) { resumeButton.disabled = true; } resumeInFlight = false; refreshStartButton(); if (card) { card.hidden = true; } const terminalSummary = document.querySelector("#backfillTerminalSummary"); if (terminalSummary) { terminalSummary.hidden = true; terminalSummary.textContent = ""; } frontendTrace("main", "Backfill Desk latest-value status cleared", { source }); return; } runStartAccepted = status.active; if (cancelButton) { cancelButton.disabled = !status.active; } if (status.active) { resumeInFlight = false; } if (resumeButton) { resumeButton.disabled = !status.terminal || !status.checkpointPresent || resumeInFlight; } refreshStartButton(); if (card) { card.hidden = false; } const terminalSummary = document.querySelector("#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 = { runStatusJobId: status.jobId, runStatusLifecycle: status.completion === null ? status.state : `${status.state} / ${status.completion}`, runStatusPhase: status.phase, runStatusSequence: status.sequence, runStatusScope: status.scopeKind, runStatusBoundary: status.discoveryBoundary ?? "—", runStatusCandidates: `${status.candidatesSelected} / ${status.candidatesAdmitted} / ${status.candidatesFinished}`, runStatusFrontier: `${status.contiguousCompleted} (holes ${status.holes})`, runStatusEntities: `${status.entitiesInserted} / ${status.entitiesExisting} / ${status.entitiesPurged}`, runStatusObservations: `${status.observationsInserted} / ${status.observationsExisting}`, runStatusAnomalies: `missing ${status.missing} / conflits ${status.conflicts} / annulés ${status.cancelledCandidates}`, runStatusConcurrency: status.maximumInFlight.toString(), runStatusCheckpoint: status.checkpointPresent ? "présent" : "absent", runStatusFailure: status.failureDomain === null || status.failureCode === null ? "—" : `${status.failureDomain}/${status.failureCode}`, }; for (const [id, value] of Object.entries(values)) { const element = document.querySelector(`#${id}`); if (element) { element.textContent = value; } } frontendTrace("main", "Backfill Desk latest-value status rendered", { active: status.active, phase: status.phase, sequence: status.sequence, source, state: status.state, terminal: status.terminal, }); } async function syncBackfillStatus(source: "cancel" | "resume" | "startup" | "start" | "user"): Promise { frontendDebug("main", "Backfill Desk latest-value status resynchronization started", { source }); const status = await invokeKsp("main", "backfill_status"); renderBackfillRunStatus(status, source); frontendDebug("main", "Backfill Desk latest-value status resynchronization completed", { source, statusPresent: status !== null }); } async function resumeBackfillRun(): Promise { const status = currentRunStatus; if (!status || !status.terminal || !status.checkpointPresent || resumeInFlight) { return; } const resumeButton = document.querySelector("#resumeBackfillRun"); resumeInFlight = true; if (resumeButton) { resumeButton.disabled = true; } frontendDebug("main", "Backfill Desk in-session Resume requested", { jobId: status.jobId, state: status.state }); try { const response = await invokeKsp("main", "backfill_resume"); const feedback = document.querySelector("#backfillResumeFeedback"); if (feedback) { feedback.hidden = false; feedback.textContent = `Checkpoint repris dans ${response.jobId} (${response.state}).`; } frontendInfo("main", "Backfill Desk in-session Resume accepted", { accepted: response.accepted, jobId: response.jobId, state: response.state, }); await syncBackfillStatus("resume"); } catch (caughtError) { resumeInFlight = false; if (resumeButton && currentRunStatus?.terminal && currentRunStatus.checkpointPresent) { resumeButton.disabled = false; } const feedback = document.querySelector("#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"); } } async function bindBackfillStatusMonitoring(): Promise { await listen("ksp-backfill-status", event => { renderBackfillRunStatus(event.payload, "event"); }); const button = document.querySelector("#refreshBackfillStatus"); if (button) { button.addEventListener("click", () => { frontendDebug("main", "Backfill Desk latest-value status refresh button clicked"); void syncBackfillStatus("user").catch(() => frontendWarn("main", "Backfill Desk latest-value status refresh failed")); }); } const cancelButton = document.querySelector("#cancelBackfillRun"); if (cancelButton) { cancelButton.addEventListener("click", () => { const status = currentRunStatus; if (!status || !status.active) { return; } cancelButton.disabled = true; frontendDebug("main", "Backfill Desk cooperative cancellation requested", { jobId: status.jobId, state: status.state }); void invokeKsp("main", "backfill_cancel", { jobId: status.jobId }) .then(response => { const feedback = document.querySelector("#backfillCancelFeedback"); if (feedback) { feedback.hidden = false; feedback.textContent = response.accepted ? `Annulation coopérative acceptée pour ${response.jobId}.` : `Aucune nouvelle annulation acceptée pour ${response.jobId} (${response.state}).`; } frontendInfo("main", "Backfill Desk cooperative cancellation response received", { accepted: response.accepted, jobId: response.jobId, state: response.state, }); return syncBackfillStatus("cancel"); }) .catch(caughtError => { const feedback = document.querySelector("#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; } }); }); } const resumeButton = document.querySelector("#resumeBackfillRun"); if (resumeButton) { resumeButton.addEventListener("click", () => { void resumeBackfillRun(); }); } frontendTrace("main", "Backfill Desk latest-value monitoring, cancellation and Resume handlers installed"); } function renderRuntimeStatus(status: ShellStatusDto): void { const values: Record = { runtimeVersion: status.applicationVersion, runtimeShellPhase: status.shellPhase, runtimeConfigDocuments: status.configDocumentCount.toString(), runtimeLoggingProfile: status.activeLoggingProfile ?? "fallback transitoire", runtimeLoggingFallback: status.fallbackLoggingActive ? "oui" : "non", }; for (const [id, value] of Object.entries(values)) { const element = document.querySelector(`#${id}`); if (element) { element.textContent = value; } } const badge = document.querySelector("#appVersionBadge"); if (badge) { badge.textContent = status.applicationVersion; } const diagnostic = document.querySelector("#runtimeDiagnostic"); if (diagnostic) { diagnostic.hidden = status.startupDiagnostic === null; diagnostic.textContent = status.startupDiagnostic === null ? "" : `${status.startupDiagnostic.domain}/${status.startupDiagnostic.code}: ${status.startupDiagnostic.message}`; } frontendTrace("main", "Backfill Desk runtime status rendered", { fallbackLoggingActive: status.fallbackLoggingActive, configDocumentCount: status.configDocumentCount, }); } async function loadRuntimeStatus(source: "startup" | "user"): Promise { frontendDebug("main", "Backfill Desk runtime status load started", { source }); const status = await invokeKsp("main", "get_runtime_status"); renderRuntimeStatus(status); frontendDebug("main", "Backfill Desk runtime status load completed", { source }); } function bindRuntimeStatusRefresh(): void { const button = document.querySelector("#refreshRuntimeStatus"); if (!button) { return; } button.addEventListener("click", () => { frontendDebug("main", "Backfill Desk runtime status refresh button clicked"); void loadRuntimeStatus("user").catch(() => frontendWarn("main", "Backfill Desk runtime status refresh failed")); }); frontendTrace("main", "Backfill Desk runtime status refresh handler installed"); } async function initializeMain(): Promise { const windowLabel = getCurrentWindow().label; frontendInfo("main", "Backfill Desk main frontend loaded", { windowLabel }); bindFrontendInteractions(); bindNavigation(); bindRuntimeStatusRefresh(); bindBackfillRouteSelection(); bindCampaignForm(); await bindBackfillStatusMonitoring(); activateView("backfill", "startup"); try { await Promise.all([loadRuntimeStatus("startup"), loadBackfillOptions("startup"), syncBackfillStatus("startup")]); } catch { frontendWarn("main", "Backfill Desk startup runtime status load failed"); } } document.addEventListener("DOMContentLoaded", () => { void initializeMain(); });