feat: surface backend sync health in cockpit

This commit is contained in:
OpenClaw Bot
2026-05-09 10:20:30 +02:00
parent d0806dccb8
commit b33ed20238
2 changed files with 70 additions and 5 deletions
+43 -5
View File
@@ -79,6 +79,8 @@ function App() {
const [promptTarget, setPromptTarget] = useState<(typeof PROMPT_TARGETS)[number]>('OpenClaw')
const [promptFeatureId, setPromptFeatureId] = useState('')
const [backendMode, setBackendMode] = useState<'connecting' | 'appwrite' | 'local-cache'>('connecting')
const [syncStatus, setSyncStatus] = useState<'connecting' | 'synced' | 'pending' | 'syncing' | 'degraded'>('connecting')
const [lastSyncedAt, setLastSyncedAt] = useState<string | null>(null)
const hasHydratedRemote = useRef(false)
const initialLocalStateRef = useRef(appState)
@@ -104,9 +106,12 @@ function App() {
setStatusMessage('Seeded Appwrite on Unraid from the local BuildPulse state.')
}
setBackendMode('appwrite')
setSyncStatus('synced')
setLastSyncedAt(nowIso())
} catch {
if (cancelled) return
setBackendMode('local-cache')
setSyncStatus('degraded')
setStatusMessage('Appwrite backend unavailable, so BuildPulse is using the local cache for now.')
} finally {
hasHydratedRemote.current = true
@@ -123,11 +128,19 @@ function App() {
useEffect(() => {
if (!hasHydratedRemote.current || backendMode !== 'appwrite') return
setSyncStatus('pending')
const timer = window.setTimeout(() => {
void pushRemoteState(appState).catch(() => {
setBackendMode('local-cache')
setStatusMessage('Saved locally. Appwrite sync tripped over itself, so the cache is carrying the load.')
})
setSyncStatus('syncing')
void pushRemoteState(appState)
.then(() => {
setSyncStatus('synced')
setLastSyncedAt(nowIso())
})
.catch(() => {
setBackendMode('local-cache')
setSyncStatus('degraded')
setStatusMessage('Saved locally. Appwrite sync tripped over itself, so the cache is carrying the load.')
})
}, 350)
return () => window.clearTimeout(timer)
@@ -548,6 +561,18 @@ function App() {
const currentFeatureCount = groupedFeatures.now.length
const recentPulsePreview = [...appState.pulses].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 3)
const backendLabel =
backendMode === 'appwrite' ? 'Appwrite backend · Unraid server' : backendMode === 'connecting' ? 'Connecting to Appwrite…' : 'Local cache fallback'
const syncLabel =
syncStatus === 'synced'
? `Synced${lastSyncedAt ? ` ${formatDateTime(lastSyncedAt)}` : ''}`
: syncStatus === 'pending'
? 'Changes queued'
: syncStatus === 'syncing'
? 'Syncing now…'
: syncStatus === 'degraded'
? 'Sync degraded · local cache active'
: 'Connecting…'
return (
<div className="app-shell">
@@ -576,6 +601,19 @@ function App() {
</div>
</header>
<section className="status-strip card">
<div>
<span className={`pill status-${backendMode === 'appwrite' ? 'healthy' : backendMode === 'connecting' ? 'connecting' : 'degraded'}`}>
{backendLabel}
</span>
</div>
<div>
<span className={`pill status-${syncStatus === 'synced' ? 'healthy' : syncStatus === 'pending' || syncStatus === 'syncing' || syncStatus === 'connecting' ? 'connecting' : 'degraded'}`}>
{syncLabel}
</span>
</div>
</section>
<section className="project-card card">
<div className="section-heading compact">
<div>
@@ -1209,7 +1247,7 @@ function App() {
<footer className="status-bar">
<span>{statusMessage}</span>
<span>{backendMode === 'appwrite' ? 'Appwrite backend · Unraid server' : backendMode === 'connecting' ? 'Connecting to Appwrite…' : 'Local cache fallback'} · schema {appState.schema_version}</span>
<span>{backendLabel} · {syncLabel} · schema {appState.schema_version}</span>
</footer>
</div>
)