feat: add terminal & wake pc

This commit is contained in:
Khairul Hidayat 2024-03-16 10:14:17 +07:00
parent 1e8252d9da
commit 5e8a3c88a2
21 changed files with 782 additions and 21 deletions

View File

@ -1,5 +1,9 @@
#
# Config
JWT_SECRET=
AUTH_USERNAME=
AUTH_PASSWORD=
# Apps
PC_MAC_ADDR=
TERMINAL_SHELL=

Binary file not shown.

1
backend/global.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module "wol";

View File

@ -1,8 +1,11 @@
import "dotenv/config";
import { Hono } from "hono";
import { cors } from "hono/cors";
import { serveStatic } from "hono/bun";
import { HTTPException } from "hono/http-exception";
import { serveStatic } from "@hono/node-server/serve-static";
import { serve } from "@hono/node-server";
import routes from "./routes/_routes";
import createWsServer from "./websocket";
const app = new Hono()
.use(cors())
@ -15,4 +18,8 @@ const app = new Hono()
return c.json({ message: err.message }, 500);
});
export default app;
const server = serve(app, (info) => {
console.log(`App listening on http://${info.address}:${info.port}`);
});
createWsServer(server);

46
backend/lib/terminal.ts Normal file
View File

@ -0,0 +1,46 @@
import Pty from "node-pty";
import type { WebSocket } from "ws";
type TerminalClient = WebSocket & { tty?: Pty.IPty | null };
export const createTerminalSession = (client: TerminalClient) => {
// Each client will have own terminal
const tty = Pty.spawn(process.env.TERMINAL_SHELL || "bash", [], {
name: "xterm-color",
cols: 80,
rows: 24,
cwd: process.env.PWD,
env: process.env,
});
client.tty = tty;
tty.onExit(() => {
client.tty = null;
client.close();
});
tty.onData(function (data) {
client.send(data);
});
client.on("close", function () {
if (client.tty) {
client.tty.kill("SIGINT");
client.tty = null;
}
});
client.on("message", function (data) {
const msg = data.toString("utf-8");
if (msg.startsWith("resize:")) {
const [cols, rows] = msg.split(":")[1].split(",");
if (client.tty) {
client.tty.resize(parseInt(cols), parseInt(rows));
}
return;
}
client.tty && client.tty.write(msg);
});
};

165
backend/package-lock.json generated Normal file
View File

