Idle sessions sign themselves out; a KVM's login is remembered; the compose editor knows YAML

A session unused for seven days is dropped, the Sessions list marks a day's idleness and
can sign all of those out at once. The hub keeps a KVM's own cookies from any browser that
has them and lends them to one that hasn't, on the wire and back to the page. The stack
editor and the new-stack dialog use CodeEditor: line numbers, YAML and .env colour under a
real textarea, Tab/Shift+Tab/Enter indentation, and a key bar on touch screens.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-10-06 01:20:27 -07:00
co-authored by Claude Fable 5.1
parent faaa6aaf9d
commit f3ca3d8f73
13 changed files with 531 additions and 20 deletions
+2 -1
View File
@@ -6,6 +6,7 @@
import { useEffect, useMemo, useState } from 'react'
import { api, StackHome } from './api'
import { askYes, Btn, Loading, useEscape } from './ui'
import CodeEditor from './CodeEditor'
interface Form { image: string; ports: string; volumes: string; env: string; labels: string; restart: string; command: string; network: string }
const EMPTY: Form = { image: '', ports: '', volumes: '', env: '', labels: '', restart: 'unless-stopped', command: '', network: '' }
@@ -119,7 +120,7 @@ export default function StackNew({ mode, from, networks = [], taken, onClose, on
<label className="muted">Command (optional)<input className="mono" value={form.command} onChange={set('command')} spellCheck={false} autoCapitalize="off" /></label>
<pre className="unit-file stack-preview">{built}</pre>
</> : <>
<textarea className="editor stack-editor" value={text} onChange={(e) => setText(e.target.value)} spellCheck={false} autoCapitalize="off" autoCorrect="off" />
<CodeEditor className="stack-editor" language="yaml" value={text} onChange={setText} />
{from?.hasEnv && <label className="check"><input type="checkbox" checked={copyEnv} onChange={(e) => setCopyEnv(e.target.checked)} /> <span>Copy {from.name}'s .env too <span className="muted">— copied on the machine itself; its values are never shown here</span></span></label>}
{!(from && copyEnv) && <label className="muted">.env (optional) — variables the compose file reads; kept readable by root only
<textarea className="mono" rows={2} value={env} onChange={(e) => setEnv(e.target.value)} placeholder={'SECRET=…'} spellCheck={false} />