mirror of
https://github.com/khairul169/home-lab.git
synced 2026-10-02 17:13:57 +07:00
feat: add authentication
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
#
|
||||
JWT_SECRET=
|
||||
|
||||
AUTH_USERNAME=
|
||||
AUTH_PASSWORD=
|
||||
Binary file not shown.
+9
-109
@@ -1,118 +1,18 @@
|
||||
import { Hono } from "hono";
|
||||
import { cors } from "hono/cors";
|
||||
import { serveStatic } from "hono/bun";
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
import z from "zod";
|
||||
import si from "systeminformation";
|
||||
|
||||
const formatBytes = (bytes: number) => {
|
||||
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
||||
if (bytes === 0) return "n/a";
|
||||
const i = parseInt(String(Math.floor(Math.log(bytes) / Math.log(1024))), 10);
|
||||
if (i === 0) return `${bytes} ${sizes[i]}`;
|
||||
return `${(bytes / 1024 ** i).toFixed(2)} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
const secondsToTime = (seconds: number) => {
|
||||
const d = Math.floor(seconds / (3600 * 24));
|
||||
const h = Math.floor((seconds % (3600 * 24)) / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
// const s = Math.floor(seconds % 60);
|
||||
return `${d}d ${h}h ${m}m`;
|
||||
};
|
||||
import { HTTPException } from "hono/http-exception";
|
||||
import routes from "./routes/_routes";
|
||||
|
||||
const app = new Hono()
|
||||
.use(cors())
|
||||
|
||||
.get("/system", async (c) => {
|
||||
const date = new Date().toISOString();
|
||||
const uptime = secondsToTime(si.time().uptime || 0);
|
||||
const system = await si.system();
|
||||
|
||||
const cpuSpeed = await si.cpuCurrentSpeed();
|
||||
const cpuTemp = await si.cpuTemperature();
|
||||
const cpuLoad = await si.currentLoad();
|
||||
const mem = await si.mem();
|
||||
|
||||
const perf = {
|
||||
cpu: {
|
||||
load: cpuLoad.currentLoad,
|
||||
speed: cpuSpeed.avg,
|
||||
temp: cpuTemp.main,
|
||||
},
|
||||
mem: {
|
||||
total: formatBytes(mem.total),
|
||||
percent: (mem.active / mem.total) * 100,
|
||||
used: formatBytes(mem.active),
|
||||
free: formatBytes(mem.total - mem.active),
|
||||
},
|
||||
};
|
||||
|
||||
const fsMounts = await si.fsSize();
|
||||
const storage = fsMounts
|
||||
.filter(
|
||||
(i) =>
|
||||
i.size > 32 * 1024 * 1024 * 1024 &&
|
||||
!i.mount.startsWith("/var/lib/docker")
|
||||
)
|
||||
.map((i) => ({
|
||||
type: i.type,
|
||||
mount: i.mount,
|
||||
used: formatBytes(i.used),
|
||||
percent: (i.used / i.size) * 100,
|
||||
total: formatBytes(i.size),
|
||||
free: formatBytes(i.available),
|
||||
}));
|
||||
|
||||
return c.json({ uptime, date, system, perf, storage });
|
||||
})
|
||||
|
||||
.get(
|
||||
"/process",
|
||||
zValidator(
|
||||
"query",
|
||||
z
|
||||
.object({ sort: z.enum(["cpu", "mem"]), limit: z.coerce.number() })
|
||||
.partial()
|
||||
.optional()
|
||||
),
|
||||
async (c) => {
|
||||
const memTotal = (await si.mem()).total;
|
||||
const sort = c.req.query("sort") || "mem";
|
||||
const limit = parseInt(c.req.query("limit") || "") || 10;
|
||||
|
||||
let processList = (await si.processes()).list;
|
||||
|
||||
if (sort) {
|
||||
switch (sort) {
|
||||
case "cpu":
|
||||
processList = processList.sort((a, b) => b.cpu - a.cpu);
|
||||
break;
|
||||
case "mem":
|
||||
processList = processList.sort((a, b) => b.mem - a.mem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const list = processList
|
||||
.map((p) => ({
|
||||
name: p.name,
|
||||
cmd: [p.name, p.params].filter(Boolean).join(" "),
|
||||
cpu: p.cpu,
|
||||
cpuPercent: p.cpu.toFixed(1) + "%",
|
||||
mem: p.mem,
|
||||
memUsage: formatBytes((p.mem / 100) * memTotal),
|
||||
path: p.path,
|
||||
user: p.user,
|
||||
}))
|
||||
.slice(0, limit);
|
||||
|
||||
return c.json({ list });
|
||||
.use("*", serveStatic({ root: "./public" }))
|
||||
.route("/", routes)
|
||||
.onError((err, c) => {
|
||||
if (err instanceof HTTPException) {
|
||||
return err.getResponse();
|
||||
}
|
||||
)
|
||||
|
||||
.use("*", serveStatic({ root: "./public" }));
|
||||
|
||||
export type AppType = typeof app;
|
||||
return c.json({ message: err.message }, 500);
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import * as jwt from "hono/jwt";
|
||||
|
||||
const JWT_SECRET =
|
||||
process.env.JWT_SECRET || "75396f4ba17e0012d4511b8d4a5bae11c51008a3";
|
||||
|
||||
export const generateToken = async (data: any) => {
|
||||
return jwt.sign(data, JWT_SECRET);
|
||||
};
|
||||
|
||||
export const verifyToken = async (token: string) => {
|
||||
return jwt.verify(token, JWT_SECRET);
|
||||
};
|
||||
|
||||
export const authMiddleware = jwt.jwt({ secret: JWT_SECRET });
|
||||
@@ -0,0 +1,15 @@
|
||||
export const formatBytes = (bytes: number) => {
|
||||
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
||||
if (bytes === 0) return "n/a";
|
||||
const i = parseInt(String(Math.floor(Math.log(bytes) / Math.log(1024))), 10);
|
||||
if (i === 0) return `${bytes} ${sizes[i]}`;
|
||||
return `${(bytes / 1024 ** i).toFixed(2)} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
export const secondsToTime = (seconds: number) => {
|
||||
const d = Math.floor(seconds / (3600 * 24));
|
||||
const h = Math.floor((seconds % (3600 * 24)) / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
// const s = Math.floor(seconds % 60);
|
||||
return `${d}d ${h}h ${m}m`;
|
||||
};
|
||||
@@ -7,7 +7,8 @@
|
||||
"start": "bun run index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
"@types/bun": "latest",
|
||||
"@types/jsonwebtoken": "^9.0.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
@@ -15,6 +16,7 @@
|
||||
"dependencies": {
|
||||
"@hono/zod-validator": "^0.2.0",
|
||||
"hono": "^4.1.0",
|
||||
"nanoid": "^5.0.6",
|
||||
"systeminformation": "^5.22.2",
|
||||
"zod": "^3.22.4"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Hono } from "hono";
|
||||
import auth from "./auth";
|
||||
import system from "./system";
|
||||
import _process from "./process";
|
||||
import { authMiddleware } from "../lib/jwt";
|
||||
|
||||
const routes = new Hono()
|
||||
.route("/auth", auth)
|
||||
.use(authMiddleware)
|
||||
.route("/system", system)
|
||||
.route("/process", _process);
|
||||
|
||||
export type AppType = typeof routes;
|
||||
|
||||
export default routes;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
import { Hono } from "hono";
|
||||
import { HTTPException } from "hono/http-exception";
|
||||
import { z } from "zod";
|
||||
import { generateToken } from "../lib/jwt";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
const loginSchema = z.object({
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
});
|
||||
type LoginSchema = z.infer<typeof loginSchema>;
|
||||
|
||||
const route = new Hono().post(
|
||||
"/login",
|
||||
zValidator("json", loginSchema),
|
||||
async (c) => {
|
||||
const input: LoginSchema = await c.req.json();
|
||||
const { AUTH_USERNAME, AUTH_PASSWORD } = process.env;
|
||||
|
||||
if (input.username !== AUTH_USERNAME || input.password !== AUTH_PASSWORD) {
|
||||
throw new HTTPException(400, {
|
||||
message: "Username or password is invalid!",
|
||||
});
|
||||
}
|
||||
|
||||
const data = { sessionId: nanoid() };
|
||||
const token = await generateToken(data);
|
||||
|
||||
return c.json({ token, ...data });
|
||||
}
|
||||
);
|
||||
|
||||
export default route;
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Hono } from "hono";
|
||||
import si from "systeminformation";
|
||||
import { formatBytes } from "../lib/utils";
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
import { z } from "zod";
|
||||
|
||||
const route = new Hono().get(
|
||||
"/",
|
||||
zValidator(
|
||||
"query",
|
||||
z
|
||||
.object({ sort: z.enum(["cpu", "mem"]), limit: z.coerce.number() })
|
||||
.partial()
|
||||
.optional()
|
||||
),
|
||||
async (c) => {
|
||||
const memTotal = (await si.mem()).total;
|
||||
const sort = c.req.query("sort") || "mem";
|
||||
const limit = parseInt(c.req.query("limit") || "") || 10;
|
||||
|
||||
let processList = (await si.processes()).list;
|
||||
|
||||
if (sort) {
|
||||
switch (sort) {
|
||||
case "cpu":
|
||||
processList = processList.sort((a, b) => b.cpu - a.cpu);
|
||||
break;
|
||||
case "mem":
|
||||
processList = processList.sort((a, b) => b.mem - a.mem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const list = processList
|
||||
.map((p) => ({
|
||||
name: p.name,
|
||||
cmd: [p.name, p.params].filter(Boolean).join(" "),
|
||||
cpu: p.cpu,
|
||||
cpuPercent: p.cpu.toFixed(1) + "%",
|
||||
mem: p.mem,
|
||||
memUsage: formatBytes((p.mem / 100) * memTotal),
|
||||
path: p.path,
|
||||
user: p.user,
|
||||
}))
|
||||
.slice(0, limit);
|
||||
|
||||
return c.json({ list });
|
||||
}
|
||||
);
|
||||
|
||||
export default route;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Hono } from "hono";
|
||||
import si from "systeminformation";
|
||||
import { formatBytes, secondsToTime } from "../lib/utils";
|
||||
|
||||
const route = new Hono().get("/", async (c) => {
|
||||
const date = new Date().toISOString();
|
||||
const uptime = secondsToTime(si.time().uptime || 0);
|
||||
const system = await si.system();
|
||||
|
||||
const cpuSpeed = await si.cpuCurrentSpeed();
|
||||
const cpuTemp = await si.cpuTemperature();
|
||||
const cpuLoad = await si.currentLoad();
|
||||
const mem = await si.mem();
|
||||
|
||||
const perf = {
|
||||
cpu: {
|
||||
load: cpuLoad.currentLoad,
|
||||
speed: cpuSpeed.avg,
|
||||
temp: cpuTemp.main,
|
||||
},
|
||||
mem: {
|
||||
total: formatBytes(mem.total),
|
||||
percent: (mem.active / mem.total) * 100,
|
||||
used: formatBytes(mem.active),
|
||||
free: formatBytes(mem.total - mem.active),
|
||||
},
|
||||
};
|
||||
|
||||
const fsMounts = await si.fsSize();
|
||||
const storage = fsMounts
|
||||
.filter(
|
||||
(i) =>
|
||||
i.size > 32 * 1024 * 1024 * 1024 &&
|
||||
!i.mount.startsWith("/var/lib/docker")
|
||||
)
|
||||
.map((i) => ({
|
||||
type: i.type,
|
||||
mount: i.mount,
|
||||
used: formatBytes(i.used),
|
||||
percent: (i.used / i.size) * 100,
|
||||
total: formatBytes(i.size),
|
||||
free: formatBytes(i.available),
|
||||
}));
|
||||
|
||||
return c.json({ uptime, date, system, perf, storage });
|
||||
});
|
||||
|
||||
export default route;
|
||||
Reference in New Issue
Block a user