Show live repair progress on requests
This commit is contained in:
@@ -1992,6 +1992,7 @@ button:disabled,
|
||||
}
|
||||
|
||||
.request-overview,
|
||||
.request-repair-activity,
|
||||
.request-journey,
|
||||
.request-advanced {
|
||||
border: 1px solid var(--ops-line);
|
||||
@@ -2005,6 +2006,63 @@ button:disabled,
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.request-repair-activity {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
padding: 18px 20px;
|
||||
border-color: rgba(126, 215, 255, 0.3);
|
||||
background:
|
||||
radial-gradient(circle at 6% 0%, rgba(14, 165, 233, 0.13), transparent 34%),
|
||||
rgba(255, 255, 255, 0.018);
|
||||
}
|
||||
.request-repair-activity.is-complete { border-color: rgba(72, 224, 178, 0.34); }
|
||||
.request-repair-activity.is-attention { border-color: rgba(255, 141, 157, 0.4); }
|
||||
.request-repair-heading {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
}
|
||||
.request-repair-heading h2 { margin: 0; font-size: clamp(1.15rem, 2vw, 1.55rem); }
|
||||
.request-repair-heading p { max-width: 88ch; margin: 7px 0 0; color: var(--ops-muted); line-height: 1.5; }
|
||||
.request-repair-meta { display: grid; justify-items: end; gap: 7px; color: var(--ops-muted); }
|
||||
.request-repair-meta small { white-space: nowrap; font-size: 0.7rem; }
|
||||
.request-repair-steps {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 9px;
|
||||
}
|
||||
.request-repair-step {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
align-items: start;
|
||||
gap: 9px;
|
||||
min-width: 0;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--ops-line-soft);
|
||||
border-radius: var(--ops-radius);
|
||||
background: rgba(255, 255, 255, 0.022);
|
||||
}
|
||||
.request-repair-step > i {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-top: 3px;
|
||||
border: 2px solid currentColor;
|
||||
border-radius: 50%;
|
||||
color: var(--ops-muted);
|
||||
}
|
||||
.request-repair-step.is-complete > i { color: var(--request-green); background: currentColor; }
|
||||
.request-repair-step.is-active > i {
|
||||
color: var(--request-cyan);
|
||||
box-shadow: 0 0 12px currentColor;
|
||||
animation: request-operation-pulse 1.15s ease-in-out infinite;
|
||||
}
|
||||
.request-repair-step.is-attention > i { color: var(--request-red); background: currentColor; }
|
||||
.request-repair-step > div { display: grid; gap: 4px; min-width: 0; }
|
||||
.request-repair-step strong { color: var(--ops-text); font-size: 0.76rem; }
|
||||
.request-repair-step span { color: var(--ops-muted); font-size: 0.72rem; line-height: 1.4; }
|
||||
|
||||
.request-overview-block {
|
||||
grid-column: span 6;
|
||||
display: grid;
|
||||
@@ -2288,6 +2346,7 @@ button:disabled,
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.request-stage { grid-column: span 6; }
|
||||
.request-repair-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
@@ -2309,6 +2368,9 @@ button:disabled,
|
||||
.request-action-row button,
|
||||
.request-release button { width: 100%; }
|
||||
.request-operation-heading { align-items: flex-start; flex-direction: column; }
|
||||
.request-repair-heading { flex-direction: column; }
|
||||
.request-repair-meta { justify-items: start; }
|
||||
.request-repair-steps { grid-template-columns: 1fr; }
|
||||
.request-operation-heading-actions { width: 100%; flex-wrap: wrap; }
|
||||
.request-operation-event { align-items: flex-start; }
|
||||
}
|
||||
|
||||
@@ -36,6 +36,24 @@ type PipelineStage = {
|
||||
link?: string | null
|
||||
}
|
||||
|
||||
type RepairActivityStep = {
|
||||
id: string
|
||||
label: string
|
||||
state: 'complete' | 'active' | 'attention' | 'waiting' | string
|
||||
detail: string
|
||||
}
|
||||
|
||||
type RepairActivity = {
|
||||
visible?: boolean
|
||||
actionId?: string
|
||||
state?: 'searching' | 'downloading' | 'importing' | 'indexing' | 'complete' | 'attention' | string
|
||||
headline?: string
|
||||
message?: string
|
||||
service?: string
|
||||
updatedAt?: string | null
|
||||
steps?: RepairActivityStep[]
|
||||
}
|
||||
|
||||
type Snapshot = {
|
||||
request_id: string
|
||||
title: string
|
||||
@@ -57,6 +75,7 @@ type Snapshot = {
|
||||
}
|
||||
nextStep?: { title?: string; description?: string; actionIds?: string[] }
|
||||
pipeline?: PipelineStage[]
|
||||
repairActivity?: RepairActivity
|
||||
}
|
||||
raw?: Record<string, any>
|
||||
}
|
||||
@@ -288,6 +307,10 @@ export default function RequestTimelinePage() {
|
||||
(stage) => stage.id === 'available' && stage.state === 'active'
|
||||
)
|
||||
)
|
||||
const repairIsActive = Boolean(
|
||||
snapshot?.presentation?.repairActivity?.visible &&
|
||||
!['complete', 'attention'].includes(snapshot.presentation.repairActivity.state ?? '')
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!requestId) return
|
||||
@@ -367,12 +390,15 @@ export default function RequestTimelinePage() {
|
||||
if (!stopped) console.error(error)
|
||||
}
|
||||
}
|
||||
const timer = window.setInterval(() => void refresh(), awaitingMediaIndex ? 5_000 : 15_000)
|
||||
const timer = window.setInterval(
|
||||
() => void refresh(),
|
||||
awaitingMediaIndex || repairIsActive ? 5_000 : 15_000,
|
||||
)
|
||||
return () => {
|
||||
stopped = true
|
||||
window.clearInterval(timer)
|
||||
}
|
||||
}, [awaitingMediaIndex, requestId, router])
|
||||
}, [awaitingMediaIndex, repairIsActive, requestId, router])
|
||||
|
||||
const liveDownloadKey = useMemo(() => {
|
||||
const downloadStage = snapshot?.presentation?.pipeline?.find((stage) => stage.id === 'download')
|
||||
@@ -470,6 +496,7 @@ export default function RequestTimelinePage() {
|
||||
const statusLabel = presentation.status?.label ?? fallbackStatusLabel(snapshot.state)
|
||||
const statusMeaning = presentation.status?.meaning ?? snapshot.state_reason ?? 'Magent is checking this request.'
|
||||
const download = presentation.download
|
||||
const repairActivity = presentation.repairActivity
|
||||
const downloadVisible = Boolean(download?.visible)
|
||||
const nextStep = presentation.nextStep ?? {
|
||||
title: snapshot.actions[0]?.label ?? 'No action needed right now',
|
||||
@@ -753,6 +780,35 @@ export default function RequestTimelinePage() {
|
||||
)}
|
||||
</section>
|
||||
|
||||
{repairActivity?.visible && (
|
||||
<section className={`request-repair-activity is-${repairActivity.state ?? 'searching'}`} aria-live="polite">
|
||||
<div className="request-repair-heading">
|
||||
<div>
|
||||
<span className="section-kicker">Repair activity</span>
|
||||
<h2>{repairActivity.headline ?? 'Repair in progress'}</h2>
|
||||
<p>{repairActivity.message ?? 'Magent is checking the repair with the connected services.'}</p>
|
||||
</div>
|
||||
<div className="request-repair-meta">
|
||||
<span className={`request-operation-status is-${repairActivity.state === 'attention' ? 'error' : repairActivity.state === 'complete' ? 'complete' : 'running'}`}>
|
||||
{repairActivity.state === 'complete' ? 'Complete' : repairActivity.state === 'attention' ? 'Attention' : 'Live'}
|
||||
</span>
|
||||
{repairActivity.updatedAt && <small>Updated {formatWhen(repairActivity.updatedAt)}</small>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="request-repair-steps">
|
||||
{(repairActivity.steps ?? []).map((step) => (
|
||||
<div className={`request-repair-step is-${step.state}`} key={step.id}>
|
||||
<i aria-hidden="true" />
|
||||
<div>
|
||||
<strong>{step.label}</strong>
|
||||
<span>{step.detail}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section className="request-journey" aria-labelledby="request-journey-heading">
|
||||
<div className="request-journey-heading">
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user