| (function () { |
| 'use strict'; |
|
|
| const API = { |
| providers: '/api/max/providers/status?symbol=BTC', |
| resources: '/api/max/resources/summary', |
| pages: '/api/max/pages/functionality', |
| market: '/api/max/market/snapshot?symbols=BTC,ETH,SOL,BNB,XRP,DOGE,AVAX', |
| sentiment: '/api/max/social/sentiment?coin=BTC', |
| kucoin: '/api/max/futures/kucoin/validate/BTCUSDT', |
| pressure: '/api/max/pressure/status', |
| fallbackPlan: '/api/max/fallback/plan', |
| systemVisual: '/api/max/system/visual?symbol=BTC', |
| help: '/api/max/help' |
| }; |
|
|
| function panel() { |
| return window.TopServicePanel; |
| } |
|
|
| function refreshSummary() { |
| const tsp = panel(); |
| if (!tsp) return; |
| const parts = window._topServiceSummary || {}; |
| tsp.setSummary(tsp.buildSummary([ |
| `Page: ${tsp.pageId()}`, |
| parts.bridge, |
| parts.native |
| ])); |
| } |
|
|
| function css() { |
| if (document.getElementById('max-provider-bridge-style')) return; |
| const style = document.createElement('style'); |
| style.id = 'max-provider-bridge-style'; |
| style.textContent = ` |
| .max-provider-bridge{padding:16px;border:1px solid rgba(148,163,184,.28);border-radius:18px;background:linear-gradient(135deg,rgba(255,255,255,.90),rgba(240,249,255,.75));backdrop-filter:blur(14px);font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;color:#0f172a;} |
| .max-provider-bridge__head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px;} |
| .max-provider-bridge__title{font-size:14px;font-weight:800;letter-spacing:.02em;display:flex;align-items:center;gap:8px;} |
| .max-provider-bridge__subtitle{font-size:11px;color:#64748b;margin-top:2px;} |
| .max-provider-bridge__grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;} |
| .max-provider-bridge__card{padding:12px;border-radius:14px;background:rgba(255,255,255,.76);border:1px solid rgba(226,232,240,.85);min-height:74px;} |
| .max-provider-bridge__label{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:#64748b;font-weight:800;} |
| .max-provider-bridge__value{font-size:22px;font-weight:900;margin-top:4px;color:#111827;} |
| .max-provider-bridge__value.small{font-size:13px;line-height:1.35;word-break:break-word;} |
| .max-provider-bridge__pill{display:inline-flex;align-items:center;gap:6px;padding:5px 9px;border-radius:999px;font-size:10px;font-weight:800;border:1px solid rgba(16,185,129,.26);background:#ecfdf5;color:#047857;} |
| .max-provider-bridge__dot{width:7px;height:7px;border-radius:999px;background:#10b981;box-shadow:0 0 0 4px rgba(16,185,129,.14);animation:maxProviderPulse 1.8s ease-in-out infinite;} |
| .max-provider-bridge__links{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px;} |
| .max-provider-bridge__links a{font-size:11px;font-weight:700;text-decoration:none;color:#2563eb;background:#eff6ff;border:1px solid #bfdbfe;border-radius:999px;padding:5px 9px;} |
| .max-provider-bridge__warn{font-size:11px;color:#92400e;background:#fffbeb;border:1px solid #fde68a;border-radius:10px;padding:8px 10px;margin-top:10px;} |
| @keyframes maxProviderPulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.48;transform:scale(.82)}} |
| @media(max-width:900px){.max-provider-bridge__grid{grid-template-columns:repeat(2,minmax(0,1fr));}.max-provider-bridge{padding:12px}} |
| @media(max-width:540px){.max-provider-bridge__grid{grid-template-columns:1fr}} |
| `; |
| document.head.appendChild(style); |
| } |
|
|
| async function getJson(url) { |
| const controller = new AbortController(); |
| const timer = setTimeout(() => controller.abort(), 12000); |
| try { |
| const res = await fetch(url, { signal: controller.signal, headers: { Accept: 'application/json' } }); |
| if (!res.ok) throw new Error(`HTTP ${res.status}`); |
| return await res.json(); |
| } finally { |
| clearTimeout(timer); |
| } |
| } |
|
|
| function render(state) { |
| css(); |
| const tsp = panel(); |
| if (!tsp || document.getElementById('max-provider-bridge-section')) return; |
|
|
| const providers = state.providers || {}; |
| const resources = state.resources || {}; |
| const market = state.market || {}; |
| const sentiment = state.sentiment || {}; |
| const kucoin = state.kucoin || {}; |
| const prices = Array.isArray(market.items) ? market.items.filter(x => x && x.success) : []; |
| const bestPrice = prices[0]; |
| const topSource = bestPrice |
| ? `${bestPrice.symbol || 'BTC'} ${bestPrice.price ? '$' + Number(bestPrice.price).toLocaleString() : ''} · ${bestPrice.source || 'source'}` |
| : 'No live market response yet'; |
| const fng = sentiment.fear_greed && sentiment.fear_greed.ok ? sentiment.fear_greed.data : null; |
|
|
| const html = ` |
| <section id="max-provider-bridge" class="max-provider-bridge"> |
| <div class="max-provider-bridge__head"> |
| <div> |
| <div class="max-provider-bridge__title">⚡ Live Provider Bridge <span class="max-provider-bridge__pill"><span class="max-provider-bridge__dot"></span> functional layer</span></div> |
| <div class="max-provider-bridge__subtitle">Connected to the maximum-provider API layer. Page: <strong>${tsp.pageId()}</strong></div> |
| </div> |
| <div class="max-provider-bridge__pill">/api/max</div> |
| </div> |
| <div class="max-provider-bridge__grid"> |
| <div class="max-provider-bridge__card"><div class="max-provider-bridge__label">Providers</div><div class="max-provider-bridge__value">${providers.healthy_count || 0}/${providers.probed_count || 0}</div></div> |
| <div class="max-provider-bridge__card"><div class="max-provider-bridge__label">Registry</div><div class="max-provider-bridge__value">${((resources.summary || {}).comprehensive_resources || 0) + ((resources.summary || {}).unified_registry_entries || 0)}</div></div> |
| <div class="max-provider-bridge__card"><div class="max-provider-bridge__label">Market sample</div><div class="max-provider-bridge__value small">${topSource}</div></div> |
| <div class="max-provider-bridge__card"><div class="max-provider-bridge__label">Futures / Sentiment</div><div class="max-provider-bridge__value small">KuCoin: ${kucoin.validated ? 'validated' : 'not validated yet'}<br>F&G: ${fng ? `${fng.value} ${fng.classification || ''}` : 'pending'}</div></div> |
| </div> |
| <div class="max-provider-bridge__links"> |
| <a href="/api/max/providers/status?include_slow=true" target="_blank" rel="noreferrer">All provider health</a> |
| <a href="/api/max/fallback/plan" target="_blank" rel="noreferrer">Fallback plan</a> |
| <a href="/api/max/pressure/status" target="_blank" rel="noreferrer">Pressure status</a> |
| <a href="/api/max/resources/summary" target="_blank" rel="noreferrer">Resource registry</a> |
| <a href="/api/max/market/snapshot" target="_blank" rel="noreferrer">Market snapshot</a> |
| <a href="/api/max/trading/ohlcv/BTCUSDT" target="_blank" rel="noreferrer">BTC OHLCV</a> |
| <a href="/api/max/trading/orderbook/BTCUSDT" target="_blank" rel="noreferrer">BTC orderbook</a> |
| <a href="/api/max/futures/kucoin/validate/BTCUSDT" target="_blank" rel="noreferrer">KuCoin futures</a> |
| <a href="/api/max/system/visual?symbol=BTC" target="_blank" rel="noreferrer">System visual API</a> |
| <a href="/api/max/help" target="_blank" rel="noreferrer">Max API help</a> |
| </div> |
| ${providers.down_count ? `<div class="max-provider-bridge__warn">${providers.down_count} provider probe(s) failed on this check; fallbacks remain available through the chain.</div>` : ''} |
| </section> |
| `; |
|
|
| tsp.mountSection('max-provider-bridge-section', '', html); |
| window._topServiceSummary = window._topServiceSummary || {}; |
| window._topServiceSummary.bridge = `Providers ${providers.healthy_count || 0}/${providers.probed_count || 0}`; |
| refreshSummary(); |
| } |
|
|
| async function boot() { |
| const tsp = panel(); |
| if (!tsp || tsp.shouldSkip()) return; |
|
|
| tsp.ensure(`Page: ${tsp.pageId()} · Loading…`); |
|
|
| const state = {}; |
| const jobs = [ |
| ['providers', API.providers], |
| ['resources', API.resources], |
| ['pages', API.pages], |
| ['market', API.market], |
| ['sentiment', API.sentiment], |
| ['kucoin', API.kucoin] |
| ]; |
| await Promise.all(jobs.map(async ([key, url]) => { |
| try { state[key] = await getJson(url); } |
| catch (error) { state[key] = { success: false, error: String(error && error.message || error) }; } |
| })); |
| render(state); |
| window.MAX_PROVIDER_BRIDGE_STATE = state; |
| } |
|
|
| function start() { |
| if (!window.TopServicePanel) { |
| console.warn('TopServicePanel not loaded; bridge panel skipped'); |
| return; |
| } |
| boot(); |
| } |
|
|
| if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); |
| else start(); |
| })(); |
|
|