```
chore: refactor logging module, update genesis timestamp, remove model relationships, and reorganize routers - Rename logging.py to logger.py and update import paths in poa.py and main.py - Update devnet genesis timestamp to 1766828620 - Remove SQLModel Relationship declarations from Block, Transaction, and Receipt models - Add SessionDep type alias and get_session dependency in coordinator-api deps - Reorganize coordinator-api routers: replace explorer/registry with exchange, users, marketplace
This commit is contained in:
@ -262,6 +262,16 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.stat-list li {
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.stat-list li code {
|
||||
word-break: break-all;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.stat-list li + li {
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
|
||||
@ -8,24 +8,29 @@ const LABELS: Record<DataMode, string> = {
|
||||
|
||||
export function initDataModeToggle(onChange: () => void): void {
|
||||
const container = document.querySelector<HTMLDivElement>("[data-role='data-mode-toggle']");
|
||||
if (!container) {
|
||||
return;
|
||||
if (!container) return;
|
||||
|
||||
const currentMode = getDataMode();
|
||||
const isLive = currentMode === "live";
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="data-mode-toggle">
|
||||
<span class="mode-label">Data Mode:</span>
|
||||
<button class="mode-button ${isLive ? "live" : "mock"}" id="dataModeBtn">
|
||||
${isLive ? "Live API" : "Mock Data"}
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const btn = document.getElementById("dataModeBtn") as HTMLButtonElement;
|
||||
if (btn) {
|
||||
btn.addEventListener("click", () => {
|
||||
const newMode = getDataMode() === "live" ? "mock" : "live";
|
||||
setDataMode(newMode);
|
||||
// Reload the page to refresh data
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
container.innerHTML = renderControls(getDataMode());
|
||||
|
||||
const select = container.querySelector<HTMLSelectElement>("select[data-mode-select]");
|
||||
if (!select) {
|
||||
return;
|
||||
}
|
||||
|
||||
select.value = getDataMode();
|
||||
select.addEventListener("change", (event) => {
|
||||
const value = (event.target as HTMLSelectElement).value as DataMode;
|
||||
setDataMode(value);
|
||||
document.documentElement.dataset.mode = value;
|
||||
onChange();
|
||||
});
|
||||
}
|
||||
|
||||
function renderControls(mode: DataMode): string {
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
export function siteHeader(title: string): string {
|
||||
const basePath = window.location.pathname.startsWith('/explorer') ? '/explorer' : '';
|
||||
|
||||
return `
|
||||
<header class="site-header">
|
||||
<div class="site-header__inner">
|
||||
<a class="site-header__brand" href="/">AITBC Explorer</a>
|
||||
<h1 class="site-header__title">${title}</h1>
|
||||
<a class="site-header__brand" href="${basePath}/">AITBC Explorer</a>
|
||||
<div class="site-header__controls">
|
||||
<div data-role="data-mode-toggle"></div>
|
||||
</div>
|
||||
<nav class="site-header__nav">
|
||||
<a href="/">Overview</a>
|
||||
<a href="/blocks">Blocks</a>
|
||||
<a href="/transactions">Transactions</a>
|
||||
<a href="/addresses">Addresses</a>
|
||||
<a href="/receipts">Receipts</a>
|
||||
<a href="${basePath}/">Overview</a>
|
||||
<a href="${basePath}/blocks">Blocks</a>
|
||||
<a href="${basePath}/transactions">Transactions</a>
|
||||
<a href="${basePath}/addresses">Addresses</a>
|
||||
<a href="${basePath}/receipts">Receipts</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@ -7,8 +7,10 @@ export interface ExplorerConfig {
|
||||
}
|
||||
|
||||
export const CONFIG: ExplorerConfig = {
|
||||
// Toggle between "mock" (static JSON under public/mock/) and "live" coordinator APIs.
|
||||
dataMode: (import.meta.env?.VITE_DATA_MODE as DataMode) ?? "mock",
|
||||
// Base URL for the coordinator API
|
||||
apiBaseUrl: "https://aitbc.bubuit.net/api",
|
||||
// Base path for mock data files (used by fetchMock)
|
||||
mockBasePath: "/explorer/mock",
|
||||
apiBaseUrl: import.meta.env?.VITE_COORDINATOR_API ?? "http://localhost:8000",
|
||||
// Default data mode: "live" or "mock"
|
||||
dataMode: "live" as "live" | "mock",
|
||||
};
|
||||
|
||||
@ -63,7 +63,7 @@ export async function fetchBlocks(): Promise<BlockSummary[]> {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${CONFIG.apiBaseUrl}/v1/explorer/blocks`);
|
||||
const response = await fetch(`${CONFIG.apiBaseUrl}/explorer/blocks`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch blocks: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
@ -71,8 +71,12 @@ export async function fetchBlocks(): Promise<BlockSummary[]> {
|
||||
return data.items;
|
||||
} catch (error) {
|
||||
console.error("[Explorer] Failed to fetch live block data", error);
|
||||
notifyError("Unable to load live block data from coordinator. Showing placeholders.");
|
||||
return [];
|
||||
notifyError("Unable to load live data. Switching to mock data mode.");
|
||||
// Auto-switch to mock mode
|
||||
setDataMode("mock");
|
||||
// Return mock data
|
||||
const data = await fetchMock<BlockListResponse>("blocks");
|
||||
return data.items;
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,7 +87,7 @@ export async function fetchTransactions(): Promise<TransactionSummary[]> {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${CONFIG.apiBaseUrl}/v1/explorer/transactions`);
|
||||
const response = await fetch(`${CONFIG.apiBaseUrl}/explorer/transactions`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch transactions: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
@ -91,8 +95,12 @@ export async function fetchTransactions(): Promise<TransactionSummary[]> {
|
||||
return data.items;
|
||||
} catch (error) {
|
||||
console.error("[Explorer] Failed to fetch live transaction data", error);
|
||||
notifyError("Unable to load transactions from coordinator. Showing placeholders.");
|
||||
return [];
|
||||
notifyError("Unable to load live data. Switching to mock data mode.");
|
||||
// Auto-switch to mock mode
|
||||
setDataMode("mock");
|
||||
// Return mock data
|
||||
const data = await fetchMock<TransactionListResponse>("transactions");
|
||||
return data.items;
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +111,7 @@ export async function fetchAddresses(): Promise<AddressSummary[]> {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${CONFIG.apiBaseUrl}/v1/explorer/addresses`);
|
||||
const response = await fetch(`${CONFIG.apiBaseUrl}/explorer/addresses`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch addresses: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
@ -111,8 +119,12 @@ export async function fetchAddresses(): Promise<AddressSummary[]> {
|
||||
return data.items;
|
||||
} catch (error) {
|
||||
console.error("[Explorer] Failed to fetch live address data", error);
|
||||
notifyError("Unable to load address summaries from coordinator. Showing placeholders.");
|
||||
return [];
|
||||
notifyError("Unable to load live data. Switching to mock data mode.");
|
||||
// Auto-switch to mock mode
|
||||
setDataMode("mock");
|
||||
// Return mock data
|
||||
const data = await fetchMock<AddressDetailResponse | AddressDetailResponse[]>("addresses");
|
||||
return Array.isArray(data) ? data : [data];
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +135,7 @@ export async function fetchReceipts(): Promise<ReceiptSummary[]> {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${CONFIG.apiBaseUrl}/v1/explorer/receipts`);
|
||||
const response = await fetch(`${CONFIG.apiBaseUrl}/explorer/receipts`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch receipts: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
@ -131,8 +143,12 @@ export async function fetchReceipts(): Promise<ReceiptSummary[]> {
|
||||
return data.items;
|
||||
} catch (error) {
|
||||
console.error("[Explorer] Failed to fetch live receipt data", error);
|
||||
notifyError("Unable to load receipts from coordinator. Showing placeholders.");
|
||||
return [];
|
||||
notifyError("Unable to load live data. Switching to mock data mode.");
|
||||
// Auto-switch to mock mode
|
||||
setDataMode("mock");
|
||||
// Return mock data
|
||||
const data = await fetchMock<ReceiptListResponse>("receipts");
|
||||
return data.items;
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,6 +164,10 @@ async function fetchMock<T>(resource: string): Promise<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;
|
||||
// Return proper empty structure based on expected response type
|
||||
if (resource === "addresses") {
|
||||
return [] as unknown as T;
|
||||
}
|
||||
return { items: [] } as unknown as T;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { fetchAddresses, type AddressSummary } from "../lib/mockData";
|
||||
import { fetchAddresses } from "../lib/mockData";
|
||||
import type { AddressSummary } from "../lib/models";
|
||||
|
||||
export const addressesTitle = "Addresses";
|
||||
|
||||
@ -48,7 +49,7 @@ export async function initAddressesPage(): Promise<void> {
|
||||
}
|
||||
|
||||
const addresses = await fetchAddresses();
|
||||
if (addresses.length === 0) {
|
||||
if (!addresses || addresses.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td class="placeholder" colspan="4">No mock addresses available.</td>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { fetchBlocks, type BlockSummary } from "../lib/mockData";
|
||||
import { fetchBlocks } from "../lib/mockData";
|
||||
import type { BlockSummary } from "../lib/models";
|
||||
|
||||
export const blocksTitle = "Blocks";
|
||||
|
||||
@ -38,7 +39,7 @@ export async function initBlocksPage(): Promise<void> {
|
||||
}
|
||||
|
||||
const blocks = await fetchBlocks();
|
||||
if (blocks.length === 0) {
|
||||
if (!blocks || blocks.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td class="placeholder" colspan="5">No mock blocks available.</td>
|
||||
|
||||
@ -44,12 +44,12 @@ export async function initOverviewPage(): Promise<void> {
|
||||
"#overview-block-stats",
|
||||
);
|
||||
if (blockStats) {
|
||||
if (blocks.length > 0) {
|
||||
if (blocks && blocks.length > 0) {
|
||||
const latest = blocks[0];
|
||||
blockStats.innerHTML = `
|
||||
<li><strong>Height:</strong> ${latest.height}</li>
|
||||
<li><strong>Hash:</strong> ${latest.hash.slice(0, 18)}…</li>
|
||||
<li><strong>Proposer:</strong> ${latest.proposer}</li>
|
||||
<li><strong>Proposer:</strong> <code>${latest.proposer.slice(0, 18)}…</code></li>
|
||||
<li><strong>Time:</strong> ${new Date(latest.timestamp).toLocaleString()}</li>
|
||||
`;
|
||||
} else {
|
||||
@ -60,7 +60,7 @@ export async function initOverviewPage(): Promise<void> {
|
||||
}
|
||||
const txStats = document.querySelector<HTMLUListElement>("#overview-transaction-stats");
|
||||
if (txStats) {
|
||||
if (transactions.length > 0) {
|
||||
if (transactions && transactions.length > 0) {
|
||||
const succeeded = transactions.filter((tx) => tx.status === "Succeeded");
|
||||
txStats.innerHTML = `
|
||||
<li><strong>Total Mock Tx:</strong> ${transactions.length}</li>
|
||||
@ -76,7 +76,7 @@ export async function initOverviewPage(): Promise<void> {
|
||||
"#overview-receipt-stats",
|
||||
);
|
||||
if (receiptStats) {
|
||||
if (receipts.length > 0) {
|
||||
if (receipts && receipts.length > 0) {
|
||||
const attested = receipts.filter((receipt) => receipt.status === "Attested");
|
||||
receiptStats.innerHTML = `
|
||||
<li><strong>Total Receipts:</strong> ${receipts.length}</li>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { fetchReceipts, type ReceiptSummary } from "../lib/mockData";
|
||||
import { fetchReceipts } from "../lib/mockData";
|
||||
import type { ReceiptSummary } from "../lib/models";
|
||||
|
||||
export const receiptsTitle = "Receipts";
|
||||
|
||||
@ -50,7 +51,7 @@ export async function initReceiptsPage(): Promise<void> {
|
||||
}
|
||||
|
||||
const receipts = await fetchReceipts();
|
||||
if (receipts.length === 0) {
|
||||
if (!receipts || receipts.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td class="placeholder" colspan="6">No mock receipts available.</td>
|
||||
@ -65,7 +66,7 @@ export async function initReceiptsPage(): Promise<void> {
|
||||
function renderReceiptRow(receipt: ReceiptSummary): string {
|
||||
return `
|
||||
<tr>
|
||||
<td><code>${receipt.jobId}</code></td>
|
||||
<td><code>N/A</code></td>
|
||||
<td><code>${receipt.receiptId}</code></td>
|
||||
<td>${receipt.miner}</td>
|
||||
<td>${receipt.coordinator}</td>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
fetchTransactions,
|
||||
type TransactionSummary,
|
||||
} from "../lib/mockData";
|
||||
import type { TransactionSummary } from "../lib/models";
|
||||
|
||||
export const transactionsTitle = "Transactions";
|
||||
|
||||
@ -42,7 +42,7 @@ export async function initTransactionsPage(): Promise<void> {
|
||||
}
|
||||
|
||||
const transactions = await fetchTransactions();
|
||||
if (transactions.length === 0) {
|
||||
if (!transactions || transactions.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td class="placeholder" colspan="6">No mock transactions available.</td>
|
||||
@ -60,7 +60,7 @@ function renderTransactionRow(tx: TransactionSummary): string {
|
||||
<td><code>${tx.hash.slice(0, 18)}…</code></td>
|
||||
<td>${tx.block}</td>
|
||||
<td><code>${tx.from.slice(0, 12)}…</code></td>
|
||||
<td><code>${tx.to.slice(0, 12)}…</code></td>
|
||||
<td><code>${tx.to ? tx.to.slice(0, 12) + '…' : 'null'}</code></td>
|
||||
<td>${tx.value}</td>
|
||||
<td>${tx.status}</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user