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:
oib
2025-12-28 21:05:53 +01:00
parent cdaf1122c3
commit ff5486fe08
146 changed files with 33301 additions and 219 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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