feat: Jobs infrastructure, endpoints, UI and worker prioritization
This commit is contained in:
@@ -26,6 +26,7 @@ document.querySelectorAll('.nav-item[data-page]').forEach(item => {
|
||||
if (page === 'traders') loadTraders();
|
||||
if (page === 'alerts') loadAlerts();
|
||||
if (page === 'markets') loadMarkets();
|
||||
if (page === 'jobs') loadJobs();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -285,10 +286,25 @@ async function loadTraders() {
|
||||
const tbody = document.getElementById('allTradersBody');
|
||||
if (!data || !data.length) { tbody.innerHTML = '<tr><td colspan="11"><div class="empty-state"><p>No traders tracked yet.</p></div></td></tr>'; return; }
|
||||
|
||||
const sortSelect = document.getElementById('tradersSort');
|
||||
if (sortSelect) currentSort = sortSelect.value;
|
||||
|
||||
const filterWinrate = document.getElementById('filterWinrate')?.value || 'all';
|
||||
const filterCopyability = document.getElementById('filterCopyability')?.value || 'all';
|
||||
|
||||
// Filtering
|
||||
if (filterWinrate === 'gt50') data = data.filter(t => t.winRate > 50);
|
||||
if (filterWinrate === 'gt60') data = data.filter(t => t.winRate > 60);
|
||||
|
||||
if (filterCopyability === 'gt50') data = data.filter(t => t.copytradingCopyabilityScore > 50);
|
||||
if (filterCopyability === 'gt80') data = data.filter(t => t.copytradingCopyabilityScore > 80);
|
||||
|
||||
// Sorting
|
||||
if (currentSort === 'score') data.sort((a, b) => b.combinedScore - a.combinedScore);
|
||||
else if (currentSort === 'name') data.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
||||
else if (currentSort === 'pnl') data.sort((a, b) => b.totalPnl - a.totalPnl);
|
||||
else if (currentSort === 'winrate') data.sort((a, b) => b.winRate - a.winRate);
|
||||
else if (currentSort === 'copyability') data.sort((a, b) => (b.copytradingCopyabilityScore || 0) - (a.copytradingCopyabilityScore || 0));
|
||||
|
||||
tbody.innerHTML = data.map((t, i) => `
|
||||
<tr>
|
||||
@@ -302,7 +318,13 @@ async function loadTraders() {
|
||||
<td>${fmt.pnl(t.totalPnl)}</td>
|
||||
<td>${fmt.tier(t.tier)}</td>
|
||||
<td>${t.strategy}</td>
|
||||
<td><button class="btn-sm" onclick="viewTrader(${t.id})">Details</button></td>
|
||||
<td>
|
||||
<div style="display:flex; gap:4px;">
|
||||
<button class="btn-sm" onclick="viewTrader(${t.id})">Details</button>
|
||||
<button class="btn-sm" onclick="queueHistorySync(${t.id})" style="background:var(--bg-input); border:1px solid var(--border); color:var(--text)">Sync</button>
|
||||
<button class="btn-sm" onclick="queueTraderAnalysis(${t.id})" style="background:var(--bg-input); border:1px solid var(--border); color:var(--text)">Analyze</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
@@ -531,3 +553,48 @@ async function triggerAiAnalysis(id, manual) {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Jobs Page ───
|
||||
async function loadJobs() {
|
||||
const data = await api('/api/jobs');
|
||||
const tbody = document.getElementById('jobsBody');
|
||||
if (!data || !data.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="9"><div class="empty-state"><p>No jobs found.</p></div></td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = data.map(j => `
|
||||
<tr>
|
||||
<td>${j.id}</td>
|
||||
<td><strong>${j.jobType}</strong></td>
|
||||
<td>${j.status === 'Completed' ? '<span style="color:var(--pnl-positive)">Completed</span>' : j.status === 'Failed' ? '<span style="color:var(--pnl-negative)">Failed</span>' : j.status}</td>
|
||||
<td><a href="#" onclick="viewTrader(${j.traderId}); return false;" style="color:var(--primary);text-decoration:none;">${j.traderName || j.traderId}</a></td>
|
||||
<td>${fmt.time(j.createdAt)}</td>
|
||||
<td>${j.startedAt ? fmt.time(j.startedAt) : '—'}</td>
|
||||
<td>${j.completedAt ? fmt.time(j.completedAt) : '—'}</td>
|
||||
<td style="color:var(--pnl-negative); max-width:200px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${j.errorMessage || ''}">${j.errorMessage || '—'}</td>
|
||||
<td><button class="btn-sm" onclick="viewTrader(${j.traderId})">View Trader</button></td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
async function queueHistorySync(id) {
|
||||
const res = await fetch(`/api/jobs/sync/${id}`, { method: 'POST' });
|
||||
if (res.ok) {
|
||||
alert('History Sync job queued successfully.');
|
||||
} else {
|
||||
alert('Failed to queue history sync.');
|
||||
}
|
||||
}
|
||||
|
||||
async function queueTraderAnalysis(id) {
|
||||
const res = await fetch(`/api/jobs/analyze/${id}`, { method: 'POST' });
|
||||
if (res.ok) {
|
||||
alert('Trader Analysis job queued successfully.');
|
||||
} else {
|
||||
alert('Failed to queue trader analysis.');
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Dashboard
|
||||
loadDashboard();
|
||||
|
||||
Reference in New Issue
Block a user