@ -0,0 +1,165 @@
{
"name": "backend",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "backend",
"dependencies": {
"@hono/zod-validator": "^0.2.0",
"hono": "^4.1.0",
"nanoid": "^5.0.6",
"systeminformation": "^5.22.2",
"wol": "^1.0.7",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/bun": "latest",
"@types/jsonwebtoken": "^9.0.6"
},
"peerDependencies": {
"typescript": "^5.0.0"
}
},
"node_modules/@hono/zod-validator": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@hono/zod-validator/-/zod-validator-0.2.0.tgz",
"integrity": "sha512-PC7akbA/DCFY406BL3+ogjYb+7Fgfs/6XPvyURBYMczo0M7kYsTUMnF8hA9mez1RORNCWPqXuFGfKrkoUVPvrQ==",
"peerDependencies": {
"hono": ">=3.9.0",
"zod": "^3.19.1"
}
},
"node_modules/@types/bun": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.0.8.tgz",
"integrity": "sha512-E6UWZuN4ymAxzUBWVIGDHJ3Zey7I8cMzDZ+cB1BqhZsmd1uPb9iAQzpWMruY1mKzsuD3R+dZPoBkZz8QL1KhSA==",
"dev": true,
"dependencies": {
"bun-types": "1.0.29"
}
},
"node_modules/@types/jsonwebtoken": {
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz",
"integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "20.11.28",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz",
"integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/ws": {
"version": "8.5.10",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
"integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/bun-types": {
"version": "1.0.29",
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.0.29.tgz",
"integrity": "sha512-Z+U1ORr/2UCwxelIZxE83pyPLclviYL9UewQCNEUmGeLObY8ao+3WF3D8N1+NMv2+S+hUWsdBJam+4GoPEz35g==",
"dev": true,
"dependencies": {
"@types/node": "~20.11.3",
"@types/ws": "~8.5.10"
}
},
"node_modules/hono": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.1.0.tgz",
"integrity": "sha512-9no6DCHb4ijB1tWdFXU6JnrnFgzwVZ1cnIcS1BjAFnMcjbtBTOMsQrDrPH3GXbkNEEEkj8kWqcYBy8Qc0bBkJQ==",
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/nanoid": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.6.tgz",
"integrity": "sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^18 || >=20"
}
},
"node_modules/systeminformation": {
"version": "5.22.3",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.22.3.tgz",
"integrity": "sha512-pTU7/kMQaYfUs929Uhl+C2heTNhKIgPuoiV5s2TMO3SLf10Zr7Rl/ZvVaiYWFbZVdFsZ+9tSXsybGdBQcr+xww==",
"os": [
"darwin",
"linux",
"win32",
"freebsd",
"openbsd",
"netbsd",
"sunos",
"android"
],
"bin": {
"systeminformation": "lib/cli.js"
},
"engines": {
"node": ">=8.0.0"
},
"funding": {
"type": "Buy me a coffee",
"url": "https://www.buymeacoffee.com/systeminfo"
}
},
"node_modules/typescript": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"node_modules/wol": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/wol/-/wol-1.0.7.tgz",
"integrity": "sha512-kg7ETY8g3V5+3GVhUfWCVjeXuCmfrX6xfw4cw4c88+MtoxkbFmcs9Y5yhT1wwOL8inogFUQZ8JMzH9OekaaawQ==",
"bin": {
"wake": "bin/wake"
}
},
"node_modules/zod": {
"version": "3.22.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
"integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View File

@ -3,21 +3,28 @@
"module": "index.ts",
"type": "module",
"scripts": {
"dev": "bun run --watch index.ts",
"start": "bun run index.ts"
"dev": "tsx --watch index.ts",
"start": "tsx index.ts"
},
"devDependencies": {
"@types/bun": "latest",
"@types/jsonwebtoken": "^9.0.6"
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.11.28",
"@types/ws": "^8.5.10",
"tsx": "^4.7.1"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"@hono/node-server": "^1.8.2",
"@hono/zod-validator": "^0.2.0",
"dotenv": "^16.4.5",
"hono": "^4.1.0",
"nanoid": "^5.0.6",
"node-pty": "^1.0.0",
"systeminformation": "^5.22.2",
"wol": "^1.0.7",
"ws": "^8.16.0",
"zod": "^3.22.4"
}
}

View File

@ -2,13 +2,15 @@ import { Hono } from "hono";
import auth from "./auth";
import system from "./system";
import _process from "./process";
import apps from "./apps";
import { authMiddleware } from "../lib/jwt";
const routes = new Hono()
.route("/auth", auth)
.use(authMiddleware)
.route("/system", system)
.route("/process", _process);
.route("/process", _process)
.route("/apps", apps);
export type AppType = typeof routes;

22
backend/routes/apps.ts Normal file
View File

@ -0,0 +1,22 @@
import { Hono } from "hono";
import wol from "wol";
import { HTTPException } from "hono/http-exception";
const route = new Hono().post("/wakepc", async (c) => {
const { PC_MAC_ADDR } = process.env;
try {
await new Promise((resolve, reject) => {
wol.wake(PC_MAC_ADDR || "", (err: any, res: any) =>
err ? reject(err) : resolve(res)
);
});
} catch (err) {
console.log(err);
throw new HTTPException(400, { message: "Cannot wake pc up!" });
}
return c.json({ message: "waking up..." });
});
export default route;

28
backend/websocket.ts Normal file
View File

@ -0,0 +1,28 @@
import { WebSocketServer } from "ws";
import { verifyToken } from "./lib/jwt";
import { createTerminalSession } from "./lib/terminal";
const createWsServer = (server: any) => {
const wss = new WebSocketServer({ server: server as never });
wss.on("connection", async (ws, req) => {
const url = new URL(req.url || "", `http://${req.headers.host}`);
const token = url.searchParams.get("token");
try {
await verifyToken(token || "");
} catch (err) {
console.log(err);
ws.close();
return;
}
if (url.pathname === "/terminal") {
createTerminalSession(ws);
}
ws.on("error", console.error);
});
};
export default createWsServer;

257
backend/yarn.lock Normal file
View File

