966 lines
52 KiB
HTML
966 lines
52 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Redis集群比对工具 - DataTools Pro</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;
|
||
}
|
||
|
||
/* 拖拽区域样式 */
|
||
#dropZone {
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
#dropZone:hover {
|
||
border-color: #dc143c !important;
|
||
background-color: #fff8f8;
|
||
}
|
||
|
||
#dropZone.dragover {
|
||
border-color: #dc143c !important;
|
||
background-color: #fff8f8;
|
||
transform: scale(1.02);
|
||
}
|
||
|
||
/* 配置预览样式 */
|
||
.config-preview-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 5px 0;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
|
||
.config-preview-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.config-preview-label {
|
||
font-weight: 600;
|
||
color: #495057;
|
||
}
|
||
|
||
.config-preview-value {
|
||
color: #6c757d;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 0.9em;
|
||
}
|
||
|
||
/* Redis日志分组样式 */
|
||
.redis-log-group {
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.redis-log-group .card-header {
|
||
transition: background-color 0.2s ease;
|
||
}
|
||
|
||
.redis-log-group .card-header:hover {
|
||
background-color: rgba(0, 123, 255, 0.1) !important;
|
||
}
|
||
|
||
.redis-log-content {
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.log-item {
|
||
background-color: #f8f9fa;
|
||
border-radius: 4px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.log-item:hover {
|
||
background-color: #e9ecef;
|
||
transform: translateX(2px);
|
||
}
|
||
|
||
.log-item .badge {
|
||
min-width: 60px;
|
||
font-size: 0.75em;
|
||
}
|
||
|
||
/* 日志级别边框颜色 */
|
||
.border-info {
|
||
border-color: #0dcaf0 !important;
|
||
}
|
||
|
||
.border-warning {
|
||
border-color: #ffc107 !important;
|
||
}
|
||
|
||
.border-danger {
|
||
border-color: #dc3545 !important;
|
||
}
|
||
|
||
.border-secondary {
|
||
border-color: #6c757d !important;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- 导航栏 -->
|
||
<nav class="navbar navbar-expand-lg navbar-dark">
|
||
<div class="container">
|
||
<a class="navbar-brand" href="/">
|
||
<i class="fab fa-redis me-2 redis-logo"></i>
|
||
DataTools Pro
|
||
</a>
|
||
<div class="navbar-nav ms-auto">
|
||
<a class="nav-link" href="/">
|
||
<i class="fas fa-home"></i> 首页
|
||
</a>
|
||
<a class="nav-link" href="/cassandra-compare">
|
||
<i class="fas fa-database"></i> Cassandra比对
|
||
</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-6">
|
||
<button class="btn btn-success btn-sm w-100" onclick="showSaveRedisConfigDialog()">
|
||
<i class="fas fa-save"></i> 保存配置组
|
||
</button>
|
||
</div>
|
||
<div class="col-6">
|
||
<button class="btn btn-info btn-sm w-100" onclick="showManageRedisConfigDialog()">
|
||
<i class="fas fa-cog"></i> 管理配置组
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row mt-2">
|
||
<div class="col-4">
|
||
<button class="btn btn-warning btn-sm w-100" onclick="showRedisQueryHistoryDialog()">
|
||
<i class="fas fa-history"></i> 查询历史
|
||
</button>
|
||
</div>
|
||
<div class="col-4">
|
||
<button class="btn btn-info btn-sm w-100" onclick="showRedisQueryLogsDialog()">
|
||
<i class="fas fa-file-alt"></i> 查询日志
|
||
</button>
|
||
</div>
|
||
<div class="col-4">
|
||
<button class="btn btn-secondary btn-sm w-100" onclick="showSaveRedisHistoryDialog()">
|
||
<i class="fas fa-bookmark"></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">
|
||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||
<h6 class="text-primary mb-0"><i class="fas fa-server"></i> 集群1 (生产)</h6>
|
||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="showImportConfigDialog('cluster1')" title="导入YAML配置">
|
||
<i class="fas fa-file-import"></i> 导入配置
|
||
</button>
|
||
</div>
|
||
<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 mb-2">
|
||
<div class="col-12">
|
||
<label class="form-label">密码</label>
|
||
<input type="password" class="form-control form-control-sm" id="cluster1Password" placeholder="可选">
|
||
</div>
|
||
</div>
|
||
<div class="row mb-2">
|
||
<div class="col-4">
|
||
<label class="form-label">连接超时(秒)</label>
|
||
<input type="number" class="form-control form-control-sm" id="cluster1SocketTimeout" value="3" min="1" max="60">
|
||
</div>
|
||
<div class="col-4">
|
||
<label class="form-label">建立超时(秒)</label>
|
||
<input type="number" class="form-control form-control-sm" id="cluster1SocketConnectTimeout" value="3" min="1" max="60">
|
||
</div>
|
||
<div class="col-4">
|
||
<label class="form-label">最大连接数</label>
|
||
<input type="number" class="form-control form-control-sm" id="cluster1MaxConnectionsPerNode" value="16" min="1" max="100">
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<button class="btn btn-outline-primary btn-sm w-100" onclick="testConnection('cluster1')">
|
||
<i class="fas fa-plug"></i> 测试连接
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 集群2配置 -->
|
||
<div class="cluster-config">
|
||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||
<h6 class="text-success mb-0"><i class="fas fa-server"></i> 集群2 (测试)</h6>
|
||
<button type="button" class="btn btn-outline-success btn-sm" onclick="showImportConfigDialog('cluster2')" title="导入YAML配置">
|
||
<i class="fas fa-file-import"></i> 导入配置
|
||
</button>
|
||
</div>
|
||
<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 mb-2">
|
||
<div class="col-12">
|
||
<label class="form-label">密码</label>
|
||
<input type="password" class="form-control form-control-sm" id="cluster2Password" placeholder="可选">
|
||
</div>
|
||
</div>
|
||
<div class="row mb-2">
|
||
<div class="col-4">
|
||
<label class="form-label">连接超时(秒)</label>
|
||
<input type="number" class="form-control form-control-sm" id="cluster2SocketTimeout" value="3" min="1" max="60">
|
||
</div>
|
||
<div class="col-4">
|
||
<label class="form-label">建立超时(秒)</label>
|
||
<input type="number" class="form-control form-control-sm" id="cluster2SocketConnectTimeout" value="3" min="1" max="60">
|
||
</div>
|
||
<div class="col-4">
|
||
<label class="form-label">最大连接数</label>
|
||
<input type="number" class="form-control form-control-sm" id="cluster2MaxConnectionsPerNode" value="16" min="1" max="100">
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<button class="btn btn-outline-primary btn-sm 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,每行一个 例如: user:example1 user:example2 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="importConfigModal" tabindex="-1" aria-labelledby="importConfigModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="importConfigModalLabel">
|
||
<i class="fas fa-file-import redis-logo"></i> 导入Redis配置
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="mb-3">
|
||
<h6 class="mb-2">导入方式</h6>
|
||
<div class="btn-group w-100" role="group">
|
||
<input type="radio" class="btn-check" name="importMethod" id="importMethodText" checked>
|
||
<label class="btn btn-outline-primary" for="importMethodText">
|
||
<i class="fas fa-keyboard"></i> 文本粘贴
|
||
</label>
|
||
<input type="radio" class="btn-check" name="importMethod" id="importMethodFile">
|
||
<label class="btn btn-outline-primary" for="importMethodFile">
|
||
<i class="fas fa-file-upload"></i> 文件上传
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 文本粘贴方式 -->
|
||
<div id="textImportSection">
|
||
<div class="mb-3">
|
||
<label for="configYamlText" class="form-label">YAML配置内容</label>
|
||
<textarea class="form-control" id="configYamlText" rows="8" placeholder="请粘贴YAML格式的配置内容,例如: clusterName: "redis-example" clusterAddress: "127.0.0.1:6379" clusterPassword: """></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 文件上传方式 -->
|
||
<div id="fileImportSection" style="display: none;">
|
||
<div class="mb-3">
|
||
<label for="configYamlFile" class="form-label">选择YAML配置文件</label>
|
||
<input type="file" class="form-control" id="configYamlFile" accept=".yml,.yaml,.txt">
|
||
</div>
|
||
<div class="border rounded p-3 text-center" id="dropZone" style="border-style: dashed !important; min-height: 100px; cursor: pointer;">
|
||
<i class="fas fa-cloud-upload-alt fa-2x text-muted mb-2"></i>
|
||
<p class="text-muted mb-0">点击选择文件或拖拽文件到此处</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 配置预览 -->
|
||
<div id="configPreview" style="display: none;">
|
||
<hr>
|
||
<h6><i class="fas fa-eye"></i> 配置预览</h6>
|
||
<div class="alert alert-info">
|
||
<div id="previewContent"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-outline-secondary me-2" onclick="previewConfig()">
|
||
<i class="fas fa-eye"></i> 预览配置
|
||
</button>
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||
<button type="button" class="btn btn-redis" onclick="importConfig()">
|
||
<i class="fas fa-file-import"></i> 导入
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Redis查询历史模态框 -->
|
||
<div class="modal fade" id="redisQueryHistoryModal" tabindex="-1" aria-labelledby="redisQueryHistoryModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-xl">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="redisQueryHistoryModalLabel">
|
||
<i class="fas fa-history redis-logo"></i> Redis查询历史管理
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||
<h6 class="mb-0">历史查询记录</h6>
|
||
<div>
|
||
<button class="btn btn-sm btn-outline-primary me-2" onclick="refreshRedisQueryHistory()">
|
||
<i class="fas fa-sync-alt"></i> 刷新
|
||
</button>
|
||
<button class="btn btn-sm btn-outline-danger" onclick="clearAllRedisHistory()">
|
||
<i class="fas fa-trash"></i> 清空全部
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div id="redisHistoryList" style="max-height: 600px; overflow-y: auto;">
|
||
<!-- Redis历史记录列表将在这里动态生成 -->
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Redis查询日志模态框 -->
|
||
<div class="modal fade" id="redisQueryLogsModal" tabindex="-1" aria-labelledby="redisQueryLogsModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-xl">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="redisQueryLogsModalLabel">
|
||
<i class="fas fa-file-alt redis-logo"></i> Redis查询日志管理
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||
<div class="d-flex align-items-center">
|
||
<h6 class="mb-0 me-3">Redis查询执行日志</h6>
|
||
<div class="form-check form-check-inline">
|
||
<input class="form-check-input" type="checkbox" id="redis-modal-log-level-info" checked onchange="filterRedisModalLogsByLevel()">
|
||
<label class="form-check-label text-primary" for="redis-modal-log-level-info">INFO</label>
|
||
</div>
|
||
<div class="form-check form-check-inline">
|
||
<input class="form-check-input" type="checkbox" id="redis-modal-log-level-warning" checked onchange="filterRedisModalLogsByLevel()">
|
||
<label class="form-check-label text-warning" for="redis-modal-log-level-warning">WARNING</label>
|
||
</div>
|
||
<div class="form-check form-check-inline">
|
||
<input class="form-check-input" type="checkbox" id="redis-modal-log-level-error" checked onchange="filterRedisModalLogsByLevel()">
|
||
<label class="form-check-label text-danger" for="redis-modal-log-level-error">ERROR</label>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<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>
|
||
<div id="redis-modal-query-logs" style="max-height: 600px; overflow-y: auto;">
|
||
<!-- Redis查询日志将在这里动态生成 -->
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||
</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>
|
||
|
||
<!-- 保存Redis配置组模态框 -->
|
||
<div class="modal fade" id="saveRedisConfigModal" tabindex="-1" aria-labelledby="saveRedisConfigModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="saveRedisConfigModalLabel">
|
||
<i class="fas fa-save redis-logo"></i> 保存Redis配置组
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="saveRedisConfigForm">
|
||
<div class="mb-3">
|
||
<label for="redisConfigName" class="form-label">配置组名称 <span class="text-danger">*</span></label>
|
||
<input type="text" class="form-control" id="redisConfigName" required>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="redisConfigDescription" class="form-label">配置描述</label>
|
||
<textarea class="form-control" id="redisConfigDescription" rows="3" placeholder="请输入配置组的详细描述..."></textarea>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||
<button type="button" class="btn btn-redis" onclick="saveRedisConfigGroup()">
|
||
<i class="fas fa-save"></i> 保存
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 管理Redis配置组模态框 -->
|
||
<div class="modal fade" id="manageRedisConfigModal" tabindex="-1" aria-labelledby="manageRedisConfigModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-xl">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="manageRedisConfigModalLabel">
|
||
<i class="fas fa-cog redis-logo"></i> 管理Redis配置组
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||
<h6 class="mb-0">已保存的配置组</h6>
|
||
<button class="btn btn-sm btn-outline-primary" onclick="refreshRedisConfigGroups()">
|
||
<i class="fas fa-sync-alt"></i> 刷新
|
||
</button>
|
||
</div>
|
||
<div id="redisConfigGroupsList" style="max-height: 600px; overflow-y: auto;">
|
||
<!-- Redis配置组列表将在这里动态生成 -->
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 保存Redis查询历史模态框 -->
|
||
<div class="modal fade" id="saveRedisHistoryModal" tabindex="-1" aria-labelledby="saveRedisHistoryModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="saveRedisHistoryModalLabel">
|
||
<i class="fas fa-bookmark redis-logo"></i> 保存Redis查询历史
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="saveRedisHistoryForm">
|
||
<div class="mb-3">
|
||
<label for="redisHistoryName" class="form-label">历史记录名称 <span class="text-danger">*</span></label>
|
||
<input type="text" class="form-control" id="redisHistoryName" required>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="redisHistoryDescription" class="form-label">历史描述</label>
|
||
<textarea class="form-control" id="redisHistoryDescription" rows="3" placeholder="请输入查询历史的详细描述..."></textarea>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||
<button type="button" class="btn btn-redis" onclick="saveRedisQueryHistory()">
|
||
<i class="fas fa-bookmark"></i> 保存
|
||
</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> |