优化界面显示

This commit is contained in:
net909
2026-05-02 20:49:09 +08:00
parent 5d53d46659
commit 9403875044
8 changed files with 286 additions and 1966 deletions

View File

@@ -1328,108 +1328,12 @@ class Domain extends BaseController
}
$dns_records = array_map('strtolower', $dns_records);
$expected_value = strtolower(trim($value));
$expected_value = strtolower(rtrim(trim($value), '.'));
if (in_array($expected_value, $dns_records)) {
return json(['code' => 0, 'data' => ['status' => 'active', 'message' => '解析已生效', 'actual' => $dns_records]]);
return json(['code' => 0, 'data' => ['status' => 'active', 'actual' => $dns_records]]);
} else {
return json(['code' => 0, 'data' => ['status' => 'mismatch', 'message' => '解析值不匹配', 'expected' => $expected_value, 'actual' => $dns_records]]);
}
}
public function dnscheck()
{
if (!checkPermission(1)) return $this->alert('error', '无权限');
if (request()->isAjax()) {
$domain = input('post.domain', null, 'trim');
$type = input('post.type', null, 'trim');
$dns_server = input('post.dns_server', 'default', 'trim');
if (empty($domain) || empty($type)) {
return json(['code' => -1, 'msg' => '域名和记录类型不能为空']);
}
$domain = strtolower($domain);
$supported_types = ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SOA', 'SRV', 'CAA', 'PTR'];
if (!in_array($type, $supported_types)) {
return json(['code' => -1, 'msg' => '该记录类型暂不支持检测']);
}
$result = $this->query_dns_with_server($domain, $type, $dns_server);
return json($result);
}
return view();
}
private function query_dns_with_server($domain, $type, $dns_server)
{
$dns_servers = [
'alidns' => ['223.5.5.5', '223.6.6.6'],
'dnspod' => ['119.29.29.29', '182.254.116.116'],
'google' => ['8.8.8.8', '8.8.4.4'],
'cloudflare' => ['1.1.1.1', '1.0.0.1'],
];
$server_names = [
'default' => '系统默认',
'alidns' => '阿里DNS',
'dnspod' => 'DNSPod',
'google' => 'Google DNS',
'cloudflare' => 'Cloudflare',
];
if ($dns_server != 'default' && isset($dns_servers[$dns_server])) {
$records = $this->query_dns_custom_server($domain, $type, $dns_servers[$dns_server][0]);
} else {
$records = DnsQueryUtils::get_dns_records($domain, $type);
if ($records === false || empty($records)) {
$records = DnsQueryUtils::query_dns_doh($domain, $type);
}
}
if ($records === false) {
return ['code' => 0, 'data' => ['status' => 'error', 'message' => '查询失败', 'dns_server' => $server_names[$dns_server] ?? $dns_server]];
}
if (empty($records)) {
return ['code' => 0, 'data' => ['status' => 'not_found', 'message' => '未找到该类型的DNS记录', 'dns_server' => $server_names[$dns_server] ?? $dns_server]];
}
return ['code' => 0, 'data' => ['status' => 'success', 'records' => $records, 'dns_server' => $server_names[$dns_server] ?? $dns_server]];
}
private function query_dns_custom_server($domain, $type, $server)
{
$dns_type = ['A' => DNS_A, 'AAAA' => DNS_AAAA, 'CNAME' => DNS_CNAME, 'MX' => DNS_MX, 'TXT' => DNS_TXT, 'NS' => DNS_NS, 'SOA' => DNS_SOA, 'PTR' => DNS_PTR, 'SRV' => DNS_SRV, 'CAA' => DNS_CAA];
if (!array_key_exists($type, $dns_type)) {
return false;
}
try {
$cmd = 'dig +short +time=5 @' . escapeshellarg($server) . ' ' . escapeshellarg($domain) . ' ' . escapeshellarg($type) . ' 2>/dev/null';
$output = shell_exec($cmd);
if (empty($output)) {
return false;
}
$lines = explode("\n", trim($output));
$result = [];
foreach ($lines as $line) {
$line = trim($line);
if (!empty($line)) {
$result[] = $line;
}
}
return empty($result) ? false : $result;
} catch (Exception $e) {
return false;
return json(['code' => 0, 'data' => ['status' => 'mismatch', 'expected' => $expected_value, 'actual' => $dns_records]]);
}
}

View File

