feat: sync httpdns sdk/platform updates without large binaries

This commit is contained in:
robin
2026-03-04 17:59:14 +08:00
parent 853897a6f8
commit 532891fad0
700 changed files with 6096 additions and 2712 deletions

View File

@@ -22046,15 +22046,16 @@ Vue.component("ssl-certs-box", {
<span class="grey" v-if="description.length > 0">{{description}}</span>
<div class="ui divider" v-if="buttonsVisible()"></div>
</div>
<div v-if="buttonsVisible()">
<div v-if="buttonsVisible()" style="display:flex;align-items:center;gap:0.5rem;flex-wrap:nowrap;white-space:nowrap;">
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button> &nbsp;
<span class="disabled">|</span> &nbsp;
<span class="disabled" style="margin:0 0.5rem;">|</span>
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传新证书</button> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="uploadBatch()">批量上传证书</button> &nbsp;
</div>
</div>`
})
Vue.component("ssl-certs-view", {
props: ["v-certs"],
data: function () {
@@ -22518,12 +22519,16 @@ Vue.component("ssl-config-box", {
<span class="red">选择或上传证书后<span v-if="vProtocol == 'https'">HTTPS</span><span v-if="vProtocol == 'tls'">TLS</span>服务才能生效。</span>
<div class="ui divider"></div>
</div>
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button> &nbsp;
<span class="disabled">|</span> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传新证书</button> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="uploadBatch()">批量上传证书</button> &nbsp;
<span class="disabled">|</span> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="requestCert()" v-if="vServerId != null && vServerId > 0">申请免费证书</button>
<div style="display: flex; align-items: center; gap: 0.5rem; flex-wrap: nowrap; white-space: nowrap;">
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button>
<span class="disabled" style="margin: 0 0.5rem;">|</span>
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传证书</button>
<button class="ui button tiny" type="button" @click.prevent="uploadBatch()">批量上传证书</button>
<template v-if="vServerId != null && vServerId > 0">
<span class="disabled" style="margin: 0 0.5rem;">|</span>
<button class="ui button tiny" type="button" @click.prevent="requestCert()">申请免费证书</button>
</template>
</div>
</td>
</tr>
<tr>

View File

@@ -22046,15 +22046,16 @@ Vue.component("ssl-certs-box", {
<span class="grey" v-if="description.length > 0">{{description}}</span>
<div class="ui divider" v-if="buttonsVisible()"></div>
</div>
<div v-if="buttonsVisible()">
<div v-if="buttonsVisible()" style="display:flex;align-items:center;gap:0.5rem;flex-wrap:nowrap;white-space:nowrap;">
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button> &nbsp;
<span class="disabled">|</span> &nbsp;
<span class="disabled" style="margin:0 0.5rem;">|</span>
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传新证书</button> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="uploadBatch()">批量上传证书</button> &nbsp;
</div>
</div>`
})
Vue.component("ssl-certs-view", {
props: ["v-certs"],
data: function () {
@@ -22518,12 +22519,16 @@ Vue.component("ssl-config-box", {
<span class="red">选择或上传证书后<span v-if="vProtocol == 'https'">HTTPS</span><span v-if="vProtocol == 'tls'">TLS</span>服务才能生效。</span>
<div class="ui divider"></div>
</div>
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button> &nbsp;
<span class="disabled">|</span> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传新证书</button> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="uploadBatch()">批量上传证书</button> &nbsp;
<span class="disabled">|</span> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="requestCert()" v-if="vServerId != null && vServerId > 0">申请免费证书</button>
<div style="display: flex; align-items: center; gap: 0.5rem; flex-wrap: nowrap; white-space: nowrap;">
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button>
<span class="disabled" style="margin: 0 0.5rem;">|</span>
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传证书</button>
<button class="ui button tiny" type="button" @click.prevent="uploadBatch()">批量上传证书</button>
<template v-if="vServerId != null && vServerId > 0">
<span class="disabled" style="margin: 0 0.5rem;">|</span>
<button class="ui button tiny" type="button" @click.prevent="requestCert()">申请免费证书</button>
</template>
</div>
</td>
</tr>
<tr>

View File

@@ -160,11 +160,11 @@ Vue.component("ssl-certs-box", {
<span class="grey" v-if="description.length > 0">{{description}}</span>
<div class="ui divider" v-if="buttonsVisible()"></div>
</div>
<div v-if="buttonsVisible()">
<div v-if="buttonsVisible()" style="display:flex;align-items:center;gap:0.5rem;flex-wrap:nowrap;white-space:nowrap;">
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button> &nbsp;
<span class="disabled">|</span> &nbsp;
<span class="disabled" style="margin:0 0.5rem;">|</span>
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传新证书</button> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="uploadBatch()">批量上传证书</button> &nbsp;
</div>
</div>`
})
})

View File

@@ -427,12 +427,16 @@ Vue.component("ssl-config-box", {
<span class="red">选择或上传证书后<span v-if="vProtocol == 'https'">HTTPS</span><span v-if="vProtocol == 'tls'">TLS</span>服务才能生效。</span>
<div class="ui divider"></div>
</div>
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button> &nbsp;
<span class="disabled">|</span> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传新证书</button> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="uploadBatch()">批量上传证书</button> &nbsp;
<span class="disabled">|</span> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="requestCert()" v-if="vServerId != null && vServerId > 0">申请免费证书</button>
<div style="display: flex; align-items: center; gap: 0.5rem; flex-wrap: nowrap; white-space: nowrap;">
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button>
<span class="disabled" style="margin: 0 0.5rem;">|</span>
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传证书</button>
<button class="ui button tiny" type="button" @click.prevent="uploadBatch()">批量上传证书</button>
<template v-if="vServerId != null && vServerId > 0">
<span class="disabled" style="margin: 0 0.5rem;">|</span>
<button class="ui button tiny" type="button" @click.prevent="requestCert()">申请免费证书</button>
</template>
</div>
</td>
</tr>
<tr>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,16 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<title>{$ htmlEncode .teaTitle}</title>
<meta charset="UTF-8"/>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
{$if eq .teaFaviconFileId 0}
<link rel="shortcut icon" href="/images/favicon.png"/>
<link rel="shortcut icon" href="/images/favicon.png" />
{$else}
<link rel="shortcut icon" href="/ui/image/{$.teaFaviconFileId}"/>
<link rel="shortcut icon" href="/ui/image/{$.teaFaviconFileId}" />
{$end}
<link rel="stylesheet" type="text/css" href="/_/@default/@layout.css" media="all"/>
<link rel="stylesheet" type="text/css" href="/_/@default/@layout.css" media="all" />
{$TEA.SEMANTIC}
{$TEA.VUE}
@@ -20,7 +21,7 @@
window.BRAND_DOCS_SITE = {$ jsonEncode .brandConfig.docsSite};
window.BRAND_DOCS_PREFIX = {$ jsonEncode .brandConfig.docsPathPrefix};
window.BRAND_PRODUCT_NAME = {$ jsonEncode .brandConfig.productName};
// 确保 teaName 和 teaVersion 在 Vue 初始化前可用
if (typeof window.TEA === "undefined") {
window.TEA = {};
@@ -36,122 +37,152 @@
</script>
<script type="text/javascript" src="/js/config/brand.js"></script>
<script type="text/javascript" src="/_/@default/@layout.js"></script>
<script type="text/javascript" src="/js/components.js"></script>
<script type="text/javascript" src="/js/utils.min.js"></script>
<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js" async></script>
<script type="text/javascript" src="/js/date.tea.js"></script>
<script type="text/javascript" src="/js/components.js"></script>
<script type="text/javascript" src="/js/utils.min.js"></script>
<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js" async></script>
<script type="text/javascript" src="/js/date.tea.js"></script>
<script type="text/javascript" src="/js/langs/base.js?v={$ .teaVersion}"></script>
<script type="text/javascript" src="/js/langs/{$.teaLang}.js?v={$ .teaVersion}"></script>
<link rel="stylesheet" type="text/css" href="/js/langs/{$.teaLang}.css?v={$ .teaVersion}"/>
<link rel="stylesheet" type="text/css" href="/js/langs/{$.teaLang}.css?v={$ .teaVersion}" />
<link rel="stylesheet" type="text/css" href="/_/@default/@layout_override.css" media="all"/>
<link rel="stylesheet" type="text/css" href="/_/@default/@layout_override.css" media="all" />
</head>
<body>
<div>
<!-- 顶部导航 -->
<div class="ui menu top-nav blue inverted small borderless" :class="(teaTheme == null || teaTheme.length == 0) ? 'theme2': teaTheme" v-cloak="">
<a href="/" class="item">
<i class="ui icon leaf" v-if="teaLogoFileId == 0"></i><img alt="logo" v-if="teaLogoFileId > 0" :src="'/ui/image/' + teaLogoFileId" style="width: auto;height: 1.6em"/> &nbsp; {{teaTitle}}&nbsp;<sup v-if="teaShowVersion">v{{teaVersion}}<span v-if="teaVersion.split('.').length == 4" title="当前版本为测试版,再次感谢您参与测试"> beta</span></sup> &nbsp;
</a>
<div class="right menu">
<!-- 集群同步 -->
<a href="" class="item" v-if="teaCheckNodeTasks && doingNodeTasks.isUpdated" @click.prevent="showNodeTasks()">
<span v-if="!doingNodeTasks.isDoing && !doingNodeTasks.hasError" class="hover-span"><i class="icon cloud disabled"></i><span class="disabled">已同步节点</span></span>
<span v-if="doingNodeTasks.isDoing && !doingNodeTasks.hasError" class="hover-span rotate"><i class="icon cloud"></i><span>正在同步节点...</span></span>
<span v-if="doingNodeTasks.hasError" class="red"><i class="icon cloud"></i>节点同步失败</span>
<div>
<!-- 顶部导航 -->
<div class="ui menu top-nav blue inverted small borderless"
:class="(teaTheme == null || teaTheme.length == 0) ? 'theme2': teaTheme" v-cloak="">
<a href="/" class="item">
<i class="ui icon leaf" v-if="teaLogoFileId == 0"></i><img alt="logo" v-if="teaLogoFileId > 0"
:src="'/ui/image/' + teaLogoFileId" style="width: auto;height: 1.6em" /> &nbsp;
{{teaTitle}}&nbsp;<sup v-if="teaShowVersion">v{{teaVersion}}<span
v-if="teaVersion.split('.').length == 4" title="当前版本为测试版,再次感谢您参与测试"> beta</span></sup> &nbsp;
</a>
<!-- DNS同步 -->
<a href="" class="item" v-if="teaCheckDNSTasks && doingDNSTasks.isUpdated" @click.prevent="showDNSTasks()">
<span v-if="!doingDNSTasks.isDoing && !doingDNSTasks.hasError" class="hover-span"><i class="icon globe disabled"></i><span class="disabled">已同步DNS</span></span>
<span v-if="doingDNSTasks.isDoing && !doingDNSTasks.hasError" class="hover-span rotate"><i class="icon globe"></i><span>正在同步DNS...</span></span>
<span v-if="doingDNSTasks.hasError" class="red"><i class="icon globe"></i>DNS同步失败</span>
</a>
<div class="right menu">
<!-- 集群同步 -->
<a href="" class="item" v-if="teaCheckNodeTasks && doingNodeTasks.isUpdated"
@click.prevent="showNodeTasks()">
<span v-if="!doingNodeTasks.isDoing && !doingNodeTasks.hasError" class="hover-span"><i
class="icon cloud disabled"></i><span class="disabled">已同步节点</span></span>
<span v-if="doingNodeTasks.isDoing && !doingNodeTasks.hasError" class="hover-span rotate"><i
class="icon cloud"></i><span>正在同步节点...</span></span>
<span v-if="doingNodeTasks.hasError" class="red"><i class="icon cloud"></i>节点同步失败</span>
</a>
<!-- 消息 -->
<a href="" class="item" :class="{active:teaMenu == 'message'}" @click.prevent="showMessages()">
<span v-if="globalMessageBadge > 0" class="blink hover-span"><i class="icon bell"></i><span>消息({{globalMessageBadge}}) </span></span>
<span v-if="globalMessageBadge == 0" class="hover-span"><i class="icon bell disabled"></i><span class="disabled">消息(0)</span></span>
</a>
<!-- DNS同步 -->
<a href="" class="item" v-if="teaCheckDNSTasks && doingDNSTasks.isUpdated"
@click.prevent="showDNSTasks()">
<span v-if="!doingDNSTasks.isDoing && !doingDNSTasks.hasError" class="hover-span"><i
class="icon globe disabled"></i><span class="disabled">已同步DNS</span></span>
<span v-if="doingDNSTasks.isDoing && !doingDNSTasks.hasError" class="hover-span rotate"><i
class="icon globe"></i><span>正在同步DNS...</span></span>
<span v-if="doingDNSTasks.hasError" class="red"><i class="icon globe"></i>DNS同步失败</span>
</a>
<!-- 用户信-->
<a href="/settings/profile" class="item">
<i class="icon user" v-if="teaUserAvatar.length == 0"></i>
<img class="avatar" alt="" :src="teaUserAvatar" v-if="teaUserAvatar.length > 0"/>
<span class="hover-span"><span class="disabled">{{teaUsername}}</span></span>
</a>
<!-- -->
<a href="" class="item" :class="{active:teaMenu == 'message'}" @click.prevent="showMessages()">
<span v-if="globalMessageBadge > 0" class="blink hover-span"><i
class="icon bell"></i><span>消息({{globalMessageBadge}}) </span></span>
<span v-if="globalMessageBadge == 0" class="hover-span"><i class="icon bell disabled"></i><span
class="disabled">消息(0)</span></span>
</a>
<a href="" class="item" title="switch language" @click.prevent="switchLang" v-show="false"><i class="icon language"></i> </a>
<!-- 用户信息 -->
<a href="/settings/profile" class="item">
<i class="icon user" v-if="teaUserAvatar.length == 0"></i>
<img class="avatar" alt="" :src="teaUserAvatar" v-if="teaUserAvatar.length > 0" />
<span class="hover-span"><span class="disabled">{{teaUsername}}</span></span>
</a>
<!-- 背景颜色 -->
<a href="" class="item" title="点击切换界面风格" @click.prevent="changeTheme()" v-if="false"><i class="icon adjust"></i></a>
<a href="" class="item" title="switch language" @click.prevent="switchLang" v-show="false"><i
class="icon language"></i> </a>
<!-- 企业版 -->
<!-- <a :href="'/settings/authority'" class="item" title="商业版" :v-if="teaIsPlus"><i class="icon gem outline yellow"></i></a>-->
<!-- 背景颜色 -->
<a href="" class="item" title="点击切换界面风格" @click.prevent="changeTheme()" v-if="false"><i
class="icon adjust"></i></a>
<!-- 退出登录 -->
<a :href="Tea.url('logout')" class="item" title="安全退出登录"><i class="icon sign out"></i>
<span class="hover-span"><span class="disabled">退出登录</span></span>
</a>
<!-- 企业版 -->
<!-- <a :href="'/settings/authority'" class="item" title="商业版" :v-if="teaIsPlus"><i class="icon gem outline yellow"></i></a>-->
<!-- 退出登录 -->
<a :href="Tea.url('logout')" class="item" title="安全退出登录"><i class="icon sign out"></i>
<span class="hover-span"><span class="disabled">退出登录</span></span>
</a>
</div>
</div>
</div>
<!-- 左侧主菜单 -->
<div class="main-menu" :class="(teaTheme == null || teaTheme.length == 0) ? 'theme2': teaTheme" v-cloak="">
<div class="ui labeled menu vertical blue inverted tiny borderless">
<div class="item"></div>
<!--<a :href="Tea.url('dashboard')" class="item" :class="{active:teaMenu == 'dashboard'}">
<!-- 左侧主菜单 -->
<div class="main-menu" :class="(teaTheme == null || teaTheme.length == 0) ? 'theme2': teaTheme" v-cloak="">
<div class="ui labeled menu vertical blue inverted tiny borderless">
<div class="item"></div>
<!--<a :href="Tea.url('dashboard')" class="item" :class="{active:teaMenu == 'dashboard'}">
<i class="ui dashboard icon"></i>
<span>仪表板</span>
</a>-->
<!-- 模块 -->
<div v-for="module in teaModules">
<a class="item" :href="Tea.url(module.code)" :class="{active:teaMenu == module.code && teaSubMenu.length == 0, separator:module.code.length == 0, expend: teaMenu == module.code}" :style="(teaMenu == module.code && teaSubMenu.length == 0) ? 'background: rgba(230, 230, 230, 0.45) !important;' : ''" v-if="module.isOn !== false">
<span v-if="module.code.length > 0">
<i class="window restore outline icon" v-if="module.icon == null"></i>
<i class="ui icon" v-if="module.icon != null" :class="module.icon"></i>
<span class="module-name">{{module.name}}</span>
</span>
<div class="subtitle" v-if="module.subtitle != null && module.subtitle.length > 0">{{module.subtitle}}</div>
</a>
<div v-if="teaMenu == module.code" class="sub-items">
<a class="item" :class="{separator:subItem.name == '-', active: subItem.code == teaSubMenu}" v-for="subItem in module.subItems" v-if="subItem.isOn !== false" :href="subItem.url" :style="(subItem.code == teaSubMenu) ? 'background: rgba(230, 230, 230, 0.55) !important;' : ''"><i class="icon angle right" v-if="subItem.name != '-' && subItem.code == teaSubMenu"></i> <span v-if="subItem.name != '-'">{{subItem.name}}</span>
<span class="ui label tiny red" v-if="subItem.badge != null && subItem.badge > 0">
<span v-if="subItem.badge < 100">{{subItem.badge}}</span>
<span v-else>99+</span>
<!-- 模块 -->
<div v-for="module in teaModules">
<a class="item" :href="Tea.url(module.code)"
:class="{active:teaMenu == module.code && teaSubMenu.length == 0, separator:module.code.length == 0, expend: teaMenu == module.code}"
:style="(teaMenu == module.code && teaSubMenu.length == 0) ? 'background: rgba(230, 230, 230, 0.45) !important;' : ''"
v-if="module.isOn !== false">
<span v-if="module.code.length > 0">
<i class="window restore outline icon" v-if="module.icon == null"></i>
<i class="ui icon" v-if="module.icon != null" :class="module.icon"></i>
<span class="module-name">{{module.name}}</span>
</span>
<div class="subtitle" v-if="module.subtitle != null && module.subtitle.length > 0">
{{module.subtitle}}</div>
</a>
</div>
</div>
<div v-if="teaMenu == module.code" class="sub-items">
<a class="item" :class="{separator:subItem.name == '-', active: subItem.code == teaSubMenu}"
v-for="subItem in module.subItems" v-if="subItem.isOn !== false" :href="subItem.url"
:style="(subItem.code == teaSubMenu) ? 'background: rgba(230, 230, 230, 0.55) !important;' : ''"><i
class="icon angle right" v-if="subItem.name != '-' && subItem.code == teaSubMenu"></i>
<span v-if="subItem.name != '-'">{{subItem.name}}</span>
<span class="ui label tiny red" v-if="subItem.badge != null && subItem.badge > 0">
<span v-if="subItem.badge < 100">{{subItem.badge}}</span>
<span v-else>99+</span>
</span>
</a>
</div>
</div>
</div>
</div>
<!-- 右侧主操作栏 -->
<div class="main"
:class="{'without-menu':teaSubMenus.menus == null || teaSubMenus.menus.length == 0 || (teaSubMenus.menus.length == 1 && teaSubMenus.menus[0].alwaysActive), 'without-secondary-menu':teaSubMenus.alwaysMenu == null || teaSubMenus.alwaysMenu.items.length <= 1, 'without-footer':!teaShowOpenSourceInfo}"
v-cloak="">
<!-- 操作菜单 -->
<div class="ui top menu tabular tab-menu small" v-if="teaTabbar.length > 1">
<a class="item" v-for="item in teaTabbar"
:class="{'active':item.isActive && !item.isDisabled, right:item.isRight, title: item.isTitle, icon: item.icon != null && item.icon.length > 0, disabled: item.isDisabled}"
:href="item.url">
<var>{{item.name}}<span v-if="item.subName.length > 0">({{item.subName}})</span><i
class="icon small" :class="item.icon" v-if="item.icon != null && item.icon.length > 0"></i>
</var>
<var v-if="item.isTitle && typeof _data.node == 'object'">{{node.name}}</var>
<div class="bottom-indicator" v-if="item.isActive && !item.isTitle"></div>
</a>
</div>
<!-- 功能区 -->
<div class="main-box">
{$TEA.VIEW}
<div class="clear"></div>
</div>
</div>
<!-- 底部 -->
{$template "/footer"}
</div>
<!-- 右侧主操作栏 -->
<div class="main" :class="{'without-menu':teaSubMenus.menus == null || teaSubMenus.menus.length == 0 || (teaSubMenus.menus.length == 1 && teaSubMenus.menus[0].alwaysActive), 'without-secondary-menu':teaSubMenus.alwaysMenu == null || teaSubMenus.alwaysMenu.items.length <= 1, 'without-footer':!teaShowOpenSourceInfo}" v-cloak="">
<!-- 操作菜单 -->
<div class="ui top menu tabular tab-menu small" v-if="teaTabbar.length > 1">
<a class="item" v-for="item in teaTabbar" :class="{'active':item.isActive && !item.isDisabled, right:item.isRight, title: item.isTitle, icon: item.icon != null && item.icon.length > 0, disabled: item.isDisabled}" :href="item.url">
<var>{{item.name}}<span v-if="item.subName.length > 0">({{item.subName}})</span><i class="icon small" :class="item.icon" v-if="item.icon != null && item.icon.length > 0"></i> </var>
<var v-if="item.isTitle && typeof _data.node == 'object'">{{node.name}}</var>
<div class="bottom-indicator" v-if="item.isActive && !item.isTitle"></div>
</a>
</div>
<!-- 功能区 -->
<div class="main-box">
{$TEA.VIEW}
<div class="clear"></div>
</div>
</div>
<!-- 底部 -->
{$template "/footer"}
</div>
{$echo "footer"}
{$echo "footer"}
</body>
</html>

View File

@@ -3,60 +3,76 @@
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
scrollbar-width: thin;
}
.clear {
clear: both;
}
.hidden {
display: none;
}
pre {
white-space: pre-wrap;
}
a.disabled,
a.disabled:hover,
a.disabled:active,
span.disabled {
color: #ccc !important;
}
a.enabled,
span.enabled,
span.green {
color: #21ba45;
}
span.grey,
label.grey,
p.grey {
color: grey !important;
}
p.grey {
margin-top: 0.8em;
}
span.red,
pre.red {
color: #db2828;
}
span.blue {
color: #4183c4;
}
pre:not(.CodeMirror-line) {
font-family: Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
}
tbody {
background: transparent;
}
.table.width30 {
width: 30em !important;
}
.table.width35 {
width: 35em !important;
}
.table.width40 {
width: 40em !important;
}
.table th,
.table td {
font-size: 0.9em !important;
}
p.comment,
div.comment {
color: #959da6;
@@ -65,37 +81,46 @@ div.comment {
word-break: break-all;
line-height: 1.8;
}
p.comment em,
div.comment em {
font-style: italic !important;
}
.truncate {
white-space: nowrap;
-ms-text-overflow: ellipsis;
overflow: hidden;
text-overflow: ellipsis;
}
div.margin,
p.margin {
margin-top: 1em;
}
/** 操作按钮容器 **/
.op.one {
width: 4em;
}
.op.two {
width: 7.4em;
}
.op.three {
width: 9em;
}
.op.four {
width: 10em;
}
/** 扩展UI **/
.field.text {
padding: 0.5em;
}
/** 右侧主操作区 **/
.main {
position: absolute;
@@ -105,166 +130,210 @@ p.margin {
padding-right: 1em;
right: 1em;
}
@media screen and (max-width: 512px) {
.main {
left: 4em;
}
}
.main.without-menu {
left: 9em;
}
.main.without-secondary-menu {
top: 2.9em;
}
@media screen and (max-width: 512px) {
.main.without-menu {
left: 4em;
}
}
.main table td.title {
width: 10em;
}
.main table td.middle-title {
width: 14em;
}
.main table td {
vertical-align: top;
}
table td.color-border {
border-left: 1px #276ac6 solid !important;
}
.main table td.vertical-top {
vertical-align: top;
}
.main table td.vertical-middle {
vertical-align: middle;
}
.main table td[colspan="2"] a {
font-weight: normal;
}
.main table td em {
font-weight: normal;
font-style: normal;
font-size: 0.9em;
}
.main h3 {
font-weight: normal;
margin-top: 0.5em !important;
}
.main h3 span {
font-size: 0.8em;
}
.main h3 span.label {
color: #6435c9;
}
.main h3 a {
margin-left: 1em;
font-size: 14px !important;
right: 1em;
}
.main h3 a::before {
content: "[";
}
.main h3 a::after {
content: "]";
}
.main h4 {
font-weight: normal;
}
.main td span.small {
font-size: 0.8em;
}
.main .button.mini {
font-size: 0.8em;
padding: 0.2em;
margin-left: 1em;
}
/** 右侧文本子菜单 **/
.text.menu {
overflow-x: auto;
}
.text.menu::-webkit-scrollbar {
width: 4px;
height: 4px;
}
/** Vue **/
[v-cloak] {
display: none !important;
}
/** auto complete **/
.autocomplete-box .menu {
background: #eee !important;
}
.autocomplete-box .menu::-webkit-scrollbar {
width: 4px;
}
.autocomplete-box .menu .item {
border-top: none !important;
}
select.auto-width {
width: auto !important;
}
/** column **/
@media screen and (max-width: 512px) {
.column:not(.one) {
width: 100% !important;
}
}
/** label **/
label[for] {
cursor: pointer !important;
}
label.blue {
color: #2185d0 !important;
}
/** Menu **/
.first-menu .menu.text {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.first-menu .divider {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.second-menu .menu.text {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.second-menu .menu.text em {
font-style: normal;
}
.second-menu .divider {
margin-top: 0 !important;
}
.menu a {
outline: none;
}
/** var **/
span.olive,
var.olive {
color: #b5cc18 !important;
}
span.dash {
border-bottom: 1px dashed grey;
}
span.hover:hover {
background: #eee;
}
/** Message **/
.message .gopher {
width: 30px;
margin-right: 10px;
}
/** checkbox **/
.checkbox label a,
.checkbox label {
font-size: 0.8em !important;
}
/** page **/
.page {
margin-top: 1em;
border-left: 1px solid #ddd;
}
.page a {
display: inline-block;
background: #fafafa;
@@ -275,40 +344,50 @@ span.hover:hover {
border: 1px solid #ddd;
border-left: 0;
}
.page a.active {
background: #2185d0 !important;
color: white;
}
.page a:hover {
background: #eee;
}
.page select {
padding-top: 0.3em !important;
padding-bottom: 0.3em !important;
}
.swal2-html-container {
overflow-x: hidden;
}
.swal2-confirm:focus,
.swal2-cancel:focus,
.swal2-close:focus {
border: 3px #ddd solid !important;
}
.swal2-confirm,
.swal2-cancel,
.swal2-close {
border: 3px #fff solid !important;
}
.swal2-cancel {
margin-left: 2em !important;
}
input.error {
border: 1px #e0b4b4 solid !important;
}
textarea.wide-code {
font-family: Menlo, Monaco, "Courier New", monospace !important;
line-height: 1.6 !important;
}
.combo-box .menu {
max-height: 17em;
overflow-y: auto;
@@ -317,9 +396,11 @@ textarea.wide-code {
border-top: 0;
z-index: 100;
}
.combo-box .menu::-webkit-scrollbar {
width: 4px;
}
code-label {
background: #fff;
border: 1px solid rgba(34, 36, 38, 0.15);
@@ -333,4 +414,62 @@ code-label {
font-weight: 700;
vertical-align: baseline;
}
/*# sourceMappingURL=@layout_popup.css.map */
/*# sourceMappingURL=@layout_popup.css.map */
/* Override Primary Button Color for WAF Platform */
.ui.primary.button,
.ui.primary.buttons .button {
background-color: #0f2c54 !important;
color: #ffffff !important;
}
.ui.primary.button:hover,
.ui.primary.buttons .button:hover {
background-color: #0a1f3a !important;
}
.ui.primary.button:focus,
.ui.primary.buttons .button:focus {
background-color: #08192e !important;
}
.ui.primary.button:active,
.ui.primary.buttons .button:active,
.ui.primary.active.button {
background-color: #050d18 !important;
}
.text-primary,
.blue {
color: #0f2c54 !important;
}
/* Override Semantic UI Default Blue */
.ui.blue.button,
.ui.blue.buttons .button {
background-color: #0f2c54 !important;
color: #ffffff !important;
}
.ui.blue.button:hover,
.ui.blue.buttons .button:hover {
background-color: #0a1f3a !important;
}
.ui.basic.blue.button,
.ui.basic.blue.buttons .button {
box-shadow: 0 0 0 1px #0f2c54 inset !important;
color: #0f2c54 !important;
}
.ui.basic.blue.button:hover,
.ui.basic.blue.buttons .button:hover {
background: transparent !important;
box-shadow: 0 0 0 1px #0a1f3a inset !important;
color: #0a1f3a !important;
}
.ui.menu .active.item {
border-color: #2185d0 !important;
color: #2185d0 !important;
}

View File

@@ -8,16 +8,10 @@
<div class="item"><strong>上传 SDK</strong></div>
</second-menu>
<form method="post"
enctype="multipart/form-data"
class="ui form"
data-tea-action="$"
data-tea-timeout="300"
data-tea-before="beforeUpload"
data-tea-done="doneUpload"
data-tea-success="successUpload">
<form method="post" enctype="multipart/form-data" class="ui form" data-tea-action="$" data-tea-timeout="300"
data-tea-before="beforeUpload" data-tea-done="doneUpload" data-tea-success="successUpload">
<csrf-token></csrf-token>
<input type="hidden" name="appId" :value="app.id"/>
<input type="hidden" name="appId" :value="app.id" />
<table class="ui table selectable definition">
<tr>
@@ -33,22 +27,22 @@
<tr>
<td class="title">版本号 *</td>
<td>
<input type="text" name="version" v-model="version" maxlength="32"/>
<input type="text" name="version" v-model="version" maxlength="32" />
<p class="comment">默认 `1.0.0`。同平台+同版本再次上传会覆盖该版本文件。</p>
</td>
</tr>
<tr>
<td class="title">SDK 包</td>
<td>
<input type="file" name="sdkFile" accept=".zip"/>
<p class="comment">支持 zip 包,例如 `httpdns-sdk-android.zip`。</p>
<input type="file" name="sdkFile" accept=".zip" />
<p class="comment">支持 zip 包,例如 `httpdns-sdk-android.zip`,单文件最大 20MB</p>
</td>
</tr>
<tr>
<td class="title">集成文档</td>
<td>
<input type="file" name="docFile" accept=".md,text/markdown"/>
<p class="comment">支持 Markdown 文件(`.md`)。</p>
<input type="file" name="docFile" accept=".md,text/markdown" />
<p class="comment">支持 Markdown 文件(`.md`,单文件最大 20MB</p>
</td>
</tr>
</table>
@@ -65,26 +59,26 @@
<h4 style="margin-top: 1.5em">已上传文件</h4>
<table class="ui table selectable celled" v-if="uploadedFiles.length > 0">
<thead>
<tr>
<th>平台</th>
<th>类型</th>
<th>版本</th>
<th>文件名</th>
<th>大小</th>
<th>更新时间</th>
<th class="one wide">操作</th>
</tr>
<tr>
<th>平台</th>
<th>类型</th>
<th>版本</th>
<th>文件名</th>
<th>大小</th>
<th>更新时间</th>
<th class="one wide">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="file in uploadedFiles">
<td>{{file.platform}}</td>
<td>{{file.fileType}}</td>
<td>{{file.version}}</td>
<td>{{file.name}}</td>
<td>{{file.sizeText}}</td>
<td>{{file.updatedAt}}</td>
<td><a href="" @click.prevent="deleteUploadedFile(file.name)">删除</a></td>
</tr>
<tr v-for="file in uploadedFiles">
<td>{{file.platform}}</td>
<td>{{file.fileType}}</td>
<td>{{file.version}}</td>
<td>{{file.name}}</td>
<td>{{file.sizeText}}</td>
<td>{{file.updatedAt}}</td>
<td><a href="" @click.prevent="deleteUploadedFile(file.name)">删除</a></td>
</tr>
</tbody>
</table>
<div class="ui message" v-else>

View File

@@ -8,6 +8,17 @@
}
this.beforeUpload = function () {
var maxSize = 20 * 1024 * 1024; // 20MB
var inputs = document.querySelectorAll("input[type=file]");
for (var i = 0; i < inputs.length; i++) {
var files = inputs[i].files;
if (files != null && files.length > 0) {
if (files[0].size > maxSize) {
teaweb.warn("文件 \"" + files[0].name + "\" 超过 20MB 限制(" + (files[0].size / 1024 / 1024).toFixed(1) + "MB请压缩后重试");
return false;
}
}
}
this.isUploading = true;
};

View File

@@ -127,7 +127,7 @@
<td>
v{{node.status.buildVersion}}
&nbsp;
<a :href="'/httpdns/clusters/upgradeRemote?clusterId=' + clusterId + '&nodeId=' + node.id" v-if="shouldUpgrade"><span class="red">发现新版本 v{{newVersion}} &raquo;</span></a>
<a :href="'/httpdns/clusters/cluster/upgradeRemote?clusterId=' + clusterId" v-if="shouldUpgrade"><span class="red">发现新版本 v{{newVersion}} &raquo;</span></a>
</td>
</tr>
<tr v-if="node.status.exePath != null && node.status.exePath.length > 0">

View File

@@ -66,30 +66,37 @@
<tr>
<td>自动远程启动</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="autoRemoteStart" value="1" v-model="settings.autoRemoteStart" />
<label></label>
</div>
<checkbox name="autoRemoteStart" v-model="settings.autoRemoteStart"></checkbox>
<p class="comment">当检测到节点离线时自动尝试远程启动前提是节点已经设置了SSH登录认证</p>
</td>
</tr>
<tr>
<td>访问日志</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="accessLogIsOn" value="1" v-model="settings.accessLogIsOn" />
<label></label>
</div>
<checkbox name="accessLogIsOn" v-model="settings.accessLogIsOn"></checkbox>
<p class="comment">启用后HTTPDNS 节点将会记录客户端的请求访问日志。</p>
</td>
</tr>
<tr>
<td>时区</td>
<td>
<div>
<span class="ui label basic small" v-if="timeZoneLocation != null">当前:{{timeZoneLocation.name}}
({{timeZoneLocation.offset}})</span>
</div>
<div style="margin-top: 0.5em">
<select class="ui dropdown auto-width" name="timeZone" v-model="settings.timeZone">
<option v-for="tz in timeZoneLocations" :value="tz.name">{{tz.name}} ({{tz.offset}})
</option>
</select>
</div>
<p class="comment">HTTPDNS 节点进程使用的时区设置,默认为 Asia/Shanghai。</p>
</td>
</tr>
<tr>
<td>启用当前集群</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="settings.isOn" />
<label></label>
</div>
<checkbox name="isOn" v-model="settings.isOn"></checkbox>
<p class="comment">取消启用后,该集群不会参与 HTTPDNS 服务。</p>
</td>
</tr>
@@ -110,4 +117,4 @@
<submit-btn></submit-btn>
</form>
</div>
</div>

View File

@@ -7,4 +7,27 @@ Tea.context(function () {
if (!this.settings) {
this.settings = {}
}
let toBool = function (v) {
if (typeof v === "boolean") {
return v
}
if (typeof v === "number") {
return v === 1
}
if (typeof v === "string") {
let s = v.toLowerCase().trim()
return s === "1" || s === "true" || s === "on" || s === "yes" || s === "enabled"
}
return false
}
this.settings.autoRemoteStart = toBool(this.settings.autoRemoteStart)
this.settings.accessLogIsOn = toBool(this.settings.accessLogIsOn)
this.settings.isOn = toBool(this.settings.isOn)
if (!this.settings.timeZone || this.settings.timeZone.length == 0) {
this.settings.timeZone = "Asia/Shanghai"
}
})

View File

@@ -43,7 +43,7 @@
<tr>
<td>节点安装根目录</td>
<td>
<input type="text" name="installDir" maxlength="255" value="/opt/edge-httpdns" />
<input type="text" name="installDir" maxlength="255" value="/root/edge-httpdns" />
<p class="comment">边缘节点安装 HTTPDNS 服务的默认目录。</p>
</td>
</tr>
@@ -59,4 +59,4 @@
</tr>
</table>
<submit-btn></submit-btn>
</form>
</form>

View File

@@ -19,7 +19,7 @@
<tr>
<td>安装目录</td>
<td>
<input type="text" name="installDir" maxlength="100" :value="cluster.installDir || '/opt/edge-httpdns'" />
<input type="text" name="installDir" maxlength="100" :value="cluster.installDir || '/root/edge-httpdns'" />
<p class="comment">默认使用集群配置目录。</p>
</td>
</tr>

View File

@@ -51,6 +51,12 @@
:class="{red:cluster.countAllNodes > cluster.countActiveNodes}">{{cluster.countAllNodes}}</span>
</a>
<span class="disabled" v-else="">-</span>
<div v-if="cluster.countUpgradeNodes > 0" style="margin-top:0.5em">
<a :href="'/httpdns/clusters/cluster/upgradeRemote?clusterId=' + cluster.id" title="点击进入远程升级页面">
<span class="red">有{{cluster.countUpgradeNodes}}个节点需要升级</span>
</a>
</div>
</td>
<td class="center">
<a :href="'/httpdns/clusters/cluster?clusterId=' + cluster.id" v-if="cluster.countActiveNodes > 0">
@@ -67,4 +73,4 @@
</table>
<div class="page" v-html="page"></div>
</div>
</div>

View File

@@ -1,3 +1,62 @@
{$layout}
{$layout}
<p class="ui message error">姝ゅ姛鑳芥殏鏈紑鏀俱€?/p>
<p class="comment" v-if="nodes.length == 0">暂时没有需要升级的节点。</p>
<div v-if="nodes.length > 0">
<h3>
所有需要升级的节点
<button class="ui button primary tiny" v-if="countCheckedNodes() > 0" @click.prevent="installBatch()">批量升级({{countCheckedNodes()}})</button>
</h3>
<table class="ui table selectable celled">
<thead>
<tr>
<th style="width:3em">
<checkbox @input="checkNodes"></checkbox>
</th>
<th>节点名</th>
<th>访问IP</th>
<th>SSH地址</th>
<th>版本变化</th>
<th class="four wide">节点状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="node in nodes">
<td>
<checkbox v-model="node.isChecked" v-if="node.installStatus == null || !node.installStatus.isOk"></checkbox>
</td>
<td>
<a :href="'/httpdns/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">{{node.name}}</a>
<a :href="'/httpdns/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id" title="节点详情" style="margin-left: 0.4em">[详情]</a>
</td>
<td>
<span v-if="node.accessIP.length > 0" class="ui label tiny basic">{{node.accessIP}}</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="node.login != null && node.login.type == 'ssh' && node.loginParams != null && node.loginParams.host != null && node.loginParams.host.length > 0">
{{node.loginParams.host}}:{{node.loginParams.port}}
</span>
<span v-else class="disabled">没有设置</span>
</td>
<td>v{{node.oldVersion}} -&gt; v{{node.newVersion}}</td>
<td>
<div v-if="node.installStatus != null && (node.installStatus.isRunning || node.installStatus.isFinished)">
<div v-if="node.installStatus.isRunning" class="blue">升级中...</div>
<div v-if="node.installStatus.isFinished">
<span v-if="node.installStatus.isOk" class="green">已升级成功</span>
<span v-if="!node.installStatus.isOk" class="red">升级过程中发生错误:{{node.installStatus.error}}</span>
</div>
</div>
<span v-else class="disabled">等待升级</span>
</td>
<td>
<a href="" @click.prevent="installNode(node)" v-if="!isInstalling">升级</a>
<span v-if="isInstalling && node.isInstalling">升级中...</span>
<span v-if="isInstalling && !node.isInstalling" class="disabled">升级</span>
</td>
</tr>
</tbody>
</table>
</div>

View File

@@ -0,0 +1,147 @@
Tea.context(function () {
this.isInstalling = false
this.isBatch = false
let installingNode = null
this.nodes.forEach(function (v) {
v.isChecked = false
})
this.$delay(function () {
this.reload()
})
let that = this
this.checkNodes = function (isChecked) {
this.nodes.forEach(function (v) {
v.isChecked = isChecked
})
}
this.countCheckedNodes = function () {
return that.nodes.$count(function (k, v) {
return v.isChecked
})
}
this.installNode = function (node) {
let that = this
if (this.isBatch) {
installingNode = node
that.isInstalling = true
node.isInstalling = true
that.$post("$")
.params({
nodeId: node.id
})
} else {
teaweb.confirm("确定要开始升级此节点吗?", function () {
installingNode = node
that.isInstalling = true
node.isInstalling = true
that.$post("$")
.params({
nodeId: node.id
})
})
}
}
this.installBatch = function () {
let that = this
this.isBatch = true
teaweb.confirm("确定要批量升级选中的节点吗?", function () {
that.installNext()
})
}
this.installNext = function () {
let nextNode = this.nodes.$find(function (k, v) {
return v.isChecked
})
if (nextNode == null) {
teaweb.success("全部升级成功", function () {
teaweb.reload()
})
} else {
this.installNode(nextNode)
}
return
}
this.reload = function () {
let that = this
if (installingNode != null) {
this.$post("/httpdns/clusters/upgradeStatus")
.params({
nodeId: installingNode.id
})
.success(function (resp) {
if (resp.data.status != null) {
installingNode.installStatus = resp.data.status
if (installingNode.installStatus.isFinished) {
if (installingNode.installStatus.isOk) {
installingNode.isChecked = false
installingNode = null
if (that.isBatch) {
that.installNext()
} else {
teaweb.success("升级成功", function () {
teaweb.reload()
})
}
} else {
let nodeId = installingNode.id
let errMsg = installingNode.installStatus.error
that.isInstalling = false
installingNode.isInstalling = false
installingNode = null
switch (resp.data.status.errorCode) {
case "EMPTY_LOGIN":
case "EMPTY_SSH_HOST":
case "EMPTY_SSH_PORT":
case "EMPTY_GRANT":
teaweb.warn("需要填写SSH登录信息", function () {
teaweb.popup("/httpdns/clusters/updateNodeSSH?nodeId=" + nodeId, {
height: "20em",
callback: function () {
teaweb.reload()
}
})
})
return
case "CREATE_ROOT_DIRECTORY_FAILED":
teaweb.warn("创建根目录失败,请检查目录权限或手工创建:" + errMsg)
return
case "INSTALL_HELPER_FAILED":
teaweb.warn("安装助手失败:" + errMsg)
return
case "TEST_FAILED":
teaweb.warn("环境测试失败:" + errMsg)
return
case "RPC_TEST_FAILED":
teaweb.confirm("html:要升级的节点到API服务之间的RPC通讯测试失败具体错误" + errMsg + "<br/>现在修改API信息", function () {
window.location = "/settings/api"
})
return
default:
teaweb.warn("升级失败:" + errMsg)
}
}
}
}
})
.done(function () {
setTimeout(this.reload, 3000)
})
} else {
setTimeout(this.reload, 3000)
}
}
})

View File

@@ -52,7 +52,7 @@
</thead>
<tr v-for="node in nodes">
<td class="node-name-td"><a :href="'/ns/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id"><keyword :v-word="keyword">{{node.name}}</keyword></a>
<div v-if="node.status != null && node.status.version.length > 0 && node.status.version != latestVersion">
<div v-if="node.status != null && node.status.version.length > 0 && latestVersion.length > 0 && Tea.versionCompare(latestVersion, node.status.version) > 0">
<span class="small red">v{{node.status.version}} -&gt; v{{latestVersion}}</span>
</div>
@@ -112,4 +112,4 @@
</table>
<div class="page" v-html="page"></div>
<div class="page" v-html="page"></div>

View File

@@ -97,4 +97,4 @@
</tr>
</table>
<p class="comment">数据更新于{{key.updatedTime}}。</p>
</div>
</div>

View File

@@ -1,45 +1,38 @@
{$layout}
<form class="ui form" method="post" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">产品名称 *</td>
<td>
<input type="text" name="productName" v-model="config.productName" maxlength="100"/>
<p class="comment">可以使用变量<code-label>${product.name}</code-label>在网页里展示此名称,建议仅包含英文和数字字符。</p>
</td>
</tr>
<tr>
<td>管理员系统名称 *</td>
<td>
<input type="text" name="adminSystemName" v-model="config.adminSystemName" maxlength="100"/>
<p class="comment">当前管理系统界面上显示的名称。</p>
</td>
</tr>
<tr>
<td>显示版本号</td>
<td>
<checkbox name="showVersion" v-model="config.showVersion"></checkbox>
<p class="comment">选中后,在界面中显示系统版本号。</p>
</td>
</tr>
<tr>
<td>定制版本号</td>
<td>
<input type="text" name="version" v-model="config.version" maxlength="100"/>
<p class="comment">定制自己的版本号,留空表示使用系统自带的版本号。</p>
</td>
</tr>
<tr v-show="teaIsPlus">
<td>显示模块</td>
<table class="ui table definition selectable">
<tr>
<td class="title">产品名称 *</td>
<td>
<checkbox name="supportModuleCDN" v-model="supportModuleCDN">CDN</checkbox> &nbsp; &nbsp; &nbsp;
<checkbox name="supportModuleNS" v-model="supportModuleNS" v-show="nsIsVisible">智能DNS</checkbox>
<p class="comment">当前管理系统中可以显示的模块,不能为空。</p>
<input type="text" name="productName" v-model="config.productName" maxlength="100" />
<p class="comment">可以使用变量<code-label>${product.name}</code-label>在网页里展示此名称,建议仅包含英文和数字字符。</p>
</td>
</tr>
<tr>
<td>管理员系统名称 *</td>
<td>
<input type="text" name="adminSystemName" v-model="config.adminSystemName" maxlength="100" />
<p class="comment">当前管理系统界面上显示的名称。</p>
</td>
</tr>
<tr>
<td>显示版本号</td>
<td>
<checkbox name="showVersion" v-model="config.showVersion"></checkbox>
<p class="comment">选中后,在界面中显示系统版本号。</p>
</td>
</tr>
<tr>
<td>定制版本号</td>
<td>
<input type="text" name="version" v-model="config.version" maxlength="100" />
<p class="comment">定制自己的版本号,留空表示使用系统自带的版本号。</p>
</td>
</tr>
<tr>
<td>显示财务相关功能</td>
<td>
@@ -51,13 +44,14 @@
<td>浏览器图标</td>
<td>
<div v-if="config.faviconFileId > 0">
<a :href="'/ui/image/' + config.faviconFileId" target="_blank"><img alt="" :src="'/ui/image/' + config.faviconFileId" style="width:32px;border:1px #ccc solid;"/></a>
<a :href="'/ui/image/' + config.faviconFileId" target="_blank"><img alt=""
:src="'/ui/image/' + config.faviconFileId" style="width:32px;border:1px #ccc solid;" /></a>
</div>
<div v-else>
<span class="disabled">还没有上传。</span>
</div>
<div style="margin-top: 0.8em">
<input type="file" name="faviconFile" accept=".png"/>
<input type="file" name="faviconFile" accept=".png" />
</div>
<p class="comment">在浏览器标签栏显示的图标请使用PNG格式。</p>
</td>
@@ -66,25 +60,27 @@
<td>Logo</td>
<td>
<div v-if="config.logoFileId > 0">
<a :href="'/ui/image/' + config.logoFileId" target="_blank"><img :src="'/ui/image/' + config.logoFileId" style="width:32px;border:1px #ccc solid;"/></a>
<a :href="'/ui/image/' + config.logoFileId" target="_blank"><img
:src="'/ui/image/' + config.logoFileId" style="width:32px;border:1px #ccc solid;" /></a>
</div>
<div v-else>
<span class="disabled">还没有上传。</span>
</div>
<div style="margin-top: 0.8em">
<input type="file" name="logoFile" accept=".png"/>
<input type="file" name="logoFile" accept=".png" />
</div>
<p class="comment">显示在系统界面上的图标请使用PNG格式。</p>
</td>
</tr>
</table>
</table>
<h4>其他</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">每页显示数</td>
<td>
<input type="text" name="defaultPageSize" v-model="config.defaultPageSize" maxlength="3" style="width: 4em"/>
<input type="text" name="defaultPageSize" v-model="config.defaultPageSize" maxlength="3"
style="width: 4em" />
<p class="comment">在有分页的地方每页显示数量不能超过100。</p>
</td>
</tr>
@@ -94,12 +90,15 @@
<div class="ui fields inline">
<div class="ui field">
<select class="ui dropdown" v-model="timeZoneGroupCode">
<option v-for="timeZoneGroup in timeZoneGroups" :value="timeZoneGroup.code">{{timeZoneGroup.name}}</option>
<option v-for="timeZoneGroup in timeZoneGroups" :value="timeZoneGroup.code">
{{timeZoneGroup.name}}</option>
</select>
</div>
<div class="ui field">
<select class="ui dropdown" name="timeZone" v-model="config.timeZone">
<option v-for="timeZoneLocation in timeZoneLocations" :value="timeZoneLocation.name" v-if="timeZoneLocation.group == timeZoneGroupCode">{{timeZoneLocation.name}} ({{timeZoneLocation.offset}})</option>
<option v-for="timeZoneLocation in timeZoneLocations" :value="timeZoneLocation.name"
v-if="timeZoneLocation.group == timeZoneGroupCode">{{timeZoneLocation.name}}
({{timeZoneLocation.offset}})</option>
</select>
</div>
</div>
@@ -119,5 +118,5 @@
</tr>
</table>
<submit-btn></submit-btn>
<submit-btn></submit-btn>
</form>

View File

@@ -1,3 +1,139 @@
{$layout}
<p class="comment">此功能暂未开放,敬请期待。</p>
<div class="margin"></div>
<!-- 自动升级设置 -->
<div class="ui segment">
<h3>自动升级</h3>
<table class="ui table definition">
<tr>
<td class="title">开启自动升级</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="autoUpgrade" v-model="config.autoUpgrade" @change="updateAutoUpgrade">
<label></label>
</div>
<p class="comment">开启后边缘节点、DNS节点、HTTPDNS节点每分钟检查新版本并自动下载升级。关闭后节点不会自动升级但管理员仍可在下方手动升级。</p>
</td>
</tr>
</table>
</div>
<div class="margin"></div>
<!-- 手动升级 -->
<div class="ui segment">
<h3 style="display: flex; justify-content: space-between; align-items: center;">
<span>手动升级</span>
<button class="ui button primary tiny" v-if="totalUpgradeCount > 0"
@click.prevent="upgradeAll()">全部升级({{totalUpgradeCount}})</button>
</h3>
<div v-if="modules.length == 0">
<p class="comment">暂无需要升级的节点。</p>
</div>
<div v-for="mod in modules" class="ui segment" style="margin-bottom: 1em; padding: 0;">
<h4 style="cursor: pointer; display: flex; justify-content: space-between; align-items: center; padding: 0.8em 1em; margin: 0; background: #f9fafb; border-bottom: 1px solid rgba(34,36,38,.15);"
@click.prevent="mod.expanded = !mod.expanded">
<div>
<i class="icon angle down" v-if="mod.expanded"></i>
<i class="icon angle right" v-else></i>
{{mod.name}}
<span class="ui label tiny basic" v-if="mod.count > 0"
style="margin-left: 0.5em;">{{mod.count}}个待升级</span>
</div>
<button class="ui button primary tiny" v-if="mod.count > 0"
@click.stop.prevent="upgradeModule(mod.code)">升级所有{{mod.name}}</button>
</h4>
<div v-show="mod.expanded" style="padding: 1em;">
<div v-for="cluster in mod.clusters" style="margin-bottom: 1em;">
<h5 style="cursor: pointer; display: flex; justify-content: space-between; align-items: center; padding: 0.6em; background: #f3f4f5; border-radius: 4px; margin: 0;"
@click.prevent="cluster.expanded = !cluster.expanded">
<div>
<i class="icon angle down" v-if="cluster.expanded"></i>
<i class="icon angle right" v-else></i>
{{cluster.name}}
<span class="ui label tiny basic" style="margin-left: 0.5em;">{{cluster.count}}个待升级</span>
</div>
<div>
<button class="ui button tiny primary" v-if="countCheckedNodesInCluster(cluster) > 0"
@click.stop.prevent="upgradeBatchInCluster(mod.code, cluster)">批量升级({{countCheckedNodesInCluster(cluster)}})</button>
<button class="ui button tiny"
@click.stop.prevent="upgradeCluster(mod.code, cluster.id)">升级集群内所有节点</button>
</div>
</h5>
<div v-show="cluster.expanded" style="margin-top: 0.5em;">
<table class="ui table selectable celled small" style="margin: 0;">
<thead>
<tr>
<th style="width:3em">
<div class="ui checkbox" @click.prevent="toggleCheckAll(cluster)">
<input type="checkbox" :checked="isAllChecked(cluster)">
<label></label>
</div>
</th>
<th>节点名</th>
<th>访问IP</th>
<th>SSH地址</th>
<th>版本变化</th>
<th class="four wide">节点状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="node in cluster.nodes">
<td>
<div class="ui checkbox" v-if="!isNodeUpgradeFinished(node)">
<input type="checkbox" v-model="node.isChecked">
<label></label>
</div>
</td>
<td>
{{node.name}}
<a :href="nodeDetailURL(mod.code, cluster.id, node.id)" title="节点详情" style="margin-left: 0.4em"><i class="icon external alternate small"></i></a>
</td>
<td>
<span v-if="node.accessIP && node.accessIP.length > 0" class="ui label tiny basic">{{node.accessIP}}</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="node.login != null && node.login.type == 'ssh' && node.loginParams != null && node.loginParams.host != null && node.loginParams.host.length > 0">
{{node.loginParams.host}}:{{node.loginParams.port}}
</span>
<span v-else class="disabled">没有设置</span>
</td>
<td>
<span v-if="node.oldVersion">v{{node.oldVersion}}</span>
<span v-else class="ui label tiny">未知</span>
-&gt; <strong>v{{node.newVersion}}</strong>
</td>
<td>
<div v-if="node.installStatus != null && (node.installStatus.isRunning || node.installStatus.isFinished)">
<div v-if="node.installStatus.isRunning && !node.installStatus.isFinished"
class="blue">
<i class="notched circle loading icon"></i> 升级中...
</div>
<div v-if="node.installStatus.isFinished">
<span v-if="node.installStatus.isOk" class="green"><i
class="icon check circle"></i>
已升级成功</span>
<span v-if="!node.installStatus.isOk" class="red"><i
class="icon warning circle"></i>
升级过程中发生错误:{{node.installStatus.error}}</span>
</div>
</div>
<span v-else class="disabled">等待升级</span>
</td>
<td>
<a href="" @click.prevent="upgradeNode(mod.code, node)" v-if="!node.isUpgrading">升级</a>
<span v-if="node.isUpgrading" class="blue">升级中...</span>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,457 @@
Tea.context(function () {
let that = this
// 计算总待升级数
this.totalUpgradeCount = 0
this.modules.forEach(function (mod) {
mod.expanded = true
that.totalUpgradeCount += mod.count
if (mod.clusters != null) {
mod.clusters.forEach(function (cluster) {
cluster.expanded = true
if (cluster.nodes != null) {
cluster.nodes.forEach(function (node) {
node.isUpgrading = false
node.isChecked = false
})
}
})
}
})
// 正在升级的节点ID集合按模块
this.upgradingNodeIds = {
"node": [],
"dns": [],
"httpdns": []
}
// 启动状态轮询
this.$delay(function () {
this.pollStatus()
})
/**
* 获取节点详情页URL
*/
this.nodeDetailURL = function (moduleCode, clusterId, nodeId) {
switch (moduleCode) {
case "node":
return "/clusters/cluster/node?clusterId=" + clusterId + "&nodeId=" + nodeId
case "dns":
return "/ns/clusters/cluster/node?clusterId=" + clusterId + "&nodeId=" + nodeId
case "httpdns":
return "/httpdns/clusters/cluster/node?clusterId=" + clusterId + "&nodeId=" + nodeId
default:
return "#"
}
}
/**
* 判断节点升级是否已完成(成功)
*/
this.isNodeUpgradeFinished = function (node) {
return node.installStatus != null && node.installStatus.isFinished && node.installStatus.isOk
}
/**
* 全选/取消全选
*/
this.toggleCheckAll = function (cluster) {
if (cluster.nodes == null) return
let allChecked = that.isAllChecked(cluster)
cluster.nodes.forEach(function (node) {
if (!that.isNodeUpgradeFinished(node)) {
node.isChecked = !allChecked
}
})
}
/**
* 是否全部选中
*/
this.isAllChecked = function (cluster) {
if (cluster.nodes == null || cluster.nodes.length == 0) return false
let checkableNodes = cluster.nodes.filter(function (node) {
return !that.isNodeUpgradeFinished(node)
})
if (checkableNodes.length == 0) return false
return checkableNodes.every(function (node) { return node.isChecked })
}
/**
* 计算集群内选中的节点数
*/
this.countCheckedNodesInCluster = function (cluster) {
if (cluster.nodes == null) return 0
return cluster.nodes.filter(function (node) { return node.isChecked }).length
}
/**
* 全部升级
*/
this.upgradeAll = function () {
var vue = this
teaweb.confirm("确定要升级所有模块的所有待升级节点吗?", function () {
vue.$post("/settings/upgrade/upgradeNode")
.params({
module: "",
scope: "all",
clusterId: 0,
nodeId: 0
})
.success(function () {
// 标记所有节点为升级中
that.modules.forEach(function (mod) {
if (mod.clusters != null) {
mod.clusters.forEach(function (cluster) {
if (cluster.nodes != null) {
cluster.nodes.forEach(function (node) {
node.installStatus = {
isRunning: true,
isFinished: false,
isOk: false,
error: "",
errorCode: ""
}
node.isUpgrading = true
that.trackNode(mod.code, node.id)
})
}
})
}
})
})
.fail(function (resp) {
teaweb.warn("升级请求失败:" + resp.message)
})
})
}
/**
* 按模块升级
*/
this.upgradeModule = function (moduleCode) {
var vue = this
teaweb.confirm("确定要升级该模块的所有待升级节点吗?", function () {
vue.$post("/settings/upgrade/upgradeNode")
.params({
module: moduleCode,
scope: "module",
clusterId: 0,
nodeId: 0
})
.success(function () {
that.markModuleUpgrading(moduleCode)
})
.fail(function (resp) {
teaweb.warn("升级请求失败:" + resp.message)
})
})
}
/**
* 按集群升级
*/
this.upgradeCluster = function (moduleCode, clusterId) {
var vue = this
teaweb.confirm("确定要升级该集群的所有待升级节点吗?", function () {
vue.$post("/settings/upgrade/upgradeNode")
.params({
module: moduleCode,
scope: "cluster",
clusterId: clusterId,
nodeId: 0
})
.success(function () {
that.markClusterUpgrading(moduleCode, clusterId)
})
.fail(function (resp) {
teaweb.warn("升级请求失败:" + resp.message)
})
})
}
/**
* 批量升级集群内选中的节点
*/
this.upgradeBatchInCluster = function (moduleCode, cluster) {
if (cluster.nodes == null) return
var checkedNodes = cluster.nodes.filter(function (node) { return node.isChecked })
if (checkedNodes.length == 0) return
var vue = this
teaweb.confirm("确定要批量升级选中的 " + checkedNodes.length + " 个节点吗?", function () {
checkedNodes.forEach(function (node) {
node.installStatus = {
isRunning: true,
isFinished: false,
isOk: false,
error: "",
errorCode: ""
}
node.isUpgrading = true
node.isChecked = false
that.trackNode(moduleCode, node.id)
vue.$post("/settings/upgrade/upgradeNode")
.params({
module: moduleCode,
scope: "node",
clusterId: 0,
nodeId: node.id
})
})
})
}
/**
* 升级单个节点
*/
this.upgradeNode = function (moduleCode, node) {
var vue = this
teaweb.confirm("确定要升级节点 \"" + node.name + "\" 吗?", function () {
node.installStatus = {
isRunning: true,
isFinished: false,
isOk: false,
error: "",
errorCode: ""
}
node.isUpgrading = true
that.trackNode(moduleCode, node.id)
vue.$post("/settings/upgrade/upgradeNode")
.params({
module: moduleCode,
scope: "node",
clusterId: 0,
nodeId: node.id
})
.success(function () { })
.fail(function (resp) {
node.isUpgrading = false
teaweb.warn("升级请求失败:" + resp.message)
})
})
}
/**
* 标记模块下所有节点为升级中
*/
this.markModuleUpgrading = function (moduleCode) {
that.modules.forEach(function (mod) {
if (mod.code == moduleCode && mod.clusters != null) {
mod.clusters.forEach(function (cluster) {
if (cluster.nodes != null) {
cluster.nodes.forEach(function (node) {
node.installStatus = {
isRunning: true,
isFinished: false,
isOk: false,
error: "",
errorCode: ""
}
node.isUpgrading = true
that.trackNode(moduleCode, node.id)
})
}
})
}
})
}
/**
* 标记集群下所有节点为升级中
*/
this.markClusterUpgrading = function (moduleCode, clusterId) {
that.modules.forEach(function (mod) {
if (mod.code == moduleCode && mod.clusters != null) {
mod.clusters.forEach(function (cluster) {
if (cluster.id == clusterId && cluster.nodes != null) {
cluster.nodes.forEach(function (node) {
node.installStatus = {
isRunning: true,
isFinished: false,
isOk: false,
error: "",
errorCode: ""
}
node.isUpgrading = true
that.trackNode(moduleCode, node.id)
})
}
})
}
})
}
/**
* 追踪节点升级状态
*/
this.trackNode = function (moduleCode, nodeId) {
if (that.upgradingNodeIds[moduleCode] == null) {
that.upgradingNodeIds[moduleCode] = []
}
if (that.upgradingNodeIds[moduleCode].indexOf(nodeId) < 0) {
that.upgradingNodeIds[moduleCode].push(nodeId)
}
}
/**
* 状态轮询
*/
this.pollStatus = function () {
var vue = this
// 检查是否有正在升级的节点
let hasUpgrading = false
for (let key in that.upgradingNodeIds) {
if (that.upgradingNodeIds[key].length > 0) {
hasUpgrading = true
break
}
}
if (!hasUpgrading) {
setTimeout(function () { that.pollStatus() }, 5000)
return
}
vue.$post("/settings/upgrade/status")
.params({
nodeIdsJSON: JSON.stringify(that.upgradingNodeIds)
})
.success(function (resp) {
let statuses = resp.data.statuses
if (statuses == null) {
return
}
// 更新各模块节点状态
for (let moduleCode in statuses) {
let nodeStatuses = statuses[moduleCode]
if (nodeStatuses == null) {
continue
}
nodeStatuses.forEach(function (ns) {
that.updateNodeStatus(moduleCode, ns.id, ns.installStatus)
})
}
})
.done(function () {
setTimeout(function () { that.pollStatus() }, 3000)
})
}
/**
* 更新节点安装状态
*/
this.updateNodeStatus = function (moduleCode, nodeId, installStatus) {
that.modules.forEach(function (mod) {
if (mod.code == moduleCode && mod.clusters != null) {
mod.clusters.forEach(function (cluster) {
if (cluster.nodes != null) {
cluster.nodes.forEach(function (node) {
if (node.id == nodeId && installStatus != null) {
node.installStatus = installStatus
// 升级完成后移除跟踪
if (installStatus.isFinished) {
node.isUpgrading = false
let idx = that.upgradingNodeIds[moduleCode].indexOf(nodeId)
if (idx >= 0) {
that.upgradingNodeIds[moduleCode].splice(idx, 1)
}
if (installStatus.isOk) {
// 升级成功,延迟后从列表中移除
setTimeout(function () {
let nIdx = cluster.nodes.indexOf(node)
if (nIdx >= 0) {
cluster.nodes.splice(nIdx, 1)
cluster.count--
mod.count--
that.totalUpgradeCount--
if (cluster.count <= 0) {
let cIdx = mod.clusters.indexOf(cluster)
if (cIdx >= 0) {
mod.clusters.splice(cIdx, 1)
}
}
if (mod.count <= 0) {
let mIdx = that.modules.indexOf(mod)
if (mIdx >= 0) {
that.modules.splice(mIdx, 1)
}
}
}
}, 2000)
} else {
// 升级失败,根据 errorCode 给出具体提示
that.handleUpgradeError(moduleCode, node, installStatus)
}
}
}
})
}
})
}
})
}
/**
* 处理升级错误,根据 errorCode 提供更友好的提示
*/
this.handleUpgradeError = function (moduleCode, node, installStatus) {
let errorCode = installStatus.errorCode || ""
let errMsg = installStatus.error || ""
switch (errorCode) {
case "EMPTY_LOGIN":
case "EMPTY_SSH_HOST":
case "EMPTY_SSH_PORT":
case "EMPTY_GRANT":
// SSH 信息未配置的错误,不弹窗(页面上已有"没有设置"提示)
break
case "CREATE_ROOT_DIRECTORY_FAILED":
teaweb.warn("节点 \"" + node.name + "\" 创建根目录失败:" + errMsg)
break
case "INSTALL_HELPER_FAILED":
teaweb.warn("节点 \"" + node.name + "\" 安装助手失败:" + errMsg)
break
case "TEST_FAILED":
teaweb.warn("节点 \"" + node.name + "\" 环境测试失败:" + errMsg)
break
case "RPC_TEST_FAILED":
teaweb.warn("节点 \"" + node.name + "\" RPC通讯测试失败" + errMsg)
break
}
}
/**
* 更新自动升级状态
*/
this.updateAutoUpgrade = function () {
var vue = this
// @change 已经翻转了值,先记录新值并立即恢复
let newValue = that.config.autoUpgrade
that.config.autoUpgrade = !newValue
let msg = newValue ? "确定要开启自动升级功能吗?开启后节点会自动下载安装新版本。" : "确定要关闭自动升级功能吗?关闭后只能通过这里手动执行升级。"
teaweb.confirm(msg, function () {
vue.$post("/settings/upgrade")
.params({
autoUpgrade: newValue ? 1 : 0
})
.success(function () {
that.config.autoUpgrade = newValue
teaweb.successToast("设置保存成功")
})
.fail(function (resp) {
teaweb.warn("设置保存失败:" + resp.message)
})
})
}
})

View File

@@ -39,7 +39,7 @@
</td>
</tr>
<tr v-if="hasHTTPDNSFeature">
<td>HTTPDNS关联集群</td>
<td style="white-space: nowrap">HTTPDNS关联集群</td>
<td>
<select class="ui dropdown auto-width" name="httpdnsClusterId" v-model="httpdnsClusterId">
<option value="0">[未选择]</option>

View File

@@ -1,3 +1,6 @@
.ui.table.definition td.title {
min-width: 12em;
}
.feature-boxes .feature-box {
margin-bottom: 1em;
width: 24em;

View File

@@ -6,7 +6,7 @@
<table class="ui table definition selectable">
<tr>
<td class="title">允许注册</td>
<td class="title" style="white-space: nowrap">允许注册</td>
<td>
<checkbox name="isOn" v-model="config.isOn"></checkbox>
<p class="comment">选中表示允许用户自行注册。</p>
@@ -40,7 +40,7 @@
<h4>登录设置</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">检查客户端区域</td>
<td class="title" style="white-space: nowrap">检查客户端区域</td>
<td>
<checkbox name="checkClientRegion" v-model="config.checkClientRegion"></checkbox>
<p class="comment">选中后,表示每次用户访问时都检查客户端所在地理区域是否和登录时一致,以提升安全性;如果当前系统下游有反向代理设置,请在<a
@@ -53,7 +53,7 @@
<h4>电子邮箱相关</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">启用电子邮箱绑定功能</td>
<td class="title" style="white-space: nowrap">启用电子邮箱绑定功能</td>
<td>
<checkbox name="emailVerificationIsOn" v-model="config.emailVerification.isOn"></checkbox>
<p class="comment">选中后,电子邮箱需要激活之后可以使用邮箱登录、找回密码等。此功能需要事先设置 <a href="/users/setting/email"
@@ -107,7 +107,7 @@
<h4>通过邮箱找回密码</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">启用找回密码功能</td>
<td class="title" style="white-space: nowrap">启用找回密码功能</td>
<td>
<checkbox name="emailResetPasswordIsOn" v-model="config.emailResetPassword.isOn"></checkbox>
<p class="comment">选中后,用户可以通过已绑定的电子邮箱找回密码;此功能需要同时开启电子邮箱绑定功能。</p>
@@ -146,7 +146,7 @@
<h4>手机号码相关</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">启用手机号码绑定功能</td>
<td class="title" style="white-space: nowrap">启用手机号码绑定功能</td>
<td>
<checkbox name="mobileVerificationIsOn" v-model="config.mobileVerification.isOn"></checkbox>
<p class="comment">选中后,手机号码需要激活之后可以使用手机号码、找回密码等。此功能需要事先设置 <a href="/users/setting/sms"
@@ -196,7 +196,7 @@
<h4>CDN服务</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">开通CDN服务</td>
<td class="title" style="white-space: nowrap">开通CDN服务</td>
<td>
<checkbox name="cdnIsOn" v-model="config.cdnIsOn"></checkbox>
<p class="comment">选中表示自动为用户开通CDN服务。 </p>
@@ -254,7 +254,7 @@
<h4>DDoS高防</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">开通DDoS高防管理</td>
<td class="title" style="white-space: nowrap">开通DDoS高防管理</td>
<td>
<checkbox name="adIsOn" v-model="config.adIsOn"></checkbox>
<p class="comment">选中表示自动为用户开通DDoS高防IP使用服务。</p>
@@ -269,7 +269,7 @@
<h4>智能DNS服务</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">开通智能DNS服务</td>
<td class="title" style="white-space: nowrap">开通智能DNS服务</td>
<td>
<checkbox name="nsIsOn" v-model="config.nsIsOn"></checkbox>
<p class="comment">选中表示自动为用户开通智能DNS服务。使用默认集群资源如需使用其他集群请到用户新增和业务设置页面中进行选择。</p>
@@ -284,7 +284,7 @@
<h4>HTTPDNS服务</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">开通HTTPDNS服务</td>
<td class="title" style="white-space: nowrap">开通HTTPDNS服务</td>
<td>
<checkbox name="httpdnsIsOn" v-model="config.httpdnsIsOn"></checkbox>
<p class="comment">选中表示自动为用户开通HTTPDNS服务。使用默认集群资源如需使用其他集群请到用户新增和业务设置页面中进行选择。</p>