/** * Redis比较工具的JavaScript功能 * 支持Redis集群配置、连接测试、数据比较等功能 */ // 全局变量 let currentResults = null; let isQuerying = false; let currentImportTarget = null; // 记录当前导入目标集群 // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', function() { initializePage(); bindEvents(); loadRedisConfigGroups(); loadRedisQueryHistory(); }); /** * 初始化页面 */ function initializePage() { // 加载默认配置 loadDefaultConfig(); // 绑定节点删除事件 bindNodeEvents(); // 绑定查询模式切换事件 bindQueryModeEvents(); } /** * 绑定事件 */ function bindEvents() { // 查询模式切换 document.querySelectorAll('input[name="queryMode"]').forEach(radio => { radio.addEventListener('change', toggleQueryMode); }); // 节点删除按钮事件委托 document.addEventListener('click', function(e) { if (e.target.closest('.remove-node')) { e.target.closest('.node-input').remove(); } }); } /** * 绑定节点相关事件 */ function bindNodeEvents() { // 初始绑定现有的删除按钮(如果只有一个节点则禁用删除) updateNodeDeleteButtons(); } /** * 绑定查询模式切换事件 */ function bindQueryModeEvents() { const randomMode = document.getElementById('randomMode'); const specifiedMode = document.getElementById('specifiedMode'); randomMode.addEventListener('change', toggleQueryMode); specifiedMode.addEventListener('change', toggleQueryMode); } /** * 切换查询模式 */ function toggleQueryMode() { const randomMode = document.getElementById('randomMode').checked; const randomOptions = document.getElementById('randomOptions'); const specifiedOptions = document.getElementById('specifiedOptions'); if (randomMode) { randomOptions.style.display = 'block'; specifiedOptions.style.display = 'none'; } else { randomOptions.style.display = 'none'; specifiedOptions.style.display = 'block'; } } /** * 添加节点 */ function addNode(clusterId) { const container = document.getElementById(`${clusterId}Nodes`); if (!container) { console.error(`节点容器未找到: ${clusterId}Nodes`); return; } const nodeInput = document.createElement('div'); nodeInput.className = 'node-input'; nodeInput.innerHTML = ` `; container.appendChild(nodeInput); updateNodeDeleteButtons(); } /** * 更新节点删除按钮状态 */ function updateNodeDeleteButtons() { // 每个集群至少需要一个节点 ['cluster1', 'cluster2'].forEach(clusterId => { const container = document.getElementById(`${clusterId}Nodes`); if (container) { const nodeInputs = container.querySelectorAll('.node-input'); const deleteButtons = container.querySelectorAll('.remove-node'); deleteButtons.forEach(btn => { btn.disabled = nodeInputs.length <= 1; }); } }); } /** * 获取集群配置 */ function getClusterConfig(clusterId) { const name = document.getElementById(`${clusterId}Name`).value; const password = document.getElementById(`${clusterId}Password`).value; // 获取节点列表 const nodes = []; const nodeInputs = document.querySelectorAll(`#${clusterId}Nodes .node-input`); nodeInputs.forEach(nodeInput => { const host = nodeInput.querySelector('input[type="text"]').value.trim(); const port = parseInt(nodeInput.querySelector('input[type="number"]').value); if (host && port) { nodes.push({ host, port }); } }); // 获取连接参数 const socketTimeout = parseInt(document.getElementById(`${clusterId}SocketTimeout`).value) || 3; const socketConnectTimeout = parseInt(document.getElementById(`${clusterId}SocketConnectTimeout`).value) || 3; const maxConnectionsPerNode = parseInt(document.getElementById(`${clusterId}MaxConnectionsPerNode`).value) || 16; return { name, nodes, password: password || null, socket_timeout: socketTimeout, socket_connect_timeout: socketConnectTimeout, max_connections_per_node: maxConnectionsPerNode }; } /** * 获取查询选项 */ function getQueryOptions() { const randomMode = document.getElementById('randomMode').checked; if (randomMode) { return { mode: 'random', count: parseInt(document.getElementById('sampleCount').value), pattern: document.getElementById('keyPattern').value, source_cluster: document.getElementById('sourceCluster').value }; } else { const keysText = document.getElementById('specifiedKeys').value.trim(); const keys = keysText ? keysText.split('\n').map(k => k.trim()).filter(k => k) : []; return { mode: 'specified', keys: keys }; } } /** * 测试连接 */ async function testConnection(clusterId) { const config = getClusterConfig(clusterId); const clusterName = config.name; if (!config.nodes || config.nodes.length === 0) { showAlert('请至少配置一个Redis节点', 'warning'); return; } try { showLoading(`正在测试${clusterName}连接...`); const response = await fetch('/api/redis/test-connection', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cluster_config: config, cluster_name: clusterName }) }); const result = await response.json(); hideLoading(); if (result.success) { showAlert(`${clusterName}连接成功!连接耗时: ${result.data.connection_time.toFixed(3)}秒`, 'success'); // 高亮成功的集群配置 - 使用正确的ID选择器 const configElement = document.querySelector(`.cluster-config[data-cluster="${clusterId}"]`) || document.querySelector(`#${clusterId}Config`) || document.querySelector(`[id*="${clusterId}"]`); if (configElement) { configElement.classList.add('active'); setTimeout(() => { configElement.classList.remove('active'); }, 2000); } } else { showAlert(`${clusterName}连接失败: ${result.error}`, 'danger'); } } catch (error) { hideLoading(); showAlert(`连接测试失败: ${error.message}`, 'danger'); } } /** * 执行Redis比较 */ async function executeRedisComparison() { if (isQuerying) { showAlert('查询正在进行中,请稍候...', 'warning'); return; } try { // 获取配置 const cluster1Config = getClusterConfig('cluster1'); const cluster2Config = getClusterConfig('cluster2'); const queryOptions = getQueryOptions(); // 验证配置 if (!cluster1Config.nodes || cluster1Config.nodes.length === 0) { showAlert('请配置集群1的Redis节点', 'warning'); return; } if (!cluster2Config.nodes || cluster2Config.nodes.length === 0) { showAlert('请配置集群2的Redis节点', 'warning'); return; } if (queryOptions.mode === 'specified' && (!queryOptions.keys || queryOptions.keys.length === 0)) { showAlert('请输入要查询的Key列表', 'warning'); return; } isQuerying = true; showLoading('正在执行Redis数据比较,请稍候...'); clearResults(); console.log('发送Redis比较请求...'); console.log('集群1配置:', cluster1Config); console.log('集群2配置:', cluster2Config); console.log('查询选项:', queryOptions); const response = await fetch('/api/redis/compare', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cluster1_config: cluster1Config, cluster2_config: cluster2Config, query_options: queryOptions }) }); console.log('Redis比较API响应状态:', response.status); const result = await response.json(); console.log('Redis比较API响应结果:', result); if (response.ok && result.success !== false) { currentResults = result; displayResults(result); showAlert('Redis数据比较完成,历史记录已自动保存!', 'success'); // 刷新历史记录列表(后台已自动保存) loadRedisQueryHistory(); } else { const errorMsg = result.error || `HTTP ${response.status} 错误`; console.error('Redis比较失败:', errorMsg); // 为常见错误提供友好的提示 if (errorMsg.includes('连接失败')) { showAlert(`Redis比较失败: ${errorMsg}。请检查Redis服务器是否运行,网络连接是否正常。`, 'danger'); } else { showAlert(`Redis比较失败: ${errorMsg}`, 'danger'); } } } catch (error) { console.error('Redis比较请求异常:', error); showAlert(`请求失败: ${error.message}。请检查网络连接和服务器状态。`, 'danger'); } finally { isQuerying = false; hideLoading(); } } /** * 显示结果 */ function displayResults(results) { // 显示统计卡片 displayStatsCards(results.stats); // 显示详细结果 displayDifferenceResults(results.different_results || []); displayIdenticalResults(results.identical_results || []); displayMissingResults(results.missing_results || []); displayPerformanceReport(results.performance_report); // 显示原生数据 displayRawData(results); // 更新标签页计数 updateTabCounts(results); // 显示结果区域 - 使用正确的ID const resultSection = document.getElementById('resultSection'); if (resultSection) { resultSection.style.display = 'block'; // 滚动到结果区域 resultSection.scrollIntoView({ behavior: 'smooth' }); } } /** * 显示统计卡片 */ function displayStatsCards(stats) { const container = document.getElementById('stats'); if (!container) { console.error('统计卡片容器未找到'); return; } container.innerHTML = `

${stats.total_keys}

总Key数量

${stats.identical_count}

相同数据

${stats.identical_percentage}%

${stats.different_count}

差异数据

${stats.different_percentage}%

${stats.missing_in_cluster1 + stats.missing_in_cluster2 + stats.both_missing}

缺失数据

