// file: crates/ksp-app-store-desk/frontend/ts/main.ts // version: 3 import DataTable from "datatables.net-bs5"; import "datatables.net-bs5/css/dataTables.bootstrap5.css"; import "bootstrap"; import ResizeObserver from "resize-observer-polyfill"; import "simplebar"; import type { ShellStatusDto } from "./bindings/ksp_app_store_desk/dto_common/ShellStatusDto.ts"; import type { StoreRuntimeStatusDto } from "./bindings/ksp_app_store_desk/dto_common/StoreRuntimeStatusDto.ts"; import { frontendDebug, frontendError, frontendInfo, frontendTrace, installFrontendConsoleBridge } from "./frontend_log"; import { invokeKsp } from "./invoke"; (window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; installFrontendConsoleBridge("main"); type ViewId = "overview" | "transactions" | "accounts" | "diagnostics"; const viewTitles: Record = { overview: "Overview", transactions: "RAW Transactions", accounts: "RAW Accounts", diagnostics: "Diagnostics", }; interface EmptyDataTable { columns: { adjust(): void }; } let transactionTable: EmptyDataTable | null = null; let accountTable: EmptyDataTable | null = null; function isViewId(value: string | undefined): value is ViewId { return value === "overview" || value === "transactions" || value === "accounts" || value === "diagnostics"; } function setText(elementId: string, value: string): void { const element = document.querySelector(`#${elementId}`); if (element) { element.textContent = value; } } function renderDiagnostic(elementId: string, diagnostic: { domain: string; code: string; message: string } | null): void { const element = document.querySelector(`#${elementId}`); if (!element) { return; } element.hidden = diagnostic === null; element.textContent = diagnostic ? `${diagnostic.domain}/${diagnostic.code}: ${diagnostic.message}` : ""; } function activateView(viewId: ViewId): void { document.querySelectorAll("[data-view-panel]").forEach(panel => { panel.hidden = panel.dataset.viewPanel !== viewId; }); document.querySelectorAll("[data-view]").forEach(button => { button.classList.toggle("active", button.dataset.view === viewId); }); const title = document.querySelector("#headerViewTitle"); if (title) { title.textContent = viewTitles[viewId]; } document.title = `Store Desk — ${viewTitles[viewId]}`; if (viewId === "transactions") { transactionTable?.columns.adjust(); } if (viewId === "accounts") { accountTable?.columns.adjust(); } frontendDebug("main", "Store Desk view activated", { viewId }); } function initializeEmptyTables(): void { const commonOptions = { data: [], paging: true, serverSide: false, pageLength: 25, lengthMenu: [25, 50, 100], searching: false, ordering: false, info: true, scrollX: true, autoWidth: false, language: { emptyTable: "Aucune donnée RAW n'est chargée dans la tranche pre.006.", }, }; transactionTable = new DataTable("#rawTransactionsTable", commonOptions) as unknown as EmptyDataTable; accountTable = new DataTable("#rawAccountsTable", commonOptions) as unknown as EmptyDataTable; frontendTrace("main", "Store Desk empty DataTables skeletons initialized", { pagingOwner: "datatables", serverSide: false }); } function renderShellStatus(status: ShellStatusDto): void { setText("appVersionBadge", status.applicationVersion); setText("runtimeVersion", status.applicationVersion); setText("runtimeShellPhase", status.shellPhase); setText("runtimeConfigDocuments", status.configDocumentCount.toString()); setText("runtimeLoggingProfile", status.activeLoggingProfile ?? "fallback"); setText("runtimeLoggingFallback", status.fallbackLoggingActive ? "oui" : "non"); renderDiagnostic("runtimeDiagnostic", status.startupDiagnostic); frontendTrace("main", "Store Desk shell status rendered", { fallbackLoggingActive: status.fallbackLoggingActive, shellPhase: status.shellPhase, }); } function renderStoreRuntimeStatus(status: StoreRuntimeStatusDto): void { const target = status.backendKind && status.network ? `${status.backendKind} / ${status.network}` : status.network ?? status.backendKind ?? "indisponible"; const migration = status.migrationVersionDecimal === null ? `inconnue · ${status.pendingMigrationCount} pending` : `v${status.migrationVersionDecimal} · ${status.pendingMigrationCount} pending`; const pool = `${status.poolAvailable}/${status.poolSize}/${status.poolCapacity} · wait ${status.poolWaiting}`; const profile = status.profileId ?? "indisponible"; setText("overviewStoreProfile", profile); setText("overviewStoreTarget", target); setText("overviewStoreHealth", status.healthState); setText("overviewStorePool", pool); setText("overviewStoreMigration", migration); renderDiagnostic("overviewStoreDiagnostic", status.diagnostic); setText("runtimeStoreProfile", profile); setText("runtimeStoreTarget", target); setText("runtimeStoreHealth", status.healthState); setText("runtimeStoreMigration", migration); setText("runtimeStorePool", pool); renderDiagnostic("runtimeStoreDiagnostic", status.diagnostic); frontendTrace("main", "Store Desk Store runtime status rendered", { healthState: status.healthState, pendingMigrationCount: status.pendingMigrationCount, poolCapacity: status.poolCapacity, poolSize: status.poolSize, storeOpen: status.storeOpen, }); } async function refreshStoreRuntime(): Promise { frontendDebug("main", "Store Desk Store runtime refresh started"); try { const status = await invokeKsp("main", "store_runtime_status"); renderStoreRuntimeStatus(status); frontendDebug("main", "Store Desk Store runtime refresh completed", { healthState: status.healthState, storeOpen: status.storeOpen }); } catch { frontendError("main", "Store Desk Store runtime refresh failed"); } } async function refreshDiagnostics(): Promise { frontendDebug("main", "Store Desk diagnostics refresh started"); try { const status = await invokeKsp("main", "get_shell_status"); renderShellStatus(status); await refreshStoreRuntime(); frontendDebug("main", "Store Desk diagnostics refresh completed"); } catch { frontendError("main", "Store Desk diagnostics refresh failed"); } } function installInteractions(): void { document.querySelectorAll("[data-view]").forEach(button => { button.addEventListener("click", () => { const viewId = button.dataset.view; frontendDebug("main", "Store Desk navigation button clicked", { viewId: viewId ?? "missing" }); if (isViewId(viewId)) { activateView(viewId); } }); }); const refreshOverview = document.querySelector("#refreshOverview"); refreshOverview?.addEventListener("click", () => { frontendDebug("main", "Store Desk Overview refresh button clicked"); void refreshStoreRuntime(); }); const refreshButton = document.querySelector("#refreshDiagnostics"); refreshButton?.addEventListener("click", () => { frontendDebug("main", "Store Desk diagnostics refresh button clicked"); void refreshDiagnostics(); }); document.addEventListener("click", event => { const eventTarget = event.target; if (!(eventTarget instanceof Element)) { return; } const control = eventTarget.closest("button, [role='tab'], [data-bs-toggle='tab']"); if (!control) { return; } if (control instanceof HTMLButtonElement && (control.dataset.view || control.id === "refreshDiagnostics" || control.id === "refreshOverview")) { return; } const controlId = control.id || control.getAttribute("data-bs-target") || control.getAttribute("aria-controls") || "anonymous"; if (control.matches("[role='tab'], [data-bs-toggle='tab']")) { frontendDebug("main", "Store Desk tab control clicked", { controlId }); return; } frontendDebug("main", "Store Desk generic button clicked", { buttonId: controlId }); }); document.addEventListener("shown.bs.tab", event => { const target = event.target; const tabId = target instanceof HTMLElement ? target.id || target.getAttribute("data-bs-target") || target.textContent?.trim() || "anonymous" : "unknown"; frontendDebug("main", "Store Desk tab activated", { tabId }); }); document.addEventListener("change", event => { const target = event.target; if (!(target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement)) { return; } frontendDebug("main", "Store Desk interactive control changed", { controlId: target.id || target.getAttribute("name") || "anonymous", controlType: target instanceof HTMLSelectElement ? "select" : target.type || "text", }); }); frontendTrace("main", "Store Desk frontend interactions installed", { buttons: true, controls: true, tabs: true }); } async function initializeMain(): Promise { frontendInfo("main", "Store Desk main frontend loaded"); initializeEmptyTables(); installInteractions(); activateView("overview"); await refreshDiagnostics(); } document.addEventListener("DOMContentLoaded", () => { void initializeMain(); });