Files
khadhroony-solana-project/crates/ksp-app-raw-transaction-ingest-desk/frontend/ts/main.ts
2026-09-16 09:07:31 +02:00

716 lines
34 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// file: crates/ksp-app-raw-transaction-ingest-desk/frontend/ts/main.ts
// version: 7
import "bootstrap";
import ResizeObserver from "resize-observer-polyfill";
import "simplebar";
import { listen } from "@tauri-apps/api/event";
import type { ShellStatusDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_common/ShellStatusDto.ts";
import type { RawIngestCommitment } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestCommitment.ts";
import type { RawIngestRouteId } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteId.ts";
import type { RawIngestRouteInventoryDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteInventoryDto.ts";
import type { RawIngestRouteRuntimeDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteRuntimeDto.ts";
import type { RawIngestRouteStartRequestDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteStartRequestDto.ts";
import type { RawIngestRouteStopRequestDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteStopRequestDto.ts";
import type { RawIngestRouteMonitoringDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/route_monitoring/RawIngestRouteMonitoringDto.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 = "routes" | "diagnostics";
const viewTitles: Record<ViewId, string> = {
routes: "Routes",
diagnostics: "Diagnostics",
};
const activeRuntimes = new Map<string, RawIngestRouteRuntimeDto>();
const lastRuntimes = new Map<string, RawIngestRouteRuntimeDto>();
const routeMonitoring = new Map<string, RawIngestRouteMonitoringDto>();
let routeInventory: RawIngestRouteInventoryDto | null = null;
let selectedProfileId: string | null = null;
let selectedMonitoringKey: string | null = null;
function isViewId(value: string | undefined): value is ViewId {
return value === "routes" || value === "diagnostics";
}
function activateView(viewId: ViewId, source: "startup" | "user"): void {
document.querySelectorAll<HTMLElement>("[data-view-panel]").forEach(panel => {
panel.hidden = panel.dataset.viewPanel !== viewId;
});
document.querySelectorAll<HTMLButtonElement>("[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<HTMLElement>("#headerViewTitle");
if (header) {
header.textContent = viewTitles[viewId];
}
document.title = `Raw Transaction Ingest Desk — ${viewTitles[viewId]}`;
frontendTrace("main", "Raw Transaction Ingest 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<HTMLElement>('button, a, input, select, textarea, [role="button"], [role="tab"], [data-view]');
if (!control) {
return;
}
frontendTrace("main", "Raw Transaction Ingest Desk frontend control clicked", {
controlId: control.id || null,
role: control.getAttribute("role"),
tagName: control.tagName.toLowerCase(),
viewId: control.dataset.view ?? null,
});
},
true,
);
}
function bindNavigation(): void {
document.querySelectorAll<HTMLButtonElement>("[data-view]").forEach(button => {
button.addEventListener("click", () => {
const viewId = button.dataset.view;
frontendDebug("main", "Raw Transaction Ingest Desk navigation tab clicked", { viewId: viewId ?? "unknown" });
if (isViewId(viewId)) {
activateView(viewId, "user");
}
});
});
}
function reasonLabel(reason: string | null): string {
const labels: Record<string, string> = {
profile_unresolved: "profil Config non résolu",
network_mismatch: "réseau Transport/Store incohérent",
missing_http_get_block: "HTTP getBlock indisponible",
missing_http_get_transaction: "HTTP getTransaction indisponible",
missing_http_block_scan: "HTTP block scan incomplet",
missing_ws_logs_capability: "capability WS Logs absente",
missing_ws_block_capability: "capability WS Block absente",
missing_helius_transaction_capability: "capability Helius Transaction absente",
missing_yellowstone_grpc: "Yellowstone gRPC absent",
missing_required_secret: "secret Config requis non résolu",
};
if (reason === null) {
return "composable depuis Config";
}
return labels[reason] ?? reason;
}
function runtimeKey(profileId: string, routeId: RawIngestRouteId): string {
return `${profileId}::${routeId}`;
}
function lifecycleBadgeClass(state: string): string {
if (state === "faulted") {
return "badge text-bg-danger";
}
if (state === "running") {
return "badge text-bg-success";
}
if (state === "starting" || state === "stopping") {
return "badge text-bg-primary";
}
if (state === "configured") {
return "badge text-bg-info";
}
return "badge text-bg-secondary";
}
function yesNo(value: boolean): string {
return value ? "oui" : "non";
}
function optionalText(value: string | null): string {
return value ?? "—";
}
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 renderRouteFeedback(message: string, level: "danger" | "info" | "success"): void {
const feedback = document.querySelector<HTMLElement>("#routeCommandFeedback");
if (!feedback) {
return;
}
feedback.hidden = false;
feedback.classList.remove("alert-danger", "alert-info", "alert-success");
feedback.classList.add(`alert-${level}`);
feedback.textContent = message;
}
function clearRouteFeedback(): void {
const feedback = document.querySelector<HTMLElement>("#routeCommandFeedback");
if (!feedback) {
return;
}
feedback.hidden = true;
feedback.textContent = "";
}
function monitoringRuntime(status: RawIngestRouteMonitoringDto): RawIngestRouteRuntimeDto {
return {
commitment: status.commitment,
inventoryGeneration: status.inventoryGeneration,
network: status.network,
profileId: status.profileId,
routeId: status.routeId,
state: status.state,
};
}
function recordRouteMonitoring(status: RawIngestRouteMonitoringDto): void {
const key = runtimeKey(status.profileId, status.routeId);
const runtime = monitoringRuntime(status);
routeMonitoring.set(key, status);
lastRuntimes.set(key, runtime);
if (runtimeIsActive(runtime)) {
activeRuntimes.set(key, runtime);
} else {
activeRuntimes.delete(key);
}
}
function selectRouteMonitoring(profileId: string, routeId: RawIngestRouteId, family: string, source: "event" | "user"): void {
const key = runtimeKey(profileId, routeId);
selectedMonitoringKey = key;
renderMonitoringDetail();
frontendDebug("main", "Raw Transaction Ingest Desk route supervision selected", {
family,
routeId,
selected: routeMonitoring.has(key),
source,
});
}
function renderSelectedProfile(): void {
if (routeInventory === null) {
return;
}
const profile = routeInventory.profiles.find(candidate => candidate.profileId === selectedProfileId) ?? routeInventory.profiles[0];
if (!profile) {
return;
}
selectedProfileId = profile.profileId;
text("#routeNetwork", profile.network ?? "non résolu");
text("#routeSelectedProfile", profile.profileId);
text("#headerProfile", profile.profileId);
const routeRoot = document.querySelector<HTMLElement>("#routeFoundation");
if (routeRoot) {
routeRoot.replaceChildren();
for (const route of profile.routes) {
const key = runtimeKey(profile.profileId, route.routeId);
const runtime = activeRuntimes.get(key) ?? lastRuntimes.get(key) ?? null;
const monitoring = routeMonitoring.get(key) ?? null;
const projectedState = monitoring?.state ?? runtime?.state ?? route.state;
const active = projectedState === "starting" || projectedState === "running" || projectedState === "stopping";
const column = document.createElement("div");
column.className = "col-12 col-xl-6 app-route-column";
const card = document.createElement("div");
card.className = "card h-100 shadow-sm app-route-card";
const body = document.createElement("div");
body.className = "card-body";
const heading = document.createElement("div");
heading.className = "d-flex align-items-start justify-content-between gap-3";
const headingText = document.createElement("div");
const title = document.createElement("h2");
title.className = "h6 mb-1";
title.textContent = route.label;
const code = document.createElement("div");
code.className = "app-route-code text-body-secondary small";
code.textContent = route.routeId;
headingText.append(title, code);
const family = document.createElement("span");
family.className = "badge text-bg-light border text-dark";
family.textContent = route.family;
heading.append(headingText, family);
const meta = document.createElement("div");
meta.className = "d-flex flex-wrap gap-2 mt-3 app-route-meta";
const state = document.createElement("span");
state.className = lifecycleBadgeClass(projectedState);
state.textContent = projectedState;
const network = document.createElement("span");
network.className = "badge text-bg-light border text-dark";
network.textContent = route.network ?? "network unresolved";
const reason = document.createElement("span");
reason.className = route.selectable ? "badge text-bg-light border text-dark" : "badge text-bg-warning";
reason.textContent = reasonLabel(route.reason);
meta.append(state, network, reason);
body.append(heading, meta);
if (monitoring) {
const summary = document.createElement("div");
summary.className = "app-route-monitoring-summary mt-3";
const health = document.createElement("span");
health.className = monitoring.health === "healthy" ? "badge text-bg-success" : "badge text-bg-warning";
health.textContent = `health: ${monitoring.health}`;
const activity = document.createElement("span");
activity.className = "badge text-bg-light border text-dark";
activity.textContent = `activity: ${monitoring.activity}`;
const persisted = document.createElement("span");
persisted.className = "badge text-bg-light border text-dark";
persisted.textContent = `persisted: ${monitoring.persistedTotal}`;
const sources = document.createElement("span");
sources.className = "badge text-bg-light border text-dark";
sources.textContent = `sources: ${monitoring.sourceActive}/${monitoring.sourceTotal}`;
const gaps = document.createElement("span");
gaps.className = monitoring.openGapCount === 0 ? "badge text-bg-light border text-dark" : "badge text-bg-warning";
gaps.textContent = `gaps: ${monitoring.openGapCount}`;
summary.append(health, activity, persisted, sources, gaps);
body.append(summary);
}
const actions = document.createElement("div");
actions.className = "d-flex flex-wrap gap-2 mt-3";
if (route.selectable) {
const start = document.createElement("button");
start.className = "btn btn-primary btn-sm";
start.type = "button";
start.textContent = "Start";
start.disabled = active;
start.dataset.routeId = route.routeId;
start.addEventListener("click", () => {
selectedMonitoringKey = key;
clearRouteFeedback();
void startRoute(route.routeId).catch(caughtError => {
renderRouteFeedback(safeCommandErrorText(caughtError, "Le démarrage de la route a été refusé."), "danger");
frontendWarn("main", "Raw Transaction Ingest Desk route Start failed", { routeId: route.routeId });
});
});
actions.append(start);
if (active && runtime !== null) {
const stop = document.createElement("button");
stop.className = "btn btn-outline-danger btn-sm";
stop.type = "button";
stop.textContent = "Stop";
stop.disabled = projectedState === "stopping";
stop.dataset.routeId = route.routeId;
stop.addEventListener("click", () => {
clearRouteFeedback();
void stopRoute(runtime).catch(caughtError => {
renderRouteFeedback(safeCommandErrorText(caughtError, "L'arrêt de la route a été refusé."), "danger");
frontendWarn("main", "Raw Transaction Ingest Desk route Stop failed", { routeId: route.routeId });
});
});
actions.append(stop);
}
}
if (monitoring) {
const supervise = document.createElement("button");
supervise.className = selectedMonitoringKey === key ? "btn btn-secondary btn-sm" : "btn btn-outline-secondary btn-sm";
supervise.type = "button";
supervise.textContent = "Supervision";
supervise.dataset.routeId = route.routeId;
supervise.addEventListener("click", () => selectRouteMonitoring(profile.profileId, route.routeId, route.family, "user"));
actions.append(supervise);
}
if (actions.childElementCount > 0) {
body.append(actions);
}
card.append(body);
column.append(card);
routeRoot.append(column);
}
}
renderMonitoringDetail();
frontendTrace("main", "Raw Transaction Ingest Desk Config route inventory profile rendered", {
activeRouteCount: activeRuntimes.size,
monitoringCount: routeMonitoring.size,
profileId: profile.profileId,
routeCount: profile.routes.length,
selectableCount: profile.routes.filter(route => route.selectable).length,
});
}
function renderMonitoringDetail(): void {
const panel = document.querySelector<HTMLElement>("#routeMonitoringDetail");
if (!panel) {
return;
}
const monitoring = selectedMonitoringKey === null ? null : (routeMonitoring.get(selectedMonitoringKey) ?? null);
if (!monitoring) {
panel.hidden = true;
return;
}
panel.hidden = false;
text("#monitorRouteId", monitoring.routeId);
text("#monitorNetwork", monitoring.network);
text("#monitorCommitment", monitoring.commitment);
text("#monitorSequence", monitoring.sequence);
text("#monitorHealth", monitoring.health);
text("#monitorActivity", monitoring.activity);
text("#monitorSourceState", optionalText(monitoring.sourceState));
text("#monitorFault", monitoring.faultDomain === null || monitoring.faultCode === null ? "—" : `${monitoring.faultDomain}/${monitoring.faultCode}`);
const lifecycle = document.querySelector<HTMLElement>("#monitorLifecycle");
if (lifecycle) {
lifecycle.className = lifecycleBadgeClass(monitoring.state);
lifecycle.textContent = monitoring.state;
}
const health = document.querySelector<HTMLElement>("#monitorHealthBadge");
if (health) {
health.className = monitoring.health === "healthy" ? "badge text-bg-success" : "badge text-bg-warning";
health.textContent = monitoring.health;
}
const activity = document.querySelector<HTMLElement>("#monitorActivityBadge");
if (activity) {
activity.className = "badge text-bg-light border text-dark";
activity.textContent = monitoring.activity;
}
const values: Record<string, string> = {
monitorAdmissionQueue: `${monitoring.admissionQueueDepth} / ${monitoring.admissionQueueCapacity}`,
monitorPersistence: `${monitoring.inFlightPersistence} / ${monitoring.persistenceConcurrency}`,
monitorAdmittedTotal: monitoring.admittedTotal,
monitorCanonicalizedTotal: monitoring.canonicalizedTotal,
monitorPersistedTotal: monitoring.persistedTotal,
monitorEntityInsertedTotal: monitoring.entityInsertedTotal,
monitorEntityAlreadyPresentTotal: monitoring.entityAlreadyPresentTotal,
monitorEntitySkippedPurgedTotal: monitoring.entitySkippedPurgedTotal,
monitorObservationInsertedTotal: monitoring.observationInsertedTotal,
monitorObservationAlreadyPresentTotal: monitoring.observationAlreadyPresentTotal,
monitorContentConflictTotal: monitoring.contentConflictTotal,
monitorStoreFailureTotal: monitoring.storeFailureTotal,
monitorSourceFailureTotal: monitoring.sourceFailureTotal,
monitorBackpressureWaitTotal: monitoring.backpressureWaitTotal,
monitorHydrationPending: monitoring.hydrationPending.toString(),
monitorProcessingFrontier: optionalText(monitoring.processingFrontierSlot),
monitorOldestPending: optionalText(monitoring.oldestPendingSlot),
monitorSourceCounts: `${monitoring.sourceActive} active / ${monitoring.sourceReconnecting} reconnecting / ${monitoring.sourceFailed} failed / ${monitoring.sourceTotal} total`,
monitorSourceReconnectTotal: monitoring.sourceReconnectTotal,
monitorSourceReplayAttemptTotal: monitoring.sourceReplayAttemptTotal,
monitorSourceContinuityGapTotal: monitoring.sourceContinuityGapTotal,
monitorContinuityPolicyObserved: yesNo(monitoring.continuityPolicyObserved),
monitorContinuityFrontier: optionalText(monitoring.continuityFrontierSlot),
monitorContinuityHasOpenGaps: yesNo(monitoring.continuityHasOpenGaps),
monitorFailedSourceLossesReconciled: yesNo(monitoring.failedSourceLossesReconciled),
monitorFutureTargetCoverage: yesNo(monitoring.futureTargetCoverage),
monitorOpenGapCount: monitoring.openGapCount.toString(),
monitorRepairingGapCount: monitoring.repairingGapCount.toString(),
monitorRepairedGapTotal: monitoring.repairedGapTotal,
monitorUnresolvedGapTotal: monitoring.unresolvedGapTotal,
monitorOldestOpenGap: optionalText(monitoring.oldestOpenGapStartSlot),
monitorReplayRepairTotal: monitoring.replayRepairTotal,
monitorRedundantCoverageRepairTotal: monitoring.redundantCoverageRepairTotal,
monitorHttpScanRepairTotal: monitoring.httpScanRepairTotal,
monitorRepairBlockFetchTotal: monitoring.repairBlockFetchTotal,
monitorRepairTransactionHydrationTotal: monitoring.repairTransactionHydrationTotal,
};
for (const [id, value] of Object.entries(values)) {
text(`#${id}`, value);
}
const gaps = document.querySelector<HTMLElement>("#monitorGapList");
if (gaps) {
gaps.replaceChildren();
if (monitoring.gaps.length === 0) {
const empty = document.createElement("div");
empty.className = "text-body-secondary small";
empty.textContent = "Aucun gap courant ou récent projeté.";
gaps.append(empty);
} else {
for (const gap of monitoring.gaps) {
const item = document.createElement("div");
item.className = "list-group-item";
const top = document.createElement("div");
top.className = "d-flex justify-content-between gap-3 flex-wrap";
const range = document.createElement("span");
range.className = "app-route-code";
range.textContent = `${gap.startSlot} ${gap.endSlot}`;
const badge = document.createElement("span");
badge.className = gap.state === "open" ? "badge text-bg-warning" : "badge text-bg-secondary";
badge.textContent = gap.state;
top.append(range, badge);
const meta = document.createElement("div");
meta.className = "small text-body-secondary mt-1";
meta.textContent = `gap ${gap.gapId} · ${gap.reason} · repair ${gap.lastMethod ?? "—"}`;
item.append(top, meta);
gaps.append(item);
}
}
}
frontendTrace("main", "Raw Transaction Ingest Desk route monitoring detail rendered", {
gapCount: monitoring.gaps.length,
routeId: monitoring.routeId,
sequence: monitoring.sequence,
state: monitoring.state,
});
}
function selectedCommitment(): RawIngestCommitment {
const selector = document.querySelector<HTMLSelectElement>("#routeCommitment");
return selector?.value === "finalized" ? "finalized" : "confirmed";
}
function runtimeIsActive(runtime: RawIngestRouteRuntimeDto): boolean {
return runtime.state === "starting" || runtime.state === "running" || runtime.state === "stopping";
}
function renderMultiRouteRuntime(): void {
const active = Array.from(activeRuntimes.values());
const state = document.querySelector<HTMLElement>("#routeRuntimeState");
if (state) {
state.textContent = active.length === 0 ? "idle" : `${active.length} active`;
state.className = active.length === 0 ? "badge text-bg-secondary" : "badge text-bg-primary";
}
const identities = active.map(runtime => `${runtime.routeId}/${runtime.commitment}`).join(", ");
text("#routeRuntimeIdentity", identities.length === 0 ? "aucun Worker actif" : identities);
const store = document.querySelector<HTMLElement>("#headerStore");
if (store) {
store.textContent = active.length === 0 ? "Store idle" : "Store shared";
store.className = active.length === 0 ? "badge text-bg-secondary" : "badge text-bg-success";
}
const profile = document.querySelector<HTMLSelectElement>("#routeProfile");
if (profile) {
profile.disabled = active.length > 0;
}
}
async function startRoute(routeId: RawIngestRouteId): Promise<void> {
if (routeInventory === null || selectedProfileId === null) {
return;
}
const key = runtimeKey(selectedProfileId, routeId);
const existing = activeRuntimes.get(key);
if (existing !== undefined && runtimeIsActive(existing)) {
return;
}
const request: RawIngestRouteStartRequestDto = {
commitment: selectedCommitment(),
inventoryGeneration: routeInventory.generation,
profileId: selectedProfileId,
routeId,
};
frontendDebug("main", "Raw Transaction Ingest Desk route Start requested", {
commitment: request.commitment,
profileId: request.profileId,
routeId: request.routeId,
});
const response = await invokeKsp<RawIngestRouteRuntimeDto>("main", "start_route", { request });
lastRuntimes.set(key, response);
if (runtimeIsActive(response)) {
activeRuntimes.set(key, response);
} else {
activeRuntimes.delete(key);
}
renderMultiRouteRuntime();
renderSelectedProfile();
renderRouteFeedback(`Route ${response.routeId} démarrée (${response.state}).`, "success");
await syncRouteMonitoring("start");
frontendInfo("main", "Raw Transaction Ingest Desk route Start acknowledged", {
activeRouteCount: activeRuntimes.size,
profileId: response.profileId,
routeId: response.routeId,
state: response.state,
});
}
async function stopRoute(runtime: RawIngestRouteRuntimeDto): Promise<void> {
const key = runtimeKey(runtime.profileId, runtime.routeId);
const request: RawIngestRouteStopRequestDto = {
profileId: runtime.profileId,
routeId: runtime.routeId,
};
frontendDebug("main", "Raw Transaction Ingest Desk targeted route Stop requested", {
profileId: request.profileId,
routeId: request.routeId,
});
const response = await invokeKsp<RawIngestRouteRuntimeDto>("main", "stop_route", { request });
lastRuntimes.set(key, response);
if (runtimeIsActive(response)) {
activeRuntimes.set(key, response);
} else {
activeRuntimes.delete(key);
}
renderMultiRouteRuntime();
renderSelectedProfile();
renderRouteFeedback(`Route ${response.routeId} arrêtée (${response.state}).`, "info");
await syncRouteMonitoring("stop");
frontendInfo("main", "Raw Transaction Ingest Desk targeted route Stop completed", {
activeRouteCount: activeRuntimes.size,
profileId: response.profileId,
routeId: response.routeId,
state: response.state,
});
}
function applyRouteMonitoring(status: RawIngestRouteMonitoringDto, source: "event" | "startup" | "start" | "stop" | "user"): void {
recordRouteMonitoring(status);
if (selectedMonitoringKey === null && status.profileId === selectedProfileId) {
selectedMonitoringKey = runtimeKey(status.profileId, status.routeId);
}
renderMultiRouteRuntime();
renderSelectedProfile();
frontendTrace("main", "Raw Transaction Ingest Desk route monitoring latest value applied", {
routeId: status.routeId,
sequence: status.sequence,
source,
state: status.state,
terminal: status.terminal,
});
}
async function syncRouteMonitoring(source: "startup" | "start" | "stop" | "user"): Promise<void> {
frontendDebug("main", "Raw Transaction Ingest Desk route monitoring resynchronization requested", { source });
const statuses = await invokeKsp<RawIngestRouteMonitoringDto[]>("main", "get_route_monitoring");
routeMonitoring.clear();
activeRuntimes.clear();
lastRuntimes.clear();
for (const status of statuses) {
recordRouteMonitoring(status);
}
if (selectedMonitoringKey !== null && !routeMonitoring.has(selectedMonitoringKey)) {
selectedMonitoringKey = null;
}
if (selectedMonitoringKey === null) {
const preferred = statuses.find(status => status.profileId === selectedProfileId) ?? statuses[0];
if (preferred) {
selectedMonitoringKey = runtimeKey(preferred.profileId, preferred.routeId);
}
}
renderMultiRouteRuntime();
renderSelectedProfile();
frontendDebug("main", "Raw Transaction Ingest Desk route monitoring resynchronization completed", {
activeRouteCount: activeRuntimes.size,
source,
statusCount: statuses.length,
});
}
async function bindRouteMonitoring(): Promise<void> {
await listen<RawIngestRouteMonitoringDto>("ksp-raw-ingest-route-status", event => {
applyRouteMonitoring(event.payload, "event");
});
document.querySelector<HTMLButtonElement>("#refreshRouteMonitoring")?.addEventListener("click", () => {
frontendDebug("main", "Raw Transaction Ingest Desk route monitoring refresh button clicked");
void syncRouteMonitoring("user").catch(caughtError => {
renderRouteFeedback(safeCommandErrorText(caughtError, "La resynchronisation du monitoring a échoué."), "danger");
frontendWarn("main", "Raw Transaction Ingest Desk route monitoring resynchronization failed");
});
});
frontendTrace("main", "Raw Transaction Ingest Desk route monitoring listener and resynchronization control installed");
}
function renderRouteInventory(inventory: RawIngestRouteInventoryDto): void {
routeInventory = inventory;
const requestedProfile = selectedProfileId;
const selectedStillExists = requestedProfile !== null && inventory.profiles.some(profile => profile.profileId === requestedProfile);
selectedProfileId = selectedStillExists ? requestedProfile : inventory.defaultProfile;
const selector = document.querySelector<HTMLSelectElement>("#routeProfile");
if (selector) {
selector.replaceChildren();
for (const profile of inventory.profiles) {
const option = document.createElement("option");
option.value = profile.profileId;
option.textContent = profile.isDefault ? `${profile.profileId} (default)` : profile.profileId;
option.selected = profile.profileId === selectedProfileId;
selector.append(option);
}
}
text("#routeInventoryGeneration", inventory.generation.toString());
text("#runtimeInventoryGeneration", inventory.generation.toString());
text("#runtimeInventoryDefaultProfile", inventory.defaultProfile);
renderSelectedProfile();
renderMultiRouteRuntime();
frontendTrace("main", "Raw Transaction Ingest Desk Config route inventory rendered", {
generation: inventory.generation,
profileCount: inventory.profiles.length,
});
}
function text(selector: string, value: string): void {
const element = document.querySelector<HTMLElement>(selector);
if (element) {
element.textContent = value;
}
}
function renderRuntimeStatus(status: ShellStatusDto): void {
text("#runtimeVersion", status.applicationVersion);
text("#runtimeShellPhase", status.shellPhase);
text("#runtimeConfigDocuments", status.configDocumentCount.toString());
text("#runtimeCompositeProfile", status.activeCompositeProfile ?? "fallback/unresolved");
text("#runtimeLoggingProfile", status.activeLoggingProfile ?? "fallback");
text("#runtimeLoggingFallback", status.fallbackLoggingActive ? "oui" : "non");
text("#headerPhase", status.shellPhase);
const diagnostic = document.querySelector<HTMLElement>("#runtimeDiagnostic");
if (diagnostic) {
diagnostic.hidden = status.startupDiagnostic === null;
diagnostic.textContent = status.startupDiagnostic ? `${status.startupDiagnostic.domain}/${status.startupDiagnostic.code}: ${status.startupDiagnostic.message}` : "";
}
frontendTrace("main", "Raw Transaction Ingest Desk runtime status rendered", {
fallbackLoggingActive: status.fallbackLoggingActive,
hasStartupDiagnostic: status.startupDiagnostic !== null,
});
}
async function refreshRouteInventory(): Promise<void> {
frontendDebug("main", "Raw Transaction Ingest Desk Config route inventory refresh requested");
const inventory = await invokeKsp<RawIngestRouteInventoryDto>("main", "get_route_inventory");
renderRouteInventory(inventory);
}
async function refreshRuntimeStatus(): Promise<void> {
frontendDebug("main", "Raw Transaction Ingest Desk runtime status refresh requested");
const status = await invokeKsp<ShellStatusDto>("main", "get_runtime_status");
renderRuntimeStatus(status);
}
function bindRefreshControls(): void {
document.querySelector<HTMLButtonElement>("#refreshRouteInventory")?.addEventListener("click", () => {
void refreshRouteInventory().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk Config route inventory refresh failed"));
});
document.querySelector<HTMLButtonElement>("#refreshRuntimeStatus")?.addEventListener("click", () => {
void refreshRuntimeStatus().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk runtime status refresh failed"));
});
document.querySelector<HTMLSelectElement>("#routeProfile")?.addEventListener("change", event => {
const selector = event.currentTarget;
if (!(selector instanceof HTMLSelectElement)) {
return;
}
selectedProfileId = selector.value;
selectedMonitoringKey = null;
const selected = routeInventory?.profiles.find(profile => profile.profileId === selectedProfileId) ?? null;
frontendDebug("main", "Raw Transaction Ingest Desk logical network selected", {
profileId: selectedProfileId,
routeCount: selected?.routes.length ?? 0,
selectionResolved: selected !== null,
});
renderSelectedProfile();
});
document.querySelector<HTMLSelectElement>("#routeCommitment")?.addEventListener("change", event => {
const selector = event.currentTarget;
if (!(selector instanceof HTMLSelectElement)) {
return;
}
frontendDebug("main", "Raw Transaction Ingest Desk commitment selection changed", { commitment: selectedCommitment() });
});
}
async function initializeMain(): Promise<void> {
frontendInfo("main", "Raw Transaction Ingest Desk main frontend loaded");
bindFrontendInteractions();
bindNavigation();
bindRefreshControls();
await bindRouteMonitoring();
activateView("routes", "startup");
await refreshRouteInventory();
await Promise.all([refreshRuntimeStatus(), syncRouteMonitoring("startup")]);
frontendInfo("main", "Raw Transaction Ingest Desk multi-route shared-Store runtime frontend ready");
}
void initializeMain().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk frontend initialization failed"));