feat: refactor API key configuration and enhance application initialization
- Renamed `check_environment` to `check_api_key_configured` for clarity, simplifying the API key validation logic. - Removed the blocking behavior of the API key check during application startup, allowing the app to run while providing a prompt for configuration. - Updated `LocalAgentApp` to accept an `api_configured` parameter, enabling conditional messaging for API key setup. - Enhanced the `SandboxRunner` to support backup management and improved execution result handling with detailed metrics. - Integrated data governance strategies into the `HistoryManager`, ensuring compliance and improved data management. - Added privacy settings and metrics tracking across various components to enhance user experience and application safety.
This commit is contained in:
338
ui/governance_panel.py
Normal file
338
ui/governance_panel.py
Normal file
@@ -0,0 +1,338 @@
|
||||
"""
|
||||
数据治理监控面板
|
||||
提供可视化的治理指标展示和管理操作
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user