[WIP] A simple wake-on-lan service
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

create webapp api helpers

+118 -5
+2 -2
src/server.rs
··· 18 18 mac: MacAddress, 19 19 } 20 20 21 - async fn wake(Json(req): Json<WakeRequest>) -> Result<(), Response<String>> { 21 + async fn wake(Json(req): Json<WakeRequest>) -> Result<Json<bool>, Response<String>> { 22 22 println!("Waking {}", req.mac); 23 - req.mac.wake().await.map_err(|err| { 23 + req.mac.wake().await.map(|_| Json(true)).map_err(|err| { 24 24 Response::builder() 25 25 .status(500) 26 26 .body(err.to_string())
+3
web/package.json
··· 18 18 "svelte-check": "^4.3.4", 19 19 "typescript": "~5.9.3", 20 20 "vite": "^7.3.1" 21 + }, 22 + "dependencies": { 23 + "zod": "^4.3.6" 21 24 } 22 25 }
+9
web/pnpm-lock.yaml
··· 7 7 importers: 8 8 9 9 .: 10 + dependencies: 11 + zod: 12 + specifier: ^4.3.6 13 + version: 4.3.6 10 14 devDependencies: 11 15 '@sveltejs/vite-plugin-svelte': 12 16 specifier: ^6.2.1 ··· 551 555 zimmerframe@1.1.4: 552 556 resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} 553 557 558 + zod@4.3.6: 559 + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} 560 + 554 561 snapshots: 555 562 556 563 '@esbuild/aix-ppc64@0.27.3': ··· 936 943 vite: 7.3.1(@types/node@24.10.13) 937 944 938 945 zimmerframe@1.1.4: {} 946 + 947 + zod@4.3.6: {}
+10 -1
web/src/App.svelte
··· 1 1 <script lang="ts"> 2 + import Api from "./lib/api"; 3 + 4 + const config = await Api.config(); 5 + console.log(config); 6 + const first = config.pinned && config.pinned[0]; 7 + const wakeFirst = 8 + first && (await Api.wake({ mac: config.targets[first].mac })); 9 + console.log(wakeFirst); 2 10 </script> 3 11 4 12 <main> 5 - <h1>WOL</h1> 13 + <h1>WOL - {first} ({first ? config.targets[first].mac : null})</h1> 14 + <pre>{JSON.stringify(config)}</pre> 6 15 </main>
+87
web/src/lib/api.ts
··· 1 + import z from "zod"; 2 + 3 + function route<T, I>(opts: { 4 + route: `/${string}`; 5 + output: z.ZodType<T>; 6 + method: "GET"; 7 + input: z.ZodType<I>; 8 + }): (data: I) => Promise<T>; 9 + function route<T, I>(opts: { 10 + route: `/${string}`; 11 + output: z.ZodType<T>; 12 + method: "POST"; 13 + input: z.ZodType<I>; 14 + }): (data: I) => Promise<T>; 15 + function route<T, I>(opts: { 16 + route: `/${string}`; 17 + output: z.ZodType<T>; 18 + method: "GET"; 19 + }): () => Promise<T>; 20 + function route<T, I>(opts: { 21 + route: `/${string}`; 22 + output: z.ZodType<T>; 23 + method: "POST"; 24 + }): () => Promise<T>; 25 + function route<T, I>({ 26 + route, 27 + output, 28 + method, 29 + input, 30 + }: { 31 + route: `/${string}`; 32 + output: z.ZodType<T>; 33 + method: "GET" | "POST"; 34 + input?: z.ZodType<I>; 35 + }): ((data: I) => Promise<T>) | (() => Promise<T>) { 36 + const stringify = 37 + method == "GET" 38 + ? (obj: I) => 39 + new URLSearchParams( 40 + Object.fromEntries( 41 + Object.entries(obj as Object).map(([k, v]) => [k, "" + v]), 42 + ), 43 + ).toString() 44 + : (obj: I) => JSON.stringify(obj); 45 + 46 + const then = (req: Response) => req.json().then(output.parseAsync); 47 + 48 + return input 49 + ? method == "GET" 50 + ? (data: I) => fetch(route + "?" + stringify(data), { method }).then(then) 51 + : (data: I) => 52 + fetch(route, { 53 + method, 54 + body: stringify(data), 55 + headers: { "Content-Type": "application/json" }, 56 + }).then(then) 57 + : () => fetch(route, { method }).then(then); 58 + } 59 + 60 + const Api = { 61 + config: route({ 62 + route: "/config", 63 + method: "GET", 64 + output: z.object({ 65 + binding: z.string(), 66 + pinned: z.array(z.string()).optional(), 67 + targets: z.record( 68 + z.string(), 69 + z.object({ 70 + mac: z.string(), 71 + ip: z.string().nullable(), 72 + }), 73 + ), 74 + }), 75 + }), 76 + 77 + wake: route({ 78 + route: "/wake", 79 + method: "POST", 80 + output: z.boolean(), 81 + input: z.object({ 82 + mac: z.string(), 83 + }), 84 + }), 85 + } as const; 86 + 87 + export default Api;
+7 -2
web/svelte.config.js
··· 1 - import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 1 + import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 2 2 3 3 /** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */ 4 4 export default { 5 5 // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 6 6 // for more information about preprocessors 7 7 preprocess: vitePreprocess(), 8 - } 8 + compilerOptions: { 9 + experimental: { 10 + async: true, 11 + }, 12 + }, 13 + };