feat: sync httpdns sdk/platform updates without large binaries
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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"/> {{teaTitle}} <sup v-if="teaShowVersion">v{{teaVersion}}<span v-if="teaVersion.split('.').length == 4" title="当前版本为测试版,再次感谢您参与测试"> beta</span></sup>
|
||||
</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" />
|
||||
{{teaTitle}} <sup v-if="teaShowVersion">v{{teaVersion}}<span
|
||||
v-if="teaVersion.split('.').length == 4" title="当前版本为测试版,再次感谢您参与测试"> beta</span></sup>
|
||||
</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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
<td>
|
||||
v{{node.status.buildVersion}}
|
||||
|
||||
<a :href="'/httpdns/clusters/upgradeRemote?clusterId=' + clusterId + '&nodeId=' + node.id" v-if="shouldUpgrade"><span class="red">发现新版本 v{{newVersion}} »</span></a>
|
||||
<a :href="'/httpdns/clusters/cluster/upgradeRemote?clusterId=' + clusterId" v-if="shouldUpgrade"><span class="red">发现新版本 v{{newVersion}} »</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="node.status.exePath != null && node.status.exePath.length > 0">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}} -> 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>
|
||||
|
||||
147
EdgeAdmin/web/views/@default/httpdns/clusters/upgradeRemote.js
Normal file
147
EdgeAdmin/web/views/@default/httpdns/clusters/upgradeRemote.js
Normal 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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}} -> v{{latestVersion}}</span>
|
||||
</div>
|
||||
|
||||
@@ -112,4 +112,4 @@
|
||||
</table>
|
||||
|
||||
|
||||
<div class="page" v-html="page"></div>
|
||||
<div class="page" v-html="page"></div>
|
||||
|
||||
@@ -97,4 +97,4 @@
|
||||
</tr>
|
||||
</table>
|
||||
<p class="comment">数据更新于{{key.updatedTime}}。</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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>
|
||||
<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>
|
||||
@@ -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>
|
||||
-> <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>
|
||||
|
||||
457
EdgeAdmin/web/views/@default/settings/upgrade/index.js
Normal file
457
EdgeAdmin/web/views/@default/settings/upgrade/index.js
Normal 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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -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>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
.ui.table.definition td.title {
|
||||
min-width: 12em;
|
||||
}
|
||||
.feature-boxes .feature-box {
|
||||
margin-bottom: 1em;
|
||||
width: 24em;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user