mirror of
https://github.com/netcccyun/dnsmgr.git
synced 2026-05-02 11:56:27 +02:00
合并增强 Cloudflare 相关能力
This commit is contained in:
@@ -1,144 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
function convertDomainToAscii($domain)
|
||||
{
|
||||
return (string)$domain;
|
||||
}
|
||||
|
||||
function http_request($url, $data = null, $referer = null, $cookie = null, $headers = null, $proxy = false, $method = null, $timeout = 10): array
|
||||
{
|
||||
$method = strtoupper((string)($method ?: ($data !== null ? 'POST' : 'GET')));
|
||||
$headerLines = [
|
||||
'User-Agent: Codex-Hostname-Smoke/1.0',
|
||||
];
|
||||
$normalizedHeaders = [];
|
||||
foreach ((array)$headers as $key => $value) {
|
||||
$normalizedHeaders[strtolower((string)$key)] = (string)$value;
|
||||
$headerLines[] = $key . ': ' . $value;
|
||||
}
|
||||
|
||||
$content = null;
|
||||
if ($data !== null && $method !== 'GET') {
|
||||
if (is_array($data) || is_object($data)) {
|
||||
$contentType = $normalizedHeaders['content-type'] ?? 'application/x-www-form-urlencoded';
|
||||
if (stripos($contentType, 'application/json') !== false) {
|
||||
$content = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
} else {
|
||||
$content = http_build_query((array)$data);
|
||||
}
|
||||
} else {
|
||||
$content = (string)$data;
|
||||
}
|
||||
}
|
||||
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => $method,
|
||||
'header' => implode("\r\n", $headerLines),
|
||||
'content' => $content,
|
||||
'timeout' => $timeout,
|
||||
'ignore_errors' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$body = @file_get_contents($url, false, $context);
|
||||
if ($body === false) {
|
||||
$body = '';
|
||||
}
|
||||
$responseHeaders = $http_response_header ?? [];
|
||||
$statusLine = $responseHeaders[0] ?? '';
|
||||
$statusCode = preg_match('#\s(\d{3})\s#', $statusLine, $match) ? intval($match[1]) : 0;
|
||||
|
||||
return [
|
||||
'code' => $statusCode,
|
||||
'headers' => $responseHeaders,
|
||||
'body' => $body,
|
||||
];
|
||||
}
|
||||
|
||||
require __DIR__ . '/../app/service/CloudflareEnhanceService.php';
|
||||
|
||||
use app\service\CloudflareEnhanceService;
|
||||
|
||||
$token = getenv('CF_API_TOKEN') ?: '';
|
||||
$zoneId = getenv('CF_ZONE_ID') ?: '';
|
||||
$zoneName = getenv('CF_ZONE_NAME') ?: '';
|
||||
|
||||
if ($token === '' || $zoneId === '' || $zoneName === '') {
|
||||
fwrite(STDERR, "Missing CF_API_TOKEN / CF_ZONE_ID / CF_ZONE_NAME\n");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
$service = new CloudflareEnhanceService([
|
||||
'apikey' => $token,
|
||||
'auth' => '1',
|
||||
]);
|
||||
|
||||
$prefix = 'codex-edit-' . time();
|
||||
$hostname = $prefix . '.' . $zoneName;
|
||||
$origin = 'origin-' . $prefix . '.' . $zoneName;
|
||||
|
||||
$summary = [
|
||||
'created' => null,
|
||||
'updated' => null,
|
||||
'refreshed' => null,
|
||||
'cleanup' => null,
|
||||
];
|
||||
|
||||
$created = null;
|
||||
|
||||
try {
|
||||
$created = $service->createCustomHostname($zoneId, $hostname, null);
|
||||
$hostnameId = (string)($created['id'] ?? '');
|
||||
$summary['created'] = [
|
||||
'id' => $hostnameId,
|
||||
'hostname' => $created['hostname'] ?? '',
|
||||
'ownership_txt_name' => $created['ownership_verification']['name'] ?? '',
|
||||
'ownership_txt_value' => $created['ownership_verification']['value'] ?? '',
|
||||
'ownership_http_url' => $created['ownership_verification_http']['http_url'] ?? '',
|
||||
'ownership_http_body' => $created['ownership_verification_http']['http_body'] ?? '',
|
||||
];
|
||||
|
||||
$updated = $service->updateCustomHostname($zoneId, $hostnameId, [
|
||||
'custom_origin_server' => $origin,
|
||||
'ssl' => [
|
||||
'method' => 'http',
|
||||
'type' => 'dv',
|
||||
],
|
||||
]);
|
||||
$summary['updated'] = [
|
||||
'custom_origin_server' => $updated['custom_origin_server'] ?? '',
|
||||
'ssl_status' => $updated['ssl']['status'] ?? '',
|
||||
'validation_record_count' => count($updated['ssl']['validation_records'] ?? []),
|
||||
'first_http_url' => $updated['ssl']['validation_records'][0]['http_url'] ?? ($updated['ssl']['http_url'] ?? ''),
|
||||
'first_http_body' => $updated['ssl']['validation_records'][0]['http_body'] ?? ($updated['ssl']['http_body'] ?? ''),
|
||||
];
|
||||
|
||||
$current = $service->getCustomHostname($zoneId, $hostnameId);
|
||||
$refreshed = $service->updateCustomHostname($zoneId, $hostnameId, [
|
||||
'custom_origin_server' => trim((string)($current['custom_origin_server'] ?? '')) !== '' ? $current['custom_origin_server'] : null,
|
||||
'ssl' => [
|
||||
'method' => $current['ssl']['method'] ?? 'http',
|
||||
'type' => $current['ssl']['type'] ?? 'dv',
|
||||
],
|
||||
]);
|
||||
$summary['refreshed'] = [
|
||||
'ssl_status' => $refreshed['ssl']['status'] ?? '',
|
||||
'validation_record_count' => count($refreshed['ssl']['validation_records'] ?? []),
|
||||
'ownership_txt_name' => $refreshed['ownership_verification']['name'] ?? '',
|
||||
'ownership_http_url' => $refreshed['ownership_verification_http']['http_url'] ?? '',
|
||||
];
|
||||
} finally {
|
||||
if ($created && !empty($created['id'])) {
|
||||
try {
|
||||
$service->deleteCustomHostname($zoneId, (string)$created['id']);
|
||||
$summary['cleanup'] = true;
|
||||
} catch (Throwable $e) {
|
||||
$summary['cleanup'] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($summary, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT), PHP_EOL;
|
||||
@@ -1,252 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
function convertDomainToAscii($domain)
|
||||
{
|
||||
return (string)$domain;
|
||||
}
|
||||
|
||||
function http_request($url, $data = null, $referer = null, $cookie = null, $headers = null, $proxy = false, $method = null, $timeout = 10): array
|
||||
{
|
||||
$method = strtoupper((string)($method ?: ($data !== null ? 'POST' : 'GET')));
|
||||
$headerLines = [
|
||||
'User-Agent: Codex-Smoke-Test/1.0',
|
||||
];
|
||||
$normalizedHeaders = [];
|
||||
foreach ((array)$headers as $key => $value) {
|
||||
$normalizedHeaders[strtolower((string)$key)] = (string)$value;
|
||||
$headerLines[] = $key . ': ' . $value;
|
||||
}
|
||||
if ($referer) {
|
||||
$headerLines[] = 'Referer: ' . $referer;
|
||||
}
|
||||
if ($cookie) {
|
||||
$headerLines[] = 'Cookie: ' . $cookie;
|
||||
}
|
||||
|
||||
$content = null;
|
||||
if ($data !== null && $method !== 'GET') {
|
||||
if (is_array($data) || is_object($data)) {
|
||||
$contentType = $normalizedHeaders['content-type'] ?? 'application/x-www-form-urlencoded';
|
||||
if (stripos($contentType, 'application/json') !== false) {
|
||||
$content = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
} else {
|
||||
$content = http_build_query((array)$data);
|
||||
}
|
||||
} else {
|
||||
$content = (string)$data;
|
||||
}
|
||||
} elseif ($data !== null && $method === 'GET' && is_array($data) && !str_contains($url, '?')) {
|
||||
$url .= '?' . http_build_query($data);
|
||||
}
|
||||
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => $method,
|
||||
'header' => implode("\r\n", $headerLines),
|
||||
'content' => $content,
|
||||
'timeout' => $timeout,
|
||||
'ignore_errors' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$body = @file_get_contents($url, false, $context);
|
||||
if ($body === false) {
|
||||
$body = '';
|
||||
}
|
||||
$responseHeaders = $http_response_header ?? [];
|
||||
$statusLine = $responseHeaders[0] ?? '';
|
||||
$statusCode = preg_match('#\s(\d{3})\s#', $statusLine, $match) ? intval($match[1]) : 0;
|
||||
|
||||
return [
|
||||
'code' => $statusCode,
|
||||
'headers' => $responseHeaders,
|
||||
'body' => $body,
|
||||
];
|
||||
}
|
||||
|
||||
require __DIR__ . '/../app/service/CloudflareEnhanceService.php';
|
||||
|
||||
use app\service\CloudflareEnhanceService;
|
||||
|
||||
$token = getenv('CF_API_TOKEN') ?: '';
|
||||
$zoneId = getenv('CF_ZONE_ID') ?: '';
|
||||
$accountId = getenv('CF_ACCOUNT_ID') ?: '';
|
||||
$zoneName = getenv('CF_ZONE_NAME') ?: '';
|
||||
|
||||
if ($token === '' || $zoneId === '' || $accountId === '' || $zoneName === '') {
|
||||
fwrite(STDERR, "Missing CF_API_TOKEN / CF_ZONE_ID / CF_ACCOUNT_ID / CF_ZONE_NAME\n");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
$service = new CloudflareEnhanceService([
|
||||
'apikey' => $token,
|
||||
'auth' => '1',
|
||||
'account_id' => $accountId,
|
||||
]);
|
||||
|
||||
$summary = [
|
||||
'account_id' => $service->getDefaultAccountId(),
|
||||
'custom_hostnames' => null,
|
||||
'fallback_origin' => null,
|
||||
'tunnel' => null,
|
||||
'cleanup' => [],
|
||||
];
|
||||
|
||||
$prefix = 'codex-php-smoke-' . time();
|
||||
$tunnelName = $prefix;
|
||||
$publicHostname = $prefix . '.' . $zoneName;
|
||||
$hostnameRoute = 'internal-' . $prefix . '.' . $zoneName;
|
||||
$customHostname = 'saas-' . $prefix . '.' . $zoneName;
|
||||
$fallbackOrigin = 'origin-' . $prefix . '.' . $zoneName;
|
||||
$cidr = '10.234.56.0/24';
|
||||
|
||||
$tunnel = null;
|
||||
$cidrRoute = null;
|
||||
$hostnameRouteRow = null;
|
||||
$customHostnameRow = null;
|
||||
$originalFallbackOrigin = null;
|
||||
|
||||
try {
|
||||
try {
|
||||
$before = $service->listCustomHostnames($zoneId);
|
||||
$summary['custom_hostnames_before'] = count($before);
|
||||
$customHostnameRow = $service->createCustomHostname($zoneId, $customHostname, null);
|
||||
$summary['custom_hostnames'] = [
|
||||
'ok' => true,
|
||||
'created' => [
|
||||
'id' => $customHostnameRow['id'] ?? '',
|
||||
'hostname' => $customHostnameRow['hostname'] ?? '',
|
||||
'ssl_status' => $customHostnameRow['ssl']['status'] ?? '',
|
||||
'ownership_status' => $customHostnameRow['ownership_verification']['http']['status']
|
||||
?? $customHostnameRow['ownership_verification']['txt']['status']
|
||||
?? '',
|
||||
],
|
||||
'after_count' => count($service->listCustomHostnames($zoneId)),
|
||||
];
|
||||
} catch (Throwable $e) {
|
||||
$summary['custom_hostnames'] = [
|
||||
'ok' => false,
|
||||
'message' => $e->getMessage(),
|
||||
'code' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$originalFallbackOrigin = $service->getFallbackOrigin($zoneId);
|
||||
$updatedFallbackOrigin = $service->updateFallbackOrigin($zoneId, $fallbackOrigin);
|
||||
$summary['fallback_origin'] = [
|
||||
'ok' => true,
|
||||
'before' => $originalFallbackOrigin,
|
||||
'after' => $updatedFallbackOrigin,
|
||||
];
|
||||
} catch (Throwable $e) {
|
||||
$summary['fallback_origin'] = [
|
||||
'ok' => false,
|
||||
'message' => $e->getMessage(),
|
||||
'code' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
|
||||
$tunnel = $service->createTunnel($accountId, $tunnelName);
|
||||
$tunnelId = (string)($tunnel['id'] ?? '');
|
||||
$summary['tunnel'] = [
|
||||
'id' => $tunnelId,
|
||||
'name' => $tunnel['name'] ?? '',
|
||||
'status' => $tunnel['status'] ?? '',
|
||||
'token_prefix' => substr($service->getTunnelToken($accountId, $tunnelId), 0, 24),
|
||||
'initial_config' => $service->getTunnelConfig($accountId, $tunnelId),
|
||||
];
|
||||
|
||||
$service->updateTunnelConfig($accountId, $tunnelId, [
|
||||
'ingress' => [
|
||||
[
|
||||
'hostname' => $publicHostname,
|
||||
'service' => 'http://127.0.0.1:8080',
|
||||
],
|
||||
[
|
||||
'service' => 'http_status:404',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$summary['tunnel']['updated_config'] = $service->getTunnelConfig($accountId, $tunnelId);
|
||||
$summary['tunnel']['dns_sync'] = $service->upsertTunnelCnameRecord($zoneId, $publicHostname, $tunnelId);
|
||||
|
||||
$cidrRoute = $service->createCidrRoute($accountId, $tunnelId, $cidr, 'php smoke');
|
||||
$hostnameRouteRow = $service->createHostnameRoute($accountId, $tunnelId, $hostnameRoute, 'php smoke');
|
||||
|
||||
$summary['tunnel']['cidr_routes'] = $service->listCidrRoutes($accountId, $tunnelId);
|
||||
$summary['tunnel']['hostname_routes'] = $service->listHostnameRoutes($accountId, $tunnelId);
|
||||
} finally {
|
||||
if ($customHostnameRow && !empty($customHostnameRow['id'])) {
|
||||
try {
|
||||
$service->deleteCustomHostname($zoneId, (string)$customHostnameRow['id']);
|
||||
$summary['cleanup']['custom_hostname'] = true;
|
||||
} catch (Throwable $e) {
|
||||
$summary['cleanup']['custom_hostname'] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if ($summary['fallback_origin']['ok'] ?? false) {
|
||||
try {
|
||||
if ($originalFallbackOrigin !== null && $originalFallbackOrigin !== '') {
|
||||
$service->updateFallbackOrigin($zoneId, $originalFallbackOrigin);
|
||||
} else {
|
||||
$service->deleteFallbackOrigin($zoneId);
|
||||
}
|
||||
$summary['cleanup']['fallback_origin'] = true;
|
||||
} catch (Throwable $e) {
|
||||
$summary['cleanup']['fallback_origin'] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if ($tunnel && !empty($tunnel['id'])) {
|
||||
$tunnelId = (string)$tunnel['id'];
|
||||
try {
|
||||
$service->deleteTunnelCnameRecordIfMatch($zoneId, $publicHostname, $tunnelId);
|
||||
$summary['cleanup']['dns'] = true;
|
||||
} catch (Throwable $e) {
|
||||
$summary['cleanup']['dns'] = $e->getMessage();
|
||||
}
|
||||
|
||||
if ($cidrRoute && !empty($cidrRoute['id'])) {
|
||||
try {
|
||||
$service->deleteCidrRoute($accountId, (string)$cidrRoute['id']);
|
||||
$summary['cleanup']['cidr'] = true;
|
||||
} catch (Throwable $e) {
|
||||
$summary['cleanup']['cidr'] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if ($hostnameRouteRow && !empty($hostnameRouteRow['id'])) {
|
||||
try {
|
||||
$service->deleteHostnameRoute($accountId, (string)$hostnameRouteRow['id']);
|
||||
$summary['cleanup']['hostname_route'] = true;
|
||||
} catch (Throwable $e) {
|
||||
$summary['cleanup']['hostname_route'] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$service->updateTunnelConfig($accountId, $tunnelId, [
|
||||
'ingress' => [
|
||||
['service' => 'http_status:404'],
|
||||
],
|
||||
]);
|
||||
$summary['cleanup']['config'] = true;
|
||||
} catch (Throwable $e) {
|
||||
$summary['cleanup']['config'] = $e->getMessage();
|
||||
}
|
||||
|
||||
try {
|
||||
$service->deleteTunnel($accountId, $tunnelId);
|
||||
$summary['cleanup']['tunnel'] = true;
|
||||
} catch (Throwable $e) {
|
||||
$summary['cleanup']['tunnel'] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($summary, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT), PHP_EOL;
|
||||
@@ -1,498 +0,0 @@
|
||||
var currentVerificationHostnameId = '';
|
||||
var currentDomainName = '6byj.cn';
|
||||
|
||||
$(document).ready(function(){
|
||||
$("#form-store").bootstrapValidator();
|
||||
loadFallbackOrigin();
|
||||
$("#listTable").bootstrapTable({
|
||||
url: '/cloudflare/hostnames/data/1',
|
||||
method: 'post',
|
||||
toolbar: '',
|
||||
classes: 'table table-striped table-hover table-bordered',
|
||||
uniqueId: 'id',
|
||||
responseHandler: hostnameResponseHandler,
|
||||
columns: [
|
||||
{field: 'hostname', title: '主机名'},
|
||||
{field: 'custom_origin_server', title: '自定义源站', formatter: function(v){ return v || '-'; }},
|
||||
{field: 'ssl_status', title: '证书状态', formatter: formatStatus},
|
||||
{field: 'ssl_validation_status', title: '证书校验', formatter: formatStatus},
|
||||
{field: 'verification_status', title: '所有权校验', formatter: formatStatus},
|
||||
{field: 'created_on', title: '创建时间', formatter: function(v){ return v || '-'; }},
|
||||
{field: 'validation_errors', title: '错误信息', formatter: function(v){ return v || '-'; }},
|
||||
{
|
||||
field: 'action',
|
||||
title: '操作',
|
||||
formatter: function(value, row){
|
||||
return ''
|
||||
+ '<a href="javascript:openEditDialog(\''+row.id+'\')" class="btn btn-info btn-xs">编辑</a> '
|
||||
+ '<a href="javascript:openVerificationDialog(\''+row.id+'\')" class="btn btn-primary btn-xs">校验</a> '
|
||||
+ '<a href="javascript:deleteHostname(\''+row.id+'\', \''+htmlEscape(row.hostname)+'\')" class="btn btn-danger btn-xs">删除</a>';
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
function hostnameResponseHandler(res){
|
||||
if(res.code !== 0){
|
||||
layer.alert(res.msg || '获取自定义主机名失败', {icon: 2});
|
||||
return {total: 0, rows: []};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function refreshHostnameList(){
|
||||
$("#listTable").bootstrapTable('refresh');
|
||||
}
|
||||
|
||||
function formatStatus(value){
|
||||
var v = String(value || '').toLowerCase();
|
||||
if(v === 'active' || v === 'active_deployed' || v === 'valid'){
|
||||
return '<span class="label label-success">'+htmlEscape(value)+'</span>';
|
||||
}
|
||||
if(v === 'pending' || v === 'pending_validation' || v === 'initializing' || v === 'in_progress'){
|
||||
return '<span class="label label-warning">'+htmlEscape(value || '-')+'</span>';
|
||||
}
|
||||
if(v && v !== '-'){
|
||||
return '<span class="label label-danger">'+htmlEscape(value)+'</span>';
|
||||
}
|
||||
return '-';
|
||||
}
|
||||
|
||||
function getHostnameRow(id){
|
||||
var row = $("#listTable").bootstrapTable('getRowByUniqueId', id);
|
||||
if(!row){
|
||||
layer.alert('未找到自定义主机名数据,请先刷新列表后重试', {icon: 2});
|
||||
return null;
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
function resetHostnameForm(){
|
||||
$("#form-store")[0].reset();
|
||||
$("#form-store input[name=hostname_id]").val('');
|
||||
$("#form-store input[name=hostname]").prop('readonly', false);
|
||||
$("#form-store").data("bootstrapValidator").resetForm(true);
|
||||
}
|
||||
|
||||
function openAddDialog(){
|
||||
resetHostnameForm();
|
||||
$("#storeTitle").text('添加自定义主机名');
|
||||
$("#hostnameHint").text('创建后主机名不能直接改名,如需改名请删除后重建。');
|
||||
$("#modal-store").modal('show');
|
||||
}
|
||||
|
||||
function openEditDialog(id){
|
||||
var row = getHostnameRow(id);
|
||||
if(!row){
|
||||
return;
|
||||
}
|
||||
resetHostnameForm();
|
||||
$("#storeTitle").text('编辑自定义主机名');
|
||||
$("#hostnameHint").text('主机名不可直接改名,当前仅支持修改或清空自定义源站。');
|
||||
$("#form-store input[name=hostname_id]").val(row.id);
|
||||
$("#form-store input[name=hostname]").val(row.hostname).prop('readonly', true);
|
||||
$("#form-store input[name=custom_origin_server]").val(row.custom_origin_server || '');
|
||||
$("#modal-store").modal('show');
|
||||
}
|
||||
|
||||
function submitHostname(){
|
||||
$("#form-store").data("bootstrapValidator").validate();
|
||||
if(!$("#form-store").data("bootstrapValidator").isValid()){
|
||||
return;
|
||||
}
|
||||
var hostnameId = $.trim($("#form-store input[name=hostname_id]").val());
|
||||
var url = hostnameId ? '/cloudflare/hostnames/update/1' : '/cloudflare/hostnames/add/1';
|
||||
var successMsg = hostnameId ? '更新自定义主机名成功' : '创建自定义主机名成功';
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: $("#form-store").serialize(),
|
||||
dataType: 'json',
|
||||
success: function(res){
|
||||
layer.close(ii);
|
||||
if(res.code === 0){
|
||||
$("#modal-store").modal('hide');
|
||||
layer.msg(res.msg || successMsg, {icon: 1, time: 1200});
|
||||
if(res.data && res.data.id){
|
||||
$("#listTable").bootstrapTable('updateByUniqueId', {id: res.data.id, row: res.data});
|
||||
if(!$("#listTable").bootstrapTable('getRowByUniqueId', res.data.id)){
|
||||
refreshHostnameList();
|
||||
}
|
||||
}else{
|
||||
refreshHostnameList();
|
||||
}
|
||||
}else{
|
||||
layer.alert(res.msg, {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
layer.close(ii);
|
||||
layer.alert('服务器错误', {icon: 2});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openVerificationDialog(id){
|
||||
var row = getHostnameRow(id);
|
||||
if(!row){
|
||||
return;
|
||||
}
|
||||
currentVerificationHostnameId = id;
|
||||
renderVerificationDialog(row);
|
||||
$("#modal-verification").modal('show');
|
||||
}
|
||||
|
||||
function refreshHostnameValidation(){
|
||||
if(!currentVerificationHostnameId){
|
||||
layer.msg('请先选择自定义主机名');
|
||||
return;
|
||||
}
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/cloudflare/hostnames/refresh/1',
|
||||
data: {hostname_id: currentVerificationHostnameId},
|
||||
dataType: 'json',
|
||||
success: function(res){
|
||||
layer.close(ii);
|
||||
if(res.code === 0){
|
||||
if(res.data && res.data.id){
|
||||
$("#listTable").bootstrapTable('updateByUniqueId', {id: res.data.id, row: res.data});
|
||||
renderVerificationDialog(res.data);
|
||||
}else{
|
||||
refreshHostnameList();
|
||||
}
|
||||
layer.msg(res.msg, {icon: 1, time: 1200});
|
||||
}else{
|
||||
layer.alert(res.msg, {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
layer.close(ii);
|
||||
layer.alert('服务器错误', {icon: 2});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderVerificationDialog(row){
|
||||
$("#verificationTitle").text('证书校验 - ' + row.hostname);
|
||||
var html = '';
|
||||
html += '<div class="alert alert-info"><strong>说明:</strong> 下列值直接来自 Cloudflare 返回结果,可直接复制到 DNS、源站或验证目录中。点击“刷新校验”会重新向 Cloudflare 发起一次校验。</div>';
|
||||
html += '<div class="row">';
|
||||
html += '<div class="col-sm-4">'+renderSummaryCard('证书状态', formatStatusText(row.ssl_status))+'</div>';
|
||||
html += '<div class="col-sm-4">'+renderSummaryCard('证书校验', formatStatusText(row.ssl_validation_status))+'</div>';
|
||||
html += '<div class="col-sm-4">'+renderSummaryCard('所有权校验', formatStatusText(row.verification_status))+'</div>';
|
||||
html += '</div>';
|
||||
|
||||
var ownership = row.ownership_verification || {};
|
||||
if(ownership.name || ownership.value){
|
||||
html += renderSection('所有权 TXT 校验',
|
||||
renderCopyInput('记录类型', ownership.type || 'txt', false)
|
||||
+ renderCopyInput('TXT 名称', ownership.name || '', true)
|
||||
+ renderCopyTextarea('TXT 值', ownership.value || '', true, 3)
|
||||
+ renderQuickAddTxtButton(ownership.name || '', ownership.value || '', '快速添加所有权 TXT')
|
||||
);
|
||||
}
|
||||
|
||||
var ownershipHttp = row.ownership_verification_http || {};
|
||||
if(ownershipHttp.http_url || ownershipHttp.http_body){
|
||||
html += renderSection('所有权 HTTP 校验',
|
||||
renderCopyTextarea('HTTP URL', ownershipHttp.http_url || '', true, 2)
|
||||
+ renderCopyTextarea('HTTP Body', ownershipHttp.http_body || '', true, 3)
|
||||
);
|
||||
}
|
||||
|
||||
var records = $.isArray(row.ssl_validation_records) ? row.ssl_validation_records : [];
|
||||
if(records.length > 0){
|
||||
var recordsHtml = '';
|
||||
for(var i = 0; i < records.length; i++){
|
||||
var item = records[i] || {};
|
||||
var emails = $.isArray(item.emails) ? item.emails.join('\n') : '';
|
||||
recordsHtml += '<div class="panel panel-default" style="margin-bottom:12px;">';
|
||||
recordsHtml += '<div class="panel-heading"><strong>证书校验记录 #' + (i + 1) + '</strong><span class="pull-right">' + formatStatusText(item.status || '-') + '</span></div>';
|
||||
recordsHtml += '<div class="panel-body">';
|
||||
recordsHtml += renderCopyInput('TXT 名称', item.txt_name || '', true);
|
||||
recordsHtml += renderCopyTextarea('TXT 值', item.txt_value || '', true, 3);
|
||||
recordsHtml += renderQuickAddTxtButton(item.txt_name || '', item.txt_value || '', '快速添加 TXT');
|
||||
recordsHtml += renderCopyInput('CNAME 名称', item.cname_name || '', true);
|
||||
recordsHtml += renderCopyTextarea('CNAME 目标', item.cname_target || '', true, 2);
|
||||
recordsHtml += renderCopyTextarea('HTTP URL', item.http_url || '', true, 2);
|
||||
recordsHtml += renderCopyTextarea('HTTP Body', item.http_body || '', true, 3);
|
||||
recordsHtml += renderCopyTextarea('邮箱地址', emails, false, 2);
|
||||
recordsHtml += '</div></div>';
|
||||
}
|
||||
html += renderSection('证书校验记录', recordsHtml);
|
||||
}else{
|
||||
html += '<div class="alert alert-warning">Cloudflare 当前尚未返回证书校验记录,请先等待状态进入 <code>pending_validation</code>,再点击“刷新校验”或稍后刷新列表。</div>';
|
||||
}
|
||||
|
||||
if(row.validation_errors){
|
||||
html += renderSection('错误信息', renderCopyTextarea('错误信息', row.validation_errors, false, 3));
|
||||
}
|
||||
|
||||
$("#verificationContent").html(html);
|
||||
}
|
||||
|
||||
function renderSummaryCard(title, value){
|
||||
return '<div class="panel panel-default"><div class="panel-heading"><strong>' + htmlEscape(title) + '</strong></div><div class="panel-body">' + value + '</div></div>';
|
||||
}
|
||||
|
||||
function renderSection(title, body){
|
||||
return '<div class="panel panel-default"><div class="panel-heading"><strong>' + htmlEscape(title) + '</strong></div><div class="panel-body">' + body + '</div></div>';
|
||||
}
|
||||
|
||||
function renderCopyInput(label, value, copyable){
|
||||
var safeValue = String(value || '');
|
||||
if(!safeValue){
|
||||
return '';
|
||||
}
|
||||
var html = '<div class="form-group">';
|
||||
html += '<label>' + htmlEscape(label) + '</label>';
|
||||
if(copyable){
|
||||
html += '<div class="input-group">';
|
||||
html += '<input type="text" class="form-control" readonly value="' + htmlEscape(safeValue) + '">';
|
||||
html += '<span class="input-group-btn"><button type="button" class="btn btn-default" data-copy="' + encodeURIComponent(safeValue) + '" onclick="copyEncodedValue(this)">复制</button></span>';
|
||||
html += '</div>';
|
||||
}else{
|
||||
html += '<input type="text" class="form-control" readonly value="' + htmlEscape(safeValue) + '">';
|
||||
}
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderCopyTextarea(label, value, copyable, rows){
|
||||
var safeValue = String(value || '');
|
||||
if(!safeValue){
|
||||
return '';
|
||||
}
|
||||
var html = '<div class="form-group">';
|
||||
html += '<label>' + htmlEscape(label) + '</label>';
|
||||
html += '<textarea class="form-control" rows="' + (rows || 3) + '" readonly>' + htmlEscape(safeValue) + '</textarea>';
|
||||
if(copyable){
|
||||
html += '<div class="text-right" style="margin-top:8px;"><button type="button" class="btn btn-default btn-xs" data-copy="' + encodeURIComponent(safeValue) + '" onclick="copyEncodedValue(this)">复制</button></div>';
|
||||
}
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderQuickAddTxtButton(name, value, label){
|
||||
var txtName = String(name || '').trim();
|
||||
var txtValue = String(value || '').trim();
|
||||
if(!txtName || !txtValue){
|
||||
return '';
|
||||
}
|
||||
return '<div class="text-right" style="margin-top:8px;margin-bottom:12px;"><button type="button" class="btn btn-success btn-xs" data-name="' + encodeURIComponent(txtName) + '" data-value="' + encodeURIComponent(txtValue) + '" onclick="quickAddTxtRecord(this)">' + htmlEscape(label || '快速添加 TXT') + '</button></div>';
|
||||
}
|
||||
|
||||
function formatStatusText(value){
|
||||
var text = value || '-';
|
||||
if(text === '-'){
|
||||
return '<span class="text-muted">-</span>';
|
||||
}
|
||||
return formatStatus(text);
|
||||
}
|
||||
|
||||
function copyEncodedValue(btn){
|
||||
copyText(decodeURIComponent($(btn).attr('data-copy') || ''));
|
||||
}
|
||||
|
||||
function copyText(text){
|
||||
var value = String(text || '');
|
||||
if(!value){
|
||||
layer.msg('没有可复制的内容');
|
||||
return;
|
||||
}
|
||||
if(navigator.clipboard && window.isSecureContext){
|
||||
navigator.clipboard.writeText(value).then(function(){
|
||||
layer.msg('已复制', {icon: 1, time: 1000});
|
||||
}).catch(function(){
|
||||
fallbackCopyText(value);
|
||||
});
|
||||
return;
|
||||
}
|
||||
fallbackCopyText(value);
|
||||
}
|
||||
|
||||
function fallbackCopyText(text){
|
||||
var $temp = $('<textarea readonly></textarea>');
|
||||
$('body').append($temp);
|
||||
$temp.val(text).select();
|
||||
try{
|
||||
document.execCommand('copy');
|
||||
layer.msg('已复制', {icon: 1, time: 1000});
|
||||
}catch(e){
|
||||
layer.alert('复制失败,请手动复制', {icon: 2});
|
||||
}
|
||||
$temp.remove();
|
||||
}
|
||||
|
||||
function quickAddTxtRecord(btn){
|
||||
var fullName = decodeURIComponent($(btn).attr('data-name') || '');
|
||||
var value = decodeURIComponent($(btn).attr('data-value') || '');
|
||||
var rr = convertFullHostnameToRecordName(fullName);
|
||||
if(rr === null){
|
||||
layer.alert('TXT 记录名称与当前域名不匹配,无法自动添加,请手动到解析页添加', {icon: 2});
|
||||
return;
|
||||
}
|
||||
|
||||
layer.confirm('确定要快速添加 TXT 记录吗?<br><code>' + htmlEscape(fullName) + '</code>', {title: '提示', icon: 0}, function(){
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/record/add/1',
|
||||
data: {
|
||||
name: rr,
|
||||
type: 'TXT',
|
||||
value: value,
|
||||
line: '0',
|
||||
ttl: 600,
|
||||
mx: 1,
|
||||
weight: 0,
|
||||
remark: 'Cloudflare证书校验'
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(res){
|
||||
layer.close(ii);
|
||||
if(res.code === 0){
|
||||
layer.closeAll();
|
||||
$("#modal-verification").modal('show');
|
||||
layer.msg('TXT 记录添加成功', {icon: 1, time: 1200});
|
||||
}else{
|
||||
layer.alert(res.msg, {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
layer.close(ii);
|
||||
layer.alert('服务器错误', {icon: 2});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function convertFullHostnameToRecordName(fullName){
|
||||
var name = String(fullName || '').trim().replace(/\.$/, '');
|
||||
var domain = String(currentDomainName || '').trim().replace(/\.$/, '');
|
||||
if(!name || !domain){
|
||||
return null;
|
||||
}
|
||||
var lowerName = name.toLowerCase();
|
||||
var lowerDomain = domain.toLowerCase();
|
||||
if(lowerName === lowerDomain){
|
||||
return '@';
|
||||
}
|
||||
if(lowerName.endsWith('.' + lowerDomain)){
|
||||
return name.slice(0, name.length - domain.length - 1);
|
||||
}
|
||||
if(name === '@'){
|
||||
return '@';
|
||||
}
|
||||
if(name.indexOf('.') === -1){
|
||||
return name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function deleteHostname(id, hostname){
|
||||
layer.confirm('确定要删除自定义主机名 ' + hostname + ' 吗?', {title: '提示', icon: 0}, function(){
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/cloudflare/hostnames/delete/1',
|
||||
data: {hostname_id: id, hostname: hostname},
|
||||
dataType: 'json',
|
||||
success: function(res){
|
||||
layer.close(ii);
|
||||
if(res.code === 0){
|
||||
layer.closeAll();
|
||||
layer.msg(res.msg, {icon: 1, time: 1000});
|
||||
refreshHostnameList();
|
||||
}else{
|
||||
layer.alert(res.msg, {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
layer.close(ii);
|
||||
layer.alert('服务器错误', {icon: 2});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadFallbackOrigin(){
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/cloudflare/fallback/get/1',
|
||||
dataType: 'json',
|
||||
success: function(res){
|
||||
if(res.code === 0){
|
||||
$("#fallbackOrigin").val((res.data && res.data.origin) ? res.data.origin : '');
|
||||
}else{
|
||||
layer.alert(res.msg, {icon: 2});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveFallbackOrigin(){
|
||||
var origin = $.trim($("#fallbackOrigin").val());
|
||||
if(!origin){
|
||||
layer.msg('请输入 Fallback Origin');
|
||||
return;
|
||||
}
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/cloudflare/fallback/set/1',
|
||||
data: {origin: origin},
|
||||
dataType: 'json',
|
||||
success: function(res){
|
||||
layer.close(ii);
|
||||
if(res.code === 0){
|
||||
$("#fallbackOrigin").val(res.data.origin || origin);
|
||||
layer.msg(res.msg, {icon: 1, time: 1200});
|
||||
}else{
|
||||
layer.alert(res.msg, {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
layer.close(ii);
|
||||
layer.alert('服务器错误', {icon: 2});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearFallbackOrigin(){
|
||||
layer.confirm('确定要清空 Fallback Origin 吗?', {title: '提示', icon: 0}, function(){
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/cloudflare/fallback/delete/1',
|
||||
dataType: 'json',
|
||||
success: function(res){
|
||||
layer.close(ii);
|
||||
if(res.code === 0){
|
||||
layer.closeAll();
|
||||
$("#fallbackOrigin").val('');
|
||||
layer.msg(res.msg, {icon: 1, time: 1200});
|
||||
}else{
|
||||
layer.alert(res.msg, {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
layer.close(ii);
|
||||
layer.alert('服务器错误', {icon: 2});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function htmlEscape(str) {
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
@@ -3,11 +3,9 @@ declare (strict_types = 1);
|
||||
|
||||
namespace app;
|
||||
|
||||
use app\lib\DnsHelper;
|
||||
use think\App;
|
||||
use think\exception\ValidateException;
|
||||
use think\Validate;
|
||||
use think\facade\Db;
|
||||
use think\facade\View;
|
||||
|
||||
/**
|
||||
@@ -98,36 +96,6 @@ abstract class BaseController
|
||||
return $v->failException(true)->check($data);
|
||||
}
|
||||
|
||||
protected function getManagedDomainOptions(?string $type = null): array
|
||||
{
|
||||
if (!checkPermission(1)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = Db::name('domain')->alias('A')
|
||||
->join('account B', 'A.aid = B.id')
|
||||
->field('A.id,A.name,B.type');
|
||||
if (!empty($type)) {
|
||||
$query->where('B.type', $type);
|
||||
}
|
||||
if (request()->user['level'] == 1) {
|
||||
$query->where('A.is_hide', 0)->where('A.name', 'in', request()->user['permission']);
|
||||
}
|
||||
|
||||
$rows = $query->order('A.name', 'asc')->select();
|
||||
$list = [];
|
||||
foreach ($rows as $row) {
|
||||
$typeName = DnsHelper::$dns_config[$row['type']]['name'] ?? strtoupper((string)$row['type']);
|
||||
$list[] = [
|
||||
'id' => intval($row['id']),
|
||||
'name' => $row['name'],
|
||||
'type' => $row['type'],
|
||||
'text' => $row['name'] . ' [' . $typeName . ']',
|
||||
];
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
||||
protected function alert($code, $msg = '', $url = null, $wait = 3)
|
||||
{
|
||||
|
||||
@@ -15,18 +15,8 @@ class Cloudflare extends BaseController
|
||||
{
|
||||
try {
|
||||
$context = $this->getCloudflareDomainContext(input('param.id/d'));
|
||||
$quickDomainOptions = $this->getManagedDomainOptions('cloudflare');
|
||||
if (empty($quickDomainOptions)) {
|
||||
$quickDomainOptions = [[
|
||||
'id' => intval($context['domain']['id']),
|
||||
'name' => $context['domain']['name'],
|
||||
'type' => 'cloudflare',
|
||||
'text' => $context['domain']['name'] . ' [Cloudflare]',
|
||||
]];
|
||||
}
|
||||
View::assign('domainId', $context['domain']['id']);
|
||||
View::assign('domainName', $context['domain']['name']);
|
||||
View::assign('quickDomainOptions', $quickDomainOptions);
|
||||
return view();
|
||||
} catch (Exception $e) {
|
||||
return $this->alert('error', $e->getMessage());
|
||||
|
||||
@@ -261,27 +261,14 @@ class Domain extends BaseController
|
||||
$name = input('post.name', null, 'trim');
|
||||
$thirdid = input('post.thirdid', null, 'trim');
|
||||
$recordcount = input('post.recordcount/d', 0);
|
||||
$result = [];
|
||||
if ($method == 1 && empty($name) || $method == 0 && (empty($name) || empty($thirdid))) return json(['code' => -1, 'msg' => '参数不能为空']);
|
||||
if (Db::name('domain')->where('aid', $aid)->where('name', $name)->find()) {
|
||||
return json(['code' => -1, 'msg' => '域名已存在']);
|
||||
}
|
||||
if ($method == 1) {
|
||||
$account = Db::name('account')->where('id', $aid)->find();
|
||||
if (!$account) {
|
||||
return json(['code' => -1, 'msg' => '域名账户不存在']);
|
||||
}
|
||||
$name = strtolower(rtrim($name, '.'));
|
||||
if ($account['type'] == 'dnspod' && getMainDomain($name) !== $name) {
|
||||
$result = $this->addDnsPodDelegatedSubdomain($account, $name);
|
||||
if (!$result['success']) {
|
||||
return json(['code' => -1, 'msg' => $result['msg']]);
|
||||
}
|
||||
} else {
|
||||
$dns = DnsHelper::getModel($aid);
|
||||
$result = $dns->addDomain($name);
|
||||
if (!$result) return json(['code' => -1, 'msg' => '添加域名失败,' . $dns->getError()]);
|
||||
}
|
||||
$dns = DnsHelper::getModel($aid);
|
||||
$result = $dns->addDomain($name);
|
||||
if (!$result) return json(['code' => -1, 'msg' => '添加域名失败,' . $dns->getError()]);
|
||||
$name = $result['name'];
|
||||
$thirdid = $result['id'];
|
||||
}
|
||||
@@ -294,11 +281,7 @@ class Domain extends BaseController
|
||||
'is_sso' => 1,
|
||||
'recordcount' => $recordcount,
|
||||
]);
|
||||
$msg = '添加域名成功!';
|
||||
if (!empty($result['msg'])) {
|
||||
$msg .= ' ' . $result['msg'];
|
||||
}
|
||||
return json(['code' => 0, 'msg' => $msg]);
|
||||
return json(['code' => 0, 'msg' => '添加域名成功!']);
|
||||
} elseif ($act == 'edit') {
|
||||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||||
$id = input('post.id/d');
|
||||
@@ -469,19 +452,9 @@ class Domain extends BaseController
|
||||
|
||||
$dnsconfig = DnsHelper::$dns_config[$dnstype];
|
||||
$dnsconfig['type'] = $dnstype;
|
||||
$quickDomainOptions = $this->getManagedDomainOptions();
|
||||
if (empty($quickDomainOptions)) {
|
||||
$quickDomainOptions = [[
|
||||
'id' => intval($id),
|
||||
'name' => $drow['name'],
|
||||
'type' => $dnstype,
|
||||
'text' => $drow['name'] . ' [' . ($dnsconfig['name'] ?? strtoupper($dnstype)) . ']',
|
||||
]];
|
||||
}
|
||||
|
||||
View::assign('domainId', $id);
|
||||
View::assign('domainName', $drow['name']);
|
||||
View::assign('quickDomainOptions', $quickDomainOptions);
|
||||
View::assign('recordLine', $recordLineArr);
|
||||
View::assign('minTTL', $minTTL ? $minTTL : 1);
|
||||
View::assign('dnsconfig', $dnsconfig);
|
||||
@@ -1217,189 +1190,6 @@ class Domain extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
private function addDnsPodDelegatedSubdomain($account, $domain)
|
||||
{
|
||||
$dns = DnsHelper::getModel(intval($account['id']));
|
||||
if (!$dns || !method_exists($dns, 'createSubdomainValidateTxtValue')) {
|
||||
return ['success' => false, 'msg' => '当前腾讯云账户不支持子域托管自动委派'];
|
||||
}
|
||||
|
||||
$parentDomainRow = $this->findManagedParentDomainRow($domain);
|
||||
if (!$parentDomainRow) {
|
||||
return ['success' => false, 'msg' => '未找到可写的父域名,请先把父域添加到系统后再创建子域托管'];
|
||||
}
|
||||
|
||||
$relativeName = $this->buildRelativeRecordName($domain, $parentDomainRow['name']);
|
||||
if ($relativeName === '@') {
|
||||
return ['success' => false, 'msg' => '当前输入看起来是根域名,请直接按普通新域名方式添加'];
|
||||
}
|
||||
|
||||
$validation = $dns->createSubdomainValidateTxtValue($domain);
|
||||
if (!$validation) {
|
||||
return ['success' => false, 'msg' => '获取腾讯云子域校验 TXT 失败,' . $dns->getError()];
|
||||
}
|
||||
|
||||
$validationRecordName = $validation['sub_domain'] !== '' ? $validation['sub_domain'] : $relativeName;
|
||||
$validationValue = $validation['value'] ?? '';
|
||||
if ($validationValue === '') {
|
||||
return ['success' => false, 'msg' => '腾讯云未返回子域校验 TXT 值,请稍后重试'];
|
||||
}
|
||||
|
||||
$saveValidation = $this->ensureManagedRecord(
|
||||
$parentDomainRow,
|
||||
$validationRecordName,
|
||||
'TXT',
|
||||
$validationValue,
|
||||
'DNSPod子域托管校验'
|
||||
);
|
||||
if (!$saveValidation['success']) {
|
||||
return ['success' => false, 'msg' => '父域自动添加校验 TXT 失败,' . $saveValidation['msg']];
|
||||
}
|
||||
|
||||
$validated = false;
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
if ($dns->describeSubdomainValidateStatus($domain)) {
|
||||
$validated = true;
|
||||
break;
|
||||
}
|
||||
if ($i < 3) {
|
||||
sleep(3);
|
||||
}
|
||||
}
|
||||
if (!$validated) {
|
||||
return [
|
||||
'success' => false,
|
||||
'msg' => '已自动向父域添加腾讯云校验 TXT,但腾讯云暂未检测到生效。请等待 DNS 生效后再次点击添加。校验主机:'
|
||||
. $validationRecordName . ';校验值:' . $validationValue,
|
||||
];
|
||||
}
|
||||
|
||||
$result = $dns->addDomain($domain);
|
||||
if (!$result) {
|
||||
return ['success' => false, 'msg' => '腾讯云创建子域托管失败,' . $dns->getError()];
|
||||
}
|
||||
|
||||
$nameServers = isset($result['name_servers']) && is_array($result['name_servers']) ? $result['name_servers'] : [];
|
||||
if (empty($nameServers)) {
|
||||
return [
|
||||
'success' => true,
|
||||
'id' => $result['id'],
|
||||
'name' => $result['name'],
|
||||
'msg' => '腾讯云子域已创建,但未返回 NS 服务器,请到腾讯云控制台查看后手动补父域委派。',
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($nameServers as $nameServer) {
|
||||
$saveNs = $this->ensureManagedRecord(
|
||||
$parentDomainRow,
|
||||
$relativeName,
|
||||
'NS',
|
||||
$nameServer,
|
||||
'DNSPod子域托管委派'
|
||||
);
|
||||
if (!$saveNs['success']) {
|
||||
return [
|
||||
'success' => false,
|
||||
'msg' => '腾讯云子域已创建,但父域自动添加 NS 委派失败,' . $saveNs['msg'] . '。请手动添加 NS:' . implode(', ', $nameServers),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'id' => $result['id'],
|
||||
'name' => $result['name'],
|
||||
'msg' => '已自动完成父域校验 TXT 和 NS 委派。',
|
||||
];
|
||||
}
|
||||
|
||||
private function findManagedParentDomainRow($domain)
|
||||
{
|
||||
$domain = strtolower(rtrim(trim($domain), '.'));
|
||||
$rows = Db::name('domain')->alias('d')
|
||||
->join('account a', 'd.aid = a.id')
|
||||
->field('d.id,d.aid,d.name,d.thirdid,a.type')
|
||||
->select()
|
||||
->toArray();
|
||||
usort($rows, function ($left, $right) {
|
||||
return strlen($right['name']) <=> strlen($left['name']);
|
||||
});
|
||||
foreach ($rows as $row) {
|
||||
$parent = strtolower(rtrim(trim($row['name']), '.'));
|
||||
if ($parent === $domain) {
|
||||
continue;
|
||||
}
|
||||
if (str_ends_with($domain, '.' . $parent)) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function buildRelativeRecordName($domain, $parentDomain)
|
||||
{
|
||||
$domain = strtolower(rtrim(trim($domain), '.'));
|
||||
$parentDomain = strtolower(rtrim(trim($parentDomain), '.'));
|
||||
if ($domain === $parentDomain) {
|
||||
return '@';
|
||||
}
|
||||
$suffix = '.' . $parentDomain;
|
||||
if (!str_ends_with($domain, $suffix)) {
|
||||
return '';
|
||||
}
|
||||
return substr($domain, 0, -strlen($suffix));
|
||||
}
|
||||
|
||||
private function ensureManagedRecord($domainRow, $name, $type, $value, $remark = null)
|
||||
{
|
||||
$dns = DnsHelper::getModel($domainRow['aid'], $domainRow['name'], $domainRow['thirdid']);
|
||||
if (!$dns) {
|
||||
return ['success' => false, 'msg' => '父域 DNS 驱动初始化失败'];
|
||||
}
|
||||
if ($this->hasExistingManagedRecord($dns, $name, $type, $value)) {
|
||||
return ['success' => true, 'msg' => '记录已存在'];
|
||||
}
|
||||
$line = DnsHelper::$line_name[$domainRow['type']]['DEF'] ?? 'default';
|
||||
$recordId = $dns->addDomainRecord($name, $type, $value, $line, 600, 1, null, $remark);
|
||||
if (!$recordId) {
|
||||
return ['success' => false, 'msg' => $dns->getError()];
|
||||
}
|
||||
$this->add_log($domainRow['name'], '添加解析', $name . ' [' . $type . '] ' . $value . ' (线路:' . $line . ' TTL:600)');
|
||||
return ['success' => true, 'msg' => '添加成功'];
|
||||
}
|
||||
|
||||
private function hasExistingManagedRecord($dns, $name, $type, $value)
|
||||
{
|
||||
$records = $dns->getSubDomainRecords($name === '@' ? '' : $name, 1, 100, $type);
|
||||
if (!$records || empty($records['list']) || !is_array($records['list'])) {
|
||||
return false;
|
||||
}
|
||||
foreach ($records['list'] as $row) {
|
||||
if (strtoupper((string)($row['Type'] ?? '')) !== strtoupper($type)) {
|
||||
continue;
|
||||
}
|
||||
if ($this->isSameDnsRecordValue($type, $row['Value'] ?? '', $value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function isSameDnsRecordValue($type, $left, $right)
|
||||
{
|
||||
$left = trim((string)$left);
|
||||
$right = trim((string)$right);
|
||||
if (strtoupper($type) === 'TXT') {
|
||||
$left = trim($left, "\"'");
|
||||
$right = trim($right, "\"'");
|
||||
return $left === $right;
|
||||
}
|
||||
if (strtoupper($type) === 'NS') {
|
||||
return strtolower(rtrim($left, '.')) === strtolower(rtrim($right, '.'));
|
||||
}
|
||||
return $left === $right;
|
||||
}
|
||||
|
||||
public function expire_notice()
|
||||
{
|
||||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||||
|
||||
@@ -366,19 +366,18 @@ class DnsHelper
|
||||
'cloudflare' => [
|
||||
'name' => 'Cloudflare',
|
||||
'icon' => 'cloudflare.ico',
|
||||
'note' => '如需使用 Cloudflare 增强与 Tunnels,建议使用 <b>API令牌</b> 认证,并补充 <b>Account ID</b>。Fallback Origin / 自定义主机名还要求目标 Zone 已开通 Cloudflare for SaaS 能力。',
|
||||
'note' => '',
|
||||
'config' => [
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
'show' => 'auth=="0"',
|
||||
],
|
||||
'apikey' => [
|
||||
'name' => 'API密钥/令牌',
|
||||
'type' => 'input',
|
||||
'placeholder' => '建议填写 Cloudflare API Token',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'auth' => [
|
||||
@@ -388,15 +387,7 @@ class DnsHelper
|
||||
'0' => 'API密钥',
|
||||
'1' => 'API令牌',
|
||||
],
|
||||
'value' => '1'
|
||||
],
|
||||
'account_id' => [
|
||||
'name' => 'Account ID',
|
||||
'type' => 'input',
|
||||
'placeholder' => '可留空,首次进入 Tunnels 时会尝试自动探测',
|
||||
'required' => false,
|
||||
'show' => 'auth=="1"',
|
||||
'note' => 'Cloudflare Tunnels 是账户级能力,建议填写 Account ID;留空时系统会尝试自动探测。'
|
||||
'value' => '0'
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
|
||||
@@ -322,53 +322,11 @@ class dnspod implements DnsInterface
|
||||
];
|
||||
$data = $this->send_request($action, $param);
|
||||
if ($data) {
|
||||
$result = [
|
||||
'id' => $data['DomainInfo']['Id'],
|
||||
'name' => $data['DomainInfo']['Domain'],
|
||||
'name_servers' => $this->normalizeNameServerList($data['DomainInfo']['GradeNsList'] ?? []),
|
||||
];
|
||||
if (!$this->modifyDomainStatus($result['name'], 'enable', intval($result['id']))) {
|
||||
return false;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
$existingDomain = $this->findExactDomain($Domain);
|
||||
if ($existingDomain) {
|
||||
if (!$this->modifyDomainStatus($existingDomain['name'], 'enable', intval($existingDomain['id']))) {
|
||||
return false;
|
||||
}
|
||||
return $existingDomain;
|
||||
return ['id' => $data['DomainInfo']['Id'], 'name' => $data['DomainInfo']['Domain']];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function createSubdomainValidateTxtValue($Domain)
|
||||
{
|
||||
$action = 'CreateSubdomainValidateTXTValue';
|
||||
$param = [
|
||||
'DomainZone' => $Domain,
|
||||
];
|
||||
$data = $this->send_request($action, $param);
|
||||
if ($data) {
|
||||
return [
|
||||
'domain' => trim((string)($data['Domain'] ?? getMainDomain($Domain))),
|
||||
'sub_domain' => trim((string)($data['Subdomain'] ?? '')),
|
||||
'value' => trim((string)($data['Value'] ?? '')),
|
||||
];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function describeSubdomainValidateStatus($Domain)
|
||||
{
|
||||
$action = 'DescribeSubdomainValidateStatus';
|
||||
$param = [
|
||||
'DomainZone' => $Domain,
|
||||
];
|
||||
$data = $this->send_request($action, $param);
|
||||
return is_array($data);
|
||||
}
|
||||
|
||||
//域名别名列表
|
||||
public function domainAliasList()
|
||||
{
|
||||
@@ -450,56 +408,4 @@ class dnspod implements DnsInterface
|
||||
$this->error = $message;
|
||||
//file_put_contents('logs.txt',date('H:i:s').' '.$message."\r\n", FILE_APPEND);
|
||||
}
|
||||
|
||||
private function normalizeNameServerList($list)
|
||||
{
|
||||
if (!is_array($list)) {
|
||||
return [];
|
||||
}
|
||||
$result = [];
|
||||
foreach ($list as $item) {
|
||||
$value = trim((string)$item);
|
||||
if ($value !== '') {
|
||||
$result[] = strtolower(rtrim($value, '.'));
|
||||
}
|
||||
}
|
||||
return array_values(array_unique($result));
|
||||
}
|
||||
|
||||
private function modifyDomainStatus($domain, $status, $domainId = 0)
|
||||
{
|
||||
$param = [
|
||||
'Domain' => $domain,
|
||||
'Status' => $status,
|
||||
];
|
||||
if ($domainId > 0) {
|
||||
$param['DomainId'] = $domainId;
|
||||
}
|
||||
$data = $this->send_request('ModifyDomainStatus', $param);
|
||||
return is_array($data);
|
||||
}
|
||||
|
||||
private function findExactDomain($domain)
|
||||
{
|
||||
$data = $this->send_request('DescribeDomainList', [
|
||||
'Offset' => 0,
|
||||
'Limit' => 20,
|
||||
'Keyword' => $domain,
|
||||
]);
|
||||
if (!$data || empty($data['DomainList']) || !is_array($data['DomainList'])) {
|
||||
return false;
|
||||
}
|
||||
foreach ($data['DomainList'] as $row) {
|
||||
$name = strtolower(trim((string)($row['Name'] ?? '')));
|
||||
if ($name !== strtolower(trim((string)$domain))) {
|
||||
continue;
|
||||
}
|
||||
return [
|
||||
'id' => $row['DomainId'],
|
||||
'name' => $row['Name'],
|
||||
'name_servers' => $this->normalizeNameServerList($row['GradeNsList'] ?? []),
|
||||
];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,6 @@
|
||||
<div class="panel-heading">
|
||||
<div class="clearfix">
|
||||
<div class="pull-right" style="margin-top:-6px;max-width:100%;">
|
||||
<div style="display:inline-block;width:300px;max-width:100%;vertical-align:middle;margin-right:6px;">
|
||||
<select id="quickDomainSwitch" class="form-control">
|
||||
{volist name="quickDomainOptions" id="item"}
|
||||
<option value="{$item.id}"{if $item.id == $domainId} selected{/if}>{$item.text}</option>
|
||||
{/volist}
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-primary" id="quickDomainSwitchBtn" style="vertical-align:middle;margin-right:6px;" onclick="return quickSwitchDomain('/cloudflare/hostnames/')"><i class="fa fa-random fa-fw"></i> 切换域名</button>
|
||||
<a href="/record/{$domainId}" class="btn btn-sm btn-default" style="vertical-align:middle;"><i class="fa fa-reply fa-fw"></i> 返回解析</a>
|
||||
</div>
|
||||
<h3 class="panel-title" style="padding-top:4px;">Cloudflare增强 - {$domainName}</h3>
|
||||
@@ -37,7 +29,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix" style="margin-bottom:15px;">
|
||||
<div class="clearfix" style="margin-bottom:5px;">
|
||||
<div class="pull-left">
|
||||
<a href="javascript:refreshHostnameList()" class="btn btn-default" title="刷新自定义主机名列表"><i class="fa fa-refresh"></i> 刷新</a>
|
||||
<a href="javascript:openAddDialog()" class="btn btn-success"><i class="fa fa-plus"></i> 添加自定义主机名</a>
|
||||
@@ -114,15 +106,6 @@
|
||||
var currentVerificationHostnameId = '';
|
||||
|
||||
$(document).ready(function(){
|
||||
initDomainQuickSwitch({
|
||||
buttonSelector: '',
|
||||
currentId: '{$domainId}',
|
||||
currentText: {$domainName|json_encode|raw},
|
||||
type: 'cloudflare',
|
||||
buildUrl: function(id){
|
||||
return '/cloudflare/hostnames/' + id;
|
||||
}
|
||||
});
|
||||
$("#form-store").bootstrapValidator();
|
||||
loadFallbackOrigin();
|
||||
$("#listTable").bootstrapTable({
|
||||
@@ -622,16 +605,22 @@ function deleteHostname(id, hostname){
|
||||
}
|
||||
|
||||
function loadFallbackOrigin(){
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/cloudflare/fallback/get/{$domainId}',
|
||||
dataType: 'json',
|
||||
success: function(res){
|
||||
layer.close(ii);
|
||||
if(res.code === 0){
|
||||
$("#fallbackOrigin").val((res.data && res.data.origin) ? res.data.origin : '');
|
||||
}else{
|
||||
layer.alert(res.msg, {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
layer.close(ii);
|
||||
layer.alert('服务器错误', {icon: 2});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ $(document).ready(function(){
|
||||
var html = '<a href="/account/edit?id='+row.id+'" class="btn btn-info btn-xs">编辑</a> <a href="javascript:delItem('+row.id+')" class="btn btn-danger btn-xs">删除</a> <a href="/domain?aid='+row.id+'" class="btn btn-default btn-xs">域名</a>';
|
||||
var rowType = String(row.type || '').toLowerCase();
|
||||
if(userLevel == '2' && rowType === 'cloudflare'){
|
||||
html += ' <a href="/cloudflare/tunnels/'+row.id+'" class="btn btn-primary btn-xs">Tunnels</a>';
|
||||
html += ' <a href="/cloudflare/tunnels/'+row.id+'" class="btn btn-default btn-xs">Tunnels</a>';
|
||||
}
|
||||
return html;
|
||||
}
|
||||
@@ -101,4 +101,4 @@ function delItem(id) {
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{/block}
|
||||
{/block}
|
||||
@@ -198,11 +198,7 @@ new Vue({
|
||||
}
|
||||
})
|
||||
this.set.config = JSON.stringify(this.config);
|
||||
this.set.name = this.resolveAccountName();
|
||||
if(!this.set.name){
|
||||
layer.alert('账户名称自动生成失败,请至少填写一个有效的认证字段', {icon: 2});
|
||||
return;
|
||||
}
|
||||
this.set.name = this.config[Object.keys(this.config)[0]];
|
||||
let loading = layer.msg('正在进行账户有效性检查', {icon: 16,shade: 0.1,time: 0});
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
@@ -225,29 +221,6 @@ new Vue({
|
||||
}
|
||||
});
|
||||
},
|
||||
resolveAccountName(){
|
||||
var preferred = [];
|
||||
if(this.set.type === 'cloudflare'){
|
||||
preferred = ['email', 'account_id', 'apikey'];
|
||||
}
|
||||
for(var i = 0; i < preferred.length; i++){
|
||||
var val = this.config[preferred[i]];
|
||||
if(typeof val === 'string' && this.trim(val)){
|
||||
return this.trim(val);
|
||||
}
|
||||
}
|
||||
var keys = Object.keys(this.config);
|
||||
for(var j = 0; j < keys.length; j++){
|
||||
var value = this.config[keys[j]];
|
||||
if(typeof value === 'string' && this.trim(value)){
|
||||
return this.trim(value);
|
||||
}
|
||||
if(typeof value === 'number'){
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
},
|
||||
isShow(show){
|
||||
if(typeof show == 'boolean' && show){
|
||||
return show;
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
<label class="col-sm-3 control-label">输入域名</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" name="adddomain" placeholder="输入要新增的域名" value="">
|
||||
<p class="help-block" id="addDomainHint" style="display:none;">腾讯云子域托管会先自动添加校验 TXT,再向父域自动补 NS 委派。父域必须已添加到系统并具备写入权限。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -294,10 +293,6 @@ $(document).ready(function(){
|
||||
title: '操作',
|
||||
formatter: function(value, row, index) {
|
||||
var html = '<a href="/record/'+row.id+'" class="btn btn-success btn-xs" onclick="loading()">解析</a>';
|
||||
var rowType = String(row.type || '').toLowerCase();
|
||||
if(userLevel == '2' && rowType === 'cloudflare'){
|
||||
html += ' <a href="/cloudflare/hostnames/'+row.id+'" class="btn btn-primary btn-xs">CF增强</a>';
|
||||
}
|
||||
if(userLevel == '2'){
|
||||
html += ' <a href="javascript:editframe('+row.id+')" class="btn btn-info btn-xs">配置</a>';
|
||||
html += ' <a href="javascript:delItem('+row.id+')" class="btn btn-danger btn-xs">删除</a>';
|
||||
@@ -321,7 +316,6 @@ $(document).ready(function(){
|
||||
$("#form-store input[name=method][value=0]").prop('checked', true);
|
||||
$("#domainSelect").show();
|
||||
$("#domainInput").hide();
|
||||
$("#addDomainHint").hide();
|
||||
var add = $(this).find('option:selected').data('add');
|
||||
if(add == '1'){
|
||||
$("#methodSelect").show();
|
||||
@@ -333,20 +327,13 @@ $(document).ready(function(){
|
||||
})
|
||||
$("#form-store input[name=method]").change(function(){
|
||||
var value = $("#form-store input[name=method]:checked").val();
|
||||
var type = String($("#form-store select[name=aid]").find('option:selected').data('type') || '');
|
||||
if(value == '0'){
|
||||
$("#domainSelect").show();
|
||||
$("#domainInput").hide();
|
||||
$("#addDomainHint").hide();
|
||||
getDomainList();
|
||||
}else{
|
||||
$("#domainSelect").hide();
|
||||
$("#domainInput").show();
|
||||
if(type === '腾讯云'){
|
||||
$("#addDomainHint").show();
|
||||
}else{
|
||||
$("#addDomainHint").hide();
|
||||
}
|
||||
$('#domainList').empty();
|
||||
}
|
||||
})
|
||||
|
||||
@@ -167,20 +167,7 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
|
||||
<div class="col-xs-12 center-block" style="float: none;">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="clearfix">
|
||||
<div class="pull-right" style="margin-top:-6px;max-width:100%;">
|
||||
<div style="display:inline-block;width:300px;max-width:100%;vertical-align:middle;margin-right:6px;">
|
||||
<select id="quickDomainSwitch" class="form-control">
|
||||
{volist name="quickDomainOptions" id="item"}
|
||||
<option value="{$item.id}"{if $item.id == $domainId} selected{/if}>{$item.text}</option>
|
||||
{/volist}
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-primary" id="quickDomainSwitchBtn" style="vertical-align:middle;margin-right:6px;" onclick="return quickSwitchDomain('/record/')"><i class="fa fa-random fa-fw"></i> 切换域名</button>
|
||||
{if $user['type'] eq 'user'}<a href="/domain" class="btn btn-sm btn-default" style="vertical-align:middle;"><i class="fa fa-reply fa-fw"></i> 返回</a>{/if}
|
||||
</div>
|
||||
<h3 class="panel-title" style="padding-top:4px;">{$domainName}</h3>
|
||||
</div>
|
||||
<h3 class="panel-title">{if request()->user['type'] eq 'user'}<a href="/domain" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回</a>{/if}{$domainName}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
@@ -196,7 +183,7 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
|
||||
<button type="submit" class="btn btn-primary"><i class="fa fa-search"></i> 搜索</button>
|
||||
<a href="javascript:searchClear()" class="btn btn-default" title="刷新解析记录列表"><i class="fa fa-refresh"></i> 刷新</a>
|
||||
<a href="javascript:addframe()" class="btn btn-success"><i class="fa fa-plus"></i> 添加记录</a>
|
||||
{if $dnsconfig.type=='cloudflare' && $user['level'] eq 2}<a href="/cloudflare/hostnames/{$domainId}" class="btn btn-primary"><i class="fa fa-cloud"></i> Cloudflare增强</a>{/if}
|
||||
{if $dnsconfig.type=='cloudflare' && $user['level'] eq 2}<a href="/cloudflare/hostnames/{$domainId}" class="btn btn-default">Cloudflare增强</a>{/if}
|
||||
{if $dnsconfig.type=='aliyun'}<a href="/record/weight/{$domainId}" class="btn btn-default">权重配置</a>{/if}
|
||||
{if $dnsconfig.type=='dnspod'}<a href="/record/alias/{$domainId}" class="btn btn-default">域名别名</a>{/if}
|
||||
<div class="btn-group" role="group">
|
||||
@@ -254,9 +241,7 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
|
||||
<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/select2-4.0.13.min.js"></script>
|
||||
<script src="/static/js/select2-i18n-zh-CN-4.0.13.min.js"></script>
|
||||
<script src="/static/js/custom.js?v=1005"></script>
|
||||
<script src="/static/js/custom.js?v=1003"></script>
|
||||
<script>
|
||||
var recordLine = {$recordLine|json_encode|raw};
|
||||
var dnsconfig = {$dnsconfig|json_encode|raw};
|
||||
@@ -264,16 +249,6 @@ var defaultLine = recordLine[0].id;
|
||||
var sidePagination = dnsconfig.page ? 'client' : 'server';
|
||||
var showWeight = dnsconfig.weight;
|
||||
$(document).ready(function(){
|
||||
if(typeof initDomainQuickSwitch === 'function'){
|
||||
initDomainQuickSwitch({
|
||||
buttonSelector: '',
|
||||
currentId: '{$domainId}',
|
||||
currentText: {$domainName|json_encode|raw},
|
||||
buildUrl: function(id){
|
||||
return '/record/' + id;
|
||||
}
|
||||
});
|
||||
}
|
||||
updateToolbar();
|
||||
let defaultPageSize = getCookie('record_pagesize') ? getCookie('record_pagesize') : 15;
|
||||
const pageNumber = typeof window.$_GET['pageNumber'] != 'undefined' ? parseInt(window.$_GET['pageNumber']) : 1;
|
||||
@@ -785,4 +760,4 @@ function htmlEscape(str) {
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
</script>
|
||||
{/block}
|
||||
{/block}
|
||||
@@ -56,110 +56,6 @@ function updateQueryStr(obj){
|
||||
history.replaceState({}, null, '?'+arr.join("&"));
|
||||
}
|
||||
|
||||
function initDomainQuickSwitch(options){
|
||||
if(typeof $ === 'undefined' || typeof $.fn.select2 === 'undefined'){
|
||||
return null;
|
||||
}
|
||||
var settings = $.extend({
|
||||
selectSelector: '#quickDomainSwitch',
|
||||
buttonSelector: '#quickDomainSwitchBtn',
|
||||
currentId: '',
|
||||
currentText: '',
|
||||
placeholder: '搜索域名后切换',
|
||||
useAjax: false,
|
||||
type: '',
|
||||
limit: 10,
|
||||
buildUrl: function(id){
|
||||
return '/record/' + id;
|
||||
}
|
||||
}, options || {});
|
||||
var $select = $(settings.selectSelector);
|
||||
if(!$select.length){
|
||||
return null;
|
||||
}
|
||||
if(settings.currentId !== '' && settings.currentText !== '' && $select.find('option[value="' + settings.currentId + '"]').length === 0){
|
||||
$select.append(new Option(settings.currentText, settings.currentId, true, true));
|
||||
}
|
||||
var select2Options = {
|
||||
width: '100%',
|
||||
language: 'zh-CN',
|
||||
placeholder: settings.placeholder
|
||||
};
|
||||
if(settings.useAjax){
|
||||
select2Options.ajax = {
|
||||
url: '/domain/data',
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
data: function(params) {
|
||||
var page = params.page || 1;
|
||||
return {
|
||||
kw: params.term || '',
|
||||
type: settings.type || '',
|
||||
offset: settings.limit * (page - 1),
|
||||
limit: settings.limit
|
||||
};
|
||||
},
|
||||
processResults: function(data, params) {
|
||||
params.page = params.page || 1;
|
||||
var rows = $.isArray(data.rows) ? data.rows : [];
|
||||
return {
|
||||
results: $.map(rows, function(item){
|
||||
var text = item.name || '';
|
||||
if(item.typename){
|
||||
text += ' [' + item.typename + ']';
|
||||
}
|
||||
return {
|
||||
id: item.id,
|
||||
text: text
|
||||
};
|
||||
}),
|
||||
pagination: {
|
||||
more: (data.total || 0) > settings.limit * params.page
|
||||
}
|
||||
};
|
||||
},
|
||||
cache: true
|
||||
};
|
||||
}
|
||||
$select.select2(select2Options);
|
||||
var navigate = function(){
|
||||
var targetId = $.trim($select.val());
|
||||
if(targetId === ''){
|
||||
if(typeof layer !== 'undefined'){
|
||||
layer.msg('请先选择域名');
|
||||
}else{
|
||||
alert('请先选择域名');
|
||||
}
|
||||
return;
|
||||
}
|
||||
window.location.href = settings.buildUrl(targetId);
|
||||
};
|
||||
if(settings.buttonSelector){
|
||||
$(settings.buttonSelector).off('click.domainSwitch').on('click.domainSwitch', navigate);
|
||||
}
|
||||
return {
|
||||
navigate: navigate
|
||||
};
|
||||
}
|
||||
|
||||
function quickSwitchDomain(basePath){
|
||||
if(typeof $ === 'undefined'){
|
||||
return false;
|
||||
}
|
||||
var targetId = $.trim($('#quickDomainSwitch').val());
|
||||
if(targetId === ''){
|
||||
if(typeof layer !== 'undefined'){
|
||||
layer.msg('请先选择域名');
|
||||
}else{
|
||||
alert('请先选择域名');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
window.location.href = String(basePath || '') + targetId;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof $.fn.bootstrapTable !== "undefined") {
|
||||
$.fn.bootstrapTable.custom = {
|
||||
method: 'post',
|
||||
@@ -275,4 +171,4 @@ function delCookie(name)
|
||||
if(cval!=null){
|
||||
document.cookie= name + "="+cval+";expires="+exp.toGMTString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user