mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2026-03-24 03:22:35 +01:00
219 lines
7.1 KiB
Vue
219 lines
7.1 KiB
Vue
<script setup lang="ts">
|
|
import Icon from '@/components/Icon.vue'
|
|
import CatalystLogo from '@/components/common/CatalystLogo.vue'
|
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger
|
|
} from '@/components/ui/dropdown-menu'
|
|
import {
|
|
Sidebar,
|
|
SidebarContent,
|
|
SidebarFooter,
|
|
SidebarGroup,
|
|
SidebarGroupLabel,
|
|
SidebarHeader,
|
|
SidebarInset,
|
|
SidebarMenu,
|
|
SidebarMenuButton,
|
|
SidebarMenuItem,
|
|
SidebarProvider,
|
|
SidebarRail
|
|
} from '@/components/ui/sidebar'
|
|
|
|
import { ChevronsUpDown, LogOut, Settings, Tag, User, Users, Zap } from 'lucide-vue-next'
|
|
|
|
import { useQuery } from '@tanstack/vue-query'
|
|
import { computed } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
|
|
import { useAPI } from '@/api.ts'
|
|
import type { Sidebar as SidebarModel } from '@/client/models'
|
|
import { useAuthStore } from '@/store/auth'
|
|
|
|
const authStore = useAuthStore()
|
|
|
|
const links = [
|
|
{
|
|
name: 'Reactions',
|
|
url: '/reactions',
|
|
icon: Zap,
|
|
permission: 'reaction:write'
|
|
},
|
|
{
|
|
name: 'Users',
|
|
url: '/users',
|
|
icon: User,
|
|
permission: 'group:write'
|
|
},
|
|
{
|
|
name: 'Groups',
|
|
url: '/groups',
|
|
icon: Users,
|
|
permission: 'group:write'
|
|
},
|
|
{
|
|
name: 'Types',
|
|
url: '/types',
|
|
icon: Tag,
|
|
permission: 'type:write'
|
|
},
|
|
{
|
|
name: 'Settings',
|
|
url: '/settings',
|
|
icon: Settings,
|
|
permission: 'settings:write'
|
|
}
|
|
]
|
|
|
|
const userLinks = computed(() => {
|
|
return links.filter((link) => authStore.hasPermission(link.permission))
|
|
})
|
|
|
|
const api = useAPI()
|
|
const router = useRouter()
|
|
|
|
const { data: sidebar } = useQuery({
|
|
queryKey: ['sidebar'],
|
|
queryFn: (): Promise<Array<SidebarModel>> => api.getSidebar()
|
|
})
|
|
|
|
const logout = () => {
|
|
authStore.setToken('')
|
|
router.push({ name: 'login' })
|
|
}
|
|
|
|
const initials = (user: { name?: string } | undefined) => {
|
|
if (!user || !user.name) return ''
|
|
const names = user.name.split(' ')
|
|
return names.length > 1 ? `${names[0][0]}${names[1][0]}` : names[0][0]
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<SidebarProvider class="h-full w-full" style="--sidebar-width: 12rem">
|
|
<Sidebar collapsible="icon">
|
|
<SidebarHeader>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem class="flex items-center gap-2 p-2">
|
|
<CatalystLogo :size="5" />
|
|
<div class="grid flex-1 text-left text-sm leading-tight">
|
|
<span class="truncate font-semibold">Catalyst</span>
|
|
<span class="truncate text-xs">Incident Management</span>
|
|
</div>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarHeader>
|
|
<SidebarContent>
|
|
<SidebarGroup>
|
|
<SidebarGroupLabel>Overview</SidebarGroupLabel>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton :tooltip="'Dashboard'" as-child>
|
|
<RouterLink to="/dashboard">
|
|
<Icon name="LayoutDashboard" class="size-4" />
|
|
<span>Dashboard</span>
|
|
</RouterLink>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarGroup>
|
|
<SidebarGroup>
|
|
<SidebarGroupLabel>Tickets</SidebarGroupLabel>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem v-for="(typ, index) of sidebar" :key="index">
|
|
<SidebarMenuButton :tooltip="typ.plural" as-child>
|
|
<RouterLink :to="`/tickets/${typ.id}`">
|
|
<Icon :name="typ.icon" class="size-4" />
|
|
<span>{{ typ.plural }}</span>
|
|
<span class="ml-auto">{{ typ.count }}</span>
|
|
</RouterLink>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarGroup>
|
|
<div class="flex-1" />
|
|
<SidebarGroup v-if="userLinks.length > 0" class="group-data-[collapsible=icon]:hidden">
|
|
<SidebarGroupLabel>Administration</SidebarGroupLabel>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem v-for="item in userLinks" :key="item.name">
|
|
<SidebarMenuButton as-child>
|
|
<RouterLink :to="item.url">
|
|
<component :is="item.icon" />
|
|
<span>{{ item.name }}</span>
|
|
</RouterLink>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarGroup>
|
|
</SidebarContent>
|
|
<SidebarFooter>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger as-child>
|
|
<SidebarMenuButton
|
|
size="lg"
|
|
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
>
|
|
<Avatar class="h-8 w-8 rounded-lg">
|
|
<AvatarImage
|
|
:src="authStore.user?.avatar ? authStore.user.avatar : ''"
|
|
:alt="authStore.user?.name"
|
|
/>
|
|
<AvatarFallback class="rounded-lg"
|
|
>{{ initials(authStore.user) }}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div class="grid flex-1 text-left text-sm leading-tight">
|
|
<span class="truncate font-semibold">{{ authStore.user?.name }}</span>
|
|
<span class="truncate text-xs">{{ authStore.user?.email }}</span>
|
|
</div>
|
|
<ChevronsUpDown class="ml-auto size-4" />
|
|
</SidebarMenuButton>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
class="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
|
side="bottom"
|
|
align="end"
|
|
:side-offset="4"
|
|
>
|
|
<DropdownMenuLabel class="p-0 font-normal">
|
|
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
|
<Avatar class="h-8 w-8 rounded-lg">
|
|
<AvatarImage
|
|
:src="authStore.user?.avatar ? authStore.user.avatar : ''"
|
|
:alt="authStore.user?.name"
|
|
/>
|
|
<AvatarFallback class="rounded-lg"
|
|
>{{ initials(authStore.user) }}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div class="grid flex-1 text-left text-sm leading-tight">
|
|
<span class="truncate font-semibold">{{ authStore.user?.name }}</span>
|
|
<span class="truncate text-xs">{{ authStore.user?.email }}</span>
|
|
</div>
|
|
</div>
|
|
</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem @click="logout">
|
|
<LogOut />
|
|
Log out
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarFooter>
|
|
<SidebarRail />
|
|
</Sidebar>
|
|
<SidebarInset class="flex h-full w-full">
|
|
<slot />
|
|
</SidebarInset>
|
|
</SidebarProvider>
|
|
</template>
|