@@ -109,12 +109,6 @@
<li class="{:checkIfActive('domain,record,record_log,record_batch_add,domain_add,weight,record_batch_add2,record_batch_edit2,expire_notice,smartparse')}">
<a href="/domain"><i class="fa fa-list-ul fa-fw"></i> <span>域名管理</span></a>
</li>
<li class="{:checkIfActive('dnscheck')}">
<a href="/domain/dnscheck"><i class="fa fa-search fa-fw"></i> <span>DNS检测工具</span></a>
</li>
<li class="{:checkIfActive('smartparse')}">
<a href="/record/smartparse"><i class="fa fa-magic fa-fw"></i> <span>智能解析</span></a>
</li>
{if request()->user['level'] eq 2}
<li class="{:checkIfActive('account,account_add')}">
<a href="/account"><i class="fa fa-lock fa-fw"></i> <span>域名账户</span></a>

View File

@@ -0,0 +1,204 @@
{extend name="common/layout" /}
{block name="title"}域名分类管理{/block}
{block name="main"}
<div class="modal" id="modal-store" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="static">
<div class="modal-dialog">
<div class="modal-content animated flipInX">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
<h4 class="modal-title" id="modal-title">添加分类</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" id="form-store">
<input type="hidden" name="id"/>
<div class="form-group">
<label class="col-sm-3 control-label">分类名称</label>
<div class="col-sm-8">
<input type="text" class="form-control" name="name" placeholder="输入分类名称" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">排序</label>
<div class="col-sm-8">
<input type="number" class="form-control" name="sort" value="0" placeholder="数字越小越靠前">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">备注</label>
<div class="col-sm-8">
<input type="text" class="form-control" name="remark" placeholder="可选">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-white" data-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" id="store" onclick="save()">保存</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 center-block" style="float: none;">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">域名分类管理</h3>
</div>
<div class="panel-body">
<form onsubmit="return searchSubmit()" method="GET" class="form-inline" id="searchToolbar">
<a href="javascript:addframe()" class="btn btn-success"><i class="fa fa-plus"></i> 添加分类</a>
<a href="javascript:searchClear()" class="btn btn-default" title="刷新列表"><i class="fa fa-refresh"></i> 刷新</a>
</form>
<table id="listTable"></table>
</div>
</div>
</div>
</div>
{/block}
{block name="script"}
<script src="/static/js/layer/layer.js"></script>
<script src="/static/js/bootstrap-table-1.21.4.min.js"></script>
<script src="/static/js/bootstrap-table-page-jump-to-1.21.4.min.js"></script>
<script src="/static/js/bootstrapValidator.min.js"></script>
<script src="/static/js/custom.js?v=1003"></script>
<script>
$(document).ready(function(){
updateToolbar();
let defaultPageSize = getCookie('category_pagesize') ? getCookie('category_pagesize') : 10;
const pageNumber = typeof window.$_GET['pageNumber'] != 'undefined' ? parseInt(window.$_GET['pageNumber']) : 1;
const pageSize = typeof window.$_GET['pageSize'] != 'undefined' ? parseInt(window.$_GET['pageSize']) : defaultPageSize;
$("#listTable").bootstrapTable({
url: '/domain/category/data',
pageNumber: pageNumber,
pageSize: pageSize,
classes: 'table table-striped table-hover table-bordered',
uniqueId: 'id',
columns: [
{
field: 'id',
title: 'ID'
},
{
field: 'name',
title: '分类名称'
},
{
field: 'remark',
title: '备注',
formatter: function(value, row, index) {
return value ? value : '-';
}
},
{
field: 'sort',
title: '排序'
},
{
field: 'domain_count',
title: '域名数量',
formatter: function(value, row, index) {
return '<span class="label label-info">' + value + '</span>';
}
},
{
field: 'addtime',
title: '添加时间'
},
{
field: 'action',
title: '操作',
formatter: function(value, row, index) {
var html = '<a href="javascript:editframe(\''+row.id+'\')" class="btn btn-primary btn-xs">修改</a>&nbsp;&nbsp;';
html += '<a href="javascript:delItem(\''+row.id+'\')" class="btn btn-danger btn-xs">删除</a>&nbsp;&nbsp;';
html += '<a href="/domain?cid='+row.id+'" class="btn btn-default btn-xs">域名</a>';
return html;
}
},
],
onPageChange: function(number, size){
if(size != defaultPageSize){
setCookie('category_pagesize', size, 24 * 3600 * 30);
}
},
});
$("#form-store").bootstrapValidator();
});
function addframe(){
$("#modal-store").modal('show');
$("#modal-title").html("添加分类");
$("#form-store input[name=id]").val('');
$("#form-store input[name=name]").val('');
$("#form-store input[name=sort]").val('0');
$("#form-store input[name=remark]").val('');
$("#form-store").data("bootstrapValidator").resetForm();
}
function editframe(id){
var row = $("#listTable").bootstrapTable('getRowByUniqueId', id);
$("#modal-store").modal('show');
$("#modal-title").html("修改分类");
$("#form-store input[name=id]").val(id);
$("#form-store input[name=name]").val(row.name);
$("#form-store input[name=sort]").val(row.sort);
$("#form-store input[name=remark]").val(row.remark);
$("#form-store").data("bootstrapValidator").resetForm();
}
function save(){
$("#form-store").data("bootstrapValidator").validate();
if(!$("#form-store").data("bootstrapValidator").isValid()){
return;
}
var id = $("#form-store input[name=id]").val();
var action = id ? 'edit' : 'add';
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/domain/category/' + action,
data : $("#form-store").serialize(),
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert(data.msg, {
icon: 1,
closeBtn: false
}, function(){
layer.closeAll();
$("#modal-store").modal('hide');
searchRefresh();
});
}else{
layer.alert(data.msg, {icon: 2});
}
}
});
}
function delItem(id) {
layer.confirm('确定要删除此分类吗?', {title: '提示', icon: 0}, function(){
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/domain/category/del',
data : {id: id},
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.closeAll();
layer.msg('删除成功', {icon: 1, time:800});
searchRefresh();
}else{
layer.alert(data.msg, {icon: 2});
}
}
});
});
}
</script>
{/block}

