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 c1926136fb
commit fb60505cdf
189 changed files with 15678 additions and 158 deletions

View File

@ -6,6 +6,45 @@
z-index: 1000;
}
@media (max-width: 600px) {
.page {
padding: 1.5rem 1rem 3rem;
}
.site-header__inner {
flex-direction: column;
align-items: flex-start;
}
.site-header__controls {
align-items: stretch;
gap: 0.5rem;
}
.site-header__nav {
gap: 0.5rem;
}
.site-header__nav a {
flex: 1 1 45%;
text-align: center;
}
.addresses__input-group,
.receipts__input-group {
flex-direction: column;
}
.toast-container {
left: 0;
right: 0;
top: auto;
bottom: 1rem;
width: min(90vw, 360px);
margin: 0 auto;
}
}
.site-header__inner {
margin: 0 auto;
max-width: 1200px;
@ -80,6 +119,37 @@
padding: 2rem 1.5rem 4rem;
}
.toast-container {
position: fixed;
top: 1.25rem;
right: 1.25rem;
display: grid;
gap: 0.75rem;
z-index: 1200;
}
.toast {
opacity: 0;
transform: translateY(-6px);
transition: opacity 150ms ease, transform 180ms ease;
border-radius: 0.75rem;
padding: 0.75rem 1rem;
font-size: 0.9rem;
min-width: 220px;
}
.toast--error {
background: rgba(255, 102, 102, 0.16);
border: 1px solid rgba(255, 102, 102, 0.35);
color: #ffd3d3;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35);
}
.toast.is-visible {
opacity: 1;
transform: translateY(0px);
}
@media (max-width: 768px) {
.site-header__inner {
justify-content: space-between;

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