|
@ -263,6 +263,85 @@ |
|
|
margin-bottom: 10px; |
|
|
margin-bottom: 10px; |
|
|
display: none; |
|
|
display: none; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* Layer过滤器样式 */ |
|
|
|
|
|
.layer-filter-section { |
|
|
|
|
|
background-color: #f8f9fa; |
|
|
|
|
|
border-bottom: 1px solid #dee2e6; |
|
|
|
|
|
padding: 8px 10px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.filter-header-row { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
justify-content: space-between; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
margin-bottom: 8px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.filter-label { |
|
|
|
|
|
font-size: 0.9em; |
|
|
|
|
|
font-weight: bold; |
|
|
|
|
|
color: #495057; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.select-all-label { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
gap: 4px; |
|
|
|
|
|
font-size: 0.8em; |
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
color: #007bff; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.select-all-label:hover { |
|
|
|
|
|
color: #0056b3; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.select-all-label input { |
|
|
|
|
|
margin: 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.filter-options-row { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
flex-wrap: wrap; |
|
|
|
|
|
gap: 12px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.filter-option { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
gap: 4px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.filter-option label { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
gap: 4px; |
|
|
|
|
|
font-size: 0.8em; |
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
padding: 2px 6px; |
|
|
|
|
|
border-radius: 3px; |
|
|
|
|
|
transition: background-color 0.2s; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.filter-option label:hover { |
|
|
|
|
|
background-color: #e9ecef; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.filter-option input { |
|
|
|
|
|
margin: 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.filter-option input:checked + span { |
|
|
|
|
|
font-weight: bold; |
|
|
|
|
|
color: #007bff; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.log-layer { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
flex: 0 0 100px; |
|
|
|
|
|
} |
|
|
</style> |
|
|
</style> |
|
|
|
|
|
|
|
|
<div class="log-container"> |
|
|
<div class="log-container"> |
|
@ -279,6 +358,19 @@ |
|
|
<span class="log-info">Info</span> |
|
|
<span class="log-info">Info</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<!-- Layer过滤器区域 --> |
|
|
|
|
|
<div class="layer-filter-section"> |
|
|
|
|
|
<div class="filter-header-row"> |
|
|
|
|
|
<span class="filter-label">Layer过滤:</span> |
|
|
|
|
|
<label class="select-all-label"> |
|
|
|
|
|
<input type="checkbox" id="select-all-layers"> 全选 |
|
|
|
|
|
</label> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="filter-options-row" id="layer-filter-options"> |
|
|
|
|
|
<!-- 选项将动态生成 --> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="log-scroll-area"> |
|
|
<div id="log-scroll-area"> |
|
|
<div id="log-content-area"></div> |
|
|
<div id="log-content-area"></div> |
|
|
</div> |
|
|
</div> |
|
@ -333,8 +425,12 @@ |
|
|
const reconnectBtn = document.getElementById('reconnect-btn'); |
|
|
const reconnectBtn = document.getElementById('reconnect-btn'); |
|
|
const logListPanel = document.querySelector('.log-list-panel'); |
|
|
const logListPanel = document.querySelector('.log-list-panel'); |
|
|
const resizer = document.getElementById('drag-resizer'); |
|
|
const resizer = document.getElementById('drag-resizer'); |
|
|
|
|
|
const layerFilterOptions = document.getElementById('layer-filter-options'); |
|
|
|
|
|
const selectAllLayers = document.getElementById('select-all-layers'); |
|
|
|
|
|
|
|
|
let allLogsData = []; |
|
|
let allLogsData = []; |
|
|
|
|
|
let availableLayers = new Set(['PHY', 'MAC', 'RLC', 'PDCP', 'RRC', 'NAS']); // 初始化标准LTE层 |
|
|
|
|
|
let selectedLayers = new Set(); // 用于跟踪选中的日志层 |
|
|
let eventSource = null; |
|
|
let eventSource = null; |
|
|
let reconnectAttempts = 0; |
|
|
let reconnectAttempts = 0; |
|
|
const maxReconnectAttempts = 5; |
|
|
const maxReconnectAttempts = 5; |
|
@ -350,6 +446,81 @@ |
|
|
no_data_text: "正在等待日志..." |
|
|
no_data_text: "正在等待日志..." |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 更新全选复选框状态 |
|
|
|
|
|
function updateSelectAllState() { |
|
|
|
|
|
if (availableLayers.size === 0) { |
|
|
|
|
|
selectAllLayers.checked = false; |
|
|
|
|
|
selectAllLayers.indeterminate = false; |
|
|
|
|
|
} else if (selectedLayers.size === 0) { |
|
|
|
|
|
selectAllLayers.checked = false; |
|
|
|
|
|
selectAllLayers.indeterminate = false; |
|
|
|
|
|
} else if (selectedLayers.size === availableLayers.size) { |
|
|
|
|
|
selectAllLayers.checked = true; |
|
|
|
|
|
selectAllLayers.indeterminate = false; |
|
|
|
|
|
} else { |
|
|
|
|
|
selectAllLayers.checked = false; |
|
|
|
|
|
selectAllLayers.indeterminate = true; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 更新Layer过滤器选项 |
|
|
|
|
|
function updateLayerFilter() { |
|
|
|
|
|
const options = []; |
|
|
|
|
|
|
|
|
|
|
|
// 按字母顺序排序 |
|
|
|
|
|
const sortedLayers = Array.from(availableLayers).sort(); |
|
|
|
|
|
|
|
|
|
|
|
sortedLayers.forEach(layer => { |
|
|
|
|
|
const isChecked = selectedLayers.has(layer) ? ' checked' : ''; |
|
|
|
|
|
options.push(`<div class="filter-option"> |
|
|
|
|
|
<label> |
|
|
|
|
|
<input type="checkbox" value="${layer}"${isChecked}> |
|
|
|
|
|
<span>${layer}</span> |
|
|
|
|
|
</label> |
|
|
|
|
|
</div>`); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
layerFilterOptions.innerHTML = options.join(''); |
|
|
|
|
|
|
|
|
|
|
|
// 重新绑定事件 |
|
|
|
|
|
bindFilterEvents(); |
|
|
|
|
|
|
|
|
|
|
|
// 更新全选状态 |
|
|
|
|
|
updateSelectAllState(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 绑定过滤器事件 |
|
|
|
|
|
function bindFilterEvents() { |
|
|
|
|
|
// 绑定单个复选框事件 |
|
|
|
|
|
layerFilterOptions.querySelectorAll('input[type="checkbox"]').forEach(checkbox => { |
|
|
|
|
|
checkbox.addEventListener('change', function() { |
|
|
|
|
|
if (this.checked) { |
|
|
|
|
|
selectedLayers.add(this.value); |
|
|
|
|
|
} else { |
|
|
|
|
|
selectedLayers.delete(this.value); |
|
|
|
|
|
} |
|
|
|
|
|
updateSelectAllState(); |
|
|
|
|
|
refreshLogList(); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 根据当前过滤器重新渲染日志列表 |
|
|
|
|
|
function refreshLogList() { |
|
|
|
|
|
let filteredLogs = allLogsData; |
|
|
|
|
|
|
|
|
|
|
|
if (selectedLayers.size > 0 && selectedLayers.size < availableLayers.size) { |
|
|
|
|
|
filteredLogs = allLogsData.filter(log => selectedLayers.has(log.Layer)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const rows = filteredLogs.map((log, i) => formatLogItem(log, i)); |
|
|
|
|
|
clusterize.clear(); |
|
|
|
|
|
clusterize.append(rows); |
|
|
|
|
|
|
|
|
|
|
|
// 更新总数显示 |
|
|
|
|
|
totalLogsEl.textContent = filteredLogs.length; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 更新连接状态 |
|
|
// 更新连接状态 |
|
|
function updateConnectionStatus(status, text) { |
|
|
function updateConnectionStatus(status, text) { |
|
|
statusIndicator.className = 'status-indicator status-' + status; |
|
|
statusIndicator.className = 'status-indicator status-' + status; |
|
@ -377,11 +548,16 @@ |
|
|
// 清空日志显示 |
|
|
// 清空日志显示 |
|
|
function clearLogsDisplay() { |
|
|
function clearLogsDisplay() { |
|
|
allLogsData = []; |
|
|
allLogsData = []; |
|
|
|
|
|
availableLayers.clear(); |
|
|
|
|
|
// 重新添加标准LTE层 |
|
|
|
|
|
['PHY', 'MAC', 'RLC', 'PDCP', 'RRC', 'NAS'].forEach(layer => availableLayers.add(layer)); |
|
|
|
|
|
selectedLayers.clear(); |
|
|
clusterize.clear(); |
|
|
clusterize.clear(); |
|
|
totalLogsEl.textContent = '0'; |
|
|
totalLogsEl.textContent = '0'; |
|
|
newLogsCountEl.textContent = ''; |
|
|
newLogsCountEl.textContent = ''; |
|
|
detailPlaceholder.classList.remove('d-none'); |
|
|
detailPlaceholder.classList.remove('d-none'); |
|
|
detailContent.classList.add('d-none'); |
|
|
detailContent.classList.add('d-none'); |
|
|
|
|
|
updateLayerFilter(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 美化方向 |
|
|
// 美化方向 |
|
@ -443,6 +619,16 @@ |
|
|
function updateLogList(logs, prepend = false) { |
|
|
function updateLogList(logs, prepend = false) { |
|
|
if (!logs || logs.length === 0) return; |
|
|
if (!logs || logs.length === 0) return; |
|
|
|
|
|
|
|
|
|
|
|
// 收集新的日志层 |
|
|
|
|
|
logs.forEach(log => { |
|
|
|
|
|
if (log.Layer) { |
|
|
|
|
|
availableLayers.add(log.Layer); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 更新过滤器选项 |
|
|
|
|
|
updateLayerFilter(); |
|
|
|
|
|
|
|
|
const newRows = logs.map((log, i) => formatLogItem(log, allLogsData.length + i)); |
|
|
const newRows = logs.map((log, i) => formatLogItem(log, allLogsData.length + i)); |
|
|
|
|
|
|
|
|
if (prepend) { |
|
|
if (prepend) { |
|
@ -451,7 +637,9 @@ |
|
|
clusterize.append(newRows); |
|
|
clusterize.append(newRows); |
|
|
} |
|
|
} |
|
|
allLogsData.push(...logs); |
|
|
allLogsData.push(...logs); |
|
|
totalLogsEl.textContent = allLogsData.length; |
|
|
|
|
|
|
|
|
// 根据当前过滤器更新显示 |
|
|
|
|
|
refreshLogList(); |
|
|
|
|
|
|
|
|
// 显示新日志数量 |
|
|
// 显示新日志数量 |
|
|
if (!prepend) { |
|
|
if (!prepend) { |
|
@ -590,6 +778,22 @@ |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 事件监听器 |
|
|
|
|
|
// 全选/取消全选 |
|
|
|
|
|
selectAllLayers.addEventListener('change', function() { |
|
|
|
|
|
const isChecked = selectAllLayers.checked; |
|
|
|
|
|
if (isChecked) { |
|
|
|
|
|
// 全选 |
|
|
|
|
|
selectedLayers.clear(); |
|
|
|
|
|
availableLayers.forEach(layer => selectedLayers.add(layer)); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 取消全选 |
|
|
|
|
|
selectedLayers.clear(); |
|
|
|
|
|
} |
|
|
|
|
|
updateLayerFilter(); |
|
|
|
|
|
refreshLogList(); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
// 事件委托处理点击事件 |
|
|
// 事件委托处理点击事件 |
|
|
contentArea.addEventListener('click', function(e) { |
|
|
contentArea.addEventListener('click', function(e) { |
|
|
const item = e.target.closest('.log-item'); |
|
|
const item = e.target.closest('.log-item'); |
|
@ -702,6 +906,10 @@ |
|
|
|
|
|
|
|
|
// 初始化连接 |
|
|
// 初始化连接 |
|
|
connectSSE(); |
|
|
connectSSE(); |
|
|
|
|
|
|
|
|
|
|
|
// 初始化Layer过滤器,显示标准LTE层 |
|
|
|
|
|
updateLayerFilter(); |
|
|
}); |
|
|
}); |
|
|
</script> |
|
|
</script> |
|
|
} |
|
|
} |
|
|
|
|
|
</rewritten_file> |