192 lines
9.5 KiB
TypeScript
192 lines
9.5 KiB
TypeScript
// file: kb-app-demo-desktop/frontend/ts/demo_core_extraction.ts
|
|
// version: 7
|
|
|
|
import * as bootstrap from "bootstrap";
|
|
import "simplebar";
|
|
import ResizeObserver from "resize-observer-polyfill";
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import { listen } from "@tauri-apps/api/event";
|
|
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log.ts";
|
|
import type { DemoCoreExtractionOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionOptionsPayload.ts";
|
|
import type { DemoCoreExtractionProgressPayload } from "./bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionProgressPayload.ts";
|
|
import type { DemoCoreExtractionRequest } from "./bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionRequest.ts";
|
|
import type { DemoCoreExtractionSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionSummaryPayload.ts";
|
|
|
|
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
|
|
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
|
|
|
const logLines: string[] = [];
|
|
const maximumLogLines = 1000;
|
|
let running = false;
|
|
|
|
function element<T extends HTMLElement>(selector: string): T {
|
|
const value = document.querySelector<T>(selector);
|
|
if (!value) {
|
|
throw new Error(`Missing UI element: ${selector}`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function inputValue(selector: string): string {
|
|
return element<HTMLInputElement | HTMLTextAreaElement>(selector).value.trim();
|
|
}
|
|
|
|
function integerValue(selector: string): number {
|
|
const parsed = Number.parseInt(inputValue(selector), 10);
|
|
if (!Number.isFinite(parsed)) {
|
|
throw new Error(`Valeur numérique invalide pour ${selector}`);
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
function nullableIntegerValue(selector: string): number | null {
|
|
const value = inputValue(selector);
|
|
if (value.length === 0) {
|
|
return null;
|
|
}
|
|
const parsed = Number.parseInt(value, 10);
|
|
if (!Number.isFinite(parsed)) {
|
|
throw new Error(`Valeur numérique invalide pour ${selector}`);
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
function appendLog(payload: DemoCoreExtractionProgressPayload | { timestamp: string; level: string; message: string }): void {
|
|
const progress = "completed" in payload ? ` [${payload.completed}/${payload.total}]` : "";
|
|
logLines.push(`${payload.timestamp} ${payload.level.toUpperCase()}${progress} ${payload.message}`);
|
|
while (logLines.length > maximumLogLines) {
|
|
logLines.shift();
|
|
}
|
|
const output = element<HTMLTextAreaElement>("#coreExtractionLogOutput");
|
|
output.value = logLines.join("\n");
|
|
output.scrollTop = output.scrollHeight;
|
|
}
|
|
|
|
function setRunning(value: boolean): void {
|
|
running = value;
|
|
document.querySelectorAll<HTMLButtonElement>(".core-extraction-start-button").forEach(button => {
|
|
button.disabled = value;
|
|
});
|
|
element<HTMLButtonElement>("#cancelCoreExtractionButton").disabled = !value;
|
|
const badge = element<HTMLElement>("#coreExtractionStatusBadge");
|
|
badge.textContent = value ? "Extraction en cours" : "Prêt";
|
|
badge.className = value ? "badge text-bg-warning" : "badge text-bg-success";
|
|
}
|
|
|
|
function buildRequest(mode: string): DemoCoreExtractionRequest {
|
|
return {
|
|
mode,
|
|
signaturesText: mode === "signatures" ? element<HTMLTextAreaElement>("#coreExtractionSignaturesTextarea").value : null,
|
|
programId: mode === "program_id" ? inputValue("#coreExtractionProgramIdInput") : null,
|
|
minSlot: mode === "slot_range" ? nullableIntegerValue("#coreExtractionMinSlotInput") : null,
|
|
maxSlot: mode === "slot_range" ? nullableIntegerValue("#coreExtractionMaxSlotInput") : null,
|
|
limit: integerValue("#coreExtractionLimitInput"),
|
|
maxConcurrentExtractions: integerValue("#coreExtractionConcurrencyInput"),
|
|
forceReplay: element<HTMLInputElement>("#coreExtractionForceReplayInput").checked,
|
|
};
|
|
}
|
|
|
|
async function executeCoreExtraction(mode: string): Promise<void> {
|
|
if (running) {
|
|
return;
|
|
}
|
|
try {
|
|
const request = buildRequest(mode);
|
|
setRunning(true);
|
|
element<HTMLTextAreaElement>("#coreExtractionSummaryOutput").value = "Exécution en cours...";
|
|
appendLog({ timestamp: new Date().toISOString(), level: "info", message: `Démarrage du mode ${mode}` });
|
|
const summary = await invoke<DemoCoreExtractionSummaryPayload>("demo_core_extraction_execute", { request });
|
|
element<HTMLTextAreaElement>("#coreExtractionSummaryOutput").value = JSON.stringify(summary, null, 2);
|
|
appendLog({
|
|
timestamp: new Date().toISOString(),
|
|
level: summary.cancelled || Number(summary.failed) > 0 ? "warn" : "info",
|
|
message: `Campagne terminée : extracted=${summary.extracted}, skipped=${summary.skipped}, failed=${summary.failed}, cancelled=${summary.cancelledCandidates}, notStarted=${summary.notStarted}`,
|
|
});
|
|
} catch (caughtError) {
|
|
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
|
element<HTMLTextAreaElement>("#coreExtractionSummaryOutput").value = JSON.stringify({ error: message }, null, 2);
|
|
appendLog({ timestamp: new Date().toISOString(), level: "error", message });
|
|
frontendError("kb_app_demo_desktop.frontend.demo_core_extraction", `Core extraction failed: ${message}`);
|
|
} finally {
|
|
setRunning(false);
|
|
}
|
|
}
|
|
|
|
async function cancelCoreExtraction(): Promise<void> {
|
|
try {
|
|
const accepted = await invoke<boolean>("demo_core_extraction_cancel");
|
|
appendLog({
|
|
timestamp: new Date().toISOString(),
|
|
level: accepted ? "warn" : "info",
|
|
message: accepted ? "Demande d'arrêt envoyée." : "Aucune campagne active.",
|
|
});
|
|
} catch (caughtError) {
|
|
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
|
frontendError("kb_app_demo_desktop.frontend.demo_core_extraction", `Cancellation failed: ${message}`);
|
|
}
|
|
}
|
|
|
|
|
|
async function openDiagnostics(command: "open_demo_store_raw_window" | "open_demo_store_core_window" | "open_demo_store_replay_candidates_window", label: string): Promise<void> {
|
|
try {
|
|
await invoke(command);
|
|
appendLog({ timestamp: new Date().toISOString(), level: "info", message: `${label} ouvert.` });
|
|
} catch (caughtError) {
|
|
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
|
appendLog({ timestamp: new Date().toISOString(), level: "error", message });
|
|
frontendError("kb_app_demo_desktop.frontend.demo_core_extraction", `${label} opening failed: ${message}`);
|
|
}
|
|
}
|
|
|
|
async function loadOptions(): Promise<void> {
|
|
const options = await invoke<DemoCoreExtractionOptionsPayload>("demo_core_extraction_options");
|
|
element<HTMLElement>("#coreExtractionVersionBadge").textContent = `Version ${options.processorVersion}`;
|
|
element<HTMLInputElement>("#coreExtractionLimitInput").value = String(options.defaultLimit);
|
|
element<HTMLInputElement>("#coreExtractionConcurrencyInput").value = String(options.defaultMaxConcurrentExtractions);
|
|
const programOptions = element<HTMLDataListElement>("#coreExtractionProgramIdOptions");
|
|
programOptions.replaceChildren(...options.programs.map(program => {
|
|
const option = document.createElement("option");
|
|
option.value = program.programId;
|
|
option.label = program.code;
|
|
return option;
|
|
}));
|
|
setRunning(options.running);
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_core_extraction");
|
|
frontendDebug("kb_app_demo_desktop.frontend.demo_core_extraction", "core extraction demo window loaded");
|
|
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(item => new bootstrap.Tooltip(item));
|
|
document.querySelectorAll<HTMLButtonElement>(".core-extraction-start-button").forEach(button => {
|
|
button.addEventListener("click", () => {
|
|
const mode = button.dataset.coreExtractionMode;
|
|
if (mode) {
|
|
void executeCoreExtraction(mode);
|
|
}
|
|
});
|
|
});
|
|
element<HTMLButtonElement>("#openRawDiagnosticsButton").addEventListener("click", () => {
|
|
void openDiagnostics("open_demo_store_raw_window", "Diagnostic raw");
|
|
});
|
|
element<HTMLButtonElement>("#openCoreDiagnosticsButton").addEventListener("click", () => {
|
|
void openDiagnostics("open_demo_store_core_window", "Diagnostic core");
|
|
});
|
|
element<HTMLButtonElement>("#openReplayCandidatesButton").addEventListener("click", () => {
|
|
void openDiagnostics("open_demo_store_replay_candidates_window", "Sélecteur de candidats replay");
|
|
});
|
|
element<HTMLButtonElement>("#cancelCoreExtractionButton").addEventListener("click", () => {
|
|
void cancelCoreExtraction();
|
|
});
|
|
element<HTMLTextAreaElement>("#coreExtractionLogOutput").addEventListener("app-log-clear", () => {
|
|
logLines.length = 0;
|
|
});
|
|
void listen<DemoCoreExtractionProgressPayload>("demo-core-extraction-progress", event => {
|
|
appendLog(event.payload);
|
|
});
|
|
void loadOptions().catch(caughtError => {
|
|
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
|
appendLog({ timestamp: new Date().toISOString(), level: "error", message });
|
|
frontendError("kb_app_demo_desktop.frontend.demo_core_extraction", `Options loading failed: ${message}`);
|
|
});
|
|
});
|