Compare commits
7 Commits
dc1decafec
...
9e4629ac2f
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e4629ac2f | |||
| aaf4660816 | |||
| 18cf4243fa | |||
| a041f137c0 | |||
| c16fdaa2a2 | |||
| fca779b064 | |||
| 8e71699ceb |
|
|
@ -44,7 +44,7 @@ async function buildTarget(target: BuildTarget) {
|
||||||
ENTRY_POINT,
|
ENTRY_POINT,
|
||||||
'--compile',
|
'--compile',
|
||||||
'--target',
|
'--target',
|
||||||
'bun',
|
`bun-${target.os}-${target.arch}`,
|
||||||
'--minify',
|
'--minify',
|
||||||
'--sourcemap=external',
|
'--sourcemap=external',
|
||||||
'--outfile',
|
'--outfile',
|
||||||
|
|
@ -52,11 +52,6 @@ async function buildTarget(target: BuildTarget) {
|
||||||
], {
|
], {
|
||||||
stdout: 'inherit',
|
stdout: 'inherit',
|
||||||
stderr: 'inherit',
|
stderr: 'inherit',
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
BUN_TARGET_OS: target.os,
|
|
||||||
BUN_TARGET_ARCH: target.arch,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const exitCode = await proc.exited
|
const exitCode = await proc.exited
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,13 @@ import {
|
||||||
interface AppSelectorProps {
|
interface AppSelectorProps {
|
||||||
render: () => void
|
render: () => void
|
||||||
onSelect?: () => void
|
onSelect?: () => void
|
||||||
|
onDashboard?: () => void
|
||||||
collapsed?: boolean
|
collapsed?: boolean
|
||||||
switcherStyle?: CSSProperties
|
switcherStyle?: CSSProperties
|
||||||
listStyle?: CSSProperties
|
listStyle?: CSSProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AppSelector({ render, onSelect, collapsed, switcherStyle, listStyle }: AppSelectorProps) {
|
export function AppSelector({ render, onSelect, onDashboard, collapsed, switcherStyle, listStyle }: AppSelectorProps) {
|
||||||
const selectApp = (name: string) => {
|
const selectApp = (name: string) => {
|
||||||
setSelectedApp(name)
|
setSelectedApp(name)
|
||||||
onSelect?.()
|
onSelect?.()
|
||||||
|
|
@ -51,6 +52,16 @@ export function AppSelector({ render, onSelect, collapsed, switcherStyle, listSt
|
||||||
</SectionSwitcher>
|
</SectionSwitcher>
|
||||||
)}
|
)}
|
||||||
<AppList style={listStyle}>
|
<AppList style={listStyle}>
|
||||||
|
{collapsed && onDashboard && (
|
||||||
|
<AppItem
|
||||||
|
onClick={onDashboard}
|
||||||
|
selected={!selectedApp ? true : undefined}
|
||||||
|
style={{ justifyContent: 'center', padding: '10px 12px' }}
|
||||||
|
title="Toes"
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: 18 }}>🐾</span>
|
||||||
|
</AppItem>
|
||||||
|
)}
|
||||||
{activeApps.map(app => (
|
{activeApps.map(app => (
|
||||||
<AppItem
|
<AppItem
|
||||||
key={app.name}
|
key={app.name}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import {
|
||||||
AppSelectorChevron,
|
AppSelectorChevron,
|
||||||
DashboardContainer,
|
DashboardContainer,
|
||||||
DashboardHeader,
|
DashboardHeader,
|
||||||
DashboardInstallCmd,
|
|
||||||
DashboardTitle,
|
DashboardTitle,
|
||||||
SettingsGear,
|
SettingsGear,
|
||||||
StatusDot,
|
StatusDot,
|
||||||
|
|
@ -50,9 +49,6 @@ export function DashboardLanding({ render }: { render: () => void }) {
|
||||||
</AppSelectorChevron>
|
</AppSelectorChevron>
|
||||||
)}
|
)}
|
||||||
</DashboardTitle>
|
</DashboardTitle>
|
||||||
<DashboardInstallCmd>
|
|
||||||
curl -fsSL {location.origin}/install | bash
|
|
||||||
</DashboardInstallCmd>
|
|
||||||
</DashboardHeader>
|
</DashboardHeader>
|
||||||
|
|
||||||
<StatusDotsRow>
|
<StatusDotsRow>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { getWifiConfig, saveWifiConfig } from '../api'
|
||||||
import { setCurrentView } from '../state'
|
import { setCurrentView } from '../state'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
DashboardInstallCmd,
|
||||||
FormActions,
|
FormActions,
|
||||||
FormField,
|
FormField,
|
||||||
FormInput,
|
FormInput,
|
||||||
|
|
@ -45,13 +46,19 @@ export function SettingsPage({ render }: { render: () => void }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Main>
|
<Main>
|
||||||
<MainHeader>
|
<MainHeader centered>
|
||||||
<MainTitle>Settings</MainTitle>
|
<MainTitle>Settings</MainTitle>
|
||||||
<HeaderActions>
|
<HeaderActions>
|
||||||
<Button onClick={goBack}>Back</Button>
|
<Button onClick={goBack}>Back</Button>
|
||||||
</HeaderActions>
|
</HeaderActions>
|
||||||
</MainHeader>
|
</MainHeader>
|
||||||
<MainContent>
|
<MainContent centered>
|
||||||
|
<Section>
|
||||||
|
<SectionTitle>Install CLI</SectionTitle>
|
||||||
|
<DashboardInstallCmd>
|
||||||
|
curl -fsSL {location.origin}/install | bash
|
||||||
|
</DashboardInstallCmd>
|
||||||
|
</Section>
|
||||||
<Section>
|
<Section>
|
||||||
<SectionTitle>WiFi</SectionTitle>
|
<SectionTitle>WiFi</SectionTitle>
|
||||||
<form onSubmit={handleSave} style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 400 }}>
|
<form onSubmit={handleSave} style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 400 }}>
|
||||||
|
|
|
||||||
|
|
@ -30,17 +30,27 @@ export function Sidebar({ render }: { render: () => void }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarContainer style={sidebarCollapsed ? { width: 'auto' } : undefined}>
|
<SidebarContainer style={sidebarCollapsed ? { width: 'auto' } : undefined}>
|
||||||
<Logo>
|
{sidebarCollapsed ? (
|
||||||
<LogoLink onClick={goToDashboard} title="Go to dashboard">
|
<div style={{ display: 'flex', justifyContent: 'center', padding: '12px 0' }}>
|
||||||
{sidebarCollapsed ? '🐾' : '🐾 Toes'}
|
<HamburgerButton onClick={toggleSidebar} title="Show sidebar">
|
||||||
</LogoLink>
|
<HamburgerLine />
|
||||||
<HamburgerButton onClick={toggleSidebar} title={sidebarCollapsed ? 'Show sidebar' : 'Hide sidebar'}>
|
<HamburgerLine />
|
||||||
<HamburgerLine />
|
<HamburgerLine />
|
||||||
<HamburgerLine />
|
</HamburgerButton>
|
||||||
<HamburgerLine />
|
</div>
|
||||||
</HamburgerButton>
|
) : (
|
||||||
</Logo>
|
<Logo>
|
||||||
<AppSelector render={render} collapsed={sidebarCollapsed} />
|
<LogoLink onClick={goToDashboard} title="Go to dashboard">
|
||||||
|
🐾 Toes
|
||||||
|
</LogoLink>
|
||||||
|
<HamburgerButton onClick={toggleSidebar} title="Hide sidebar">
|
||||||
|
<HamburgerLine />
|
||||||
|
<HamburgerLine />
|
||||||
|
<HamburgerLine />
|
||||||
|
</HamburgerButton>
|
||||||
|
</Logo>
|
||||||
|
)}
|
||||||
|
<AppSelector render={render} collapsed={sidebarCollapsed} onDashboard={goToDashboard} />
|
||||||
{!sidebarCollapsed && (
|
{!sidebarCollapsed && (
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<NewAppButton onClick={openNewAppModal}>+ New App</NewAppButton>
|
<NewAppButton onClick={openNewAppModal}>+ New App</NewAppButton>
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,13 @@ export const MainHeader = define('MainHeader', {
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
padding: '0 24px',
|
padding: '0 24px',
|
||||||
borderBottom: `1px solid ${theme('colors-border')}`,
|
borderBottom: `1px solid ${theme('colors-border')}`,
|
||||||
|
variants: {
|
||||||
|
centered: {
|
||||||
|
maxWidth: 560,
|
||||||
|
margin: '0 auto',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const MainTitle = define('MainTitle', {
|
export const MainTitle = define('MainTitle', {
|
||||||
|
|
@ -196,6 +203,13 @@ export const MainContent = define('MainContent', {
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
padding: '10px 24px',
|
padding: '10px 24px',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
variants: {
|
||||||
|
centered: {
|
||||||
|
maxWidth: 560,
|
||||||
|
margin: '0 auto',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const DashboardContainer = define('DashboardContainer', {
|
export const DashboardContainer = define('DashboardContainer', {
|
||||||
|
|
|
||||||
|
|
@ -59,9 +59,36 @@ app.all('/api/tools/:tool/:path{.+}', async c => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const BUILD_SCRIPT = import.meta.dir + '/../../scripts/build.ts'
|
||||||
const DIST_DIR = import.meta.dir + '/../../dist'
|
const DIST_DIR = import.meta.dir + '/../../dist'
|
||||||
const INSTALL_SCRIPT = await Bun.file(import.meta.dir + '/install.sh').text()
|
const INSTALL_SCRIPT = await Bun.file(import.meta.dir + '/install.sh').text()
|
||||||
|
|
||||||
|
const BUILD_TARGETS = [
|
||||||
|
'toes-macos-arm64',
|
||||||
|
'toes-macos-x64',
|
||||||
|
'toes-linux-arm64',
|
||||||
|
'toes-linux-x64',
|
||||||
|
]
|
||||||
|
|
||||||
|
const buildInFlight = new Map<string, Promise<boolean>>()
|
||||||
|
|
||||||
|
async function buildBinary(name: string): Promise<boolean> {
|
||||||
|
const existing = buildInFlight.get(name)
|
||||||
|
if (existing) return existing
|
||||||
|
|
||||||
|
const promise = (async () => {
|
||||||
|
const proc = Bun.spawn(
|
||||||
|
['bun', 'run', BUILD_SCRIPT, `--target=${name}`],
|
||||||
|
{ stdout: 'inherit', stderr: 'inherit' },
|
||||||
|
)
|
||||||
|
return (await proc.exited) === 0
|
||||||
|
})()
|
||||||
|
|
||||||
|
buildInFlight.set(name, promise)
|
||||||
|
promise.finally(() => buildInFlight.delete(name))
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
|
||||||
// Install script: curl -fsSL http://toes.local/install | bash
|
// Install script: curl -fsSL http://toes.local/install | bash
|
||||||
app.get('/install', c => {
|
app.get('/install', c => {
|
||||||
if (!TOES_URL) return c.text('TOES_URL is not configured', 500)
|
if (!TOES_URL) return c.text('TOES_URL is not configured', 500)
|
||||||
|
|
@ -69,7 +96,7 @@ app.get('/install', c => {
|
||||||
return c.text(script, 200, { 'content-type': 'text/plain' })
|
return c.text(script, 200, { 'content-type': 'text/plain' })
|
||||||
})
|
})
|
||||||
|
|
||||||
// Serve built CLI binaries from dist/
|
// Serve built CLI binaries from dist/, building on-demand if needed
|
||||||
app.get('/dist/:file', async c => {
|
app.get('/dist/:file', async c => {
|
||||||
const file = c.req.param('file')
|
const file = c.req.param('file')
|
||||||
if (!file || file.includes('/') || file.includes('..')) {
|
if (!file || file.includes('/') || file.includes('..')) {
|
||||||
|
|
@ -77,9 +104,11 @@ app.get('/dist/:file', async c => {
|
||||||
}
|
}
|
||||||
const bunFile = Bun.file(`${DIST_DIR}/${file}`)
|
const bunFile = Bun.file(`${DIST_DIR}/${file}`)
|
||||||
if (!(await bunFile.exists())) {
|
if (!(await bunFile.exists())) {
|
||||||
return c.text(`Binary "${file}" not found — run cli:build:all on the server`, 404)
|
if (!BUILD_TARGETS.includes(file)) return c.text('Not found', 404)
|
||||||
|
const ok = await buildBinary(file)
|
||||||
|
if (!ok) return c.text(`Failed to build "${file}"`, 500)
|
||||||
}
|
}
|
||||||
return new Response(bunFile, {
|
return new Response(Bun.file(`${DIST_DIR}/${file}`), {
|
||||||
headers: { 'content-type': 'application/octet-stream' },
|
headers: { 'content-type': 'application/octet-stream' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user