This commit is contained in:
unknown
2026-02-04 20:27:13 +08:00
commit 3b042d1dad
9410 changed files with 1488147 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
{$layout}
<first-menu>
<a href="" class="item" @click.prevent="createNode()">[添加节点]</a>
<a href="/settings/api/methodStats" class="item" v-if="hasMethodStats">用时统计</a>
</first-menu>
<p class="comment" v-if="nodes.length == 0">暂时还没有节点。</p>
<div class="table-box">
<table class="ui table selectable celled" v-if="nodes.length > 0">
<thead>
<tr>
<th>节点名称</th>
<th>GRPC访问地址</th>
<th>HTTP访问地址</th>
<th class="width6 center">版本号</th>
<th class="width5 center">CPU</th>
<th class="width5 center">内存</th>
<th class="center width10">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="node in nodes">
<td><a :href="'/settings/api/node?nodeId=' + node.id">{{node.name}}</a>
<div v-if="node.isPrimary">
<grey-label v-if="node.isPrimary">主节点</grey-label>
</div>
<div v-if="node.status != null && node.status.shouldUpgrade">
<span class="red small">v{{node.status.buildVersion}} -&gt; v{{node.status.latestVersion}}<br/><a href="" v-if="node.status.canUpgrade" @click.prevent="upgradeNode(node.id)">[远程升级]</a> </span>
</div>
</td>
<td>
<div v-if="node.accessAddrs != null && node.accessAddrs.length > 0">
<div v-for="addr in node.accessAddrs">
<span class="ui label tiny basic" style="margin-bottom: 0.5em">{{addr}}</span>
</div>
</div>
<div v-else class="disabled">-</div>
</td>
<td>
<div v-if="node.restAccessAddrs != null && node.restAccessAddrs.length > 0">
<div v-for="addr in node.restAccessAddrs">
<span class="ui label tiny basic" style="margin-bottom: 0.5em">{{addr}}</span>
</div>
</div>
<div v-else class="disabled">-</div>
</td>
<td class="center">
<span v-if="node.status.buildVersion.length > 0">v{{node.status.buildVersion}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">
<span v-if="node.status.isActive" :class="{red:node.status.cpuUsage > 0.80}">{{node.status.cpuUsageText}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">
<span v-if="node.status.isActive" :class="{red:node.status.memUsage > 0.80}">{{node.status.memUsageText}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">
<span v-if="!node.isOn"><label-on :v-is-on="node.isOn"></label-on></span>
<div v-else-if="node.status.isActive">
<span class="green">运行中</span>
</div>
<span v-else-if="node.status.updatedAt > 0" class="red">已断开</span>
<span v-else-if="node.status.updatedAt == 0" class="red">未连接</span>
</td>
<td>
<a :href="'/settings/api/node?nodeId=' + node.id">详情</a> &nbsp;
<a href="" @click.prevent="deleteNode(node.id)">删除</a>
</td>
</tr>
</table>
</div>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,35 @@
Tea.context(function () {
// 创建节点
this.createNode = function () {
teaweb.popup(".node.createPopup", {
width: "50em",
height: "30em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
// 删除节点
this.deleteNode = function (nodeId) {
let that = this
teaweb.confirm("确定要删除此节点吗?", function () {
that.$post(".delete")
.params({
nodeId: nodeId
})
.refresh()
})
}
// 升级节点
this.upgradeNode = function (nodeId) {
teaweb.popup(".node.upgradePopup?nodeId=" + nodeId, {
onClose: function () {
teaweb.reload()
}
})
}
})

View File

@@ -0,0 +1,46 @@
{$layout}
<div class="margin"></div>
<form method="get" action="/settings/api/methodStats" class="ui form">
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="method" v-model="method" placeholder="方法"/>
</div>
<div class="ui field">
<input type="text" name="tag" v-model="tag" placeholder="标签"/>
</div>
<div class="ui field">
<select class="ui dropdown auto-width" name="order" v-model="order">
<option value="">[排序]</option>
<option value="method">方法</option>
<option value="costMs.desc">平均耗时</option>
<option value="peekMs.desc">峰值耗时</option>
<option value="calls.desc">调用次数</option>
</select>
</div>
<div class="ui field">
<button class="ui button" type="submit">搜索</button>
&nbsp;
<a href="/settings/api/methodStats" v-if="method.length > 0 || tag.length > 0 || order.length > 0">清除条件</a>
</div>
</div>
</form>
<table class="ui table selectable celled">
<thead>
<tr>
<th>方法</th>
<th>标签</th>
<th>平均耗时</th>
<th>峰值耗时</th>
<td>调用次数</td>
</tr>
</thead>
<tr v-for="stat in stats">
<td>{{stat.method}}</td>
<td>{{stat.tag}}</td>
<td>{{stat.costMs}}ms</td>
<td>{{stat.peekMs}}ms</td>
<td>{{stat.countCalls}}次</td>
</tr>
</table>

View File

@@ -0,0 +1,8 @@
<first-menu>
<menu-item href="/settings/api">节点列表</menu-item>
<span class="item">|</span>
<menu-item :href="'/settings/api/node?nodeId=' + node.id" code="index">"{{node.name}}"详情</menu-item>
<menu-item :href="'/settings/api/node/logs?nodeId=' + node.id" code="log">运行日志</menu-item>
<menu-item :href="'/settings/api/node/install?nodeId=' + node.id" code="install">安装节点</menu-item>
<menu-item :href="'/settings/api/node/update?nodeId=' + node.id" code="update">修改节点</menu-item>
</first-menu>

View File

@@ -0,0 +1,24 @@
{$layout "layout_popup"}
<h3>添加访问地址</h3>
<form class="ui form" method="post" data-tea-action="$" data-tea-success="success">
<table class="ui table definition selectable">
<tr>
<td>网络协议</td>
<td>
<select class="ui dropdown auto-width" name="protocol">
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</select>
</td>
</tr>
<tr>
<td class="title">访问地址</td>
<td>
<input type="text" name="addr" maxlength="100" ref="focus"/>
<p class="comment">可以是"IP:端口"或者"域名:端口"。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,68 @@
{$layout "layout_popup"}
<h3>添加API节点</h3>
<form class="ui form" method="post" data-tea-action="$" data-tea-success="success">
<table class="ui table selectable definition">
<tr>
<td class="title">节点名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus"/>
</td>
</tr>
<tr>
<td>GRPC监听端口 *</td>
<td>
<network-addresses-box :v-name="'listensJSON'" :v-server-type="'httpWeb'" @change="changeListens" :v-from="'apiNode'"></network-addresses-box>
<p class="comment">通过GRPC访问API节点进程监听的网络端口。</p>
</td>
</tr>
<tr v-if="hasHTTPS">
<td>HTTPS证书 *</td>
<td>
<ssl-certs-box :v-protocol="'https'"></ssl-certs-box>
</td>
</tr>
<tr>
<td>GRPC 外部访问地址 *</td>
<td>
<api-node-addresses-box :v-name="'accessAddrsJSON'"></api-node-addresses-box>
<p class="comment">边缘节点和管理平台等外部节点访问API节点的网络地址需要保证其他节点能够通过此地址访问到API节点服务。<strong>访问地址里的端口号和监听端口通常一致,除非你做了端口转发。</strong></p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td :class="{'color-border': restIsOn}">开启HTTP API端口</td>
<td>
<checkbox name="restIsOn" v-model="restIsOn">启用HTTP API</checkbox>
<p class="comment">启用后用户可以通过HTTP API调用服务接口。</p>
</td>
</tr>
<tr v-if="restIsOn">
<td class="color-border">HTTP API监听端口 *</td>
<td>
<network-addresses-box :v-name="'restListensJSON'" :v-server-type="'httpWeb'" @change="changeRestListens"></network-addresses-box>
<p class="comment">HTTP API节点进程监听的网络端口需要和当前节点的GRPC端口不同。</p>
</td>
</tr>
<tr>
<td>描述</td>
<td>
<textarea name="description" maxlength="200" rows="3"></textarea>
</td>
</tr>
<tr>
<td>启用当前节点</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" checked="checked"/>
<label></label>
</div>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,27 @@
Tea.context(function () {
this.hasHTTPS = false
this.grpcAddrs = []
this.restAddrs = []
this.changeListens = function (addrs) {
this.grpcAddrs = addrs
this.hasHTTPS = this.grpcAddrs.$any(function (k, v) {
return v.protocol == "https"
}) || (this.restIsOn && this.restAddrs.$any(function (k, v) {
return v.protocol == "https"
}))
}
this.changeRestListens = function (addrs) {
this.restAddrs = addrs
this.hasHTTPS = this.grpcAddrs.$any(function (k, v) {
return v.protocol == "https"
}) || (this.restIsOn && this.restAddrs.$any(function (k, v) {
return v.protocol == "https"
}))
}
this.restIsOn = false
})

View File

@@ -0,0 +1,80 @@
{$layout}
{$template "menu"}
<table class="ui table selectable definition">
<tr>
<td class="title">节点名称</td>
<td>
{{node.name}}
</td>
</tr>
<tr>
<td>状态</td>
<td>
<label-on :v-is-on="node.isOn"></label-on>
</td>
</tr>
<tr>
<td>GRPC监听端口</td>
<td>
<network-addresses-view :v-addresses="node.listens"></network-addresses-view>
<p class="comment">API节点进程监听的网络端口。</p>
</td>
</tr>
<tr v-if="node.hasHTTPS">
<td>HTTPS证书</td>
<td>
<div v-if="node.certs != null && node.certs.length > 0">
<ssl-certs-view :v-certs="node.certs"></ssl-certs-view>
</div>
<span v-else class="red">还没有设置证书可能会导致HTTPS相关服务不可用。</span>
</td>
</tr>
<tr>
<td>GRPC外部访问地址</td>
<td>
<network-addresses-view :v-addresses="node.accessAddrs"></network-addresses-view>
<p class="comment">通过GRPC访问API节点的网络地址。</p>
</td>
</tr>
<tr v-if="node.restIsOn">
<td>HTTP外部访问地址</td>
<td>
<network-addresses-view :v-addresses="node.restAccessAddrs"></network-addresses-view>
<p class="comment">通过HTTP访问API节点的网络地址。</p>
</td>
</tr>
<tr v-if="node.statusIsValid">
<td>CPU用量</td>
<td>{{node.status.cpuUsageText}} &nbsp; <span v-if="node.status.cpuPhysicalCount > 0" class="small grey">{{node.status.cpuPhysicalCount}}核心/{{node.status.cpuLogicalCount}}线程)</span></td>
</tr>
<tr v-if="node.statusIsValid">
<td>内存用量</td>
<td>{{node.status.memUsageText}}</td>
</tr>
<tr v-if="node.statusIsValid">
<td>版本</td>
<td>v{{node.status.buildVersion}}
&nbsp; <span class="red" v-if="newVersion.length > 0">需要升级到新版本v{{newVersion}}</span>
</td>
</tr>
<tr v-if="node.statusIsValid">
<td>主程序位置</td>
<td>{{node.status.exePath}}</td>
</tr>
<tr>
<td>主节点</td>
<td>
<span v-if="node.isPrimary" class="green">Y</span>
<span v-else class="disabled">N</span>
<p class="comment" v-if="node.isPrimary">设置为主节点后,主要的后台任务会在此节点上运行。</p>
</td>
</tr>
<tr>
<td>描述</td>
<td>
<span v-if="node.description.length > 0">{{node.description}}</span>
<span v-else class="disabled">暂时还没有描述。</span>
</td>
</tr>
</table>

View File

@@ -0,0 +1,36 @@
{$layout}
{$template "menu"}
{$template "/code_editor"}
<h3>安装步骤</h3>
<ol class="ui list">
<li>按照下面的配置信息替换<code-label>configs/api.yaml</code-label>内容;如果文件不存在,请先创建</li>
<li>按照下面的配置信息替换<code-label>configs/db.yaml</code-label>内容;如果文件不存在,请先创建</li>
<li>请修改*号为真实的数据库密码,并保存</li>
<li>使用<code-label>bin/edge-api start</code-label>启动节点</li>
<li>可以在<code-label>logs/run.log</code-label>中查看启动是否有异常</li>
<li>配置修改后,使用<code-label>bin/edge-api restart</code-label>重启节点</li>
</ol>
<div class="ui divider"></div>
<h3>配置信息</h3>
<table class="ui table definition selectable">
<tr>
<td class="title">configs/api.yaml<em><br/><download-link :v-element="'api-code'" :v-file="'api.yaml'">[下载]</download-link></em></td>
<td>
<source-code-box id="api-code" type="text/yaml">nodeId: "{{node.uniqueId}}"
secret: "{{node.secret}}"</source-code-box>
</td>
</tr>
<tr>
<td>configs/db.yaml<em><br/><download-link :v-element="'db-code'" :v-file="'db.yaml'">[下载]</download-link></em></td>
<td>
<span class="red" v-if="dbConfig.error.length > 0">
<span v-if="dbConfig.isNotFound">找不到数据库配置文件configs/api_db.yaml ,请重新 <a href="/settings/database">[配置数据库]</a></span>
<span v-else class="red">无法生成配置内容,错误原因:</span>
{{dbConfig.error}}
</span>
<source-code-box id="db-code" type="text/yaml">{{dbConfig.config}}</source-code-box>
</td>
</tr>
</table>

View File

@@ -0,0 +1,5 @@
pre.log-box {
margin: 0;
padding: 0;
}
/*# sourceMappingURL=logs.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["logs.less"],"names":[],"mappings":"AAAA,GAAG;EACF,SAAA;EACA,UAAA","file":"logs.css"}

View File

@@ -0,0 +1,51 @@
{$layout}
{$template "menu"}
{$template "/datepicker"}
<div class="margin"></div>
<form method="get" action="/settings/api/node/logs" class="ui form" autocomplete="off">
<input type="hidden" name="nodeId" :value="nodeId"/>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="dayFrom" placeholder="开始日期" v-model="dayFrom" value="" style="width:8em" id="day-from-picker"/>
</div>
<div class="ui field">
<input type="text" name="dayTo" placeholder="结束日期" v-model="dayTo" value="" style="width:8em" id="day-to-picker"/>
</div>
<div class="ui field">
<select class="ui dropdown" name="level" v-model="level">
<option value="">[级别]</option>
<option value="error">错误</option>
<option value="warning">警告</option>
<option value="info">信息</option>
<option value="success">成功</option>
</select>
</div>
<div class="ui field">
<input type="text" name="keyword" style="width:10em" v-model="keyword" placeholder="关键词"/>
</div>
<div class="ui field">
<button type="submit" class="ui button">查询</button>
</div>
<div class="ui field" v-if="dayFrom.length > 0 || dayTo.length > 0 || keyword.length > 0 || level.length > 0">
<a :href="'/settings/api/node/logs?nodeId=' + nodeId">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="logs.length == 0">暂时还没有日志。</p>
<table class="ui table selectable" v-if="logs.length > 0">
<thead>
<tr>
</tr>
</thead>
<tr v-for="log in logs">
<td>
<node-log-row :v-log="log" :v-keyword="keyword"></node-log-row>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,6 @@
Tea.context(function () {
this.$delay(function () {
teaweb.datepicker("day-from-picker")
teaweb.datepicker("day-to-picker")
})
})

View File

@@ -0,0 +1,4 @@
pre.log-box {
margin: 0;
padding: 0;
}

View File

@@ -0,0 +1,80 @@
{$layout}
{$template "menu"}
<div class="ui warning message"><i class="icon warning circle"></i>如果你修改了GRPC访问地址、端口、证书相关信息时需要重启<code-label>edge-api</code-label>进程(<code-label>edge-node restart</code-label>)才能生效,并可能需要手工修改其他节点的配置信息。</div>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="nodeId" :value="node.id"/>
<input type="hidden" name="sslPolicyId" :value="node.sslPolicyId"/>
<table class="ui table selectable definition">
<tr>
<td class="title">节点名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus" v-model="node.name"/>
</td>
</tr>
<tr>
<td>GRPC监听端口 *</td>
<td>
<network-addresses-box :v-name="'listensJSON'" :v-server-type="'httpWeb'" :v-addresses="node.listens" @change="changeListens" :v-from="'apiNode'"></network-addresses-box>
<p class="comment">通过GRPC访问API节点进程监听的网络端口。</p>
</td>
</tr>
<tr v-if="hasHTTPS">
<td>HTTPS证书 *</td>
<td>
<ssl-certs-box :v-certs="node.certs" :v-protocol="'https'"></ssl-certs-box>
</td>
</tr>
<tr>
<td>GRPC访问地址 *</td>
<td>
<api-node-addresses-box :v-name="'accessAddrsJSON'" :v-addrs="node.accessAddrs"></api-node-addresses-box>
<p class="comment">边缘节点和管理平台等外部节点访问API节点的网络地址需要保证其他节点能够通过此地址访问到API节点服务。<strong>访问地址里的端口号和监听端口通常一致,除非你做了端口转发。</strong></p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td :class="{'color-border': node.restIsOn}">开启HTTP API端口</td>
<td>
<checkbox name="restIsOn" v-model="node.restIsOn">启用HTTP API</checkbox>
<p class="comment">启用后用户可以通过HTTP API调用服务接口。</p>
</td>
</tr>
<tr v-if="node.restIsOn">
<td class="color-border">HTTP API监听端口 *</td>
<td>
<network-addresses-box :v-name="'restListensJSON'" :v-server-type="'httpWeb'" @change="changeRestListens" :v-addresses="node.restListens"></network-addresses-box>
<p class="comment">HTTP API节点进程监听的网络端口需要和当前节点的GRPC端口不同。</p>
</td>
</tr>
<tr>
<td>描述</td>
<td>
<textarea name="description" maxlength="200" rows="3" v-model="node.description"></textarea>
</td>
</tr>
<tr>
<td>主节点</td>
<td>
<checkbox name="isPrimary" v-model="node.isPrimary"></checkbox>
<p class="comment">设置为主节点后,主要的后台任务会在此节点上运行。</p>
</td>
</tr>
<tr>
<td>启用当前节点</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="node.isOn"/>
<label></label>
</div>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,31 @@
Tea.context(function () {
this.success = NotifySuccess("保存成功", "/settings/api/node?nodeId=" + this.node.id)
this.hasHTTPS = this.node.listens.$any(function (k, v) {
return v.protocol == "https"
}) || (this.node.restIsOn && this.node.restListens.$any(function (k, v) {
return v.protocol == "https"
}))
this.grpcAddrs = []
this.restAddrs = []
this.changeListens = function (addrs) {
this.grpcAddrs = addrs
this.hasHTTPS = this.grpcAddrs.$any(function (k, v) {
return v.protocol == "https"
}) || (this.node.restIsOn && this.restAddrs.$any(function (k, v) {
return v.protocol == "https"
}))
}
this.changeRestListens = function (addrs) {
this.restAddrs = addrs
this.hasHTTPS = this.grpcAddrs.$any(function (k, v) {
return v.protocol == "https"
}) || (this.node.restIsOn && this.restAddrs.$any(function (k, v) {
return v.protocol == "https"
}))
}
})

View File

@@ -0,0 +1,24 @@
{$layout "layout_popup"}
<h3>修改访问地址</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<table class="ui table definition selectable">
<tr>
<td>网络协议</td>
<td>
<select class="ui dropdown auto-width" name="protocol" v-model="protocol">
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</select>
</td>
</tr>
<tr>
<td class="title">访问地址</td>
<td>
<input type="text" name="addr" maxlength="100" ref="focus" v-model="addr"/>
<p class="comment">可以是"IP:端口"或者"域名:端口"。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,5 @@
Tea.context(function () {
let addr = window.parent.UPDATING_ADDR
this.protocol = addr.protocol
this.addr = addr.host.quoteIP() + ":" + addr.portRange
})

View File

@@ -0,0 +1,35 @@
{$layout "layout_popup"}
<h3>远程升级</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success" data-tea-timeout="3600" data-tea-before="before" data-tea-done="done">
<csrf-token></csrf-token>
<input type="hidden" name="nodeId" :value="nodeId"/>
<table class="ui table definition selectable">
<tr v-show="nodeName.length > 0">
<td class="title">API节点</td>
<td>{{nodeName}}</td>
</tr>
<tr v-show="currentVersion.length > 0">
<td>当前版本</td>
<td>v{{currentVersion}}</td>
</tr>
<tr v-show="latestVersion.length > 0">
<td>目标版本</td>
<td>v{{latestVersion}}</td>
</tr>
<tr>
<td class="title">升级结果</td>
<td>
<span v-if="currentVersion == latestVersion" class="green">已经升级到最新版本</span>
<span :class="{red: !resultIsOk}" v-if="currentVersion != latestVersion">
<span v-if="!isRequesting">{{result}}</span>
<span v-if="isRequesting">升级中...</span>
</span>
</td>
</tr>
</table>
<submit-btn v-show="canUpgrade && !isRequesting && !isUpgrading">开始升级</submit-btn>
</form>

View File

@@ -0,0 +1,39 @@
Tea.context(function () {
this.$delay(function () {
this.checkLoop()
})
this.success = function () {
}
this.isRequesting = false
this.before = function () {
this.isRequesting = true
}
this.done = function () {
this.isRequesting = false
}
this.checkLoop = function () {
if (this.currentVersion == this.latestVersion) {
return
}
this.$post(".upgradeCheck")
.params({
nodeId: this.nodeId
})
.success(function (resp) {
if (resp.data.isOk) {
teaweb.reload()
}
})
.done(function () {
this.$delay(function () {
this.checkLoop()
}, 3000)
})
}
})

View File

@@ -0,0 +1,6 @@
<first-menu>
<menu-item href="/settings/authority" code="index">认证信息</menu-item>
<!--<menu-item href="/settings/authority/nodes" code="node">认证节点</menu-item>-->
<menu-item href="/settings/authority/apply" code="apply">申请</menu-item>
<menu-item href="/settings/authority/activate" code="activate">激活</menu-item>
</first-menu>

View File

@@ -0,0 +1,5 @@
textarea {
line-height: 1.5 !important;
font-size: 0.9em !important;
}
/*# sourceMappingURL=activate.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["activate.less"],"names":[],"mappings":"AAAA;EACC,gBAAA;EACA,gBAAA","file":"activate.css"}

View File

@@ -0,0 +1,19 @@
{$layout}
{$template "menu"}
<div class="margin"></div>
<form class="ui form" data-tea-action="$" data-tea-success="success" data-tea-before="before" data-tea-done="done" data-tea-timeout="1800" spellcheck="false">
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">注册码 *</td>
<td>
<textarea rows="8" name="key" ref="focus"></textarea>
<p class="comment">请输入商业版/企业版注册码。</p>
</td>
</tr>
</table>
<submit-btn v-show="!isRequesting">激活</submit-btn>
<button class="ui button disabled" v-show="isRequesting">激活中...</button>
</form>

View File

@@ -0,0 +1,12 @@
Tea.context(function () {
this.success = NotifySuccess("激活成功", "/settings/authority")
this.isRequesting = false
this.before = function () {
this.isRequesting = true
}
this.done = function () {
this.isRequesting = false
}
})

View File

@@ -0,0 +1,4 @@
textarea {
line-height: 1.5 !important;
font-size: 0.9em !important;
}

View File

@@ -0,0 +1,6 @@
textarea {
line-height: 1.5 !important;
font-size: 0.9em !important;
background: #eee !important;
}
/*# sourceMappingURL=apply.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["apply.less"],"names":[],"mappings":"AAAA;EACC,gBAAA;EACA,gBAAA;EACA,gBAAA","file":"apply.css"}

View File

@@ -0,0 +1,13 @@
{$layout}
{$template "menu"}
<div class="margin"></div>
<div style="margin-bottom: 0.5em">购买商业版后或者申请免费试用时,需要向本系统开发者官方先提交申请码。</div>
<form class="ui form">
<textarea rows="4" autocomplete="off" spellcheck="false">{{requestCode}}</textarea>
</form>
<div style="line-height: 1.8; margin-top: 0.5em"><strong>请拷贝上面的申请码发送给系统开发者,以便生成注册码</strong>。注册码生成后,只能在当前服务器上使用管理平台;如需迁移当前管理系统到别的服务器,请向官方重新申请注册码(重新申请后,商业版有效期保持不变)。</div>
<div style="line-height: 1.8; margin-top: 0.5em">虚拟机或容器重启、系统升级、配置变更等可能会导致申请码失效,请小心操作。</div>

View File

@@ -0,0 +1,5 @@
textarea {
line-height: 1.5 !important;
font-size: 0.9em !important;
background: #eee !important;
}

View File

@@ -0,0 +1,7 @@
.message .icon.gem {
font-size: 1.2em !important;
}
.message ul {
line-height: 1.8;
}
/*# sourceMappingURL=index.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,QAAS,MAAK;EACb,gBAAA;;AAGD,QACC;EACC,gBAAA","file":"index.css"}

View File

@@ -0,0 +1,100 @@
{$layout}
{$template "menu"}
<div v-if="key == null">
<div class="margin"></div>
<div class="ui message error icon" v-if="plusErr.length > 0">
<i class="icon warning circle"></i>
出现异常错误,请重新激活:{{plusErr}}
</div>
<div>
<warning-message>
你暂时尚未激活商业版本:
<ul>
<li><a href="https://goedge.cn/commercial" target="_blank">了解专业版</a> </li>
<li><a href="https://goedge.cn/docs/Plus/Price.md" target="_blank">价格体系</a> </li>
<li><a href="https://goedge.taobao.com/" target="_blank">淘宝购买专业版</a></li>
<li><a href="https://goedge.cn/community" target="_blank">其他方式购买专业版</a> </li>
<li><a href="https://goedge.cn/community" target="_blank">联系试用</a> </li>
</ul>
</warning-message>
<warning-message>
<a href="/settings/authority/activate">如果你已经有注册码,点这里激活 &raquo;</a>
</warning-message>
</div>
</div>
<div v-if="key != null">
<div class="margin"></div>
<div class="ui message green icon" v-if="!key.isExpired && plusErr.length == 0">
<i class="icon gem outline small yellow"></i>
<div class="content">
恭喜您已经成为尊贵的商业版用户。
</div>
</div>
<div class="ui message error icon" v-if="plusErr.length > 0">
<i class="icon warning circle"></i>
{{plusErr}}
</div>
<div class="margin"></div>
<table class="ui table definition selectable">
<tr>
<td class="title">状态</td>
<td>
<span class="red" v-if="key.isExpiring">即将过期</span>
<span class="red" v-else-if="key.isExpired">已过期</span>
<span class="green" v-else>已认证</span>
</td>
</tr>
<tr v-if="key.editionName != null && key.editionName.length > 0">
<td>版本</td>
<td>{{key.editionName}}</td>
</tr>
<tr v-if="key.nodes > 0">
<td>节点数限制</td>
<td>{{key.nodes}}个节点<span class="small grey">(已使用{{quota.countNodes}}个节点)</span></td>
</tr>
<tr>
<td>公司/组织名</td>
<td>{{key.company}}
<div v-if="key.company != null && key.company.indexOf('试用') > 0">
<span class="red">试用版本</span>
</div>
</td>
</tr>
<tr>
<td class="title">开始日期</td>
<td>{{key.dayFrom}}</td>
</tr>
<tr>
<td class="title">结束日期</td>
<td><span :class="{green: key.dayTo == '终身授权' ? 'green' : ''}">{{key.dayTo}}</span>
<div v-if="key.isExpiring" style="margin-top: 0.5em">
<span class="red">即将过期</span>
</div>
<div v-else-if="key.isExpired" style="margin-top: 0.5em">
<span class="red">已过期,请尽快更新认证信息。</span>
</div>
</td>
</tr>
<tr>
<td>开通组件</td>
<td>
<span v-if="key.components.length == 0" class="ui label basic small">*</span>
<span v-for="c in key.components" class="ui label basic small">{{c}}</span>
</td>
</tr>
<tr>
<td>验证方式</td>
<td>
<span v-if="key.method == 'remote'">远程</span>
<span v-else>离线</span>
</td>
</tr>
</table>
<p class="comment">数据更新于{{key.updatedTime}}。</p>
</div>

View File

@@ -0,0 +1,9 @@
.message .icon.gem {
font-size: 1.2em!important;
}
.message {
ul {
line-height: 1.8;
}
}

View File

@@ -0,0 +1,3 @@
{$layout}
<p class="comment">此功能暂未开放,敬请期待。</p>

View File

@@ -0,0 +1,28 @@
{$layout "layout_popup"}
<h3>修改浏览器信息</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="browserId" :value="browser.id"/>
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">浏览器名称 *</td>
<td>
<input type="text" name="name" ref="focus" v-model="browser.name"/>
</td>
</tr>
<tr>
<td>代号</td>
<td>
<values-box name="codes" :v-values="browser.codes"></values-box>
</td>
</tr>
<tr>
<td>数据ID *</td>
<td>
<input type="text" name="dataId" maxlength="10" style="width: 10em" v-model="browser.dataId"/>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,27 @@
{$layout "layout_popup"}
<h3>创建浏览器信息</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">浏览器名称 *</td>
<td>
<input type="text" name="name" ref="focus"/>
</td>
</tr>
<tr>
<td>代号</td>
<td>
<values-box name="codes"></values-box>
</td>
</tr>
<tr>
<td>数据ID *</td>
<td>
<input type="text" name="dataId" maxlength="10" style="width: 10em"/>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,42 @@
{$layout}
<second-menu>
<menu-item @click.prevent="createBrowser">[添加新浏览器]</menu-item>
</second-menu>
<form class="ui form" method="get" action="/settings/client-browsers">
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="keyword" v-model="keyword" placeholder="关键词"/>
</div>
<div class="ui field">
<button type="submit" class="ui button">搜索</button>&nbsp;
<a href="/settings/client-browsers" v-if="keyword.length > 0">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="browsers.length == 0">暂时还没有浏览器信息。</p>
<table class="ui table selectable celled" v-if="browsers.length > 0">
<thead>
<tr>
<th class="three wide">浏览器名称</th>
<th>代号</th>
<th class="two wide">数据ID</th>
<th class="one op">操作</th>
</tr>
</thead>
<tr v-for="browser in browsers">
<td>{{browser.name}}</td>
<td>
<span class="ui label basic" v-for="code in browser.codes"><keyword :v-word="keyword">{{code}}</keyword></span>
</td>
<td>{{browser.dataId}}</td>
<td>
<a href="" @click.prevent="updateBrowser(browser.dataId)">修改</a>
</td>
</tr>
</table>
<page-box></page-box>

View File

@@ -0,0 +1,19 @@
Tea.context(function () {
this.createBrowser = function () {
teaweb.popup(".createPopup", {
height: "24em",
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
this.updateBrowser = function (dataId) {
teaweb.popup(".browser.updatePopup?dataId=" + dataId, {
height: "24em",
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
})

View File

@@ -0,0 +1,27 @@
{$layout "layout_popup"}
<h3>创建操作系统信息</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">操作系统名称 *</td>
<td>
<input type="text" name="name" ref="focus"/>
</td>
</tr>
<tr>
<td>代号</td>
<td>
<values-box name="codes"></values-box>
</td>
</tr>
<tr>
<td>数据ID *</td>
<td>
<input type="text" name="dataId" maxlength="10" style="width: 10em"/>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,42 @@
{$layout}
<second-menu>
<menu-item @click.prevent="createSystem">[添加新操作系统]</menu-item>
</second-menu>
<form class="ui form" method="get" action="/settings/client-systems">
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="keyword" v-model="keyword" placeholder="关键词"/>
</div>
<div class="ui field">
<button type="submit" class="ui button">搜索</button>&nbsp;
<a href="/settings/client-systems" v-if="keyword.length > 0">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="systems.length == 0">暂时还没有操作系统信息。</p>
<table class="ui table selectable celled" v-if="systems.length > 0">
<thead>
<tr>
<th class="three wide">操作系统名称</th>
<th>代号</th>
<th class="two wide">数据ID</th>
<th class="one op">操作</th>
</tr>
</thead>
<tr v-for="system in systems">
<td>{{system.name}}</td>
<td>
<span class="ui label basic" v-for="code in system.codes"><keyword :v-word="keyword">{{code}}</keyword></span>
</td>
<td>{{system.dataId}}</td>
<td>
<a href="" @click.prevent="updateSystem(system.dataId)">修改</a>
</td>
</tr>
</table>
<page-box></page-box>

View File

@@ -0,0 +1,19 @@
Tea.context(function () {
this.createSystem = function () {
teaweb.popup(".createPopup", {
height: "24em",
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
this.updateSystem = function (dataId) {
teaweb.popup(".system.updatePopup?dataId=" + dataId, {
height: "24em",
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
})

View File

@@ -0,0 +1,28 @@
{$layout "layout_popup"}
<h3>修改操作系统信息</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="systemId" :value="system.id"/>
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">操作系统名称 *</td>
<td>
<input type="text" name="name" ref="focus" v-model="system.name"/>
</td>
</tr>
<tr>
<td>代号</td>
<td>
<values-box name="codes" :v-values="system.codes"></values-box>
</td>
</tr>
<tr>
<td>数据ID *</td>
<td>
<input type="text" name="dataId" maxlength="10" style="width: 10em" v-model="system.dataId"/>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,10 @@
<first-menu>
<menu-item href="/settings/database" code="index">配置模板</menu-item>
<menu-item href="/settings/database/update" code="update">修改模板</menu-item>
<span class="item disabled">|</span>
<menu-item href="/settings/database/clean" code="clean">手动清理</menu-item>
<menu-item href="/settings/database/cleanSetting" code="cleanSetting">自动清理设置</menu-item>
<span class="item disabled">|</span>
<span class="item"><tip-icon content="在这里可以设置API节点可以使用的数据库修改后请重新配置并启动API节点才能生效。"></tip-icon></span>
</first-menu>
<div class="margin"></div>

View File

@@ -0,0 +1,30 @@
{$layout}
{$template "menu"}
<div class="ui message" v-if="isLoading">正在加载中...</div>
<div class="ui message small warning" v-if="tables.length > 0">
<p>请确认数据表用途后再执行相关操作。</p>
</div>
<table class="ui table selectable" v-if="tables.length > 0">
<thead>
<tr>
<th>数据表名<sort-arrow name="orderTable"></sort-arrow></th>
<th>占用空间<sort-arrow name="orderSize"></sort-arrow></th>
<th>用途</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="table in tables">
<td>{{table.name}}</td>
<td>{{table.size}}
<span class="grey small" v-if="table.rows > 0">{{table.rows}}行)</span>
</td>
<td>{{table.comment}}</td>
<td>
<a href="" v-if="table.canDelete" @click.prevent="deleteTable(table.name)">删除</a><span v-if="table.canDelete"> &nbsp;</span>
<a href="" v-if="table.canClean" @click.prevent="truncateTable(table.name)">清空</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,53 @@
Tea.context(function () {
this.tables = []
this.isLoading = true
this.$delay(function () {
this.reload()
})
this.reload = function () {
this.isLoading = true
this.$post("$")
.params({
orderTable: this.orderTable,
orderSize: this.orderSize
})
.success(function (resp) {
this.tables = resp.data.tables;
})
.done(function () {
this.isLoading = false
})
}
this.deleteTable = function (tableName) {
let that = this
teaweb.confirm("html:确定要删除此数据表吗?<br/>删除后数据不能恢复!", function () {
that.$post(".deleteTable")
.params({
table: tableName
})
.success(function () {
teaweb.success("操作成功", function () {
that.reload()
})
})
})
}
this.truncateTable = function (tableName) {
let that = this
teaweb.confirm("html:确定要清空此数据表吗?<br/>清空后数据不能恢复!", function () {
that.$post(".truncateTable")
.params({
table: tableName
})
.success(function () {
teaweb.success("操作成功", function () {
that.reload()
})
})
})
}
})

View File

@@ -0,0 +1,189 @@
{$layout}
{$template "menu"}
<form class="ui form" data-tea-success="success" data-tea-action="$">
<csrf-token></csrf-token>
<h4 style="margin-top: 0">网站访问日志</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">数据保存天数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="serverAccessLogCleanDays" v-model="config.serverAccessLog.clean.days" style="width:6em" maxlength="6"/>
<span class="ui label"></span>
</div>
<p class="comment">天数包括当天。如果填0表示不自动清理。此设置也会同时作用于日志数据库。</p>
</td>
</tr>
</table>
<div>
<a href=""><more-options-indicator>更多设置</more-options-indicator></a>
<div class="margin"></div>
</div>
<div v-show="moreOptionsVisible">
<div class="ui divider"></div>
<h4 style="margin-top: 0">网站带宽统计</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">数据保存天数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="serverBandwidthStatCleanDays" v-model="config.serverBandwidthStat.clean.days" style="width:6em" maxlength="6"/>
<span class="ui label"></span>
</div>
<p class="comment"><pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<h4 style="margin-top: 0">用户带宽统计</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">数据保存天数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="userBandwidthStatCleanDays" v-model="config.userBandwidthStat.clean.days" style="width:6em" maxlength="6"/>
<span class="ui label"></span>
</div>
<p class="comment"><pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<h4 style="margin-top: 0">用户套餐带宽统计</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">数据保存天数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="userPlanBandwidthStatCleanDays" v-model="config.userPlanBandwidthStat.clean.days" style="width:6em" maxlength="6"/>
<span class="ui label"></span>
</div>
<p class="comment"><pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<h4 style="margin-top: 0">网站流量统计</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">数据保存天数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="serverDailyStatCleanDays" v-model="config.serverDailyStat.clean.days" style="width:6em" maxlength="6"/>
<span class="ui label"></span>
</div>
<p class="comment"><pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<div class="ui divider"></div>
<h4 style="margin-top: 0">网站按小时域名统计</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">数据保存天数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="serverDomainHourlyStatCleanDays" v-model="config.serverDomainHourlyStat.clean.days" style="width:6em" maxlength="6"/>
<span class="ui label"></span>
</div>
<p class="comment"><pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<h4 style="margin-top: 0">系统整体按天流量统计</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">数据保存天数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="trafficDailyStatCleanDays" v-model="config.trafficDailyStat.clean.days" style="width:6em" maxlength="6"/>
<span class="ui label"></span>
</div>
<p class="comment"><pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<h4 style="margin-top: 0">系统整体按小时流量统计</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">数据保存天数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="trafficHourlyStatCleanDays" v-model="config.trafficHourlyStat.clean.days" style="width:6em" maxlength="6"/>
<span class="ui label"></span>
</div>
<p class="comment"><pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<h4 style="margin-top: 0">集群按天流量统计</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">数据保存天数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="nodeClusterTrafficDailyStatCleanDays" v-model="config.nodeClusterTrafficDailyStat.clean.days" style="width:6em" maxlength="6"/>
<span class="ui label"></span>
</div>
<p class="comment"><pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<h4 style="margin-top: 0">节点按天流量统计</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">数据保存天数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="nodeTrafficDailyStatCleanDays" v-model="config.nodeTrafficDailyStat.clean.days" style="width:6em" maxlength="6"/>
<span class="ui label"></span>
</div>
<p class="comment"><pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<h4 style="margin-top: 0">节点按小时流量统计</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">数据保存天数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="nodeTrafficHourlyStatCleanDays" v-model="config.nodeTrafficHourlyStat.clean.days" style="width:6em" maxlength="6"/>
<span class="ui label"></span>
</div>
<p class="comment"><pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<h4 style="margin-top: 0">缓存任务</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">数据保存天数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="httpCacheTaskCleanDays" v-model="config.httpCacheTask.clean.days" style="width:6em" maxlength="6"/>
<span class="ui label"></span>
</div>
<p class="comment"><pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<div class="margin"></div>
</div>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
})

View File

@@ -0,0 +1,34 @@
{$layout}
{$template "menu"}
<p class="ui message error" v-if="error.length > 0">{{error}}</p>
<div v-if="error.length == 0 && dbConfig != null">
<table class="ui table selectable definition">
<tr>
<td class="title">主机地址</td>
<td>{{dbConfig.host}}</td>
</tr>
<tr>
<td>数据库端口</td>
<td>{{dbConfig.port}}</td>
</tr>
<tr>
<td>数据库名称</td>
<td>{{dbConfig.database}}</td>
</tr>
<tr>
<td>用户名</td>
<td>
<span v-if="dbConfig.username.length > 0">{{dbConfig.username}}</span>
<span v-else class="disabled">不使用用户名。</span>
</td>
</tr>
<tr>
<td>密码</td>
<td>
<span v-if="dbConfig.password.length > 0">{{dbConfig.password}}</span>
<span v-else class="disabled">不使用密码。</span>
</td>
</tr>
</table>
</div>

View File

@@ -0,0 +1,44 @@
{$layout}
{$template "menu"}
<div class="ui message small warning">
<p>这里只能修改配置模板文件里的配置信息,并不能直接修改数据库自身的用户名、密码等信息。</p>
</div>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<table class="ui table selectable definition">
<tr>
<td class="title">主机地址 *</td>
<td>
<input type="text" name="host" v-model="dbConfig.host" maxlength="100"/>
</td>
</tr>
<tr>
<td>数据库端口 *</td>
<td>
<input type="text" name="port" style="width:6em" v-model="dbConfig.port" maxlength="5"/>
</td>
</tr>
<tr>
<td>数据库名称 *</td>
<td>
<input type="text" name="database" maxlength="100" v-model="dbConfig.database"/>
</td>
</tr>
<tr>
<td>用户名 *</td>
<td>
<input type="text" name="username" maxlength="100" v-model="dbConfig.username"/>
<p class="comment">用来连接数据库的用户名。</p>
</td>
</tr>
<tr>
<td>密码</td>
<td>
<input type="password" name="password" maxlength="100" v-model="dbConfig.password"/>
<p class="comment">用来连接数据库的密码。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
this.success = NotifySuccess("保存成功", "/settings/database")
})

View File

@@ -0,0 +1,3 @@
{$layout}
<p class="comment">此功能暂未开放,敬请期待。</p>

View File

@@ -0,0 +1,27 @@
<first-menu v-if="canAccess">
<menu-item href="/settings/ip-library" code="index">IP库</menu-item>
<menu-item href="/settings/ip-library/libraries/create" code="create">新制作</menu-item>
<menu-item href="/settings/ip-library/upload" code="upload">上传</menu-item>
<menu-item href="/settings/ip-library/library/test" code="test">测试</menu-item>
<span class="item disabled">|</span>
<span class="item"> <tip-icon content="如果没有上传IP库或者没有设置IP库为使用状态那么将使用内置的IP库。"></tip-icon></span>
<span class="item disabled">|</span>
<menu-item href="/settings/ip-library/libraries" code="library">IP库源文件</menu-item>
<!--<menu-item href="/settings/ip-library/unfinished" code="unfinished">未完成</menu-item>-->
<span class="item disabled">|</span>
<menu-item href="/settings/ip-library/countries" code="country">国家/地区</menu-item>
<menu-item href="/settings/ip-library/provinces" code="province">省份/州</menu-item>
<menu-item href="/settings/ip-library/cities" code="city">城市/市</menu-item>
<menu-item href="/settings/ip-library/towns" code="town">区/县</menu-item>
<menu-item href="/settings/ip-library/providers" code="provider">ISP运营商</menu-item>
<span class="item disabled">|</span>
<span class="item"><tip-icon content="为什么要修改国家、地区、省份、ISP运营商等别名<br/>因为每个IP库提供商提供的IP库中的地区、ISP运营商名称都不一致无法直接对应到数据库中的地区信息所以需要一一人工校对后才能导入。"></tip-icon></span>
</first-menu>
<first-menu v-if="!canAccess">
<menu-item href="/settings/ip-library" code="index">IP库</menu-item>
<menu-item href="/settings/ip-library/upload" code="upload">上传</menu-item>
<menu-item href="/settings/ip-library/library/test" code="test">测试</menu-item>
<span class="item disabled">|</span>
<span class="item"><tip-icon content="如果没有上传IP库或者没有设置IP库为使用状态那么将使用内置的IP库目前只允许上传MaxMind官方提供的IP库文件。"></tip-icon></span>
</first-menu>

View File

@@ -0,0 +1,60 @@
{$layout}
{$template "../menu"}
<div class="margin"></div>
<form class="ui form" method="get" action="/settings/ip-library/cities">
<div class="ui fields inline">
<div class="ui field">
<combo-box name="countryId" :v-items="countries" :v-value="countryId" @change="changeCountry" placeholder="国家/地区"></combo-box>
</div>
<div class="ui field">
<combo-box name="provinceId" :data-url="'/settings/ip-library/cities/provinceOptions?countryId=' + countryId" data-key="provinces" :v-value="provinceId" ref="provinceOptionsRef" placeholder="省/州"></combo-box>
</div>
<div class="ui field">
<button class="ui button" type="submit">搜索</button>
</div>
</div>
</form>
<not-found-box v-if="cities.length == 0">暂时还没有城市/市。</not-found-box>
<p class="ui basic message" v-if="cities.length > 0">共 {{cities.length}} 个城市/市。</p>
<table class="ui table selectable celled" v-if="cities.length > 0">
<thead>
<tr>
<th style="width: 3em">ID</th>
<th style="width: 12em">城市/市名称</th>
<th style="width: 12em">内置别名</th>
<th style="width: 12em">自定义名称 <tip-icon content="修改在界面上显示的城市/市名称"></tip-icon></th>
<th style="width: 12em">自定义别名 <tip-icon content="可以在IP库中通过别名找到当前城市/市,比如通过”北京市“、”北京“都能找到北京"></tip-icon></th>
<th class="one op">操作</th>
</tr>
</thead>
<tr v-for="city in cities">
<td>{{city.id}}</td>
<td>{{city.name}}</td>
<td>
<div v-if="city.codes.length > 0">
<span v-for="code in city.codes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled"></span>
</td>
<td>
<span v-if="city.customName.length > 0">{{city.customName}}</span>
<span v-else class="disabled">暂无</span>
</td>
<td>
<div v-if="city.customCodes.length > 0">
<span v-for="code in city.customCodes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled">暂无</span>
</td>
<td>
<a href="" @click.prevent="updateCity(city.id)">修改</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,20 @@
Tea.context(function () {
this.changeCountry = function (item) {
let provinceOptionsBox = this.$refs.provinceOptionsRef
if (item != null) {
provinceOptionsBox.setDataURL("/settings/ip-library/cities/provinceOptions?countryId=" + item.value)
provinceOptionsBox.reloadData()
}
provinceOptionsBox.clear()
}
this.updateCity = function (cityId) {
teaweb.popup("/settings/ip-library/cities/updatePopup?cityId=" + cityId, {
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
})

View File

@@ -0,0 +1,35 @@
{$layout "layout_popup"}
<h3>定制城市/市信息</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="cityId" :value="city.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">省份/州名称</td>
<td>{{city.name}}</td>
</tr>
<tr>
<td>内置别名</td>
<td>
<div v-if="city.codes.length > 0">
<span v-for="code in city.codes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled"></span>
</td>
</tr>
<tr>
<td>自定义名称</td>
<td>
<input type="text" name="customName" maxlength="100" v-model="city.customName"/>
</td>
</tr>
<tr>
<td>自定义别名</td>
<td>
<values-box name="customCodes" :v-values="city.customCodes"></values-box>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,38 @@
{$layout}
{$template "../menu"}
<table class="ui table selectable celled">
<thead>
<tr>
<th style="width: 3em">ID</th>
<th style="width: 12em">国家/地区名称</th>
<th style="width: 12em">内置别名</th>
<th style="width: 12em">自定义名称 <tip-icon content="修改在界面上显示的国家/地区名称"></tip-icon></th>
<th style="width: 12em">自定义别名 <tip-icon content="可以在IP库中通过别名找到当前国家/地区,比如通过美国、美利坚合众国都可以找到美国这个国家"></tip-icon></th>
<th class="one op">操作</th>
</tr>
</thead>
<tr v-for="country in countries">
<td>{{country.id}}</td>
<td>{{country.name}}</td>
<td>
<div v-if="country.codes.length > 0">
<span v-for="code in country.codes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled"></span>
</td>
<td>
<span v-if="country.customName.length > 0">{{country.customName}}</span>
<span v-else class="disabled">暂无</span>
</td>
<td>
<div v-if="country.customCodes.length > 0">
<span v-for="code in country.customCodes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled">暂无</span>
</td>
<td>
<a href="" @click.prevent="updateCountry(country.id)">修改</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,9 @@
Tea.context(function () {
this.updateCountry = function (countryId) {
teaweb.popup("/settings/ip-library/countries/updatePopup?countryId=" + countryId, {
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
})

View File

@@ -0,0 +1,35 @@
{$layout "layout_popup"}
<h3>定制国家/地区信息</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="countryId" :value="country.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">国家/地区名称</td>
<td>{{country.name}}</td>
</tr>
<tr>
<td>内置别名</td>
<td>
<div v-if="country.codes.length > 0">
<span v-for="code in country.codes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled"></span>
</td>
</tr>
<tr>
<td>自定义名称</td>
<td>
<input type="text" name="customName" maxlength="100" v-model="country.customName"/>
</td>
</tr>
<tr>
<td>自定义别名</td>
<td>
<values-box name="customCodes" :v-values="country.customCodes"></values-box>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,4 @@
th.number-col {
width: 7em;
}
/*# sourceMappingURL=index.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,EAAE;EACD,UAAA","file":"index.css"}

View File

@@ -0,0 +1,39 @@
{$layout}
{$template "menu"}
<not-found-box v-if="artifacts.length == 0">暂时还没有IP库。</not-found-box>
<table class="ui table selectable celled" v-if="artifacts.length > 0">
<thead>
<tr>
<th>IP库名称</th>
<th class="two wide">IP库类型</th>
<th class="two wide">尺寸</th>
<th class="one wide center">下载</th>
<th class="two wide">是否正在使用</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="artifact in artifacts" :class="{positive: artifact.isPublic}">
<td>{{artifact.name}}
<div>
<span class="small grey">{{artifact.createdTime}}</span>
</div>
</td>
<td>{{artifact.libraryType}}</td>
<td>
<span v-if="artifact.file != null && artifact.file.size > 0">{{teaweb.formatBytes(artifact.file.size)}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">
<a :href="'/settings/ip-library/download?artifactId=' + artifact.id" title="下载"><i class="icon download small"></i></a>
</td>
<td>
<span v-if="artifact.isPublic" class="green">Y</span>
<span v-else class="disabled">N</span>
</td>
<td>
<a href="" @click.prevent="deleteArtifact(artifact.id)">删除</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,16 @@
Tea.context(function () {
this.teaweb = teaweb
this.deleteArtifact = function (artifactId) {
let that = this
teaweb.confirm("确定要删除此IP库吗", function () {
that.$post("/settings/ip-library/delete")
.params({
artifactId: artifactId
})
.success(function () {
teaweb.successRefresh("删除成功")
})
})
}
})

View File

@@ -0,0 +1,3 @@
th.number-col {
width: 7em;
}

View File

@@ -0,0 +1,4 @@
.steps .step.active {
font-weight: bold;
}
/*# sourceMappingURL=create.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["create.less"],"names":[],"mappings":"AAAA,MACC,MAAK;EACJ,iBAAA","file":"create.css"}

View File

@@ -0,0 +1,300 @@
{$layout}
{$template "../menu"}
<div class="ui steps small fluid">
<div class="ui step" :class="{active: step == STEP_TEMPLATE}">
<div class="content">设置数据格式</div>
</div>
<div class="ui step" :class="{active: step == STEP_UPLOAD}">
<div class="content">上传文件</div>
</div>
<div class="ui step" :class="{active: step == STEP_COUNTRY}">
<div class="content">处理国家/地区</div>
</div>
<div class="ui step" :class="{active: step == STEP_PROVINCE}">
<div class="content">处理省份/州</div>
</div>
<div class="ui step" :class="{active: step == STEP_CITY}">
<div class="content">处理城市/市</div>
</div>
<div class="ui step" :class="{active: step == STEP_TOWN}">
<div class="content">处理县/区</div>
</div>
<div class="ui step" :class="{active: step == STEP_PROVIDER}">
<div class="content">处理ISP运营商</div>
</div>
<div class="ui step" :class="{active: step == STEP_FINISH}">
<div class="content">完成</div>
</div>
</div>
<!-- 设置件格式 -->
<div v-show="step == STEP_TEMPLATE">
<form class="ui form">
<table class="ui table selectable definition">
<tr>
<td>IP库名称 *</td>
<td>
<input type="text" name="" v-model="libraryName" ref="libraryName" maxlength="50" v-show="libraryFileId == 0"/>
<span v-if="libraryFileId > 0">{{libraryName}}</span>
<p class="comment">给当前IP库起一个容易识别的名称。</p>
</td>
</tr>
<tr>
<td class="title">数据格式模板 *</td>
<td>
<input type="text" name="" v-model="rowTemplate" ref="rowTemplate" v-show="libraryFileId == 0"/>
<span v-if="libraryFileId > 0">{{rowTemplate}}</span>
<p class="comment">只支持纯文本(比如.txt的数据内容每行数据的格式其中<code-label>${ipFrom}</code-label>表示开始IPIPv4和IPv6均支持<code-label>${ipTo}</code-label>表示结束IP<code-label>${country}</code-label>表示国家/地区,<code-label>${province}</code-label>表示省份/州,<code-label>${city}</code-label>表示城市/市级单位,<code-label>${town}</code-label>表示区县(暂时不处理),<code-label>${provider}</code-label>表示ISP运营商<code-label>${any}</code-label>表示不需要识别的内容。<a href="" @click.prevent="formatIP2Region()">[IP2Region示例]</a> &nbsp;<a href="" @click.prevent="formatIP138()">[IP138示例]</a> </p>
</td>
</tr>
<tr v-show="rowTemplate.length > 0 && libraryFileId == 0">
<td>数据格式分析测试</td>
<td>
<input type="text" name="" v-model="formatTestText" placeholder="单行测试数据" @input="changeFormatTestText" @keyup.enter="testFormat()" @keypress.enter.prevent="1"/>
<p class="comment"><a href="" @click.prevent="testFormat">[执行测试]</a>
<span v-if="formatTestResult.length > 0" class="green">&nbsp; &nbsp; 测试成功:{{formatTestResult}}</span>
</p>
</td>
</tr>
<tr>
<td>空值列表</td>
<td>
<values-box ref="emptyValues" :v-values="emptyValues" v-show="libraryFileId == 0"></values-box>
<span v-for="emptyValue in emptyValues" class="ui label basic small" v-show="libraryFileId > 0">{{emptyValue}}</span>
<p class="comment">内容中如果有这些值,表示没有填写;比如如果空值是<code-label>0</code-label>,那么<code-label>字段1|0|字段2|0</code-label>中只有<code-label>字段1</code-label><code-label>字段2</code-label>两个信息是有效的。</p>
</td>
</tr>
<tr>
<td>密码</td>
<td>
<input type="password" maxlength="32" name="password" v-model="password"/>
</td>
</tr>
</table>
<button class="ui button primary" type="button" @click.prevent="templateGoNext">下一步</button>
</form>
</div>
<!-- 上传文件 -->
<div v-show="step == STEP_UPLOAD">
<form class="ui form">
<table class="ui table definition selectable">
<tr>
<td class="title">选择数据文件 *</td>
<td>
<input type="file" accept="text/plain, .txt" ref="dataFile"/>
<p class="comment">只支持纯文本(比如.txt的数据内容每行数据的格式需要和上一步设置的数据格式模板一致。</p>
</td>
</tr>
</table>
<button class="ui button primary" type="button" @click.prevent="upload" v-if="!isUploading">开始分析</button>
<button class="ui button disabled" type="button" v-if="isUploading">正在上传并分析中...</button>
&nbsp; &nbsp;
<a href="" @click.prevent="goStep(STEP_TEMPLATE)" v-if="!isUploading">上一步</a>
</form>
</div>
<!-- 处理国家/地区 -->
<div v-show="step == STEP_COUNTRY">
<div v-if="missingCountries.length == 0 && missingCountriesLoaded">
<div class="margin"></div>
暂时没有需要处理的国家/地区。
<div class="margin"></div>
</div>
<div v-if="missingCountries.length > 0 && missingCountriesLoaded">
<p class="comment">以下如无任何可以进行的操作,请直接进入下一步。</p>
<table class="ui table selectable celled">
<thead>
<tr>
<th class="three wide">国家/地区</th>
<th>问题</th>
</tr>
</thead>
<tr v-for="missingCountry in missingCountries">
<td>{{missingCountry.countryName}}</td>
<td><strong>"{{missingCountry.countryName}}"</strong>没有录入数据库
<div v-if="missingCountry.similarCountries.length > 0">
<div class="ui divider"></div>
我们发现了以下类似国家/地区,如果有相同的国家/地区,请选择:
<div style="margin-top: 0.5em">
<a href="" v-for="country in missingCountry.similarCountries" class="ui label basic small" @click.prevent="addCountryCustomCode(country, missingCountry.countryName)">{{country.displayName}}</a>
</div>
</div>
</td>
</tr>
</table>
<div class="margin"></div>
</div>
<button class="ui button primary" type="button" @click.prevent="goStep(STEP_PROVINCE)">下一步</button>
&nbsp; &nbsp; <a href="" @click.prevent="reloadCountries">刷新</a>
</div>
<!-- 处理省份/州 -->
<div v-show="step == STEP_PROVINCE">
<div v-if="missingProvinces.length == 0 && missingProvincesLoaded">
<div class="margin"></div>
暂时没有需要处理的省份/州。
<div class="margin"></div>
</div>
<div v-if="missingProvinces.length > 0 && missingProvincesLoaded">
<p class="comment">以下如无任何可以进行的操作,请直接进入下一步。</p>
<table class="ui table selectable celled">
<thead>
<tr>
<th class="three wide">国家/地区</th>
<th class="three wide">省份/州</th>
<th>问题</th>
</tr>
</thead>
<tr v-for="missingProvince in missingProvinces">
<td>{{missingProvince.countryName}}</td>
<td>{{missingProvince.provinceName}}</td>
<td><strong>"{{missingProvince.countryName}} - {{missingProvince.provinceName}}"</strong>没有录入数据库
<div v-if="missingProvince.similarProvinces.length > 0">
<div class="ui divider"></div>
我们发现了以下类似省份/州,如果有相同的省份/州,请选择:
<div style="margin-top: 0.5em">
<a href="" v-for="province in missingProvince.similarProvinces" class="ui label basic small" @click.prevent="addProvinceCustomCode(province, missingProvince.provinceName)">{{province.displayName}}</a>
</div>
</div>
</td>
</tr>
</table>
<div class="margin"></div>
</div>
<button class="ui button primary" type="button" @click.prevent="goStep(STEP_CITY)">下一步</button>
&nbsp; &nbsp; <a href="" @click.prevent="reloadProvinces">刷新</a> &nbsp; <span class="disabled">|</span> &nbsp; <a href="" @click.prevent="goStep(STEP_COUNTRY)">上一步</a>
</div>
<!-- 处理城市/市 -->
<div v-show="step == STEP_CITY">
<div v-if="missingCities.length == 0 && missingCitiesLoaded">
<div class="margin"></div>
暂时没有需要处理的城市/市。
<div class="margin"></div>
</div>
<div v-if="missingCities.length > 0 && missingCitiesLoaded">
<p class="comment">以下如无任何可以进行的操作,请直接进入下一步。每次最多显示{{sizePerPage}}条。</p>
<table class="ui table selectable celled">
<thead>
<tr>
<th class="three wide">国家/地区</th>
<th class="three wide">省份/州</th>
<th class="three wide">城市/市</th>
<th>问题</th>
</tr>
</thead>
<tr v-for="missingCity in missingCities">
<td>{{missingCity.countryName}}</td>
<td>{{missingCity.provinceName}}</td>
<td>{{missingCity.cityName}}</td>
<td><strong>"{{missingCity.countryName}} - {{missingCity.provinceName}} - {{missingCity.cityName}}"</strong>没有录入数据库
<div v-if="missingCity.similarCities.length > 0">
<div class="ui divider"></div>
我们发现了以下类似城市/市,如果有相同的城市/市,请选择:
<div style="margin-top: 0.5em">
<a href="" v-for="city in missingCity.similarCities" class="ui label basic small" @click.prevent="addCityCustomCode(city, missingCity.cityName)">{{city.displayName}}</a>
</div>
</div>
</td>
</tr>
</table>
<div class="margin"></div>
</div>
<button class="ui button primary" type="button" @click.prevent="goStep(STEP_TOWN)">下一步</button>
&nbsp; &nbsp; <a href="" @click.prevent="reloadCities">刷新</a> &nbsp; <span class="disabled">|</span> &nbsp; <a href="" @click.prevent="goStep(STEP_PROVINCE)">上一步</a>
</div>
<!-- 处理城市/市 -->
<div v-show="step == STEP_TOWN">
<div v-if="missingTowns.length == 0 && missingTownsLoaded">
<div class="margin"></div>
暂时没有需要处理的区/县。
<div class="margin"></div>
</div>
<div v-if="missingTowns.length > 0 && missingTownsLoaded">
<p class="comment">以下如无任何可以进行的操作,请直接进入下一步。每次最多显示{{sizePerPage}}条。</p>
<table class="ui table selectable celled">
<thead>
<tr>
<th class="three wide">国家/地区</th>
<th class="three wide">省份/州</th>
<th class="three wide">城市/市</th>
<th class="three wide">区/县</th>
<th>问题</th>
</tr>
</thead>
<tr v-for="missingTown in missingTowns">
<td>{{missingTown.countryName}}</td>
<td>{{missingTown.provinceName}}</td>
<td>{{missingTown.cityName}}</td>
<td>{{missingTown.townName}}</td>
<td><strong>"{{missingTown.countryName}} - {{missingTown.provinceName}} - {{missingTown.cityName}} - {{missingTown.townName}}"</strong>没有录入数据库
<div v-if="missingTown.similarTowns.length > 0">
<div class="ui divider"></div>
我们发现了以下类似区/县,如果有相同的区/县,请选择:
<div style="margin-top: 0.5em">
<a href="" v-for="town in missingTown.similarTowns" class="ui label basic small" @click.prevent="addTownCustomCode(town, missingTown.townName)">{{town.displayName}}</a>
</div>
</div>
</td>
</tr>
</table>
<div class="margin"></div>
</div>
<button class="ui button primary" type="button" @click.prevent="goStep(STEP_PROVIDER)">下一步</button>
&nbsp; &nbsp; <a href="" @click.prevent="reloadTowns">刷新</a> &nbsp; <span class="disabled">|</span> &nbsp; <a href="" @click.prevent="goStep(STEP_CITY)">上一步</a>
</div>
<!-- 处理ISP运营商 -->
<div v-show="step == STEP_PROVIDER">
<div v-if="missingProviders.length == 0 && missingProvidersLoaded">
<div class="margin"></div>
暂时没有需要处理的ISP运营商。
<div class="margin"></div>
</div>
<div v-if="missingProviders.length > 0 && missingProvidersLoaded">
<p class="comment">以下如无任何可以进行的操作,请直接进入下一步。</p>
<table class="ui table selectable celled">
<thead>
<tr>
<th class="three wide">ISP运营商</th>
<th>问题</th>
</tr>
</thead>
<tr v-for="missingProvider in missingProviders">
<td>{{missingProvider.providerName}}</td>
<td><strong>"{{missingProvider.providerName}}"</strong>没有录入数据库
<div v-if="missingProvider.similarProviders.length > 0">
<div class="ui divider"></div>
我们发现了以下类似ISP运营商如果有相同的ISP运营商请选择
<div style="margin-top: 0.5em">
<a href="" v-for="provider in missingProvider.similarProviders" class="ui label basic small" @click.prevent="addProviderCustomCode(provider, missingProvider.providerName)">{{provider.displayName}}</a>
</div>
</div>
</td>
</tr>
</table>
<div class="margin"></div>
</div>
<button class="ui button primary" type="button" @click.prevent="goStep(STEP_FINISH)">下一步</button>
&nbsp; &nbsp; <a href="" @click.prevent="reloadProviders">刷新</a> &nbsp; <span class="disabled">|</span> &nbsp; <a href="" @click.prevent="goStep(STEP_TOWN)">上一步</a>
</div>
<!-- 完成 -->
<div v-show="step == STEP_FINISH">
<p>现在可以确认完成当前IP库的上传了后期仍然可以修改国家/地区、省份/州、城市/市、区/县、ISP运营商等信息。</p>
<button class="ui button primary" type="button" @click.prevent="finish" v-if="!isFinishing">确认完成</button>
<button class="ui button disabled" type="button" v-if="isFinishing">正在完成最后操作...</button>
</div>

View File

@@ -0,0 +1,386 @@
Tea.context(function () {
this.STEP_TEMPLATE = "template"
this.STEP_UPLOAD = "upload"
this.STEP_COUNTRY = "country"
this.STEP_PROVINCE = "province"
this.STEP_CITY = "city"
this.STEP_TOWN = "town"
this.STEP_PROVIDER = "provider"
this.STEP_FINISH = "finish"
this.step = this.STEP_TEMPLATE
this.goStep = function (step) {
this.step = step
switch (step) {
case this.STEP_UPLOAD:
if (this.libraryFileId > 0) {
this.goStep(this.STEP_COUNTRY)
}
break
case this.STEP_COUNTRY:
this.reloadCountries()
break
case this.STEP_PROVINCE:
this.reloadProvinces()
break
case this.STEP_CITY:
this.reloadCities()
break
case this.STEP_TOWN:
this.reloadTowns()
break
case this.STEP_PROVIDER:
this.reloadProviders()
break
}
}
this.$delay(function () {
switch (this.step) {
case this.STEP_TEMPLATE:
this.$refs.libraryName.focus()
break
case this.STEP_COUNTRY:
this.reloadCountries()
break
case this.STEP_PROVINCE:
this.reloadProvinces()
break
case this.STEP_CITY:
this.reloadCities()
break
case this.STEP_TOWN:
this.reloadTowns()
break
case this.STEP_PROVIDER:
this.reloadProviders()
break
}
})
/**
* 数据格式
*/
this.rowTemplate = ""
this.formatTestText = ""
this.formatTestResult = ""
this.password = ""
this.libraryName = ""
this.emptyValues = []
this.libraryFileId = 0
if (this.updatingLibraryFile != null) {
this.libraryFileId = this.updatingLibraryFile.id
this.rowTemplate = this.updatingLibraryFile.template
this.libraryName = this.updatingLibraryFile.name
if (this.updatingLibraryFile.emptyValues != null) {
this.emptyValues = this.updatingLibraryFile.emptyValues
}
}
this.formatIP2Region = function () {
this.rowTemplate = "${ipFrom}|${ipTo}|${country}|${any}|${province}|${city}|${provider}"
}
this.formatIP138 = function () {
this.rowTemplate = "${any},${any},${ipFrom},${ipTo},${country},${province},${city},${town},${provider},${any},${any},${any}"
}
this.testFormat = function () {
this.$post("/settings/ip-library/creating/testFormat")
.params({
template: this.rowTemplate,
text: this.formatTestText
})
.success(function (resp) {
let values = resp.data.values
let pieces = []
if (values["country"] != null && values["country"].length > 0) {
pieces.push("国家/地区:" + values["country"])
}
if (values["province"] != null && values["province"].length > 0) {
pieces.push("省份/州:" + values["province"])
}
if (values["city"] != null && values["city"].length > 0) {
pieces.push("城市/市:" + values["city"])
}
if (values["town"] != null && values["town"].length > 0) {
pieces.push("区县:" + values["town"])
}
if (values["provider"] != null && values["provider"].length > 0) {
pieces.push("ISP运营商" + values["provider"])
}
this.formatTestResult = pieces.join("")
})
}
this.changeFormatTestText = function () {
this.formatTestResult = ""
}
this.templateGoNext = function () {
if (this.libraryName.length == 0) {
let that = this
teaweb.warn("请输入IP库名字", function () {
that.$refs.libraryName.focus()
})
return
}
if (this.rowTemplate.length == 0) {
let that = this
teaweb.warn("请先输入数据格式模板", function () {
that.$refs.rowTemplate.focus()
})
return
}
this.goStep(this.STEP_UPLOAD)
}
/**
* 上传
*/
this.isUploading = false
this.upload = function () {
let dataFile = this.$refs.dataFile
if (dataFile.files.length == 0) {
teaweb.warn("请先上传文件")
return
}
this.isUploading = true
let emptyValues = this.$refs.emptyValues.allValues()
this.$post("/settings/ip-library/creating/upload")
.timeout(300)
.params({
name: this.libraryName,
template: this.rowTemplate,
file: dataFile.files[0],
emptyValues: emptyValues,
password: this.password
})
.success(function (resp) {
this.libraryFileId = resp.data.libraryFileId
let that = this
teaweb.success("上传成功", function () {
that.step = that.STEP_COUNTRY
that.reloadCountries()
})
})
.error(function () {
teaweb.warn("操作超时,可能是网络太慢")
})
.done(function () {
this.isUploading = false
})
}
/**
* 国家
*/
this.missingCountries = []
this.missingCountriesLoaded = false
this.reloadCountries = function () {
this.missingCountriesLoaded = false
this.$post("/settings/ip-library/creating/countries")
.params({
"libraryFileId": this.libraryFileId
})
.success(function (resp) {
this.missingCountries = resp.data.missingCountries
})
.done(function () {
this.missingCountriesLoaded = true
})
}
this.addCountryCustomCode = function (country, code) {
let that = this
teaweb.confirm("html:确定要将 \"<strong>" + teaweb.encodeHTML(code) + "</strong>\" 加入到 \"<strong>" + teaweb.encodeHTML(country.displayName) + "</strong>\" 别名中吗?<br/>请再三确认无误后,才进行确定操作!", function () {
that.$post("/settings/ip-library/creating/addCountryCustomCode")
.params({
countryId: country.id,
code: code
})
.success(function () {
teaweb.success("操作成功", function () {
that.reloadCountries()
})
})
})
}
/**
* 省
*/
this.missingProvinces = []
this.missingProvincesLoaded = false
this.reloadProvinces = function () {
this.missingProvincesLoaded = false
this.$post("/settings/ip-library/creating/provinces")
.params({
"libraryFileId": this.libraryFileId
})
.success(function (resp) {
this.missingProvinces = resp.data.missingProvinces
})
.done(function () {
this.missingProvincesLoaded = true
})
}
this.addProvinceCustomCode = function (province, code) {
let that = this
teaweb.confirm("html:确定要将 \"<strong>" + teaweb.encodeHTML(code) + "</strong>\" 加入到 \"<strong>" + teaweb.encodeHTML(province.displayName) + "</strong>\" 别名中吗?<br/>请再三确认无误后,才进行确定操作!", function () {
that.$post("/settings/ip-library/creating/addProvinceCustomCode")
.params({
provinceId: province.id,
code: code
})
.success(function () {
teaweb.success("操作成功", function () {
that.reloadProvinces()
})
})
})
}
/**
* 市
*/
this.missingCities = []
this.missingCitiesLoaded = false
this.sizePerPage = 100
this.reloadCities = function () {
this.missingCitiesLoaded = false
this.$post("/settings/ip-library/creating/cities")
.params({
libraryFileId: this.libraryFileId,
size: this.sizePerPage
})
.success(function (resp) {
this.missingCities = resp.data.missingCities
})
.done(function () {
this.missingCitiesLoaded = true
})
}
this.addCityCustomCode = function (city, code) {
let that = this
teaweb.confirm("html:确定要将 \"<strong>" + teaweb.encodeHTML(code) + "</strong>\" 加入到 \"<strong>" + teaweb.encodeHTML(city.displayName) + "</strong>\" 别名中吗?<br/>请再三确认无误后,才进行确定操作!", function () {
that.$post("/settings/ip-library/creating/addCityCustomCode")
.params({
cityId: city.id,
code: code
})
.success(function () {
teaweb.success("操作成功", function () {
that.reloadCities()
})
})
})
}
/**
* 县
*/
this.missingTowns = []
this.missingTownsLoaded = false
this.reloadTowns = function () {
this.missingTownsLoaded = false
this.$post("/settings/ip-library/creating/towns")
.params({
"libraryFileId": this.libraryFileId
})
.success(function (resp) {
this.missingTowns = resp.data.missingTowns
})
.done(function () {
this.missingTownsLoaded = true
})
}
this.addTownCustomCode = function (town, code) {
let that = this
teaweb.confirm("html:确定要将 \"<strong>" + teaweb.encodeHTML(code) + "</strong>\" 加入到 \"<strong>" + teaweb.encodeHTML(town.displayName) + "</strong>\" 别名中吗?<br/>请再三确认无误后,才进行确定操作!", function () {
that.$post("/settings/ip-library/creating/addTownCustomCode")
.params({
townId: town.id,
code: code
})
.success(function () {
teaweb.success("操作成功", function () {
that.reloadTowns()
})
})
})
}
/**
* ISP
*/
this.missingProviders = []
this.missingProvidersLoaded = false
this.reloadProviders = function () {
this.missingProvidersLoaded = false
this.$post("/settings/ip-library/creating/providers")
.params({
"libraryFileId": this.libraryFileId
})
.success(function (resp) {
this.missingProviders = resp.data.missingProviders
})
.done(function () {
this.missingProvidersLoaded = true
})
}
this.addProviderCustomCode = function (provider, code) {
let that = this
teaweb.confirm("html:确定要将 \"<strong>" + teaweb.encodeHTML(code) + "</strong>\" 加入到 \"<strong>" + teaweb.encodeHTML(provider.displayName) + "</strong>\" 别名中吗?<br/>请再三确认无误后,才进行确定操作!", function () {
that.$post("/settings/ip-library/creating/addProviderCustomCode")
.params({
providerId: provider.id,
code: code
})
.success(function () {
teaweb.success("操作成功", function () {
that.reloadProviders()
})
})
})
}
/**
* 完成
*/
this.isFinishing = false
this.finish = function () {
let that = this
teaweb.confirm("html:确定标记当前IP库已完成<br/>后期仍然可以修改国家/地区、省份/州、城市/市、区/县、ISP运营商等信息。", function () {
that.isFinishing = true
that.$post("/settings/ip-library/creating/finish")
.params({
libraryFileId: this.libraryFileId
})
.timeout(300)
.success(function () {
teaweb.success("保存成功", function () {
window.location = "/settings/ip-library"
})
})
.done(function () {
that.isFinishing = false
})
})
}
})

View File

@@ -0,0 +1,5 @@
.steps {
.step.active {
font-weight: bold;
}
}

View File

@@ -0,0 +1,32 @@
{$layout}
{$template "../menu"}
<not-found-box v-if="libraries.length == 0">暂时还没有已完成的IP库。</not-found-box>
<p class="ui message blue" v-if="isGenerating">正在生成IP库文件请耐心等待...</p>
<table class="ui table selectable celled" v-if="libraries.length > 0">
<thead>
<tr>
<th class="three wide">IP库名称</th>
<th>库文件生成时间</th>
<th style="width: 13em">操作</th>
</tr>
</thead>
<tr v-for="library in libraries">
<td>{{library.name}}</td>
<td>
<span v-if="library.generatedFileId > 0">{{library.generatedTime}}
&nbsp; <a :href="'/settings/ip-library/library/download?libraryFileId=' + library.id" title="下载"><i class="icon download small"></i></a>
</span>
<span v-else class="disabled">尚未生成</span>
</td>
<td>
<a href="" @click.prevent="generateLibrary(library.id)" :class="{disabled: isGenerating}">重新生成</a> &nbsp;
<span class="disabled">|</span> &nbsp;
<a :href="'/settings/ip-library/libraries/create?libraryFileId=' + library.id" :class="{disabled: isGenerating}">修改</a> &nbsp;
<span class="disabled">|</span> &nbsp;
<a href="" @click.prevent="deleteLibrary(library.id)" :class="{disabled: isGenerating}">删除</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,34 @@
Tea.context(function () {
this.isGenerating = false
this.generateLibrary = function (libraryFileId) {
let that = this
teaweb.confirm("确定要重新生成库文件吗?", function () {
that.isGenerating = true
that.$post("/settings/ip-library/creating/generate")
.params({
libraryFileId: libraryFileId
})
.timeout(300)
.success(function () {
teaweb.successRefresh("生成成功")
})
.done(function () {
that.isGenerating = false
})
})
}
this.deleteLibrary = function (libraryFileId) {
let that = this
teaweb.confirm("html:确定要删除此库文件吗?", function () {
that.$post("/settings/ip-library/delete")
.params({
libraryFileId: libraryFileId
})
.success(function () {
teaweb.successRefresh("删除成功")
})
})
}
})

View File

@@ -0,0 +1,113 @@
{$layout}
{$template "../menu"}
<div class="ui segment">
<h3>IP库测试</h3>
<p class="comment">输入IP地址测试当前使用的IP库是否能正确查询地理位置信息</p>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<table class="ui table definition">
<tr>
<td class="title">MaxMind文件状态</td>
<td>
<div v-if="usingMaxMind">
<span v-if="maxMindCityExists" class="green">✓ City数据库已上传</span>
<span v-else-if="usingEmbeddedMaxMind" class="green">✓ City数据库使用嵌入的默认库</span>
<span v-else class="red">✗ City数据库未上传</span>
<br/>
<span v-if="maxMindASNExists" class="green">✓ ASN数据库已上传</span>
<span v-else class="grey">- ASN数据库未上传可选</span>
<p class="comment green">✓ 当前正在使用MaxMind GeoIP2数据库</p>
</div>
<div v-else>
<span v-if="maxMindCityExists" class="green">✓ City数据库已上传</span>
<span v-else class="red">✗ City数据库未上传</span>
<br/>
<span v-if="maxMindASNExists" class="green">✓ ASN数据库已上传</span>
<span v-else class="grey">- ASN数据库未上传可选</span>
<p class="comment">如果已上传MaxMind文件系统会优先使用MaxMind库。如果没有上传系统会使用嵌入的默认MaxMind库。</p>
</div>
</td>
</tr>
<tr>
<td class="title">IP地址 *</td>
<td>
<input type="text" name="ip" class="text" maxlength="100" ref="focus" placeholder="例如8.8.8.8" v-model="ip"/>
<p class="comment">输入要测试的IP地址例如8.8.8.8Google DNS美国、114.114.114.114中国DNS、202.96.0.20(中国北京联通)</p>
</td>
</tr>
</table>
<div class="ui divider"></div>
<button type="submit" class="ui button primary">测试查询</button>
</form>
<!-- 查询结果显示区域 -->
<div class="ui segment" v-if="result.isDone" ref="resultBox" style="margin-top: 2em;">
<h4>查询结果</h4>
<div v-if="!result.isOk" class="ui message error">
<strong>查询失败:</strong>{{result.error}}
</div>
<div v-if="result.isOk">
<div class="ui message success">
<strong>IP地址</strong>{{result.ip}}<br/>
<strong>IP库类型</strong>{{result.libraryType}}
<span v-if="result.libraryVersion"> (版本 {{result.libraryVersion}})</span>
</div>
<table class="ui table definition">
<tr>
<td class="title" style="width: 150px;">国家</td>
<td>
<strong>{{result.country || '-'}}</strong>
<span v-if="result.countryId > 0" class="grey"> (ID: {{result.countryId}})</span>
</td>
</tr>
<tr>
<td class="title">省份</td>
<td>
<strong>{{result.province || '-'}}</strong>
<span v-if="result.provinceId > 0" class="grey"> (ID: {{result.provinceId}})</span>
</td>
</tr>
<tr>
<td class="title">城市</td>
<td>
<strong>{{result.city || '-'}}</strong>
<span v-if="result.cityId > 0" class="grey"> (ID: {{result.cityId}})</span>
</td>
</tr>
<tr>
<td class="title">区县</td>
<td>
<strong>{{result.town || '-'}}</strong>
<span v-if="result.townId > 0" class="grey"> (ID: {{result.townId}})</span>
</td>
</tr>
<tr>
<td class="title">ISP/运营商</td>
<td>
<strong>{{result.provider || '-'}}</strong>
<span v-if="result.providerId > 0" class="grey"> (ID: {{result.providerId}})</span>
</td>
</tr>
<tr v-if="result.regionSummary">
<td class="title">区域摘要</td>
<td>{{result.regionSummary}}</td>
</tr>
<tr v-if="result.summary">
<td class="title">完整摘要</td>
<td>{{result.summary}}</td>
</tr>
</table>
<div class="ui message" v-if="result.libraryType == 'MaxMind GeoIP2'" style="margin-top: 1em;">
<i class="icon check circle green"></i> 当前使用的是MaxMind GeoIP2数据库IP库已成功替换
</div>
<div class="ui message warning" v-else style="margin-top: 1em;">
<i class="icon warning circle"></i> 当前使用的是默认IP库。如果已上传MaxMind文件请重启服务后再次测试。
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,71 @@
Tea.context(function () {
this.ip = ""
this.result = {
isDone: false
}
// 如果数据还没有从后端传入window.TEA.ACTION.data则设置默认值
if (typeof this.maxMindCityExists === "undefined") {
this.maxMindCityExists = false
}
if (typeof this.maxMindASNExists === "undefined") {
this.maxMindASNExists = false
}
if (typeof this.usingMaxMind === "undefined") {
this.usingMaxMind = false
}
if (typeof this.usingEmbeddedMaxMind === "undefined") {
this.usingEmbeddedMaxMind = false
}
this.$delay(function () {
if (this.$refs.focus) {
this.$refs.focus.focus()
}
// 确保表单使用AJAX提交
var form = this.$el.querySelector('form')
if (form) {
form.addEventListener('submit', function (e) {
e.preventDefault()
e.stopPropagation()
// 使用TeaGo的AJAX提交
Tea.runActionOn(form)
})
}
})
// 如果数据还没有从后端传入,则通过 AJAX 获取(作为后备方案)
if (this.maxMindCityExists === false && this.maxMindASNExists === false && this.usingMaxMind === false) {
this.$get("/settings/ip-library/library/test")
.success(function (resp) {
this.maxMindCityExists = resp.data.maxMindCityExists || false
this.maxMindASNExists = resp.data.maxMindASNExists || false
this.usingMaxMind = resp.data.usingMaxMind || false
this.usingEmbeddedMaxMind = resp.data.usingEmbeddedMaxMind || false
})
}
this.success = function (resp) {
if (!resp || !resp.data) {
return
}
this.result = resp.data.result || {
isDone: true,
isOk: false
}
this.maxMindCityExists = resp.data.maxMindCityExists || false
this.maxMindASNExists = resp.data.maxMindASNExists || false
this.usingMaxMind = resp.data.usingMaxMind || false
this.usingEmbeddedMaxMind = resp.data.usingEmbeddedMaxMind || false
// 滚动到结果区域
this.$nextTick(function () {
if (this.$refs.resultBox) {
this.$refs.resultBox.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
})
}
})

View File

@@ -0,0 +1,40 @@
{$layout}
{$template "../menu"}
<not-found-box v-if="providers.length == 0">暂时还没有ISP运营商。</not-found-box>
<table class="ui table selectable celled" v-if="providers.length > 0">
<thead>
<tr>
<th style="width: 3em">ID</th>
<th style="width: 12em">ISP运营商名称</th>
<th style="width: 12em">内置别名</th>
<th style="width: 12em">自定义名称 <tip-icon content="修改在界面上显示的ISP运营商名称"></tip-icon></th>
<th style="width: 12em">自定义别名 <tip-icon content="可以在IP库中通过别名找到当前ISP运营商比如通过联通、中国联通都可以找到联通这个ISP服务商"></tip-icon></th>
<th class="one op">操作</th>
</tr>
</thead>
<tr v-for="provider in providers">
<td>{{provider.id}}</td>
<td>{{provider.name}}</td>
<td>
<div v-if="provider.codes.length > 0">
<span v-for="code in provider.codes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled"></span>
</td>
<td>
<span v-if="provider.customName.length > 0">{{provider.customName}}</span>
<span v-else class="disabled">暂无</span>
</td>
<td>
<div v-if="provider.customCodes.length > 0">
<span v-for="code in provider.customCodes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled">暂无</span>
</td>
<td>
<a href="" @click.prevent="updateProvider(provider.id)">修改</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,9 @@
Tea.context(function () {
this.updateProvider = function (providerId) {
teaweb.popup("/settings/ip-library/providers/updatePopup?providerId=" + providerId, {
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
})

View File

@@ -0,0 +1,35 @@
{$layout "layout_popup"}
<h3>定制ISP运营商信息</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="providerId" :value="provider.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">ISP供应商名称</td>
<td>{{provider.name}}</td>
</tr>
<tr>
<td>内置别名</td>
<td>
<div v-if="provider.codes.length > 0">
<span v-for="code in provider.codes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled"></span>
</td>
</tr>
<tr>
<td>自定义名称</td>
<td>
<input type="text" name="customName" maxlength="100" v-model="provider.customName"/>
</td>
</tr>
<tr>
<td>自定义别名</td>
<td>
<values-box name="customCodes" :v-values="provider.customCodes"></values-box>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,55 @@
{$layout}
{$template "../menu"}
<div class="margin"></div>
<form class="ui form" action="/settings/ip-library/provinces">
<div class="ui fields inline">
<div class="ui field">
<combo-box :v-items="countries" name="countryId" :v-value="countryId" placeholder="国家/地区"></combo-box>
</div>
<div class="ui field">
<button class="ui button" type="submit">搜索</button>
</div>
</div>
</form>
<not-found-box v-if="provinces.length == 0">暂时还没有省份/州。</not-found-box>
<p class="ui basic message" v-if="provinces.length > 0">共 {{provinces.length}} 个省份/州。</p>
<table class="ui table selectable celled" v-if="provinces.length > 0">
<thead>
<tr>
<th style="width: 3em">ID</th>
<th style="width: 12em">省份/州名称</th>
<th style="width: 12em">内置别名</th>
<th style="width: 12em">自定义名称 <tip-icon content="修改在界面上显示的省份/州名称"></tip-icon></th>
<th style="width: 12em">自定义别名 <tip-icon content="可以在IP库中通过别名找到当前省份/州,比如”广西“、”广西壮族自治区“"></tip-icon></th>
<th class="one op">操作</th>
</tr>
</thead>
<tr v-for="province in provinces">
<td>{{province.id}}</td>
<td>{{province.name}}</td>
<td>
<div v-if="province.codes.length > 0">
<span v-for="code in province.codes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled"></span>
</td>
<td>
<span v-if="province.customName.length > 0">{{province.customName}}</span>
<span v-else class="disabled">暂无</span>
</td>
<td>
<div v-if="province.customCodes.length > 0">
<span v-for="code in province.customCodes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled">暂无</span>
</td>
<td>
<a href="" @click.prevent="updateProvince(province.id)">修改</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,9 @@
Tea.context(function () {
this.updateProvince = function (provinceId) {
teaweb.popup("/settings/ip-library/provinces/updatePopup?provinceId=" + provinceId, {
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
})

View File

@@ -0,0 +1,35 @@
{$layout "layout_popup"}
<h3>定制省份/州信息</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="provinceId" :value="province.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">省份/州名称</td>
<td>{{province.name}}</td>
</tr>
<tr>
<td>内置别名</td>
<td>
<div v-if="province.codes.length > 0">
<span v-for="code in province.codes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled"></span>
</td>
</tr>
<tr>
<td>自定义名称</td>
<td>
<input type="text" name="customName" maxlength="100" v-model="province.customName"/>
</td>
</tr>
<tr>
<td>自定义别名</td>
<td>
<values-box name="customCodes" :v-values="province.customCodes"></values-box>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,62 @@
{$layout}
{$template "../menu"}
<div class="margin"></div>
<form class="ui form" method="get" action="/settings/ip-library/towns">
<div class="ui fields inline">
<div class="ui field">
<combo-box name="countryId" :v-items="countries" :v-value="countryId" @change="changeCountry" placeholder="国家/地区"></combo-box>
</div>
<div class="ui field">
<combo-box name="provinceId" :data-url="'/settings/ip-library/towns/provinceOptions?countryId=' + countryId" data-key="provinces" :v-value="provinceId" ref="provinceOptionsRef" @change="changeProvince" placeholder="省份/州"></combo-box>
</div>
<div class="ui field">
<combo-box name="cityId" :data-url="'/settings/ip-library/towns/cityOptions?provinceId=' + provinceId" data-key="cities" :v-value="cityId" ref="cityOptionsRef" placeholder="城市/市"></combo-box>
</div>
<div class="ui field">
<button class="ui button" type="submit">搜索</button>
</div>
</div>
</form>
<not-found-box v-if="towns.length == 0">暂时还没有区/县。</not-found-box>
<p class="ui basic message" v-if="towns.length > 0">共 {{towns.length}} 个区/县。</p>
<table class="ui table selectable celled" v-if="towns.length > 0">
<thead>
<tr>
<th style="width: 3em">ID</th>
<th style="width: 12em">区/县名称</th>
<th style="width: 12em">内置别名</th>
<th style="width: 12em">自定义名称 <tip-icon content="修改在界面上显示的区/县名称"></tip-icon></th>
<th style="width: 12em">自定义别名 <tip-icon content="可以在IP库中通过别名找到当前区/县,比如通过”朝阳区“、”朝阳“都能找到朝阳区"></tip-icon></th>
<th class="one op">操作</th>
</tr>
</thead>
<tr v-for="town in towns">
<td>{{town.id}}</td>
<td>{{town.name}}</td>
<td>
<div v-if="town.codes.length > 0">
<span v-for="code in town.codes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled"></span>
</td>
<td>
<span v-if="town.customName.length > 0">{{town.customName}}</span>
<span v-else class="disabled">暂无</span>
</td>
<td>
<div v-if="town.customCodes.length > 0">
<span v-for="code in town.customCodes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled">暂无</span>
</td>
<td>
<a href="" @click.prevent="updateTown(town.id)">修改</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,33 @@
Tea.context(function () {
this.changeCountry = function (item) {
let provinceOptionsBox = this.$refs.provinceOptionsRef
if (item != null) {
provinceOptionsBox.setDataURL("/settings/ip-library/towns/provinceOptions?countryId=" + item.value)
provinceOptionsBox.reloadData()
}
provinceOptionsBox.clear()
this.changeProvince(null)
}
this.changeProvince = function (item) {
let cityOptionsBox = this.$refs.cityOptionsRef
if (item != null) {
cityOptionsBox.setDataURL("/settings/ip-library/towns/cityOptions?provinceId=" + item.value)
cityOptionsBox.reloadData()
}
cityOptionsBox.clear()
}
this.updateTown = function (townId) {
teaweb.popup("/settings/ip-library/towns/updatePopup?townId=" + townId, {
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
})

View File

@@ -0,0 +1,35 @@
{$layout "layout_popup"}
<h3>定制区/县信息</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="townId" :value="town.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">省份/州名称</td>
<td>{{town.name}}</td>
</tr>
<tr>
<td>内置别名</td>
<td>
<div v-if="town.codes.length > 0">
<span v-for="code in town.codes" class="ui label basic">{{code}}</span>
</div>
<span v-else class="disabled"></span>
</td>
</tr>
<tr>
<td>自定义名称</td>
<td>
<input type="text" name="customName" maxlength="100" v-model="town.customName"/>
</td>
</tr>
<tr>
<td>自定义别名</td>
<td>
<values-box name="customCodes" :v-values="town.customCodes"></values-box>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,25 @@
{$layout}
{$template "menu"}
<form class="ui form" data-tea-action="$" data-tea-success="success" data-tea-timeout="120" data-tea-before="before" data-tea-done="done">
<csrf-token></csrf-token>
<table class="ui table selectable definition">
<tr>
<td>IP库名称 *</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus"/>
</td>
</tr>
<tr>
<td class="title">选择IP库文件 *</td>
<td>
<input type="file" name="file" accept=".mmdb"/>
<p class="comment">只允许上传MaxMind专有格式的数据库文件。</p>
</td>
</tr>
</table>
<submit-btn v-show="!isUploading">开始上传</submit-btn>
<button class="ui button disabled" type="button" v-if="isUploading">IP库上传中...</button>
</form>

View File

@@ -0,0 +1,13 @@
Tea.context(function () {
this.success = NotifySuccess("上传成功", "/settings/ip-library")
this.isUploading = false
this.before = function () {
this.isUploading = true
}
this.done = function () {
this.isUploading = false
}
})

View File

@@ -0,0 +1,30 @@
{$layout}
<div class="margin"></div>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<table class="ui table definition selectable">
<tr>
<td class="title">登录用户名 *</td>
<td>
<input type="text" name="username" maxlength="50" v-model="admin.username" ref="focus"/>
<p class="comment">只能含有英文、数字或下划线。</p>
</td>
</tr>
<tr>
<td>登录密码</td>
<td>
<input type="password" name="password" maxlength="50"/>
<p class="comment">不填表示保留原有密码。</p>
</td>
</tr>
<tr>
<td>确认密码</td>
<td>
<input type="password" name="password2" maxlength="50"/>
<p class="comment">不填表示保留原有密码。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
})

View File

@@ -0,0 +1,15 @@
{$layout}
<div class="margin"></div>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<table class="ui table definition selectable">
<tr>
<td class="title">你的姓名 *</td>
<td>
<input type="text" name="fullname" maxlength="50" ref="focus" v-model="admin.fullname"/>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
})

View File

@@ -0,0 +1,112 @@
{$layout}
<div class="margin"></div>
<p class="comment">当前安全设置只适用于管理系统,对用户网站没有任何影响。</p>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">Frame嵌套条件</td>
<td>
<select class="ui dropdown auto-width" name="frame" v-model="config.frame">
<option value="">不限制</option>
<option value="SAMEORIGIN">仅限同域名</option>
<!--<option value="DENY">完全禁止</option>-->
</select>
<p class="comment">当前服务被别的网页框架嵌套的条件限制。</p>
</td>
</tr>
<tr>
<td>允许访问的国家和地区</td>
<td>
<countries-selector :v-countries="countries"></countries-selector>
<p class="comment">设置后,只有这些国家和地区才能访问管理界面,如果不设置表示没有限制。</p>
</td>
</tr>
<tr>
<td>允许访问的省份(中国)</td>
<td>
<provinces-selector :v-provinces="provinces"></provinces-selector>
<p class="comment">设置后,只有这些省份才能访问管理界面,如果不设置表示没有限制。</p>
</td>
</tr>
<tr>
<td>允许访问的IP</td>
<td>
<values-box name="allowIPs" placeholder="IP或者CIDR IP段" :values="config.allowIPs"></values-box>
<p class="comment">如果不为空,则<strong>仅仅允许</strong>这些IP访问其他的IP访问时会被拒绝。</p>
</td>
</tr>
<tr>
<td>允许局域网访问</td>
<td>
<checkbox name="allowLocal" v-model="config.allowLocal"></checkbox>
<p class="comment">选中表示总是允许在本机和局域网访问,不需要其他限制条件。</p>
</td>
</tr>
<tr>
<td>允许记住登录</td>
<td>
<checkbox name="allowRememberLogin" v-model="config.allowRememberLogin"></checkbox>
<p class="comment">选中表示允许在登录界面可以选择记住登录。</p>
</td>
</tr>
<tr>
<td colspan="2">
<more-options-indicator></more-options-indicator>
</td>
</tr>
<tbody v-show="moreOptionsVisible || showAll">
<tr>
<td class="color-border">自定义客户端IP报头</td>
<td>
<input type="text" name="clientIPHeaderNames" v-model="config.clientIPHeaderNames"/>
<p class="comment"><a id="client-header-names"></a>可以通过此报头获取客户端IP类似于<code-label>X-Forwarded-For X-Real-IP True-Client-IP Client-IP</code-label>&nbsp;<a href=""><span class="small" @click.prevent="addDefaultClientIPHeaderNames('X-Forwarded-For X-Real-IP True-Client-IP Client-IP')">[填入]</span></a>,用于使用反向代理访问管理系统的情形;如果有多个报头可以使用空格隔开。</p>
</td>
</tr>
<tr v-show="config.clientIPHeaderNames.length > 0">
<td class="color-border">仅从自定义报头中获取IP</td>
<td>
<checkbox name="clientIPHeaderOnly" v-model="config.clientIPHeaderOnly"></checkbox>
<p class="comment">选中后表示仅从自定义报头中获取IP意味着客户端必须使用反向代理访问当前系统不允许直接访问。</p>
</td>
</tr>
<tr>
<td>禁止搜索引擎</td>
<td>
<checkbox name="denySearchEngines" v-model="config.denySearchEngines"></checkbox>
<p class="comment">禁止常见的搜索引擎访问。</p>
</td>
</tr>
<tr>
<td>禁止爬虫</td>
<td>
<checkbox name="denySpiders" v-model="config.denySpiders"></checkbox>
<p class="comment">禁止常见的爬虫访问。</p>
</td>
</tr>
<tr>
<td>允许访问的域名</td>
<td>
<domains-box :v-domains="config.allowDomains"></domains-box>
<p class="comment">只允许通过这些域名或者IP作为主机地址访问当前管理系统不填表示没有限制。</p>
</td>
</tr>
<tr>
<td>检查客户端指纹</td>
<td>
<checkbox name="checkClientFingerprint" v-model="config.checkClientFingerprint"></checkbox>
<p class="comment">选中后表示每次管理员访问时都检查客户端相关信息是否跟登录时一致如果客户端IP或User-Agent信息变化比较频繁请不要启用此选项如果当前系统下游有反向代理设置请设置当前表单中的“自定义客户端IP报头”选项。</p>
</td>
</tr>
<tr>
<td>检查客户端区域</td>
<td>
<checkbox name="checkClientRegion" v-model="config.checkClientRegion"></checkbox>
<p class="comment">选中后表示每次管理员访问时都检查客户端所在地理区域是否和登录时一致如果客户端所处地理区域变化比较频繁请不要启用此选项如果当前系统下游有反向代理设置请设置当前表单中的“自定义客户端IP报头”选项。</p>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,11 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
this.addDefaultClientIPHeaderNames = function (headerNames) {
if (this.config.clientIPHeaderNames == null || this.config.clientIPHeaderNames.length == 0) {
this.config.clientIPHeaderNames = headerNames
} else {
this.config.clientIPHeaderNames += " " + headerNames
}
}
})

View File

@@ -0,0 +1,57 @@
{$layout}
<div v-if="serverIsChanged">
<div class="margin"></div>
<warning-message>服务配置已修改,请在命令行下重启后生效(<code-label>edge-admin restart</code-label>)。</warning-message>
</div>
<h3>通过HTTP访问管理平台 <a href="/settings/server/http" v-if="!teaDemoEnabled" @click.prevent="updateHTTP()">修改</a><a v-if="teaDemoEnabled">[演示版无法修改]</a></h3>
<table class="ui table definition selectable">
<tr>
<td>状态</td>
<td>
<label-on :v-is-on="serverConfig.http.on"></label-on>
</td>
</tr>
<tr>
<td class="title">绑定地址</td>
<td>
<span v-for="listen in serverConfig.http.listen" class="ui label tiny basic">{{listen}}</span>
<p class="ui comment">访问当前管理系统的HTTP地址如果地址中的IP是0.0.0.0表示服务器的所有IP都可以访问。</p>
</td>
</tr>
</table>
<div class="ui divider"></div>
<h3>通过HTTPS访问管理平台 <a href="" v-if="!teaDemoEnabled" @click.prevent="updateHTTPS()">修改</a><a v-if="teaDemoEnabled">[演示版无法修改]</a></h3>
<table class="ui table definition selectable">
<tr>
<td>状态</td>
<td>
<label-on :v-is-on="serverConfig.https.on"></label-on>
</td>
</tr>
<tr>
<td class="title">绑定地址</td>
<td>
<span v-for="listen in serverConfig.https.listen" class="ui label tiny basic">{{listen}}</span>
<p class="ui comment">访问当前管理系统的HTTPS地址如果地址中的IP是0.0.0.0表示服务器的所有IP都可以访问。</p>
</td>
</tr>
<tr>
<td>证书文件<span class="small">Cert</span></td>
<td>
<span v-if="serverConfig.https.cert.length > 0">{{serverConfig.https.cert}}</span>
<span class="disabled" v-else>还没有设置证书</span>
</td>
</tr>
<tr>
<td>私钥文件<span class="small">Key</span></td>
<td>
<span v-if="serverConfig.https.key.length > 0">{{serverConfig.https.key}}</span>
<span class="disabled" v-else>还没有设置私钥</span>
</td>
</tr>
</table>

View File

@@ -0,0 +1,19 @@
Tea.context(function () {
this.updateHTTP = function () {
teaweb.popup("/settings/server/updateHTTPPopup", {
callback: function () {
teaweb.success("保存成功", teaweb.reload)
}
})
}
this.updateHTTPS = function () {
teaweb.popup("/settings/server/updateHTTPSPopup", {
height: "26em",
width:"50em",
callback: function () {
teaweb.success("保存成功", teaweb.reload)
}
})
}
})

View File

@@ -0,0 +1,27 @@
{$layout "layout_popup"}
<h3>修改HTTP设置</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<table class="ui table definition selectable">
<tr>
<td>启用HTTP</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="serverConfig.http.on"/>
<label></label>
</div>
</td>
</tr>
<tr>
<td class="title">绑定地址</td>
<td>
<values-box name="listens" :values="addresses"></values-box>
<p class="comment" style="margin-bottom:0">地址格式为:"IP:端口",比如:"127.0.0.1:7777"。</p>
<p class="comment">如果地址中的IP是0.0.0.0表示服务器的所有IP都可以用来使用访问此服务。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,8 @@
Tea.context(function () {
this.success = NotifyPopup
this.addresses = [];
if (this.serverConfig != null && this.serverConfig.http != null && this.serverConfig.http.listen != null) {
this.addresses = this.serverConfig.http.listen
}
})

View File

@@ -0,0 +1,33 @@
{$layout "layout_popup"}
<h3>修改HTTPS配置</h3>
<form method="post" data-tea-action="$" data-tea-success="success" class="ui form">
<table class="ui table definition selectable">
<tr>
<td>启用HTTPS</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="serverConfig.https.on"/>
<label></label>
</div>
</td>
</tr>
<tr>
<td class="title">绑定地址</td>
<td>
<values-box name="listens" :values="addresses"></values-box>
<p class="comment" style="margin-bottom:0">每行一个地址,地址格式为:"IP:端口",比如:"127.0.0.1:7778"。</p>
<p class="comment">如果地址中的IP是0.0.0.0表示服务器的所有IP都可以用来使用访问此服务。</p>
</td>
</tr>
<tr v-show="serverConfig.https.on">
<td>选择证书文件</td>
<td>
<ssl-certs-box :v-certs="certConfigs" :v-protocol="'http'" :v-view-size="'mini'" :v-single-mode="true"></ssl-certs-box>
</td>
</tr>
</table>
<button class="ui button primary">保存</button>
</form>

View File

@@ -0,0 +1,8 @@
Tea.context(function () {
this.success = NotifyPopup
this.addresses = [];
if (this.serverConfig != null && this.serverConfig.https != null && this.serverConfig.https.listen != null) {
this.addresses = this.serverConfig.https.listen
}
})

View File

@@ -0,0 +1,13 @@
.steps .step.active .content {
font-weight: bold;
}
.step-box .button-group {
margin-top: 1em;
}
.step-box .button-group .next {
float: right;
}
.step-box .content {
min-height: 6em;
}
/*# sourceMappingURL=index.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,MACC,MAAK,OACJ;EACC,iBAAA;;AAKH,SACC;EACC,eAAA;;AAFF,SACC,cAGC;EACC,YAAA;;AALH,SASC;EACC,eAAA","file":"index.css"}

View File

@@ -0,0 +1,304 @@
{$layout}
<div class="margin"></div>
<div class="ui steps fluid small">
<div class="ui step" :class="{active: step == STEP_PREPARE}">
<div class="content">开始</div>
</div>
<div class="ui step" :class="{active: step == STEP_DATABASE}">
<div class="content">迁移数据库</div>
</div>
<div class="ui step" :class="{active: step == STEP_API}">
<div class="content">迁移API节点</div>
</div>
<div class="ui step" :class="{active: step == STEP_ADDRESS}">
<div class="content">变更地址</div>
</div>
<div class="ui step" :class="{active: step == STEP_ADMIN}">
<div class="content">迁移管理平台</div>
</div>
<div class="ui step" :class="{active: step == STEP_UPGRADE}">
<div class="content">升级节点配置</div>
</div>
<div class="ui step" :class="{active: step == STEP_FINISH}">
<div class="content">完成</div>
</div>
</div>
<!-- 准备工作 -->
<div class="step-box" v-if="step == STEP_PREPARE">
<div class="content">
<p>通过此引导程序可以帮助你将当前平台配置迁移到新的平台中。</p>
<p>每个步骤请小心操作,一旦配置错误可能需要手工还原。</p>
</div>
<div class="ui button-group">
<button class="ui button primary next" @click.prevent="doPrepare()">下一步</button>
</div>
</div>
<!-- 数据库 -->
<div class="step-box" v-if="step == STEP_DATABASE">
<form class="ui form">
<table class="ui table selectable definition">
<tr>
<td class="title">数据库地址是否改变?</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<div class="ui radio checkbox">
<input type="radio" value="0" v-model="databaseChanged" id="database-changed-0" class="hidden"/>
<label for="database-changed-0">没有变</label>
</div>
</div>
<div class="ui field">
<div class="ui radio checkbox">
<input type="radio" value="1" v-model="databaseChanged" id="database-changed-1" class="hidden"/>
<label for="database-changed-1">变了</label>
</div>
</div>
</div>
</td>
</tr>
<tr v-if="databaseChanged == 1">
<td>数据是否已经导入到新的数据库?</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<div class="ui radio checkbox">
<input type="radio" value="0" v-model="databaseTransferred" id="database-transferred-0" class="hidden"/>
<label for="database-transferred-0">没有导入</label>
</div>
</div>
<div class="ui field">
<div class="ui radio checkbox">
<input type="radio" value="1" v-model="databaseTransferred" id="database-transferred-1" class="hidden"/>
<label for="database-transferred-1">已经导入</label>
</div>
</div>
</div>
<p class="comment" v-if="databaseTransferred == 0"><span class="red">请自行将当前数据库中数据导入到新的数据库中。</span></p>
</td>
</tr>
</table>
</form>
<div class="ui button-group">
<button class="ui button prev" @click.prevent="doBack(STEP_PREPARE)">上一步</button>
<button class="ui button primary next" @click.prevent="doDatabase()">下一步</button>
</div>
</div>
<!-- 检查API节点 -->
<div class="step-box" v-if="step == STEP_API">
<form class="ui form">
<table class="ui table selectable definition">
<tr>
<td class="title">API节点地址是否变更</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<div class="ui radio checkbox">
<input type="radio" value="1" v-model="apiNodeChanged" id="api-node-changed-1" class="hidden"/>
<label for="api-node-changed-1">已经变更</label>
</div>
</div>
<div class="ui field">
<div class="ui radio checkbox">
<input type="radio" value="0" v-model="apiNodeChanged" id="api-node-changed-0" class="hidden"/>
<label for="api-node-changed-0">没有变更</label>
</div>
</div>
</div>
</td>
</tr>
<tbody v-show="apiNodeChanged == 1">
<tr>
<td>是否已安装新的API节点</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<div class="ui radio checkbox">
<input type="radio" value="1" v-model="apiNodeInstalled" id="api-node-installed-1" class="hidden"/>
<label for="api-node-installed-1">已经安装</label>
</div>
</div>
<div class="ui field">
<div class="ui radio checkbox">
<input type="radio" value="0" v-model="apiNodeInstalled" id="api-node-installed-0" class="hidden"/>
<label for="api-node-installed-0">没有安装</label>
</div>
</div>
</div>
<p class="comment" v-if="apiNodeInstalled == 0"><span class="red">请先安装新的API节点请拷贝当前的API节点配置<code-label>configs/api.yaml</code-label><code-label>configs/db.yaml</code-label>到新API节点对应的位置并记得修改其中的<code-label>configs/db.yaml</code-label>中的数据库地址、用户名、密码。</span></p>
</td>
</tr>
<tr>
<td>新的API节点IP或域名 *</td>
<td>
<input type="text" name="newAPINodeHost" placeholder="IP或域名" v-model="apiNodeHost" style="width: 14em" maxlength="100"/>
<p class="comment">如果有多个IP或者域名填写任意其中一个即可。</p>
</td>
</tr>
<tr>
<td>新的API节点端口 *</td>
<td>
<input type="text" name="newAPINodePort" v-model="apiNodePort" style="width: 5em" maxlength="5" placeholder="端口"/>
</td>
</tr>
<tr>
<td>新的API节点协议</td>
<td>
<select class="ui dropdown auto-width" v-model="apiNodeProtocol">
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</select>
</td>
</tr>
</tbody>
</table>
</form>
<div class="button-group">
<button class="ui button prev" @click.prevent="doBack(STEP_DATABASE)">上一步</button>
<button class="ui button primary next" @click.prevent="doAPI()">下一步</button>
</div>
</div>
<!-- 地址 -->
<div class="step-box" v-show="step == STEP_ADDRESS">
<form method="post" class="ui form" data-tea-action=".updateHosts" data-tea-success="doAddress">
<input type="hidden" name="host" :value="apiNodeHost"/>
<input type="hidden" name="port" :value="apiNodePort"/>
<input type="hidden" name="protocol" :value="apiNodeProtocol"/>
<table class="ui table selectable celled">
<thead>
<tr>
<th style="width: 50%">API节点原地址</th>
<th>API节点新地址留空表示不修改</th>
</tr>
</thead>
<tr v-for="host in apiAddressHosts">
<td>{{host}}</td>
<td>
<input type="text" maxlength="100" name="newHosts"/>
<input type="hidden" name="oldHosts" :value="host"/>
</td>
</tr>
</table>
<div class="button-group">
<button class="ui button prev" @click.prevent="doBack(STEP_API)">上一步</button>
<button type="submit" class="ui button primary next">下一步</button>
</div>
</form>
</div>
<!-- admin -->
<div class="step-box" v-if="step == STEP_ADMIN">
<form class="ui form">
<table class="ui table selectable definition">
<tr>
<td class="title">管理平台地址是否变更?</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<div class="ui radio checkbox">
<input type="radio" value="1" v-model="adminNodeChanged" id="admin-node-changed-1" class="hidden"/>
<label for="admin-node-changed-1">已经变更</label>
</div>
</div>
<div class="ui field">
<div class="ui radio checkbox">
<input type="radio" value="0" v-model="adminNodeChanged" id="admin-node-changed-0" class="hidden"/>
<label for="admin-node-changed-0">没有变更</label>
</div>
</div>
</div>
</td>
</tr>
<tbody v-show="adminNodeChanged == 1">
<tr>
<td>是否已安装新的管理平台</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<div class="ui radio checkbox">
<input type="radio" value="1" v-model="adminNodeInstalled" id="admin-node-installed-1" class="hidden"/>
<label for="admin-node-installed-1">已经安装</label>
</div>
</div>
<div class="ui field">
<div class="ui radio checkbox">
<input type="radio" value="0" v-model="adminNodeInstalled" id="admin-node-installed-0" class="hidden"/>
<label for="admin-node-installed-0">没有安装</label>
</div>
</div>
</div>
<p class="comment" v-if="adminNodeInstalled == 0"><span class="red">请先安装新的管理平台,请拷贝当前的管理平台下的<code-label>configs/</code-label>目录下的配置到新管理平台对应的位置。</span></p>
</td>
</tr>
</tbody>
</table>
</form>
<div class="ui button-group">
<button class="ui button prev" @click.prevent="doBack(STEP_API)" v-if="apiNodeChanged == 0">上一步</button>
<button class="ui button prev" @click.prevent="doBack(STEP_ADDRESS)" v-if="apiNodeChanged == 1">上一步</button>
<button class="ui button primary next" @click.prevent="doAdmin()">下一步</button>
</div>
</div>
<!-- 升级节点配置 -->
<div class="step-box" v-if="step == STEP_UPGRADE">
<form class="ui form">
<table class="ui table selectable definition">
<tr>
<td class="title">升级节点API地址配置</td>
<td>
<span>{{apiNodeProtocol}}://<span v-if="apiNodeHost.indexOf(':')>0">[{{apiNodeHost}}]</span><span v-else>{{apiNodeHost}}</span>:{{apiNodePort}}</span>
</td>
</tr>
<tr>
<td>当前平台节点数</td>
<td>
<span v-if="isUpgrading">{{countNodes}}</span>
<span v-else>-</span>
</td>
</tr>
<tr>
<td>新平台节点数</td>
<td>
<span v-if="isUpgrading">{{countFinishedNodes}}</span>
<span v-else>-</span>
</td>
</tr>
<tr>
<td>完成比例</td>
<td>
<span v-if="isUpgrading">{{percentNodes}}%</span>
<span v-else>-</span>
</td>
</tr>
</table>
</form>
<div class="ui button-group">
<button class="ui button prev" @click.prevent="doBack(STEP_ADMIN)">上一步</button>
<button class="ui button primary next" @click.prevent="doStartUpgrade()" v-if="!isUpgrading">开始升级</button>
<button class="ui button next" :class="{disabled: percentNodes<100}" v-if="isUpgrading && percentNodes < 100">等待完成</button>
<button class="ui button primary next" @click.prevent="doUpgrade()" v-if="isUpgrading && percentNodes == 100">下一步</button>
</div>
</div>
<!-- 完成 -->
<div class="step-box" v-if="step == STEP_FINISH">
<div class="content">
<p v-if="adminNodeChanged == 1">所有迁移任务已完成,请登录新的管理平台进行管理。</p>
<p v-if="adminNodeChanged == 0">所有迁移任务已完成。</p>
</div>
<div class="ui button-group">
<button class="ui button prev" @click.prevent="doBack(STEP_UPGRADE)">上一步</button>
<button class="ui button primary next" @click.prevent="doFinish()">完成</button>
</div>
</div>

View File

@@ -0,0 +1,191 @@
Tea.context(function () {
this.STEP_PREPARE = "prepare"
this.STEP_DATABASE = "database"
this.STEP_ADMIN = "admin"
this.STEP_API = "api"
this.STEP_ADDRESS = "address"
this.STEP_UPGRADE = "upgrade"
this.STEP_FINISH = "finish"
this.step = this.STEP_PREPARE
this.doBack = function (step) {
this.step = step
switch (step) {
case this.STEP_UPGRADE:
if (this.apiNodeChanged == 0) {
this.doBack(this.STEP_ADMIN)
}
}
}
/**
* 准备工作
*/
this.doPrepare = function () {
this.step = this.STEP_DATABASE
}
/**
* 数据库
*/
this.databaseChanged = 1
this.databaseTransferred = 0
this.doDatabase = function () {
if (this.databaseChanged == 1 && this.databaseTransferred == 0) {
teaweb.warn("请先将当前的数据导入到新的数据库中。")
return
}
this.step = this.STEP_API
}
/**
* API
*/
this.apiNodeChanged = 1
this.apiNodeHost = ""
this.apiNodePort = ""
this.apiNodeProtocol = "http"
this.apiNodeInstalled = 1
this.doAPI = function () {
if (this.apiNodeChanged == 0) {
this.step = this.STEP_ADMIN
return
}
if (this.apiNodeInstalled == 0) {
teaweb.warn("请先安装新的API节点")
return
}
this.$post(".validateAPI")
.params({
host: this.apiNodeHost,
port: this.apiNodePort,
protocol: this.apiNodeProtocol
})
.timeout(30)
.success(function (resp) {
if (this.apiNodeChanged == 1) {
this.step = this.STEP_ADDRESS
this.apiAddressHosts = resp.data.hosts
} else {
this.step = this.STEP_ADMIN
}
})
}
/**
* 修改地址
*/
this.apiAddresses = []
this.apiAddressHosts = []
this.doAddress = function () {
this.step = this.STEP_ADMIN
}
/**
* 管理平台
*/
this.adminNodeChanged = 1
this.adminNodeInstalled = 1
this.doAdmin = function () {
if (this.adminNodeChanged == 1 && this.adminNodeInstalled == 0) {
teaweb.warn("请先安装新的管理平台")
return
}
if (this.apiNodeChanged == 0) {
this.step = this.STEP_FINISH
} else {
this.step = this.STEP_UPGRADE
}
}
/**
* 边缘节点
*/
this.isUpgrading = false
this.percentNodes = 0
this.countNodes = 0
this.countFinishedNodes = 0
this.doStartUpgrade = function () {
this.percentNodes = 0
this.countNodes = 0
this.countFinishedNodes = 0
this.$post(".statNodes")
.success(function (resp) {
this.countNodes = resp.data.countNodes
if (this.countNodes == 0) {
this.isUpgrading = true
this.percentNodes = 100
return
}
this.isUpgrading = true
this.upgradeNodeTimer()
})
.fail(function () {
})
.error(function () {
})
}
this.upgradeNodeTimer = function () {
if (!this.isUpgrading) {
return
}
if (this.percentNodes == 100) {
return
}
this.$post(".upgradeNodes")
.params({
apiNodeProtocol: this.apiNodeProtocol,
apiNodeHost: this.apiNodeHost,
apiNodePort: this.apiNodePort
})
.success(function (resp) {
this.countFinishedNodes += resp.data.count
if (this.countNodes > 0) {
this.percentNodes = this.countFinishedNodes * 100 / this.countNodes
if (this.percentNodes > 100) {
this.percentNodes = 100
}
}
if (resp.data.hasNext) {
this.$delay(function () {
this.upgradeNodeTimer()
}, 5000)
}
})
.fail(function (resp) {
this.isUpgrading = false
teaweb.warn(resp.message)
})
.error(function (err) {
teaweb.warn("请求错误:" + err.message)
this.isUpgrading = false
})
}
this.doUpgrade = function () {
this.step = this.STEP_FINISH
}
/**
* 完成
*/
this.doFinish = function () {
window.location = "/"
}
})

Some files were not shown because too many files have changed in this diff Show More