|
|
|
@{
|
|
|
|
ViewData["Title"] = "测试客户端配置";
|
|
|
|
var testConfig = ViewBag.TestConfig as LTEMvcApp.Models.ClientConfig;
|
|
|
|
// 只保留不含 EVENT 的日志层
|
|
|
|
var allLayers = LTEMvcApp.Models.LogLayerTypes.AllLayers.Where(l => l != "EVENT").ToArray();
|
|
|
|
var allIMSLayers = LTEMvcApp.Models.LogLayerTypes.AllIMSLayers.Where(l => l != "EVENT").ToArray(); // 暂时使用AllLayers,后续可以扩展
|
|
|
|
var layerConfigs = testConfig?.Logs?.Layers ?? new Dictionary<string, LTEMvcApp.Models.LogLayerConfig>();
|
|
|
|
}
|
|
|
|
|
|
|
|
<style>
|
|
|
|
/* 页面整体布局 */
|
|
|
|
.config-container {
|
|
|
|
max-height: calc(100vh - 280px);
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 表单容器 */
|
|
|
|
.config-form {
|
|
|
|
flex: 1;
|
|
|
|
overflow-y: auto;
|
|
|
|
overflow-x: hidden;
|
|
|
|
padding: 0.75rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 自定义滚动条 */
|
|
|
|
.config-form::-webkit-scrollbar {
|
|
|
|
width: 6px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.config-form::-webkit-scrollbar-track {
|
|
|
|
background: #f1f1f1;
|
|
|
|
border-radius: 3px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.config-form::-webkit-scrollbar-thumb {
|
|
|
|
background: #c1c1c1;
|
|
|
|
border-radius: 3px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.config-form::-webkit-scrollbar-thumb:hover {
|
|
|
|
background: #a8a8a8;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 表单样式 */
|
|
|
|
.form-group {
|
|
|
|
margin-bottom: 0.75rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.form-group label {
|
|
|
|
font-weight: 600;
|
|
|
|
color: #495057;
|
|
|
|
margin-bottom: 0.25rem;
|
|
|
|
font-size: 0.9rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.form-control {
|
|
|
|
border-radius: 0.375rem;
|
|
|
|
border: 1px solid #ced4da;
|
|
|
|
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
|
|
|
padding: 0.375rem 0.75rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.form-control:focus {
|
|
|
|
border-color: #80bdff;
|
|
|
|
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 表格样式 */
|
|
|
|
.table-responsive {
|
|
|
|
border-radius: 0.5rem;
|
|
|
|
overflow: hidden;
|
|
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
|
|
flex: 1;
|
|
|
|
min-height: 0;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
}
|
|
|
|
|
|
|
|
.table {
|
|
|
|
margin-bottom: 0;
|
|
|
|
flex: 1;
|
|
|
|
font-size: 0.85rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.table th {
|
|
|
|
background-color: #343a40;
|
|
|
|
color: white;
|
|
|
|
font-weight: 600;
|
|
|
|
border: none;
|
|
|
|
position: sticky;
|
|
|
|
top: 0;
|
|
|
|
z-index: 10;
|
|
|
|
padding: 0.5rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.table td {
|
|
|
|
vertical-align: middle;
|
|
|
|
border-color: #dee2e6;
|
|
|
|
padding: 0.5rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 按钮样式 */
|
|
|
|
.btn {
|
|
|
|
border-radius: 0.375rem;
|
|
|
|
font-weight: 500;
|
|
|
|
transition: all 0.2s;
|
|
|
|
padding: 0.375rem 0.75rem;
|
|
|
|
font-size: 0.9rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.btn:hover {
|
|
|
|
transform: translateY(-1px);
|
|
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
|
|
}
|
|
|
|
|
|
|
|
.form-check {
|
|
|
|
margin-bottom: 0.5rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.form-check-input:checked {
|
|
|
|
background-color: #007bff;
|
|
|
|
border-color: #007bff;
|
|
|
|
}
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
color: #495057;
|
|
|
|
font-weight: 600;
|
|
|
|
margin-bottom: 0.5rem;
|
|
|
|
padding-bottom: 0.25rem;
|
|
|
|
border-bottom: 2px solid #e9ecef;
|
|
|
|
font-size: 1rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 按钮区域 */
|
|
|
|
.button-area {
|
|
|
|
flex-shrink: 0;
|
|
|
|
background: white;
|
|
|
|
border-top: 1px solid #dee2e6;
|
|
|
|
padding: 0.5rem 0.75rem;
|
|
|
|
display: flex;
|
|
|
|
gap: 0.5rem;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 响应式调整 */
|
|
|
|
@@media (max-width: 768px) {
|
|
|
|
.config-container {
|
|
|
|
max-height: calc(100vh - 240px);
|
|
|
|
}
|
|
|
|
|
|
|
|
.button-area {
|
|
|
|
padding: 0.4rem 0.6rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.config-form {
|
|
|
|
padding: 0.6rem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@@media (max-width: 576px) {
|
|
|
|
.config-container {
|
|
|
|
max-height: calc(100vh - 220px);
|
|
|
|
}
|
|
|
|
|
|
|
|
.button-area {
|
|
|
|
padding: 0.3rem 0.5rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.config-form {
|
|
|
|
padding: 0.5rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.btn {
|
|
|
|
font-size: 0.8rem;
|
|
|
|
padding: 0.3rem 0.6rem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 日志层配置容器 */
|
|
|
|
.logs-config-section {
|
|
|
|
flex: 1;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
min-height: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.logs-config-content {
|
|
|
|
flex: 1;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
min-height: 0;
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
|
|
|
|
<div class="container">
|
|
|
|
<div class="row">
|
|
|
|
<div class="col-12">
|
|
|
|
<div class="card">
|
|
|
|
<div class="card-header">
|
|
|
|
<h3 class="card-title">
|
|
|
|
<i class="fas fa-cog"></i> 测试客户端配置
|
|
|
|
</h3>
|
|
|
|
</div>
|
|
|
|
<div class="card-body p-0">
|
|
|
|
<div class="config-container">
|
|
|
|
<form id="testClientConfigForm" class="config-form">
|
|
|
|
<!-- 基本配置 -->
|
|
|
|
<div class="row">
|
|
|
|
<div class="col-md-6">
|
|
|
|
<div class="form-group">
|
|
|
|
<label for="name">客户端名称</label>
|
|
|
|
<input type="text" class="form-control" id="name" name="name" value="@testConfig?.Name" required>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
|
|
<div class="form-group">
|
|
|
|
<label for="address">服务器地址</label>
|
|
|
|
<input type="text" class="form-control" id="address" name="address" value="@testConfig?.Address" placeholder="192.168.13.12:9001">
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="row">
|
|
|
|
<div class="col-md-6">
|
|
|
|
<div class="form-group">
|
|
|
|
<label for="password">密码</label>
|
|
|
|
<input type="password" class="form-control" id="password" name="password" value="@testConfig?.Password">
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
|
|
<div class="form-group">
|
|
|
|
<label for="reconnectDelay">重连延迟 (毫秒)</label>
|
|
|
|
<input type="number" class="form-control" id="reconnectDelay" name="reconnectDelay" value="@testConfig?.ReconnectDelay">
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="row mb-3">
|
|
|
|
<div class="col-md-12 d-flex align-items-center flex-wrap">
|
|
|
|
<div class="form-check form-check-inline mr-3">
|
|
|
|
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" @(testConfig?.Enabled == true ? "checked" : "")>
|
|
|
|
<label class="form-check-label" for="enabled">启用</label>
|
|
|
|
</div>
|
|
|
|
<div class="form-check form-check-inline mr-3">
|
|
|
|
<input type="checkbox" class="form-check-input" id="ssl" name="ssl" @(testConfig?.Ssl == true ? "checked" : "")>
|
|
|
|
<label class="form-check-label" for="ssl">使用SSL</label>
|
|
|
|
</div>
|
|
|
|
<div class="form-check form-check-inline mr-3">
|
|
|
|
<input type="checkbox" class="form-check-input" id="readonly" name="readonly" @(testConfig?.Readonly == true ? "checked" : "")>
|
|
|
|
<label class="form-check-label" for="readonly">只读模式</label>
|
|
|
|
</div>
|
|
|
|
<div class="form-check form-check-inline mr-3">
|
|
|
|
<input type="radio" class="form-check-input" id="mode_ran" name="mode" value="ran" @(testConfig?.Mode == "ran" ? "checked" : "checked")>
|
|
|
|
<label class="form-check-label" for="mode_ran">RAN</label>
|
|
|
|
</div>
|
|
|
|
<div class="form-check form-check-inline">
|
|
|
|
<input type="radio" class="form-check-input" id="mode_ims" name="mode" value="ims" @(testConfig?.Mode == "ims" ? "checked" : "")>
|
|
|
|
<label class="form-check-label" for="mode_ims">IMS</label>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 日志层配置表格 -->
|
|
|
|
<div class="row logs-config-section">
|
|
|
|
<div class="col-md-12">
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-2 px-3 py-2" style="background: #f8f9fa; border-radius: 0.5rem; border: 1px solid #e9ecef;">
|
|
|
|
<h5 class="section-title mb-0" style="font-size: 1.1rem; color: #343a40;">
|
|
|
|
<i class="fas fa-layer-group"></i> 日志层配置
|
|
|
|
</h5>
|
|
|
|
<button type="submit" class="btn btn-primary" form="testClientConfigForm" style="min-width: 110px;">
|
|
|
|
<i class="fas fa-save"></i> 保存配置
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<div class="logs-config-content">
|
|
|
|
<div class="table-responsive">
|
|
|
|
<table class="table table-bordered table-sm">
|
|
|
|
<thead class="table-dark">
|
|
|
|
<tr>
|
|
|
|
<th>日志层</th>
|
|
|
|
<th>过滤器</th>
|
|
|
|
<th>级别</th>
|
|
|
|
<th>最大大小</th>
|
|
|
|
<th>包含负载</th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody id="layersTableBody">
|
|
|
|
<!-- RAN 模式下的日志层 -->
|
|
|
|
<tbody id="ranLayers" class="mode-layers" style="">
|
|
|
|
@foreach (var layer in allLayers)
|
|
|
|
{
|
|
|
|
var config = layerConfigs.ContainsKey(layer) ? layerConfigs[layer] : new LTEMvcApp.Models.LogLayerConfig();
|
|
|
|
<tr>
|
|
|
|
<td><strong>@layer</strong></td>
|
|
|
|
<td>
|
|
|
|
<select class="form-control form-control-sm" name="layers[@layer][filter]">
|
|
|
|
@foreach (var logLevel in LTEMvcApp.Models.LogLayerTypes.LogLevels)
|
|
|
|
{
|
|
|
|
if (logLevel == config.Filter)
|
|
|
|
{
|
|
|
|
<option value="@logLevel" selected>@logLevel.ToUpper()</option>
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
<option value="@logLevel">@logLevel.ToUpper()</option>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</select>
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<select class="form-control form-control-sm" name="layers[@layer][level]">
|
|
|
|
@foreach (var logLevel in LTEMvcApp.Models.LogLayerTypes.LogLevels)
|
|
|
|
{
|
|
|
|
if (logLevel == config.Level)
|
|
|
|
{
|
|
|
|
<option value="@logLevel" selected>@logLevel.ToUpper()</option>
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
<option value="@logLevel">@logLevel.ToUpper()</option>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</select>
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<input type="number" class="form-control form-control-sm" name="layers[@layer][max_size]" value="@config.MaxSize" min="1" max="1000">
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<div class="form-check">
|
|
|
|
<input type="checkbox" class="form-check-input" name="layers[@layer][payload]" @(config.Payload ? "checked" : "")>
|
|
|
|
</div>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
}
|
|
|
|
</tbody>
|
|
|
|
|
|
|
|
<!-- IMS 模式下的日志层 -->
|
|
|
|
<tbody id="imsLayers" class="mode-layers" style="display: none;">
|
|
|
|
@foreach (var layer in allIMSLayers)
|
|
|
|
{
|
|
|
|
var config = layerConfigs.ContainsKey(layer) ? layerConfigs[layer] : new LTEMvcApp.Models.LogLayerConfig();
|
|
|
|
<tr>
|
|
|
|
<td><strong>@layer</strong></td>
|
|
|
|
<td>
|
|
|
|
<select class="form-control form-control-sm" name="layers[@layer][filter]">
|
|
|
|
@foreach (var logLevel in LTEMvcApp.Models.LogLayerTypes.LogLevels)
|
|
|
|
{
|
|
|
|
if (logLevel == config.Filter)
|
|
|
|
{
|
|
|
|
<option value="@logLevel" selected>@logLevel.ToUpper()</option>
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
<option value="@logLevel">@logLevel.ToUpper()</option>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</select>
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<select class="form-control form-control-sm" name="layers[@layer][level]">
|
|
|
|
@foreach (var logLevel in LTEMvcApp.Models.LogLayerTypes.LogLevels)
|
|
|
|
{
|
|
|
|
if (logLevel == config.Level)
|
|
|
|
{
|
|
|
|
<option value="@logLevel" selected>@logLevel.ToUpper()</option>
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
<option value="@logLevel">@logLevel.ToUpper()</option>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</select>
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<input type="number" class="form-control form-control-sm" name="layers[@layer][max_size]" value="@config.MaxSize" min="1" max="1000">
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<div class="form-check">
|
|
|
|
<input type="checkbox" class="form-check-input" name="layers[@layer][payload]" @(config.Payload ? "checked" : "")>
|
|
|
|
</div>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
}
|
|
|
|
</tbody>
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
@section Scripts {
|
|
|
|
<script>
|
|
|
|
$(document).ready(function() {
|
|
|
|
$('#testClientConfigForm').on('submit', function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
saveTestClientConfig();
|
|
|
|
});
|
|
|
|
|
|
|
|
// 监听模式切换
|
|
|
|
$('input[name="mode"]').on('change', function() {
|
|
|
|
var selectedMode = $(this).val();
|
|
|
|
$('.mode-layers').hide();
|
|
|
|
$('#' + selectedMode + 'Layers').show();
|
|
|
|
});
|
|
|
|
|
|
|
|
// 监听地址输入框变化,自动查询配置
|
|
|
|
$('#address').on('blur', function() {
|
|
|
|
var address = $(this).val().trim();
|
|
|
|
if (address) {
|
|
|
|
loadConfigByAddress(address);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// 根据地址加载配置
|
|
|
|
function loadConfigByAddress(address) {
|
|
|
|
$.ajax({
|
|
|
|
url: '/api/testconfig/address/' + encodeURIComponent(address),
|
|
|
|
type: 'GET',
|
|
|
|
success: function(config) {
|
|
|
|
if (config) {
|
|
|
|
// 填充表单
|
|
|
|
$('#name').val(config.name);
|
|
|
|
$('#password').val(config.password);
|
|
|
|
$('#reconnectDelay').val(config.reconnectDelay);
|
|
|
|
$('#enabled').prop('checked', config.enabled);
|
|
|
|
$('#ssl').prop('checked', config.ssl);
|
|
|
|
$('#readonly').prop('checked', config.readonly);
|
|
|
|
|
|
|
|
// 设置模式
|
|
|
|
if (config.mode) {
|
|
|
|
$('input[name="mode"][value="' + config.mode + '"]').prop('checked', true).trigger('change');
|
|
|
|
}
|
|
|
|
|
|
|
|
// 填充日志层配置
|
|
|
|
if (config.logs && config.logs.layers) {
|
|
|
|
Object.keys(config.logs.layers).forEach(function(layer) {
|
|
|
|
var layerConfig = config.logs.layers[layer];
|
|
|
|
$(`select[name="layers[${layer}][level]"]`).val(layerConfig.level);
|
|
|
|
$(`select[name="layers[${layer}][filter]"]`).val(layerConfig.filter);
|
|
|
|
$(`input[name="layers[${layer}][max_size]"]`).val(layerConfig.maxSize);
|
|
|
|
$(`input[name="layers[${layer}][payload]"]`).prop('checked', layerConfig.payload);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('已加载配置:', config.name);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
error: function(xhr) {
|
|
|
|
if (xhr.status === 404) {
|
|
|
|
console.log('未找到该地址的配置,将创建新配置');
|
|
|
|
} else {
|
|
|
|
console.error('查询配置失败:', xhr.responseText);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function saveTestClientConfig() {
|
|
|
|
var formData = {
|
|
|
|
name: $('#name').val(),
|
|
|
|
address: $('#address').val(),
|
|
|
|
password: $('#password').val(),
|
|
|
|
reconnectDelay: parseInt($('#reconnectDelay').val()) || 15000,
|
|
|
|
enabled: $('#enabled').is(':checked'),
|
|
|
|
ssl: $('#ssl').is(':checked'),
|
|
|
|
readonly: $('#readonly').is(':checked'),
|
|
|
|
mode: $('input[name="mode"]:checked').val(),
|
|
|
|
logs: {
|
|
|
|
layers: {}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 根据当前模式获取日志层
|
|
|
|
var selectedMode = $('input[name="mode"]:checked').val();
|
|
|
|
var layers = selectedMode === 'ims' ? @Html.Raw(System.Text.Json.JsonSerializer.Serialize(allIMSLayers)) : @Html.Raw(System.Text.Json.JsonSerializer.Serialize(allLayers));
|
|
|
|
|
|
|
|
layers.forEach(function(layer) {
|
|
|
|
var level = $(`select[name="layers[${layer}][level]"]`).val();
|
|
|
|
var filter = $(`select[name="layers[${layer}][filter]"]`).val();
|
|
|
|
var maxSize = parseInt($(`input[name="layers[${layer}][max_size]"]`).val()) || 1;
|
|
|
|
var payload = $(`input[name="layers[${layer}][payload]"]`).is(':checked');
|
|
|
|
|
|
|
|
formData.logs.layers[layer] = {
|
|
|
|
level: level,
|
|
|
|
maxSize: maxSize,
|
|
|
|
payload: payload,
|
|
|
|
filter: filter
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
$.ajax({
|
|
|
|
url: '/api/testconfig/default',
|
|
|
|
type: 'POST',
|
|
|
|
contentType: 'application/json',
|
|
|
|
data: JSON.stringify(formData),
|
|
|
|
success: function(response) {
|
|
|
|
alert('配置保存成功!');
|
|
|
|
location.reload();
|
|
|
|
},
|
|
|
|
error: function(xhr) {
|
|
|
|
alert('保存配置失败:' + xhr.responseText);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
</script>
|
|
|
|
}
|