index.html 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>软件更新管理系统</title>
  7. <style>
  8. * {
  9. margin: 0;
  10. padding: 0;
  11. box-sizing: border-box;
  12. }
  13. body {
  14. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  15. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  16. min-height: 100vh;
  17. padding: 20px;
  18. }
  19. .container {
  20. max-width: 1200px;
  21. margin: 0 auto;
  22. background: white;
  23. border-radius: 15px;
  24. box-shadow: 0 20px 40px rgba(0,0,0,0.1);
  25. overflow: hidden;
  26. }
  27. .header {
  28. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  29. color: white;
  30. padding: 30px;
  31. text-align: center;
  32. }
  33. .header h1 {
  34. font-size: 2.5em;
  35. margin-bottom: 10px;
  36. }
  37. .content {
  38. padding: 30px;
  39. }
  40. .upload-section {
  41. background: #f8f9fa;
  42. border-radius: 10px;
  43. padding: 25px;
  44. margin-bottom: 20px;
  45. box-shadow: 0 5px 15px rgba(0,0,0,0.05);
  46. border-left: 5px solid;
  47. }
  48. .network-section {
  49. border-left-color: #667eea;
  50. }
  51. .compute-section {
  52. border-left-color: #00b09b;
  53. }
  54. .monitor-section {
  55. border-left-color: #ff416c;
  56. }
  57. .upload-section h2 {
  58. color: #333;
  59. margin-bottom: 20px;
  60. padding-bottom: 10px;
  61. border-bottom: 2px solid;
  62. }
  63. .network-section h2 {
  64. border-bottom-color: #667eea;
  65. }
  66. .compute-section h2 {
  67. border-bottom-color: #00b09b;
  68. }
  69. .monitor-section h2 {
  70. border-bottom-color: #ff416c;
  71. }
  72. .section-header {
  73. display: flex;
  74. justify-content: space-between;
  75. align-items: center;
  76. margin-bottom: 15px;
  77. }
  78. .section-tag {
  79. padding: 4px 12px;
  80. border-radius: 20px;
  81. font-size: 0.8em;
  82. font-weight: 600;
  83. color: white;
  84. }
  85. .network-tag {
  86. background: #667eea;
  87. }
  88. .compute-tag {
  89. background: #00b09b;
  90. }
  91. .monitor-tag {
  92. background: #ff416c;
  93. }
  94. .file-input-container {
  95. margin-bottom: 20px;
  96. }
  97. .file-input-label {
  98. display: inline-block;
  99. padding: 12px 24px;
  100. color: white;
  101. border-radius: 6px;
  102. cursor: pointer;
  103. font-weight: 600;
  104. transition: all 0.3s ease;
  105. }
  106. .network-upload-btn {
  107. background: #667eea;
  108. }
  109. .compute-upload-btn {
  110. background: #00b09b;
  111. }
  112. .monitor-upload-btn {
  113. background: #ff416c;
  114. }
  115. .file-input-label:hover {
  116. transform: translateY(-2px);
  117. box-shadow: 0 5px 15px rgba(0,0,0,0.1);
  118. }
  119. .network-upload-btn:hover {
  120. background: #5a6fd8;
  121. }
  122. .compute-upload-btn:hover {
  123. background: #009688;
  124. }
  125. .monitor-upload-btn:hover {
  126. background: #ff2b55;
  127. }
  128. .file-input {
  129. display: none;
  130. }
  131. .file-name {
  132. margin-left: 15px;
  133. color: #666;
  134. font-style: italic;
  135. }
  136. .selected-file {
  137. margin-top: 15px;
  138. padding: 15px;
  139. background: #e9ecef;
  140. border-radius: 8px;
  141. border-left: 4px solid;
  142. }
  143. .network-file {
  144. border-left-color: #667eea;
  145. }
  146. .compute-file {
  147. border-left-color: #00b09b;
  148. }
  149. .monitor-file {
  150. border-left-color: #ff416c;
  151. }
  152. .selected-file h4 {
  153. margin-bottom: 5px;
  154. color: #333;
  155. }
  156. .selected-file p {
  157. color: #666;
  158. font-size: 0.9em;
  159. }
  160. .output-container {
  161. height: 400px;
  162. overflow-y: auto;
  163. background: #1e1e1e;
  164. border-radius: 8px;
  165. padding: 20px;
  166. font-family: 'Consolas', 'Monaco', monospace;
  167. font-size: 14px;
  168. color: #d4d4d4;
  169. line-height: 1.5;
  170. }
  171. .output-container::-webkit-scrollbar {
  172. width: 8px;
  173. }
  174. .output-container::-webkit-scrollbar-track {
  175. background: #2d2d2d;
  176. }
  177. .output-container::-webkit-scrollbar-thumb {
  178. background: #555;
  179. border-radius: 4px;
  180. }
  181. .button-group {
  182. display: flex;
  183. gap: 10px;
  184. margin-top: 20px;
  185. flex-wrap: wrap;
  186. }
  187. button {
  188. padding: 12px 24px;
  189. border: none;
  190. border-radius: 6px;
  191. cursor: pointer;
  192. font-weight: 600;
  193. transition: all 0.3s ease;
  194. font-size: 16px;
  195. display: flex;
  196. align-items: center;
  197. justify-content: center;
  198. gap: 8px;
  199. }
  200. .update-btn {
  201. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  202. color: white;
  203. }
  204. .compute-update-btn {
  205. background: linear-gradient(135deg, #00b09b 0%, #96c93d 100%);
  206. color: white;
  207. }
  208. .monitor-update-btn {
  209. background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%);
  210. color: white;
  211. }
  212. .stop-btn {
  213. background: linear-gradient(135deg, #6c757d 0%, #495057 100%);
  214. color: white;
  215. }
  216. .clear-btn {
  217. background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
  218. color: white;
  219. }
  220. .log-btn {
  221. background: linear-gradient(135deg, #ff9800 0%, #ff5722 100%);
  222. color: white;
  223. }
  224. .compute-log-btn {
  225. background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);
  226. color: white;
  227. }
  228. .monitor-log-btn {
  229. background: linear-gradient(135deg, #9c27b0 0%, #6a1b9a 100%);
  230. color: white;
  231. }
  232. button:hover:not(:disabled) {
  233. transform: translateY(-2px);
  234. box-shadow: 0 7px 14px rgba(0,0,0,0.1);
  235. }
  236. button:disabled {
  237. opacity: 0.6;
  238. cursor: not-allowed;
  239. }
  240. .status-indicator {
  241. display: flex;
  242. align-items: center;
  243. gap: 10px;
  244. padding: 10px 20px;
  245. border-radius: 20px;
  246. font-weight: 600;
  247. margin-bottom: 20px;
  248. background: #f8f9fa;
  249. border: 1px solid #dee2e6;
  250. }
  251. .status-running {
  252. background: #d4edda;
  253. border-color: #c3e6cb;
  254. color: #155724;
  255. }
  256. .status-stopped {
  257. background: #f8d7da;
  258. border-color: #f5c6cb;
  259. color: #721c24;
  260. }
  261. .status-idle {
  262. background: #fff3cd;
  263. border-color: #ffeeba;
  264. color: #856404;
  265. }
  266. .status-dot {
  267. width: 12px;
  268. height: 12px;
  269. border-radius: 50%;
  270. animation: pulse 2s infinite;
  271. }
  272. .running-dot {
  273. background: #28a745;
  274. }
  275. .stopped-dot {
  276. background: #dc3545;
  277. }
  278. .idle-dot {
  279. background: #ffc107;
  280. }
  281. @keyframes pulse {
  282. 0% { opacity: 1; }
  283. 50% { opacity: 0.5; }
  284. 100% { opacity: 1; }
  285. }
  286. .loading {
  287. display: inline-block;
  288. width: 20px;
  289. height: 20px;
  290. border: 3px solid #f3f3f3;
  291. border-top: 3px solid;
  292. border-radius: 50%;
  293. animation: spin 1s linear infinite;
  294. }
  295. .network-loading {
  296. border-top-color: #667eea;
  297. }
  298. .compute-loading {
  299. border-top-color: #00b09b;
  300. }
  301. .monitor-loading {
  302. border-top-color: #ff416c;
  303. }
  304. @keyframes spin {
  305. 0% { transform: rotate(0deg); }
  306. 100% { transform: rotate(360deg); }
  307. }
  308. .info-box {
  309. background: #e7f3ff;
  310. border-radius: 8px;
  311. padding: 15px;
  312. margin-bottom: 20px;
  313. border-left: 4px solid #2196F3;
  314. }
  315. .info-box h3 {
  316. color: #1976D2;
  317. margin-bottom: 10px;
  318. }
  319. .info-box ul {
  320. padding-left: 20px;
  321. color: #555;
  322. }
  323. .info-box li {
  324. margin-bottom: 5px;
  325. }
  326. .upload-icon {
  327. font-size: 1.2em;
  328. }
  329. .software-grid {
  330. display: grid;
  331. grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  332. gap: 20px;
  333. margin-bottom: 30px;
  334. }
  335. .software-card {
  336. background: white;
  337. border-radius: 10px;
  338. padding: 20px;
  339. box-shadow: 0 5px 15px rgba(0,0,0,0.05);
  340. border: 1px solid #dee2e6;
  341. }
  342. .software-card h3 {
  343. color: #333;
  344. margin-bottom: 15px;
  345. padding-bottom: 10px;
  346. border-bottom: 2px solid;
  347. }
  348. .network-card h3 {
  349. border-bottom-color: #667eea;
  350. }
  351. .compute-card h3 {
  352. border-bottom-color: #00b09b;
  353. }
  354. .monitor-card h3 {
  355. border-bottom-color: #ff416c;
  356. }
  357. .software-info {
  358. margin-bottom: 15px;
  359. color: #666;
  360. font-size: 0.9em;
  361. }
  362. .current-version {
  363. background: #e9ecef;
  364. padding: 8px 12px;
  365. border-radius: 4px;
  366. font-family: monospace;
  367. margin-top: 10px;
  368. }
  369. .tabs {
  370. display: flex;
  371. border-bottom: 1px solid #dee2e6;
  372. margin-bottom: 20px;
  373. }
  374. .tab {
  375. padding: 10px 20px;
  376. cursor: pointer;
  377. border-bottom: 3px solid transparent;
  378. transition: all 0.3s ease;
  379. }
  380. .tab:hover {
  381. background: #f8f9fa;
  382. }
  383. .tab.active {
  384. border-bottom-color: #667eea;
  385. color: #667eea;
  386. font-weight: 600;
  387. }
  388. .tab-content {
  389. display: none;
  390. }
  391. .tab-content.active {
  392. display: block;
  393. }
  394. .log-info {
  395. background: #fff3cd;
  396. border-left: 4px solid #ffc107;
  397. padding: 12px 15px;
  398. margin-top: 15px;
  399. border-radius: 6px;
  400. font-size: 0.9em;
  401. }
  402. .log-info h4 {
  403. color: #856404;
  404. margin-bottom: 5px;
  405. }
  406. .log-info ul {
  407. padding-left: 20px;
  408. color: #856404;
  409. }
  410. .notification {
  411. position: fixed;
  412. top: 20px;
  413. right: 20px;
  414. padding: 15px 20px;
  415. background: white;
  416. border-radius: 8px;
  417. box-shadow: 0 5px 20px rgba(0,0,0,0.2);
  418. display: flex;
  419. align-items: center;
  420. gap: 10px;
  421. z-index: 1000;
  422. animation: slideIn 0.3s ease;
  423. border-left: 4px solid;
  424. }
  425. .notification-success {
  426. border-left-color: #28a745;
  427. }
  428. .notification-error {
  429. border-left-color: #dc3545;
  430. }
  431. .notification-info {
  432. border-left-color: #17a2b8;
  433. }
  434. @keyframes slideIn {
  435. from {
  436. transform: translateX(100%);
  437. opacity: 0;
  438. }
  439. to {
  440. transform: translateX(0);
  441. opacity: 1;
  442. }
  443. }
  444. .close-notification {
  445. background: none;
  446. border: none;
  447. font-size: 1.2em;
  448. cursor: pointer;
  449. color: #666;
  450. padding: 0;
  451. width: 24px;
  452. height: 24px;
  453. display: flex;
  454. align-items: center;
  455. justify-content: center;
  456. }
  457. .close-notification:hover {
  458. color: #333;
  459. }
  460. </style>
  461. </head>
  462. <body>
  463. <div class="container">
  464. <div class="header">
  465. <h1>软件更新管理系统</h1>
  466. <p>统一管理网络解析、计算、画面监控软件的更新和日志</p>
  467. </div>
  468. <div class="content">
  469. <div id="statusIndicator" class="status-indicator status-idle">
  470. <span class="status-dot idle-dot"></span>
  471. <span id="statusText">就绪 - 等待操作</span>
  472. <span id="currentFile" style="margin-left: auto; font-weight: normal; color: #666;"></span>
  473. </div>
  474. <div class="info-box">
  475. <h3>使用说明</h3>
  476. <ul>
  477. <li>选择对应软件类型的更新文件,点击更新按钮</li>
  478. <li>支持的文件类型: .tar.gz, .tgz</li>
  479. <li>处理过程实时显示在下方输出区域</li>
  480. <li>不同软件使用不同的颜色标识,便于区分</li>
  481. <li>点击"下载日志"按钮可以下载软件的所有日志文件</li>
  482. </ul>
  483. </div>
  484. <div class="tabs">
  485. <div class="tab active" data-tab="network">网络解析软件</div>
  486. <div class="tab" data-tab="compute">计算软件</div>
  487. <div class="tab" data-tab="monitor">画面监控软件</div>
  488. </div>
  489. <!-- 网络解析软件更新 -->
  490. <div id="network-tab" class="tab-content active">
  491. <div class="upload-section network-section">
  492. <h2>网络解析软件更新</h2>
  493. <div class="file-input-container">
  494. <label for="networkFileInput" class="file-input-label network-upload-btn">
  495. <span class="upload-icon">📁</span> 选择网络软件更新文件
  496. </label>
  497. <input type="file" id="networkFileInput" class="file-input" accept=".tar.gz,.tgz">
  498. <span id="networkFileName" class="file-name">未选择文件</span>
  499. </div>
  500. <div id="networkFileInfo" class="selected-file network-file" style="display: none;">
  501. <h4>已选择网络软件文件:</h4>
  502. <p id="networkFileInfoText"></p>
  503. </div>
  504. <div class="log-info">
  505. <h4>日志文件说明:</h4>
  506. <ul>
  507. <li>支持下载所有.log文件为ZIP包</li>
  508. </ul>
  509. </div>
  510. <div class="button-group">
  511. <button id="networkUpdateBtn" class="update-btn" disabled>
  512. <span>更新网络软件</span>
  513. </button>
  514. <button id="stopBtn" class="stop-btn" disabled>
  515. <span>🛑</span> 停止处理
  516. </button>
  517. <button id="clearBtn" class="clear-btn">
  518. <span>🗑️</span> 清空输出
  519. </button>
  520. <button id="networkLogBtn" class="log-btn">
  521. <span>📋</span> 下载日志
  522. </button>
  523. </div>
  524. </div>
  525. </div>
  526. <!-- 计算软件更新 -->
  527. <div id="compute-tab" class="tab-content">
  528. <div class="upload-section compute-section">
  529. <h2>计算软件更新</h2>
  530. <div class="file-input-container">
  531. <label for="computeFileInput" class="file-input-label compute-upload-btn">
  532. <span class="upload-icon">📁</span> 选择计算软件更新文件
  533. </label>
  534. <input type="file" id="computeFileInput" class="file-input" accept=".tar.gz,.tgz">
  535. <span id="computeFileName" class="file-name">未选择文件</span>
  536. </div>
  537. <div id="computeFileInfo" class="selected-file compute-file" style="display: none;">
  538. <h4>已选择计算软件文件:</h4>
  539. <p id="computeFileInfoText"></p>
  540. </div>
  541. <div class="log-info">
  542. <h4>日志文件说明:</h4>
  543. <ul>
  544. <li>支持下载所有.log文件为ZIP包</li>
  545. </ul>
  546. </div>
  547. <div class="button-group">
  548. <button id="computeUpdateBtn" class="compute-update-btn" disabled>
  549. <span>更新计算软件</span>
  550. </button>
  551. <button id="stopBtnCompute" class="stop-btn" disabled>
  552. <span>🛑</span> 停止处理
  553. </button>
  554. <button id="clearBtnCompute" class="clear-btn">
  555. <span>🗑️</span> 清空输出
  556. </button>
  557. <button id="computeLogBtn" class="compute-log-btn">
  558. <span>📋</span> 下载日志
  559. </button>
  560. </div>
  561. </div>
  562. </div>
  563. <!-- 画面监控软件更新 -->
  564. <div id="monitor-tab" class="tab-content">
  565. <div class="upload-section monitor-section">
  566. <h2>画面监控软件更新</h2>
  567. <div class="file-input-container">
  568. <label for="monitorFileInput" class="file-input-label monitor-upload-btn">
  569. <span class="upload-icon">📁</span> 选择监控软件更新文件
  570. </label>
  571. <input type="file" id="monitorFileInput" class="file-input" accept=".tar.gz,.tgz">
  572. <span id="monitorFileName" class="file-name">未选择文件</span>
  573. </div>
  574. <div id="monitorFileInfo" class="selected-file monitor-file" style="display: none;">
  575. <h4>已选择监控软件文件:</h4>
  576. <p id="monitorFileInfoText"></p>
  577. </div>
  578. <div class="log-info">
  579. <h4>日志文件说明:</h4>
  580. <ul>
  581. <li>支持下载所有.log文件为ZIP包</li>
  582. </ul>
  583. </div>
  584. <div class="button-group">
  585. <button id="monitorUpdateBtn" class="monitor-update-btn" disabled>
  586. <span>更新监控软件</span>
  587. </button>
  588. <button id="stopBtnMonitor" class="stop-btn" disabled>
  589. <span>🛑</span> 停止处理
  590. </button>
  591. <button id="clearBtnMonitor" class="clear-btn">
  592. <span>🗑️</span> 清空输出
  593. </button>
  594. <button id="monitorLogBtn" class="monitor-log-btn">
  595. <span>📋</span> 下载日志
  596. </button>
  597. </div>
  598. </div>
  599. </div>
  600. <div class="upload-section">
  601. <h2>执行输出</h2>
  602. <div id="output" class="output-container">
  603. 选择软件更新文件并点击更新按钮开始处理...
  604. </div>
  605. </div>
  606. </div>
  607. </div>
  608. <div id="notificationContainer" style="display: none;"></div>
  609. <script>
  610. let selectedFiles = {
  611. network: null,
  612. compute: null,
  613. monitor: null
  614. };
  615. let isRunning = false;
  616. let eventSource = null;
  617. let uploadInProgress = false;
  618. let currentSoftware = 'network';
  619. // 初始化
  620. document.addEventListener('DOMContentLoaded', function() {
  621. // 标签页切换
  622. document.querySelectorAll('.tab').forEach(tab => {
  623. tab.addEventListener('click', () => {
  624. const tabId = tab.dataset.tab;
  625. switchTab(tabId);
  626. });
  627. });
  628. // 网络软件事件监听
  629. document.getElementById('networkFileInput').addEventListener('change', (e) => handleFileSelect(e, 'network'));
  630. document.getElementById('networkUpdateBtn').addEventListener('click', () => uploadAndExecute('network'));
  631. document.getElementById('networkLogBtn').addEventListener('click', () => downloadLogs('network'));
  632. // 计算软件事件监听
  633. document.getElementById('computeFileInput').addEventListener('change', (e) => handleFileSelect(e, 'compute'));
  634. document.getElementById('computeUpdateBtn').addEventListener('click', () => uploadAndExecute('compute'));
  635. document.getElementById('computeLogBtn').addEventListener('click', () => downloadLogs('compute'));
  636. // 监控软件事件监听
  637. document.getElementById('monitorFileInput').addEventListener('change', (e) => handleFileSelect(e, 'monitor'));
  638. document.getElementById('monitorUpdateBtn').addEventListener('click', () => uploadAndExecute('monitor'));
  639. document.getElementById('monitorLogBtn').addEventListener('click', () => downloadLogs('monitor'));
  640. // 停止按钮事件监听(共用)
  641. document.getElementById('stopBtn').addEventListener('click', stopExecution);
  642. document.getElementById('stopBtnCompute').addEventListener('click', stopExecution);
  643. document.getElementById('stopBtnMonitor').addEventListener('click', stopExecution);
  644. // 清空按钮事件监听
  645. document.getElementById('clearBtn').addEventListener('click', clearOutput);
  646. document.getElementById('clearBtnCompute').addEventListener('click', clearOutput);
  647. document.getElementById('clearBtnMonitor').addEventListener('click', clearOutput);
  648. // 检查初始状态
  649. checkStatus();
  650. });
  651. // 切换标签页
  652. function switchTab(tabId) {
  653. // 更新标签页
  654. document.querySelectorAll('.tab').forEach(tab => {
  655. tab.classList.remove('active');
  656. });
  657. document.querySelector(`.tab[data-tab="${tabId}"]`).classList.add('active');
  658. // 更新内容
  659. document.querySelectorAll('.tab-content').forEach(content => {
  660. content.classList.remove('active');
  661. });
  662. document.getElementById(`${tabId}-tab`).classList.add('active');
  663. currentSoftware = tabId;
  664. }
  665. // 处理文件选择
  666. function handleFileSelect(event, softwareType) {
  667. const file = event.target.files[0];
  668. if (file) {
  669. selectedFiles[softwareType] = file;
  670. // 更新UI
  671. document.getElementById(`${softwareType}FileName`).textContent = file.name;
  672. document.getElementById(`${softwareType}FileInfoText`).textContent =
  673. `${file.name} (${formatFileSize(file.size)})`;
  674. document.getElementById(`${softwareType}FileInfo`).style.display = 'block';
  675. document.getElementById(`${softwareType}UpdateBtn`).disabled = false;
  676. updateStatus('idle', `已选择${getSoftwareName(softwareType)}文件: ${file.name}`);
  677. }
  678. }
  679. // 获取软件名称
  680. function getSoftwareName(softwareType) {
  681. const names = {
  682. network: '网络解析软件',
  683. compute: '计算软件',
  684. monitor: '画面监控软件'
  685. };
  686. return names[softwareType] || '软件';
  687. }
  688. // 格式化文件大小
  689. function formatFileSize(bytes) {
  690. if (bytes === 0) return '0 Bytes';
  691. const k = 1024;
  692. const sizes = ['Bytes', 'KB', 'MB', 'GB'];
  693. const i = Math.floor(Math.log(bytes) / Math.log(k));
  694. return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  695. }
  696. // 下载日志文件
  697. // 下载日志文件
  698. async function downloadLogs(softwareType) {
  699. try {
  700. // 显示加载状态
  701. const logBtn = document.getElementById(`${softwareType}LogBtn`);
  702. const originalText = logBtn.innerHTML;
  703. logBtn.innerHTML = '<span class="loading"></span> 正在下载...';
  704. logBtn.disabled = true;
  705. // 发送下载请求
  706. const response = await fetch(`/download_logs?software=${softwareType}`, {
  707. method: 'GET'
  708. });
  709. if (response.ok) {
  710. // 检查响应类型
  711. const contentType = response.headers.get('content-type');
  712. if (contentType && contentType.includes('application/zip')) {
  713. // 获取文件名
  714. const contentDisposition = response.headers.get('content-disposition');
  715. let filename = `${softwareType}_logs.zip`;
  716. if (contentDisposition) {
  717. const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/i.exec(contentDisposition);
  718. if (matches && matches[1]) {
  719. filename = matches[1].replace(/['"]/g, '');
  720. }
  721. }
  722. // 创建blob并下载
  723. const blob = await response.blob();
  724. // 检查blob是否有效
  725. if (blob.size === 0) {
  726. throw new Error('下载的文件为空');
  727. }
  728. const url = window.URL.createObjectURL(blob);
  729. const a = document.createElement('a');
  730. a.href = url;
  731. a.download = filename;
  732. document.body.appendChild(a);
  733. a.click();
  734. // 清理
  735. setTimeout(() => {
  736. document.body.removeChild(a);
  737. window.URL.revokeObjectURL(url);
  738. }, 100);
  739. // 显示成功通知
  740. showNotification(`${getSoftwareName(softwareType)}日志下载成功`, 'success');
  741. updateStatus('idle', `${getSoftwareName(softwareType)}日志已下载`);
  742. } else {
  743. // 尝试解析JSON错误响应
  744. try {
  745. const errorData = await response.json();
  746. throw new Error(errorData.message || '下载失败:服务器返回错误');
  747. } catch (jsonError) {
  748. // 如果不是JSON,可能是HTML错误页面
  749. const text = await response.text();
  750. if (text.includes('<!doctype') || text.includes('<html')) {
  751. throw new Error('服务器错误:返回了HTML页面而非文件');
  752. } else {
  753. throw new Error(`下载失败:${text.substring(0, 100)}`);
  754. }
  755. }
  756. }
  757. } else {
  758. // 处理HTTP错误
  759. let errorMessage = `HTTP错误: ${response.status}`;
  760. try {
  761. const errorData = await response.json();
  762. errorMessage = errorData.message || errorMessage;
  763. } catch (e) {
  764. // 如果不是JSON,尝试获取文本
  765. const text = await response.text();
  766. if (text) {
  767. errorMessage = `服务器错误: ${text.substring(0, 200)}`;
  768. }
  769. }
  770. throw new Error(errorMessage);
  771. }
  772. } catch (error) {
  773. console.error('下载日志失败:', error);
  774. // 更详细的错误处理
  775. let userMessage = `下载失败: ${error.message}`;
  776. if (error.message.includes('Network Error') || error.message.includes('Failed to fetch')) {
  777. userMessage = '网络连接失败,请检查服务器状态';
  778. } else if (error.message.includes('HTML')) {
  779. userMessage = '服务器配置错误,请检查后端服务';
  780. }
  781. showNotification(userMessage, 'error');
  782. updateStatus('idle', '日志下载失败');
  783. // 显示调试信息(仅开发环境)
  784. if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
  785. console.debug('下载失败详情:', {
  786. softwareType,
  787. error: error.message,
  788. stack: error.stack
  789. });
  790. }
  791. } finally {
  792. // 恢复按钮状态
  793. const logBtn = document.getElementById(`${softwareType}LogBtn`);
  794. logBtn.innerHTML = '<span>📋</span> 下载日志';
  795. logBtn.disabled = false;
  796. }
  797. }
  798. // 显示通知
  799. function showNotification(message, type = 'info') {
  800. const notificationContainer = document.getElementById('notificationContainer');
  801. const notification = document.createElement('div');
  802. notification.className = `notification notification-${type}`;
  803. const messageSpan = document.createElement('span');
  804. messageSpan.textContent = message;
  805. const closeBtn = document.createElement('button');
  806. closeBtn.className = 'close-notification';
  807. closeBtn.innerHTML = '×';
  808. closeBtn.onclick = () => notification.remove();
  809. notification.appendChild(messageSpan);
  810. notification.appendChild(closeBtn);
  811. notificationContainer.appendChild(notification);
  812. notificationContainer.style.display = 'block';
  813. // 3秒后自动移除
  814. setTimeout(() => {
  815. if (notification.parentNode) {
  816. notification.remove();
  817. }
  818. if (notificationContainer.children.length === 0) {
  819. notificationContainer.style.display = 'none';
  820. }
  821. }, 3000);
  822. }
  823. // 上传并执行
  824. async function uploadAndExecute(softwareType) {
  825. const file = selectedFiles[softwareType];
  826. if (!file || isRunning || uploadInProgress) return;
  827. uploadInProgress = true;
  828. currentSoftware = softwareType;
  829. updateStatus('running', `正在上传${getSoftwareName(softwareType)}...`);
  830. try {
  831. const formData = new FormData();
  832. formData.append('file', file);
  833. formData.append('software_type', softwareType);
  834. const response = await fetch('/upload', {
  835. method: 'POST',
  836. body: formData
  837. });
  838. const data = await response.json();
  839. if (data.success) {
  840. // 清除输出
  841. document.getElementById('output').innerHTML = '';
  842. // 开始接收SSE流
  843. startEventStream();
  844. // 更新按钮状态
  845. document.getElementById(`${softwareType}UpdateBtn`).disabled = true;
  846. document.getElementById(`${softwareType}LogBtn`).disabled = true;
  847. document.getElementById('stopBtn').disabled = false;
  848. document.getElementById('stopBtnCompute').disabled = false;
  849. document.getElementById('stopBtnMonitor').disabled = false;
  850. updateStatus('running', `正在处理${getSoftwareName(softwareType)}: ${data.filename}`);
  851. } else {
  852. alert(data.message);
  853. updateStatus('idle', `${getSoftwareName(softwareType)}上传失败`);
  854. }
  855. } catch (error) {
  856. console.error('上传失败:', error);
  857. alert('上传失败: ' + error.message);
  858. updateStatus('idle', '上传失败');
  859. } finally {
  860. uploadInProgress = false;
  861. }
  862. }
  863. // 开始事件流
  864. function startEventStream() {
  865. if (eventSource) {
  866. eventSource.close();
  867. }
  868. eventSource = new EventSource('/stream');
  869. eventSource.onmessage = function(event) {
  870. const data = JSON.parse(event.data);
  871. const outputDiv = document.getElementById('output');
  872. // 根据软件类型添加颜色标识
  873. let outputHtml = '';
  874. if (data.output) {
  875. const color = getSoftwareColor(currentSoftware);
  876. outputHtml = `<span style="color: ${color}">[${getSoftwareName(currentSoftware)}] </span>${data.output}`;
  877. } else {
  878. outputHtml = data.output;
  879. }
  880. outputDiv.innerHTML += outputHtml;
  881. // 自动滚动到底部
  882. outputDiv.scrollTop = outputDiv.scrollHeight;
  883. // 检查是否结束
  884. if (data.output && data.output.includes('处理完成')) {
  885. stopEventStream();
  886. isRunning = false;
  887. updateStatus('idle', '处理完成');
  888. // 重新启用当前软件的更新按钮和日志按钮
  889. document.getElementById(`${currentSoftware}UpdateBtn`).disabled = false;
  890. document.getElementById(`${currentSoftware}LogBtn`).disabled = false;
  891. document.getElementById('stopBtn').disabled = true;
  892. document.getElementById('stopBtnCompute').disabled = true;
  893. document.getElementById('stopBtnMonitor').disabled = true;
  894. showNotification(`${getSoftwareName(currentSoftware)}更新完成`, 'success');
  895. }
  896. };
  897. eventSource.onerror = function(error) {
  898. console.error('EventSource错误:', error);
  899. stopEventStream();
  900. updateStatus('idle', '连接错误');
  901. // 重新启用按钮
  902. document.getElementById(`${currentSoftware}UpdateBtn`).disabled = false;
  903. document.getElementById(`${currentSoftware}LogBtn`).disabled = false;
  904. document.getElementById('stopBtn').disabled = true;
  905. document.getElementById('stopBtnCompute').disabled = true;
  906. document.getElementById('stopBtnMonitor').disabled = true;
  907. showNotification('连接中断,处理可能未完成', 'error');
  908. };
  909. }
  910. // 获取软件颜色
  911. function getSoftwareColor(softwareType) {
  912. const colors = {
  913. network: '#667eea',
  914. compute: '#00b09b',
  915. monitor: '#ff416c'
  916. };
  917. return colors[softwareType] || '#d4d4d4';
  918. }
  919. // 停止事件流
  920. function stopEventStream() {
  921. if (eventSource) {
  922. eventSource.close();
  923. eventSource = null;
  924. }
  925. }
  926. // 停止执行
  927. async function stopExecution() {
  928. if (!isRunning) return;
  929. try {
  930. const response = await fetch('/stop');
  931. const data = await response.json();
  932. if (data.success) {
  933. isRunning = false;
  934. updateStatus('idle', '已停止');
  935. stopEventStream();
  936. // 更新按钮状态
  937. document.getElementById(`${currentSoftware}UpdateBtn`).disabled = false;
  938. document.getElementById(`${currentSoftware}LogBtn`).disabled = false;
  939. document.getElementById('stopBtn').disabled = true;
  940. document.getElementById('stopBtnCompute').disabled = true;
  941. document.getElementById('stopBtnMonitor').disabled = true;
  942. const color = getSoftwareColor(currentSoftware);
  943. document.getElementById('output').innerHTML +=
  944. `<br><span style="color: ${color}">[${getSoftwareName(currentSoftware)}] </span>` +
  945. '<span style="color: orange;">用户手动停止处理</span><br>';
  946. showNotification('处理已停止', 'info');
  947. }
  948. } catch (error) {
  949. console.error('停止失败:', error);
  950. showNotification('停止失败', 'error');
  951. }
  952. }
  953. // 清空输出
  954. async function clearOutput() {
  955. try {
  956. const response = await fetch('/clear_output', {
  957. method: 'POST'
  958. });
  959. const data = await response.json();
  960. if (data.success) {
  961. document.getElementById('output').innerHTML = '输出已清空<br>';
  962. showNotification('输出已清空', 'info');
  963. }
  964. } catch (error) {
  965. console.error('清空输出失败:', error);
  966. showNotification('清空输出失败', 'error');
  967. }
  968. }
  969. // 检查状态
  970. async function checkStatus() {
  971. try {
  972. const response = await fetch('/status');
  973. const data = await response.json();
  974. if (data.is_running) {
  975. isRunning = true;
  976. updateStatus('running', '正在处理中...');
  977. // 根据服务器返回的软件类型禁用对应按钮
  978. const runningSoftware = data.software_type || 'network';
  979. document.getElementById(`${runningSoftware}UpdateBtn`).disabled = true;
  980. document.getElementById(`${runningSoftware}LogBtn`).disabled = true;
  981. document.getElementById('stopBtn').disabled = false;
  982. document.getElementById('stopBtnCompute').disabled = false;
  983. document.getElementById('stopBtnMonitor').disabled = false;
  984. currentSoftware = runningSoftware;
  985. startEventStream();
  986. } else {
  987. updateStatus('idle', '就绪 - 等待上传文件');
  988. }
  989. } catch (error) {
  990. console.error('检查状态失败:', error);
  991. }
  992. }
  993. // 更新状态指示器
  994. function updateStatus(status, message) {
  995. const indicator = document.getElementById('statusIndicator');
  996. const statusText = document.getElementById('statusText');
  997. const statusDot = indicator.querySelector('.status-dot');
  998. // 移除所有状态类
  999. indicator.classList.remove('status-running', 'status-stopped', 'status-idle');
  1000. statusDot.classList.remove('running-dot', 'stopped-dot', 'idle-dot');
  1001. // 添加新状态类
  1002. if (status === 'running') {
  1003. indicator.classList.add('status-running');
  1004. statusDot.classList.add('running-dot');
  1005. isRunning = true;
  1006. } else if (status === 'stopped') {
  1007. indicator.classList.add('status-stopped');
  1008. statusDot.classList.add('stopped-dot');
  1009. isRunning = false;
  1010. } else {
  1011. indicator.classList.add('status-idle');
  1012. statusDot.classList.add('idle-dot');
  1013. isRunning = false;
  1014. }
  1015. statusText.textContent = message;
  1016. }
  1017. </script>
  1018. </body>
  1019. </html>