Browse Source

Remove HTTP server status checking

- Remove automatic server status checking from HTTP components
  - Eliminate status tracking overhead in HTTPServerSection and HTTP tab
  - Simplify persistent server list by removing status indicators
  - Clean up unused imports and status-related functions
  - Replace || with ?? for nullish coalescing consistency
pull/645/head
tpaulsoncdw 1 year ago
parent
commit
523cdbc8f9
  1. 10
      src/components/Dashboard/BluetoothSection.tsx
  2. 105
      src/components/Dashboard/HTTPServerSection.tsx
  3. 12
      src/components/Dashboard/SerialSection.tsx
  4. 51
      src/components/PageComponents/Connect/HTTP.test.tsx
  5. 79
      src/components/PageComponents/Connect/HTTP.tsx

10
src/components/Dashboard/BluetoothSection.tsx

@ -61,7 +61,7 @@ export const BluetoothSection = ({ onConnect }: BluetoothSectionProps) => {
onConnect?.(); onConnect?.();
} catch (error) { } catch (error) {
console.error("Bluetooth connection error:", error); console.error("Bluetooth connection error:", error);
setConnectionError(`Failed to connect to ${bleDevice.name || "device"}`); setConnectionError(`Failed to connect to ${bleDevice.name ?? "device"}`);
} finally { } finally {
setConnectingToDevice(null); setConnectingToDevice(null);
} }
@ -102,11 +102,11 @@ export const BluetoothSection = ({ onConnect }: BluetoothSectionProps) => {
return ( return (
<Circle className="h-3 w-3 fill-yellow-500 text-yellow-500 animate-spin" /> <Circle className="h-3 w-3 fill-yellow-500 text-yellow-500 animate-spin" />
); );
} else if (isConnected) { }
if (isConnected) {
return <Circle className="h-3 w-3 fill-blue-500 text-blue-500" />; return <Circle className="h-3 w-3 fill-blue-500 text-blue-500" />;
} else {
return <Circle className="h-3 w-3 fill-slate-300 text-slate-300" />;
} }
return <Circle className="h-3 w-3 fill-slate-300 text-slate-300" />;
}; };
const getStatusText = (device: BluetoothDevice) => { const getStatusText = (device: BluetoothDevice) => {
@ -148,7 +148,7 @@ export const BluetoothSection = ({ onConnect }: BluetoothSectionProps) => {
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="font-medium text-slate-900 dark:text-slate-100 truncate"> <span className="font-medium text-slate-900 dark:text-slate-100 truncate">
{device.name || "Unknown Device"} {device.name ?? "Unknown Device"}
</span> </span>
<Bluetooth className="h-4 w-4 text-blue-500" /> <Bluetooth className="h-4 w-4 text-blue-500" />
</div> </div>

105
src/components/Dashboard/HTTPServerSection.tsx

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useState } from "react";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { Input } from "@components/UI/Input.tsx"; import { Input } from "@components/UI/Input.tsx";
import { Label } from "@components/UI/Label.tsx"; import { Label } from "@components/UI/Label.tsx";
@ -18,14 +18,12 @@ import { TransportHTTP } from "@meshtastic/transport-http";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { import {
AlertTriangle, AlertTriangle,
Circle,
Clock, Clock,
Lock, Lock,
LockOpen, LockOpen,
Plus, Plus,
Server, Server,
Trash2, Trash2,
Users,
Wifi, Wifi,
} from "lucide-react"; } from "lucide-react";
import { useMessageStore } from "@core/stores/messageStore/index.ts"; import { useMessageStore } from "@core/stores/messageStore/index.ts";
@ -56,7 +54,6 @@ export const HTTPServerSection = ({ onConnect }: HTTPServerSectionProps) => {
addSavedServer, addSavedServer,
removeSavedServer, removeSavedServer,
clearSavedServers, clearSavedServers,
updateServerStatus,
getSavedServers, getSavedServers,
} = useAppStore(); } = useAppStore();
@ -77,48 +74,6 @@ export const HTTPServerSection = ({ onConnect }: HTTPServerSectionProps) => {
const secureValue = watch("secure"); 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);
const response = await fetch(`${server.url}/api/v1/fromradio?all=false`, {
method: "GET",
mode: "cors",
signal: controller.signal,
});
clearTimeout(timeoutId);
if (response.ok) {
updateServerStatus(server.url, "online");
} else {
const fallbackResponse = await fetch(`${server.url}/`, {
method: "GET",
mode: "cors",
signal: AbortSignal.timeout(3000),
});
updateServerStatus(
server.url,
fallbackResponse.ok ? "online" : "offline",
);
}
} catch {
updateServerStatus(server.url, "offline");
}
};
const connectToServer = async (server: SavedServer) => { const connectToServer = async (server: SavedServer) => {
setConnectingToServer(server.url); setConnectingToServer(server.url);
setConnectionError(null); setConnectionError(null);
@ -137,13 +92,11 @@ export const HTTPServerSection = ({ onConnect }: HTTPServerSectionProps) => {
subscribeAll(device, connection, messageStore); subscribeAll(device, connection, messageStore);
addSavedServer(server.host, server.protocol); addSavedServer(server.host, server.protocol);
updateServerStatus(server.url, "online");
onConnect?.(); onConnect?.();
} catch (error) { } catch (error) {
console.error("Connection error:", error); console.error("Connection error:", error);
setConnectionError(`Failed to connect to ${server.host}`); setConnectionError(`Failed to connect to ${server.host}`);
updateServerStatus(server.url, "offline");
} finally { } finally {
setConnectingToServer(null); setConnectingToServer(null);
} }
@ -177,40 +130,12 @@ export const HTTPServerSection = ({ onConnect }: HTTPServerSectionProps) => {
} }
}); });
const getStatusIcon = (status?: string) => {
switch (status) {
case "online":
return <Circle className="h-3 w-3 fill-green-500 text-green-500" />;
case "offline":
return <Circle className="h-3 w-3 fill-red-500 text-red-500" />;
case "checking":
return (
<Circle className="h-3 w-3 fill-yellow-500 text-yellow-500 animate-spin" />
);
default:
return <Circle className="h-3 w-3 fill-slate-300 text-slate-300" />;
}
};
const getSecurityIcon = (protocol: "http" | "https") => { const getSecurityIcon = (protocol: "http" | "https") => {
return protocol === "https" return protocol === "https"
? <Lock className="h-4 w-4 text-green-600" /> ? <Lock className="h-4 w-4 text-green-600" />
: <LockOpen className="h-4 w-4 text-yellow-600" />; : <LockOpen className="h-4 w-4 text-yellow-600" />;
}; };
const getStatusText = (status?: string) => {
switch (status) {
case "online":
return "Online";
case "offline":
return "Offline";
case "checking":
return "Checking...";
default:
return "Unknown";
}
};
return ( return (
<> <>
<div className="space-y-3"> <div className="space-y-3">
@ -249,11 +174,6 @@ export const HTTPServerSection = ({ onConnect }: HTTPServerSectionProps) => {
key={server.url} key={server.url}
className="flex items-center gap-3 p-3 rounded-lg bg-white dark:bg-slate-700 border border-slate-200 dark:border-slate-600 hover:shadow-sm transition-shadow" className="flex items-center gap-3 p-3 rounded-lg bg-white dark:bg-slate-700 border border-slate-200 dark:border-slate-600 hover:shadow-sm transition-shadow"
> >
{/* Status */}
<div className="flex-shrink-0">
{getStatusIcon(server.status)}
</div>
{/* Server Info */} {/* Server Info */}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -262,29 +182,6 @@ export const HTTPServerSection = ({ onConnect }: HTTPServerSectionProps) => {
</span> </span>
{getSecurityIcon(server.protocol)} {getSecurityIcon(server.protocol)}
</div> </div>
<div className="flex items-center gap-3 text-xs text-slate-500 dark:text-slate-400 mt-1">
<span>{getStatusText(server.status)}</span>
{server.deviceInfo?.model && (
<>
<span></span>
<span className="font-mono bg-slate-200 dark:bg-slate-600 px-1 rounded">
{server.deviceInfo.model}
</span>
</>
)}
{server.deviceInfo?.nodeCount && (
<>
<span></span>
<span className="flex items-center gap-1">
<Users className="h-3 w-3" />
{server.deviceInfo.nodeCount}
</span>
</>
)}
</div>
</div> </div>
{/* Actions */} {/* Actions */}

12
src/components/Dashboard/SerialSection.tsx

@ -27,7 +27,7 @@ export const SerialSection = ({ onConnect }: SerialSectionProps) => {
const updateSerialPortList = useCallback(async () => { const updateSerialPortList = useCallback(async () => {
try { try {
setSerialPorts(await navigator?.serial?.getPorts() || []); setSerialPorts((await navigator?.serial?.getPorts()) ?? []);
} catch (error) { } catch (error) {
console.error("Error getting serial ports:", error); console.error("Error getting serial ports:", error);
} }
@ -111,11 +111,11 @@ export const SerialSection = ({ onConnect }: SerialSectionProps) => {
return ( return (
<Circle className="h-3 w-3 fill-yellow-500 text-yellow-500 animate-spin" /> <Circle className="h-3 w-3 fill-yellow-500 text-yellow-500 animate-spin" />
); );
} else if (isConnected) { }
if (isConnected) {
return <Circle className="h-3 w-3 fill-green-500 text-green-500" />; return <Circle className="h-3 w-3 fill-green-500 text-green-500" />;
} else {
return <Circle className="h-3 w-3 fill-slate-400 text-slate-400" />;
} }
return <Circle className="h-3 w-3 fill-slate-400 text-slate-400" />;
}; };
const getStatusText = (port: SerialPort) => { const getStatusText = (port: SerialPort) => {
@ -145,7 +145,9 @@ export const SerialSection = ({ onConnect }: SerialSectionProps) => {
: ( : (
serialPorts.map((port, index) => ( serialPorts.map((port, index) => (
<div <div
key={`${port}-${index}`} key={`serial-${port.getInfo().usbVendorId ?? "unknown"}-${
port.getInfo().usbProductId ?? "unknown"
}`}
className="flex items-center gap-3 p-3 rounded-lg bg-white dark:bg-slate-700 border border-slate-200 dark:border-slate-600 hover:shadow-sm transition-shadow" className="flex items-center gap-3 p-3 rounded-lg bg-white dark:bg-slate-700 border border-slate-200 dark:border-slate-600 hover:shadow-sm transition-shadow"
> >
{/* Status */} {/* Status */}

51
src/components/PageComponents/Connect/HTTP.test.tsx

@ -5,7 +5,13 @@ import { TransportHTTP } from "@meshtastic/transport-http";
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
vi.mock("@core/stores/appStore.ts", () => ({ vi.mock("@core/stores/appStore.ts", () => ({
useAppStore: vi.fn(() => ({ setSelectedDevice: vi.fn() })), useAppStore: vi.fn(() => ({
setSelectedDevice: vi.fn(),
addSavedServer: vi.fn(),
removeSavedServer: vi.fn(),
clearSavedServers: vi.fn(),
getSavedServers: vi.fn(() => []),
})),
})); }));
vi.mock("@core/stores/deviceStore.ts", () => ({ vi.mock("@core/stores/deviceStore.ts", () => ({
@ -14,10 +20,18 @@ vi.mock("@core/stores/deviceStore.ts", () => ({
})), })),
})); }));
vi.mock("@core/stores/messageStore/index.ts", () => ({
useMessageStore: vi.fn(() => ({})),
}));
vi.mock("@core/utils/randId.ts", () => ({ vi.mock("@core/utils/randId.ts", () => ({
randId: vi.fn(() => "mock-id"), randId: vi.fn(() => "mock-id"),
})); }));
vi.mock("@core/subscriptions.ts", () => ({
subscribeAll: vi.fn(),
}));
vi.mock("@meshtastic/transport-http", () => ({ vi.mock("@meshtastic/transport-http", () => ({
TransportHTTP: { TransportHTTP: {
create: vi.fn(() => Promise.resolve({})), create: vi.fn(() => Promise.resolve({})),
@ -33,34 +47,35 @@ vi.mock("@meshtastic/core", () => ({
describe("HTTP Component", () => { describe("HTTP Component", () => {
it("renders correctly", () => { it("renders correctly", () => {
render(<Http closeDialog={vi.fn()} />); render(<Http closeDialog={vi.fn()} />);
expect(screen.getByText("IP Address/Hostname")).toBeInTheDocument(); expect(screen.getByText("Meshtastic Servers")).toBeInTheDocument();
expect(screen.getByRole("textbox")).toBeInTheDocument(); expect(screen.getByText("Add New Server")).toBeInTheDocument();
expect(screen.getByPlaceholderText("000.000.000.000 / meshtastic.local")) expect(screen.getByText("No saved servers yet")).toBeInTheDocument();
.toBeInTheDocument();
expect(screen.getByText("Use HTTPS")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Connect" })).toBeInTheDocument();
}); });
it("allows input field to be updated", () => { it("opens dialog when add new server is clicked", () => {
render(<Http closeDialog={vi.fn()} />); render(<Http closeDialog={vi.fn()} />);
const inputField = screen.getByRole("textbox"); const addButton = screen.getByText("Add New Server");
fireEvent.change(inputField, { target: { value: "meshtastic.local" } }); fireEvent.click(addButton);
expect(screen.getByPlaceholderText("000.000.000.000 / meshtastic.local")) expect(screen.getByText("Hostname or IP Address")).toBeInTheDocument();
expect(screen.getByPlaceholderText("meshtastic.local or 192.168.1.100"))
.toBeInTheDocument(); .toBeInTheDocument();
}); });
it("toggles HTTPS switch and updates prefix", () => { it("toggles HTTPS switch in dialog", () => {
render(<Http closeDialog={vi.fn()} />); render(<Http closeDialog={vi.fn()} />);
// Open the dialog first
const addButton = screen.getByText("Add New Server");
fireEvent.click(addButton);
const switchInput = screen.getByRole("switch"); const switchInput = screen.getByRole("switch");
expect(screen.getByText("http://")).toBeInTheDocument(); expect(screen.getByText("Use HTTPS (Secure)")).toBeInTheDocument();
fireEvent.click(switchInput); fireEvent.click(switchInput);
expect(screen.getByText("https://")).toBeInTheDocument(); expect(switchInput).toBeChecked();
fireEvent.click(switchInput); fireEvent.click(switchInput);
expect(switchInput).not.toBeChecked(); expect(switchInput).not.toBeChecked();
expect(screen.getByText("http://")).toBeInTheDocument();
}); });
it("enables HTTPS toggle when location protocol is https", () => { it("enables HTTPS toggle when location protocol is https", () => {
@ -71,10 +86,12 @@ describe("HTTP Component", () => {
render(<Http closeDialog={vi.fn()} />); render(<Http closeDialog={vi.fn()} />);
// Open the dialog first
const addButton = screen.getByText("Add New Server");
fireEvent.click(addButton);
const switchInput = screen.getByRole("switch"); const switchInput = screen.getByRole("switch");
expect(switchInput).toBeChecked(); expect(switchInput).toBeChecked();
expect(screen.getByText("https://")).toBeInTheDocument();
}); });
it.skip("submits form and triggers connection process", async () => { it.skip("submits form and triggers connection process", async () => {

79
src/components/PageComponents/Connect/HTTP.tsx

@ -15,7 +15,7 @@ import { subscribeAll } from "@core/subscriptions.ts";
import { randId } from "@core/utils/randId.ts"; import { randId } from "@core/utils/randId.ts";
import { MeshDevice } from "@meshtastic/core"; import { MeshDevice } from "@meshtastic/core";
import { TransportHTTP } from "@meshtastic/transport-http"; import { TransportHTTP } from "@meshtastic/transport-http";
import { useCallback, useEffect, useState } from "react"; import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { import {
AlertTriangle, AlertTriangle,
@ -54,7 +54,6 @@ export const HTTP = ({ closeDialog }: TabElementProps) => {
addSavedServer, addSavedServer,
removeSavedServer, removeSavedServer,
clearSavedServers, clearSavedServers,
updateServerStatus,
getSavedServers, getSavedServers,
} = useAppStore(); } = useAppStore();
@ -75,80 +74,6 @@ export const HTTP = ({ closeDialog }: TabElementProps) => {
const secureValue = watch("secure"); const secureValue = watch("secure");
const checkServerStatus = useCallback(
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");
}
},
[updateServerStatus],
);
// Auto-check server status on component mount
useEffect(() => {
for (const server of savedServers) {
if (server.status !== "checking") {
checkServerStatus(server);
}
}
}, [savedServers, checkServerStatus]);
const connectToServer = async (server: SavedServer) => { const connectToServer = async (server: SavedServer) => {
setConnectingToServer(server.url); setConnectingToServer(server.url);
setConnectionError(null); setConnectionError(null);
@ -168,13 +93,11 @@ export const HTTP = ({ closeDialog }: TabElementProps) => {
// Update last used time // Update last used time
addSavedServer(server.host, server.protocol); addSavedServer(server.host, server.protocol);
updateServerStatus(server.url, "online");
closeDialog(); closeDialog();
} catch (error) { } catch (error) {
console.error("Connection error:", error); console.error("Connection error:", error);
setConnectionError(`Failed to connect to ${server.host}`); setConnectionError(`Failed to connect to ${server.host}`);
updateServerStatus(server.url, "offline");
} finally { } finally {
setConnectingToServer(null); setConnectingToServer(null);
} }

Loading…
Cancel
Save