Compare commits

..

2 Commits

Author SHA1 Message Date
ff99caa644 feat!: new targets.json structure, add target status endpoint, move frontend to HTML file with table
BREAKING CHANGE: convert older targets.json file to new structure
2026-02-03 00:28:19 +01:00
163d995008 feat: add ping function 2026-02-03 00:15:32 +01:00
5 changed files with 103 additions and 29 deletions

47
api.js
View File

@@ -1,34 +1,20 @@
import express from 'express'; import express from 'express';
import cors from 'cors'; import cors from 'cors';
import { sendWolPacket } from './wol.js'; import { ping, sendWolPacket } from './wol.js';
import targets from "./targets.json" with { type: "json" }; import targets from "./targets.json" with { type: "json" };
import { fileURLToPath } from "node:url";
import path from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express(); const app = express();
const port = 9999; const port = 9999;
app.use(cors()).use(express.json()); app.use(cors()).use(express.json());
app.get("/", (req, res) => {
let html = `<!DOCTYPE html><head>
<style>
body { background: #fff; color: #000; }
@media (prefers-color-scheme: dark) {
body { background: #111; color: #eee; }
a { color: #7295f6; }
}
</style>
</head>
<body>
<h2 style="margin-bottom: 5px">Targets:</h2>
<ul style="margin: 0; padding-left: 0; list-style-position: inside">
${Object.keys(targets).map(key => `<li><a href="/wake/${key}">${key}</a></li>`).join("")}
</ul>
</body>`;
res.send(html);
});
app.get("/targets", (req, res) => { app.get("/targets", (req, res) => {
res.json(Object.keys(targets)); res.json(targets);
}); });
app.post("/wake", (req, res) => { app.post("/wake", (req, res) => {
@@ -62,14 +48,12 @@ app.get("/wake", (req, res) => {
}); });
app.get("/wake/:name", (req, res) => { app.get("/wake/:name", (req, res) => {
const name = req.params.name; const target = targets[req.params.name];
const mac = targets[name]; if (!target?.mac) {
if (!mac) {
return res.status(404).json({ status: "error", message: "Unknown target" }); return res.status(404).json({ status: "error", message: "Unknown target" });
} }
sendWolPacket(mac) sendWolPacket(target.mac)
.then(() => res.json({ status: "ok" })) .then(() => res.json({ status: "ok" }))
.catch(err => { .catch(err => {
console.error(err); console.error(err);
@@ -77,6 +61,17 @@ app.get("/wake/:name", (req, res) => {
}); });
}); });
app.get("/status/:name", (req, res) => {
const target = targets[req.params.name];
if (!target?.ip) {
return res.json({ isUp: null });
}
ping(target.ip).then(isUp => res.json({ isUp }));
});
app.use(express.static(path.join(__dirname, 'public')));
app.listen(port, () => { app.listen(port, () => {
console.log('WOL server running on port ' + port); console.log('WOL server running on port ' + port);
}); });

View File

@@ -1,6 +1,6 @@
{ {
"name": "wol-api", "name": "wol-api",
"version": "1.2.0", "version": "2.0.0",
"description": "Wake-on-LAN API in Node.js", "description": "Wake-on-LAN API in Node.js",
"keywords": [ "keywords": [
"node", "node",

64
public/index.html Normal file
View File

@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WOL</title>
<style>
body {
background: #fff;
color: #000;
}
@media (prefers-color-scheme: dark) {
body {
background: #111;
color: #eee;
}
a {
color: #7295f6;
}
}
</style>
</head>
<body>
<h2>Targets</h2>
<table border="1" cellpadding="6">
<tr>
<th>Name</th>
<th>MAC</th>
<th>Status</th>
<th></th>
</tr>
<tbody id="targets"></tbody>
</body>
<script>
async function loadTargets() {
const res = await fetch("/targets");
const targets = await res.json();
const tbody = document.getElementById("targets");
for (const [name, t] of Object.entries(targets)) {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${name}</td>
<td>${t.mac}</td>
<td id="status-${name}">checking...</td>
<td><a href="#" onclick="fetch('/wake/${name}')">Send WOL</a></td>
`;
tbody.appendChild(tr);
const checkStatus = async () => {
fetch("/status/" + name)
.then(res => res.ok ? res.json() : Promise.reject())
.then(json => document.getElementById("status-" + name).textContent = json.isUp ? "online" : "offline")
.catch(() => document.getElementById("status-" + name).textContent = "unknown");
}
checkStatus();
setInterval(checkStatus, 5000);
}
}
loadTargets();
</script>
</html>

View File

@@ -1,4 +1,10 @@
{ {
"pc": "00:AA:BB:CC:DD:EE", "pc": {
"server": "01:23:45:67:89:00" "mac": "00:AA:BB:CC:DD:EE",
"ip": "192.168.0.1"
},
"server": {
"mac": "01:23:45:67:89:00",
"ip": "192.168.0.2"
}
} }

9
wol.js
View File

@@ -1,4 +1,5 @@
import dgram from "node:dgram" import dgram from "node:dgram"
import { exec } from "node:child_process";
export function createWolPacket(mac) { export function createWolPacket(mac) {
const parts = mac.split(/[:-]/); const parts = mac.split(/[:-]/);
@@ -38,4 +39,12 @@ export async function sendWolPacket(mac, broadcast = "255.255.255.255", port = 9
}); });
}); });
}); });
}
export function ping(ip) {
return new Promise(resolve => {
exec(`ping -c 1 -W 1 ${ip}`, err => {
resolve(!err);
});
});
} }