Skip to main content
Glama
dashboard.tsx8.9 kB
import { useState, useEffect } from "react"; import { useLocation } from "wouter"; import { useQuery } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Bolt, Search, Key } from "lucide-react"; import ToolCard from "@/components/tool-card"; import ToolModal from "@/components/tool-modal"; import TokenManager from "@/components/tool-manager"; import LoadingSpinner from "@/components/ui/loading-spinner"; import { useToast } from "@/hooks/use-toast"; interface Tool { name: string; description: string; category?: string; inputSchema: { type: "object"; properties: Record<string, any>; required?: string[]; }; } export default function Dashboard() { const [, setLocation] = useLocation(); const [searchTerm, setSearchTerm] = useState(""); const [selectedCategory, setSelectedCategory] = useState(""); const [selectedTool, setSelectedTool] = useState<Tool | null>(null); const [showTokenManager, setShowTokenManager] = useState(false); const { toast } = useToast(); // Check token status const { data: tokenStatus, isLoading: tokenLoading } = useQuery({ queryKey: ["/api/token/status"], }); // Fetch tools const { data: toolsData, isLoading: toolsLoading, error: toolsError } = useQuery({ queryKey: ["/api/tools"], enabled: (tokenStatus as any)?.hasToken, }); // Redirect if no token useEffect(() => { if (!tokenLoading && !(tokenStatus as any)?.hasToken) { toast({ title: "No Token Found", description: "Please provide a GitHub token first.", variant: "destructive", }); setLocation("/"); } }, [tokenStatus, tokenLoading, setLocation, toast]); if (tokenLoading) { return ( <div className="min-h-screen flex items-center justify-center"> <LoadingSpinner className="h-8 w-8" /> </div> ); } if (!(tokenStatus as any)?.hasToken) { return null; } const tools: Tool[] = (toolsData as any)?.tools || []; // Group tools by category const groupedTools = tools.reduce((acc, tool) => { const category = tool.category || "Uncategorized"; if (!acc[category]) { acc[category] = []; } acc[category].push(tool); return acc; }, {} as Record<string, Tool[]>); // Filter tools based on search and category const filteredGroups = Object.entries(groupedTools).reduce((acc, [category, categoryTools]) => { if (selectedCategory && selectedCategory !== "all" && category !== selectedCategory) { return acc; } const filteredTools = categoryTools.filter(tool => tool.name.toLowerCase().includes(searchTerm.toLowerCase()) || tool.description.toLowerCase().includes(searchTerm.toLowerCase()) ); if (filteredTools.length > 0) { acc[category] = filteredTools; } return acc; }, {} as Record<string, Tool[]>); const categories = Object.keys(groupedTools); return ( <div className="min-h-screen bg-background"> {/* Header */} <header className="bg-card border-b border-border sticky top-0 z-40"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="flex items-center justify-between h-16"> <div className="flex items-center"> <div className="w-8 h-8 bg-gradient-to-r from-purple-600 to-blue-600 rounded-lg flex items-center justify-center"> <svg className="h-5 w-5 text-white" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"> <title>GitHub</title> <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/> </svg> </div> <h1 className="ml-3 text-xl font-semibold">GitHub MCP</h1> </div> {/* User Menu */} <div className="flex items-center space-x-4"> <Button variant="ghost" size="sm" onClick={() => setShowTokenManager(true)} className="text-muted-foreground hover:text-foreground" > <h3>Update Token</h3> <Key className="h-4 w-4" /> </Button> <div className="flex items-center text-sm text-muted-foreground"> <div className="w-2 h-2 bg-green-500 rounded-full mr-2"></div> Connected </div> </div> </div> </div> </header> {/* Main Content */} <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> {/* Search and Filters */} <div className="mb-8"> <div className="flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between"> <div> <h2 className="text-2xl font-bold">Available Tools</h2> <p className="text-muted-foreground mt-1">Select a tool to configure and execute GitHub operations</p> </div> <div className="flex flex-col sm:flex-row gap-3 w-full sm:w-auto"> <div className="relative"> <Input type="text" placeholder="Search tools..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} className="pl-10 w-full sm:w-64" /> <Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" /> </div> <Select value={selectedCategory} onValueChange={setSelectedCategory}> <SelectTrigger className="w-full sm:w-48"> <SelectValue placeholder="All Categories" /> </SelectTrigger> <SelectContent> <SelectItem value="all">All Categories</SelectItem> {categories.map((category) => ( <SelectItem key={category} value={category}> {category} </SelectItem> ))} </SelectContent> </Select> </div> </div> </div> {/* Bolt Content */} {toolsLoading ? ( <div className="flex items-center justify-center py-12"> <LoadingSpinner className="h-8 w-8" /> </div> ) : toolsError ? ( <div className="text-center py-12"> <p className="text-destructive">Failed to load tools. Please try again.</p> </div> ) : Object.keys(filteredGroups).length === 0 ? ( <div className="text-center py-12"> <p className="text-muted-foreground">No tools found matching your criteria.</p> </div> ) : ( <div className="space-y-8"> {Object.entries(filteredGroups).map(([category, categoryTools]) => ( <div key={category} className="tool-category"> <h3 className="text-lg font-semibold text-primary mb-4 flex items-center"> <Bolt className="mr-2 h-5 w-5" /> {category} <span className="ml-2 text-sm bg-muted text-muted-foreground px-2 py-1 rounded-full"> {categoryTools.length} </span> </h3> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {categoryTools.map((tool) => ( <ToolCard key={tool.name} tool={tool} onClick={() => setSelectedTool(tool)} /> ))} </div> </div> ))} </div> )} </main> {/* Modals */} {selectedTool && ( <ToolModal tool={selectedTool} onClose={() => setSelectedTool(null)} /> )} {showTokenManager && ( <TokenManager onClose={() => setShowTokenManager(false)} /> )} </div> ); }

Latest Blog Posts

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/Rohitkumar0056/GitHub-MCP'

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