Files
BigDataTool/static/js/redis_compare.js
2025-08-04 09:14:27 +08:00

1292 lines
43 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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`);
const nodeInput = document.createElement('div');
nodeInput.className = 'node-input';
nodeInput.innerHTML = `
<input type="text" class="form-control node-host" placeholder="主机地址" value="127.0.0.1">
<input type="number" class="form-control node-port" placeholder="端口" value="7000" style="width: 100px;">
<button type="button" class="btn btn-outline-danger btn-sm remove-node">
<i class="fas fa-minus"></i>
</button>
`;
container.appendChild(nodeInput);
updateNodeDeleteButtons();
}
/**
* 更新节点删除按钮状态
*/
function updateNodeDeleteButtons() {
// 每个集群至少需要一个节点
['cluster1', 'cluster2'].forEach(clusterId => {
const container = document.getElementById(`${clusterId}-nodes`);
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 timeout = parseInt(document.getElementById(`${clusterId}-timeout`).value);
const maxConn = parseInt(document.getElementById(`${clusterId}-max-conn`).value);
// 获取节点列表
const nodes = [];
const nodeInputs = document.querySelectorAll(`#${clusterId}-nodes .node-input`);
nodeInputs.forEach(nodeInput => {
const host = nodeInput.querySelector('.node-host').value.trim();
const port = parseInt(nodeInput.querySelector('.node-port').value);
if (host && port) {
nodes.push({ host, port });
}
});
return {
name,
nodes,
password: password || null,
socket_timeout: timeout,
socket_connect_timeout: timeout,
max_connections_per_node: maxConn
};
}
/**
* 获取查询选项
*/
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');
// 高亮成功的集群配置
document.getElementById(`${clusterId}-config`).classList.add('active');
setTimeout(() => {
document.getElementById(`${clusterId}-config`).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;
}
// 获取配置
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;
}
try {
isQuerying = true;
showLoading('正在执行Redis数据比较请稍候...');
clearResults();
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
})
});
const result = await response.json();
if (response.ok && result.success !== false) {
currentResults = result;
displayResults(result);
showAlert('Redis数据比较完成', 'success');
} else {
showAlert(`比较失败: ${result.error}`, 'danger');
}
} catch (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);
// 更新标签页计数
updateTabCounts(results);
// 显示结果区域
document.getElementById('results').style.display = 'block';
// 滚动到结果区域
document.getElementById('results').scrollIntoView({ behavior: 'smooth' });
}
/**
* 显示统计卡片
*/
function displayStatsCards(stats) {
const container = document.getElementById('statsCards');
container.innerHTML = `
<div class="col-md-3">
<div class="stat-card bg-primary text-white">
<h3>${stats.total_keys}</h3>
<p>总Key数量</p>
</div>
</div>
<div class="col-md-3">
<div class="stat-card bg-success text-white">
<h3>${stats.identical_count}</h3>
<p>相同数据</p>
<small>${stats.identical_percentage}%</small>
</div>
</div>
<div class="col-md-3">
<div class="stat-card bg-danger text-white">
<h3>${stats.different_count}</h3>
<p>差异数据</p>
<small>${stats.different_percentage}%</small>
</div>
</div>
<div class="col-md-3">
<div class="stat-card bg-warning text-white">
<h3>${stats.missing_in_cluster1 + stats.missing_in_cluster2 + stats.both_missing}</h3>
<p>缺失数据</p>
<small>${stats.missing_percentage}%</small>
</div>
</div>
`;
}
/**
* 显示差异结果
*/
function displayDifferenceResults(differences) {
const container = document.getElementById('differenceResults');
if (differences.length === 0) {
container.innerHTML = '<div class="alert alert-success"><i class="fas fa-check-circle me-2"></i>未发现数据差异</div>';
return;
}
let html = '';
differences.forEach((diff, index) => {
html += `
<div class="difference-item">
<h6><i class="fas fa-key me-2"></i>Key: <code>${diff.key}</code></h6>
<div class="row">
<div class="col-md-6">
<strong>${currentResults.clusters.cluster1_name}:</strong>
<pre class="redis-value mt-2">${formatRedisValue(diff.cluster1_value)}</pre>
</div>
<div class="col-md-6">
<strong>${currentResults.clusters.cluster2_name}:</strong>
<pre class="redis-value mt-2">${formatRedisValue(diff.cluster2_value)}</pre>
</div>
</div>
<div class="mt-2">
<span class="badge bg-danger">${diff.message}</span>
</div>
</div>
`;
});
container.innerHTML = html;
}
/**
* 显示相同结果
*/
function displayIdenticalResults(identical) {
const container = document.getElementById('identicalResults');
if (identical.length === 0) {
container.innerHTML = '<div class="alert alert-info"><i class="fas fa-info-circle me-2"></i>没有相同的数据</div>';
return;
}
let html = '';
identical.forEach((item, index) => {
html += `
<div class="identical-item">
<h6><i class="fas fa-key me-2"></i>Key: <code>${item.key}</code></h6>
<div class="mt-2">
<strong>值:</strong>
<pre class="redis-value mt-2">${formatRedisValue(item.value)}</pre>
</div>
<div class="mt-2">
<span class="badge bg-success">数据一致</span>
</div>
</div>
`;
});
container.innerHTML = html;
}
/**
* 显示缺失结果
*/
function displayMissingResults(missing) {
const container = document.getElementById('missingResults');
if (missing.length === 0) {
container.innerHTML = '<div class="alert alert-success"><i class="fas fa-check-circle me-2"></i>没有缺失的数据</div>';
return;
}
let html = '';
missing.forEach((item, index) => {
html += `
<div class="missing-item">
<h6><i class="fas fa-key me-2"></i>Key: <code>${item.key}</code></h6>
<div class="mt-2">
<span class="badge bg-warning">${item.message}</span>
</div>
${item.cluster1_value !== undefined ? `
<div class="mt-2">
<strong>${currentResults.clusters.cluster1_name}:</strong>
<pre class="redis-value mt-1">${formatRedisValue(item.cluster1_value)}</pre>
</div>
` : ''}
${item.cluster2_value !== undefined ? `
<div class="mt-2">
<strong>${currentResults.clusters.cluster2_name}:</strong>
<pre class="redis-value mt-1">${formatRedisValue(item.cluster2_value)}</pre>
</div>
` : ''}
</div>
`;
});
container.innerHTML = html;
}
/**
* 显示性能报告
*/
function displayPerformanceReport(performanceReport) {
const container = document.getElementById('performanceReport');
const connections = performanceReport.connections || {};
const operations = performanceReport.operations || {};
let html = `
<h5><i class="fas fa-chart-line me-2"></i>性能统计报告</h5>
<div class="row">
<div class="col-md-6">
<h6>连接统计</h6>
<table class="table table-sm">
<thead>
<tr>
<th>集群</th>
<th>状态</th>
<th>耗时</th>
</tr>
</thead>
<tbody>
`;
Object.entries(connections).forEach(([clusterName, status]) => {
const statusClass = status.success ? 'success' : 'danger';
const statusText = status.success ? '成功' : '失败';
html += `
<tr>
<td>${clusterName}</td>
<td><span class="badge bg-${statusClass}">${statusText}</span></td>
<td>${status.connect_time.toFixed(3)}s</td>
</tr>
`;
});
html += `
</tbody>
</table>
</div>
<div class="col-md-6">
<h6>操作统计</h6>
<table class="table table-sm">
<thead>
<tr>
<th>操作</th>
<th>耗时</th>
</tr>
</thead>
<tbody>
`;
if (operations.scan_time > 0) {
html += `<tr><td>扫描Keys</td><td>${operations.scan_time.toFixed(3)}s</td></tr>`;
}
Object.entries(operations.queries || {}).forEach(([operation, duration]) => {
html += `<tr><td>${operation}</td><td>${duration.toFixed(3)}s</td></tr>`;
});
if (operations.comparison_time > 0) {
html += `<tr><td>数据比对</td><td>${operations.comparison_time.toFixed(3)}s</td></tr>`;
}
html += `
<tr class="table-primary">
<td><strong>总耗时</strong></td>
<td><strong>${performanceReport.total_time.toFixed(3)}s</strong></td>
</tr>
</tbody>
</table>
</div>
</div>
`;
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() {
document.getElementById('results').style.display = 'none';
currentResults = null;
}
/**
* 显示加载状态
*/
function showLoading(message = '正在处理...') {
const loadingElement = document.querySelector('.loading');
const messageElement = loadingElement.querySelector('span');
messageElement.textContent = message;
loadingElement.style.display = 'block';
}
/**
* 隐藏加载状态
*/
function hideLoading() {
document.querySelector('.loading').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}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
// 插入到页面顶部
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');
select.innerHTML = '<option value="">选择Redis配置组...</option>';
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);
});
}
} catch (error) {
console.error('加载Redis配置组失败:', error);
}
}
// 显示保存Redis配置对话框
function showSaveRedisConfigDialog() {
// 生成默认名称
const timestamp = new Date().toLocaleString('zh-CN');
document.getElementById('redisConfigGroupName').value = `Redis配置_${timestamp}`;
document.getElementById('redisConfigGroupDescription').value = '';
new bootstrap.Modal(document.getElementById('saveRedisConfigModal')).show();
}
// 保存Redis配置组
async function saveRedisConfigGroup() {
const name = document.getElementById('redisConfigGroupName').value.trim();
const description = document.getElementById('redisConfigGroupDescription').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) {
// 设置集群名称
document.getElementById(`${clusterId}-name`).value = config.name || '';
// 设置密码
document.getElementById(`${clusterId}-password`).value = config.password || '';
// 设置超时和连接数
document.getElementById(`${clusterId}-timeout`).value = config.socket_timeout || 3;
document.getElementById(`${clusterId}-max-conn`).value = config.max_connections_per_node || 16;
// 清空现有节点
const container = document.getElementById(`${clusterId}-nodes`);
container.innerHTML = '';
// 添加节点
if (config.nodes && config.nodes.length > 0) {
config.nodes.forEach(node => {
const nodeInput = document.createElement('div');
nodeInput.className = 'node-input';
nodeInput.innerHTML = `
<input type="text" class="form-control node-host" placeholder="主机地址" value="${node.host}">
<input type="number" class="form-control node-port" placeholder="端口" value="${node.port}" style="width: 100px;">
<button type="button" class="btn btn-outline-danger btn-sm remove-node">
<i class="fas fa-minus"></i>
</button>
`;
container.appendChild(nodeInput);
});
} else {
// 添加默认节点
addNode(clusterId);
}
updateNodeDeleteButtons();
}
// 加载查询选项
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 (result.success && result.data && result.data.length > 0) {
let html = '<div class="table-responsive"><table class="table table-striped">';
html += '<thead><tr><th>名称</th><th>描述</th><th>创建时间</th><th>操作</th></tr></thead><tbody>';
result.data.forEach(group => {
html += `
<tr>
<td>${group.name}</td>
<td>${group.description || '无描述'}</td>
<td>${new Date(group.created_at).toLocaleString('zh-CN')}</td>
<td>
<button class="btn btn-primary btn-sm me-1" onclick="loadRedisConfigGroupById(${group.id})">
<i class="fas fa-download"></i> 加载
</button>
<button class="btn btn-danger btn-sm" onclick="deleteRedisConfigGroup(${group.id}, '${group.name}')">
<i class="fas fa-trash"></i> 删除
</button>
</td>
</tr>
`;
});
html += '</tbody></table></div>';
container.innerHTML = html;
} else {
container.innerHTML = '<div class="text-center text-muted py-4">暂无Redis配置组</div>';
}
} catch (error) {
document.getElementById('redisConfigGroupList').innerHTML = '<div class="alert alert-danger">加载失败: ' + error.message + '</div>';
}
}
// 通过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');
select.innerHTML = '<option value="">选择历史记录...</option>';
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);
});
}
} catch (error) {
console.error('加载Redis查询历史失败:', error);
}
}
// 显示保存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 displayResults = {
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 = displayResults;
displayResults(displayResults);
showAlert(`历史记录 "${history.name}" 加载成功`, 'success');
} else {
showAlert(`历史记录 "${history.name}" 配置加载成功,但没有结果数据`, 'info');
}
} else {
showAlert(result.error, 'danger');
}
} catch (error) {
showAlert(`加载失败: ${error.message}`, 'danger');
}
}
// 显示Redis查询历史管理对话框
function showManageRedisHistoryDialog() {
loadRedisHistoryForManagement();
new bootstrap.Modal(document.getElementById('manageRedisHistoryModal')).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 (result.success && result.data && result.data.length > 0) {
let html = '<div class="table-responsive"><table class="table table-striped">';
html += '<thead><tr><th>名称</th><th>描述</th><th>Key数量</th><th>差异数</th><th>执行时间</th><th>创建时间</th><th>操作</th></tr></thead><tbody>';
result.data.forEach(history => {
html += `
<tr>
<td>${history.name}</td>
<td>${history.description || '无描述'}</td>
<td>${history.total_keys}</td>
<td>${history.different_count}</td>
<td>${history.execution_time.toFixed(3)}s</td>
<td>${new Date(history.created_at).toLocaleString('zh-CN')}</td>
<td>
<button class="btn btn-warning btn-sm me-1" onclick="loadRedisHistoryById(${history.id})">
<i class="fas fa-history"></i> 加载
</button>
<button class="btn btn-danger btn-sm" onclick="deleteRedisHistory(${history.id}, '${history.name}')">
<i class="fas fa-trash"></i> 删除
</button>
</td>
</tr>
`;
});
html += '</tbody></table></div>';
container.innerHTML = html;
} else {
container.innerHTML = '<div class="text-center text-muted py-4">暂无Redis查询历史</div>';
}
} catch (error) {
document.getElementById('redisHistoryList').innerHTML = '<div class="alert alert-danger">加载失败: ' + error.message + '</div>';
}
}
// 通过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 displayResults = {
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 = displayResults;
displayResults(displayResults);
}
// 关闭管理对话框
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/query-logs?limit=1000');
const result = await response.json();
const container = document.getElementById('redisQueryLogs');
if (result.success && result.data && result.data.length > 0) {
// 过滤Redis相关的日志
const redisLogs = result.data.filter(log =>
log.message.toLowerCase().includes('redis') ||
log.query_type === 'redis'
);
if (redisLogs.length > 0) {
let html = '';
redisLogs.forEach(log => {
const levelClass = log.level === 'ERROR' ? 'text-danger' :
log.level === 'WARNING' ? 'text-warning' : 'text-info';
html += `
<div class="mb-2">
<span class="text-muted">[${log.timestamp}]</span>
<span class="badge bg-secondary">${log.level}</span>
<span class="${levelClass}">${log.message}</span>
</div>
`;
});
container.innerHTML = html;
} else {
container.innerHTML = '<div class="text-center text-muted py-4">暂无Redis查询日志</div>';
}
} else {
container.innerHTML = '<div class="text-center text-muted py-4">暂无查询日志</div>';
}
} catch (error) {
document.getElementById('redisQueryLogs').innerHTML = '<div class="alert alert-danger">加载日志失败: ' + error.message + '</div>';
}
}
// 刷新Redis查询日志
function refreshRedisQueryLogs() {
loadRedisQueryLogs();
showAlert('查询日志已刷新', 'info');
}
// 清空Redis查询日志
async function clearRedisQueryLogs() {
if (!confirm('确定要清空所有查询日志吗?此操作不可恢复。')) {
return;
}
try {
const response = await fetch('/api/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');
}
}