@ -0,0 +1,257 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@esbuild/aix-ppc64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz#d1bc06aedb6936b3b6d313bf809a5a40387d2b7f"
integrity sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==
"@esbuild/android-arm64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz#7ad65a36cfdb7e0d429c353e00f680d737c2aed4"
integrity sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==
"@esbuild/android-arm@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.12.tgz#b0c26536f37776162ca8bde25e42040c203f2824"
integrity sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==
"@esbuild/android-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.12.tgz#cb13e2211282012194d89bf3bfe7721273473b3d"
integrity sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==
"@esbuild/darwin-arm64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz#cbee41e988020d4b516e9d9e44dd29200996275e"
integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==
"@esbuild/darwin-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz#e37d9633246d52aecf491ee916ece709f9d5f4cd"
integrity sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==
"@esbuild/freebsd-arm64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz#1ee4d8b682ed363b08af74d1ea2b2b4dbba76487"
integrity sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==
"@esbuild/freebsd-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz#37a693553d42ff77cd7126764b535fb6cc28a11c"
integrity sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==
"@esbuild/linux-arm64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz#be9b145985ec6c57470e0e051d887b09dddb2d4b"
integrity sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==
"@esbuild/linux-arm@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz#207ecd982a8db95f7b5279207d0ff2331acf5eef"
integrity sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==
"@esbuild/linux-ia32@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz#d0d86b5ca1562523dc284a6723293a52d5860601"
integrity sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==
"@esbuild/linux-loong64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz#9a37f87fec4b8408e682b528391fa22afd952299"
integrity sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==
"@esbuild/linux-mips64el@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz#4ddebd4e6eeba20b509d8e74c8e30d8ace0b89ec"
integrity sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==
"@esbuild/linux-ppc64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz#adb67dadb73656849f63cd522f5ecb351dd8dee8"
integrity sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==
"@esbuild/linux-riscv64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz#11bc0698bf0a2abf8727f1c7ace2112612c15adf"
integrity sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==
"@esbuild/linux-s390x@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz#e86fb8ffba7c5c92ba91fc3b27ed5a70196c3cc8"
integrity sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==
"@esbuild/linux-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz#5f37cfdc705aea687dfe5dfbec086a05acfe9c78"
integrity sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==
"@esbuild/netbsd-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz#29da566a75324e0d0dd7e47519ba2f7ef168657b"
integrity sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==
"@esbuild/openbsd-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz#306c0acbdb5a99c95be98bdd1d47c916e7dc3ff0"
integrity sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==
"@esbuild/sunos-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz#0933eaab9af8b9b2c930236f62aae3fc593faf30"
integrity sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==
"@esbuild/win32-arm64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz#773bdbaa1971b36db2f6560088639ccd1e6773ae"
integrity sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==
"@esbuild/win32-ia32@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz#000516cad06354cc84a73f0943a4aa690ef6fd67"
integrity sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==
"@esbuild/win32-x64@0.19.12":
version "0.19.12"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz#c57c8afbb4054a3ab8317591a0b7320360b444ae"
integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==
"@hono/node-server@^1.8.2":
version "1.8.2"
resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.8.2.tgz#940b3a0dbd7adbc510b79b626f3603258493354b"
integrity sha512-h8l2TBLCPHZBUrrkosZ6L5CpBLj6zdESyF4B+zngiCDF7aZFQJ0alVbLx7jn8PCVi9EyoFf8a4hOZFi1tD95EA==
"@hono/zod-validator@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@hono/zod-validator/-/zod-validator-0.2.0.tgz#77d8f1f167cba85b008a52f594ed823c841b2300"
integrity sha512-PC7akbA/DCFY406BL3+ogjYb+7Fgfs/6XPvyURBYMczo0M7kYsTUMnF8hA9mez1RORNCWPqXuFGfKrkoUVPvrQ==
"@types/jsonwebtoken@^9.0.6":
version "9.0.6"
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz#d1af3544d99ad992fb6681bbe60676e06b032bd3"
integrity sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@^20.11.28":
version "20.11.28"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.28.tgz#4fd5b2daff2e580c12316e457473d68f15ee6f66"
integrity sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==
dependencies:
undici-types "~5.26.4"
"@types/ws@^8.5.10":
version "8.5.10"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787"
integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==
dependencies:
"@types/node" "*"
dotenv@^16.4.5:
version "16.4.5"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
esbuild@~0.19.10:
version "0.19.12"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.12.tgz#dc82ee5dc79e82f5a5c3b4323a2a641827db3e04"
integrity sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==
optionalDependencies:
"@esbuild/aix-ppc64" "0.19.12"
"@esbuild/android-arm" "0.19.12"
"@esbuild/android-arm64" "0.19.12"
"@esbuild/android-x64" "0.19.12"
"@esbuild/darwin-arm64" "0.19.12"
"@esbuild/darwin-x64" "0.19.12"
"@esbuild/freebsd-arm64" "0.19.12"
"@esbuild/freebsd-x64" "0.19.12"
"@esbuild/linux-arm" "0.19.12"
"@esbuild/linux-arm64" "0.19.12"
"@esbuild/linux-ia32" "0.19.12"
"@esbuild/linux-loong64" "0.19.12"
"@esbuild/linux-mips64el" "0.19.12"
"@esbuild/linux-ppc64" "0.19.12"
"@esbuild/linux-riscv64" "0.19.12"
"@esbuild/linux-s390x" "0.19.12"
"@esbuild/linux-x64" "0.19.12"
"@esbuild/netbsd-x64" "0.19.12"
"@esbuild/openbsd-x64" "0.19.12"
"@esbuild/sunos-x64" "0.19.12"
"@esbuild/win32-arm64" "0.19.12"
"@esbuild/win32-ia32" "0.19.12"
"@esbuild/win32-x64" "0.19.12"
fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
get-tsconfig@^4.7.2:
version "4.7.3"
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.3.tgz#0498163d98f7b58484dd4906999c0c9d5f103f83"
integrity sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==
dependencies:
resolve-pkg-maps "^1.0.0"
hono@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/hono/-/hono-4.1.0.tgz#62cef81df0dbf731643155e1e5c1b9dffb230dc4"
integrity sha512-9no6DCHb4ijB1tWdFXU6JnrnFgzwVZ1cnIcS1BjAFnMcjbtBTOMsQrDrPH3GXbkNEEEkj8kWqcYBy8Qc0bBkJQ==
nan@^2.17.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0"
integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==
nanoid@^5.0.6:
version "5.0.6"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.6.tgz#7f99a033aa843e4dcf9778bdaec5eb02f4dc44d5"
integrity sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==
node-pty@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.0.0.tgz#7daafc0aca1c4ca3de15c61330373af4af5861fd"
integrity sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==
dependencies:
nan "^2.17.0"
resolve-pkg-maps@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f"
integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
systeminformation@^5.22.2:
version "5.22.3"
resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.22.3.tgz#33ef8bd045125d672f64e7000015fefae07a77cb"
integrity sha512-pTU7/kMQaYfUs929Uhl+C2heTNhKIgPuoiV5s2TMO3SLf10Zr7Rl/ZvVaiYWFbZVdFsZ+9tSXsybGdBQcr+xww==
tsx@^4.7.1:
version "4.7.1"
resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.7.1.tgz#27af6cbf4e1cdfcb9b5425b1c61bb7e668eb5e84"
integrity sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==
dependencies:
esbuild "~0.19.10"
get-tsconfig "^4.7.2"
optionalDependencies:
fsevents "~2.3.3"
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
wol@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/wol/-/wol-1.0.7.tgz#a2e70efca2a28324a744a5d12331d359da470ff7"
integrity sha512-kg7ETY8g3V5+3GVhUfWCVjeXuCmfrX6xfw4cw4c88+MtoxkbFmcs9Y5yhT1wwOL8inogFUQZ8JMzH9OekaaawQ==
ws@^8.16.0:
version "8.16.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4"
integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==
zod@^3.22.4:
version "3.22.4"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==

