feat: add foreign key constraints and metrics for blockchain node
This commit is contained in:
34
apps/explorer-web/src/components/notifications.ts
Normal file
34
apps/explorer-web/src/components/notifications.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
const TOAST_DURATION_MS = 4000;
|
||||
|
||||
let container: HTMLDivElement | null = null;
|
||||
|
||||
export function initNotifications(): void {
|
||||
if (!container) {
|
||||
container = document.createElement("div");
|
||||
container.className = "toast-container";
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
}
|
||||
|
||||
export function notifyError(message: string): void {
|
||||
if (!container) {
|
||||
initNotifications();
|
||||
}
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toast = document.createElement("div");
|
||||
toast.className = "toast toast--error";
|
||||
toast.textContent = message;
|
||||
container.appendChild(toast);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
toast.classList.add("is-visible");
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
toast.classList.remove("is-visible");
|
||||
setTimeout(() => toast.remove(), 250);
|
||||
}, TOAST_DURATION_MS);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CONFIG, type DataMode } from "../config";
|
||||
import { notifyError } from "../components/notifications";
|
||||
import type {
|
||||
BlockListResponse,
|
||||
TransactionListResponse,
|
||||
@@ -35,6 +36,7 @@ export async function fetchBlocks(): Promise<BlockSummary[]> {
|
||||
return data.items;
|
||||
} catch (error) {
|
||||
console.warn("[Explorer] Failed to fetch live block data", error);
|
||||
notifyError("Unable to load live block data. Displaying placeholders.");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -54,6 +56,7 @@ export async function fetchTransactions(): Promise<TransactionSummary[]> {
|
||||
return data.items;
|
||||
} catch (error) {
|
||||
console.warn("[Explorer] Failed to fetch live transaction data", error);
|
||||
notifyError("Unable to load live transaction data. Displaying placeholders.");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -73,6 +76,7 @@ export async function fetchAddresses(): Promise<AddressSummary[]> {
|
||||
return Array.isArray(data) ? data : data.items;
|
||||
} catch (error) {
|
||||
console.warn("[Explorer] Failed to fetch live address data", error);
|
||||
notifyError("Unable to load live address data. Displaying placeholders.");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -92,6 +96,7 @@ export async function fetchReceipts(): Promise<ReceiptSummary[]> {
|
||||
return data.items;
|
||||
} catch (error) {
|
||||
console.warn("[Explorer] Failed to fetch live receipt data", error);
|
||||
notifyError("Unable to load live receipt data. Displaying placeholders.");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -107,6 +112,7 @@ async function fetchMock<T>(resource: string): Promise<T> {
|
||||
return (await response.json()) as T;
|
||||
} catch (error) {
|
||||
console.warn(`[Explorer] Failed to fetch mock data from ${url}`, error);
|
||||
notifyError("Mock data is unavailable. Please verify development assets.");
|
||||
return [] as unknown as T;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { addressesTitle, renderAddressesPage, initAddressesPage } from "./pages/
|
||||
import { receiptsTitle, renderReceiptsPage, initReceiptsPage } from "./pages/receipts";
|
||||
import { initDataModeToggle } from "./components/dataModeToggle";
|
||||
import { getDataMode } from "./lib/mockData";
|
||||
import { initNotifications } from "./components/notifications";
|
||||
|
||||
type PageConfig = {
|
||||
title: string;
|
||||
@@ -49,14 +50,13 @@ const routes: Record<string, PageConfig> = {
|
||||
};
|
||||
|
||||
function render(): void {
|
||||
initNotifications();
|
||||
const root = document.querySelector<HTMLDivElement>("#app");
|
||||
if (!root) {
|
||||
console.warn("[Explorer] Missing #app root element");
|
||||
return;
|
||||
}
|
||||
|
||||
document.documentElement.dataset.mode = getDataMode();
|
||||
|
||||
const currentPath = window.location.pathname.replace(/\/$/, "");
|
||||
const normalizedPath = currentPath === "" ? "/" : currentPath;
|
||||
const page = routes[normalizedPath] ?? null;
|
||||
|
||||
@@ -40,7 +40,6 @@ export async function initOverviewPage(): Promise<void> {
|
||||
fetchTransactions(),
|
||||
fetchReceipts(),
|
||||
]);
|
||||
|
||||
const blockStats = document.querySelector<HTMLUListElement>(
|
||||
"#overview-block-stats",
|
||||
);
|
||||
@@ -54,13 +53,12 @@ export async function initOverviewPage(): Promise<void> {
|
||||
<li><strong>Time:</strong> ${new Date(latest.timestamp).toLocaleString()}</li>
|
||||
`;
|
||||
} else {
|
||||
blockStats.innerHTML = `<li class="placeholder">No mock block data available.</li>`;
|
||||
blockStats.innerHTML = `
|
||||
<li class="placeholder">No blocks available. Try switching data mode.</li>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
const txStats = document.querySelector<HTMLUListElement>(
|
||||
"#overview-transaction-stats",
|
||||
);
|
||||
const txStats = document.querySelector<HTMLUListElement>("#overview-transaction-stats");
|
||||
if (txStats) {
|
||||
if (transactions.length > 0) {
|
||||
const succeeded = transactions.filter((tx) => tx.status === "Succeeded");
|
||||
@@ -70,7 +68,7 @@ export async function initOverviewPage(): Promise<void> {
|
||||
<li><strong>Pending:</strong> ${transactions.length - succeeded.length}</li>
|
||||
`;
|
||||
} else {
|
||||
txStats.innerHTML = `<li class="placeholder">No mock transaction data available.</li>`;
|
||||
txStats.innerHTML = `<li class="placeholder">No transactions available. Try switching data mode.</li>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +84,7 @@ export async function initOverviewPage(): Promise<void> {
|
||||
<li><strong>Pending:</strong> ${receipts.length - attested.length}</li>
|
||||
`;
|
||||
} else {
|
||||
receiptStats.innerHTML = `<li class="placeholder">No mock receipt data available.</li>`;
|
||||
receiptStats.innerHTML = `<li class="placeholder">No receipts available. Try switching data mode.</li>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user