dnsmgr/app/view/cert/certorder.html
2025-10-16 23:09:13 +08:00

472 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{extend name="common/layout" /}
{block name="title"}SSL证书订单列表{/block}
{block name="main"}
<style>
tbody tr>td:nth-child(5){overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:180px;}
.tips{cursor:pointer;}
textarea.form-control{margin-bottom: 3px;}
hr{margin-top: 10px;margin-bottom: 15px;border-top: 1px solid #eee;}
.modal-log{padding: 15px 15px 0 15px}
pre.pre-log{height: 330px;overflow-y: auto;width: 100%;background-color: rgba(51, 51, 51, 1);white-space: pre-line;color: rgba(236, 236, 236, 1)}
</style>
<div class="row">
<div class="col-xs-12 center-block" style="float: none;">
<div class="panel panel-default panel-intro">
<div class="panel-body">
<form onsubmit="return searchSubmit()" method="GET" class="form-inline" id="searchToolbar">
<input type="hidden" name="id" value="">
<input type="hidden" name="aid" value="">
<div class="form-group">
<label>搜索</label>
<div class="form-group">
<input type="text" class="form-control" name="domain" placeholder="域名">
</div>
</div>
<div class="form-group">
<select name="type" class="form-control"><option value="">所有平台</option>{foreach $types as $k=>$v}
<option value="{$k}">{$v}</option>
{/foreach}</select>
</div>
<div class="form-group">
<select name="status" class="form-control"><option value="">所有状态</option><option value="0">待提交</option><option value="1">待验证</option><option value="2">正在验证</option><option value="5">失败</option><option value="3">已签发</option><option value="4">已吊销</option><option value="6">即将过期</option><option value="7">已过期</option></select>
</div>
<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>
<div class="btn-group">
<a href="/cert/order/add" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>
</div>
<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="javascript:operation('delete')">删除</a></li><li><a href="javascript:operation('reset')">重置订单</a></li><li><a href="javascript:operation('open')">开启续签</a></li><li><a href="javascript:operation('close')">关闭续签</a></li></ul>
</div>
</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/FileSaver-2.0.5.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 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: '/cert/order/data',
pageNumber: pageNumber,
pageSize: pageSize,
classes: 'table table-striped table-hover table-bordered',
uniqueId: 'id',
columns: [
{
field: '',
checkbox: true
},
{
field: 'id',
title: 'ID'
},
{
field: 'typename',
title: '证书账户',
formatter: function(value, row, index) {
if(value){
return '<span title="'+row.aremark+'" data-toggle="tooltip" data-placement="right"><img src="/static/images/'+row.icon+'" class="type-logo">'+value+'('+row.aid+')</span>';
}
return '手动续期';
}
},
{
field: 'domains',
title: '绑定域名',
formatter: function(value, row, index) {
return value.join('<br/>');
}
},
{
field: 'keytype',
title: '证书信息',
formatter: function(value, row, index) {
return '<span class="text-muted">签名算法:</span>'+row.keytype+'('+row.keysize+')'+(row.issuer?'<br/><span class="text-muted">颁发机构:</span>'+row.issuer:'');
}
},
{
field: 'isauto',
title: '自动续签',
formatter: function(value, row, index) {
if(value == 1){
return '<div class="material-switch"><input id="isauto'+row.id+'" type="checkbox" checked onchange="setAuto('+row.id+',0)"/><label for="isauto'+row.id+'" class="label-primary"></label></div>';
}else{
return '<div class="material-switch"><input id="isauto'+row.id+'" type="checkbox" onchange="setAuto('+row.id+',1)"/><label for="isauto'+row.id+'" class="label-primary"></label></div>';
}
}
},
{
field: 'issuetime',
title: '签发时间',
formatter: function(value, row, index) {
return value ? value.substring(0,10) : '暂未签发';
}
},
{
field: 'end_day',
title: '到期时间',
formatter: function(value, row, index) {
if(value){
if(value > 7){
return '<span title="'+row.expiretime+'" data-toggle="tooltip" data-placement="right" style="color:green">剩余' + value + '天<span>';
}else if(value > 0){
return '<span title="'+row.expiretime+'" data-toggle="tooltip" data-placement="right" style="color:#ff7f00">剩余' + value + '天<span>';
}else{
return '<span title="'+row.expiretime+'" data-toggle="tooltip" data-placement="right" style="color:red">已过期<span>';
}
}else{
return '暂未签发';
}
}
},
{
field: 'status',
title: '状态',
formatter: function(value, row, index) {
if(value == 4) {
return '<span class="label" style="background-color: #a5a5a5;">已吊销</span>';
} else if(value == 3) {
return '<span class="label label-success">已签发</span>';
} else if(value == 2) {
if(row.retrytime != null){
var now = new Date().getTime();
var retry = new Date(row.retrytime).getTime();
var diff = retry - now;
if(diff > 0){
var min = Math.floor(diff / 60000);
var sec = Math.floor((diff - min * 60000) / 1000);
return '<span title="'+min+'分'+sec+'秒后自动验证" data-toggle="tooltip" data-placement="top" class="label" style="background-color: #3e76fb;">正在验证</span>';
}
}
return '<span class="label" style="background-color: #3e76fb;">正在验证</span>';
} else if(value == 1) {
if(row.retrytime != null){
var now = new Date().getTime();
var retry = new Date(row.retrytime).getTime();
var diff = retry - now;
if(diff > 0){
var min = Math.floor(diff / 60000);
var sec = Math.floor((diff - min * 60000) / 1000);
return '<span title="'+min+'分'+sec+'秒后自动验证" data-toggle="tooltip" data-placement="top" class="label" style="background-color: #3e76fb;">待验证</span>';
}
}
return '<span class="label" style="background-color: #3e76fb;">待验证</span>';
} else if(value == 0) {
return '<span class="label label-info">待提交</span>';
} else {
var title = '失败';
if(value == -1) title = '购买证书失败';
else if(value == -2) title = '创建订单失败';
else if(value == -3) title = '添加DNS失败';
else if(value == -4) title = '验证DNS失败';
else if(value == -5) title = '验证订单失败';
else if(value == -6) title = '订单验证未通过';
else if(value == -7) title = '签发证书失败';
if(row.retrytime != null){
var now = new Date().getTime();
var retry = new Date(row.retrytime).getTime();
var diff = retry - now;
if(diff > 0){
var min = Math.floor(diff / 60000);
var sec = Math.floor((diff - min * 60000) / 1000);
return '<span title="'+min+'分'+sec+'秒后自动重试" data-toggle="tooltip" data-placement="top" class="label label-danger">'+title+'</span>'+(row.error?' <span onclick="showmsg(\''+row.error+'\')" class="tips" title="失败原因"><i class="fa fa-info-circle"></i></span>':'');
}
}
return '<span class="label label-danger">'+title+'</span>'+(row.error?' <span onclick="showmsg(\''+row.error+'\')" class="tips" title="失败原因"><i class="fa fa-info-circle"></i></span>':'');
}
}
},
{
field: 'action',
title: '操作',
formatter: function(value, row, index) {
var html = '';
if(row.status == 0) {
html += '<a href="javascript:doOrder(\''+row.id+'\')" class="btn btn-success btn-xs"><i class="fa fa-play-circle"></i> 立即提交</a>&nbsp;&nbsp;';
}else if(row.status == 1) {
html += '<a href="javascript:doOrder(\''+row.id+'\')" class="btn btn-success btn-xs"><i class="fa fa-check-circle"></i> 提交验证</a>&nbsp;&nbsp;';
}else if(row.status == 2) {
html += '<a href="javascript:doOrder(\''+row.id+'\')" class="btn btn-success btn-xs"><i class="fa fa-check-circle"></i> 继续验证</a>&nbsp;&nbsp;';
}else if(row.status == 3) {
html += '<a href="javascript:download(\''+row.id+'\')" class="btn btn-success btn-xs"><i class="fa fa-download"></i> 下载</a>&nbsp;&nbsp;';
if(row.aid > 0){
html += '<a href="javascript:renewOrder(\''+row.id+'\')" class="btn btn-warning btn-xs"><i class="fa fa-refresh"></i> 续签</a>&nbsp;&nbsp;';
}
}else if(row.status == 4) {
html += '<a href="javascript:renewOrder(\''+row.id+'\')" class="btn btn-success btn-xs"><i class="fa fa-play-circle"></i> 重新申请</a>&nbsp;&nbsp;';
}else{
html += '<a href="javascript:doOrder(\''+row.id+'\')" class="btn btn-success btn-xs"><i class="fa fa-repeat"></i> 重试</a>&nbsp;&nbsp;';
}
html += '<a href="/cert/order/edit?id='+row.id+'" class="btn btn-primary btn-xs"><i class="fa fa-edit"></i> 修改</a>&nbsp;&nbsp;';
html += '<div class="btn-group dropdown-group" role="group"><button type="button" class="btn btn-info btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">更多 <span class="caret"></span></button><ul class="dropdown-menu">';
html += '<li><a href="javascript:showLog(\''+row.processid+'\')">查看日志</a></li>';
if(row.status == 3){
html += '<li><a href="/cert/deploytask?oid='+row.id+'">部署任务</a></li>';
if(row.aid > 0){
html += '<li><a href="javascript:revokeOrder(\''+row.id+'\')">吊销证书</a></li>';
}
}else if(row.status < 0){
html += '<li><a href="javascript:resetOrder(\''+row.id+'\')">重置订单</a></li>';
}else if(row.status == 1 || row.status == 2){
html += '<li><a href="javascript:resetOrder(\''+row.id+'\')">取消订单</a></li>';
}
html += '<li><a href="javascript:delItem('+row.id+','+row.status+')">删除</a></li>';
html += '</ul></div>';
return html;
}
},
],
onLoadSuccess: function(data) {
$('[data-toggle="tooltip"]').tooltip()
$('.dropdown-group').on('show.bs.dropdown', function (e) {
var btnPos = $(e.target)[0].getBoundingClientRect();
var screenWidth = $(window).width();
var screenHeight = $(window).height();
var childrenWidth = $(e.target).children('.dropdown-menu').width();
var childrenHeight = $(e.target).children('.dropdown-menu').height();
var top = btnPos.bottom;
if(top + childrenHeight + 12 > screenHeight){
top = btnPos.top - childrenHeight - 12;
}
var left = btnPos.left;
if(left + childrenWidth + 7 > screenWidth){
left = screenWidth - childrenWidth - 7;
}
$(e.target).children('.dropdown-menu').css({position:'fixed', top:top, left:left});
});
}
})
})
function showmsg(msg){
layer.alert(msg, {icon: 0, title: '失败原因'});
}
function setAuto(id, status){
$.post('/cert/order/setauto', {id: id, isauto: status}, function(data){
if(data.code == 0) {
layer.msg('已'+(status==1?'开启':'关闭')+'自动续签', {icon: 1, time:800});
$('#listTable').bootstrapTable('refresh');
} else {
layer.msg(data.msg, {icon: 2});
}
}, 'json');
}
function delItem(id,status){
var msg = '确定要删除此证书订单吗?';
if(status == 3) msg += '删除后将无法再次下载证书';
layer.confirm(msg, {
btn: ['确定','取消']
}, function(){
$.post('/cert/order/del', {id: id}, function(data){
if(data.code == 0) {
layer.msg('删除成功', {icon: 1, time:800});
$('#listTable').bootstrapTable('refresh');
} else {
layer.msg(data.msg, {icon: 2});
}
}, 'json');
});
}
function doOrder(id, reset){
reset = reset || 0;
var ii = layer.msg('正在处理证书订单...', {icon: 16,shade: 0.1,time: 0});
$.ajax({
type: "POST",
url: "/cert/order/process",
data: {id: id, reset: reset},
dataType: 'json',
success: function(data) {
layer.close(ii);
if(data.code == 0) {
layer.alert(data.msg, {icon: 1});
$('#listTable').bootstrapTable('refresh');
} else {
if(data.msg == '订单正在处理中,请稍后再试'){
layer.alert(data.msg, {icon: 2}, function(){
layer.closeAll();
var row = $("#listTable").bootstrapTable('getRowByUniqueId', id);
showLog(row.processid)
});
}else{
layer.alert(data.msg, {icon: 2});
$('#listTable').bootstrapTable('refresh');
}
}
},
error: function(data){
layer.close(ii);
layer.msg('执行超时,请稍后刷新列表或查看日志', {icon:0});
}
});
}
function resetOrder(id){
layer.confirm('重置订单后,订单将变成待提交状态,是否确定?', {
btn: ['确定','取消'], title: '重置订单', icon: 0
}, function(){
$.post('/cert/order/reset', {id: id}, function(data){
if(data.code == 0) {
layer.msg('重置订单状态成功', {icon: 1, time:800});
$('#listTable').bootstrapTable('refresh');
} else {
layer.msg(data.msg, {icon: 2});
}
}, 'json');
});
}
function revokeOrder(id){
layer.confirm('是否确定要吊销该证书?吊销后浏览器将不再信任该证书', {
btn: ['确定','取消'], title: '吊销证书', icon: 0
}, function(){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.post('/cert/order/revoke', {id: id}, function(data){
layer.close(ii);
if(data.code == 0) {
layer.alert('吊销证书成功!', {icon: 1});
$('#listTable').bootstrapTable('refresh');
} else {
layer.msg(data.msg, {icon: 2});
}
}, 'json');
});
}
function renewOrder(id){
layer.confirm('是否确定重新申请该证书?', {
btn: ['确定','取消'], title: '续签证书', icon: 0
}, function(){
doOrder(id, 1);
});
}
function download(id){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.post('/cert/order/get', {id: id}, function(data){
layer.close(ii);
if(data.code == 0) {
layer.open({
type: 1,
title: '下载证书',
area: [$(window).width() > 768 ? '600px' : '100%', '360px'],
shadeClose: true,
content: '<div class="modal-body"><div class="row text-center"><div class="form-group col-xs-6"><label>证书(PEM格式)</label><textarea rows="6" class="form-control" name="fullchain">'+data.data.fullchain+'</textarea><a onclick="copy(\'fullchain\')" class="btn btn-default"><i class="fa fa-copy"></i> 复制</a>&nbsp;&nbsp;<a onclick="downloadFile(\'fullchain.crt\',\'fullchain\')" class="btn btn-default"><i class="fa fa-download"></i> 下载</a></div><div class="form-group col-xs-6"><label>私钥(PEM格式)</label><textarea rows="6" class="form-control" name="privatekey">'+data.data.privatekey+'</textarea><a onclick="copy(\'privatekey\')" class="btn btn-default"><i class="fa fa-copy"></i> 复制</a>&nbsp;&nbsp;<a onclick="downloadFile(\'private.key\',\'privatekey\')" class="btn btn-default"><i class="fa fa-download"></i> 下载</a></div></div><hr/><label>IIS服务器(pfx格式)</label><input type="hidden" name="pfx" value="'+data.data.pfx+'"><a onclick="downloadPFX()" class="btn btn-default"><i class="fa fa-download"></i> 下载</a>密码为123456</div>',
});
} else {
layer.alert(data.msg, {icon: 2});
}
}, 'json');
}
function copy(obj){
$("textarea[name='"+obj+"']").select();
document.execCommand("Copy");
layer.msg('复制成功', {icon:1, time:600});
}
function downloadFile(filename,obj){
var content = $("textarea[name='"+obj+"']").val();
if(!content) return;
var blob = new Blob([content], {type: "application/force-download"});
saveAs(blob, filename);
}
function downloadPFX(id){
var content = $("input[name='pfx']").val();
if(!content) return;
var bstr = atob(content),
n = bstr.length,
u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
var filename = 'cert.pfx';
var blob = new Blob([u8arr], {type: "application/force-download"});
saveAs(blob, filename);
}
var intverval;
function showLog(processid){
if(processid == '' || processid == 'null'){
layer.msg('暂无日志', {time: 600});
return;
}
$.post('/cert/order/show_log', {processid: processid}, function(data){
if(data.code == 0) {
layer.closeAll();
var filemtime = data.time;
layer.open({
type: 1,
title: '查看日志',
area: [$(window).width() > 768 ? '600px' : '100%', '400px'],
shadeClose: true,
resize: false,
content: '<div class="modal-log"><pre class="pre-log" id="execLog">'+data.data+'</pre></div>',
success: function(){
var exec_log = $('#execLog');
exec_log[0].scrollTop = exec_log[0].scrollHeight
intverval = setInterval(function(){
$.post('/cert/order/show_log', {processid: processid}, function(data){
if(data.code == 0 && data.time != filemtime) {
var exec_log = $('#execLog');
exec_log.html(data.data);
filemtime = data.time;
exec_log[0].scrollTop = exec_log[0].scrollHeight
}
}, 'json');
}, 1500)
},
end: function(){
clearInterval(intverval);
}
});
} else {
layer.msg(data.msg, {icon: 2, time: 600});
}
}, 'json');
}
function operation(action){
var rows = $("#listTable").bootstrapTable('getSelections');
if(rows.length == 0){
layer.msg('请选择要操作的订单');
return;
}
var ids = [];
for(var i in rows){
ids.push(rows[i].id);
}
if(action == 'delete'){
if(!confirm('确定要删除所选证书吗?删除后将无法再次下载')) return;
}else if(action == 'reset'){
if(!confirm('重置订单后,订单将变成待提交状态,是否确定重置?')) return;
}
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/cert/order/operation',
data : {act: action, ids: ids},
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.closeAll();
layer.alert(data.msg, {icon: 1});
searchRefresh();
}else{
layer.alert(data.msg, {icon: 2});
}
}
});
}
</script>
{/block}