""" 数据治理监控面板 提供可视化的治理指标展示和管理操作 """ import tkinter as tk from tkinter import ttk, messagebox, filedialog from pathlib import Path from typing import Optional from history.manager import HistoryManager from history.data_governance import GovernanceMetrics class GovernancePanel: """ 数据治理监控面板 显示治理指标、执行清理操作、导出数据 """ def __init__(self, parent: tk.Widget, history_manager: HistoryManager): self.parent = parent self.history = history_manager self.frame = None self._create_widgets() def _create_widgets(self): """创建 UI 组件""" self.frame = tk.Frame(self.parent, bg='#1e1e1e') # 标题 title_frame = tk.Frame(self.frame, bg='#1e1e1e') title_frame.pack(fill=tk.X, padx=10, pady=10) title_label = tk.Label( title_frame, text="🛡️ 数据治理监控", font=('Microsoft YaHei UI', 14, 'bold'), fg='#ffd54f', bg='#1e1e1e' ) title_label.pack(side=tk.LEFT) # 刷新按钮 refresh_btn = tk.Button( title_frame, text="🔄 刷新", font=('Microsoft YaHei UI', 10), bg='#424242', fg='white', activebackground='#616161', activeforeground='white', relief=tk.FLAT, padx=10, cursor='hand2', command=self._refresh_metrics ) refresh_btn.pack(side=tk.RIGHT) # 主内容区域 content_frame = tk.Frame(self.frame, bg='#1e1e1e') content_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) # 左侧:指标展示 metrics_frame = tk.LabelFrame( content_frame, text=" 治理指标 ", font=('Microsoft YaHei UI', 10, 'bold'), fg='#4fc3f7', bg='#1e1e1e', relief=tk.GROOVE ) metrics_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5)) # 指标显示区域 self.metrics_text = tk.Text( metrics_frame, wrap=tk.WORD, font=('Consolas', 10), bg='#2d2d2d', fg='#d4d4d4', relief=tk.FLAT, padx=15, pady=15, state=tk.DISABLED, height=20 ) self.metrics_text.pack(fill=tk.BOTH, expand=True, padx=3, pady=3) # 配置标签样式 self.metrics_text.tag_configure('title', font=('Microsoft YaHei UI', 11, 'bold'), foreground='#ffd54f') self.metrics_text.tag_configure('label', font=('Microsoft YaHei UI', 10, 'bold'), foreground='#4fc3f7') self.metrics_text.tag_configure('value', font=('Consolas', 10), foreground='#81c784') self.metrics_text.tag_configure('warning', font=('Consolas', 10), foreground='#ef5350') self.metrics_text.tag_configure('normal', font=('Microsoft YaHei UI', 10), foreground='#d4d4d4') # 右侧:操作面板 action_frame = tk.LabelFrame( content_frame, text=" 管理操作 ", font=('Microsoft YaHei UI', 10, 'bold'), fg='#81c784', bg='#1e1e1e', relief=tk.GROOVE ) action_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=(5, 0)) # 操作按钮 btn_config = { 'font': ('Microsoft YaHei UI', 10), 'relief': tk.FLAT, 'cursor': 'hand2', 'width': 18 } # 手动清理按钮 cleanup_btn = tk.Button( action_frame, text="🧹 执行数据清理", bg='#f57c00', fg='white', activebackground='#ff9800', activeforeground='white', command=self._manual_cleanup, **btn_config ) cleanup_btn.pack(padx=10, pady=(10, 5)) tk.Label( action_frame, text="清理过期和敏感数据", font=('Microsoft YaHei UI', 8), fg='#888888', bg='#1e1e1e' ).pack(padx=10, pady=(0, 15)) # 导出脱敏数据按钮 export_btn = tk.Button( action_frame, text="📤 导出脱敏数据", bg='#0e639c', fg='white', activebackground='#1177bb', activeforeground='white', command=self._export_sanitized, **btn_config ) export_btn.pack(padx=10, pady=(0, 5)) tk.Label( action_frame, text="导出安全的历史记录", font=('Microsoft YaHei UI', 8), fg='#888888', bg='#1e1e1e' ).pack(padx=10, pady=(0, 15)) # 查看归档按钮 archive_btn = tk.Button( action_frame, text="📁 打开归档目录", bg='#424242', fg='white', activebackground='#616161', activeforeground='white', command=self._open_archive, **btn_config ) archive_btn.pack(padx=10, pady=(0, 5)) tk.Label( action_frame, text="查看已归档的记录", font=('Microsoft YaHei UI', 8), fg='#888888', bg='#1e1e1e' ).pack(padx=10, pady=(0, 15)) # 分隔线 ttk.Separator(action_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, padx=10, pady=15) # 策略说明 policy_label = tk.Label( action_frame, text="数据分级策略", font=('Microsoft YaHei UI', 10, 'bold'), fg='#ce93d8', bg='#1e1e1e' ) policy_label.pack(padx=10, pady=(0, 10)) policy_text = """ • 完整保存 (90天) 敏感度 < 0.3 • 脱敏保存 (30天) 0.3 ≤ 敏感度 < 0.7 • 最小化保存 (7天) 敏感度 ≥ 0.7 • 自动归档 过期数据自动降级或归档 """ policy_info = tk.Label( action_frame, text=policy_text, font=('Microsoft YaHei UI', 9), fg='#b0b0b0', bg='#1e1e1e', justify=tk.LEFT ) policy_info.pack(padx=10, pady=(0, 10)) # 加载指标 self._refresh_metrics() def _refresh_metrics(self): """刷新指标显示""" metrics = self.history.get_governance_metrics() self.metrics_text.config(state=tk.NORMAL) self.metrics_text.delete(1.0, tk.END) if not metrics: self.metrics_text.insert(tk.END, "暂无治理指标数据\n\n", 'normal') self.metrics_text.insert(tk.END, "执行任务后将自动收集指标", 'normal') self.metrics_text.config(state=tk.DISABLED) return # 显示指标 self.metrics_text.insert(tk.END, "📊 数据统计\n\n", 'title') self.metrics_text.insert(tk.END, "总记录数: ", 'label') self.metrics_text.insert(tk.END, f"{metrics.total_records}\n", 'value') self.metrics_text.insert(tk.END, "完整保存: ", 'label') self.metrics_text.insert(tk.END, f"{metrics.full_records}\n", 'value') self.metrics_text.insert(tk.END, "脱敏保存: ", 'label') self.metrics_text.insert(tk.END, f"{metrics.sanitized_records}\n", 'value') self.metrics_text.insert(tk.END, "最小化保存: ", 'label') self.metrics_text.insert(tk.END, f"{metrics.minimal_records}\n", 'value') self.metrics_text.insert(tk.END, "已归档: ", 'label') self.metrics_text.insert(tk.END, f"{metrics.archived_records}\n\n", 'value') # 存储大小 size_mb = metrics.total_size_bytes / 1024 / 1024 self.metrics_text.insert(tk.END, "存储占用: ", 'label') self.metrics_text.insert(tk.END, f"{size_mb:.2f} MB\n\n", 'value') # 过期记录 if metrics.expired_records > 0: self.metrics_text.insert(tk.END, "⚠️ 待清理: ", 'label') self.metrics_text.insert(tk.END, f"{metrics.expired_records} 条过期记录\n\n", 'warning') # 敏感字段命中统计 if metrics.sensitive_field_hits: self.metrics_text.insert(tk.END, "🔍 敏感字段命中统计\n\n", 'title') for field, count in sorted(metrics.sensitive_field_hits.items(), key=lambda x: x[1], reverse=True): self.metrics_text.insert(tk.END, f" {field}: ", 'label') self.metrics_text.insert(tk.END, f"{count} 次\n", 'value') # 最后清理时间 self.metrics_text.insert(tk.END, f"\n\n最后清理: ", 'label') self.metrics_text.insert(tk.END, f"{metrics.last_cleanup_time}\n", 'normal') self.metrics_text.config(state=tk.DISABLED) def _manual_cleanup(self): """手动执行数据清理""" result = messagebox.askyesno( "确认清理", "将执行以下操作:\n\n" "• 完整数据过期 → 降级为脱敏\n" "• 脱敏数据过期 → 归档\n" "• 最小化数据过期 → 删除\n\n" "是否继续?", icon='question' ) if result: try: stats = self.history.manual_cleanup() self._refresh_metrics() messagebox.showinfo( "清理完成", f"数据清理完成:\n\n" f"归档: {stats['archived']} 条\n" f"删除: {stats['deleted']} 条\n" f"保留: {stats['remaining']} 条" ) except Exception as e: messagebox.showerror("清理失败", f"数据清理失败:\n{e}") def _export_sanitized(self): """导出脱敏数据""" file_path = filedialog.asksaveasfilename( title="导出脱敏数据", defaultextension=".json", filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")] ) if file_path: try: count = self.history.export_sanitized(Path(file_path)) messagebox.showinfo("导出成功", f"已导出 {count} 条脱敏记录到:\n{file_path}") except Exception as e: messagebox.showerror("导出失败", f"导出失败:\n{e}") def _open_archive(self): """打开归档目录""" archive_dir = self.history.workspace / "archive" if archive_dir.exists(): import os os.startfile(str(archive_dir)) else: messagebox.showinfo("提示", "归档目录不存在,暂无归档数据") def show(self): """显示面板""" self._refresh_metrics() self.frame.pack(fill=tk.BOTH, expand=True) def hide(self): """隐藏面板""" self.frame.pack_forget() def get_frame(self) -> tk.Frame: """获取主框架""" return self.frame