// apps/admin/src/app/(admin)/releases/page.tsx // D3RO Voice Admin CRM — Release & Distribution Hub (Forgejo live feed) import { Box, Typography, Button } from '@mui/material' import { DoubleBezelCard, TactileBadge } from '@d3ro/ui/components/ds' import { C, FONT_SANS, FONT_MONO, panelSx, tableSx, statusBadgeSx } from '@/lib/console-theme' import { requireManager } from '@/lib/admin-guard' import { fetchReleaseHub, type ReleaseAssetPlatform, type ReleaseHub } from '@/lib/forgejo-releases' import { ChecksumCopy } from '@/components/checksum-copy' export const dynamic = 'force-dynamic' const PLATFORM_LABEL: Record = { windows: 'Windows', macos: 'macOS', android: 'Android', linux: 'Linux', feed: 'Update Feed', other: 'Other' } const PLATFORM_BADGE: Record = { windows: 'blue', macos: 'purple', android: 'green', linux: 'orange', feed: 'cyan', other: 'orange' } function formatSize(sizeBytes: number): string { if (sizeBytes <= 0) return '—' if (sizeBytes < 1024 * 1024) return `${(sizeBytes / 1024).toFixed(1)} KB` return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB` } function formatDate(value: string): string { if (!value) return '—' const parsed = new Date(value) return Number.isNaN(parsed.getTime()) ? '—' : parsed.toISOString().slice(0, 10) } function HeaderBar({ feedLive, repoHtmlUrl }: { feedLive: boolean; repoHtmlUrl: string }): React.ReactElement { return ( Release & Distribution Hub {feedLive ? ( LIVE FORGEJO FEED ) : ( FEED UNREACHABLE )} Desktop & mobile installers, SHA-256 integrity, download telemetry ) } function KpiCards({ hub }: { hub: ReleaseHub }): React.ReactElement { const latest = hub.latestStable const latestPlatforms = [...new Set(latest?.assets.map((asset) => asset.platform) ?? [])].filter( (platform) => platform !== 'other' ) const cards = [ { title: 'Total Asset Downloads', value: hub.totalDownloads.toLocaleString(), subtext: 'Forgejo attachment download counters' }, { title: 'Latest Stable Release', value: latest?.tagName ?? '—', subtext: latest ? `Published ${formatDate(latest.publishedAt)}` : 'No stable release published' }, { title: 'Published Releases', value: hub.releases.length.toLocaleString(), subtext: `${hub.prereleaseCount.toLocaleString()} pre-release channel builds` }, { title: 'Latest Platform Coverage', value: latestPlatforms.length ? latestPlatforms.map((platform) => PLATFORM_LABEL[platform]).join(' · ') : '—', subtext: latest ? `${latest.assets.length.toLocaleString()} packaged artifacts` : 'Awaiting first artifact upload' } ] return ( {cards.map((card) => ( {card.title} {card.value} {card.subtext} ))} ) } function LatestAssetsPanel({ latest }: { latest: ReleaseHub['latestStable'] }): React.ReactElement { return ( Latest Binary Packages & Checksums {latest && ( {latest.tagName} )} {!latest || latest.assets.length === 0 ? ( 최신 릴리스에 업로드된 아티팩트가 없습니다. ) : ( Artifact Platform Size Downloads SHA-256 Link {latest.assets.map((asset) => ( {asset.name} {PLATFORM_LABEL[asset.platform]} {formatSize(asset.sizeBytes)} {asset.downloadCount.toLocaleString()} {asset.sha256 ? ( ) : ( not published )} Download ↗ ))} )} ) } function ReleaseHistoryPanel({ hub }: { hub: ReleaseHub }): React.ReactElement { return ( Release Channel History {hub.releases.length === 0 ? ( 발행된 릴리스가 없습니다. ) : ( Tag Release Published Channel Assets Downloads Source {hub.releases.map((release) => ( {release.tagName} {release.name} {formatDate(release.publishedAt)} {release.isPrerelease ? 'PRE-RELEASE' : 'STABLE'} {release.assets.length.toLocaleString()} {release.downloadCount.toLocaleString()} Forgejo ↗ ))} )} ) } export default async function ReleasesManagementPage(): Promise { await requireManager() let hub: ReleaseHub | null = null let feedError: string | null = null try { hub = await fetchReleaseHub() } catch (error) { feedError = error instanceof Error ? error.message : 'Unknown release feed error' } if (!hub) { return ( Forgejo 릴리스 피드에 연결하지 못했습니다. {feedError} 네트워크 상태를 확인하거나 RELEASE_REPO_URL 환경변수를 점검해주세요. 샘플 수치는 표시하지 않습니다. ) } return ( ) }