meatloaf-config/src/app/components/NetworkPage.tsx

261 lines
10 KiB
TypeScript

import { useState } from 'react';
import { Bluetooth, Globe, Wifi, Trash2, Scan } from 'lucide-react';
import WiFiScanOverlay from './WiFiScanOverlay';
import { toast } from 'sonner';
import { useWs } from '../ws';
interface NetworkPageProps {
config: any;
setConfig: (config: any) => void;
}
export default function NetworkPage({ config, setConfig }: NetworkPageProps) {
const { send: wsSend } = useWs();
const [showWiFiScan, setShowWiFiScan] = useState(false);
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 network = config.network || {};
const bluetooth = config.bluetooth || {};
const wifi = config.wifi || [];
const removeWifiNetwork = (index: number) => {
const network = wifi[index];
const newWifi = wifi.filter((_: any, i: number) => i !== index);
updateSetting(['wifi'], newWifi);
toast.success(`Removed ${network.ssid}`);
};
const addWifiNetwork = (ssid: string, passphrase: string) => {
// Disconnect all existing networks
const updatedWifi = wifi.map((network: any) => ({
...network,
enabled: 0
}));
// Add new network at the top with enabled status
const newWifi = [{ ssid, passphrase, enabled: 1 }, ...updatedWifi];
updateSetting(['wifi'], newWifi);
};
return (
<div className="p-4 space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-sm text-neutral-500 flex items-center gap-2"><Wifi className="w-4 h-4" /> Known WiFi Networks</h2>
<button
onClick={() => { wsSend('scan'); setShowWiFiScan(true); }}
className="flex items-center gap-1 px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg"
>
<Scan className="w-4 h-4" />
Scan
</button>
</div>
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
{wifi.map((network: any, index: number) => (
<div key={index} className="p-4 flex items-center justify-between">
<button
className="flex items-center gap-3 flex-1 min-w-0 text-left hover:bg-neutral-50 -m-1 p-1 rounded"
onClick={() => {
const cmd = network.passphrase
? `connect ${network.ssid} ${network.passphrase}`
: `connect ${network.ssid}`;
wsSend(cmd);
toast.info(`Connecting to ${network.ssid}`);
const updated = wifi
.map((n: any, i: number) => ({ ...n, enabled: i === index ? 1 : 0 }))
.sort((a: any, b: any) => b.enabled - a.enabled);
updateSetting(['wifi'], updated);
}}
>
<Wifi className={`w-5 h-5 flex-shrink-0 ${network.enabled ? 'text-blue-600' : 'text-neutral-400'}`} />
<div className="flex-1 min-w-0">
<div className="font-medium truncate">{network.ssid}</div>
{network.enabled === 1 && (
<div className="text-xs text-green-600">Connected</div>
)}
</div>
</button>
<button
onClick={() => removeWifiNetwork(index)}
className="p-2 text-red-600 hover:bg-red-50 rounded ml-2"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
))}
{wifi.length === 0 && (
<div className="p-8 text-center text-neutral-500 text-sm">
No WiFi networks configured
</div>
)}
</div>
{showWiFiScan && (
<WiFiScanOverlay
onClose={() => setShowWiFiScan(false)}
onNetworkAdded={addWifiNetwork}
existingNetworks={wifi.map((n: any) => n.ssid)}
/>
)}
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><Globe className="w-4 h-4" /> Network</h2>
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">Hostname</label>
<input
type="text"
value={network.hostname || ''}
onChange={(e) => updateSetting(['network', 'hostname'], 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">SNTP Server</label>
<input
type="text"
value={network.sntpserver || ''}
onChange={(e) => updateSetting(['network', 'sntpserver'], e.target.value)}
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
/>
</div>
<div className="p-4 flex items-center justify-between">
<label className="text-sm text-neutral-500">HTTPD</label>
<button
onClick={() => updateSetting(['network', 'services', 'httpd', 'enabled'], network.services?.httpd?.enabled ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${
network.services?.httpd?.enabled ? 'bg-blue-600' : 'bg-neutral-300'
}`}
>
<div
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
network.services?.httpd?.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">WebSockets</label>
<button
onClick={() => updateSetting(['network', 'services', 'httpd', 'websockets'], network.services?.httpd?.websockets ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${
network.services?.httpd?.websockets ? 'bg-blue-600' : 'bg-neutral-300'
}`}
>
<div
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
network.services?.httpd?.websockets ? '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">mDNS</label>
<button
onClick={() => updateSetting(['network', 'services', 'mdns'], network.services?.mdns ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${
network.services?.mdns ? 'bg-blue-600' : 'bg-neutral-300'
}`}
>
<div
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
network.services?.mdns ? '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">SSDP</label>
<button
onClick={() => updateSetting(['network', 'services', 'ssdp'], network.services?.ssdp ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${
network.services?.ssdp ? 'bg-blue-600' : 'bg-neutral-300'
}`}
>
<div
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
network.services?.ssdp ? '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">TCP Console</label>
<button
onClick={() => updateSetting(['network', 'services', 'tcp_console'], network.services?.tcp_console ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${
network.services?.tcp_console ? 'bg-blue-600' : 'bg-neutral-300'
}`}
>
<div
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
network.services?.tcp_console ? 'translate-x-6' : 'translate-x-0.5'
}`}
/>
</button>
</div>
</div>
<h2 className="text-sm text-neutral-500 pt-4 flex items-center gap-2"><Bluetooth className="w-4 h-4" /> Bluetooth</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(['bluetooth', 'enabled'], bluetooth.enabled ? 0 : 1)}
className={`relative w-12 h-6 rounded-full transition-colors ${
bluetooth.enabled ? 'bg-blue-600' : 'bg-neutral-300'
}`}
>
<div
className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${
bluetooth.enabled ? 'translate-x-6' : 'translate-x-0.5'
}`}
/>
</button>
</div>
<div className="p-4">
<label className="text-sm text-neutral-500 block mb-2">Device Name</label>
<input
type="text"
value={bluetooth.devicename || ''}
onChange={(e) => updateSetting(['bluetooth', 'devicename'], 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">Baud Rate</label>
<input
type="number"
value={bluetooth.baud || ''}
onChange={(e) => updateSetting(['bluetooth', 'baud'], parseInt(e.target.value))}
className="w-full px-3 py-2 border border-neutral-300 rounded-lg"
/>
</div>
</div>
</div>
);
}