mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-01-01 17:20:14 -06:00
extended sidebar with apps
This commit is contained in:
255
src/app/sidebar-client.tsx
Normal file
255
src/app/sidebar-client.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()]
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user