feat: add foreign key constraints and metrics for blockchain node

This commit is contained in:
oib
2025-09-28 06:04:30 +02:00
parent fe29631a86
commit b8b640666d
188 changed files with 15678 additions and 158 deletions

View 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);
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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>`;
}
}
}