import type { TabElementProps } from "@components/Dialog/NewDeviceDialog.tsx"; import { Button } from "@components/UI/Button.tsx"; import { Input } from "@components/UI/Input.tsx"; import { Label } from "@components/UI/Label.tsx"; import { Switch } from "@components/UI/Switch.tsx"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@components/UI/Dialog.tsx"; import { useAppStore } from "@core/stores/appStore.ts"; import { useDeviceStore } from "@core/stores/deviceStore.ts"; import { subscribeAll } from "@core/subscriptions.ts"; import { randId } from "@core/utils/randId.ts"; import { MeshDevice } from "@meshtastic/core"; import { TransportHTTP } from "@meshtastic/transport-http"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { AlertTriangle, Circle, Clock, Lock, LockOpen, MessageCircle, Plus, Server, Trash2, Users, Wifi, } from "lucide-react"; import { useMessageStore } from "@core/stores/messageStore/index.ts"; import type { SavedServer } from "@core/stores/appStore.ts"; interface AddServerFormData { hostname: string; secure: boolean; } export const HTTP = ({ closeDialog }: TabElementProps) => { const [connectionInProgress, setConnectionInProgress] = useState(false); const [connectingToServer, setConnectingToServer] = useState( null, ); const [addServerOpen, setAddServerOpen] = useState(false); const [connectionError, setConnectionError] = useState(null); const isURLHTTPS = location.protocol === "https:"; const { addDevice } = useDeviceStore(); const messageStore = useMessageStore(); const { setSelectedDevice, addSavedServer, removeSavedServer, clearSavedServers, updateServerStatus, getSavedServers, } = useAppStore(); const savedServers = getSavedServers(); const { register, handleSubmit, reset, setValue, watch } = useForm< AddServerFormData >({ defaultValues: { hostname: ["client.meshtastic.org", "localhost"].includes( globalThis.location.hostname, ) ? "meshtastic.local" : globalThis.location.host, secure: isURLHTTPS, }, }); const secureValue = watch("secure"); // Auto-check server status on component mount useEffect(() => { savedServers.forEach((server) => { if (server.status !== "checking") { checkServerStatus(server); } }); }, []); const checkServerStatus = async (server: SavedServer) => { updateServerStatus(server.url, "checking"); try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 8000); // Try to get actual device info from fromradio endpoint const response = await fetch(`${server.url}/api/v1/fromradio?all=false`, { method: "GET", mode: "cors", signal: controller.signal, }); clearTimeout(timeoutId); if (response.ok) { // Try to get basic device info if available try { // First check if we can get a simple status response const statusResponse = await fetch(`${server.url}/api/v1/fromradio`, { method: "GET", mode: "cors", signal: AbortSignal.timeout(3000), }); if (statusResponse.ok) { // For now, just mark as online - we could parse protobuf later for device details updateServerStatus(server.url, "online"); // TODO: Parse protobuf response to get device info // This would require proper protobuf decoding which is complex // For now, we'll show it as online and get device info after connection } else { updateServerStatus(server.url, "offline"); } } catch { // If fromradio fails, try the simpler status endpoint const fallbackResponse = await fetch(`${server.url}/`, { method: "GET", mode: "cors", signal: AbortSignal.timeout(3000), }); updateServerStatus( server.url, fallbackResponse.ok ? "online" : "offline", ); } } else { updateServerStatus(server.url, "offline"); } } catch { updateServerStatus(server.url, "offline"); } }; const connectToServer = async (server: SavedServer) => { setConnectingToServer(server.url); setConnectionError(null); try { const id = randId(); const transport = await TransportHTTP.create( server.host, server.protocol === "https", ); const device = addDevice(id); const connection = new MeshDevice(transport, id); connection.configure(); setSelectedDevice(id); device.addConnection(connection); subscribeAll(device, connection, messageStore); // Update last used time addSavedServer(server.host, server.protocol); updateServerStatus(server.url, "online"); closeDialog(); } catch (error) { console.error("Connection error:", error); setConnectionError(`Failed to connect to ${server.host}`); updateServerStatus(server.url, "offline"); } finally { setConnectingToServer(null); } }; const handleAddServer = handleSubmit(async (data) => { setConnectionInProgress(true); setConnectionError(null); try { const protocol = data.secure ? "https" : "http"; const id = randId(); const transport = await TransportHTTP.create(data.hostname, data.secure); const device = addDevice(id); const connection = new MeshDevice(transport, id); connection.configure(); setSelectedDevice(id); device.addConnection(connection); subscribeAll(device, connection, messageStore); // Save to history addSavedServer(data.hostname, protocol); setAddServerOpen(false); reset(); closeDialog(); } catch (error) { console.error("Connection error:", error); setConnectionError(`Failed to connect to ${data.hostname}`); } finally { setConnectionInProgress(false); } }); const getStatusIcon = (status?: string) => { switch (status) { case "online": return ; case "offline": return ; case "checking": return ( ); default: return ; } }; const getSecurityIcon = (protocol: "http" | "https") => { return protocol === "https" ? : ; }; const getStatusText = (status?: string) => { switch (status) { case "online": return "Online"; case "offline": return "Offline"; case "checking": return "Checking..."; default: return "Unknown"; } }; return ( <>
{/* Server List Header */}
Meshtastic Servers
{savedServers.length > 0 && ( )}
{/* Server List */}
{savedServers.length === 0 ? (

No saved servers yet

Add your first Meshtastic node to get started

) : ( savedServers.slice(0, 5).map((server) => (
{/* Status Indicator */}
{getStatusIcon(server.status)}
{/* Server Info */}
{server.host} {getSecurityIcon(server.protocol)}
{getStatusText(server.status)} {/* Device info if available */} {server.deviceInfo?.model && ( <> {server.deviceInfo.model} )} {server.deviceInfo?.nodeCount && ( <> {server.deviceInfo.nodeCount} )} {server.deviceInfo?.unreadCount && server.deviceInfo.unreadCount > 0 && ( <> {server.deviceInfo.unreadCount} )}
{/* Actions */}
)) )}
{/* Add New Server Button */} {/* Connection Error */} {connectionError && (

{connectionError}

)}
{/* Add Server Dialog */} Add New Server
setValue("secure", checked)} disabled={isURLHTTPS} {...register("secure")} />
); };