/ok
This commit is contained in:
parent
067fd12e94
commit
2ef00c9d53
|
|
@ -3,6 +3,7 @@ import { Hype } from '@because/hype'
|
|||
const app = new Hype
|
||||
|
||||
app.get('/', c => c.html(<h1>Hi there!</h1>))
|
||||
app.get('/ok', c => c.text('ok'))
|
||||
|
||||
const apps = () => {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ app.get('/styles.css', c => c.text(stylesToCSS(), 200, {
|
|||
'Content-Type': 'text/css; charset=utf-8',
|
||||
}))
|
||||
|
||||
app.get('/ok', c => c.text('ok'))
|
||||
|
||||
app.get('/', c => c.html(
|
||||
<html>
|
||||
<head>
|
||||
|
|
|
|||
|
|
@ -319,6 +319,8 @@ function Layout({ title, children, highlight, editable }: LayoutProps) {
|
|||
)
|
||||
}
|
||||
|
||||
app.get('/ok', c => c.text('ok'))
|
||||
|
||||
app.get('/styles.css', c => c.text(baseStyles + stylesToCSS(), 200, {
|
||||
'Content-Type': 'text/css; charset=utf-8',
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -217,6 +217,8 @@ function statusColor(job: CronJob): string {
|
|||
}
|
||||
|
||||
// Routes
|
||||
app.get('/ok', c => c.text('ok'))
|
||||
|
||||
app.get('/styles.css', c => c.text(baseStyles + stylesToCSS(), 200, {
|
||||
'Content-Type': 'text/css; charset=utf-8',
|
||||
}))
|
||||
|
|
|
|||
2
apps/env/20260130-000000/index.tsx
vendored
2
apps/env/20260130-000000/index.tsx
vendored
|
|
@ -248,6 +248,8 @@ function appEnvPath(appName: string): string {
|
|||
return join(ENV_DIR, `${appName}.env`)
|
||||
}
|
||||
|
||||
app.get('/ok', c => c.text('ok'))
|
||||
|
||||
app.get('/styles.css', c => c.text(baseStyles + stylesToCSS(), 200, {
|
||||
'Content-Type': 'text/css; charset=utf-8',
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@ import { Hype } from '@because/hype'
|
|||
const app = new Hype
|
||||
|
||||
app.get('/', c => c.html(<h1>My Profile!!!</h1>))
|
||||
app.get('/ok', c => c.text('ok'))
|
||||
|
||||
export default app.defaults
|
||||
|
|
|
|||
|
|
@ -190,6 +190,8 @@ function serializeTodo(todo: ParsedTodo): string {
|
|||
return lines.join('\n') + '\n'
|
||||
}
|
||||
|
||||
app.get('/ok', c => c.text('ok'))
|
||||
|
||||
app.get('/styles.css', c => c.text(baseStyles + todoStyles + stylesToCSS(), 200, {
|
||||
'Content-Type': 'text/css; charset=utf-8',
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -121,6 +121,8 @@ function Layout({ title, subtitle, children }: LayoutProps) {
|
|||
)
|
||||
}
|
||||
|
||||
app.get('/ok', c => c.text('ok'))
|
||||
|
||||
app.get('/styles.css', c => c.text(baseStyles + stylesToCSS(), 200, {
|
||||
'Content-Type': 'text/css; charset=utf-8',
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -12,4 +12,5 @@ export {
|
|||
startApp,
|
||||
stopApp,
|
||||
} from './manage'
|
||||
export { statsApp } from './stats'
|
||||
export { cleanApp, diffApp, getApp, pullApp, pushApp, rollbackApp, stashApp, stashListApp, stashPopApp, statusApp, syncApp, versionsApp } from './sync'
|
||||
|
|
|
|||
103
src/cli/commands/stats.ts
Normal file
103
src/cli/commands/stats.ts
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import color from 'kleur'
|
||||
import { get } from '../http'
|
||||
import { resolveAppName } from '../name'
|
||||
|
||||
interface AppStats {
|
||||
name: string
|
||||
state: string
|
||||
port?: number
|
||||
pid?: number
|
||||
cpu?: number
|
||||
memory?: number
|
||||
rss?: number
|
||||
tool?: boolean
|
||||
}
|
||||
|
||||
function formatRss(kb?: number): string {
|
||||
if (kb === undefined) return '-'
|
||||
if (kb < 1024) return `${kb} KB`
|
||||
if (kb < 1024 * 1024) return `${(kb / 1024).toFixed(1)} MB`
|
||||
return `${(kb / 1024 / 1024).toFixed(2)} GB`
|
||||
}
|
||||
|
||||
function formatPercent(value?: number): string {
|
||||
if (value === undefined) return '-'
|
||||
return `${value.toFixed(1)}%`
|
||||
}
|
||||
|
||||
function pad(str: string, len: number, right = false): string {
|
||||
if (right) return str.padStart(len)
|
||||
return str.padEnd(len)
|
||||
}
|
||||
|
||||
export async function statsApp(arg?: string) {
|
||||
// If arg is provided, show stats for that app only
|
||||
if (arg) {
|
||||
const name = resolveAppName(arg)
|
||||
if (!name) return
|
||||
|
||||
const stats: AppStats | undefined = await get(`/api/tools/stats/api/stats/${name}`)
|
||||
if (!stats) {
|
||||
console.error(`App not found: ${name}`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`${color.bold(stats.name)} ${stats.tool ? color.gray('[tool]') : ''}`)
|
||||
console.log(` State: ${stats.state}`)
|
||||
if (stats.pid) console.log(` PID: ${stats.pid}`)
|
||||
if (stats.port) console.log(` Port: ${stats.port}`)
|
||||
if (stats.cpu !== undefined) console.log(` CPU: ${formatPercent(stats.cpu)}`)
|
||||
if (stats.memory !== undefined) console.log(` Memory: ${formatPercent(stats.memory)}`)
|
||||
if (stats.rss !== undefined) console.log(` RSS: ${formatRss(stats.rss)}`)
|
||||
return
|
||||
}
|
||||
|
||||
// Show stats for all apps
|
||||
const stats: AppStats[] | undefined = await get('/api/tools/stats/api/stats')
|
||||
if (!stats || stats.length === 0) {
|
||||
console.log('No apps found')
|
||||
return
|
||||
}
|
||||
|
||||
// Sort: running first, then by name
|
||||
stats.sort((a, b) => {
|
||||
if (a.state === 'running' && b.state !== 'running') return -1
|
||||
if (a.state !== 'running' && b.state === 'running') return 1
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
|
||||
// Calculate column widths
|
||||
const nameWidth = Math.max(4, ...stats.map(s => s.name.length + (s.tool ? 7 : 0)))
|
||||
const stateWidth = Math.max(5, ...stats.map(s => s.state.length))
|
||||
|
||||
// Header
|
||||
console.log(
|
||||
color.gray(
|
||||
`${pad('NAME', nameWidth)} ${pad('STATE', stateWidth)} ${pad('PID', 7, true)} ${pad('CPU', 7, true)} ${pad('MEM', 7, true)} ${pad('RSS', 10, true)}`
|
||||
)
|
||||
)
|
||||
|
||||
// Rows
|
||||
for (const s of stats) {
|
||||
const name = s.tool ? `${s.name} ${color.gray('[tool]')}` : s.name
|
||||
const stateColor = s.state === 'running' ? color.green : s.state === 'invalid' ? color.red : color.gray
|
||||
const state = stateColor(s.state)
|
||||
|
||||
const pid = s.pid ? String(s.pid) : '-'
|
||||
const cpu = formatPercent(s.cpu)
|
||||
const mem = formatPercent(s.memory)
|
||||
const rss = formatRss(s.rss)
|
||||
|
||||
console.log(
|
||||
`${pad(name, nameWidth)} ${pad(state, stateWidth)} ${pad(pid, 7, true)} ${pad(cpu, 7, true)} ${pad(mem, 7, true)} ${pad(rss, 10, true)}`
|
||||
)
|
||||
}
|
||||
|
||||
// Summary
|
||||
const running = stats.filter(s => s.state === 'running')
|
||||
const totalCpu = running.reduce((sum, s) => sum + (s.cpu ?? 0), 0)
|
||||
const totalRss = running.reduce((sum, s) => sum + (s.rss ?? 0), 0)
|
||||
|
||||
console.log()
|
||||
console.log(color.gray(`${running.length} running, ${formatPercent(totalCpu)} CPU, ${formatRss(totalRss)} total`))
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import {
|
|||
stashListApp,
|
||||
stashPopApp,
|
||||
startApp,
|
||||
statsApp,
|
||||
statusApp,
|
||||
stopApp,
|
||||
syncApp,
|
||||
|
|
@ -109,6 +110,12 @@ program
|
|||
.option('-g, --grep <pattern>', 'filter logs by pattern')
|
||||
.action(logApp)
|
||||
|
||||
program
|
||||
.command('stats')
|
||||
.description('Show CPU and memory stats for apps')
|
||||
.argument('[name]', 'app name (uses current directory if omitted)')
|
||||
.action(statsApp)
|
||||
|
||||
program
|
||||
.command('open')
|
||||
.description('Open an app in browser')
|
||||
|
|
|
|||
|
|
@ -715,7 +715,7 @@ function startHealthChecks(app: App, port: number) {
|
|||
const controller = new AbortController()
|
||||
const timeout = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT)
|
||||
|
||||
const response = await fetch(`http://localhost:${port}/`, {
|
||||
const response = await fetch(`http://localhost:${port}/ok`, {
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@ import { Hype } from '@because/hype'
|
|||
const app = new Hype()
|
||||
|
||||
app.get('/', c => c.text('$$APP_NAME$$'))
|
||||
app.get('/ok', c => c.text('ok'))
|
||||
|
||||
export default app.defaults
|
||||
|
|
|
|||
|
|
@ -4,5 +4,6 @@ const app = new Hype({ layout: false })
|
|||
|
||||
// custom routes go here
|
||||
// app.get("/my-custom-routes", (c) => c.text("wild, wild stuff"))
|
||||
app.get('/ok', c => c.text('ok'))
|
||||
|
||||
export default app.defaults
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user