refactor: consolidate blockchain explorer into single app and update backup ignore patterns

- Remove standalone explorer-web app (README, HTML, package files)
- Add /web endpoint to blockchain-explorer for web interface access
- Update .gitignore to exclude application backup archives (*.tar.gz, *.zip)
- Add backup documentation files to .gitignore (BACKUP_INDEX.md, README.md)
- Consolidate explorer functionality into main blockchain-explorer application
This commit is contained in:
oib
2026-03-06 18:14:49 +01:00
parent dc1561d457
commit bb5363bebc
295 changed files with 35501 additions and 3734 deletions

View File

@@ -0,0 +1,132 @@
import { loadSession } from "./auth";
export type DataMode = "mock" | "live";
interface OfferRecord {
id: string;
provider: string;
capacity: number;
price: number;
sla: string;
status: string;
created_at?: string;
gpu_model?: string;
gpu_memory_gb?: number;
gpu_count?: number;
cuda_version?: string;
price_per_hour?: number;
region?: string;
attributes?: {
ollama_host?: string;
models?: string[];
vram_mb?: number;
driver?: string;
[key: string]: unknown;
};
}
interface OffersResponse {
offers: OfferRecord[];
}
export interface MarketplaceStats {
totalOffers: number;
openCapacity: number;
averagePrice: number;
activeBids: number;
}
export interface MarketplaceOffer extends OfferRecord {}
const CONFIG = {
dataMode: (import.meta.env?.VITE_MARKETPLACE_DATA_MODE as DataMode) ?? "mock",
mockBase: "/mock",
apiBase: import.meta.env?.VITE_MARKETPLACE_API ?? "http://localhost:8081",
enableBids:
(import.meta.env?.VITE_MARKETPLACE_ENABLE_BIDS ?? "true").toLowerCase() !==
"false",
requireAuth:
(import.meta.env?.VITE_MARKETPLACE_REQUIRE_AUTH ?? "false").toLowerCase() ===
"true",
};
function buildHeaders(): HeadersInit {
const headers: Record<string, string> = {
"Cache-Control": "no-cache",
};
const session = loadSession();
if (session) {
headers.Authorization = `Bearer ${session.token}`;
}
return headers;
}
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(path, {
...init,
headers: {
...buildHeaders(),
...init?.headers,
},
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json() as Promise<T>;
}
export async function fetchMarketplaceStats(): Promise<MarketplaceStats> {
if (CONFIG.dataMode === "mock") {
return request<MarketplaceStats>(`${CONFIG.mockBase}/stats.json`);
}
return request<MarketplaceStats>(`${CONFIG.apiBase}/marketplace/stats`);
}
export async function fetchMarketplaceOffers(): Promise<MarketplaceOffer[]> {
if (CONFIG.dataMode === "mock") {
const payload = await request<OffersResponse>(`${CONFIG.mockBase}/offers.json`);
return payload.offers;
}
return request<MarketplaceOffer[]>(`${CONFIG.apiBase}/marketplace/offers`);
}
export async function submitMarketplaceBid(input: {
provider: string;
capacity: number;
price: number;
notes?: string;
}): Promise<void> {
if (!CONFIG.enableBids) {
throw new Error("Bid submissions are disabled by configuration");
}
if (CONFIG.dataMode === "mock") {
await new Promise((resolve) => setTimeout(resolve, 600));
return;
}
if (CONFIG.requireAuth && !loadSession()) {
throw new Error("Authentication required to submit bids");
}
const response = await fetch(`${CONFIG.apiBase}/marketplace/bids`, {
method: "POST",
headers: {
"Content-Type": "application/json",
...buildHeaders(),
},
body: JSON.stringify(input),
});
if (!response.ok) {
const message = await response.text();
throw new Error(message || "Failed to submit bid");
}
}
export const MARKETPLACE_CONFIG = CONFIG;

View File

@@ -0,0 +1,33 @@
export interface MarketplaceSession {
token: string;
expiresAt: number;
}
const STORAGE_KEY = "marketplace-session";
export function saveSession(session: MarketplaceSession): void {
localStorage.setItem(STORAGE_KEY, JSON.stringify(session));
}
export function loadSession(): MarketplaceSession | null {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) {
return null;
}
try {
const data = JSON.parse(raw) as MarketplaceSession;
if (typeof data.token === "string" && typeof data.expiresAt === "number") {
if (data.expiresAt > Date.now()) {
return data;
}
clearSession();
}
} catch (error) {
console.warn("Failed to parse stored marketplace session", error);
}
return null;
}
export function clearSession(): void {
localStorage.removeItem(STORAGE_KEY);
}

View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}