Fix: Implement FixPlan part A9, A10, B
This commit is contained in:
@@ -27,6 +27,7 @@ document.querySelectorAll('.nav-item[data-page]').forEach(item => {
|
||||
if (page === 'alerts') loadAlerts();
|
||||
if (page === 'markets') loadMarkets();
|
||||
if (page === 'jobs') loadJobs();
|
||||
if (page === 'watchlist') loadWatchlist();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -106,14 +107,7 @@ async function manualAddTrader() {
|
||||
}
|
||||
}
|
||||
|
||||
async function manualUpdateTrader(id) {
|
||||
const res = await fetch(`/api/traders/${id}/refresh`, { method: 'POST' });
|
||||
if (res.ok) {
|
||||
alert('Sync triggered manually. Data will update in a few minutes.');
|
||||
} else {
|
||||
alert('Failed to trigger sync.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let currentPlatform = 'All';
|
||||
let currentSort = 'default';
|
||||
@@ -134,14 +128,16 @@ function refreshActivePage() {
|
||||
if (activePage === 'page-dashboard') loadDashboard();
|
||||
else if (activePage === 'page-traders') loadTraders();
|
||||
else if (activePage === 'page-markets') loadMarkets();
|
||||
else if (activePage === 'page-watchlist') loadWatchlist();
|
||||
}
|
||||
|
||||
// ─── API Helpers ───
|
||||
async function api(endpoint) {
|
||||
async function api(endpoint, options = {}) {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}${endpoint}`);
|
||||
const res = await fetch(`${API_BASE}${endpoint}`, options);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
return await res.json();
|
||||
const text = await res.text();
|
||||
return text ? JSON.parse(text) : true;
|
||||
} catch (err) {
|
||||
console.error(`API Error [${endpoint}]:`, err);
|
||||
return null;
|
||||
@@ -349,6 +345,41 @@ async function loadTraders() {
|
||||
`).join('');
|
||||
}
|
||||
|
||||
async function loadWatchlist() {
|
||||
const data = await api(`/api/watchlist`);
|
||||
if (!data) return;
|
||||
|
||||
const tbody = document.getElementById('watchlistBody');
|
||||
tbody.innerHTML = data.map(w => `
|
||||
<tr>
|
||||
<td class="trader-name">
|
||||
<div class="avatar">${w.displayName.substring(0, 2).toUpperCase()}</div>
|
||||
<div>
|
||||
<strong>${w.displayName}</strong><br>
|
||||
<span style="font-size:12px; color:var(--text-secondary)">${w.platformUserId.substring(0, 8)}...</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>${w.platform ?? 'Unknown'}</td>
|
||||
<td>${Number(w.copytradingScore || 0).toFixed(1)}</td>
|
||||
<td class="${w.winRate > 0.5 ? 'text-green' : 'text-red'}">${fmt.pct(w.winRate)}</td>
|
||||
<td class="${w.totalPnl >= 0 ? 'text-green' : 'text-red'}">${fmt.pnl(w.totalPnl)}</td>
|
||||
<td>${w.label || ''}</td>
|
||||
<td>${new Date(w.createdAt).toLocaleDateString()}</td>
|
||||
<td>
|
||||
<button class="btn-sm" onclick="viewTrader(${w.traderId})">View</button>
|
||||
<button class="btn-sm btn-outline-danger" onclick="removeFromWatchlist(${w.traderId})">Remove</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('') || '<tr><td colspan="8">Your watchlist is empty.</td></tr>';
|
||||
}
|
||||
|
||||
async function removeFromWatchlist(id) {
|
||||
if (confirm('Remove this trader from watchlist?')) {
|
||||
await api(`/api/traders/${id}/watchlist`, { method: 'DELETE' });
|
||||
loadWatchlist();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Alerts Page ───
|
||||
async function loadAlerts() {
|
||||
const data = await api('/api/alerts?count=50');
|
||||
@@ -429,22 +460,21 @@ async function viewTrader(id) {
|
||||
|
||||
const syncBtn = document.getElementById('btn-sync-trader');
|
||||
if (syncBtn) {
|
||||
syncBtn.onclick = () => manualUpdateTrader(id);
|
||||
syncBtn.onclick = () => queueHistorySync(id);
|
||||
}
|
||||
|
||||
const deepSyncBtn = document.getElementById('btn-deep-resync-trader');
|
||||
if (deepSyncBtn) {
|
||||
deepSyncBtn.onclick = () => {
|
||||
if (confirm("Are you sure? This will delete all compacted trades and reset positions, then fetch all historical trades via pagination.")) {
|
||||
queueDeepResync(id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const analyzeBtn = document.getElementById('btn-analyze-trader');
|
||||
if (analyzeBtn) {
|
||||
analyzeBtn.onclick = async () => {
|
||||
analyzeBtn.disabled = true;
|
||||
analyzeBtn.textContent = '...';
|
||||
try {
|
||||
await api(`/api/traders/${id}/force-analyze`, { method: 'POST' });
|
||||
alert('Deep Analysis queued! Please wait a moment and then refresh.');
|
||||
} finally {
|
||||
analyzeBtn.disabled = false;
|
||||
analyzeBtn.textContent = '⚙ Analyze';
|
||||
}
|
||||
};
|
||||
analyzeBtn.onclick = () => queueTraderAnalysis(id);
|
||||
}
|
||||
|
||||
const aiBtn = document.getElementById('btn-ai-analysis');
|
||||
@@ -658,6 +688,15 @@ async function queueHistorySync(id) {
|
||||
}
|
||||
}
|
||||
|
||||
async function queueDeepResync(id) {
|
||||
const res = await fetch(`/api/jobs/deep-resync/${id}`, { method: 'POST' });
|
||||
if (res.ok) {
|
||||
alert('Deep Resync job queued successfully.');
|
||||
} else {
|
||||
alert('Failed to queue deep resync.');
|
||||
}
|
||||
}
|
||||
|
||||
async function queueTraderAnalysis(id) {
|
||||
const res = await fetch(`/api/jobs/analyze/${id}`, { method: 'POST' });
|
||||
if (res.ok) {
|
||||
|
||||
Reference in New Issue
Block a user