#
tokens: 45546/50000 16/77 files (page 2/3)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 of 3. Use http://codebase.md/hatrigt/hana-mcp-server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .env.example
├── .github
│   └── pull_request_template.md
├── .gitignore
├── .npmignore
├── claude_template.json
├── docs
│   ├── hana_mcp_architecture.svg
│   └── hana_mcp_ui.gif
├── hana-mcp-server.js
├── hana-mcp-ui
│   ├── .gitignore
│   ├── bin
│   │   └── cli.js
│   ├── hana_mcp_ui.gif
│   ├── index.html
│   ├── logo.png
│   ├── package.json
│   ├── postcss.config.js
│   ├── README.md
│   ├── server
│   │   └── index.js
│   ├── src
│   │   ├── App.jsx
│   │   ├── components
│   │   │   ├── ClaudeConfigTile.jsx
│   │   │   ├── ClaudeDesktopView.jsx
│   │   │   ├── ClaudeServerCard.jsx
│   │   │   ├── ConfigurationModal.jsx
│   │   │   ├── ConnectionDetailsModal.jsx
│   │   │   ├── DashboardView.jsx
│   │   │   ├── DatabaseListView.jsx
│   │   │   ├── EnhancedServerCard.jsx
│   │   │   ├── EnvironmentManager.jsx
│   │   │   ├── EnvironmentSelector.jsx
│   │   │   ├── layout
│   │   │   │   ├── index.js
│   │   │   │   └── VerticalSidebar.jsx
│   │   │   ├── MainApp.jsx
│   │   │   ├── PathConfigModal.jsx
│   │   │   ├── PathSetupModal.jsx
│   │   │   ├── SearchAndFilter.jsx
│   │   │   └── ui
│   │   │       ├── DatabaseTypeBadge.jsx
│   │   │       ├── GlassCard.jsx
│   │   │       ├── GlassWindow.jsx
│   │   │       ├── GradientButton.jsx
│   │   │       ├── IconComponent.jsx
│   │   │       ├── index.js
│   │   │       ├── LoadingSpinner.jsx
│   │   │       ├── MetricCard.jsx
│   │   │       ├── StatusBadge.jsx
│   │   │       └── Tabs.jsx
│   │   ├── index.css
│   │   ├── main.jsx
│   │   └── utils
│   │       ├── cn.js
│   │       ├── databaseTypes.js
│   │       └── theme.js
│   ├── start.js
│   ├── tailwind.config.js
│   └── vite.config.js
├── LICENSE
├── manifest.yml
├── package-lock.json
├── package.json
├── README.md
├── setup.sh
├── src
│   ├── constants
│   │   ├── mcp-constants.js
│   │   └── tool-definitions.js
│   ├── database
│   │   ├── connection-manager.js
│   │   ├── hana-client.js
│   │   └── query-executor.js
│   ├── server
│   │   ├── index.js
│   │   ├── lifecycle-manager.js
│   │   └── mcp-handler.js
│   ├── tools
│   │   ├── config-tools.js
│   │   ├── index-tools.js
│   │   ├── index.js
│   │   ├── query-tools.js
│   │   ├── schema-tools.js
│   │   └── table-tools.js
│   └── utils
│       ├── config.js
│       ├── formatters.js
│       ├── logger.js
│       └── validators.js
└── tests
    ├── automated
    │   └── test-mcp-inspector.js
    ├── manual
    │   └── manual-test.js
    ├── mcpInspector
    │   ├── mcp-inspector-config.json
    │   └── mcp-inspector-config.template.json
    ├── mcpTestingGuide.md
    └── README.md
```

# Files

--------------------------------------------------------------------------------
/hana-mcp-ui/src/components/PathSetupModal.jsx:
--------------------------------------------------------------------------------

```javascript
  1 | import { motion } from 'framer-motion'
  2 | import { useEffect } from 'react'
  3 | import { XMarkIcon, Cog6ToothIcon, ExclamationCircleIcon, InformationCircleIcon } from '@heroicons/react/24/outline'
  4 | 
  5 | const PathSetupModal = ({
  6 |   isOpen,
  7 |   onClose,
  8 |   pathInput,
  9 |   setPathInput,
 10 |   onSave,
 11 |   isLoading
 12 | }) => {
 13 |   useEffect(() => {
 14 |     if (!isOpen) return
 15 |     const onKeyDown = (e) => {
 16 |       if (e.key === 'Escape') onClose()
 17 |     }
 18 |     window.addEventListener('keydown', onKeyDown)
 19 |     return () => window.removeEventListener('keydown', onKeyDown)
 20 |   }, [isOpen, onClose])
 21 | 
 22 |   return (
 23 |     <motion.div
 24 |       className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4"
 25 |       initial={{ opacity: 0 }}
 26 |       animate={{ opacity: 1 }}
 27 |       exit={{ opacity: 0 }}
 28 |       onClick={onClose}
 29 |     >
 30 |       <motion.div
 31 |         className="bg-white rounded-2xl shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden border border-gray-200 flex flex-col"
 32 |         initial={{ scale: 0.9, opacity: 0, y: 20 }}
 33 |         animate={{ scale: 1, opacity: 1, y: 0 }}
 34 |         exit={{ scale: 0.9, opacity: 0, y: 20 }}
 35 |         transition={{ type: "spring", stiffness: 300, damping: 25 }}
 36 |         onClick={(e) => e.stopPropagation()}
 37 |       >
 38 |         {/* Header */}
 39 |         <div className="px-8 py-6 border-b border-gray-100 bg-white rounded-t-2xl">
 40 |           <div className="flex items-center justify-between">
 41 |             <div className="flex items-center gap-4">
 42 |               <div className="p-3 bg-gray-100 rounded-xl">
 43 |                 <Cog6ToothIcon className="w-5 h-5 text-gray-600" />
 44 |               </div>
 45 |               <div>
 46 |                 <h2 className="text-2xl font-bold text-gray-900 leading-tight">
 47 |                   Setup Claude Desktop Configuration
 48 |                 </h2>
 49 |                 <p className="text-base text-gray-600 mt-2 font-medium">
 50 |                   First-time setup: Configure Claude Desktop config file path
 51 |                 </p>
 52 |               </div>
 53 |             </div>
 54 |             <button
 55 |               onClick={onClose}
 56 |               className="p-3 rounded-xl text-gray-400 hover:text-gray-600 hover:bg-gray-50 transition-colors"
 57 |             >
 58 |               <XMarkIcon className="w-5 h-5" />
 59 |             </button>
 60 |           </div>
 61 |         </div>
 62 | 
 63 |         {/* Body */}
 64 |         <div className="flex-1 overflow-y-auto p-8">
 65 |           {/* Info Alert */}
 66 |           <div className="bg-orange-50 border border-orange-200 rounded-xl p-4 mb-6">
 67 |             <div className="flex items-start gap-3">
 68 |               <ExclamationCircleIcon className="w-5 h-5 text-orange-500 mt-0.5 flex-shrink-0" />
 69 |               <div>
 70 |                 <h3 className="font-semibold text-orange-900 mb-1">Configuration Required</h3>
 71 |                 <p className="text-orange-800 text-sm leading-relaxed">
 72 |                   To add servers to Claude Desktop, we need to know where your Claude configuration file is located. 
 73 |                   This is typically in your user directory.
 74 |                 </p>
 75 |               </div>
 76 |             </div>
 77 |           </div>
 78 | 
 79 |           {/* Form */}
 80 |           <div className="mb-6">
 81 |             <label className="block text-sm font-medium text-gray-700 mb-3">
 82 |               Claude Desktop Config Path
 83 |             </label>
 84 |             <input
 85 |               type="text"
 86 |               value={pathInput}
 87 |               onChange={(e) => setPathInput(e.target.value)}
 88 |               placeholder="Enter path to claude_desktop_config.json"
 89 |               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 font-mono"
 90 |             />
 91 |             
 92 |             {/* Common Paths */}
 93 |             <div className="mt-4 bg-gray-50 border border-gray-200 rounded-xl p-4">
 94 |               <div className="flex items-center gap-2 mb-3">
 95 |                 <InformationCircleIcon className="w-4 h-4 text-blue-500" />
 96 |                 <h4 className="text-sm font-medium text-gray-700">Common Paths:</h4>
 97 |               </div>
 98 |               <div className="space-y-2 text-sm text-gray-600 font-mono">
 99 |                 <div>
100 |                   <span className="text-gray-500">macOS:</span> 
101 |                   <span className="ml-2">~/Library/Application Support/Claude/claude_desktop_config.json</span>
102 |                 </div>
103 |                 <div>
104 |                   <span className="text-gray-500">Windows:</span> 
105 |                   <span className="ml-2">%APPDATA%/Claude/claude_desktop_config.json</span>
106 |                 </div>
107 |                 <div>
108 |                   <span className="text-gray-500">Linux:</span> 
109 |                   <span className="ml-2">~/.config/claude/claude_desktop_config.json</span>
110 |                 </div>
111 |               </div>
112 |             </div>
113 |           </div>
114 |         </div>
115 | 
116 |         {/* Footer */}
117 |         <div className="px-8 py-6 border-t border-gray-100 bg-gray-50 rounded-b-2xl flex justify-end gap-4">
118 |           <button
119 |             onClick={onSave}
120 |             disabled={isLoading || !pathInput.trim()}
121 |             className="px-8 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-[150px] transition-colors shadow-md hover:shadow-lg"
122 |           >
123 |             {isLoading ? 'Saving...' : 'Save Configuration'}
124 |           </button>
125 |         </div>
126 |       </motion.div>
127 |     </motion.div>
128 |   )
129 | }
130 | 
131 | export default PathSetupModal
```

--------------------------------------------------------------------------------
/tests/manual/manual-test.js:
--------------------------------------------------------------------------------

```javascript
  1 | const { spawn } = require('child_process');
  2 | const readline = require('readline');
  3 | 
  4 | console.log('🔍 HANA MCP Server Manual Tester');
  5 | console.log('================================\n');
  6 | 
  7 | // Spawn the MCP server process
  8 | const server = spawn('/opt/homebrew/bin/node', ['../../hana-mcp-server.js'], {
  9 |   stdio: ['pipe', 'pipe', 'pipe'],
 10 |   env: {
 11 |     HANA_HOST: "your-hana-host.com",
 12 |     HANA_PORT: "443",
 13 |     HANA_USER: "your-username",
 14 |     HANA_PASSWORD: "your-password",
 15 |     HANA_SCHEMA: "your-schema",
 16 |     HANA_SSL: "true",
 17 |     HANA_ENCRYPT: "true",
 18 |     HANA_VALIDATE_CERT: "true"
 19 |   }
 20 | });
 21 | 
 22 | // Handle server output
 23 | server.stdout.on('data', (data) => {
 24 |   try {
 25 |     const response = JSON.parse(data.toString().trim());
 26 |     console.log('\n📤 Response:', JSON.stringify(response, null, 2));
 27 |   } catch (error) {
 28 |     console.log('🔧 Server Log:', data.toString().trim());
 29 |   }
 30 | });
 31 | 
 32 | server.stderr.on('data', (data) => {
 33 |   console.log('🔧 Server Log:', data.toString().trim());
 34 | });
 35 | 
 36 | // Send request function
 37 | function sendRequest(method, params = {}) {
 38 |   const request = {
 39 |     jsonrpc: '2.0',
 40 |     id: Date.now(),
 41 |     method,
 42 |     params
 43 |   };
 44 |   
 45 |   console.log(`\n📤 Sending: ${method}`);
 46 |   server.stdin.write(JSON.stringify(request) + '\n');
 47 | }
 48 | 
 49 | // Initialize server
 50 | async function initializeServer() {
 51 |   console.log('🚀 Initializing server...');
 52 |   sendRequest('initialize', {
 53 |     protocolVersion: '2024-11-05',
 54 |     capabilities: {},
 55 |     clientInfo: { name: 'manual-test-client', version: '1.0.0' }
 56 |   });
 57 |   await new Promise(resolve => setTimeout(resolve, 1000));
 58 | }
 59 | 
 60 | // List available tools
 61 | async function listTools() {
 62 |   console.log('\n📋 Listing tools...');
 63 |   sendRequest('tools/list', {});
 64 |   await new Promise(resolve => setTimeout(resolve, 1000));
 65 | }
 66 | 
 67 | // Interactive menu
 68 | function showMenu() {
 69 |   console.log('\n\n🎯 Available Tests:');
 70 |   console.log('1. Show HANA Config');
 71 |   console.log('2. Test Connection');
 72 |   console.log('3. List Schemas');
 73 |   console.log('4. List Tables');
 74 |   console.log('5. Describe Table');
 75 |   console.log('6. List Indexes');
 76 |   console.log('7. Execute Query');
 77 |   console.log('8. Show Environment Variables');
 78 |   console.log('9. Exit');
 79 |   console.log('\nEnter your choice (1-9):');
 80 | }
 81 | 
 82 | // Test functions
 83 | function testShowConfig() {
 84 |   sendRequest('tools/call', {
 85 |     name: "hana_show_config",
 86 |     arguments: {}
 87 |   });
 88 | }
 89 | 
 90 | function testConnection() {
 91 |   sendRequest('tools/call', {
 92 |     name: "hana_test_connection",
 93 |     arguments: {}
 94 |   });
 95 | }
 96 | 
 97 | function testListSchemas() {
 98 |   sendRequest('tools/call', {
 99 |     name: "hana_list_schemas",
100 |     arguments: {}
101 |   });
102 | }
103 | 
104 | function testListTables() {
105 |   const rl = readline.createInterface({
106 |     input: process.stdin,
107 |     output: process.stdout
108 |   });
109 |   
110 |   rl.question('Enter schema name (or press Enter for default): ', (schema) => {
111 |     const args = schema.trim() ? { schema_name: schema.trim() } : {};
112 |     sendRequest('tools/call', {
113 |       name: "hana_list_tables",
114 |       arguments: args
115 |     });
116 |     rl.close();
117 |   });
118 | }
119 | 
120 | function testDescribeTable() {
121 |   const rl = readline.createInterface({
122 |     input: process.stdin,
123 |     output: process.stdout
124 |   });
125 |   
126 |   rl.question('Enter schema name: ', (schema) => {
127 |     rl.question('Enter table name: ', (table) => {
128 |       sendRequest('tools/call', {
129 |         name: "hana_describe_table",
130 |         arguments: {
131 |           schema_name: schema.trim(),
132 |           table_name: table.trim()
133 |         }
134 |       });
135 |       rl.close();
136 |     });
137 |   });
138 | }
139 | 
140 | function testListIndexes() {
141 |   const rl = readline.createInterface({
142 |     input: process.stdin,
143 |     output: process.stdout
144 |   });
145 |   
146 |   rl.question('Enter schema name: ', (schema) => {
147 |     rl.question('Enter table name: ', (table) => {
148 |       sendRequest('tools/call', {
149 |         name: "hana_list_indexes",
150 |         arguments: {
151 |           schema_name: schema.trim(),
152 |           table_name: table.trim()
153 |         }
154 |       });
155 |       rl.close();
156 |     });
157 |   });
158 | }
159 | 
160 | function testExecuteQuery() {
161 |   const rl = readline.createInterface({
162 |     input: process.stdin,
163 |     output: process.stdout
164 |   });
165 |   
166 |   rl.question('Enter SQL query: ', (query) => {
167 |     sendRequest('tools/call', {
168 |       name: "hana_execute_query",
169 |       arguments: {
170 |         query: query.trim()
171 |       }
172 |     });
173 |     rl.close();
174 |   });
175 | }
176 | 
177 | function testShowEnvVars() {
178 |   sendRequest('tools/call', {
179 |     name: "hana_show_env_vars",
180 |     arguments: {}
181 |   });
182 | }
183 | 
184 | // Main interactive loop
185 | async function startInteractive() {
186 |   await initializeServer();
187 |   await listTools();
188 |   
189 |   const rl = readline.createInterface({
190 |     input: process.stdin,
191 |     output: process.stdout
192 |   });
193 |   
194 |   const askQuestion = () => {
195 |     showMenu();
196 |     rl.question('', (answer) => {
197 |       switch (answer.trim()) {
198 |         case '1':
199 |           testShowConfig();
200 |           break;
201 |         case '2':
202 |           testConnection();
203 |           break;
204 |         case '3':
205 |           testListSchemas();
206 |           break;
207 |         case '4':
208 |           testListTables();
209 |           break;
210 |         case '5':
211 |           testDescribeTable();
212 |           break;
213 |         case '6':
214 |           testListIndexes();
215 |           break;
216 |         case '7':
217 |           testExecuteQuery();
218 |           break;
219 |         case '8':
220 |           testShowEnvVars();
221 |           break;
222 |         case '9':
223 |           console.log('👋 Goodbye!');
224 |           rl.close();
225 |           server.kill();
226 |           return;
227 |         default:
228 |           console.log('❌ Invalid option. Please select 1-9.');
229 |       }
230 |       
231 |       setTimeout(askQuestion, 2000);
232 |     });
233 |   };
234 |   
235 |   askQuestion();
236 | }
237 | 
238 | // Handle server exit
239 | server.on('close', (code) => {
240 |   console.log(`\n🔚 Server closed with code ${code}`);
241 |   process.exit(0);
242 | });
243 | 
244 | server.on('error', (error) => {
245 |   console.error('❌ Server error:', error);
246 |   process.exit(1);
247 | });
248 | 
249 | // Start interactive testing
250 | startInteractive().catch(console.error); 
```

--------------------------------------------------------------------------------
/src/utils/config.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Configuration management utility for HANA MCP Server
  3 |  */
  4 | 
  5 | const { logger } = require('./logger');
  6 | 
  7 | class Config {
  8 |   constructor() {
  9 |     this.config = this.loadConfig();
 10 |   }
 11 | 
 12 |   loadConfig() {
 13 |     return {
 14 |       hana: {
 15 |         host: process.env.HANA_HOST,
 16 |         port: parseInt(process.env.HANA_PORT) || 443,
 17 |         user: process.env.HANA_USER,
 18 |         password: process.env.HANA_PASSWORD,
 19 |         schema: process.env.HANA_SCHEMA,
 20 |         instanceNumber: process.env.HANA_INSTANCE_NUMBER,
 21 |         databaseName: process.env.HANA_DATABASE_NAME,
 22 |         connectionType: process.env.HANA_CONNECTION_TYPE || 'auto',
 23 |         ssl: process.env.HANA_SSL !== 'false',
 24 |         encrypt: process.env.HANA_ENCRYPT !== 'false',
 25 |         validateCert: process.env.HANA_VALIDATE_CERT !== 'false'
 26 |       },
 27 |       server: {
 28 |         logLevel: process.env.LOG_LEVEL || 'INFO',
 29 |         enableFileLogging: process.env.ENABLE_FILE_LOGGING === 'true',
 30 |         enableConsoleLogging: process.env.ENABLE_CONSOLE_LOGGING !== 'false'
 31 |       }
 32 |     };
 33 |   }
 34 | 
 35 |   getHanaConfig() {
 36 |     return this.config.hana;
 37 |   }
 38 | 
 39 |   getServerConfig() {
 40 |     return this.config.server;
 41 |   }
 42 | 
 43 |   /**
 44 |    * Determine HANA database type based on configuration
 45 |    */
 46 |   getHanaDatabaseType() {
 47 |     const hana = this.config.hana;
 48 |     
 49 |     // Use explicit type if set and not 'auto'
 50 |     if (hana.connectionType && hana.connectionType !== 'auto') {
 51 |       return hana.connectionType;
 52 |     }
 53 |     
 54 |     // Auto-detect based on available parameters
 55 |     if (hana.instanceNumber && hana.databaseName) {
 56 |       return 'mdc_tenant';
 57 |     } else if (hana.instanceNumber && !hana.databaseName) {
 58 |       return 'mdc_system';
 59 |     } else {
 60 |       return 'single_container';
 61 |     }
 62 |   }
 63 | 
 64 |   /**
 65 |    * Build connection parameters based on database type
 66 |    */
 67 |   getConnectionParams() {
 68 |     const hana = this.config.hana;
 69 |     const dbType = this.getHanaDatabaseType();
 70 |     
 71 |     const baseParams = {
 72 |       uid: hana.user,
 73 |       pwd: hana.password,
 74 |       encrypt: hana.encrypt,
 75 |       sslValidateCertificate: hana.validateCert
 76 |     };
 77 | 
 78 |     // Build connection string based on database type
 79 |     switch (dbType) {
 80 |       case 'mdc_tenant':
 81 |         baseParams.serverNode = `${hana.host}:${hana.port}`;
 82 |         baseParams.databaseName = hana.databaseName;
 83 |         break;
 84 |       case 'mdc_system':
 85 |         baseParams.serverNode = `${hana.host}:${hana.port}`;
 86 |         break;
 87 |       case 'single_container':
 88 |       default:
 89 |         baseParams.serverNode = `${hana.host}:${hana.port}`;
 90 |         break;
 91 |     }
 92 |     
 93 |     return baseParams;
 94 |   }
 95 | 
 96 |   isHanaConfigured() {
 97 |     const hana = this.config.hana;
 98 |     return !!(hana.host && hana.user && hana.password);
 99 |   }
100 | 
101 |   getHanaConnectionString() {
102 |     const hana = this.config.hana;
103 |     return `${hana.host}:${hana.port}`;
104 |   }
105 | 
106 |   // Get configuration info for display (hiding sensitive data)
107 |   getDisplayConfig() {
108 |     const hana = this.config.hana;
109 |     const dbType = this.getHanaDatabaseType();
110 |     
111 |     return {
112 |       databaseType: dbType,
113 |       connectionType: hana.connectionType,
114 |       host: hana.host || 'NOT SET',
115 |       port: hana.port,
116 |       user: hana.user || 'NOT SET',
117 |       password: hana.password ? 'SET (hidden)' : 'NOT SET',
118 |       schema: hana.schema || 'NOT SET',
119 |       instanceNumber: hana.instanceNumber || 'NOT SET',
120 |       databaseName: hana.databaseName || 'NOT SET',
121 |       ssl: hana.ssl,
122 |       encrypt: hana.encrypt,
123 |       validateCert: hana.validateCert
124 |     };
125 |   }
126 | 
127 |   // Get environment variables for display
128 |   getEnvironmentVars() {
129 |     return {
130 |       HANA_HOST: process.env.HANA_HOST || 'NOT SET',
131 |       HANA_PORT: process.env.HANA_PORT || 'NOT SET',
132 |       HANA_USER: process.env.HANA_USER || 'NOT SET',
133 |       HANA_PASSWORD: process.env.HANA_PASSWORD ? 'SET (hidden)' : 'NOT SET',
134 |       HANA_SCHEMA: process.env.HANA_SCHEMA || 'NOT SET',
135 |       HANA_INSTANCE_NUMBER: process.env.HANA_INSTANCE_NUMBER || 'NOT SET',
136 |       HANA_DATABASE_NAME: process.env.HANA_DATABASE_NAME || 'NOT SET',
137 |       HANA_CONNECTION_TYPE: process.env.HANA_CONNECTION_TYPE || 'NOT SET',
138 |       HANA_SSL: process.env.HANA_SSL || 'NOT SET',
139 |       HANA_ENCRYPT: process.env.HANA_ENCRYPT || 'NOT SET',
140 |       HANA_VALIDATE_CERT: process.env.HANA_VALIDATE_CERT || 'NOT SET'
141 |     };
142 |   }
143 | 
144 |   // Validate configuration
145 |   validate() {
146 |     const hana = this.config.hana;
147 |     const errors = [];
148 |     const dbType = this.getHanaDatabaseType();
149 | 
150 |     // Common required fields
151 |     if (!hana.host) errors.push('HANA_HOST is required');
152 |     if (!hana.user) errors.push('HANA_USER is required');
153 |     if (!hana.password) errors.push('HANA_PASSWORD is required');
154 | 
155 |     // Type-specific validation
156 |     switch (dbType) {
157 |       case 'mdc_tenant':
158 |         if (!hana.instanceNumber) errors.push('HANA_INSTANCE_NUMBER is required for MDC Tenant Database');
159 |         if (!hana.databaseName) errors.push('HANA_DATABASE_NAME is required for MDC Tenant Database');
160 |         break;
161 |       case 'mdc_system':
162 |         if (!hana.instanceNumber) errors.push('HANA_INSTANCE_NUMBER is required for MDC System Database');
163 |         break;
164 |       case 'single_container':
165 |         if (!hana.schema) errors.push('HANA_SCHEMA is recommended for Single-Container Database');
166 |         break;
167 |     }
168 | 
169 |     if (errors.length > 0) {
170 |       logger.warn('Configuration validation failed:', errors);
171 |       return false;
172 |     }
173 | 
174 |     logger.info(`Configuration validation passed for ${dbType} database type`);
175 |     return true;
176 |   }
177 | 
178 |   /**
179 |    * Get default schema from environment variables
180 |    */
181 |   getDefaultSchema() {
182 |     return this.config.hana.schema;
183 |   }
184 | 
185 |   /**
186 |    * Check if default schema is configured
187 |    */
188 |   hasDefaultSchema() {
189 |     return !!this.config.hana.schema;
190 |   }
191 | }
192 | 
193 | // Create default config instance
194 | const config = new Config();
195 | 
196 | module.exports = { Config, config }; 
```

