|
14 | 14 | 'auto_refresh': 'Auto Refresh', |
15 | 15 | 'seconds': 'seconds', |
16 | 16 | 'refresh': 'Refresh', |
| 17 | + 'clear_requests': 'Clear', |
| 18 | + 'confirm': 'Confirm', |
| 19 | + 'clear_confirm': 'Clear all captured requests?', |
17 | 20 | 'captured_requests': 'Captured Requests', |
18 | 21 | 'no_requests': 'No requests captured yet.', |
19 | 22 | 'welcome_title': 'Welcome!', |
|
36 | 39 | 'auto_refresh': '自动刷新', |
37 | 40 | 'seconds': '秒', |
38 | 41 | 'refresh': '刷新', |
| 42 | + 'clear_requests': '清空', |
| 43 | + 'confirm': '确定', |
| 44 | + 'clear_confirm': '确定清空所有已捕获的请求?', |
39 | 45 | 'captured_requests': '已捕获的请求', |
40 | 46 | 'no_requests': '尚未捕获任何请求。', |
41 | 47 | 'welcome_title': '欢迎!', |
|
108 | 114 | </div> |
109 | 115 | <div class="w-1/3 flex justify-end items-center pr-4"> |
110 | 116 | <div class="flex items-center space-x-2"> |
111 | | - <button id="manualRefreshButton" class="hidden px-3 py-1 text-sm bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors" data-i18n-title="refresh_requests"> |
112 | | - <svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
113 | | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path> |
114 | | - </svg> |
115 | | - <span data-i18n="refresh">刷新</span> |
116 | | - </button> |
117 | 117 | <input type="checkbox" id="autoRefreshCheckbox" class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"> |
118 | 118 | <label for="autoRefreshCheckbox" class="text-sm text-gray-600" data-i18n="auto_refresh">自动刷新</label> |
119 | 119 | <input type="number" id="refreshIntervalInput" class="w-16 text-sm border-gray-300 rounded-md shadow-sm" min="1" value="5"> |
|
125 | 125 | <div class="flex flex-1 overflow-hidden"> |
126 | 126 | <!-- Left Column: Request List --> |
127 | 127 | <aside class="w-1/3 bg-white border-r flex flex-col"> |
128 | | - <div class="p-4 border-b"> |
| 128 | + <div class="p-4 border-b flex items-center justify-between gap-3"> |
129 | 129 | <h2 class="text-lg font-semibold" data-i18n="captured_requests">已捕获的请求</h2> |
| 130 | + <div class="flex items-center gap-2"> |
| 131 | + <button id="clearRequestsButton" class="px-3 py-1.5 text-xs font-semibold text-red-700 bg-red-50 border border-red-200 rounded-md hover:bg-red-100 hover:border-red-300 transition-colors flex items-center gap-1.5"> |
| 132 | + <svg class="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> |
| 133 | + <path d="M8.5 2a1 1 0 00-1 1v1H4.75a.75.75 0 000 1.5h10.5a.75.75 0 000-1.5H12.5V3a1 1 0 00-1-1h-3z" /> |
| 134 | + <path d="M6 7.25a.75.75 0 01.75-.75h6.5a.75.75 0 01.75.75v7.25a2 2 0 01-2 2h-4a2 2 0 01-2-2V7.25z" /> |
| 135 | + </svg> |
| 136 | + <span data-i18n="clear_requests">清理</span> |
| 137 | + </button> |
| 138 | + <button id="listRefreshButton" class="px-3 py-1.5 text-xs font-semibold text-white bg-blue-600 border border-blue-600 rounded-md hover:bg-blue-700 transition-colors flex items-center gap-1.5"> |
| 139 | + <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"> |
| 140 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path> |
| 141 | + </svg> |
| 142 | + <span data-i18n="refresh">刷新</span> |
| 143 | + </button> |
| 144 | + </div> |
130 | 145 | </div> |
131 | 146 | <div class="overflow-y-auto flex-1"> |
132 | 147 | {{if .AllRequests}} |
|
268 | 283 | // --- Auto-refresh logic --- |
269 | 284 | const checkbox = document.getElementById('autoRefreshCheckbox'); |
270 | 285 | const intervalInput = document.getElementById('refreshIntervalInput'); |
271 | | - const manualRefreshButton = document.getElementById('manualRefreshButton'); |
| 286 | + const clearRequestsButton = document.getElementById('clearRequestsButton'); |
| 287 | + const listRefreshButton = document.getElementById('listRefreshButton'); |
272 | 288 | let refreshInterval; |
273 | 289 |
|
274 | 290 | // --- Pagination controls --- |
|
292 | 308 | refreshInterval = null; |
293 | 309 | } |
294 | 310 |
|
295 | | - function toggleManualRefreshButton() { |
296 | | - if (checkbox.checked) { |
297 | | - manualRefreshButton.classList.add('hidden'); |
298 | | - } else { |
299 | | - manualRefreshButton.classList.remove('hidden'); |
300 | | - } |
301 | | - } |
302 | 311 |
|
303 | 312 | // Function to update pagination info |
304 | 313 | function updatePaginationInfo(data) { |
|
430 | 439 | toggleManualRefreshButton(); |
431 | 440 | }); |
432 | 441 |
|
433 | | - // Manual refresh button click handler |
434 | | - let isRefreshing = false; |
435 | | - const originalButtonContent = ` |
436 | | - <svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
437 | | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path> |
438 | | - </svg> |
439 | | - <span data-i18n="refresh">${translations[currentLang]['refresh']}</span> |
440 | | - `; |
441 | | - |
442 | | - function resetButtonState() { |
443 | | - manualRefreshButton.innerHTML = originalButtonContent; |
444 | | - isRefreshing = false; |
| 442 | + |
| 443 | + if (clearRequestsButton) { |
| 444 | + let isConfirmingClear = false; |
| 445 | + let clearConfirmTimer = null; |
| 446 | + const clearOriginalClasses = clearRequestsButton.className; |
| 447 | + const clearOriginalLabel = clearRequestsButton.innerHTML; |
| 448 | + |
| 449 | + const resetClearButton = () => { |
| 450 | + isConfirmingClear = false; |
| 451 | + clearRequestsButton.className = clearOriginalClasses; |
| 452 | + clearRequestsButton.innerHTML = clearOriginalLabel; |
| 453 | + if (clearConfirmTimer) { |
| 454 | + clearTimeout(clearConfirmTimer); |
| 455 | + clearConfirmTimer = null; |
| 456 | + } |
| 457 | + }; |
| 458 | + |
| 459 | + clearRequestsButton.addEventListener('click', async () => { |
| 460 | + if (clearRequestsButton.disabled) return; |
| 461 | + |
| 462 | + if (!isConfirmingClear) { |
| 463 | + isConfirmingClear = true; |
| 464 | + clearRequestsButton.className = 'px-3 py-1.5 text-xs font-semibold text-white bg-red-600 border border-red-600 rounded-md hover:bg-red-700 transition-colors flex items-center gap-1.5'; |
| 465 | + clearRequestsButton.innerHTML = ` |
| 466 | + <svg class="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> |
| 467 | + <path fill-rule="evenodd" d="M16.704 6.29a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0l-3.25-3.25a.75.75 0 111.06-1.06l2.72 2.72 6.72-6.72a.75.75 0 011.06 0z" clip-rule="evenodd" /> |
| 468 | + </svg> |
| 469 | + <span data-i18n="confirm">${translations[currentLang]['confirm']}</span> |
| 470 | + `; |
| 471 | + clearConfirmTimer = setTimeout(resetClearButton, 2500); |
| 472 | + return; |
| 473 | + } |
| 474 | + |
| 475 | + clearRequestsButton.disabled = true; |
| 476 | + clearRequestsButton.classList.add('opacity-60', 'cursor-not-allowed'); |
| 477 | + |
| 478 | + try { |
| 479 | + const response = await fetch('/api/requests/clear', { method: 'POST' }); |
| 480 | + if (!response.ok) { |
| 481 | + throw new Error('clear_failed'); |
| 482 | + } |
| 483 | + window.location.href = '/'; |
| 484 | + } catch (error) { |
| 485 | + console.error('Error clearing requests:', error); |
| 486 | + clearRequestsButton.disabled = false; |
| 487 | + clearRequestsButton.classList.remove('opacity-60', 'cursor-not-allowed'); |
| 488 | + resetClearButton(); |
| 489 | + } |
| 490 | + }); |
| 491 | + } |
| 492 | + |
| 493 | + if (listRefreshButton) { |
| 494 | + listRefreshButton.addEventListener('click', () => { |
| 495 | + if (isUpdating) return; |
| 496 | + const refreshIcon = listRefreshButton.querySelector('svg'); |
| 497 | + listRefreshButton.disabled = true; |
| 498 | + listRefreshButton.classList.add('opacity-70', 'cursor-not-allowed'); |
| 499 | + if (refreshIcon) { |
| 500 | + refreshIcon.classList.add('animate-spin'); |
| 501 | + } |
| 502 | + Promise.resolve(updateRequestList()) |
| 503 | + .catch(() => {}) |
| 504 | + .finally(() => { |
| 505 | + if (refreshIcon) { |
| 506 | + refreshIcon.classList.remove('animate-spin'); |
| 507 | + } |
| 508 | + listRefreshButton.disabled = false; |
| 509 | + listRefreshButton.classList.remove('opacity-70', 'cursor-not-allowed'); |
| 510 | + }); |
| 511 | + }); |
445 | 512 | } |
446 | | - |
447 | | - manualRefreshButton.addEventListener('click', () => { |
448 | | - if (isRefreshing) return; // Prevent multiple clicks |
449 | | - |
450 | | - isRefreshing = true; |
451 | | - updateRequestList(); |
452 | | - |
453 | | - // Add visual feedback |
454 | | - manualRefreshButton.innerHTML = ` |
455 | | - <svg class="w-4 h-4 inline mr-1 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
456 | | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path> |
457 | | - </svg> |
458 | | - <span data-i18n="refresh">${translations[currentLang]['refresh']}</span> |
459 | | - `; |
460 | | - |
461 | | - setTimeout(resetButtonState, 500); |
462 | | - }); |
463 | 513 |
|
464 | 514 | intervalInput.addEventListener('change', () => { |
465 | 515 | let interval = parseInt(intervalInput.value, 10); |
|
480 | 530 | if (isEnabled) { |
481 | 531 | startRefresh(); |
482 | 532 | } |
483 | | - toggleManualRefreshButton(); // Initialize manual refresh button visibility |
484 | 533 |
|
485 | 534 | // Initialize pagination and request list |
486 | 535 | updateRequestList(); |
|
0 commit comments