View File

@ -31,10 +31,14 @@
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "~3.29.0",
"react-native-svg": "14.1.0",
"react-native-toast-notifications": "^3.4.0",
"react-native-web": "~0.19.6",
"react-query": "^3.39.3",
"twrnc": "^4.1.0",
"typescript": "^5.3.0",
"xterm": "^5.3.0",
"xterm-addon-attach": "^0.9.0",
"xterm-addon-fit": "^0.8.0",
"zod": "^3.22.4",
"zustand": "^4.5.2"
},

View File

@ -7,8 +7,10 @@ import { cn, tw } from "@/lib/utils";
import { useDeviceContext } from "twrnc";
import { StatusBar } from "expo-status-bar";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Toast from "react-native-toast-notifications";
import { useStore } from "zustand";
import authStore from "@/stores/authStore";
import { toastStore } from "@/stores/toastStore";
const RootLayout = () => {
const insets = useSafeAreaInsets();
@ -39,6 +41,11 @@ const RootLayout = () => {
<View style={cn("flex-1 bg-[#f2f7fb]", { paddingTop: insets.top })}>
<Slot />
</View>
<Toast
ref={(ref) => {
toastStore.setState(ref);
}}
/>
</QueryClientProvider>
);
};

13
src/app/apps/lib.ts Normal file
View File