View File

@@ -1,262 +0,0 @@
{extend name="common/layout" /}
{block name="title"}DNS解析检测工具{/block}
{block name="main"}
<style>
.result-box {
margin-top: 20px;
}
.result-item {
padding: 15px;
margin-bottom: 15px;
border-radius: 4px;
border: 1px solid #ddd;
}
.result-success {
background-color: #f0f9eb;
border-color: #67c23a;
}
.result-warning {
background-color: #fdf6ec;
border-color: #e6a23c;
}
.result-error {
background-color: #fef0f0;
border-color: #f56c6c;
}
.result-title {
font-weight: bold;
margin-bottom: 10px;
font-size: 16px;
}
.result-content {
color: #666;
}
.result-content ul {
margin: 5px 0;
padding-left: 20px;
}
.result-content li {
margin: 3px 0;
word-break: break-all;
}
.dns-server-tag {
display: inline-block;
padding: 2px 8px;
margin: 2px;
background-color: #f0f0f0;
border-radius: 3px;
font-size: 12px;
}
</style>
<div class="row">
<div class="col-xs-12 center-block" style="float: none;">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">DNS解析检测工具</h3>
</div>
<div class="panel-body">
<form class="form-horizontal" id="checkForm">
<div class="form-group">
<label class="col-sm-2 control-label">域名</label>
<div class="col-sm-8">
<input type="text" class="form-control" name="domain" placeholder="输入要检测的域名例如www.example.com" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">记录类型</label>
<div class="col-sm-8">
<select name="type" class="form-control">
<option value="A">A (IPv4地址)</option>
<option value="AAAA">AAAA (IPv6地址)</option>
<option value="CNAME">CNAME (别名记录)</option>
<option value="MX">MX (邮件交换)</option>
<option value="TXT">TXT (文本记录)</option>
<option value="NS">NS (域名服务器)</option>
<option value="SOA">SOA (起始授权)</option>
<option value="SRV">SRV (服务定位)</option>
<option value="CAA">CAA (证书授权)</option>
<option value="PTR">PTR (反向解析)</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">DNS服务器</label>
<div class="col-sm-8">
<label class="radio-inline">
<input type="radio" name="dns_server" value="default" checked> 系统默认
</label>
<label class="radio-inline">
<input type="radio" name="dns_server" value="alidns"> 阿里DNS
</label>
<label class="radio-inline">
<input type="radio" name="dns_server" value="dnspod"> DNSPod
</label>
<label class="radio-inline">
<input type="radio" name="dns_server" value="google"> Google DNS
</label>
<label class="radio-inline">
<input type="radio" name="dns_server" value="cloudflare"> Cloudflare
</label>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-8">
<button type="submit" class="btn btn-primary" id="checkBtn">
<i class="fa fa-search"></i> 开始检测
</button>
<button type="button" class="btn btn-default" onclick="clearResult()">
<i class="fa fa-trash"></i> 清空结果
</button>
</div>
</div>
</form>
<div id="resultBox" class="result-box" style="display:none;">
<div class="form-group">
<label class="col-sm-2 control-label">检测结果</label>
<div class="col-sm-8" id="resultContent">
</div>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">使用说明</h3>
</div>
<div class="panel-body">
<ul>
<li><strong>A记录</strong>检测域名解析到的IPv4地址</li>
<li><strong>AAAA记录</strong>检测域名解析到的IPv6地址</li>
<li><strong>CNAME记录</strong>:检测域名的别名指向</li>
<li><strong>MX记录</strong>:检测邮件服务器配置</li>
<li><strong>TXT记录</strong>检测文本记录内容常用于SPF、DKIM验证</li>
<li><strong>NS记录</strong>检测域名的DNS服务器</li>
<li><strong>SOA记录</strong>:检测域名的授权信息</li>
<li><strong>SRV记录</strong>:检测服务定位记录</li>
<li><strong>CAA记录</strong>:检测证书颁发机构授权</li>
<li><strong>PTR记录</strong>:检测反向解析记录</li>
</ul>
<p class="text-muted">提示可以选择不同的DNS服务器来检测解析是否在全球生效。</p>
</div>
</div>
</div>
</div>
{/block}
{block name="script"}
<script src="/static/js/layer/layer.js"></script>
<script>
$(document).ready(function(){
$('#checkForm').on('submit', function(e){
e.preventDefault();
doCheck();
});
});
function doCheck(){
var domain = $('input[name="domain"]').val().trim();
var type = $('select[name="type"]').val();
var dnsServer = $('input[name="dns_server"]:checked').val();
if(!domain){
layer.msg('请输入域名', {icon: 2});
return;
}
$('#checkBtn').prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> 检测中...');
$.ajax({
type: 'POST',
url: '/domain/dnscheck',
data: {
domain: domain,
type: type,
dns_server: dnsServer
},
dataType: 'json',
success: function(data){
$('#checkBtn').prop('disabled', false).html('<i class="fa fa-search"></i> 开始检测');
if(data.code == 0){
showResult(data.data, domain, type);
}else{
layer.alert(data.msg, {icon: 2});
}
},
error: function(){
$('#checkBtn').prop('disabled', false).html('<i class="fa fa-search"></i> 开始检测');
layer.msg('请求失败,请稍后重试', {icon: 2});
}
});
}
function showResult(result, domain, type){
var html = '';
var statusClass = '';
var statusTitle = '';
var statusIcon = '';
if(result.status == 'success'){
statusClass = 'result-success';
statusTitle = '查询成功';
statusIcon = '<i class="fa fa-check-circle text-success"></i> ';
}else if(result.status == 'not_found'){
statusClass = 'result-warning';
statusTitle = '未找到记录';
statusIcon = '<i class="fa fa-exclamation-circle text-warning"></i> ';
}else{
statusClass = 'result-error';
statusTitle = '查询失败';
statusIcon = '<i class="fa fa-times-circle text-danger"></i> ';
}
html += '<div class="result-item ' + statusClass + '">';
html += '<div class="result-title">' + statusIcon + statusTitle + '</div>';
html += '<div class="result-content">';
html += '<p><strong>查询域名:</strong>' + htmlEscape(domain) + '</p>';
html += '<p><strong>记录类型:</strong>' + type + '</p>';
if(result.dns_server){
html += '<p><strong>DNS服务器</strong><span class="dns-server-tag">' + htmlEscape(result.dns_server) + '</span></p>';
}
if(result.records && result.records.length > 0){
html += '<p><strong>解析结果:</strong></p>';
html += '<ul>';
for(var i = 0; i < result.records.length; i++){
html += '<li>' + htmlEscape(result.records[i]) + '</li>';
}
html += '</ul>';
}
if(result.ttl){
html += '<p><strong>TTL</strong>' + result.ttl + ' 秒</p>';
}
if(result.message && result.status != 'success'){
html += '<p><strong>提示:</strong>' + htmlEscape(result.message) + '</p>';
}
html += '</div>';
html += '</div>';
$('#resultContent').html(html);
$('#resultBox').show();
}
function clearResult(){
$('#resultContent').html('');
$('#resultBox').hide();
$('input[name="domain"]').val('');
}
function htmlEscape(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
</script>
{/block}

View File

@@ -163,7 +163,7 @@
{if $user['level'] eq 2}<a href="javascript:addframe()" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>
<div class="btn-group" role="group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">批量操作 <span class="caret"></span></button>
<ul class="dropdown-menu"><li><a href="/domain/add">添加域名</a></li><li><a href="javascript:operation('setcategory')">设置分类</a></li><li><a href="javascript:operation('editremark')">修改域名备注</a></li><li><a href="javascript:operation('opennotice')">开启到期提醒</a></li><li><a href="javascript:operation('closenotice')">关闭到期提醒</a></li><li><a href="javascript:operation('updateexpire')">刷新到期时间</a></li><li><a href="javascript:operation('delete')">删除域名</a></li><li role="separator" class="divider"></li><li><a href="javascript:operation('addrecord')">添加解析</a></li><li><a href="javascript:operation('editrecord')">修改解析</a></li></ul>
<ul class="dropdown-menu"><li><a href="/domain/add">添加域名</a></li><li><a href="javascript:operation('setcategory')">设置分类</a></li><li><a href="javascript:operation('editremark')">修改域名备注</a></li><li><a href="javascript:operation('opennotice')">开启到期提醒</a></li><li><a href="javascript:operation('closenotice')">关闭到期提醒</a></li><li><a href="javascript:operation('updateexpire')">刷新到期时间</a></li><li><a href="javascript:operation('delete')">删除域名</a></li><li role="separator" class="divider"></li><li><a href="javascript:operation('addrecord')">添加解析</a></li><li><a href="javascript:operation('editrecord')">修改解析</a></li><li><a href="/record/smartparse">智能添加解析</a></li></ul>
</div>
<a href="/domain/expirenotice" class="btn btn-default">到期提醒设置</a>{/if}
</form>
@@ -215,13 +215,6 @@ $(document).ready(function(){
return '<img src="/static/images/'+row.icon+'" class="type-logo"></img>'+(row.aremark?row.aremark:value+'('+row.aid+')');
}
},
{
field: 'category_name',
title: '分类',
formatter: function(value, row, index) {
return value ? '<span class="label label-primary">' + value + '</span>' : '-';
}
},
{
field: 'name',
title: '域名',
@@ -305,6 +298,13 @@ $(document).ready(function(){
return value==1?'<font color="green">是</font>':'<font color="red">否</font>';
}
},
{
field: 'category_name',
title: '分类',
formatter: function(value, row, index) {
return value ? '<span class="label label-default">' + value + '</span>' : '-';
}
},
{
field: 'remark',
title: '备注'

View File

@@ -735,20 +735,19 @@ function checkRecord(recordid) {
$.ajax({
type : 'POST',
url : '/record/check/{$domainId}',
data : {recordid: recordid, name: row.Name, type: row.Type, value: row.Value},
data : {recordid: recordid, name: row.Name, type: row.Type, value: Array.isArray(row.Value) ? row.Value[0] : row.Value},
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
var result = data.data;
var icon = result.status === 'active' ? 1 : (result.status === 'not_found' ? 0 : 2);
var title = result.status === 'active' ? '解析已生效' : (result.status === 'not_found' ? '未查询到解析' : '解析值不匹配');
var content = '<div style="padding:15px;">';
var title = result.status === 'active' ? '<font color="green"><i class="fa fa-check-circle"></i> 解析已生效</font>' : (result.status === 'not_found' ? '<font color="red"><i class="fa fa-times-circle"></i> 未查询到解析</font>' : '<font color="red"><i class="fa fa-times-circle"></i> 解析值不匹配</font>');
var content = '<div style="padding:0 10px;">';
content += '<p><strong>主机记录:</strong>' + row.Name + '</p>';
content += '<p><strong>记录类型:</strong>' + row.Type + '</p>';
content += '<p><strong>记录值:</strong>' + htmlEscape(row.Value) + '</p>';
content += '<hr style="margin:10px 0;">';
content += '<p><strong>检测结果:</strong>' + result.message + '</p>';
content += '<p><strong>检测结果:</strong>' + title + '</p>';
if(result.actual && result.actual.length > 0){
content += '<p><strong>实际解析值:</strong></p>';
content += '<ul style="max-height:150px;overflow-y:auto;">';
@@ -761,7 +760,7 @@ function checkRecord(recordid) {
content += '<p><strong>期望解析值:</strong>' + htmlEscape(result.expected) + '</p>';
}
content += '</div>';
layer.alert(content, {title: title, icon: icon, area: ['450px']});
layer.alert(content, {title: 'DNS解析检测', area: ['450px'], shadeClose: true});
}else{
layer.alert(data.msg, {icon: 2});
}

File diff suppressed because it is too large Load Diff

View File

@@ -31,7 +31,7 @@ return [
'show_error_msg' => true,
'exception_tmpl' => \think\facade\App::getAppPath() . 'view/exception.tpl',
'version' => '1049',
'version' => '1050',
'dbversion' => '1049'
];