Datasourceforcryptocurrency / archive_html /enhanced_dashboard.html
Really-amin's picture
Upload 303 files
b068b76 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enhanced Crypto Data Tracker</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1600px;
margin: 0 auto;
}
header {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 20px 30px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
h1 {
color: white;
font-size: 28px;
display: flex;
align-items: center;
gap: 10px;
}
.connection-status {
display: flex;
align-items: center;
gap: 10px;
color: white;
font-size: 14px;
}
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background: #10b981;
animation: pulse 2s infinite;
}
.status-indicator.disconnected {
background: #ef4444;
animation: none;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.controls {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 20px;
margin-bottom: 20px;
}
.controls-row {
display: flex;
gap: 15px;
flex-wrap: wrap;
align-items: center;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: white;
color: #667eea;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.btn-success {
background: #10b981;
color: white;
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn-info {
background: #3b82f6;
color: white;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 20px;
color: white;
}
.card h2 {
font-size: 18px;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.stat-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
}
.stat-item {
background: rgba(255, 255, 255, 0.1);
padding: 15px;
border-radius: 10px;
}
.stat-label {
font-size: 12px;
opacity: 0.8;
margin-bottom: 5px;
}
.stat-value {
font-size: 24px;
font-weight: 700;
}
.api-list {
max-height: 400px;
overflow-y: auto;
}
.api-item {
background: rgba(255, 255, 255, 0.05);
padding: 15px;
border-radius: 10px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.api-info {
flex: 1;
}
.api-name {
font-weight: 600;
margin-bottom: 5px;
}
.api-meta {
font-size: 12px;
opacity: 0.7;
display: flex;
gap: 15px;
}
.api-controls {
display: flex;
gap: 10px;
}
.small-btn {
padding: 5px 12px;
font-size: 12px;
border-radius: 5px;
border: none;
cursor: pointer;
background: rgba(255, 255, 255, 0.2);
color: white;
transition: all 0.2s;
}
.small-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.status-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
}
.status-success {
background: #10b981;
color: white;
}
.status-pending {
background: #f59e0b;
color: white;
}
.status-failed {
background: #ef4444;
color: white;
}
.log-container {
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
padding: 15px;
max-height: 300px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
}
.log-entry {
margin-bottom: 8px;
padding: 5px;
border-left: 3px solid #667eea;
padding-left: 10px;
}
.log-time {
opacity: 0.6;
margin-right: 10px;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal.active {
display: flex;
}
.modal-content {
background: white;
border-radius: 15px;
padding: 30px;
max-width: 500px;
width: 90%;
color: #333;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.modal-close {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
}
.form-group {
margin-bottom: 15px;
}
.form-label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #333;
}
.form-input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
}
.form-select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
.toast {
position: fixed;
bottom: 20px;
right: 20px;
background: white;
color: #333;
padding: 15px 20px;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
opacity: 0;
transform: translateY(20px);
transition: all 0.3s ease;
z-index: 2000;
}
.toast.show {
opacity: 1;
transform: translateY(0);
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>
<span>πŸš€</span>
Enhanced Crypto Data Tracker
</h1>
<div class="connection-status">
<div class="status-indicator" id="wsStatus"></div>
<span id="wsStatusText">Connecting...</span>
</div>
</header>
<div class="controls">
<div class="controls-row">
<button class="btn btn-primary" onclick="exportJSON()">
πŸ’Ύ Export JSON
</button>
<button class="btn btn-primary" onclick="exportCSV()">
πŸ“Š Export CSV
</button>
<button class="btn btn-success" onclick="createBackup()">
πŸ”„ Create Backup
</button>
<button class="btn btn-info" onclick="showScheduleModal()">
⏰ Configure Schedule
</button>
<button class="btn btn-info" onclick="forceUpdateAll()">
πŸ”ƒ Force Update All
</button>
<button class="btn btn-danger" onclick="clearCache()">
πŸ—‘οΈ Clear Cache
</button>
</div>
</div>
<div class="grid">
<div class="card">
<h2>πŸ“Š System Statistics</h2>
<div class="stat-grid">
<div class="stat-item">
<div class="stat-label">Total APIs</div>
<div class="stat-value" id="totalApis">0</div>
</div>
<div class="stat-item">
<div class="stat-label">Active Tasks</div>
<div class="stat-value" id="activeTasks">0</div>
</div>
<div class="stat-item">
<div class="stat-label">Cached Data</div>
<div class="stat-value" id="cachedData">0</div>
</div>
<div class="stat-item">
<div class="stat-label">WS Connections</div>
<div class="stat-value" id="wsConnections">0</div>
</div>
</div>
</div>
<div class="card">
<h2>πŸ“ˆ Recent Activity</h2>
<div class="log-container" id="activityLog">
<div class="log-entry">
<span class="log-time">--:--:--</span>
Waiting for updates...
</div>
</div>
</div>
</div>
<div class="card">
<h2>πŸ”Œ API Sources</h2>
<div class="api-list" id="apiList">
Loading...
</div>
</div>
</div>
<!-- Schedule Modal -->
<div class="modal" id="scheduleModal">
<div class="modal-content">
<div class="modal-header">
<h2>⏰ Configure Schedule</h2>
<button class="modal-close" onclick="closeScheduleModal()">Γ—</button>
</div>
<div class="form-group">
<label class="form-label">API Source</label>
<select class="form-select" id="scheduleApiSelect"></select>
</div>
<div class="form-group">
<label class="form-label">Interval (seconds)</label>
<input type="number" class="form-input" id="scheduleInterval" value="60" min="10">
</div>
<div class="form-group">
<label class="form-label">Enabled</label>
<input type="checkbox" id="scheduleEnabled" checked>
</div>
<button class="btn btn-primary" onclick="updateSchedule()">Save Schedule</button>
</div>
</div>
<!-- Toast notification -->
<div class="toast" id="toast"></div>
<script>
let ws = null;
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
const reconnectDelay = 3000;
// Initialize WebSocket connection
function initWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/api/v2/ws`;
ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('WebSocket connected');
updateWSStatus(true);
reconnectAttempts = 0;
// Subscribe to all updates
ws.send(JSON.stringify({ type: 'subscribe_all' }));
// Start heartbeat
startHeartbeat();
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
handleWSMessage(message);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('WebSocket disconnected');
updateWSStatus(false);
attemptReconnect();
};
}
function attemptReconnect() {
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
console.log(`Reconnecting... Attempt ${reconnectAttempts}`);
setTimeout(initWebSocket, reconnectDelay);
}
}
let heartbeatInterval;
function startHeartbeat() {
clearInterval(heartbeatInterval);
heartbeatInterval = setInterval(() => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
}
function updateWSStatus(connected) {
const indicator = document.getElementById('wsStatus');
const text = document.getElementById('wsStatusText');
if (connected) {
indicator.classList.remove('disconnected');
text.textContent = 'Connected';
} else {
indicator.classList.add('disconnected');
text.textContent = 'Disconnected';
}
}
function handleWSMessage(message) {
console.log('Received:', message);
switch (message.type) {
case 'api_update':
handleApiUpdate(message);
break;
case 'status_update':
handleStatusUpdate(message);
break;
case 'schedule_update':
handleScheduleUpdate(message);
break;
case 'subscribed':
addLog(`Subscribed to ${message.api_id || 'all updates'}`);
break;
}
}
function handleApiUpdate(message) {
addLog(`Updated: ${message.api_id}`, 'success');
loadSystemStatus();
}
function handleStatusUpdate(message) {
addLog('System status updated');
loadSystemStatus();
}
function handleScheduleUpdate(message) {
addLog(`Schedule updated for ${message.schedule.api_id}`);
loadAPIs();
}
function addLog(text, type = 'info') {
const logContainer = document.getElementById('activityLog');
const time = new Date().toLocaleTimeString();
const entry = document.createElement('div');
entry.className = 'log-entry';
entry.innerHTML = `<span class="log-time">${time}</span>${text}`;
logContainer.insertBefore(entry, logContainer.firstChild);
// Keep only last 50 entries
while (logContainer.children.length > 50) {
logContainer.removeChild(logContainer.lastChild);
}
}
function showToast(message, duration = 3000) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, duration);
}
// Load system status
async function loadSystemStatus() {
try {
const response = await fetch('/api/v2/status');
const data = await response.json();
document.getElementById('totalApis').textContent =
data.services.config_loader.apis_loaded;
document.getElementById('activeTasks').textContent =
data.services.scheduler.total_tasks;
document.getElementById('cachedData').textContent =
data.services.persistence.cached_apis;
document.getElementById('wsConnections').textContent =
data.services.websocket.total_connections;
} catch (error) {
console.error('Error loading status:', error);
}
}
// Load APIs
async function loadAPIs() {
try {
const response = await fetch('/api/v2/config/apis');
const data = await response.json();
const scheduleResponse = await fetch('/api/v2/schedule/tasks');
const schedules = await scheduleResponse.json();
displayAPIs(data.apis, schedules);
} catch (error) {
console.error('Error loading APIs:', error);
}
}
function displayAPIs(apis, schedules) {
const listElement = document.getElementById('apiList');
listElement.innerHTML = '';
for (const [apiId, api] of Object.entries(apis)) {
const schedule = schedules[apiId] || {};
const item = document.createElement('div');
item.className = 'api-item';
item.innerHTML = `
<div class="api-info">
<div class="api-name">${api.name}</div>
<div class="api-meta">
<span>πŸ“‚ ${api.category}</span>
<span>⏱️ ${schedule.interval || 300}s</span>
<span class="status-badge ${schedule.last_status === 'success' ? 'status-success' : 'status-pending'}">
${schedule.last_status || 'pending'}
</span>
</div>
</div>
<div class="api-controls">
<button class="small-btn" onclick="forceUpdate('${apiId}')">πŸ”„ Update</button>
<button class="small-btn" onclick="showScheduleModalFor('${apiId}')">βš™οΈ Schedule</button>
</div>
`;
listElement.appendChild(item);
}
}
// Export functions
async function exportJSON() {
try {
const response = await fetch('/api/v2/export/json', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ include_history: true })
});
const data = await response.json();
showToast('βœ… JSON export created!');
addLog(`Exported to JSON: ${data.filepath}`);
// Trigger download
window.open(data.download_url, '_blank');
} catch (error) {
showToast('❌ Export failed');
console.error(error);
}
}
async function exportCSV() {
try {
const response = await fetch('/api/v2/export/csv', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ flatten: true })
});
const data = await response.json();
showToast('βœ… CSV export created!');
addLog(`Exported to CSV: ${data.filepath}`);
// Trigger download
window.open(data.download_url, '_blank');
} catch (error) {
showToast('❌ Export failed');
console.error(error);
}
}
async function createBackup() {
try {
const response = await fetch('/api/v2/backup', {
method: 'POST'
});
const data = await response.json();
showToast('βœ… Backup created!');
addLog(`Backup created: ${data.backup_file}`);
} catch (error) {
showToast('❌ Backup failed');
console.error(error);
}
}
async function forceUpdate(apiId) {
try {
const response = await fetch(`/api/v2/schedule/tasks/${apiId}/force-update`, {
method: 'POST'
});
const data = await response.json();
showToast(`βœ… ${apiId} updated!`);
addLog(`Forced update: ${apiId}`);
loadAPIs();
} catch (error) {
showToast('❌ Update failed');
console.error(error);
}
}
async function forceUpdateAll() {
showToast('πŸ”„ Updating all APIs...');
addLog('Forcing update for all APIs');
try {
const response = await fetch('/api/v2/config/apis');
const data = await response.json();
for (const apiId of Object.keys(data.apis)) {
await forceUpdate(apiId);
await new Promise(resolve => setTimeout(resolve, 100)); // Small delay
}
showToast('βœ… All APIs updated!');
} catch (error) {
showToast('❌ Update failed');
console.error(error);
}
}
async function clearCache() {
if (!confirm('Clear all cached data?')) return;
try {
const response = await fetch('/api/v2/cleanup/cache', {
method: 'POST'
});
showToast('βœ… Cache cleared!');
addLog('Cache cleared');
loadSystemStatus();
} catch (error) {
showToast('❌ Failed to clear cache');
console.error(error);
}
}
// Schedule modal functions
function showScheduleModal() {
loadAPISelectOptions();
document.getElementById('scheduleModal').classList.add('active');
}
function closeScheduleModal() {
document.getElementById('scheduleModal').classList.remove('active');
}
async function showScheduleModalFor(apiId) {
await loadAPISelectOptions();
document.getElementById('scheduleApiSelect').value = apiId;
// Load current schedule
try {
const response = await fetch(`/api/v2/schedule/tasks/${apiId}`);
const schedule = await response.json();
document.getElementById('scheduleInterval').value = schedule.interval;
document.getElementById('scheduleEnabled').checked = schedule.enabled;
} catch (error) {
console.error(error);
}
showScheduleModal();
}
async function loadAPISelectOptions() {
try {
const response = await fetch('/api/v2/config/apis');
const data = await response.json();
const select = document.getElementById('scheduleApiSelect');
select.innerHTML = '';
for (const [apiId, api] of Object.entries(data.apis)) {
const option = document.createElement('option');
option.value = apiId;
option.textContent = api.name;
select.appendChild(option);
}
} catch (error) {
console.error(error);
}
}
async function updateSchedule() {
const apiId = document.getElementById('scheduleApiSelect').value;
const interval = parseInt(document.getElementById('scheduleInterval').value);
const enabled = document.getElementById('scheduleEnabled').checked;
try {
const response = await fetch(`/api/v2/schedule/tasks/${apiId}?interval=${interval}&enabled=${enabled}`, {
method: 'PUT'
});
const data = await response.json();
showToast('βœ… Schedule updated!');
addLog(`Updated schedule for ${apiId}`);
closeScheduleModal();
loadAPIs();
} catch (error) {
showToast('❌ Schedule update failed');
console.error(error);
}
}
// Initialize on load
window.addEventListener('load', () => {
initWebSocket();
loadSystemStatus();
loadAPIs();
// Refresh status every 30 seconds
setInterval(loadSystemStatus, 30000);
});
</script>
</body>
</html>