toes/src/pages/index.tsx

141 lines
3.0 KiB
TypeScript

import { define, Styles } from 'forge'
import { allApps } from '../server/apps'
import type { AppState } from '../shared/types'
const Apps = define('Apps', {
margin: '0 auto',
width: 750,
paddingTop: 20,
})
const color = '#00c0c9'
const hoverColor = 'magenta'
const Link = define({
base: 'a',
color,
textDecoration: 'none',
borderBottom: `1px solid ${color}`,
selectors: {
'&:hover': {
color: hoverColor,
cursor: 'pointer'
}
}
})
const AppCard = define('AppCard', {
marginBottom: 24,
padding: 16,
border: '1px solid #333',
borderRadius: 8,
})
const AppHeader = define('AppHeader', {
display: 'flex',
alignItems: 'center',
gap: 12,
marginBottom: 8,
})
const AppName = define('AppName', {
fontSize: 20,
fontWeight: 'bold',
margin: 0,
})
const State = define('State', {
fontSize: 14,
padding: '2px 8px',
borderRadius: 4,
variants: {
status: {
invalid: { background: '#4a1c1c', color: '#f87171' },
stopped: { background: '#3a3a3a', color: '#9ca3af' },
starting: { background: '#3b3117', color: '#fbbf24' },
running: { background: '#14532d', color: '#4ade80' },
stopping: { background: '#3b3117', color: '#fbbf24' },
}
}
})
const Info = define('Info', {
fontSize: 14,
color: '#9ca3af',
margin: '4px 0',
})
const ActionBar = define('ActionBar', {
marginTop: 12,
display: 'flex',
gap: 8,
})
const Button = define({
base: 'button',
selectors: {
'form:has(>&)': {
display: 'inline'
}
},
render({ props, parts: { Root } }) {
if (!props.post)
return <Root>{props.children}</Root>
return (
<form method='post' action={props.post}>
<Root onClick="this.closest('form').submit()">{props.children}</Root>
</form>
)
}
})
const stateLabels: Record<AppState, string> = {
invalid: 'Invalid',
stopped: 'Stopped',
starting: 'Starting...',
running: 'Running',
stopping: 'Stopping...',
}
export default () => (
<Apps>
<Styles />
<h1>🐾 Apps</h1>
{allApps().map(app => (
<AppCard>
<AppHeader>
<AppName>
{app.state === 'running' && app.port ? (
<Link href={`http://localhost:${app.port}`}>{app.name}</Link>
) : (
app.name
)}
</AppName>
<State status={app.state}>{stateLabels[app.state]}</State>
</AppHeader>
{app.port && <Info>Port: {app.port}</Info>}
{app.started && <Info>Started: {new Date(app.started).toLocaleString()}</Info>}
<ActionBar>
{app.state === 'stopped' && (
<Button post={`/apps/${app.name}/start`}>Start</Button>
)}
{app.state === 'running' && (
<>
<Button post={`/apps/${app.name}/stop`}>Stop</Button>
<Button post={`/apps/${app.name}/restart`}>Restart</Button>
</>
)}
{app.state === 'invalid' && (
<Info>Missing or invalid package.json</Info>
)}
</ActionBar>
</AppCard>
))}
</Apps>
)