v0.3.8-pre.008
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-store-desk/frontend/main.html -->
|
||||
<!-- version: 4 -->
|
||||
<!-- version: 5 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -127,14 +127,57 @@
|
||||
</section>
|
||||
|
||||
<section data-view-panel="accounts" hidden>
|
||||
<div class="mb-4">
|
||||
<h1 class="h3 mb-1">RAW Accounts</h1>
|
||||
<p class="text-body-secondary mb-0">Structure DataTables conservée hors connexion métier jusqu'à pre.008. Aucun byte Account ne traverse IPC dans cette tranche.</p>
|
||||
<div class="d-flex flex-wrap align-items-start justify-content-between gap-3 mb-3">
|
||||
<div>
|
||||
<h1 class="h3 mb-1">RAW Accounts</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="refreshAccounts" 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-lg-5">
|
||||
<label class="form-label" for="accountPubkey">Pubkey exacte</label>
|
||||
<input id="accountPubkey" class="form-control font-monospace" type="text" autocomplete="off" placeholder="optionnel">
|
||||
</div>
|
||||
<div class="col-md-4 col-lg-2">
|
||||
<label class="form-label" for="accountSlotMin">Slot minimum</label>
|
||||
<input id="accountSlotMin" class="form-control font-monospace" type="text" inputmode="numeric" autocomplete="off" placeholder="optionnel">
|
||||
</div>
|
||||
<div class="col-md-4 col-lg-2">
|
||||
<label class="form-label" for="accountSlotMax">Slot maximum</label>
|
||||
<input id="accountSlotMax" class="form-control font-monospace" type="text" inputmode="numeric" autocomplete="off" placeholder="optionnel">
|
||||
</div>
|
||||
<div class="col-md-4 col-lg-3">
|
||||
<label class="form-label" for="accountDirection">Direction canonique</label>
|
||||
<select id="accountDirection" class="form-select">
|
||||
<option value="descending" selected>Descending</option>
|
||||
<option value="ascending">Ascending</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="accountsQueryError" class="alert alert-danger" role="status" aria-live="polite" hidden></div>
|
||||
<div class="table-responsive app-store-table-container">
|
||||
<table id="rawAccountsTable" class="table table-hover align-middle mb-0 app-store-table">
|
||||
<caption class="visually-hidden">Structure future de la table RAW Accounts.</caption>
|
||||
<thead><tr><th>Pubkey</th><th>Slot</th><th>State hash</th><th>Owner</th><th>Lamports</th><th>Data length</th></tr></thead>
|
||||
<caption class="visually-hidden">Inspection server-side des états RAW Account.</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Pubkey</th>
|
||||
<th>Slot</th>
|
||||
<th>State hash</th>
|
||||
<th>Owner</th>
|
||||
<th>Lamports</th>
|
||||
<th>Executable</th>
|
||||
<th>Rent epoch</th>
|
||||
<th>Data length</th>
|
||||
<th>Détail</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -211,6 +254,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="accountDetailModal" class="modal fade" tabindex="-1" aria-labelledby="accountDetailTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 id="accountDetailTitle" class="modal-title h5">RAW Account detail</h2>
|
||||
<button class="btn-close" type="button" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="accountDetailError" class="alert alert-danger" role="status" aria-live="polite" hidden></div>
|
||||
<dl class="row app-runtime-list mb-4">
|
||||
<dt class="col-sm-3">Pubkey</dt><dd id="accountDetailPubkey" class="col-sm-9 text-break">—</dd>
|
||||
<dt class="col-sm-3">Slot</dt><dd id="accountDetailSlot" class="col-sm-9">—</dd>
|
||||
<dt class="col-sm-3">State hash</dt><dd id="accountDetailStateHash" class="col-sm-9 text-break">—</dd>
|
||||
<dt class="col-sm-3">Owner</dt><dd id="accountDetailOwner" class="col-sm-9 text-break">—</dd>
|
||||
<dt class="col-sm-3">Lamports</dt><dd id="accountDetailLamports" class="col-sm-9">—</dd>
|
||||
<dt class="col-sm-3">Executable</dt><dd id="accountDetailExecutable" class="col-sm-9">—</dd>
|
||||
<dt class="col-sm-3">Rent epoch</dt><dd id="accountDetailRentEpoch" class="col-sm-9">—</dd>
|
||||
<dt class="col-sm-3">Data length</dt><dd id="accountDetailDataLength" 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">Account data preview hex</h3>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<button id="accountDetailDataPreviewCopy" class="btn btn-sm btn-outline-secondary" type="button" title="Copier le preview hex affiché" aria-label="Copier le preview hex affiché" hidden>
|
||||
<i class="fa-regular fa-copy" aria-hidden="true"></i>
|
||||
<span>Copier</span>
|
||||
</button>
|
||||
<span id="accountDetailPreviewTruncated" class="badge text-bg-warning" hidden>Preview tronquée à 512 bytes</span>
|
||||
</div>
|
||||
</div>
|
||||
<pre id="accountDetailDataPreview" 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,11 +1,16 @@
|
||||
// file: crates/ksp-app-store-desk/frontend/ts/main.ts
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
import DataTable from "datatables.net-bs5";
|
||||
import "datatables.net-bs5/css/dataTables.bootstrap5.css";
|
||||
import { Modal } from "bootstrap";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
import "simplebar";
|
||||
import type { StoreAccountDetailDto } from "./bindings/ksp_app_store_desk/dto_account/StoreAccountDetailDto.ts";
|
||||
import type { StoreAccountDetailRequestDto } from "./bindings/ksp_app_store_desk/dto_account/StoreAccountDetailRequestDto.ts";
|
||||
import type { StoreAccountQueryRequestDto } from "./bindings/ksp_app_store_desk/dto_account/StoreAccountQueryRequestDto.ts";
|
||||
import type { StoreAccountQueryResponseDto } from "./bindings/ksp_app_store_desk/dto_account/StoreAccountQueryResponseDto.ts";
|
||||
import type { StoreAccountRowDto } from "./bindings/ksp_app_store_desk/dto_account/StoreAccountRowDto.ts";
|
||||
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";
|
||||
@@ -332,23 +337,128 @@ function initializeTransactionTable(): void {
|
||||
frontendTrace("main", "Store Desk transaction DataTable initialized", { pagingOwner: "datatables", serverSide: true });
|
||||
}
|
||||
|
||||
function initializeAccountSkeleton(): void {
|
||||
function currentAccountQuery(page: DataTablesPageRequest): StoreAccountQueryRequestDto | null {
|
||||
if (!Number.isSafeInteger(page.start) || page.start < 0 || !Number.isSafeInteger(page.length) || ![25, 50, 100].includes(page.length)) {
|
||||
return null;
|
||||
}
|
||||
const pubkeyElement = document.querySelector<HTMLInputElement>("#accountPubkey");
|
||||
const slotMinElement = document.querySelector<HTMLInputElement>("#accountSlotMin");
|
||||
const slotMaxElement = document.querySelector<HTMLInputElement>("#accountSlotMax");
|
||||
const directionElement = document.querySelector<HTMLSelectElement>("#accountDirection");
|
||||
const pubkey = pubkeyElement?.value.trim() ?? "";
|
||||
const slotMin = slotMinElement?.value.trim() ?? "";
|
||||
const slotMax = slotMaxElement?.value.trim() ?? "";
|
||||
return {
|
||||
direction: directionElement?.value ?? "descending",
|
||||
limit: page.length,
|
||||
offset: page.start.toString(),
|
||||
pubkey: pubkey === "" ? null : pubkey,
|
||||
slotMax: slotMax === "" ? null : slotMax,
|
||||
slotMin: slotMin === "" ? null : slotMin,
|
||||
};
|
||||
}
|
||||
|
||||
function emptyAccountPage(draw: number): DataTablesPageResponse<StoreAccountRowDto> {
|
||||
return { data: [], draw, recordsFiltered: 0, recordsTotal: 0 };
|
||||
}
|
||||
|
||||
function setAccountQueryError(message: string | null): void {
|
||||
const element = document.querySelector<HTMLElement>("#accountsQueryError");
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
element.hidden = message === null;
|
||||
element.textContent = message ?? "";
|
||||
}
|
||||
|
||||
async function queryAccounts(page: DataTablesPageRequest, callback: (response: DataTablesPageResponse<StoreAccountRowDto>) => void): Promise<void> {
|
||||
const draw = Number.isSafeInteger(page.draw) && page.draw >= 0 ? page.draw : 0;
|
||||
const request = currentAccountQuery(page);
|
||||
if (!request) {
|
||||
setAccountQueryError("La pagination demandée n'est pas admise par Store Desk.");
|
||||
callback(emptyAccountPage(draw));
|
||||
frontendError("main", "Store Desk account DataTables query rejected before IPC");
|
||||
return;
|
||||
}
|
||||
setAccountQueryError(null);
|
||||
frontendDebug("main", "Store Desk account DataTables server-side query started", {
|
||||
pageLength: request.limit,
|
||||
pubkeyFilterActive: request.pubkey !== null,
|
||||
slotFilterActive: request.slotMin !== null || request.slotMax !== null,
|
||||
});
|
||||
try {
|
||||
const response = await invokeKsp<StoreAccountQueryResponseDto>("main", "store_query_accounts", { request });
|
||||
const recordsTotal = decimalCountToSafeNumber(response.recordsTotalDecimal);
|
||||
const recordsFiltered = decimalCountToSafeNumber(response.recordsFilteredDecimal);
|
||||
if (recordsTotal === null || recordsFiltered === null) {
|
||||
setAccountQueryError("Le volume Store dépasse le domaine entier sûr de l'interface.");
|
||||
callback(emptyAccountPage(draw));
|
||||
frontendError("main", "Store Desk account DataTables exact count exceeds frontend safe integer domain");
|
||||
return;
|
||||
}
|
||||
callback({ data: response.rows, draw, recordsFiltered, recordsTotal });
|
||||
frontendDebug("main", "Store Desk account DataTables server-side query completed", { rowCount: response.rows.length });
|
||||
} catch {
|
||||
setAccountQueryError("La requête RAW Accounts a échoué.");
|
||||
callback(emptyAccountPage(draw));
|
||||
frontendError("main", "Store Desk account DataTables server-side query failed");
|
||||
}
|
||||
}
|
||||
|
||||
function initializeAccountTable(): void {
|
||||
accountTable = new DataTable("#rawAccountsTable", {
|
||||
ajax: (data, callback) => {
|
||||
const request = data as unknown as DataTablesPageRequest;
|
||||
void queryAccounts(request, response => callback(response));
|
||||
},
|
||||
autoWidth: false,
|
||||
data: [],
|
||||
columns: [
|
||||
{ data: "pubkey", render: (data, type) => (type === "display" ? renderCopyableLongText(String(data), "account-pubkey") : String(data)) },
|
||||
{ className: "font-monospace", data: "slotDecimal" },
|
||||
{ data: "stateHash", render: (data, type) => (type === "display" ? renderCopyableLongText(String(data), "account-state-hash") : String(data)) },
|
||||
{ data: "owner", render: (data, type) => (type === "display" ? renderCopyableLongText(String(data), "account-owner") : String(data)) },
|
||||
{ className: "font-monospace", data: "lamportsDecimal" },
|
||||
{ data: "executable", render: data => (Boolean(data) ? '<span class="badge text-bg-info">oui</span>' : '<span class="badge text-bg-secondary">non</span>') },
|
||||
{ className: "font-monospace", data: "rentEpochDecimal" },
|
||||
{ className: "font-monospace", data: "dataLengthDecimal" },
|
||||
{
|
||||
data: null,
|
||||
defaultContent: "",
|
||||
render: (_data, _type, row) => {
|
||||
const account = row as StoreAccountRowDto;
|
||||
return `<button class="btn btn-sm btn-outline-primary" type="button" data-account-detail data-account-pubkey="${escapeHtml(account.pubkey)}" data-account-slot="${escapeHtml(account.slotDecimal)}" data-account-state-hash="${escapeHtml(account.stateHash)}"><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: false,
|
||||
serverSide: true,
|
||||
scrollX: true,
|
||||
language: {
|
||||
emptyTable: "Aucune donnée RAW Account n'est chargée avant la tranche pre.008.",
|
||||
emptyTable: "Aucun état RAW Account ne correspond à la requête Store.",
|
||||
info: "_START_ à _END_ sur _TOTAL_ état(s) Account",
|
||||
infoEmpty: "0 état Account",
|
||||
lengthMenu: "Afficher _MENU_",
|
||||
loadingRecords: "Chargement Store…",
|
||||
processing: "Inspection Store…",
|
||||
zeroRecords: "Aucun état RAW Account correspondant.",
|
||||
},
|
||||
}) as unknown as StoreDataTable;
|
||||
frontendTrace("main", "Store Desk account DataTable skeleton initialized", { pagingOwner: "datatables", serverSide: false });
|
||||
accountTable.on("page", () => {
|
||||
frontendDebug("main", "Store Desk account DataTables page changed");
|
||||
});
|
||||
accountTable.on("length", () => {
|
||||
frontendDebug("main", "Store Desk account DataTables page length changed");
|
||||
});
|
||||
accountTable.on("draw", () => {
|
||||
frontendTrace("main", "Store Desk account DataTables redraw completed");
|
||||
});
|
||||
frontendTrace("main", "Store Desk account DataTable initialized", { pagingOwner: "datatables", serverSide: true });
|
||||
}
|
||||
|
||||
function renderShellStatus(status: ShellStatusDto): void {
|
||||
@@ -414,6 +524,83 @@ async function refreshDiagnostics(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function clearAccountDetail(): void {
|
||||
for (const elementId of [
|
||||
"accountDetailPubkey",
|
||||
"accountDetailSlot",
|
||||
"accountDetailStateHash",
|
||||
"accountDetailOwner",
|
||||
"accountDetailLamports",
|
||||
"accountDetailExecutable",
|
||||
"accountDetailRentEpoch",
|
||||
"accountDetailDataLength",
|
||||
"accountDetailDataPreview",
|
||||
]) {
|
||||
setText(elementId, "—");
|
||||
}
|
||||
setVisible("accountDetailError", false);
|
||||
setVisible("accountDetailPreviewTruncated", false);
|
||||
const dataPreviewCopy = document.querySelector<HTMLButtonElement>("#accountDetailDataPreviewCopy");
|
||||
if (dataPreviewCopy) {
|
||||
delete dataPreviewCopy.dataset.copyLongText;
|
||||
delete dataPreviewCopy.dataset.copyField;
|
||||
dataPreviewCopy.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
function renderAccountDetail(detail: StoreAccountDetailDto): void {
|
||||
setCopyableLongText("accountDetailPubkey", detail.pubkey, "account-detail-pubkey");
|
||||
setText("accountDetailSlot", detail.slotDecimal);
|
||||
setCopyableLongText("accountDetailStateHash", detail.stateHash, "account-detail-state-hash");
|
||||
setCopyableLongText("accountDetailOwner", detail.owner, "account-detail-owner");
|
||||
setText("accountDetailLamports", detail.lamportsDecimal);
|
||||
setText("accountDetailExecutable", detail.executable ? "oui" : "non");
|
||||
setText("accountDetailRentEpoch", detail.rentEpochDecimal);
|
||||
setText("accountDetailDataLength", detail.dataLengthDecimal);
|
||||
const dataPreview = detail.dataPreviewHex;
|
||||
setText("accountDetailDataPreview", dataPreview === "" ? "Données Account vides." : dataPreview);
|
||||
const dataPreviewCopy = document.querySelector<HTMLButtonElement>("#accountDetailDataPreviewCopy");
|
||||
if (dataPreviewCopy && dataPreview !== "") {
|
||||
dataPreviewCopy.dataset.copyLongText = dataPreview;
|
||||
dataPreviewCopy.dataset.copyField = "account-data-preview";
|
||||
dataPreviewCopy.hidden = false;
|
||||
} else if (dataPreviewCopy) {
|
||||
delete dataPreviewCopy.dataset.copyLongText;
|
||||
delete dataPreviewCopy.dataset.copyField;
|
||||
dataPreviewCopy.hidden = true;
|
||||
}
|
||||
setVisible("accountDetailPreviewTruncated", detail.dataPreviewTruncated);
|
||||
}
|
||||
|
||||
async function openAccountDetail(pubkey: string, slot: string, stateHash: string): Promise<void> {
|
||||
const modalElement = document.querySelector<HTMLElement>("#accountDetailModal");
|
||||
if (!modalElement) {
|
||||
return;
|
||||
}
|
||||
clearAccountDetail();
|
||||
setCopyableLongText("accountDetailPubkey", pubkey, "account-detail-pubkey");
|
||||
setText("accountDetailSlot", slot);
|
||||
setCopyableLongText("accountDetailStateHash", stateHash, "account-detail-state-hash");
|
||||
frontendDebug("main", "Store Desk account detail load requested");
|
||||
const modal = Modal.getOrCreateInstance(modalElement);
|
||||
modal.show();
|
||||
const request: StoreAccountDetailRequestDto = { pubkey, slot, stateHash };
|
||||
try {
|
||||
const detail = await invokeKsp<StoreAccountDetailDto>("main", "store_get_account_detail", { request });
|
||||
renderAccountDetail(detail);
|
||||
frontendDebug("main", "Store Desk account detail opened", { dataPreviewTruncated: detail.dataPreviewTruncated });
|
||||
} catch {
|
||||
setText("accountDetailError", "Le détail de cet état Account n'a pas pu être chargé.");
|
||||
setVisible("accountDetailError", true);
|
||||
frontendError("main", "Store Desk account detail load failed");
|
||||
}
|
||||
}
|
||||
|
||||
function redrawAccounts(resetPaging: boolean, reason: string): void {
|
||||
frontendDebug("main", "Store Desk account DataTables redraw requested", { reason });
|
||||
accountTable?.draw(resetPaging);
|
||||
}
|
||||
|
||||
function clearTransactionDetail(): void {
|
||||
for (const elementId of [
|
||||
"transactionDetailSignature",
|
||||
@@ -507,6 +694,11 @@ function installInteractions(): void {
|
||||
frontendDebug("main", "Store Desk RAW Transactions refresh button clicked");
|
||||
redrawTransactions(false, "refresh");
|
||||
});
|
||||
const refreshAccounts = document.querySelector<HTMLButtonElement>("#refreshAccounts");
|
||||
refreshAccounts?.addEventListener("click", () => {
|
||||
frontendDebug("main", "Store Desk RAW Accounts refresh button clicked");
|
||||
redrawAccounts(false, "refresh");
|
||||
});
|
||||
for (const elementId of ["transactionSlotMin", "transactionSlotMax", "transactionDirection"]) {
|
||||
const control = document.querySelector<HTMLElement>(`#${elementId}`);
|
||||
control?.addEventListener("change", () => {
|
||||
@@ -514,6 +706,13 @@ function installInteractions(): void {
|
||||
redrawTransactions(true, "store-filter");
|
||||
});
|
||||
}
|
||||
for (const elementId of ["accountPubkey", "accountSlotMin", "accountSlotMax", "accountDirection"]) {
|
||||
const control = document.querySelector<HTMLElement>(`#${elementId}`);
|
||||
control?.addEventListener("change", () => {
|
||||
frontendDebug("main", "Store Desk account Store filter changed", { controlId: elementId });
|
||||
redrawAccounts(true, "store-filter");
|
||||
});
|
||||
}
|
||||
const refreshButton = document.querySelector<HTMLButtonElement>("#refreshDiagnostics");
|
||||
refreshButton?.addEventListener("click", () => {
|
||||
frontendDebug("main", "Store Desk diagnostics refresh button clicked");
|
||||
@@ -533,6 +732,16 @@ function installInteractions(): void {
|
||||
}
|
||||
return;
|
||||
}
|
||||
const accountDetailButton = eventTarget.closest<HTMLElement>("[data-account-detail]");
|
||||
if (accountDetailButton) {
|
||||
const pubkey = accountDetailButton.dataset.accountPubkey;
|
||||
const slot = accountDetailButton.dataset.accountSlot;
|
||||
const stateHash = accountDetailButton.dataset.accountStateHash;
|
||||
if (pubkey && slot && stateHash) {
|
||||
void openAccountDetail(pubkey, slot, stateHash);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const detailButton = eventTarget.closest<HTMLElement>("[data-transaction-detail]");
|
||||
if (detailButton) {
|
||||
const signature = detailButton.dataset.transactionDetail;
|
||||
@@ -545,7 +754,7 @@ function installInteractions(): void {
|
||||
if (!control) {
|
||||
return;
|
||||
}
|
||||
if (control instanceof HTMLButtonElement && (control.dataset.view || control.id === "refreshDiagnostics" || control.id === "refreshOverview" || control.id === "refreshTransactions")) {
|
||||
if (control instanceof HTMLButtonElement && (control.dataset.view || control.id === "refreshDiagnostics" || control.id === "refreshOverview" || control.id === "refreshTransactions" || control.id === "refreshAccounts")) {
|
||||
return;
|
||||
}
|
||||
const controlId = control.id || control.getAttribute("data-bs-target") || control.getAttribute("aria-controls") || "anonymous";
|
||||
@@ -570,6 +779,11 @@ function installInteractions(): void {
|
||||
controlType: target instanceof HTMLSelectElement ? "select" : target.type || "text",
|
||||
});
|
||||
});
|
||||
const accountDetailModal = document.querySelector<HTMLElement>("#accountDetailModal");
|
||||
accountDetailModal?.addEventListener("hidden.bs.modal", () => {
|
||||
clearAccountDetail();
|
||||
frontendDebug("main", "Store Desk account detail closed");
|
||||
});
|
||||
const detailModal = document.querySelector<HTMLElement>("#transactionDetailModal");
|
||||
detailModal?.addEventListener("hidden.bs.modal", () => {
|
||||
clearTransactionDetail();
|
||||
@@ -581,7 +795,7 @@ function installInteractions(): void {
|
||||
async function initializeMain(): Promise<void> {
|
||||
frontendInfo("main", "Store Desk main frontend loaded");
|
||||
initializeTransactionTable();
|
||||
initializeAccountSkeleton();
|
||||
initializeAccountTable();
|
||||
installInteractions();
|
||||
activateView("overview");
|
||||
await refreshDiagnostics();
|
||||
|
||||
Reference in New Issue
Block a user