Compare commits

..

No commits in common. "07300eb96f8e0a4b4867baf06d670452d922f528" and "3068c719cd5a8e6004fdd0ea13533d78371ff50c" have entirely different histories.

5 changed files with 11 additions and 110 deletions

View File

@ -1,4 +1,4 @@
import { Hype } from '@because/hype' import { Hype } from 'hype'
const app = new Hype const app = new Hype

View File

@ -1,4 +1,4 @@
import { Hype } from '@because/hype' import { Hype } from 'hype'
import { define, stylesToCSS } from '@because/forge' import { define, stylesToCSS } from '@because/forge'
const app = new Hype() const app = new Hype()

View File

@ -1,4 +1,4 @@
import { Hype } from '@because/hype' import { Hype } from 'hype'
const app = new Hype const app = new Hype

View File

@ -276,20 +276,18 @@ async function getApp(name: string) {
console.log(color.green(`✓ Downloaded ${name}`)) console.log(color.green(`✓ Downloaded ${name}`))
} }
function getAppPackage(): { name?: string; scripts?: { toes?: string } } | null { function isApp(): boolean {
try { try {
return JSON.parse(readFileSync(join(process.cwd(), 'package.json'), 'utf-8')) const pkg = JSON.parse(readFileSync(join(process.cwd(), 'package.json'), 'utf-8'))
} catch { return !!pkg?.scripts?.toes
return null } catch (e) {
return false
} }
} }
const isApp = () => !!getAppPackage()?.scripts?.toes
function resolveAppName(name?: string): string | undefined { function resolveAppName(name?: string): string | undefined {
if (name) return name if (name) return name
const pkg = getAppPackage() if (isApp()) return basename(process.cwd())
if (pkg?.scripts?.toes) return pkg.name || basename(process.cwd())
console.error('No app specified and current directory is not a toes app') console.error('No app specified and current directory is not a toes app')
return undefined return undefined
} }
@ -650,22 +648,6 @@ async function rmApp(arg?: string) {
program program
.name('toes') .name('toes')
.version('0.0.1', '-v, --version') .version('0.0.1', '-v, --version')
.addHelpText('beforeAll', (ctx) => {
if (ctx.command === program) {
return color.bold().cyan('\n🐾 Toes') + color.gray(' - personal web appliance\n')
}
return ''
})
.configureOutput({
writeOut: (str) => {
const colored = str
.replace(/^(Usage:)/gm, color.yellow('$1'))
.replace(/^(Commands:)/gm, color.yellow('$1'))
.replace(/^(Options:)/gm, color.yellow('$1'))
.replace(/^(Arguments:)/gm, color.yellow('$1'))
process.stdout.write(colored)
},
})
program program
.command('info') .command('info')

View File

@ -403,11 +403,6 @@ const FormActions = define('FormActions', {
let newAppError = '' let newAppError = ''
let newAppCreating = false let newAppCreating = false
// Delete App confirmation
let deleteAppError = ''
let deleteAppDeleting = false
let deleteAppTarget: App | null = null
async function createNewApp(input: HTMLInputElement) { async function createNewApp(input: HTMLInputElement) {
const name = input.value.trim().toLowerCase().replace(/\s+/g, '-') const name = input.value.trim().toLowerCase().replace(/\s+/g, '-')
@ -489,82 +484,6 @@ function openNewAppModal() {
)) ))
} }
// Delete App confirmation modal
async function deleteApp(input: HTMLInputElement) {
if (!deleteAppTarget) return
const expected = `sudo rm ${deleteAppTarget.name}`
const value = input.value.trim()
if (value !== expected) {
deleteAppError = `Type "${expected}" to confirm`
rerenderModal()
return
}
deleteAppDeleting = true
deleteAppError = ''
rerenderModal()
try {
const res = await fetch(`/api/sync/apps/${deleteAppTarget.name}`, {
method: 'DELETE',
})
if (!res.ok) {
throw new Error(`Failed to delete app: ${res.statusText}`)
}
// Success - close modal and clear selection
if (selectedApp === deleteAppTarget.name) {
selectedApp = null
localStorage.removeItem('selectedApp')
}
closeModal()
} catch (err) {
deleteAppError = err instanceof Error ? err.message : 'Failed to delete app'
deleteAppDeleting = false
rerenderModal()
}
}
function openDeleteAppModal(app: App) {
deleteAppError = ''
deleteAppDeleting = false
deleteAppTarget = app
const expected = `sudo rm ${app.name}`
openModal('Delete App', () => (
<Form onSubmit={(e: Event) => {
e.preventDefault()
const input = (e.target as HTMLFormElement).querySelector('input') as HTMLInputElement
deleteApp(input)
}}>
<p style={{ margin: '0 0 16px', color: theme('colors-textMuted') }}>
This will <strong style={{ color: theme('colors-error') }}>permanently delete</strong> <strong>{app.name}</strong> from the server.
</p>
<FormField>
<FormLabel for="delete-confirm">Type "{expected}" to confirm</FormLabel>
<FormInput
id="delete-confirm"
type="text"
placeholder={expected}
autofocus
/>
{deleteAppError && <FormError>{deleteAppError}</FormError>}
</FormField>
<FormActions>
<Button type="button" onClick={closeModal} disabled={deleteAppDeleting}>
Cancel
</Button>
<Button type="submit" variant="danger" disabled={deleteAppDeleting}>
{deleteAppDeleting ? 'Deleting...' : 'Delete App'}
</Button>
</FormActions>
</Form>
))
}
// Actions - call API then let SSE update the state // Actions - call API then let SSE update the state
const startApp = (name: string) => fetch(`/api/apps/${name}/start`, { method: 'POST' }) const startApp = (name: string) => fetch(`/api/apps/${name}/start`, { method: 'POST' })
const stopApp = (name: string) => fetch(`/api/apps/${name}/stop`, { method: 'POST' }) const stopApp = (name: string) => fetch(`/api/apps/${name}/stop`, { method: 'POST' })
@ -605,8 +524,8 @@ const AppDetail = ({ app }: { app: App }) => (
{app.name} {app.name}
</MainTitle> </MainTitle>
<HeaderActions> <HeaderActions>
{/* <Button>Settings</Button> */} <Button>Settings</Button>
<Button variant="danger" onClick={() => openDeleteAppModal(app)}>Delete</Button> <Button variant="danger">Delete</Button>
</HeaderActions> </HeaderActions>
</MainHeader> </MainHeader>
<MainContent> <MainContent>