extended sidebar with apps

This commit is contained in:
biersoeckli
2024-12-04 10:51:45 +00:00
parent 02aa57711d
commit 111f3fbf66
4 changed files with 271 additions and 206 deletions

255
src/app/sidebar-client.tsx Normal file
View File

@@ -0,0 +1,255 @@
'use client'
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarHeader,
SidebarFooter,
SidebarMenuSub,
SidebarMenuSubItem,
SidebarMenuAction,
useSidebar
} from "@/components/ui/sidebar"
import { AppleIcon, BookOpen, Boxes, Calendar, ChartNoAxesCombined, ChevronDown, ChevronRight, ChevronUp, Dot, FolderClosed, Home, Inbox, Info, Plus, Radio, Search, Server, Settings, Settings2, User, User2 } from "lucide-react"
import Link from "next/link"
import { CreateProjectDialog } from "./projects/create-project-dialog"
import projectService from "@/server/services/project.service"
import { getAuthUserSession, getUserSession } from "@/server/utils/action-wrapper.utils"
import { SidebarLogoutButton } from "./sidebar-logout-button"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar"
import { App, Project } from "@prisma/client"
import { useIsMobile } from "@/frontend/hooks/use-mobile"
import { UserSession } from "@/shared/model/sim-session.model"
const settingsMenu = [
{
title: "Profile",
url: "/settings/profile",
icon: User,
},
{
title: "QuickStack Settings",
url: "/settings/server",
icon: Settings,
},
{
title: "Cluster",
url: "/settings/cluster",
icon: Server,
},
]
export function SidebarCient({
projects,
session
}: {
projects: (Project & { apps: App[] })[];
session: UserSession;
}) {
const {
state,
open,
setOpen,
openMobile,
setOpenMobile,
isMobile,
toggleSidebar,
} = useSidebar()
return (
<Sidebar collapsible="icon">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<Boxes className="size-4" />
</div>
<div className="grid flex-1 text-left text-sm leading-tight my-4">
<span className="truncate font-semibold">QuickStack</span>
<span className="truncate text-xs">Admin Panel</span>
</div>
<ChevronDown className="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[--radix-popper-anchor-width]">
<Link href="https://quickstack.dev" target="_blank">
<DropdownMenuItem>
<Info />
<span>QuickStack Website</span>
</DropdownMenuItem>
</Link>
<Link href="https://docs.quickstack.dev" target="_blank">
<DropdownMenuItem>
<BookOpen />
<span>QuickStack Docs</span>
</DropdownMenuItem>
</Link>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Menu</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild tooltip={{
children: 'All Projects',
hidden: open,
}}>
<Link href="/">
<FolderClosed />
<span>Projects</span>
</Link>
</SidebarMenuButton>
<SidebarMenuAction>
<CreateProjectDialog>
<Plus />
</CreateProjectDialog>
</SidebarMenuAction>
<SidebarMenu>
{projects.map((item) => (
<DropdownMenu key={item.id}>
<SidebarMenuItem>
<SidebarMenuButton asChild tooltip={{
children: `Project: ${item.name}`,
hidden: open,
}}>
<Link href={`/project?projectId=${item.id}`}>
<Dot /> <span>{item.name}</span>
</Link>
</SidebarMenuButton>
{item.apps.length ? (<>
<DropdownMenuTrigger asChild>
<SidebarMenuAction className="">
<ChevronRight />
<span className="sr-only">Toggle</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
side={isMobile ? "bottom" : "right"}
align={isMobile ? "end" : "start"}
className="min-w-56 rounded-lg"
>
{item.apps.map((app) => (
<DropdownMenuItem asChild key={app.name}>
<a href={`/project/app/${app.id}`}>{app.name}</a>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</>) : null}
</SidebarMenuItem>
</DropdownMenu>
))}
</SidebarMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild tooltip={{
children: 'Monitoring',
hidden: open,
}}>
<Link href="/monitoring">
<ChartNoAxesCombined />
<span>Monitoring</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild tooltip={{
children: 'Settings',
hidden: open,
}}>
<Link href="/settings/profile">
<Settings2 />
<span>Settings</span>
</Link>
</SidebarMenuButton>
<SidebarMenuSub>
{settingsMenu.map((item) => (
<SidebarMenuSubItem key={item.title}>
<SidebarMenuButton asChild>
<Link href={item.url}>
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
<Avatar className="h-8 w-8 rounded-lg">
<AvatarFallback className="rounded-lg">{session.email.substring(0, 1) || 'Q'}</AvatarFallback>
</Avatar>
{session.email}
<ChevronUp className="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
className="w-[--radix-popper-anchor-width]"
>
<Link href="/settings/profile">
<DropdownMenuItem>
<User />
<span>Profile</span>
</DropdownMenuItem>
</Link>
<SidebarLogoutButton />
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>
)
}

View File

@@ -1,49 +1,6 @@
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarHeader,
SidebarFooter,
SidebarMenuSub,
SidebarMenuSubItem
} from "@/components/ui/sidebar"
import { AppleIcon, BookOpen, Boxes, Calendar, ChartNoAxesCombined, ChevronDown, ChevronUp, FolderClosed, Home, Inbox, Info, Plus, Radio, Search, Server, Settings, Settings2, User, User2 } from "lucide-react"
import Link from "next/link"
import { CreateProjectDialog } from "./projects/create-project-dialog"
import projectService from "@/server/services/project.service"
import { getAuthUserSession, getUserSession } from "@/server/utils/action-wrapper.utils"
import { SidebarLogoutButton } from "./sidebar-logout-button"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar"
const settingsMenu = [
{
title: "Profile",
url: "/settings/profile",
icon: User,
},
{
title: "QuickStack Settings",
url: "/settings/server",
icon: Settings,
},
{
title: "Cluster",
url: "/settings/cluster",
icon: Server,
},
]
import { getUserSession } from "@/server/utils/action-wrapper.utils"
import { SidebarCient } from "./sidebar-client"
export async function AppSidebar() {
@@ -55,164 +12,5 @@ export async function AppSidebar() {
const projects = await projectService.getAllProjects();
return (
<Sidebar collapsible="icon">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<Boxes className="size-4" />
</div>
<div className="grid flex-1 text-left text-sm leading-tight my-4">
<span className="truncate font-semibold">QuickStack</span>
<span className="truncate text-xs">Admin Panel</span>
</div>
<ChevronDown className="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[--radix-popper-anchor-width]">
<Link href="https://quickstack.dev" target="_blank">
<DropdownMenuItem>
<Info />
<span>QuickStack Website</span>
</DropdownMenuItem>
</Link>
<Link href="https://docs.quickstack.dev" target="_blank">
<DropdownMenuItem>
<BookOpen />
<span>QuickStack Docs</span>
</DropdownMenuItem>
</Link>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Menu</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<Collapsible defaultOpen className="group/collapsible">
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton asChild>
<Link href="/">
<FolderClosed />
<span>Projects</span>
<CreateProjectDialog>
<Plus className="ml-auto" />
</CreateProjectDialog>
</Link>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{projects.map((item) => (
<SidebarMenuSubItem key={item.id}>
<SidebarMenuButton asChild>
<Link href={`/project?projectId=${item.id}`}>
<span>{item.name}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
<Collapsible defaultOpen className="group/collapsible">
<SidebarMenuItem>
<SidebarMenuButton asChild>
<Link href="/monitoring">
<ChartNoAxesCombined />
<span>Monitoring</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
</Collapsible>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
<Collapsible defaultOpen className="group/collapsible">
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton asChild>
<Link href="/settings/profile">
<Settings2 />
<span>Settings</span>
</Link>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{settingsMenu.map((item) => (
<SidebarMenuSubItem key={item.title}>
<SidebarMenuButton asChild>
<Link href={item.url}>
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
<Avatar className="h-8 w-8 rounded-lg">
<AvatarFallback className="rounded-lg">{session.email.substring(0, 1) || 'Q'}</AvatarFallback>
</Avatar>
{session.email}
<ChevronUp className="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
className="w-[--radix-popper-anchor-width]"
>
<Link href="/settings/profile">
<DropdownMenuItem>
<User />
<span>Profile</span>
</DropdownMenuItem>
</Link>
<SidebarLogoutButton />
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>
)
return <SidebarCient projects={projects} session={session} />
}

View File

@@ -63,6 +63,7 @@ class AppService {
} finally {
revalidateTag(Tags.apps(existingApp.projectId));
revalidateTag(Tags.app(existingApp.id));
revalidateTag(Tags.projects());
}
}
@@ -70,6 +71,9 @@ class AppService {
return await unstable_cache(async (projectId: string) => await dataAccess.client.app.findMany({
where: {
projectId
},
orderBy: {
name: 'asc'
}
}),
[Tags.apps(projectId)], {
@@ -130,6 +134,7 @@ class AppService {
} finally {
revalidateTag(Tags.apps(item.projectId as string));
revalidateTag(Tags.app(item.id as string));
revalidateTag(Tags.projects());
}
return savedItem;
}

View File

@@ -28,7 +28,14 @@ class ProjectService {
}
async getAllProjects() {
return await unstable_cache(async () => await dataAccess.client.project.findMany(),
return await unstable_cache(async () => await dataAccess.client.project.findMany({
include: {
apps: true
},
orderBy: {
name: 'asc'
}
}),
[Tags.projects()], {
tags: [Tags.projects()]
})();