|
|
@@ -12,19 +12,16 @@
|
|
|
background-color: #f4f7f6; /* Lighter background for the page */
|
|
|
}
|
|
|
.task-card {
|
|
|
- margin-bottom: 15px; /* Slightly reduced margin */
|
|
|
- /* border: 1px solid #ddd; */ /* Border is handled by Bootstrap card */
|
|
|
- /* border-radius: 5px; */ /* Bootstrap card has rounded corners */
|
|
|
+ margin-bottom: 15px;
|
|
|
}
|
|
|
- .task-card .card-header { /* Styling for the clickable header */
|
|
|
- padding: 0; /* Remove padding to let button fill it */
|
|
|
+ .task-card .card-header {
|
|
|
+ padding: 0;
|
|
|
}
|
|
|
.task-card .card-header button {
|
|
|
- /* text-decoration: none !important; */ /* Remove underline from btn-link if used */
|
|
|
- color: #333; /* Darker text for better readability on light bg */
|
|
|
+ color: #333;
|
|
|
}
|
|
|
.task-card .card-header button:hover {
|
|
|
- background-color: #e9ecef; /* Slightly darker on hover */
|
|
|
+ background-color: #e9ecef;
|
|
|
}
|
|
|
.state-flow-table th, .state-flow-table td {
|
|
|
font-size: 0.9em;
|
|
|
@@ -38,10 +35,10 @@
|
|
|
.log-msg {
|
|
|
white-space: pre-wrap;
|
|
|
word-break: break-all;
|
|
|
- max-height: 150px; /* Increased max height for logs */
|
|
|
+ max-height: 150px;
|
|
|
overflow-y: auto;
|
|
|
font-size: 0.85em;
|
|
|
- background-color: #fdfdfd; /* Slightly off-white for log background */
|
|
|
+ background-color: #fdfdfd;
|
|
|
padding: 8px;
|
|
|
border: 1px solid #eee;
|
|
|
border-radius: 3px;
|
|
|
@@ -56,12 +53,18 @@
|
|
|
margin-bottom: 0;
|
|
|
margin-right: 0.5rem;
|
|
|
}
|
|
|
+ .button-group {
|
|
|
+ margin-bottom: 1.5rem; /* Increased margin for button group */
|
|
|
+ }
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div class="container-fluid">
|
|
|
<h1 class="mb-4">套利流程监控</h1>
|
|
|
- <button id="refreshButton" class="btn btn-primary mb-4">刷新数据</button>
|
|
|
+ <div class="button-group">
|
|
|
+ <button id="refreshButton" class="btn btn-primary mr-2">手动刷新</button>
|
|
|
+ <button id="toggleAutoRefreshButton" class="btn btn-secondary">暂停自动刷新</button>
|
|
|
+ </div>
|
|
|
|
|
|
<div class="row">
|
|
|
<div class="col-md-6">
|
|
|
@@ -86,8 +89,15 @@
|
|
|
</div>
|
|
|
|
|
|
<script>
|
|
|
- const API_BASE_URL = 'http://localhost:5002'; // 修改为你的 Flask API 地址
|
|
|
+ const API_BASE_URL = 'http://localhost:5002';
|
|
|
+ const REFRESH_INTERVAL_MS = 10000; // 自动刷新间隔:10秒
|
|
|
+ let autoRefreshIntervalId = null;
|
|
|
+ let isAutoRefreshPaused = false;
|
|
|
|
|
|
+ const refreshButton = document.getElementById('refreshButton');
|
|
|
+ const toggleAutoRefreshButton = document.getElementById('toggleAutoRefreshButton');
|
|
|
+
|
|
|
+ // ... (getStatusClass, status_includes_any, getStatusBadgeClass, formatStateFlow, createTaskCard 函数保持不变) ...
|
|
|
function getStatusClass(status) { // For text color in stateFlow
|
|
|
status = status ? status.toLowerCase() : 'info';
|
|
|
if (status.includes('success') || status.includes('completed')) return 'status-success';
|
|
|
@@ -146,17 +156,15 @@
|
|
|
}
|
|
|
|
|
|
function createTaskCard(task, listIdForAccordion) {
|
|
|
- // Generate a safer ID for HTML attributes, especially if task.id could have unusual chars
|
|
|
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'; // Added shadow-sm for a bit of depth
|
|
|
+ card.className = 'card task-card shadow-sm';
|
|
|
|
|
|
- // Card Header (Clickable Toggle)
|
|
|
const cardHeader = document.createElement('div');
|
|
|
- cardHeader.className = 'card-header'; // Bootstrap handles padding now
|
|
|
+ cardHeader.className = 'card-header';
|
|
|
cardHeader.id = headingId;
|
|
|
cardHeader.innerHTML = `
|
|
|
<h2 class="mb-0">
|
|
|
@@ -174,12 +182,11 @@
|
|
|
</h2>
|
|
|
`;
|
|
|
|
|
|
- // Card Body (Collapsible Content)
|
|
|
const collapseDiv = document.createElement('div');
|
|
|
collapseDiv.id = collapseId;
|
|
|
- collapseDiv.className = 'collapse'; // Default collapsed
|
|
|
+ collapseDiv.className = 'collapse';
|
|
|
collapseDiv.setAttribute('aria-labelledby', headingId);
|
|
|
- collapseDiv.setAttribute('data-parent', `#${listIdForAccordion}`); // For accordion behavior
|
|
|
+ collapseDiv.setAttribute('data-parent', `#${listIdForAccordion}`);
|
|
|
|
|
|
collapseDiv.innerHTML = `
|
|
|
<div class="card-body">
|
|
|
@@ -204,7 +211,13 @@
|
|
|
console.error(`Element not found: ${targetElementId} or ${countElementId}`);
|
|
|
return;
|
|
|
}
|
|
|
- targetElement.innerHTML = '<p class="text-muted"><em>加载中...</em></p>';
|
|
|
+
|
|
|
+ // 如果不是首次加载 (即 targetElement.innerHTML 为 '加载中...'),则不显示 "加载中..." 文本,避免闪烁
|
|
|
+ const isInitialLoad = targetElement.querySelector('p') && targetElement.querySelector('p').textContent.includes('加载中');
|
|
|
+ if (isInitialLoad) {
|
|
|
+ targetElement.innerHTML = '<p class="text-muted"><em>加载中...</em></p>';
|
|
|
+ }
|
|
|
+
|
|
|
try {
|
|
|
const response = await fetch(`${API_BASE_URL}${endpoint}`);
|
|
|
if (!response.ok) {
|
|
|
@@ -212,27 +225,19 @@
|
|
|
}
|
|
|
const data = await response.json();
|
|
|
|
|
|
- // 对数据按 creationTime 倒序排序
|
|
|
if (data && data.length > 0) {
|
|
|
data.sort((a, b) => {
|
|
|
- // 假设 creationTime 是 'YYYY-MM-DD HH:MM:SS,ms' 格式
|
|
|
- // 如果不是有效字符串,则将其视为较早的时间
|
|
|
const timeA = a.creationTime || '0';
|
|
|
const timeB = b.creationTime || '0';
|
|
|
- if (timeA < timeB) {
|
|
|
- return 1; // b 在 a 之前,所以 b 应该排在前面(对于倒序)
|
|
|
- }
|
|
|
- if (timeA > timeB) {
|
|
|
- return -1; // a 在 b 之前,所以 a 应该排在前面(对于倒序)
|
|
|
- }
|
|
|
- return 0; // 时间相同
|
|
|
+ if (timeA < timeB) { return 1; }
|
|
|
+ if (timeA > timeB) { return -1; }
|
|
|
+ return 0;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
targetElement.innerHTML = '';
|
|
|
if (data && data.length > 0) {
|
|
|
data.forEach(task => {
|
|
|
- // Pass targetElementId to createTaskCard for the data-parent attribute of accordion
|
|
|
targetElement.appendChild(createTaskCard(task, targetElementId));
|
|
|
});
|
|
|
} else {
|
|
|
@@ -242,25 +247,63 @@
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error(`获取 ${endpoint} 数据失败:`, error);
|
|
|
- targetElement.innerHTML = `<p class="text-danger">加载数据失败: ${error.message}</p>`;
|
|
|
+ if (isInitialLoad) { // 只有在初始加载失败时才替换整个内容
|
|
|
+ targetElement.innerHTML = `<p class="text-danger">加载数据失败: ${error.message}</p>`;
|
|
|
+ } else {
|
|
|
+ console.warn("获取更新数据失败,保留旧数据。") // 或者可以加一个小的错误提示
|
|
|
+ }
|
|
|
countElement.textContent = 'ERR';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function refreshAllData() {
|
|
|
+ console.log("Refreshing data..."); // 用于调试
|
|
|
fetchData('/processing', 'processing-list', 'processing-count');
|
|
|
fetchData('/history', 'history-list', 'history-count');
|
|
|
}
|
|
|
|
|
|
- document.getElementById('refreshButton').addEventListener('click', refreshAllData);
|
|
|
+ function startAutoRefresh() {
|
|
|
+ if (autoRefreshIntervalId) { // Clear existing interval if any
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ function stopAutoRefresh() {
|
|
|
+ if (autoRefreshIntervalId) {
|
|
|
+ clearInterval(autoRefreshIntervalId);
|
|
|
+ autoRefreshIntervalId = null; // Set to null to indicate it's stopped
|
|
|
+ console.log("自动刷新已暂停。");
|
|
|
+ }
|
|
|
+ isAutoRefreshPaused = true;
|
|
|
+ toggleAutoRefreshButton.textContent = '恢复自动刷新';
|
|
|
+ toggleAutoRefreshButton.classList.remove('btn-secondary');
|
|
|
+ toggleAutoRefreshButton.classList.add('btn-success');
|
|
|
+ }
|
|
|
+
|
|
|
+ refreshButton.addEventListener('click', refreshAllData);
|
|
|
+
|
|
|
+ toggleAutoRefreshButton.addEventListener('click', () => {
|
|
|
+ if (isAutoRefreshPaused) {
|
|
|
+ startAutoRefresh();
|
|
|
+ refreshAllData(); // Resume and immediately refresh
|
|
|
+ } else {
|
|
|
+ stopAutoRefresh();
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
- refreshAllData();
|
|
|
- // setInterval(refreshAllData, 10000); // 每10秒刷新一次 (可选)
|
|
|
+ refreshAllData(); // Initial load
|
|
|
+ startAutoRefresh(); // Start auto-refresh by default
|
|
|
});
|
|
|
</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>
|