Bump version to 1.0.9

Fix Wake-on-LAN: set SO_BROADCAST on UDP socket, send 3 magic packets,
expose broadcast_addr and wake timeout fields in UI, add Test Wake endpoint,
surface send errors in job status, bump default wake timeout to 180s.
This commit is contained in:
2026-07-08 19:19:50 -04:00
parent 25a31af64c
commit 5d5b6c99a7
13 changed files with 280 additions and 48 deletions
+5 -1
View File
@@ -63,7 +63,11 @@ export interface ErrorCodeInfo {
const ERROR_CODES: Record<string, ErrorCodeInfo> = {
wol_timeout: {
title: "Machine didn't wake up",
hint: 'Check Wake-on-LAN settings, MAC address, and network connectivity',
hint: 'Waited for machine to respond and got no SSH reply. Make sure the server and the target machine are on the same L2 broadcast domain. For HP Microservers and similar hardware, increase the Wake timeout in the machine settings (180s or more).',
},
wol_send_failed: {
title: 'Wake-on-LAN packet failed to send',
hint: 'The magic packet could not be sent. Check the server logs for details. The server must be on the same broadcast domain as the target machine NIC and must have NET_ADMIN or CAP_NET_RAW capability.',
},
rsync_error: {
title: 'rsync failed',
+91 -4
View File
@@ -26,7 +26,7 @@ import {
import { EmptyState } from '@/components/ui/EmptyState';
import { CopyButton } from '@/components/ui/CopyButton';
import { Card } from '@/components/ui/Card';
import { Pencil, Trash2, Plus, Server } from 'lucide-react';
import { Pencil, Trash2, Plus, Server, Zap } from 'lucide-react';
import { toast } from 'sonner';
import { cn } from '@/lib/utils';
@@ -39,6 +39,7 @@ type MachineForm = {
ssh_key_id: number | null;
mac_address: string;
wol_enabled: boolean;
broadcast_addr: string;
wake_timeout_seconds: number;
wake_check_interval_seconds: number;
};
@@ -52,7 +53,8 @@ const defaultForm: MachineForm = {
ssh_key_id: null,
mac_address: '',
wol_enabled: false,
wake_timeout_seconds: 120,
broadcast_addr: '',
wake_timeout_seconds: 180,
wake_check_interval_seconds: 5,
};
@@ -94,8 +96,9 @@ export default function Machines() {
ssh_key_id: m.ssh_key_id,
mac_address: m.mac_address || '',
wol_enabled: m.wol_enabled,
wake_timeout_seconds: m.wake_timeout_seconds,
wake_check_interval_seconds: m.wake_check_interval_seconds,
broadcast_addr: m.broadcast_addr || '',
wake_timeout_seconds: m.wake_timeout_seconds || 180,
wake_check_interval_seconds: m.wake_check_interval_seconds || 5,
});
setModalOpen(true);
}
@@ -124,6 +127,7 @@ export default function Machines() {
ssh_key_id: form.ssh_key_id,
mac_address: form.mac_address || null,
wol_enabled: Boolean(form.wol_enabled),
broadcast_addr: form.broadcast_addr || null,
wake_timeout_seconds: Number(form.wake_timeout_seconds),
wake_check_interval_seconds: Number(form.wake_check_interval_seconds),
};
@@ -153,6 +157,15 @@ export default function Machines() {
}
}
async function handleTestWol(m: Machine) {
try {
await api<{ ok: boolean; sent: number }>(`/api/machines/${m.id}/test-wol`, { method: 'POST' });
toast.success(`Magic packet sent (${m.mac_address})`);
} catch (e: unknown) {
toast.error(`Wake failed: ${(e as Error).message}`);
}
}
function keyLabel(id: number | null) {
if (!id) return 'Server Key';
const k = sshKeys.find(k => k.id === id);
@@ -194,6 +207,7 @@ export default function Machines() {
<TableHead>Host</TableHead>
<TableHead>SSH Key</TableHead>
<TableHead>WoL</TableHead>
<TableHead>WoL Timeout</TableHead>
<TableHead>Status</TableHead>
<TableHead className="w-24">Actions</TableHead>
</TableRow>
@@ -217,6 +231,15 @@ export default function Machines() {
<span className="text-fg-subtle text-xs">No</span>
)}
</TableCell>
<TableCell>
{m.wol_enabled ? (
<span className="text-xs text-fg-muted">
{m.wake_timeout_seconds}s
</span>
) : (
<span className="text-fg-subtle text-xs"></span>
)}
</TableCell>
<TableCell>
<StatusBadge status={m.status} />
</TableCell>
@@ -230,6 +253,16 @@ export default function Machines() {
>
<Pencil className="h-3.5 w-3.5" />
</Button>
{m.wol_enabled && (
<Button
variant="ghost"
size="icon-sm"
onClick={() => handleTestWol(m)}
title="Test Wake-on-LAN"
>
<Zap className="h-3.5 w-3.5" />
</Button>
)}
<Button
variant="ghost"
size="icon-sm"
@@ -364,6 +397,60 @@ export default function Machines() {
Enable Wake-on-LAN
</Label>
</div>
{form.wol_enabled && (
<div className="space-y-3 p-3 border border-border rounded-card bg-surface-raised">
<p className="text-xs text-fg-subtle font-medium">Wake-on-LAN Settings</p>
<div className="space-y-1.5">
<Label htmlFor="broadcast_addr">Broadcast Address</Label>
<Input
id="broadcast_addr"
placeholder="255.255.255.255"
value={form.broadcast_addr}
onChange={e =>
setForm({ ...form, broadcast_addr: e.target.value })
}
/>
<p className="text-xs text-fg-subtle">
Leave blank to use 255.255.255.255. Set to your subnet broadcast
address if the server has multiple interfaces.
</p>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<Label htmlFor="wake_timeout_seconds">Wake timeout (s)</Label>
<Input
id="wake_timeout_seconds"
type="number"
min={10}
max={600}
value={form.wake_timeout_seconds}
onChange={e =>
setForm({ ...form, wake_timeout_seconds: Number(e.target.value) })
}
/>
<p className="text-xs text-fg-subtle">
HP Microservers typically need ~180s.
</p>
</div>
<div className="space-y-1.5">
<Label htmlFor="wake_check_interval_seconds">Check interval (s)</Label>
<Input
id="wake_check_interval_seconds"
type="number"
min={1}
max={60}
value={form.wake_check_interval_seconds}
onChange={e =>
setForm({ ...form, wake_check_interval_seconds: Number(e.target.value) })
}
/>
<p className="text-xs text-fg-subtle">
How often to poll SSH readiness.
</p>
</div>
</div>
</div>
)}
</ModalBody>
<ModalFooter>
<Button