feat(web): add homepage operations map
This commit is contained in:
@@ -1879,6 +1879,60 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
}),
|
||||
},
|
||||
]
|
||||
const productModuleRows: Array<{
|
||||
key: string
|
||||
module: string
|
||||
owns: string
|
||||
evidence: string
|
||||
href: string
|
||||
tone: HomepageWorkTone
|
||||
}> = [
|
||||
{
|
||||
key: 'awooop',
|
||||
module: tDashboard('homeProductMap.modules.awooop.module'),
|
||||
owns: tDashboard('homeProductMap.modules.awooop.owns'),
|
||||
evidence: tDashboard('automationDiagrams.workspace.liveEvidence.intake.metric', {
|
||||
runs: formatAutomationNumber(automationBrief.runsList?.total, runsListLoaded),
|
||||
linked: formatAutomationNumber(recurrenceSummary?.linked_run_total, eventRecurrenceLoaded),
|
||||
}),
|
||||
href: `/${locale}/awooop/runs?project_id=awoooi`,
|
||||
tone: 'live',
|
||||
},
|
||||
{
|
||||
key: 'routing',
|
||||
module: tDashboard('homeProductMap.modules.routing.module'),
|
||||
owns: tDashboard('homeProductMap.modules.routing.owns'),
|
||||
evidence: tDashboard('automationDiagrams.workspace.values.aiRoute', {
|
||||
lane: aiRouteLaneMode,
|
||||
provider: aiRouteSelectedProvider,
|
||||
}),
|
||||
href: `/${locale}/awooop/work-items?project_id=awoooi`,
|
||||
tone: hasAiRouteStatus ? 'live' : 'watching',
|
||||
},
|
||||
{
|
||||
key: 'execution',
|
||||
module: tDashboard('homeProductMap.modules.execution.module'),
|
||||
owns: tDashboard('homeProductMap.modules.execution.owns'),
|
||||
evidence: tDashboard('automationDiagrams.workspace.values.ansible', {
|
||||
checkMode: formatAutomationNumber(executionBackend?.ansible_check_mode_total, automationQualityLoaded),
|
||||
pending: formatAutomationNumber(executionBackend?.ansible_pending_check_mode_total, automationQualityLoaded),
|
||||
blocker: ansibleRuntimeBlockerLabel,
|
||||
}),
|
||||
href: `/${locale}/automation`,
|
||||
tone: ansibleRuntime?.can_run_check_mode ? 'live' : automationQualityAvailable ? 'blocked' : 'watching',
|
||||
},
|
||||
{
|
||||
key: 'learning',
|
||||
module: tDashboard('homeProductMap.modules.learning.module'),
|
||||
owns: tDashboard('homeProductMap.modules.learning.owns'),
|
||||
evidence: tDashboard('automationDiagrams.workspace.liveEvidence.verify.metric', {
|
||||
stale: formatAutomationNumber(kmStaleDisplayCount, kmGovernanceLoaded),
|
||||
ratio: staleRatioLabel,
|
||||
}),
|
||||
href: `/${locale}/knowledge-base`,
|
||||
tone: typeof kmStaleDisplayCount === 'number' && kmStaleDisplayCount > 0 ? 'progress' : hasKmStaleCandidates ? 'live' : 'watching',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<AppLayout locale={locale} showBackground={false} fullBleed>
|
||||
@@ -1894,14 +1948,173 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: 'calc(100vh - 68px)',
|
||||
minHeight: 'calc(100vh - 68px)',
|
||||
background: '#f5f4ed',
|
||||
fontFamily: 'var(--font-body), monospace',
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'auto',
|
||||
overscrollBehavior: 'contain',
|
||||
overflowY: 'visible',
|
||||
}}>
|
||||
|
||||
<section
|
||||
data-testid="homepage-product-map"
|
||||
style={{
|
||||
margin: primarySectionMargin,
|
||||
background: '#fff',
|
||||
borderTop: '0.5px solid #d8d3c7',
|
||||
borderBottom: '0.5px solid #d8d3c7',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: compactViewport ? '1fr' : 'minmax(0, 1.4fr) minmax(260px, 0.6fr)',
|
||||
gap: compactViewport ? 10 : 18,
|
||||
padding: compactViewport ? '14px 12px' : '18px 20px',
|
||||
borderBottom: '0.5px solid #e0ddd4',
|
||||
background: '#fffdf8',
|
||||
}}>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 850, color: '#1f5b9b', textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
||||
{tDashboard('homeProductMap.eyebrow')}
|
||||
</div>
|
||||
<h1 style={{ margin: '5px 0 0', fontSize: compactViewport ? 22 : 28, lineHeight: 1.1, fontWeight: 900, color: '#141413' }}>
|
||||
{tDashboard('homeProductMap.title')}
|
||||
</h1>
|
||||
<p style={{ margin: '8px 0 0', maxWidth: 820, fontSize: 13, lineHeight: 1.65, color: '#4e4a43' }}>
|
||||
{tDashboard('homeProductMap.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
<div style={{
|
||||
alignSelf: 'stretch',
|
||||
display: 'grid',
|
||||
gap: 8,
|
||||
border: `0.5px solid ${automationDeliveryClaimTone.border}`,
|
||||
background: automationDeliveryClaimTone.bg,
|
||||
padding: '11px 12px',
|
||||
}}>
|
||||
<div style={{ fontSize: 10, fontWeight: 850, color: automationDeliveryClaimTone.color, textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
||||
{tDashboard('homeProductMap.currentGate')}
|
||||
</div>
|
||||
<div style={{ fontSize: 16, fontWeight: 900, color: '#141413', lineHeight: 1.2 }}>
|
||||
{automationDeliveryHeadline}
|
||||
</div>
|
||||
<div style={{ fontSize: 11, lineHeight: 1.5, color: '#5f5b52' }}>
|
||||
{automationDeliveryClaimDetail}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: compactViewport ? '1fr' : 'minmax(0, 1.35fr) minmax(320px, 0.85fr)',
|
||||
gap: compactViewport ? 10 : 14,
|
||||
padding: compactViewport ? 12 : 16,
|
||||
}}>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10, marginBottom: 10 }}>
|
||||
<h2 style={{ margin: 0, fontSize: 15, fontWeight: 900, color: '#141413' }}>
|
||||
{tDashboard('homeProductMap.flowTitle')}
|
||||
</h2>
|
||||
<a href={`/${locale}/awooop/runs?project_id=awoooi`} style={{ fontSize: 11, fontWeight: 800, color: '#1f5b9b', textDecoration: 'none' }}>
|
||||
{tDashboard('automationDelivery.openRuns')}
|
||||
</a>
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: compactViewport ? '1fr' : 'repeat(4, minmax(0, 1fr))',
|
||||
gap: 8,
|
||||
}}>
|
||||
{automationFlowStages.map((stage, index) => {
|
||||
const tone = automationWorkToneStyle[stage.tone]
|
||||
return (
|
||||
<a
|
||||
key={`product-map-${stage.key}`}
|
||||
href={`/${locale}?blueprint_stage=${stage.key}#homepage-ai-command-map`}
|
||||
onClick={() => setSelectedBlueprintStageKey(stage.key as HomepageBlueprintStageKey)}
|
||||
style={{
|
||||
minWidth: 0,
|
||||
border: `0.5px solid ${tone.border}`,
|
||||
background: tone.bg,
|
||||
padding: '9px 10px',
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
|
||||
<span style={{ fontSize: 10, fontWeight: 900, color: tone.color, fontFamily: "'JetBrains Mono', monospace" }}>
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</span>
|
||||
<span style={{ fontSize: 9, fontWeight: 850, color: tone.color, textTransform: 'uppercase', whiteSpace: 'nowrap' }}>
|
||||
{stage.status}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ marginTop: 8, fontSize: 12, fontWeight: 900, color: '#141413', lineHeight: 1.25 }}>
|
||||
{stage.title}
|
||||
</div>
|
||||
<div style={{ marginTop: 6, fontSize: 10, lineHeight: 1.45, color: '#5f5b52', minHeight: 30, overflow: 'hidden' }}>
|
||||
{stage.liveEvidence.metric}
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10, marginBottom: 10 }}>
|
||||
<h2 style={{ margin: 0, fontSize: 15, fontWeight: 900, color: '#141413' }}>
|
||||
{tDashboard('homeProductMap.moduleTitle')}
|
||||
</h2>
|
||||
<a href={`/${locale}/topology`} style={{ fontSize: 11, fontWeight: 800, color: '#1f5b9b', textDecoration: 'none' }}>
|
||||
{tDashboard('automationDiagrams.openTopology')}
|
||||
</a>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gap: 1, border: '0.5px solid #e0ddd4', background: '#e0ddd4' }}>
|
||||
{productModuleRows.map((row) => {
|
||||
const tone = automationWorkToneStyle[row.tone]
|
||||
return (
|
||||
<a
|
||||
key={row.key}
|
||||
href={row.href}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: compactViewport ? '1fr' : '0.72fr 1fr',
|
||||
gap: 8,
|
||||
padding: '9px 10px',
|
||||
background: '#fff',
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontSize: 12, fontWeight: 900, color: '#141413' }}>{row.module}</div>
|
||||
<div style={{ marginTop: 3, fontSize: 10, lineHeight: 1.4, color: '#5f5b52' }}>{row.owns}</div>
|
||||
</div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{
|
||||
display: 'inline-flex',
|
||||
maxWidth: '100%',
|
||||
border: `0.5px solid ${tone.border}`,
|
||||
background: tone.bg,
|
||||
color: tone.color,
|
||||
padding: '2px 7px',
|
||||
fontSize: 10,
|
||||
fontWeight: 850,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}>
|
||||
{row.evidence}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style={{
|
||||
margin: primarySectionMargin,
|
||||
background: '#fff',
|
||||
|
||||
Reference in New Issue
Block a user