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:
132
apps/marketplace/src/lib/api.ts
Normal file
132
apps/marketplace/src/lib/api.ts
Normal 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;
|
||||
33
apps/marketplace/src/lib/auth.ts
Normal file
33
apps/marketplace/src/lib/auth.ts
Normal 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);
|
||||
}
|
||||
6
apps/marketplace/src/lib/utils.ts
Normal file
6
apps/marketplace/src/lib/utils.ts
Normal 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))
|
||||
}
|
||||
Reference in New Issue
Block a user