Initial commit: Docker Dashboard with Next.js and Netbox integration

This commit is contained in:
Gemini Agent
2025-12-03 17:18:56 +00:00
commit b84e45a1fd
25 changed files with 7619 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
import { NextRequest, NextResponse } from "next/server";
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const apiUrl = process.env.NETBOX_API_URL;
const apiToken = process.env.NETBOX_API_TOKEN;
if (!apiUrl || !apiToken) {
return NextResponse.json(
{ error: "NetBox configuration missing on server" },
{ status: 500 }
);
}
try {
const body = await request.json();
// Whitelist: Only allow specific fields to be updated
const payload: any = {};
if (typeof body.comments === "string") {
payload.comments = body.comments;
}
if (body.custom_fields) {
// Initialize custom_fields object if not present, but strictly controlled
const cf: any = {};
let hasCustomFields = false;
if (typeof body.custom_fields.docker_run_command === "string") {
cf.docker_run_command = body.custom_fields.docker_run_command;
hasCustomFields = true;
}
if (typeof body.custom_fields.docker_volumes === "string") {
cf.docker_volumes = body.custom_fields.docker_volumes;
hasCustomFields = true;
}
if (hasCustomFields) {
payload.custom_fields = cf;
}
}
if (Object.keys(payload).length === 0) {
return NextResponse.json(
{ error: "No valid fields provided for update" },
{ status: 400 }
);
}
// Remove trailing slash from API URL if present and construct endpoint
const baseUrl = apiUrl.replace(/\/api\/?$/, "").replace(/\/$/, "");
const endpoint = `${baseUrl}/api/virtualization/virtual-machines/${id}/`;
const res = await fetch(endpoint, {
method: "PATCH",
headers: {
"Authorization": `Token ${apiToken}`,
"Content-Type": "application/json",
"Accept": "application/json",
},
body: JSON.stringify(payload),
});
if (!res.ok) {
const errorText = await res.text();
console.error(`NetBox API Error (${res.status}):`, errorText);
return NextResponse.json(
{ error: `NetBox rejected update: ${res.statusText}`, details: errorText },
{ status: res.status }
);
}
const updatedVM = await res.json();
return NextResponse.json(updatedVM);
} catch (error) {
console.error("Server Error updating VM:", error);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 }
);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,12 @@
@import "tailwindcss";
:root {
--background: #f8fafc; /* slate-50 */
--foreground: #0f172a; /* slate-900 */
}
body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
}

View File

@@ -0,0 +1,34 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}

18
frontend/src/app/page.tsx Normal file
View File

@@ -0,0 +1,18 @@
import { getDockerContainers } from "@/lib/netbox";
import { ContainerViewer } from "@/components/ContainerViewer";
export const dynamic = 'force-dynamic';
export default async function Home() {
const groupedContainers = await getDockerContainers();
const appTitle = process.env.APP_TITLE || "Docker Inventory";
const appLogo = process.env.APP_LOGO || "";
return (
<ContainerViewer
groupedContainers={groupedContainers}
appTitle={appTitle}
appLogo={appLogo}
/>
);
}