Skip to main content
Glama

HANA Cloud MCP Server

by HatriGt
ConfigurationModal.jsxโ€ข33.2 kB
import { motion } from 'framer-motion' import { useState, useEffect } from 'react' import { XMarkIcon, ServerIcon, PlusIcon, PencilIcon, ExclamationTriangleIcon, TrashIcon } from '@heroicons/react/24/outline' import toast from 'react-hot-toast' import { detectDatabaseType, shouldShowMDCFields, validateForDatabaseType } from '../utils/databaseTypes' const ConfigurationModal = ({ isOpen, onClose, server, formData, activeTab, setActiveTab, onFormChange, onServerInfoChange, onSave, isLoading }) => { const [availableEnvironments, setAvailableEnvironments] = useState([ { id: 'development', name: 'Development', color: 'blue' }, { id: 'staging', name: 'Staging', color: 'amber' }, { id: 'production', name: 'Production', color: 'green' } ]) const [selectedEnvironments, setSelectedEnvironments] = useState(new Set()) const [showEnvironmentSelector, setShowEnvironmentSelector] = useState(false) const [validationErrors, setValidationErrors] = useState({}) const [showDeleteConfirm, setShowDeleteConfirm] = useState(null) useEffect(() => { // Load saved environments const savedEnvironments = localStorage.getItem('hana-environments') if (savedEnvironments) { setAvailableEnvironments(JSON.parse(savedEnvironments)) } // Set selected environments based on formData.environments if (formData && formData.environments) { const envKeys = Object.keys(formData.environments) setSelectedEnvironments(new Set(envKeys)) } else { // Clear selected environments for new server setSelectedEnvironments(new Set()) } }, [formData, isOpen]) useEffect(() => { if (!isOpen) return const onKeyDown = (e) => { if (e.key === 'Escape') { onClose() } } window.addEventListener('keydown', onKeyDown) return () => window.removeEventListener('keydown', onKeyDown) }, [isOpen, onClose]) // validation function with database type support const validateEnvironment = (envId, envData) => { const detectedType = detectDatabaseType(envData) const manualType = envData.HANA_CONNECTION_TYPE || 'auto' const dbType = manualType === 'auto' ? detectedType : manualType const validation = validateForDatabaseType(envData, dbType) return validation.errors } // Validate all environments const validateAllEnvironments = () => { const allErrors = {} if (formData.environments) { Object.keys(formData.environments).forEach(envId => { const envErrors = validateEnvironment(envId, formData.environments[envId]) if (Object.keys(envErrors).length > 0) { allErrors[envId] = envErrors } }) } setValidationErrors(allErrors) return Object.keys(allErrors).length === 0 } // Check if current environment has validation errors const getCurrentEnvironmentErrors = () => { if (!activeTab || !validationErrors[activeTab]) return {} return validationErrors[activeTab] } // Handle form change with validation const handleFormChange = (environment, field, value) => { onFormChange(environment, field, value) // Clear validation error for this field if it exists if (validationErrors[environment] && validationErrors[environment][field]) { const newErrors = { ...validationErrors } delete newErrors[environment][field] if (Object.keys(newErrors[environment]).length === 0) { delete newErrors[environment] } setValidationErrors(newErrors) } } const addEnvironment = (envId) => { const newSelected = new Set(selectedEnvironments) newSelected.add(envId) setSelectedEnvironments(newSelected) // Initialize the environment in formData if it doesn't exist if (!formData.environments || !formData.environments[envId]) { onFormChange(envId, 'ENVIRONMENT', envId.toUpperCase()) } // Make the newly added environment active setActiveTab(envId) setShowEnvironmentSelector(false) } const removeEnvironment = (envId) => { const newSelected = new Set(selectedEnvironments) newSelected.delete(envId) // Remove the environment from formData as well if (formData.environments && formData.environments[envId]) { const newFormData = { ...formData } delete newFormData.environments[envId] // Update the parent's formData onServerInfoChange('environments', newFormData.environments) } // Clear validation errors for this environment if (validationErrors[envId]) { const newErrors = { ...validationErrors } delete newErrors[envId] setValidationErrors(newErrors) } // If we removed the active tab, switch to the first available if (activeTab === envId) { const remaining = Array.from(newSelected) setActiveTab(remaining.length > 0 ? remaining[0] : null) } setSelectedEnvironments(newSelected) setShowDeleteConfirm(null) } const getAvailableEnvironmentsToAdd = () => { const available = availableEnvironments.filter(env => !selectedEnvironments.has(env.id)) return available } // Handle save with validation const handleSave = () => { if (!formData.name.trim()) { toast.error('Server name is required') return } // Validate all environments if (!validateAllEnvironments()) { toast.error('Please fill in all required fields for selected environments') return } onSave() } if (!isOpen) return null return ( <motion.div className='fixed inset-0 bg-gray-900/20 backdrop-blur-sm z-50 flex items-center justify-center p-4' initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} onClick={onClose} > <motion.div className='bg-white rounded-2xl shadow-xl max-w-5xl w-full max-h-[90vh] overflow-hidden border border-gray-200 flex flex-col' initial={{ scale: 0.9, opacity: 0, y: 20 }} animate={{ scale: 1, opacity: 1, y: 0 }} exit={{ scale: 0.9, opacity: 0, y: 20 }} transition={{ type: 'spring', stiffness: 300, damping: 25 }} onClick={(e) => e.stopPropagation()} > {/* Fixed Header */} <div className='sticky top-0 z-10 px-8 py-6 border-b border-gray-100 bg-white rounded-t-2xl'> <div className='flex items-center justify-between'> <div className='flex items-center gap-4'> <div className='p-3 bg-gray-100 rounded-xl'> {server ? <PencilIcon className='w-5 h-5 text-gray-600' /> : <PlusIcon className='w-5 h-5 text-gray-600' />} </div> <div> <h2 className='text-2xl font-bold text-gray-900 leading-tight'> {server ? 'Edit HANA Server' : 'Add HANA Server'} </h2> <p className='text-base text-gray-600 mt-2 font-medium'> {server ? 'Update database connection settings' : 'Configure a new database connection'} </p> </div> </div> <button onClick={onClose} className='p-3 rounded-xl text-gray-400 hover:text-gray-600 hover:bg-gray-50 transition-colors' > <XMarkIcon className='w-5 h-5' /> </button> </div> </div> {/* Scrollable Body */} <div className='flex-1 overflow-y-auto p-8'> {/* Server Info */} <div className='mb-8'> <h3 className='text-xl font-bold text-gray-900 mb-6 flex items-center gap-3'> <ServerIcon className='w-5 h-5 text-gray-600' /> Server Information </h3> <div className='grid grid-cols-1 md:grid-cols-2 gap-6'> <div> <label className='block text-base font-semibold text-gray-800 mb-3'> Server Name <span className='text-red-500'>*</span> </label> <input type='text' value={formData.name} onChange={(e) => onServerInfoChange('name', e.target.value)} placeholder='e.g. Production HANA' disabled={!!server} className={`w-full px-4 py-3 border border-gray-300 rounded-xl text-base focus:outline-none focus:ring-2 focus:ring-[#86a0ff] focus:border-[#86a0ff] transition-colors ${ server ? 'bg-gray-100 text-gray-600 cursor-not-allowed' : 'text-gray-400' }`} /> </div> <div> <label className='block text-base font-semibold text-gray-800 mb-3'> Description </label> <input type='text' value={formData.description} onChange={(e) => onServerInfoChange('description', e.target.value)} placeholder='Optional description' className='w-full px-4 py-3 border border-gray-300 rounded-xl text-gray-900 placeholder-gray-400 text-base focus:outline-none focus:ring-2 focus:ring-[#86a0ff] focus:border-[#86a0ff] transition-colors' /> </div> </div> </div> {/* Environment Configuration */} <div className='mb-10'> <div className='flex items-center justify-between mb-6'> <h3 className='text-xl font-bold text-gray-900'>Environment Configuration</h3> <span className='text-sm px-4 py-2 bg-gray-100 text-gray-600 rounded-lg font-semibold'>Optional</span> </div> <p className='text-base text-gray-600 mb-6 font-medium'> Configure for specific environments: </p> {/* Configured Environments */} {selectedEnvironments.size > 0 && ( <div className='space-y-3 mb-6'> {Array.from(selectedEnvironments).map((envId) => { const env = availableEnvironments.find(e => e.id === envId) const hasErrors = validationErrors[envId] && Object.keys(validationErrors[envId]).length > 0 const isDeleteConfirm = showDeleteConfirm === envId return ( <div key={envId} className={`flex items-center justify-between p-4 border rounded-xl group transition-all ${ hasErrors ? 'border-red-200 bg-red-50' : 'border-gray-200 bg-gray-50' }`} > <div className='flex items-center gap-4'> <div className={`w-4 h-4 rounded-full bg-${env?.color}-500`}></div> <div className='flex items-center gap-3'> <h4 className='text-base font-semibold text-gray-900'>{env?.name}</h4> {hasErrors && ( <div className='flex items-center gap-1 text-red-600'> <ExclamationTriangleIcon className='w-4 h-4' /> <span className='text-sm font-medium'> {Object.keys(validationErrors[envId]).length} required field(s) missing </span> </div> )} </div> </div> <div className='flex items-center gap-2'> {isDeleteConfirm ? ( <> <button type='button' onClick={() => setShowDeleteConfirm(null)} className='px-3 py-1 text-sm font-medium text-gray-600 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors' > Cancel </button> <button type='button' onClick={() => removeEnvironment(envId)} className='px-3 py-1 text-sm font-medium text-white bg-red-600 border border-red-600 rounded-lg hover:bg-red-700 transition-colors' > Delete </button> </> ) : ( <button type='button' onClick={() => setShowDeleteConfirm(envId)} className='p-2 text-gray-400 hover:text-red-500 transition-colors opacity-0 group-hover:opacity-100' title='Delete environment configuration' > <TrashIcon className='w-4 h-4' /> </button> )} </div> </div> ) })} </div> )} {/* Add Environment Button */} {getAvailableEnvironmentsToAdd().length > 0 && ( <button type='button' onClick={() => setShowEnvironmentSelector(true)} className='w-full p-6 border-2 border-dashed border-gray-300 rounded-xl hover:border-[#86a0ff] hover:bg-[#86a0ff]/5 transition-colors flex items-center justify-center gap-4 text-gray-600 hover:text-[#86a0ff] group' > <PlusIcon className='w-5 h-5 group-hover:scale-110 transition-transform' /> <span className='text-lg font-bold'>Add Environment</span> </button> )} {/* Environment Tabs - Only show if environments are selected */} {selectedEnvironments.size > 0 && ( <> <div className='mt-8 mb-6'> <div className='flex border-b border-gray-200 overflow-x-auto'> {Array.from(selectedEnvironments).map((envId) => { const env = availableEnvironments.find(e => e.id === envId) const hasErrors = validationErrors[envId] && Object.keys(validationErrors[envId]).length > 0 return ( <button key={envId} type='button' onClick={() => setActiveTab(envId)} className={`px-6 py-3 font-semibold text-sm transition-colors border-b-2 whitespace-nowrap flex items-center gap-2 ${ activeTab === envId ? 'text-blue-600 border-blue-600' : 'text-gray-500 border-transparent hover:text-gray-700' }`} > <div className='flex items-center gap-3'> <div className={`w-3 h-3 rounded-full bg-${env?.color}-500`}></div> {env?.name || envId} </div> {hasErrors && ( <div className='w-2 h-2 rounded-full bg-red-500'></div> )} </button> ) })} </div> </div> {/* Tab Content - Only show if an environment is selected */} {activeTab && selectedEnvironments.has(activeTab) && ( <motion.div key={activeTab} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.2 }} > <EnvironmentForm environment={activeTab} data={formData.environments[activeTab] || {}} onChange={handleFormChange} errors={getCurrentEnvironmentErrors()} /> </motion.div> )} </> )} {/* No environments selected - encouraging message */} {selectedEnvironments.size === 0 && ( <div className='text-center py-12 bg-gray-50 border border-gray-200 rounded-xl'> <div className='w-12 h-12 mx-auto mb-4 bg-gray-100 rounded-full flex items-center justify-center'> <svg className='w-6 h-6 text-gray-600' fill='none' stroke='currentColor' viewBox='0 0 24 24'> <path strokeLinecap='round' strokeLinejoin='round' strokeWidth={2} d='M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z' /> </svg> </div> <p className='text-lg font-bold text-gray-800'>No environments configured</p> <p className='text-base text-gray-600 mt-2'>Add environment-specific settings or skip for a basic connection</p> </div> )} </div> </div> {/* Fixed Footer */} <div className='sticky bottom-0 z-10 px-8 py-6 border-t border-gray-100 bg-gray-50 rounded-b-2xl flex justify-end gap-4'> <button onClick={onClose} disabled={isLoading} className='px-8 py-3 text-base font-semibold text-gray-700 bg-white border border-gray-300 rounded-xl hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-gray-400 disabled:opacity-50 transition-colors shadow-sm hover:shadow-md' > Cancel </button> <button onClick={handleSave} disabled={isLoading} className='px-10 py-3 text-base font-semibold text-white bg-[#86a0ff] border border-transparent rounded-xl hover:bg-[#7990e6] focus:outline-none focus:ring-2 focus:ring-[#86a0ff] disabled:opacity-50 min-w-[160px] transition-colors shadow-md hover:shadow-lg' > {isLoading ? 'Saving...' : (server ? 'Update Server' : 'Add Server')} </button> </div> </motion.div> {/* Environment Selector Modal */} {showEnvironmentSelector && ( <div className='absolute inset-0 bg-black/20 flex items-center justify-center p-4 z-10' onClick={() => setShowEnvironmentSelector(false)} > <motion.div initial={{ opacity: 0, scale: 0.98 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.15 }} className='bg-white rounded-xl shadow-xl max-w-md w-full border border-gray-200' onClick={(e) => e.stopPropagation()} > <div className='p-6 border-b border-gray-200'> <div className='flex items-center justify-between'> <h3 className='text-xl font-bold text-gray-900'>Add Environment</h3> <button onClick={() => setShowEnvironmentSelector(false)} className='p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-50 rounded-lg transition-colors' > <XMarkIcon className='w-5 h-5' /> </button> </div> <p className='text-base text-gray-600 mt-2 font-medium'>Select an environment for which you want to configure the connection</p> </div> <div className='p-6 max-h-80 overflow-y-auto'> <div className='space-y-3'> {getAvailableEnvironmentsToAdd().map((env) => ( <button key={env.id} type='button' onClick={(e) => { e.preventDefault() e.stopPropagation() addEnvironment(env.id) }} className='w-full p-5 text-left border border-gray-200 rounded-xl hover:bg-gray-50 hover:border-[#86a0ff] hover:shadow-sm transition-all duration-200 group' > <div className='flex items-center gap-4'> <div className={`w-5 h-5 rounded-full bg-${env.color}-500 shadow-sm`}></div> <div className='flex-1'> <h4 className='text-lg font-bold text-gray-900 group-hover:text-[#86a0ff] transition-colors'>{env.name}</h4> </div> <svg className='w-5 h-5 text-gray-400 group-hover:text-[#86a0ff] transition-colors' fill='none' stroke='currentColor' viewBox='0 0 24 24'> <path strokeLinecap='round' strokeLinejoin='round' strokeWidth={2} d='M12 4v16m8-8H4' /> </svg> </div> </button> ))} {getAvailableEnvironmentsToAdd().length === 0 && ( <div className='text-center py-8 text-gray-500'> <svg className='w-12 h-12 mx-auto mb-4 text-gray-400' fill='none' stroke='currentColor' viewBox='0 0 24 24'> <path strokeLinecap='round' strokeLinejoin='round' strokeWidth={2} d='M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z' /> </svg> <p className='text-lg font-semibold text-gray-600'>All available environments are already configured</p> </div> )} </div> </div> </motion.div> </div> )} </motion.div> ) } // Toggle Switch Component const ToggleSwitch = ({ label, value, onChange, description }) => { const isEnabled = value === 'true' || value === true return ( <div className="flex items-center justify-between"> <div className="flex-1"> <label className="block text-base font-semibold text-gray-700">{label}</label> {description && <p className="text-sm text-gray-500 mt-1 font-medium">{description}</p>} </div> <button type="button" onClick={() => onChange(!isEnabled ? 'true' : 'false')} className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-[#86a0ff] focus:ring-offset-2 ${ isEnabled ? 'bg-[#86a0ff]' : 'bg-gray-200' }`} > <span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${ isEnabled ? 'translate-x-6' : 'translate-x-1' }`} /> </button> </div> ) } // Environment Form Component const EnvironmentForm = ({ environment, data = {}, onChange, errors = {} }) => { // Get environment display name const getEnvironmentDisplayName = (envId) => { const envMap = { 'development': 'DEVELOPMENT', 'staging': 'STAGING', 'production': 'PRODUCTION', 'testing': 'TESTING', 'qa': 'QA' } return envMap[envId] || envId.toUpperCase() } // Ensure ENVIRONMENT parameter is automatically set const environmentValue = data.ENVIRONMENT || getEnvironmentDisplayName(environment) // Database type state - default to single_container if not specified const [manualType, setManualType] = useState(data.HANA_CONNECTION_TYPE || 'single_container') // Note: We no longer use auto-detect in the UI, users must explicitly select database type // Database type options for radio buttons const databaseTypeOptions = [ { label: 'Single-Container Database', value: 'single_container', description: 'Basic HANA database - HOST:PORT connection', required: ['HOST', 'PORT', 'USER', 'PASSWORD', 'SCHEMA'] }, { label: 'MDC System Database', value: 'mdc_system', description: 'Multi-tenant system database - HOST:PORT;INSTANCE', required: ['HOST', 'PORT', 'USER', 'PASSWORD', 'INSTANCE_NUMBER'] }, { label: 'MDC Tenant Database', value: 'mdc_tenant', description: 'Multi-tenant tenant database - HOST:PORT + DATABASE_NAME', required: ['HOST', 'PORT', 'USER', 'PASSWORD', 'INSTANCE_NUMBER', 'DATABASE_NAME'] } ] // Auto-set default values when component renders useEffect(() => { const defaults = { ENVIRONMENT: environmentValue, HANA_PORT: '443', HANA_SSL: 'true', HANA_ENCRYPT: 'true', HANA_VALIDATE_CERT: 'true', HANA_CONNECTION_TYPE: 'auto', LOG_LEVEL: 'info', ENABLE_FILE_LOGGING: 'true', ENABLE_CONSOLE_LOGGING: 'false' } // Set any missing default values Object.entries(defaults).forEach(([key, defaultValue]) => { if (!data[key]) { onChange(environment, key, defaultValue) } }) }, [environment, data, environmentValue, onChange]) // Handle connection type change const handleConnectionTypeChange = (e) => { const newType = e.target.value setManualType(newType) onChange(environment, 'HANA_CONNECTION_TYPE', newType) } // Helper function to render input field with error handling const renderInputField = (field, label, type = 'text', placeholder = '', required = false) => { const hasError = errors[field] return ( <div> <label className={`block text-base font-semibold mb-3 ${ hasError ? 'text-red-700' : 'text-gray-800' }`}> {label} {required && <span className='text-red-500'>*</span>} </label> <input type={type} value={data[field] || ''} onChange={(e) => onChange(environment, field, e.target.value)} placeholder={placeholder} className={`w-full px-4 py-3 border rounded-xl text-gray-900 placeholder-gray-400 text-base focus:outline-none focus:ring-2 transition-colors ${ hasError ? 'border-red-300 focus:ring-red-500 focus:border-red-500 bg-red-50' : 'border-gray-300 focus:ring-[#86a0ff] focus:border-[#86a0ff]' }`} /> {hasError && ( <p className='text-sm text-red-600 mt-1 font-medium flex items-center gap-1'> <ExclamationTriangleIcon className='w-3 h-3' /> {hasError} </p> )} </div> ) } return ( <div className='space-y-8'> {/* Connection Settings */} <div> <h4 className='text-lg font-bold text-gray-900 mb-6'>Connection Settings</h4> <div className='grid grid-cols-1 lg:grid-cols-3 gap-6'> {renderInputField('HANA_HOST', 'Host', 'text', 'your-hana-host.com', true)} {renderInputField('HANA_PORT', 'Port', 'number', '443')} {renderInputField('HANA_SCHEMA', 'Schema', 'text', 'your-schema', true)} </div> {/* Database Type Selection */} <div className='mt-6'> <label className='block text-base font-semibold mb-3 text-gray-800'> Database Type </label> <div className='space-y-3'> {databaseTypeOptions.map((option) => ( <label key={option.value} className={` flex items-start gap-4 p-4 rounded-xl border-2 cursor-pointer transition-all duration-200 ${manualType === option.value ? 'border-[#86a0ff] bg-blue-50 shadow-md' : 'border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm' } `} > <input type="radio" name="databaseType" value={option.value} checked={manualType === option.value} onChange={handleConnectionTypeChange} className="mt-1 w-4 h-4 text-[#86a0ff] border-gray-300 focus:ring-[#86a0ff] focus:ring-2" /> <div className="flex-1"> <div className="mb-1"> <span className="font-semibold text-gray-900">{option.label}</span> </div> <p className="text-sm text-gray-600 mb-2">{option.description}</p> <div> <p className="text-xs text-gray-500 mb-1">Required fields:</p> <div className="flex flex-wrap gap-1"> {option.required.map((field) => ( <span key={field} className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-700" > {field.replace('HANA_', '')} </span> ))} </div> </div> </div> </label> ))} </div> </div> {/* MDC-specific fields - show conditionally */} {shouldShowMDCFields(detectDatabaseType(data), manualType) && ( <div className='mt-6'> <h5 className='text-base font-semibold text-gray-800 mb-2'>MDC Configuration</h5> <p className='text-sm text-gray-600 mb-4'> {manualType === 'mdc_system' ? 'MDC System Database requires instance number for connection string format: HOST:PORT;INSTANCE' : 'MDC Tenant Database requires both instance number and database name for connection' } </p> <div className='grid grid-cols-1 md:grid-cols-2 gap-6'> {renderInputField('HANA_INSTANCE_NUMBER', 'Instance Number', 'number', '10', true)} {manualType === 'mdc_tenant' && renderInputField('HANA_DATABASE_NAME', 'Database Name', 'text', 'HQQ', true)} </div> </div> )} <div className='grid grid-cols-1 md:grid-cols-2 gap-6 mt-6'> {renderInputField('HANA_USER', 'Username', 'text', 'your-username', true)} {renderInputField('HANA_PASSWORD', 'Password', 'password', 'โ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ข', true)} </div> </div> {/* Security & SSL Configuration */} <div> <h4 className='text-lg font-bold text-gray-900 mb-6'>Security & SSL</h4> <div className='bg-gray-50 rounded-xl p-6 space-y-6'> <ToggleSwitch label="Enable SSL" description="Use SSL/TLS for secure connection" value={data.HANA_SSL || 'true'} onChange={(value) => onChange(environment, 'HANA_SSL', value)} /> <ToggleSwitch label="Encrypt Connection" description="Encrypt data transmission" value={data.HANA_ENCRYPT || 'true'} onChange={(value) => onChange(environment, 'HANA_ENCRYPT', value)} /> <ToggleSwitch label="Validate Certificate" description="Verify SSL certificate authenticity" value={data.HANA_VALIDATE_CERT || 'false'} onChange={(value) => onChange(environment, 'HANA_VALIDATE_CERT', value)} /> </div> </div> {/* Logging Configuration */} <div> <h4 className='text-lg font-bold text-gray-900 mb-6'>Logging Configuration</h4> <div className='grid grid-cols-1 md:grid-cols-3 gap-6'> <div> <label className='block text-base font-semibold text-gray-800 mb-3'>Log Level</label> <select value={data.LOG_LEVEL || 'info'} onChange={(e) => onChange(environment, 'LOG_LEVEL', e.target.value)} className='w-full px-4 py-3 border border-gray-300 rounded-xl text-gray-900 text-base focus:outline-none focus:ring-2 focus:ring-[#86a0ff] focus:border-[#86a0ff] transition-colors' > <option value='error'>Error</option> <option value='warn'>Warning</option> <option value='info'>Info</option> <option value='debug'>Debug</option> </select> </div> <div className='flex items-end'> <div className='w-full'> <ToggleSwitch label="File Logging" description="Save logs to file" value={data.ENABLE_FILE_LOGGING || 'true'} onChange={(value) => onChange(environment, 'ENABLE_FILE_LOGGING', value)} /> </div> </div> <div className='flex items-end'> <div className='w-full'> <ToggleSwitch label="Console Logging" description="Display logs in console" value={data.ENABLE_CONSOLE_LOGGING || 'false'} onChange={(value) => onChange(environment, 'ENABLE_CONSOLE_LOGGING', value)} /> </div> </div> </div> </div> </div> ) } export default ConfigurationModal

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