Skip to main content
Glama
CustomRulesTab.tsx13.8 kB
import React, { useState, useEffect } from 'react'; interface CustomRule { id: number; engine_name: string; rule_name: string; rule_content: string; enabled: number; category: string | null; description: string | null; created_at: string; updated_at: string; } const CustomRulesTab: React.FC = () => { const [rules, setRules] = useState<CustomRule[]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); const [showAddModal, setShowAddModal] = useState(false); const [showSuccessToast, setShowSuccessToast] = useState(false); const [showDeleteToast, setShowDeleteToast] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [ruleToDelete, setRuleToDelete] = useState<number | null>(null); const [newRule, setNewRule] = useState({ rule_name: '', pattern: '', description: '', }); // Fixed to PII Leak Engine only const ENGINE_NAME = 'pii_leak_engine'; useEffect(() => { loadRules(); }, []); const loadRules = async (showLoading: boolean = true) => { try { if (showLoading) { setLoading(true); } setError(null); const data = await window.electronAPI.getCustomRules(ENGINE_NAME); setRules(data.rules || []); } catch (error) { console.error('Failed to load custom rules:', error); setError(error instanceof Error ? error.message : 'Failed to load custom rules. Make sure the server is running on port 8282.'); setRules([]); } finally { if (showLoading) { setLoading(false); } } }; const handleAddRule = async () => { if (!newRule.rule_name || !newRule.pattern) { alert('Rule name and detection pattern are required'); return; } // Auto-generate YARA rule from user input (category fixed to PII) const rule_content = `rule ${newRule.rule_name} { meta: category = "PII" description = "${newRule.description || newRule.rule_name}" strings: $pattern = "${newRule.pattern}" condition: $pattern }`; try { const data = await window.electronAPI.addCustomRule({ engine_name: ENGINE_NAME, rule_name: newRule.rule_name, rule_content: rule_content, category: 'PII', description: newRule.description || newRule.rule_name, }); if (data.success) { // Close modal and reset form immediately setShowAddModal(false); setNewRule({ rule_name: '', pattern: '', description: '' }); // Show success toast setShowSuccessToast(true); setTimeout(() => setShowSuccessToast(false), 3000); // Reload rules in background loadRules(false); } else { const errorMsg = data.error?.includes('UNIQUE constraint') || data.error?.includes('insert custom rule') ? 'A rule with the same name already exists. Please use a different name.' : `Failed to add rule: ${data.error}`; alert(errorMsg); } } catch (error) { console.error('Failed to add custom rule:', error); alert('Failed to add custom rule'); } }; const handleToggleRule = async (ruleId: number, currentEnabled: number) => { try { const data = await window.electronAPI.toggleCustomRule( ruleId, currentEnabled === 1 ? false : true ); if (data.success) { loadRules(false); } else { alert(`Failed to toggle rule: ${data.error}`); } } catch (error) { console.error('Failed to toggle custom rule:', error); alert('Failed to toggle custom rule'); } }; const handleDeleteRule = (ruleId: number) => { setRuleToDelete(ruleId); setShowDeleteConfirm(true); }; const confirmDelete = async () => { if (ruleToDelete === null) return; setShowDeleteConfirm(false); try { const data = await window.electronAPI.deleteCustomRule(ruleToDelete); if (data.success) { // Show delete success toast setShowDeleteToast(true); setTimeout(() => setShowDeleteToast(false), 3000); // Reload rules in background loadRules(false); } else { alert(`Failed to delete rule: ${data.error}`); } } catch (error) { console.error('Failed to delete custom rule:', error); alert('Failed to delete custom rule'); } finally { setRuleToDelete(null); } }; return ( <div className="space-y-4 relative"> {/* Success Toast */} {showSuccessToast && ( <div className="fixed top-4 right-4 bg-white border-l-4 border-green-500 px-6 py-3 rounded-lg shadow-xl z-50 flex items-center gap-3 animate-slide-left"> <div className="shrink-0 w-8 h-8 bg-green-100 rounded-full flex items-center justify-center"> <svg className="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /> </svg> </div> <span className="text-gray-800 font-medium">Rule added successfully!</span> </div> )} {/* Delete Toast */} {showDeleteToast && ( <div className="fixed top-4 right-4 bg-white border-l-4 border-red-500 px-6 py-3 rounded-lg shadow-xl z-50 flex items-center gap-3 animate-slide-left"> <div className="shrink-0 w-8 h-8 bg-red-100 rounded-full flex items-center justify-center"> <svg className="w-5 h-5 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> </svg> </div> <span className="text-gray-800 font-medium">Rule removed successfully!</span> </div> )} <div className="flex justify-between items-start mb-4"> <div> <h3 className="text-base font-semibold">Custom PII Rules</h3> <p className="text-xs text-gray-600 mt-0.5">Add custom detection patterns</p> </div> <button onClick={() => setShowAddModal(true)} className="px-2.5 py-1 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 whitespace-nowrap flex-shrink-0" > + Add </button> </div> {loading ? ( <div className="text-center py-8">Loading rules...</div> ) : error ? ( <div className="text-center py-8"> <div className="text-red-500 mb-2">Failed to load custom rules</div> <div className="text-sm text-gray-600">{error}</div> <button onClick={() => loadRules()} className="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700" > Retry </button> </div> ) : rules.length === 0 ? ( <div className="text-center py-8 text-gray-500"> No custom rules found for this engine. </div> ) : ( <div className="space-y-3"> {rules.map((rule) => ( <div key={rule.id} className="border border-gray-200 rounded p-3 bg-white" > <div className="flex justify-between items-start mb-2"> <div className="flex-1"> <h4 className="font-semibold text-base">{rule.rule_name}</h4> {rule.description && ( <p className="text-xs text-gray-600 mt-1">{rule.description}</p> )} {rule.category && ( <span className="inline-block mt-1.5 px-2 py-0.5 text-xs bg-blue-100 text-blue-800 rounded"> {rule.category} </span> )} </div> <div className="flex gap-2 ml-4"> <button onClick={() => handleToggleRule(rule.id, rule.enabled)} className={`px-2.5 py-1 rounded text-xs ${ rule.enabled === 1 ? 'bg-green-100 text-green-800 hover:bg-green-200' : 'bg-gray-100 text-gray-800 hover:bg-gray-200' }`} > {rule.enabled === 1 ? 'Enabled' : 'Disabled'} </button> <button onClick={() => handleDeleteRule(rule.id)} className="px-2.5 py-1 bg-red-100 text-red-800 rounded text-xs hover:bg-red-200" > Delete </button> </div> </div> <div className="mt-2 bg-gray-50 p-2 rounded"> <div className="text-xs font-medium text-gray-700 mb-1">Detection Pattern:</div> <div className="text-xs font-mono bg-white px-2 py-1 rounded border border-gray-200"> {rule.rule_content.match(/\$pattern = "(.+?)"/)?.[1] || 'N/A'} </div> </div> <div className="mt-1.5 text-xs text-gray-500"> Created: {new Date(rule.created_at).toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })} </div> </div> ))} </div> )} {/* Add Rule Modal */} {showAddModal && ( <div className="fixed inset-0 bg-white/20 backdrop-blur-sm flex items-center justify-center z-50"> <div className="bg-white rounded-lg p-6 max-w-2xl w-full max-h-[90vh] overflow-y-auto shadow-2xl"> <h3 className="text-xl font-semibold mb-4">Add Custom Detection Rule</h3> <p className="text-sm text-gray-600 mb-4"> Create a simple detection rule by providing a name and the text pattern to detect. </p> <div className="space-y-4"> <div> <label className="block text-sm font-medium mb-1">Rule Name *</label> <input type="text" value={newRule.rule_name} onChange={(e) => setNewRule({ ...newRule, rule_name: e.target.value })} placeholder="e.g., MySecretKey" className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" /> <p className="text-xs text-gray-500 mt-1">A unique name for this rule (no spaces)</p> </div> <div> <label className="block text-sm font-medium mb-1">Detection Pattern *</label> <input type="text" value={newRule.pattern} onChange={(e) => setNewRule({ ...newRule, pattern: e.target.value })} placeholder="e.g., MY_SECRET_KEY" className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" /> <p className="text-xs text-gray-500 mt-1">The text pattern to detect in communications</p> </div> <div> <label className="block text-sm font-medium mb-1">Description (Optional)</label> <input type="text" value={newRule.description} onChange={(e) => setNewRule({ ...newRule, description: e.target.value })} placeholder="e.g., Detects my application secret key" className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> </div> <div className="flex justify-end gap-3 mt-6"> <button onClick={() => { setShowAddModal(false); setNewRule({ rule_name: '', pattern: '', description: '' }); }} className="px-4 py-2 border rounded hover:bg-gray-50" > Cancel </button> <button onClick={handleAddRule} className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700" > Add Rule </button> </div> </div> </div> )} {/* Delete Confirmation Modal */} {showDeleteConfirm && ( <div className="fixed inset-0 bg-white/20 backdrop-blur-sm flex items-center justify-center z-50"> <div className="bg-white rounded-lg p-6 max-w-md w-full shadow-2xl"> <h3 className="text-xl font-semibold mb-4">Delete Rule</h3> <p className="text-sm text-gray-600 mb-6"> Are you sure you want to delete this rule? This action cannot be undone. </p> <div className="flex justify-end gap-3"> <button onClick={() => { setShowDeleteConfirm(false); setRuleToDelete(null); }} className="px-4 py-2 border rounded hover:bg-gray-50" > Cancel </button> <button onClick={confirmDelete} className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700" > Delete </button> </div> </div> </div> )} </div> ); }; export default CustomRulesTab;

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/seungwon9201/MCP-Dandan'

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