import { useEffect, useRef, useState } from 'react' import { ArrowUpCircle, Activity, ScrollText, RefreshCw, TerminalSquare, HardDrive, ChevronDown, ChevronRight, Play } from 'lucide-react' import { PageShell } from '../components/PageShell' import type { User } from '../types' interface AppStatus { repo: string host: string currentCommit: string currentCommitAt: string | null latestCommit: string latestCommitAt: string | null updateAvailable: boolean commitsBehind: number | null deployed: boolean checkedAt: string } function relativeTime(iso: string | null | undefined): string | null { if (!iso) return null const diff = Date.now() - new Date(iso).getTime() if (diff < 60_000) return 'just now' if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago` if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago` if (diff < 7 * 86_400_000) return `${Math.floor(diff / 86_400_000)}d ago` return new Date(iso).toLocaleDateString(undefined, { day: 'numeric', month: 'short' }) } interface StatRange { total: number; used: number } interface HealthStatus { name: string host: string up: boolean ms: number | null ram: StatRange | null disk: StatRange | null } function formatBytes(b: number): string { if (b >= 1_073_741_824) return `${(b / 1_073_741_824).toFixed(1)} GB` if (b >= 1_048_576) return `${(b / 1_048_576).toFixed(0)} MB` return `${(b / 1024).toFixed(0)} KB` } function StatBar({ label, stat }: { label: string; stat: StatRange }) { const pct = Math.min(100, Math.round((stat.used / stat.total) * 100)) const color = pct > 85 ? 'var(--danger)' : pct > 70 ? '#d97706' : '#16a34a' return (
No apps in deploy map yet.
)}Checking services…
)}No deploys recorded yet.
)} {deploys.map((d, i) => { const isSuccess = d.status === 'success' const isFailed = d.status === 'failed' return ({d.output || d.error}
)}
No backup runs recorded yet. Configure Nextcloud in Settings then click Run Now.
)} {/* Run history */}
{shellOutput || Select a container and run a command…}