昨天已經把大部分的 GUI 弄完了,之前也已經寫好了伺服器的程式,今天我們把兩邊拼起來吧!
我們把之前寫的 server.js 搬過來新的專案資料夾中:
然後因為前面沒有 config.js 幫忙檔怪東西,輸出也改成在 GUI 中,所以我們也得改一下 server.js。
因為現在我們的輸出應該要在 GUI 上面,而不是本來的 Terminal,所以新增一個 Logger 類別:
class Logger {
#events = {};
constructor(log, logfile = null) {
this._log = log;
this._logfile = logfile;
if (this._log && this._logfile) {
this._stream = fs.createWriteStream(this._logfile, { flags: "a" });
this._stream.write(`\n========\n`);
this.closed = false;
}
}
log(type = "INFO", message) {
if (typeof this.#events[type] === "function") this.#events[type](message);
if (this._stream && !this.closed) this._stream.write(`${new Date().toISOString()} [${type}] ${message}\n`);
return this;
}
info(message) {
return this.log("INFO", message);
}
success(message) {
return this.log("SUCCESS", message);
}
error(message) {
return this.log("ERROR", message);
}
warn(message) {
return this.log("WARN", message);
}
debug(message) {
return this.log("DEBUG", message);
}
close() {
if (this._stream && !this.closed) {
this._stream.end();
this.closed = true;
}
return this;
}
on(event, callback) {
this.#events[event] = callback;
return this;
}
}
這個 Logger 很簡單,就...寫紀錄而已,但你可以用 on
來掛事件監聽。
因為現在少了 config.js 來阻擋怪怪的東西,所以新增一個 check 函式做型別檢查,希望從 agent.js 往後丟時就把型別處裡好。
function check({ port, folder, log, logfile }) {
if (typeof port !== "number") return false;
if (typeof folder !== "string") return false;
if (typeof log !== "boolean") return false;
if (log && typeof logfile !== "string") return false;
return true;
}
原本的 createServer
函式需要稍做修改才能套用 Logger 和 check。
function createServer({ port, folder, log, logfile }, on = {}) {
const logger = new Logger(log, logfile);
for (const event in on) logger.on(event.toUpperCase(), on[event]);
if (!check({ port, folder, log, logfile })) {
logger.error("Invalid server configuration");
return null;
}
try {
const app = new Koa();
app.use(async (ctx, next) => {
logger.info(`Process ${ctx.request.method} ${ctx.request.url} from ${ctx.request.ip}`);
await next();
});
app.use(require("koa-static")(folder));
const server = app.listen(port);
logger.success(`Server started at port ${port}`);
logger.success(`Serving static files from ${folder}`);
logger.success(`Visit http://localhost:${port}/ to see your website.`);
if (log) logger.info(`Log file: ${logfile}`);
return async () => {
server.close();
logger.info(`Server closed.`);
logger.close();
};
} catch (error) {
logger.error(error);
logger.close();
console.error(error);
return null;
}
}
我們讓 createServer
有了第二個參數,用來掛上 Logger 的事件監聽。
然後用 check 來檢查參數。
(但這其實有個小問題,就是 log 和 logfile 在還沒檢查就丟進 Logger 了,因為檢查出錯誤時需要 logger)
其它的部分因為有 Logger ,所以簡化了一些 log 相關的程式。
其實就是加上 logs 的 CSS 還有加一些 id 和 class 而已。
log 的 CSS,用偽元素來標示類別:
.log {
padding: 0 8px;
word-break: break-all;
transition: all 0.2s;
}
.log:hover {
background: var(--nord4);
}
.log.success {
color: var(--nord14);
text-shadow: 0 0 var(--nord1);
}
.log.success::before {
content: "[success] ";
}
.log.info {
color: var(--nord9);
}
.log.info::before {
content: "[info] ";
}
.log.error {
color: var(--nord11);
}
.log.error::before {
content: "[error] ";
}
.log.warn {
color: var(--nord12);
}
.log.warn::before {
content: "[warn] ";
}
我們讓 agent.js 用 IPC 向 main.js 傳送請求及接收資訊。
我們在 registerListener
中加上啟動和停止的請求機制:
document.querySelector("#launch").addEventListener("click", async () => {
if (document.querySelector("#launch").classList.contains("launched")) {
ipc.send("server-stop", +document.querySelector("#port").value);
return;
} else {
// 取得輸入值
const folder = document.querySelector("#folder").value;
const port = +document.querySelector("#port").value;
const log = document.querySelector("#log").checked;
const logfile = document.querySelector("#logfile").value;
// 將輸入值傳給 main.js
ipc.send("server-launch", { folder, port, log, logfile });
document.querySelector("#launch").classList.add("launched");
document.querySelector("#launch").innerHTML = "停止";
}
});
我們用啟動按鈕的 class 來判斷是啟動還是停止。
然後停止後必須回復啟動按鈕狀態:
ipc.on("server-stopped", async (evt) => {
document.querySelector("#launch").classList.remove("launched");
document.querySelector("#launch").innerHTML = "啟動";
});
伺服器停止後會用 IPC 向前端通知。
在 agent.js 中接收各種訊息並新增至畫面上:
ipc.on("log-info", async (evt, msg) => {
const logs = document.querySelector("#logs");
const log = document.createElement("div");
log.classList.add("log", "info");
log.innerHTML = msg;
logs.appendChild(log);
});
ipc.on("log-success", async (evt, msg) => {
const logs = document.querySelector("#logs");
const log = document.createElement("div");
log.classList.add("log", "success");
log.innerHTML = msg;
logs.appendChild(log);
});
ipc.on("log-error", async (evt, msg) => {
const logs = document.querySelector("#logs");
const log = document.createElement("div");
log.classList.add("log", "error");
log.innerHTML = msg;
logs.appendChild(log);
});
ipc.on("log-warn", async (evt, msg) => {
const logs = document.querySelector("#logs");
const log = document.createElement("div");
log.classList.add("log", "warn");
log.innerHTML = msg;
logs.appendChild(log);
});
各種訊息大同小異,差別只在套用的 class 所用的 CSS。
我們的 main.js 會收到兩種伺服器相關的請求,一是啟動,二是停止:
const createServer = require("./server");
const Server = {}; // 保留未來擴充多伺服器的可能
ipc.on("server-launch", async (evt, config) => {
console.log("server-launch", config);
Servers[config.port] = createServer(config, {
info: (msg) => evt.sender.send("log-info", msg),
success: (msg) => evt.sender.send("log-success", msg),
error: (msg) => evt.sender.send("log-error", msg),
warn: (msg) => evt.sender.send("log-warn", msg),
});
if (Servers[config.port] === null) {
delete Servers[config.port];
evt.sender.send("log-error", "啟動伺服器失敗");
evt.sender.send("server-stopped");
}
});
ipc.on("server-stop", async (evt, port) => {
console.log("server-stop", port);
await Servers[port]();
delete Servers[port];
evt.sender.send("server-stopped");
});
main.js 的部分相對簡單,只需要轉發兩種請求並掛上監聽而已。
程式就完成了。
大概就是這樣,錯誤也很正常的噴回前端了。
當然就是打包成跨平台應用程式啊。
以 10/11 20:00 ~ 10/12 20:00 文章觀看數增加值排名
+143
Day 21: 人工智慧在音樂領域的應用 (AI作曲-基因演算法四 掌握生殺大權-Interactive Fitness Function)
+138
Day27 海鮮義大利燉飯Risotto
+136
Day 22: 人工智慧在音樂領域的應用 (AI作曲-基因演算法五 基於規則(Rule-Based)的Fitness Function)
+129
表單處理 Object 裡的 Array
+129
[職場]不放過每個細節,完成一場 0 失誤的專案 Demo!
+117
Day 23: 人工智慧在音樂領域的應用 (AI作曲-基因演算法六 總要敬老尊賢吧?)
+117
Proxmox VE 設定客體機高可用性
+113
Day 27: 人工智慧在音樂領域的應用 (索尼-Flow Machine、谷歌-Magenta )
+108
Day 26: 人工智慧在音樂領域的應用 (AI作曲 - 生成對抗網路 Gan (幹) )
+102
【Day 27】Google Apps Script - API Blueprint 篇 - Apiary 建立專案與版本控制