155 lines
4.8 KiB
TypeScript
155 lines
4.8 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 { apps, selectedTab } from '../state'
|
|
import {
|
|
ActionBar,
|
|
Button,
|
|
ClickableAppName,
|
|
HeaderActions,
|
|
InfoLabel,
|
|
InfoRow,
|
|
InfoValue,
|
|
Link,
|
|
Main,
|
|
MainContent,
|
|
MainHeader,
|
|
MainTitle,
|
|
Section,
|
|
SectionTitle,
|
|
stateLabels,
|
|
StatusDot,
|
|
TabContent,
|
|
} from '../styles'
|
|
import { openEmojiPicker } from './emoji-picker'
|
|
import { LogsSection } from './LogsSection'
|
|
import { Nav } from './Nav'
|
|
import { theme } from '../themes'
|
|
|
|
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 }) {
|
|
// Find all tools
|
|
const tools = apps.filter(a => a.tool)
|
|
|
|
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 app={app} 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={`${location.protocol}//${location.hostname}:${app.port}`} target="_blank">
|
|
{location.protocol}//{location.hostname}:{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>
|
|
|
|
<LogsSection app={app} />
|
|
|
|
<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>
|
|
|
|
{tools.map(tool => {
|
|
const toolName = typeof tool.tool === 'string' ? tool.tool : tool.name
|
|
const isSelected = selectedTab === tool.name
|
|
return (
|
|
<TabContent key={tool.name} active={isSelected}>
|
|
<Section>
|
|
{tool.state !== 'running' && (
|
|
<p style={{ color: theme('colors-textFaint') }}>
|
|
Tool is {stateLabels[tool.state].toLowerCase()}
|
|
</p>
|
|
)}
|
|
{/* Target for iframe overlay positioning */}
|
|
{tool.state === 'running' && (
|
|
<div
|
|
data-tool-target={isSelected ? tool.name : undefined}
|
|
style={{ width: '100%', height: '600px' }}
|
|
/>
|
|
)}
|
|
</Section>
|
|
</TabContent>
|
|
)
|
|
})}
|
|
</MainContent>
|
|
</Main>
|
|
)
|
|
}
|