${stats.missing_percentage}%
`; } /** * 显示差异结果 */ function displayDifferenceResults(differences) { const container = document.getElementById('differences-content'); if (!container) { console.error('差异结果容器未找到'); return; } if (differences.length === 0) { container.innerHTML = '
未发现数据差异
'; return; } let html = ''; differences.forEach((diff, index) => { const cluster1Type = diff.cluster1_type ? ` (${diff.cluster1_type})` : ''; const cluster2Type = diff.cluster2_type ? ` (${diff.cluster2_type})` : ''; html += `
Key: ${diff.key}
${currentResults.clusters.cluster1_name || '集群1'}${cluster1Type}:
${formatRedisValue(diff.cluster1_value)}
${currentResults.clusters.cluster2_name || '集群2'}${cluster2Type}:
${formatRedisValue(diff.cluster2_value)}
${diff.message}
`; }); container.innerHTML = html; } /** * 显示相同结果 */ function displayIdenticalResults(identical) { const container = document.getElementById('identical-content'); if (!container) { console.error('相同结果容器未找到'); return; } if (identical.length === 0) { container.innerHTML = '
没有相同的数据
'; return; } let html = ''; identical.forEach((item, index) => { const typeInfo = item.type ? ` (${item.type})` : ''; html += `
Key: ${item.key}
值${typeInfo}:
${formatRedisValue(item.value)}
数据一致
`; }); container.innerHTML = html; } /** * 显示缺失结果 */ function displayMissingResults(missing) { const container = document.getElementById('missing-content'); if (!container) { console.error('缺失结果容器未找到'); return; } if (missing.length === 0) { container.innerHTML = '
没有缺失的数据
'; return; } let html = ''; missing.forEach((item, index) => { html += `
Key: ${item.key}
${item.message}
${item.cluster1_value !== undefined && item.cluster1_value !== null ? `
${currentResults.clusters.cluster1_name || '集群1'}${item.cluster1_type ? ` (${item.cluster1_type})` : ''}:
${formatRedisValue(item.cluster1_value)}
` : ''} ${item.cluster2_value !== undefined && item.cluster2_value !== null ? `
${currentResults.clusters.cluster2_name || '集群2'}${item.cluster2_type ? ` (${item.cluster2_type})` : ''}:
${formatRedisValue(item.cluster2_value)}
` : ''}
`; }); container.innerHTML = html; } /** * 显示性能报告 */ function displayPerformanceReport(performanceReport) { const container = document.getElementById('performanceReport'); const connections = performanceReport.connections || {}; const operations = performanceReport.operations || {}; let html = `
性能统计报告
连接统计
`; Object.entries(connections).forEach(([clusterName, status]) => { const statusClass = status.success ? 'success' : 'danger'; const statusText = status.success ? '成功' : '失败'; html += ` `; }); html += `
集群 状态 耗时
${clusterName} ${statusText} ${status.connect_time.toFixed(3)}s
操作统计
`; if (operations.scan_time > 0) { html += ``; } Object.entries(operations.queries || {}).forEach(([operation, duration]) => { html += ``; }); if (operations.comparison_time > 0) { html += ``; } html += `
操作 耗时
扫描Keys${operations.scan_time.toFixed(3)}s
${operation}${duration.toFixed(3)}s
数据比对${operations.comparison_time.toFixed(3)}s
总耗时 ${performanceReport.total_time.toFixed(3)}s
`; container.innerHTML = html; } /** * 更新标签页计数 */ function updateTabCounts(results) { document.getElementById('diff-count').textContent = (results.different_results || []).length; document.getElementById('identical-count').textContent = (results.identical_results || []).length; document.getElementById('missing-count').textContent = (results.missing_results || []).length; } /** * 格式化Redis值显示 */ function formatRedisValue(value) { if (value === null) { return '(null)'; } if (value === undefined) { return '(undefined)'; } // 如果是字符串且看起来像JSON,尝试格式化 if (typeof value === 'string') { try { const parsed = JSON.parse(value); return JSON.stringify(parsed, null, 2); } catch (e) { // 不是JSON,直接返回 return value; } } return String(value); } /** * 加载默认配置 */ async function loadDefaultConfig() { try { const response = await fetch('/api/redis/default-config'); const config = await response.json(); // 这里可以根据需要设置默认值,当前HTML已经包含了合理的默认值 } catch (error) { console.warn('加载默认配置失败:', error); } } /** * 清空结果 */ function clearResults() { const resultSection = document.getElementById('resultSection'); if (resultSection) { resultSection.style.display = 'none'; } currentResults = null; } /** * 显示加载状态 */ function showLoading(message = '正在处理...') { const loadingElement = document.querySelector('.loading') || document.getElementById('loadingIndicator'); if (loadingElement) { const messageElement = loadingElement.querySelector('span') || loadingElement.querySelector('#loadingText'); if (messageElement) { messageElement.textContent = message; } loadingElement.style.display = 'block'; } } /** * 隐藏加载状态 */ function hideLoading() { const loadingElement = document.querySelector('.loading') || document.getElementById('loadingIndicator'); if (loadingElement) { loadingElement.style.display = 'none'; } } /** * 显示提示消息 */ function showAlert(message, type = 'info') { // 移除现有的alert const existingAlert = document.querySelector('.alert-custom'); if (existingAlert) { existingAlert.remove(); } // 创建新的alert const alertDiv = document.createElement('div'); alertDiv.className = `alert alert-${type} alert-dismissible fade show alert-custom`; alertDiv.innerHTML = ` ${message} `; // 插入到页面顶部 const container = document.querySelector('.container'); container.insertBefore(alertDiv, container.firstChild); // 5秒后自动消失 setTimeout(() => { if (alertDiv.parentNode) { alertDiv.remove(); } }, 5000); } /** * Redis配置管理功能 */ // 加载Redis配置组列表 async function loadRedisConfigGroups() { try { const response = await fetch('/api/redis/config-groups'); const result = await response.json(); const select = document.getElementById('redisConfigGroupSelect'); if (!select) { console.error('未找到Redis配置组下拉框元素: redisConfigGroupSelect'); return; } select.innerHTML = ''; if (result.success && result.data) { result.data.forEach(group => { const option = document.createElement('option'); option.value = group.id; option.textContent = `${group.name} - ${group.description || '无描述'}`; select.appendChild(option); }); console.log(`成功加载 ${result.data.length} 个Redis配置组`); } else { console.log('没有找到Redis配置组数据'); } } catch (error) { console.error('加载Redis配置组失败:', error); showAlert('加载Redis配置组失败: ' + error.message, 'danger'); } } // 显示保存Redis配置对话框 function showSaveRedisConfigDialog() { // 生成默认名称 const timestamp = new Date().toLocaleString('zh-CN'); document.getElementById('redisConfigName').value = `Redis配置_${timestamp}`; document.getElementById('redisConfigDescription').value = ''; new bootstrap.Modal(document.getElementById('saveRedisConfigModal')).show(); } // 保存Redis配置组 async function saveRedisConfigGroup() { const name = document.getElementById('redisConfigName').value.trim(); const description = document.getElementById('redisConfigDescription').value.trim(); if (!name) { showAlert('请输入配置组名称', 'warning'); return; } const cluster1Config = getClusterConfig('cluster1'); const cluster2Config = getClusterConfig('cluster2'); const queryOptions = getQueryOptions(); if (!cluster1Config.nodes || cluster1Config.nodes.length === 0) { showAlert('请配置集群1信息', 'warning'); return; } if (!cluster2Config.nodes || cluster2Config.nodes.length === 0) { showAlert('请配置集群2信息', 'warning'); return; } try { const response = await fetch('/api/redis/config-groups', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, description: description, cluster1_config: cluster1Config, cluster2_config: cluster2Config, query_options: queryOptions }) }); const result = await response.json(); if (result.success) { showAlert(result.message, 'success'); bootstrap.Modal.getInstance(document.getElementById('saveRedisConfigModal')).hide(); loadRedisConfigGroups(); // 重新加载配置组列表 } else { showAlert(result.error, 'danger'); } } catch (error) { showAlert(`保存失败: ${error.message}`, 'danger'); } } // 加载选定的Redis配置组 async function loadSelectedRedisConfigGroup() { const groupId = document.getElementById('redisConfigGroupSelect').value; if (!groupId) { showAlert('请选择配置组', 'warning'); return; } try { const response = await fetch(`/api/redis/config-groups/${groupId}`); const result = await response.json(); if (result.success && result.data) { const config = result.data; // 加载集群1配置 loadClusterConfig('cluster1', config.cluster1_config); // 加载集群2配置 loadClusterConfig('cluster2', config.cluster2_config); // 加载查询选项 loadQueryOptions(config.query_options); showAlert(`配置组 "${config.name}" 加载成功`, 'success'); } else { showAlert(result.error, 'danger'); } } catch (error) { showAlert(`加载失败: ${error.message}`, 'danger'); } } // 加载集群配置到界面 function loadClusterConfig(clusterId, config) { // 设置集群名称 const nameElement = document.getElementById(`${clusterId}Name`); if (nameElement && config.name) { nameElement.value = config.name; } // 设置密码 const passwordElement = document.getElementById(`${clusterId}Password`); if (passwordElement && config.password) { passwordElement.value = config.password; } // 清空现有节点 const container = document.getElementById(`${clusterId}Nodes`); if (container) { container.innerHTML = ''; // 添加节点 if (config.nodes && config.nodes.length > 0) { config.nodes.forEach(node => { addRedisNode(clusterId); const nodeInputs = container.querySelectorAll('.node-input'); const lastNodeInput = nodeInputs[nodeInputs.length - 1]; const hostInput = lastNodeInput.querySelector('input[type="text"]'); const portInput = lastNodeInput.querySelector('input[type="number"]'); if (hostInput) hostInput.value = node.host || ''; if (portInput) portInput.value = node.port || 6379; }); } else { // 添加默认节点 addRedisNode(clusterId); } } } // 加载查询选项 function loadQueryOptions(queryOptions) { if (queryOptions.mode === 'random') { document.getElementById('randomMode').checked = true; document.getElementById('sampleCount').value = queryOptions.count || 100; document.getElementById('keyPattern').value = queryOptions.pattern || '*'; document.getElementById('sourceCluster').value = queryOptions.source_cluster || 'cluster2'; } else { document.getElementById('specifiedMode').checked = true; document.getElementById('specifiedKeys').value = (queryOptions.keys || []).join('\n'); } toggleQueryMode(); } // 显示Redis配置管理对话框 function showManageRedisConfigDialog() { loadRedisConfigGroupsForManagement(); new bootstrap.Modal(document.getElementById('manageRedisConfigModal')).show(); } // 为管理界面加载Redis配置组 async function loadRedisConfigGroupsForManagement() { try { const response = await fetch('/api/redis/config-groups'); const result = await response.json(); const container = document.getElementById('redisConfigGroupList'); if (!container) { console.error('Redis配置组列表容器未找到: redisConfigGroupList'); showAlert('Redis配置组列表容器未找到,请检查页面结构', 'danger'); return; } if (result.success && result.data && result.data.length > 0) { let html = '
'; html += ''; result.data.forEach(group => { const safeName = (group.name || '').replace(/'/g, "\\'"); html += ` `; }); html += '
名称描述创建时间操作
${group.name || '未命名'} ${group.description || '无描述'} ${new Date(group.created_at).toLocaleString('zh-CN')}
'; container.innerHTML = html; console.log(`成功加载 ${result.data.length} 个Redis配置组到管理界面`); } else { container.innerHTML = '
暂无Redis配置组
'; console.log('没有找到Redis配置组数据'); } } catch (error) { const container = document.getElementById('redisConfigGroupList'); if (container) { container.innerHTML = '
加载失败: ' + error.message + '
'; } console.error('加载Redis配置组失败:', error); showAlert('加载Redis配置组失败: ' + error.message, 'danger'); } } // 通过ID加载Redis配置组 async function loadRedisConfigGroupById(groupId) { try { const response = await fetch(`/api/redis/config-groups/${groupId}`); const result = await response.json(); if (result.success && result.data) { const config = result.data; // 加载配置 loadClusterConfig('cluster1', config.cluster1_config); loadClusterConfig('cluster2', config.cluster2_config); loadQueryOptions(config.query_options); // 关闭管理对话框 bootstrap.Modal.getInstance(document.getElementById('manageRedisConfigModal')).hide(); showAlert(`配置组 "${config.name}" 加载成功`, 'success'); } else { showAlert(result.error, 'danger'); } } catch (error) { showAlert(`加载失败: ${error.message}`, 'danger'); } } // 删除Redis配置组 async function deleteRedisConfigGroup(groupId, groupName) { if (!confirm(`确定要删除配置组 "${groupName}" 吗?此操作不可恢复。`)) { return; } try { const response = await fetch(`/api/redis/config-groups/${groupId}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showAlert(result.message, 'success'); loadRedisConfigGroupsForManagement(); // 重新加载列表 loadRedisConfigGroups(); // 重新加载下拉框 } else { showAlert(result.error, 'danger'); } } catch (error) { showAlert(`删除失败: ${error.message}`, 'danger'); } } // 显示Redis配置导入对话框 function showImportRedisConfigDialog(targetCluster) { currentImportTarget = targetCluster; document.getElementById('redisConfigImportText').value = ''; new bootstrap.Modal(document.getElementById('importRedisConfigModal')).show(); } // 导入Redis配置 async function importRedisConfig() { const configText = document.getElementById('redisConfigImportText').value.trim(); if (!configText) { showAlert('请输入配置内容', 'warning'); return; } try { const response = await fetch('/api/redis/import-config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ config_text: configText }) }); const result = await response.json(); if (result.success && result.data) { // 将配置应用到目标集群 loadClusterConfig(currentImportTarget, result.data); bootstrap.Modal.getInstance(document.getElementById('importRedisConfigModal')).hide(); showAlert(result.message, 'success'); } else { showAlert(result.error, 'danger'); } } catch (error) { showAlert(`导入失败: ${error.message}`, 'danger'); } } /** * Redis查询历史管理功能 */ // 加载Redis查询历史 async function loadRedisQueryHistory() { try { const response = await fetch('/api/redis/query-history'); const result = await response.json(); const select = document.getElementById('redisHistorySelect'); if (!select) { console.error('未找到Redis查询历史下拉框元素: redisHistorySelect'); return; } select.innerHTML = ''; if (result.success && result.data) { result.data.forEach(history => { const option = document.createElement('option'); option.value = history.id; option.textContent = `${history.name} - ${new Date(history.created_at).toLocaleString('zh-CN')}`; select.appendChild(option); }); console.log(`成功加载 ${result.data.length} 个Redis查询历史记录`); } else { console.log('没有找到Redis查询历史数据'); } } catch (error) { console.error('加载Redis查询历史失败:', error); showAlert('加载Redis查询历史失败: ' + error.message, 'danger'); } } // 显示保存Redis查询历史对话框 function showSaveRedisHistoryDialog() { if (!currentResults) { showAlert('请先执行Redis比较查询', 'warning'); return; } // 生成默认名称 const timestamp = new Date().toLocaleString('zh-CN'); document.getElementById('redisHistoryName').value = `Redis查询_${timestamp}`; document.getElementById('redisHistoryDescription').value = `Redis比较结果 - 总计${currentResults.stats.total_keys}个Key,发现${currentResults.stats.different_count}处差异`; new bootstrap.Modal(document.getElementById('saveRedisHistoryModal')).show(); } // 保存Redis查询历史 async function saveRedisQueryHistory() { if (!currentResults) { showAlert('没有可保存的查询结果', 'warning'); return; } const name = document.getElementById('redisHistoryName').value.trim(); const description = document.getElementById('redisHistoryDescription').value.trim(); if (!name) { showAlert('请输入历史记录名称', 'warning'); return; } try { const response = await fetch('/api/redis/query-history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, description: description, cluster1_config: getClusterConfig('cluster1'), cluster2_config: getClusterConfig('cluster2'), query_options: getQueryOptions(), query_keys: currentResults.query_options?.keys || [], results_summary: currentResults.stats, execution_time: currentResults.performance_report?.total_time || 0, total_keys: currentResults.stats.total_keys, different_count: currentResults.stats.different_count, identical_count: currentResults.stats.identical_count, missing_count: currentResults.stats.missing_in_cluster1 + currentResults.stats.missing_in_cluster2, raw_results: { identical_results: currentResults.identical_results, different_results: currentResults.different_results, missing_results: currentResults.missing_results, performance_report: currentResults.performance_report } }) }); const result = await response.json(); if (result.success) { showAlert(result.message, 'success'); bootstrap.Modal.getInstance(document.getElementById('saveRedisHistoryModal')).hide(); loadRedisQueryHistory(); // 重新加载历史记录列表 } else { showAlert(result.error, 'danger'); } } catch (error) { showAlert(`保存失败: ${error.message}`, 'danger'); } } // 加载选定的Redis查询历史 async function loadSelectedRedisHistory() { const historyId = document.getElementById('redisHistorySelect').value; if (!historyId) { showAlert('请选择历史记录', 'warning'); return; } try { const response = await fetch(`/api/redis/query-history/${historyId}`); const result = await response.json(); if (result.success && result.data) { const history = result.data; // 加载配置 loadClusterConfig('cluster1', history.cluster1_config); loadClusterConfig('cluster2', history.cluster2_config); loadQueryOptions(history.query_options); // 如果有原始结果,直接显示 if (history.raw_results) { const resultsData = { stats: history.results_summary, identical_results: history.raw_results.identical_results || [], different_results: history.raw_results.different_results || [], missing_results: history.raw_results.missing_results || [], performance_report: history.raw_results.performance_report || {}, clusters: { cluster1_name: history.cluster1_config.name, cluster2_name: history.cluster2_config.name } }; currentResults = resultsData; displayResults(resultsData); showAlert(`历史记录 "${history.name}" 加载成功`, 'success'); } else { showAlert(`历史记录 "${history.name}" 配置加载成功,但没有结果数据`, 'info'); } } else { showAlert(result.error, 'danger'); } } catch (error) { showAlert(`加载失败: ${error.message}`, 'danger'); } } // 显示Redis查询历史管理对话框 function showRedisQueryHistoryDialog() { loadRedisHistoryForManagement(); new bootstrap.Modal(document.getElementById('redisQueryHistoryModal')).show(); } // 为管理界面加载Redis查询历史 async function loadRedisHistoryForManagement() { try { const response = await fetch('/api/redis/query-history'); const result = await response.json(); const container = document.getElementById('redisHistoryList'); if (!container) { console.error('历史记录列表容器未找到: redisHistoryList'); showAlert('历史记录列表容器未找到,请检查页面结构', 'danger'); return; } if (result.success && result.data && result.data.length > 0) { // 添加批量操作控制栏 let html = `
`; html += '
'; html += ''; result.data.forEach(history => { const safeName = (history.name || '').replace(/'/g, "\\'"); html += ` `; }); html += '
选择名称描述Key数量差异数执行时间创建时间操作
${history.name || '未命名'} ${history.description || '无描述'} ${history.total_keys || 0} ${history.different_count || 0} ${(history.execution_time || 0).toFixed(3)}s ${new Date(history.created_at).toLocaleString('zh-CN')}
'; container.innerHTML = html; console.log(`成功加载 ${result.data.length} 个Redis历史记录到管理界面`); } else { container.innerHTML = '
暂无Redis查询历史
'; console.log('没有找到Redis历史记录数据'); } } catch (error) { const container = document.getElementById('redisHistoryList'); if (container) { container.innerHTML = '
加载失败: ' + error.message + '
'; } console.error('加载Redis历史记录失败:', error); showAlert('加载Redis历史记录失败: ' + error.message, 'danger'); } } // 通过ID加载Redis查询历史 async function loadRedisHistoryById(historyId) { try { const response = await fetch(`/api/redis/query-history/${historyId}`); const result = await response.json(); if (result.success && result.data) { const history = result.data; // 加载配置 loadClusterConfig('cluster1', history.cluster1_config); loadClusterConfig('cluster2', history.cluster2_config); loadQueryOptions(history.query_options); // 如果有原始结果,直接显示 if (history.raw_results) { const resultsData = { stats: history.results_summary, identical_results: history.raw_results.identical_results || [], different_results: history.raw_results.different_results || [], missing_results: history.raw_results.missing_results || [], performance_report: history.raw_results.performance_report || {}, clusters: { cluster1_name: history.cluster1_config.name, cluster2_name: history.cluster2_config.name } }; currentResults = resultsData; displayResults(resultsData); } // 关闭管理对话框 bootstrap.Modal.getInstance(document.getElementById('manageRedisHistoryModal')).hide(); showAlert(`历史记录 "${history.name}" 加载成功`, 'success'); } else { showAlert(result.error, 'danger'); } } catch (error) { showAlert(`加载失败: ${error.message}`, 'danger'); } } // 删除Redis查询历史 async function deleteRedisHistory(historyId, historyName) { if (!confirm(`确定要删除历史记录 "${historyName}" 吗?此操作不可恢复。`)) { return; } try { const response = await fetch(`/api/redis/query-history/${historyId}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showAlert(result.message, 'success'); loadRedisHistoryForManagement(); // 重新加载列表 loadRedisQueryHistory(); // 重新加载下拉框 } else { showAlert(result.error, 'danger'); } } catch (error) { showAlert(`删除失败: ${error.message}`, 'danger'); } } /** * Redis查询日志功能 */ // 显示Redis查询日志对话框 function showRedisQueryLogsDialog() { loadRedisQueryLogs(); new bootstrap.Modal(document.getElementById('redisQueryLogsModal')).show(); } // 加载Redis查询日志 async function loadRedisQueryLogs() { try { const response = await fetch('/api/redis/query-logs?limit=1000&grouped=true'); const result = await response.json(); const container = document.getElementById('redis-modal-query-logs'); if (result.success && result.data && result.data.length > 0) { displayRedisGroupedLogs(result.data, container); } else { container.innerHTML = '
暂无Redis查询日志
'; } } catch (error) { document.getElementById('redis-modal-query-logs').innerHTML = '
加载日志失败: ' + error.message + '
'; } } // 显示分组的Redis查询日志 function displayRedisGroupedLogs(groupedLogs, container) { let html = ''; // 添加全部展开/折叠控制按钮 html += `
Redis查询日志 (共 ${groupedLogs.length} 个批次)
`; // 按时间倒序显示日志组(最新的在前面) groupedLogs.reverse().forEach((group, index) => { const [batchId, logs] = group; const isFirstGroup = index === 0; // 第一个(最新的)组默认展开 // 分析批次信息 const batchInfo = analyzeBatchInfo(logs); const statusIcon = getStatusIcon(batchInfo.status); const statusClass = getStatusClass(batchInfo.status); html += `
${statusIcon} 批次: ${batchId} ${batchInfo.startTime}
${batchInfo.totalKeys} 个Key | ${batchInfo.duration} | ${batchInfo.summary}
${logs.length} 条日志
${generateLogItemsHtml(logs)}
`; }); container.innerHTML = html; } // 生成日志项的HTML function generateLogItemsHtml(logs) { let html = ''; logs.forEach(log => { const levelClass = log.level === 'ERROR' ? 'text-danger' : log.level === 'WARNING' ? 'text-warning' : 'text-info'; const timestamp = log.timestamp || '未知时间'; const level = log.level || 'INFO'; const message = log.message || '无消息内容'; // 提取时间部分(只显示时分秒) const timeOnly = timestamp.split(' ')[1] || timestamp; html += `
${level}
${message}
${timeOnly}
`; }); return html; } // 分析批次信息 function analyzeBatchInfo(logs) { const firstLog = logs[0]; const lastLog = logs[logs.length - 1]; // 提取开始时间 const startTime = firstLog.timestamp || '未知时间'; // 计算持续时间 let duration = '未知'; if (firstLog.timestamp && lastLog.timestamp) { const start = new Date(firstLog.timestamp); const end = new Date(lastLog.timestamp); const diffMs = end - start; if (diffMs >= 0) { duration = `${(diffMs / 1000).toFixed(2)}秒`; } } // 分析状态 let status = 'success'; let totalKeys = 0; let summary = ''; const hasError = logs.some(log => log.level === 'ERROR'); const hasWarning = logs.some(log => log.level === 'WARNING'); if (hasError) { status = 'error'; } else if (hasWarning) { status = 'warning'; } // 提取Key数量和总结信息 logs.forEach(log => { const message = log.message || ''; // 提取Key数量 const keyMatch = message.match(/(\d+)\s*个[Kk]ey/); if (keyMatch) { totalKeys = Math.max(totalKeys, parseInt(keyMatch[1])); } // 提取总结信息 if (message.includes('比对统计总览') || message.includes('数据一致') || message.includes('数据不同')) { const summaryMatch = message.match(/数据一致:\s*(\d+).*数据不同:\s*(\d+)/); if (summaryMatch) { summary = `一致${summaryMatch[1]}个, 不同${summaryMatch[2]}个`; } } }); if (!summary) { summary = `${logs.length}条日志`; } return { startTime, duration, status, totalKeys: totalKeys || '未知', summary }; } // 获取状态图标 function getStatusIcon(status) { switch (status) { case 'success': return ''; case 'warning': return ''; case 'error': return ''; default: return ''; } } // 获取状态样式类 function getStatusClass(status) { switch (status) { case 'success': return 'bg-light border-success'; case 'warning': return 'bg-warning bg-opacity-10 border-warning'; case 'error': return 'bg-danger bg-opacity-10 border-danger'; default: return 'bg-light'; } } // 获取边框样式类 function getBorderClass(level) { switch (level) { case 'ERROR': return 'border-danger'; case 'WARNING': return 'border-warning'; case 'INFO': return 'border-info'; default: return 'border-secondary'; } } // 获取徽章样式类 function getBadgeClass(level) { switch (level) { case 'ERROR': return 'bg-danger'; case 'WARNING': return 'bg-warning text-dark'; case 'INFO': return 'bg-info'; default: return 'bg-secondary'; } } // 切换日志组的展开/折叠状态 function toggleRedisLogGroup(batchId) { const logsContainer = document.getElementById(`logs-${batchId}`); const chevron = document.getElementById(`chevron-${batchId}`); if (logsContainer.classList.contains('d-none')) { logsContainer.classList.remove('d-none'); chevron.className = 'fas fa-chevron-up ms-2'; } else { logsContainer.classList.add('d-none'); chevron.className = 'fas fa-chevron-down ms-2'; } } // 展开所有日志组 function expandAllRedisLogGroups() { document.querySelectorAll('.redis-log-group .card-body').forEach(container => { container.classList.remove('d-none'); }); document.querySelectorAll('[id^="chevron-"]').forEach(chevron => { chevron.className = 'fas fa-chevron-up ms-2'; }); } // 折叠所有日志组 function collapseAllRedisLogGroups() { document.querySelectorAll('.redis-log-group .card-body').forEach(container => { container.classList.add('d-none'); }); document.querySelectorAll('[id^="chevron-"]').forEach(chevron => { chevron.className = 'fas fa-chevron-down ms-2'; }); } // 刷新Redis查询日志 function refreshRedisQueryLogs() { loadRedisQueryLogs(); showAlert('查询日志已刷新', 'info'); } // 清空Redis查询日志 async function clearRedisQueryLogs() { if (!confirm('确定要清空所有查询日志吗?此操作不可恢复。')) { return; } try { const response = await fetch('/api/redis/query-logs', { method: 'DELETE' }); const result = await response.json(); if (result.success) { showAlert(result.message, 'success'); loadRedisQueryLogs(); // 重新加载日志 } else { showAlert(result.error, 'danger'); } } catch (error) { showAlert(`清空日志失败: ${error.message}`, 'danger'); } } // 显示导入Redis配置对话框 function showImportRedisConfigDialog() { updateConfigTemplate(); new bootstrap.Modal(document.getElementById('importRedisConfigModal')).show(); } // 更新配置模板 function updateConfigTemplate() { const format = document.getElementById('configFormat').value; const templateElement = document.getElementById('configTemplate'); if (format === 'yaml') { templateElement.textContent = `# Redis集群配置示例 (YAML格式) cluster1: clusterName: "redis-example1" clusterAddress: "127.0.0.1:6379" clusterPassword: "" cachePrefix: "message.status.Reader." cacheTtl: 2000 async: true nodes: - host: "127.0.0.1" port: 6379 - host: "127.0.0.2" port: 6379 cluster2: clusterName: "redis-example2" clusterAddress: "127.0.0.1:6380" clusterPassword: "" cachePrefix: "message.status.Reader." cacheTtl: 2000 async: true nodes: - host: "127.0.0.1" port: 6380 - host: "127.0.0.2" port: 6380 queryOptions: mode: "random" # random 或 specified count: 100 # 随机采样数量 pattern: "*" # Key匹配模式 sourceCluster: "cluster2" # 指定Key模式下的键值列表 keys: - "user:example1" - "user:example2"`; } else { templateElement.textContent = `{ "cluster1": { "clusterName": "redis-example1", "clusterAddress": "127.0.0.1:6379", "clusterPassword": "", "cachePrefix": "message.status.Reader.", "cacheTtl": 2000, "async": true, "nodes": [ {"host": "127.0.0.1", "port": 6379}, {"host": "127.0.0.2", "port": 6379} ] }, "cluster2": { "clusterName": "redis-example2", "clusterAddress": "127.0.0.1:6380", "clusterPassword": "", "cachePrefix": "message.status.Reader.", "cacheTtl": 2000, "async": true, "nodes": [ {"host": "127.0.0.1", "port": 6380}, {"host": "127.0.0.2", "port": 6380} ] }, "queryOptions": { "mode": "random", "count": 100, "pattern": "*", "sourceCluster": "cluster2", "keys": ["user:example1", "user:example2"] } }`; } } // 导入Redis配置 async function importRedisConfig() { const format = document.getElementById('configFormat').value; const content = document.getElementById('configContent').value.trim(); if (!content) { showAlert('请输入配置内容', 'warning'); return; } try { let config; if (format === 'yaml') { // 解析YAML格式(简单解析,不使用第三方库) config = parseSimpleYaml(content); } else { // 解析JSON格式 config = JSON.parse(content); } // 验证配置结构 if (!config.cluster1 || !config.cluster2) { showAlert('配置格式错误:缺少cluster1或cluster2配置', 'danger'); return; } // 应用配置到页面 if (config.cluster1) { loadClusterConfig('cluster1', config.cluster1); } if (config.cluster2) { loadClusterConfig('cluster2', config.cluster2); } if (config.queryOptions) { loadQueryOptions(config.queryOptions); } // 关闭对话框 bootstrap.Modal.getInstance(document.getElementById('importRedisConfigModal')).hide(); showAlert('配置导入成功!', 'success'); } catch (error) { showAlert(`配置解析失败: ${error.message}`, 'danger'); } } // 简单的YAML解析器(仅支持基本格式) function parseSimpleYaml(yamlContent) { const lines = yamlContent.split('\n'); const result = {}; let currentSection = null; let currentSubSection = null; for (let line of lines) { line = line.trim(); // 跳过注释和空行 if (!line || line.startsWith('#')) continue; // 检查是否是主节点 if (line.match(/^[a-zA-Z][a-zA-Z0-9_]*:$/)) { currentSection = line.slice(0, -1); result[currentSection] = {}; currentSubSection = null; } // 检查是否是子节点 else if (line.match(/^[a-zA-Z][a-zA-Z0-9_]*:/) && currentSection) { const [key, ...valueParts] = line.split(':'); let value = valueParts.join(':').trim(); // 处理字符串值 if (value.startsWith('"') && value.endsWith('"')) { value = value.slice(1, -1); } else if (value === 'true') { value = true; } else if (value === 'false') { value = false; } else if (!isNaN(value) && value !== '') { value = Number(value); } if (key === 'nodes') { result[currentSection][key] = []; currentSubSection = key; } else { result[currentSection][key] = value; } } // 处理数组项 else if (line.startsWith('- ') && currentSubSection === 'nodes') { const nodeStr = line.substring(2).trim(); if (nodeStr.includes('host:') && nodeStr.includes('port:')) { // 解析 "host: xxx, port: xxx" 格式 const hostMatch = nodeStr.match(/host:\s*"?([^",]+)"?/); const portMatch = nodeStr.match(/port:\s*(\d+)/); if (hostMatch && portMatch) { result[currentSection][currentSubSection].push({ host: hostMatch[1], port: parseInt(portMatch[1]) }); } } } } return result; } // 添加Redis节点 function addRedisNode(clusterId) { const nodeContainer = document.getElementById(`${clusterId}Nodes`); const nodeDiv = document.createElement('div'); nodeDiv.className = 'node-input'; nodeDiv.innerHTML = ` `; nodeContainer.appendChild(nodeDiv); } // 删除Redis节点 function removeRedisNode(button, clusterId) { const nodeContainer = document.getElementById(`${clusterId}Nodes`); const nodeInputs = nodeContainer.querySelectorAll('.node-input'); // 至少保留一个节点 if (nodeInputs.length <= 1) { showAlert('至少需要保留一个Redis节点', 'warning'); return; } // 删除当前节点 button.parentElement.remove(); } // 切换查询模式 function toggleQueryMode() { const randomMode = document.getElementById('randomMode'); const randomOptions = document.getElementById('randomOptions'); const specifiedOptions = document.getElementById('specifiedOptions'); if (randomMode.checked) { randomOptions.style.display = 'block'; specifiedOptions.style.display = 'none'; } else { randomOptions.style.display = 'none'; specifiedOptions.style.display = 'block'; } } /** * 原生数据展示功能 */ // 全局变量存储原始数据和当前视图状态 let rawDataState = { cluster1: { data: null, view: 'formatted' }, cluster2: { data: null, view: 'formatted' } }; /** * 显示原生数据 */ function displayRawData(results) { console.log('开始显示原生数据'); // 从结果中收集所有键值数据 const cluster1Data = collectClusterData(results, 'cluster1'); const cluster2Data = collectClusterData(results, 'cluster2'); // 存储原始数据 rawDataState.cluster1.data = cluster1Data; rawDataState.cluster2.data = cluster2Data; // 显示数据 renderRawData('cluster1', cluster1Data, 'formatted'); renderRawData('cluster2', cluster2Data, 'formatted'); console.log(`原生数据显示完成 - 集群1: ${cluster1Data.length}条, 集群2: ${cluster2Data.length}条`); } /** * 从结果中收集集群数据 */ function collectClusterData(results, clusterType) { const data = []; const clusterField = clusterType === 'cluster1' ? 'cluster1_value' : 'cluster2_value'; const clusterTypeField = clusterType === 'cluster1' ? 'cluster1_type' : 'cluster2_type'; // 从相同结果中收集数据 if (results.identical_results) { results.identical_results.forEach(item => { if (item.key && item.value !== undefined) { data.push({ key: item.key, value: item.value, type: 'identical', redis_type: item.type || 'unknown' }); } }); } // 从差异结果中收集数据 if (results.different_results) { results.different_results.forEach(item => { if (item.key && item[clusterField] !== undefined) { data.push({ key: item.key, value: item[clusterField], type: 'different', redis_type: item[clusterTypeField] || 'unknown' }); } }); } // 从缺失结果中收集数据 if (results.missing_results) { results.missing_results.forEach(item => { if (item.key && item[clusterField] !== undefined && item[clusterField] !== null) { data.push({ key: item.key, value: item[clusterField], type: 'missing', redis_type: item[clusterTypeField] || 'unknown' }); } }); } return data; } /** * 渲染原生数据 */ function renderRawData(clusterId, data, viewType) { const container = document.getElementById(`${clusterId}-raw-data`); if (!container) { console.error(`原生数据容器未找到: ${clusterId}-raw-data`); return; } if (!data || data.length === 0) { container.innerHTML = '
无数据
'; return; } let html = ''; if (viewType === 'formatted') { // 格式化视图 html = '
'; data.forEach((item, index) => { const typeClass = getTypeClass(item.type); const formattedValue = formatRedisValue(item.value); const redisTypeInfo = item.redis_type ? ` [${item.redis_type}]` : ''; html += `
Key: ${escapeHtml(item.key)}${redisTypeInfo} ${item.type}
${escapeHtml(formattedValue)}
`; }); html += '
'; } else { // 原始视图 html = '
'; html += '
';
        data.forEach((item, index) => {
            html += `Key: ${escapeHtml(item.key)}\n`;
            html += `Redis Type: ${item.redis_type || 'unknown'}\n`;
            html += `Comparison Type: ${item.type}\n`;
            html += `Value: ${escapeHtml(String(item.value))}\n`;
            if (index < data.length - 1) {
                html += '\n' + '='.repeat(50) + '\n\n';
            }
        });
        html += '
'; html += '
'; } container.innerHTML = html; } /** * 获取类型对应的CSS类 */ function getTypeClass(type) { switch (type) { case 'identical': return 'border-success'; case 'different': return 'border-warning'; case 'missing': return 'border-danger'; default: return 'border-secondary'; } } /** * HTML转义 */ function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * 切换原生数据视图 */ function switchRawDataView(clusterId, viewType) { if (!rawDataState[clusterId] || !rawDataState[clusterId].data) { console.error(`没有${clusterId}的原生数据`); return; } // 更新按钮状态 const container = document.getElementById(`${clusterId}-raw-data`); const buttonGroup = container.parentElement.querySelector('.btn-group'); if (buttonGroup) { const buttons = buttonGroup.querySelectorAll('button'); buttons.forEach(btn => { btn.classList.remove('active'); const btnText = btn.textContent.trim(); if ((viewType === 'formatted' && btnText === '格式化') || (viewType === 'raw' && btnText === '原始')) { btn.classList.add('active'); } }); } // 更新视图状态 rawDataState[clusterId].view = viewType; // 重新渲染数据 renderRawData(clusterId, rawDataState[clusterId].data, viewType); console.log(`${clusterId}视图已切换到: ${viewType}`); } /** * 导出原生数据 */ function exportRawData(clusterId) { if (!rawDataState[clusterId] || !rawDataState[clusterId].data) { showAlert(`没有${clusterId}的原生数据可导出`, 'warning'); return; } const data = rawDataState[clusterId].data; const clusterName = clusterId === 'cluster1' ? (currentResults?.clusters?.cluster1_name || '集群1') : (currentResults?.clusters?.cluster2_name || '集群2'); // 生成导出内容 let content = `Redis原生数据导出 - ${clusterName}\n`; content += `导出时间: ${new Date().toLocaleString('zh-CN')}\n`; content += `数据总数: ${data.length}\n`; content += '='.repeat(60) + '\n\n'; data.forEach((item, index) => { content += `[${index + 1}] Key: ${item.key}\n`; content += `Type: ${item.type}\n`; content += `Value:\n${item.value}\n`; content += '\n' + '-'.repeat(40) + '\n\n'; }); // 创建下载 const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `redis_raw_data_${clusterId}_${new Date().getTime()}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showAlert(`${clusterName}原生数据导出成功`, 'success'); } /** * Redis配置组管理功能 */ // 显示保存Redis配置组对话框 function showSaveRedisConfigDialog() { // 清空表单 document.getElementById('redisConfigName').value = ''; document.getElementById('redisConfigDescription').value = ''; // 显示模态框 new bootstrap.Modal(document.getElementById('saveRedisConfigModal')).show(); } // 保存Redis配置组 async function saveRedisConfigGroup() { const name = document.getElementById('redisConfigName').value.trim(); const description = document.getElementById('redisConfigDescription').value.trim(); if (!name) { showAlert('请输入配置组名称', 'warning'); return; } try { // 获取当前配置 const cluster1Config = getClusterConfig('cluster1'); const cluster2Config = getClusterConfig('cluster2'); const queryOptions = getQueryOptions(); const configData = { name: name, description: description, cluster1_config: cluster1Config, cluster2_config: cluster2Config, query_options: queryOptions }; const response = await fetch('/api/redis/config-groups', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(configData) }); const result = await response.json(); if (result.success) { showAlert('Redis配置组保存成功', 'success'); bootstrap.Modal.getInstance(document.getElementById('saveRedisConfigModal')).hide(); loadRedisConfigGroups(); // 刷新配置组列表 } else { showAlert(`保存失败: ${result.error}`, 'danger'); } } catch (error) { console.error('保存Redis配置组失败:', error); showAlert(`保存失败: ${error.message}`, 'danger'); } } // 显示管理Redis配置组对话框 function showManageRedisConfigDialog() { loadRedisConfigGroupsForManagement(); new bootstrap.Modal(document.getElementById('manageRedisConfigModal')).show(); } // 为管理界面加载Redis配置组 async function loadRedisConfigGroupsForManagement() { try { const response = await fetch('/api/redis/config-groups'); const result = await response.json(); if (result.success) { displayRedisConfigGroupsForManagement(result.data); } else { showAlert(`加载配置组失败: ${result.error}`, 'danger'); } } catch (error) { console.error('加载Redis配置组失败:', error); showAlert(`加载失败: ${error.message}`, 'danger'); } } // 显示Redis配置组管理列表 function displayRedisConfigGroupsForManagement(configGroups) { const container = document.getElementById('redisConfigGroupsList'); if (!configGroups || configGroups.length === 0) { container.innerHTML = '
暂无保存的配置组
'; return; } let html = ''; configGroups.forEach(config => { html += `
${config.name}

