diff --git a/src/client/api.ts b/src/client/api.ts index 9d76e13..c74b0d3 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -4,7 +4,7 @@ export const getLogDates = (name: string): Promise => export const getLogsForDate = (name: string, date: string): Promise => fetch(`/api/apps/${name}/logs?date=${date}`).then(r => r.json()) -export const getSystemInfo = (): Promise<{ version: string, sha: string }> => +export const getSystemInfo = (): Promise<{ version: string, sha: string, uptime: number }> => fetch('/api/system/info').then(r => r.json()) export const shareApp = (name: string) => diff --git a/src/client/components/SettingsPage.tsx b/src/client/components/SettingsPage.tsx index f115b7a..7677929 100644 --- a/src/client/components/SettingsPage.tsx +++ b/src/client/components/SettingsPage.tsx @@ -19,6 +19,20 @@ import { type UpdateInfo = { available: boolean, current: string, latest: string, commits: string[] } +function formatUptime(ms: number): string { + const seconds = Math.floor(ms / 1000) + const days = Math.floor(seconds / 86400) + const hours = Math.floor((seconds % 86400) / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const secs = seconds % 60 + const parts: string[] = [] + if (days > 0) parts.push(`${days}d`) + if (hours > 0) parts.push(`${hours}h`) + if (minutes > 0) parts.push(`${minutes}m`) + parts.push(`${secs}s`) + return parts.join(' ') +} + function pollUntilBack(onBack: () => void, onTimeout?: () => void) { let elapsed = 0 const poll = setInterval(async () => { @@ -41,6 +55,7 @@ function pollUntilBack(onBack: () => void, onTimeout?: () => void) { export function SettingsPage({ render }: { render: () => void }) { const [version, setVersion] = useState('') const [sha, setSha] = useState('') + const [uptime, setUptime] = useState(0) const [themeChoice, setThemeChoice] = useState(localStorage.getItem('theme') || 'system') const [restarting, setRestarting] = useState(false) const [updateInfo, setUpdateInfo] = useState(null) @@ -51,9 +66,16 @@ export function SettingsPage({ render }: { render: () => void }) { getSystemInfo().then(info => { setVersion(info.version) setSha(info.sha) + setUptime(info.uptime) }) }, []) + // Tick uptime every second + useEffect(() => { + const interval = setInterval(() => setUptime(u => u + 1000), 1000) + return () => clearInterval(interval) + }, []) + const goBack = () => { navigate('/') } @@ -73,6 +95,7 @@ export function SettingsPage({ render }: { render: () => void }) { getSystemInfo().then(info => { setVersion(info.version) setSha(info.sha) + setUptime(info.uptime) }) } @@ -175,9 +198,14 @@ export function SettingsPage({ render }: { render: () => void }) {
Server - +
+ Uptime: {formatUptime(uptime)} +
+
+ +
diff --git a/src/server/api/system.ts b/src/server/api/system.ts index 32b4a8f..ad750e4 100644 --- a/src/server/api/system.ts +++ b/src/server/api/system.ts @@ -203,12 +203,13 @@ router.sse('/metrics/stream', (send) => { // System info const projectRoot = join(import.meta.dir, '../../..') +const startedAt = Date.now() const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf-8')) const sha = Bun.spawnSync(['git', 'rev-parse', '--short', 'HEAD'], { cwd: projectRoot }).stdout.toString().trim() || 'unknown' let isUpdating = false router.get('/info', c => { - return c.json({ version: pkg.version, sha }) + return c.json({ version: pkg.version, sha, uptime: Date.now() - startedAt }) }) // Get recent unified logs