|
|
@@ -0,0 +1,1210 @@
|
|
|
+<!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>
|