Files
khadhroony-solana-project/crates/ksp-app-raw-transaction-ingest-desk/frontend/ts/main.ts

190 lines
8.1 KiB
TypeScript

// file: crates/ksp-app-raw-transaction-ingest-desk/frontend/ts/main.ts
// version: 2
import "bootstrap";
import ResizeObserver from "resize-observer-polyfill";
import "simplebar";
import type { ShellStatusDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_common/ShellStatusDto.ts";
import type { RawIngestRouteFoundationDto } from "./bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteFoundationDto.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",
};
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 routeLabel(routeId: string): string {
const labels: Record<string, string> = {
"yellowstone-hydrated": "Yellowstone + HTTP hydration",
"standard-logs-hydrated": "Standard Logs + HTTP hydration",
"standard-block-direct": "Standard Block direct",
"helius-transaction-hydrated": "Helius Transaction + HTTP hydration",
"http-block-polling": "HTTP Block Polling",
};
return labels[routeId] ?? routeId;
}
function renderRouteFoundation(foundation: RawIngestRouteFoundationDto): void {
const routeRoot = document.querySelector<HTMLElement>("#routeFoundation");
if (routeRoot) {
routeRoot.replaceChildren();
for (const routeId of foundation.routeIds) {
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 title = document.createElement("h2");
title.className = "h6 mb-2";
title.textContent = routeLabel(routeId);
const code = document.createElement("div");
code.className = "app-route-code text-body-secondary small";
code.textContent = routeId;
const badge = document.createElement("span");
badge.className = "badge text-bg-secondary mt-3";
badge.textContent = "foundation only";
body.append(title, code, badge);
card.append(body);
column.append(card);
routeRoot.append(column);
}
}
const statesRoot = document.querySelector<HTMLElement>("#routeStates");
if (statesRoot) {
statesRoot.replaceChildren();
for (const state of foundation.states) {
const badge = document.createElement("span");
badge.className = "badge text-bg-light border text-dark";
badge.textContent = state;
statesRoot.append(badge);
}
}
frontendTrace("main", "Raw Transaction Ingest Desk route foundation rendered", {
routeCount: foundation.routeIds.length,
stateCount: foundation.states.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 refreshRouteFoundation(): Promise<void> {
frontendDebug("main", "Raw Transaction Ingest Desk route foundation refresh requested");
const foundation = await invokeKsp<RawIngestRouteFoundationDto>("main", "get_route_foundation");
renderRouteFoundation(foundation);
}
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>("#refreshRouteFoundation")?.addEventListener("click", () => {
void refreshRouteFoundation().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk route foundation refresh failed"));
});
document.querySelector<HTMLButtonElement>("#refreshRuntimeStatus")?.addEventListener("click", () => {
void refreshRuntimeStatus().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk runtime status refresh failed"));
});
}
async function initializeMain(): Promise<void> {
frontendInfo("main", "Raw Transaction Ingest Desk main frontend loaded");
bindFrontendInteractions();
bindNavigation();
bindRefreshControls();
activateView("routes", "startup");
await refreshRouteFoundation();
await refreshRuntimeStatus();
frontendInfo("main", "Raw Transaction Ingest Desk scaffold frontend ready");
}
void initializeMain().catch(() => frontendWarn("main", "Raw Transaction Ingest Desk frontend initialization failed"));