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

251 lines
9.3 KiB
Python

# dialogs_undo.py
"""撤销功能对话框"""
import tkinter as tk
from tkinter import ttk, messagebox
from operation_history import OperationHistory, OperationStatus, OperationType
from dialogs_history import PartitionBackupDialog
class UndoDialog:
"""撤销操作对话框"""
def __init__(self, parent, history, partition_backup):
self.parent = parent
self.history = history
self.partition_backup = partition_backup
self.result = False
self.dialog = tk.Toplevel(parent)
self.dialog.title("撤销操作")
self.dialog.geometry("700x500")
self.dialog.transient(parent)
self._center_window()
self._create_widgets()
self._load_undoable_operations()
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="15")
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))
# 说明
info_label = ttk.Label(
main_frame,
text="以下操作可以通过分区表备份进行回滚。选择操作查看详情并撤销。",
wraplength=600
)
info_label.pack(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("时间", width=150)
self.tree.column("操作类型", width=150)
self.tree.column("设备", width=150)
self.tree.column("状态", width=100)
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)
# 绑定选择事件
self.tree.bind("<<TreeviewSelect>>", self._on_select)
# 详情区域
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=8, wrap=tk.WORD)
self.detail_text.pack(fill=tk.BOTH, expand=True)
self.detail_text.config(state=tk.DISABLED)
# 按钮
btn_frame = ttk.Frame(main_frame)
btn_frame.pack(fill=tk.X, pady=10)
ttk.Button(btn_frame, text="关闭", command=self.dialog.destroy).pack(side=tk.RIGHT, padx=5)
ttk.Button(btn_frame, text="刷新", command=self._load_undoable_operations).pack(side=tk.RIGHT, padx=5)
self.undo_btn = ttk.Button(btn_frame, text="撤销选中操作", command=self._undo_selected, state=tk.DISABLED)
self.undo_btn.pack(side=tk.RIGHT, padx=5)
self.selected_record = None
def _load_undoable_operations(self):
"""加载可撤销的操作"""
# 清空列表
for item in self.tree.get_children():
self.tree.delete(item)
# 获取所有成功完成的操作
records = self.history.get_recent_records(limit=50)
self.undoable_records = []
for record in records:
# 检查是否有回滚数据
if record.rollback_data and record.status == OperationStatus.SUCCESS.value:
# 检查是否是可撤销的操作类型
if record.operation_type in [
OperationType.CREATE_PARTITION.value,
OperationType.DELETE_PARTITION.value,
OperationType.WIPE_PARTITION_TABLE.value
]:
self.undoable_records.append(record)
# 格式化时间
try:
from datetime import datetime
dt = datetime.fromisoformat(record.timestamp)
time_str = dt.strftime("%Y-%m-%d %H:%M:%S")
except:
time_str = record.timestamp
values = (
time_str,
record.operation_type,
record.device_path,
record.status
)
self.tree.insert("", tk.END, values=values)
def _on_select(self, event):
"""选择操作"""
selection = self.tree.selection()
if not selection:
self.undo_btn.config(state=tk.DISABLED)
return
index = self.tree.index(selection[0])
if index < len(self.undoable_records):
self.selected_record = self.undoable_records[index]
self._show_details()
self.undo_btn.config(state=tk.NORMAL)
else:
self.selected_record = None
self.undo_btn.config(state=tk.DISABLED)
def _show_details(self):
"""显示详情"""
if not self.selected_record:
return
self.detail_text.config(state=tk.NORMAL)
self.detail_text.delete(1.0, tk.END)
record = self.selected_record
text = f"操作ID: {record.id}\n"
text += f"时间: {record.timestamp}\n"
text += f"类型: {record.operation_type}\n"
text += f"设备: {record.device_path}\n\n"
text += "操作详情:\n"
text += f"{record.details}\n\n"
if record.rollback_data:
text += "可撤销数据:\n"
if 'partition_table_backup' in record.rollback_data:
backup_file = record.rollback_data['partition_table_backup']
text += f" 分区表备份: {backup_file}\n"
if backup_file:
import os
if os.path.exists(backup_file):
text += " 状态: 备份文件存在,可以恢复\n"
else:
text += " 状态: 备份文件不存在!无法恢复\n"
self.undo_btn.config(state=tk.DISABLED)
else:
text += f"{record.rollback_data}\n"
self.detail_text.insert(1.0, text)
self.detail_text.config(state=tk.DISABLED)
def _undo_selected(self):
"""撤销选中的操作"""
if not self.selected_record:
return
record = self.selected_record
# 确认对话框
result = messagebox.askyesno(
"确认撤销",
f"确定要撤销以下操作吗?\n\n"
f"操作类型: {record.operation_type}\n"
f"设备: {record.device_path}\n"
f"时间: {record.timestamp}\n\n"
f"警告:这将恢复分区表到操作前的状态!\n"
f"此操作可能导致数据丢失!",
icon=messagebox.WARNING
)
if not result:
return
# 检查是否有备份
if not record.rollback_data or 'partition_table_backup' not in record.rollback_data:
messagebox.showerror("错误", "没有找到可恢复的备份数据")
return
backup_file = record.rollback_data['partition_table_backup']
if not backup_file:
messagebox.showerror("错误", "备份文件路径为空")
return
import os
if not os.path.exists(backup_file):
messagebox.showerror("错误", f"备份文件不存在:\n{backup_file}")
return
# 执行恢复
device_path = record.device_path
# 对于分区操作,需要获取父磁盘
if record.operation_type == OperationType.DELETE_PARTITION.value:
import re
match = re.match(r'(/dev/[a-z]+)\d+', device_path)
if match:
device_path = match.group(1)
if self.partition_backup.restore_partition_table(device_path, backup_file):
# 更新操作状态
self.history.update_status(
record.id,
OperationStatus.ROLLED_BACK
)
messagebox.showinfo("成功", f"操作已成功撤销!\n分区表已恢复到 {device_path}")
self.result = True
self.dialog.destroy()
else:
messagebox.showerror("错误", "撤销操作失败!\n请检查日志获取详细信息。")
def wait_for_result(self):
self.dialog.wait_window()
return self.result