# dialogs_history.py """操作历史对话框""" import tkinter as tk from tkinter import ttk, messagebox, filedialog from datetime import datetime from operation_history import OperationHistory, PartitionTableBackup, OperationStatus class OperationHistoryDialog: """操作历史对话框""" def __init__(self, parent): self.parent = parent self.history = OperationHistory() self.dialog = tk.Toplevel(parent) self.dialog.title("操作历史") self.dialog.geometry("900x600") self.dialog.transient(parent) self._center_window() self._create_widgets() self._load_history() def _center_window(self): self.dialog.update_idletasks() width = self.dialog.winfo_width() height = self.dialog.winfo_height() x = (self.dialog.winfo_screenwidth() // 2) - (width // 2) y = (self.dialog.winfo_screenheight() // 2) - (height // 2) self.dialog.geometry(f"{width}x{height}+{x}+{y}") def _create_widgets(self): main_frame = ttk.Frame(self.dialog, padding="10") main_frame.pack(fill=tk.BOTH, expand=True) # 标题 title_label = ttk.Label( main_frame, text="操作历史记录", font=("Arial", 14, "bold") ) title_label.pack(pady=(0, 10)) # 工具栏 toolbar = ttk.Frame(main_frame) toolbar.pack(fill=tk.X, pady=(0, 10)) ttk.Button(toolbar, text="刷新", command=self._load_history).pack(side=tk.LEFT, padx=5) ttk.Button(toolbar, text="导出", command=self._export_history).pack(side=tk.LEFT, padx=5) ttk.Button(toolbar, text="清理旧记录", command=self._clear_old).pack(side=tk.LEFT, padx=5) ttk.Button(toolbar, text="清空所有", command=self._clear_all).pack(side=tk.LEFT, padx=5) # 历史记录列表 list_frame = ttk.Frame(main_frame) list_frame.pack(fill=tk.BOTH, expand=True) columns = ("时间", "操作类型", "设备", "状态", "详情", "耗时") self.tree = ttk.Treeview(list_frame, columns=columns, show="headings") for col in columns: self.tree.heading(col, text=col) self.tree.column("时间", width=150) self.tree.column("操作类型", width=120) self.tree.column("设备", width=100) self.tree.column("状态", width=80) self.tree.column("详情", width=300) self.tree.column("耗时", width=80) # 滚动条 scrollbar_y = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview) scrollbar_x = ttk.Scrollbar(list_frame, orient=tk.HORIZONTAL, command=self.tree.xview) self.tree.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set) self.tree.grid(row=0, column=0, sticky="nsew") scrollbar_y.grid(row=0, column=1, sticky="ns") scrollbar_x.grid(row=1, column=0, sticky="ew") list_frame.grid_rowconfigure(0, weight=1) list_frame.grid_columnconfigure(0, weight=1) # 颜色标签 self.tree.tag_configure("success", foreground="green") self.tree.tag_configure("failed", foreground="red") self.tree.tag_configure("cancelled", foreground="gray") self.tree.tag_configure("pending", foreground="orange") # 详情区域 detail_frame = ttk.LabelFrame(main_frame, text="操作详情", padding="10") detail_frame.pack(fill=tk.X, pady=(10, 0)) self.detail_text = tk.Text(detail_frame, height=6, wrap=tk.WORD) self.detail_text.pack(fill=tk.BOTH, expand=True) # 绑定选择事件 self.tree.bind("<>", self._on_select) # 关闭按钮 ttk.Button(main_frame, text="关闭", command=self.dialog.destroy).pack(pady=10) def _load_history(self): """加载历史记录""" # 清空现有数据 for item in self.tree.get_children(): self.tree.delete(item) records = self.history.get_recent_records(limit=100) for record in records: # 格式化时间 try: dt = datetime.fromisoformat(record.timestamp) time_str = dt.strftime("%Y-%m-%d %H:%M:%S") except: time_str = record.timestamp # 格式化耗时 time_cost = "" if record.execution_time: if record.execution_time < 1: time_cost = f"{record.execution_time*1000:.0f}ms" else: time_cost = f"{record.execution_time:.1f}s" # 格式化详情 details = str(record.details)[:50] + "..." if len(str(record.details)) > 50 else str(record.details) values = ( time_str, record.operation_type, record.device_path, record.status, details, time_cost ) # 根据状态设置标签 tag = "" status_lower = record.status.lower() if "成功" in status_lower or "success" in status_lower: tag = "success" elif "失败" in status_lower or "failed" in status_lower: tag = "failed" elif "取消" in status_lower or "cancelled" in status_lower: tag = "cancelled" elif "待" in status_lower or "pending" in status_lower: tag = "pending" self.tree.insert("", tk.END, values=values, tags=(tag,)) def _on_select(self, event): """选择记录时显示详情""" selection = self.tree.selection() if not selection: return item = selection[0] index = self.tree.index(item) records = self.history.get_recent_records(limit=100) if index < len(records): record = records[index] detail_text = f"操作ID: {record.id}\n" detail_text += f"时间: {record.timestamp}\n" detail_text += f"类型: {record.operation_type}\n" detail_text += f"设备: {record.device_path}\n" detail_text += f"状态: {record.status}\n" if record.execution_time: detail_text += f"耗时: {record.execution_time:.2f}秒\n" detail_text += f"\n详情:\n{record.details}\n" if record.error_message: detail_text += f"\n错误信息:\n{record.error_message}\n" if record.rollback_data: detail_text += f"\n可撤销数据:\n{record.rollback_data}\n" self.detail_text.delete(1.0, tk.END) self.detail_text.insert(1.0, detail_text) def _export_history(self): """导出历史记录""" file_path = filedialog.asksaveasfilename( defaultextension=".json", filetypes=[("JSON files", "*.json"), ("All files", "*.*")], title="导出操作历史" ) if file_path: if self.history.export_history(file_path): messagebox.showinfo("成功", f"历史记录已导出到:\n{file_path}") else: messagebox.showerror("错误", "导出失败") def _clear_old(self): """清理旧记录""" result = messagebox.askyesno( "确认清理", "是否清理 30 天之前的操作记录?" ) if result: self.history.clear_history(days=30) self._load_history() messagebox.showinfo("成功", "旧记录已清理") def _clear_all(self): """清空所有记录""" result = messagebox.askyesno( "确认清空", "是否清空所有操作记录?\n此操作不可恢复!", icon=messagebox.WARNING ) if result: self.history.clear_history() self._load_history() messagebox.showinfo("成功", "所有记录已清空") class PartitionBackupDialog: """分区表备份管理对话框""" def __init__(self, parent): self.parent = parent self.backup_manager = PartitionTableBackup() self.dialog = tk.Toplevel(parent) self.dialog.title("分区表备份管理") self.dialog.geometry("800x500") self.dialog.transient(parent) self._center_window() self._create_widgets() self._load_backups() def _center_window(self): self.dialog.update_idletasks() width = self.dialog.winfo_width() height = self.dialog.winfo_height() x = (self.dialog.winfo_screenwidth() // 2) - (width // 2) y = (self.dialog.winfo_screenheight() // 2) - (height // 2) self.dialog.geometry(f"{width}x{height}+{x}+{y}") def _create_widgets(self): main_frame = ttk.Frame(self.dialog, padding="10") main_frame.pack(fill=tk.BOTH, expand=True) # 标题 title_label = ttk.Label( main_frame, text="分区表备份管理", font=("Arial", 14, "bold") ) title_label.pack(pady=(0, 10)) # 工具栏 toolbar = ttk.Frame(main_frame) toolbar.pack(fill=tk.X, pady=(0, 10)) ttk.Button(toolbar, text="刷新", command=self._load_backups).pack(side=tk.LEFT, padx=5) ttk.Button(toolbar, text="删除选中", command=self._delete_selected).pack(side=tk.LEFT, padx=5) # 备份列表 list_frame = ttk.Frame(main_frame) list_frame.pack(fill=tk.BOTH, expand=True) columns = ("设备", "备份时间", "文件大小", "文件路径") self.tree = ttk.Treeview(list_frame, columns=columns, show="headings") for col in columns: self.tree.heading(col, text=col) self.tree.column("设备", width=100) self.tree.column("备份时间", width=150) self.tree.column("文件大小", width=100) self.tree.column("文件路径", width=400) scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview) self.tree.configure(yscrollcommand=scrollbar.set) self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 详情和操作按钮 detail_frame = ttk.LabelFrame(main_frame, text="操作", padding="10") detail_frame.pack(fill=tk.X, pady=(10, 0)) ttk.Label(detail_frame, text="选中备份文件后可执行以下操作:").pack(anchor=tk.W) btn_frame = ttk.Frame(detail_frame) btn_frame.pack(fill=tk.X, pady=5) ttk.Button(btn_frame, text="查看内容", command=self._view_backup).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="恢复此备份", command=self._restore_backup).pack(side=tk.LEFT, padx=5) # 关闭按钮 ttk.Button(main_frame, text="关闭", command=self.dialog.destroy).pack(pady=10) def _load_backups(self): """加载备份列表""" for item in self.tree.get_children(): self.tree.delete(item) backups = self.backup_manager.list_backups() for backup in backups: size_str = self._format_size(backup['size']) values = ( backup['device'], backup['created'], size_str, backup['file'] ) self.tree.insert("", tk.END, values=values) def _format_size(self, size_bytes): """格式化文件大小""" if size_bytes < 1024: return f"{size_bytes} B" elif size_bytes < 1024 * 1024: return f"{size_bytes / 1024:.1f} KB" else: return f"{size_bytes / (1024 * 1024):.1f} MB" def _get_selected_backup(self): """获取选中的备份""" selection = self.tree.selection() if not selection: messagebox.showwarning("警告", "请先选择一个备份") return None item = selection[0] file_path = self.tree.item(item, "values")[3] return file_path def _view_backup(self): """查看备份内容""" file_path = self._get_selected_backup() if not file_path: return try: with open(file_path, 'r') as f: content = f.read() # 显示内容对话框 content_dialog = tk.Toplevel(self.dialog) content_dialog.title(f"备份内容 - {file_path}") content_dialog.geometry("600x400") text = tk.Text(content_dialog, wrap=tk.WORD, padx=10, pady=10) text.pack(fill=tk.BOTH, expand=True) text.insert(1.0, content) text.config(state=tk.DISABLED) ttk.Button(content_dialog, text="关闭", command=content_dialog.destroy).pack(pady=10) except Exception as e: messagebox.showerror("错误", f"无法读取备份文件:\n{e}") def _restore_backup(self): """恢复备份""" file_path = self._get_selected_backup() if not file_path: return # 解析设备名 filename = os.path.basename(file_path) device_name = filename.rsplit('_pt_', 1)[0] device_path = f"/dev/{device_name}" result = messagebox.askyesno( "确认恢复", f"是否将备份恢复到 {device_path}?\n\n" f"警告:这将覆盖当前的分区表!\n" f"请确保您了解此操作的后果。", icon=messagebox.WARNING ) if result: if self.backup_manager.restore_partition_table(device_path, file_path): messagebox.showinfo("成功", f"分区表已恢复到 {device_path}") else: messagebox.showerror("错误", "恢复失败") def _delete_selected(self): """删除选中的备份""" file_path = self._get_selected_backup() if not file_path: return result = messagebox.askyesno( "确认删除", f"是否删除备份文件?\n{file_path}" ) if result: if self.backup_manager.delete_backup(file_path): self._load_backups() messagebox.showinfo("成功", "备份已删除") else: messagebox.showerror("错误", "删除失败") class OperationQueueDialog: """操作队列对话框""" def __init__(self, parent, operation_queue): self.parent = parent self.queue = operation_queue self.dialog = tk.Toplevel(parent) self.dialog.title("操作队列") self.dialog.geometry("600x400") self.dialog.transient(parent) self._center_window() self._create_widgets() self._refresh_status() def _center_window(self): self.dialog.update_idletasks() width = self.dialog.winfo_width() height = self.dialog.winfo_height() x = (self.dialog.winfo_screenwidth() // 2) - (width // 2) y = (self.dialog.winfo_screenheight() // 2) - (height // 2) self.dialog.geometry(f"{width}x{height}+{x}+{y}") def _create_widgets(self): main_frame = ttk.Frame(self.dialog, padding="10") main_frame.pack(fill=tk.BOTH, expand=True) # 标题 title_label = ttk.Label( main_frame, text="待执行操作队列", font=("Arial", 14, "bold") ) title_label.pack(pady=(0, 10)) # 状态显示 self.status_label = ttk.Label(main_frame, text="队列状态: 空闲") self.status_label.pack(anchor=tk.W, pady=(0, 10)) # 操作列表 list_frame = ttk.Frame(main_frame) list_frame.pack(fill=tk.BOTH, expand=True) columns = ("类型", "设备", "状态") self.tree = ttk.Treeview(list_frame, columns=columns, show="headings") for col in columns: self.tree.heading(col, text=col) self.tree.column(col, width=150) scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview) self.tree.configure(yscrollcommand=scrollbar.set) self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 按钮 btn_frame = ttk.Frame(main_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button(btn_frame, text="开始执行", command=self._start_execution).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="取消当前", command=self._cancel_current).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="清空队列", command=self._clear_queue).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="刷新", command=self._refresh_status).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="关闭", command=self.dialog.destroy).pack(side=tk.RIGHT, padx=5) # 注册回调 self.queue.register_callback('on_start', self._on_operation_start) self.queue.register_callback('on_complete', self._on_operation_complete) self.queue.register_callback('on_error', self._on_operation_error) def _refresh_status(self): """刷新队列状态""" status = self.queue.get_queue_status() if status['running']: self.status_label.configure(text=f"队列状态: 执行中 ({status['pending_count']} 个待执行)") else: self.status_label.configure(text=f"队列状态: 空闲 ({status['pending_count']} 个待执行)") # 刷新列表 for item in self.tree.get_children(): self.tree.delete(item) pending = self.queue.get_pending_operations() for op in pending: values = ( op['type'].value if hasattr(op['type'], 'value') else str(op['type']), op['device_path'], op['status'].value if hasattr(op['status'], 'value') else str(op['status']) ) self.tree.insert("", tk.END, values=values) def _start_execution(self): """开始执行队列""" self.queue.start_execution() self._refresh_status() def _cancel_current(self): """取消当前操作""" self.queue.cancel_current() self._refresh_status() def _clear_queue(self): """清空队列""" result = messagebox.askyesno("确认", "是否清空所有待执行操作?") if result: self.queue.clear_queue() self._refresh_status() def _on_operation_start(self, operation): """操作开始回调""" self._refresh_status() def _on_operation_complete(self, operation, success): """操作完成回调""" self._refresh_status() def _on_operation_error(self, operation, error): """操作错误回调""" self._refresh_status() messagebox.showerror("操作错误", f"操作失败:\n{error}")