--------------------------------------------------------------------------------
/hana-mcp-ui/src/components/ui/GradientButton.jsx:
--------------------------------------------------------------------------------

```javascript
  1 | import { motion } from 'framer-motion'
  2 | import { cn } from '../../utils/cn'
  3 | import { colors, transitions } from '../../utils/theme'
  4 | import { IconComponent } from './index'
  5 | 
  6 | /**
  7 |  * Button - A versatile button component with multiple variants and styles
  8 |  * 
  9 |  * @param {Object} props - Component props
 10 |  * @param {React.ReactNode} props.children - Button content
 11 |  * @param {string} props.variant - Visual variant (primary, secondary, success, warning, danger)
 12 |  * @param {string} props.style - Button style (solid, outline, ghost, link)
 13 |  * @param {string} props.size - Button size (xs, sm, md, lg, xl)
 14 |  * @param {boolean} props.loading - Whether the button is in loading state
 15 |  * @param {React.ElementType} props.icon - Icon component to render
 16 |  * @param {string} props.iconPosition - Position of the icon (left, right)
 17 |  * @param {boolean} props.fullWidth - Whether the button should take full width
 18 |  * @param {string} props.className - Additional CSS classes
 19 |  * @returns {JSX.Element} - Rendered button component
 20 |  */
 21 | const Button = ({ 
 22 |   children, 
 23 |   variant = 'primary', 
 24 |   style = 'solid',
 25 |   size = 'md', 
 26 |   loading = false,
 27 |   icon,
 28 |   iconPosition = 'left',
 29 |   fullWidth = false,
 30 |   className,
 31 |   ...props 
 32 | }) => {
 33 |   // Style variants (solid, outline, ghost, link) - blue theme to match design
 34 |   const styleVariants = {
 35 |     solid: {
 36 |       primary: 'bg-[#86a0ff] text-white hover:bg-[#7990e6] focus:ring-[#86a0ff]',
 37 |       secondary: 'bg-gray-100 text-gray-800 hover:bg-gray-200 focus:ring-blue-500',
 38 |       success: 'bg-[#86a0ff] text-white hover:bg-[#7990e6] focus:ring-[#86a0ff]',
 39 |       warning: 'bg-yellow-500 text-white hover:bg-yellow-600 focus:ring-yellow-500',
 40 |       danger: 'bg-red-50 text-red-600 hover:bg-red-100 hover:text-red-700 focus:ring-red-500 border border-red-200',
 41 |     },
 42 |     outline: {
 43 |               primary: 'bg-transparent border border-[#86a0ff] text-[#86a0ff] hover:bg-[#86a0ff]/10 focus:ring-[#86a0ff]',
 44 |       secondary: 'bg-transparent border border-gray-600 text-gray-600 hover:bg-gray-50 focus:ring-blue-500',
 45 |               success: 'bg-transparent border border-[#86a0ff] text-[#86a0ff] hover:bg-[#86a0ff]/10 focus:ring-[#86a0ff]',
 46 |       warning: 'bg-transparent border border-yellow-500 text-yellow-600 hover:bg-yellow-50 focus:ring-yellow-500',
 47 |       danger: 'bg-transparent border border-red-300 text-red-600 hover:bg-red-50 focus:ring-red-500',
 48 |     },
 49 |     ghost: {
 50 |               primary: 'bg-transparent text-[#86a0ff] hover:bg-[#86a0ff]/10 focus:ring-[#86a0ff]',
 51 |       secondary: 'bg-transparent text-gray-600 hover:bg-gray-50 focus:ring-blue-500',
 52 |               success: 'bg-transparent text-[#86a0ff] hover:bg-[#86a0ff]/10 focus:ring-[#86a0ff]',
 53 |       warning: 'bg-transparent text-yellow-600 hover:bg-yellow-50 focus:ring-yellow-500',
 54 |       danger: 'bg-transparent text-red-600 hover:bg-red-50 focus:ring-red-500',
 55 |     },
 56 |     link: {
 57 |               primary: 'bg-transparent text-[#86a0ff] hover:underline focus:ring-[#86a0ff] p-0 shadow-none',
 58 |       secondary: 'bg-transparent text-gray-600 hover:underline focus:ring-blue-500 p-0 shadow-none',
 59 |               success: 'bg-transparent text-[#86a0ff] hover:underline focus:ring-[#86a0ff] p-0 shadow-none',
 60 |       warning: 'bg-transparent text-yellow-600 hover:underline focus:ring-yellow-500 p-0 shadow-none',
 61 |       danger: 'bg-transparent text-red-600 hover:underline focus:ring-red-500 p-0 shadow-none',
 62 |     }
 63 |   };
 64 |   
 65 |   // Size variants
 66 |   const sizes = {
 67 |     xs: 'px-2 py-1 text-xs',
 68 |     sm: 'px-3 py-1.5 text-sm',
 69 |     md: 'px-4 py-2 text-base', 
 70 |     lg: 'px-5 py-2.5 text-lg',
 71 |     xl: 'px-6 py-3 text-xl'
 72 |   };
 73 |   
 74 |   // Icon sizes based on button size
 75 |   const iconSizes = {
 76 |     xs: 'xs',
 77 |     sm: 'sm',
 78 |     md: 'md',
 79 |     lg: 'lg',
 80 |     xl: 'lg'
 81 |   };
 82 |   
 83 |   // Get the appropriate variant classes
 84 |   const variantClasses = styleVariants[style][variant];
 85 |   
 86 |   // Animation settings
 87 |   const animations = {
 88 |     solid: {
 89 |       hover: { scale: 1.02, y: -1 },
 90 |       tap: { scale: 0.98 }
 91 |     },
 92 |     outline: {
 93 |       hover: { scale: 1.02 },
 94 |       tap: { scale: 0.98 }
 95 |     },
 96 |     ghost: {
 97 |       hover: { scale: 1.02 },
 98 |       tap: { scale: 0.98 }
 99 |     },
100 |     link: {
101 |       hover: {},
102 |       tap: { scale: 0.98 }
103 |     }
104 |   };
105 |   
106 |   return (
107 |     <motion.button
108 |       className={cn(
109 |         // Base styles
110 |         'rounded-lg font-medium inline-flex items-center justify-center gap-2 transition-colors',
111 |         'focus:outline-none focus:ring-2 focus:ring-offset-1',
112 |         // Style and size variants
113 |         variantClasses,
114 |         sizes[size],
115 |         // Full width option
116 |         fullWidth && 'w-full',
117 |         // Disabled state
118 |         (loading || props.disabled) && 'opacity-60 cursor-not-allowed',
119 |         // Custom classes
120 |         className
121 |       )}
122 |       whileHover={!loading && !props.disabled ? animations[style].hover : {}}
123 |       whileTap={!loading && !props.disabled ? animations[style].tap : {}}
124 |       transition={{ type: "spring", stiffness: 400, damping: 25 }}
125 |       disabled={loading || props.disabled}
126 |       {...props}
127 |     >
128 |       {/* Loading spinner */}
129 |       {loading && (
130 |         <svg className="animate-spin h-4 w-4 text-current" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
131 |           <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
132 |           <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
133 |         </svg>
134 |       )}
135 |       
136 |       {/* Left icon */}
137 |       {icon && iconPosition === 'left' && !loading && (
138 |         <IconComponent 
139 |           icon={icon} 
140 |           size={iconSizes[size]} 
141 |           variant={style === 'solid' ? 'white' : variant}
142 |         />
143 |       )}
144 |       
145 |       {/* Button text */}
146 |       {children && <span>{children}</span>}
147 |       
148 |       {/* Right icon */}
149 |       {icon && iconPosition === 'right' && !loading && (
150 |         <IconComponent 
151 |           icon={icon} 
152 |           size={iconSizes[size]} 
153 |           variant={style === 'solid' ? 'white' : variant}
154 |         />
155 |       )}
156 |     </motion.button>
157 |   );
158 | };
159 | 
160 | // For backward compatibility, export as GradientButton
161 | const GradientButton = Button;
162 | 
163 | export default GradientButton;
```

--------------------------------------------------------------------------------
/hana-mcp-ui/tailwind.config.js:
--------------------------------------------------------------------------------

```javascript
  1 | /** @type {import('tailwindcss').Config} */
  2 | export default {
  3 |   content: [
  4 |     "./index.html",
  5 |     "./src/**/*.{js,ts,jsx,tsx}",
  6 |   ],
  7 |   darkMode: 'class',
  8 |   theme: {
  9 |     extend: {
 10 |       fontFamily: {
 11 |         sans: ['Inter', 'system-ui', 'sans-serif'],
 12 |         display: ['Inter', 'system-ui', 'sans-serif'],
 13 |         body: ['Inter', 'system-ui', 'sans-serif'],
 14 |       },
 15 |       fontSize: {
 16 |         'xs': ['0.75rem', { lineHeight: '1rem', letterSpacing: '0.025em' }],
 17 |         'sm': ['0.875rem', { lineHeight: '1.25rem', letterSpacing: '0.025em' }],
 18 |         'base': ['1rem', { lineHeight: '1.5rem', letterSpacing: '0.025em' }],
 19 |         'lg': ['1.125rem', { lineHeight: '1.75rem', letterSpacing: '0.025em' }],
 20 |         'xl': ['1.25rem', { lineHeight: '1.75rem', letterSpacing: '0.025em' }],
 21 |         '2xl': ['1.5rem', { lineHeight: '2rem', letterSpacing: '0.025em' }],
 22 |         '3xl': ['1.875rem', { lineHeight: '2.25rem', letterSpacing: '0.025em' }],
 23 |         '4xl': ['2.25rem', { lineHeight: '2.5rem', letterSpacing: '0.025em' }],
 24 |         '5xl': ['3rem', { lineHeight: '1', letterSpacing: '0.025em' }],
 25 |         '6xl': ['3.75rem', { lineHeight: '1', letterSpacing: '0.025em' }],
 26 |         '7xl': ['4.5rem', { lineHeight: '1', letterSpacing: '0.025em' }],
 27 |         '8xl': ['6rem', { lineHeight: '1', letterSpacing: '0.025em' }],
 28 |         '9xl': ['8rem', { lineHeight: '1', letterSpacing: '0.025em' }],
 29 |       },
 30 |       fontWeight: {
 31 |         thin: '100',
 32 |         extralight: '200',
 33 |         light: '300',
 34 |         normal: '400',
 35 |         medium: '500',
 36 |         semibold: '600',
 37 |         bold: '700',
 38 |         extrabold: '800',
 39 |         black: '900',
 40 |       },
 41 |       lineHeight: {
 42 |         'tight': '1.25',
 43 |         'snug': '1.375',
 44 |         'normal': '1.5',
 45 |         'relaxed': '1.625',
 46 |         'loose': '2',
 47 |       },
 48 |       letterSpacing: {
 49 |         'tighter': '-0.05em',
 50 |         'tight': '-0.025em',
 51 |         'normal': '0em',
 52 |         'wide': '0.025em',
 53 |         'wider': '0.05em',
 54 |         'widest': '0.1em',
 55 |       },
 56 |       colors: {
 57 |         // Professional Light Color System - Matching Image Theme
 58 |         primary: {
 59 |           50: '#eff6ff',
 60 |           100: '#dbeafe',
 61 |           200: '#bfdbfe',
 62 |           300: '#93c5fd',
 63 |           400: '#60a5fa',
 64 |           500: '#3b82f6',
 65 |           600: '#2563eb',
 66 |           700: '#1d4ed8',
 67 |           800: '#1e40af',
 68 |           900: '#1e3a8a',
 69 |           950: '#172554',
 70 |         },
 71 |         accent: {
 72 |           50: '#faf5ff',
 73 |           100: '#f3e8ff',
 74 |           200: '#e9d5ff',
 75 |           300: '#d8b4fe',
 76 |           400: '#c084fc',
 77 |           500: '#a855f7',
 78 |           600: '#9333ea',
 79 |           700: '#7c3aed',
 80 |           800: '#6b21a8',
 81 |           900: '#581c87',
 82 |           950: '#3b0764',
 83 |         },
 84 |         // Professional grays
 85 |         gray: {
 86 |           50: '#f9fafb',
 87 |           100: '#f3f4f6',
 88 |           200: '#e5e7eb',
 89 |           300: '#d1d5db',
 90 |           400: '#9ca3af',
 91 |           500: '#6b7280',
 92 |           600: '#4b5563',
 93 |           700: '#374151',
 94 |           800: '#1f2937',
 95 |           900: '#111827',
 96 |           950: '#030712',
 97 |         },
 98 |         // Status colors with professional styling - Matching Image
 99 |         success: {
100 |           50: '#ecfdf5',
101 |           100: '#d1fae5',
102 |           200: '#a7f3d0',
103 |           300: '#6ee7b7',
104 |           400: '#34d399',
105 |           500: '#10b981',
106 |           600: '#059669',
107 |           700: '#047857',
108 |           800: '#065f46',
109 |           900: '#064e3b',
110 |         },
111 |         warning: {
112 |           50: '#fffbeb',
113 |           100: '#fef3c7',
114 |           200: '#fde68a',
115 |           300: '#fcd34d',
116 |           400: '#fbbf24',
117 |           500: '#f59e0b',
118 |           600: '#d97706',
119 |           700: '#b45309',
120 |           800: '#92400e',
121 |           900: '#78350f',
122 |         },
123 |         danger: {
124 |           50: '#fef2f2',
125 |           100: '#fee2e2',
126 |           200: '#fecaca',
127 |           300: '#fca5a5',
128 |           400: '#f87171',
129 |           500: '#ef4444',
130 |           600: '#dc2626',
131 |           700: '#b91c1c',
132 |           800: '#991b1b',
133 |           900: '#7f1d1d',
134 |         },
135 |         info: {
136 |           50: '#f0f9ff',
137 |           100: '#e0f2fe',
138 |           200: '#bae6fd',
139 |           300: '#7dd3fc',
140 |           400: '#38bdf8',
141 |           500: '#0ea5e9',
142 |           600: '#0284c7',
143 |           700: '#0369a1',
144 |           800: '#075985',
145 |           900: '#0c4a6e',
146 |         },
147 |         // Button colors matching image theme
148 |         button: {
149 |           primary: '#86a0ff',      // New custom blue for primary actions
150 |           secondary: '#f3f4f6',    // Light gray for secondary
151 |           success: '#10b981',      // Green for success
152 |           danger: '#ef4444',       // Red for danger
153 |           warning: '#f59e0b',      // Orange for warning
154 |           light: '#f3f4f6',        // Light gray for subtle actions
155 |         }
156 |       },
157 |       backgroundColor: {
158 |         'glass': 'rgba(255, 255, 255, 0.9)',
159 |         'glass-dark': 'rgba(248, 250, 252, 0.8)',
160 |       },
161 |       backdropBlur: {
162 |         'xs': '2px',
163 |         'glass': '20px',
164 |       },
165 |       boxShadow: {
166 |         'glass': '0 4px 24px rgba(0, 0, 0, 0.06), 0 1px 6px rgba(0, 0, 0, 0.04)',
167 |         'glass-hover': '0 12px 32px rgba(0, 0, 0, 0.08), 0 4px 16px rgba(0, 0, 0, 0.06)',
168 |         'glow-blue': '0 0 24px rgba(59, 130, 246, 0.15)',
169 |         'glow-purple': '0 0 24px rgba(168, 85, 247, 0.15)',
170 |         'glow-green': '0 0 24px rgba(16, 185, 129, 0.15)',
171 |         'glow-red': '0 0 24px rgba(239, 68, 68, 0.15)',
172 |       },
173 |       animation: {
174 |         'fade-in': 'fadeIn 0.5s ease-in-out',
175 |         'slide-up': 'slideUp 0.3s ease-out',
176 |         'pulse-glow': 'pulseGlow 2s ease-in-out infinite alternate',
177 |         'float': 'float 3s ease-in-out infinite',
178 |       },
179 |       keyframes: {
180 |         fadeIn: {
181 |           '0%': { opacity: '0' },
182 |           '100%': { opacity: '1' },
183 |         },
184 |         slideUp: {
185 |           '0%': { transform: 'translateY(10px)', opacity: '0' },
186 |           '100%': { transform: 'translateY(0)', opacity: '1' },
187 |         },
188 |         pulseGlow: {
189 |           '0%': { boxShadow: '0 0 5px rgba(59, 130, 246, 0.2)' },
190 |           '100%': { boxShadow: '0 0 20px rgba(59, 130, 246, 0.4)' },
191 |         },
192 |         float: {
193 |           '0%, 100%': { transform: 'translateY(0px)' },
194 |           '50%': { transform: 'translateY(-4px)' },
195 |         },
196 |       },
197 |       borderRadius: {
198 |         'xl': '12px',
199 |         '2xl': '16px',
200 |         '3xl': '24px',
201 |       },
202 |     },
203 |   },
204 |   plugins: [
205 |     require('@tailwindcss/forms'),
206 |   ],
207 | }
```

