feat(web): 視覺化治理自動化盤點首屏
This commit is contained in:
@@ -3524,6 +3524,69 @@
|
||||
"cd_login_banner_observed_os_only": "僅 CD banner 觀察"
|
||||
}
|
||||
},
|
||||
"visualOps": {
|
||||
"title": "自動化戰情視覺總覽",
|
||||
"subtitle": "{current} → {next};自動化 {overall}%;決策支援 {support}%。先看流程與 Gate,再下鑽證據。",
|
||||
"flowTitle": "AI 自動化流程",
|
||||
"flowBadge": "{stages} 階段",
|
||||
"gateTitle": "Gate / 阻塞矩陣",
|
||||
"weighted": "決策支援 {value}%",
|
||||
"focusTitle": "本輪優先焦點",
|
||||
"focusDetail": "先補 Runner {runner}、Live 證據 {evidence}、修復候選複核 {candidate}、人工批准任務 {approval};未通過前不開 runtime gate。",
|
||||
"badges": {
|
||||
"readOnly": "只讀營運台",
|
||||
"runtimeGate": "正式寫入 {value}",
|
||||
"approval": "需批准 {value}"
|
||||
},
|
||||
"stages": {
|
||||
"intake": {
|
||||
"label": "告警 / 服務收件",
|
||||
"detail": "健康目標中 {actions} 個仍需處置。"
|
||||
},
|
||||
"evidence": {
|
||||
"label": "MCP / 來源證據",
|
||||
"detail": "仍缺 {missing} 個 live evidence 綁定。"
|
||||
},
|
||||
"candidate": {
|
||||
"label": "修復候選",
|
||||
"detail": "{review} 個待 owner 複核;{blocked} 個被 allowlist / policy 阻擋。"
|
||||
},
|
||||
"approval": {
|
||||
"label": "人工 Gate",
|
||||
"detail": "共 {total} 個任務邊界,未批准不會執行。"
|
||||
},
|
||||
"verifier": {
|
||||
"label": "執行讀回 / Verifier",
|
||||
"detail": "{blocked} 個 release readback 仍阻擋。"
|
||||
},
|
||||
"learning": {
|
||||
"label": "KM / PlayBook 學習",
|
||||
"detail": "{gates} 個 learning gate 等 owner review。"
|
||||
}
|
||||
},
|
||||
"gates": {
|
||||
"notification": {
|
||||
"label": "Failure-only 通知",
|
||||
"detail": "成功降噪 {quiet};需處置 {action};立即升級 {escalation}。"
|
||||
},
|
||||
"repairCandidate": {
|
||||
"label": "修復候選完整度",
|
||||
"detail": "待 owner review {review};verifier plan {verifier}。"
|
||||
},
|
||||
"approval": {
|
||||
"label": "批准邊界",
|
||||
"detail": "{blocked} 個批准項仍是明確 false。"
|
||||
},
|
||||
"execution": {
|
||||
"label": "正式執行寫入",
|
||||
"detail": "讀回 {readback};阻擋 {blocked};0 代表尚未開 live write。"
|
||||
},
|
||||
"learning": {
|
||||
"label": "PlayBook / KM 回寫",
|
||||
"detail": "候選 {candidates};learning gate {gates}。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"overview": {
|
||||
"title": "決策指揮摘要",
|
||||
"mode": "只讀決策支援",
|
||||
|
||||
@@ -3524,6 +3524,69 @@
|
||||
"cd_login_banner_observed_os_only": "僅 CD banner 觀察"
|
||||
}
|
||||
},
|
||||
"visualOps": {
|
||||
"title": "自動化戰情視覺總覽",
|
||||
"subtitle": "{current} → {next};自動化 {overall}%;決策支援 {support}%。先看流程與 Gate,再下鑽證據。",
|
||||
"flowTitle": "AI 自動化流程",
|
||||
"flowBadge": "{stages} 階段",
|
||||
"gateTitle": "Gate / 阻塞矩陣",
|
||||
"weighted": "決策支援 {value}%",
|
||||
"focusTitle": "本輪優先焦點",
|
||||
"focusDetail": "先補 Runner {runner}、Live 證據 {evidence}、修復候選複核 {candidate}、人工批准任務 {approval};未通過前不開 runtime gate。",
|
||||
"badges": {
|
||||
"readOnly": "只讀營運台",
|
||||
"runtimeGate": "正式寫入 {value}",
|
||||
"approval": "需批准 {value}"
|
||||
},
|
||||
"stages": {
|
||||
"intake": {
|
||||
"label": "告警 / 服務收件",
|
||||
"detail": "健康目標中 {actions} 個仍需處置。"
|
||||
},
|
||||
"evidence": {
|
||||
"label": "MCP / 來源證據",
|
||||
"detail": "仍缺 {missing} 個 live evidence 綁定。"
|
||||
},
|
||||
"candidate": {
|
||||
"label": "修復候選",
|
||||
"detail": "{review} 個待 owner 複核;{blocked} 個被 allowlist / policy 阻擋。"
|
||||
},
|
||||
"approval": {
|
||||
"label": "人工 Gate",
|
||||
"detail": "共 {total} 個任務邊界,未批准不會執行。"
|
||||
},
|
||||
"verifier": {
|
||||
"label": "執行讀回 / Verifier",
|
||||
"detail": "{blocked} 個 release readback 仍阻擋。"
|
||||
},
|
||||
"learning": {
|
||||
"label": "KM / PlayBook 學習",
|
||||
"detail": "{gates} 個 learning gate 等 owner review。"
|
||||
}
|
||||
},
|
||||
"gates": {
|
||||
"notification": {
|
||||
"label": "Failure-only 通知",
|
||||
"detail": "成功降噪 {quiet};需處置 {action};立即升級 {escalation}。"
|
||||
},
|
||||
"repairCandidate": {
|
||||
"label": "修復候選完整度",
|
||||
"detail": "待 owner review {review};verifier plan {verifier}。"
|
||||
},
|
||||
"approval": {
|
||||
"label": "批准邊界",
|
||||
"detail": "{blocked} 個批准項仍是明確 false。"
|
||||
},
|
||||
"execution": {
|
||||
"label": "正式執行寫入",
|
||||
"detail": "讀回 {readback};阻擋 {blocked};0 代表尚未開 live write。"
|
||||
},
|
||||
"learning": {
|
||||
"label": "PlayBook / KM 回寫",
|
||||
"detail": "候選 {candidates};learning gate {gates}。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"overview": {
|
||||
"title": "決策指揮摘要",
|
||||
"mode": "只讀決策支援",
|
||||
|
||||
@@ -454,6 +454,105 @@ function SummaryTile({
|
||||
)
|
||||
}
|
||||
|
||||
function FlowStageTile({
|
||||
index,
|
||||
label,
|
||||
value,
|
||||
detail,
|
||||
icon,
|
||||
tone = 'neutral',
|
||||
}: {
|
||||
index: string
|
||||
label: string
|
||||
value: string
|
||||
detail: string
|
||||
icon: ReactNode
|
||||
tone?: 'ok' | 'warn' | 'danger' | 'neutral'
|
||||
}) {
|
||||
const color = toneColor(tone)
|
||||
return (
|
||||
<div style={{
|
||||
position: 'relative',
|
||||
padding: 12,
|
||||
border: `0.5px solid ${color}45`,
|
||||
borderRadius: 7,
|
||||
background: '#fff',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '34px minmax(0, 1fr)',
|
||||
gap: 10,
|
||||
alignItems: 'start',
|
||||
minWidth: 0,
|
||||
}}>
|
||||
<div style={{
|
||||
width: 34,
|
||||
height: 34,
|
||||
borderRadius: 8,
|
||||
border: `0.5px solid ${color}55`,
|
||||
background: `${color}12`,
|
||||
color,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
{icon}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 5, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
|
||||
{label}
|
||||
</span>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color, fontWeight: 800, whiteSpace: 'nowrap' }}>
|
||||
{index}
|
||||
</span>
|
||||
</div>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 24, fontWeight: 750, color, lineHeight: 1 }}>
|
||||
{value}
|
||||
</span>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#6d6a61', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{detail}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function GateMatrixRow({
|
||||
label,
|
||||
value,
|
||||
detail,
|
||||
tone = 'neutral',
|
||||
}: {
|
||||
label: string
|
||||
value: string
|
||||
detail: string
|
||||
tone?: 'ok' | 'warn' | 'danger' | 'neutral'
|
||||
}) {
|
||||
const color = toneColor(tone)
|
||||
return (
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'minmax(0, 1fr) 64px',
|
||||
gap: 10,
|
||||
alignItems: 'center',
|
||||
padding: '9px 0',
|
||||
borderBottom: '0.5px solid #eee9dd',
|
||||
minWidth: 0,
|
||||
}}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflowWrap: 'anywhere' }}>
|
||||
{label}
|
||||
</span>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
|
||||
{detail}
|
||||
</span>
|
||||
</div>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 13, fontWeight: 800, color, textAlign: 'right', whiteSpace: 'nowrap' }}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function AutomationInventoryTab() {
|
||||
const t = useTranslations('governance.automationInventory')
|
||||
const [snapshot, setSnapshot] = useState<AiAgentAutomationInventorySnapshot | null>(null)
|
||||
@@ -2155,6 +2254,7 @@ export function AutomationInventoryTab() {
|
||||
const runtimeSecrets = runtimeSurface?.rollups.secret_surface_ids.length ?? 0
|
||||
const runtimeLiveMissing = runtimeSurface?.rollups.live_check_missing_surface_ids.length ?? 0
|
||||
const runtimeBoundComponents = runtimeSurface?.rollups.source_components_with_runtime_binding ?? 0
|
||||
const runtimeSurfaceComponentTotal = runtimeSurface?.rollups.total_source_components ?? 0
|
||||
const giteaRunnerActions = giteaHealth.rollups.workflow_ids_requiring_runner_attestation.length
|
||||
const giteaQuietPolicies = giteaHealth.rollups.notification_contracts_quiet_success_count
|
||||
const observabilityActions = observabilityMatrix.rollups.surface_ids_requiring_action.length
|
||||
@@ -3813,6 +3913,122 @@ export function AutomationInventoryTab() {
|
||||
const blockedApprovals = Object.entries(snapshot.approval_boundaries)
|
||||
.filter(([, allowed]) => allowed === false)
|
||||
.map(([key]) => key)
|
||||
const visualFlowStages: Array<{
|
||||
key: string
|
||||
label: string
|
||||
value: string
|
||||
detail: string
|
||||
tone: 'ok' | 'warn' | 'danger' | 'neutral'
|
||||
icon: ReactNode
|
||||
}> = [
|
||||
{
|
||||
key: 'intake',
|
||||
label: t('visualOps.stages.intake.label'),
|
||||
value: String(serviceHealthGapMatrix.rollups.total_targets),
|
||||
detail: t('visualOps.stages.intake.detail', { actions: serviceHealthActions }),
|
||||
tone: serviceHealthActions > 0 ? 'warn' : 'ok',
|
||||
icon: <BellRing size={16} />,
|
||||
},
|
||||
{
|
||||
key: 'evidence',
|
||||
label: t('visualOps.stages.evidence.label'),
|
||||
value: `${runtimeBoundComponents}/${runtimeSurfaceComponentTotal}`,
|
||||
detail: t('visualOps.stages.evidence.detail', { missing: runtimeLiveMissing }),
|
||||
tone: runtimeLiveMissing > 0 ? 'warn' : 'ok',
|
||||
icon: <Fingerprint size={16} />,
|
||||
},
|
||||
{
|
||||
key: 'candidate',
|
||||
label: t('visualOps.stages.candidate.label'),
|
||||
value: `${candidateDryRunEvidenceCount}/${candidateDryRunCount}`,
|
||||
detail: t('visualOps.stages.candidate.detail', {
|
||||
review: candidateDryRunNeedsReview,
|
||||
blocked: candidateDryRunAllowlistBlocked + candidateDryRunPolicyBlocked,
|
||||
}),
|
||||
tone: candidateDryRunNeedsReview > 0 || candidateDryRunPolicyBlocked > 0 ? 'warn' : 'ok',
|
||||
icon: <ClipboardCheck size={16} />,
|
||||
},
|
||||
{
|
||||
key: 'approval',
|
||||
label: t('visualOps.stages.approval.label'),
|
||||
value: String(explicitApprovalTaskCount),
|
||||
detail: t('visualOps.stages.approval.detail', { total: taskBoundaryCount }),
|
||||
tone: explicitApprovalTaskCount > 0 ? 'warn' : 'ok',
|
||||
icon: <Lock size={16} />,
|
||||
},
|
||||
{
|
||||
key: 'verifier',
|
||||
label: t('visualOps.stages.verifier.label'),
|
||||
value: `${resultCaptureReleaseReadbacks}`,
|
||||
detail: t('visualOps.stages.verifier.detail', { blocked: resultCaptureReleaseReadbackBlocked }),
|
||||
tone: resultCaptureReleaseReadbackBlocked > 0 ? 'warn' : 'ok',
|
||||
icon: <ShieldCheck size={16} />,
|
||||
},
|
||||
{
|
||||
key: 'learning',
|
||||
label: t('visualOps.stages.learning.label'),
|
||||
value: `${learningWritebackLiveWrites}/${learningWritebackLanes}`,
|
||||
detail: t('visualOps.stages.learning.detail', { gates: learningWritebackApprovals }),
|
||||
tone: learningWritebackApprovals > 0 ? 'warn' : 'ok',
|
||||
icon: <BookOpenCheck size={16} />,
|
||||
},
|
||||
]
|
||||
const visualGateRows: Array<{
|
||||
key: string
|
||||
label: string
|
||||
value: string
|
||||
detail: string
|
||||
tone: 'ok' | 'warn' | 'danger' | 'neutral'
|
||||
}> = [
|
||||
{
|
||||
key: 'notification',
|
||||
label: t('visualOps.gates.notification.label'),
|
||||
value: String(serviceHealthNotificationAllowedCount),
|
||||
detail: t('visualOps.gates.notification.detail', {
|
||||
quiet: serviceHealthSuppressedSuccess,
|
||||
action: serviceHealthActionRequiredRules,
|
||||
escalation: serviceHealthImmediateEscalations,
|
||||
}),
|
||||
tone: serviceHealthNotificationAllowedCount === 0 ? 'ok' : 'danger',
|
||||
},
|
||||
{
|
||||
key: 'repairCandidate',
|
||||
label: t('visualOps.gates.repairCandidate.label'),
|
||||
value: `${candidateDryRunEvidenceCount}/${candidateDryRunCount}`,
|
||||
detail: t('visualOps.gates.repairCandidate.detail', {
|
||||
review: candidateDryRunNeedsReview,
|
||||
verifier: candidateDryRunVerifierPlans,
|
||||
}),
|
||||
tone: candidateDryRunEvidenceCount < candidateDryRunCount ? 'warn' : 'ok',
|
||||
},
|
||||
{
|
||||
key: 'approval',
|
||||
label: t('visualOps.gates.approval.label'),
|
||||
value: `${explicitApprovalTaskCount}/${taskBoundaryCount}`,
|
||||
detail: t('visualOps.gates.approval.detail', { blocked: blockedApprovals.length }),
|
||||
tone: explicitApprovalTaskCount > 0 ? 'warn' : 'ok',
|
||||
},
|
||||
{
|
||||
key: 'execution',
|
||||
label: t('visualOps.gates.execution.label'),
|
||||
value: String(resultCaptureReleaseReadbackLiveWrites),
|
||||
detail: t('visualOps.gates.execution.detail', {
|
||||
readback: resultCaptureReleaseReadbacks,
|
||||
blocked: resultCaptureReleaseReadbackBlocked,
|
||||
}),
|
||||
tone: resultCaptureReleaseReadbackLiveWrites === 0 ? 'ok' : 'danger',
|
||||
},
|
||||
{
|
||||
key: 'learning',
|
||||
label: t('visualOps.gates.learning.label'),
|
||||
value: String(matchedPlaybookUpdated),
|
||||
detail: t('visualOps.gates.learning.detail', {
|
||||
candidates: matchedPlaybookCandidates,
|
||||
gates: matchedPlaybookGates,
|
||||
}),
|
||||
tone: matchedPlaybookGates > 0 ? 'warn' : 'ok',
|
||||
},
|
||||
]
|
||||
|
||||
const statusLabel = (value: string) => {
|
||||
try {
|
||||
@@ -4016,6 +4232,121 @@ export function AutomationInventoryTab() {
|
||||
]}
|
||||
/>
|
||||
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, minWidth: 0 }}>
|
||||
<div style={{
|
||||
width: 38,
|
||||
height: 38,
|
||||
borderRadius: 8,
|
||||
border: '0.5px solid #2563eb40',
|
||||
background: 'rgba(37,99,235,0.08)',
|
||||
color: '#2563eb',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<Gauge size={18} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 5, minWidth: 0 }}>
|
||||
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 18, fontWeight: 760, color: '#141413', lineHeight: 1.15, overflowWrap: 'anywhere' }}>
|
||||
{t('visualOps.title')}
|
||||
</span>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, color: '#5c5a55', lineHeight: 1.55, overflowWrap: 'anywhere' }}>
|
||||
{t('visualOps.subtitle', {
|
||||
current: snapshot.program_status.current_task_id,
|
||||
next: snapshot.program_status.next_task_id,
|
||||
overall: backlogProgressPercent,
|
||||
support: decisionSupportCoverage,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'flex-end', gap: 6, minWidth: 0 }}>
|
||||
<Chip value={t('visualOps.badges.readOnly')} muted />
|
||||
<Chip value={t('visualOps.badges.runtimeGate', { value: resultCaptureReleaseReadbackLiveWrites })} />
|
||||
<Chip value={t('visualOps.badges.approval', { value: explicitApprovalTaskCount })} muted={explicitApprovalTaskCount === 0} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.35fr) minmax(280px, 0.65fr)', gap: 12 }} className="automation-inventory-visual-grid">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
|
||||
<SmallLabel>{t('visualOps.flowTitle')}</SmallLabel>
|
||||
<Chip value={t('visualOps.flowBadge', { stages: visualFlowStages.length })} muted />
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-stage-grid">
|
||||
{visualFlowStages.map((stage, index) => (
|
||||
<FlowStageTile
|
||||
key={stage.key}
|
||||
index={String(index + 1).padStart(2, '0')}
|
||||
label={stage.label}
|
||||
value={stage.value}
|
||||
detail={stage.detail}
|
||||
icon={stage.icon}
|
||||
tone={stage.tone}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
|
||||
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 4, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
|
||||
<SmallLabel>{t('visualOps.gateTitle')}</SmallLabel>
|
||||
<Chip value={t('visualOps.weighted', { value: decisionSupportCoverage })} muted />
|
||||
</div>
|
||||
{visualGateRows.map(row => (
|
||||
<GateMatrixRow
|
||||
key={row.key}
|
||||
label={row.label}
|
||||
value={row.value}
|
||||
detail={row.detail}
|
||||
tone={row.tone}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ padding: 12, border: `0.5px solid ${toneColor(decisionCoverageTone)}55`, borderRadius: 7, background: '#faf9f3', display: 'flex', flexDirection: 'column', gap: 9, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
|
||||
<SmallLabel>{t('visualOps.focusTitle')}</SmallLabel>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 18, fontWeight: 800, color: toneColor(decisionCoverageTone), whiteSpace: 'nowrap' }}>
|
||||
{decisionSupportCoverage}%
|
||||
</span>
|
||||
</div>
|
||||
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#6d6a61', lineHeight: 1.55, overflowWrap: 'anywhere' }}>
|
||||
{t('visualOps.focusDetail', {
|
||||
runner: giteaRunnerActions,
|
||||
evidence: runtimeLiveMissing,
|
||||
candidate: candidateDryRunNeedsReview,
|
||||
approval: explicitApprovalTaskCount,
|
||||
})}
|
||||
</span>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 8 }} className="automation-inventory-visual-factor-grid">
|
||||
<MiniBar
|
||||
label={t('decisionSupport.factors.taskCompletion')}
|
||||
value={completedTaskCoverage}
|
||||
detail={t('decisionSupport.details.taskCompletion', { done: completedTasks, total: snapshot.tasks.length })}
|
||||
tone={completedTaskCoverage >= 70 ? 'ok' : completedTaskCoverage >= 45 ? 'warn' : 'danger'}
|
||||
/>
|
||||
<MiniBar
|
||||
label={t('decisionSupport.factors.runtimeBinding')}
|
||||
value={runtimeBindingCoverage}
|
||||
detail={t('decisionSupport.details.runtimeBinding', {
|
||||
bound: runtimeBoundComponents,
|
||||
total: runtimeSurfaceComponentTotal,
|
||||
})}
|
||||
tone={runtimeBindingCoverage >= 80 ? 'ok' : runtimeBindingCoverage >= 50 ? 'warn' : 'danger'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
|
||||
@@ -15387,6 +15718,9 @@ export function AutomationInventoryTab() {
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.automation-inventory-visual-grid,
|
||||
.automation-inventory-stage-grid,
|
||||
.automation-inventory-visual-factor-grid,
|
||||
.automation-inventory-kpi-grid,
|
||||
.automation-inventory-command-grid,
|
||||
.automation-inventory-summary-grid,
|
||||
|
||||
Reference in New Issue
Block a user