|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class ConfigHelperModal { |
|
|
constructor() { |
|
|
this.modal = null; |
|
|
this.services = this.getServicesConfig(); |
|
|
} |
|
|
|
|
|
getServicesConfig() { |
|
|
const baseUrl = window.location.origin; |
|
|
|
|
|
return [ |
|
|
|
|
|
{ |
|
|
name: 'Discovery & Health', |
|
|
category: 'Getting Started', |
|
|
description: 'Verify the server is online and discover all available endpoints', |
|
|
endpoints: [ |
|
|
{ method: 'GET', path: '/api/health', desc: 'Health check' }, |
|
|
{ method: 'GET', path: '/api/status', desc: 'System status' }, |
|
|
{ method: 'GET', path: '/api/routers', desc: 'Loaded routers status' }, |
|
|
{ method: 'GET', path: '/api/endpoints', desc: 'Full endpoints list (grouped)' }, |
|
|
{ method: 'GET', path: '/docs', desc: 'Swagger UI documentation' } |
|
|
], |
|
|
example: `// Health check |
|
|
fetch('${baseUrl}/api/health') |
|
|
.then(res => res.json()) |
|
|
.then(console.log); |
|
|
|
|
|
// Get full endpoints list |
|
|
fetch('${baseUrl}/api/endpoints') |
|
|
.then(res => res.json()) |
|
|
.then(data => console.log('Total:', data.total_endpoints));` |
|
|
}, |
|
|
|
|
|
|
|
|
{ |
|
|
name: 'Unified Service API', |
|
|
category: 'Core Services', |
|
|
description: 'Single entry point for all cryptocurrency data needs', |
|
|
endpoints: [ |
|
|
{ method: 'GET', path: '/api/service/rate?pair=BTC/USDT', desc: 'Get exchange rate' }, |
|
|
{ method: 'GET', path: '/api/service/rate/batch?pairs=BTC/USDT,ETH/USDT', desc: 'Multiple rates' }, |
|
|
{ method: 'GET', path: '/api/service/history?symbol=BTC&interval=60&limit=200', desc: 'Historical OHLC (minutes interval)' }, |
|
|
{ method: 'GET', path: '/api/service/market-status', desc: 'Market overview' }, |
|
|
{ method: 'GET', path: '/api/service/top?n=10', desc: 'Top cryptocurrencies' }, |
|
|
{ method: 'GET', path: '/api/service/sentiment?symbol=BTC', desc: 'Get sentiment' }, |
|
|
{ method: 'GET', path: '/api/service/whales?chain=ethereum&min_amount_usd=1000000', desc: 'Whale transactions (service)' }, |
|
|
{ method: 'GET', path: '/api/service/onchain?address=0x...&chain=ethereum', desc: 'On-chain data (service)' }, |
|
|
{ method: 'POST', path: '/api/service/query', desc: 'Universal query endpoint' } |
|
|
], |
|
|
example: `// Get BTC price |
|
|
fetch('${baseUrl}/api/service/rate?pair=BTC/USDT') |
|
|
.then(res => res.json()) |
|
|
.then(data => console.log('BTC Price:', data.data.price)); |
|
|
|
|
|
// Get multiple prices |
|
|
fetch('${baseUrl}/api/service/rate/batch?pairs=BTC/USDT,ETH/USDT,BNB/USDT') |
|
|
.then(res => res.json()) |
|
|
.then(data => data.data.forEach(r => console.log(r.pair + ': $' + r.price)));` |
|
|
}, |
|
|
|
|
|
|
|
|
{ |
|
|
name: 'Market Data API', |
|
|
category: 'Market Data', |
|
|
description: 'Real-time prices, OHLC/OHLCV, and market statistics', |
|
|
endpoints: [ |
|
|
{ method: 'GET', path: '/api/market?limit=100', desc: 'Market data with prices' }, |
|
|
{ method: 'GET', path: '/api/coins/top?limit=50', desc: 'Top coins by market cap' }, |
|
|
{ method: 'GET', path: '/api/trending', desc: 'Trending cryptocurrencies' }, |
|
|
{ method: 'GET', path: '/api/market/ohlc?symbol=BTC&timeframe=1h', desc: 'OHLC (multi-source, recommended)' }, |
|
|
{ method: 'GET', path: '/api/ohlcv?symbol=BTC&timeframe=1h&limit=100', desc: 'OHLCV (query-style)' }, |
|
|
{ method: 'GET', path: '/api/klines?symbol=BTCUSDT&interval=1h&limit=100', desc: 'Klines alias (Binance style)' }, |
|
|
{ method: 'GET', path: '/api/historical?symbol=BTC&days=30', desc: 'Daily historical candles (alias)' } |
|
|
], |
|
|
example: `// Get OHLCV data for charting |
|
|
fetch('${baseUrl}/api/ohlcv?symbol=BTC&timeframe=1h&limit=100') |
|
|
.then(res => res.json()) |
|
|
.then(data => { |
|
|
console.log('OHLCV data:', data.data); |
|
|
// Each candle: { timestamp, open, high, low, close, volume } |
|
|
});` |
|
|
}, |
|
|
|
|
|
|
|
|
{ |
|
|
name: 'News Aggregator API', |
|
|
category: 'News & Media', |
|
|
description: 'Crypto news from 9+ sources including RSS feeds', |
|
|
endpoints: [ |
|
|
{ method: 'GET', path: '/api/news/latest?limit=20', desc: 'Latest crypto news' }, |
|
|
{ method: 'GET', path: '/api/news?limit=20', desc: 'Alias for latest (compat)' } |
|
|
], |
|
|
example: `// Get latest news |
|
|
fetch('${baseUrl}/api/news/latest?limit=10') |
|
|
.then(res => res.json()) |
|
|
.then(data => { |
|
|
data.articles.forEach(article => { |
|
|
console.log(article.title, '-', article.source); |
|
|
}); |
|
|
});` |
|
|
}, |
|
|
|
|
|
|
|
|
{ |
|
|
name: 'Sentiment Analysis API', |
|
|
category: 'Sentiment', |
|
|
description: 'Fear & Greed Index, social sentiment, and AI-powered analysis', |
|
|
endpoints: [ |
|
|
{ method: 'GET', path: '/api/sentiment/global', desc: 'Global market sentiment' }, |
|
|
{ method: 'GET', path: '/api/fear-greed', desc: 'Fear & Greed Index (alias)' }, |
|
|
{ method: 'GET', path: '/api/sentiment/asset/{symbol}', desc: 'Asset-specific sentiment' }, |
|
|
{ method: 'POST', path: '/api/sentiment/analyze', desc: 'Analyze custom text' } |
|
|
], |
|
|
example: `// Get Fear & Greed Index |
|
|
fetch('${baseUrl}/api/fear-greed') |
|
|
.then(res => res.json()) |
|
|
.then(data => { |
|
|
console.log('Fear & Greed:', data.value, '-', data.classification); |
|
|
}); |
|
|
|
|
|
// Analyze text sentiment |
|
|
fetch('${baseUrl}/api/sentiment/analyze', { |
|
|
method: 'POST', |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
body: JSON.stringify({ text: 'Bitcoin is going to the moon!', mode: 'crypto' }) |
|
|
}) |
|
|
.then(res => res.json()) |
|
|
.then(data => console.log('Sentiment:', data.sentiment, data.score));` |
|
|
}, |
|
|
|
|
|
|
|
|
{ |
|
|
name: 'On-Chain Analytics API', |
|
|
category: 'Analytics', |
|
|
description: 'Blockchain data, whale tracking, and network statistics', |
|
|
endpoints: [ |
|
|
{ method: 'GET', path: '/api/service/whales?chain=ethereum&min_amount_usd=1000000&limit=20', desc: 'Whale transactions (service)' }, |
|
|
{ method: 'GET', path: '/api/service/onchain?address=0x...&chain=ethereum', desc: 'On-chain snapshot (service)' } |
|
|
], |
|
|
example: `// Get whale transactions |
|
|
fetch('${baseUrl}/api/service/whales?chain=ethereum&min_amount_usd=1000000&limit=20') |
|
|
.then(res => res.json()) |
|
|
.then(data => console.log(data));` |
|
|
}, |
|
|
|
|
|
|
|
|
{ |
|
|
name: 'Technical Analysis API', |
|
|
category: 'Analysis Services', |
|
|
description: '5 analysis modes: Quick TA, Fundamental, On-Chain, Risk, Comprehensive', |
|
|
endpoints: [ |
|
|
{ method: 'POST', path: '/api/technical/ta-quick', desc: 'Quick technical analysis' }, |
|
|
{ method: 'POST', path: '/api/technical/fa-eval', desc: 'Fundamental evaluation' }, |
|
|
{ method: 'POST', path: '/api/technical/onchain-health', desc: 'On-chain network health' }, |
|
|
{ method: 'POST', path: '/api/technical/risk-assessment', desc: 'Risk & volatility assessment' }, |
|
|
{ method: 'POST', path: '/api/technical/comprehensive', desc: 'Comprehensive analysis' } |
|
|
], |
|
|
example: `// Quick Technical Analysis |
|
|
const ohlcv = await fetch('${baseUrl}/api/ohlcv?symbol=BTC&timeframe=4h&limit=200') |
|
|
.then(r => r.json()).then(d => d.data); |
|
|
|
|
|
fetch('${baseUrl}/api/technical/ta-quick', { |
|
|
method: 'POST', |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
body: JSON.stringify({ |
|
|
symbol: 'BTC', |
|
|
timeframe: '4h', |
|
|
ohlcv: ohlcv |
|
|
}) |
|
|
}) |
|
|
.then(res => res.json()) |
|
|
.then(data => { |
|
|
console.log('Trend:', data.trend); |
|
|
console.log('RSI:', data.rsi); |
|
|
console.log('Entry Range:', data.entry_range); |
|
|
});` |
|
|
}, |
|
|
|
|
|
|
|
|
{ |
|
|
name: 'Indicator Services API', |
|
|
category: 'Analysis Services', |
|
|
description: 'Technical indicators (RSI, MACD, SMA/EMA, BB, ATR, StochRSI) + comprehensive signals', |
|
|
endpoints: [ |
|
|
{ method: 'GET', path: '/api/indicators/services', desc: 'List available indicator services' }, |
|
|
{ method: 'GET', path: '/api/indicators/rsi?symbol=BTC&timeframe=1h&period=14', desc: 'RSI' }, |
|
|
{ method: 'GET', path: '/api/indicators/macd?symbol=BTC&timeframe=1h', desc: 'MACD' }, |
|
|
{ method: 'GET', path: '/api/indicators/comprehensive?symbol=BTC&timeframe=1h', desc: 'Comprehensive indicator analysis' } |
|
|
], |
|
|
example: `// List indicator services |
|
|
fetch('${baseUrl}/api/indicators/services') |
|
|
.then(r => r.json()) |
|
|
.then(console.log); |
|
|
|
|
|
// RSI |
|
|
fetch('${baseUrl}/api/indicators/rsi?symbol=BTC&timeframe=1h&period=14') |
|
|
.then(r => r.json()) |
|
|
.then(console.log);` |
|
|
}, |
|
|
|
|
|
|
|
|
{ |
|
|
name: 'AI Models API', |
|
|
category: 'AI Services', |
|
|
description: 'HuggingFace AI models for sentiment, analysis, and predictions', |
|
|
endpoints: [ |
|
|
{ method: 'GET', path: '/api/models/status', desc: 'Models status' }, |
|
|
{ method: 'GET', path: '/api/models/list', desc: 'List all models' }, |
|
|
{ method: 'GET', path: '/api/models/summary', desc: 'Models grouped by category (frontend-ready)' }, |
|
|
{ method: 'GET', path: '/api/models/health', desc: 'Model health check' }, |
|
|
{ method: 'POST', path: '/api/models/reinitialize', desc: 'Reinitialize models (UI button)' }, |
|
|
{ method: 'POST', path: '/api/ai/decision', desc: 'AI trading decision' } |
|
|
], |
|
|
example: `// Get AI trading decision |
|
|
fetch('${baseUrl}/api/ai/decision', { |
|
|
method: 'POST', |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
body: JSON.stringify({ symbol: 'BTC', horizon: 'swing', risk_tolerance: 'moderate' }) |
|
|
}) |
|
|
.then(res => res.json()) |
|
|
.then(data => { |
|
|
console.log('Decision:', data.decision); |
|
|
console.log('Confidence:', data.confidence); |
|
|
console.log('Signals:', data.signals); |
|
|
});` |
|
|
}, |
|
|
|
|
|
|
|
|
{ |
|
|
name: 'DeFi Data API', |
|
|
category: 'DeFi Services', |
|
|
description: 'DefiLlama public endpoints (no API key): TVL, protocols, yields', |
|
|
endpoints: [ |
|
|
{ method: 'GET', path: '/api/defi/tvl', desc: 'Total Value Locked' }, |
|
|
{ method: 'GET', path: '/api/defi/protocols?limit=20', desc: 'Top DeFi protocols' }, |
|
|
{ method: 'GET', path: '/api/defi/yields?limit=20', desc: 'DeFi yield pools' } |
|
|
], |
|
|
example: `// Get DeFi TVL data |
|
|
fetch('${baseUrl}/api/defi/protocols?limit=10') |
|
|
.then(res => res.json()) |
|
|
.then(data => { |
|
|
(data.protocols || []).forEach(p => { |
|
|
console.log(p.name, '- TVL:', p.tvl); |
|
|
}); |
|
|
});` |
|
|
}, |
|
|
|
|
|
|
|
|
{ |
|
|
name: 'Trading & Backtesting API', |
|
|
category: 'Trading', |
|
|
description: 'Historical backtests and strategy runs (uses exchange/data fallbacks)', |
|
|
endpoints: [ |
|
|
{ method: 'GET', path: '/api/trading/backtest/historical/BTCUSDT?timeframe=1h&days=30', desc: 'Historical candles for backtest' }, |
|
|
{ method: 'GET', path: '/api/trading/backtest/run/BTCUSDT?strategy=sma_crossover&days=30&initial_capital=10000', desc: 'Run a backtest strategy' } |
|
|
], |
|
|
example: `// Run SMA crossover backtest |
|
|
fetch('${baseUrl}/api/trading/backtest/run/BTCUSDT?strategy=sma_crossover&days=30&initial_capital=10000') |
|
|
.then(r => r.json()) |
|
|
.then(console.log);` |
|
|
}, |
|
|
|
|
|
|
|
|
{ |
|
|
name: 'Resources & Monitoring API', |
|
|
category: 'System Services', |
|
|
description: 'API resources, providers status, and system health', |
|
|
endpoints: [ |
|
|
{ method: 'GET', path: '/api/resources/stats', desc: 'Resources statistics' }, |
|
|
{ method: 'GET', path: '/api/resources/apis', desc: 'All APIs list' }, |
|
|
{ method: 'GET', path: '/api/resources/summary', desc: 'Resources summary' }, |
|
|
{ method: 'GET', path: '/api/providers', desc: 'Data providers list' }, |
|
|
{ method: 'GET', path: '/api/status', desc: 'System status' }, |
|
|
{ method: 'GET', path: '/api/health', desc: 'Health check' } |
|
|
], |
|
|
example: `// Check system health |
|
|
fetch('${baseUrl}/api/health') |
|
|
.then(res => res.json()) |
|
|
.then(data => { |
|
|
console.log('Status:', data.status); |
|
|
console.log('Providers:', data.providers); |
|
|
}); |
|
|
|
|
|
// Get resources stats |
|
|
fetch('${baseUrl}/api/resources/stats') |
|
|
.then(res => res.json()) |
|
|
.then(data => { |
|
|
console.log('Total APIs:', data.total_functional); |
|
|
console.log('Success Rate:', data.success_rate + '%'); |
|
|
});` |
|
|
}, |
|
|
|
|
|
|
|
|
{ |
|
|
name: 'Support & Debug API', |
|
|
category: 'System Services', |
|
|
description: 'Client-accessible support files and real endpoint list', |
|
|
endpoints: [ |
|
|
{ method: 'GET', path: '/api/support/realendpoints', desc: 'Endpoints list (JSON)' }, |
|
|
{ method: 'GET', path: '/api/support/realendpoints?format=txt', desc: 'Endpoints list (TXT)' }, |
|
|
{ method: 'GET', path: '/realendpoint.txt', desc: 'Download endpoints list (TXT)' }, |
|
|
{ method: 'GET', path: '/api/support/fualt?tail=200', desc: 'Tail of fault log (JSON)' }, |
|
|
{ method: 'GET', path: '/fualt.txt', desc: 'Download full fault log (TXT)' } |
|
|
], |
|
|
example: `// Fetch tail of fualt.txt |
|
|
fetch('${baseUrl}/api/support/fualt?tail=200') |
|
|
.then(r => r.json()) |
|
|
.then(data => console.log(data.content));` |
|
|
} |
|
|
]; |
|
|
} |
|
|
|
|
|
show() { |
|
|
if (this.modal) { |
|
|
this.modal.style.display = 'flex'; |
|
|
return; |
|
|
} |
|
|
|
|
|
this.modal = this.createModal(); |
|
|
document.body.appendChild(this.modal); |
|
|
} |
|
|
|
|
|
hide() { |
|
|
if (this.modal) { |
|
|
this.modal.style.display = 'none'; |
|
|
} |
|
|
} |
|
|
|
|
|
createModal() { |
|
|
const modal = document.createElement('div'); |
|
|
modal.className = 'config-helper-modal'; |
|
|
modal.innerHTML = ` |
|
|
<div class="config-helper-overlay"></div> |
|
|
<div class="config-helper-content"> |
|
|
<div class="config-helper-header"> |
|
|
<h2> |
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
|
|
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/> |
|
|
</svg> |
|
|
API Configuration Guide |
|
|
</h2> |
|
|
<button class="config-helper-close" aria-label="Close"> |
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
|
|
<line x1="18" y1="6" x2="6" y2="18"/> |
|
|
<line x1="6" y1="6" x2="18" y2="18"/> |
|
|
</svg> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="config-helper-body"> |
|
|
<div class="config-helper-intro"> |
|
|
<p>Use this guide to integrate quickly. Every endpoint listed below exists in the server and is safe to copy.</p> |
|
|
<div class="config-helper-base-url"> |
|
|
<strong>Base URL:</strong> |
|
|
<code>${window.location.origin}</code> |
|
|
<button class="copy-btn" data-copy="${window.location.origin}"> |
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
|
|
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/> |
|
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> |
|
|
</svg> |
|
|
</button> |
|
|
</div> |
|
|
<div class="config-helper-stats"> |
|
|
<span>✅ Copy-ready URLs</span> |
|
|
<span>✅ Copy-ready code snippets</span> |
|
|
<span>✅ `/api/endpoints` for discovery</span> |
|
|
<span>✅ `/docs` for Swagger</span> |
|
|
</div> |
|
|
|
|
|
<div class="config-helper-snippets"> |
|
|
<div class="snippet-card"> |
|
|
<div class="snippet-head"> |
|
|
<span>cURL</span> |
|
|
<button class="copy-btn">Copy</button> |
|
|
</div> |
|
|
<pre><code>curl -s '${window.location.origin}/api/health' | jq .</code></pre> |
|
|
</div> |
|
|
<div class="snippet-card"> |
|
|
<div class="snippet-head"> |
|
|
<span>JavaScript (fetch)</span> |
|
|
<button class="copy-btn">Copy</button> |
|
|
</div> |
|
|
<pre><code>fetch('${window.location.origin}/api/market?limit=10') |
|
|
.then(r => r.json()) |
|
|
.then(console.log);</code></pre> |
|
|
</div> |
|
|
<div class="snippet-card"> |
|
|
<div class="snippet-head"> |
|
|
<span>Python (requests)</span> |
|
|
<button class="copy-btn">Copy</button> |
|
|
</div> |
|
|
<pre><code>import requests |
|
|
print(requests.get('${window.location.origin}/api/market?limit=10', timeout=10).json())</code></pre> |
|
|
</div> |
|
|
<div class="snippet-card"> |
|
|
<div class="snippet-head"> |
|
|
<span>Optional HF Token</span> |
|
|
<button class="copy-btn">Copy</button> |
|
|
</div> |
|
|
<pre><code>export HF_TOKEN='YOUR_TOKEN_HERE'</code></pre> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="config-helper-services"> |
|
|
${this.renderServices()} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
|
|
|
modal.querySelector('.config-helper-close').addEventListener('click', () => this.hide()); |
|
|
modal.querySelector('.config-helper-overlay').addEventListener('click', () => this.hide()); |
|
|
|
|
|
|
|
|
modal.querySelectorAll('.copy-btn').forEach(btn => { |
|
|
btn.addEventListener('click', (e) => { |
|
|
e.stopPropagation(); |
|
|
let text = btn.getAttribute('data-copy'); |
|
|
if (!text) { |
|
|
|
|
|
const codeEl = btn.closest('.snippet-card')?.querySelector('pre code'); |
|
|
text = codeEl?.textContent || ''; |
|
|
} |
|
|
this.copyToClipboard(text, btn); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
modal.querySelectorAll('.service-header').forEach(header => { |
|
|
header.addEventListener('click', () => { |
|
|
const service = header.parentElement; |
|
|
service.classList.toggle('expanded'); |
|
|
}); |
|
|
}); |
|
|
|
|
|
return modal; |
|
|
} |
|
|
|
|
|
renderServices() { |
|
|
const categories = {}; |
|
|
|
|
|
|
|
|
this.services.forEach(service => { |
|
|
if (!categories[service.category]) { |
|
|
categories[service.category] = []; |
|
|
} |
|
|
categories[service.category].push(service); |
|
|
}); |
|
|
|
|
|
return Object.entries(categories).map(([category, services]) => ` |
|
|
<div class="service-category"> |
|
|
<h3 class="category-title">${category}</h3> |
|
|
${services.map(service => this.renderService(service)).join('')} |
|
|
</div> |
|
|
`).join(''); |
|
|
} |
|
|
|
|
|
renderService(service) { |
|
|
return ` |
|
|
<div class="service-item"> |
|
|
<div class="service-header"> |
|
|
<div class="service-title"> |
|
|
<span class="service-name">${service.name}</span> |
|
|
<svg class="expand-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
|
|
<polyline points="6 9 12 15 18 9"/> |
|
|
</svg> |
|
|
</div> |
|
|
<p class="service-desc">${service.description}</p> |
|
|
</div> |
|
|
|
|
|
<div class="service-details"> |
|
|
<div class="endpoints-list"> |
|
|
<h4>Endpoints:</h4> |
|
|
${service.endpoints.map(ep => ` |
|
|
<div class="endpoint-item"> |
|
|
<span class="method-badge ${ep.method.toLowerCase()}">${ep.method}</span> |
|
|
<code class="endpoint-path">${ep.path}</code> |
|
|
<span class="endpoint-desc">${ep.desc}</span> |
|
|
<button class="copy-btn" data-copy="${window.location.origin}${ep.path}"> |
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
|
|
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/> |
|
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> |
|
|
</svg> |
|
|
</button> |
|
|
</div> |
|
|
`).join('')} |
|
|
</div> |
|
|
|
|
|
<div class="code-example"> |
|
|
<div class="code-header"> |
|
|
<span>Example Usage:</span> |
|
|
<button class="copy-btn" data-copy="${service.example}"> |
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
|
|
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/> |
|
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> |
|
|
</svg> |
|
|
Copy |
|
|
</button> |
|
|
</div> |
|
|
<pre><code>${this.escapeHtml(service.example)}</code></pre> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
escapeHtml(text) { |
|
|
const div = document.createElement('div'); |
|
|
div.textContent = text; |
|
|
return div.innerHTML; |
|
|
} |
|
|
|
|
|
async copyToClipboard(text, button) { |
|
|
try { |
|
|
if (navigator.clipboard?.writeText) { |
|
|
await navigator.clipboard.writeText(text); |
|
|
} else { |
|
|
|
|
|
const ta = document.createElement('textarea'); |
|
|
ta.value = text; |
|
|
ta.setAttribute('readonly', ''); |
|
|
ta.style.position = 'fixed'; |
|
|
ta.style.left = '-9999px'; |
|
|
document.body.appendChild(ta); |
|
|
ta.select(); |
|
|
document.execCommand('copy'); |
|
|
ta.remove(); |
|
|
} |
|
|
|
|
|
|
|
|
const originalHTML = button.innerHTML; |
|
|
button.innerHTML = ` |
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
|
|
<polyline points="20 6 9 17 4 12"/> |
|
|
</svg> |
|
|
`; |
|
|
button.classList.add('copied'); |
|
|
|
|
|
setTimeout(() => { |
|
|
button.innerHTML = originalHTML; |
|
|
button.classList.remove('copied'); |
|
|
}, 2000); |
|
|
} catch (err) { |
|
|
console.error('Failed to copy:', err); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const style = document.createElement('style'); |
|
|
style.textContent = ` |
|
|
.config-helper-modal { |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
bottom: 0; |
|
|
z-index: 10000; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
padding: 20px; |
|
|
} |
|
|
|
|
|
.config-helper-overlay { |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
bottom: 0; |
|
|
background: rgba(0, 0, 0, 0.7); |
|
|
backdrop-filter: blur(4px); |
|
|
} |
|
|
|
|
|
.config-helper-content { |
|
|
position: relative; |
|
|
background: var(--bg-main, #ffffff); |
|
|
border-radius: 16px; |
|
|
max-width: 900px; |
|
|
width: 100%; |
|
|
max-height: 85vh; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); |
|
|
animation: modalSlideIn 0.3s ease; |
|
|
} |
|
|
|
|
|
@keyframes modalSlideIn { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: translateY(-20px) scale(0.95); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: translateY(0) scale(1); |
|
|
} |
|
|
} |
|
|
|
|
|
.config-helper-header { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: space-between; |
|
|
padding: 24px; |
|
|
border-bottom: 1px solid var(--border-light, #e5e7eb); |
|
|
} |
|
|
|
|
|
.config-helper-header h2 { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 12px; |
|
|
margin: 0; |
|
|
font-size: 20px; |
|
|
font-weight: 600; |
|
|
color: var(--text-primary, #0f2926); |
|
|
} |
|
|
|
|
|
.config-helper-header svg { |
|
|
color: var(--teal, #14b8a6); |
|
|
} |
|
|
|
|
|
.config-helper-close { |
|
|
background: none; |
|
|
border: none; |
|
|
padding: 8px; |
|
|
cursor: pointer; |
|
|
border-radius: 8px; |
|
|
color: var(--text-muted, #6b7280); |
|
|
transition: all 0.2s; |
|
|
} |
|
|
|
|
|
.config-helper-close:hover { |
|
|
background: var(--bg-secondary, #f3f4f6); |
|
|
color: var(--text-primary, #0f2926); |
|
|
} |
|
|
|
|
|
.config-helper-body { |
|
|
overflow-y: auto; |
|
|
padding: 24px; |
|
|
} |
|
|
|
|
|
.config-helper-intro { |
|
|
margin-bottom: 24px; |
|
|
} |
|
|
|
|
|
.config-helper-intro p { |
|
|
color: var(--text-secondary, #6b7280); |
|
|
margin-bottom: 12px; |
|
|
} |
|
|
|
|
|
.config-helper-base-url { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 8px; |
|
|
padding: 12px; |
|
|
background: var(--bg-secondary, #f3f4f6); |
|
|
border-radius: 8px; |
|
|
font-size: 14px; |
|
|
margin-bottom: 12px; |
|
|
} |
|
|
|
|
|
.config-helper-base-url code { |
|
|
flex: 1; |
|
|
padding: 4px 8px; |
|
|
background: var(--bg-main, #ffffff); |
|
|
border-radius: 4px; |
|
|
font-family: 'Courier New', monospace; |
|
|
font-size: 13px; |
|
|
} |
|
|
|
|
|
.config-helper-stats { |
|
|
display: flex; |
|
|
flex-wrap: wrap; |
|
|
gap: 12px; |
|
|
padding: 12px; |
|
|
background: linear-gradient(135deg, #e0f2f1 0%, #b2dfdb 100%); |
|
|
border-radius: 8px; |
|
|
font-size: 13px; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
.config-helper-stats span { |
|
|
padding: 4px 8px; |
|
|
background: rgba(255,255,255,0.7); |
|
|
border-radius: 4px; |
|
|
} |
|
|
|
|
|
.config-helper-snippets { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(2, minmax(0, 1fr)); |
|
|
gap: 12px; |
|
|
margin-top: 12px; |
|
|
} |
|
|
|
|
|
.snippet-card { |
|
|
background: var(--bg-secondary, #f8fdfc); |
|
|
border: 1px solid var(--border-light, #e5e7eb); |
|
|
border-radius: 12px; |
|
|
padding: 12px; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.snippet-head { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: space-between; |
|
|
font-size: 12px; |
|
|
font-weight: 700; |
|
|
color: var(--text-secondary, #6b7280); |
|
|
margin-bottom: 8px; |
|
|
} |
|
|
|
|
|
.snippet-card pre { |
|
|
margin: 0; |
|
|
background: #0b1220; |
|
|
color: #e2e8f0; |
|
|
padding: 10px; |
|
|
border-radius: 10px; |
|
|
overflow-x: auto; |
|
|
font-size: 12px; |
|
|
line-height: 1.5; |
|
|
} |
|
|
|
|
|
.service-category { |
|
|
margin-bottom: 24px; |
|
|
} |
|
|
|
|
|
.category-title { |
|
|
font-size: 16px; |
|
|
font-weight: 600; |
|
|
color: var(--teal, #14b8a6); |
|
|
margin-bottom: 12px; |
|
|
padding-bottom: 8px; |
|
|
border-bottom: 2px solid var(--teal-light, #2dd4bf); |
|
|
} |
|
|
|
|
|
.service-item { |
|
|
background: var(--bg-secondary, #f8fdfc); |
|
|
border: 1px solid var(--border-light, #e5e7eb); |
|
|
border-radius: 12px; |
|
|
margin-bottom: 12px; |
|
|
overflow: hidden; |
|
|
transition: all 0.2s; |
|
|
} |
|
|
|
|
|
.service-item:hover { |
|
|
border-color: var(--teal-light, #2dd4bf); |
|
|
} |
|
|
|
|
|
.service-header { |
|
|
padding: 16px; |
|
|
cursor: pointer; |
|
|
user-select: none; |
|
|
} |
|
|
|
|
|
.service-title { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: space-between; |
|
|
margin-bottom: 4px; |
|
|
} |
|
|
|
|
|
.service-name { |
|
|
font-weight: 600; |
|
|
color: var(--text-primary, #0f2926); |
|
|
font-size: 15px; |
|
|
} |
|
|
|
|
|
.expand-icon { |
|
|
color: var(--text-muted, #6b7280); |
|
|
transition: transform 0.2s; |
|
|
} |
|
|
|
|
|
.service-item.expanded .expand-icon { |
|
|
transform: rotate(180deg); |
|
|
} |
|
|
|
|
|
.service-desc { |
|
|
color: var(--text-secondary, #6b7280); |
|
|
font-size: 13px; |
|
|
margin: 0; |
|
|
} |
|
|
|
|
|
.service-details { |
|
|
max-height: 0; |
|
|
overflow: hidden; |
|
|
transition: max-height 0.3s ease; |
|
|
} |
|
|
|
|
|
.service-item.expanded .service-details { |
|
|
max-height: 1000px; |
|
|
} |
|
|
|
|
|
.endpoints-list { |
|
|
padding: 0 16px 16px; |
|
|
} |
|
|
|
|
|
.endpoints-list h4 { |
|
|
font-size: 13px; |
|
|
font-weight: 600; |
|
|
color: var(--text-secondary, #6b7280); |
|
|
margin-bottom: 8px; |
|
|
} |
|
|
|
|
|
.endpoint-item { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 8px; |
|
|
padding: 8px; |
|
|
background: var(--bg-main, #ffffff); |
|
|
border-radius: 6px; |
|
|
margin-bottom: 6px; |
|
|
font-size: 13px; |
|
|
} |
|
|
|
|
|
.method-badge { |
|
|
padding: 2px 8px; |
|
|
border-radius: 4px; |
|
|
font-weight: 600; |
|
|
font-size: 11px; |
|
|
text-transform: uppercase; |
|
|
} |
|
|
|
|
|
.method-badge.get { |
|
|
background: #10b981; |
|
|
color: white; |
|
|
} |
|
|
|
|
|
.method-badge.post { |
|
|
background: #3b82f6; |
|
|
color: white; |
|
|
} |
|
|
|
|
|
.method-badge.ws { |
|
|
background: #8b5cf6; |
|
|
color: white; |
|
|
} |
|
|
|
|
|
.endpoint-path { |
|
|
flex: 1; |
|
|
font-family: 'Courier New', monospace; |
|
|
font-size: 12px; |
|
|
color: var(--text-primary, #0f2926); |
|
|
word-break: break-all; |
|
|
} |
|
|
|
|
|
.endpoint-desc { |
|
|
color: var(--text-muted, #6b7280); |
|
|
font-size: 12px; |
|
|
white-space: nowrap; |
|
|
} |
|
|
|
|
|
.code-example { |
|
|
padding: 0 16px 16px; |
|
|
} |
|
|
|
|
|
.code-header { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: space-between; |
|
|
margin-bottom: 8px; |
|
|
font-size: 13px; |
|
|
font-weight: 600; |
|
|
color: var(--text-secondary, #6b7280); |
|
|
} |
|
|
|
|
|
.code-example pre { |
|
|
background: #1e293b; |
|
|
color: #e2e8f0; |
|
|
padding: 12px; |
|
|
border-radius: 8px; |
|
|
overflow-x: auto; |
|
|
margin: 0; |
|
|
font-size: 12px; |
|
|
line-height: 1.6; |
|
|
} |
|
|
|
|
|
.copy-btn { |
|
|
background: var(--teal, #14b8a6); |
|
|
color: white; |
|
|
border: none; |
|
|
padding: 6px 12px; |
|
|
border-radius: 6px; |
|
|
cursor: pointer; |
|
|
display: inline-flex; |
|
|
align-items: center; |
|
|
gap: 4px; |
|
|
font-size: 12px; |
|
|
transition: all 0.2s; |
|
|
} |
|
|
|
|
|
.copy-btn:hover { |
|
|
background: var(--teal-dark, #0d7377); |
|
|
transform: translateY(-1px); |
|
|
} |
|
|
|
|
|
.copy-btn.copied { |
|
|
background: #10b981; |
|
|
} |
|
|
|
|
|
@media (max-width: 768px) { |
|
|
.config-helper-content { |
|
|
max-width: 100%; |
|
|
max-height: 95vh; |
|
|
margin: 10px; |
|
|
} |
|
|
|
|
|
.endpoint-item { |
|
|
flex-wrap: wrap; |
|
|
} |
|
|
|
|
|
.endpoint-desc { |
|
|
width: 100%; |
|
|
margin-top: 4px; |
|
|
white-space: normal; |
|
|
} |
|
|
|
|
|
.config-helper-stats { |
|
|
flex-direction: column; |
|
|
} |
|
|
|
|
|
.config-helper-snippets { |
|
|
grid-template-columns: 1fr; |
|
|
} |
|
|
} |
|
|
`; |
|
|
document.head.appendChild(style); |
|
|
|