Files
BigDataTool/templates/redis_compare.html
2025-08-04 21:55:35 +08:00

668 lines
36 KiB
HTML
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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redis集群比对工具</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.config-section {
background-color: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.result-section {
margin-top: 30px;
}
.difference-item {
border-left: 4px solid #dc3545;
padding-left: 15px;
margin-bottom: 15px;
background-color: #fff5f5;
padding: 15px;
border-radius: 5px;
}
.identical-item {
border-left: 4px solid #28a745;
padding-left: 15px;
margin-bottom: 15px;
background-color: #f8fff8;
padding: 15px;
border-radius: 5px;
}
.missing-item {
border-left: 4px solid #ffc107;
padding-left: 15px;
margin-bottom: 15px;
background-color: #fffbf0;
padding: 15px;
border-radius: 5px;
}
.stat-card {
text-align: center;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
}
.loading {
display: none;
}
.query-keys {
min-height: 120px;
}
.redis-value {
font-family: 'Courier New', monospace;
font-size: 0.9em;
background-color: #f8f9fa !important;
white-space: pre-wrap;
word-break: break-all;
}
.node-input {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.node-input input {
margin-right: 10px;
}
.btn-redis {
background: linear-gradient(135deg, #dc143c 0%, #b91c1c 100%);
border: none;
color: white;
}
.btn-redis:hover {
background: linear-gradient(135deg, #b91c1c 0%, #991b1b 100%);
color: white;
transform: translateY(-1px);
}
.redis-logo {
color: #dc143c;
}
.cluster-config {
border: 2px solid #e9ecef;
border-radius: 10px;
padding: 15px;
margin-bottom: 15px;
}
.cluster-config.active {
border-color: #dc143c;
background-color: #fff8f8;
}
.log-viewer {
background-color: #1e1e1e;
color: #d4d4d4;
font-family: 'Courier New', monospace;
font-size: 0.9em;
padding: 15px;
border-radius: 5px;
max-height: 400px;
overflow-y: auto;
}
.navbar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
}
.breadcrumb {
background: none;
padding: 0;
}
.breadcrumb-item a {
color: #007bff;
text-decoration: none;
}
.pagination {
--bs-pagination-padding-x: 0.5rem;
--bs-pagination-padding-y: 0.25rem;
--bs-pagination-font-size: 0.875rem;
}
/* 确保提示消息在最顶层 */
.alert {
position: fixed !important;
top: 20px !important;
left: 50% !important;
transform: translateX(-50%) !important;
z-index: 9999 !important;
min-width: 300px !important;
max-width: 600px !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
}
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container">
<a class="navbar-brand" href="/">
<i class="fas fa-database me-2"></i>
大数据工具集合
</a>
<div class="navbar-nav ms-auto">
<a class="nav-link" href="/">
<i class="fas fa-home"></i> 首页
</a>
<a class="nav-link" href="/db-compare">
<i class="fas fa-exchange-alt"></i> 数据库比对
</a>
<a class="nav-link active" href="/redis-compare">
<i class="fab fa-redis"></i> Redis比对
</a>
</div>
</div>
</nav>
<div class="container-fluid mt-4">
<!-- 面包屑导航 -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">首页</a></li>
<li class="breadcrumb-item active" aria-current="page">Redis集群比对工具</li>
</ol>
</nav>
<!-- 页面标题 -->
<div class="row">
<div class="col-12">
<h1 class="text-center mb-4">
<i class="fab fa-redis redis-logo"></i> Redis集群比对工具
<small class="text-muted d-block fs-6 mt-2">专业的Redis集群数据比对工具支持随机采样和指定Key查询</small>
</h1>
</div>
</div>
<div class="row">
<!-- 配置面板 -->
<div class="col-lg-4">
<div class="config-section">
<h4><i class="fas fa-cogs"></i> 配置管理</h4>
<!-- 配置组管理 -->
<div class="card mb-3">
<div class="card-header">
<h6><i class="fas fa-layer-group"></i> 配置组管理</h6>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-8">
<select class="form-select form-select-sm" id="redisConfigGroupSelect">
<option value="">选择Redis配置组...</option>
</select>
</div>
<div class="col-4">
<button class="btn btn-primary btn-sm w-100" onclick="loadSelectedRedisConfigGroup()">
<i class="fas fa-download"></i> 加载
</button>
</div>
</div>
<div class="row">
<div class="col-4">
<button class="btn btn-success btn-sm w-100" onclick="showSaveRedisConfigDialog()">
<i class="fas fa-save"></i> 保存配置组
</button>
</div>
<div class="col-4">
<button class="btn btn-info btn-sm w-100" onclick="showManageRedisConfigDialog()">
<i class="fas fa-cog"></i> 管理配置组
</button>
</div>
<div class="col-4">
<button class="btn btn-warning btn-sm w-100" onclick="showImportRedisConfigDialog()">
<i class="fas fa-file-import"></i> 导入配置
</button>
</div>
</div>
<!-- 查询历史和日志 -->
<div class="row mt-3">
<div class="col-12">
<h6><i class="fas fa-history"></i> 历史记录与日志</h6>
</div>
</div>
<div class="row mb-3">
<div class="col-8">
<select class="form-select form-select-sm" id="redisHistorySelect">
<option value="">选择查询历史...</option>
</select>
</div>
<div class="col-4">
<button class="btn btn-warning btn-sm w-100" onclick="loadSelectedRedisHistory()">
<i class="fas fa-download"></i> 加载
</button>
</div>
</div>
<div class="row">
<div class="col-6">
<button class="btn btn-info btn-sm w-100" onclick="showManageRedisHistoryDialog()">
<i class="fas fa-history"></i> 管理历史
</button>
</div>
<div class="col-6">
<button class="btn btn-outline-info btn-sm w-100" onclick="showRedisQueryLogsDialog()">
<i class="fas fa-file-alt"></i> 查询日志
</button>
</div>
</div>
</div>
</div>
<!-- Redis集群配置 -->
<div class="card mb-3">
<div class="card-header">
<h6><i class="fas fa-server"></i> Redis集群配置</h6>
</div>
<div class="card-body">
<!-- 集群1配置 -->
<div class="cluster-config mb-3">
<h6 class="text-primary"><i class="fas fa-server"></i> 集群1 (生产)</h6>
<div class="row mb-2">
<div class="col-12">
<label class="form-label">集群名称</label>
<input type="text" class="form-control form-control-sm" id="cluster1Name" placeholder="生产集群" value="生产集群">
</div>
</div>
<div class="mb-2">
<label class="form-label">Redis节点</label>
<div id="cluster1Nodes">
<div class="node-input">
<input type="text" class="form-control form-control-sm me-2" placeholder="127.0.0.1" value="127.0.0.1" style="flex: 2;">
<input type="number" class="form-control form-control-sm me-2" placeholder="6379" value="6379" style="flex: 1;">
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeRedisNode(this, 'cluster1')">
<i class="fas fa-minus"></i>
</button>
</div>
</div>
<button type="button" class="btn btn-outline-primary btn-sm mt-2" onclick="addRedisNode('cluster1')">
<i class="fas fa-plus"></i> 添加节点
</button>
</div>
<div class="row">
<div class="col-6">
<label class="form-label">密码</label>
<input type="password" class="form-control form-control-sm" id="cluster1Password" placeholder="可选">
</div>
<div class="col-6">
<button class="btn btn-outline-primary btn-sm mt-4 w-100" onclick="testConnection('cluster1')">
<i class="fas fa-plug"></i> 测试连接
</button>
</div>
</div>
</div>
<!-- 集群2配置 -->
<div class="cluster-config">
<h6 class="text-success"><i class="fas fa-server"></i> 集群2 (测试)</h6>
<div class="row mb-2">
<div class="col-12">
<label class="form-label">集群名称</label>
<input type="text" class="form-control form-control-sm" id="cluster2Name" placeholder="测试集群" value="测试集群">
</div>
</div>
<div class="mb-2">
<label class="form-label">Redis节点</label>
<div id="cluster2Nodes">
<div class="node-input">
<input type="text" class="form-control form-control-sm me-2" placeholder="127.0.0.1" value="127.0.0.1" style="flex: 2;">
<input type="number" class="form-control form-control-sm me-2" placeholder="6380" value="6380" style="flex: 1;">
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeRedisNode(this, 'cluster2')">
<i class="fas fa-minus"></i>
</button>
</div>
</div>
<button type="button" class="btn btn-outline-primary btn-sm mt-2" onclick="addRedisNode('cluster2')">
<i class="fas fa-plus"></i> 添加节点
</button>
</div>
<div class="row">
<div class="col-6">
<label class="form-label">密码</label>
<input type="password" class="form-control form-control-sm" id="cluster2Password" placeholder="可选">
</div>
<div class="col-6">
<button class="btn btn-outline-primary btn-sm mt-4 w-100" onclick="testConnection('cluster2')">
<i class="fas fa-plug"></i> 测试连接
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 查询面板和结果展示 -->
<div class="col-lg-8">
<div class="config-section">
<h4><i class="fas fa-search"></i> 查询配置</h4>
<!-- 查询模式选择 -->
<div class="card mb-3">
<div class="card-header">
<h6><i class="fas fa-sliders-h"></i> 查询模式</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="radio" name="queryMode" id="randomMode" value="random" checked onchange="toggleQueryMode()">
<label class="form-check-label" for="randomMode">
<strong>随机采样模式</strong>
</label>
</div>
<div id="randomOptions" class="mt-2">
<div class="row">
<div class="col-6">
<label class="form-label">采样数量</label>
<input type="number" class="form-control form-control-sm" id="sampleCount" value="100" min="1" max="10000">
</div>
<div class="col-6">
<label class="form-label">Key模式</label>
<input type="text" class="form-control form-control-sm" id="keyPattern" value="*" placeholder="*">
</div>
</div>
<div class="mt-2">
<label class="form-label">源集群</label>
<select class="form-select form-select-sm" id="sourceCluster">
<option value="cluster1">从集群1获取Key</option>
<option value="cluster2" selected>从集群2获取Key</option>
</select>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="radio" name="queryMode" id="specifiedMode" value="specified" onchange="toggleQueryMode()">
<label class="form-check-label" for="specifiedMode">
<strong>指定Key模式</strong>
</label>
</div>
<div id="specifiedOptions" class="mt-2" style="display: none;">
<label class="form-label">Key列表 (每行一个)</label>
<textarea class="form-control query-keys" id="specifiedKeys" rows="6" placeholder="输入要查询的Key每行一个&#10;例如:&#10;user:1001&#10;user:1002&#10;session:abc123"></textarea>
<small class="form-text text-muted">支持大批量Key查询建议单次不超过1000个</small>
</div>
</div>
</div>
</div>
</div>
<!-- 执行按钮 -->
<div class="text-center mb-4">
<button type="button" class="btn btn-redis btn-lg me-3" onclick="executeRedisComparison()">
<i class="fas fa-play me-2"></i>开始Redis数据比较
</button>
<button type="button" class="btn btn-outline-secondary btn-lg" onclick="clearResults()">
<i class="fas fa-eraser me-2"></i>清空结果
</button>
</div>
</div>
<!-- 结果展示区域 -->
<div class="result-section" id="resultSection" style="display: none;">
<!-- 统计信息 -->
<div class="row" id="stats">
<!-- 统计卡片将在这里动态生成 -->
</div>
<!-- 结果选项卡导航 -->
<div class="card mt-4">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs" id="resultTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="differences-tab" data-bs-toggle="tab" data-bs-target="#differences-panel" type="button" role="tab">
<i class="fas fa-exclamation-triangle"></i> 差异详情 <span class="badge bg-danger ms-1" id="diff-count">0</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="identical-tab" data-bs-toggle="tab" data-bs-target="#identical-panel" type="button" role="tab">
<i class="fas fa-check-circle"></i> 相同结果 <span class="badge bg-success ms-1" id="identical-count">0</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="missing-tab" data-bs-toggle="tab" data-bs-target="#missing-panel" type="button" role="tab">
<i class="fas fa-question-circle"></i> 缺失数据 <span class="badge bg-warning ms-1" id="missing-count">0</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="raw-data-tab" data-bs-toggle="tab" data-bs-target="#raw-data-panel" type="button" role="tab">
<i class="fas fa-database"></i> 原生数据
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="summary-tab" data-bs-toggle="tab" data-bs-target="#summary-panel" type="button" role="tab">
<i class="fas fa-chart-pie"></i> 比较总结
</button>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content" id="resultTabContent">
<!-- 差异详情面板 -->
<div class="tab-pane fade show active" id="differences-panel" role="tabpanel">
<div id="differences-content">
<!-- 差异内容将在这里动态生成 -->
</div>
<div id="differences-pagination" class="d-flex justify-content-center mt-3">
<!-- 分页将在这里动态生成 -->
</div>
</div>
<!-- 相同结果面板 -->
<div class="tab-pane fade" id="identical-panel" role="tabpanel">
<div id="identical-content">
<!-- 相同结果内容将在这里动态生成 -->
</div>
<div id="identical-pagination" class="d-flex justify-content-center mt-3">
<!-- 分页将在这里动态生成 -->
</div>
</div>
<!-- 缺失数据面板 -->
<div class="tab-pane fade" id="missing-panel" role="tabpanel">
<div id="missing-content">
<!-- 缺失数据内容将在这里动态生成 -->
</div>
<div id="missing-pagination" class="d-flex justify-content-center mt-3">
<!-- 分页将在这里动态生成 -->
</div>
</div>
<!-- 原生数据面板 -->
<div class="tab-pane fade" id="raw-data-panel" role="tabpanel">
<div class="row">
<div class="col-md-6">
<h5><i class="fas fa-server text-primary"></i> 集群1 原生数据</h5>
<div class="mb-3">
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-outline-primary active" onclick="switchRawDataView('cluster1', 'formatted')">格式化</button>
<button type="button" class="btn btn-outline-primary" onclick="switchRawDataView('cluster1', 'raw')">原始</button>
<button type="button" class="btn btn-outline-primary" onclick="exportRawData('cluster1')">导出</button>
</div>
</div>
<div id="cluster1-raw-data" class="redis-value" style="max-height: 500px; overflow-y: auto;">
<!-- 集群1原生数据将在这里显示 -->
</div>
</div>
<div class="col-md-6">
<h5><i class="fas fa-server text-success"></i> 集群2 原生数据</h5>
<div class="mb-3">
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-outline-success active" onclick="switchRawDataView('cluster2', 'formatted')">格式化</button>
<button type="button" class="btn btn-outline-success" onclick="switchRawDataView('cluster2', 'raw')">原始</button>
<button type="button" class="btn btn-outline-success" onclick="exportRawData('cluster2')">导出</button>
</div>
</div>
<div id="cluster2-raw-data" class="redis-value" style="max-height: 500px; overflow-y: auto;">
<!-- 集群2原生数据将在这里显示 -->
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
<strong>提示:</strong>原生数据显示实际从Redis查询到的数据。格式化视图便于阅读原始视图保持数据原始格式。
</div>
</div>
</div>
</div>
<!-- 比较总结面板 -->
<div class="tab-pane fade" id="summary-panel" role="tabpanel">
<div id="performanceReport">
<!-- 性能报告将在这里动态生成 -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 加载提示 -->
<div class="loading" id="loadingIndicator">
<div class="position-fixed top-50 start-50 translate-middle bg-dark text-white p-4 rounded shadow" style="z-index: 9999;">
<div class="d-flex align-items-center">
<div class="spinner-border spinner-border-sm me-3" role="status"></div>
<span id="loadingText">正在执行Redis数据比较请稍候...</span>
</div>
</div>
</div>
<!-- 配置组保存对话框 -->
<div class="modal fade" id="saveRedisConfigModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">保存Redis配置组</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="redisConfigName" class="form-label">配置组名称</label>
<input type="text" class="form-control" id="redisConfigName" placeholder="输入配置组名称">
</div>
<div class="mb-3">
<label for="redisConfigDescription" class="form-label">描述</label>
<textarea class="form-control" id="redisConfigDescription" rows="3" placeholder="配置组描述(可选)"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-success" onclick="saveRedisConfigGroup()">保存</button>
</div>
</div>
</div>
</div>
<!-- 配置组管理对话框 -->
<div class="modal fade" id="manageRedisConfigModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">管理Redis配置组</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="redisConfigGroupList">
<!-- 配置组列表将在这里动态生成 -->
</div>
</div>
</div>
</div>
</div>
<!-- 历史记录管理对话框 -->
<div class="modal fade" id="manageRedisHistoryModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">管理Redis查询历史</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="redisHistoryList">
<!-- 历史记录列表将在这里动态生成 -->
</div>
</div>
</div>
</div>
</div>
<!-- 查询日志对话框 -->
<div class="modal fade" id="redisQueryLogsModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Redis查询日志</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="d-flex justify-content-end mb-3">
<button class="btn btn-sm btn-outline-primary me-2" onclick="refreshRedisQueryLogs()">
<i class="fas fa-sync-alt"></i> 刷新
</button>
<button class="btn btn-sm btn-outline-danger" onclick="clearRedisQueryLogs()">
<i class="fas fa-trash"></i> 清空日志
</button>
</div>
<div class="log-viewer" id="redisQueryLogs">
<!-- 查询日志将在这里动态生成 -->
</div>
</div>
</div>
</div>
</div>
<!-- 导入配置对话框 -->
<div class="modal fade" id="importRedisConfigModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">导入Redis配置</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="configFormat" class="form-label">配置格式</label>
<select class="form-select" id="configFormat" onchange="updateConfigTemplate()">
<option value="yaml">YAML格式</option>
<option value="json">JSON格式</option>
</select>
</div>
<div class="mb-3">
<label for="configContent" class="form-label">配置内容</label>
<textarea class="form-control" id="configContent" rows="15" placeholder="请粘贴配置内容..."></textarea>
<small class="form-text text-muted">支持YAML和JSON格式的Redis配置</small>
</div>
<div class="mb-3">
<div class="accordion" id="configTemplateAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="templateHeading">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#templateCollapse">
<i class="fas fa-code me-2"></i>配置示例
</button>
</h2>
<div id="templateCollapse" class="accordion-collapse collapse" data-bs-parent="#configTemplateAccordion">
<div class="accordion-body">
<pre id="configTemplate" class="bg-light p-3 rounded" style="font-size: 0.85em;"></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" onclick="importRedisConfig()">导入配置</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/redis_compare.js"></script>
</body>
</html>