|
|
|
@ -1330,10 +1330,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
// Navigation
|
|
|
|
// Navigation
|
|
|
|
function navigateTo(page) {
|
|
|
|
function navigateTo(page) {
|
|
|
|
|
|
|
|
if (!page) return;
|
|
|
|
|
|
|
|
const pageEl = document.getElementById(`page-${page}`);
|
|
|
|
|
|
|
|
if (!pageEl) return;
|
|
|
|
|
|
|
|
|
|
|
|
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
|
|
|
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
|
|
|
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
|
|
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById(`page-${page}`).classList.add('active');
|
|
|
|
pageEl.classList.add('active');
|
|
|
|
document.querySelector(`.nav-item[data-page="${page}"]`).classList.add('active');
|
|
|
|
document.querySelector(`.nav-item[data-page="${page}"]`).classList.add('active');
|
|
|
|
|
|
|
|
|
|
|
|
const titles = {
|
|
|
|
const titles = {
|
|
|
|
@ -1347,7 +1351,7 @@
|
|
|
|
document.getElementById('pageTitle').textContent = titles[page] || page;
|
|
|
|
document.getElementById('pageTitle').textContent = titles[page] || page;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.querySelectorAll('.nav-item').forEach(item => {
|
|
|
|
document.querySelectorAll('.nav-item[data-page]').forEach(item => {
|
|
|
|
item.addEventListener('click', () => navigateTo(item.dataset.page));
|
|
|
|
item.addEventListener('click', () => navigateTo(item.dataset.page));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
@ -2019,15 +2023,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
const jsonStr = JSON.stringify(exportObj, null, 2);
|
|
|
|
const jsonStr = JSON.stringify(exportObj, null, 2);
|
|
|
|
const blob = new Blob([jsonStr], { type: 'application/json' });
|
|
|
|
const blob = new Blob([jsonStr], { type: 'application/json' });
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
const downloadUrl = URL.createObjectURL(blob);
|
|
|
|
|
|
|
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
const a = document.createElement('a');
|
|
|
|
a.href = url;
|
|
|
|
a.href = downloadUrl;
|
|
|
|
a.download = filename;
|
|
|
|
a.download = filename;
|
|
|
|
document.body.appendChild(a);
|
|
|
|
document.body.appendChild(a);
|
|
|
|
a.click();
|
|
|
|
a.click();
|
|
|
|
document.body.removeChild(a);
|
|
|
|
document.body.removeChild(a);
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
URL.revokeObjectURL(downloadUrl);
|
|
|
|
|
|
|
|
|
|
|
|
const periodCount = currentQueryData.timeframes.length;
|
|
|
|
const periodCount = currentQueryData.timeframes.length;
|
|
|
|
const totalCandles = currentQueryData.timeframes.reduce((sum, tf) => sum + (tf.candles ? tf.candles.length : 0), 0);
|
|
|
|
const totalCandles = currentQueryData.timeframes.reduce((sum, tf) => sum + (tf.candles ? tf.candles.length : 0), 0);
|
|
|
|
@ -2096,9 +2100,9 @@
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const queryString = params.toString();
|
|
|
|
const queryString = params.toString();
|
|
|
|
const url = `${API}/data/latest/${symbol}${queryString ? '?' + queryString : ''}`;
|
|
|
|
const fetchUrl = `${API}/data/latest/${symbol}${queryString ? '?' + queryString : ''}`;
|
|
|
|
|
|
|
|
|
|
|
|
const fetchRes = await fetch(url);
|
|
|
|
const fetchRes = await fetch(fetchUrl);
|
|
|
|
if (!fetchRes.ok) {
|
|
|
|
if (!fetchRes.ok) {
|
|
|
|
failedCount++;
|
|
|
|
failedCount++;
|
|
|
|
failedSymbols.push(symbol);
|
|
|
|
failedSymbols.push(symbol);
|
|
|
|
@ -2133,15 +2137,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
const jsonStr = JSON.stringify(exportObj, null, 2);
|
|
|
|
const jsonStr = JSON.stringify(exportObj, null, 2);
|
|
|
|
const blob = new Blob([jsonStr], { type: 'application/json' });
|
|
|
|
const blob = new Blob([jsonStr], { type: 'application/json' });
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
const downloadUrl = URL.createObjectURL(blob);
|
|
|
|
|
|
|
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
const a = document.createElement('a');
|
|
|
|
a.href = url;
|
|
|
|
a.href = downloadUrl;
|
|
|
|
a.download = filename;
|
|
|
|
a.download = filename;
|
|
|
|
document.body.appendChild(a);
|
|
|
|
document.body.appendChild(a);
|
|
|
|
a.click();
|
|
|
|
a.click();
|
|
|
|
document.body.removeChild(a);
|
|
|
|
document.body.removeChild(a);
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
URL.revokeObjectURL(downloadUrl);
|
|
|
|
|
|
|
|
|
|
|
|
const candleCount = fetchData.timeframes.reduce((sum, tf) => sum + (tf.candles ? tf.candles.length : 0), 0);
|
|
|
|
const candleCount = fetchData.timeframes.reduce((sum, tf) => sum + (tf.candles ? tf.candles.length : 0), 0);
|
|
|
|
successCount++;
|
|
|
|
successCount++;
|
|
|
|
@ -2618,51 +2622,6 @@
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Load Tasks
|
|
|
|
|
|
|
|
async function loadTasks() {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const res = await fetch(`${API}/tasks`);
|
|
|
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!data.tasks.length) {
|
|
|
|
|
|
|
|
document.getElementById('taskTable').innerHTML = '<div class="empty-state"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg><p>暂无定时任务</p></div>';
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let html = `<table>
|
|
|
|
|
|
|
|
<thead><tr><th>ID</th><th>品种</th><th>周期</th><th>间隔</th><th>状态</th><th>最后执行</th><th>操作</th></tr></thead>
|
|
|
|
|
|
|
|
<tbody>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const t of data.tasks) {
|
|
|
|
|
|
|
|
const statusBadge = t.running
|
|
|
|
|
|
|
|
? '<span class="badge badge-success">运行中</span>'
|
|
|
|
|
|
|
|
: t.enabled
|
|
|
|
|
|
|
|
? '<span class="badge badge-warning">已停止</span>'
|
|
|
|
|
|
|
|
: '<span class="badge badge-gray">已禁用</span>';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
html += `<tr>
|
|
|
|
|
|
|
|
<td>${t.id}</td>
|
|
|
|
|
|
|
|
<td><code style="color: var(--primary);">${t.symbol}</code></td>
|
|
|
|
|
|
|
|
<td>${t.periods.join(', ')}</td>
|
|
|
|
|
|
|
|
<td>${t.interval_seconds}s</td>
|
|
|
|
|
|
|
|
<td>${statusBadge}</td>
|
|
|
|
|
|
|
|
<td>${t.last_run ? new Date(t.last_run).toLocaleString() : '-'}</td>
|
|
|
|
|
|
|
|
<td>
|
|
|
|
|
|
|
|
${t.running
|
|
|
|
|
|
|
|
? `<button class="btn btn-warning btn-sm" onclick="stopTask(${t.id})">停止</button>`
|
|
|
|
|
|
|
|
: `<button class="btn btn-success btn-sm" onclick="startTask(${t.id})">启动</button>`
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
<button class="btn btn-danger btn-sm" onclick="deleteTask(${t.id})">删除</button>
|
|
|
|
|
|
|
|
</td>
|
|
|
|
|
|
|
|
</tr>`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
html += '</tbody></table>';
|
|
|
|
|
|
|
|
document.getElementById('taskTable').innerHTML = html;
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
addLog(`加载任务失败: ${e.message}`, 'error');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function stopTask(id) {
|
|
|
|
async function stopTask(id) {
|
|
|
|
await fetch(`${API}/tasks/${id}/stop`, { method: 'POST' });
|
|
|
|
await fetch(`${API}/tasks/${id}/stop`, { method: 'POST' });
|
|
|
|
showToast('任务已停止', 'success');
|
|
|
|
showToast('任务已停止', 'success');
|
|
|
|
|