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:
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user