Create simple template renderer

This commit is contained in:
2025-10-19 01:59:17 +08:00
parent 941d93401c
commit eabc75c8de
9 changed files with 165 additions and 46 deletions

View File

@@ -3,6 +3,7 @@ services:
build: .
image: pinlin/wakeup3770:latest
environment:
- NODE_ENV=production
- PORT=3000
- MAC_ADDRESS=12:34:56:78:90:AB
- OIDC_WELL_KNOWN_URL=

View File

@@ -1,3 +1,4 @@
NODE_ENV=production
PORT=3000
MAC_ADDRESS=12:34:56:78:90:AB
OIDC_WELL_KNOWN_URL=

View File

@@ -2,38 +2,27 @@ const express = require("express");
const session = require("express-session");
const { Issuer } = require("openid-client");
const wol = require("wake_on_lan");
const { createTemplateRenderer } = require("./utils/templates.jsm");
require('dotenv').config();
require("dotenv").config();
const PORT = process.env.PORT || 3000;
const REDIRECT_URI =
process.env.REDIRECT_URI || "http://127.0.0.1:3000/callback";
const app = express();
const PORT = process.env.PORT || 3000;
const REDIRECT_URI = process.env.REDIRECT_URI || "http://127.0.0.1:3000/callback";
app.use(session({
app.use(
session({
secret: process.env.COOKIE_SECRET,
saveUninitialized: false,
cookie: { maxAge: 10 * 60 * 1000 }
}));
cookie: { maxAge: 10 * 60 * 1000 },
})
);
function expandHtml(body) {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: sans-serif; padding: 1.5rem; font-size: 1.2rem; }
a, button { font-size: 1.1rem; }
</style>
</head>
<body>
${body}
</body>
</html>
`
}
const renderHtml = createTemplateRenderer({
useCache: process.env.NODE_ENV === "production",
});
let client;
@@ -43,32 +32,21 @@ let client;
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
redirect_uris: [REDIRECT_URI],
response_types: ["code"]
response_types: ["code"],
});
app.get("/", (req, res) => {
if (req.session.user) {
res.send(expandHtml(`
<h1>喚醒 PinLin3770</h1>
<p>已登入為身分 ${req.session.user.sub}</p>
<form method="POST" action="/logout">
<button type="submit">登出</button>
</form>
<br>
<a href="/wakeup3770">喚醒 PinLin3770</a>
`));
res.send(renderHtml("index.html", { USERNAME: req.session.user.sub }));
} else {
res.send(expandHtml(`
<h1>喚醒 PinLin3770</h1>
<a href="/login">點此登入</a>
`));
res.send(renderHtml("login.html"));
}
});
app.get("/login", (req, res) => {
const url = client.authorizationUrl({
scope: "openid profile",
redirect_uri: REDIRECT_URI
redirect_uri: REDIRECT_URI,
});
res.redirect(url);
});
@@ -81,8 +59,8 @@ let client;
req.session.user = userinfo;
res.redirect("/");
} catch (err) {
res.send(expandHtml(`<p>⚠️ 登入失敗</p><p>${err}</p><a href="/">回首頁</a>`));
} catch (error) {
res.send(renderHtml("login-fail.html", { error }));
}
});
@@ -91,9 +69,10 @@ let client;
wol.wake(process.env.MAC_ADDRESS, (error) => {
if (error) {
return res.send(expandHtml(`<p>⚠️ 發送魔法封包失敗</p><p>${error}</p><a href="/">回首頁</a>`));
res.send(renderHtml("wakeup-fail.html", { error }));
} else {
res.send(renderHtml("wakeup-success.html"));
}
res.send(expandHtml(`<p>✅ 已經發送魔法封包</p><a href="/">回首頁</a>`));
});
});

25
templates/index.html Normal file
View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>喚醒 PinLin3770</title>
<style>
body {
font-family: sans-serif; padding: 1.5rem; font-size: 1.2rem;
}
a, button {
font-size: 1.1rem;
}
</style>
</head>
<body>
<h1>喚醒 PinLin3770</h1>
<p>已登入為身分 {{USERNAME}}</p>
<form method="POST" action="/logout">
<button type="submit">登出</button>
</form>
<br>
<a href="/wakeup3770">喚醒 PinLin3770</a>
</body>
</html>

22
templates/login-fail.html Normal file
View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登入失敗 | 喚醒 PinLin3770</title>
<style>
body {
font-family: sans-serif; padding: 1.5rem; font-size: 1.2rem;
}
a, button {
font-size: 1.1rem;
}
</style>
</head>
<body>
<h1>喚醒 PinLin3770</h1>
<p>⚠️ 登入失敗</p>
<p>{{error}}</p>
<a href="/">回首頁</a>
</body>
</html>

20
templates/login.html Normal file
View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登入 | 喚醒 PinLin3770</title>
<style>
body {
font-family: sans-serif; padding: 1.5rem; font-size: 1.2rem;
}
a, button {
font-size: 1.1rem;
}
</style>
</head>
<body>
<h1>喚醒 PinLin3770</h1>
<a href="/login">點此登入</a>
</body>
</html>

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>喚醒失敗 | 喚醒 PinLin3770</title>
<style>
body {
font-family: sans-serif; padding: 1.5rem; font-size: 1.2rem;
}
a, button {
font-size: 1.1rem;
}
</style>
</head>
<body>
<h1>喚醒 PinLin3770</h1>
<p>⚠️ 發送魔法封包失敗</p>
<p>{{error}}</p>
<a href="/">回首頁</a>
</body>
</html>

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>喚醒成功 | 喚醒 PinLin3770</title>
<style>
body {
font-family: sans-serif; padding: 1.5rem; font-size: 1.2rem;
}
a, button {
font-size: 1.1rem;
}
</style>
</head>
<body>
<h1>喚醒 PinLin3770</h1>
<p>✅ 已經發送魔法封包</p>
<a href="/">回首頁</a>
</body>
</html>

28
utils/templates.jsm Normal file
View File

@@ -0,0 +1,28 @@
import fs from "fs";
import path from "path";
export function createTemplateRenderer({
baseDir = path.resolve("templates"),
useCache = true,
} = {}) {
const cache = {};
return function render(fileName, vars = {}) {
const filePath = path.join(baseDir, fileName);
let template;
if (useCache && cache[filePath]) {
template = cache[filePath];
} else {
template = fs.readFileSync(filePath, "utf8");
if (useCache) cache[filePath] = template;
}
let html = template;
for (const [key, value] of Object.entries(vars)) {
html = html.replace(new RegExp(`{{${key}}}`, "g"), value);
}
return html;
};
}