app.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. // 应用程序入口模块
  2. document.addEventListener('DOMContentLoaded', function() {
  3. // console.log('DOM 加载完成,初始化模块...');
  4. // 启动应用程序
  5. core.initApp();
  6. // 在核心应用初始化后,再初始化其他模块
  7. initializeModules();
  8. // console.log('模块初始化已启动');
  9. });
  10. // 初始化所有模块
  11. async function initializeModules() {
  12. // console.log('开始初始化所有模块...');
  13. try {
  14. // 初始化核心模块
  15. // console.log('正在初始化核心模块...');
  16. if (typeof core !== 'undefined') {
  17. // core.init() 已经在core.initApp()中调用,这里不再重复调用
  18. // console.log('核心模块初始化完成');
  19. } else {
  20. // console.error('核心模块未定义');
  21. }
  22. // 初始化认证模块
  23. // console.log('正在初始化认证模块...');
  24. if (typeof auth !== 'undefined') {
  25. await auth.init();
  26. // console.log('认证模块初始化完成');
  27. } else {
  28. // console.error('认证模块未定义');
  29. }
  30. // 初始化用户中心
  31. // console.log('正在初始化用户中心...');
  32. if (typeof userCenter !== 'undefined') {
  33. await userCenter.init();
  34. // console.log('用户中心初始化完成');
  35. } else {
  36. // console.error('用户中心未定义');
  37. }
  38. // 初始化菜单管理
  39. // console.log('正在初始化菜单管理...');
  40. if (typeof menuManager !== 'undefined') {
  41. await menuManager.init();
  42. // console.log('菜单管理初始化完成');
  43. } else {
  44. // console.error('菜单管理未定义');
  45. }
  46. // 初始化文档管理
  47. // console.log('正在初始化文档管理...');
  48. if (typeof documentManager !== 'undefined') {
  49. await documentManager.init();
  50. // console.log('文档管理初始化完成');
  51. } else {
  52. // console.error('文档管理未定义');
  53. }
  54. // 初始化Docker管理
  55. // console.log('正在初始化Docker管理...');
  56. if (typeof dockerManager !== 'undefined') {
  57. await dockerManager.init();
  58. // console.log('Docker管理初始化完成');
  59. } else {
  60. // console.error('Docker管理未定义');
  61. }
  62. // 初始化系统状态
  63. // console.log('正在初始化系统状态...');
  64. if (typeof systemStatus !== 'undefined') {
  65. if (typeof systemStatus.initDashboard === 'function') {
  66. await systemStatus.initDashboard();
  67. // console.log('系统状态初始化完成');
  68. } else {
  69. // console.error('systemStatus.initDashboard 函数未定义!');
  70. }
  71. } else {
  72. // console.error('系统状态未定义');
  73. }
  74. // 初始化网络测试
  75. // console.log('正在初始化网络测试...');
  76. if (typeof networkTest !== 'undefined') {
  77. await networkTest.init();
  78. // console.log('网络测试初始化完成');
  79. } else {
  80. // console.error('网络测试未定义');
  81. }
  82. // 加载监控配置
  83. await loadMonitoringConfig();
  84. // 加载已停止的容器列表
  85. refreshStoppedContainers();
  86. // 显示默认页面 - 使用core中的showSection函数
  87. core.showSection('dashboard');
  88. // console.log('所有模块初始化完成');
  89. } catch (error) {
  90. // console.error('初始化模块时发生错误:', error);
  91. // 尝试使用 core.showAlert,如果 core 本身加载失败则用 console.error
  92. if (typeof core !== 'undefined' && core.showAlert) {
  93. core.showAlert('初始化失败: ' + error.message, 'error');
  94. } else {
  95. // console.error('核心模块无法加载,无法显示警告弹窗');
  96. }
  97. }
  98. }
  99. // 监控配置相关函数
  100. function loadMonitoringConfig() {
  101. // console.log('正在加载监控配置...');
  102. fetch('/api/monitoring-config')
  103. .then(response => {
  104. // console.log('监控配置API响应:', response.status, response.statusText);
  105. if (!response.ok) {
  106. throw new Error(`HTTP状态错误 ${response.status}: ${response.statusText}`);
  107. }
  108. return response.json();
  109. })
  110. .then(config => {
  111. // console.log('获取到监控配置:', config);
  112. // 填充表单
  113. document.getElementById('notificationType').value = config.notificationType || 'wechat';
  114. document.getElementById('webhookUrl').value = config.webhookUrl || '';
  115. document.getElementById('telegramToken').value = config.telegramToken || '';
  116. document.getElementById('telegramChatId').value = config.telegramChatId || '';
  117. document.getElementById('monitorInterval').value = config.monitorInterval || 60;
  118. // 显示或隐藏相应的字段
  119. toggleNotificationFields();
  120. // 更新监控状态
  121. document.getElementById('monitoringStatus').textContent =
  122. config.isEnabled ? '已启用' : '已禁用';
  123. document.getElementById('monitoringStatus').style.color =
  124. config.isEnabled ? '#4CAF50' : '#F44336';
  125. document.getElementById('toggleMonitoringBtn').textContent =
  126. config.isEnabled ? '禁用监控' : '启用监控';
  127. // 添加通知类型选择变化的监听器
  128. const notificationTypeSelect = document.getElementById('notificationType');
  129. notificationTypeSelect.addEventListener('change', toggleNotificationFields);
  130. // console.log('监控配置加载完成');
  131. })
  132. .catch(error => {
  133. // console.error('加载监控配置失败:', error);
  134. // 使用安全的方式调用core.showAlert
  135. if (typeof core !== 'undefined' && core && typeof core.showAlert === 'function') {
  136. core.showAlert('加载监控配置失败: ' + error.message, 'error');
  137. } else {
  138. // 如果core未定义,使用alert作为备选
  139. alert('加载监控配置失败: ' + error.message);
  140. }
  141. });
  142. }
  143. function toggleNotificationFields() {
  144. const type = document.getElementById('notificationType').value;
  145. if (type === 'wechat') {
  146. document.getElementById('wechatFields').style.display = 'block';
  147. document.getElementById('telegramFields').style.display = 'none';
  148. } else {
  149. document.getElementById('wechatFields').style.display = 'none';
  150. document.getElementById('telegramFields').style.display = 'block';
  151. }
  152. }
  153. function testNotification() {
  154. const notificationType = document.getElementById('notificationType').value;
  155. const webhookUrl = document.getElementById('webhookUrl').value;
  156. const telegramToken = document.getElementById('telegramToken').value;
  157. const telegramChatId = document.getElementById('telegramChatId').value;
  158. // 验证输入
  159. if (notificationType === 'wechat' && !webhookUrl) {
  160. Swal.fire({
  161. icon: 'error',
  162. title: '验证失败',
  163. text: '请输入企业微信机器人 Webhook URL',
  164. confirmButtonText: '确定'
  165. });
  166. return;
  167. }
  168. if (notificationType === 'telegram' && (!telegramToken || !telegramChatId)) {
  169. Swal.fire({
  170. icon: 'error',
  171. title: '验证失败',
  172. text: '请输入 Telegram Bot Token 和 Chat ID',
  173. confirmButtonText: '确定'
  174. });
  175. return;
  176. }
  177. // 显示处理中的状态
  178. Swal.fire({
  179. title: '发送中...',
  180. html: '<i class="fas fa-spinner fa-spin"></i> 正在发送测试通知',
  181. showConfirmButton: false,
  182. allowOutsideClick: false,
  183. willOpen: () => {
  184. Swal.showLoading();
  185. }
  186. });
  187. fetch('/api/test-notification', {
  188. method: 'POST',
  189. headers: {
  190. 'Content-Type': 'application/json'
  191. },
  192. body: JSON.stringify({
  193. notificationType,
  194. webhookUrl,
  195. telegramToken,
  196. telegramChatId
  197. })
  198. })
  199. .then(response => {
  200. if (!response.ok) throw new Error('测试通知失败');
  201. return response.json();
  202. })
  203. .then(() => {
  204. Swal.fire({
  205. icon: 'success',
  206. title: '发送成功',
  207. text: '测试通知已发送,请检查您的接收设备',
  208. timer: 2000,
  209. showConfirmButton: false
  210. });
  211. })
  212. .catch(error => {
  213. // console.error('测试通知失败:', error);
  214. Swal.fire({
  215. icon: 'error',
  216. title: '发送失败',
  217. text: '测试通知发送失败: ' + error.message,
  218. confirmButtonText: '确定'
  219. });
  220. });
  221. }
  222. function saveMonitoringConfig() {
  223. const notificationType = document.getElementById('notificationType').value;
  224. const webhookUrl = document.getElementById('webhookUrl').value;
  225. const telegramToken = document.getElementById('telegramToken').value;
  226. const telegramChatId = document.getElementById('telegramChatId').value;
  227. const monitorInterval = document.getElementById('monitorInterval').value;
  228. // 验证输入
  229. if (notificationType === 'wechat' && !webhookUrl) {
  230. Swal.fire({
  231. icon: 'error',
  232. title: '验证失败',
  233. text: '请输入企业微信机器人 Webhook URL',
  234. confirmButtonText: '确定'
  235. });
  236. return;
  237. }
  238. if (notificationType === 'telegram' && (!telegramToken || !telegramChatId)) {
  239. Swal.fire({
  240. icon: 'error',
  241. title: '验证失败',
  242. text: '请输入 Telegram Bot Token 和 Chat ID',
  243. confirmButtonText: '确定'
  244. });
  245. return;
  246. }
  247. // 显示保存中的状态
  248. Swal.fire({
  249. title: '保存中...',
  250. html: '<i class="fas fa-spinner fa-spin"></i> 正在保存监控配置',
  251. showConfirmButton: false,
  252. allowOutsideClick: false,
  253. willOpen: () => {
  254. Swal.showLoading();
  255. }
  256. });
  257. fetch('/api/monitoring-config', {
  258. method: 'POST',
  259. headers: {
  260. 'Content-Type': 'application/json'
  261. },
  262. body: JSON.stringify({
  263. notificationType,
  264. webhookUrl,
  265. telegramToken,
  266. telegramChatId,
  267. monitorInterval,
  268. isEnabled: document.getElementById('monitoringStatus').textContent === '已启用'
  269. })
  270. })
  271. .then(response => {
  272. if (!response.ok) throw new Error('保存配置失败');
  273. return response.json();
  274. })
  275. .then(() => {
  276. Swal.fire({
  277. icon: 'success',
  278. title: '保存成功',
  279. text: '监控配置已成功保存',
  280. timer: 2000,
  281. showConfirmButton: false
  282. });
  283. loadMonitoringConfig();
  284. })
  285. .catch(error => {
  286. console.error('保存监控配置失败:', error);
  287. Swal.fire({
  288. icon: 'error',
  289. title: '保存失败',
  290. text: '保存监控配置失败: ' + error.message,
  291. confirmButtonText: '确定'
  292. });
  293. });
  294. }
  295. function toggleMonitoring() {
  296. const isCurrentlyEnabled = document.getElementById('monitoringStatus').textContent === '已启用';
  297. const newStatus = !isCurrentlyEnabled ? '启用' : '禁用';
  298. Swal.fire({
  299. title: `确认${newStatus}监控?`,
  300. html: `
  301. <div style="text-align: left; margin-top: 10px;">
  302. <p>您确定要<strong>${newStatus}</strong>容器监控系统吗?</p>
  303. ${isCurrentlyEnabled ?
  304. '<p><i class="fas fa-exclamation-triangle" style="color: #f39c12;"></i> 禁用后,系统将停止监控容器状态并停止发送通知。</p>' :
  305. '<p><i class="fas fa-info-circle" style="color: #3498db;"></i> 启用后,系统将开始定期检查容器状态并在发现异常时发送通知。</p>'}
  306. </div>
  307. `,
  308. icon: 'question',
  309. showCancelButton: true,
  310. confirmButtonColor: isCurrentlyEnabled ? '#d33' : '#3085d6',
  311. cancelButtonColor: '#6c757d',
  312. confirmButtonText: `确认${newStatus}`,
  313. cancelButtonText: '取消'
  314. }).then((result) => {
  315. if (result.isConfirmed) {
  316. // 显示处理中状态
  317. Swal.fire({
  318. title: '处理中...',
  319. html: `<i class="fas fa-spinner fa-spin"></i> 正在${newStatus}监控`,
  320. showConfirmButton: false,
  321. allowOutsideClick: false,
  322. willOpen: () => {
  323. Swal.showLoading();
  324. }
  325. });
  326. fetch('/api/toggle-monitoring', {
  327. method: 'POST',
  328. headers: {
  329. 'Content-Type': 'application/json'
  330. },
  331. body: JSON.stringify({
  332. isEnabled: !isCurrentlyEnabled
  333. })
  334. })
  335. .then(response => {
  336. if (!response.ok) throw new Error('切换监控状态失败');
  337. return response.json();
  338. })
  339. .then(() => {
  340. loadMonitoringConfig();
  341. Swal.fire({
  342. icon: 'success',
  343. title: `${newStatus}成功`,
  344. text: `监控已成功${newStatus}`,
  345. timer: 2000,
  346. showConfirmButton: false
  347. });
  348. })
  349. .catch(error => {
  350. console.error('切换监控状态失败:', error);
  351. Swal.fire({
  352. icon: 'error',
  353. title: `${newStatus}失败`,
  354. text: '切换监控状态失败: ' + error.message,
  355. confirmButtonText: '确定'
  356. });
  357. });
  358. }
  359. });
  360. }
  361. function refreshStoppedContainers() {
  362. fetch('/api/stopped-containers')
  363. .then(response => {
  364. if (!response.ok) throw new Error('获取已停止容器列表失败');
  365. // 保存原始响应文本用于调试
  366. return response.text().then(text => {
  367. try {
  368. // 尝试解析为JSON
  369. const data = JSON.parse(text);
  370. // 打印原始响应
  371. console.log('原始响应:', text);
  372. console.log('解析后对象:', data);
  373. // 打印镜像字段
  374. if (Array.isArray(data)) {
  375. data.forEach(container => {
  376. console.log('容器镜像字段:', container.image,
  377. '类型:', typeof container.image,
  378. 'JSON字符串:', JSON.stringify(container));
  379. });
  380. }
  381. return data;
  382. } catch (e) {
  383. console.error('解析JSON失败:', e, '原始文本:', text);
  384. throw new Error('解析响应失败');
  385. }
  386. });
  387. })
  388. .then(containers => {
  389. // 添加调试信息
  390. console.log('已停止的容器数据:', JSON.stringify(containers, null, 2));
  391. const tbody = document.getElementById('stoppedContainersBody');
  392. tbody.innerHTML = '';
  393. if (containers.length === 0) {
  394. tbody.innerHTML = '<tr><td colspan="4" style="text-align: center;">没有已停止的容器</td></tr>';
  395. return;
  396. }
  397. containers.forEach(container => {
  398. // 调试单个容器数据
  399. console.log('容器数据:', container.id, container.name,
  400. '镜像:', container.image,
  401. '状态:', container.status);
  402. const row = `
  403. <tr>
  404. <td>${container.id}</td>
  405. <td>${container.name}</td>
  406. <td>${container.image ? container.image : '未知'}</td>
  407. <td>${container.status}</td>
  408. </tr>
  409. `;
  410. tbody.innerHTML += row;
  411. });
  412. })
  413. .catch(error => {
  414. console.error('获取已停止容器列表失败:', error);
  415. document.getElementById('stoppedContainersBody').innerHTML =
  416. '<tr><td colspan="4" style="text-align: center; color: red;">获取已停止容器列表失败</td></tr>';
  417. });
  418. }
  419. // 保存配置函数
  420. function saveConfig(configData) {
  421. core.showLoading();
  422. fetch('/api/config', {
  423. method: 'POST',
  424. headers: {
  425. 'Content-Type': 'application/json'
  426. },
  427. body: JSON.stringify(configData)
  428. })
  429. .then(response => {
  430. if (!response.ok) {
  431. return response.text().then(text => {
  432. throw new Error(`保存配置失败: ${text || response.statusText || response.status}`);
  433. });
  434. }
  435. return response.json();
  436. })
  437. .then(() => {
  438. core.showAlert('配置已保存', 'success');
  439. // 如果更新了菜单,重新加载菜单项
  440. if (configData.menuItems) {
  441. menuManager.loadMenuItems();
  442. }
  443. // 重新加载系统配置
  444. core.loadSystemConfig();
  445. })
  446. .catch(error => {
  447. console.error('保存配置失败:', error);
  448. core.showAlert('保存配置失败: ' + error.message, 'error');
  449. })
  450. .finally(() => {
  451. core.hideLoading();
  452. });
  453. }
  454. // 验证输入并保存配置
  455. function validateAndSaveConfig(type) {
  456. if (type === 'logo') {
  457. const logoUrl = document.getElementById('logoUrl').value.trim();
  458. if (!logoUrl) {
  459. Swal.fire({
  460. icon: 'error',
  461. title: '输入错误',
  462. text: 'Logo URL不能为空!',
  463. confirmButtonText: '确定'
  464. });
  465. return;
  466. }
  467. saveConfig({logo: logoUrl});
  468. } else if (type === 'proxy') {
  469. const proxyDomain = document.getElementById('proxyDomain').value.trim();
  470. if (!proxyDomain) {
  471. Swal.fire({
  472. icon: 'error',
  473. title: '输入错误',
  474. text: 'Docker镜像代理地址不能为空,这是必填项!',
  475. confirmButtonText: '确定'
  476. });
  477. return;
  478. }
  479. saveConfig({proxyDomain: proxyDomain});
  480. }
  481. }
  482. // 加载基本配置
  483. function loadBasicConfig() {
  484. fetch('/api/config')
  485. .then(response => {
  486. if (!response.ok) throw new Error('加载配置失败');
  487. return response.json();
  488. })
  489. .then(config => {
  490. // 填充Logo URL
  491. if (document.getElementById('logoUrl')) {
  492. document.getElementById('logoUrl').value = config.logo || '';
  493. }
  494. // 填充代理域名
  495. if (document.getElementById('proxyDomain')) {
  496. document.getElementById('proxyDomain').value = config.proxyDomain || '';
  497. }
  498. console.log('基本配置已加载');
  499. })
  500. .catch(error => {
  501. console.error('加载基本配置失败:', error);
  502. });
  503. }
  504. // 导出需要的函数到window.app对象
  505. window.app = {
  506. saveMonitoringConfig,
  507. testNotification,
  508. toggleMonitoring,
  509. toggleNotificationFields,
  510. refreshStoppedContainers,
  511. saveConfig,
  512. loadBasicConfig,
  513. validateAndSaveConfig
  514. };