added settings page

This commit is contained in:
biersoeckli
2024-11-11 11:53:51 +00:00
parent 46482e0e49
commit 20485a9ff2
13 changed files with 205 additions and 9 deletions
+3 -2
View File
@@ -22,6 +22,7 @@ export function NavBar() {
<nav className="flex items-center space-x-4 lg:space-x-6 mx-6">
<Link href="/" className={pathname === '/' ? activeCss : inactiveCss}>Projects</Link>
<Link href="/metrics" className={pathname.startsWith('/metrics') ? activeCss : inactiveCss}>Metrics</Link>
<Link href="/settings/profile" className={pathname.startsWith('/settings') ? activeCss : inactiveCss}>Settings</Link>
</nav>
<div className="ml-auto flex items-center space-x-4">
@@ -34,9 +35,9 @@ export function NavBar() {
</button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<Link href="/profile">
<Link href="/settings/profile">
<DropdownMenuItem>
Profile
View Profile
</DropdownMenuItem>
</Link>
<DropdownMenuSeparator />
@@ -0,0 +1,54 @@
'use client'
import { getAuthUserSession } from "@/server/utils/action-wrapper.utils";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
} from "@/components/ui/breadcrumb"
import PageTitle from "@/components/custom/page-title";
import userService from "@/server/services/user.service";
import clusterService from "@/server/services/node.service";
import { NodeInfoModel } from "@/model/node-info.model";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
export default async function NodeInfo({ nodeInfos }: { nodeInfos: NodeInfoModel[] }) {
return (
<Card>
<CardHeader>
<CardTitle>Cluster Info</CardTitle>
<CardDescription>View the components fo yout</CardDescription>
</CardHeader>
<CardContent className="flex gap-4">
{nodeInfos.map((nodeInfo, index) => (
<div key={index} className="space-y-4 rounded-lg border p-4">
<h3 className="font-semibold text-xl text-center">Node {index + 1}</h3>
<div className="space-y-2">
<div>
<span className="font-semibold">Name:</span> {nodeInfo.name}
</div>
<div>
<span className="font-semibold">IP:</span>
</div>
<div>
<span className="font-semibold">CPU:</span> {nodeInfo.cpuCapacity}
</div>
<div>
<span className="font-semibold">Memory:</span> {nodeInfo.ramCapacity}
</div>
<div>
<span className="font-semibold">OS:</span> {nodeInfo.os}
</div>
<div>
<span className="font-semibold">Architektur:</span> {nodeInfo.architecture}
</div>
</div>
</div>
))}
</CardContent>
</Card >
)
}
+17
View File
@@ -0,0 +1,17 @@
'use server'
import { getAuthUserSession } from "@/server/utils/action-wrapper.utils";
import PageTitle from "@/components/custom/page-title";
import clusterService from "@/server/services/node.service";
import NodeInfo from "./nodeInfo";
export default async function ClusterInfoPage() {
const session = await getAuthUserSession();
const nodeInfo = await clusterService.getNodeInfo();
return (
<div className="flex-1 space-y-4 p-8 pt-6">
<NodeInfo nodeInfos={nodeInfo} />
</div>
)
}
+25
View File
@@ -0,0 +1,25 @@
import { Suspense } from "react";
import FullLoadingSpinner from "@/components/ui/full-loading-spinnter";
import { Button } from "@/components/ui/button";
import SettingsNav from "./settings-nav";
export default function SettingsLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<main className="flex w-full gap-4">
<div className="w-46 pt-6">
<SettingsNav />
</div>
<div className="flex-1">
<Suspense fallback={<FullLoadingSpinner />}>
{children}
</Suspense>
</div>
</main>
);
}
@@ -18,13 +18,6 @@ export default async function ProjectPage() {
const data = await userService.getUserByEmail(session.email);
return (
<div className="flex-1 space-y-4 p-8 pt-6">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/profile">Profile</BreadcrumbLink>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<PageTitle
title={'Profile'}
subtitle={`View or edit your Profile information and configure your authentication.`}>
+25
View File
@@ -0,0 +1,25 @@
'use server'
import { getAuthUserSession } from "@/server/utils/action-wrapper.utils";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
} from "@/components/ui/breadcrumb"
import PageTitle from "@/components/custom/page-title";
import userService from "@/server/services/user.service";
export default async function ProjectPage() {
const session = await getAuthUserSession();
const data = await userService.getUserByEmail(session.email);
return (
<div className="flex-1 space-y-4 p-8 pt-6">
<PageTitle
title={'Server Settings'}
subtitle={`View or edit Server Settings`}>
</PageTitle>
</div>
)
}
+46
View File
@@ -0,0 +1,46 @@
'use client'
import { Suspense } from "react";
import FullLoadingSpinner from "@/components/ui/full-loading-spinnter";
import { Button } from "@/components/ui/button";
import { Info, Server, Settings, Settings2, User } from "lucide-react";
import { usePathname } from "next/navigation";
import Link from "next/link";
export default function SettingsNav() {
const pathname = usePathname();
const selectedCss = `
inline-flex gap-2 items-center w-full
whitespace-nowrap rounded-md text-sm font-medium
ring-offset-background transition-colors focus-visible:outline-none
focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
disabled:pointer-events-none disabled:opacity-50 bg-secondary text-secondary-foreground
hover:bg-secondary/80 h-10 px-4 py-2 w-full text-left
[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0`;
const notSelectedCss = `
inline-flex items-center gap-2 whitespace-nowrap rounded-md w-full
text-sm font-medium ring-offset-background transition-colors
focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none
disabled:opacity-50 hover:text-accent-foreground hover:bg-secondary/80 h-10 px-4 py-2
[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0`
return (
<div className="space-y-2 border-r pb-8 pr-8">
<h3 className="font-semibold text-xl pb-4">Settings</h3>
<div>
<Link href="/settings/profile"> <button className={pathname === '/settings/profile' ? selectedCss : notSelectedCss}><User /> Profile</button></Link>
</div>
<div>
<Link href="/settings/server"> <button className={pathname === '/settings/server' ? selectedCss : notSelectedCss}><Settings /> QuickStack Settings</button></Link>
</div>
<div>
<Link href="/settings/cluster-info"> <button className={pathname === '/settings/cluster-info' ? selectedCss : notSelectedCss}><Server /> Cluster Info</button></Link>
</div>
</div>
);
}
+13
View File
@@ -0,0 +1,13 @@
import { stringToNumber, stringToOptionalNumber } from "@/lib/zod.utils";
import { z } from "zod";
export const nodeInfoZodModel = z.object({
name: z.string(),
status: z.string(),
os: z.string(),
architecture: z.string(),
cpuCapacity: z.string(),
ramCapacity: z.string(),
})
export type NodeInfoModel = z.infer<typeof nodeInfoZodModel>;
+22
View File
@@ -0,0 +1,22 @@
import k3s from "../adapter/kubernetes-api.adapter";
import { NodeInfoModel } from "@/model/node-info.model";
class ClusterService {
async getNodeInfo(): Promise<NodeInfoModel[]> {
const nodeReturnInfo = await k3s.core.listNode();
return nodeReturnInfo.body.items.map((node) => {
return {
name: node.metadata?.name!,
status: node.status?.conditions?.filter((condition) => condition.type === 'Ready')[0].status!,
os: node.status?.nodeInfo?.osImage!,
architecture: node.status?.nodeInfo?.architecture!,
cpuCapacity: node.status?.capacity?.cpu!,
ramCapacity: node.status?.capacity?.memory!,
}
});
}
}
const clusterService = new ClusterService();
export default clusterService;