skyfffire 5 місяців тому
батько
коміт
0b488f2480
2 змінених файлів з 318 додано та 203 видалено
  1. 317 202
      monitor.html
  2. 1 1
      price_checker_ok.py

+ 317 - 202
monitor.html

@@ -3,109 +3,167 @@
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>套利流程监控</title>
-    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
+    <title>套利流程监控 (Tailwind)</title>
+    <script src="https://cdn.tailwindcss.com"></script>
     <style>
         body {
             font-family: sans-serif;
-            padding: 20px;
-            background-color: #f4f7f6; /* Lighter background for the page */
+            background-color: #f3f4f6; /* Tailwind bg-gray-100 */
         }
-        .task-card {
-            margin-bottom: 15px; 
+        .modal {
+            transition: opacity 0.25s ease;
         }
-        .task-card .card-header { 
-            padding: 0; 
-        }
-        .task-card .card-header button {
-            color: #333; 
-        }
-        .task-card .card-header button:hover {
-            background-color: #e9ecef; 
+        .modal-active {
+            overflow-x: hidden;
+            overflow-y: auto;
         }
-        .state-flow-table th, .state-flow-table td {
-            font-size: 0.9em;
-            vertical-align: middle;
+        /* MODAL CONTENT: 调整最大高度和溢出处理 */
+        .modal-content {
+            max-height: calc(100vh - 4rem); /* 模态框内容最大高度,留出上下边距 */
+            overflow-y: auto;
         }
-        .status-success { color: green; font-weight: bold; }
-        .status-fail { color: red; font-weight: bold; }
-        .status-pending { color: orange; }
-        .status-info { color: blue; }
-
         .log-msg {
             white-space: pre-wrap;
             word-break: break-all;
-            max-height: 150px; 
+            /* max-height is now set via inline style in JS for easier adjustment */
             overflow-y: auto;
-            font-size: 0.85em;
-            background-color: #fdfdfd; 
-            padding: 8px;
-            border: 1px solid #eee;
-            border-radius: 3px;
-            margin-top: 5px;
+            font-size: 0.82em; /* Slightly larger log font */
+            line-height: 1.45; /* Increased line height for log messages */
+            background-color: #f9fafb;
+            padding: 6px 8px;
+            border: 1px solid #e5e7eb;
+            border-radius: 4px;
+            margin-top: 3px;
         }
-        .section-title {
-            display: flex;
-            align-items: center;
-            margin-bottom: 1rem;
-        }
-        .section-title h2 {
-            margin-bottom: 0;
-            margin-right: 0.5rem;
-        }
-        .button-group {
-            margin-bottom: 1.5rem; /* Increased margin for button group */
+        .table-responsive-container {
+            overflow-x: auto;
+            -webkit-overflow-scrolling: touch;
         }
+        .status-success { color: #10b981; font-weight: 600; } /* Tailwind green-600 semibold */
+        .status-fail { color: #ef4444; font-weight: 600; }    /* Tailwind red-500 semibold */
+        .status-pending { color: #f59e0b; font-weight: 500; } /* Tailwind amber-500 medium */
+        .status-info { color: #3b82f6; font-weight: 500; }    /* Tailwind blue-500 medium */
     </style>
 </head>
-<body>
-    <div class="container-fluid">
-        <h1 class="mb-4">套利流程监控</h1>
-        <div class="button-group">
-            <button id="refreshButton" class="btn btn-primary mr-2">手动刷新</button>
-            <button id="toggleAutoRefreshButton" class="btn btn-secondary">暂停自动刷新</button>
+<body class="text-gray-800">
+    <div class="container mx-auto p-4 md:p-6">
+        <!-- Header and Tabs remain the same -->
+        <header class="mb-6">
+            <h1 class="text-3xl font-bold text-gray-700">套利流程监控</h1>
+            <div class="mt-4 space-x-2">
+                <button id="refreshButton" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition duration-150">手动刷新</button>
+                <button id="toggleAutoRefreshButton" class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600 transition duration-150">暂停自动刷新</button>
+            </div>
+        </header>
+
+        <div class="mb-4 border-b border-gray-200">
+            <ul class="flex flex-wrap -mb-px text-sm font-medium text-center" id="myTab" role="tablist">
+                <li class="mr-2" role="presentation">
+                    <button class="tab-button inline-block p-4 border-b-2 rounded-t-lg" id="processing-tab" type="button" role="tab" aria-controls="processing" aria-selected="true">
+                        正在处理 <span id="processing-count-badge" class="ml-1 px-2 py-0.5 bg-blue-100 text-blue-800 text-xs font-semibold rounded-full">0</span>
+                    </button>
+                </li>
+                <li class="mr-2" role="presentation">
+                    <button class="tab-button inline-block p-4 border-b-2 rounded-t-lg border-transparent hover:text-gray-600 hover:border-gray-300" id="history-tab" type="button" role="tab" aria-controls="history" aria-selected="false">
+                        历史记录 <span id="history-count-badge" class="ml-1 px-2 py-0.5 bg-gray-100 text-gray-800 text-xs font-semibold rounded-full">0</span>
+                    </button>
+                </li>
+            </ul>
         </div>
-        
-        <div class="row">
-            <div class="col-md-6">
-                <div class="section-title">
-                    <h2>正在处理</h2>
-                    <span id="processing-count" class="badge badge-info badge-pill" style="font-size: 1rem;">0</span>
-                </div>
-                <div id="processing-list" class="accordion">
-                    <p>加载中...</p>
+
+        <div id="tabContent">
+            <div class="tab-pane p-1 bg-white rounded-lg shadow" id="processing" role="tabpanel" aria-labelledby="processing-tab">
+                <div class="table-responsive-container">
+                    <table class="min-w-full divide-y divide-gray-200">
+                        <thead class="bg-gray-50">
+                            <tr>
+                                <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">交易对</th>
+                                <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">当前状态</th>
+                                <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden md:table-cell">利润/阈值</th>
+                                <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden sm:table-cell">创建时间</th>
+                                <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
+                            </tr>
+                        </thead>
+                        <tbody id="processing-list" class="bg-white divide-y divide-gray-200">
+                             <tr><td colspan="5" class="text-center p-4 text-gray-500">加载中...</td></tr>
+                        </tbody>
+                    </table>
                 </div>
             </div>
-            <div class="col-md-6">
-                <div class="section-title">
-                    <h2>历史记录</h2>
-                    <span id="history-count" class="badge badge-secondary badge-pill" style="font-size: 1rem;">0</span>
-                </div>
-                <div id="history-list" class="accordion">
-                    <p>加载中...</p>
+            <div class="tab-pane hidden p-1 bg-white rounded-lg shadow" id="history" role="tabpanel" aria-labelledby="history-tab">
+                 <div class="table-responsive-container">
+                    <table class="min-w-full divide-y divide-gray-200">
+                        <thead class="bg-gray-50">
+                           <tr>
+                                <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">交易对</th>
+                                <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">最终状态</th>
+                                <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden md:table-cell">利润/阈值</th>
+                                <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden sm:table-cell">创建时间</th>
+                                <th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
+                            </tr>
+                        </thead>
+                        <tbody id="history-list" class="bg-white divide-y divide-gray-200">
+                             <tr><td colspan="5" class="text-center p-4 text-gray-500">加载中...</td></tr>
+                        </tbody>
+                    </table>
                 </div>
             </div>
         </div>
     </div>
 
+    <!-- Modal: INCREASED MAX-WIDTH, PADDING, and added outer padding for small screens -->
+    <div id="taskDetailModal" class="modal fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full flex items-center justify-center hidden z-50 p-4 sm:p-6 md:p-8">
+        <div class="modal-content relative mx-auto p-5 sm:p-6 border w-full max-w-xl md:max-w-3xl lg:max-w-5xl shadow-xl rounded-lg bg-white">
+            <div class="flex justify-between items-center pb-4 border-b border-gray-200">
+                <h3 class="text-xl sm:text-2xl font-semibold text-gray-800">任务详情</h3>
+                <button id="closeModalButton" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center">
+                    <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
+                    <span class="sr-only">关闭</span>
+                </button>
+            </div>
+            <div id="modalBody" class="pt-4 text-sm leading-relaxed space-y-3">
+                <!-- Modal content will be injected here -->
+            </div>
+        </div>
+    </div>
+
     <script>
+        // Constants and global variables (API_BASE_URL, etc.) remain the same
         const API_BASE_URL = 'http://localhost:5002';
-        const REFRESH_INTERVAL_MS = 10000; // 自动刷新间隔:10秒
+        const REFRESH_INTERVAL_MS = 10000;
         let autoRefreshIntervalId = null;
         let isAutoRefreshPaused = false;
+        let allTasksData = {};
 
         const refreshButton = document.getElementById('refreshButton');
         const toggleAutoRefreshButton = document.getElementById('toggleAutoRefreshButton');
+        const processingTab = document.getElementById('processing-tab');
+        const historyTab = document.getElementById('history-tab');
+        const processingPane = document.getElementById('processing');
+        const historyPane = document.getElementById('history');
+        const processingCountBadge = document.getElementById('processing-count-badge');
+        const historyCountBadge = document.getElementById('history-count-badge');
+        const taskDetailModal = document.getElementById('taskDetailModal');
+        const modalBody = document.getElementById('modalBody');
+        const closeModalButton = document.getElementById('closeModalButton');
 
-        // ... (getStatusClass, status_includes_any, getStatusBadgeClass, formatStateFlow, createTaskCard 函数保持不变) ...
-        function getStatusClass(status) { // For text color in stateFlow
+        // Helper functions (getStatusTextClass, getStatusBadgeClasses, status_includes_any) remain the same
+        function getStatusTextClass(status) {
             status = status ? status.toLowerCase() : 'info';
             if (status.includes('success') || status.includes('completed')) return 'status-success';
             if (status.includes('fail') || status.includes('failed') || status.includes('error')) return 'status-fail';
-            if (status.includes('pending') || status.includes('waiting') || status.includes('buying') || status_includes_any(status, ['received', 'processing', 'starting', 'pending_start'])) return 'status-pending';
+            if (status_includes_any(status, ['pending', 'waiting', 'buying', 'received', 'processing', 'starting', 'pending_start'])) return 'status-pending';
             return 'status-info';
         }
-        
+
+        function getStatusBadgeClasses(status) {
+            status = status ? status.toLowerCase() : 'info';
+            if (status.includes('success') || status.includes('completed')) return 'bg-green-100 text-green-800';
+            if (status.includes('fail') || status.includes('failed') || status.includes('error')) return 'bg-red-100 text-red-800';
+            if (status_includes_any(status, ['pending', 'waiting', 'buying', 'received', 'processing', 'starting', 'pending_start'])) return 'bg-yellow-100 text-yellow-800';
+            return 'bg-blue-100 text-blue-800';
+        }
+
         function status_includes_any(status, keywords) {
             for (const keyword of keywords) {
                 if (status.includes(keyword)) return true;
@@ -113,197 +171,254 @@
             return false;
         }
 
-        function getStatusBadgeClass(status) { // For Bootstrap badge component in card header
-            status = status ? status.toLowerCase() : 'info';
-            if (status.includes('success') || status.includes('completed')) return 'badge-success';
-            if (status.includes('fail') || status.includes('failed') || status.includes('error')) return 'badge-danger';
-            if (status_includes_any(status, ['pending', 'waiting', 'buying', 'received', 'processing', 'starting', 'pending_start'])) return 'badge-warning';
-            return 'badge-info';
-        }
-
-        function formatStateFlow(stateFlow) {
+        // MODIFIED: formatStateFlowForModal - Adjusted column widths and log-msg max-height
+        function formatStateFlowForModal(stateFlow) {
             if (!stateFlow || stateFlow.length === 0) {
-                return '<p><em>无状态流转记录。</em></p>';
+                return '<p class="text-gray-500 italic">无状态流转记录。</p>';
             }
+            // Increased padding, adjusted font, and widths for better readability
             let tableHtml = `
-                <table class="table table-sm table-hover table-bordered state-flow-table mt-2">
-                    <thead class="thead-light">
-                        <tr>
-                            <th>时间戳</th>
-                            <th>状态名</th>
-                            <th>消息</th>
-                            <th>结果</th>
-                        </tr>
-                    </thead>
-                    <tbody>
+                <div class="overflow-x-auto border border-gray-200 rounded-md shadow-sm">
+                    <table class="min-w-full divide-y divide-gray-200 text-xs sm:text-sm">
+                        <thead class="bg-gray-100">
+                            <tr>
+                                <th class="px-3 py-2.5 sm:px-4 text-left font-semibold text-gray-600 uppercase tracking-wider" style="min-width: 130px;">时间戳</th>
+                                <th class="px-3 py-2.5 sm:px-4 text-left font-semibold text-gray-600 uppercase tracking-wider" style="min-width: 100px;">状态名</th>
+                                <th class="px-3 py-2.5 sm:px-4 text-left font-semibold text-gray-600 uppercase tracking-wider" style="min-width: 250px;">消息</th>
+                                <th class="px-3 py-2.5 sm:px-4 text-left font-semibold text-gray-600 uppercase tracking-wider" style="min-width: 80px;">结果</th>
+                            </tr>
+                        </thead>
+                        <tbody class="bg-white divide-y divide-gray-200">
             `;
             stateFlow.forEach(state => {
                 let actualStatus = state.status;
-                if (Array.isArray(state.status)) { 
-                    actualStatus = state.status[state.status.length-1];
+                if (Array.isArray(state.status)) {
+                    actualStatus = state.status[state.status.length - 1];
                 }
+                // Increased log-msg max-height
                 tableHtml += `
                     <tr>
-                        <td style="min-width: 140px;">${state.timestamp || 'N/A'}</td>
-                        <td>${state.stateName || 'N/A'}</td>
-                        <td><div class="log-msg">${state.msg || ''}</div></td>
-                        <td class="${getStatusClass(actualStatus)}">${actualStatus || 'N/A'}</td>
+                        <td class="px-3 py-2 sm:px-4 whitespace-nowrap text-gray-700">${state.timestamp || 'N/A'}</td>
+                        <td class="px-3 py-2 sm:px-4 text-gray-700">${state.stateName || 'N/A'}</td>
+                        <td class="px-3 py-2 sm:px-4 text-gray-700">
+                            <div class="log-msg" style="max-height: 150px;">${state.msg || ''}</div>
+                        </td>
+                        <td class="px-3 py-2 sm:px-4 ${getStatusTextClass(actualStatus)}">${actualStatus || 'N/A'}</td>
                     </tr>
                 `;
             });
-            tableHtml += '</tbody></table>';
+            tableHtml += '</tbody></table></div>';
             return tableHtml;
         }
 
-        function createTaskCard(task, listIdForAccordion) {
-            const safeId = `task-${task.id.replace(/[^a-zA-Z0-9-_]/g, '')}`;
-            const headingId = `heading-${safeId}`;
-            const collapseId = `collapse-${safeId}`;
-
-            const card = document.createElement('div');
-            card.className = 'card task-card shadow-sm';
-
-            const cardHeader = document.createElement('div');
-            cardHeader.className = 'card-header';
-            cardHeader.id = headingId;
-            cardHeader.innerHTML = `
-                <h2 class="mb-0">
-                    <button class="btn btn-light btn-block text-left p-3" type="button" data-toggle="collapse" data-target="#${collapseId}" aria-expanded="false" aria-controls="${collapseId}">
-                        <div class="d-flex justify-content-between align-items-center w-100">
-                            <span style="font-size: 1.1rem; font-weight: bold; color: #007bff;">${task.symbol || 'N/A'}</span>
-                            <span class="badge ${getStatusBadgeClass(task.currentState)} p-2" style="font-size: 0.8rem;">${task.currentState || 'N/A'}</span>
-                        </div>
-                        <div style="font-size: 0.85em;" class="text-muted mt-2">
-                            <span>创建: ${task.creationTime || 'N/A'}</span>
-                            <span class="mx-2">|</span>
-                            <span>利润: ${task.profit || 'N/A'} (阈值: ${task.profitLimit || 'N/A'})</span>
-                        </div>
-                    </button>
-                </h2>
+        // createTableRow function remains the same
+        function createTableRow(task) {
+            const tr = document.createElement('tr');
+            tr.className = 'hover:bg-gray-50 transition duration-150';
+            const profitText = task.profit && task.profitLimit ? `${task.profit} (${task.profitLimit})` : (task.profit || 'N/A');
+            
+            tr.innerHTML = `
+                <td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">${task.symbol || 'N/A'}</td>
+                <td class="px-4 py-3 whitespace-nowrap text-sm">
+                    <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-4 py-3 whitespace-nowrap text-sm text-gray-500 hidden md:table-cell">${profitText}</td>
+                <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500 hidden sm:table-cell">${task.creationTime || 'N/A'}</td>
+                <td class="px-4 py-3 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>
             `;
+            return tr;
+        }
 
-            const collapseDiv = document.createElement('div');
-            collapseDiv.id = collapseId;
-            collapseDiv.className = 'collapse';
-            collapseDiv.setAttribute('aria-labelledby', headingId);
-            collapseDiv.setAttribute('data-parent', `#${listIdForAccordion}`);
-
-            collapseDiv.innerHTML = `
-                <div class="card-body">
-                    <p class="mb-1"><small class="text-muted">ID: ${task.id}</small></p>
-                    <hr class="my-2">
-                    <p class="mb-1"><strong>来源代币:</strong> ${task.fromTokenAmountHuman || 'N/A'} ${task.fromToken || ''}</p>
-                    <p><strong>目标代币:</strong> ${task.toTokenAmountHuman || 'N/A'} ${task.toToken || ''}</p>
-                    <h6 class="mt-3">状态流转:</h6>
-                    ${formatStateFlow(task.stateFlow)}
-                </div>
-            `;
+        // renderTable and fetchDataForTab functions remain the same
+        function renderTable(data, targetTableBodyId, countBadgeElement) {
+            const tableBody = document.getElementById(targetTableBodyId);
+            if (!tableBody || !countBadgeElement) return;
 
-            card.appendChild(cardHeader);
-            card.appendChild(collapseDiv);
-            return card;
-        }
+            tableBody.innerHTML = ''; 
 
-        async function fetchData(endpoint, targetElementId, countElementId) {
-            const targetElement = document.getElementById(targetElementId);
-            const countElement = document.getElementById(countElementId);
-            if (!targetElement || !countElement) {
-                console.error(`Element not found: ${targetElementId} or ${countElementId}`);
-                return;
+            if (data && data.length > 0) {
+                data.sort((a, b) => (a.creationTime || '0') < (b.creationTime || '0') ? 1 : -1); 
+                data.forEach(task => {
+                    allTasksData[task.id] = task; 
+                    tableBody.appendChild(createTableRow(task));
+                });
+            } else {
+                tableBody.innerHTML = `<tr><td colspan="5" class="text-center p-4 text-gray-500">没有任务。</td></tr>`;
             }
-            
-            // 如果不是首次加载 (即 targetElement.innerHTML 为 '加载中...'),则不显示 "加载中..." 文本,避免闪烁
-            const isInitialLoad = targetElement.querySelector('p') && targetElement.querySelector('p').textContent.includes('加载中');
-            if (isInitialLoad) {
-                 targetElement.innerHTML = '<p class="text-muted"><em>加载中...</em></p>';
+            countBadgeElement.textContent = data.length;
+        }
+        
+        async function fetchDataForTab(endpoint, tableBodyId, countBadgeElement) {
+            const tableBody = document.getElementById(tableBodyId);
+            const isInitialLoad = tableBody.querySelector('td[colspan="5"]');
+             if (isInitialLoad) {
+                 tableBody.innerHTML = `<tr><td colspan="5" class="text-center p-4 text-gray-500">加载中...</td></tr>`;
             }
 
             try {
                 const response = await fetch(`${API_BASE_URL}${endpoint}`);
-                if (!response.ok) {
-                    throw new Error(`HTTP error! status: ${response.status}`);
-                }
+                if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
                 const data = await response.json();
-
-                if (data && data.length > 0) {
-                    data.sort((a, b) => {
-                        const timeA = a.creationTime || '0'; 
-                        const timeB = b.creationTime || '0';
-                        if (timeA < timeB) { return 1; }
-                        if (timeA > timeB) { return -1; }
-                        return 0;
-                    });
-                }
-                
-                targetElement.innerHTML = ''; 
-                if (data && data.length > 0) {
-                    data.forEach(task => {
-                        targetElement.appendChild(createTaskCard(task, targetElementId));
-                    });
-                } else {
-                    targetElement.innerHTML = '<p class="text-muted"><em>没有任务。</em></p>';
-                }
-                countElement.textContent = data.length;
-
+                renderTable(data, tableBodyId, countBadgeElement);
             } catch (error) {
                 console.error(`获取 ${endpoint} 数据失败:`, error);
-                if (isInitialLoad) { // 只有在初始加载失败时才替换整个内容
-                    targetElement.innerHTML = `<p class="text-danger">加载数据失败: ${error.message}</p>`;
+                 if (isInitialLoad) {
+                    tableBody.innerHTML = `<tr><td colspan="5" class="text-center p-4 text-red-500">加载数据失败: ${error.message}</td></tr>`;
                 } else {
-                    console.warn("获取更新数据失败,保留旧数据。") // 或者可以加一个小的错误提示
+                     console.warn("获取更新数据失败,保留旧数据。")
                 }
-                countElement.textContent = 'ERR';
+                countBadgeElement.textContent = 'ERR';
             }
         }
 
+        // refreshAllData, startAutoRefresh, stopAutoRefresh functions remain the same
         function refreshAllData() {
-            console.log("Refreshing data..."); // 用于调试
-            fetchData('/processing', 'processing-list', 'processing-count');
-            fetchData('/history', 'history-list', 'history-count');
+            console.log("Refreshing data...");
+            fetchDataForTab('/processing', 'processing-list', processingCountBadge);
+            fetchDataForTab('/history', 'history-list', historyCountBadge);
         }
 
         function startAutoRefresh() {
-            if (autoRefreshIntervalId) { // Clear existing interval if any
-                clearInterval(autoRefreshIntervalId);
-            }
+            if (autoRefreshIntervalId) clearInterval(autoRefreshIntervalId);
             autoRefreshIntervalId = setInterval(refreshAllData, REFRESH_INTERVAL_MS);
             isAutoRefreshPaused = false;
             toggleAutoRefreshButton.textContent = '暂停自动刷新';
-            toggleAutoRefreshButton.classList.remove('btn-success');
-            toggleAutoRefreshButton.classList.add('btn-secondary');
-            console.log("自动刷新已启动。 Interval ID:", autoRefreshIntervalId);
+            toggleAutoRefreshButton.classList.remove('bg-green-500', 'hover:bg-green-600');
+            toggleAutoRefreshButton.classList.add('bg-gray-500', 'hover:bg-gray-600');
+            console.log("自动刷新已启动。");
         }
 
         function stopAutoRefresh() {
-            if (autoRefreshIntervalId) {
-                clearInterval(autoRefreshIntervalId);
-                autoRefreshIntervalId = null; // Set to null to indicate it's stopped
-                console.log("自动刷新已暂停。");
-            }
+            if (autoRefreshIntervalId) clearInterval(autoRefreshIntervalId);
+            autoRefreshIntervalId = null;
             isAutoRefreshPaused = true;
             toggleAutoRefreshButton.textContent = '恢复自动刷新';
-            toggleAutoRefreshButton.classList.remove('btn-secondary');
-            toggleAutoRefreshButton.classList.add('btn-success');
+            toggleAutoRefreshButton.classList.remove('bg-gray-500', 'hover:bg-gray-600');
+            toggleAutoRefreshButton.classList.add('bg-green-500', 'hover:bg-green-600');
+            console.log("自动刷新已暂停。");
         }
 
-        refreshButton.addEventListener('click', refreshAllData);
+        // Tab switching logic remains the same
+         function switchTab(activeTab, activePane, inactiveTab, inactivePane) {
+            activeTab.setAttribute('aria-selected', 'true');
+            activeTab.classList.remove('border-transparent', 'hover:text-gray-600', 'hover:border-gray-300');
+            activeTab.classList.add('border-blue-500', 'text-blue-600'); 
+
+            inactiveTab.setAttribute('aria-selected', 'false');
+            inactiveTab.classList.remove('border-blue-500', 'text-blue-600');
+            inactiveTab.classList.add('border-transparent', 'hover:text-gray-600', 'hover:border-gray-300');
+
+            activePane.classList.remove('hidden');
+            inactivePane.classList.add('hidden');
+        }
+
+        processingTab.addEventListener('click', () => switchTab(processingTab, processingPane, historyTab, historyPane));
+        historyTab.addEventListener('click', () => switchTab(historyTab, historyPane, processingTab, processingPane));
+
+        // MODIFIED: openModal - Basic info section uses a more structured layout (definition list style)
+        function openModal(taskId) {
+            const task = allTasksData[taskId];
+            if (!task) {
+                console.error("Task data not found for ID:", taskId);
+                modalBody.innerHTML = '<p class="text-red-500">无法加载任务详情。</p>';
+                taskDetailModal.classList.remove('hidden');
+                document.body.classList.add('modal-active');
+                return;
+            }
 
+            // Improved basic info layout: using a definition list style with grid for responsiveness
+            let basicInfoHtml = '<dl class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-x-6 gap-y-4">'; // gap-y-4 added for more vertical space
+
+            const infoMap = new Map([
+                ["ID", task.id], // ID is often long, let it span more columns if needed.
+                ["交易对", task.symbol || 'N/A'],
+                ["当前状态", `<span class="${getStatusTextClass(task.currentState)} font-semibold">${task.currentState || 'N/A'}</span>`],
+                ["来源代币", `${task.fromTokenAmountHuman || 'N/A'} ${task.fromToken || ''}`],
+                ["目标代币", `${task.toTokenAmountHuman || 'N/A'} ${task.toToken || ''}`],
+                ["创建时间", task.creationTime || 'N/A'],
+                ["利润", task.profit || 'N/A'],
+                ["利润阈值", task.profitLimit || 'N/A']
+            ]);
+            
+            infoMap.forEach((value, label) => {
+                let itemClass = "sm:col-span-1"; // Default span
+                if (label === "ID") { // ID can span more
+                    itemClass = "sm:col-span-2 lg:col-span-3 break-all"; // break-all for very long IDs
+                } else  if (label === "来源代币" || label === "目标代币") { // These might also be long
+                     itemClass = "sm:col-span-1 lg:col-span-1 break-words";
+                }
+
+                basicInfoHtml += `
+                    <div class="${itemClass}">
+                        <dt class="text-xs font-medium text-gray-500 uppercase">${label}</dt>
+                        <dd class="mt-1 text-gray-900">${value}</dd>
+                    </div>
+                `;
+            });
+            basicInfoHtml += '</dl>';
+
+            modalBody.innerHTML = `
+                ${basicInfoHtml}
+                <hr class="my-4 sm:my-6">
+                <div>
+                  <h4 class="text-base sm:text-lg font-semibold text-gray-800 mb-2">状态流转:</h4>
+                  ${formatStateFlowForModal(task.stateFlow)}
+                </div>
+            `;
+            taskDetailModal.classList.remove('hidden');
+            document.body.classList.add('modal-active');
+        }
+
+        // closeModal, event listeners for modal, and initial setup remain the same
+        function closeModal() {
+            taskDetailModal.classList.add('hidden');
+            document.body.classList.remove('modal-active');
+        }
+
+        closeModalButton.addEventListener('click', closeModal);
+        taskDetailModal.addEventListener('click', (event) => { 
+            if (event.target === taskDetailModal) {
+                closeModal();
+            }
+        });
+        document.addEventListener('keydown', (event) => { 
+            if (event.key === 'Escape' && !taskDetailModal.classList.contains('hidden')) {
+                closeModal();
+            }
+        });
+
+        document.getElementById('tabContent').addEventListener('click', function(event) {
+            if (event.target.classList.contains('view-details-btn')) {
+                const taskId = event.target.dataset.taskId;
+                openModal(taskId);
+            }
+        });
+
+        refreshButton.addEventListener('click', refreshAllData);
         toggleAutoRefreshButton.addEventListener('click', () => {
             if (isAutoRefreshPaused) {
                 startAutoRefresh();
-                refreshAllData(); // Resume and immediately refresh
+                refreshAllData(); 
             } else {
                 stopAutoRefresh();
             }
         });
 
         document.addEventListener('DOMContentLoaded', () => {
-            refreshAllData(); // Initial load
-            startAutoRefresh(); // Start auto-refresh by default
+            processingTab.setAttribute('aria-selected', 'true');
+            processingTab.classList.add('border-blue-500', 'text-blue-600');
+            processingPane.classList.remove('hidden');
+            
+            historyTab.classList.add('border-transparent', 'hover:text-gray-600', 'hover:border-gray-300');
+            historyPane.classList.add('hidden');
+
+            refreshAllData();
+            startAutoRefresh();
         });
     </script>
-    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
-    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
-    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
 </body>
-</html>
+</html>

+ 1 - 1
price_checker_ok.py

@@ -27,7 +27,7 @@ ARB_EXECUTOR_URL = "http://localhost:5002/submit_process"
 # --- 配置部分 ---
 IN_AMOUNT_TO_QUERY = decimal.Decimal('350')
 EXCHANGE_OUT_AMOUNT = decimal.Decimal('30000000')
-PROFIT_LIMIT = 0.02                                                 # 触发交易的利润阈值
+PROFIT_LIMIT = 0.03                                                 # 触发交易的利润阈值
 IN_TOKEN_ADDRESS = '0xdAC17F958D2ee523a2206206994597C13D831ec7'     # USDT on Ethereum
 IN_TOKEN_DECIMALS = 6
 OUT_TOKEN_ADDRESS = '0xf816507E690f5Aa4E29d164885EB5fa7a5627860'    # RATO on Ethereum