diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 8bd0e39..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,5 +0,0 @@ - -# This is NOT the Next.js you know - -This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. - diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 43c994c..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -@AGENTS.md diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts new file mode 100644 index 0000000..1ef9181 --- /dev/null +++ b/app/api/auth/login/route.ts @@ -0,0 +1,28 @@ +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(req: NextRequest) { + const { username, password } = await req.json(); + + const res = await fetch("http://localhost:3001/auth/login", { + method: "POST", + headers: { + Authorization: + "Basic " + Buffer.from(`${username}:${password}`).toString("base64"), + }, + }); + + if (!res.ok) { + return NextResponse.json({ error: "Invalid credentials" }, { status: 401 }); + } + + const { token } = await res.json(); + const response = NextResponse.json({ success: true }); + response.cookies.set("token", token, { + httpOnly: true, + secure: true, + sameSite: "strict", + maxAge: 60 * 60 * 8, + path: "/", + }); + return response; +} diff --git a/app/api/auth/logout/route.ts b/app/api/auth/logout/route.ts new file mode 100644 index 0000000..cbd2c71 --- /dev/null +++ b/app/api/auth/logout/route.ts @@ -0,0 +1,7 @@ +import { NextResponse } from "next/server"; + +export async function POST() { + const response = NextResponse.json({ success: true }); + response.cookies.delete("token"); + return response; +} diff --git a/app/api/services/[service]/[action]/route.ts b/app/api/services/[service]/[action]/route.ts new file mode 100644 index 0000000..b81ebf0 --- /dev/null +++ b/app/api/services/[service]/[action]/route.ts @@ -0,0 +1,29 @@ +import { NextRequest, NextResponse } from "next/server"; + +const ALLOWED_ACTIONS = ["start", "stop", "restart"]; + +export async function POST( + req: NextRequest, + { params }: { params: { service: string; action: string } }, +) { + const token = req.cookies.get("token")?.value; + if (!token) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + if (!ALLOWED_ACTIONS.includes(params.action)) { + return NextResponse.json({ error: "Invalid action" }, { status: 400 }); + } + + const res = await fetch( + `http://localhost:3001/services/${params.service}/${params.action}`, + { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); + + return NextResponse.json(await res.json(), { status: res.status }); +} diff --git a/app/api/services/[service]/logs/route.ts b/app/api/services/[service]/logs/route.ts new file mode 100644 index 0000000..3d62668 --- /dev/null +++ b/app/api/services/[service]/logs/route.ts @@ -0,0 +1,22 @@ +import { NextRequest, NextResponse } from "next/server"; + +export async function GET( + req: NextRequest, + { params }: { params: { service: string } }, +) { + const token = req.cookies.get("token")?.value; + if (!token) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const res = await fetch( + `http://localhost:3001/services/${params.service}/logs`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); + + return NextResponse.json(await res.json(), { status: res.status }); +} diff --git a/app/api/system/reboot/route.ts b/app/api/system/reboot/route.ts new file mode 100644 index 0000000..3efc377 --- /dev/null +++ b/app/api/system/reboot/route.ts @@ -0,0 +1,17 @@ +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(req: NextRequest) { + const token = req.cookies.get("token")?.value; + if (!token) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const res = await fetch("http://localhost:3001/system/reboot", { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + return NextResponse.json(await res.json(), { status: res.status }); +} diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx index 7afb238..a0c0e82 100644 --- a/app/components/Hero.tsx +++ b/app/components/Hero.tsx @@ -1,3 +1,5 @@ +"use client"; + interface HeroProps { lastUpdated: string | null; } @@ -8,12 +10,32 @@ export default function Hero({ lastUpdated }: HeroProps) {
dell-xps-nixos-serv
-
{lastUpdated
? `Last updated ${new Date(lastUpdated).toLocaleTimeString()}`
diff --git a/app/components/StatsGrid.tsx b/app/components/StatsGrid.tsx
index 35fdf9b..4e373b0 100644
--- a/app/components/StatsGrid.tsx
+++ b/app/components/StatsGrid.tsx
@@ -12,7 +12,7 @@ export default function StatsGrid({ stats }: StatsGridProps) {
label="CPU"
value={stats ? `${stats.cpu.percent.toFixed(1)}%` : "—"}
sub={stats?.cpu.model.replace(/\(R\)/g, "").replace(/\(TM\)/g, "").trim()}
- percent={stats?.cpu.percent.toFixed(1)}
+ percent={Number(stats?.cpu.percent.toFixed(1))}
delay={0}
/>