skyfffire hai 2 meses
pai
achega
c2faa1cf0d
Modificáronse 1 ficheiros con 85 adicións e 101 borrados
  1. 85 101
      monitor.html

+ 85 - 101
monitor.html

@@ -5,8 +5,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>套利流程监控</title>
     <script src="https://cdn.tailwindcss.com"></script>
-    <link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" type="text/css">
-    <script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest" type="text/javascript"></script>
+    <!-- 不再需要 simple-datatables 的 CSS 和 JS -->
     <style>
         body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,"Helvetica Neue", Arial, "Noto Sans", sans-serif,"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; background-color: #f7fafc; }
         .modal { transition: opacity 0.25s ease; }
@@ -15,15 +14,11 @@
         .log-msg { white-space: pre-wrap; word-break: break-all; font-size: 0.75rem; line-height: 1.5; font-family: 'Menlo', 'Monaco', 'Consolas', "Liberation Mono", "Courier New", monospace; background-color: #f9fafb; padding: 4px 6px; border: 1px solid #e5e7eb; border-radius: 4px; margin-top: 2px; }
         .table-responsive-container { overflow-x: auto; }
         .status-success { color: #10b981; } .status-fail { color: #ef4444; } .status-pending { color: #f59e0b; } .status-info { color: #3b82f6; }
-        .datatable-wrapper { padding: 0; }
-        .datatable-container { border: 1px solid #e2e8f0; border-radius: 0.375rem; }
-        .datatable-top { padding: 0.5rem; }
-        .datatable-input { padding: 0.3rem 0.6rem; font-size: 0.875rem; border-radius: 0.375rem; border: 1px solid #d1d5db; }
-        .datatable-table { font-size: 0.875rem; }
-        .datatable-table th, .datatable-table td { padding: 0.6rem 0.8rem; border-bottom: 1px solid #e2e8f0; }
-        .datatable-bottom { display: none; }
-        .datatable-sorter { color: #4b5563; text-decoration: none; }
-        .datatable-sorter::before, .datatable-sorter::after { border-color: #9ca3af; }
+        /* 简单表格样式 */
+        .simple-table { font-size: 0.875rem; min-width: 100%; }
+        .simple-table th, .simple-table td { padding: 0.6rem 0.8rem; border-bottom: 1px solid #e2e8f0; text-align: left; }
+        .simple-table thead { background-color: #f9fafb; }
+        .simple-table th { font-medium text-xs text-gray-500 uppercase tracking-wider; }
     </style>
 </head>
 <body class="text-gray-800 antialiased">
@@ -49,21 +44,21 @@
         <div id="tabContent">
             <div class="tab-pane bg-white rounded-lg shadow-sm" id="processing" role="tabpanel">
                 <div class="table-responsive-container">
-                    <table class="min-w-full text-sm">
-                        <thead class="bg-gray-50"><tr><th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">交易对</th><th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th><th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden md:table-cell">利润</th><th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden sm:table-cell">创建时间</th><th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th></tr></thead>
+                    <!-- 使用新的简单表格样式 -->
+                    <table class="simple-table">
+                        <thead><tr><th>交易对</th><th>状态</th><th class="hidden md:table-cell">利润</th><th class="hidden sm:table-cell">创建时间</th><th>操作</th></tr></thead>
                         <tbody id="processing-list" class="divide-y divide-gray-200"></tbody>
                     </table>
                 </div>
             </div>
+            <!-- 历史记录容器,JS会填充内容 -->
             <div class="tab-pane hidden bg-white rounded-lg shadow-sm" id="history" role="tabpanel">
-                <div class="table-responsive-container">
-                    <table id="history-table" class="min-w-full"></table>
-                </div>
+                 <div id="history-container" class="table-responsive-container"></div>
             </div>
         </div>
     </div>
 
-    <!-- Modal -->
+    <!-- Modal (保持不变) -->
     <div id="taskDetailModal" class="modal fixed inset-0 bg-gray-600 bg-opacity-75 h-full w-full flex items-center justify-center hidden z-50">
         <div class="modal-content relative mx-auto p-4 sm:p-5 border w-full shadow-xl rounded-lg bg-white">
             <div class="flex justify-between items-center pb-3 border-b border-gray-200 sticky top-0 bg-white z-10 -mt-4 -mx-4 sm:-mt-5 sm:-mx-5 px-4 sm:px-5 pt-4 sm:pt-5 rounded-t-lg">
@@ -79,12 +74,11 @@
     <script>
         document.addEventListener('DOMContentLoaded', () => {
             const API_BASE_URL = 'http://localhost:1888';
-            const REFRESH_INTERVAL_MS = 10000;
+            const REFRESH_INTERVAL_MS = 10000; // 自动刷新间隔改为10秒
             let autoRefreshIntervalId = null;
             let isAutoRefreshPaused = false;
             let allTasksData = {};
-            let historyDataTable = null;
-            let isFetching = false; // <--- 在这里添加“刷新锁”变量
+            // 不再需要 isFetching 和 historyDataTable 变量
 
             const refreshButton = document.getElementById('refreshButton');
             const toggleAutoRefreshButton = document.getElementById('toggleAutoRefreshButton');
@@ -95,7 +89,7 @@
             const modalBody = document.getElementById('modalBody');
             const closeModalButton = document.getElementById('closeModalButton');
 
-            // --- Helper Functions ---
+            // --- Helper Functions (保持不变) ---
             function getStatusTextClass(status) {
                 status = status ? status.toLowerCase() : '';
                 if (status_includes_any(status, ['fail', 'error'])) return 'status-fail';
@@ -122,18 +116,24 @@
             }
 
             // --- Table Rendering Functions ---
+            // processing 表格渲染基本不变
             function renderProcessingTable(data) {
                 const tableBody = document.getElementById('processing-list');
                 tableBody.innerHTML = '';
                 if (data && data.length > 0) {
-                    data.sort((a, b) => (a.creationTime || '0') < (b.creationTime || '0') ? 1 : -1);
+                    data.sort((a, b) => new Date(b.creationTime.replace(/,/g, '.')) - new Date(a.creationTime.replace(/,/g, '.')));
                     data.forEach(task => {
                         allTasksData[task.id] = task;
-                        const tr = document.createElement('tr');
-                        tr.className = 'hover:bg-gray-50';
                         const profitText = task.profit !== null && !isNaN(parseFloat(task.profit)) ? `${parseFloat(task.profit).toFixed(4)}` : 'N/A';
-                        tr.innerHTML = `<td class="px-3 py-2 whitespace-nowrap font-medium text-gray-900">${task.symbol||'N/A'}</td><td class="px-3 py-2 whitespace-nowrap"><span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${getStatusBadgeClasses(task.currentState)}">${task.currentState||'N/A'}</span></td><td class="px-3 py-2 whitespace-nowrap text-gray-600 hidden md:table-cell">${profitText}</td><td class="px-3 py-2 whitespace-nowrap text-gray-600 hidden sm:table-cell">${task.creationTime||'N/A'}</td><td class="px-3 py-2 whitespace-nowrap text-sm font-medium"><button class="text-indigo-600 hover:text-indigo-900 view-details-btn" data-task-id="${task.id}">详情</button></td>`;
-                        tableBody.appendChild(tr);
+                        const rowHtml = `
+                            <tr class="hover:bg-gray-50">
+                                <td class="px-3 py-2 whitespace-nowrap font-medium text-gray-900">${task.symbol||'N/A'}</td>
+                                <td class="px-3 py-2 whitespace-nowrap"><span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${getStatusBadgeClasses(task.currentState)}">${task.currentState||'N/A'}</span></td>
+                                <td class="px-3 py-2 whitespace-nowrap text-gray-600 hidden md:table-cell">${profitText}</td>
+                                <td class="px-3 py-2 whitespace-nowrap text-gray-600 hidden sm:table-cell">${task.creationTime||'N/A'}</td>
+                                <td class="px-3 py-2 whitespace-nowrap text-sm font-medium"><button class="text-indigo-600 hover:text-indigo-900 view-details-btn" data-task-id="${task.id}">详情</button></td>
+                            </tr>`;
+                        tableBody.innerHTML += rowHtml;
                     });
                 } else {
                     tableBody.innerHTML = `<tr><td colspan="5" class="text-center p-4 text-gray-500">没有正在处理的任务。</td></tr>`;
@@ -141,111 +141,96 @@
                 processingCountBadge.textContent = data.length;
             }
 
+            // 【【【核心修改】】】
+            // 重写历史记录表格渲染函数,不再使用 simple-datatables
             function renderHistoryDataTable(data) {
-                const tableContainer = document.getElementById('history-table');
-
-                // 1. 如果 DataTable 实例已存在,则彻底销毁它并清理 DOM
-                if (historyDataTable) {
-                    historyDataTable.destroy();
-                    // 关键步骤:手动清空容器,确保创建一个全新的、干净的环境
-                    tableContainer.innerHTML = ''; 
-                }
+                const container = document.getElementById('history-container');
+                historyCountBadge.textContent = data.length;
 
-                // 2. 准备数据
-                // 如果没有数据,显示提示信息并提前退出
                 if (!data || data.length === 0) {
-                    tableContainer.innerHTML = '<thead><tr><th>交易对</th><th>状态</th><th>利润</th><th>创建时间</th><th>操作</th></tr></thead><tbody><tr><td colspan="5" class="text-center p-4 text-gray-500">没有历史记录。</td></tr></tbody>';
-                    historyCountBadge.textContent = 0;
-                    historyDataTable = null; // 确保实例也被清空
+                    container.innerHTML = '<div class="text-center p-4 text-gray-500">没有历史记录。</div>';
                     return;
                 }
-                
-                // 将原始数据映射为表格需要的格式
-                data.forEach(task => allTasksData[task.id] = task); 
-                const headings = ['交易对', '状态', '利润', '创建时间', '操作'];
-                const tableData = data.map(task => {
-                    let creationTimeForSort = task.creationTime ? String(task.creationTime).replace(/,/g, '.') : "N/A";
-                    return [
-                        task.symbol || 'N/A',
-                        `<span class="text-xs font-semibold ${getStatusTextClass(task.currentState)}">${task.currentState || 'N/A'}</span>`,
-                        task.profit !== null && !isNaN(parseFloat(task.profit)) ? parseFloat(task.profit) : null,
-                        creationTimeForSort,
-                        `<button class="text-indigo-600 hover:text-indigo-900 view-details-btn" data-task-id="${task.id}">详情</button>`
-                    ];
-                });
 
-                historyCountBadge.textContent = data.length;
-                
-                // 3. 在清理过的容器上创建全新的 DataTable 实例
-                historyDataTable = new simpleDatatables.DataTable(tableContainer, {
-                    data: { headings, data: tableData },
-                    paging: false,
-                    perPageSelect: false,
-                    searchable: true,
-                    labels: { placeholder: "搜索...", noRows: "未找到记录" },
-                    columns: [ 
-                        { select: 2, type: 'number' },
-                        { select: 3, type: 'date', format: "YYYY-MM-DD HH:mm:ss.SSS" },
-                        { select: 4, sortable: false }
-                    ]
-                });
-                
-                // 确保每次创建后都进行排序
-                historyDataTable.on('datatable.init', () => {
-                    historyDataTable.columns.sort(3, 'desc');
+                // 1. 手动排序:按创建时间倒序排列
+                data.sort((a, b) => {
+                    // 为了防止 creationTime 不存在导致程序崩溃,提供一个默认的旧时间
+                    const timeA = a.creationTime[0] || '1970-01-01 00:00:00,000';
+                    const timeB = b.creationTime[0] || '1970-01-01 00:00:00,000';
+
+                    // 将 'YYYY-MM-DD HH:mm:ss,SSS' 格式的字符串转换为可比较的 Date 对象
+                    // .replace(',', '.') 是为了确保所有浏览器都能正确解析这个时间格式
+                    const dateB = new Date(timeB.replace(',', '.'));
+                    const dateA = new Date(timeA.replace(',', '.'));
+
+                    // 返回 b - a 实现时间上的倒序排列(最新的在最前)
+                    return dateB - dateA;
                 });
 
-                // 对于重建的表格,需要手动触发一次排序,因为 'datatable.init' 可能仅在首次初始化时触发
-                if (historyDataTable.initialized) {
-                   historyDataTable.columns.sort(3, 'desc');
-                }
+                // 2. 生成行HTML
+                const rowsHtml = data.map(task => {
+                    allTasksData[task.id] = task; // 存储任务数据以供弹窗使用
+                    const profitText = task.profit !== null && !isNaN(parseFloat(task.profit)) ? `${parseFloat(task.profit).toFixed(4)}` : 'N/A';
+                    return `
+                        <tr class="hover:bg-gray-50">
+                            <td>${task.symbol || 'N/A'}</td>
+                            <td><span class="text-xs font-semibold ${getStatusTextClass(task.currentState)}">${task.currentState || 'N/A'}</span></td>
+                            <td class="hidden md:table-cell">${profitText}</td>
+                            <td class="hidden sm:table-cell">${task.creationTime || 'N/A'}</td>
+                            <td><button class="text-indigo-600 hover:text-indigo-900 view-details-btn" data-task-id="${task.id}">详情</button></td>
+                        </tr>`;
+                }).join('');
+
+                // 3. 组装完整的表格HTML并一次性写入DOM
+                const tableHtml = `
+                    <table class="simple-table">
+                        <thead>
+                            <tr>
+                                <th>交易对</th>
+                                <th>状态</th>
+                                <th class="hidden md:table-cell">利润</th>
+                                <th class="hidden sm:table-cell">创建时间</th>
+                                <th>操作</th>
+                            </tr>
+                        </thead>
+                        <tbody class="divide-y divide-gray-200">
+                            ${rowsHtml}
+                        </tbody>
+                    </table>`;
+                
+                container.innerHTML = tableHtml;
             }
 
-            // --- Main Logic Functions ---
+            // --- Main Logic Functions (简化) ---
             async function fetchData() {
-                // 检查锁:如果当前正在获取数据,则直接跳过本次刷新,防止冲突
-                if (isFetching) {
-                    console.log("刷新任务正在进行中,跳过此次周期。");
-                    return;
-                }
-
-                // 上锁:标记刷新任务开始
-                isFetching = true; 
-
                 try {
                     const [processingRes, historyRes] = await Promise.all([
                         fetch(`${API_BASE_URL}/processing`),
                         fetch(`${API_BASE_URL}/history`)
                     ]);
                     
-                    // 检查网络请求是否成功
                     if (!processingRes.ok) throw new Error(`Processing fetch failed: ${processingRes.status}`);
                     if (!historyRes.ok) throw new Error(`History fetch failed: ${historyRes.status}`);
                     
                     const processingData = await processingRes.json();
                     const historyData = await historyRes.json();
 
-                    // 渲染是同步操作,在锁的保护下是安全的
                     renderProcessingTable(processingData);
                     renderHistoryDataTable(historyData);
                 } catch (error) {
                     console.error("获取数据失败:", error);
-                    // 即使出错,也要更新UI以反馈错误
                     document.getElementById('processing-list').innerHTML = `<tr><td colspan="5" class="text-center p-4 text-red-500">加载数据失败</td></tr>`;
-                    const historyTable = document.getElementById('history-table');
-                    if(historyDataTable) historyDataTable.destroy();
-                    historyTable.innerHTML = `<thead><tr><th>交易对</th><th>状态</th><th>利润</th><th>创建时间</th><th>操作</th></tr></thead><tbody><tr><td colspan="5" class="text-center p-4 text-red-500">加载数据失败</td></tr></tbody>`;
-                    
+                    document.getElementById('history-container').innerHTML = `<div class="text-center p-4 text-red-500">加载数据失败</div>`;
                     processingCountBadge.textContent = 'ERR';
                     historyCountBadge.textContent = 'ERR';
-                } finally {
-                    // 释放锁:无论成功还是失败,最后都要确保释放锁,以便下次刷新可以进行
-                    isFetching = false; 
                 }
             }
+
             function refreshAllData() { console.log("Refreshing data..."); fetchData(); }
             function startAutoRefresh() { if (autoRefreshIntervalId) clearInterval(autoRefreshIntervalId); autoRefreshIntervalId = setInterval(refreshAllData, REFRESH_INTERVAL_MS); toggleAutoRefreshButton.textContent = '暂停自动刷新'; toggleAutoRefreshButton.classList.remove('bg-green-500');toggleAutoRefreshButton.classList.add('bg-gray-500'); isAutoRefreshPaused = false; }
             function stopAutoRefresh() { if (autoRefreshIntervalId) clearInterval(autoRefreshIntervalId); autoRefreshIntervalId = null; toggleAutoRefreshButton.textContent = '恢复自动刷新'; toggleAutoRefreshButton.classList.remove('bg-gray-500');toggleAutoRefreshButton.classList.add('bg-green-500'); isAutoRefreshPaused = true; }
+            
+            // --- Event Listeners and Initial Load (基本不变) ---
             function switchTab(activeTab) {
                 tabButtons.forEach(tab => {
                     tab.classList.remove('border-blue-500', 'text-blue-600');
@@ -283,7 +268,6 @@
             }
             function closeModal() { taskDetailModal.classList.add('hidden'); document.body.classList.remove('modal-active'); }
 
-            // --- Event Listeners ---
             refreshButton.addEventListener('click', refreshAllData);
             toggleAutoRefreshButton.addEventListener('click', () => isAutoRefreshPaused ? startAutoRefresh() : stopAutoRefresh());
             tabButtons.forEach(tab => tab.addEventListener('click', () => switchTab(tab)));
@@ -291,16 +275,16 @@
             taskDetailModal.addEventListener('click', e => (e.target === taskDetailModal) && closeModal());
             document.addEventListener('keydown', e => (e.key === 'Escape' && !taskDetailModal.classList.contains('hidden')) && closeModal());
             document.getElementById('tabContent').addEventListener('click', e => {
-                if (e.target?.classList.contains('view-details-btn')) {
-                    openModal(e.target.dataset.taskId);
+                const button = e.target.closest('.view-details-btn');
+                if (button) {
+                    openModal(button.dataset.taskId);
                 }
             });
 
-            // --- Initial Load ---
             switchTab(document.getElementById('processing-tab'));
             refreshAllData();
             startAutoRefresh();
         });
     </script>
 </body>
-</html>
+</html>