Skip to main content
Glama
admin-sidebar.tsx10.1 kB
"use client" import { Button } from "@repo/ui/components/ui/button" import { ScrollArea } from "@repo/ui/components/ui/scroll-area" import { Sheet, SheetContent, SheetTrigger } from "@repo/ui/components/ui/sheet" import { cn } from "@repo/ui/lib/utils" import { BarChart, ChevronDown, ChevronRight, CreditCard, LayoutDashboard, Lightbulb, LogOut, Menu, Package, Settings, Shield, Users, } from "lucide-react" import Link from "next/link" import { usePathname } from "next/navigation" import type React from "react" import { useState } from "react" import { LogoIcon } from "@/components/web/logo-icon" interface SidebarNavItemProps { href: string icon: React.ReactNode label: string active?: boolean badge?: number expanded?: boolean subItems?: { href: string label: string active?: boolean badge?: number }[] onToggleExpand?: () => void } function SidebarNavItem({ href, icon, label, active, badge, expanded, subItems, onToggleExpand }: SidebarNavItemProps) { const hasSubItems = subItems && subItems.length > 0 return ( <div className="space-y-1"> <div className="flex items-center"> <Link href={hasSubItems ? "#" : href} onClick={hasSubItems ? onToggleExpand : undefined} className={cn( "group flex items-center rounded-md px-3 py-2 text-sm font-medium hover:bg-muted w-full", active && !hasSubItems ? "bg-muted" : "transparent", )} > {icon} <span className="ml-3 flex-1">{label}</span> {badge && ( <span className="ml-auto flex h-5 w-5 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground"> {badge} </span> )} {hasSubItems && ( <div className="ml-auto"> {expanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />} </div> )} </Link> </div> {hasSubItems && expanded && ( <div className="pl-6 space-y-1"> {subItems.map((subItem, index) => ( <Link key={index} href={subItem.href} className={cn( "group flex items-center rounded-md px-3 py-2 text-sm font-medium hover:bg-muted", subItem.active ? "bg-muted" : "transparent", )} > <span className="flex-1">{subItem.label}</span> {subItem.badge && ( <span className="ml-auto flex h-5 w-5 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground"> {subItem.badge} </span> )} </Link> ))} </div> )} </div> ) } export function AdminSidebar() { const pathname = usePathname() const [expanded, setExpanded] = useState<Record<string, boolean>>({ apps: false, ads: false, payments: false, users: false, settings: false, }) const [open, setOpen] = useState(false) const toggleExpand = (key: string) => { setExpanded((prev) => ({ ...prev, [key]: !prev[key], })) } // 计算待处理项数量 const pendingItems = { apps: 2, claims: 3, suggestions: 5, ads: 2, payments: 1, } const totalPendingItems = Object.values(pendingItems).reduce((a, b) => a + b, 0) // 侧边栏导航项 const navItems = [ { href: "/admin", icon: <LayoutDashboard className="h-5 w-5" />, label: "仪表盘", active: pathname === "/admin", badge: totalPendingItems, }, { href: "/admin/apps", icon: <Package className="h-5 w-5" />, label: "应用管理", active: pathname.startsWith("/admin/apps"), expanded: expanded.apps, onToggleExpand: () => toggleExpand("apps"), badge: pendingItems.apps, subItems: [ { href: "/admin/apps", label: "所有应用", active: pathname === "/admin/apps", }, { href: "/admin/apps/pending", label: "待审核应用", active: pathname === "/admin/apps/pending", badge: pendingItems.apps, }, { href: "/admin/apps/create", label: "添加应用", active: pathname === "/admin/apps/create", }, ], }, { href: "/admin/ads", icon: <BarChart className="h-5 w-5" />, label: "广告管理", active: pathname.startsWith("/admin/ads"), expanded: expanded.ads, onToggleExpand: () => toggleExpand("ads"), badge: pendingItems.ads, subItems: [ { href: "/admin/ads", label: "所有广告", active: pathname === "/admin/ads", }, { href: "/admin/ads/pending", label: "待审核广告", active: pathname === "/admin/ads/pending", badge: pendingItems.ads, }, { href: "/admin/ads/analytics", label: "广告分析", active: pathname === "/admin/ads/analytics", }, ], }, { href: "/admin/payments", icon: <CreditCard className="h-5 w-5" />, label: "支付管理", active: pathname.startsWith("/admin/payments"), expanded: expanded.payments, onToggleExpand: () => toggleExpand("payments"), badge: pendingItems.payments, subItems: [ { href: "/admin/payments", label: "所有支付", active: pathname === "/admin/payments", }, { href: "/admin/payments/pending", label: "待处理支付", active: pathname === "/admin/payments/pending", badge: pendingItems.payments, }, { href: "/admin/payments/invoices", label: "发票管理", active: pathname === "/admin/payments/invoices", }, ], }, { href: "/admin/claims", icon: <Shield className="h-5 w-5" />, label: "所有权申请", active: pathname.startsWith("/admin/claims"), badge: pendingItems.claims, }, { href: "/admin/suggestions", icon: <Lightbulb className="h-5 w-5" />, label: "建议管理", active: pathname.startsWith("/admin/suggestions"), badge: pendingItems.suggestions, }, { href: "/admin/users", icon: <Users className="h-5 w-5" />, label: "用户管理", active: pathname.startsWith("/admin/users"), expanded: expanded.users, onToggleExpand: () => toggleExpand("users"), subItems: [ { href: "/admin/users", label: "所有用户", active: pathname === "/admin/users", }, { href: "/admin/users/roles", label: "角色管理", active: pathname === "/admin/users/roles", }, { href: "/admin/users/permissions", label: "权限管理", active: pathname === "/admin/users/permissions", }, ], }, { href: "/admin/settings", icon: <Settings className="h-5 w-5" />, label: "系统设置", active: pathname.startsWith("/admin/settings"), expanded: expanded.settings, onToggleExpand: () => toggleExpand("settings"), subItems: [ { href: "/admin/settings/general", label: "基本设置", active: pathname === "/admin/settings/general", }, { href: "/admin/settings/appearance", label: "外观设置", active: pathname === "/admin/settings/appearance", }, { href: "/admin/settings/security", label: "安全设置", active: pathname === "/admin/settings/security", }, ], }, ] const sidebarContent = ( <div className="flex h-full flex-col"> <div className="flex h-14 items-center border-b px-4"> <Link href="/admin" className="flex items-center gap-2 font-semibold"> <LogoIcon type="openmcp" /> <span>OpenMCP 管理后台</span> </Link> </div> <ScrollArea className="flex-1 py-2"> <nav className="grid gap-1 px-2"> {navItems.map((item, index) => ( <SidebarNavItem key={index} href={item.href} icon={item.icon} label={item.label} active={item.active} badge={item.badge} expanded={item.expanded} subItems={item.subItems} onToggleExpand={item.onToggleExpand} /> ))} </nav> </ScrollArea> <div className="mt-auto border-t p-4"> <div className="flex items-center gap-2 mb-4"> <div className="h-8 w-8 rounded-full bg-muted flex items-center justify-center"> <Users className="h-4 w-4" /> </div> <div> <div className="font-medium text-sm">管理员</div> <div className="text-xs text-muted-foreground">admin@openmcp.cn</div> </div> </div> <Button variant="outline" size="sm" className="w-full" asChild> <Link href="/login"> <LogOut className="mr-2 h-4 w-4" /> 退出登录 </Link> </Button> </div> </div> ) return ( <> {/* 移动端侧边栏 */} <Sheet open={open} onOpenChange={setOpen}> <SheetTrigger asChild className="lg:hidden"> <Button variant="outline" size="icon" className="absolute left-4 top-4 z-50"> <Menu className="h-5 w-5" /> <span className="sr-only">打开菜单</span> </Button> </SheetTrigger> <SheetContent side="left" className="p-0 w-[280px]"> {sidebarContent} </SheetContent> </Sheet> {/* 桌面端侧边栏 */} <div className="hidden lg:block w-[280px] border-r bg-background">{sidebarContent}</div> </> ) }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/metacode0602/open-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server