feat(App, DevicesPage, ToolsPage): remove OtherPage and enhance device settings management with hardware, modem, cassette, and BOIP configurations

This commit is contained in:
Jaime Idolpx 2026-06-09 03:48:24 -04:00
parent 48f0de753e
commit 7892ce699e
4 changed files with 182 additions and 275 deletions

View File

@ -6,7 +6,6 @@ import DevicesPage from './components/DevicesPage';
import GeneralPage from './components/GeneralPage';
import NetworkPage from './components/NetworkPage';
import IECPage from './components/IECPage';
import OtherPage from './components/OtherPage';
import ToolsPage from './components/ToolsPage';
import SearchOverlay from './components/SearchOverlay';
import MediaManager from './components/MediaManager';
@ -16,7 +15,7 @@ import logoSvg from '../imports/logo.svg';
import { useSettings } from './settings';
import { WsProvider } from './ws';
type Page = 'status' | 'devices' | 'iec' | 'network' | 'other' | 'general' | 'tools' | 'apps' | AppId;
type Page = 'status' | 'devices' | 'iec' | 'network' | 'general' | 'tools' | 'apps' | AppId;
type AppId =
| 'file-manager'
@ -73,7 +72,6 @@ export default function App() {
devices: <DevicesPage config={config} setConfig={setConfig} openDeviceId={devicesOpenId} onClearOpenDevice={() => setDevicesOpenId(null)} />,
iec: <IECPage config={config} setConfig={setConfig} />,
network: <NetworkPage config={config} setConfig={setConfig} />,
other: <OtherPage config={config} setConfig={setConfig} />,
general: <GeneralPage config={config} setConfig={setConfig} />,
tools: <ToolsPage config={config} setConfig={setConfig} />,
apps: (
@ -327,7 +325,7 @@ function AppPage({ title, onBack }: { title: string; onBack: () => void }) {
<span className="text-xs text-white">Network</span>
</button>
<button
onClick={() => setCurrentPage('other')}
onClick={() => setCurrentPage('devices')}
className="flex-1 flex flex-col items-center gap-1 py-2"
>
<MoreHorizontal className="w-5 h-5 text-white" />

View File

@ -36,6 +36,11 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
const [selectedDeviceIndex, setSelectedDeviceIndex] = useState<number | null>(null);
const [isScanning, setIsScanning] = useState(false);
const hardware = config.hardware || {};
const modem = config.modem || {};
const cassette = config.cassette || {};
const boip = config.boip || {};
const devices: Device[] = [];
// Printer devices
@ -319,6 +324,112 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
hasNext={selectedDeviceIndex < devices.length - 1}
/>
)}
{/* ── Hardware ── */}
<h2 className="text-sm text-neutral-500 pt-4">Hardware</h2>
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
<div className="p-4 flex items-center justify-between">
<label className="text-sm text-neutral-500">PS/2</label>
<button
onClick={() => updateSetting(['hardware', 'ps2'], hardware.ps2 ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${hardware.ps2 ? 'bg-blue-600' : 'bg-neutral-300'}`}
>
<div className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${hardware.ps2 ? 'translate-x-6' : 'translate-x-0.5'}`} />
</button>
</div>
<div className="p-4 flex items-center justify-between">
<label className="text-sm text-neutral-500">User Port Enabled</label>
<button
onClick={() => updateSetting(['hardware', 'userport', 'enabled'], hardware.userport?.enabled ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${hardware.userport?.enabled ? 'bg-blue-600' : 'bg-neutral-300'}`}
>
<div className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${hardware.userport?.enabled ? 'translate-x-6' : 'translate-x-0.5'}`} />
</button>
</div>
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">User Port Mode</label>
<select
value={hardware.userport?.mode?.split('|')[0] || 'serial'}
onChange={(e) => updateSetting(['hardware', 'userport', 'mode'], e.target.value)}
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
>
<option value="serial">Serial</option>
<option value="parallel">Parallel</option>
<option value="IEEE-488">IEEE-488</option>
</select>
</div>
</div>
{/* ── Modem ── */}
<h2 className="text-sm text-neutral-500 pt-4">Modem</h2>
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
<div className="p-4 flex items-center justify-between">
<label className="text-sm text-neutral-500">Modem Enabled</label>
<button
onClick={() => updateSetting(['modem', 'modem_enabled'], modem.modem_enabled ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${modem.modem_enabled ? 'bg-blue-600' : 'bg-neutral-300'}`}
>
<div className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${modem.modem_enabled ? 'translate-x-6' : 'translate-x-0.5'}`} />
</button>
</div>
<div className="p-4 flex items-center justify-between">
<label className="text-sm text-neutral-500">Sniffer Enabled</label>
<button
onClick={() => updateSetting(['modem', 'sniffer_enabled'], modem.sniffer_enabled ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${modem.sniffer_enabled ? 'bg-blue-600' : 'bg-neutral-300'}`}
>
<div className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${modem.sniffer_enabled ? 'translate-x-6' : 'translate-x-0.5'}`} />
</button>
</div>
</div>
{/* ── Cassette ── */}
<h2 className="text-sm text-neutral-500 pt-4">Cassette</h2>
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
<div className="p-4 flex items-center justify-between">
<label className="text-sm text-neutral-500">Enabled</label>
<button
onClick={() => updateSetting(['cassette', 'enabled'], cassette.enabled ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${cassette.enabled ? 'bg-blue-600' : 'bg-neutral-300'}`}
>
<div className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${cassette.enabled ? 'translate-x-6' : 'translate-x-0.5'}`} />
</button>
</div>
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">Play/Record</label>
<input type="text" value={cassette.play_record || ''} onChange={(e) => updateSetting(['cassette', 'play_record'], e.target.value)} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
</div>
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">Pulldown</label>
<input type="text" value={cassette.pulldown || ''} onChange={(e) => updateSetting(['cassette', 'pulldown'], e.target.value)} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
</div>
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">URL</label>
<input type="text" value={cassette.url || ''} onChange={(e) => updateSetting(['cassette', 'url'], e.target.value)} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
</div>
</div>
{/* ── BOIP ── */}
<h2 className="text-sm text-neutral-500 pt-4">BOIP</h2>
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
<div className="p-4 flex items-center justify-between">
<label className="text-sm text-neutral-500">Enabled</label>
<button
onClick={() => updateSetting(['boip', 'enabled'], boip.enabled ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${boip.enabled ? 'bg-blue-600' : 'bg-neutral-300'}`}
>
<div className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${boip.enabled ? 'translate-x-6' : 'translate-x-0.5'}`} />
</button>
</div>
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">Host</label>
<input type="text" value={boip.host || ''} onChange={(e) => updateSetting(['boip', 'host'], e.target.value)} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
</div>
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">Port</label>
<input type="text" value={boip.port || ''} onChange={(e) => updateSetting(['boip', 'port'], e.target.value)} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
</div>
</div>
</div>
);
}

View File

@ -1,202 +0,0 @@
interface OtherPageProps {
config: any;
setConfig: (config: any) => void;
}
export default function OtherPage({ config, setConfig }: OtherPageProps) {
const updateSetting = (path: string[], value: any) => {
const newConfig = JSON.parse(JSON.stringify(config));
let current = newConfig;
for (let i = 0; i < path.length - 1; i++) {
current = current[path[i]];
}
current[path[path.length - 1]] = value;
setConfig(newConfig);
};
const hardware = config.hardware || {};
const modem = config.modem || {};
const cassette = config.cassette || {};
const boip = config.boip || {};
return (
<div className="p-4 space-y-4">
<h2 className="text-sm text-neutral-500">Hardware</h2>
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
<div className="p-4 flex items-center justify-between">
<label className="text-sm text-neutral-500">PS/2</label>
<button
onClick={() => updateSetting(['hardware', 'ps2'], hardware.ps2 ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${
hardware.ps2 ? 'bg-blue-600' : 'bg-neutral-300'
}`}
>
<div
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
hardware.ps2 ? 'translate-x-6' : 'translate-x-0.5'
}`}
/>
</button>
</div>
<div className="p-4 flex items-center justify-between">
<label className="text-sm text-neutral-500">User Port Enabled</label>
<button
onClick={() => updateSetting(['hardware', 'userport', 'enabled'], hardware.userport?.enabled ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${
hardware.userport?.enabled ? 'bg-blue-600' : 'bg-neutral-300'
}`}
>
<div
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
hardware.userport?.enabled ? 'translate-x-6' : 'translate-x-0.5'
}`}
/>
</button>
</div>
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">User Port Mode</label>
<select
value={hardware.userport?.mode?.split('|')[0] || 'serial'}
onChange={(e) => updateSetting(['hardware', 'userport', 'mode'], e.target.value)}
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
>
<option value="serial">Serial</option>
<option value="parallel">Parallel</option>
<option value="IEEE-488">IEEE-488</option>
</select>
</div>
</div>
<h2 className="text-sm text-neutral-500 pt-4">Modem</h2>
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
<div className="p-4 flex items-center justify-between">
<label className="text-sm text-neutral-500">Modem Enabled</label>
<button
onClick={() => updateSetting(['modem', 'modem_enabled'], modem.modem_enabled ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${
modem.modem_enabled ? 'bg-blue-600' : 'bg-neutral-300'
}`}
>
<div
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
modem.modem_enabled ? 'translate-x-6' : 'translate-x-0.5'
}`}
/>
</button>
</div>
<div className="p-4 flex items-center justify-between">
<label className="text-sm text-neutral-500">Sniffer Enabled</label>
<button
onClick={() => updateSetting(['modem', 'sniffer_enabled'], modem.sniffer_enabled ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${
modem.sniffer_enabled ? 'bg-blue-600' : 'bg-neutral-300'
}`}
>
<div
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
modem.sniffer_enabled ? 'translate-x-6' : 'translate-x-0.5'
}`}
/>
</button>
</div>
</div>
<h2 className="text-sm text-neutral-500 pt-4">Cassette</h2>
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
<div className="p-4 flex items-center justify-between">
<label className="text-sm text-neutral-500">Enabled</label>
<button
onClick={() => updateSetting(['cassette', 'enabled'], cassette.enabled ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${
cassette.enabled ? 'bg-blue-600' : 'bg-neutral-300'
}`}
>
<div
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
cassette.enabled ? 'translate-x-6' : 'translate-x-0.5'
}`}
/>
</button>
</div>
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">Play/Record</label>
<input
type="text"
value={cassette.play_record || ''}
onChange={(e) => updateSetting(['cassette', 'play_record'], e.target.value)}
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
/>
</div>
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">Pulldown</label>
<input
type="text"
value={cassette.pulldown || ''}
onChange={(e) => updateSetting(['cassette', 'pulldown'], e.target.value)}
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
/>
</div>
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">URL</label>
<input
type="text"
value={cassette.url || ''}
onChange={(e) => updateSetting(['cassette', 'url'], e.target.value)}
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
/>
</div>
</div>
<h2 className="text-sm text-neutral-500 pt-4">BOIP</h2>
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
<div className="p-4 flex items-center justify-between">
<label className="text-sm text-neutral-500">Enabled</label>
<button
onClick={() => updateSetting(['boip', 'enabled'], boip.enabled ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${
boip.enabled ? 'bg-blue-600' : 'bg-neutral-300'
}`}
>
<div
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
boip.enabled ? 'translate-x-6' : 'translate-x-0.5'
}`}
/>
</button>
</div>
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">Host</label>
<input
type="text"
value={boip.host || ''}
onChange={(e) => updateSetting(['boip', 'host'], e.target.value)}
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
/>
</div>
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">Port</label>
<input
type="text"
value={boip.port || ''}
onChange={(e) => updateSetting(['boip', 'port'], e.target.value)}
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
/>
</div>
</div>
</div>
);
}

View File

@ -200,6 +200,75 @@ export default function ToolsPage({ config }: ToolsPageProps) {
return (
<div className="p-4 space-y-4">
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
<div className="p-4">
<div className="flex items-center gap-3 mb-3">
<div className="w-10 h-10 bg-neutral-100 rounded-lg flex items-center justify-center">
<Wrench className="w-5 h-5 text-neutral-600" />
</div>
<div className="flex-1">
<div className="font-medium">System Information</div>
</div>
</div>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-neutral-500">Firmware Version</span>
<span className="text-neutral-900">v2.5.1</span>
</div>
<div className="flex justify-between">
<span className="text-neutral-500">Hardware Revision</span>
<span className="text-neutral-900">Rev C</span>
</div>
<div className="flex justify-between">
<span className="text-neutral-500">Serial Number</span>
<span className="text-neutral-900">ML-2024-0420</span>
</div>
</div>
</div>
<button
onClick={handleSystemUpdate}
className="w-full p-4 flex items-center gap-3 hover:bg-neutral-50 text-left"
>
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
<Download className="w-5 h-5 text-purple-600" />
</div>
<div className="flex-1">
<div className="font-medium">System Update</div>
<div className="text-sm text-neutral-500">Check for firmware updates</div>
</div>
</button>
<button
onClick={() => setShowFirmware(true)}
className="w-full p-4 flex items-center gap-3 hover:bg-neutral-50 text-left"
>
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
<Cpu className="w-5 h-5 text-purple-600" />
</div>
<div className="flex-1">
<div className="font-medium">Change Firmware</div>
<div className="text-sm text-neutral-500">Select a firmware version to install</div>
</div>
<ChevronRight className="w-4 h-4 text-neutral-400" />
</button>
<button
onClick={handleExportLogs}
className="w-full p-4 flex items-center gap-3 hover:bg-neutral-50 text-left"
>
<div className="w-10 h-10 bg-neutral-100 rounded-lg flex items-center justify-center">
<FileText className="w-5 h-5 text-neutral-600" />
</div>
<div className="flex-1">
<div className="font-medium">Export System Logs</div>
<div className="text-sm text-neutral-500">Download diagnostic logs</div>
</div>
</button>
</div>
<h2 className="text-sm text-neutral-500">System Tools</h2>
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
@ -270,75 +339,6 @@ export default function ToolsPage({ config }: ToolsPageProps) {
</div>
</div>
<h2 className="text-sm text-neutral-500 pt-4">System</h2>
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
<div className="p-4">
<div className="flex items-center gap-3 mb-3">
<div className="w-10 h-10 bg-neutral-100 rounded-lg flex items-center justify-center">
<Wrench className="w-5 h-5 text-neutral-600" />
</div>
<div className="flex-1">
<div className="font-medium">System Information</div>
</div>
</div>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-neutral-500">Firmware Version</span>
<span className="text-neutral-900">v2.5.1</span>
</div>
<div className="flex justify-between">
<span className="text-neutral-500">Hardware Revision</span>
<span className="text-neutral-900">Rev C</span>
</div>
<div className="flex justify-between">
<span className="text-neutral-500">Serial Number</span>
<span className="text-neutral-900">ML-2024-0420</span>
</div>
</div>
</div>
<button
onClick={handleSystemUpdate}
className="w-full p-4 flex items-center gap-3 hover:bg-neutral-50 text-left"
>
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
<Download className="w-5 h-5 text-purple-600" />
</div>
<div className="flex-1">
<div className="font-medium">System Update</div>
<div className="text-sm text-neutral-500">Check for firmware updates</div>
</div>
</button>
<button
onClick={() => setShowFirmware(true)}
className="w-full p-4 flex items-center gap-3 hover:bg-neutral-50 text-left"
>
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
<Cpu className="w-5 h-5 text-purple-600" />
</div>
<div className="flex-1">
<div className="font-medium">Change Firmware</div>
<div className="text-sm text-neutral-500">Select a firmware version to install</div>
</div>
<ChevronRight className="w-4 h-4 text-neutral-400" />
</button>
<button
onClick={handleExportLogs}
className="w-full p-4 flex items-center gap-3 hover:bg-neutral-50 text-left"
>
<div className="w-10 h-10 bg-neutral-100 rounded-lg flex items-center justify-center">
<FileText className="w-5 h-5 text-neutral-600" />
</div>
<div className="flex-1">
<div className="font-medium">Export System Logs</div>
<div className="text-sm text-neutral-500">Download diagnostic logs</div>
</div>
</button>
</div>
{showFirmware && <FirmwareOverlay onClose={() => setShowFirmware(false)} />}
</div>