v0.3.8-pre.007
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-store-desk/frontend/main.html -->
|
||||
<!-- version: 2 -->
|
||||
<!-- version: 3 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -74,14 +74,53 @@
|
||||
</section>
|
||||
|
||||
<section data-view-panel="transactions" hidden>
|
||||
<div class="mb-4">
|
||||
<h1 class="h3 mb-1">RAW Transactions</h1>
|
||||
<p class="text-body-secondary mb-0">Structure DataTables conservée hors connexion métier jusqu'à pre.007. Le runtime Store est ouvert par le composite.</p>
|
||||
<div class="d-flex flex-wrap align-items-start justify-content-between gap-3 mb-3">
|
||||
<div>
|
||||
<h1 class="h3 mb-1">RAW Transactions</h1>
|
||||
<p class="text-body-secondary mb-0">Inspection Store random-access ; DataTables reste l'unique propriétaire de la pagination visible.</p>
|
||||
</div>
|
||||
<button id="refreshTransactions" class="btn btn-outline-primary" type="button">
|
||||
<i class="fa-solid fa-rotate me-2" aria-hidden="true"></i>Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-body">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="transactionSlotMin">Slot minimum</label>
|
||||
<input id="transactionSlotMin" class="form-control font-monospace" type="text" inputmode="numeric" autocomplete="off" placeholder="optionnel">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="transactionSlotMax">Slot maximum</label>
|
||||
<input id="transactionSlotMax" class="form-control font-monospace" type="text" inputmode="numeric" autocomplete="off" placeholder="optionnel">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="transactionDirection">Direction canonique</label>
|
||||
<select id="transactionDirection" class="form-select">
|
||||
<option value="descending" selected>Descending</option>
|
||||
<option value="ascending">Ascending</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="transactionsQueryError" class="alert alert-danger" role="status" aria-live="polite" hidden></div>
|
||||
<div class="table-responsive app-store-table-container">
|
||||
<table id="rawTransactionsTable" class="table table-hover align-middle mb-0 app-store-table">
|
||||
<caption class="visually-hidden">Structure future de la table RAW Transactions.</caption>
|
||||
<thead><tr><th>Signature</th><th>Slot</th><th>Block time</th><th>Format</th><th>Payload size</th><th>Retention</th></tr></thead>
|
||||
<caption class="visually-hidden">Inspection server-side des transactions RAW.</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Signature</th>
|
||||
<th>Slot</th>
|
||||
<th>Block time</th>
|
||||
<th>Format ID</th>
|
||||
<th>Version</th>
|
||||
<th>Payload size</th>
|
||||
<th>Content hash</th>
|
||||
<th>Retention</th>
|
||||
<th>Détail</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -138,6 +177,34 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div id="transactionDetailModal" class="modal fade" tabindex="-1" aria-labelledby="transactionDetailTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 id="transactionDetailTitle" class="modal-title h5">RAW Transaction detail</h2>
|
||||
<button class="btn-close" type="button" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="transactionDetailError" class="alert alert-danger" role="status" aria-live="polite" hidden></div>
|
||||
<dl class="row app-runtime-list mb-4">
|
||||
<dt class="col-sm-3">Signature</dt><dd id="transactionDetailSignature" class="col-sm-9 text-break">—</dd>
|
||||
<dt class="col-sm-3">Slot</dt><dd id="transactionDetailSlot" class="col-sm-9">—</dd>
|
||||
<dt class="col-sm-3">Block time</dt><dd id="transactionDetailBlockTime" class="col-sm-9">—</dd>
|
||||
<dt class="col-sm-3">Format</dt><dd id="transactionDetailFormat" class="col-sm-9">—</dd>
|
||||
<dt class="col-sm-3">Content hash</dt><dd id="transactionDetailContentHash" class="col-sm-9 text-break">—</dd>
|
||||
<dt class="col-sm-3">Retention</dt><dd id="transactionDetailRetention" class="col-sm-9">—</dd>
|
||||
<dt class="col-sm-3">Payload size</dt><dd id="transactionDetailPayloadSize" class="col-sm-9">—</dd>
|
||||
</dl>
|
||||
<div class="d-flex align-items-center justify-content-between gap-3 mb-2">
|
||||
<h3 class="h6 mb-0">Payload preview hex</h3>
|
||||
<span id="transactionDetailPreviewTruncated" class="badge text-bg-warning" hidden>Preview tronquée à 512 bytes</span>
|
||||
</div>
|
||||
<pre id="transactionDetailPayloadPreview" class="small bg-body-tertiary border rounded p-3 overflow-auto text-break mb-0">—</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="app-footer bg-dark text-light">
|
||||
<div class="container h-100 d-flex align-items-center justify-content-center">
|
||||
<small>© 2026 SASEDEV</small>
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
// file: crates/ksp-app-store-desk/frontend/ts/main.ts
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
import DataTable from "datatables.net-bs5";
|
||||
import "datatables.net-bs5/css/dataTables.bootstrap5.css";
|
||||
import "bootstrap";
|
||||
import { Modal } from "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 type { StoreTransactionDetailDto } from "./bindings/ksp_app_store_desk/dto_transaction/StoreTransactionDetailDto.ts";
|
||||
import type { StoreTransactionDetailRequestDto } from "./bindings/ksp_app_store_desk/dto_transaction/StoreTransactionDetailRequestDto.ts";
|
||||
import type { StoreTransactionQueryRequestDto } from "./bindings/ksp_app_store_desk/dto_transaction/StoreTransactionQueryRequestDto.ts";
|
||||
import type { StoreTransactionQueryResponseDto } from "./bindings/ksp_app_store_desk/dto_transaction/StoreTransactionQueryResponseDto.ts";
|
||||
import type { StoreTransactionRowDto } from "./bindings/ksp_app_store_desk/dto_transaction/StoreTransactionRowDto.ts";
|
||||
import { frontendDebug, frontendError, frontendInfo, frontendTrace, installFrontendConsoleBridge } from "./frontend_log";
|
||||
import { invokeKsp } from "./invoke";
|
||||
|
||||
@@ -16,6 +21,25 @@ installFrontendConsoleBridge("main");
|
||||
|
||||
type ViewId = "overview" | "transactions" | "accounts" | "diagnostics";
|
||||
|
||||
interface DataTablesPageRequest {
|
||||
draw: number;
|
||||
length: number;
|
||||
start: number;
|
||||
}
|
||||
|
||||
interface DataTablesPageResponse<T> {
|
||||
data: T[];
|
||||
draw: number;
|
||||
recordsFiltered: number;
|
||||
recordsTotal: number;
|
||||
}
|
||||
|
||||
interface StoreDataTable {
|
||||
columns: { adjust(): void };
|
||||
draw(resetPaging?: boolean): void;
|
||||
on(event: string, callback: (...args: unknown[]) => void): StoreDataTable;
|
||||
}
|
||||
|
||||
const viewTitles: Record<ViewId, string> = {
|
||||
overview: "Overview",
|
||||
transactions: "RAW Transactions",
|
||||
@@ -23,12 +47,8 @@ const viewTitles: Record<ViewId, string> = {
|
||||
diagnostics: "Diagnostics",
|
||||
};
|
||||
|
||||
interface EmptyDataTable {
|
||||
columns: { adjust(): void };
|
||||
}
|
||||
|
||||
let transactionTable: EmptyDataTable | null = null;
|
||||
let accountTable: EmptyDataTable | null = null;
|
||||
let transactionTable: StoreDataTable | null = null;
|
||||
let accountTable: StoreDataTable | null = null;
|
||||
|
||||
function isViewId(value: string | undefined): value is ViewId {
|
||||
return value === "overview" || value === "transactions" || value === "accounts" || value === "diagnostics";
|
||||
@@ -41,6 +61,13 @@ function setText(elementId: string, value: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
function setVisible(elementId: string, visible: boolean): void {
|
||||
const element = document.querySelector<HTMLElement>(`#${elementId}`);
|
||||
if (element) {
|
||||
element.hidden = !visible;
|
||||
}
|
||||
}
|
||||
|
||||
function renderDiagnostic(elementId: string, diagnostic: { domain: string; code: string; message: string } | null): void {
|
||||
const element = document.querySelector<HTMLElement>(`#${elementId}`);
|
||||
if (!element) {
|
||||
@@ -71,25 +98,160 @@ function activateView(viewId: ViewId): void {
|
||||
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.",
|
||||
},
|
||||
function decimalCountToSafeNumber(value: string): number | null {
|
||||
if (!/^\d+$/.test(value)) {
|
||||
return null;
|
||||
}
|
||||
const exact = BigInt(value);
|
||||
if (exact > BigInt(Number.MAX_SAFE_INTEGER)) {
|
||||
return null;
|
||||
}
|
||||
return Number(exact);
|
||||
}
|
||||
|
||||
function currentTransactionQuery(page: DataTablesPageRequest): StoreTransactionQueryRequestDto | null {
|
||||
if (!Number.isSafeInteger(page.start) || page.start < 0 || !Number.isSafeInteger(page.length) || ![25, 50, 100].includes(page.length)) {
|
||||
return null;
|
||||
}
|
||||
const slotMinElement = document.querySelector<HTMLInputElement>("#transactionSlotMin");
|
||||
const slotMaxElement = document.querySelector<HTMLInputElement>("#transactionSlotMax");
|
||||
const directionElement = document.querySelector<HTMLSelectElement>("#transactionDirection");
|
||||
const slotMin = slotMinElement?.value.trim() ?? "";
|
||||
const slotMax = slotMaxElement?.value.trim() ?? "";
|
||||
return {
|
||||
direction: directionElement?.value ?? "descending",
|
||||
limit: page.length,
|
||||
offset: page.start.toString(),
|
||||
slotMax: slotMax === "" ? null : slotMax,
|
||||
slotMin: slotMin === "" ? null : slotMin,
|
||||
};
|
||||
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 emptyTransactionPage(draw: number): DataTablesPageResponse<StoreTransactionRowDto> {
|
||||
return { data: [], draw, recordsFiltered: 0, recordsTotal: 0 };
|
||||
}
|
||||
|
||||
function setTransactionQueryError(message: string | null): void {
|
||||
const element = document.querySelector<HTMLElement>("#transactionsQueryError");
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
element.hidden = message === null;
|
||||
element.textContent = message ?? "";
|
||||
}
|
||||
|
||||
async function queryTransactions(page: DataTablesPageRequest, callback: (response: DataTablesPageResponse<StoreTransactionRowDto>) => void): Promise<void> {
|
||||
const draw = Number.isSafeInteger(page.draw) && page.draw >= 0 ? page.draw : 0;
|
||||
const request = currentTransactionQuery(page);
|
||||
if (!request) {
|
||||
setTransactionQueryError("La pagination demandée n'est pas admise par Store Desk.");
|
||||
callback(emptyTransactionPage(draw));
|
||||
frontendError("main", "Store Desk transaction DataTables query rejected before IPC");
|
||||
return;
|
||||
}
|
||||
setTransactionQueryError(null);
|
||||
frontendDebug("main", "Store Desk transaction DataTables server-side query started", {
|
||||
pageLength: request.limit,
|
||||
slotFilterActive: request.slotMin !== null || request.slotMax !== null,
|
||||
});
|
||||
try {
|
||||
const response = await invokeKsp<StoreTransactionQueryResponseDto>("main", "store_query_transactions", { request });
|
||||
const recordsTotal = decimalCountToSafeNumber(response.recordsTotalDecimal);
|
||||
const recordsFiltered = decimalCountToSafeNumber(response.recordsFilteredDecimal);
|
||||
if (recordsTotal === null || recordsFiltered === null) {
|
||||
setTransactionQueryError("Le volume Store dépasse le domaine entier sûr de l'interface.");
|
||||
callback(emptyTransactionPage(draw));
|
||||
frontendError("main", "Store Desk transaction DataTables exact count exceeds frontend safe integer domain");
|
||||
return;
|
||||
}
|
||||
callback({ data: response.rows, draw, recordsFiltered, recordsTotal });
|
||||
frontendDebug("main", "Store Desk transaction DataTables server-side query completed", { rowCount: response.rows.length });
|
||||
} catch {
|
||||
setTransactionQueryError("La requête RAW Transactions a échoué.");
|
||||
callback(emptyTransactionPage(draw));
|
||||
frontendError("main", "Store Desk transaction DataTables server-side query failed");
|
||||
}
|
||||
}
|
||||
|
||||
function retentionBadge(retentionState: string): string {
|
||||
const label = retentionState === "full" ? "Full" : retentionState === "archived" ? "Archived" : retentionState === "purged" ? "Purged" : retentionState;
|
||||
const className = retentionState === "full" ? "text-bg-success" : retentionState === "archived" ? "text-bg-info" : retentionState === "purged" ? "text-bg-secondary" : "text-bg-warning";
|
||||
return `<span class="badge ${className}">${label}</span>`;
|
||||
}
|
||||
|
||||
function initializeTransactionTable(): void {
|
||||
transactionTable = new DataTable("#rawTransactionsTable", {
|
||||
ajax: (data, callback) => {
|
||||
const request = data as unknown as DataTablesPageRequest;
|
||||
void queryTransactions(request, response => callback(response));
|
||||
},
|
||||
autoWidth: false,
|
||||
columns: [
|
||||
{ className: "font-monospace", data: "signature" },
|
||||
{ className: "font-monospace", data: "slotDecimal" },
|
||||
{ data: "blockTimeUnixMillisDecimal", defaultContent: "—" },
|
||||
{ data: "formatId" },
|
||||
{ data: "formatVersion" },
|
||||
{ data: "payloadSizeDecimal", defaultContent: "—" },
|
||||
{ className: "font-monospace", data: "contentHash" },
|
||||
{ data: "retentionState", render: data => retentionBadge(String(data)) },
|
||||
{
|
||||
data: null,
|
||||
defaultContent: "",
|
||||
render: (_data, _type, row) => {
|
||||
const transaction = row as StoreTransactionRowDto;
|
||||
return `<button class="btn btn-sm btn-outline-primary" type="button" data-transaction-detail="${transaction.signature}"><i class="fa-solid fa-magnifying-glass me-1" aria-hidden="true"></i>Détail</button>`;
|
||||
},
|
||||
},
|
||||
],
|
||||
info: true,
|
||||
lengthMenu: [25, 50, 100],
|
||||
ordering: false,
|
||||
pageLength: 25,
|
||||
paging: true,
|
||||
processing: true,
|
||||
searching: false,
|
||||
serverSide: true,
|
||||
scrollX: true,
|
||||
language: {
|
||||
emptyTable: "Aucune transaction RAW ne correspond à la requête Store.",
|
||||
info: "_START_ à _END_ sur _TOTAL_ transaction(s)",
|
||||
infoEmpty: "0 transaction",
|
||||
lengthMenu: "Afficher _MENU_",
|
||||
loadingRecords: "Chargement Store…",
|
||||
processing: "Inspection Store…",
|
||||
zeroRecords: "Aucune transaction RAW correspondante.",
|
||||
},
|
||||
}) as unknown as StoreDataTable;
|
||||
transactionTable.on("page", () => {
|
||||
frontendDebug("main", "Store Desk transaction DataTables page changed");
|
||||
});
|
||||
transactionTable.on("length", () => {
|
||||
frontendDebug("main", "Store Desk transaction DataTables page length changed");
|
||||
});
|
||||
transactionTable.on("draw", () => {
|
||||
frontendTrace("main", "Store Desk transaction DataTables redraw completed");
|
||||
});
|
||||
frontendTrace("main", "Store Desk transaction DataTable initialized", { pagingOwner: "datatables", serverSide: true });
|
||||
}
|
||||
|
||||
function initializeAccountSkeleton(): void {
|
||||
accountTable = new DataTable("#rawAccountsTable", {
|
||||
autoWidth: false,
|
||||
data: [],
|
||||
info: true,
|
||||
lengthMenu: [25, 50, 100],
|
||||
ordering: false,
|
||||
pageLength: 25,
|
||||
paging: true,
|
||||
searching: false,
|
||||
serverSide: false,
|
||||
scrollX: true,
|
||||
language: {
|
||||
emptyTable: "Aucune donnée RAW Account n'est chargée avant la tranche pre.008.",
|
||||
},
|
||||
}) as unknown as StoreDataTable;
|
||||
frontendTrace("main", "Store Desk account DataTable skeleton initialized", { pagingOwner: "datatables", serverSide: false });
|
||||
}
|
||||
|
||||
function renderShellStatus(status: ShellStatusDto): void {
|
||||
@@ -111,21 +273,18 @@ function renderStoreRuntimeStatus(status: StoreRuntimeStatusDto): void {
|
||||
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,
|
||||
@@ -158,6 +317,62 @@ async function refreshDiagnostics(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function clearTransactionDetail(): void {
|
||||
for (const elementId of [
|
||||
"transactionDetailSignature",
|
||||
"transactionDetailSlot",
|
||||
"transactionDetailBlockTime",
|
||||
"transactionDetailFormat",
|
||||
"transactionDetailContentHash",
|
||||
"transactionDetailRetention",
|
||||
"transactionDetailPayloadSize",
|
||||
"transactionDetailPayloadPreview",
|
||||
]) {
|
||||
setText(elementId, "—");
|
||||
}
|
||||
setVisible("transactionDetailError", false);
|
||||
setVisible("transactionDetailPreviewTruncated", false);
|
||||
}
|
||||
|
||||
function renderTransactionDetail(detail: StoreTransactionDetailDto): void {
|
||||
setText("transactionDetailSignature", detail.signature);
|
||||
setText("transactionDetailSlot", detail.slotDecimal);
|
||||
setText("transactionDetailBlockTime", detail.blockTimeUnixMillisDecimal ?? "—");
|
||||
setText("transactionDetailFormat", `${detail.formatId} v${detail.formatVersion}`);
|
||||
setText("transactionDetailContentHash", detail.contentHash);
|
||||
setText("transactionDetailRetention", detail.retentionState);
|
||||
setText("transactionDetailPayloadSize", detail.payloadSizeDecimal ?? "—");
|
||||
setText("transactionDetailPayloadPreview", detail.payloadPreviewHex ?? "Payload non disponible dans cet état de rétention.");
|
||||
setVisible("transactionDetailPreviewTruncated", detail.payloadPreviewTruncated);
|
||||
}
|
||||
|
||||
async function openTransactionDetail(signature: string): Promise<void> {
|
||||
const modalElement = document.querySelector<HTMLElement>("#transactionDetailModal");
|
||||
if (!modalElement) {
|
||||
return;
|
||||
}
|
||||
clearTransactionDetail();
|
||||
setText("transactionDetailSignature", signature);
|
||||
frontendDebug("main", "Store Desk transaction detail load requested");
|
||||
const modal = Modal.getOrCreateInstance(modalElement);
|
||||
modal.show();
|
||||
const request: StoreTransactionDetailRequestDto = { signature };
|
||||
try {
|
||||
const detail = await invokeKsp<StoreTransactionDetailDto>("main", "store_get_transaction_detail", { request });
|
||||
renderTransactionDetail(detail);
|
||||
frontendDebug("main", "Store Desk transaction detail opened", { payloadPreviewTruncated: detail.payloadPreviewTruncated, retentionState: detail.retentionState });
|
||||
} catch {
|
||||
setText("transactionDetailError", "Le détail de cette transaction n'a pas pu être chargé.");
|
||||
setVisible("transactionDetailError", true);
|
||||
frontendError("main", "Store Desk transaction detail load failed");
|
||||
}
|
||||
}
|
||||
|
||||
function redrawTransactions(resetPaging: boolean, reason: string): void {
|
||||
frontendDebug("main", "Store Desk transaction DataTables redraw requested", { reason });
|
||||
transactionTable?.draw(resetPaging);
|
||||
}
|
||||
|
||||
function installInteractions(): void {
|
||||
document.querySelectorAll<HTMLButtonElement>("[data-view]").forEach(button => {
|
||||
button.addEventListener("click", () => {
|
||||
@@ -173,6 +388,18 @@ function installInteractions(): void {
|
||||
frontendDebug("main", "Store Desk Overview refresh button clicked");
|
||||
void refreshStoreRuntime();
|
||||
});
|
||||
const refreshTransactions = document.querySelector<HTMLButtonElement>("#refreshTransactions");
|
||||
refreshTransactions?.addEventListener("click", () => {
|
||||
frontendDebug("main", "Store Desk RAW Transactions refresh button clicked");
|
||||
redrawTransactions(false, "refresh");
|
||||
});
|
||||
for (const elementId of ["transactionSlotMin", "transactionSlotMax", "transactionDirection"]) {
|
||||
const control = document.querySelector<HTMLElement>(`#${elementId}`);
|
||||
control?.addEventListener("change", () => {
|
||||
frontendDebug("main", "Store Desk transaction Store filter changed", { controlId: elementId });
|
||||
redrawTransactions(true, "store-filter");
|
||||
});
|
||||
}
|
||||
const refreshButton = document.querySelector<HTMLButtonElement>("#refreshDiagnostics");
|
||||
refreshButton?.addEventListener("click", () => {
|
||||
frontendDebug("main", "Store Desk diagnostics refresh button clicked");
|
||||
@@ -183,11 +410,19 @@ function installInteractions(): void {
|
||||
if (!(eventTarget instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
const detailButton = eventTarget.closest<HTMLElement>("[data-transaction-detail]");
|
||||
if (detailButton) {
|
||||
const signature = detailButton.dataset.transactionDetail;
|
||||
if (signature) {
|
||||
void openTransactionDetail(signature);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const control = eventTarget.closest<HTMLElement>("button, [role='tab'], [data-bs-toggle='tab']");
|
||||
if (!control) {
|
||||
return;
|
||||
}
|
||||
if (control instanceof HTMLButtonElement && (control.dataset.view || control.id === "refreshDiagnostics" || control.id === "refreshOverview")) {
|
||||
if (control instanceof HTMLButtonElement && (control.dataset.view || control.id === "refreshDiagnostics" || control.id === "refreshOverview" || control.id === "refreshTransactions")) {
|
||||
return;
|
||||
}
|
||||
const controlId = control.id || control.getAttribute("data-bs-target") || control.getAttribute("aria-controls") || "anonymous";
|
||||
@@ -212,12 +447,18 @@ function installInteractions(): void {
|
||||
controlType: target instanceof HTMLSelectElement ? "select" : target.type || "text",
|
||||
});
|
||||
});
|
||||
frontendTrace("main", "Store Desk frontend interactions installed", { buttons: true, controls: true, tabs: true });
|
||||
const detailModal = document.querySelector<HTMLElement>("#transactionDetailModal");
|
||||
detailModal?.addEventListener("hidden.bs.modal", () => {
|
||||
clearTransactionDetail();
|
||||
frontendDebug("main", "Store Desk transaction detail closed");
|
||||
});
|
||||
frontendTrace("main", "Store Desk frontend interactions installed", { buttons: true, controls: true, datatables: true, tabs: true });
|
||||
}
|
||||
|
||||
async function initializeMain(): Promise<void> {
|
||||
frontendInfo("main", "Store Desk main frontend loaded");
|
||||
initializeEmptyTables();
|
||||
initializeTransactionTable();
|
||||
initializeAccountSkeleton();
|
||||
installInteractions();
|
||||
activateView("overview");
|
||||
await refreshDiagnostics();
|
||||
|
||||
Reference in New Issue
Block a user