${config.description || '无描述'}

创建时间: ${config.created_at} ${config.updated_at !== config.created_at ? `
更新时间: ${config.updated_at}` : ''}
`; }); container.innerHTML = html; } // 刷新Redis配置组列表 function refreshRedisConfigGroups() { loadRedisConfigGroupsForManagement(); } /** * Redis查询历史保存功能 */ // 显示保存Redis查询历史对话框 function showSaveRedisHistoryDialog() { if (!currentResults) { showAlert('请先执行查询后再保存历史记录', 'warning'); return; } // 生成默认名称 const now = new Date(); const defaultName = `Redis查询_${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`; document.getElementById('redisHistoryName').value = defaultName; // 生成默认描述 const stats = currentResults.stats || {}; const defaultDescription = `自动保存 - 查询${stats.total_keys || 0}个Key,发现${stats.different_count || 0}处差异`; document.getElementById('redisHistoryDescription').value = defaultDescription; // 显示模态框 new bootstrap.Modal(document.getElementById('saveRedisHistoryModal')).show(); } // 保存Redis查询历史 async function saveRedisQueryHistory() { const name = document.getElementById('redisHistoryName').value.trim(); const description = document.getElementById('redisHistoryDescription').value.trim(); if (!name) { showAlert('请输入历史记录名称', 'warning'); return; } if (!currentResults) { showAlert('没有可保存的查询结果', 'warning'); return; } try { const historyData = { name: name, description: description, cluster1_config: getClusterConfig('cluster1'), cluster2_config: getClusterConfig('cluster2'), query_options: getQueryOptions(), query_keys: currentResults.query_keys || [], results_summary: currentResults.stats || {}, execution_time: currentResults.performance_report?.total_time || 0, total_keys: currentResults.stats?.total_keys || 0, different_count: currentResults.stats?.different_count || 0, identical_count: currentResults.stats?.identical_count || 0, raw_results: currentResults }; const response = await fetch('/api/redis/query-history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(historyData) }); const result = await response.json(); if (result.success) { showAlert('Redis查询历史保存成功', 'success'); bootstrap.Modal.getInstance(document.getElementById('saveRedisHistoryModal')).hide(); loadRedisQueryHistory(); // 刷新历史记录列表 } else { showAlert(`保存失败: ${result.error}`, 'danger'); } } catch (error) { console.error('保存Redis查询历史失败:', error); showAlert(`保存失败: ${error.message}`, 'danger'); } } // 刷新Redis查询历史 function refreshRedisQueryHistory() { loadRedisHistoryForManagement(); } // 清空所有Redis历史记录 async function clearAllRedisHistory() { if (!confirm('确定要清空所有Redis查询历史记录吗?此操作不可恢复。')) { return; } try { // 获取所有Redis历史记录 const response = await fetch('/api/redis/query-history'); const result = await response.json(); if (result.success && result.data && result.data.length > 0) { const allIds = result.data.map(history => history.id); const deleteResponse = await fetch('/api/redis/query-history/batch-delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ history_ids: allIds }) }); const deleteResult = await deleteResponse.json(); if (deleteResult.success) { showAlert(`成功删除 ${deleteResult.deleted_count} 条历史记录`, 'success'); loadRedisHistoryForManagement(); loadRedisQueryHistory(); // 重新加载下拉框 } else { showAlert(deleteResult.error || '批量删除失败', 'danger'); } } else { showAlert('没有历史记录需要删除', 'info'); } } catch (error) { console.error('清空Redis历史记录失败:', error); showAlert(`清空失败: ${error.message}`, 'danger'); } } // 切换全选Redis历史记录 function toggleAllRedisHistorySelection() { const selectAllCheckbox = document.getElementById('selectAllRedisHistory'); const historyCheckboxes = document.querySelectorAll('.redis-history-checkbox'); historyCheckboxes.forEach(checkbox => { checkbox.checked = selectAllCheckbox.checked; }); updateRedisHistorySelectionCount(); } // 更新Redis历史记录选择数量 function updateRedisHistorySelectionCount() { const selectedCheckboxes = document.querySelectorAll('.redis-history-checkbox:checked'); const count = selectedCheckboxes.length; const totalCheckboxes = document.querySelectorAll('.redis-history-checkbox'); // 更新显示计数 const countSpan = document.getElementById('selectedRedisHistoryCount'); if (countSpan) { countSpan.textContent = count; } // 更新批量删除按钮状态 const batchDeleteBtn = document.getElementById('batchDeleteRedisHistoryBtn'); if (batchDeleteBtn) { batchDeleteBtn.disabled = count === 0; } // 更新全选复选框状态 const selectAllCheckbox = document.getElementById('selectAllRedisHistory'); if (selectAllCheckbox) { if (count === 0) { selectAllCheckbox.indeterminate = false; selectAllCheckbox.checked = false; } else if (count === totalCheckboxes.length) { selectAllCheckbox.indeterminate = false; selectAllCheckbox.checked = true; } else { selectAllCheckbox.indeterminate = true; } } } // 批量删除Redis历史记录 async function batchDeleteRedisHistory() { const selectedCheckboxes = document.querySelectorAll('.redis-history-checkbox:checked'); const selectedIds = Array.from(selectedCheckboxes).map(cb => parseInt(cb.value)); if (selectedIds.length === 0) { showAlert('请选择要删除的历史记录', 'warning'); return; } if (!confirm(`确定要删除选中的 ${selectedIds.length} 条历史记录吗?此操作不可恢复。`)) { return; } try { const response = await fetch('/api/redis/query-history/batch-delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ history_ids: selectedIds }) }); const result = await response.json(); if (result.success) { showAlert(`成功删除 ${result.deleted_count} 条历史记录`, 'success'); loadRedisHistoryForManagement(); // 重新加载列表 loadRedisQueryHistory(); // 重新加载下拉框 } else { showAlert(result.error || '批量删除失败', 'danger'); } } catch (error) { console.error('批量删除Redis历史记录失败:', error); showAlert(`批量删除失败: ${error.message}`, 'danger'); } } // 过滤Redis模态框日志级别 function filterRedisModalLogsByLevel() { const showInfo = document.getElementById('redis-modal-log-level-info').checked; const showWarning = document.getElementById('redis-modal-log-level-warning').checked; const showError = document.getElementById('redis-modal-log-level-error').checked; const logContainer = document.getElementById('redis-modal-query-logs'); const logItems = logContainer.querySelectorAll('.log-item'); logItems.forEach(item => { const level = item.dataset.level; let show = false; if (level === 'INFO' && showInfo) show = true; if (level === 'WARNING' && showWarning) show = true; if (level === 'ERROR' && showError) show = true; item.style.display = show ? 'block' : 'none'; }); // 检查每个日志组是否还有可见的日志项 document.querySelectorAll('.redis-log-group').forEach(group => { const visibleLogs = group.querySelectorAll('.log-item:not([style*="display: none"])'); const cardBody = group.querySelector('.card-body'); if (visibleLogs.length === 0) { // 如果没有可见的日志项,隐藏整个组 group.style.display = 'none'; } else { // 如果有可见的日志项,显示组 group.style.display = 'block'; } }); } /** * Redis配置导入功能 */ // 当前导入的目标集群 let currentImportCluster = null; // 显示导入配置对话框 function showImportConfigDialog(clusterId) { currentImportCluster = clusterId; // 更新模态框标题 const clusterName = clusterId === 'cluster1' ? '集群1 (生产)' : '集群2 (测试)'; document.getElementById('importConfigModalLabel').innerHTML = ` 导入Redis配置 - ${clusterName}`; // 重置表单 resetImportForm(); // 显示模态框 new bootstrap.Modal(document.getElementById('importConfigModal')).show(); } // 重置导入表单 function resetImportForm() { // 重置导入方式选择 document.getElementById('importMethodText').checked = true; document.getElementById('importMethodFile').checked = false; // 显示文本导入区域,隐藏文件导入区域 document.getElementById('textImportSection').style.display = 'block'; document.getElementById('fileImportSection').style.display = 'none'; // 清空内容 document.getElementById('configYamlText').value = ''; document.getElementById('configYamlFile').value = ''; // 隐藏预览区域 document.getElementById('configPreview').style.display = 'none'; document.getElementById('previewContent').innerHTML = ''; } // 绑定导入方式切换事件 document.addEventListener('DOMContentLoaded', function() { // 导入方式切换 document.querySelectorAll('input[name="importMethod"]').forEach(radio => { radio.addEventListener('change', function() { if (this.id === 'importMethodText') { document.getElementById('textImportSection').style.display = 'block'; document.getElementById('fileImportSection').style.display = 'none'; } else { document.getElementById('textImportSection').style.display = 'none'; document.getElementById('fileImportSection').style.display = 'block'; } // 隐藏预览 document.getElementById('configPreview').style.display = 'none'; }); }); // 文件选择事件 document.getElementById('configYamlFile').addEventListener('change', handleFileSelect); // 拖拽事件 const dropZone = document.getElementById('dropZone'); dropZone.addEventListener('click', () => document.getElementById('configYamlFile').click()); dropZone.addEventListener('dragover', handleDragOver); dropZone.addEventListener('dragleave', handleDragLeave); dropZone.addEventListener('drop', handleFileDrop); }); // 处理文件选择 function handleFileSelect(event) { const file = event.target.files[0]; if (file) { readFileContent(file); } } // 处理拖拽悬停 function handleDragOver(event) { event.preventDefault(); event.stopPropagation(); event.currentTarget.classList.add('dragover'); } // 处理拖拽离开 function handleDragLeave(event) { event.preventDefault(); event.stopPropagation(); event.currentTarget.classList.remove('dragover'); } // 处理文件拖拽放置 function handleFileDrop(event) { event.preventDefault(); event.stopPropagation(); event.currentTarget.classList.remove('dragover'); const files = event.dataTransfer.files; if (files.length > 0) { const file = files[0]; // 更新文件输入框 const fileInput = document.getElementById('configYamlFile'); const dataTransfer = new DataTransfer(); dataTransfer.items.add(file); fileInput.files = dataTransfer.files; readFileContent(file); } } // 读取文件内容 function readFileContent(file) { // 检查文件类型 const validExtensions = ['.yml', '.yaml', '.txt']; const fileName = file.name.toLowerCase(); const isValidFile = validExtensions.some(ext => fileName.endsWith(ext)); if (!isValidFile) { showAlert('请选择YAML格式的配置文件(.yml, .yaml, .txt)', 'warning'); return; } const reader = new FileReader(); reader.onload = function(e) { const content = e.target.result; document.getElementById('configYamlText').value = content; showAlert('文件读取成功,请点击"预览配置"查看解析结果', 'success'); }; reader.onerror = function() { showAlert('文件读取失败,请重试', 'danger'); }; reader.readAsText(file); } // 预览配置 function previewConfig() { const yamlContent = document.getElementById('configYamlText').value.trim(); if (!yamlContent) { showAlert('请输入YAML配置内容', 'warning'); return; } try { const parsedConfig = parseYamlConfig(yamlContent); displayConfigPreview(parsedConfig); document.getElementById('configPreview').style.display = 'block'; } catch (error) { showAlert(`配置解析失败: ${error.message}`, 'danger'); document.getElementById('configPreview').style.display = 'none'; } } // 解析YAML配置 function parseYamlConfig(yamlContent) { const config = {}; const lines = yamlContent.split('\n'); for (let line of lines) { line = line.trim(); // 跳过空行和注释 if (!line || line.startsWith('#')) { continue; } // 解析键值对 const colonIndex = line.indexOf(':'); if (colonIndex === -1) { continue; } const key = line.substring(0, colonIndex).trim(); let value = line.substring(colonIndex + 1).trim(); // 移除引号 if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { value = value.slice(1, -1); } config[key] = value; } // 验证必需字段 if (!config.clusterName && !config.name) { throw new Error('缺少集群名称字段 (clusterName 或 name)'); } if (!config.clusterAddress && !config.address && !config.nodes) { throw new Error('缺少集群地址字段 (clusterAddress, address 或 nodes)'); } // 标准化字段名 const standardConfig = { clusterName: config.clusterName || config.name || '', clusterAddress: config.clusterAddress || config.address || config.nodes || '', clusterPassword: config.clusterPassword || config.password || '', socketTimeout: config.socketTimeout || config.timeout || 3, socketConnectTimeout: config.socketConnectTimeout || config.connectTimeout || 3, maxConnectionsPerNode: config.maxConnectionsPerNode || config.maxConnections || 16 }; return standardConfig; } // 显示配置预览 function displayConfigPreview(config) { const previewContent = document.getElementById('previewContent'); // 解析节点地址 const nodes = parseClusterAddress(config.clusterAddress); let html = `
集群名称: ${config.clusterName}
节点数量: ${nodes.length} 个节点
`; nodes.forEach((node, index) => { html += `
节点 ${index + 1}: ${node.host}:${node.port}
`; }); html += `
密码: ${config.clusterPassword ? '已设置' : '未设置'}
连接超时: ${config.socketTimeout}秒
连接建立超时: ${config.socketConnectTimeout}秒
最大连接数: ${config.maxConnectionsPerNode}
`; previewContent.innerHTML = html; } // 解析集群地址 function parseClusterAddress(addressString) { if (!addressString) { return []; } const nodes = []; const addresses = addressString.split(','); for (let address of addresses) { address = address.trim(); if (!address) continue; const parts = address.split(':'); if (parts.length === 2) { const host = parts[0].trim(); const port = parseInt(parts[1].trim()); if (host && !isNaN(port) && port > 0 && port <= 65535) { nodes.push({ host, port }); } } } return nodes; } // 导入配置 function importConfig() { const yamlContent = document.getElementById('configYamlText').value.trim(); if (!yamlContent) { showAlert('请输入YAML配置内容', 'warning'); return; } if (!currentImportCluster) { showAlert('未指定导入目标集群', 'danger'); return; } try { const parsedConfig = parseYamlConfig(yamlContent); applyConfigToCluster(currentImportCluster, parsedConfig); // 关闭模态框 bootstrap.Modal.getInstance(document.getElementById('importConfigModal')).hide(); showAlert(`配置已成功导入到${currentImportCluster === 'cluster1' ? '集群1 (生产)' : '集群2 (测试)'}`, 'success'); } catch (error) { showAlert(`配置导入失败: ${error.message}`, 'danger'); } } // 将解析的配置应用到指定集群 function applyConfigToCluster(clusterId, config) { // 设置集群名称 const nameInput = document.getElementById(`${clusterId}Name`); if (nameInput) { nameInput.value = config.clusterName; } // 设置密码 const passwordInput = document.getElementById(`${clusterId}Password`); if (passwordInput) { passwordInput.value = config.clusterPassword; } // 设置连接参数 const timeoutInput = document.getElementById(`${clusterId}SocketTimeout`); if (timeoutInput) { timeoutInput.value = config.socketTimeout; } const connectTimeoutInput = document.getElementById(`${clusterId}SocketConnectTimeout`); if (connectTimeoutInput) { connectTimeoutInput.value = config.socketConnectTimeout; } const maxConnectionsInput = document.getElementById(`${clusterId}MaxConnectionsPerNode`); if (maxConnectionsInput) { maxConnectionsInput.value = config.maxConnectionsPerNode; } // 解析并设置节点 const nodes = parseClusterAddress(config.clusterAddress); if (nodes.length > 0) { // 清空现有节点 const nodesContainer = document.getElementById(`${clusterId}Nodes`); if (nodesContainer) { nodesContainer.innerHTML = ''; // 添加解析的节点 nodes.forEach((node, index) => { addNodeToCluster(clusterId, node.host, node.port, index === 0); }); } } } // 添加节点到集群 function addNodeToCluster(clusterId, host, port, isFirst = false) { const container = document.getElementById(`${clusterId}Nodes`); if (!container) { console.error(`节点容器未找到: ${clusterId}Nodes`); return; } const nodeInput = document.createElement('div'); nodeInput.className = 'node-input'; nodeInput.innerHTML = ` `; container.appendChild(nodeInput); // 更新删除按钮状态 updateNodeDeleteButtons(); } /** * Redis配置组管理功能增强 */ // 加载Redis配置组(增强版) async function loadRedisConfigGroup(configId) { try { const response = await fetch(`/api/redis/config-groups/${configId}`); const result = await response.json(); if (result.success) { const config = result.data; // 应用配置到两个集群 if (config.cluster1_config) { applyConfigToUI('cluster1', config.cluster1_config); } if (config.cluster2_config) { applyConfigToUI('cluster2', config.cluster2_config); } if (config.query_options) { applyQueryOptionsToUI(config.query_options); } // 关闭管理对话框 bootstrap.Modal.getInstance(document.getElementById('manageRedisConfigModal')).hide(); showAlert(`配置组"${config.name}"加载成功`, 'success'); } else { showAlert(`加载配置组失败: ${result.error}`, 'danger'); } } catch (error) { console.error('加载Redis配置组失败:', error); showAlert(`加载失败: ${error.message}`, 'danger'); } } // 应用配置到UI function applyConfigToUI(clusterId, clusterConfig) { // 设置集群名称 const nameInput = document.getElementById(`${clusterId}Name`); if (nameInput && clusterConfig.name) { nameInput.value = clusterConfig.name; } // 设置密码 const passwordInput = document.getElementById(`${clusterId}Password`); if (passwordInput && clusterConfig.password !== undefined) { passwordInput.value = clusterConfig.password; } // 设置连接参数 if (clusterConfig.socket_timeout !== undefined) { const timeoutInput = document.getElementById(`${clusterId}SocketTimeout`); if (timeoutInput) { timeoutInput.value = clusterConfig.socket_timeout; } } if (clusterConfig.socket_connect_timeout !== undefined) { const connectTimeoutInput = document.getElementById(`${clusterId}SocketConnectTimeout`); if (connectTimeoutInput) { connectTimeoutInput.value = clusterConfig.socket_connect_timeout; } } if (clusterConfig.max_connections_per_node !== undefined) { const maxConnectionsInput = document.getElementById(`${clusterId}MaxConnectionsPerNode`); if (maxConnectionsInput) { maxConnectionsInput.value = clusterConfig.max_connections_per_node; } } // 设置节点 if (clusterConfig.nodes && Array.isArray(clusterConfig.nodes)) { const nodesContainer = document.getElementById(`${clusterId}Nodes`); if (nodesContainer) { nodesContainer.innerHTML = ''; clusterConfig.nodes.forEach((node, index) => { addNodeToCluster(clusterId, node.host, node.port, index === 0); }); } } } // 删除Redis配置组 async function deleteRedisConfigGroup(configId, configName) { if (!confirm(`确定要删除配置组"${configName}"吗?此操作不可恢复。`)) { return; } try { const response = await fetch(`/api/redis/config-groups/${configId}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showAlert(`配置组"${configName}"删除成功`, 'success'); loadRedisConfigGroupsForManagement(); // 刷新列表 } else { showAlert(`删除失败: ${result.error}`, 'danger'); } } catch (error) { console.error('删除Redis配置组失败:', error); showAlert(`删除失败: ${error.message}`, 'danger'); } } // 导出Redis配置组为YAML格式 async function exportRedisConfigGroup(configId, configName) { try { const response = await fetch(`/api/redis/config-groups/${configId}`); const result = await response.json(); if (result.success) { const config = result.data; const yamlContent = convertConfigToYaml(config); // 创建下载链接 const blob = new Blob([yamlContent], { type: 'text/yaml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `redis_config_${configName.replace(/[^a-zA-Z0-9]/g, '_')}.yml`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showAlert(`配置组"${configName}"导出成功`, 'success'); } else { showAlert(`导出失败: ${result.error}`, 'danger'); } } catch (error) { console.error('导出Redis配置组失败:', error); showAlert(`导出失败: ${error.message}`, 'danger'); } } // 将配置转换为YAML格式 function convertConfigToYaml(config) { let yaml = `# Redis配置组: ${config.name}\n`; yaml += `# 描述: ${config.description || '无描述'}\n`; yaml += `# 创建时间: ${config.created_at}\n\n`; // 集群1配置 if (config.cluster1_config) { yaml += `# 集群1 (生产环境) 配置\n`; yaml += `cluster1:\n`; yaml += ` clusterName: "${config.cluster1_config.name || ''}"\n`; if (config.cluster1_config.nodes && config.cluster1_config.nodes.length > 0) { const addresses = config.cluster1_config.nodes.map(node => `${node.host}:${node.port}`).join(','); yaml += ` clusterAddress: "${addresses}"\n`; } yaml += ` clusterPassword: "${config.cluster1_config.password || ''}"\n`; yaml += ` socketTimeout: ${config.cluster1_config.socket_timeout || 3}\n`; yaml += ` socketConnectTimeout: ${config.cluster1_config.socket_connect_timeout || 3}\n`; yaml += ` maxConnectionsPerNode: ${config.cluster1_config.max_connections_per_node || 16}\n\n`; } // 集群2配置 if (config.cluster2_config) { yaml += `# 集群2 (测试环境) 配置\n`; yaml += `cluster2:\n`; yaml += ` clusterName: "${config.cluster2_config.name || ''}"\n`; if (config.cluster2_config.nodes && config.cluster2_config.nodes.length > 0) { const addresses = config.cluster2_config.nodes.map(node => `${node.host}:${node.port}`).join(','); yaml += ` clusterAddress: "${addresses}"\n`; } yaml += ` clusterPassword: "${config.cluster2_config.password || ''}"\n`; yaml += ` socketTimeout: ${config.cluster2_config.socket_timeout || 3}\n`; yaml += ` socketConnectTimeout: ${config.cluster2_config.socket_connect_timeout || 3}\n`; yaml += ` maxConnectionsPerNode: ${config.cluster2_config.max_connections_per_node || 16}\n\n`; } // 查询选项 if (config.query_options) { yaml += `# 查询选项\n`; yaml += `queryOptions:\n`; yaml += ` mode: "${config.query_options.mode || 'random'}"\n`; yaml += ` count: ${config.query_options.count || 100}\n`; yaml += ` pattern: "${config.query_options.pattern || '*'}"\n`; yaml += ` sourceCluster: "${config.query_options.source_cluster || 'cluster1'}"\n`; } return yaml; } // 复制Redis配置组 async function copyRedisConfigGroup(configId, configName) { try { const response = await fetch(`/api/redis/config-groups/${configId}`); const result = await response.json(); if (result.success) { const originalConfig = result.data; // 创建副本配置 const copyConfig = { name: `${originalConfig.name}_副本`, description: `${originalConfig.description || ''} (复制自: ${originalConfig.name})`, cluster1_config: originalConfig.cluster1_config, cluster2_config: originalConfig.cluster2_config, query_options: originalConfig.query_options }; // 保存副本 const saveResponse = await fetch('/api/redis/config-groups', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(copyConfig) }); const saveResult = await saveResponse.json(); if (saveResult.success) { showAlert(`配置组"${configName}"复制成功`, 'success'); loadRedisConfigGroupsForManagement(); // 刷新列表 } else { showAlert(`复制失败: ${saveResult.error}`, 'danger'); } } else { showAlert(`获取配置失败: ${result.error}`, 'danger'); } } catch (error) { console.error('复制Redis配置组失败:', error); showAlert(`复制失败: ${error.message}`, 'danger'); } } // 测试Redis配置连接 async function testRedisConfigConnection(configId) { try { const response = await fetch(`/api/redis/config-groups/${configId}`); const result = await response.json(); if (result.success) { const config = result.data; // 测试集群1连接 if (config.cluster1_config) { showAlert('正在测试集群1连接...', 'info'); await testSingleClusterConnection(config.cluster1_config, '集群1'); } // 测试集群2连接 if (config.cluster2_config) { showAlert('正在测试集群2连接...', 'info'); await testSingleClusterConnection(config.cluster2_config, '集群2'); } } else { showAlert(`获取配置失败: ${result.error}`, 'danger'); } } catch (error) { console.error('测试Redis配置连接失败:', error); showAlert(`连接测试失败: ${error.message}`, 'danger'); } } // 测试单个集群连接 async function testSingleClusterConnection(clusterConfig, clusterName) { try { const testResponse = await fetch('/api/redis/test-connection', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cluster_config: clusterConfig }) }); const testResult = await testResponse.json(); if (testResult.success) { showAlert(`${clusterName}连接测试成功`, 'success'); } else { showAlert(`${clusterName}连接测试失败: ${testResult.error}`, 'danger'); } } catch (error) { showAlert(`${clusterName}连接测试异常: ${error.message}`, 'danger'); } } // 应用查询选项到UI function applyQueryOptionsToUI(queryOptions) { // 设置查询模式 if (queryOptions.mode === 'random') { document.getElementById('randomMode').checked = true; document.getElementById('specifiedMode').checked = false; } else { document.getElementById('randomMode').checked = false; document.getElementById('specifiedMode').checked = true; } // 触发模式切换 toggleQueryMode(); // 设置查询参数 if (queryOptions.count !== undefined) { const countInput = document.getElementById('sampleCount'); if (countInput) { countInput.value = queryOptions.count; } } if (queryOptions.pattern !== undefined) { const patternInput = document.getElementById('keyPattern'); if (patternInput) { patternInput.value = queryOptions.pattern; } } if (queryOptions.source_cluster !== undefined) { const sourceSelect = document.getElementById('sourceCluster'); if (sourceSelect) { sourceSelect.value = queryOptions.source_cluster; } } // 设置指定Key列表 if (queryOptions.keys && Array.isArray(queryOptions.keys)) { const keysTextarea = document.getElementById('specifiedKeys'); if (keysTextarea) { keysTextarea.value = queryOptions.keys.join('\n'); } } }