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
+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