@ -0,0 +1,13 @@
// src/app/apps/lib.ts
import api from "@/lib/api";
import { showToast } from "@/stores/toastStore";
export const wakePcUp = async () => {
try {
await api.apps.wakepc.$post();
showToast("Waking up PC...");
} catch (err) {
showToast("Cannot wake up the PC!", { type: "danger" });
}
};

93
src/app/apps/terminal.tsx Normal file
View File

@ -0,0 +1,93 @@
import { Platform } from "react-native";
import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import { AttachAddon } from "xterm-addon-attach";
import Box from "@ui/Box";
import Text from "@ui/Text";
import React, { useEffect, useRef } from "react";
import "xterm/css/xterm.css";
import { API_BASEURL } from "@/lib/constants";
import authStore, { useAuth } from "@/stores/authStore";
const isWeb = Platform.OS === "web";
const TerminalPage = () => {
const { token } = useAuth();
const terminalRef = useRef<any>();
const fitRef = useRef<FitAddon | null>(null);
useEffect(() => {
if (!isWeb || !token) {
return;
}
const term = new Terminal({ theme: { background: "#1d1e2b" } });
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
const baseUrl = API_BASEURL.replace("https://", "wss://").replace(
"http://",
"ws://"
);
const socket = new WebSocket(baseUrl + "/terminal?token=" + token);
const attachAddon = new AttachAddon(socket);
// Attach the socket to term
term.loadAddon(attachAddon);
term.open(terminalRef.current);
fitAddon.fit();
fitRef.current = fitAddon;
const sendResizeSignal = (data: { cols: number; rows: number }) => {
socket.send("resize:" + [data.cols, data.rows].join(","));
};
term.onResize(sendResizeSignal);
setTimeout(() => {
sendResizeSignal({ cols: term.cols, rows: term.rows });
}, 1000);
return () => {
attachAddon.dispose();
fitAddon.dispose();
term.dispose();
fitRef.current = null;
};
}, [token]);
useEffect(() => {
if (!isWeb) {
return;
}
const onResize = () => {
if (fitRef.current) {
fitRef.current.fit();
}
};
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, []);
if (!isWeb) {
return (
<Box className="p-8">
<Text className="text-center">Only web platform suppported.</Text>
</Box>
);
}
return (
<div
ref={terminalRef}
style={{ height: "100vh", background: "#1d1e2b", padding: 16 }}
/>
);
};
export default TerminalPage;

View File

@ -0,0 +1,57 @@
import React, { ComponentProps } from "react";
import Text from "@ui/Text";
import Box from "@ui/Box";
import { Ionicons } from "@ui/Icons";
import { HStack } from "@ui/Stack";
import Button from "@ui/Button";
import { useNavigation } from "expo-router";
import { wakePcUp } from "@/app/apps/lib";
type Props = ComponentProps<typeof Box>;
const Apps = (props: Props) => {
const navigation = useNavigation();
const appList = [
{
name: "Terminal",
icon: <Ionicons name="terminal" />,
path: "terminal",
},
{
name: "PC Control",
icon: <Ionicons name="desktop" />,
action: wakePcUp,
},
];
return (
<Box {...props}>
<Text className="text-lg md:text-2xl font-medium">Apps</Text>
<HStack className="mt-4 flex-wrap gap-4 md:gap-8">
{appList.map((app, idx) => (
<Button
key={idx}
className="flex-col flex-1 basis-1/3 md:max-w-[160px] h-32 bg-white"
labelClasses="text-sm text-center text-gray-900"
iconClassName="text-2xl mb-1 text-primary"
variant="outline"
icon={app.icon}
label={app.name}
onPress={() => {
if (app.path) {
navigation.navigate(("apps/" + app.path) as never);
}
if (app.action) {
app.action();
}
}}
/>
))}
</HStack>
</Box>
);
};
export default React.memo(Apps);

View File

@ -11,7 +11,7 @@ type Props = {
const Summary = ({ data }: Props) => {
return (
<Box className="px-4 py-6 mt-4 bg-white border border-gray-100 rounded-lg">
<Box className="px-4 mt-4 py-6 bg-white border border-gray-100 rounded-lg">
<Text className="text-5xl">{dayjs(data?.date).format("HH:mm")}</Text>
<Text className="mt-2">
{dayjs(data?.date).format("dddd, DD MMM YYYY")}

View File

@ -7,6 +7,9 @@ import Summary from "./_sections/Summary";
import Storage from "./_sections/Storage";
import Container from "@ui/Container";
import { useAuth } from "@/stores/authStore";
import { HStack } from "@ui/Stack";
import Box from "@ui/Box";
import Apps from "./_sections/Apps";
const HomePage = () => {
const { isLoggedIn } = useAuth();
@ -22,12 +25,17 @@ const HomePage = () => {
}
return (
<Container scrollable className="px-4 py-8 md:py-16">
<Text className="text-2xl font-medium">Home Lab</Text>
<Summary data={system} />
<Performance data={system} />
<Storage data={system} />
<Container scrollable className="px-4 md:px-8 max-w-none py-8">
<HStack className="items-start gap-8">
<Box className="flex-1 md:max-w-lg">
<Text className="text-2xl font-medium">Home Lab</Text>
<Summary data={system} />
<Apps className="md:hidden mt-6" />
<Performance data={system} />
<Storage data={system} />
</Box>
<Apps className="hidden md:flex md:flex-col md:flex-1" />
</HStack>
</Container>
);
};

View File

@ -60,6 +60,7 @@ interface ButtonProps
labelClasses?: string;
className?: string;
icon?: React.ReactNode;
iconClassName?: string;
children?: string;
}
function Button({
@ -69,12 +70,15 @@ function Button({
variant,
size,
icon,
iconClassName,
children,
...props
}: ButtonProps) {
const textStyles = cn(
buttonTextVariants({ variant, size, className: labelClasses })
);
const textStyles = buttonTextVariants({
variant,
size,
className: labelClasses,
});
return (
<Pressable
@ -86,10 +90,12 @@ function Button({
}
{...props}
>
{icon ? <Slot.View style={textStyles}>{icon}</Slot.View> : null}
{icon ? (
<Slot.View style={cn(textStyles, iconClassName)}>{icon}</Slot.View>
) : null}
{label || children ? (
<Text style={textStyles}>{label || children}</Text>
<Text style={cn(textStyles)}>{label || children}</Text>
) : null}
</Pressable>
);

14
src/stores/toastStore.ts Normal file
View File

@ -0,0 +1,14 @@
import { createStore } from "zustand";
import { Toast, ToastOptions } from "react-native-toast-notifications";
export const toastStore = createStore<typeof Toast | null>(() => null);
export const showToast = (
message: string | JSX.Element,
toastOptions?: ToastOptions
) => {
const toast = toastStore.getState();
if (toast) {
toast.show(message, toastOptions);
}
};

View File

@ -6406,6 +6406,11 @@ react-native-svg@14.1.0:
css-select "^5.1.0"
css-tree "^1.1.3"
react-native-toast-notifications@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/react-native-toast-notifications/-/react-native-toast-notifications-3.4.0.tgz#9f2c79ec80fba4af6b788ecddf704f94cf21dbc2"
integrity sha512-ZvB//jLhRiBRemtcH7vGP1maiKCikqtW4aDqo+QYvEIOcX0y3GrjxRayVAqI4oh0qJgd/26DkbM8COobj+0MEQ==
react-native-web@~0.19.6:
version "0.19.10"
resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.19.10.tgz#5f7205f8909c0889bc89c9fde7c6e287defa7c63"
@ -7827,6 +7832,21 @@ xtend@~4.0.1:
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
xterm-addon-attach@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.9.0.tgz#dd18057f147a402de1df852c1de4c8f3b63b37be"
integrity sha512-NykWWOsobVZPPK3P9eFkItrnBK9Lw0f94uey5zhqIVB1bhswdVBfl+uziEzSOhe2h0rT9wD0wOeAYsdSXeavPw==
xterm-addon-fit@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz#48ca99015385141918f955ca7819e85f3691d35f"
integrity sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==
xterm@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0.tgz#867daf9cc826f3d45b5377320aabd996cb0fce46"
integrity sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==
y18n@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"