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:
Mimikko-zeus
2026-02-27 14:32:30 +08:00
parent ab5bbff6f7
commit 8a538bb950
58 changed files with 13457 additions and 350 deletions

338
ui/governance_panel.py Normal file
View 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