Files
diskmanager2/dialogs_history.py
2026-02-09 17:59:50 +08:00

532 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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}")