Files tab: browser with breadcrumbs/sort/filter, text editor, image preview, streamed upload with progress, download, mkdir/touch/rename/move/chmod/delete, open-in-terminal; root toggle via sudo-reexecuted 'hamlab fs' helper
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
38d4ee09ae
commit
63b020d556
+16
-2
@@ -8,6 +8,7 @@ import Services from './Services'
|
||||
import Cron from './Cron'
|
||||
import Updates from './Updates'
|
||||
import Disks from './Disks'
|
||||
import Files from './Files'
|
||||
import Power from './Power'
|
||||
import { Btn, NetBar, Toasts, toast } from './ui'
|
||||
|
||||
@@ -18,9 +19,10 @@ const GROUPS: Group[] = [
|
||||
{ id: 'cron', label: 'Cron', icon: '⏱', page: true },
|
||||
{ id: 'updates', label: 'Updates', icon: '⇪', page: true },
|
||||
{ id: 'disks', label: 'Disks', icon: '▤', page: true },
|
||||
{ id: 'files', label: 'Files', icon: '🗀', page: true },
|
||||
]
|
||||
|
||||
type View = 'terminal' | 'dashboard' | 'services' | 'cron' | 'updates' | 'disks'
|
||||
type View = 'terminal' | 'dashboard' | 'services' | 'cron' | 'updates' | 'disks' | 'files'
|
||||
|
||||
function fmtUptime(s: number): string {
|
||||
const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600), m = Math.floor((s % 3600) / 60)
|
||||
@@ -68,6 +70,17 @@ export default function Shell({ me, onLoggedOut }: { me: Me; onLoggedOut: () =>
|
||||
}
|
||||
async function logout() { await api.logout(); onLoggedOut() }
|
||||
|
||||
// Files → "open in terminal": type into the active shell (creating one if needed) and switch to it.
|
||||
async function runInTerminal(cmd: string) {
|
||||
let id = active
|
||||
if (!id || !terms.some((t) => t.id === id && t.running)) {
|
||||
try { const t = await api.createTerminal(); await reload(); id = t.id; setActive(t.id) } catch (e) { toast(`terminal — ${e instanceof Error ? e.message : 'failed'}`, 'err'); return }
|
||||
}
|
||||
setView('terminal'); setDrawer(false)
|
||||
const send = (tries: number) => { const h = handles.current[id!]; if (h) h.input(cmd + '\n'); else if (tries > 0) window.setTimeout(() => send(tries - 1), 100) }
|
||||
window.setTimeout(() => send(20), 50)
|
||||
}
|
||||
|
||||
const activeTerm = terms.find((t) => t.id === active)
|
||||
const termView = view === 'terminal'
|
||||
|
||||
@@ -99,6 +112,7 @@ export default function Shell({ me, onLoggedOut }: { me: Me; onLoggedOut: () =>
|
||||
{view === 'cron' && <Cron />}
|
||||
{view === 'updates' && <Updates />}
|
||||
{view === 'disks' && <Disks />}
|
||||
{view === 'files' && <Files onOpenInTerminal={runInTerminal} />}
|
||||
{termView && terms.length === 0 && (
|
||||
<div className="center muted">
|
||||
<div>No terminals.</div>
|
||||
@@ -121,7 +135,7 @@ export default function Shell({ me, onLoggedOut }: { me: Me; onLoggedOut: () =>
|
||||
<span>{me.version.startsWith('v') ? me.version : 'v' + me.version}</span>
|
||||
<span>up {fmtUptime(me.uptime)}</span>
|
||||
<span className="spacer" />
|
||||
<span className="muted">{view === 'dashboard' ? 'Dashboard' : view === 'services' ? 'Services' : view === 'cron' ? 'Cron' : view === 'updates' ? 'Updates' : view === 'disks' ? 'Disks' : activeTerm ? activeTerm.name : ''}</span>
|
||||
<span className="muted">{view === 'dashboard' ? 'Dashboard' : view === 'services' ? 'Services' : view === 'cron' ? 'Cron' : view === 'updates' ? 'Updates' : view === 'disks' ? 'Disks' : view === 'files' ? 'Files' : activeTerm ? activeTerm.name : ''}</span>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user