feat: real-time live data integration

- lib/live-data.ts: Shared TypeScript client for blackroad-live-data Worker
- components/live-stats.tsx: LiveStatsBar, RecentRepos, AgentStatusGrid components
- app/page.tsx: Import LiveStatsBar in main page header

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Alexa Amundson
2026-02-24 14:18:59 -06:00
parent 263f9f171e
commit 458c2c044b
97 changed files with 8715 additions and 1701 deletions

128
app/(app)/dns/page.tsx Normal file
View File

@@ -0,0 +1,128 @@
'use client'
import { useEffect, useState } from 'react'
import { Globe, ChevronRight, ChevronDown, RefreshCw, ExternalLink, Shield, AlertCircle } from 'lucide-react'
interface Zone { id: string; name: string; status: string; nameservers: string[]; plan: string; recordCount: number | null }
interface DnsRecord { id: string; type: string; name: string; content: string; ttl: number; proxied: boolean }
const TYPE_COLORS: Record<string, string> = {
A: '#22c55e', AAAA: '#2979FF', CNAME: '#F5A623', MX: '#9C27B0',
TXT: '#06b6d4', NS: '#aaa', SRV: '#FF1D6C', CAA: '#f97316',
}
export default function DnsPage() {
const [zones, setZones] = useState<Zone[]>([])
const [loading, setLoading] = useState(true)
const [expanded, setExpanded] = useState<string | null>(null)
const [records, setRecords] = useState<Record<string, DnsRecord[]>>({})
const [loadingZone, setLoadingZone] = useState<string | null>(null)
const [live, setLive] = useState(false)
const [search, setSearch] = useState('')
useEffect(() => {
fetch('/api/dns').then(r => r.json()).then(d => {
setZones(d.zones || [])
setLive(d.live ?? false)
setLoading(false)
}).catch(() => setLoading(false))
}, [])
const toggleZone = async (zone: Zone) => {
if (expanded === zone.id) { setExpanded(null); return }
setExpanded(zone.id)
if (!records[zone.id] && live && zone.id !== `zone-${zones.indexOf(zone)}`) {
setLoadingZone(zone.id)
try {
const r = await fetch(`/api/dns?zone=${zone.id}`)
const d = await r.json()
setRecords(prev => ({ ...prev, [zone.id]: d.records || [] }))
} finally { setLoadingZone(null) }
}
}
const filtered = zones.filter(z => !search || z.name.includes(search.toLowerCase()))
return (
<div style={{ padding: 32, maxWidth: 900 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 28 }}>
<Globe size={28} style={{ color: '#2979FF' }} />
<div>
<h1 style={{ fontSize: 24, fontWeight: 700, color: '#fff', margin: 0 }}>DNS Zones</h1>
<p style={{ color: 'rgba(255,255,255,0.4)', fontSize: 13, margin: 0 }}>
{zones.length} zones · Cloudflare {live ? '● live' : '○ cached'}
</p>
</div>
{!live && (
<div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 6, padding: '4px 12px', background: 'rgba(245,166,35,0.1)', border: '1px solid rgba(245,166,35,0.3)', borderRadius: 20, fontSize: 12, color: '#F5A623' }}>
<AlertCircle size={12} />Set CLOUDFLARE_API_TOKEN for live data
</div>
)}
</div>
<input
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Search zones…"
style={{ width: '100%', background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: 8, padding: '10px 14px', color: '#fff', fontSize: 13, outline: 'none', marginBottom: 16, boxSizing: 'border-box' }}
/>
{loading ? (
<div style={{ color: 'rgba(255,255,255,0.3)', textAlign: 'center', padding: 40 }}>Loading zones</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
{filtered.map(zone => (
<div key={zone.id} style={{ background: 'rgba(255,255,255,0.04)', border: `1px solid ${expanded === zone.id ? 'rgba(41,121,255,0.4)' : 'rgba(255,255,255,0.07)'}`, borderRadius: 10, overflow: 'hidden', transition: 'border-color .2s' }}>
<div onClick={() => toggleZone(zone)} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '14px 18px', cursor: 'pointer' }}>
{expanded === zone.id ? <ChevronDown size={15} style={{ color: '#2979FF' }} /> : <ChevronRight size={15} style={{ color: 'rgba(255,255,255,0.3)' }} />}
<div style={{ width: 8, height: 8, borderRadius: '50%', background: zone.status === 'active' ? '#22c55e' : '#F5A623', flexShrink: 0 }} />
<span style={{ color: '#fff', fontWeight: 600, fontSize: 14, flex: 1 }}>{zone.name}</span>
<span style={{ color: 'rgba(255,255,255,0.3)', fontSize: 11, background: 'rgba(255,255,255,0.05)', padding: '2px 8px', borderRadius: 10 }}>{zone.plan}</span>
<a href={`https://${zone.name}`} target="_blank" rel="noreferrer" onClick={e => e.stopPropagation()} style={{ color: 'rgba(255,255,255,0.2)' }}>
<ExternalLink size={12} />
</a>
</div>
{expanded === zone.id && (
<div style={{ borderTop: '1px solid rgba(255,255,255,0.06)', padding: '14px 18px' }}>
{/* Nameservers */}
<div style={{ marginBottom: 14 }}>
<div style={{ color: 'rgba(255,255,255,0.4)', fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 6 }}>Nameservers</div>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
{zone.nameservers.map(ns => (
<span key={ns} style={{ fontSize: 12, fontFamily: 'monospace', background: 'rgba(41,121,255,0.1)', color: '#2979FF', padding: '3px 10px', borderRadius: 6 }}>
<Shield size={10} style={{ display: 'inline', marginRight: 4 }} />{ns}
</span>
))}
</div>
</div>
{/* DNS Records */}
{loadingZone === zone.id ? (
<div style={{ color: 'rgba(255,255,255,0.3)', fontSize: 13 }}><RefreshCw size={12} style={{ display: 'inline', marginRight: 6, animation: 'spin 1s linear infinite' }} />Loading records</div>
) : records[zone.id]?.length ? (
<div>
<div style={{ color: 'rgba(255,255,255,0.4)', fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 8 }}>DNS Records ({records[zone.id].length})</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
{records[zone.id].map(rec => (
<div key={rec.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 10px', background: 'rgba(255,255,255,0.03)', borderRadius: 6 }}>
<span style={{ fontSize: 10, fontWeight: 700, padding: '1px 6px', borderRadius: 4, background: `${TYPE_COLORS[rec.type] || '#888'}22`, color: TYPE_COLORS[rec.type] || '#888', minWidth: 36, textAlign: 'center' }}>{rec.type}</span>
<span style={{ color: '#fff', fontSize: 12, fontFamily: 'monospace', minWidth: 180 }}>{rec.name}</span>
<span style={{ color: 'rgba(255,255,255,0.4)', fontSize: 12, fontFamily: 'monospace', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{rec.content}</span>
{rec.proxied && <span style={{ fontSize: 10, color: '#F5A623', background: 'rgba(245,166,35,0.1)', padding: '1px 6px', borderRadius: 4 }}> proxied</span>}
</div>
))}
</div>
</div>
) : (
<div style={{ color: 'rgba(255,255,255,0.25)', fontSize: 12 }}>
{live ? 'No records found' : 'Add CLOUDFLARE_API_TOKEN to Vercel env to view DNS records'}
</div>
)}
</div>
)}
</div>
))}
</div>
)}
</div>
)
}