feat(web): bind homepage blueprint to live evidence
This commit is contained in:
@@ -525,6 +525,57 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"liveEvidence": {
|
||||
"title": "Live Evidence",
|
||||
"realtime": "Live read",
|
||||
"fields": {
|
||||
"metric": "Metric",
|
||||
"detail": "Detail",
|
||||
"source": "Read Source"
|
||||
},
|
||||
"sources": {
|
||||
"dossierCoverage": "/api/v1/platform/events/dossier/coverage",
|
||||
"runsAndCicd": "/api/v1/platform/runs/list + /api/v1/platform/cicd/events",
|
||||
"aiRouteStatus": "/api/v1/platform/ai-route-status",
|
||||
"runsAndStatusChain": "/api/v1/platform/runs/list + /api/v1/platform/status-chain",
|
||||
"qualityAndRecurrence": "/api/v1/platform/truth-chain/quality/summary + /api/v1/platform/events/dossier/recurrence",
|
||||
"truthChainQuality": "/api/v1/platform/truth-chain/quality/summary",
|
||||
"approvalsAndQuality": "approval store + /api/v1/platform/truth-chain/quality/summary",
|
||||
"kmBurndown": "/api/v1/ai/governance/km-stale-owner-review-burndown"
|
||||
},
|
||||
"signal": {
|
||||
"metric": "sources {sources} / refs {refs}",
|
||||
"detail": "missing refs {missing}, duplicates {duplicates}; Alert {alert} / Sentry {sentry} / SigNoz {signoz}"
|
||||
},
|
||||
"intake": {
|
||||
"metric": "Runs {runs} / linked {linked}",
|
||||
"detail": "latest CI/CD {stage}:{status}, commit {commit}, needs attention {attention}"
|
||||
},
|
||||
"ai": {
|
||||
"metric": "{lane} / {provider}",
|
||||
"detail": "skipped lanes {skipped}, operator action={action}, reason={reason}"
|
||||
},
|
||||
"mcp": {
|
||||
"metric": "MCP observations {observations} / gateway {gateway}",
|
||||
"detail": "success {success}, failed {failed}, server={server}, route={route}"
|
||||
},
|
||||
"playbook": {
|
||||
"metric": "gate {gate} / automation gaps {gaps}",
|
||||
"detail": "open work items {workItems}, verified groups {verifiedGroups}, auto-repair linked {linkedAutoRepair}"
|
||||
},
|
||||
"ansible": {
|
||||
"metric": "check-mode {checkMode} / pending {pending}",
|
||||
"detail": "blocker={blocker}, candidates={candidates}, operations={operations}"
|
||||
},
|
||||
"approval": {
|
||||
"metric": "pending {pending} / verified {verified}/{evaluated}",
|
||||
"detail": "human gates {humanGates}, auto-repair records {autoRepairRecords}, operation records {operations}"
|
||||
},
|
||||
"verify": {
|
||||
"metric": "stale {stale} / ratio {ratio}",
|
||||
"detail": "owner review pending {pending}, completed {completed}, remaining to threshold {remaining}"
|
||||
}
|
||||
},
|
||||
"values": {
|
||||
"verified": "verified {verified}/{evaluated}",
|
||||
"topGate": "{gate} missing {count}",
|
||||
|
||||
@@ -526,6 +526,57 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"liveEvidence": {
|
||||
"title": "Live Evidence",
|
||||
"realtime": "即時讀取",
|
||||
"fields": {
|
||||
"metric": "指標",
|
||||
"detail": "細節",
|
||||
"source": "讀取來源"
|
||||
},
|
||||
"sources": {
|
||||
"dossierCoverage": "/api/v1/platform/events/dossier/coverage",
|
||||
"runsAndCicd": "/api/v1/platform/runs/list + /api/v1/platform/cicd/events",
|
||||
"aiRouteStatus": "/api/v1/platform/ai-route-status",
|
||||
"runsAndStatusChain": "/api/v1/platform/runs/list + /api/v1/platform/status-chain",
|
||||
"qualityAndRecurrence": "/api/v1/platform/truth-chain/quality/summary + /api/v1/platform/events/dossier/recurrence",
|
||||
"truthChainQuality": "/api/v1/platform/truth-chain/quality/summary",
|
||||
"approvalsAndQuality": "approval store + /api/v1/platform/truth-chain/quality/summary",
|
||||
"kmBurndown": "/api/v1/ai/governance/km-stale-owner-review-burndown"
|
||||
},
|
||||
"signal": {
|
||||
"metric": "來源 {sources} / refs {refs}",
|
||||
"detail": "missing refs {missing},duplicates {duplicates};Alert {alert} / Sentry {sentry} / SigNoz {signoz}"
|
||||
},
|
||||
"intake": {
|
||||
"metric": "Runs {runs} / linked {linked}",
|
||||
"detail": "最新 CI/CD {stage}:{status},commit {commit},需注意 {attention}"
|
||||
},
|
||||
"ai": {
|
||||
"metric": "{lane} / {provider}",
|
||||
"detail": "skipped lanes {skipped},operator action={action},reason={reason}"
|
||||
},
|
||||
"mcp": {
|
||||
"metric": "MCP observations {observations} / gateway {gateway}",
|
||||
"detail": "success {success},failed {failed},server={server},route={route}"
|
||||
},
|
||||
"playbook": {
|
||||
"metric": "gate {gate} / automation gaps {gaps}",
|
||||
"detail": "open work items {workItems},verified groups {verifiedGroups},auto-repair linked {linkedAutoRepair}"
|
||||
},
|
||||
"ansible": {
|
||||
"metric": "check-mode {checkMode} / pending {pending}",
|
||||
"detail": "blocker={blocker},candidates={candidates},operations={operations}"
|
||||
},
|
||||
"approval": {
|
||||
"metric": "pending {pending} / verified {verified}/{evaluated}",
|
||||
"detail": "human gates {humanGates},auto-repair records {autoRepairRecords},operation records {operations}"
|
||||
},
|
||||
"verify": {
|
||||
"metric": "stale {stale} / ratio {ratio}",
|
||||
"detail": "owner review pending {pending},completed {completed},距離門檻剩 {remaining}"
|
||||
}
|
||||
},
|
||||
"values": {
|
||||
"verified": "verified {verified}/{evaluated}",
|
||||
"topGate": "{gate} 缺 {count}",
|
||||
|
||||
@@ -104,10 +104,97 @@ interface HomepageKmStaleCandidatesResponse {
|
||||
threshold_days?: number
|
||||
}
|
||||
|
||||
interface HomepageDossierCoverageResponse {
|
||||
summary?: {
|
||||
source_count?: number
|
||||
source_ref_total?: number
|
||||
missing_source_refs_total?: number
|
||||
duplicate_total?: number
|
||||
alert_ref_total?: number
|
||||
sentry_ref_total?: number
|
||||
signoz_ref_total?: number
|
||||
latest_received_at?: string | null
|
||||
}
|
||||
}
|
||||
|
||||
interface HomepageEventRecurrenceResponse {
|
||||
summary?: {
|
||||
source_event_total?: number
|
||||
linked_run_total?: number
|
||||
unlinked_event_total?: number
|
||||
auto_repair_linked_total?: number
|
||||
verified_repair_group_total?: number
|
||||
open_work_item_group_total?: number
|
||||
automation_gap_group_total?: number
|
||||
recurrent_group_total?: number
|
||||
recurrence_group_total?: number
|
||||
duplicate_event_total?: number
|
||||
latest_received_at?: string | null
|
||||
}
|
||||
}
|
||||
|
||||
interface HomepageRunSummary {
|
||||
run_id?: string
|
||||
state?: string
|
||||
created_at?: string | null
|
||||
remediation_summary?: {
|
||||
status?: string | null
|
||||
evidence_total?: number | null
|
||||
mcp_observation_total?: number | null
|
||||
mcp_observation_success?: number | null
|
||||
mcp_observation_failed?: number | null
|
||||
latest_mcp_server?: string | null
|
||||
latest_route?: string | null
|
||||
has_mcp_investigation?: boolean | null
|
||||
has_dry_run?: boolean | null
|
||||
human_gate_open?: boolean | null
|
||||
} | null
|
||||
}
|
||||
|
||||
interface HomepageRunsListResponse {
|
||||
runs?: HomepageRunSummary[]
|
||||
total?: number
|
||||
}
|
||||
|
||||
interface HomepageCicdEventsResponse {
|
||||
total?: number
|
||||
items?: Array<{
|
||||
stage?: string | null
|
||||
status?: string | null
|
||||
summary?: string | null
|
||||
commit_sha?: string | null
|
||||
needs_attention?: boolean | null
|
||||
created_at?: string | null
|
||||
}>
|
||||
}
|
||||
|
||||
interface HomepageKmOwnerReviewBurndownResponse {
|
||||
burn_down_status?: string | null
|
||||
current_snapshot?: {
|
||||
stale_count?: number
|
||||
total_count?: number
|
||||
stale_ratio?: number
|
||||
threshold?: number
|
||||
stale_days?: number
|
||||
} | null
|
||||
entries_to_threshold?: number | null
|
||||
pending_owner_reviews?: number | null
|
||||
completed_owner_reviews?: number | null
|
||||
completion_audit_total?: number | null
|
||||
stale_ratio_recheck_total?: number | null
|
||||
manual_review_required?: boolean | null
|
||||
generated_at?: string | null
|
||||
}
|
||||
|
||||
interface HomepageAutomationBriefSnapshot {
|
||||
callbackReplies?: HomepageCallbackRepliesResponse | null
|
||||
aiRouteStatus?: HomepageAiRouteStatusResponse | null
|
||||
kmStaleCandidates?: HomepageKmStaleCandidatesResponse | null
|
||||
dossierCoverage?: HomepageDossierCoverageResponse | null
|
||||
eventRecurrence?: HomepageEventRecurrenceResponse | null
|
||||
runsList?: HomepageRunsListResponse | null
|
||||
cicdEvents?: HomepageCicdEventsResponse | null
|
||||
kmOwnerReviewBurndown?: HomepageKmOwnerReviewBurndownResponse | null
|
||||
}
|
||||
|
||||
type HomepageWorkTone = 'live' | 'progress' | 'blocked' | 'watching'
|
||||
@@ -125,6 +212,12 @@ interface HomepageBlueprintStage extends HomepageWorkItemSummary {
|
||||
owner: string
|
||||
evidence: string
|
||||
nextAction: string
|
||||
liveEvidence: {
|
||||
metric: string
|
||||
detail: string
|
||||
source: string
|
||||
updated: string
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchHomepageJson<T>(url: string, signal?: AbortSignal): Promise<T | null> {
|
||||
@@ -689,8 +782,46 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
`${API_BASE}/api/v1/ai/governance/km-stale-candidates?project_id=${encodedProjectId}&limit=5`,
|
||||
controller.signal
|
||||
),
|
||||
]).then(([callbackReplies, aiRouteStatus, kmStaleCandidates]) => {
|
||||
setAutomationBrief({ callbackReplies, aiRouteStatus, kmStaleCandidates })
|
||||
fetchHomepageJson<HomepageDossierCoverageResponse>(
|
||||
`${API_BASE}/api/v1/platform/events/dossier/coverage?project_id=${encodedProjectId}&limit=100`,
|
||||
controller.signal
|
||||
),
|
||||
fetchHomepageJson<HomepageEventRecurrenceResponse>(
|
||||
`${API_BASE}/api/v1/platform/events/dossier/recurrence?project_id=${encodedProjectId}&limit=100`,
|
||||
controller.signal
|
||||
),
|
||||
fetchHomepageJson<HomepageRunsListResponse>(
|
||||
`${API_BASE}/api/v1/platform/runs/list?project_id=${encodedProjectId}&per_page=25`,
|
||||
controller.signal
|
||||
),
|
||||
fetchHomepageJson<HomepageCicdEventsResponse>(
|
||||
`${API_BASE}/api/v1/platform/cicd/events?project_id=${encodedProjectId}&limit=12`,
|
||||
controller.signal
|
||||
),
|
||||
fetchHomepageJson<HomepageKmOwnerReviewBurndownResponse>(
|
||||
`${API_BASE}/api/v1/ai/governance/km-stale-owner-review-burndown?project_id=${encodedProjectId}&limit=5`,
|
||||
controller.signal
|
||||
),
|
||||
]).then(([
|
||||
callbackReplies,
|
||||
aiRouteStatus,
|
||||
kmStaleCandidates,
|
||||
dossierCoverage,
|
||||
eventRecurrence,
|
||||
runsList,
|
||||
cicdEvents,
|
||||
kmOwnerReviewBurndown,
|
||||
]) => {
|
||||
setAutomationBrief({
|
||||
callbackReplies,
|
||||
aiRouteStatus,
|
||||
kmStaleCandidates,
|
||||
dossierCoverage,
|
||||
eventRecurrence,
|
||||
runsList,
|
||||
cicdEvents,
|
||||
kmOwnerReviewBurndown,
|
||||
})
|
||||
}).catch(() => {})
|
||||
.finally(() => setAutomationBriefLoaded(true))
|
||||
return () => controller.abort()
|
||||
@@ -701,6 +832,17 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
const unavailableValue = tDashboard('automationDelivery.unavailableValue')
|
||||
const loadingStatus = tDashboard('automationDelivery.status.loading')
|
||||
const unavailableStatus = tDashboard('automationDelivery.status.unavailable')
|
||||
const formatLiveEvidenceTime = (value: string | null | undefined) => {
|
||||
if (!value) return automationBriefLoaded ? unavailableValue : loadingStatus
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return unavailableValue
|
||||
return date.toLocaleString(locale === 'en' ? 'en-US' : 'zh-TW', {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
const evaluatedAutomationTotal = automationQuality?.evaluated_total
|
||||
const verifiedAutomationTotal = automationQuality?.verified_auto_repair_total
|
||||
const formatAutomationNumber = (value: number | null | undefined, loaded: boolean) => {
|
||||
@@ -753,6 +895,66 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
const hasAiRouteStatus = automationBrief.aiRouteStatus !== undefined && automationBrief.aiRouteStatus !== null
|
||||
const hasKmStaleCandidates = automationBrief.kmStaleCandidates !== undefined && automationBrief.kmStaleCandidates !== null
|
||||
const canClaimFullAutoRepair = automationQualityAvailable && Boolean(automationQuality?.production_claim?.can_claim_full_auto_repair)
|
||||
const dossierCoverageSummary = automationBrief.dossierCoverage?.summary ?? null
|
||||
const recurrenceSummary = automationBrief.eventRecurrence?.summary ?? null
|
||||
const recentRuns = automationBrief.runsList?.runs ?? []
|
||||
const latestRun = recentRuns[0]
|
||||
const runEvidenceSummary = recentRuns.reduce(
|
||||
(acc, run) => {
|
||||
const summary = run.remediation_summary
|
||||
acc.mcpTotal += summary?.mcp_observation_total ?? 0
|
||||
acc.mcpSuccess += summary?.mcp_observation_success ?? 0
|
||||
acc.mcpFailed += summary?.mcp_observation_failed ?? 0
|
||||
acc.evidenceTotal += summary?.evidence_total ?? 0
|
||||
if (summary?.has_mcp_investigation) acc.mcpInvestigatedRuns += 1
|
||||
if (summary?.human_gate_open) acc.humanGateRuns += 1
|
||||
if (!acc.latestMcpServer && summary?.latest_mcp_server) acc.latestMcpServer = summary.latest_mcp_server
|
||||
if (!acc.latestRoute && summary?.latest_route) acc.latestRoute = summary.latest_route
|
||||
return acc
|
||||
},
|
||||
{
|
||||
mcpTotal: 0,
|
||||
mcpSuccess: 0,
|
||||
mcpFailed: 0,
|
||||
evidenceTotal: 0,
|
||||
mcpInvestigatedRuns: 0,
|
||||
humanGateRuns: 0,
|
||||
latestMcpServer: null as string | null,
|
||||
latestRoute: null as string | null,
|
||||
}
|
||||
)
|
||||
const statusChainValues = Object.values(statusChains).filter((chain): chain is NonNullable<typeof chain> => Boolean(chain && !chain.fetch_error))
|
||||
const statusChainEvidenceSummary = statusChainValues.reduce(
|
||||
(acc, chain) => {
|
||||
acc.mcpGateway += chain.mcp?.gateway?.total ?? chain.evidence?.mcp_gateway_total ?? 0
|
||||
acc.mcpGatewaySuccess += chain.mcp?.gateway?.success ?? 0
|
||||
acc.mcpGatewayFailed += chain.mcp?.gateway?.failed ?? 0
|
||||
acc.operationRecords += chain.evidence?.operation_records ?? chain.execution?.operation_total ?? 0
|
||||
acc.autoRepairRecords += chain.evidence?.auto_repair_records ?? 0
|
||||
acc.knowledgeEntries += chain.evidence?.knowledge_entries ?? 0
|
||||
acc.inboundRefs += chain.source_refs?.inbound_total ?? 0
|
||||
acc.outboundRefs += chain.source_refs?.outbound_total ?? 0
|
||||
return acc
|
||||
},
|
||||
{
|
||||
mcpGateway: 0,
|
||||
mcpGatewaySuccess: 0,
|
||||
mcpGatewayFailed: 0,
|
||||
operationRecords: 0,
|
||||
autoRepairRecords: 0,
|
||||
knowledgeEntries: 0,
|
||||
inboundRefs: 0,
|
||||
outboundRefs: 0,
|
||||
}
|
||||
)
|
||||
const cicdItems = automationBrief.cicdEvents?.items ?? []
|
||||
const latestCicdEvent = cicdItems[0]
|
||||
const cicdNeedsAttention = cicdItems.filter(item => item.needs_attention).length
|
||||
const kmOwnerReviewBurndown = automationBrief.kmOwnerReviewBurndown ?? null
|
||||
const kmBurndownSnapshot = kmOwnerReviewBurndown?.current_snapshot ?? null
|
||||
const staleRatioLabel = typeof kmBurndownSnapshot?.stale_ratio === 'number'
|
||||
? `${(kmBurndownSnapshot.stale_ratio * 100).toFixed(1)}%`
|
||||
: automationBriefLoaded ? unavailableValue : loadingStatus
|
||||
const automationWorkToneStyle: Record<HomepageWorkTone, { bg: string; border: string; color: string }> = {
|
||||
live: { bg: '#f0faf2', border: '#9bc7a4', color: '#17602a' },
|
||||
progress: { bg: '#fff7e8', border: '#d9b36f', color: '#8a5a08' },
|
||||
@@ -986,6 +1188,21 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
owner: tDashboard('automationDiagrams.workspace.inspector.stages.signal.owner'),
|
||||
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.signal.evidence'),
|
||||
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.signal.nextAction'),
|
||||
liveEvidence: {
|
||||
metric: tDashboard('automationDiagrams.workspace.liveEvidence.signal.metric', {
|
||||
sources: formatAutomationNumber(dossierCoverageSummary?.source_count, automationBriefLoaded),
|
||||
refs: formatAutomationNumber(dossierCoverageSummary?.source_ref_total, automationBriefLoaded),
|
||||
}),
|
||||
detail: tDashboard('automationDiagrams.workspace.liveEvidence.signal.detail', {
|
||||
missing: formatAutomationNumber(dossierCoverageSummary?.missing_source_refs_total, automationBriefLoaded),
|
||||
duplicates: formatAutomationNumber(dossierCoverageSummary?.duplicate_total, automationBriefLoaded),
|
||||
alert: formatAutomationNumber(dossierCoverageSummary?.alert_ref_total, automationBriefLoaded),
|
||||
sentry: formatAutomationNumber(dossierCoverageSummary?.sentry_ref_total, automationBriefLoaded),
|
||||
signoz: formatAutomationNumber(dossierCoverageSummary?.signoz_ref_total, automationBriefLoaded),
|
||||
}),
|
||||
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.dossierCoverage'),
|
||||
updated: formatLiveEvidenceTime(dossierCoverageSummary?.latest_received_at),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'intake',
|
||||
@@ -997,6 +1214,20 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
owner: tDashboard('automationDiagrams.workspace.inspector.stages.intake.owner'),
|
||||
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.intake.evidence'),
|
||||
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.intake.nextAction'),
|
||||
liveEvidence: {
|
||||
metric: tDashboard('automationDiagrams.workspace.liveEvidence.intake.metric', {
|
||||
runs: formatAutomationNumber(automationBrief.runsList?.total, automationBriefLoaded),
|
||||
linked: formatAutomationNumber(recurrenceSummary?.linked_run_total, automationBriefLoaded),
|
||||
}),
|
||||
detail: tDashboard('automationDiagrams.workspace.liveEvidence.intake.detail', {
|
||||
stage: latestCicdEvent?.stage ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
|
||||
status: latestCicdEvent?.status ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
|
||||
commit: latestCicdEvent?.commit_sha?.slice(0, 7) ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
|
||||
attention: formatAutomationNumber(cicdNeedsAttention, automationBriefLoaded),
|
||||
}),
|
||||
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.runsAndCicd'),
|
||||
updated: formatLiveEvidenceTime(latestCicdEvent?.created_at ?? latestRun?.created_at),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'ai',
|
||||
@@ -1011,17 +1242,49 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
owner: tDashboard('automationDiagrams.workspace.inspector.stages.ai.owner'),
|
||||
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.ai.evidence'),
|
||||
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.ai.nextAction'),
|
||||
liveEvidence: {
|
||||
metric: tDashboard('automationDiagrams.workspace.liveEvidence.ai.metric', {
|
||||
lane: aiRouteLaneMode,
|
||||
provider: aiRouteSelectedProvider,
|
||||
}),
|
||||
detail: tDashboard('automationDiagrams.workspace.liveEvidence.ai.detail', {
|
||||
skipped: formatAutomationNumber(automationBrief.aiRouteStatus?.skipped_lanes?.length, automationBriefLoaded),
|
||||
action: automationBrief.aiRouteStatus?.operator_action?.action ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
|
||||
reason: automationBrief.aiRouteStatus?.operator_action?.reason ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
|
||||
}),
|
||||
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.aiRouteStatus'),
|
||||
updated: automationBriefLoaded ? tDashboard('automationDiagrams.workspace.liveEvidence.realtime') : loadingStatus,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'mcp',
|
||||
title: tDashboard('automationDiagrams.workspace.flow.stages.mcp'),
|
||||
status: tDashboard('automationDelivery.status.watching'),
|
||||
detail: tDashboard('automationDiagrams.cards.incidentFlow.detail'),
|
||||
status: runEvidenceSummary.mcpTotal > 0 || statusChainEvidenceSummary.mcpGateway > 0
|
||||
? tDashboard('automationDelivery.status.live')
|
||||
: tDashboard('automationDelivery.status.watching'),
|
||||
detail: tDashboard('automationDiagrams.workspace.liveEvidence.mcp.metric', {
|
||||
observations: formatAutomationNumber(runEvidenceSummary.mcpTotal, automationBriefLoaded),
|
||||
gateway: formatAutomationNumber(statusChainEvidenceSummary.mcpGateway, true),
|
||||
}),
|
||||
href: `/${locale}/awooop/runs?project_id=awoooi`,
|
||||
tone: 'watching',
|
||||
tone: runEvidenceSummary.mcpFailed > 0 ? 'progress' : (runEvidenceSummary.mcpTotal > 0 || statusChainEvidenceSummary.mcpGateway > 0) ? 'live' : 'watching',
|
||||
owner: tDashboard('automationDiagrams.workspace.inspector.stages.mcp.owner'),
|
||||
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.mcp.evidence'),
|
||||
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.mcp.nextAction'),
|
||||
liveEvidence: {
|
||||
metric: tDashboard('automationDiagrams.workspace.liveEvidence.mcp.metric', {
|
||||
observations: formatAutomationNumber(runEvidenceSummary.mcpTotal, automationBriefLoaded),
|
||||
gateway: formatAutomationNumber(statusChainEvidenceSummary.mcpGateway, true),
|
||||
}),
|
||||
detail: tDashboard('automationDiagrams.workspace.liveEvidence.mcp.detail', {
|
||||
success: formatAutomationNumber(runEvidenceSummary.mcpSuccess + statusChainEvidenceSummary.mcpGatewaySuccess, automationBriefLoaded),
|
||||
failed: formatAutomationNumber(runEvidenceSummary.mcpFailed + statusChainEvidenceSummary.mcpGatewayFailed, automationBriefLoaded),
|
||||
server: runEvidenceSummary.latestMcpServer ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
|
||||
route: runEvidenceSummary.latestRoute ?? (automationBriefLoaded ? unavailableValue : loadingStatus),
|
||||
}),
|
||||
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.runsAndStatusChain'),
|
||||
updated: formatLiveEvidenceTime(latestRun?.created_at),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'playbook',
|
||||
@@ -1036,6 +1299,19 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
owner: tDashboard('automationDiagrams.workspace.inspector.stages.playbook.owner'),
|
||||
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.playbook.evidence'),
|
||||
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.playbook.nextAction'),
|
||||
liveEvidence: {
|
||||
metric: tDashboard('automationDiagrams.workspace.liveEvidence.playbook.metric', {
|
||||
gate: topAutomationGate?.gate ?? (automationQualityLoaded ? unavailableValue : loadingStatus),
|
||||
gaps: formatAutomationNumber(recurrenceSummary?.automation_gap_group_total, automationBriefLoaded),
|
||||
}),
|
||||
detail: tDashboard('automationDiagrams.workspace.liveEvidence.playbook.detail', {
|
||||
workItems: formatAutomationNumber(recurrenceSummary?.open_work_item_group_total, automationBriefLoaded),
|
||||
verifiedGroups: formatAutomationNumber(recurrenceSummary?.verified_repair_group_total, automationBriefLoaded),
|
||||
linkedAutoRepair: formatAutomationNumber(recurrenceSummary?.auto_repair_linked_total, automationBriefLoaded),
|
||||
}),
|
||||
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.qualityAndRecurrence'),
|
||||
updated: formatLiveEvidenceTime(recurrenceSummary?.latest_received_at),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'ansible',
|
||||
@@ -1051,6 +1327,19 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
owner: tDashboard('automationDiagrams.workspace.inspector.stages.ansible.owner'),
|
||||
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.ansible.evidence'),
|
||||
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.ansible.nextAction'),
|
||||
liveEvidence: {
|
||||
metric: tDashboard('automationDiagrams.workspace.liveEvidence.ansible.metric', {
|
||||
checkMode: formatAutomationNumber(executionBackend?.ansible_check_mode_total, automationQualityLoaded),
|
||||
pending: formatAutomationNumber(executionBackend?.ansible_pending_check_mode_total, automationQualityLoaded),
|
||||
}),
|
||||
detail: tDashboard('automationDiagrams.workspace.liveEvidence.ansible.detail', {
|
||||
blocker: ansibleRuntime?.blockers?.join(' / ') || (automationQualityLoaded ? unavailableValue : loadingStatus),
|
||||
candidates: formatAutomationNumber(executionBackend?.ansible_candidate_total, automationQualityLoaded),
|
||||
operations: formatAutomationNumber(executionBackend?.operation_records_total, automationQualityLoaded),
|
||||
}),
|
||||
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.truthChainQuality'),
|
||||
updated: automationQualityLoaded ? tDashboard('automationDiagrams.workspace.liveEvidence.realtime') : loadingStatus,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'approval',
|
||||
@@ -1065,6 +1354,20 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
owner: tDashboard('automationDiagrams.workspace.inspector.stages.approval.owner'),
|
||||
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.approval.evidence'),
|
||||
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.approval.nextAction'),
|
||||
liveEvidence: {
|
||||
metric: tDashboard('automationDiagrams.workspace.liveEvidence.approval.metric', {
|
||||
pending: formatAutomationNumber(pendingApprovals, true),
|
||||
verified: formatAutomationNumber(verifiedAutomationTotal, automationQualityLoaded),
|
||||
evaluated: formatAutomationNumber(evaluatedAutomationTotal, automationQualityLoaded),
|
||||
}),
|
||||
detail: tDashboard('automationDiagrams.workspace.liveEvidence.approval.detail', {
|
||||
humanGates: formatAutomationNumber(runEvidenceSummary.humanGateRuns, automationBriefLoaded),
|
||||
autoRepairRecords: formatAutomationNumber(statusChainEvidenceSummary.autoRepairRecords, true),
|
||||
operations: formatAutomationNumber(statusChainEvidenceSummary.operationRecords, true),
|
||||
}),
|
||||
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.approvalsAndQuality'),
|
||||
updated: automationQualityLoaded ? tDashboard('automationDiagrams.workspace.liveEvidence.realtime') : loadingStatus,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'verify',
|
||||
@@ -1079,6 +1382,19 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
owner: tDashboard('automationDiagrams.workspace.inspector.stages.verify.owner'),
|
||||
evidence: tDashboard('automationDiagrams.workspace.inspector.stages.verify.evidence'),
|
||||
nextAction: tDashboard('automationDiagrams.workspace.inspector.stages.verify.nextAction'),
|
||||
liveEvidence: {
|
||||
metric: tDashboard('automationDiagrams.workspace.liveEvidence.verify.metric', {
|
||||
stale: formatAutomationNumber(kmBurndownSnapshot?.stale_count ?? kmStaleTotal, automationBriefLoaded),
|
||||
ratio: staleRatioLabel,
|
||||
}),
|
||||
detail: tDashboard('automationDiagrams.workspace.liveEvidence.verify.detail', {
|
||||
pending: formatAutomationNumber(kmOwnerReviewBurndown?.pending_owner_reviews, automationBriefLoaded),
|
||||
completed: formatAutomationNumber(kmOwnerReviewBurndown?.completed_owner_reviews, automationBriefLoaded),
|
||||
remaining: formatAutomationNumber(kmOwnerReviewBurndown?.entries_to_threshold, automationBriefLoaded),
|
||||
}),
|
||||
source: tDashboard('automationDiagrams.workspace.liveEvidence.sources.kmBurndown'),
|
||||
updated: formatLiveEvidenceTime(kmOwnerReviewBurndown?.generated_at),
|
||||
},
|
||||
},
|
||||
]
|
||||
const runtimeTopologyLayers = [
|
||||
@@ -1714,6 +2030,18 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
<div style={{ marginTop: 5, fontSize: 10, color: '#5f5b52', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{stage.detail}
|
||||
</div>
|
||||
<div style={{
|
||||
marginTop: 7,
|
||||
borderTop: `0.5px solid ${tone.border}`,
|
||||
paddingTop: 6,
|
||||
fontSize: 9,
|
||||
fontWeight: 800,
|
||||
color: tone.color,
|
||||
lineHeight: 1.35,
|
||||
overflowWrap: 'anywhere',
|
||||
}}>
|
||||
{stage.liveEvidence.metric}
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
@@ -1806,6 +2134,56 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{
|
||||
gridColumn: compactViewport ? 'auto' : '1 / -1',
|
||||
border: `0.5px solid ${selectedBlueprintTone.border}`,
|
||||
borderRadius: 8,
|
||||
background: '#fff',
|
||||
padding: '9px 10px',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
|
||||
<div style={{ fontSize: 10, fontWeight: 800, color: '#77736a', textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
||||
{tDashboard('automationDiagrams.workspace.liveEvidence.title')}
|
||||
</div>
|
||||
<span style={{
|
||||
border: `0.5px solid ${selectedBlueprintTone.border}`,
|
||||
background: selectedBlueprintTone.bg,
|
||||
color: selectedBlueprintTone.color,
|
||||
borderRadius: 999,
|
||||
padding: '2px 7px',
|
||||
fontSize: 9,
|
||||
fontWeight: 800,
|
||||
}}>
|
||||
{selectedBlueprintStage.liveEvidence.updated}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{
|
||||
marginTop: 8,
|
||||
display: 'grid',
|
||||
gridTemplateColumns: compactViewport ? '1fr' : 'repeat(auto-fit, minmax(170px, 1fr))',
|
||||
gap: 8,
|
||||
}}>
|
||||
{[
|
||||
[tDashboard('automationDiagrams.workspace.liveEvidence.fields.metric'), selectedBlueprintStage.liveEvidence.metric],
|
||||
[tDashboard('automationDiagrams.workspace.liveEvidence.fields.detail'), selectedBlueprintStage.liveEvidence.detail],
|
||||
[tDashboard('automationDiagrams.workspace.liveEvidence.fields.source'), selectedBlueprintStage.liveEvidence.source],
|
||||
].map(([label, value]) => (
|
||||
<div key={label} style={{
|
||||
border: '0.5px solid #e0ddd4',
|
||||
borderRadius: 7,
|
||||
background: '#fbfaf6',
|
||||
padding: '8px 9px',
|
||||
}}>
|
||||
<div style={{ fontSize: 9, fontWeight: 800, color: '#77736a', textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
||||
{label}
|
||||
</div>
|
||||
<div style={{ marginTop: 5, fontSize: 11, fontWeight: 700, color: '#2e2b26', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{value}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user