--------------------------------------------------------------------------------
/tests/mcpTestingGuide.md:
--------------------------------------------------------------------------------

```markdown
  1 | # HANA MCP Server Testing Guide
  2 | 
  3 | This guide shows you how to test your HANA MCP server using different methods, including MCP Inspector.
  4 | 
  5 | ## 🎯 Testing Methods
  6 | 
  7 | ### 1. **MCP Inspector (Recommended)**
  8 | 
  9 | MCP Inspector is the official tool for testing MCP servers. Here's how to use it:
 10 | 
 11 | #### Installation
 12 | ```bash
 13 | # Install MCP Inspector globally
 14 | npm install -g @modelcontextprotocol/inspector
 15 | 
 16 | # Or install locally
 17 | npm install @modelcontextprotocol/inspector
 18 | ```
 19 | 
 20 | #### Usage
 21 | ```bash
 22 | # Start MCP Inspector with your server
 23 | mcp-inspector --config mcp-inspector-config.json
 24 | 
 25 | # Or run directly with command
 26 | mcp-inspector --command "/opt/homebrew/bin/node" --args "hana-mcp-server.js" --env-file .env
 27 | ```
 28 | 
 29 | ### 2. **Manual Testing Scripts**
 30 | 
 31 | We've created custom testing scripts for your convenience:
 32 | 
 33 | #### Automated Test Suite
 34 | ```bash
 35 | # Run all tests automatically
 36 | /opt/homebrew/bin/node test-mcp-inspector.js
 37 | ```
 38 | 
 39 | #### Interactive Manual Tester
 40 | ```bash
 41 | # Interactive menu for testing individual tools
 42 | /opt/homebrew/bin/node manual-test.js
 43 | ```
 44 | 
 45 | ### 3. **Command Line Testing**
 46 | 
 47 | Test individual commands manually:
 48 | 
 49 | ```bash
 50 | # Test initialization
 51 | echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | /opt/homebrew/bin/node hana-mcp-server.js
 52 | 
 53 | # Test tools listing
 54 | echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | /opt/homebrew/bin/node hana-mcp-server.js
 55 | 
 56 | # Test a specific tool
 57 | echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"hana_show_config","arguments":{}}}' | /opt/homebrew/bin/node hana-mcp-server.js
 58 | ```
 59 | 
 60 | ## 🔧 Configuration Files
 61 | 
 62 | ### MCP Inspector Config (`mcp-inspector-config.json`)
 63 | ```json
 64 | {
 65 |   "mcpServers": {
 66 |     "HANA Database": {
 67 |       "command": "/opt/homebrew/bin/node",
 68 |       "args": [
 69 |         "/Users/Common/ProjectsRepo/tools/hana-mcp-server/hana-mcp-server.js"
 70 |       ],
 71 |       "env": {
 72 |         "HANA_HOST": "your-hana-host.com",
 73 |         "HANA_PORT": "443",
 74 |         "HANA_USER": "your-username",
 75 |         "HANA_PASSWORD": "your-password",
 76 |         "HANA_SCHEMA": "your-schema",
 77 |         "HANA_SSL": "true",
 78 |         "HANA_ENCRYPT": "true",
 79 |         "HANA_VALIDATE_CERT": "true"
 80 |       }
 81 |     }
 82 |   }
 83 | }
 84 | ```
 85 | 
 86 | ### Environment File (`.env`)
 87 | ```bash
 88 | HANA_HOST=your-hana-host.com
 89 | HANA_PORT=443
 90 | HANA_USER=your-username
 91 | HANA_PASSWORD=your-password
 92 | HANA_SCHEMA=your-schema
 93 | HANA_SSL=true
 94 | HANA_ENCRYPT=true
 95 | HANA_VALIDATE_CERT=true
 96 | ```
 97 | 
 98 | ## 🧪 Test Scenarios
 99 | 
100 | ### Basic Functionality Tests
101 | 1. **Server Initialization** - Verify server starts correctly
102 | 2. **Tools Discovery** - Check all 9 tools are available
103 | 3. **Configuration Display** - Show HANA connection details
104 | 4. **Connection Test** - Verify HANA database connectivity
105 | 
106 | ### Database Operation Tests
107 | 1. **Schema Listing** - List all available schemas
108 | 2. **Table Discovery** - List tables in a specific schema
109 | 3. **Table Structure** - Describe table columns and types
110 | 4. **Index Information** - List and describe indexes
111 | 5. **Query Execution** - Run custom SQL queries
112 | 
113 | ### Error Handling Tests
114 | 1. **Missing Parameters** - Test required parameter validation
115 | 2. **Invalid Credentials** - Test connection failure handling
116 | 3. **Invalid Queries** - Test SQL error handling
117 | 4. **Missing Tables/Schemas** - Test not found scenarios
118 | 
119 | ## 📋 Expected Test Results
120 | 
121 | ### Successful Initialization
122 | ```json
123 | {
124 |   "jsonrpc": "2.0",
125 |   "id": 1,
126 |   "result": {
127 |     "protocolVersion": "2024-11-05",
128 |     "capabilities": {
129 |       "tools": {}
130 |     },
131 |     "serverInfo": {
132 |       "name": "HANA MCP Server",
133 |       "version": "1.0.0"
134 |     }
135 |   }
136 | }
137 | ```
138 | 
139 | ### Tools List Response
140 | ```json
141 | {
142 |   "jsonrpc": "2.0",
143 |   "id": 2,
144 |   "result": {
145 |     "tools": [
146 |       {
147 |         "name": "hana_show_config",
148 |         "description": "Show the HANA database configuration",
149 |         "inputSchema": {
150 |           "type": "object",
151 |           "properties": {},
152 |           "required": []
153 |         }
154 |       },
155 |       // ... 8 more tools
156 |     ]
157 |   }
158 | }
159 | ```
160 | 
161 | ### Tool Execution Response
162 | ```json
163 | {
164 |   "jsonrpc": "2.0",
165 |   "id": 3,
166 |   "result": {
167 |     "content": [
168 |       {
169 |         "type": "text",
170 |         "text": "📋 Available schemas in HANA database:\n\n- SCHEMA1\n- SCHEMA2\n..."
171 |       }
172 |     ]
173 |   }
174 | }
175 | ```
176 | 
177 | ## 🚨 Troubleshooting
178 | 
179 | ### Common Issues
180 | 
181 | 1. **"HANA client not connected"**
182 |    - Check environment variables are set correctly
183 |    - Verify HANA credentials are valid
184 |    - Ensure network connectivity to HANA host
185 | 
186 | 2. **"Tool not found"**
187 |    - Verify tool name spelling
188 |    - Check tools/list response includes the tool
189 |    - Ensure server is properly initialized
190 | 
191 | 3. **"Missing required parameters"**
192 |    - Check tool documentation for required parameters
193 |    - Verify parameter names match exactly
194 |    - Ensure parameters are in correct format
195 | 
196 | 4. **"Parse error"**
197 |    - Verify JSON-RPC format is correct
198 |    - Check for extra/missing commas in JSON
199 |    - Ensure proper escaping of special characters
200 | 
201 | ### Debug Mode
202 | Enable debug logging by setting environment variables:
203 | ```bash
204 | export LOG_LEVEL=debug
205 | export ENABLE_FILE_LOGGING=true
206 | ```
207 | 
208 | ## 📊 Performance Testing
209 | 
210 | ### Load Testing
211 | ```bash
212 | # Test multiple concurrent requests
213 | for i in {1..10}; do
214 |   echo '{"jsonrpc":"2.0","id":'$i',"method":"tools/call","params":{"name":"hana_list_schemas","arguments":{}}}' | /opt/homebrew/bin/node hana-mcp-server.js &
215 | done
216 | wait
217 | ```
218 | 
219 | ### Response Time Testing
220 | ```bash
221 | # Measure response time
222 | time echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"hana_list_schemas","arguments":{}}}' | /opt/homebrew/bin/node hana-mcp-server.js
223 | ```
224 | 
225 | ## 🔄 Continuous Testing
226 | 
227 | ### Automated Test Script
228 | Create a CI/CD pipeline script:
229 | 
230 | ```bash
231 | #!/bin/bash
232 | set -e
233 | 
234 | echo "🧪 Running HANA MCP Server Tests..."
235 | 
236 | # Start server
237 | node hana-mcp-server.js &
238 | SERVER_PID=$!
239 | 
240 | # Wait for server to start
241 | sleep 2
242 | 
243 | # Run tests
244 | node test-mcp-inspector.js
245 | 
246 | # Cleanup
247 | kill $SERVER_PID
248 | 
249 | echo "✅ All tests passed!"
250 | ```
251 | 
252 | ## 📝 Test Checklist
253 | 
254 | - [ ] Server initializes correctly
255 | - [ ] All 9 tools are discoverable
256 | - [ ] Configuration tool shows correct settings
257 | - [ ] Connection test passes
258 | - [ ] Schema listing works
259 | - [ ] Table listing works
260 | - [ ] Table description works
261 | - [ ] Index listing works
262 | - [ ] Query execution works
263 | - [ ] Error handling works correctly
264 | - [ ] Performance is acceptable
265 | - [ ] No memory leaks detected
266 | 
267 | ## 🎉 Success Criteria
268 | 
269 | Your HANA MCP server is ready for production when:
270 | - All tests pass consistently
271 | - Response times are under 5 seconds
272 | - Error handling is robust
273 | - Documentation is complete
274 | - Integration with Claude Desktop works seamlessly 
```

