forked from defunkt/toes
Add error handling and timeout to system operations
This commit is contained in:
parent
f54cc401dc
commit
f1fc4fcde8
|
|
@ -14,15 +14,15 @@ export const unshareApp = (name: string) =>
|
||||||
fetch(`/api/apps/${name}/tunnel`, { method: 'DELETE' })
|
fetch(`/api/apps/${name}/tunnel`, { method: 'DELETE' })
|
||||||
|
|
||||||
export const applyUpdate = () =>
|
export const applyUpdate = () =>
|
||||||
fetch('/api/system/update', { method: 'POST' }).then(r => r.json())
|
fetch('/api/system/update', { method: 'POST' }).then(r => { if (!r.ok) throw new Error('update failed'); return r.json() })
|
||||||
|
|
||||||
export const checkForUpdate = (): Promise<{ available: boolean, current: string, latest: string, commits: string[] }> =>
|
export const checkForUpdate = (): Promise<{ available: boolean, current: string, latest: string, commits: string[] }> =>
|
||||||
fetch('/api/system/update').then(r => r.json())
|
fetch('/api/system/update').then(r => { if (!r.ok) throw new Error('check failed'); return r.json() })
|
||||||
|
|
||||||
export const restartApp = (name: string) => fetch(`/api/apps/${name}/restart`, { method: 'POST' })
|
export const restartApp = (name: string) => fetch(`/api/apps/${name}/restart`, { method: 'POST' })
|
||||||
|
|
||||||
export const restartServer = () =>
|
export const restartServer = () =>
|
||||||
fetch('/api/system/restart', { method: 'POST' }).then(r => r.json())
|
fetch('/api/system/restart', { method: 'POST' }).then(r => { if (!r.ok) throw new Error('restart failed'); return r.json() })
|
||||||
|
|
||||||
export const startApp = (name: string) => fetch(`/api/apps/${name}/start`, { method: 'POST' })
|
export const startApp = (name: string) => fetch(`/api/apps/${name}/start`, { method: 'POST' })
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,15 @@ import {
|
||||||
|
|
||||||
type UpdateInfo = { available: boolean, current: string, latest: string, commits: string[] }
|
type UpdateInfo = { available: boolean, current: string, latest: string, commits: string[] }
|
||||||
|
|
||||||
function pollUntilBack(onBack: () => void) {
|
function pollUntilBack(onBack: () => void, onTimeout?: () => void) {
|
||||||
|
let elapsed = 0
|
||||||
const poll = setInterval(async () => {
|
const poll = setInterval(async () => {
|
||||||
|
elapsed += 2000
|
||||||
|
if (elapsed > 60000) {
|
||||||
|
clearInterval(poll)
|
||||||
|
onTimeout?.()
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/system/info')
|
const res = await fetch('/api/system/info')
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
|
|
@ -62,17 +69,21 @@ export function SettingsPage({ render }: { render: () => void }) {
|
||||||
setTheme()
|
setTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const refreshSystemInfo = () => {
|
||||||
|
getSystemInfo().then(info => {
|
||||||
|
setVersion(info.version)
|
||||||
|
setSha(info.sha)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handleRestart = () => {
|
const handleRestart = () => {
|
||||||
if (!confirm('Are you sure you want to restart the server?')) return
|
if (!confirm('Are you sure you want to restart the server?')) return
|
||||||
setRestarting(true)
|
setRestarting(true)
|
||||||
restartServer().catch(() => {})
|
restartServer().catch(() => {})
|
||||||
pollUntilBack(() => {
|
pollUntilBack(
|
||||||
setRestarting(false)
|
() => { setRestarting(false); refreshSystemInfo() },
|
||||||
getSystemInfo().then(info => {
|
() => { setRestarting(false) },
|
||||||
setVersion(info.version)
|
)
|
||||||
setSha(info.sha)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCheckUpdate = async () => {
|
const handleCheckUpdate = async () => {
|
||||||
|
|
@ -91,15 +102,13 @@ export function SettingsPage({ render }: { render: () => void }) {
|
||||||
setUpdating(true)
|
setUpdating(true)
|
||||||
try {
|
try {
|
||||||
await applyUpdate()
|
await applyUpdate()
|
||||||
} catch {}
|
pollUntilBack(
|
||||||
pollUntilBack(() => {
|
() => { setUpdating(false); refreshSystemInfo(); setUpdateInfo(null) },
|
||||||
|
() => { setUpdating(false) },
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
setUpdating(false)
|
setUpdating(false)
|
||||||
getSystemInfo().then(info => {
|
}
|
||||||
setVersion(info.version)
|
|
||||||
setSha(info.sha)
|
|
||||||
})
|
|
||||||
setUpdateInfo(null)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -186,8 +186,10 @@ router.sse('/metrics/stream', (send) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// System info
|
// System info
|
||||||
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '../../../package.json'), 'utf-8'))
|
const projectRoot = join(import.meta.dir, '../../..')
|
||||||
const sha = Bun.spawnSync(['git', 'rev-parse', '--short', 'HEAD'], { cwd: join(import.meta.dir, '../../..') }).stdout.toString().trim() || 'unknown'
|
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 => {
|
router.get('/info', c => {
|
||||||
return c.json({ version: pkg.version, sha })
|
return c.json({ version: pkg.version, sha })
|
||||||
|
|
@ -274,16 +276,15 @@ router.post('/restart', c => {
|
||||||
|
|
||||||
// Check for updates
|
// Check for updates
|
||||||
router.get('/update', async c => {
|
router.get('/update', async c => {
|
||||||
const cwd = join(import.meta.dir, '../../..')
|
|
||||||
try {
|
try {
|
||||||
const fetch = Bun.spawnSync(['git', 'fetch', 'origin', 'main'], { cwd })
|
const gitFetch = await Bun.spawn(['git', 'fetch', 'origin', 'main'], { cwd: projectRoot }).exited
|
||||||
if (fetch.exitCode !== 0) return c.json({ available: false })
|
if (gitFetch !== 0) return c.json({ available: false })
|
||||||
|
|
||||||
const log = Bun.spawnSync(['git', 'log', 'HEAD..origin/main', '--oneline'], { cwd })
|
const log = Bun.spawnSync(['git', 'log', 'HEAD..origin/main', '--oneline'], { cwd: projectRoot })
|
||||||
const output = log.stdout.toString().trim()
|
const output = log.stdout.toString().trim()
|
||||||
const commits = output ? output.split('\n') : []
|
const commits = output ? output.split('\n') : []
|
||||||
|
|
||||||
const latest = Bun.spawnSync(['git', 'rev-parse', '--short', 'origin/main'], { cwd })
|
const latest = Bun.spawnSync(['git', 'rev-parse', '--short', 'origin/main'], { cwd: projectRoot })
|
||||||
.stdout.toString().trim()
|
.stdout.toString().trim()
|
||||||
|
|
||||||
return c.json({
|
return c.json({
|
||||||
|
|
@ -299,21 +300,24 @@ router.get('/update', async c => {
|
||||||
|
|
||||||
// Apply update and restart
|
// Apply update and restart
|
||||||
router.post('/update', async c => {
|
router.post('/update', async c => {
|
||||||
const cwd = join(import.meta.dir, '../../..')
|
if (isUpdating) return c.json({ ok: false, error: 'update already in progress' }, 409)
|
||||||
|
isUpdating = true
|
||||||
try {
|
try {
|
||||||
const pull = Bun.spawnSync(['git', 'pull', 'origin', 'main'], { cwd })
|
const pull = await Bun.spawn(['git', 'pull', 'origin', 'main'], { cwd: projectRoot }).exited
|
||||||
if (pull.exitCode !== 0) return c.json({ ok: false, error: 'git pull failed' }, 500)
|
if (pull !== 0) return c.json({ ok: false, error: 'git pull failed' }, 500)
|
||||||
|
|
||||||
const install = Bun.spawnSync(['bun', 'install'], { cwd })
|
const install = await Bun.spawn(['bun', 'install'], { cwd: projectRoot }).exited
|
||||||
if (install.exitCode !== 0) return c.json({ ok: false, error: 'bun install failed' }, 500)
|
if (install !== 0) return c.json({ ok: false, error: 'bun install failed' }, 500)
|
||||||
|
|
||||||
const build = Bun.spawnSync(['bun', 'run', 'build'], { cwd })
|
const build = await Bun.spawn(['bun', 'run', 'build'], { cwd: projectRoot }).exited
|
||||||
if (build.exitCode !== 0) return c.json({ ok: false, error: 'build failed' }, 500)
|
if (build !== 0) return c.json({ ok: false, error: 'build failed' }, 500)
|
||||||
|
|
||||||
setTimeout(() => process.exit(0), 100)
|
setTimeout(() => process.exit(0), 100)
|
||||||
return c.json({ ok: true })
|
return c.json({ ok: true })
|
||||||
} catch {
|
} catch {
|
||||||
return c.json({ ok: false, error: 'update failed' }, 500)
|
return c.json({ ok: false, error: 'update failed' }, 500)
|
||||||
|
} finally {
|
||||||
|
isUpdating = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user