Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d613e010c7 | ||
|
|
e431edc0d3 | ||
|
|
9a0b113160 | ||
|
|
b866bb8faf | ||
|
|
2d6b3761dd | ||
|
|
f3a790fdf3 | ||
|
|
6bc64665f3 | ||
|
|
0c546f83e4 | ||
|
|
0160b98131 | ||
|
|
0d30488be6 | ||
|
|
16ce87f912 | ||
|
|
1898dda3da |
@@ -0,0 +1,37 @@
|
||||
"""API adapter for visual AIOS operations dashboard."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
try:
|
||||
from fastapi import APIRouter
|
||||
except Exception:
|
||||
APIRouter = None
|
||||
|
||||
from .aios_visual_ops_flow import AIOSVisualOpsFlow
|
||||
|
||||
visual = AIOSVisualOpsFlow()
|
||||
|
||||
|
||||
def get_visual_operations_dashboard():
|
||||
return {
|
||||
"flow": visual.build_flow(),
|
||||
"timeline": visual.build_timeline(),
|
||||
"trends": visual.build_trends(),
|
||||
}
|
||||
|
||||
|
||||
def build_router():
|
||||
if APIRouter is None:
|
||||
return None
|
||||
router = APIRouter(prefix="/admin/ai-evolution/visual", tags=["visual-ops"])
|
||||
|
||||
@router.get("/overview", operation_id="getAIOSVisualOperationsDashboard")
|
||||
def overview():
|
||||
return get_visual_operations_dashboard()
|
||||
|
||||
return router
|
||||
|
||||
|
||||
router = build_router()
|
||||
|
||||
__all__ = ["router", "get_visual_operations_dashboard"]
|
||||
@@ -0,0 +1,134 @@
|
||||
"""Visual operations flow model for AIOS Web UI.
|
||||
|
||||
Provides graph/timeline/trend data so the admin UI can show system behavior as
|
||||
flows and operational diagrams instead of only numeric tables.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
|
||||
def utc_now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FlowNode:
|
||||
id: str
|
||||
label: str
|
||||
node_type: str
|
||||
status: str = "ok"
|
||||
group: str = "default"
|
||||
detail: str = ""
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FlowEdge:
|
||||
source: str
|
||||
target: str
|
||||
label: str
|
||||
status: str = "ok"
|
||||
count: int = 0
|
||||
latency_ms: int | None = None
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TimelineEvent:
|
||||
id: str
|
||||
title: str
|
||||
event_type: str
|
||||
status: str
|
||||
occurred_at: str
|
||||
summary: str = ""
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TrendPoint:
|
||||
timestamp: str
|
||||
values: dict[str, Any]
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
class AIOSVisualOpsFlow:
|
||||
"""Builds visual workflow data for `/admin/ai-evolution` dashboards."""
|
||||
|
||||
def build_flow(self, runtime: dict[str, Any] | None = None, counters: dict[str, int] | None = None) -> dict[str, Any]:
|
||||
runtime = runtime or {}
|
||||
counters = counters or {}
|
||||
nodes = [
|
||||
FlowNode("event", "事件/命令输入", "input", group="ingest"),
|
||||
FlowNode("watcher", "Command Watcher", "service", runtime.get("watcher_status", "ok"), "control"),
|
||||
FlowNode("controller", "Autonomous Controller", "service", runtime.get("controller_status", "ok"), "control"),
|
||||
FlowNode("diagnosis", "诊断/策略判断", "process", group="analysis"),
|
||||
FlowNode("patch", "Patch 生成", "process", group="gitops"),
|
||||
FlowNode("owner_gate", "Owner Gate", "gate", runtime.get("owner_gate_status", "ok"), "governance"),
|
||||
FlowNode("git", "Git Archive", "store", runtime.get("git_status", "ok"), "gitops"),
|
||||
FlowNode("audit", "Audit Plane", "store", runtime.get("audit_status", "ok"), "audit"),
|
||||
FlowNode("ui", "Web UI 可视化", "output", group="visibility"),
|
||||
FlowNode("notify", "通知/日报", "output", runtime.get("notification_status", "ok"), "visibility"),
|
||||
]
|
||||
edges = [
|
||||
FlowEdge("event", "watcher", "发现命令", count=counters.get("commands_seen", 0)),
|
||||
FlowEdge("watcher", "controller", "触发自治循环", count=counters.get("controller_cycles", 0)),
|
||||
FlowEdge("controller", "diagnosis", "健康检查/差异检测", count=counters.get("health_checks", 0)),
|
||||
FlowEdge("diagnosis", "patch", "生成修复计划", count=counters.get("patch_candidates", 0)),
|
||||
FlowEdge("patch", "owner_gate", "提交审批", count=counters.get("pending_owner_gate", 0)),
|
||||
FlowEdge("owner_gate", "git", "批准后归档", count=counters.get("archived_changes", 0)),
|
||||
FlowEdge("git", "audit", "写入审计", count=counters.get("audit_events", 0)),
|
||||
FlowEdge("audit", "ui", "展示证据", count=counters.get("ui_refresh", 0)),
|
||||
FlowEdge("audit", "notify", "汇报状态", count=counters.get("notifications", 0)),
|
||||
]
|
||||
return {
|
||||
"type": "flow_graph",
|
||||
"layout": "left_to_right",
|
||||
"generated_at": utc_now(),
|
||||
"nodes": [n.to_dict() for n in nodes],
|
||||
"edges": [e.to_dict() for e in edges],
|
||||
}
|
||||
|
||||
def build_timeline(self, events: list[dict[str, Any]] | None = None) -> dict[str, Any]:
|
||||
if not events:
|
||||
events = [
|
||||
{"id": "baseline", "title": "Baseline V1R3C1 生效", "event_type": "release", "status": "ok", "summary": "自治系统 baseline 已晋升"},
|
||||
{"id": "watcher", "title": "Command Watcher 心跳", "event_type": "heartbeat", "status": "ok", "summary": "最近周期正常"},
|
||||
{"id": "audit", "title": "GitOps 差异巡检", "event_type": "audit", "status": "ok", "summary": "无未归档变更"},
|
||||
]
|
||||
timeline = [
|
||||
TimelineEvent(
|
||||
id=e.get("id", f"event-{idx}"),
|
||||
title=e.get("title", "event"),
|
||||
event_type=e.get("event_type", "generic"),
|
||||
status=e.get("status", "ok"),
|
||||
occurred_at=e.get("occurred_at", utc_now()),
|
||||
summary=e.get("summary", ""),
|
||||
).to_dict()
|
||||
for idx, e in enumerate(events)
|
||||
]
|
||||
return {"type": "timeline", "events": timeline, "generated_at": utc_now()}
|
||||
|
||||
def build_trends(self, points: list[dict[str, Any]] | None = None) -> dict[str, Any]:
|
||||
points = points or [
|
||||
{"timestamp": utc_now(), "values": {"health_score": 100, "drift_count": 0, "pending_owner_gate": 0, "failed_commands": 0}}
|
||||
]
|
||||
return {
|
||||
"type": "trend_series",
|
||||
"series": [TrendPoint(p["timestamp"], p.get("values", {})).to_dict() for p in points],
|
||||
"recommended_charts": ["health_score_line", "drift_bar", "owner_gate_queue", "failure_rate"],
|
||||
}
|
||||
|
||||
|
||||
__all__ = ["FlowNode", "FlowEdge", "TimelineEvent", "TrendPoint", "AIOSVisualOpsFlow"]
|
||||
@@ -0,0 +1,42 @@
|
||||
"""GitOps change archive and drift audit engine."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
|
||||
def utc_now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ChangeAuditReport:
|
||||
status: str
|
||||
workspace_dirty: bool
|
||||
missing_metadata: list[str]
|
||||
drift_items: list[str]
|
||||
checked_at: str = field(default_factory=utc_now)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
class GitOpsChangeAuditor:
|
||||
def audit(
|
||||
self,
|
||||
workspace_dirty: bool,
|
||||
metadata: dict[str, Any],
|
||||
baseline_diff: list[str],
|
||||
) -> ChangeAuditReport:
|
||||
required = ["actor", "timestamp", "reason", "branch", "commit"]
|
||||
missing = [key for key in required if not metadata.get(key)]
|
||||
drift = list(baseline_diff)
|
||||
status = "healthy"
|
||||
if workspace_dirty or missing or drift:
|
||||
status = "attention_required"
|
||||
return ChangeAuditReport(status, workspace_dirty, missing, drift)
|
||||
|
||||
|
||||
__all__ = ["ChangeAuditReport", "GitOpsChangeAuditor"]
|
||||
@@ -0,0 +1,40 @@
|
||||
"""API adapter for GitOps operations dashboard."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from fastapi import APIRouter
|
||||
except Exception:
|
||||
APIRouter = None
|
||||
|
||||
from .gitops_ops_operating_audit_dashboard import GitOpsOpsOperatingAuditDashboard
|
||||
|
||||
|
||||
dashboard = GitOpsOpsOperatingAuditDashboard()
|
||||
|
||||
|
||||
def get_gitops_ops_dashboard(
|
||||
baseline: dict[str, Any] | None = None,
|
||||
runtime: dict[str, Any] | None = None,
|
||||
audit_report: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return dashboard.overview(baseline, runtime, audit_report)
|
||||
|
||||
|
||||
def build_router():
|
||||
if APIRouter is None:
|
||||
return None
|
||||
router = APIRouter(prefix="/admin/ai-evolution/gitops", tags=["gitops-dashboard"])
|
||||
|
||||
@router.get("/overview", operation_id="getGitOpsOpsOperatingAuditDashboard")
|
||||
def overview():
|
||||
return get_gitops_ops_dashboard()
|
||||
|
||||
return router
|
||||
|
||||
|
||||
router = build_router()
|
||||
|
||||
__all__ = ["router", "get_gitops_ops_dashboard"]
|
||||
@@ -0,0 +1,121 @@
|
||||
"""Web UI dashboard model for GitOps 运维 / 运营 / 审计 visibility.
|
||||
|
||||
This module produces framework-neutral data for `/admin/ai-evolution/gitops`.
|
||||
It is intentionally read-only: no secrets, no direct Git writes, no main push.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
|
||||
def utc_now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PanelMetric:
|
||||
key: str
|
||||
title: str
|
||||
value: Any
|
||||
status: str = "ok"
|
||||
detail: str = ""
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DashboardPanel:
|
||||
key: str
|
||||
title: str
|
||||
description: str
|
||||
metrics: list[PanelMetric] = field(default_factory=list)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"key": self.key,
|
||||
"title": self.title,
|
||||
"description": self.description,
|
||||
"metrics": [m.to_dict() for m in self.metrics],
|
||||
}
|
||||
|
||||
|
||||
class GitOpsOpsOperatingAuditDashboard:
|
||||
"""Aggregates GitOps archive/audit state into three UI sections."""
|
||||
|
||||
def overview(
|
||||
self,
|
||||
baseline: dict[str, Any] | None = None,
|
||||
runtime: dict[str, Any] | None = None,
|
||||
audit_report: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
baseline = baseline or {}
|
||||
runtime = runtime or {}
|
||||
audit_report = audit_report or {}
|
||||
panels = [
|
||||
self.ops_panel(baseline, runtime, audit_report),
|
||||
self.operating_panel(baseline, runtime, audit_report),
|
||||
self.audit_panel(baseline, runtime, audit_report),
|
||||
]
|
||||
return {
|
||||
"module": "gitops-ops-operating-audit",
|
||||
"route": "/admin/ai-evolution/gitops",
|
||||
"title": "GitOps 运维 / 运营 / 审计",
|
||||
"status": self._overall_status(panels),
|
||||
"generated_at": utc_now(),
|
||||
"panels": [p.to_dict() for p in panels],
|
||||
}
|
||||
|
||||
def ops_panel(self, baseline: dict[str, Any], runtime: dict[str, Any], audit_report: dict[str, Any]) -> DashboardPanel:
|
||||
return DashboardPanel(
|
||||
key="ops",
|
||||
title="运维视图",
|
||||
description="Runtime、Baseline、Git 同步和差异巡检状态。",
|
||||
metrics=[
|
||||
PanelMetric("runtime_status", "Runtime 状态", runtime.get("status", "unknown"), "ok" if runtime.get("status") == "active" else "warn"),
|
||||
PanelMetric("baseline", "当前 Baseline", baseline.get("current_baseline") or baseline.get("active_version") or "unknown"),
|
||||
PanelMetric("git_rev", "Git Revision", baseline.get("git", "unknown")),
|
||||
PanelMetric("workspace_dirty", "未归档工作区变化", bool(audit_report.get("workspace_dirty", False)), "warn" if audit_report.get("workspace_dirty") else "ok"),
|
||||
],
|
||||
)
|
||||
|
||||
def operating_panel(self, baseline: dict[str, Any], runtime: dict[str, Any], audit_report: dict[str, Any]) -> DashboardPanel:
|
||||
counters = audit_report.get("counters", {})
|
||||
return DashboardPanel(
|
||||
key="operating",
|
||||
title="运营视图",
|
||||
description="自治运行效果、变更吞吐、待审批事项和巡检结果。",
|
||||
metrics=[
|
||||
PanelMetric("change_events", "今日变更事件", counters.get("change_events", 0)),
|
||||
PanelMetric("diff_audits", "差异巡检次数", counters.get("diff_audits", 0)),
|
||||
PanelMetric("pending_owner_gate", "待 Owner 审批", counters.get("pending_owner_gate", 0), "warn" if counters.get("pending_owner_gate", 0) else "ok"),
|
||||
PanelMetric("auto_repair_candidates", "自动修复候选", counters.get("auto_repair_candidates", 0)),
|
||||
],
|
||||
)
|
||||
|
||||
def audit_panel(self, baseline: dict[str, Any], runtime: dict[str, Any], audit_report: dict[str, Any]) -> DashboardPanel:
|
||||
missing = audit_report.get("missing_metadata", [])
|
||||
drift = audit_report.get("drift_items", [])
|
||||
return DashboardPanel(
|
||||
key="audit",
|
||||
title="审计视图",
|
||||
description="Git 归档完整性、元数据合规、runtime-only 变更和差异证据。",
|
||||
metrics=[
|
||||
PanelMetric("archive_policy", "所有配置/代码变更必须入 Git", "enabled"),
|
||||
PanelMetric("missing_metadata", "缺失变更元数据", len(missing), "warn" if missing else "ok", ",".join(missing)),
|
||||
PanelMetric("drift_items", "发现差异项", len(drift), "warn" if drift else "ok", ",".join(drift[:10])),
|
||||
PanelMetric("secrets_in_git", "Secrets in Git", False, "ok"),
|
||||
],
|
||||
)
|
||||
|
||||
def _overall_status(self, panels: list[DashboardPanel]) -> str:
|
||||
for panel in panels:
|
||||
if any(metric.status == "warn" for metric in panel.metrics):
|
||||
return "attention_required"
|
||||
return "healthy"
|
||||
|
||||
|
||||
__all__ = ["PanelMetric", "DashboardPanel", "GitOpsOpsOperatingAuditDashboard"]
|
||||
@@ -0,0 +1,26 @@
|
||||
gitops_change_archive:
|
||||
enabled: true
|
||||
rule: every_code_and_config_change_must_be_git_archived
|
||||
enforcement:
|
||||
uncommitted_workspace_changes: alert
|
||||
direct_runtime_mutation: alert
|
||||
missing_commit_metadata: alert
|
||||
required_metadata:
|
||||
- actor
|
||||
- timestamp
|
||||
- reason
|
||||
- branch
|
||||
- commit
|
||||
diff_audit:
|
||||
enabled: true
|
||||
interval: hourly
|
||||
compare:
|
||||
- workspace_vs_head
|
||||
- head_vs_baseline
|
||||
- runtime_config_vs_git
|
||||
reports:
|
||||
path: audit/change-diff/
|
||||
governance:
|
||||
direct_main_push: false
|
||||
secrets_in_git: false
|
||||
owner_gate_required: true
|
||||
@@ -0,0 +1,8 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: GitOps Ops Operating Audit Dashboard API
|
||||
version: V1R3C1+
|
||||
paths:
|
||||
/admin/ai-evolution/gitops/overview:
|
||||
get:
|
||||
operationId: getGitOpsOpsOperatingAuditDashboard
|
||||
@@ -0,0 +1,34 @@
|
||||
# CMD-20260709-enable-gitops-change-archive-audit
|
||||
|
||||
## Goal
|
||||
|
||||
Enforce that all AIOS code and configuration changes are archived in Git and
|
||||
periodically checked for drift.
|
||||
|
||||
## Execute
|
||||
|
||||
1. Enable GitOps change archive policy.
|
||||
2. Run hourly diff audits:
|
||||
- workspace vs HEAD
|
||||
- HEAD vs baseline
|
||||
- runtime config vs Git
|
||||
3. Write reports:
|
||||
- audit/change-diff/
|
||||
4. Alert on:
|
||||
- uncommitted changes
|
||||
- runtime-only mutation
|
||||
- missing actor/reason/branch/commit metadata
|
||||
|
||||
## Validation
|
||||
|
||||
Expected signal:
|
||||
|
||||
- AIOS_GITOPS_CHANGE_ARCHIVE_READY
|
||||
- AIOS_DIFF_AUDITOR_READY
|
||||
|
||||
## Governance
|
||||
|
||||
- No direct main push.
|
||||
- No secrets in Git.
|
||||
- All changes require commit metadata.
|
||||
- Owner gate remains enabled.
|
||||
@@ -0,0 +1,30 @@
|
||||
# CMD-20260709-enable-gitops-ops-operating-audit-ui
|
||||
|
||||
## Goal
|
||||
|
||||
Deploy GitOps 运维 / 运营 / 审计 Web UI visibility.
|
||||
|
||||
## Deploy
|
||||
|
||||
1. Register dashboard API:
|
||||
- getGitOpsOpsOperatingAuditDashboard
|
||||
2. Mount Web UI route:
|
||||
- /admin/ai-evolution/gitops
|
||||
3. Display panels:
|
||||
- 运维: runtime, baseline, git revision, drift
|
||||
- 运营: changes, audits, approvals, repairs
|
||||
- 审计: metadata, diff, archive compliance
|
||||
4. Enable hourly diff audit data feed.
|
||||
|
||||
## Validation signals
|
||||
|
||||
- AIOS_GITOPS_UI_OPS_READY
|
||||
- AIOS_GITOPS_UI_OPERATING_READY
|
||||
- AIOS_GITOPS_UI_AUDIT_READY
|
||||
|
||||
## Governance
|
||||
|
||||
- All changes archived in Git.
|
||||
- No direct main push.
|
||||
- No secrets in Git.
|
||||
- Owner gate remains enabled.
|
||||
@@ -0,0 +1,29 @@
|
||||
# CMD-20260709-enable-visual-aios-ops-dashboard
|
||||
|
||||
## Goal
|
||||
|
||||
Upgrade AIOS Web UI from numeric dashboards to visual operational flow.
|
||||
|
||||
## Deploy
|
||||
|
||||
1. Register visual operations API:
|
||||
- getAIOSVisualOperationsDashboard
|
||||
2. Add UI panels:
|
||||
- autonomous control flow graph
|
||||
- command lifecycle timeline
|
||||
- health trend charts
|
||||
- drift/failure visualization
|
||||
- owner gate flow
|
||||
3. Integrate with GitOps audit data.
|
||||
|
||||
## Validation signals
|
||||
|
||||
- AIOS_VISUAL_OPS_FLOW_READY
|
||||
- AIOS_TIMELINE_DASHBOARD_READY
|
||||
- AIOS_HEALTH_TREND_READY
|
||||
|
||||
## Governance
|
||||
|
||||
- All visual data comes from audited Git/runtime sources.
|
||||
- No direct runtime mutation.
|
||||
- No secrets in Git.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "branch",
|
||||
"branch": "patch/gitops-change-archive-audit",
|
||||
"base": "main",
|
||||
"created_by": "chatgpt-gitops-bot",
|
||||
"created_at": "2026-07-08T12:43:11Z",
|
||||
"direct_main_push": false,
|
||||
"owner_approval_required": true
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
from active_runtime.app.gitops_ops_operating_audit_dashboard import GitOpsOpsOperatingAuditDashboard
|
||||
|
||||
|
||||
def test_dashboard_contains_ops_operating_audit():
|
||||
result = GitOpsOpsOperatingAuditDashboard().overview(
|
||||
baseline={"current_baseline": "V1R3C1", "git": "test"},
|
||||
runtime={"status": "active"},
|
||||
audit_report={"counters": {"change_events": 3}}
|
||||
)
|
||||
|
||||
assert result["module"] == "gitops-ops-operating-audit"
|
||||
assert len(result["panels"]) == 3
|
||||
assert result["panels"][2]["key"] == "audit"
|
||||
Reference in New Issue
Block a user