Skip to main content
Glama

Industrial MCP Server

by intecrel
page.tsx13.9 kB
'use client' import { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' interface ApiKeyConfig { userId: string name: string key: string rateLimitPerHour?: number permissions: string[] created?: string lastUsed?: string } interface UsageStats { userId: string totalRequests: number lastHour: number topTools: Array<{tool: string, count: number}> } export default function ApiKeysManagement() { const [apiKeys, setApiKeys] = useState<ApiKeyConfig[]>([]) const [usageStats, setUsageStats] = useState<Record<string, UsageStats>>({}) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [showAddForm, setShowAddForm] = useState(false) const [newKey, setNewKey] = useState({ userId: '', name: '', rateLimitPerHour: 100 }) const router = useRouter() useEffect(() => { loadApiKeys() loadUsageStats() }, []) const loadApiKeys = async () => { try { const response = await fetch('/api/admin/api-keys', { headers: { 'x-api-key': localStorage.getItem('adminApiKey') || '' } }) if (!response.ok) { throw new Error('Failed to load API keys') } const data = await response.json() setApiKeys(data.keys || []) } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load data') } } const loadUsageStats = async () => { try { const response = await fetch('/api/admin/usage-stats', { headers: { 'x-api-key': localStorage.getItem('adminApiKey') || '' } }) if (response.ok) { const data = await response.json() setUsageStats(data.stats || {}) } } catch (err) { console.warn('Failed to load usage stats:', err) } finally { setLoading(false) } } const generateApiKey = () => { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' let result = 'imcp-' for (let i = 0; i < 32; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)) } return result } const handleAddKey = async (e: React.FormEvent) => { e.preventDefault() if (!newKey.userId || !newKey.name) { setError('User ID and name are required') return } const generatedKey = generateApiKey() try { const response = await fetch('/api/admin/api-keys', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': localStorage.getItem('adminApiKey') || '' }, body: JSON.stringify({ userId: newKey.userId, name: newKey.name, key: generatedKey, rateLimitPerHour: newKey.rateLimitPerHour }) }) if (!response.ok) { throw new Error('Failed to create API key') } await loadApiKeys() setShowAddForm(false) setNewKey({ userId: '', name: '', rateLimitPerHour: 100 }) setError('') } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create key') } } const handleRevokeKey = async (userId: string) => { if (!confirm(`Are you sure you want to revoke the API key for ${userId}?`)) { return } try { const response = await fetch(`/api/admin/api-keys/${userId}`, { method: 'DELETE', headers: { 'x-api-key': localStorage.getItem('adminApiKey') || '' } }) if (!response.ok) { throw new Error('Failed to revoke API key') } await loadApiKeys() } catch (err) { setError(err instanceof Error ? err.message : 'Failed to revoke key') } } if (loading) { return ( <div className="min-h-screen bg-gray-50 flex items-center justify-center"> <div className="text-center"> <div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600"></div> <p className="mt-4 text-gray-600">Loading API Keys...</p> </div> </div> ) } return ( <div className="min-h-screen bg-gray-50 py-8"> <div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="bg-white shadow-lg rounded-lg"> <div className="px-6 py-4 border-b border-gray-200"> <div className="flex justify-between items-center"> <div> <h1 className="text-2xl font-bold text-gray-900">API Key Management</h1> <p className="text-gray-600">Manage MCP server API keys and access controls</p> </div> <button onClick={() => setShowAddForm(true)} className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors" > + Add New Key </button> </div> </div> {error && ( <div className="mx-6 mt-4 p-4 bg-red-50 border border-red-200 rounded-lg"> <p className="text-red-700">{error}</p> </div> )} {showAddForm && ( <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <div className="bg-white rounded-lg p-6 w-full max-w-md"> <h2 className="text-xl font-bold mb-4">Add New API Key</h2> <form onSubmit={handleAddKey} className="space-y-4"> <div> <label className="block text-sm font-medium text-gray-700 mb-2"> User ID </label> <input type="text" value={newKey.userId} onChange={(e) => setNewKey({...newKey, userId: e.target.value})} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="e.g., user123" required /> </div> <div> <label className="block text-sm font-medium text-gray-700 mb-2"> Display Name </label> <input type="text" value={newKey.name} onChange={(e) => setNewKey({...newKey, name: e.target.value})} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="e.g., Production Client" required /> </div> <div> <label className="block text-sm font-medium text-gray-700 mb-2"> Rate Limit (requests/hour) </label> <input type="number" value={newKey.rateLimitPerHour} onChange={(e) => setNewKey({...newKey, rateLimitPerHour: parseInt(e.target.value)})} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" min="1" max="10000" /> </div> <div className="flex space-x-3 pt-4"> <button type="submit" className="flex-1 bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition-colors" > Generate Key </button> <button type="button" onClick={() => setShowAddForm(false)} className="flex-1 bg-gray-300 text-gray-700 py-2 rounded-lg hover:bg-gray-400 transition-colors" > Cancel </button> </div> </form> </div> </div> )} <div className="p-6"> {apiKeys.length === 0 ? ( <div className="text-center py-8"> <div className="text-gray-400 text-6xl mb-4">🔑</div> <h3 className="text-lg font-medium text-gray-900 mb-2">No API Keys</h3> <p className="text-gray-500">Create your first API key to get started</p> </div> ) : ( <div className="overflow-x-auto"> <table className="w-full"> <thead> <tr className="border-b border-gray-200"> <th className="text-left py-3 px-4 font-medium text-gray-900">User</th> <th className="text-left py-3 px-4 font-medium text-gray-900">API Key</th> <th className="text-left py-3 px-4 font-medium text-gray-900">Rate Limit</th> <th className="text-left py-3 px-4 font-medium text-gray-900">Usage (Last Hour)</th> <th className="text-left py-3 px-4 font-medium text-gray-900">Total Requests</th> <th className="text-left py-3 px-4 font-medium text-gray-900">Actions</th> </tr> </thead> <tbody> {apiKeys.map((key) => { const stats = usageStats[key.userId] || { totalRequests: 0, lastHour: 0, topTools: [] } return ( <tr key={key.userId} className="border-b border-gray-100 hover:bg-gray-50"> <td className="py-3 px-4"> <div> <div className="font-medium text-gray-900">{key.name}</div> <div className="text-sm text-gray-500">{key.userId}</div> </div> </td> <td className="py-3 px-4"> <div className="font-mono text-sm bg-gray-100 px-2 py-1 rounded"> {key.key.substring(0, 12)}...{key.key.substring(key.key.length - 4)} </div> </td> <td className="py-3 px-4"> <span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded"> {key.rateLimitPerHour || 'Unlimited'}/hour </span> </td> <td className="py-3 px-4"> <div className="flex items-center"> <div className="text-sm font-medium text-gray-900">{stats.lastHour}</div> {key.rateLimitPerHour && ( <div className="ml-2 w-16 bg-gray-200 rounded-full h-2"> <div className="bg-blue-600 h-2 rounded-full" style={{width: `${Math.min(100, (stats.lastHour / key.rateLimitPerHour) * 100)}%`}} ></div> </div> )} </div> </td> <td className="py-3 px-4"> <div className="text-sm font-medium text-gray-900">{stats.totalRequests}</div> {stats.topTools.length > 0 && ( <div className="text-xs text-gray-500"> Top: {stats.topTools[0]?.tool} </div> )} </td> <td className="py-3 px-4"> <button onClick={() => handleRevokeKey(key.userId)} className="text-red-600 hover:text-red-800 text-sm font-medium" > Revoke </button> </td> </tr> ) })} </tbody> </table> </div> )} </div> </div> <div className="mt-6 bg-white shadow-lg rounded-lg p-6"> <h2 className="text-lg font-bold text-gray-900 mb-4">Usage Overview</h2> <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> <div className="bg-blue-50 p-4 rounded-lg"> <div className="text-2xl font-bold text-blue-600">{apiKeys.length}</div> <div className="text-sm text-blue-800">Active API Keys</div> </div> <div className="bg-green-50 p-4 rounded-lg"> <div className="text-2xl font-bold text-green-600"> {Object.values(usageStats).reduce((sum, stats) => sum + stats.totalRequests, 0)} </div> <div className="text-sm text-green-800">Total Requests</div> </div> <div className="bg-purple-50 p-4 rounded-lg"> <div className="text-2xl font-bold text-purple-600"> {Object.values(usageStats).reduce((sum, stats) => sum + stats.lastHour, 0)} </div> <div className="text-sm text-purple-800">Requests (Last Hour)</div> </div> </div> </div> <div className="mt-4 text-center"> <button onClick={() => router.push('/')} className="text-blue-600 hover:text-blue-800 text-sm" > ← Back to Dashboard </button> </div> </div> </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/intecrel/industrial-mcp'

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