| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>软件更新管理系统</title>
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
-
- body {
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- min-height: 100vh;
- padding: 20px;
- }
-
- .container {
- max-width: 1200px;
- margin: 0 auto;
- background: white;
- border-radius: 15px;
- box-shadow: 0 20px 40px rgba(0,0,0,0.1);
- overflow: hidden;
- }
-
- .header {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
- padding: 30px;
- text-align: center;
- }
-
- .header h1 {
- font-size: 2.5em;
- margin-bottom: 10px;
- }
-
- .content {
- padding: 30px;
- }
-
- .upload-section {
- background: #f8f9fa;
- border-radius: 10px;
- padding: 25px;
- margin-bottom: 20px;
- box-shadow: 0 5px 15px rgba(0,0,0,0.05);
- border-left: 5px solid;
- }
-
- .network-section {
- border-left-color: #667eea;
- }
-
- .compute-section {
- border-left-color: #00b09b;
- }
-
- .monitor-section {
- border-left-color: #ff416c;
- }
-
- .upload-section h2 {
- color: #333;
- margin-bottom: 20px;
- padding-bottom: 10px;
- border-bottom: 2px solid;
- }
-
- .network-section h2 {
- border-bottom-color: #667eea;
- }
-
- .compute-section h2 {
- border-bottom-color: #00b09b;
- }
-
- .monitor-section h2 {
- border-bottom-color: #ff416c;
- }
-
- .section-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 15px;
- }
-
- .section-tag {
- padding: 4px 12px;
- border-radius: 20px;
- font-size: 0.8em;
- font-weight: 600;
- color: white;
- }
-
- .network-tag {
- background: #667eea;
- }
-
- .compute-tag {
- background: #00b09b;
- }
-
- .monitor-tag {
- background: #ff416c;
- }
-
- .file-input-container {
- margin-bottom: 20px;
- }
-
- .file-input-label {
- display: inline-block;
- padding: 12px 24px;
- color: white;
- border-radius: 6px;
- cursor: pointer;
- font-weight: 600;
- transition: all 0.3s ease;
- }
-
- .network-upload-btn {
- background: #667eea;
- }
-
- .compute-upload-btn {
- background: #00b09b;
- }
-
- .monitor-upload-btn {
- background: #ff416c;
- }
-
- .file-input-label:hover {
- transform: translateY(-2px);
- box-shadow: 0 5px 15px rgba(0,0,0,0.1);
- }
-
- .network-upload-btn:hover {
- background: #5a6fd8;
- }
-
- .compute-upload-btn:hover {
- background: #009688;
- }
-
- .monitor-upload-btn:hover {
- background: #ff2b55;
- }
-
- .file-input {
- display: none;
- }
-
- .file-name {
- margin-left: 15px;
- color: #666;
- font-style: italic;
- }
-
- .selected-file {
- margin-top: 15px;
- padding: 15px;
- background: #e9ecef;
- border-radius: 8px;
- border-left: 4px solid;
- }
-
- .network-file {
- border-left-color: #667eea;
- }
-
- .compute-file {
- border-left-color: #00b09b;
- }
-
- .monitor-file {
- border-left-color: #ff416c;
- }
-
- .selected-file h4 {
- margin-bottom: 5px;
- color: #333;
- }
-
- .selected-file p {
- color: #666;
- font-size: 0.9em;
- }
-
- .output-container {
- height: 400px;
- overflow-y: auto;
- background: #1e1e1e;
- border-radius: 8px;
- padding: 20px;
- font-family: 'Consolas', 'Monaco', monospace;
- font-size: 14px;
- color: #d4d4d4;
- line-height: 1.5;
- }
-
- .output-container::-webkit-scrollbar {
- width: 8px;
- }
-
- .output-container::-webkit-scrollbar-track {
- background: #2d2d2d;
- }
-
- .output-container::-webkit-scrollbar-thumb {
- background: #555;
- border-radius: 4px;
- }
-
- .button-group {
- display: flex;
- gap: 10px;
- margin-top: 20px;
- flex-wrap: wrap;
- }
-
- button {
- padding: 12px 24px;
- border: none;
- border-radius: 6px;
- cursor: pointer;
- font-weight: 600;
- transition: all 0.3s ease;
- font-size: 16px;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- }
-
- .update-btn {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
- }
-
- .compute-update-btn {
- background: linear-gradient(135deg, #00b09b 0%, #96c93d 100%);
- color: white;
- }
-
- .monitor-update-btn {
- background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%);
- color: white;
- }
-
- .stop-btn {
- background: linear-gradient(135deg, #6c757d 0%, #495057 100%);
- color: white;
- }
-
- .clear-btn {
- background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
- color: white;
- }
-
- .log-btn {
- background: linear-gradient(135deg, #ff9800 0%, #ff5722 100%);
- color: white;
- }
-
- .compute-log-btn {
- background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);
- color: white;
- }
-
- .monitor-log-btn {
- background: linear-gradient(135deg, #9c27b0 0%, #6a1b9a 100%);
- color: white;
- }
-
- button:hover:not(:disabled) {
- transform: translateY(-2px);
- box-shadow: 0 7px 14px rgba(0,0,0,0.1);
- }
-
- button:disabled {
- opacity: 0.6;
- cursor: not-allowed;
- }
-
- .status-indicator {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 10px 20px;
- border-radius: 20px;
- font-weight: 600;
- margin-bottom: 20px;
- background: #f8f9fa;
- border: 1px solid #dee2e6;
- }
-
- .status-running {
- background: #d4edda;
- border-color: #c3e6cb;
- color: #155724;
- }
-
- .status-stopped {
- background: #f8d7da;
- border-color: #f5c6cb;
- color: #721c24;
- }
-
- .status-idle {
- background: #fff3cd;
- border-color: #ffeeba;
- color: #856404;
- }
-
- .status-dot {
- width: 12px;
- height: 12px;
- border-radius: 50%;
- animation: pulse 2s infinite;
- }
-
- .running-dot {
- background: #28a745;
- }
-
- .stopped-dot {
- background: #dc3545;
- }
-
- .idle-dot {
- background: #ffc107;
- }
-
- @keyframes pulse {
- 0% { opacity: 1; }
- 50% { opacity: 0.5; }
- 100% { opacity: 1; }
- }
-
- .loading {
- display: inline-block;
- width: 20px;
- height: 20px;
- border: 3px solid #f3f3f3;
- border-top: 3px solid;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- }
-
- .network-loading {
- border-top-color: #667eea;
- }
-
- .compute-loading {
- border-top-color: #00b09b;
- }
-
- .monitor-loading {
- border-top-color: #ff416c;
- }
-
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
-
- .info-box {
- background: #e7f3ff;
- border-radius: 8px;
- padding: 15px;
- margin-bottom: 20px;
- border-left: 4px solid #2196F3;
- }
-
- .info-box h3 {
- color: #1976D2;
- margin-bottom: 10px;
- }
-
- .info-box ul {
- padding-left: 20px;
- color: #555;
- }
-
- .info-box li {
- margin-bottom: 5px;
- }
-
- .upload-icon {
- font-size: 1.2em;
- }
-
- .software-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
- gap: 20px;
- margin-bottom: 30px;
- }
-
- .software-card {
- background: white;
- border-radius: 10px;
- padding: 20px;
- box-shadow: 0 5px 15px rgba(0,0,0,0.05);
- border: 1px solid #dee2e6;
- }
-
- .software-card h3 {
- color: #333;
- margin-bottom: 15px;
- padding-bottom: 10px;
- border-bottom: 2px solid;
- }
-
- .network-card h3 {
- border-bottom-color: #667eea;
- }
-
- .compute-card h3 {
- border-bottom-color: #00b09b;
- }
-
- .monitor-card h3 {
- border-bottom-color: #ff416c;
- }
-
- .software-info {
- margin-bottom: 15px;
- color: #666;
- font-size: 0.9em;
- }
-
- .current-version {
- background: #e9ecef;
- padding: 8px 12px;
- border-radius: 4px;
- font-family: monospace;
- margin-top: 10px;
- }
-
- .tabs {
- display: flex;
- border-bottom: 1px solid #dee2e6;
- margin-bottom: 20px;
- }
-
- .tab {
- padding: 10px 20px;
- cursor: pointer;
- border-bottom: 3px solid transparent;
- transition: all 0.3s ease;
- }
-
- .tab:hover {
- background: #f8f9fa;
- }
-
- .tab.active {
- border-bottom-color: #667eea;
- color: #667eea;
- font-weight: 600;
- }
-
- .tab-content {
- display: none;
- }
-
- .tab-content.active {
- display: block;
- }
-
- .log-info {
- background: #fff3cd;
- border-left: 4px solid #ffc107;
- padding: 12px 15px;
- margin-top: 15px;
- border-radius: 6px;
- font-size: 0.9em;
- }
-
- .log-info h4 {
- color: #856404;
- margin-bottom: 5px;
- }
-
- .log-info ul {
- padding-left: 20px;
- color: #856404;
- }
-
- .notification {
- position: fixed;
- top: 20px;
- right: 20px;
- padding: 15px 20px;
- background: white;
- border-radius: 8px;
- box-shadow: 0 5px 20px rgba(0,0,0,0.2);
- display: flex;
- align-items: center;
- gap: 10px;
- z-index: 1000;
- animation: slideIn 0.3s ease;
- border-left: 4px solid;
- }
-
- .notification-success {
- border-left-color: #28a745;
- }
-
- .notification-error {
- border-left-color: #dc3545;
- }
-
- .notification-info {
- border-left-color: #17a2b8;
- }
-
- @keyframes slideIn {
- from {
- transform: translateX(100%);
- opacity: 0;
- }
- to {
- transform: translateX(0);
- opacity: 1;
- }
- }
-
- .close-notification {
- background: none;
- border: none;
- font-size: 1.2em;
- cursor: pointer;
- color: #666;
- padding: 0;
- width: 24px;
- height: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .close-notification:hover {
- color: #333;
- }
- </style>
- </head>
- <body>
- <div class="container">
- <div class="header">
- <h1>软件更新管理系统</h1>
- <p>统一管理网络解析、计算、画面监控软件的更新和日志</p>
- </div>
-
- <div class="content">
- <div id="statusIndicator" class="status-indicator status-idle">
- <span class="status-dot idle-dot"></span>
- <span id="statusText">就绪 - 等待操作</span>
- <span id="currentFile" style="margin-left: auto; font-weight: normal; color: #666;"></span>
- </div>
-
- <div class="info-box">
- <h3>使用说明</h3>
- <ul>
- <li>选择对应软件类型的更新文件,点击更新按钮</li>
- <li>支持的文件类型: .tar.gz, .tgz</li>
- <li>处理过程实时显示在下方输出区域</li>
- <li>不同软件使用不同的颜色标识,便于区分</li>
- <li>点击"下载日志"按钮可以下载软件的所有日志文件</li>
- </ul>
- </div>
-
- <div class="tabs">
- <div class="tab active" data-tab="network">网络解析软件</div>
- <div class="tab" data-tab="compute">计算软件</div>
- <div class="tab" data-tab="monitor">画面监控软件</div>
- </div>
-
- <!-- 网络解析软件更新 -->
- <div id="network-tab" class="tab-content active">
- <div class="upload-section network-section">
- <h2>网络解析软件更新</h2>
- <div class="file-input-container">
- <label for="networkFileInput" class="file-input-label network-upload-btn">
- <span class="upload-icon">📁</span> 选择网络软件更新文件
- </label>
- <input type="file" id="networkFileInput" class="file-input" accept=".tar.gz,.tgz">
- <span id="networkFileName" class="file-name">未选择文件</span>
- </div>
-
- <div id="networkFileInfo" class="selected-file network-file" style="display: none;">
- <h4>已选择网络软件文件:</h4>
- <p id="networkFileInfoText"></p>
- </div>
-
- <div class="log-info">
- <h4>日志文件说明:</h4>
- <ul>
- <li>支持下载所有.log文件为ZIP包</li>
- </ul>
- </div>
-
- <div class="button-group">
- <button id="networkUpdateBtn" class="update-btn" disabled>
- <span>更新网络软件</span>
- </button>
- <button id="stopBtn" class="stop-btn" disabled>
- <span>🛑</span> 停止处理
- </button>
- <button id="clearBtn" class="clear-btn">
- <span>🗑️</span> 清空输出
- </button>
- <button id="networkLogBtn" class="log-btn">
- <span>📋</span> 下载日志
- </button>
- </div>
- </div>
- </div>
-
- <!-- 计算软件更新 -->
- <div id="compute-tab" class="tab-content">
- <div class="upload-section compute-section">
- <h2>计算软件更新</h2>
- <div class="file-input-container">
- <label for="computeFileInput" class="file-input-label compute-upload-btn">
- <span class="upload-icon">📁</span> 选择计算软件更新文件
- </label>
- <input type="file" id="computeFileInput" class="file-input" accept=".tar.gz,.tgz">
- <span id="computeFileName" class="file-name">未选择文件</span>
- </div>
-
- <div id="computeFileInfo" class="selected-file compute-file" style="display: none;">
- <h4>已选择计算软件文件:</h4>
- <p id="computeFileInfoText"></p>
- </div>
-
- <div class="log-info">
- <h4>日志文件说明:</h4>
- <ul>
- <li>支持下载所有.log文件为ZIP包</li>
- </ul>
- </div>
-
- <div class="button-group">
- <button id="computeUpdateBtn" class="compute-update-btn" disabled>
- <span>更新计算软件</span>
- </button>
- <button id="stopBtnCompute" class="stop-btn" disabled>
- <span>🛑</span> 停止处理
- </button>
- <button id="clearBtnCompute" class="clear-btn">
- <span>🗑️</span> 清空输出
- </button>
- <button id="computeLogBtn" class="compute-log-btn">
- <span>📋</span> 下载日志
- </button>
- </div>
- </div>
- </div>
-
- <!-- 画面监控软件更新 -->
- <div id="monitor-tab" class="tab-content">
- <div class="upload-section monitor-section">
- <h2>画面监控软件更新</h2>
- <div class="file-input-container">
- <label for="monitorFileInput" class="file-input-label monitor-upload-btn">
- <span class="upload-icon">📁</span> 选择监控软件更新文件
- </label>
- <input type="file" id="monitorFileInput" class="file-input" accept=".tar.gz,.tgz">
- <span id="monitorFileName" class="file-name">未选择文件</span>
- </div>
-
- <div id="monitorFileInfo" class="selected-file monitor-file" style="display: none;">
- <h4>已选择监控软件文件:</h4>
- <p id="monitorFileInfoText"></p>
- </div>
-
- <div class="log-info">
- <h4>日志文件说明:</h4>
- <ul>
- <li>支持下载所有.log文件为ZIP包</li>
- </ul>
- </div>
-
- <div class="button-group">
- <button id="monitorUpdateBtn" class="monitor-update-btn" disabled>
- <span>更新监控软件</span>
- </button>
- <button id="stopBtnMonitor" class="stop-btn" disabled>
- <span>🛑</span> 停止处理
- </button>
- <button id="clearBtnMonitor" class="clear-btn">
- <span>🗑️</span> 清空输出
- </button>
- <button id="monitorLogBtn" class="monitor-log-btn">
- <span>📋</span> 下载日志
- </button>
- </div>
- </div>
- </div>
-
- <div class="upload-section">
- <h2>执行输出</h2>
- <div id="output" class="output-container">
- 选择软件更新文件并点击更新按钮开始处理...
- </div>
- </div>
- </div>
- </div>
- <div id="notificationContainer" style="display: none;"></div>
- <script>
- let selectedFiles = {
- network: null,
- compute: null,
- monitor: null
- };
-
- let isRunning = false;
- let eventSource = null;
- let uploadInProgress = false;
- let currentSoftware = 'network';
- // 初始化
- document.addEventListener('DOMContentLoaded', function() {
- // 标签页切换
- document.querySelectorAll('.tab').forEach(tab => {
- tab.addEventListener('click', () => {
- const tabId = tab.dataset.tab;
- switchTab(tabId);
- });
- });
- // 网络软件事件监听
- document.getElementById('networkFileInput').addEventListener('change', (e) => handleFileSelect(e, 'network'));
- document.getElementById('networkUpdateBtn').addEventListener('click', () => uploadAndExecute('network'));
- document.getElementById('networkLogBtn').addEventListener('click', () => downloadLogs('network'));
-
- // 计算软件事件监听
- document.getElementById('computeFileInput').addEventListener('change', (e) => handleFileSelect(e, 'compute'));
- document.getElementById('computeUpdateBtn').addEventListener('click', () => uploadAndExecute('compute'));
- document.getElementById('computeLogBtn').addEventListener('click', () => downloadLogs('compute'));
-
- // 监控软件事件监听
- document.getElementById('monitorFileInput').addEventListener('change', (e) => handleFileSelect(e, 'monitor'));
- document.getElementById('monitorUpdateBtn').addEventListener('click', () => uploadAndExecute('monitor'));
- document.getElementById('monitorLogBtn').addEventListener('click', () => downloadLogs('monitor'));
- // 停止按钮事件监听(共用)
- document.getElementById('stopBtn').addEventListener('click', stopExecution);
- document.getElementById('stopBtnCompute').addEventListener('click', stopExecution);
- document.getElementById('stopBtnMonitor').addEventListener('click', stopExecution);
-
- // 清空按钮事件监听
- document.getElementById('clearBtn').addEventListener('click', clearOutput);
- document.getElementById('clearBtnCompute').addEventListener('click', clearOutput);
- document.getElementById('clearBtnMonitor').addEventListener('click', clearOutput);
-
- // 检查初始状态
- checkStatus();
- });
- // 切换标签页
- function switchTab(tabId) {
- // 更新标签页
- document.querySelectorAll('.tab').forEach(tab => {
- tab.classList.remove('active');
- });
- document.querySelector(`.tab[data-tab="${tabId}"]`).classList.add('active');
-
- // 更新内容
- document.querySelectorAll('.tab-content').forEach(content => {
- content.classList.remove('active');
- });
- document.getElementById(`${tabId}-tab`).classList.add('active');
-
- currentSoftware = tabId;
- }
- // 处理文件选择
- function handleFileSelect(event, softwareType) {
- const file = event.target.files[0];
- if (file) {
- selectedFiles[softwareType] = file;
-
- // 更新UI
- document.getElementById(`${softwareType}FileName`).textContent = file.name;
- document.getElementById(`${softwareType}FileInfoText`).textContent =
- `${file.name} (${formatFileSize(file.size)})`;
- document.getElementById(`${softwareType}FileInfo`).style.display = 'block';
- document.getElementById(`${softwareType}UpdateBtn`).disabled = false;
-
- updateStatus('idle', `已选择${getSoftwareName(softwareType)}文件: ${file.name}`);
- }
- }
- // 获取软件名称
- function getSoftwareName(softwareType) {
- const names = {
- network: '网络解析软件',
- compute: '计算软件',
- monitor: '画面监控软件'
- };
- return names[softwareType] || '软件';
- }
- // 格式化文件大小
- function formatFileSize(bytes) {
- if (bytes === 0) return '0 Bytes';
- const k = 1024;
- const sizes = ['Bytes', 'KB', 'MB', 'GB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
- }
- // 下载日志文件
- // 下载日志文件
- async function downloadLogs(softwareType) {
- try {
- // 显示加载状态
- const logBtn = document.getElementById(`${softwareType}LogBtn`);
- const originalText = logBtn.innerHTML;
- logBtn.innerHTML = '<span class="loading"></span> 正在下载...';
- logBtn.disabled = true;
-
- // 发送下载请求
- const response = await fetch(`/download_logs?software=${softwareType}`, {
- method: 'GET'
- });
-
- if (response.ok) {
- // 检查响应类型
- const contentType = response.headers.get('content-type');
-
- if (contentType && contentType.includes('application/zip')) {
- // 获取文件名
- const contentDisposition = response.headers.get('content-disposition');
- let filename = `${softwareType}_logs.zip`;
-
- if (contentDisposition) {
- const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/i.exec(contentDisposition);
- if (matches && matches[1]) {
- filename = matches[1].replace(/['"]/g, '');
- }
- }
-
- // 创建blob并下载
- const blob = await response.blob();
-
- // 检查blob是否有效
- if (blob.size === 0) {
- throw new Error('下载的文件为空');
- }
-
- const url = window.URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = filename;
- document.body.appendChild(a);
- a.click();
-
- // 清理
- setTimeout(() => {
- document.body.removeChild(a);
- window.URL.revokeObjectURL(url);
- }, 100);
-
- // 显示成功通知
- showNotification(`${getSoftwareName(softwareType)}日志下载成功`, 'success');
- updateStatus('idle', `${getSoftwareName(softwareType)}日志已下载`);
-
- } else {
- // 尝试解析JSON错误响应
- try {
- const errorData = await response.json();
- throw new Error(errorData.message || '下载失败:服务器返回错误');
- } catch (jsonError) {
- // 如果不是JSON,可能是HTML错误页面
- const text = await response.text();
- if (text.includes('<!doctype') || text.includes('<html')) {
- throw new Error('服务器错误:返回了HTML页面而非文件');
- } else {
- throw new Error(`下载失败:${text.substring(0, 100)}`);
- }
- }
- }
- } else {
- // 处理HTTP错误
- let errorMessage = `HTTP错误: ${response.status}`;
-
- try {
- const errorData = await response.json();
- errorMessage = errorData.message || errorMessage;
- } catch (e) {
- // 如果不是JSON,尝试获取文本
- const text = await response.text();
- if (text) {
- errorMessage = `服务器错误: ${text.substring(0, 200)}`;
- }
- }
-
- throw new Error(errorMessage);
- }
- } catch (error) {
- console.error('下载日志失败:', error);
-
- // 更详细的错误处理
- let userMessage = `下载失败: ${error.message}`;
-
- if (error.message.includes('Network Error') || error.message.includes('Failed to fetch')) {
- userMessage = '网络连接失败,请检查服务器状态';
- } else if (error.message.includes('HTML')) {
- userMessage = '服务器配置错误,请检查后端服务';
- }
-
- showNotification(userMessage, 'error');
- updateStatus('idle', '日志下载失败');
-
- // 显示调试信息(仅开发环境)
- if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
- console.debug('下载失败详情:', {
- softwareType,
- error: error.message,
- stack: error.stack
- });
- }
- } finally {
- // 恢复按钮状态
- const logBtn = document.getElementById(`${softwareType}LogBtn`);
- logBtn.innerHTML = '<span>📋</span> 下载日志';
- logBtn.disabled = false;
- }
- }
- // 显示通知
- function showNotification(message, type = 'info') {
- const notificationContainer = document.getElementById('notificationContainer');
-
- const notification = document.createElement('div');
- notification.className = `notification notification-${type}`;
-
- const messageSpan = document.createElement('span');
- messageSpan.textContent = message;
-
- const closeBtn = document.createElement('button');
- closeBtn.className = 'close-notification';
- closeBtn.innerHTML = '×';
- closeBtn.onclick = () => notification.remove();
-
- notification.appendChild(messageSpan);
- notification.appendChild(closeBtn);
-
- notificationContainer.appendChild(notification);
- notificationContainer.style.display = 'block';
-
- // 3秒后自动移除
- setTimeout(() => {
- if (notification.parentNode) {
- notification.remove();
- }
- if (notificationContainer.children.length === 0) {
- notificationContainer.style.display = 'none';
- }
- }, 3000);
- }
- // 上传并执行
- async function uploadAndExecute(softwareType) {
- const file = selectedFiles[softwareType];
- if (!file || isRunning || uploadInProgress) return;
-
- uploadInProgress = true;
- currentSoftware = softwareType;
- updateStatus('running', `正在上传${getSoftwareName(softwareType)}...`);
-
- try {
- const formData = new FormData();
- formData.append('file', file);
- formData.append('software_type', softwareType);
-
- const response = await fetch('/upload', {
- method: 'POST',
- body: formData
- });
-
- const data = await response.json();
-
- if (data.success) {
- // 清除输出
- document.getElementById('output').innerHTML = '';
-
- // 开始接收SSE流
- startEventStream();
-
- // 更新按钮状态
- document.getElementById(`${softwareType}UpdateBtn`).disabled = true;
- document.getElementById(`${softwareType}LogBtn`).disabled = true;
- document.getElementById('stopBtn').disabled = false;
- document.getElementById('stopBtnCompute').disabled = false;
- document.getElementById('stopBtnMonitor').disabled = false;
-
- updateStatus('running', `正在处理${getSoftwareName(softwareType)}: ${data.filename}`);
- } else {
- alert(data.message);
- updateStatus('idle', `${getSoftwareName(softwareType)}上传失败`);
- }
- } catch (error) {
- console.error('上传失败:', error);
- alert('上传失败: ' + error.message);
- updateStatus('idle', '上传失败');
- } finally {
- uploadInProgress = false;
- }
- }
- // 开始事件流
- function startEventStream() {
- if (eventSource) {
- eventSource.close();
- }
-
- eventSource = new EventSource('/stream');
-
- eventSource.onmessage = function(event) {
- const data = JSON.parse(event.data);
- const outputDiv = document.getElementById('output');
-
- // 根据软件类型添加颜色标识
- let outputHtml = '';
- if (data.output) {
- const color = getSoftwareColor(currentSoftware);
- outputHtml = `<span style="color: ${color}">[${getSoftwareName(currentSoftware)}] </span>${data.output}`;
- } else {
- outputHtml = data.output;
- }
-
- outputDiv.innerHTML += outputHtml;
-
- // 自动滚动到底部
- outputDiv.scrollTop = outputDiv.scrollHeight;
-
- // 检查是否结束
- if (data.output && data.output.includes('处理完成')) {
- stopEventStream();
- isRunning = false;
- updateStatus('idle', '处理完成');
-
- // 重新启用当前软件的更新按钮和日志按钮
- document.getElementById(`${currentSoftware}UpdateBtn`).disabled = false;
- document.getElementById(`${currentSoftware}LogBtn`).disabled = false;
- document.getElementById('stopBtn').disabled = true;
- document.getElementById('stopBtnCompute').disabled = true;
- document.getElementById('stopBtnMonitor').disabled = true;
-
- showNotification(`${getSoftwareName(currentSoftware)}更新完成`, 'success');
- }
- };
-
- eventSource.onerror = function(error) {
- console.error('EventSource错误:', error);
- stopEventStream();
- updateStatus('idle', '连接错误');
-
- // 重新启用按钮
- document.getElementById(`${currentSoftware}UpdateBtn`).disabled = false;
- document.getElementById(`${currentSoftware}LogBtn`).disabled = false;
- document.getElementById('stopBtn').disabled = true;
- document.getElementById('stopBtnCompute').disabled = true;
- document.getElementById('stopBtnMonitor').disabled = true;
-
- showNotification('连接中断,处理可能未完成', 'error');
- };
- }
- // 获取软件颜色
- function getSoftwareColor(softwareType) {
- const colors = {
- network: '#667eea',
- compute: '#00b09b',
- monitor: '#ff416c'
- };
- return colors[softwareType] || '#d4d4d4';
- }
- // 停止事件流
- function stopEventStream() {
- if (eventSource) {
- eventSource.close();
- eventSource = null;
- }
- }
- // 停止执行
- async function stopExecution() {
- if (!isRunning) return;
-
- try {
- const response = await fetch('/stop');
- const data = await response.json();
-
- if (data.success) {
- isRunning = false;
- updateStatus('idle', '已停止');
- stopEventStream();
-
- // 更新按钮状态
- document.getElementById(`${currentSoftware}UpdateBtn`).disabled = false;
- document.getElementById(`${currentSoftware}LogBtn`).disabled = false;
- document.getElementById('stopBtn').disabled = true;
- document.getElementById('stopBtnCompute').disabled = true;
- document.getElementById('stopBtnMonitor').disabled = true;
-
- const color = getSoftwareColor(currentSoftware);
- document.getElementById('output').innerHTML +=
- `<br><span style="color: ${color}">[${getSoftwareName(currentSoftware)}] </span>` +
- '<span style="color: orange;">用户手动停止处理</span><br>';
-
- showNotification('处理已停止', 'info');
- }
- } catch (error) {
- console.error('停止失败:', error);
- showNotification('停止失败', 'error');
- }
- }
- // 清空输出
- async function clearOutput() {
- try {
- const response = await fetch('/clear_output', {
- method: 'POST'
- });
- const data = await response.json();
-
- if (data.success) {
- document.getElementById('output').innerHTML = '输出已清空<br>';
- showNotification('输出已清空', 'info');
- }
- } catch (error) {
- console.error('清空输出失败:', error);
- showNotification('清空输出失败', 'error');
- }
- }
- // 检查状态
- async function checkStatus() {
- try {
- const response = await fetch('/status');
- const data = await response.json();
-
- if (data.is_running) {
- isRunning = true;
- updateStatus('running', '正在处理中...');
-
- // 根据服务器返回的软件类型禁用对应按钮
- const runningSoftware = data.software_type || 'network';
- document.getElementById(`${runningSoftware}UpdateBtn`).disabled = true;
- document.getElementById(`${runningSoftware}LogBtn`).disabled = true;
- document.getElementById('stopBtn').disabled = false;
- document.getElementById('stopBtnCompute').disabled = false;
- document.getElementById('stopBtnMonitor').disabled = false;
-
- currentSoftware = runningSoftware;
- startEventStream();
- } else {
- updateStatus('idle', '就绪 - 等待上传文件');
- }
- } catch (error) {
- console.error('检查状态失败:', error);
- }
- }
- // 更新状态指示器
- function updateStatus(status, message) {
- const indicator = document.getElementById('statusIndicator');
- const statusText = document.getElementById('statusText');
- const statusDot = indicator.querySelector('.status-dot');
-
- // 移除所有状态类
- indicator.classList.remove('status-running', 'status-stopped', 'status-idle');
- statusDot.classList.remove('running-dot', 'stopped-dot', 'idle-dot');
-
- // 添加新状态类
- if (status === 'running') {
- indicator.classList.add('status-running');
- statusDot.classList.add('running-dot');
- isRunning = true;
- } else if (status === 'stopped') {
- indicator.classList.add('status-stopped');
- statusDot.classList.add('stopped-dot');
- isRunning = false;
- } else {
- indicator.classList.add('status-idle');
- statusDot.classList.add('idle-dot');
- isRunning = false;
- }
-
- statusText.textContent = message;
- }
- </script>
- </body>
- </html>
|