MVP: Go daemon, password auth, persistent PTY terminals, React/xterm UI, PWA
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
190604247f
commit
436732dac0
@@ -0,0 +1,36 @@
|
||||
export class Unauthenticated extends Error {}
|
||||
|
||||
async function req<T>(method: string, path: string, body?: unknown): Promise<T> {
|
||||
const r = await fetch(path, {
|
||||
method,
|
||||
credentials: 'same-origin',
|
||||
headers: body !== undefined ? { 'Content-Type': 'application/json' } : undefined,
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
if (r.status === 401) throw new Unauthenticated()
|
||||
if (!r.ok) {
|
||||
let msg = r.statusText
|
||||
try { msg = (await r.json()).error ?? msg } catch { /* ignore */ }
|
||||
throw new Error(msg)
|
||||
}
|
||||
if (r.status === 204) return undefined as T
|
||||
return r.json()
|
||||
}
|
||||
|
||||
export interface Me { host: string; user: string; version: string; load: string; uptime: number }
|
||||
export interface Term { id: string; name: string; running: boolean; exit_code: number; created: string; clients: number }
|
||||
|
||||
export const api = {
|
||||
login: (password: string) => req<void>('POST', '/api/login', { password }),
|
||||
logout: () => req<void>('POST', '/api/logout'),
|
||||
me: () => req<Me>('GET', '/api/me'),
|
||||
terminals: () => req<Term[]>('GET', '/api/terminals'),
|
||||
createTerminal: (name?: string) => req<{ id: string; name: string }>('POST', '/api/terminals', { name }),
|
||||
renameTerminal: (id: string, name: string) => req<void>('PATCH', `/api/terminals/${id}`, { name }),
|
||||
deleteTerminal: (id: string) => req<void>('DELETE', `/api/terminals/${id}`),
|
||||
}
|
||||
|
||||
export function wsURL(id: string): string {
|
||||
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
return `${proto}//${location.host}/api/terminals/${id}/attach`
|
||||
}
|
||||
Reference in New Issue
Block a user