fix(GeneralPage): update section title from "General Settings" to "Preferences" for clarity
This commit is contained in:
parent
05be758754
commit
521fb8f613
|
|
@ -42,7 +42,7 @@ export default function GeneralPage({ config, setConfig }: GeneralPageProps) {
|
|||
|
||||
return (
|
||||
<div className="p-4 space-y-4">
|
||||
<h2 className="text-sm text-neutral-500 flex items-center gap-2"><Settings className="w-4 h-4" /> General Settings</h2>
|
||||
<h2 className="text-sm text-neutral-500 flex items-center gap-2"><Settings className="w-4 h-4" /> Preferences</h2>
|
||||
|
||||
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,23 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
import { ChevronLeft, Plug, Radio, RotateCcw, Unplug } from 'lucide-react';
|
||||
import { ChevronLeft, Radio, RotateCcw } from 'lucide-react';
|
||||
import { useWs } from '../ws';
|
||||
|
||||
interface Props { onBack: () => void; }
|
||||
|
||||
type ConnStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
|
||||
|
||||
export default function SerialConsolePage({ onBack }: Props) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const termRef = useRef<Terminal | null>(null);
|
||||
const fitRef = useRef<FitAddon | null>(null);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const [status, setStatus] = useState<ConnStatus>('disconnected');
|
||||
const { status, send, subscribe } = useWs();
|
||||
|
||||
// Initialize xterm on mount
|
||||
// Keep a stable ref to `send` so the xterm onData closure never goes stale
|
||||
const sendRef = useRef(send);
|
||||
useEffect(() => { sendRef.current = send; }, [send]);
|
||||
|
||||
// Initialize xterm once on mount
|
||||
useEffect(() => {
|
||||
const el = containerRef.current;
|
||||
if (!el) return;
|
||||
|
|
@ -42,12 +44,9 @@ export default function SerialConsolePage({ onBack }: Props) {
|
|||
fitRef.current = fit;
|
||||
|
||||
term.writeln('\x1b[2m── Meatloaf Serial Console ──\x1b[0m');
|
||||
term.writeln('\x1b[2mPress Connect to open a session.\x1b[0m');
|
||||
term.writeln('');
|
||||
|
||||
term.onData((data) => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) wsRef.current.send(data);
|
||||
});
|
||||
term.onData((data) => sendRef.current(data));
|
||||
|
||||
const onResize = () => fitRef.current?.fit();
|
||||
window.addEventListener('resize', onResize);
|
||||
|
|
@ -60,79 +59,20 @@ export default function SerialConsolePage({ onBack }: Props) {
|
|||
};
|
||||
}, []);
|
||||
|
||||
// Close WebSocket on unmount
|
||||
// Forward all incoming WS messages to the terminal
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (wsRef.current) {
|
||||
wsRef.current.onopen = wsRef.current.onmessage = wsRef.current.onerror = wsRef.current.onclose = null;
|
||||
wsRef.current.close();
|
||||
wsRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const writeln = (msg: string) => termRef.current?.writeln(msg);
|
||||
|
||||
const connect = () => {
|
||||
if (wsRef.current) {
|
||||
wsRef.current.onopen = wsRef.current.onmessage = wsRef.current.onerror = wsRef.current.onclose = null;
|
||||
wsRef.current.close();
|
||||
wsRef.current = null;
|
||||
}
|
||||
|
||||
const url = `ws://${window.location.hostname}/console`;
|
||||
setStatus('connecting');
|
||||
writeln(`\x1b[2mConnecting to ${url}…\x1b[0m`);
|
||||
|
||||
const ws = new WebSocket(url);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
wsRef.current = ws;
|
||||
|
||||
ws.onopen = () => {
|
||||
setStatus('connected');
|
||||
writeln('\x1b[32mConnected.\x1b[0m');
|
||||
};
|
||||
|
||||
ws.onmessage = (ev) => {
|
||||
if (!termRef.current) return;
|
||||
if (ev.data instanceof ArrayBuffer) termRef.current.write(new Uint8Array(ev.data));
|
||||
else termRef.current.write(String(ev.data));
|
||||
};
|
||||
|
||||
let hadError = false;
|
||||
ws.onerror = () => {
|
||||
hadError = true;
|
||||
writeln('\x1b[31mConnection error.\x1b[0m');
|
||||
};
|
||||
|
||||
ws.onclose = (ev) => {
|
||||
setStatus(hadError ? 'error' : 'disconnected');
|
||||
wsRef.current = null;
|
||||
if (!hadError) writeln(`\x1b[2mSession ended${ev.reason ? `: ${ev.reason}` : ''}.\x1b[0m`);
|
||||
};
|
||||
};
|
||||
|
||||
const disconnect = () => {
|
||||
if (wsRef.current) {
|
||||
wsRef.current.onopen = wsRef.current.onmessage = wsRef.current.onerror = wsRef.current.onclose = null;
|
||||
wsRef.current.close();
|
||||
wsRef.current = null;
|
||||
}
|
||||
setStatus('disconnected');
|
||||
writeln('\x1b[2mDisconnected.\x1b[0m');
|
||||
};
|
||||
|
||||
const busy = status === 'connecting' || status === 'connected';
|
||||
return subscribe((msg) => {
|
||||
termRef.current?.write(msg);
|
||||
});
|
||||
}, [subscribe]);
|
||||
|
||||
const statusColor =
|
||||
status === 'connected' ? 'text-green-400' :
|
||||
status === 'connecting' ? 'text-amber-400' :
|
||||
status === 'error' ? 'text-red-400' : 'text-neutral-500';
|
||||
status === 'connected' ? 'text-green-400' :
|
||||
status === 'connecting' ? 'text-amber-400' : 'text-neutral-500';
|
||||
|
||||
const statusLabel =
|
||||
status === 'connected' ? 'Connected' :
|
||||
status === 'connecting' ? 'Connecting…' :
|
||||
status === 'error' ? 'Error' : 'Disconnected';
|
||||
status === 'connecting' ? 'Connecting…' : 'Disconnected';
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-[#0a0a0a] z-40 flex flex-col">
|
||||
|
|
@ -143,7 +83,7 @@ export default function SerialConsolePage({ onBack }: Props) {
|
|||
style={{ paddingTop: 'max(0.5rem, env(safe-area-inset-top))' }}
|
||||
>
|
||||
<button
|
||||
onClick={() => { disconnect(); onBack(); }}
|
||||
onClick={onBack}
|
||||
className="p-1.5 rounded hover:bg-neutral-800 text-neutral-400 hover:text-white transition"
|
||||
title="Back"
|
||||
>
|
||||
|
|
@ -165,18 +105,6 @@ export default function SerialConsolePage({ onBack }: Props) {
|
|||
>
|
||||
<RotateCcw className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={busy ? disconnect : connect}
|
||||
className={`flex items-center gap-1.5 px-3 py-1 rounded text-xs font-medium transition ${
|
||||
busy
|
||||
? 'bg-red-900/60 hover:bg-red-800 text-red-300'
|
||||
: 'bg-green-900/60 hover:bg-green-800 text-green-300'
|
||||
}`}
|
||||
>
|
||||
{busy ? <Unplug className="w-3.5 h-3.5" /> : <Plug className="w-3.5 h-3.5" />}
|
||||
<span>{busy ? 'Disconnect' : 'Connect'}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Terminal */}
|
||||
|
|
|
|||
|
|
@ -941,7 +941,7 @@ class DAVServer(ThreadingMixIn, HTTPServer):
|
|||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
# WebDav TCP Port
|
||||
# WebDav TCP Port
|
||||
srvport = 80
|
||||
# Get local IP address
|
||||
myaddr = get_localip()
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user