|
19 | 19 | import sys |
20 | 20 | import re |
21 | 21 | import subprocess |
| 22 | +import json |
22 | 23 | from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, |
23 | 24 | QFormLayout, QLabel, QLineEdit, QPushButton, QTableWidget, QTableWidgetItem, |
24 | 25 | QHeaderView, QMessageBox, QSystemTrayIcon, QMenu, QToolBar, QStatusBar, |
@@ -408,7 +409,43 @@ def test_command(self): |
408 | 409 | msg.setText(content) |
409 | 410 | msg.setDetailedText(content) |
410 | 411 | msg.setIcon(QMessageBox.Icon.Information if process.returncode == 0 else QMessageBox.Icon.Warning) |
411 | | - msg.exec() |
| 412 | + |
| 413 | + # 添加打开日志目录和脚本目录的按钮 |
| 414 | + open_logs_button = msg.addButton('打开日志目录', QMessageBox.ButtonRole.ActionRole) |
| 415 | + open_scripts_button = msg.addButton('打开脚本目录', QMessageBox.ButtonRole.ActionRole) |
| 416 | + msg.addButton(QMessageBox.StandardButton.Ok) |
| 417 | + |
| 418 | + # 设置弹窗样式和布局 |
| 419 | + msg.setStyleSheet(""" |
| 420 | + QMessageBox { |
| 421 | + min-width: 600px; |
| 422 | + } |
| 423 | + QLabel { |
| 424 | + min-width: 500px; |
| 425 | + font-size: 13px; |
| 426 | + padding: 10px; |
| 427 | + } |
| 428 | + QMessageBox QLabel#qt_msgbox_label { |
| 429 | + min-height: 40px; |
| 430 | + } |
| 431 | + QMessageBox QLabel#qt_msgboxex_icon_label { |
| 432 | + min-width: 100px; |
| 433 | + padding-right: 20px; |
| 434 | + } |
| 435 | + QPushButton { |
| 436 | + min-width: 100px; |
| 437 | + padding: 6px; |
| 438 | + } |
| 439 | + """) |
| 440 | + |
| 441 | + result = msg.exec() |
| 442 | + |
| 443 | + # 处理按钮点击事件 |
| 444 | + clicked_button = msg.clickedButton() |
| 445 | + if clicked_button == open_logs_button: |
| 446 | + self.parent().open_logs_directory() |
| 447 | + elif clicked_button == open_scripts_button: |
| 448 | + self.parent().open_scripts_directory() |
412 | 449 |
|
413 | 450 | # 清理临时文件 |
414 | 451 | os.unlink(temp_file.name) |
@@ -893,6 +930,22 @@ def setup_ui(self): |
893 | 930 |
|
894 | 931 | # 帮助菜单 |
895 | 932 | help_menu = menubar.addMenu('帮助') |
| 933 | + help_menu = menubar.addMenu('帮助') |
| 934 | + |
| 935 | + # 添加导入/导出菜单 |
| 936 | + import_export_menu = menubar.addMenu('导入/导出') |
| 937 | + self.export_action = QAction('导出任务', self) |
| 938 | + self.export_action.setShortcut('Ctrl+Shift+E') |
| 939 | + self.export_action.setToolTip('将当前任务配置导出到文件') |
| 940 | + self.export_action.triggered.connect(self.export_tasks) |
| 941 | + import_export_menu.addAction(self.export_action) |
| 942 | + |
| 943 | + self.import_action = QAction('导入任务', self) |
| 944 | + self.import_action.setShortcut('Ctrl+Shift+I') |
| 945 | + self.import_action.setToolTip('从文件导入任务配置') |
| 946 | + self.import_action.triggered.connect(self.import_tasks) |
| 947 | + import_export_menu.addAction(self.import_action) |
| 948 | + |
896 | 949 | about_action = QAction('关于', self) |
897 | 950 | about_action.triggered.connect(self.show_about) |
898 | 951 | help_menu.addAction(about_action) |
@@ -976,54 +1029,7 @@ def setup_ui(self): |
976 | 1029 | self.refresh_action.triggered.connect(self.refresh_jobs) |
977 | 1030 | self.view_log_action.triggered.connect(self.view_log) |
978 | 1031 |
|
979 | | - def test_command(self): |
980 | | - """测试命令功能""" |
981 | | - command = self.command_edit.toPlainText().strip() |
982 | | - if not command: |
983 | | - QMessageBox.warning(self, '警告', '请输入要测试的命令') |
984 | | - return |
985 | | - |
986 | | - try: |
987 | | - # 创建一个临时的日志文件来捕获命令输出 |
988 | | - import tempfile |
989 | | - import subprocess |
990 | | - |
991 | | - with tempfile.NamedTemporaryFile(mode='w+', delete=False) as temp_file: |
992 | | - process = subprocess.Popen( |
993 | | - command, |
994 | | - shell=True, |
995 | | - stdout=subprocess.PIPE, |
996 | | - stderr=subprocess.PIPE, |
997 | | - text=True |
998 | | - ) |
999 | | - |
1000 | | - stdout, stderr = process.communicate(timeout=10) |
1001 | | - |
1002 | | - if process.returncode == 0: |
1003 | | - temp_file.write(f"命令执行成功!\n\n输出:\n{stdout}") |
1004 | | - title = '测试成功' |
1005 | | - else: |
1006 | | - temp_file.write(f"命令执行失败!\n\n错误输出:\n{stderr}\n\n标准输出:\n{stdout}") |
1007 | | - title = '测试失败' |
1008 | | - |
1009 | | - # 显示结果对话框 |
1010 | | - with open(temp_file.name, 'r') as f: |
1011 | | - content = f.read() |
1012 | | - |
1013 | | - msg = QMessageBox(self) |
1014 | | - msg.setWindowTitle(title) |
1015 | | - msg.setText(content) |
1016 | | - msg.setDetailedText(content) |
1017 | | - msg.setIcon(QMessageBox.Icon.Information if process.returncode == 0 else QMessageBox.Icon.Warning) |
1018 | | - msg.exec() |
1019 | | - |
1020 | | - # 清理临时文件 |
1021 | | - os.unlink(temp_file.name) |
1022 | | - |
1023 | | - except subprocess.TimeoutExpired: |
1024 | | - QMessageBox.critical(self, '错误', '命令执行超时') |
1025 | | - except Exception as e: |
1026 | | - QMessageBox.critical(self, '错误', f'执行命令时发生错误:{str(e)}') |
| 1032 | + |
1027 | 1033 |
|
1028 | 1034 | def add_job(self): |
1029 | 1035 | dialog = JobDialog(self) |
@@ -1163,7 +1169,7 @@ def create_script_file(self, name, command): |
1163 | 1169 | script_path = self.get_script_path(name) |
1164 | 1170 | log_path = self.get_log_path(name) |
1165 | 1171 |
|
1166 | | - script_content = f"#!/bin/bash\n\n# Task: {name}\n# Created: {datetime.datetime.now()}\n\n## {command}\n\n# 设置错误处理\nset -e\n\n# 添加分隔符\necho \"\n----------------------------------------\n执行时间: $(date '+%Y-%m-%d %H:%M:%S')\n----------------------------------------\n\" | tee -a \"{log_path}\"\n\n# 执行命令并记录日志\n{{ {command}; }} 2>&1 | tee -a \"{log_path}\" || {{\n echo \"[$(date '+%Y-%m-%d %H:%M:%S')] 执行失败\" | tee -a \"{log_path}\"\n exit 1\n}}\n\necho \"[$(date '+%Y-%m-%d %H:%M:%S')] 执行成功\" | tee -a \"{log_path}\"\n" |
| 1172 | + script_content = f"#!/bin/bash\n\n# Task: {name}\n# Created: {datetime.datetime.now()}\n\n## {command}\n\n# 设置错误处理\nset -e\n\n# 设置工作目录\ncd $(dirname \"$0\")\n\n# 添加分隔符\necho \"\n----------------------------------------\n执行时间: $(date '+%Y-%m-%d %H:%M:%S')\n----------------------------------------\n\" | tee -a \"{log_path}\"\n\n# 执行命令并记录日志\n{{ {command}; }} 2>&1 | tee -a \"{log_path}\" || {{\n echo \"[$(date '+%Y-%m-%d %H:%M:%S')] 执行失败\" | tee -a \"{log_path}\"\n exit 1\n}}\n\necho \"[$(date '+%Y-%m-%d %H:%M:%S')] 执行成功\" | tee -a \"{log_path}\"\n" |
1167 | 1173 |
|
1168 | 1174 | try: |
1169 | 1175 | with open(script_path, 'w') as f: |
@@ -1440,6 +1446,100 @@ def show_about(self): |
1440 | 1446 | '</div>' |
1441 | 1447 | QMessageBox.about(self, '关于', about_text) |
1442 | 1448 |
|
| 1449 | + def export_tasks(self): |
| 1450 | + """导出任务配置到文件""" |
| 1451 | + file_path, _ = QFileDialog.getSaveFileName( |
| 1452 | + self, |
| 1453 | + '导出任务', |
| 1454 | + '', |
| 1455 | + 'JSON文件 (*.json);;所有文件 (*)' |
| 1456 | + ) |
| 1457 | + if not file_path: |
| 1458 | + return |
| 1459 | + |
| 1460 | + try: |
| 1461 | + tasks = [] |
| 1462 | + for row in range(self.table.rowCount()): |
| 1463 | + task = { |
| 1464 | + 'name': self.table.item(row, 0).text(), |
| 1465 | + 'command': self.table.item(row, 1).text(), |
| 1466 | + 'schedule': self.table.item(row, 2).text(), |
| 1467 | + 'enabled': self.table.item(row, 3).text() == '是' |
| 1468 | + } |
| 1469 | + tasks.append(task) |
| 1470 | + |
| 1471 | + with open(file_path, 'w', encoding='utf-8') as f: |
| 1472 | + json.dump(tasks, f, ensure_ascii=False, indent=2) |
| 1473 | + |
| 1474 | + QMessageBox.information(self, '成功', '任务配置已成功导出!') |
| 1475 | + except Exception as e: |
| 1476 | + QMessageBox.critical(self, '错误', f'导出任务配置时发生错误:{str(e)}') |
| 1477 | + |
| 1478 | + def import_tasks(self): |
| 1479 | + """从文件导入任务配置""" |
| 1480 | + file_path, _ = QFileDialog.getOpenFileName( |
| 1481 | + self, |
| 1482 | + '导入任务', |
| 1483 | + '', |
| 1484 | + 'JSON文件 (*.json);;所有文件 (*)' |
| 1485 | + ) |
| 1486 | + if not file_path: |
| 1487 | + return |
| 1488 | + |
| 1489 | + try: |
| 1490 | + with open(file_path, 'r', encoding='utf-8') as f: |
| 1491 | + tasks = json.load(f) |
| 1492 | + |
| 1493 | + if not isinstance(tasks, list): |
| 1494 | + raise ValueError('无效的任务配置文件格式') |
| 1495 | + |
| 1496 | + for task in tasks: |
| 1497 | + if not all(key in task for key in ['name', 'command', 'schedule', 'enabled']): |
| 1498 | + raise ValueError('任务配置缺少必要字段') |
| 1499 | + |
| 1500 | + # 添加任务到表格 |
| 1501 | + row = self.table.rowCount() |
| 1502 | + self.table.insertRow(row) |
| 1503 | + self.table.setItem(row, 0, QTableWidgetItem(task['name'])) |
| 1504 | + self.table.setItem(row, 1, QTableWidgetItem(task['command'])) |
| 1505 | + self.table.setItem(row, 2, QTableWidgetItem(task['schedule'])) |
| 1506 | + self.table.setItem(row, 3, QTableWidgetItem('是' if task['enabled'] else '否')) |
| 1507 | + |
| 1508 | + QMessageBox.information(self, '成功', '任务配置已成功导入!') |
| 1509 | + self.save_tasks() # 保存导入的任务 |
| 1510 | + except Exception as e: |
| 1511 | + QMessageBox.critical(self, '错误', f'导入任务配置时发生错误:{str(e)}') |
| 1512 | + |
| 1513 | + def save_tasks(self): |
| 1514 | + """将表格中的任务保存到 crontab""" |
| 1515 | + try: |
| 1516 | + # 清空现有的 crontab 任务 |
| 1517 | + self.cron.remove_all() |
| 1518 | + |
| 1519 | + # 遍历表格中的所有任务 |
| 1520 | + for row in range(self.table.rowCount()): |
| 1521 | + name = self.table.item(row, 0).text() |
| 1522 | + command = self.table.item(row, 1).text() |
| 1523 | + schedule = self.table.item(row, 2).text() |
| 1524 | + enabled = self.table.item(row, 3).text() == '⬤' and self.table.item(row, 3).foreground().color().name() == '#2e7d32' |
| 1525 | + |
| 1526 | + # 创建执行脚本 |
| 1527 | + script_path = self.create_script_file(name, command) |
| 1528 | + |
| 1529 | + # 添加到 crontab |
| 1530 | + job = self.cron.new(command=script_path, comment=name) |
| 1531 | + job.setall(schedule) |
| 1532 | + |
| 1533 | + # 设置任务状态 |
| 1534 | + if not enabled: |
| 1535 | + job.enable(False) |
| 1536 | + |
| 1537 | + # 写入 crontab 文件 |
| 1538 | + self.cron.write() |
| 1539 | + self.refresh_jobs() |
| 1540 | + except Exception as e: |
| 1541 | + QMessageBox.critical(self, '错误', f'保存任务配置时发生错误:{str(e)}') |
| 1542 | + |
1443 | 1543 | def toggle_stay_on_top(self): |
1444 | 1544 | flags = self.windowFlags() |
1445 | 1545 | if self.stay_on_top_action.isChecked(): |
|
0 commit comments