- SystemdWidget: groen als N=M healthy, oranje als 0<N<M, rood als N=0 - GitWidget: groen als 0 dirty repos, oranje als >0; toon K/M formaat Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
77 lines
2.5 KiB
TypeScript
77 lines
2.5 KiB
TypeScript
'use client'
|
|
|
|
import { useCallback, useEffect, useState } from 'react'
|
|
import Link from 'next/link'
|
|
import { parseGitStatus } from '@/lib/parse-git'
|
|
import { fetchAgentOutput } from '@/lib/agent-fetch'
|
|
|
|
type GitData = { dirty: number; total: number }
|
|
export type GitInitial =
|
|
| { configured: false }
|
|
| { data: GitData; error: null }
|
|
| { data: null; error: string }
|
|
|
|
async function refreshGit(repos: string[]): Promise<GitData> {
|
|
const results = await Promise.allSettled(
|
|
repos.map(async (path) => {
|
|
const output = await fetchAgentOutput('git_status', [path])
|
|
return parseGitStatus(output)
|
|
}),
|
|
)
|
|
const dirty = results.filter(
|
|
(r) => r.status === 'fulfilled' && r.value.dirty,
|
|
).length
|
|
return { dirty, total: repos.length }
|
|
}
|
|
|
|
export default function GitWidget({ initial, repos }: { initial: GitInitial; repos: string[] }) {
|
|
const notConfigured = 'configured' in initial && initial.configured === false
|
|
const [data, setData] = useState<GitData | null>(
|
|
!notConfigured && 'data' in initial ? initial.data : null,
|
|
)
|
|
const [error, setError] = useState<string | null>(
|
|
!notConfigured && 'error' in initial ? initial.error : null,
|
|
)
|
|
|
|
const refresh = useCallback(async () => {
|
|
if (notConfigured || repos.length === 0) return
|
|
try {
|
|
const d = await refreshGit(repos)
|
|
setData(d)
|
|
setError(null)
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'refresh failed')
|
|
}
|
|
}, [notConfigured, repos])
|
|
|
|
useEffect(() => {
|
|
if (notConfigured) return
|
|
const id = setInterval(refresh, 30_000)
|
|
return () => clearInterval(id)
|
|
}, [refresh, notConfigured])
|
|
|
|
return (
|
|
<Link href="/git" className="block rounded-lg border bg-card p-5 transition-colors hover:bg-accent">
|
|
<h2 className="text-sm font-medium text-muted-foreground">Git</h2>
|
|
{notConfigured ? (
|
|
<p className="mt-2 text-sm text-muted-foreground">niet geconfigureerd</p>
|
|
) : error ? (
|
|
<p className="mt-2 text-sm text-destructive truncate">{error}</p>
|
|
) : data ? (
|
|
<p
|
|
className={[
|
|
'mt-2 text-2xl font-semibold',
|
|
data.dirty === 0 ? 'text-green-600' : 'text-orange-500',
|
|
].join(' ')}
|
|
>
|
|
{data.dirty}/{data.total}
|
|
<span className="text-sm font-normal text-muted-foreground">
|
|
{' '}repos uncommitted
|
|
</span>
|
|
</p>
|
|
) : (
|
|
<p className="mt-2 text-sm text-muted-foreground">—</p>
|
|
)}
|
|
</Link>
|
|
)
|
|
}
|