Skip to main content
Glama

HANA Cloud MCP Server

by HatriGt
MainApp.jsx16.2 kB
import { useState, useEffect } from 'react'; import axios from 'axios'; import toast from 'react-hot-toast'; import { motion, AnimatePresence } from 'framer-motion'; // Import components import VerticalSidebar from './layout/VerticalSidebar'; import DashboardView from './DashboardView'; import DatabaseListView from './DatabaseListView'; import ClaudeConfigTile from './ClaudeConfigTile' import ClaudeDesktopView from './ClaudeDesktopView' import ConnectionDetailsModal from './ConnectionDetailsModal'; // Import existing components import ConfigurationModal from './ConfigurationModal'; import EnvironmentSelector from './EnvironmentSelector'; import PathSetupModal from './PathSetupModal'; import { LoadingOverlay, GlassWindow } from './ui'; const API_BASE = 'http://localhost:3001/api'; const MainApp = () => { // State management const [activeView, setActiveView] = useState('dashboard'); const [hanaServers, setHanaServers] = useState({}); const [claudeServers, setClaudeServers] = useState([]); const [claudeConfigPath, setClaudeConfigPath] = useState(null); const [activeEnvironments, setActiveEnvironments] = useState({}); // UI State const [isConfigModalOpen, setIsConfigModalOpen] = useState(false); const [isPathSetupOpen, setIsPathSetupOpen] = useState(false) const [isConnectionDetailsOpen, setIsConnectionDetailsOpen] = useState(false) const [selectedConnection, setSelectedConnection] = useState(null); const [isEnvironmentSelectorOpen, setIsEnvironmentSelectorOpen] = useState(false); const [selectedServer, setSelectedServer] = useState(null); const [deploymentTarget, setDeploymentTarget] = useState(null); const [activeTab, setActiveTab] = useState('Production'); const [isLoading, setIsLoading] = useState(false); // Form data for multi-environment configuration const [formData, setFormData] = useState({ name: '', description: '', environments: { Production: { HANA_HOST: '', HANA_PORT: '443', HANA_USER: '', HANA_PASSWORD: '', HANA_SCHEMA: '', HANA_SSL: 'true', HANA_ENCRYPT: 'true', HANA_VALIDATE_CERT: 'true', LOG_LEVEL: 'info', ENABLE_FILE_LOGGING: 'true', ENABLE_CONSOLE_LOGGING: 'false' }, Development: { HANA_HOST: '', HANA_PORT: '443', HANA_USER: '', HANA_PASSWORD: '', HANA_SCHEMA: '', HANA_SSL: 'true', HANA_ENCRYPT: 'true', HANA_VALIDATE_CERT: 'false', LOG_LEVEL: 'debug', ENABLE_FILE_LOGGING: 'true', ENABLE_CONSOLE_LOGGING: 'true' }, Staging: { HANA_HOST: '', HANA_PORT: '443', HANA_USER: '', HANA_PASSWORD: '', HANA_SCHEMA: '', HANA_SSL: 'true', HANA_ENCRYPT: 'true', HANA_VALIDATE_CERT: 'true', LOG_LEVEL: 'info', ENABLE_FILE_LOGGING: 'true', ENABLE_CONSOLE_LOGGING: 'false' } } }); const [pathInput, setPathInput] = useState(''); // Load data on component mount useEffect(() => { loadData(); }, []); // Auto-refresh Claude data when Claude tab is opened (only if not initial load) useEffect(() => { if (activeView === 'claude' && claudeServers.length > 0) { // Only refresh if we already have data (prevents race condition on first load) // Silent refresh (no toast notification) refreshClaudeData(false); } }, [activeView]); const loadData = async () => { try { setIsLoading(true); await Promise.all([ loadHanaServers(), loadClaudeServers(), loadClaudeConfigPath(), loadActiveEnvironments() ]); } catch (error) { console.error('Error loading data:', error); toast.error('Failed to load data'); } finally { setIsLoading(false); } }; const refreshClaudeData = async (showToast = true) => { try { await Promise.all([ loadClaudeServers(), loadActiveEnvironments() ]); if (showToast) { toast.success('Configuration refreshed'); } } catch (error) { console.error('Error refreshing Claude data:', error); if (showToast) { toast.error('Failed to refresh configuration'); } } }; const loadHanaServers = async () => { try { const response = await axios.get(`${API_BASE}/hana-servers`); setHanaServers(response.data); } catch (error) { console.error('Error loading HANA servers:', error); } }; const loadClaudeServers = async () => { try { const response = await axios.get(`${API_BASE}/claude`); setClaudeServers(response.data); } catch (error) { console.error('Error loading Claude servers:', error); } }; const loadClaudeConfigPath = async () => { try { const response = await axios.get(`${API_BASE}/claude/config-path`); setClaudeConfigPath(response.data.configPath); if (!response.data.configPath) { setPathInput(response.data.defaultPath || ''); setIsPathSetupOpen(true); } } catch (error) { console.error('Error loading Claude config path:', error); } }; const loadActiveEnvironments = async () => { try { const response = await axios.get(`${API_BASE}/claude/active-environments`); setActiveEnvironments(response.data); } catch (error) { console.error('Error loading active environments:', error); } }; // Form handlers const handleFormChange = (environment, field, value) => { setFormData(prev => ({ ...prev, environments: { ...prev.environments, [environment]: { ...(prev.environments[environment] || {}), [field]: value } } })); }; const handleServerInfoChange = (field, value) => { setFormData(prev => ({ ...prev, [field]: value })); }; // Handle environment-specific updates const handleEnvironmentUpdate = (environments) => { setFormData(prev => ({ ...prev, environments: environments })); }; // Navigation handlers const handleViewChange = (view) => { setActiveView(view); // Handle special actions if (view === 'add-database') { openConfigModal(); return; } }; const handleQuickAction = (actionId) => { switch (actionId) { case 'add-database': openConfigModal(); break; case 'manage-databases': setActiveView('databases'); break; case 'claude-integration': setActiveView('claude'); break; default: console.warn(`Unknown quick action: ${actionId}`); } }; const handleBulkAction = (action, selectedItems) => { switch (action) { case 'deploy': toast.success(`Adding ${selectedItems.length} database(s) to Claude`); break; case 'test': toast.success(`Testing connections for ${selectedItems.length} database(s)`); break; case 'export': toast.success(`Exporting ${selectedItems.length} database configuration(s)`); break; default: console.warn(`Unknown bulk action: ${action}`); } }; const handleConfigPathChange = async (newPath) => { try { // Update the config path via API await axios.post(`${API_BASE}/claude/config-path`, { configPath: newPath }); // Update local state setClaudeConfigPath(newPath); toast.success('Configuration path updated successfully'); // Refresh Claude data to reflect changes await refreshClaudeData(false); } catch (error) { console.error('Error updating config path:', error); toast.error('Failed to update configuration path'); } }; // Modal handlers const openConfigModal = (server = null) => { if (server) { setFormData(server); setSelectedServer(server); // Set active tab to first available environment when editing const envKeys = Object.keys(server.environments || {}); setActiveTab(envKeys.length > 0 ? envKeys[0] : null); } else { // Reset form for new server setFormData({ name: '', description: '', environments: {} }); setSelectedServer(null); setActiveTab(null); } setIsConfigModalOpen(true); }; const closeConfigModal = () => { setIsConfigModalOpen(false); setSelectedServer(null); setActiveTab(null); }; // Server operations const saveServer = async () => { try { setIsLoading(true); if (selectedServer) { await axios.put(`${API_BASE}/hana-servers/${selectedServer.name}`, formData); toast.success('Database updated successfully'); } else { await axios.post(`${API_BASE}/hana-servers`, formData); toast.success('Database created successfully'); } closeConfigModal(); await loadHanaServers(); } catch (error) { console.error('Error saving server:', error); toast.error(error.response?.data?.error || 'Failed to save database'); } finally { setIsLoading(false); } }; const deleteServer = async (serverName) => { try { setIsLoading(true); await axios.delete(`${API_BASE}/hana-servers/${serverName}`); toast.success('Database deleted successfully'); await loadHanaServers(); } catch (error) { console.error('Error deleting server:', error); toast.error('Failed to delete database'); } finally { setIsLoading(false); } }; // Claude operations const openEnvironmentSelector = (serverName) => { setDeploymentTarget(serverName); setIsEnvironmentSelectorOpen(true); }; const deployToClaude = async (environment) => { try { setIsLoading(true); await axios.post(`${API_BASE}/apply-to-claude`, { serverName: deploymentTarget, environment: environment }); toast.success(`Added ${deploymentTarget} (${environment}) to Claude Desktop configuration`); setIsEnvironmentSelectorOpen(false); setDeploymentTarget(null); await loadClaudeServers(); await loadActiveEnvironments(); } catch (error) { console.error('Error adding to Claude:', error); toast.error(error.response?.data?.error || 'Failed to add to Claude configuration'); } finally { setIsLoading(false); } }; const removeFromClaude = async (serverName) => { try { setIsLoading(true); await axios.delete(`${API_BASE}/claude/${encodeURIComponent(serverName)}`); toast.success(`Removed ${serverName} from Claude Desktop`); await loadClaudeServers(); await loadActiveEnvironments(); } catch (error) { console.error('Error removing from Claude:', error); toast.error('Failed to remove from Claude'); } finally { setIsLoading(false); } }; // Claude path operations const saveClaudePath = async () => { try { setIsLoading(true); await axios.post(`${API_BASE}/claude/config-path`, { configPath: pathInput }); setClaudeConfigPath(pathInput); setIsPathSetupOpen(false); toast.success('Claude config path saved successfully'); await loadClaudeServers(); } catch (error) { console.error('Error saving Claude path:', error); toast.error('Failed to save Claude config path'); } finally { setIsLoading(false); } }; // Render main content based on active view const renderMainContent = () => { switch (activeView) { case 'dashboard': return ( <DashboardView hanaServers={hanaServers} claudeServers={claudeServers} activeEnvironments={activeEnvironments} onQuickAction={handleQuickAction} /> ); case 'databases': return ( <DatabaseListView hanaServers={hanaServers} claudeServers={claudeServers} activeEnvironments={activeEnvironments} onEditServer={openConfigModal} onAddToClaudeServer={openEnvironmentSelector} onDeleteServer={deleteServer} onBulkAction={handleBulkAction} onAddDatabase={() => openConfigModal()} /> ); case 'claude': return ( <ClaudeDesktopView claudeConfigPath={claudeConfigPath} claudeServers={claudeServers} activeEnvironments={activeEnvironments} onSetupPath={() => setIsPathSetupOpen(true)} onRemoveConnection={removeFromClaude} onViewConnection={(connection) => { setSelectedConnection(connection); setIsConnectionDetailsOpen(true); }} onRefresh={refreshClaudeData} onConfigPathChange={handleConfigPathChange} /> ); default: return ( <DashboardView hanaServers={hanaServers} claudeServers={claudeServers} activeEnvironments={activeEnvironments} onQuickAction={handleQuickAction} /> ); } }; return ( <GlassWindow maxWidth="full" maxHeight="full"> <div className="flex h-full bg-transparent p-3 sm:p-4 overflow-hidden"> {/* Loading Overlay */} {isLoading && ( <LoadingOverlay message="Processing your request..." /> )} {/* Floating Sidebar */} <div className="flex-shrink-0 h-full"> <VerticalSidebar activeView={activeView} onViewChange={handleViewChange} databaseCount={Object.keys(hanaServers).length} activeConnections={claudeServers.length} claudeConfigured={!!claudeConfigPath} /> </div> {/* Main Content */} <div className="flex-1 flex flex-col overflow-hidden bg-white/50 backdrop-blur-sm rounded-r-2xl sm:rounded-r-3xl ml-3"> {/* Main Content Area */} <main className="flex-1 overflow-hidden p-4 sm:p-6 pb-8"> <AnimatePresence mode="wait"> <motion.div key={activeView} initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }} transition={{ duration: 0.2 }} className="h-full" > {renderMainContent()} </motion.div> </AnimatePresence> </main> </div> </div> {/* Modals */} {isConfigModalOpen && ( <ConfigurationModal isOpen={isConfigModalOpen} onClose={closeConfigModal} server={selectedServer} formData={formData} activeTab={activeTab} setActiveTab={setActiveTab} onFormChange={handleFormChange} onServerInfoChange={handleServerInfoChange} onSave={saveServer} isLoading={isLoading} /> )} {isEnvironmentSelectorOpen && deploymentTarget && ( <EnvironmentSelector isOpen={isEnvironmentSelectorOpen} onClose={() => { setIsEnvironmentSelectorOpen(false); setDeploymentTarget(null); }} serverName={deploymentTarget} environments={hanaServers[deploymentTarget]?.environments || {}} activeEnvironment={activeEnvironments[deploymentTarget]} onDeploy={deployToClaude} isLoading={isLoading} /> )} {isPathSetupOpen && ( <PathSetupModal isOpen={isPathSetupOpen} onClose={() => setIsPathSetupOpen(false)} pathInput={pathInput} setPathInput={setPathInput} onSave={saveClaudePath} isLoading={isLoading} /> )} <ConnectionDetailsModal isOpen={isConnectionDetailsOpen} onClose={() => { setIsConnectionDetailsOpen(false); setSelectedConnection(null); }} connection={selectedConnection} /> </GlassWindow> ); }; export default MainApp;

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/HatriGt/hana-mcp-server'

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