152 lines
4.5 KiB
TypeScript
152 lines
4.5 KiB
TypeScript
import { define } from '@because/forge'
|
|
import type { App } from '../../shared/types'
|
|
import { restartApp, startApp, stopApp } from '../api'
|
|
import { openDeleteAppModal, openRenameAppModal } from '../modals'
|
|
import { selectedTab } from '../state'
|
|
import {
|
|
ActionBar,
|
|
Button,
|
|
ClickableAppName,
|
|
HeaderActions,
|
|
InfoLabel,
|
|
InfoRow,
|
|
InfoValue,
|
|
Link,
|
|
LogLine,
|
|
LogsContainer,
|
|
LogTime,
|
|
Main,
|
|
MainContent,
|
|
MainHeader,
|
|
MainTitle,
|
|
Section,
|
|
SectionTitle,
|
|
stateLabels,
|
|
StatusDot,
|
|
TabContent,
|
|
} from '../styles'
|
|
import { openEmojiPicker } from './emoji-picker'
|
|
import { theme } from '../themes'
|
|
import { Nav } from './Nav'
|
|
|
|
const OpenEmojiPicker = define('OpenEmojiPicker', {
|
|
cursor: 'pointer',
|
|
|
|
render({ props: { app, children, render: renderFn }, parts: { Root } }) {
|
|
return <Root onClick={() => openEmojiPicker((emoji) => {
|
|
if (!app) return
|
|
|
|
fetch(`/api/apps/${app.name}/icon?icon=${emoji}`, { method: 'POST' })
|
|
app.icon = emoji
|
|
renderFn()
|
|
})}>{children}</Root>
|
|
}
|
|
})
|
|
|
|
export function AppDetail({ app, render }: { app: App, render: () => void }) {
|
|
return (
|
|
<Main>
|
|
<MainHeader>
|
|
<MainTitle>
|
|
<OpenEmojiPicker app={app} render={render}>{app.icon}</OpenEmojiPicker>
|
|
|
|
<ClickableAppName onClick={() => openRenameAppModal(app)}>{app.name}</ClickableAppName>
|
|
</MainTitle>
|
|
<HeaderActions>
|
|
<Button variant="danger" onClick={() => openDeleteAppModal(app)}>Delete</Button>
|
|
</HeaderActions>
|
|
</MainHeader>
|
|
<MainContent>
|
|
<Nav render={render} />
|
|
|
|
<TabContent active={selectedTab === 'overview'}>
|
|
<Section>
|
|
<SectionTitle>Status</SectionTitle>
|
|
<InfoRow>
|
|
<InfoLabel>State</InfoLabel>
|
|
<InfoValue>
|
|
<StatusDot state={app.state} />
|
|
{stateLabels[app.state]}
|
|
</InfoValue>
|
|
</InfoRow>
|
|
{app.state === 'running' && app.port && (
|
|
<InfoRow>
|
|
<InfoLabel>URL</InfoLabel>
|
|
<InfoValue>
|
|
<Link href={`http://localhost:${app.port}`} target="_blank">
|
|
http://localhost:{app.port}
|
|
</Link>
|
|
</InfoValue>
|
|
</InfoRow>
|
|
)}
|
|
{app.state === 'running' && app.port && (
|
|
<InfoRow>
|
|
<InfoLabel>Port</InfoLabel>
|
|
<InfoValue>
|
|
{app.port}
|
|
</InfoValue>
|
|
</InfoRow>
|
|
)}
|
|
{app.started && (
|
|
<InfoRow>
|
|
<InfoLabel>Started</InfoLabel>
|
|
<InfoValue>{new Date(app.started).toLocaleString()}</InfoValue>
|
|
</InfoRow>
|
|
)}
|
|
{app.error && (
|
|
<InfoRow>
|
|
<InfoLabel>Error</InfoLabel>
|
|
<InfoValue style={{ color: theme('colors-error') }}>
|
|
{app.error}
|
|
</InfoValue>
|
|
</InfoRow>
|
|
)}
|
|
</Section>
|
|
|
|
<Section>
|
|
<SectionTitle>Logs</SectionTitle>
|
|
<LogsContainer>
|
|
{app.logs?.length ? (
|
|
app.logs.map((line, i) => (
|
|
<LogLine key={i}>
|
|
<LogTime>{new Date(line.time).toLocaleTimeString()}</LogTime>
|
|
<span>{line.text}</span>
|
|
</LogLine>
|
|
))
|
|
) : (
|
|
<LogLine>
|
|
<LogTime>--:--:--</LogTime>
|
|
<span style={{ color: theme('colors-textFaint') }}>No logs yet</span>
|
|
</LogLine>
|
|
)}
|
|
</LogsContainer>
|
|
</Section>
|
|
|
|
<ActionBar>
|
|
{app.state === 'stopped' && (
|
|
<Button variant="primary" onClick={() => startApp(app.name)}>
|
|
Start
|
|
</Button>
|
|
)}
|
|
{app.state === 'running' && (
|
|
<>
|
|
<Button onClick={() => restartApp(app.name)}>Restart</Button>
|
|
<Button variant="danger" onClick={() => stopApp(app.name)}>
|
|
Stop
|
|
</Button>
|
|
</>
|
|
)}
|
|
{(app.state === 'starting' || app.state === 'stopping') && (
|
|
<Button disabled>{stateLabels[app.state]}...</Button>
|
|
)}
|
|
</ActionBar>
|
|
</TabContent>
|
|
|
|
<TabContent active={selectedTab === 'todo'}>
|
|
<h1>hardy har har</h1>
|
|
</TabContent>
|
|
</MainContent>
|
|
</Main>
|
|
)
|
|
}
|