feat(widgets): voeg relativeTime toe in lib/utils, expiringWarning-badge in CaddyWidget

- relativeTime(date: Date) helper toegevoegd aan lib/utils.ts
- AuditWidget gebruikt nu gedeelde relativeTime in plaats van inline functie
- CaddyWidget toont rode badge als soonest cert-expiry <30 dagen
- app/page.tsx berekent expiringWarning voor CaddyInitial

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scrum4Me Agent 2026-05-13 22:10:51 +02:00
parent faa1463cd7
commit 08d4b48190
4 changed files with 27 additions and 17 deletions

View file

@ -3,6 +3,7 @@
import { useCallback, useEffect, useState } from 'react'
import Link from 'next/link'
import { apiFetch } from '@/lib/csrf'
import { relativeTime } from '@/lib/utils'
type LatestRun = { id: string; flow_key: string; status: string; started_at: string }
export type AuditInitial =
@ -17,16 +18,6 @@ const STATUS_STYLES: Record<string, string> = {
cancelled: 'bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400',
}
function relativeTime(isoString: string): string {
const diff = Date.now() - new Date(isoString).getTime()
const minutes = Math.floor(diff / 60_000)
if (minutes < 1) return 'zojuist'
if (minutes < 60) return `${minutes}m geleden`
const hours = Math.floor(minutes / 60)
if (hours < 24) return `${hours}u geleden`
return `${Math.floor(hours / 24)}d geleden`
}
async function fetchLatestRun(): Promise<LatestRun | null> {
const res = await apiFetch('/api/audit/latest')
if (!res.ok) throw new Error(`${res.status}`)
@ -66,7 +57,7 @@ export default function AuditWidget({ initial }: { initial: AuditInitial }) {
>
{data.status}
</span>
<span className="text-xs text-muted-foreground">{relativeTime(data.started_at)}</span>
<span className="text-xs text-muted-foreground">{relativeTime(new Date(data.started_at))}</span>
</div>
<p className="font-mono text-xs text-muted-foreground truncate">{data.flow_key}</p>
</div>

View file

@ -5,7 +5,7 @@ import Link from 'next/link'
import { parseCertList } from '@/lib/parse-caddy'
import { fetchAgentOutput } from '@/lib/agent-fetch'
type CaddyData = { soonestExpiryMs: number | null; count: number }
type CaddyData = { soonestExpiryMs: number | null; count: number; expiringWarning: boolean }
export type CaddyInitial = { data: CaddyData; error: null } | { data: null; error: string }
async function refreshCaddy(): Promise<CaddyData> {
@ -15,7 +15,8 @@ async function refreshCaddy(): Promise<CaddyData> {
.filter((c) => c.notAfter)
.map((c) => new Date(c.notAfter).getTime())
const soonestExpiryMs = expiryTimes.length > 0 ? Math.min(...expiryTimes) : null
return { soonestExpiryMs, count: certs.length }
const expiringWarning = certs.some((c) => c.expiringWarning)
return { soonestExpiryMs, count: certs.length, expiringWarning }
}
function daysUntil(ms: number): number {
@ -49,10 +50,17 @@ export default function CaddyWidget({ initial }: { initial: CaddyInitial }) {
) : data ? (
<div className="mt-2">
{data.soonestExpiryMs !== null ? (
<p className="text-2xl font-semibold">
{daysUntil(data.soonestExpiryMs)}
<span className="text-sm font-normal text-muted-foreground"> days to expiry</span>
</p>
<div className="flex items-center gap-2">
<p className="text-2xl font-semibold">
{daysUntil(data.soonestExpiryMs)}
<span className="text-sm font-normal text-muted-foreground"> dagen tot expiry</span>
</p>
{data.expiringWarning && (
<span className="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400">
&lt;30d
</span>
)}
</div>
) : (
<p className="text-sm text-muted-foreground">no certs</p>
)}

View file

@ -70,6 +70,7 @@ export default async function Home() {
data: {
soonestExpiryMs: expiryTimes.length > 0 ? Math.min(...expiryTimes) : null,
count: certs.length,
expiringWarning: certs.some((c) => c.expiringWarning),
},
error: null,
}

View file

@ -4,3 +4,13 @@ import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function relativeTime(date: Date): string {
const diff = Date.now() - date.getTime()
const minutes = Math.floor(diff / 60_000)
if (minutes < 1) return 'net nu'
if (minutes < 60) return `${minutes}m geleden`
const hours = Math.floor(minutes / 60)
if (hours < 24) return `${hours}u geleden`
return `${Math.floor(hours / 24)}d geleden`
}