461 lines
22 KiB
HTML
461 lines
22 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>数据库查询比对工具</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;
|
||
}
|
||
.stat-card {
|
||
text-align: center;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.loading {
|
||
display: none;
|
||
}
|
||
.query-keys {
|
||
min-height: 120px;
|
||
}
|
||
.compare-fields {
|
||
min-height: 80px;
|
||
}
|
||
.exclude-fields {
|
||
min-height: 80px;
|
||
}
|
||
.json-field {
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 0.9em;
|
||
background-color: #f8f9fa !important;
|
||
}
|
||
.badge {
|
||
font-size: 0.7em;
|
||
}
|
||
.field-container {
|
||
border: 1px solid #e9ecef;
|
||
border-radius: 5px;
|
||
padding: 10px;
|
||
margin-bottom: 10px;
|
||
}
|
||
.field-value {
|
||
font-size: 0.85em;
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
margin: 0;
|
||
}
|
||
.field-header {
|
||
font-weight: 600;
|
||
color: #495057;
|
||
}
|
||
.pagination {
|
||
--bs-pagination-padding-x: 0.5rem;
|
||
--bs-pagination-padding-y: 0.25rem;
|
||
--bs-pagination-font-size: 0.875rem;
|
||
}
|
||
.copy-btn {
|
||
position: absolute;
|
||
top: 5px;
|
||
right: 5px;
|
||
padding: 2px 6px;
|
||
font-size: 0.75rem;
|
||
border-radius: 3px;
|
||
}
|
||
.field-container {
|
||
position: relative;
|
||
}
|
||
|
||
/* 确保提示消息在最顶层 */
|
||
.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;
|
||
}
|
||
|
||
/* 树形视图样式 */
|
||
.tree-view {
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 0.9em;
|
||
max-height: 500px;
|
||
overflow-y: auto;
|
||
border: 1px solid #e9ecef;
|
||
border-radius: 5px;
|
||
padding: 10px;
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
.tree-node {
|
||
margin: 2px 0;
|
||
}
|
||
|
||
.tree-toggle {
|
||
cursor: pointer;
|
||
user-select: none;
|
||
margin-right: 5px;
|
||
color: #6c757d;
|
||
}
|
||
|
||
.tree-toggle:hover {
|
||
color: #495057;
|
||
}
|
||
|
||
.tree-children {
|
||
margin-left: 15px;
|
||
}
|
||
|
||
.tree-item {
|
||
margin: 1px 0;
|
||
padding: 1px 0;
|
||
}
|
||
|
||
/* 原生数据搜索高亮 */
|
||
.raw-data-container mark {
|
||
background-color: #fff3cd !important;
|
||
padding: 1px 2px;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
/* 自定义分页输入框 */
|
||
.form-control:focus {
|
||
border-color: #86b7fe;
|
||
outline: 0;
|
||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- 导航栏 -->
|
||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||
<div class="container">
|
||
<a class="navbar-brand" href="/">
|
||
<i class="fas fa-tools"></i> 大数据工具集合
|
||
</a>
|
||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||
<span class="navbar-toggler-icon"></span>
|
||
</button>
|
||
<div class="collapse navbar-collapse" id="navbarNav">
|
||
<ul class="navbar-nav ms-auto">
|
||
<li class="nav-item">
|
||
<a class="nav-link" href="/">首页</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link active" href="/db-compare">数据库比对</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<div class="container-fluid py-4">
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<h1 class="text-center mb-4">
|
||
<i class="fas fa-database"></i> 数据库查询比对工具
|
||
</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="configGroupSelect">
|
||
<option value="">选择配置组...</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-4">
|
||
<button class="btn btn-primary btn-sm w-100" onclick="loadSelectedConfigGroup()">
|
||
<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="showSaveConfigDialog()">
|
||
<i class="fas fa-save"></i> 保存配置组
|
||
</button>
|
||
</div>
|
||
<div class="col-6">
|
||
<button class="btn btn-info btn-sm w-100" onclick="showManageConfigDialog()">
|
||
<i class="fas fa-cog"></i> 管理配置组
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="row mt-2">
|
||
<div class="col-6">
|
||
<button class="btn btn-warning btn-sm w-100" onclick="showQueryHistoryDialog()">
|
||
<i class="fas fa-history"></i> 查询历史
|
||
</button>
|
||
</div>
|
||
<div class="col-6">
|
||
<button class="btn btn-secondary btn-sm w-100" onclick="showSaveHistoryDialog()">
|
||
<i class="fas fa-bookmark"></i> 保存历史
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<button class="btn btn-secondary btn-sm" onclick="loadDefaultConfig()">
|
||
<i class="fas fa-refresh"></i> 重置为空配置
|
||
</button>
|
||
<button class="btn btn-success btn-sm" onclick="exportConfig()">
|
||
<i class="fas fa-file-export"></i> 导出配置
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 生产环境配置 -->
|
||
<div class="card mb-3">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<h6><i class="fas fa-server"></i> 生产环境配置</h6>
|
||
<button class="btn btn-sm btn-outline-primary" onclick="showImportDialog('pro')">
|
||
<i class="fas fa-download"></i> 一键导入
|
||
</button>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<div class="col-6">
|
||
<label class="form-label">集群名称</label>
|
||
<input type="text" class="form-control form-control-sm" id="pro_cluster_name" placeholder="Production Cluster">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="form-label">数据中心</label>
|
||
<input type="text" class="form-control form-control-sm" id="pro_datacenter" placeholder="dc1">
|
||
</div>
|
||
</div>
|
||
<div class="row mt-2">
|
||
<div class="col-8">
|
||
<label class="form-label">集群节点 (逗号分隔)</label>
|
||
<input type="text" class="form-control form-control-sm" id="pro_hosts" placeholder="10.20.2.22,10.20.2.23">
|
||
</div>
|
||
<div class="col-4">
|
||
<label class="form-label">端口</label>
|
||
<input type="number" class="form-control form-control-sm" id="pro_port" placeholder="9042">
|
||
</div>
|
||
</div>
|
||
<div class="row mt-2">
|
||
<div class="col-6">
|
||
<label class="form-label">用户名</label>
|
||
<input type="text" class="form-control form-control-sm" id="pro_username" placeholder="cbase">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="form-label">密码</label>
|
||
<input type="password" class="form-control form-control-sm" id="pro_password">
|
||
</div>
|
||
</div>
|
||
<div class="row mt-2">
|
||
<div class="col-6">
|
||
<label class="form-label">Keyspace</label>
|
||
<input type="text" class="form-control form-control-sm" id="pro_keyspace" placeholder="yuqing_skinny">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="form-label">表名</label>
|
||
<input type="text" class="form-control form-control-sm" id="pro_table" placeholder="document">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 测试环境配置 -->
|
||
<div class="card mb-3">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<h6><i class="fas fa-flask"></i> 测试环境配置</h6>
|
||
<button class="btn btn-sm btn-outline-primary" onclick="showImportDialog('test')">
|
||
<i class="fas fa-download"></i> 一键导入
|
||
</button>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<div class="col-6">
|
||
<label class="form-label">集群名称</label>
|
||
<input type="text" class="form-control form-control-sm" id="test_cluster_name" placeholder="Test Cluster">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="form-label">数据中心</label>
|
||
<input type="text" class="form-control form-control-sm" id="test_datacenter" placeholder="dc1">
|
||
</div>
|
||
</div>
|
||
<div class="row mt-2">
|
||
<div class="col-8">
|
||
<label class="form-label">集群节点 (逗号分隔)</label>
|
||
<input type="text" class="form-control form-control-sm" id="test_hosts" placeholder="10.20.2.22,10.20.2.23">
|
||
</div>
|
||
<div class="col-4">
|
||
<label class="form-label">端口</label>
|
||
<input type="number" class="form-control form-control-sm" id="test_port" placeholder="9042">
|
||
</div>
|
||
</div>
|
||
<div class="row mt-2">
|
||
<div class="col-6">
|
||
<label class="form-label">用户名</label>
|
||
<input type="text" class="form-control form-control-sm" id="test_username" placeholder="cbase">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="form-label">密码</label>
|
||
<input type="password" class="form-control form-control-sm" id="test_password">
|
||
</div>
|
||
</div>
|
||
<div class="row mt-2">
|
||
<div class="col-6">
|
||
<label class="form-label">Keyspace</label>
|
||
<input type="text" class="form-control form-control-sm" id="test_keyspace" placeholder="yuqing_skinny">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="form-label">表名</label>
|
||
<input type="text" class="form-control form-control-sm" id="test_table" placeholder="document_test">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 查询配置 -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h6><i class="fas fa-search"></i> 查询配置</h6>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="mb-3">
|
||
<label class="form-label">主键字段 (逗号分隔)</label>
|
||
<input type="text" class="form-control form-control-sm" id="keys" placeholder="docid" value="docid">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">比较字段 (空则比较全部,逗号分隔)</label>
|
||
<textarea class="form-control form-control-sm compare-fields" id="fields_to_compare" placeholder="留空表示比较所有字段 或输入: field1,field2,field3"></textarea>
|
||
</div>
|
||
<div>
|
||
<label class="form-label">排除字段 (逗号分隔)</label>
|
||
<textarea class="form-control form-control-sm exclude-fields" id="exclude_fields" placeholder="排除不需要比较的字段 如: field1,field2"></textarea>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 查询面板 -->
|
||
<div class="col-lg-8">
|
||
<div class="config-section">
|
||
<h4><i class="fas fa-key"></i> 查询Key管理</h4>
|
||
<div class="mb-3">
|
||
<label class="form-label">批量Key输入 (一行一个)</label>
|
||
<textarea class="form-control query-keys" id="query_values" placeholder="请输入查询的Key值,一行一个 例如: key1 key2 key3"></textarea>
|
||
</div>
|
||
<div class="mb-3">
|
||
<button class="btn btn-primary" onclick="executeQuery()">
|
||
<i class="fas fa-play"></i> 执行查询比对
|
||
</button>
|
||
<button class="btn btn-secondary" onclick="clearResults()">
|
||
<i class="fas fa-trash"></i> 清空结果
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 加载动画 -->
|
||
<div class="loading text-center" id="loading">
|
||
<div class="spinner-border text-primary" role="status">
|
||
<span class="visually-hidden">查询中...</span>
|
||
</div>
|
||
<p class="mt-2">正在执行查询比对...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 结果面板 -->
|
||
<div class="result-section" id="results" 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="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 class="mt-2">
|
||
<button class="btn btn-sm btn-outline-primary" onclick="exportResults()">
|
||
<i class="fas fa-download"></i> 导出结果
|
||
</button>
|
||
</div>
|
||
</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">
|
||
<!-- 差异内容将在这里动态生成 -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 相同结果面板 -->
|
||
<div class="tab-pane fade" id="identical-panel" role="tabpanel">
|
||
<div id="identical-results">
|
||
<!-- 相同结果将在这里动态生成 -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 比较总结面板 -->
|
||
<div class="tab-pane fade" id="summary-panel" role="tabpanel">
|
||
<div id="comparison-summary">
|
||
<!-- 总结报告将在这里动态生成 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||
</body>
|
||
</html> |