mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35:24 +00:00
feat: convert wake-on-lan into a plugin (B20)
This commit is contained in:
1 parent
d4ec444558
commit
2ac7779eda
50 files changed
+17844
-199
No files matched your search
@@ -126,15 +126,6 @@ export async function updateSSHHost(
|
||||
}
|
||||
}
|
||||
|
||||
export async function wakeOnLan(hostId: number): Promise<{ success: boolean }> {
|
||||
try {
|
||||
const response = await sshHostApi.post(`/db/host/${hostId}/wake`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "wake on LAN");
|
||||
}
|
||||
}
|
||||
|
||||
export async function bulkImportSSHHosts(
|
||||
hosts: SSHHostData[],
|
||||
overwrite = false,
|
||||
|
||||
@@ -570,9 +570,6 @@
|
||||
"secureShell": "Secure Shell",
|
||||
"addressIp": "Address / IP",
|
||||
"friendlyName": "Friendly Name",
|
||||
"macAddress": "MAC Address",
|
||||
"wolBroadcastAddress": "WoL Broadcast Address",
|
||||
"wolBroadcastAddressDesc": "Optional directed broadcast for Docker/routed networks (e.g. 192.168.1.255). Leave empty to use 255.255.255.255.",
|
||||
"folderAndAdvanced": "Folder & Advanced",
|
||||
"showAdvancedSettings": "Show advanced settings",
|
||||
"hideAdvancedSettings": "Hide advanced settings",
|
||||
@@ -971,9 +968,6 @@
|
||||
"remoteDesktopUrlCopied": "Remote Desktop URL copied",
|
||||
"expandActions": "Expand actions",
|
||||
"collapseActions": "Collapse actions",
|
||||
"wakeOnLanAction": "Wake on LAN",
|
||||
"wakeOnLanSuccess": "Magic packet sent to {{name}}",
|
||||
"wakeOnLanError": "Failed to send magic packet",
|
||||
"cloneHostAction": "Clone Host",
|
||||
"copyAddress": "Copy Address",
|
||||
"copyLink": "Copy Link",
|
||||
@@ -2796,6 +2790,10 @@
|
||||
"title": "Accept connections",
|
||||
"consequence": "It opens a port on the Termix server."
|
||||
},
|
||||
"network:broadcast": {
|
||||
"title": "Send network broadcasts",
|
||||
"consequence": "It can send packets to devices on the local network, such as a Wake-on-LAN signal."
|
||||
},
|
||||
"users:read": {
|
||||
"title": "See usernames and roles",
|
||||
"consequence": "Never password hashes, never two factor state."
|
||||
|
||||
@@ -1340,7 +1340,6 @@ export {
|
||||
getSSHHosts,
|
||||
createSSHHost,
|
||||
updateSSHHost,
|
||||
wakeOnLan,
|
||||
bulkImportSSHHosts,
|
||||
importSSHConfigHosts,
|
||||
discoverProxmoxGuests,
|
||||
|
||||
@@ -143,8 +143,6 @@ export function createHostEditorForm(
|
||||
tagInput: "",
|
||||
notes: host?.notes ?? "",
|
||||
pin: host?.pin ?? false,
|
||||
macAddress: host?.macAddress ?? "",
|
||||
wolBroadcastAddress: host?.wolBroadcastAddress ?? "",
|
||||
useSocks5: host?.useSocks5 ?? d?.useSocks5 ?? false,
|
||||
socks5Host: host?.socks5Host ?? d?.socks5Host ?? "",
|
||||
socks5Port: host?.socks5Port ?? d?.socks5Port ?? 1080,
|
||||
@@ -418,8 +416,6 @@ export function buildHostEditorPayload(
|
||||
usesVault && form.vaultProfileId ? Number(form.vaultProfileId) : null,
|
||||
overrideCredentialUsername: form.overrideCredentialUsername,
|
||||
notes: form.notes,
|
||||
macAddress: form.macAddress || null,
|
||||
wolBroadcastAddress: form.wolBroadcastAddress || null,
|
||||
enableTerminal: form.enableTerminal,
|
||||
enableSessionLogging: form.enableSessionLogging,
|
||||
enableCommandHistory: form.enableCommandHistory,
|
||||
|
||||
@@ -190,45 +190,6 @@ export function HostEditorGeneralTab({
|
||||
onChange={(e) => setField("name", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{protocols.enableSsh && !simpleMode && (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
|
||||
{t("hosts.macAddress")}
|
||||
</label>
|
||||
<a
|
||||
href="https://docs.termix.site/features/networking/wake-on-lan"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-[10px] text-accent-brand hover:underline"
|
||||
>
|
||||
{t("hosts.docsLink")}
|
||||
</a>
|
||||
</div>
|
||||
<Input
|
||||
placeholder="AA:BB:CC:DD:EE:FF"
|
||||
value={form.macAddress}
|
||||
onChange={(e) => setField("macAddress", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{protocols.enableSsh && !simpleMode && form.macAddress && (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
|
||||
{t("hosts.wolBroadcastAddress")}
|
||||
</label>
|
||||
<Input
|
||||
placeholder="192.168.1.255"
|
||||
value={form.wolBroadcastAddress}
|
||||
onChange={(e) =>
|
||||
setField("wolBroadcastAddress", e.target.value)
|
||||
}
|
||||
/>
|
||||
<p className="text-[10px] text-muted-foreground/60">
|
||||
{t("hosts.wolBroadcastAddressDesc")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
@@ -85,8 +85,6 @@ export function sshHostToHost(h: SSHHostWithStatus): Host {
|
||||
notes: h.notes,
|
||||
pin: h.pin ?? false,
|
||||
sortOrder: h.sortOrder ?? null,
|
||||
macAddress: h.macAddress,
|
||||
wolBroadcastAddress: h.wolBroadcastAddress,
|
||||
connectionOrigin: h.connectionOrigin ?? null,
|
||||
enableSsh: h.enableSsh != null ? h.enableSsh : isSshHost,
|
||||
enableTerminal:
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
Terminal,
|
||||
Trash2,
|
||||
Users,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -35,7 +34,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/dropdown-menu";
|
||||
import { toast } from "sonner";
|
||||
import { getHostPassword, wakeOnLan } from "@/main-axios";
|
||||
import { getHostPassword } from "@/main-axios";
|
||||
import type { Host, TabType } from "@/types/ui-types";
|
||||
import type {
|
||||
HostDensity,
|
||||
@@ -366,16 +365,6 @@ export function HostItem({
|
||||
}
|
||||
}
|
||||
|
||||
async function handleWakeOnLan(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
try {
|
||||
await wakeOnLan(Number(host.id));
|
||||
toast.success(t("hosts.wakeOnLanSuccess", { name: host.name }));
|
||||
} catch {
|
||||
toast.error(t("hosts.wakeOnLanError"));
|
||||
}
|
||||
}
|
||||
|
||||
if (query && !hostMatchesQuery(host, query)) return null;
|
||||
|
||||
const depthStyle =
|
||||
@@ -506,15 +495,6 @@ export function HostItem({
|
||||
);
|
||||
return separator ? [separator, button] : button;
|
||||
})}
|
||||
{host.macAddress && (
|
||||
<button
|
||||
title={t("hosts.wakeOnLanAction")}
|
||||
onClick={handleWakeOnLan}
|
||||
className={trayButtonClass}
|
||||
>
|
||||
<Zap className="size-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -704,12 +684,6 @@ export function HostItem({
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
{host.macAddress && (
|
||||
<DropdownMenuItem onClick={handleWakeOnLan}>
|
||||
<Zap className="size-3.5 mr-2" />
|
||||
{t("hosts.wakeOnLanAction")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
|
||||
@@ -596,7 +596,6 @@ export function SidebarTree({
|
||||
tags: host.tags ?? [],
|
||||
pin: host.pin ?? false,
|
||||
notes: host.notes,
|
||||
macAddress: host.macAddress,
|
||||
// Key material is never sent to the frontend, so a cloned key-auth
|
||||
// host would have authType "key" with no key — unusable. Reset to
|
||||
// password so the clone is in a connectable (editable) state.
|
||||
|
||||
@@ -3,19 +3,6 @@ import type { SSHHostWithStatus } from "@/main-axios";
|
||||
import { sshHostToHost } from "@/sidebar/HostManagerData";
|
||||
|
||||
describe("sshHostToHost", () => {
|
||||
it("preserves the Wake-on-LAN broadcast address for editing", () => {
|
||||
const host = sshHostToHost({
|
||||
id: 1,
|
||||
name: "server",
|
||||
ip: "192.168.0.10",
|
||||
port: 22,
|
||||
username: "root",
|
||||
wolBroadcastAddress: "192.168.0.255",
|
||||
} as SSHHostWithStatus);
|
||||
|
||||
expect(host.wolBroadcastAddress).toBe("192.168.0.255");
|
||||
});
|
||||
|
||||
it.each(["local", "remote"] as const)(
|
||||
"preserves the %s connection origin for editing",
|
||||
(connectionOrigin) => {
|
||||
|
||||
@@ -36,7 +36,6 @@ vi.mock("@/hooks/use-status-color-scheme", () => ({
|
||||
|
||||
vi.mock("@/main-axios", () => ({
|
||||
getHostPassword: vi.fn(),
|
||||
wakeOnLan: vi.fn(),
|
||||
}));
|
||||
|
||||
import { HostItem } from "../../../../sidebar/tree/HostItem/HostItem";
|
||||
@@ -64,7 +63,6 @@ const baseHost: Host = {
|
||||
enableRdp: true,
|
||||
enableVnc: true,
|
||||
enableTelnet: true,
|
||||
macAddress: "aa:bb:cc:dd:ee:ff",
|
||||
quickActions: [],
|
||||
} as unknown as Host;
|
||||
|
||||
@@ -140,7 +138,6 @@ describe("HostItem density parity", () => {
|
||||
0,
|
||||
);
|
||||
expect(screen.getAllByText("hosts.shareHost").length).toBeGreaterThan(0);
|
||||
expect(screen.getByText("hosts.wakeOnLanAction")).toBeTruthy();
|
||||
});
|
||||
|
||||
it.each(["comfortable", "compact"] as const)(
|
||||
@@ -177,14 +174,6 @@ describe("HostItem density parity", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["comfortable", "compact"] as const)(
|
||||
"exposes the wake-on-LAN button when a MAC address is set, in %s density",
|
||||
(density) => {
|
||||
renderHostItem(density);
|
||||
expect(screen.getByTitle("hosts.wakeOnLanAction")).toBeTruthy();
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["comfortable", "compact"] as const)(
|
||||
"exposes the copy-password action in %s density",
|
||||
(density) => {
|
||||
|
||||
Reference in new issue
Block a user