taolib.testing.data_sync.server.app#
FastAPI 应用工厂模块。
创建和配置 FastAPI 应用实例。
Attributes#
Functions#
|
应用生命周期管理。 |
|
创建 FastAPI 应用实例。 |
Module Contents#
- taolib.testing.data_sync.server.app.logger#
- async taolib.testing.data_sync.server.app.lifespan(app: fastapi.FastAPI) collections.abc.AsyncGenerator[None]#
应用生命周期管理。
- taolib.testing.data_sync.server.app.create_app() fastapi.FastAPI#
创建 FastAPI 应用实例。
- taolib.testing.data_sync.server.app._DASHBOARD_HTML = Multiline-String#
Show Value
""" <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Data Sync Dashboard</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; } .header { background: #2c3e50; color: white; padding: 20px; } .header h1 { font-size: 24px; font-weight: 500; } .container { max-width: 1200px; margin: 0 auto; padding: 20px; } .card { background: white; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .card h2 { font-size: 18px; color: #333; margin-bottom: 15px; border-bottom: 1px solid #eee; padding-bottom: 10px; } table { width: 100%; border-collapse: collapse; } th, td { text-align: left; padding: 12px; border-bottom: 1px solid #eee; } th { background: #f8f9fa; font-weight: 500; color: #666; } .status { padding: 4px 8px; border-radius: 4px; font-size: 12px; } .status.pending { background: #fff3cd; color: #856404; } .status.running { background: #cce5ff; color: #004085; } .status.completed { background: #d4edda; color: #155724; } .status.failed { background: #f8d7da; color: #721c24; } .btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; } .btn-primary { background: #3498db; color: white; } .btn-primary:hover { background: #2980b9; } .btn-danger { background: #e74c3c; color: white; } .btn-danger:hover { background: #c0392b; } .metric { display: flex; gap: 30px; flex-wrap: wrap; } .metric-item { text-align: center; min-width: 120px; } .metric-value { font-size: 32px; font-weight: 600; color: #3498db; } .metric-value.danger { color: #e74c3c; } .metric-label { font-size: 14px; color: #666; margin-top: 5px; } .refresh-btn { float: right; } .tabs { display: flex; gap: 8px; margin-bottom: 15px; } .tab { padding: 6px 16px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; background: white; } .tab.active { background: #3498db; color: white; border-color: #3498db; } .truncated { max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } </style> </head> <body> <div class="header"> <h1>Data Sync Dashboard</h1> </div> <div class="container"> <div class="card"> <h2>Overview</h2> <div class="metric"> <div class="metric-item"> <div class="metric-value" id="totalJobs">-</div> <div class="metric-label">Total Jobs</div> </div> <div class="metric-item"> <div class="metric-value" id="activeJobs">-</div> <div class="metric-label">Active Jobs</div> </div> <div class="metric-item"> <div class="metric-value" id="totalLogs">-</div> <div class="metric-label">Total Runs</div> </div> <div class="metric-item"> <div class="metric-value danger" id="failedLogs">-</div> <div class="metric-label">Failed</div> </div> <div class="metric-item"> <div class="metric-value danger" id="totalFailures">-</div> <div class="metric-label">Failure Records</div> </div> </div> </div> <div class="card"> <h2> Sync Jobs <button class="btn btn-primary refresh-btn" onclick="loadJobs()">Refresh</button> </h2> <table> <thead> <tr> <th>Name</th> <th>Scope</th> <th>Mode</th> <th>Last Run</th> <th>Status</th> <th>Actions</th> </tr> </thead> <tbody id="jobsTable"> <tr><td colspan="6">Loading...</td></tr> </tbody> </table> </div> <div class="card"> <h2> Recent Logs <button class="btn btn-primary refresh-btn" onclick="loadLogs()">Refresh</button> </h2> <table> <thead> <tr> <th>Job</th> <th>Status</th> <th>Started</th> <th>Duration</th> <th>Extracted</th> <th>Loaded</th> <th>Failed</th> </tr> </thead> <tbody id="logsTable"> <tr><td colspan="7">Loading...</td></tr> </tbody> </table> </div> <div class="card"> <h2> Recent Failures <button class="btn btn-primary refresh-btn" onclick="loadFailures()">Refresh</button> </h2> <table> <thead> <tr> <th>Document ID</th> <th>Collection</th> <th>Phase</th> <th>Error Type</th> <th>Error Message</th> <th>Time</th> </tr> </thead> <tbody id="failuresTable"> <tr><td colspan="6">Loading...</td></tr> </tbody> </table> </div> </div> <script> const API_BASE = '/api/v1'; async function loadJobs() { try { const res = await fetch(API_BASE + '/jobs'); const data = await res.json(); renderJobs(data.items); document.getElementById('totalJobs').textContent = data.total; document.getElementById('activeJobs').textContent = data.items.filter(j => j.enabled).length; } catch (e) { console.error('Failed to load jobs:', e); } } function renderJobs(jobs) { const tbody = document.getElementById('jobsTable'); if (jobs.length === 0) { tbody.innerHTML = '<tr><td colspan="6">No jobs found</td></tr>'; return; } tbody.innerHTML = jobs.map(job => ` <tr> <td>${job.name}</td> <td>${job.scope}</td> <td>${job.mode}</td> <td>${job.last_run_at ? new Date(job.last_run_at).toLocaleString() : '-'}</td> <td><span class="status ${job.enabled ? 'completed' : 'pending'}"> ${job.enabled ? 'Enabled' : 'Disabled'}</span></td> <td> <button class="btn btn-primary" onclick="runJob('${job._id || job.id}')"> Run</button> </td> </tr> `).join(''); } async function loadLogs() { try { const res = await fetch(API_BASE + '/logs?limit=20'); const data = await res.json(); renderLogs(data.items); document.getElementById('totalLogs').textContent = data.total; document.getElementById('failedLogs').textContent = data.items.filter(l => l.status === 'failed').length; } catch (e) { console.error('Failed to load logs:', e); } } function renderLogs(logs) { const tbody = document.getElementById('logsTable'); if (logs.length === 0) { tbody.innerHTML = '<tr><td colspan="7">No logs found</td></tr>'; return; } tbody.innerHTML = logs.map(log => { const m = log.metrics || {}; return ` <tr> <td>${log.job_name}</td> <td><span class="status ${log.status}">${log.status}</span></td> <td>${new Date(log.started_at).toLocaleString()}</td> <td>${log.duration_seconds ? log.duration_seconds.toFixed(1) + 's' : '-'}</td> <td>${m.total_extracted || 0}</td> <td>${m.total_loaded || 0}</td> <td>${m.total_failed || 0}</td> </tr>`; }).join(''); } async function loadFailures() { try { const res = await fetch(API_BASE + '/failures?limit=20'); const data = await res.json(); renderFailures(data.items); document.getElementById('totalFailures').textContent = data.total; } catch (e) { console.error('Failed to load failures:', e); } } function renderFailures(failures) { const tbody = document.getElementById('failuresTable'); if (failures.length === 0) { tbody.innerHTML = '<tr><td colspan="6">No failures found</td></tr>'; return; } tbody.innerHTML = failures.map(f => ` <tr> <td class="truncated">${f.document_id || '-'}</td> <td>${f.collection_name || '-'}</td> <td><span class="status failed">${f.phase || '-'}</span></td> <td>${f.error_type || '-'}</td> <td class="truncated">${f.error_message || '-'}</td> <td>${f.created_at ? new Date(f.created_at).toLocaleString() : '-'}</td> </tr> `).join(''); } async function loadMetrics() { try { const res = await fetch(API_BASE + '/metrics'); const data = await res.json(); document.getElementById('totalJobs').textContent = data.total_jobs; document.getElementById('totalLogs').textContent = data.recent_runs; document.getElementById('failedLogs').textContent = data.failed; } catch (e) { // Fall back to job/log based counts } } async function runJob(jobId) { try { const res = await fetch( API_BASE + '/jobs/' + jobId + '/run', { method: 'POST' }, ); const data = await res.json(); alert('Job ' + data.status + ': ' + data.message); loadLogs(); } catch (e) { alert('Failed to run job: ' + e.message); } } // Load data on page load loadJobs(); loadLogs(); loadFailures(); loadMetrics(); setInterval(() => { loadJobs(); loadLogs(); loadFailures(); loadMetrics(); }, 30000); // Auto-refresh every 30s </script> </body> </html> """