Files
dnsmgr/app/view/cloudflare/hostnames.html
T
luo-bo 1b1605400d ```
feat(cloudflare): 添加 Cloudflare Tunnels 和增强功能支持

- 在 .gitignore 中添加 .ace-tool/ 忽略规则
- 更新 Cloudflare 配置项,添加详细的使用说明和 API 令牌认证支持
- 新增 Account ID 配置字段用于 Cloudflare Tunnels 功能
- 在账户管理页面添加 Tunnels 功能入口按钮
- 实现智能账户名称自动生成逻辑,优先使用关键认证字段
- 添加 Cloudflare 增强功能菜单项,仅对管理员可见
- 定义完整的 Cloudflare 相关路由,包括 hostnames、tunnels 等功能模块
```
2026-03-24 00:21:03 +08:00

266 lines
8.8 KiB
HTML

{extend name="common/layout" /}
{block name="title"}Cloudflare增强 - {$domainName}{/block}
{block name="main"}
<div class="row">
<div class="col-xs-12 center-block" style="float:none;">
<div class="panel panel-default panel-intro">
<div class="panel-heading">
<h3 class="panel-title">
<a href="/record/{$domainId}" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回解析</a>
Cloudflare增强 - {$domainName}
</h3>
</div>
<div class="panel-body">
<div class="alert alert-info">
<strong>说明:</strong> 这里管理 Cloudflare 自定义主机名、证书状态与 Fallback Origin。
</div>
<div class="well well-sm">
<div class="form-inline">
<div class="form-group" style="width:70%;max-width:720px;">
<label>Fallback Origin</label>
<input type="text" id="fallbackOrigin" class="form-control" style="width:80%;" placeholder="例如 origin.example.com">
</div>
<button type="button" class="btn btn-primary" onclick="saveFallbackOrigin()">保存</button>
<button type="button" class="btn btn-default" onclick="loadFallbackOrigin()">刷新</button>
<button type="button" class="btn btn-danger" onclick="clearFallbackOrigin()">清空</button>
</div>
</div>
<form onsubmit="return searchSubmit()" method="GET" class="form-inline" id="searchToolbar">
<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:openAddDialog()" class="btn btn-success"><i class="fa fa-plus"></i> 添加自定义主机名</a>
</form>
<table id="listTable"></table>
</div>
</div>
</div>
</div>
<div class="modal" id="modal-store" role="dialog" 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>&times;</span></button>
<h4 class="modal-title">添加自定义主机名</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" id="form-store">
<div class="form-group">
<label class="col-sm-3 control-label">主机名</label>
<div class="col-sm-9">
<input type="text" class="form-control" name="hostname" placeholder="例如 app.example.com 或 *.example.com" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">自定义源站</label>
<div class="col-sm-9">
<input type="text" class="form-control" name="custom_origin_server" placeholder="可留空,例如 origin.example.com">
</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" onclick="submitHostname()">保存</button>
</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"></script>
<script>
$(document).ready(function(){
updateToolbar();
$("#form-store").bootstrapValidator();
loadFallbackOrigin();
$("#listTable").bootstrapTable({
url: '/cloudflare/hostnames/data/{$domainId}',
method: 'post',
classes: 'table table-striped table-hover table-bordered',
uniqueId: 'id',
responseHandler: function(res){
if(res.code !== 0){
layer.alert(res.msg || '获取自定义主机名失败', {icon: 2});
return {total: 0, rows: []};
}
return res;
},
columns: [
{field: 'hostname', title: '主机名'},
{field: 'custom_origin_server', title: '自定义源站', formatter: function(v){ return v || '-'; }},
{field: 'ssl_status', title: '证书状态', formatter: formatStatus},
{field: 'verification_status', title: '验证状态', formatter: function(v){ return v || '-'; }},
{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:deleteHostname(\''+row.id+'\', \''+htmlEscape(row.hostname)+'\')" class="btn btn-danger btn-xs">删除</a>';
}
}
]
});
});
function formatStatus(value){
var v = (value || '').toLowerCase();
if(v === 'active' || v === 'active_deployed'){
return '<span class="label label-success">'+htmlEscape(value)+'</span>';
}
if(v === 'pending' || v === 'pending_validation' || v === 'initializing'){
return '<span class="label label-warning">'+htmlEscape(value || '-')+'</span>';
}
if(v){
return '<span class="label label-danger">'+htmlEscape(value)+'</span>';
}
return '-';
}
function openAddDialog(){
$("#form-store")[0].reset();
$("#form-store").data("bootstrapValidator").resetForm();
$("#modal-store").modal('show');
}
function submitHostname(){
$("#form-store").data("bootstrapValidator").validate();
if(!$("#form-store").data("bootstrapValidator").isValid()){
return;
}
var ii = layer.load(2);
$.ajax({
type: 'POST',
url: '/cloudflare/hostnames/add/{$domainId}',
data: $("#form-store").serialize(),
dataType: 'json',
success: function(res){
layer.close(ii);
if(res.code === 0){
$("#modal-store").modal('hide');
layer.msg(res.msg, {icon: 1, time: 1200});
searchRefresh();
}else{
layer.alert(res.msg, {icon: 2});
}
},
error: function(){
layer.close(ii);
layer.alert('服务器错误', {icon: 2});
}
});
}
function deleteHostname(id, hostname){
layer.confirm('确定要删除自定义主机名 '+hostname+' 吗?', {title: '提示', icon: 0}, function(){
var ii = layer.load(2);
$.ajax({
type: 'POST',
url: '/cloudflare/hostnames/delete/{$domainId}',
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});
searchRefresh();
}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/{$domainId}',
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/{$domainId}',
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/{$domainId}',
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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
</script>
{/block}