This commit is contained in:
zj
2026-02-09 17:59:50 +08:00
parent 1a3a4746a3
commit 0112e4d3b1
11 changed files with 3223 additions and 13 deletions

531
dialogs_history.py Normal file
View File

@@ -0,0 +1,531 @@
# 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("<<TreeviewSelect>>", 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}")