--------------------------------------------------------------------------------
/src/utils/formatters.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Response formatting utilities for HANA MCP Server
  3 |  */
  4 | 
  5 | const { logger } = require('./logger');
  6 | 
  7 | class Formatters {
  8 |   /**
  9 |    * Create a standard MCP tool response
 10 |    */
 11 |   static createResponse(text, type = 'text') {
 12 |     return {
 13 |       content: [
 14 |         {
 15 |           type,
 16 |           text
 17 |         }
 18 |       ]
 19 |     };
 20 |   }
 21 | 
 22 |   /**
 23 |    * Create an error response
 24 |    */
 25 |   static createErrorResponse(message, details = '') {
 26 |     const text = details ? `${message}\n\n${details}` : message;
 27 |     return this.createResponse(`❌ ${text}`);
 28 |   }
 29 | 
 30 |   /**
 31 |    * Create a success response
 32 |    */
 33 |   static createSuccessResponse(message, details = '') {
 34 |     const text = details ? `${message}\n\n${details}` : message;
 35 |     return this.createResponse(`✅ ${text}`);
 36 |   }
 37 | 
 38 |   /**
 39 |    * Format configuration display
 40 |    */
 41 |   static formatConfig(config) {
 42 |     const lines = [
 43 |       'HANA Configuration:',
 44 |       '',
 45 |       `Host: ${config.host}`,
 46 |       `Port: ${config.port}`,
 47 |       `User: ${config.user}`,
 48 |       `Password: ${config.password}`,
 49 |       `Schema: ${config.schema}`,
 50 |       `SSL: ${config.ssl}`,
 51 |       ''
 52 |     ];
 53 | 
 54 |     const status = (config.host !== 'NOT SET' && config.user !== 'NOT SET' && config.password !== 'NOT SET') 
 55 |       ? 'PROPERLY CONFIGURED' 
 56 |       : 'MISSING REQUIRED VALUES';
 57 | 
 58 |     lines.push(`Status: ${status}`);
 59 |     
 60 |     return lines.join('\n');
 61 |   }
 62 | 
 63 |   /**
 64 |    * Format environment variables display
 65 |    */
 66 |   static formatEnvironmentVars(envVars) {
 67 |     const lines = [
 68 |       '🔧 Environment Variables:',
 69 |       ''
 70 |     ];
 71 | 
 72 |     for (const [key, value] of Object.entries(envVars)) {
 73 |       lines.push(`${key}: ${value}`);
 74 |     }
 75 | 
 76 |     lines.push('');
 77 |     lines.push('Mode: Real HANA Connection');
 78 |     
 79 |     return lines.join('\n');
 80 |   }
 81 | 
 82 |   /**
 83 |    * Format schema list
 84 |    */
 85 |   static formatSchemaList(schemas) {
 86 |     const lines = [
 87 |       '📋 Available schemas in HANA database:',
 88 |       ''
 89 |     ];
 90 | 
 91 |     schemas.forEach(schema => {
 92 |       lines.push(`- ${schema}`);
 93 |     });
 94 | 
 95 |     lines.push('');
 96 |     lines.push(`Total schemas: ${schemas.length}`);
 97 |     
 98 |     return lines.join('\n');
 99 |   }
100 | 
101 |   /**
102 |    * Format table list
103 |    */
104 |   static formatTableList(tables, schemaName) {
105 |     const lines = [
106 |       `📋 Tables in schema '${schemaName}':`,
107 |       ''
108 |     ];
109 | 
110 |     tables.forEach(table => {
111 |       lines.push(`- ${table}`);
112 |     });
113 | 
114 |     lines.push('');
115 |     lines.push(`Total tables: ${tables.length}`);
116 |     
117 |     return lines.join('\n');
118 |   }
119 | 
120 |   /**
121 |    * Format table structure
122 |    */
123 |   static formatTableStructure(columns, schemaName, tableName) {
124 |     const lines = [
125 |       `📋 Table structure for '${schemaName}.${tableName}':`,
126 |       ''
127 |     ];
128 | 
129 |     if (columns.length === 0) {
130 |       lines.push('No columns found.');
131 |       return lines.join('\n');
132 |     }
133 | 
134 |     // Create header
135 |     const header = 'Column Name          | Data Type    | Length | Nullable | Default | Description';
136 |     const separator = '---------------------|--------------|--------|----------|---------|-------------';
137 |     
138 |     lines.push(header);
139 |     lines.push(separator);
140 | 
141 |     // Add columns
142 |     columns.forEach(col => {
143 |       const nullable = col.IS_NULLABLE === 'TRUE' ? 'YES' : 'NO';
144 |       const defaultValue = col.DEFAULT_VALUE || '-';
145 |       const description = col.COMMENTS || '-';
146 |       const dataType = col.DATA_TYPE_NAME + 
147 |         (col.LENGTH ? `(${col.LENGTH})` : '') + 
148 |         (col.SCALE ? `,${col.SCALE}` : '');
149 |       
150 |       const line = `${col.COLUMN_NAME.padEnd(20)} | ${dataType.padEnd(12)} | ${(col.LENGTH || '-').toString().padEnd(6)} | ${nullable.padEnd(8)} | ${defaultValue.padEnd(8)} | ${description}`;
151 |       lines.push(line);
152 |     });
153 | 
154 |     lines.push('');
155 |     lines.push(`Total columns: ${columns.length}`);
156 |     
157 |     return lines.join('\n');
158 |   }
159 | 
160 |   /**
161 |    * Format index list
162 |    */
163 |   static formatIndexList(indexMap, schemaName, tableName) {
164 |     const lines = [
165 |       `📋 Indexes for table '${schemaName}.${tableName}':`,
166 |       ''
167 |     ];
168 | 
169 |     if (Object.keys(indexMap).length === 0) {
170 |       lines.push('No indexes found.');
171 |       return lines.join('\n');
172 |     }
173 | 
174 |     Object.entries(indexMap).forEach(([indexName, index]) => {
175 |       const type = index.isUnique ? 'Unique' : index.type;
176 |       const columns = index.columns.join(', ');
177 |       lines.push(`- ${indexName} (${type}) - Columns: ${columns}`);
178 |     });
179 | 
180 |     lines.push('');
181 |     lines.push(`Total indexes: ${Object.keys(indexMap).length}`);
182 |     
183 |     return lines.join('\n');
184 |   }
185 | 
186 |   /**
187 |    * Format index details
188 |    */
189 |   static formatIndexDetails(results, schemaName, tableName, indexName) {
190 |     if (results.length === 0) {
191 |       return `❌ Index '${schemaName}.${tableName}.${indexName}' not found.`;
192 |     }
193 | 
194 |     const indexInfo = results[0];
195 |     const columns = results.map(row => `${row.COLUMN_NAME} (${row.ORDER || 'ASC'})`).join(', ');
196 | 
197 |     const lines = [
198 |       `📋 Index details for '${schemaName}.${tableName}.${indexName}':`,
199 |       '',
200 |       `Index Name: ${indexInfo.INDEX_NAME}`,
201 |       `Table: ${schemaName}.${tableName}`,
202 |       `Type: ${indexInfo.INDEX_TYPE}`,
203 |       `Unique: ${indexInfo.IS_UNIQUE === 'TRUE' ? 'Yes' : 'No'}`,
204 |       `Columns: ${columns}`,
205 |       `Total columns: ${results.length}`
206 |     ];
207 | 
208 |     return lines.join('\n');
209 |   }
210 | 
211 |   /**
212 |    * Format query results as table
213 |    */
214 |   static formatQueryResults(results, query) {
215 |     const lines = [
216 |       '🔍 Query executed successfully:',
217 |       '',
218 |       `Query: ${query}`,
219 |       ''
220 |     ];
221 | 
222 |     if (results.length === 0) {
223 |       lines.push('Query executed successfully but returned no results.');
224 |       return lines.join('\n');
225 |     }
226 | 
227 |     // Format as markdown table
228 |     const columns = Object.keys(results[0]);
229 |     const header = `| ${columns.join(' | ')} |`;
230 |     const separator = `| ${columns.map(() => '---').join(' | ')} |`;
231 |     const rows = results.map(row => 
232 |       `| ${columns.map(col => String(row[col] || '')).join(' | ')} |`
233 |     ).join('\n');
234 | 
235 |     lines.push(`Results (${results.length} rows):`);
236 |     lines.push(header);
237 |     lines.push(separator);
238 |     lines.push(rows);
239 |     
240 |     return lines.join('\n');
241 |   }
242 | 
243 |   /**
244 |    * Format connection test result
245 |    */
246 |   static formatConnectionTest(config, success, error = null, testResult = null) {
247 |     if (!success) {
248 |       const lines = [
249 |         '❌ Connection test failed!',
250 |         ''
251 |       ];
252 | 
253 |       if (error) {
254 |         lines.push(`Error: ${error}`);
255 |         lines.push('');
256 |       }
257 | 
258 |       lines.push('Please check your HANA database configuration and ensure the database is accessible.');
259 |       lines.push('');
260 |       lines.push('Configuration:');
261 |       lines.push(`- Host: ${config.host}`);
262 |       lines.push(`- Port: ${config.port}`);
263 |       lines.push(`- User: ${config.user}`);
264 |       lines.push(`- Schema: ${config.schema || 'default'}`);
265 |       lines.push(`- SSL: ${config.ssl ? 'enabled' : 'disabled'}`);
266 | 
267 |       return lines.join('\n');
268 |     }
269 | 
270 |     const lines = [
271 |       '✅ Connection test successful!',
272 |       '',
273 |       'Configuration looks good:',
274 |       `- Host: ${config.host}`,
275 |       `- Port: ${config.port}`,
276 |       `- User: ${config.user}`,
277 |       `- Schema: ${config.schema || 'default'}`,
278 |       `- SSL: ${config.ssl ? 'enabled' : 'disabled'}`
279 |     ];
280 | 
281 |     if (testResult) {
282 |       lines.push('');
283 |       lines.push(`Test query result: ${testResult}`);
284 |     }
285 | 
286 |     return lines.join('\n');
287 |   }
288 | }
289 | 
290 | module.exports = Formatters; 
```

--------------------------------------------------------------------------------
/hana-mcp-ui/src/components/EnvironmentManager.jsx:
--------------------------------------------------------------------------------

```javascript
  1 | import { useState, useEffect } from 'react';
  2 | import { motion, AnimatePresence } from 'framer-motion';
  3 | import { GradientButton } from './ui';
  4 | import { cn } from '../utils/cn';
  5 | 
  6 | const EnvironmentManager = ({ isOpen, onClose, onSave }) => {
  7 |   const [environments, setEnvironments] = useState([
  8 |     { id: 'development', name: 'Development', color: 'blue', required: false },
  9 |     { id: 'staging', name: 'Staging', color: 'amber', required: false },
 10 |     { id: 'production', name: 'Production', color: 'green', required: false }
 11 |   ]);
 12 |   const [newEnvironmentName, setNewEnvironmentName] = useState('');
 13 |   const [selectedColor, setSelectedColor] = useState('purple');
 14 | 
 15 |   useEffect(() => {
 16 |     if (!isOpen) return;
 17 |     const onKeyDown = (e) => {
 18 |       if (e.key === 'Escape') onClose();
 19 |     };
 20 |     window.addEventListener('keydown', onKeyDown);
 21 |     return () => window.removeEventListener('keydown', onKeyDown);
 22 |   }, [isOpen, onClose]);
 23 | 
 24 |   const colorOptions = [
 25 |     { id: 'blue', name: 'Blue', class: 'bg-blue-500' },
 26 |     { id: 'green', name: 'Green', class: 'bg-green-500' },
 27 |     { id: 'amber', name: 'Amber', class: 'bg-amber-500' },
 28 |     { id: 'purple', name: 'Purple', class: 'bg-purple-500' },
 29 |     { id: 'indigo', name: 'Indigo', class: 'bg-indigo-500' },
 30 |     { id: 'red', name: 'Red', class: 'bg-red-500' },
 31 |     { id: 'pink', name: 'Pink', class: 'bg-pink-500' },
 32 |     { id: 'teal', name: 'Teal', class: 'bg-teal-500' }
 33 |   ];
 34 | 
 35 |   useEffect(() => {
 36 |     // Load existing environments from localStorage or API
 37 |     const savedEnvironments = localStorage.getItem('hana-environments');
 38 |     if (savedEnvironments) {
 39 |       setEnvironments(JSON.parse(savedEnvironments));
 40 |     }
 41 |   }, []);
 42 | 
 43 |   const addEnvironment = () => {
 44 |     if (!newEnvironmentName.trim()) return;
 45 | 
 46 |     const newEnv = {
 47 |       id: newEnvironmentName.toLowerCase().replace(/\s+/g, '-'),
 48 |       name: newEnvironmentName,
 49 |       color: selectedColor,
 50 |       required: false
 51 |     };
 52 | 
 53 |     const updatedEnvironments = [...environments, newEnv];
 54 |     setEnvironments(updatedEnvironments);
 55 |     setNewEnvironmentName('');
 56 |     setSelectedColor('purple');
 57 |   };
 58 | 
 59 |   const removeEnvironment = (envId) => {
 60 |     setEnvironments(environments.filter(env => env.id !== envId));
 61 |   };
 62 | 
 63 |   const handleSave = () => {
 64 |     // Save to localStorage (in real app, this would be an API call)
 65 |     localStorage.setItem('hana-environments', JSON.stringify(environments));
 66 |     onSave(environments);
 67 |     onClose();
 68 |   };
 69 | 
 70 |   if (!isOpen) return null;
 71 | 
 72 |   return (
 73 |     <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
 74 |       <motion.div
 75 |         initial={{ opacity: 0, scale: 0.95 }}
 76 |         animate={{ opacity: 1, scale: 1 }}
 77 |         exit={{ opacity: 0, scale: 0.95 }}
 78 |         className="bg-white rounded-xl shadow-2xl p-6 w-full max-w-2xl max-h-[80vh] overflow-y-auto"
 79 |       >
 80 |         <div className="flex items-center justify-between mb-6">
 81 |           <h2 className="text-xl font-semibold text-gray-900">Manage Environments</h2>
 82 |           <button
 83 |             onClick={onClose}
 84 |             className="text-gray-400 hover:text-gray-600"
 85 |           >
 86 |             <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
 87 |               <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
 88 |             </svg>
 89 |           </button>
 90 |         </div>
 91 | 
 92 |         {/* Current Environments */}
 93 |         <div className="mb-6">
 94 |           <h3 className="text-lg font-medium text-gray-900 mb-3">Current Environments</h3>
 95 |           <div className="space-y-2">
 96 |             {environments.map((env) => (
 97 |               <div
 98 |                 key={env.id}
 99 |                 className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"
100 |               >
101 |                 <div className="flex items-center space-x-3">
102 |                   <div className={cn('w-4 h-4 rounded-full', `bg-${env.color}-500`)} />
103 |                   <span className="font-medium text-gray-900">{env.name}</span>
104 |                   <span className="text-sm text-gray-500">({env.id})</span>
105 |                 </div>
106 |                 <button
107 |                   onClick={() => removeEnvironment(env.id)}
108 |                   className="text-red-600 hover:text-red-800 text-sm"
109 |                 >
110 |                   Remove
111 |                 </button>
112 |               </div>
113 |             ))}
114 |           </div>
115 |         </div>
116 | 
117 |         {/* Add New Environment */}
118 |         <div className="mb-6">
119 |           <h3 className="text-lg font-medium text-gray-900 mb-3">Add New Environment</h3>
120 |           <div className="space-y-4">
121 |             <div>
122 |               <label className="block text-sm font-medium text-gray-700 mb-1">
123 |                 Environment Name
124 |               </label>
125 |               <input
126 |                 type="text"
127 |                 value={newEnvironmentName}
128 |                 onChange={(e) => setNewEnvironmentName(e.target.value)}
129 |                 placeholder="e.g., Pre-Production, QA, Testing"
130 |                 className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
131 |               />
132 |             </div>
133 | 
134 |             <div>
135 |               <label className="block text-sm font-medium text-gray-700 mb-2">
136 |                 Color
137 |               </label>
138 |               <div className="flex flex-wrap gap-2">
139 |                 {colorOptions.map((color) => (
140 |                   <button
141 |                     key={color.id}
142 |                     onClick={() => setSelectedColor(color.id)}
143 |                     className={cn(
144 |                       'w-8 h-8 rounded-full border-2 transition-all',
145 |                       color.class,
146 |                       selectedColor === color.id
147 |                         ? 'border-gray-800 scale-110'
148 |                         : 'border-gray-300 hover:scale-105'
149 |                     )}
150 |                     title={color.name}
151 |                   />
152 |                 ))}
153 |               </div>
154 |             </div>
155 | 
156 |             <GradientButton
157 |               onClick={addEnvironment}
158 |               disabled={!newEnvironmentName.trim()}
159 |               className="w-full"
160 |             >
161 |               Add Environment
162 |             </GradientButton>
163 |           </div>
164 |         </div>
165 | 
166 |         {/* Note */}
167 |         <div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
168 |           <div className="flex">
169 |             <svg className="w-5 h-5 text-blue-600 mr-2 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
170 |               <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" />
171 |             </svg>
172 |             <div>
173 |               <p className="text-sm text-blue-800">
174 |                 <strong>Note:</strong> Environments are optional for databases. You can configure any combination of environments for each database based on your needs.
175 |               </p>
176 |             </div>
177 |           </div>
178 |         </div>
179 | 
180 |         {/* Actions */}
181 |         <div className="flex justify-end space-x-3">
182 |           <GradientButton variant="secondary" onClick={onClose}>
183 |             Cancel
184 |           </GradientButton>
185 |           <GradientButton onClick={handleSave}>
186 |             Save Changes
187 |           </GradientButton>
188 |         </div>
189 |       </motion.div>
190 |     </div>
191 |   );
192 | };
193 | 
194 | export default EnvironmentManager;
195 | 
```

--------------------------------------------------------------------------------
/hana-mcp-ui/src/components/ClaudeDesktopView.jsx:
--------------------------------------------------------------------------------

```javascript
  1 | import { useState } from 'react';
  2 | import { motion } from 'framer-motion';
  3 | import ClaudeConfigTile from './ClaudeConfigTile';
  4 | import BackupHistoryModal from './BackupHistoryModal';
  5 | import { cn } from '../utils/cn';
  6 | import { ArchiveBoxIcon, ArrowPathIcon } from '@heroicons/react/24/outline';
  7 | 
  8 | const ClaudeDesktopView = ({ 
  9 |   claudeConfigPath, 
 10 |   claudeServers, 
 11 |   activeEnvironments,
 12 |   onSetupPath,
 13 |   onRemoveConnection,
 14 |   onViewConnection,
 15 |   onRefresh,
 16 |   onConfigPathChange
 17 | }) => {
 18 |   const activeConnections = claudeServers.length;
 19 |   const [isRefreshing, setIsRefreshing] = useState(false);
 20 |   const [showBackupHistory, setShowBackupHistory] = useState(false);
 21 | 
 22 |   const handleRefresh = async () => {
 23 |     setIsRefreshing(true);
 24 |     try {
 25 |       await onRefresh();
 26 |     } finally {
 27 |       setIsRefreshing(false);
 28 |     }
 29 |   };
 30 | 
 31 |   return (
 32 |     <div className="p-6 space-y-6 bg-gray-100 rounded-2xl sm:rounded-3xl">
 33 |       <div className="flex items-center justify-between">
 34 |         <div>
 35 |           <h1 className="text-2xl font-bold text-gray-900 mb-2">Claude Desktop Integration</h1>
 36 |           <p className="text-gray-600">
 37 |             Manage your HANA database connections available in Claude Desktop
 38 |           </p>
 39 |         </div>
 40 |         <div className="flex items-center gap-3">
 41 |           <button
 42 |             onClick={() => setShowBackupHistory(true)}
 43 |             className="flex items-center px-3 py-2 text-sm font-medium bg-gray-100 border border-gray-200 text-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors hover:bg-gray-200 hover:border-gray-300 shadow-sm hover:shadow-md"
 44 |             title="Manage configuration backups"
 45 |           >
 46 |             <ArchiveBoxIcon className="w-4 h-4 mr-2" />
 47 |             Backups
 48 |           </button>
 49 |           <button
 50 |             onClick={handleRefresh}
 51 |             disabled={isRefreshing}
 52 |             className={cn(
 53 |               "flex items-center px-3 py-2 text-sm font-medium bg-gray-100 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors shadow-sm hover:shadow-md",
 54 |               isRefreshing 
 55 |                 ? "text-gray-400 cursor-not-allowed" 
 56 |                 : "text-gray-700 hover:bg-gray-200 hover:border-gray-300"
 57 |             )}
 58 |             title="Refresh configuration from Claude Desktop"
 59 |           >
 60 |             <ArrowPathIcon className={cn(
 61 |               "w-4 h-4 mr-2",
 62 |               isRefreshing && "animate-spin"
 63 |             )} />
 64 |             {isRefreshing ? 'Refreshing...' : 'Refresh'}
 65 |           </button>
 66 |         </div>
 67 |       </div>
 68 | 
 69 |       {/* Configuration Status */}
 70 |       <ClaudeConfigTile 
 71 |         claudeConfigPath={claudeConfigPath}
 72 |         claudeServers={claudeServers}
 73 |         onSetupPath={onSetupPath}
 74 |         onConfigPathChange={onConfigPathChange}
 75 |       />
 76 | 
 77 |       {/* Active Database Connections */}
 78 |       <div className="bg-white rounded-xl border border-gray-200 p-6">
 79 |         <div className="flex items-center justify-between mb-6">
 80 |           <h2 className="text-lg font-semibold text-gray-900">Active Database Connections</h2>
 81 |           <div className="flex items-center">
 82 |             <div className={cn(
 83 |               'w-2 h-2 rounded-full mr-2',
 84 |               activeConnections > 0 ? 'bg-green-500' : 'bg-gray-300'
 85 |             )} />
 86 |             <span className="text-sm text-gray-600">
 87 |               {activeConnections} {activeConnections === 1 ? 'connection' : 'connections'} active
 88 |             </span>
 89 |           </div>
 90 |         </div>
 91 | 
 92 |         {activeConnections > 0 ? (
 93 |           <div className="overflow-x-auto max-h-96 overflow-y-auto claude-table-scrollbar">
 94 |             <table className="min-w-full divide-y divide-gray-200">
 95 |               <thead className="bg-gray-100">
 96 |                 <tr>
 97 |                   <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
 98 |                     Database
 99 |                   </th>
100 |                   <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
101 |                     Environment
102 |                   </th>
103 |                   <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
104 |                     Actions
105 |                   </th>
106 |                 </tr>
107 |               </thead>
108 |               <tbody className="bg-white divide-y divide-gray-200">
109 |                 {claudeServers.map((server) => (
110 |                   <tr 
111 |                     key={server.name} 
112 |                     className="hover:bg-gray-50 cursor-pointer transition-colors"
113 |                     onClick={() => onViewConnection(server)}
114 |                   >
115 |                     <td className="px-6 py-4 whitespace-nowrap">
116 |                       <div className="flex items-center">
117 |                         <div className="h-8 w-8 bg-blue-50 rounded-full flex items-center justify-center">
118 |                           <svg className="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
119 |                             <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
120 |                           </svg>
121 |                         </div>
122 |                         <div className="ml-4">
123 |                           <div className="text-sm font-medium text-gray-900">{server.name}</div>
124 |                           <div className="text-sm text-gray-500">{server.env.HANA_HOST}</div>
125 |                         </div>
126 |                       </div>
127 |                     </td>
128 |                     <td className="px-6 py-4 whitespace-nowrap">
129 |                       <span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
130 |                         <span className="w-1.5 h-1.5 bg-green-600 rounded-full mr-1.5"></span>
131 |                         {server.env?.ENVIRONMENT || 'Development'}
132 |                       </span>
133 |                     </td>
134 |                     <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
135 |                       <button
136 |                         onClick={(e) => {
137 |                           e.stopPropagation();
138 |                           onViewConnection(server);
139 |                         }}
140 |                         className="text-blue-600 hover:text-blue-900 mr-3"
141 |                       >
142 |                         View
143 |                       </button>
144 |                       <button
145 |                         onClick={(e) => {
146 |                           e.stopPropagation();
147 |                           onRemoveConnection(server.name);
148 |                         }}
149 |                         className="text-red-600 hover:text-red-700 bg-red-50 hover:bg-red-100 px-2 py-1 rounded transition-colors"
150 |                       >
151 |                         Remove
152 |                       </button>
153 |                     </td>
154 |                   </tr>
155 |                 ))}
156 |               </tbody>
157 |             </table>
158 |           </div>
159 |         ) : (
160 |           <div className="text-center py-12">
161 |             <div className="mx-auto w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
162 |               <svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
163 |                 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
164 |               </svg>
165 |             </div>
166 |             <h3 className="text-lg font-medium text-gray-900 mb-2">No active connections</h3>
167 |             <p className="text-gray-600 mb-4">
168 |               You haven't added any HANA databases to Claude Desktop yet
169 |             </p>
170 |             <button
171 |               onClick={onSetupPath}
172 |               className="inline-flex items-center px-4 py-2 bg-[#86a0ff] text-white text-sm font-medium rounded-lg hover:bg-[#7990e6] transition-colors shadow-sm hover:shadow-md"
173 |             >
174 |               <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
175 |                 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
176 |               </svg>
177 |               Setup Claude Desktop
178 |             </button>
179 |           </div>
180 |         )}
181 |       </div>
182 | 
183 |       {/* Backup History Modal */}
184 |       <BackupHistoryModal
185 |         isOpen={showBackupHistory}
186 |         onClose={() => setShowBackupHistory(false)}
187 |       />
188 |     </div>
189 |   );
190 | };
191 | 
192 | export default ClaudeDesktopView;
193 | 
```

--------------------------------------------------------------------------------
/hana-mcp-ui/src/components/SearchAndFilter.jsx:
--------------------------------------------------------------------------------

```javascript
  1 | import { useState } from 'react';
  2 | import { motion, AnimatePresence } from 'framer-motion';
  3 | import { cn } from '../utils/cn';
  4 | 
  5 | const SearchAndFilter = ({ 
  6 |   searchQuery, 
  7 |   onSearchChange, 
  8 |   filters, 
  9 |   onFilterChange,
 10 |   onClearFilters,
 11 |   placeholder = "Search databases..."
 12 | }) => {
 13 |   const [isFilterOpen, setIsFilterOpen] = useState(false);
 14 |   const [sortBy, setSortBy] = useState('name');
 15 |   const [sortOrder, setSortOrder] = useState('asc');
 16 | 
 17 |   const filterOptions = [
 18 |     { id: 'all', label: 'All Databases', count: filters.total || 0 },
 19 |     { id: 'active', label: 'Active in Claude', count: filters.activeInClaude || 0, highlight: true },
 20 |     { id: 'production', label: 'Production', count: filters.production || 0 },
 21 |     { id: 'development', label: 'Development', count: filters.development || 0 },
 22 |     { id: 'staging', label: 'Staging', count: filters.staging || 0 }
 23 |   ];
 24 | 
 25 |   const sortOptions = [
 26 |     { id: 'name', label: 'Name' },
 27 |     { id: 'created', label: 'Date Created' },
 28 |     { id: 'modified', label: 'Last Modified' },
 29 |     { id: 'environments', label: 'Environment Count' }
 30 |   ];
 31 | 
 32 |   const handleSortChange = (newSortBy) => {
 33 |     if (sortBy === newSortBy) {
 34 |       setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
 35 |     } else {
 36 |       setSortBy(newSortBy);
 37 |       setSortOrder('asc');
 38 |     }
 39 |     // Call parent callback if provided
 40 |     if (onFilterChange) {
 41 |       onFilterChange({ sortBy: newSortBy, sortOrder: sortOrder === 'asc' ? 'desc' : 'asc' });
 42 |     }
 43 |   };
 44 | 
 45 |   return (
 46 |     <div className="p-4">
 47 |       <div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-4">
 48 |         {/* Search Input */}
 49 |         <div className="flex-1 relative">
 50 |           <div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
 51 |             <svg className="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
 52 |               <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
 53 |             </svg>
 54 |           </div>
 55 |           <input
 56 |             type="text"
 57 |             value={searchQuery}
 58 |             onChange={(e) => onSearchChange(e.target.value)}
 59 |             placeholder={placeholder}
 60 |             className="block w-full pl-10 pr-3 py-2 border border-gray-100 rounded-lg leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
 61 |           />
 62 |           {searchQuery && (
 63 |             <button
 64 |               onClick={() => onSearchChange('')}
 65 |               className="absolute inset-y-0 right-0 pr-4 flex items-center group"
 66 |             >
 67 |               <svg className="h-5 w-5 text-gray-400 group-hover:text-gray-600 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
 68 |                 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
 69 |               </svg>
 70 |             </button>
 71 |           )}
 72 |         </div>
 73 | 
 74 |         <div className="flex items-center gap-4">
 75 |         {/* Claude Integration Status */}
 76 |         {filters.activeInClaude > 0 && (
 77 |           <div className="flex items-center space-x-2 px-3 py-2 bg-green-50 border border-green-200 rounded-lg">
 78 |             <svg className="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
 79 |               <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
 80 |             </svg>
 81 |             <span className="text-sm font-medium text-green-700">
 82 |               {filters.activeInClaude} Claude
 83 |             </span>
 84 |           </div>
 85 |         )}
 86 | 
 87 |         {/* Filter Toggle */}
 88 |         <button
 89 |           onClick={() => setIsFilterOpen(!isFilterOpen)}
 90 |           className={cn(
 91 |             'flex items-center px-4 py-2 border rounded-lg transition-colors',
 92 |             isFilterOpen
 93 |               ? 'border-blue-300 bg-blue-50 text-blue-700'
 94 |               : 'border-gray-100 bg-white text-gray-700 hover:bg-gray-50'
 95 |           )}
 96 |         >
 97 |           <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
 98 |             <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.414A1 1 0 013 6.707V4z" />
 99 |           </svg>
100 |           Filters
101 |           <svg className={cn("w-4 h-4 ml-2 transition-transform duration-200", isFilterOpen && "rotate-180")} fill="none" stroke="currentColor" viewBox="0 0 24 24">
102 |             <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
103 |           </svg>
104 |         </button>
105 | 
106 |         {/* Sort Dropdown */}
107 |         <div className="relative">
108 |           <select
109 |             value={`${sortBy}-${sortOrder}`}
110 |             onChange={(e) => {
111 |               const [newSortBy, newSortOrder] = e.target.value.split('-');
112 |               setSortBy(newSortBy);
113 |               setSortOrder(newSortOrder);
114 |               if (onFilterChange) {
115 |                 onFilterChange({ sortBy: newSortBy, sortOrder: newSortOrder });
116 |               }
117 |             }}
118 |             className="appearance-none bg-white border border-gray-100 rounded-lg px-4 py-2 pr-8 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
119 |           >
120 |             {sortOptions.map(option => (
121 |               <option key={`${option.id}-asc`} value={`${option.id}-asc`}>{option.label} (A-Z)</option>
122 |             ))}
123 |             {sortOptions.map(option => (
124 |               <option key={`${option.id}-desc`} value={`${option.id}-desc`}>{option.label} (Z-A)</option>
125 |             ))}
126 |           </select>
127 |           <div className="absolute inset-y-0 right-0 flex items-center px-3 pointer-events-none">
128 |             <svg className="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
129 |               <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
130 |             </svg>
131 |           </div>
132 |         </div>
133 |         </div>
134 |       </div>
135 | 
136 |       {/* Filter Panel */}
137 |       <AnimatePresence>
138 |         {isFilterOpen && (
139 |           <motion.div
140 |             initial={{ height: 0, opacity: 0 }}
141 |             animate={{ height: 'auto', opacity: 1 }}
142 |             exit={{ height: 0, opacity: 0 }}
143 |             transition={{ duration: 0.3, ease: "easeInOut" }}
144 |             className="overflow-hidden"
145 |           >
146 |             <div className="pt-4 mt-4 border-t border-gray-200">
147 |               <div className="flex items-center justify-between mb-3">
148 |                 <h3 className="text-sm font-medium text-gray-900">Filter by Status</h3>
149 |                 <button
150 |                   onClick={onClearFilters}
151 |                   className="text-sm text-blue-600 hover:text-blue-800"
152 |                 >
153 |                   Clear all
154 |                 </button>
155 |               </div>
156 |               
157 |               <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3">
158 |                 {filterOptions.map(option => (
159 |                   <button
160 |                     key={option.id}
161 |                     onClick={() => onFilterChange && onFilterChange({ status: option.id })}
162 |                     className={cn(
163 |                       'flex items-center justify-between p-3 rounded-lg border transition-colors text-left',
164 |                       filters.activeFilter === option.id
165 |                         ? option.highlight 
166 |                           ? 'border-green-300 bg-green-50 text-green-700'
167 |                           : 'border-blue-300 bg-blue-50 text-blue-700'
168 |                         : option.highlight
169 |                           ? 'border-green-200 bg-white text-green-700 hover:bg-green-50'
170 |                           : 'border-gray-200 bg-white text-gray-700 hover:bg-gray-50'
171 |                     )}
172 |                   >
173 |                     <span className="text-sm font-medium flex items-center space-x-1">
174 |                       {option.highlight && (
175 |                         <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
176 |                           <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
177 |                         </svg>
178 |                       )}
179 |                       <span>{option.label}</span>
180 |                     </span>
181 |                     <span className={cn(
182 |                       "text-xs px-2 py-0.5 rounded-full",
183 |                       option.highlight 
184 |                         ? filters.activeFilter === option.id
185 |                           ? 'bg-green-200 text-green-700'
186 |                           : 'bg-green-100 text-green-600'
187 |                         : 'bg-gray-100 text-gray-600'
188 |                     )}>
189 |                       {option.count}
190 |                     </span>
191 |                   </button>
192 |                 ))}
193 |               </div>
194 |             </div>
195 |           </motion.div>
196 |         )}
197 |       </AnimatePresence>
198 |     </div>
199 |   );
200 | };
201 | 
202 | export default SearchAndFilter;
203 | 
```

--------------------------------------------------------------------------------
/hana-mcp-ui/src/components/layout/VerticalSidebar.jsx:
--------------------------------------------------------------------------------

```javascript
  1 | import { useState } from 'react';
  2 | import { motion } from 'framer-motion';
  3 | import { cn } from '../../utils/cn';
  4 | 
  5 | const VerticalSidebar = ({ 
  6 |   activeView, 
  7 |   onViewChange, 
  8 |   databaseCount, 
  9 |   activeConnections,
 10 |   claudeConfigured 
 11 | }) => {
 12 |   const [collapsed, setCollapsed] = useState(false);
 13 | 
 14 |   const navigationItems = [
 15 |     {
 16 |       id: 'dashboard',
 17 |       label: 'Dashboard',
 18 |       icon: (
 19 |         <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
 20 |           <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2v0a2 2 0 002-2h10a2 2 0 012 2v0a2 2 0 012 2z" />
 21 |         </svg>
 22 |       ),
 23 |       description: 'Overview & insights'
 24 |     },
 25 |     {
 26 |       id: 'databases',
 27 |       label: 'My Local Databases',
 28 |       icon: (
 29 |         <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
 30 |           <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
 31 |         </svg>
 32 |       ),
 33 |       description: 'Manage configurations',
 34 |       count: databaseCount,
 35 |       hasSubmenu: true
 36 |     },
 37 |     {
 38 |       id: 'claude',
 39 |       label: 'Claude Desktop',
 40 |       icon: (
 41 |         <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
 42 |           <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
 43 |         </svg>
 44 |       ),
 45 |       description: 'Integration status',
 46 |       count: activeConnections,
 47 |       status: claudeConfigured ? 'online' : 'offline'
 48 |     }
 49 |   ];
 50 | 
 51 |   return (
 52 |     <motion.div
 53 |       className={cn(
 54 |         'bg-white border border-gray-200 flex flex-col h-full rounded-xl shadow-lg overflow-hidden',
 55 |         collapsed ? 'w-12 sm:w-16' : 'w-56 sm:w-64'
 56 |       )}
 57 |       initial={false}
 58 |       animate={{ 
 59 |         width: collapsed ? 64 : 256,
 60 |         transition: {
 61 |           type: "spring",
 62 |           stiffness: 300,
 63 |           damping: 30,
 64 |           mass: 0.8
 65 |         }
 66 |       }}
 67 |       transition={{
 68 |         type: "spring",
 69 |         stiffness: 300,
 70 |         damping: 30,
 71 |         mass: 0.8
 72 |       }}
 73 |     >
 74 |       {/* Header */}
 75 |       <div className="p-3 sm:p-4 border-b border-gray-200 rounded-t-xl">
 76 |         <div className="flex items-center justify-between">
 77 |           <motion.div 
 78 |             className="flex items-center space-x-3"
 79 |             initial={false}
 80 |             animate={{ 
 81 |               opacity: collapsed ? 0 : 1,
 82 |               x: collapsed ? -20 : 0,
 83 |               transition: {
 84 |                 duration: 0.2,
 85 |                 delay: collapsed ? 0 : 0.1
 86 |               }
 87 |             }}
 88 |             style={{ display: collapsed ? 'none' : 'flex' }}
 89 |           >
 90 |             <img 
 91 |               src="/logo.png" 
 92 |               alt="HANA MCP Logo" 
 93 |               className="w-8 h-8 flex-shrink-0"
 94 |             />
 95 |             <div>
 96 |               <h2 className="text-lg font-semibold text-gray-900">HANA MCP</h2>
 97 |               <p className="text-xs text-gray-500">Database Manager</p>
 98 |             </div>
 99 |           </motion.div>
100 |           <motion.div 
101 |             className="flex flex-col items-center w-full space-y-2"
102 |             initial={false}
103 |             animate={{ 
104 |               opacity: collapsed ? 1 : 0,
105 |               scale: collapsed ? 1 : 0.8,
106 |               transition: {
107 |                 duration: 0.2,
108 |                 delay: collapsed ? 0.1 : 0
109 |               }
110 |             }}
111 |             style={{ display: collapsed ? 'flex' : 'none' }}
112 |           >
113 |             <img 
114 |               src="/logo.png" 
115 |               alt="HANA MCP Logo" 
116 |               className="w-6 h-6"
117 |             />
118 |             <motion.button
119 |               onClick={() => setCollapsed(!collapsed)}
120 |               className="p-1 rounded-lg hover:bg-gray-100 transition-colors"
121 |               whileHover={{ scale: 1.05 }}
122 |               whileTap={{ scale: 0.95 }}
123 |               title="Expand sidebar"
124 |             >
125 |               <motion.svg 
126 |                 className="w-4 h-4 text-gray-500" 
127 |                 fill="none" 
128 |                 stroke="currentColor" 
129 |                 viewBox="0 0 24 24"
130 |                 animate={{ rotate: collapsed ? 180 : 0 }}
131 |                 transition={{ duration: 0.3, ease: "easeInOut" }}
132 |               >
133 |                 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 5l7 7-7 7M5 5l7 7-7 7" />
134 |               </motion.svg>
135 |             </motion.button>
136 |           </motion.div>
137 |           <motion.button
138 |             onClick={() => setCollapsed(!collapsed)}
139 |             className={cn(
140 |               "p-1.5 rounded-lg hover:bg-gray-100 transition-colors",
141 |               collapsed && "hidden"
142 |             )}
143 |             whileHover={{ scale: 1.05 }}
144 |             whileTap={{ scale: 0.95 }}
145 |           >
146 |             <motion.svg 
147 |               className="w-4 h-4 text-gray-500" 
148 |               fill="none" 
149 |               stroke="currentColor" 
150 |               viewBox="0 0 24 24"
151 |               animate={{ rotate: collapsed ? 180 : 0 }}
152 |               transition={{ duration: 0.3, ease: "easeInOut" }}
153 |             >
154 |               <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
155 |             </motion.svg>
156 |           </motion.button>
157 |         </div>
158 |       </div>
159 | 
160 |       {/* Navigation */}
161 |       <nav className="flex-1 p-1 sm:p-2">
162 |         <ul className="space-y-1">
163 |           {navigationItems.map((item) => (
164 |             <li key={item.id}>
165 |               <button
166 |                 onClick={() => onViewChange(item.id)}
167 |                 className={cn(
168 |                   'w-full flex items-center p-3 rounded-lg text-left transition-all duration-200',
169 |                   'hover:bg-gray-50 group',
170 |                   activeView === item.id
171 |                     ? 'bg-blue-50 text-blue-700 border border-blue-200'
172 |                     : 'text-gray-700 hover:text-gray-900'
173 |                 )}
174 |               >
175 |                 <div className={cn(
176 |                   'flex-shrink-0',
177 |                   activeView === item.id ? 'text-blue-600' : 'text-gray-400 group-hover:text-gray-600'
178 |                 )}>
179 |                   {item.icon}
180 |                 </div>
181 |                 
182 |                 <motion.div 
183 |                   className="ml-3 flex-1 min-w-0"
184 |                   initial={false}
185 |                   animate={{ 
186 |                     opacity: collapsed ? 0 : 1,
187 |                     x: collapsed ? -10 : 0,
188 |                     transition: {
189 |                       duration: 0.2,
190 |                       delay: collapsed ? 0 : 0.1
191 |                     }
192 |                   }}
193 |                   style={{ display: collapsed ? 'none' : 'block' }}
194 |                 >
195 |                   <div className="flex items-center justify-between">
196 |                     <span className="text-sm font-medium truncate">
197 |                       {item.label}
198 |                     </span>
199 |                     {item.count !== undefined && item.count > 0 && (
200 |                       <motion.span 
201 |                         className={cn(
202 |                           'ml-2 px-2 py-0.5 text-xs font-medium rounded-full',
203 |                           activeView === item.id
204 |                             ? 'bg-blue-100 text-blue-700'
205 |                             : 'bg-gray-100 text-gray-600'
206 |                         )}
207 |                         initial={{ scale: 0 }}
208 |                         animate={{ scale: 1 }}
209 |                         transition={{ delay: 0.2 }}
210 |                       >
211 |                         {item.count}
212 |                       </motion.span>
213 |                     )}
214 |                     {item.status && (
215 |                       <motion.div 
216 |                         className={cn(
217 |                           'ml-2 w-2 h-2 rounded-full',
218 |                           item.status === 'online' ? 'bg-green-500' : 'bg-gray-300'
219 |                         )}
220 |                         initial={{ scale: 0 }}
221 |                         animate={{ scale: 1 }}
222 |                         transition={{ delay: 0.2 }}
223 |                       />
224 |                     )}
225 |                   </div>
226 |                   <p className="text-xs text-gray-500 truncate">
227 |                     {item.description}
228 |                   </p>
229 |                 </motion.div>
230 |               </button>
231 |             </li>
232 |           ))}
233 |         </ul>
234 |       </nav>
235 | 
236 |       {/* Quick Actions */}
237 |       <motion.div 
238 |         className="p-3 sm:p-4 border-t border-gray-200 rounded-b-xl"
239 |         initial={false}
240 |         animate={{ 
241 |           opacity: collapsed ? 0 : 1,
242 |           y: collapsed ? 20 : 0,
243 |           transition: {
244 |             duration: 0.2,
245 |             delay: collapsed ? 0 : 0.15
246 |           }
247 |         }}
248 |         style={{ display: collapsed ? 'none' : 'block' }}
249 |       >
250 |         <motion.button
251 |           onClick={() => onViewChange('add-database')}
252 |           className="w-full flex items-center justify-center px-4 py-2 bg-[#86a0ff] text-white text-sm font-medium rounded-lg hover:bg-[#7990e6] transition-colors"
253 |           whileHover={{ scale: 1.02 }}
254 |           whileTap={{ scale: 0.98 }}
255 |         >
256 |           <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
257 |             <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
258 |           </svg>
259 |            Add Database
260 |         </motion.button>
261 |       </motion.div>
262 | 
263 |     </motion.div>
264 |   );
265 | };
266 | 
267 | export default VerticalSidebar;
268 | 
```

--------------------------------------------------------------------------------
/hana-mcp-ui/src/components/DashboardView.jsx:
--------------------------------------------------------------------------------

```javascript
  1 | import { useState, useEffect } from 'react';
  2 | import { motion } from 'framer-motion';
  3 | import { MetricCard } from './ui';
  4 | import { cn } from '../utils/cn';
  5 | import EnvironmentManager from './EnvironmentManager';
  6 | 
  7 | const DashboardView = ({ 
  8 |   hanaServers, 
  9 |   claudeServers, 
 10 |   activeEnvironments,
 11 |   onQuickAction 
 12 | }) => {
 13 |   const [showEnvironmentManager, setShowEnvironmentManager] = useState(false);
 14 |   // Calculate insights
 15 |   const totalDatabases = Object.keys(hanaServers).length;
 16 |   const activeConnections = claudeServers.length;
 17 |   const totalEnvironments = Object.values(hanaServers).reduce((total, server) => {
 18 |     return total + Object.keys(server.environments || {}).length;
 19 |   }, 0);
 20 | 
 21 |   const environmentBreakdown = Object.values(hanaServers).reduce((breakdown, server) => {
 22 |     Object.keys(server.environments || {}).forEach(env => {
 23 |       breakdown[env] = (breakdown[env] || 0) + 1;
 24 |     });
 25 |     return breakdown;
 26 |   }, {});
 27 | 
 28 |   // Calculate real connection status
 29 |   const connectionStatus = activeConnections > 0 ? 'Connected' : 'Not Connected';
 30 |   const configuredDatabases = Object.keys(hanaServers).filter(key => 
 31 |     Object.keys(hanaServers[key].environments || {}).length > 0
 32 |   ).length;
 33 | 
 34 |   const quickActions = [
 35 |     {
 36 |       id: 'add-database',
 37 |       title: 'Add New Database',
 38 |       description: 'Configure a new HANA database connection',
 39 |       icon: (
 40 |         <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
 41 |           <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
 42 |         </svg>
 43 |       ),
 44 |       color: 'blue',
 45 |       enabled: true
 46 |     },
 47 |     {
 48 |       id: 'manage-databases',
 49 |       title: 'Manage Databases',
 50 |       description: 'View and configure your database connections',
 51 |       icon: (
 52 |         <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
 53 |           <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
 54 |         </svg>
 55 |       ),
 56 |       color: 'green',
 57 |       enabled: totalDatabases > 0
 58 |     },
 59 |     {
 60 |       id: 'claude-integration',
 61 |       title: 'Claude Integration',
 62 |       description: 'Manage Claude Desktop integration',
 63 |       icon: (
 64 |         <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
 65 |           <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
 66 |         </svg>
 67 |       ),
 68 |       color: 'purple',
 69 |       enabled: true
 70 |     }
 71 |   ].filter(action => action.enabled);
 72 | 
 73 |   const getStatusIcon = (status) => {
 74 |     switch (status) {
 75 |       case 'success':
 76 |         return <div className="w-2 h-2 bg-green-500 rounded-full" />;
 77 |       case 'warning':
 78 |         return <div className="w-2 h-2 bg-yellow-500 rounded-full" />;
 79 |       case 'error':
 80 |         return <div className="w-2 h-2 bg-red-500 rounded-full" />;
 81 |       default:
 82 |         return <div className="w-2 h-2 bg-blue-500 rounded-full" />;
 83 |     }
 84 |   };
 85 | 
 86 |   return (
 87 |     <div className="p-4 space-y-4 bg-gray-100 rounded-2xl sm:rounded-3xl">
 88 |       {/* Welcome Header */}
 89 |       <div className="mb-4">
 90 |         <h1 className="text-xl font-bold text-gray-900 mb-1">Dashboard</h1>
 91 |       </div>
 92 | 
 93 |       {/* Key Metrics */}
 94 |       <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
 95 |         <MetricCard
 96 |           title="Total Databases"
 97 |           value={totalDatabases}
 98 |         />
 99 |         <MetricCard
100 |           title="Active Connections"
101 |           value={activeConnections}
102 |         />
103 |         <MetricCard
104 |           title="Total Environments"
105 |           value={totalEnvironments}
106 |         />
107 |         <MetricCard
108 |           title="Configured Databases"
109 |           value={configuredDatabases}
110 |         />
111 |       </div>
112 | 
113 |       {/* Quick Actions */}
114 |       <div className="bg-white rounded-xl border border-gray-200 p-4">
115 |         <h2 className="text-lg font-semibold text-gray-900 mb-3">Quick Actions</h2>
116 |         <div className="grid grid-cols-1 md:grid-cols-3 gap-3">
117 |           {quickActions.map((action) => (
118 |             <button
119 |               key={action.id}
120 |               onClick={() => onQuickAction(action.id)}
121 |               className="flex flex-col items-center p-4 bg-white border border-gray-200 rounded-xl hover:border-[#86a0ff] hover:shadow-sm transition-all duration-200 group"
122 |             >
123 |               <div className="w-10 h-10 rounded-lg flex items-center justify-center mb-2 transition-colors text-gray-900 group-hover:text-[#86a0ff]">
124 |                 {action.icon}
125 |               </div>
126 |               <h3 className="font-semibold text-gray-900 mb-1 text-sm">{action.title}</h3>
127 |               <p className="text-xs text-gray-900 text-center">{action.description}</p>
128 |             </button>
129 |           ))}
130 |         </div>
131 |       </div>
132 | 
133 |       {/* System Status */}
134 |       <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
135 |         <div className="bg-white rounded-xl border border-gray-200 p-4">
136 |           <h2 className="text-lg font-semibold text-gray-900 mb-3">System Status</h2>
137 |           <div className="space-y-2">
138 |             <div className="flex items-center justify-between p-2 bg-gray-100 rounded-lg">
139 |               <div className="flex items-center">
140 |                 <div className={cn(
141 |                   'w-3 h-3 rounded-full mr-3',
142 |                   totalDatabases > 0 ? 'bg-green-500' : 'bg-gray-400'
143 |                 )} />
144 |                 <span className="text-sm font-medium text-gray-900">Database Connections</span>
145 |               </div>
146 |               <span className="text-sm text-gray-600">
147 |                 {totalDatabases > 0 ? `${totalDatabases} configured` : 'No databases'}
148 |               </span>
149 |             </div>
150 | 
151 |             <div className="flex items-center justify-between p-2 bg-gray-100 rounded-lg">
152 |               <div className="flex items-center">
153 |                 <div className={cn(
154 |                   'w-3 h-3 rounded-full mr-3',
155 |                   activeConnections > 0 ? 'bg-green-500' : 'bg-gray-400'
156 |                 )} />
157 |                 <span className="text-sm font-medium text-gray-900">Claude Integration</span>
158 |               </div>
159 |               <span className="text-sm text-gray-600">
160 |                 {activeConnections > 0 ? `${activeConnections} active` : 'Not connected'}
161 |               </span>
162 |             </div>
163 | 
164 |             <div className="flex items-center justify-between p-2 bg-gray-100 rounded-lg">
165 |               <div className="flex items-center">
166 |                 <div className={cn(
167 |                   'w-3 h-3 rounded-full mr-3',
168 |                   totalEnvironments > 0 ? 'bg-green-500' : 'bg-gray-400'
169 |                 )} />
170 |                 <span className="text-sm font-medium text-gray-900">Environment Setup</span>
171 |               </div>
172 |               <span className="text-sm text-gray-600">
173 |                 {totalEnvironments > 0 ? `${totalEnvironments} environments` : 'No environments'}
174 |               </span>
175 |             </div>
176 |           </div>
177 |         </div>
178 | 
179 |         {/* Environment Breakdown */}
180 |         <div className="bg-white rounded-xl border border-gray-200 p-4">
181 |           <div className="flex items-center justify-between mb-3">
182 |             <h2 className="text-lg font-semibold text-gray-900">Environment Distribution</h2>
183 |             <button
184 |               onClick={() => setShowEnvironmentManager(true)}
185 |               className="text-sm text-blue-600 hover:text-blue-800 flex items-center"
186 |             >
187 |               <svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
188 |                 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
189 |               </svg>
190 |               Manage Environments
191 |             </button>
192 |           </div>
193 |           <div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-3">
194 |             {Object.entries(environmentBreakdown).map(([env, count]) => (
195 |               <div key={env} className="text-center p-3 bg-gray-100 rounded-lg">
196 |                 <div className="text-xl font-bold text-gray-900">{count}</div>
197 |                 <div className="text-xs text-gray-600">{env}</div>
198 |               </div>
199 |             ))}
200 |             {Object.keys(environmentBreakdown).length === 0 && (
201 |               <div className="col-span-full text-center text-gray-500 py-6">
202 |                 <svg className="w-10 h-10 mx-auto text-gray-300 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
203 |                   <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
204 |                 </svg>
205 |                 <p className="text-sm">No environments configured yet</p>
206 |                 <button
207 |                   onClick={() => setShowEnvironmentManager(true)}
208 |                   className="mt-2 text-blue-600 hover:text-blue-800 text-xs"
209 |                 >
210 |                   Click to add environments
211 |                 </button>
212 |               </div>
213 |             )}
214 |           </div>
215 |         </div>
216 |       </div>
217 | 
218 |       {/* Claude Integration Status */}
219 |       <div className="bg-white rounded-xl border border-gray-200 p-4">
220 |         <div className="flex items-center justify-between mb-3">
221 |           <h2 className="text-lg font-semibold text-gray-900">Claude Desktop Integration</h2>
222 |           <div className="flex items-center">
223 |             <div className={cn(
224 |               'w-2 h-2 rounded-full mr-2',
225 |               activeConnections > 0 ? 'bg-green-500' : 'bg-gray-300'
226 |             )} />
227 |             <span className="text-sm text-gray-600">
228 |               {activeConnections > 0 ? 'Connected' : 'Disconnected'}
229 |             </span>
230 |           </div>
231 |         </div>
232 |         
233 |         <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
234 |           <div className="p-3 bg-green-50 rounded-lg">
235 |             <div className="text-base font-semibold text-green-900">{activeConnections}</div>
236 |             <div className="text-xs text-green-700">Active Connections</div>
237 |           </div>
238 |           <div className="p-3 bg-blue-50 rounded-lg">
239 |             <div className="text-base font-semibold text-blue-900">{Math.max(0, totalDatabases - activeConnections)}</div>
240 |             <div className="text-xs text-blue-700">Available to Add</div>
241 |           </div>
242 |         </div>
243 |       </div>
244 | 
245 |       {/* Environment Manager Modal */}
246 |       <EnvironmentManager
247 |         isOpen={showEnvironmentManager}
248 |         onClose={() => setShowEnvironmentManager(false)}
249 |         onSave={(environments) => {
250 |           // This would update the environments in the app state
251 |       
252 |           // In a real app, you'd update the global state here
253 |         }}
254 |       />
255 |     </div>
256 |   );
257 | };
258 | 
259 | export default DashboardView;
260 | 
```

--------------------------------------------------------------------------------
/hana-mcp-ui/src/index.css:
--------------------------------------------------------------------------------

```css
  1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
  2 | @tailwind base;
  3 | @tailwind components;
  4 | @tailwind utilities;
  5 | 
  6 | 
  7 | /* Professional Light Theme Base Styles */
  8 | @layer base {
  9 |   * {
 10 |     @apply border-gray-200;
 11 |   }
 12 |   
 13 |   html {
 14 |     @apply scroll-smooth overflow-hidden;
 15 |   }
 16 |   
 17 |   body {
 18 |     @apply bg-gray-100 text-gray-900 font-sans;
 19 |     @apply h-screen overflow-hidden;
 20 |     font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
 21 |     -webkit-font-smoothing: antialiased;
 22 |     -moz-osx-font-smoothing: grayscale;
 23 |   }
 24 | 
 25 |   /* Enhanced Typography System */
 26 |   h1, .h1 {
 27 |     @apply text-4xl font-bold text-gray-900 leading-tight tracking-tight;
 28 |   }
 29 |   
 30 |   h2, .h2 {
 31 |     @apply text-3xl font-semibold text-gray-800 leading-tight tracking-tight;
 32 |   }
 33 |   
 34 |   h3, .h3 {
 35 |     @apply text-2xl font-semibold text-gray-800 leading-snug tracking-tight;
 36 |   }
 37 |   
 38 |   h4, .h4 {
 39 |     @apply text-xl font-medium text-gray-700 leading-snug;
 40 |   }
 41 |   
 42 |   h5, .h5 {
 43 |     @apply text-lg font-medium text-gray-700 leading-normal;
 44 |   }
 45 |   
 46 |   h6, .h6 {
 47 |     @apply text-base font-medium text-gray-700 leading-normal;
 48 |   }
 49 | 
 50 |   /* Body text styles */
 51 |   p, .body-text {
 52 |     @apply text-base text-gray-600 leading-relaxed;
 53 |   }
 54 |   
 55 |   .body-text-sm {
 56 |     @apply text-sm text-gray-600 leading-relaxed;
 57 |   }
 58 |   
 59 |   .body-text-lg {
 60 |     @apply text-lg text-gray-600 leading-relaxed;
 61 |   }
 62 | 
 63 |   /* Special text styles */
 64 |   .display-text {
 65 |     @apply text-5xl font-black text-gray-900 leading-none tracking-tight;
 66 |   }
 67 |   
 68 |   .hero-text {
 69 |     @apply text-6xl font-black text-gray-900 leading-none tracking-tight;
 70 |   }
 71 |   
 72 |   .caption-text {
 73 |     @apply text-sm font-medium text-gray-500 leading-tight;
 74 |   }
 75 |   
 76 |   .label-text {
 77 |     @apply text-sm font-semibold text-gray-700 uppercase tracking-wide;
 78 |   }
 79 | 
 80 |   /* Link styles */
 81 |   a {
 82 |     @apply text-blue-600 hover:text-blue-700 transition-colors duration-200;
 83 |   }
 84 |   
 85 |   /* Custom scrollbar */
 86 |   ::-webkit-scrollbar {
 87 |     @apply w-2;
 88 |   }
 89 |   
 90 |   ::-webkit-scrollbar-track {
 91 |     @apply bg-gray-100;
 92 |   }
 93 |   
 94 |   ::-webkit-scrollbar-thumb {
 95 |     @apply bg-gray-300 rounded-full;
 96 |   }
 97 |   
 98 |   ::-webkit-scrollbar-thumb:hover {
 99 |     @apply bg-gray-400;
100 |   }
101 |   
102 |   /* Enhanced scrollbar for modal content */
103 |   .modal-scrollbar::-webkit-scrollbar {
104 |     @apply w-3;
105 |   }
106 |   
107 |   .modal-scrollbar::-webkit-scrollbar-track {
108 |     @apply bg-gray-100 rounded-lg;
109 |   }
110 |   
111 |   .modal-scrollbar::-webkit-scrollbar-thumb {
112 |     @apply bg-gray-300 rounded-lg;
113 |   }
114 |   
115 |   .modal-scrollbar::-webkit-scrollbar-thumb:hover {
116 |     @apply bg-gray-500;
117 |   }
118 |   
119 |   .modal-scrollbar::-webkit-scrollbar-corner {
120 |     @apply bg-transparent;
121 |   }
122 | }
123 | 
124 | @layer components {
125 |   /* Professional Light Card Base */
126 |   .glass-card {
127 |     @apply bg-white rounded-2xl border border-gray-200;
128 |     @apply shadow-sm shadow-gray-100/50 transition-all duration-300;
129 |     @apply hover:shadow-md hover:shadow-gray-200/60 hover:-translate-y-0.5;
130 |   }
131 |   
132 |   .glass-card-primary {
133 |     @apply bg-blue-50 border-blue-200/60;
134 |   }
135 |   
136 |   .glass-card-success {
137 |     @apply bg-emerald-50 border-emerald-200/60;
138 |   }
139 |   
140 |   .glass-card-warning {
141 |     @apply bg-amber-50 border-amber-200/60;
142 |   }
143 |   
144 |   .glass-card-danger {
145 |     @apply bg-red-50 border-red-200/60;
146 |   }
147 |   
148 |   /* Professional Buttons - Matching Image Theme */
149 |   .btn-gradient {
150 |     @apply relative inline-flex items-center justify-center gap-2;
151 |     @apply rounded-lg font-semibold px-6 py-2.5;
152 |     @apply transition-all duration-200 overflow-hidden;
153 |     @apply hover:-translate-y-0.5 active:translate-y-0 active:scale-95;
154 |     @apply disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none;
155 |   }
156 |   
157 |   .btn-primary {
158 |     @apply bg-[#86a0ff] text-white;
159 |     @apply shadow-sm hover:shadow-md hover:bg-[#7990e6];
160 |     @apply hover:shadow-[#86a0ff]/20;
161 |   }
162 |   
163 |   .btn-secondary {
164 |     @apply bg-gray-100 border border-gray-200 text-gray-700;
165 |     @apply shadow-sm hover:shadow-md hover:bg-gray-200;
166 |     @apply hover:border-gray-300;
167 |   }
168 |   
169 |   .btn-success {
170 |     @apply bg-emerald-600 text-white;
171 |     @apply shadow-sm hover:shadow-md hover:bg-emerald-700;
172 |     @apply hover:shadow-emerald-200/60;
173 |   }
174 |   
175 |   .btn-danger {
176 |     @apply bg-red-600 text-white;
177 |     @apply shadow-sm hover:shadow-md hover:bg-red-700;
178 |     @apply hover:shadow-red-200/60;
179 |   }
180 | 
181 |   .btn-warning {
182 |     @apply bg-amber-500 text-white;
183 |     @apply shadow-sm hover:shadow-md hover:bg-amber-600;
184 |     @apply hover:shadow-amber-200/60;
185 |   }
186 | 
187 |   .btn-light {
188 |     @apply bg-gray-100 border border-gray-200 text-gray-700;
189 |     @apply shadow-sm hover:shadow-md hover:bg-gray-200;
190 |     @apply hover:border-gray-300;
191 |   }
192 |   
193 |   /* Form Elements */
194 |   .form-input {
195 |     @apply w-full px-4 py-3 bg-white border border-gray-300;
196 |     @apply rounded-lg text-gray-900 placeholder-gray-500;
197 |     @apply transition-all duration-200 font-medium;
198 |     @apply focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20;
199 |     @apply focus:outline-none;
200 |   }
201 |   
202 |   .form-select {
203 |     @apply form-input;
204 |     @apply appearance-none bg-no-repeat bg-right bg-[length:16px];
205 |     @apply bg-[url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")];
206 |   }
207 |   
208 |   .form-checkbox {
209 |     @apply w-4 h-4 text-blue-600 bg-white border-gray-300;
210 |     @apply rounded focus:ring-blue-500/20 focus:ring-2;
211 |   }
212 |   
213 |   /* Status Badges - Matching Image Colors */
214 |   .status-badge {
215 |     @apply inline-flex items-center gap-2 px-3 py-1 rounded-full;
216 |     @apply text-sm font-semibold border;
217 |   }
218 |   
219 |   .status-online {
220 |     @apply bg-emerald-100 text-emerald-800 border-emerald-200;
221 |   }
222 |   
223 |   .status-offline {
224 |     @apply bg-gray-100 text-gray-700 border-gray-200;
225 |   }
226 |   
227 |   .status-warning {
228 |     @apply bg-amber-100 text-amber-800 border-amber-200;
229 |   }
230 |   
231 |   .status-error {
232 |     @apply bg-red-100 text-red-800 border-red-200;
233 |   }
234 | 
235 |   .status-pending {
236 |     @apply bg-amber-100 text-amber-800 border-amber-200;
237 |   }
238 | 
239 |   .status-done {
240 |     @apply bg-blue-100 text-blue-800 border-blue-200;
241 |   }
242 |   
243 |   /* Environment Badges */
244 |   .env-production {
245 |     @apply bg-red-100 border-red-300 text-red-800;
246 |   }
247 |   
248 |   .env-development {
249 |     @apply bg-blue-100 border-blue-300 text-blue-800;
250 |   }
251 |   
252 |   .env-staging {
253 |     @apply bg-amber-100 border-amber-300 text-amber-800;
254 |   }
255 | 
256 |   /* Dashboard-specific typography classes */
257 |   .metric-value {
258 |     @apply text-3xl font-bold text-gray-900 leading-none tracking-tight;
259 |   }
260 |   
261 |   .metric-label {
262 |     @apply text-sm font-semibold text-gray-600 uppercase tracking-wide;
263 |   }
264 |   
265 |   .metric-description {
266 |     @apply text-sm font-medium text-gray-500 leading-relaxed;
267 |   }
268 |   
269 |   .card-title {
270 |     @apply text-lg font-semibold text-gray-800 leading-tight;
271 |   }
272 |   
273 |   .card-subtitle {
274 |     @apply text-sm font-medium text-gray-600 leading-relaxed;
275 |   }
276 |   
277 |   .table-header {
278 |     @apply text-sm font-semibold text-gray-700 uppercase tracking-wide;
279 |   }
280 |   
281 |   .table-cell {
282 |     @apply text-sm font-medium text-gray-600 leading-relaxed;
283 |   }
284 | }
285 | 
286 | @layer utilities {
287 |   .text-gradient {
288 |     @apply bg-gradient-to-r from-[#86a0ff] to-[#7990e6] bg-clip-text text-transparent;
289 |   }
290 |   
291 |   .border-gradient {
292 |     @apply bg-gradient-to-r from-blue-200/50 to-blue-300/50;
293 |     @apply border border-transparent bg-clip-padding;
294 |   }
295 |   
296 |   /* Animation utilities */
297 |   .animate-in {
298 |     @apply animate-fade-in;
299 |   }
300 |   
301 |   .animate-up {
302 |     @apply animate-slide-up;
303 |   }
304 |   
305 |   .animate-glow {
306 |     @apply animate-pulse-glow;
307 |   }
308 |   
309 |   .animate-float {
310 |     @apply animate-float;
311 |   }
312 | 
313 |   /* Typography utilities */
314 |   .font-thin {
315 |     font-weight: 100;
316 |   }
317 |   
318 |   .font-extralight {
319 |     font-weight: 200;
320 |   }
321 |   
322 |   .font-light {
323 |     font-weight: 300;
324 |   }
325 |   
326 |   .font-normal {
327 |     font-weight: 400;
328 |   }
329 |   
330 |   .font-medium {
331 |     font-weight: 500;
332 |   }
333 |   
334 |   .font-semibold {
335 |     font-weight: 600;
336 |   }
337 |   
338 |   .font-bold {
339 |     font-weight: 700;
340 |   }
341 |   
342 |   .font-extrabold {
343 |     font-weight: 800;
344 |   }
345 |   
346 |   .font-black {
347 |     font-weight: 900;
348 |   }
349 |   
350 |   /* Text truncation utilities */
351 |   .line-clamp-1 {
352 |     overflow: hidden;
353 |     display: -webkit-box;
354 |     -webkit-box-orient: vertical;
355 |     -webkit-line-clamp: 1;
356 |   }
357 |   
358 |   .line-clamp-2 {
359 |     overflow: hidden;
360 |     display: -webkit-box;
361 |     -webkit-box-orient: vertical;
362 |     -webkit-line-clamp: 2;
363 |   }
364 |   
365 |   .line-clamp-3 {
366 |     overflow: hidden;
367 |     display: -webkit-box;
368 |     -webkit-box-orient: vertical;
369 |     -webkit-line-clamp: 3;
370 |   }
371 | }
372 | 
373 | /* Custom background patterns */
374 | .bg-dots {
375 |   background-image: radial-gradient(circle, rgba(99, 102, 241, 0.1) 1px, transparent 1px);
376 |   background-size: 20px 20px;
377 | }
378 | 
379 | /* Glass window specific styles */
380 | .glass-window {
381 |   backdrop-filter: blur(20px);
382 |   -webkit-backdrop-filter: blur(20px);
383 |   background: rgba(255, 255, 255, 0.8);
384 |   border: 1px solid rgba(255, 255, 255, 0.2);
385 | }
386 | 
387 | /* Perfect centering for glass window */
388 | .glass-window-container {
389 |   display: flex;
390 |   align-items: center;
391 |   justify-content: center;
392 |   min-height: 100vh;
393 |   padding: 1rem;
394 | }
395 | 
396 | .glass-window-content {
397 |   width: 100%;
398 |   max-width: 110rem; /* 8xl - maximum width */
399 |   height: calc(100vh - 2rem);
400 |   margin: 0 auto;
401 |   box-sizing: border-box;
402 | }
403 | 
404 | /* Ensure content is contained within the glass window */
405 | .glass-window-content * {
406 |   box-sizing: border-box;
407 | }
408 | 
409 | /* Prevent content overflow */
410 | .glass-window-content > div {
411 |   max-width: 100%;
412 |   overflow: visible;
413 | }
414 | 
415 | /* Custom scrollbar for database list */
416 | .database-list-scrollbar {
417 |   scrollbar-width: thin;
418 |   scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
419 | }
420 | 
421 | .database-list-scrollbar::-webkit-scrollbar {
422 |   width: 6px;
423 | }
424 | 
425 | .database-list-scrollbar::-webkit-scrollbar-track {
426 |   background: transparent;
427 | }
428 | 
429 | .database-list-scrollbar::-webkit-scrollbar-thumb {
430 |   background: rgba(156, 163, 175, 0.5);
431 |   border-radius: 3px;
432 | }
433 | 
434 | .database-list-scrollbar::-webkit-scrollbar-thumb:hover {
435 |   background: rgba(156, 163, 175, 0.7);
436 | }
437 | 
438 | /* Custom scrollbar for Claude table */
439 | .claude-table-scrollbar {
440 |   scrollbar-width: thin;
441 |   scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
442 | }
443 | 
444 | .claude-table-scrollbar::-webkit-scrollbar {
445 |   width: 6px;
446 | }
447 | 
448 | .claude-table-scrollbar::-webkit-scrollbar-track {
449 |   background: transparent;
450 | }
451 | 
452 | .claude-table-scrollbar::-webkit-scrollbar-thumb {
453 |   background: rgba(156, 163, 175, 0.5);
454 |   border-radius: 3px;
455 | }
456 | 
457 | .claude-table-scrollbar::-webkit-scrollbar-thumb:hover {
458 |   background: rgba(156, 163, 175, 0.7);
459 | }
460 | 
461 | 
462 | 
463 | .bg-grid {
464 |   background-image: 
465 |     linear-gradient(rgba(99, 102, 241, 0.05) 1px, transparent 1px),
466 |     linear-gradient(90deg, rgba(99, 102, 241, 0.05) 1px, transparent 1px);
467 |   background-size: 20px 20px;
468 | }
469 | 
470 | /* Loading spinner */
471 | @keyframes spin {
472 |   to {
473 |     transform: rotate(360deg);
474 |   }
475 | }
476 | 
477 | .loading-spinner {
478 |   @apply inline-block w-6 h-6 border-2 border-gray-200 border-t-blue-600 rounded-full;
479 |   animation: spin 1s linear infinite;
480 | }
481 | /* Shine effect for buttons */
482 | .btn-shine::before {
483 |   content: '';
484 |   @apply absolute inset-0 bg-gradient-to-r from-transparent via-white/40 to-transparent;
485 |   @apply opacity-0 transition-all duration-500 -skew-x-12;
486 |   transform: translateX(-100%);
487 | }
488 | 
489 | .btn-shine:hover::before {
490 |   @apply opacity-100;
491 |   transform: translateX(100%);
492 | }
493 | 
```

--------------------------------------------------------------------------------
/hana-mcp-ui/src/components/ConnectionDetailsModal.jsx:
--------------------------------------------------------------------------------

```javascript
  1 | import { useEffect } from 'react';
  2 | import { motion } from 'framer-motion';
  3 | import { EnvironmentBadge } from './ui/StatusBadge';
  4 | import { DatabaseTypeBadge } from './ui';
  5 | import { detectDatabaseType, getDatabaseTypeDisplayName } from '../utils/databaseTypes';
  6 | 
  7 | const ConnectionDetailsModal = ({ isOpen, onClose, connection }) => {
  8 |   useEffect(() => {
  9 |     if (!isOpen) return;
 10 |     const onKeyDown = (e) => {
 11 |       if (e.key === 'Escape') onClose();
 12 |     };
 13 |     window.addEventListener('keydown', onKeyDown);
 14 |     return () => window.removeEventListener('keydown', onKeyDown);
 15 |   }, [isOpen, onClose]);
 16 | 
 17 |   if (!isOpen || !connection) return null;
 18 | 
 19 |   // Detect database type from connection data
 20 |   const databaseType = detectDatabaseType(connection.env || {});
 21 | 
 22 |   return (
 23 |     <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
 24 |       <motion.div
 25 |         initial={{ opacity: 0, scale: 0.95 }}
 26 |         animate={{ opacity: 1, scale: 1 }}
 27 |         exit={{ opacity: 0, scale: 0.95 }}
 28 |         className="bg-white rounded-xl shadow-2xl p-6 w-full max-w-4xl max-h-[90vh] overflow-hidden"
 29 |       >
 30 |         <div className="flex items-center justify-between mb-4">
 31 |           <div className="flex items-center space-x-3">
 32 |             <div className="h-10 w-10 bg-blue-50 rounded-full flex items-center justify-center">
 33 |               <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
 34 |                 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
 35 |               </svg>
 36 |             </div>
 37 |             <div>
 38 |               <h2 className="text-xl font-semibold text-gray-900">{connection.name}</h2>
 39 |               <p className="text-sm text-gray-500">Database Connection Details</p>
 40 |             </div>
 41 |           </div>
 42 |           <button
 43 |             onClick={onClose}
 44 |             className="text-gray-400 hover:text-gray-600 transition-colors"
 45 |           >
 46 |             <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
 47 |               <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
 48 |             </svg>
 49 |           </button>
 50 |         </div>
 51 | 
 52 |         {/* Connection Status */}
 53 |         <div className="mb-4">
 54 |           <div className="flex items-center justify-between p-4 bg-green-50 rounded-lg border border-green-200">
 55 |             <div className="flex items-center space-x-3">
 56 |               <div className="w-3 h-3 bg-green-500 rounded-full"></div>
 57 |               <div>
 58 |                 <p className="text-sm font-medium text-green-800">Connected to Claude Desktop</p>
 59 |                 <p className="text-xs text-green-600">Active and available for use</p>
 60 |               </div>
 61 |             </div>
 62 |             <EnvironmentBadge 
 63 |               environment={connection.env?.ENVIRONMENT || connection.environment || 'Development'} 
 64 |               active={true}
 65 |               size="sm" 
 66 |             />
 67 |           </div>
 68 |         </div>
 69 | 
 70 |         {/* Database Type Information */}
 71 |         <div className="mb-4">
 72 |           <div className="flex items-center justify-between p-4 bg-blue-50 rounded-lg border border-blue-200">
 73 |             <div className="flex items-center space-x-3">
 74 |               <DatabaseTypeBadge type={databaseType} size="md" />
 75 |               <div>
 76 |                 <p className="text-sm font-medium text-blue-800">Database Type</p>
 77 |                 <p className="text-xs text-blue-600">{getDatabaseTypeDisplayName(databaseType)}</p>
 78 |               </div>
 79 |             </div>
 80 |           </div>
 81 |         </div>
 82 | 
 83 |         {/* Connection Configuration */}
 84 |         <div className="space-y-4">
 85 |           {/* Basic Connection Settings */}
 86 |           <div>
 87 |             <h3 className="text-lg font-medium text-gray-900 mb-3">Connection Configuration</h3>
 88 |             <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
 89 |               <div className="p-3 bg-gray-50 rounded-lg">
 90 |                 <label className="block text-sm font-medium text-gray-700 mb-1">Host</label>
 91 |                 <p className="text-sm text-gray-900 font-mono break-all">
 92 |                   {connection.env?.HANA_HOST || 'Not configured'}
 93 |                 </p>
 94 |               </div>
 95 |               <div className="p-3 bg-gray-50 rounded-lg">
 96 |                 <label className="block text-sm font-medium text-gray-700 mb-1">Port</label>
 97 |                 <p className="text-sm text-gray-900 font-mono">
 98 |                   {connection.env?.HANA_PORT || '443'}
 99 |                 </p>
100 |               </div>
101 |               <div className="p-3 bg-gray-50 rounded-lg">
102 |                 <label className="block text-sm font-medium text-gray-700 mb-1">User</label>
103 |                 <p className="text-sm text-gray-900 font-mono break-all">
104 |                   {connection.env?.HANA_USER || 'Not set'}
105 |                 </p>
106 |               </div>
107 |               <div className="p-3 bg-gray-50 rounded-lg">
108 |                 <label className="block text-sm font-medium text-gray-700 mb-1">Schema</label>
109 |                 <p className="text-sm text-gray-900 font-mono break-all">
110 |                   {connection.env?.HANA_SCHEMA || 'Not set'}
111 |                 </p>
112 |               </div>
113 |             </div>
114 |             
115 |             {/* MDC-specific fields - show conditionally */}
116 |             {(databaseType === 'mdc_tenant' || databaseType === 'mdc_system') && (
117 |               <div className="mt-4">
118 |                 <h4 className="text-md font-medium text-gray-800 mb-3">MDC Configuration</h4>
119 |                 <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
120 |                   {databaseType === 'mdc_tenant' && connection.env?.HANA_DATABASE_NAME && (
121 |                     <div className="p-3 bg-amber-50 rounded-lg border border-amber-200">
122 |                       <label className="block text-sm font-medium text-amber-800 mb-1">Database Name</label>
123 |                       <p className="text-sm text-amber-900 font-mono break-all">
124 |                         {connection.env.HANA_DATABASE_NAME}
125 |                       </p>
126 |                     </div>
127 |                   )}
128 |                   {connection.env?.HANA_INSTANCE_NUMBER && (
129 |                     <div className="p-3 bg-amber-50 rounded-lg border border-amber-200">
130 |                       <label className="block text-sm font-medium text-amber-800 mb-1">Instance Number</label>
131 |                       <p className="text-sm text-amber-900 font-mono">
132 |                         {connection.env.HANA_INSTANCE_NUMBER}
133 |                       </p>
134 |                     </div>
135 |                   )}
136 |                 </div>
137 |               </div>
138 |             )}
139 |           </div>
140 | 
141 |           {/* Security & SSL Configuration */}
142 |           <div>
143 |             <h3 className="text-lg font-medium text-gray-900 mb-3">Security & SSL Configuration</h3>
144 |             <div className="grid grid-cols-1 md:grid-cols-3 gap-3">
145 |               <div className="p-3 bg-gray-50 rounded-lg">
146 |                 <label className="block text-sm font-medium text-gray-700 mb-1">SSL Enabled</label>
147 |                 <div className="flex items-center space-x-2">
148 |                   <div className={`w-3 h-3 rounded-full ${
149 |                     connection.env?.HANA_SSL === 'true' ? 'bg-green-500' : 'bg-red-500'
150 |                   }`}></div>
151 |                   <p className="text-sm text-gray-900">
152 |                     {connection.env?.HANA_SSL === 'true' ? 'Enabled' : 'Disabled'}
153 |                   </p>
154 |                 </div>
155 |               </div>
156 |               <div className="p-3 bg-gray-50 rounded-lg">
157 |                 <label className="block text-sm font-medium text-gray-700 mb-1">Encryption</label>
158 |                 <div className="flex items-center space-x-2">
159 |                   <div className={`w-3 h-3 rounded-full ${
160 |                     connection.env?.HANA_ENCRYPT === 'true' ? 'bg-green-500' : 'bg-red-500'
161 |                   }`}></div>
162 |                   <p className="text-sm text-gray-900">
163 |                     {connection.env?.HANA_ENCRYPT === 'true' ? 'Enabled' : 'Disabled'}
164 |                   </p>
165 |                 </div>
166 |               </div>
167 |               <div className="p-3 bg-gray-50 rounded-lg">
168 |                 <label className="block text-sm font-medium text-gray-700 mb-1">Certificate Validation</label>
169 |                 <div className="flex items-center space-x-2">
170 |                   <div className={`w-3 h-3 rounded-full ${
171 |                     connection.env?.HANA_VALIDATE_CERT === 'true' ? 'bg-green-500' : 'bg-yellow-500'
172 |                   }`}></div>
173 |                   <p className="text-sm text-gray-900">
174 |                     {connection.env?.HANA_VALIDATE_CERT === 'true' ? 'Enabled' : 'Disabled'}
175 |                   </p>
176 |                 </div>
177 |               </div>
178 |             </div>
179 |           </div>
180 | 
181 |           {/* Logging Configuration */}
182 |           <div>
183 |             <h3 className="text-lg font-medium text-gray-900 mb-3">Logging Configuration</h3>
184 |             <div className="grid grid-cols-1 md:grid-cols-3 gap-3">
185 |               <div className="p-3 bg-gray-50 rounded-lg">
186 |                 <label className="block text-sm font-medium text-gray-700 mb-1">Log Level</label>
187 |                 <p className="text-sm text-gray-900 font-semibold uppercase">
188 |                   {connection.env?.LOG_LEVEL || 'info'}
189 |                 </p>
190 |               </div>
191 |               <div className="p-3 bg-gray-50 rounded-lg">
192 |                 <label className="block text-sm font-medium text-gray-700 mb-1">File Logging</label>
193 |                 <div className="flex items-center space-x-2">
194 |                   <div className={`w-3 h-3 rounded-full ${
195 |                     connection.env?.ENABLE_FILE_LOGGING === 'true' ? 'bg-green-500' : 'bg-red-500'
196 |                   }`}></div>
197 |                   <p className="text-sm text-gray-900">
198 |                     {connection.env?.ENABLE_FILE_LOGGING === 'true' ? 'Enabled' : 'Disabled'}
199 |                   </p>
200 |                 </div>
201 |               </div>
202 |               <div className="p-3 bg-gray-50 rounded-lg">
203 |                 <label className="block text-sm font-medium text-gray-700 mb-1">Console Logging</label>
204 |                 <div className="flex items-center space-x-2">
205 |                   <div className={`w-3 h-3 rounded-full ${
206 |                     connection.env?.ENABLE_CONSOLE_LOGGING === 'true' ? 'bg-green-500' : 'bg-red-500'
207 |                   }`}></div>
208 |                   <p className="text-sm text-gray-900">
209 |                     {connection.env?.ENABLE_CONSOLE_LOGGING === 'true' ? 'Enabled' : 'Disabled'}
210 |                   </p>
211 |                 </div>
212 |               </div>
213 |             </div>
214 |           </div>
215 |         </div>
216 | 
217 |         {/* Action Buttons */}
218 |         <div className="flex justify-end space-x-3 mt-4 pt-4 border-t border-gray-200">
219 |           <button
220 |             onClick={onClose}
221 |             className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors"
222 |           >
223 |             Close
224 |           </button>
225 |         </div>
226 |       </motion.div>
227 |     </div>
228 |   );
229 | };
230 | 
231 | export default ConnectionDetailsModal;
232 | 
```

--------------------------------------------------------------------------------
/hana-mcp-ui/src/components/DatabaseListView.jsx:
--------------------------------------------------------------------------------

```javascript
  1 | import { useState, useMemo } from 'react';
  2 | import { motion, AnimatePresence } from 'framer-motion';
  3 | import SearchAndFilter from './SearchAndFilter';
  4 | import EnhancedServerCard from './EnhancedServerCard';
  5 | import { cn } from '../utils/cn';
  6 | 
  7 | const DatabaseListView = ({
  8 |   hanaServers,
  9 |   claudeServers,
 10 |   activeEnvironments,
 11 |   onEditServer,
 12 |   onAddToClaudeServer,
 13 |   onDeleteServer,
 14 |   onAddDatabase
 15 | }) => {
 16 |   const [searchQuery, setSearchQuery] = useState('');
 17 |   const [selectedDatabase, setSelectedDatabase] = useState(null);
 18 |   const [filters, setFilters] = useState({
 19 |     status: 'all',
 20 |     sortBy: 'name',
 21 |     sortOrder: 'asc'
 22 |   });
 23 | 
 24 |   // Selection handlers
 25 |   const handleDatabaseSelect = (databaseName) => {
 26 |     setSelectedDatabase(databaseName);
 27 |   };
 28 | 
 29 |   const handleEditSelected = () => {
 30 |     if (selectedDatabase && hanaServers[selectedDatabase]) {
 31 |       onEditServer(hanaServers[selectedDatabase]);
 32 |     }
 33 |   };
 34 | 
 35 |   const handleAddToClaudeSelected = () => {
 36 |     if (selectedDatabase) {
 37 |       onAddToClaudeServer(selectedDatabase);
 38 |     }
 39 |   };
 40 | 
 41 |   const handleDeleteSelected = () => {
 42 |     if (selectedDatabase) {
 43 |       onDeleteServer(selectedDatabase);
 44 |       setSelectedDatabase(null);
 45 |     }
 46 |   };
 47 | 
 48 |   // Calculate filter counts
 49 |   const filterCounts = useMemo(() => {
 50 |     const servers = Object.entries(hanaServers);
 51 |     const activeInClaude = Object.keys(activeEnvironments).length;
 52 |     return {
 53 |       total: servers.length,
 54 |       active: servers.filter(([name]) => claudeServers.some(cs => cs.name === name)).length,
 55 |       activeInClaude: activeInClaude,
 56 |       production: servers.filter(([, server]) => server.environments?.Production).length,
 57 |       development: servers.filter(([, server]) => server.environments?.Development).length,
 58 |       staging: servers.filter(([, server]) => server.environments?.Staging).length,
 59 |       activeFilter: filters.status
 60 |     };
 61 |   }, [hanaServers, claudeServers, filters.status, activeEnvironments]);
 62 | 
 63 |   // Filter and sort servers
 64 |   const filteredServers = useMemo(() => {
 65 |     let filtered = Object.entries(hanaServers);
 66 | 
 67 |     // Apply search filter
 68 |     if (searchQuery) {
 69 |       filtered = filtered.filter(([name, server]) =>
 70 |         name.toLowerCase().includes(searchQuery.toLowerCase()) ||
 71 |         server.description?.toLowerCase().includes(searchQuery.toLowerCase())
 72 |       );
 73 |     }
 74 | 
 75 |     // Apply status filter
 76 |     if (filters.status !== 'all') {
 77 |       switch (filters.status) {
 78 |         case 'active':
 79 |           filtered = filtered.filter(([name]) =>
 80 |             claudeServers.some(cs => cs.name === name)
 81 |           );
 82 |           break;
 83 |         case 'production':
 84 |         case 'development':
 85 |         case 'staging':
 86 |           filtered = filtered.filter(([, server]) =>
 87 |             server.environments?.[filters.status.charAt(0).toUpperCase() + filters.status.slice(1)]
 88 |           );
 89 |           break;
 90 |       }
 91 |     }
 92 | 
 93 |     // Apply sorting
 94 |     filtered.sort(([nameA, serverA], [nameB, serverB]) => {
 95 |       let valueA, valueB;
 96 | 
 97 |       switch (filters.sortBy) {
 98 |         case 'name':
 99 |           valueA = nameA.toLowerCase();
100 |           valueB = nameB.toLowerCase();
101 |           break;
102 |         case 'created':
103 |           valueA = new Date(serverA.created || 0);
104 |           valueB = new Date(serverB.created || 0);
105 |           break;
106 |         case 'modified':
107 |           valueA = new Date(serverA.modified || 0);
108 |           valueB = new Date(serverB.modified || 0);
109 |           break;
110 |         case 'environments':
111 |           valueA = Object.keys(serverA.environments || {}).length;
112 |           valueB = Object.keys(serverB.environments || {}).length;
113 |           break;
114 |         default:
115 |           valueA = nameA.toLowerCase();
116 |           valueB = nameB.toLowerCase();
117 |       }
118 | 
119 |       if (filters.sortOrder === 'desc') {
120 |         [valueA, valueB] = [valueB, valueA];
121 |       }
122 | 
123 |       if (valueA < valueB) return -1;
124 |       if (valueA > valueB) return 1;
125 |       return 0;
126 |     });
127 | 
128 |     return filtered;
129 |   }, [hanaServers, claudeServers, searchQuery, filters]);
130 | 
131 | 
132 | 
133 |   const handleFilterChange = (newFilters) => {
134 |     setFilters(prev => ({ ...prev, ...newFilters }));
135 |   };
136 | 
137 |   const handleClearFilters = () => {
138 |     setFilters({
139 |       status: 'all',
140 |       sortBy: 'name',
141 |       sortOrder: 'asc'
142 |     });
143 |     setSearchQuery('');
144 |   };
145 | 
146 |   return (
147 |     <div className="p-6 space-y-6 bg-gray-100 rounded-2xl sm:rounded-3xl overflow-y-auto max-h-full database-list-scrollbar">
148 |       {/* Header */}
149 |       <div className="flex items-center justify-between">
150 |         <div>
151 |           <h1 className="text-2xl font-bold text-gray-900 mb-2">My Local Databases</h1>
152 |           <p className="text-gray-600">
153 |             Manage your HANA database configurations
154 |           </p>
155 |           {filterCounts.activeInClaude > 0 && (
156 |             <div className="flex items-center space-x-2 mt-2">
157 |               <div className="flex items-center space-x-1 text-green-600 bg-green-50 px-3 py-1 rounded-full">
158 |                 <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
159 |                   <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
160 |                 </svg>
161 |                 <span className="text-sm font-medium">
162 |                   {filterCounts.activeInClaude} environment{filterCounts.activeInClaude !== 1 ? 's' : ''} connected to Claude
163 |                 </span>
164 |               </div>
165 |             </div>
166 |           )}
167 |         </div>
168 |         <button
169 |           onClick={onAddDatabase}
170 |           className="flex items-center px-4 py-2 bg-[#86a0ff] text-white text-sm font-medium rounded-lg hover:bg-[#7990e6] transition-colors shadow-sm hover:shadow-md"
171 |         >
172 |           <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
173 |             <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
174 |           </svg>
175 |           Add Database
176 |         </button>
177 |       </div>
178 | 
179 |       {/* Search and Filter Bar */}
180 |       <div className="bg-white rounded-xl border border-gray-100 p-6">
181 |         <SearchAndFilter
182 |           searchQuery={searchQuery}
183 |           onSearchChange={setSearchQuery}
184 |           filters={filters}
185 |           onFiltersChange={setFilters}
186 |           filterCounts={filterCounts}
187 |         />
188 |       </div>
189 | 
190 |       {/* Top Bar with Actions */}
191 |       <div className="bg-white rounded-xl border border-gray-200 p-6">
192 |         <div className="flex items-center justify-between">
193 |           <div className="flex items-center space-x-4">
194 |             <span className="text-sm font-medium text-gray-700">
195 |               {filteredServers.length} of {Object.keys(hanaServers).length} databases
196 |             </span>
197 |           </div>
198 |           
199 |           <div className="flex items-center space-x-3">
200 |             <button
201 |               onClick={handleEditSelected}
202 |               disabled={!selectedDatabase}
203 |               className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
204 |             >
205 |               Edit
206 |             </button>
207 |             
208 |             <button
209 |               onClick={handleAddToClaudeSelected}
210 |               disabled={!selectedDatabase}
211 |               className="px-4 py-2 text-sm font-medium text-white bg-[#86a0ff] border border-[#86a0ff] rounded-lg hover:bg-[#7990e6] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
212 |             >
213 |               Add to Claude
214 |             </button>
215 |             
216 |             <button
217 |               onClick={handleDeleteSelected}
218 |               disabled={!selectedDatabase}
219 |               className="px-4 py-2 text-sm font-medium text-red-600 bg-red-50 border border-red-200 rounded-lg hover:bg-red-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
220 |             >
221 |               Delete
222 |             </button>
223 |           </div>
224 |         </div>
225 |       </div>
226 | 
227 |       {/* Database Table */}
228 |       <div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
229 |         {filteredServers.length === 0 ? (
230 |           <div className="text-center py-12">
231 |             <svg className="w-16 h-16 mx-auto text-gray-300 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
232 |               <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
233 |             </svg>
234 |             <h3 className="text-lg font-medium text-gray-900 mb-2">No databases found</h3>
235 |             <p className="text-gray-600 mb-4">
236 |               {searchQuery ? `No databases match "${searchQuery}"` : 'Get started by adding your first database'}
237 |             </p>
238 |             <button
239 |               onClick={onAddDatabase}
240 |               className="inline-flex items-center px-6 py-3 bg-[#86a0ff] text-white font-medium rounded-lg hover:bg-[#7990e6] transition-colors shadow-sm hover:shadow-md"
241 |             >
242 |               <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
243 |                 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
244 |               </svg>
245 |               Add Your First Database
246 |             </button>
247 |           </div>
248 |         ) : (
249 |           <>
250 |             {/* Table Header */}
251 |             <div className="bg-gray-50 px-6 py-3 border-b border-gray-200">
252 |               <div className="grid grid-cols-12 gap-4 items-center">
253 |                 <div className="col-span-1">
254 |                   {/* Empty header for radio button column */}
255 |                 </div>
256 |                 <div className="col-span-4">
257 |                   <h3 className="text-sm font-semibold text-gray-700 uppercase tracking-wide">Database</h3>
258 |                 </div>
259 |                 <div className="col-span-2">
260 |                   <h3 className="text-sm font-semibold text-gray-700 uppercase tracking-wide">Active Environment</h3>
261 |                 </div>
262 |                 <div className="col-span-2">
263 |                   <h3 className="text-sm font-semibold text-gray-700 uppercase tracking-wide">Environments</h3>
264 |                 </div>
265 |                 <div className="col-span-3">
266 |                   <h3 className="text-sm font-semibold text-gray-700 uppercase tracking-wide">Description</h3>
267 |                 </div>
268 |               </div>
269 |             </div>
270 | 
271 |             {/* Database List */}
272 |             <div className="divide-y divide-gray-200">
273 |               <AnimatePresence>
274 |                 {filteredServers.map(([name, server], index) => (
275 |                   <motion.div
276 |                     key={name}
277 |                     initial={{ opacity: 0, x: -10 }}
278 |                     animate={{ opacity: 1, x: 0 }}
279 |                     exit={{ opacity: 0, x: -10 }}
280 |                     transition={{ duration: 0.2, delay: index * 0.02 }}
281 |                   >
282 |                     <EnhancedServerCard
283 |                       name={name}
284 |                       server={server}
285 |                       index={index}
286 |                       isSelected={selectedDatabase === name}
287 |                       activeEnvironment={activeEnvironments[name]}
288 |                       onSelect={handleDatabaseSelect}
289 |                       onEdit={() => onEditServer(server)}
290 |                       onAddToClaude={() => onAddToClaudeServer(name)}
291 |                       onDelete={() => onDeleteServer(name)}
292 |                     />
293 |                   </motion.div>
294 |                 ))}
295 |               </AnimatePresence>
296 |             </div>
297 |           </>
298 |         )}
299 |       </div>
300 |     </div>
301 |   );
302 | };
303 | 
304 | export default DatabaseListView;
305 | 
```

--------------------------------------------------------------------------------
/hana-mcp-ui/src/components/PathConfigModal.jsx:
--------------------------------------------------------------------------------

```javascript
  1 | import { useState, useEffect } from 'react';
  2 | import { motion, AnimatePresence } from 'framer-motion';
  3 | import { XMarkIcon } from '@heroicons/react/24/outline';
  4 | import { cn } from '../utils/cn';
  5 | 
  6 | // Reusable styling constants (following the same pattern as BackupHistoryModal)
  7 | const BUTTON_STYLES = {
  8 |   primary: "inline-flex items-center gap-2 px-4 py-2 bg-[#86a0ff] text-white rounded-lg text-sm font-medium hover:bg-[#7990e6] transition-colors",
  9 |   secondary: "px-4 py-2 text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
 10 | };
 11 | 
 12 | const MODAL_ANIMATIONS = {
 13 |   backdrop: {
 14 |     initial: { opacity: 0 },
 15 |     animate: { opacity: 1 },
 16 |     exit: { opacity: 0 }
 17 |   },
 18 |   modal: {
 19 |     initial: { scale: 0.95, opacity: 0 },
 20 |     animate: { scale: 1, opacity: 1 },
 21 |     exit: { scale: 0.95, opacity: 0 }
 22 |   }
 23 | };
 24 | 
 25 | // Default configuration paths for different operating systems
 26 | const DEFAULT_PATHS = {
 27 |   windows: [
 28 |     '%APPDATA%\\Claude\\claude_desktop_config.json',
 29 |     '%APPDATA%\\Claude\\desktop\\claude_desktop_config.json',
 30 |     '%LOCALAPPDATA%\\Claude\\claude_desktop_config.json',
 31 |     'C:\\Users\\%USERNAME%\\AppData\\Roaming\\Claude\\claude_desktop_config.json',
 32 |     'C:\\Users\\%USERNAME%\\AppData\\Local\\Claude\\claude_desktop_config.json'
 33 |   ],
 34 |   mac: [
 35 |     '~/Library/Application Support/Claude/claude_desktop_config.json',
 36 |     '~/Library/Application Support/Claude/desktop/claude_desktop_config.json',
 37 |     '/Users/$USER/Library/Application Support/Claude/claude_desktop_config.json',
 38 |     '/Users/$USER/Library/Application Support/Claude/desktop/claude_desktop_config.json',
 39 |     '/Users/$USER/.config/claude/claude_desktop_config.json'
 40 |   ],
 41 |   linux: [
 42 |     '~/.config/claude/claude_desktop_config.json',
 43 |     '/home/$USER/.config/claude/claude_desktop_config.json',
 44 |     '/home/$USER/.local/share/claude/claude_desktop_config.json'
 45 |   ]
 46 | };
 47 | 
 48 | const PathConfigModal = ({ 
 49 |   isOpen, 
 50 |   onClose, 
 51 |   onConfigPathChange, 
 52 |   currentPath = '' 
 53 | }) => {
 54 |   const [pathInput, setPathInput] = useState(currentPath);
 55 |   const [isSubmitting, setIsSubmitting] = useState(false);
 56 |   const [detectedOS, setDetectedOS] = useState('mac');
 57 | 
 58 |   // Detect OS
 59 |   useEffect(() => {
 60 |     const userAgent = navigator.userAgent;
 61 |     let os = 'mac';
 62 |     if (userAgent.includes('Windows')) os = 'windows';
 63 |     else if (userAgent.includes('Linux')) os = 'linux';
 64 |     
 65 |     setDetectedOS(os);
 66 |   }, []);
 67 | 
 68 |   // Reset form when modal opens/closes
 69 |   useEffect(() => {
 70 |     if (isOpen) {
 71 |       setPathInput(currentPath);
 72 |       setIsSubmitting(false);
 73 |     }
 74 |   }, [isOpen, currentPath]);
 75 | 
 76 |   // Handle escape key
 77 |   useEffect(() => {
 78 |     if (!isOpen) return;
 79 |     const onKeyDown = (e) => {
 80 |       if (e.key === 'Escape') {
 81 |         onClose();
 82 |       }
 83 |     };
 84 |     window.addEventListener('keydown', onKeyDown);
 85 |     return () => window.removeEventListener('keydown', onKeyDown);
 86 |   }, [isOpen, onClose]);
 87 | 
 88 |   const selectPath = (path) => {
 89 |     // Replace environment variables with actual values for better user experience
 90 |     let resolvedPath = path;
 91 |     
 92 |     if (detectedOS === 'mac' || detectedOS === 'linux') {
 93 |       // For Mac/Linux, replace $USER with actual username if we can detect it
 94 |       // Try to get username from common sources
 95 |       let username = 'YourUsername';
 96 |       
 97 |       // Try to get username from localStorage if previously set
 98 |       const savedUsername = localStorage.getItem('claude_username');
 99 |       if (savedUsername) {
100 |         username = savedUsername;
101 |       } else {
102 |         // Try to extract username from common patterns
103 |         if (detectedOS === 'mac') {
104 |           // For Mac, try to get username from common locations
105 |           username = 'YourUsername';
106 |         } else if (detectedOS === 'linux') {
107 |           username = 'YourUsername';
108 |         }
109 |       }
110 |       
111 |       resolvedPath = path.replace(/\$USER/g, username);
112 |     }
113 |     
114 |     setPathInput(resolvedPath);
115 |   };
116 | 
117 |   const handleSubmit = async () => {
118 |     if (!pathInput.trim()) {
119 |       alert('Please select or enter a configuration path');
120 |       return;
121 |     }
122 | 
123 |     setIsSubmitting(true);
124 |     try {
125 |       if (onConfigPathChange) {
126 |         await onConfigPathChange(pathInput.trim());
127 |       }
128 |       onClose();
129 |     } catch (error) {
130 |       console.error('Error updating config path:', error);
131 |       alert('Failed to update configuration path. Please try again.');
132 |     } finally {
133 |       setIsSubmitting(false);
134 |     }
135 |   };
136 | 
137 |   if (!isOpen) return null;
138 | 
139 |   return (
140 |     <AnimatePresence>
141 |       <motion.div
142 |         {...MODAL_ANIMATIONS.backdrop}
143 |         className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
144 |         onClick={onClose}
145 |       >
146 |         <motion.div
147 |           {...MODAL_ANIMATIONS.modal}
148 |           onClick={(e) => e.stopPropagation()}
149 |           className="bg-white rounded-xl shadow-xl w-full max-w-2xl max-h-[90vh] flex flex-col"
150 |         >
151 |           {/* Header */}
152 |           <div className="px-6 py-3 border-b border-gray-200">
153 |             <div className="flex items-center justify-between">
154 |               <div>
155 |                 <h2 className="text-xl font-semibold text-gray-900">Configure Claude Desktop Path</h2>
156 |                 <p className="text-sm text-gray-600">Select or enter the path to your Claude Desktop configuration file</p>
157 |               </div>
158 |               <button
159 |                 onClick={onClose}
160 |                 className="text-gray-400 hover:text-gray-600 transition-colors"
161 |               >
162 |                 <XMarkIcon className="w-5 h-5" />
163 |               </button>
164 |             </div>
165 |           </div>
166 | 
167 |           {/* Content */}
168 |           <div className="p-6 space-y-4 flex-1 overflow-y-auto">
169 |             {/* Path Input */}
170 |             <div className="space-y-3">
171 |               <div className="space-y-2">
172 |                 <label htmlFor="pathInput" className="text-sm font-medium text-gray-700">
173 |                   Configuration Path
174 |                 </label>
175 |                 <input
176 |                   type="text"
177 |                   id="pathInput"
178 |                   value={pathInput}
179 |                   onChange={(e) => setPathInput(e.target.value)}
180 |                   placeholder="Select a path below or enter custom path"
181 |                   className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent font-mono text-sm"
182 |                 />
183 |                 <p className="text-xs text-gray-500">
184 |                   The selected path will be used to locate your Claude Desktop configuration
185 |                 </p>
186 |               </div>
187 | 
188 |               {/* Selectable Path Locations */}
189 |               <div className="space-y-2">
190 |                 <h4 className="text-sm font-medium text-gray-700">
191 |                   📁 Common Claude Desktop Config Locations for {detectedOS === 'windows' ? 'Windows' : detectedOS === 'mac' ? 'macOS' : 'Linux'}:
192 |                 </h4>
193 |                 <div className="grid gap-2">
194 |                   {DEFAULT_PATHS[detectedOS].map((path, index) => (
195 |                     <div
196 |                       key={index}
197 |                       className={cn(
198 |                         "border rounded-lg p-3 transition-all duration-200",
199 |                         pathInput === path
200 |                           ? "border-blue-500 bg-blue-50"
201 |                           : "border-gray-200 bg-white hover:border-blue-300 hover:bg-blue-50"
202 |                       )}
203 |                     >
204 |                       <div className="flex items-center justify-between">
205 |                         <div className="flex-1">
206 |                           <code className="text-sm font-mono text-gray-700 break-all">
207 |                             {path}
208 |                           </code>
209 |                         </div>
210 |                         <button
211 |                           onClick={() => selectPath(path)}
212 |                           className={cn(
213 |                             "ml-3 px-3 py-1.5 text-xs font-medium rounded-md transition-colors",
214 |                             pathInput === path
215 |                               ? "bg-blue-600 text-white"
216 |                               : "bg-gray-100 text-gray-700 hover:bg-blue-100 hover:text-blue-700"
217 |                           )}
218 |                         >
219 |                           {pathInput === path ? 'Selected' : 'Select'}
220 |                         </button>
221 |                       </div>
222 |                     </div>
223 |                   ))}
224 |                 </div>
225 |                 <p className="text-xs text-gray-500">
226 |                   💡 Click "Select" next to any path above to choose it, then click "Update Path" below to save
227 |                 </p>
228 |               </div>
229 |             </div>
230 | 
231 |             {/* Help Section */}
232 |             <div className="p-3 bg-blue-50 border border-blue-200 rounded-lg">
233 |                               <h4 className="text-sm font-medium text-blue-900 mb-1.5">💡 How to find your config file:</h4>
234 |               <div className="text-sm text-blue-700 space-y-0.5">
235 |                 {detectedOS === 'windows' ? (
236 |                   <>
237 |                     <p>• <strong>Windows:</strong> Check these locations:</p>
238 |                     <ul className="ml-4 space-y-0.5">
239 |                       <li>• <code className="bg-blue-100 px-1 rounded">%APPDATA%\\Claude\\</code> (usually C:\Users\YourUsername\AppData\Roaming\Claude\)</li>
240 |                       <li>• <code className="bg-blue-100 px-1 rounded">%LOCALAPPDATA%\\Claude\\</code> (usually C:\Users\YourUsername\AppData\Local\Claude\)</li>
241 |                       <li>• <code className="bg-blue-100 px-1 rounded">C:\\Users\\YourUsername\\AppData\\Roaming\\Claude\\</code></li>
242 |                     </ul>
243 |                   </>
244 |                 ) : detectedOS === 'mac' ? (
245 |                   <>
246 |                     <p>• <strong>macOS:</strong> Check these locations:</p>
247 |                     <ul className="ml-4 space-y-1">
248 |                       <li>• <code className="bg-blue-100 px-1 rounded">~/Library/Application Support/Claude/</code></li>
249 |                       <li>• <code className="bg-blue-100 px-1 rounded">/Users/YourUsername/Library/Application Support/Claude/</code></li>
250 |                     </ul>
251 |                   </>
252 |                 ) : (
253 |                   <>
254 |                     <p>• <strong>Linux:</strong> Check these locations:</p>
255 |                     <ul className="ml-4 space-y-1">
256 |                       <li>• <code className="bg-blue-100 px-1 rounded">~/.config/claude/</code></li>
257 |                       <li>• <code className="bg-blue-100 px-1 rounded">/home/YourUsername/.config/claude/</code></li>
258 |                     </ul>
259 |                   </>
260 |                 )}
261 |                 <p className="mt-2">• Look for a file named <code className="bg-blue-100 px-1 rounded">claude_desktop_config.json</code></p>
262 |               </div>
263 |             </div>
264 |           </div>
265 | 
266 |           {/* Footer */}
267 |           <div className="flex items-center justify-end gap-3 p-4 border-t border-gray-200">
268 |             <button
269 |               onClick={onClose}
270 |               className={BUTTON_STYLES.secondary}
271 |             >
272 |               Cancel
273 |             </button>
274 |             <button
275 |               onClick={handleSubmit}
276 |               disabled={isSubmitting || !pathInput.trim()}
277 |               className={cn(
278 |                 BUTTON_STYLES.primary,
279 |                 isSubmitting || !pathInput.trim()
280 |                   ? "opacity-50 cursor-not-allowed" 
281 |                   : ""
282 |               )}
283 |             >
284 |               {isSubmitting ? (
285 |                 <>
286 |                   <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
287 |                   Updating...
288 |                 </>
289 |               ) : (
290 |                 'Update Path'
291 |               )}
292 |             </button>
293 |           </div>
294 |         </motion.div>
295 |       </motion.div>
296 |     </AnimatePresence>
297 |   );
298 | };
299 | 
300 | export default PathConfigModal;
301 | 
```
Page 2/3FirstPrevNextLast