Compare commits
6 Commits
2046af1407
...
13fa2b202a
| Author | SHA1 | Date | |
|---|---|---|---|
| 13fa2b202a | |||
| 65d4fe85cf | |||
| 8fba4cccba | |||
| fdc14a5021 | |||
| 5e21323b54 | |||
| 35341600c1 |
|
|
@ -96,11 +96,24 @@ const RepoName = define('RepoName', {
|
||||||
// Interfaces
|
// Interfaces
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
interface AppRepoProps {
|
||||||
|
appName: string
|
||||||
|
baseUrl: string
|
||||||
|
branch: string
|
||||||
|
exists: boolean
|
||||||
|
commits: boolean
|
||||||
|
}
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
title: string
|
title: string
|
||||||
children: Child
|
children: Child
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RepoListPageProps {
|
||||||
|
baseUrl: string
|
||||||
|
repos: Array<{ name: string; commits: boolean; branch: string }>
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Functions
|
// Functions
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -400,6 +413,123 @@ async function withDeployLock<T>(repo: string, fn: () => Promise<T>): Promise<T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AppRepo({ appName, baseUrl, branch, exists, commits }: AppRepoProps) {
|
||||||
|
return (
|
||||||
|
<Layout title={`Git - ${appName}`}>
|
||||||
|
{exists && commits ? (
|
||||||
|
<>
|
||||||
|
<Heading>Repository</Heading>
|
||||||
|
<RepoList>
|
||||||
|
<RepoItem>
|
||||||
|
<div>
|
||||||
|
<RepoName>{appName}</RepoName>
|
||||||
|
<HelpText style="margin: 4px 0 0; font-size: 12px">
|
||||||
|
git clone {baseUrl}/{appName}
|
||||||
|
</HelpText>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 8px; align-items: center">
|
||||||
|
<Badge>{branch}</Badge>
|
||||||
|
<Badge style={`color: ${theme('colors-statusRunning')}`}>deployed</Badge>
|
||||||
|
</div>
|
||||||
|
</RepoItem>
|
||||||
|
</RepoList>
|
||||||
|
|
||||||
|
<Heading>Push Changes</Heading>
|
||||||
|
<CodeBlock>{[
|
||||||
|
`git push toes ${branch}`,
|
||||||
|
'',
|
||||||
|
'# Or if remote not yet added:',
|
||||||
|
`git remote add toes ${baseUrl}/${appName}`,
|
||||||
|
`git push toes ${branch}`,
|
||||||
|
].join('\n')}</CodeBlock>
|
||||||
|
</>
|
||||||
|
) : exists ? (
|
||||||
|
<>
|
||||||
|
<Heading>Repository</Heading>
|
||||||
|
<RepoList>
|
||||||
|
<RepoItem>
|
||||||
|
<div>
|
||||||
|
<RepoName>{appName}</RepoName>
|
||||||
|
<HelpText style="margin: 4px 0 0; font-size: 12px">
|
||||||
|
git clone {baseUrl}/{appName}
|
||||||
|
</HelpText>
|
||||||
|
</div>
|
||||||
|
<Badge>empty</Badge>
|
||||||
|
</RepoItem>
|
||||||
|
</RepoList>
|
||||||
|
|
||||||
|
<Heading>Push to Deploy</Heading>
|
||||||
|
<CodeBlock>{[
|
||||||
|
`git remote add toes ${baseUrl}/${appName}`,
|
||||||
|
'git push toes main',
|
||||||
|
].join('\n')}</CodeBlock>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Heading>Push to Deploy</Heading>
|
||||||
|
<HelpText>
|
||||||
|
No git repository for <strong>{appName}</strong> yet.
|
||||||
|
Push to create one and deploy.
|
||||||
|
</HelpText>
|
||||||
|
<CodeBlock>{[
|
||||||
|
`git remote add toes ${baseUrl}/${appName}`,
|
||||||
|
'git push toes main',
|
||||||
|
].join('\n')}</CodeBlock>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function RepoListPage({ baseUrl, repos }: RepoListPageProps) {
|
||||||
|
return (
|
||||||
|
<Layout title="Git">
|
||||||
|
<Heading>Push to Deploy</Heading>
|
||||||
|
<HelpText>
|
||||||
|
Push a git repository to deploy it as a toes app.
|
||||||
|
The repo must contain a <code>package.json</code> with a <code>scripts.toes</code> entry.
|
||||||
|
</HelpText>
|
||||||
|
|
||||||
|
<CodeBlock>{[
|
||||||
|
'# Add this server as a remote and push',
|
||||||
|
`git remote add toes ${baseUrl}/<app-name>`,
|
||||||
|
'git push toes main',
|
||||||
|
'',
|
||||||
|
'# Or push an existing repo',
|
||||||
|
`git push ${baseUrl}/<app-name> main`,
|
||||||
|
].join('\n')}</CodeBlock>
|
||||||
|
|
||||||
|
{repos.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Heading>Repositories</Heading>
|
||||||
|
<RepoList>
|
||||||
|
{repos.map(({ name, commits, branch }) => (
|
||||||
|
<RepoItem>
|
||||||
|
<div>
|
||||||
|
<RepoName>{name}</RepoName>
|
||||||
|
<HelpText style="margin: 4px 0 0; font-size: 12px">
|
||||||
|
git clone {baseUrl}/{name}
|
||||||
|
</HelpText>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 8px; align-items: center">
|
||||||
|
<Badge>{branch}</Badge>
|
||||||
|
{commits
|
||||||
|
? <Badge style={`color: ${theme('colors-statusRunning')}`}>deployed</Badge>
|
||||||
|
: <Badge>empty</Badge>}
|
||||||
|
</div>
|
||||||
|
</RepoItem>
|
||||||
|
))}
|
||||||
|
</RepoList>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{repos.length === 0 && (
|
||||||
|
<HelpText>No repositories yet. Push one to get started.</HelpText>
|
||||||
|
)}
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Module init
|
// Module init
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -503,63 +633,30 @@ app.on('POST', ['/:repo{.+\\.git}/git-receive-pack', '/:repo/git-receive-pack'],
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get('/', async c => {
|
app.get('/', async c => {
|
||||||
const repos = await listRepos()
|
const appName = c.req.query('app')
|
||||||
const host = c.req.header('host') ?? 'git.toes.local'
|
const host = c.req.header('host') ?? 'git.toes.local'
|
||||||
|
|
||||||
const baseUrl = `http://${host}`
|
const baseUrl = `http://${host}`
|
||||||
|
|
||||||
|
// When viewing a specific app, only show that app's repo
|
||||||
|
if (appName) {
|
||||||
|
const bare = repoPath(appName)
|
||||||
|
const exists = await dirExists(bare)
|
||||||
|
const [commits, branch] = exists
|
||||||
|
? await Promise.all([hasCommits(bare), getDefaultBranch(bare)])
|
||||||
|
: [false, 'main']
|
||||||
|
|
||||||
|
return c.html(<AppRepo appName={appName} baseUrl={baseUrl} branch={branch} exists={exists} commits={commits} />)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No app selected — show all repos
|
||||||
|
const repos = await listRepos()
|
||||||
const repoData = await Promise.all(repos.map(async name => {
|
const repoData = await Promise.all(repos.map(async name => {
|
||||||
const bare = repoPath(name)
|
const bare = repoPath(name)
|
||||||
const [commits, branch] = await Promise.all([hasCommits(bare), getDefaultBranch(bare)])
|
const [commits, branch] = await Promise.all([hasCommits(bare), getDefaultBranch(bare)])
|
||||||
return { name, commits, branch }
|
return { name, commits, branch }
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return c.html(
|
return c.html(<RepoListPage baseUrl={baseUrl} repos={repoData} />)
|
||||||
<Layout title="Git">
|
|
||||||
<Heading>Push to Deploy</Heading>
|
|
||||||
<HelpText>
|
|
||||||
Push a git repository to deploy it as a toes app.
|
|
||||||
The repo must contain a <code>package.json</code> with a <code>scripts.toes</code> entry.
|
|
||||||
</HelpText>
|
|
||||||
|
|
||||||
<CodeBlock>{[
|
|
||||||
'# Add this server as a remote and push',
|
|
||||||
`git remote add toes ${baseUrl}/<app-name>.git`,
|
|
||||||
'git push toes main',
|
|
||||||
'',
|
|
||||||
'# Or push an existing repo',
|
|
||||||
`git push ${baseUrl}/<app-name>.git main`,
|
|
||||||
].join('\n')}</CodeBlock>
|
|
||||||
|
|
||||||
{repoData.length > 0 && (
|
|
||||||
<>
|
|
||||||
<Heading>Repositories</Heading>
|
|
||||||
<RepoList>
|
|
||||||
{repoData.map(({ name, commits, branch }) => (
|
|
||||||
<RepoItem>
|
|
||||||
<div>
|
|
||||||
<RepoName>{name}</RepoName>
|
|
||||||
<HelpText style="margin: 4px 0 0; font-size: 12px">
|
|
||||||
git clone {baseUrl}/{name}.git
|
|
||||||
</HelpText>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; gap: 8px; align-items: center">
|
|
||||||
<Badge>{branch}</Badge>
|
|
||||||
{commits
|
|
||||||
? <Badge style={`color: ${theme('colors-statusRunning')}`}>deployed</Badge>
|
|
||||||
: <Badge>empty</Badge>}
|
|
||||||
</div>
|
|
||||||
</RepoItem>
|
|
||||||
))}
|
|
||||||
</RepoList>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{repoData.length === 0 && (
|
|
||||||
<HelpText>No repositories yet. Push one to get started.</HelpText>
|
|
||||||
)}
|
|
||||||
</Layout>,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export default app.defaults
|
export default app.defaults
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,14 @@ function convert(app: BackendApp): SharedApp {
|
||||||
// SSE: full app state snapshots for the dashboard UI (every state change)
|
// SSE: full app state snapshots for the dashboard UI (every state change)
|
||||||
// For discrete lifecycle events consumed by app processes, see /api/events/stream
|
// For discrete lifecycle events consumed by app processes, see /api/events/stream
|
||||||
router.sse('/stream', (send) => {
|
router.sse('/stream', (send) => {
|
||||||
|
let queue = Promise.resolve()
|
||||||
const broadcast = () => {
|
const broadcast = () => {
|
||||||
const apps: SharedApp[] = allApps().map(({
|
const apps: SharedApp[] = allApps().map(({
|
||||||
name, state, icon, error, port, started, logs, tool, tunnelEnabled, tunnelUrl
|
name, state, icon, error, port, started, logs, tool, tunnelEnabled, tunnelUrl
|
||||||
}) => ({
|
}) => ({
|
||||||
name, state, icon, error, port, started, logs, tool, tunnelEnabled, tunnelUrl,
|
name, state, icon, error, port, started, logs, tool, tunnelEnabled, tunnelUrl,
|
||||||
}))
|
}))
|
||||||
send(apps)
|
queue = queue.then(() => send(apps))
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcast()
|
broadcast()
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,12 @@ const router = Hype.router()
|
||||||
// Unlike /api/apps/stream (full state snapshots for the dashboard), this sends
|
// Unlike /api/apps/stream (full state snapshots for the dashboard), this sends
|
||||||
// individual events so apps can react to specific lifecycle changes.
|
// individual events so apps can react to specific lifecycle changes.
|
||||||
router.sse('/stream', (send) => {
|
router.sse('/stream', (send) => {
|
||||||
const unsub = onEvent(event => send(event))
|
let queue = Promise.resolve()
|
||||||
const heartbeat = setInterval(() => send('', 'ping'), 60_000)
|
const safeSend = (...args: Parameters<typeof send>) => {
|
||||||
|
queue = queue.then(() => send(...args))
|
||||||
|
}
|
||||||
|
const unsub = onEvent(event => safeSend(event))
|
||||||
|
const heartbeat = setInterval(() => safeSend('', 'ping'), 60_000)
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(heartbeat)
|
clearInterval(heartbeat)
|
||||||
unsub()
|
unsub()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user