构建
This commit is contained in:
531
dialogs_history.py
Normal file
531
dialogs_history.py
Normal 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}")
|
||||
Reference in New Issue
Block a user