['warning', 'error'].includes((plugin.governance_status || '').toLowerCase())).length;
},
- executionRiskCount() {
- // 这里把执行风险单独统计出来,和治理告警区分开:
- // 治理告警偏配置/依赖/加载问题,执行风险偏运行过程中的失败、超时与熔断。
- return (this.plugins || []).filter(plugin => ['warning', 'error'].includes((((plugin.execution_summary || {}).status) || '').toLowerCase())).length;
- },
- openCircuitCount() {
- return (this.plugins || []).filter(plugin => ((((plugin.execution_summary || {}).circuit_state) || '').toLowerCase() === 'open')).length;
- },
- topRiskPlugins() {
- // 风险排行优先按熔断状态、执行状态和连续失败次数排序,
- // 让页面顶部尽量把“最值得先排查”的插件顶上来。
- const statusPriority = {
- error: 0,
- warning: 1,
- info: 2,
- healthy: 3
- };
- return (this.plugins || [])
- .filter(plugin => ['warning', 'error'].includes((((plugin.execution_summary || {}).status) || '').toLowerCase()))
- .slice()
- .sort((left, right) => {
- const leftSummary = left.execution_summary || {};
- const rightSummary = right.execution_summary || {};
- const leftPriority = statusPriority[(leftSummary.status || 'info').toLowerCase()];
- const rightPriority = statusPriority[(rightSummary.status || 'info').toLowerCase()];
- return (
- (typeof leftPriority === 'number' ? leftPriority : 9) - (typeof rightPriority === 'number' ? rightPriority : 9)
- || Number(rightSummary.consecutive_failures || 0) - Number(leftSummary.consecutive_failures || 0)
- || Number(rightSummary.failure_count_total || 0) - Number(leftSummary.failure_count_total || 0)
- || Number(rightSummary.timeout_count_total || 0) - Number(leftSummary.timeout_count_total || 0)
- );
- })
- .slice(0, 5);
- },
- slowestPlugins() {
- // 慢插件排行只看有执行样本的插件,避免未执行插件把榜单冲掉。
- return (this.plugins || [])
- .filter(plugin => Number((plugin.execution_summary || {}).total_executions || 0) > 0)
- .slice()
- .sort((left, right) => {
- return Number((right.execution_summary || {}).last_process_time_ms || 0) - Number((left.execution_summary || {}).last_process_time_ms || 0);
- })
- .slice(0, 5);
- },
// 弹窗宽度按视口分级收缩,保证手机上弹窗内容不会贴边或继续触发横向溢出。
pluginInfoDialogWidth() {
return this.isMobileViewport ? '94%' : '64%';
@@ -707,7 +530,7 @@
},
pluginStatusLabel(plugin) {
if (plugin && plugin.status_label) return plugin.status_label;
- const normalizedStatus = String((plugin && plugin.status) || '').toUpperCase();
+ const normalizedStatus = String(plugin?.status || '').toUpperCase();
const mapping = {
RUNNING: '运行中',
STOPPED: '已停用',
@@ -735,27 +558,10 @@
};
return mapping[normalizedLevel] || '提示';
},
- executionTagType(level) {
- const normalizedLevel = String(level || '').toLowerCase();
- if (normalizedLevel === 'error') return 'danger';
- if (normalizedLevel === 'warning') return 'warning';
- if (normalizedLevel === 'healthy') return 'success';
- return 'info';
- },
- executionLabel(level) {
- const normalizedLevel = String(level || '').toLowerCase();
- const mapping = {
- healthy: '稳定',
- warning: '需关注',
- error: '高风险',
- info: '暂无样本'
- };
- return mapping[normalizedLevel] || '暂无样本';
- },
governanceIssueSummary(plugin) {
- const errorCount = Number((plugin && plugin.governance_error_count) || 0);
- const warningCount = Number((plugin && plugin.governance_warning_count) || 0);
- const infoCount = Number((plugin && plugin.governance_info_count) || 0);
+ const errorCount = Number(plugin?.governance_error_count || 0);
+ const warningCount = Number(plugin?.governance_warning_count || 0);
+ const infoCount = Number(plugin?.governance_info_count || 0);
if (errorCount > 0 || warningCount > 0) {
return `错误 ${errorCount} / 告警 ${warningCount}`;
}
@@ -764,16 +570,6 @@
}
return '暂无治理问题';
},
- formatPercent(value) {
- const normalizedValue = Number(value || 0);
- if (!Number.isFinite(normalizedValue)) return '0.00%';
- return `${normalizedValue.toFixed(2)}%`;
- },
- formatDurationMs(value) {
- const normalizedValue = Number(value || 0);
- if (!Number.isFinite(normalizedValue) || normalizedValue <= 0) return '-';
- return `${normalizedValue.toFixed(2)} ms`;
- },
loadPlugins() {
this.loading = true;
axios.get('/api/plugins')
@@ -891,7 +687,7 @@
})
.catch(error => {
console.error('保存配置出错:', error);
- this.configError = '保存配置出错: ' + (((error.response || {}).data || {}).message || error.message);
+ this.configError = '保存配置出错: ' + (error.response?.data?.message || error.message);
});
} catch (e) {
this.configError = '处理配置时出错: ' + e.message;
@@ -1058,7 +854,6 @@
font-size: 14px;
}
.overview-grid .el-col { margin-bottom: 16px; }
- .insight-grid .el-col { margin-bottom: 16px; }
.overview-card { min-height: 112px; }
.overview-card--primary {
background: linear-gradient(180deg, rgba(79,70,229,0.10), rgba(255,255,255,0.94)) !important;
@@ -1074,64 +869,6 @@
}
.workspace-header h3 { font-size: 18px; margin-bottom: 4px; }
.workspace-header p { font-size: 13px; color: #64748b; }
- .rank-list {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .rank-item {
- display: flex;
- align-items: flex-start;
- gap: 12px;
- padding: 14px;
- border-radius: 16px;
- background: rgba(248,250,252,0.82);
- border: 1px solid rgba(148,163,184,0.12);
- }
- .rank-item__index {
- width: 28px;
- height: 28px;
- border-radius: 50%;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- background: rgba(79,70,229,0.10);
- color: #4f46e5;
- font-size: 12px;
- font-weight: 700;
- flex-shrink: 0;
- }
- .rank-item__content {
- flex: 1;
- min-width: 0;
- }
- .rank-item__title-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- margin-bottom: 8px;
- }
- .rank-item__title,
- .rank-item__value {
- font-size: 14px;
- font-weight: 700;
- color: #0f172a;
- }
- .rank-item__summary {
- font-size: 13px;
- line-height: 1.7;
- color: #475569;
- word-break: break-word;
- }
- .rank-item__meta {
- display: flex;
- flex-wrap: wrap;
- gap: 8px 14px;
- margin-top: 8px;
- font-size: 12px;
- color: #94a3b8;
- }
.entity-cell { display: flex; align-items: center; gap: 12px; }
.entity-badge {
width: 30px; height: 30px; border-radius: 50%; display: inline-flex; align-items: center;
@@ -1166,28 +903,6 @@
color: #94a3b8;
line-height: 1.4;
}
- .execution-cell {
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
- .execution-cell__head {
- display: flex;
- align-items: center;
- gap: 8px;
- flex-wrap: wrap;
- }
- .execution-cell__metric {
- font-size: 12px;
- color: #64748b;
- font-weight: 600;
- }
- .execution-cell__summary {
- font-size: 12px;
- color: #94a3b8;
- line-height: 1.6;
- word-break: break-word;
- }
.config-overview-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
@@ -1364,10 +1079,6 @@
.mobile-plugin-card__header {
flex-direction: column;
}
- .rank-item__title-row {
- flex-direction: column;
- align-items: flex-start;
- }
.mobile-plugin-card__actions .el-button,
.mobile-group-card__actions .el-button {
flex: 1 1 calc(50% - 8px);
diff --git a/base/plugin_common/plugin_manager.py b/base/plugin_common/plugin_manager.py
index 91fb48e..f9f96b2 100644
--- a/base/plugin_common/plugin_manager.py
+++ b/base/plugin_common/plugin_manager.py
@@ -602,107 +602,11 @@ class PluginManager:
"info_count": level_counts["info"],
}
- @staticmethod
- def _format_runtime_timestamp(timestamp_value: Any) -> str:
- """把运行态中的 unix 时间戳转成后台可读文本。"""
- try:
- normalized = float(timestamp_value or 0.0)
- except (TypeError, ValueError):
- return ""
- if normalized <= 0:
- return ""
- try:
- return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(normalized))
- except (OverflowError, OSError, ValueError):
- return ""
-
- @staticmethod
- def _safe_percent(numerator: Any, denominator: Any) -> float:
- """安全计算百分比,避免分母为空时抛异常。"""
- try:
- denominator_value = float(denominator or 0.0)
- if denominator_value <= 0:
- return 0.0
- return round((float(numerator or 0.0) / denominator_value) * 100, 2)
- except (TypeError, ValueError, ZeroDivisionError):
- return 0.0
-
- def _build_execution_summary(self, guard_snapshot: Dict[str, Any]) -> Dict[str, Any]:
- """把执行保护记录转换成更适合后台页面展示的执行摘要。
-
- 设计考虑:
- 1. 原始 execution_guard 更偏底层状态,前端直接消费会充满规则判断;
- 2. 这里统一补出成功率、总执行次数、最近成功/失败时间、最近错误摘要;
- 3. 未来如果还要做“高风险插件排行”“慢插件排行”,也能直接复用该摘要。
- """
- guard_snapshot = dict(guard_snapshot or {})
- success_count_total = int(guard_snapshot.get("success_count_total", 0) or 0)
- failure_count_total = int(guard_snapshot.get("failure_count_total", 0) or 0)
- timeout_count_total = int(guard_snapshot.get("timeout_count_total", 0) or 0)
- consecutive_failures = int(guard_snapshot.get("consecutive_failures", 0) or 0)
- consecutive_timeouts = int(guard_snapshot.get("consecutive_timeouts", 0) or 0)
- last_process_time_ms = round(float(guard_snapshot.get("last_process_time_ms", 0.0) or 0.0), 2)
- circuit_state = str(guard_snapshot.get("circuit_state", "closed") or "closed").strip().lower()
- last_error_message = str(guard_snapshot.get("last_error_message") or "").strip()
- if len(last_error_message) > 240:
- last_error_message = f"{last_error_message[:237]}..."
-
- total_executions = success_count_total + failure_count_total
- success_rate = self._safe_percent(success_count_total, total_executions)
- timeout_rate = self._safe_percent(timeout_count_total, total_executions)
- last_success_at_text = self._format_runtime_timestamp(guard_snapshot.get("last_success_at"))
- last_failure_at_text = self._format_runtime_timestamp(guard_snapshot.get("last_failure_at"))
-
- status = "info"
- summary = "暂无执行样本"
- if total_executions > 0:
- status = "healthy"
- summary = (
- f"累计执行 {total_executions} 次,成功率 {success_rate}%,"
- f"最近耗时 {last_process_time_ms}ms"
- )
-
- # 熔断打开是最明确的高风险信号,应优先标记为 error。
- if circuit_state == "open":
- status = "error"
- summary = (
- f"插件当前处于熔断中,连续失败 {consecutive_failures} 次,"
- f"恢复剩余 {int(guard_snapshot.get('open_remaining_seconds', 0) or 0)}s"
- )
- elif failure_count_total > 0 or timeout_count_total > 0 or consecutive_failures > 0 or consecutive_timeouts > 0:
- status = "warning"
- summary = (
- f"累计失败 {failure_count_total} 次,超时 {timeout_count_total} 次,"
- f"成功率 {success_rate}%"
- )
-
- return {
- "status": status,
- "summary": summary,
- "total_executions": total_executions,
- "success_count_total": success_count_total,
- "failure_count_total": failure_count_total,
- "timeout_count_total": timeout_count_total,
- "success_rate": success_rate,
- "timeout_rate": timeout_rate,
- "consecutive_failures": consecutive_failures,
- "consecutive_timeouts": consecutive_timeouts,
- "last_process_time_ms": last_process_time_ms,
- "last_success_at_text": last_success_at_text,
- "last_failure_at_text": last_failure_at_text,
- "last_error_message": last_error_message,
- "last_failure_type": str(guard_snapshot.get("last_failure_type") or "").strip(),
- "last_timeout_seconds": int(guard_snapshot.get("last_timeout_seconds", 0) or 0),
- "circuit_state": circuit_state,
- "open_remaining_seconds": int(guard_snapshot.get("open_remaining_seconds", 0) or 0),
- }
-
def _build_plugin_snapshot(self, plugin: PluginInterface) -> Dict[str, Any]:
"""为已加载插件生成标准治理快照。"""
module_name = self._get_module_name_from_plugin(plugin) or "unknown"
runtime_record = self._get_module_runtime_state(module_name)
guard_snapshot = self.get_plugin_guard_snapshot(module_name)
- execution_summary = self._build_execution_summary(guard_snapshot)
config_path = plugin.get_config_path()
config_overview = self._read_plugin_config_overview(config_path)
commands = self._collect_plugin_commands(plugin)
@@ -744,14 +648,12 @@ class PluginManager:
"runtime_state": runtime_record.get("state", "loaded"),
"runtime_message": runtime_record.get("message", ""),
"execution_guard": guard_snapshot,
- "execution_summary": execution_summary,
}
def _build_unloaded_plugin_snapshot(self, module_name: str) -> Dict[str, Any]:
"""为未成功加载的插件模块生成治理快照。"""
runtime_record = self._get_module_runtime_state(module_name)
guard_snapshot = self.get_plugin_guard_snapshot(module_name)
- execution_summary = self._build_execution_summary(guard_snapshot)
config_path = os.path.join(self.plugin_dir, module_name, "config.toml")
if not os.path.exists(config_path):
config_path = os.path.join(self.plugin_dir, f"{module_name}", "config.toml")
@@ -798,7 +700,6 @@ class PluginManager:
"runtime_state": runtime_state or "discovered",
"runtime_message": runtime_record.get("message", ""),
"execution_guard": guard_snapshot,
- "execution_summary": execution_summary,
}
@staticmethod
diff --git a/docs/工程优化与Feature清单.md b/docs/工程优化与Feature清单.md
index 12cb132..835d5b4 100644
--- a/docs/工程优化与Feature清单.md
+++ b/docs/工程优化与Feature清单.md
@@ -405,7 +405,6 @@
- 第一阶段已完成:`PluginManager` 已输出统一插件治理快照,后台不再只展示“加载成功的插件”
- 第一阶段已完成:后台插件管理页已补充治理健康、能力类型、Feature Key、依赖与配置概览信息
- 第一阶段已完成:插件配置保存前已增加格式校验,避免坏配置直接写回线上文件
-- 第二阶段已完成:插件管理页已补充执行表现摘要、最近错误信息与高风险/慢插件排行,便于快速定位运行异常插件
- 后续可继续补充插件错误历史、性能排名、依赖图与熔断/隔离控制
建议内容: