mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-07-09 22:43:44 +08:00
新增 doctor 诊断自救功能
This commit is contained in:
151
app/doctor/models.py
Normal file
151
app/doctor/models.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from enum import StrEnum
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
class DoctorSeverity(StrEnum):
|
||||
"""
|
||||
诊断结果严重级别。
|
||||
"""
|
||||
|
||||
Info = "info"
|
||||
Warn = "warn"
|
||||
Error = "error"
|
||||
|
||||
|
||||
class DoctorFindingStatus(StrEnum):
|
||||
"""
|
||||
单项诊断状态。
|
||||
"""
|
||||
|
||||
Ok = "ok"
|
||||
Skipped = "skipped"
|
||||
Degraded = "degraded"
|
||||
Failed = "failed"
|
||||
Fixed = "fixed"
|
||||
|
||||
|
||||
class DoctorReportStatus(StrEnum):
|
||||
"""
|
||||
整体诊断报告状态。
|
||||
"""
|
||||
|
||||
Healthy = "healthy"
|
||||
Degraded = "degraded"
|
||||
Failed = "failed"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DoctorFinding:
|
||||
"""
|
||||
单条诊断发现,描述问题、原因、建议和可选修复状态。
|
||||
"""
|
||||
|
||||
id: str
|
||||
severity: DoctorSeverity
|
||||
status: DoctorFindingStatus
|
||||
title: str
|
||||
detail: str
|
||||
recommendation: str
|
||||
fixable: bool = False
|
||||
fixed: bool = False
|
||||
context: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""
|
||||
转换为稳定的 JSON 字典结构。
|
||||
"""
|
||||
payload: dict[str, Any] = {
|
||||
"id": self.id,
|
||||
"severity": self.severity.value,
|
||||
"status": self.status.value,
|
||||
"title": self.title,
|
||||
"detail": self.detail,
|
||||
"recommendation": self.recommendation,
|
||||
"fixable": self.fixable,
|
||||
"fixed": self.fixed,
|
||||
}
|
||||
if self.context:
|
||||
payload["context"] = self.context
|
||||
return payload
|
||||
|
||||
|
||||
@dataclass
|
||||
class DoctorReport:
|
||||
"""
|
||||
MoviePilot 离线诊断报告。
|
||||
"""
|
||||
|
||||
generated_at: datetime
|
||||
version: str
|
||||
environment: dict[str, Any]
|
||||
findings: list[DoctorFinding] = field(default_factory=list)
|
||||
schema_version: int = 1
|
||||
|
||||
@property
|
||||
def status(self) -> DoctorReportStatus:
|
||||
"""
|
||||
根据诊断发现计算整体状态。
|
||||
"""
|
||||
unresolved = [finding for finding in self.findings if not finding.fixed]
|
||||
if any(finding.severity == DoctorSeverity.Error for finding in unresolved):
|
||||
return DoctorReportStatus.Failed
|
||||
if any(finding.severity == DoctorSeverity.Warn for finding in unresolved):
|
||||
return DoctorReportStatus.Degraded
|
||||
return DoctorReportStatus.Healthy
|
||||
|
||||
@property
|
||||
def summary(self) -> dict[str, int]:
|
||||
"""
|
||||
统计不同严重级别的诊断发现数量。
|
||||
"""
|
||||
counts = {
|
||||
"total": len(self.findings),
|
||||
"info": 0,
|
||||
"warn": 0,
|
||||
"error": 0,
|
||||
"fixed": 0,
|
||||
}
|
||||
for finding in self.findings:
|
||||
counts[finding.severity.value] += 1
|
||||
if finding.fixed:
|
||||
counts["fixed"] += 1
|
||||
return counts
|
||||
|
||||
def exit_code(self) -> int:
|
||||
"""
|
||||
返回适合 CLI 和自动化脚本使用的退出码。
|
||||
"""
|
||||
return 2 if self.status == DoctorReportStatus.Failed else 0
|
||||
|
||||
def add_finding(self, finding: DoctorFinding) -> None:
|
||||
"""
|
||||
添加一条诊断发现。
|
||||
"""
|
||||
self.findings.append(finding)
|
||||
|
||||
def find(self, finding_id: str) -> Optional[DoctorFinding]:
|
||||
"""
|
||||
按诊断项 ID 查找发现。
|
||||
"""
|
||||
for finding in self.findings:
|
||||
if finding.id == finding_id:
|
||||
return finding
|
||||
return None
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""
|
||||
转换为稳定的 JSON 字典结构。
|
||||
"""
|
||||
return {
|
||||
"schema_version": self.schema_version,
|
||||
"status": self.status.value,
|
||||
"generated_at": self.generated_at.isoformat(timespec="seconds"),
|
||||
"version": self.version,
|
||||
"environment": self.environment,
|
||||
"summary": self.summary,
|
||||
"findings": [finding.to_dict() for finding in self.findings],
|
||||
}
|
||||
Reference in New Issue
Block a user