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 (
|
return (
|
||||||
<div className="p-4 space-y-4">
|
<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">
|
<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 { Terminal } from '@xterm/xterm';
|
||||||
import { FitAddon } from '@xterm/addon-fit';
|
import { FitAddon } from '@xterm/addon-fit';
|
||||||
import '@xterm/xterm/css/xterm.css';
|
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; }
|
interface Props { onBack: () => void; }
|
||||||
|
|
||||||
type ConnStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
|
|
||||||
|
|
||||||
export default function SerialConsolePage({ onBack }: Props) {
|
export default function SerialConsolePage({ onBack }: Props) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const termRef = useRef<Terminal | null>(null);
|
const termRef = useRef<Terminal | null>(null);
|
||||||
const fitRef = useRef<FitAddon | null>(null);
|
const fitRef = useRef<FitAddon | null>(null);
|
||||||
const wsRef = useRef<WebSocket | null>(null);
|
const { status, send, subscribe } = useWs();
|
||||||
const [status, setStatus] = useState<ConnStatus>('disconnected');
|
|
||||||
|
|
||||||
// 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(() => {
|
useEffect(() => {
|
||||||
const el = containerRef.current;
|
const el = containerRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
@ -42,12 +44,9 @@ export default function SerialConsolePage({ onBack }: Props) {
|
||||||
fitRef.current = fit;
|
fitRef.current = fit;
|
||||||
|
|
||||||
term.writeln('\x1b[2m── Meatloaf Serial Console ──\x1b[0m');
|
term.writeln('\x1b[2m── Meatloaf Serial Console ──\x1b[0m');
|
||||||
term.writeln('\x1b[2mPress Connect to open a session.\x1b[0m');
|
|
||||||
term.writeln('');
|
term.writeln('');
|
||||||
|
|
||||||
term.onData((data) => {
|
term.onData((data) => sendRef.current(data));
|
||||||
if (wsRef.current?.readyState === WebSocket.OPEN) wsRef.current.send(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
const onResize = () => fitRef.current?.fit();
|
const onResize = () => fitRef.current?.fit();
|
||||||
window.addEventListener('resize', onResize);
|
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(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return subscribe((msg) => {
|
||||||
if (wsRef.current) {
|
termRef.current?.write(msg);
|
||||||
wsRef.current.onopen = wsRef.current.onmessage = wsRef.current.onerror = wsRef.current.onclose = null;
|
});
|
||||||
wsRef.current.close();
|
}, [subscribe]);
|
||||||
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';
|
|
||||||
|
|
||||||
const statusColor =
|
const statusColor =
|
||||||
status === 'connected' ? 'text-green-400' :
|
status === 'connected' ? 'text-green-400' :
|
||||||
status === 'connecting' ? 'text-amber-400' :
|
status === 'connecting' ? 'text-amber-400' : 'text-neutral-500';
|
||||||
status === 'error' ? 'text-red-400' : 'text-neutral-500';
|
|
||||||
|
|
||||||
const statusLabel =
|
const statusLabel =
|
||||||
status === 'connected' ? 'Connected' :
|
status === 'connected' ? 'Connected' :
|
||||||
status === 'connecting' ? 'Connecting…' :
|
status === 'connecting' ? 'Connecting…' : 'Disconnected';
|
||||||
status === 'error' ? 'Error' : 'Disconnected';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-[#0a0a0a] z-40 flex flex-col">
|
<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))' }}
|
style={{ paddingTop: 'max(0.5rem, env(safe-area-inset-top))' }}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
onClick={() => { disconnect(); onBack(); }}
|
onClick={onBack}
|
||||||
className="p-1.5 rounded hover:bg-neutral-800 text-neutral-400 hover:text-white transition"
|
className="p-1.5 rounded hover:bg-neutral-800 text-neutral-400 hover:text-white transition"
|
||||||
title="Back"
|
title="Back"
|
||||||
>
|
>
|
||||||
|
|
@ -165,18 +105,6 @@ export default function SerialConsolePage({ onBack }: Props) {
|
||||||
>
|
>
|
||||||
<RotateCcw className="w-4 h-4" />
|
<RotateCcw className="w-4 h-4" />
|
||||||
</button>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Terminal */}
|
{/* Terminal */}
|
||||||
|
|
|
||||||
|
|
@ -941,7 +941,7 @@ class DAVServer(ThreadingMixIn, HTTPServer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# WebDav TCP Port
|
# WebDav TCP Port
|
||||||
srvport = 80
|
srvport = 80
|
||||||
# Get local IP address
|
# Get local IP address
|
||||||
myaddr = get_localip()
|
myaddr = get_localip()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user