Skip to main content
Glama
ServicesCard.tsx8.79 kB
import React, { useState } from 'react'; import { Copy, AlertTriangle, CheckCircle } from 'lucide-react'; interface ServiceConfig { name: 'winbox' | 'ssh' | 'api' | 'api-ssl' | 'www' | 'www-ssl' | 'telnet' | 'ftp'; disabled: boolean; port?: number; address?: string; } interface ServicesParams { services: ServiceConfig[]; } interface ServicesCardProps { onCompile?: (params: ServicesParams) => Promise<string[]>; } export const ServicesCard: React.FC<ServicesCardProps> = ({ onCompile }) => { const [winboxEnabled, setWinboxEnabled] = useState(true); const [winboxAddress, setWinboxAddress] = useState(''); const [sshEnabled, setSshEnabled] = useState(true); const [sshAddress, setSshAddress] = useState(''); const [apiEnabled, setApiEnabled] = useState(false); const [apiAddress, setApiAddress] = useState(''); const [wwwEnabled, setWwwEnabled] = useState(false); const [commands, setCommands] = useState<string[]>([]); const [warnings, setWarnings] = useState<string[]>([]); const [copied, setCopied] = useState(false); const handleGenerate = async () => { const services: ServiceConfig[] = [ { name: 'winbox', disabled: !winboxEnabled, address: winboxAddress || undefined }, { name: 'ssh', disabled: !sshEnabled, address: sshAddress || undefined }, { name: 'api', disabled: !apiEnabled, address: apiAddress || undefined }, { name: 'www', disabled: !wwwEnabled }, ]; const params: ServicesParams = { services }; // Check for lockout risk const newWarnings: string[] = []; if (!winboxEnabled && !sshEnabled) { newWarnings.push('⚠️ LOCKOUT RISK: Disabling both Winbox and SSH will lock you out!'); } if (winboxEnabled && !winboxAddress) { newWarnings.push('⚠️ Security: Winbox is enabled without address restriction. Recommended: restrict to management subnet.'); } if (sshEnabled && !sshAddress) { newWarnings.push('⚠️ Security: SSH is enabled without address restriction. Recommended: restrict to management subnet.'); } setWarnings(newWarnings); if (onCompile) { const result = await onCompile(params); setCommands(result); } else { // Fallback: generate commands locally const cmds: string[] = []; services.forEach(svc => { if (svc.disabled) { cmds.push(`/ip service set ${svc.name} disabled=yes`); } else { const parts = [`/ip service set ${svc.name} disabled=no`]; if (svc.address) { parts[0] += ` address="${svc.address}"`; } cmds.push(parts[0]); } }); setCommands(cmds); } }; const handleCopy = () => { navigator.clipboard.writeText(commands.join('\n')); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return ( <div className="bg-white rounded-lg shadow-md p-6 border border-gray-200"> <div className="flex items-center justify-between mb-4"> <h3 className="text-lg font-semibold text-gray-800">Services Management</h3> <span className="px-2 py-1 bg-yellow-100 text-yellow-700 text-xs font-medium rounded"> Medium Risk </span> </div> <div className="space-y-4 mb-4"> <div className="border border-gray-200 rounded p-3"> <div className="flex items-center justify-between mb-2"> <label className="flex items-center"> <input type="checkbox" checked={winboxEnabled} onChange={(e) => setWinboxEnabled(e.target.checked)} className="mr-2 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" /> <span className="text-sm font-medium text-gray-700">Enable Winbox</span> </label> <span className="text-xs text-gray-500">Port 8291</span> </div> {winboxEnabled && ( <input type="text" value={winboxAddress} onChange={(e) => setWinboxAddress(e.target.value)} placeholder="Allowed addresses (e.g., 192.168.1.0/24)" className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent" /> )} </div> <div className="border border-gray-200 rounded p-3"> <div className="flex items-center justify-between mb-2"> <label className="flex items-center"> <input type="checkbox" checked={sshEnabled} onChange={(e) => setSshEnabled(e.target.checked)} className="mr-2 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" /> <span className="text-sm font-medium text-gray-700">Enable SSH</span> </label> <span className="text-xs text-gray-500">Port 22</span> </div> {sshEnabled && ( <input type="text" value={sshAddress} onChange={(e) => setSshAddress(e.target.value)} placeholder="Allowed addresses (e.g., 192.168.1.0/24)" className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent" /> )} </div> <div className="border border-gray-200 rounded p-3"> <div className="flex items-center justify-between mb-2"> <label className="flex items-center"> <input type="checkbox" checked={apiEnabled} onChange={(e) => setApiEnabled(e.target.checked)} className="mr-2 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" /> <span className="text-sm font-medium text-gray-700">Enable API</span> </label> <span className="text-xs text-gray-500">Port 8728</span> </div> {apiEnabled && ( <input type="text" value={apiAddress} onChange={(e) => setApiAddress(e.target.value)} placeholder="Allowed addresses (e.g., 192.168.1.0/24)" className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent" /> )} </div> <div className="border border-gray-200 rounded p-3"> <label className="flex items-center"> <input type="checkbox" checked={wwwEnabled} onChange={(e) => setWwwEnabled(e.target.checked)} className="mr-2 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" /> <span className="text-sm font-medium text-gray-700">Enable WebFig (WWW)</span> <span className="ml-auto text-xs text-gray-500">Port 80</span> </label> </div> </div> {warnings.length > 0 && ( <div className="mb-4 p-3 bg-yellow-50 border border-yellow-200 rounded"> <div className="flex items-start gap-2"> <AlertTriangle className="w-5 h-5 text-yellow-600 flex-shrink-0 mt-0.5" /> <div className="space-y-1"> {warnings.map((warning, idx) => ( <p key={idx} className="text-sm text-yellow-800">{warning}</p> ))} </div> </div> </div> )} <button onClick={handleGenerate} className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition-colors mb-4" > Generate Commands </button> {commands.length > 0 && ( <div className="mt-4 border-t pt-4"> <div className="flex items-center justify-between mb-2"> <span className="text-sm font-medium text-gray-700"> Generated Commands ({commands.length}) </span> <button onClick={handleCopy} className="flex items-center gap-1 px-3 py-1 text-sm text-blue-600 hover:bg-blue-50 rounded transition-colors" > {copied ? ( <> <CheckCircle className="w-4 h-4" /> <span>Copied!</span> </> ) : ( <> <Copy className="w-4 h-4" /> <span>Copy</span> </> )} </button> </div> <pre className="bg-gray-50 p-3 rounded text-xs font-mono overflow-x-auto border border-gray-200"> {commands.join('\n')} </pre> </div> )} </div> ); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/babasida246/ai-mcp-gateway'

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