Initial commit (code only without large binaries)

This commit is contained in:
robin
2026-02-15 18:58:44 +08:00
commit 35df75498f
9442 changed files with 1495866 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
<first-menu>
<menu-item href="/ns/domains" code="index">域名列表</menu-item>
<span class="item disabled">|</span>
<menu-item href="/ns/domains/create" code="create">[添加域名]</menu-item>
</first-menu>

View File

@@ -0,0 +1,14 @@
<first-menu>
<menu-item href="/ns/domains">我的域名</menu-item>
<span class="item disabled">|</span>
<menu-item :href="'/ns/domains/records?domainId=' + domain.id">{{domain.name}}</menu-item>
<menu-item :href="'/ns/domains/records?domainId=' + domain.id" code="record">解析记录({{domain.countRecords}})</menu-item>
<!--<menu-item :href="'/ns/domains/keys?domainId=' + domain.id" code="key">密钥({{domain.countKeys}})</menu-item>
<menu-item :href="'/ns/domains/tsig?domainId=' + domain.id" code="tsig">TSIG</menu-item>-->
<span class="item disabled">|</span>
<menu-item :href="'/ns/domains/update?domainId=' + domain.id" code="update">设置</menu-item>
<span class="item disabled">|</span>
<menu-item :href="'/ns/domains/healthCheck?domainId=' + domain.id" code="healthCheck">健康检查<span class="small green" v-if="domain.enableHealthCheck">(启用)</span><span></span></menu-item>
<!--<menu-item :href="'/ns/domains/keys?domainId=' + domain.id" code="key">密钥({{domain.countKeys}})</menu-item>
<menu-item :href="'/ns/domains/tsig?domainId=' + domain.id" code="tsig">TSIG</menu-item>-->
</first-menu>

View File

@@ -0,0 +1,9 @@
<first-menu>
<menu-item href="/ns/domains/batch" :active="secondMenuItem == 'index'">添加域名</menu-item>
<menu-item href="/ns/domains/batch/deleteDomains" :active="secondMenuItem == 'deleteDomains'">删除域名</menu-item>
<menu-item href="/ns/domains/batch/createRecords" :active="secondMenuItem == 'createRecords'">添加解析</menu-item>
<menu-item href="/ns/domains/batch/updateRecords" :active="secondMenuItem == 'updateRecords'">修改解析</menu-item>
<menu-item href="/ns/domains/batch/deleteRecords" :active="secondMenuItem == 'deleteRecords'">删除解析</menu-item>
<menu-item href="/ns/domains/batch/enableRecords" :active="secondMenuItem == 'enableRecords'">启用/暂停解析</menu-item>
<menu-item href="/ns/domains/batch/importRecords" :active="secondMenuItem == 'importRecords'">混合添加解析</menu-item>
</first-menu>

View File

@@ -0,0 +1,35 @@
{$layout}
{$template "menu"}
<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>
<textarea rows="15" name="names" placeholder="每行一个域名" ref="focus"></textarea>
</td>
</tr>
<tr>
<td>解析记录 *</td>
<td>
<ns-create-records-table :v-types="types" :v-default-ttl="defaultTTL"></ns-create-records-table>
</td>
</tr>
<tr>
<td>删除原有同名记录</td>
<td>
<checkbox name="removeOld"></checkbox>
<p class="comment">选中后,在导入新的记录之前,先删除域名中的同名且同类型记录。</p>
</td>
</tr>
<tr>
<td>清空原有记录</td>
<td>
<checkbox name="removeAll"></checkbox>
<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}
{$template "menu"}
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<table class="ui table selectable definition">
<tr>
<td class="title">域名 *</td>
<td>
<textarea name="names" rows="15" ref="focus" placeholder="每行一个域名"></textarea>
</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,50 @@
{$layout}
{$template "menu"}
<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>
<textarea rows="15" name="names" placeholder="每行一个域名" ref="focus"></textarea>
</td>
</tr>
<tr>
<td>匹配条件</td>
<td>
<table class="ui table definition">
<tr>
<td class="title">记录名</td>
<td>
<input type="text" name="searchName"/>
<p class="comment">只有不为空时才生效。</p>
</td>
</tr>
<tr>
<td>记录值</td>
<td>
<input type="text" name="searchValue"/>
<p class="comment">只有不为空时才生效。</p>
</td>
</tr>
<tr>
<td>记录类型</td>
<td>
<select class="ui dropdown auto-width" name="searchType">
<option v-for="type in types" :value="type.type">{{type.type}}</option>
</select>
</td>
</tr>
<tr>
<td>线路</td>
<td>
<ns-routes-selector name="searchRouteCodes"></ns-routes-selector>
</td>
</tr>
</table>
</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,59 @@
{$layout}
{$template "menu"}
<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>
<textarea rows="15" name="names" placeholder="每行一个域名" ref="focus"></textarea>
</td>
</tr>
<tr>
<td>匹配条件</td>
<td>
<table class="ui table definition">
<tr>
<td class="title">记录名</td>
<td>
<input type="text" name="searchName"/>
<p class="comment">只有不为空时才生效。</p>
</td>
</tr>
<tr>
<td>记录值</td>
<td>
<input type="text" name="searchValue"/>
<p class="comment">只有不为空时才生效。</p>
</td>
</tr>
<tr>
<td>记录类型</td>
<td>
<select class="ui dropdown auto-width" name="searchType">
<option v-for="type in types" :value="type.type">{{type.type}}</option>
</select>
</td>
</tr>
<tr>
<td>线路</td>
<td>
<ns-routes-selector name="searchRouteCodes"></ns-routes-selector>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>修改状态为</td>
<td>
<select class="ui dropdown auto-width" name="isOn">
<option value="1">启用</option>
<option value="0">停用</option>
</select>
</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,17 @@
{$layout}
{$template "menu"}
<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>
<textarea rows="15" name="records" placeholder="每行一个解析记录
example.com www A 192.168.1.100 600" ref="focus"></textarea>
<p class="comment">每行一个解析记录,格式:<code-label>域名 记录名 记录类型 记录值 TTL</code-label>,比如<code-label>example.com www A 192.168.1.100 600</code-label>,各项用空格隔开。</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,21 @@
{$layout}
{$template "menu"}
<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>
<textarea rows="15" name="names" placeholder="每行一个域名" ref="namesInput"></textarea>
</td>
</tr>
<tr v-if="hasGroups">
<td>所属分组</td>
<td>
<ns-domain-group-selector ref="groupSelector"></ns-domain-group-selector>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,24 @@
Tea.context(function () {
this.$delay(function () {
this.$refs.namesInput.focus()
this.changeUserId(0)
})
this.success = NotifyReloadSuccess("保存成功")
this.hasGroups = false
this.changeUserId = function (userId) {
this.$post("/ns/domains/groups/options")
.params({
userId: userId
})
.success(function (resp) {
this.hasGroups = resp.data.groups.length > 0
if (this.hasGroups) {
this.$delay(function () {
this.$refs.groupSelector.reload(userId)
})
}
})
}
})

View File

@@ -0,0 +1,85 @@
{$layout}
{$template "menu"}
<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>
<textarea rows="15" name="names" placeholder="每行一个域名" ref="focus"></textarea>
</td>
</tr>
<tr>
<td>匹配条件</td>
<td>
<table class="ui table definition">
<tr>
<td class="title">记录名</td>
<td>
<input type="text" name="searchName"/>
<p class="comment">只有不为空时才生效。</p>
</td>
</tr>
<tr>
<td>记录值</td>
<td>
<input type="text" name="searchValue"/>
<p class="comment">只有不为空时才生效。</p>
</td>
</tr>
<tr>
<td>记录类型</td>
<td>
<select class="ui dropdown auto-width" name="searchType">
<option v-for="type in types" :value="type.type">{{type.type}}</option>
</select>
</td>
</tr>
<tr>
<td>线路</td>
<td>
<ns-routes-selector name="searchRouteCodes"></ns-routes-selector>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>修改后的记录</td>
<td>
<table class="ui table definition">
<tr>
<td class="title">记录名</td>
<td>
<input type="text" name="newName"/>
<p class="comment">只有不为空时才生效。</p>
</td>
</tr>
<tr>
<td>记录值</td>
<td>
<input type="text" name="newValue"/>
<p class="comment">只有不为空时才生效。</p>
</td>
</tr>
<tr>
<td>记录类型</td>
<td>
<select class="ui dropdown auto-width" name="newType">
<option v-for="type in types" :value="type.type">{{type.type}}</option>
</select>
</td>
</tr>
<tr>
<td>线路</td>
<td>
<ns-routes-selector name="newRouteCodes"></ns-routes-selector>
</td>
</tr>
</table>
</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,47 @@
{$layout}
{$template "menu"}
<div v-if="!configIsValid">
<div class="margin"></div>
<p class="ui message error">管理员尚未完成系统配置,暂时无法添加域名,请联系客服。</p>
</div>
<form class="ui form" data-tea-action="$" data-tea-success="success" v-show="configIsValid">
<csrf-token></csrf-token>
<input type="hidden" name="addingType" :value="addingType"/>
<table class="ui table definition selectable">
<tr>
<td class="title">域名 *</td>
<td>
<!-- 单个添加 -->
<div v-show="addingType == 'one'">
<input type="text" name="name" maxlength="255" ref="nameInput" placeholder="单个域名"/>
<p class="comment">
<a href="" @click.prevent="setAddingType('batch')">[批量添加]</a>
</p>
</div>
<!-- 批量添加 -->
<div v-show="addingType == 'batch'">
<textarea rows="10" name="names" placeholder="每行一个域名" ref="namesInput"></textarea>
<p class="comment">
<a href="" @click.prevent="setAddingType('one')">[单个添加]</a>
</p>
</div>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr v-if="hasGroups">
<td>所属分组</td>
<td>
<ns-domain-group-selector ref="groupSelector"></ns-domain-group-selector>
</td>
</tr>
</tbody>
</table>
<submit-btn v-if="quotaCanCreate"></submit-btn>
<p class="ui message error" v-if="!quotaCanCreate">已添加域名个数已超出最大限额({{quotaMaxDomains}}个域名),请升级套餐后重试。</p>
</form>

View File

@@ -0,0 +1,42 @@
Tea.context(function () {
this.addingType = "one"
this.$delay(function () {
this.$refs.nameInput.focus()
this.changeUserId(0)
})
this.success = NotifySuccess("保存成功", "/ns/domains")
this.setAddingType = function (addingType) {
this.addingType = addingType
switch (addingType) {
case "one":
this.$delay(function () {
this.$refs.nameInput.focus()
})
break
case "batch":
this.$delay(function () {
this.$refs.namesInput.focus()
})
break
}
}
this.hasGroups = false
this.changeUserId = function (userId) {
this.$post("/ns/domains/groups/options")
.params({
userId: userId
})
.success(function (resp) {
this.hasGroups = resp.data.groups.length > 0
if (this.hasGroups) {
this.$delay(function () {
this.$refs.groupSelector.reload(userId)
})
}
})
}
})

View File

@@ -0,0 +1,26 @@
{$layout}
{$template "../menu_domain"}
<table class="ui table definition selectable">
<tr>
<td class="title">域名</td>
<td>{{domain.name}}</td>
</tr>
<tr>
<td>状态</td>
<td>
<span v-if="!domain.isOn" class="red">已停用</span>
<span v-if="domain.isOn" :class="{green:domain.status == 'verified', red:domain.status == 'rejected' || domain.status == 'forbidden'}">{{domain.statusName}}</span>
&nbsp; &nbsp;
</td>
</tr>
<tr>
<td>所属分组</td>
<td>
<div v-if="domain.groups.length > 0">
<a :href="'/ns/domains?userId=' + group.userId + '&groupId=' + group.id" class="ui label small basic" v-for="group in domain.groups">{{group.name}}</a>
</div>
<span v-else class="disabled">暂时没有分组</span>
</td>
</tr>
</table>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
})

View File

@@ -0,0 +1,38 @@
{$layout "layout_popup"}
<h3 style="margin-bottom: 0.4em">验证域名</h3>
<p class="comment">当前域名验证后,其下的域名解析才会生效。</p>
<form class="ui form" data-tea-action="$" data-tea-before="before" data-tea-success="submitSuccess" data-tea-done="done">
<csrf-token></csrf-token>
<input type="hidden" name="domainId" :value="domain.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">域名</td>
<td>{{domain.name}}</td>
</tr>
<tr>
<td>DNS主机地址</td>
<td>
<span v-if="hosts.length > 0">
<span v-for="host in hosts" class="ui label basic small">{{host}}</span>
</span>
<span v-if="hosts.length == 0" class="red">管理员没有设置可用的DNS主机域名请联系客服。</span>
<p class="comment" v-if="hosts.length > 0">请修改域名的DNS主机地址为以上地址。</p>
</td>
</tr>
<tr v-if="requireTXT && txt.length > 0">
<td>TXT记录</td>
<td>
<span v-if="txt.length > 0" id="txt-text">{{txt}}</span> <copy-to-clipboard :v-target="'txt-text'"></copy-to-clipboard>
<p class="comment">由于当前域名已被别的用户添加并验证所以需要重新验证所有权。请在域名中加入以上TXT记录子域名为<code-label>yanzheng</code-label>,需要在{{txtExpiresTime}}之前完成验证。</p>
</td>
</tr>
</table>
<submit-btn v-if="domain.status == 'none' && hosts.length > 0 && (!requireTXT || txt.length > 0)">验证</submit-btn>
<div class="margin"></div>
<div>
<span v-if="isVerifying" class="red">验证中,请稍候...</span>
<span class="red" v-if="!isOk && errorMessage.length > 0">验证失败:{{errorMessage}} <span v-if="rawErrorMessage.length > 0">({{rawErrorMessage}})</span></span>
</div>
</form>

View File

@@ -0,0 +1,29 @@
Tea.context(function () {
this.isOk = false
this.errorMessage = ""
this.rawErrorMessage= ""
this.isVerifying = false
this.before = function () {
this.isOk = false
this.errorMessage = ""
this.rawErrorMessage= ""
this.isVerifying = true
}
this.submitSuccess = function (resp) {
this.isOk = resp.data.isOk
this.errorMessage = resp.data.message
this.rawErrorMessage = resp.data.rawErrorMessage
if (this.isOk) {
teaweb.success("验证完成", function () {
window.parent.location.reload()
})
}
}
this.done = function () {
this.isVerifying = false
}
})

View File

@@ -0,0 +1,15 @@
{$layout "layout_popup"}
<h3>创建分组</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<table class="ui table selectable definition">
<tr>
<td class="title">分组名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus"/>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,22 @@
{$layout "layout_popup"}
<h3>修改分组</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="groupId" :value="group.id"/>
<table class="ui table selectable definition">
<tr>
<td class="title">分组名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus" v-model="group.name"/>
</td>
</tr>
<tr>
<td>启用</td>
<td>
<checkbox name="isOn" v-model="group.isOn"></checkbox>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,31 @@
{$layout}
<first-menu>
<menu-item href="/ns/domains/groups" active="true">分组列表</menu-item>
<span class="item disabled">|</span>
<menu-item @click.prevent="createGroup">[创建分组]</menu-item>
</first-menu>
<not-found-box v-if="groups.length == 0">暂时还没有分组。</not-found-box>
<table class="ui table selectable celled" v-if="groups.length > 0">
<thead>
<tr>
<th>分组名称</th>
<th class="width6">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="group in groups">
<td>
<a href="" @click.prevent="updateGroup(group.id)">{{group.name}} <i class="icon expand small"></i></a>
</td>
<td>
<label-on :v-is-on="group.isOn"></label-on>
</td>
<td>
<a href="" @click.prevent="updateGroup(group.id)">修改</a> &nbsp;
<a href="" @click.prevent="deleteGroup(group.id)">删除</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,30 @@
Tea.context(function () {
this.createGroup = function () {
teaweb.popup("/ns/domains/groups/createPopup", {
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
this.updateGroup = function (groupId) {
teaweb.popup("/ns/domains/groups/group/updatePopup?groupId=" + groupId, {
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
this.deleteGroup = function (groupId) {
let that = this
teaweb.confirm("确定要删除此分组吗?", function () {
that.$post("/ns/domains/groups/group/delete")
.params({
groupId: groupId
})
.success(function () {
teaweb.successRefresh("删除成功")
})
})
}
})

View File

@@ -0,0 +1,14 @@
{$layout}
{$template "menu_domain"}
<p class="ui message warning" v-if="!canUpdate">当前用户没有权限修改健康检查设置,请检查用户所属套餐。</p>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="domainId" :value="domainId"/>
<ns-records-health-check-config-box v-model="config"></ns-records-health-check-config-box>
<submit-btn :class="{disabled: !canUpdate}"></submit-btn>
</form>

View File

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

View File

@@ -0,0 +1,86 @@
{$layout}
{$template "menu"}
<div class="margin"></div>
<form class="ui form" method="get" action="/ns/domains" v-show="!hasSelectedDomains">
<div class="ui fields inline">
<div class="ui field" v-if="hasGroups">
<ns-domain-group-selector :v-domain-group-id="groupId" ref="groupSelector"></ns-domain-group-selector>
</div>
<div class="ui field">
<input type="text" name="keyword" v-model="keyword" placeholder="域名..."/>
</div>
<div class="ui field">
<button class="ui button" type="submit">搜索</button>
&nbsp;
<a :href="Tea.url('.')" v-if="groupId > 0 || keyword.length > 0">[清除条件]</a>
</div>
<div class="ui field"></div>
</div>
</form>
<form class="ui form" v-show="hasSelectedDomains">
<div class="ui fields inline">
<div class="ui field" v-if="hasVerifyingDomains">
<button class="ui button basic" type="button" @click.prevent="verifyDomains">批量验证选中域名</button>
</div>
<div class="ui field">
<button class="ui button basic" type="button" @click.prevent="deleteDomains">批量删除选中域名</button>
</div>
<div class="ui field">
<button class="ui button basic" type="button" @click.prevent="cancelDomains">取消</button>
</div>
</div>
</form>
<div v-if="domains.length == 0">
<div class="margin"></div>
<p class="comment">暂时还没有域名。</p>
</div>
<!-- 域名列表 -->
<table class="ui table selectable celled" v-if="domains.length > 0">
<thead>
<tr>
<th style="width: 1em">
<checkbox v-model="selectedAll"></checkbox>
</th>
<th>域名</th>
<th class="width5">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="domain in domains">
<td>
<checkbox ref="domainCheckbox" :v-value="domain.id" :checked="selectedAll ? 'checked' : ''"
@input="changeSelectedDomains" :status="domain.status"></checkbox>
</td>
<td>
<a :href="'/ns/domains/domain?domainId=' + domain.id">
<keyword :v-word="keyword">{{domain.name}}</keyword>
</a>
<div v-if="domain.groups.length > 0">
<a v-for="group in domain.groups" :href="'/ns/domains?userId=' + group.userId + '&groupId=' + group.id">
<grey-label :key="group.id">{{group.name}}</grey-label>
</a>
</div>
</td>
<td>
<span v-if="!domain.isOn" class="red">已停用</span>
<span v-if="domain.isOn">
<a href="" v-if="domain.status == 'none'" @click.prevent="verifyDomain(domain.id)"><span class="red"
style="border-bottom: 1px #db2828 dashed">未验证</span></a>
<span v-else
:class="{green:domain.status == 'verified', red:domain.status == 'rejected' || domain.status == 'forbidden'}">{{domain.statusName}}</span>
</span>
</td>
<td>
<a :href="'/ns/domains/domain?domainId=' + domain.id">详情</a> &nbsp; <a href=""
@click.prevent="deleteDomain(domain.id)">删除</a>
</td>
</tr>
</table>
<page-box></page-box>

View File

@@ -0,0 +1,151 @@
Tea.context(function () {
this.$delay(function () {
this.changeUserId(this.userId)
})
this.deleteDomain = function (domainId) {
let that = this
teaweb.confirm("确定要删除此域名吗?", function () {
that.$post("/ns/domains/delete")
.params({
domainId: domainId
})
.success(function () {
teaweb.reload()
})
})
}
this.verifyDomain = function (domainId) {
teaweb.popup("/ns/domains/domain/verifyPopup?domainId=" + domainId, {
height: "25em",
callback: function () {
}
})
}
this.hasGroups = false
this.changeUserId = function (userId) {
this.$post("/ns/domains/groups/options")
.params({
userId: userId
})
.success(function (resp) {
this.hasGroups = resp.data.groups.length > 0
if (this.hasGroups) {
this.$delay(function () {
this.$refs.groupSelector.reload(userId)
})
}
})
}
/**
* 批量操作
*/
this.selectedAll = false
this.hasSelectedDomains = false
this.hasVerifyingDomains = false
this.$delay(function () {
let that = this
this.$watch("selectedAll", function (b) {
let checkboxes = that.$refs.domainCheckbox
if (checkboxes != null) {
checkboxes.forEach(function (checkbox) {
if (b) {
checkbox.check()
that.hasSelectedDomains = true
let status = checkbox.$el.getAttribute("status")
if (status == "none") {
that.hasVerifyingDomains = true
}
} else {
checkbox.uncheck()
that.hasSelectedDomains = false
}
})
}
})
})
this.changeSelectedDomains = function () {
if (this.$refs == null) {
return
}
let checkboxes = this.$refs.domainCheckbox
if (checkboxes != null) {
let hasSelectedDomains = false
let hasVerifyingDomains = false
checkboxes.forEach(function (checkbox) {
if (checkbox.isChecked()) {
hasSelectedDomains = true
let status = checkbox.$el.getAttribute("status")
if (status == "none") {
hasVerifyingDomains = true
}
}
})
this.hasSelectedDomains = hasSelectedDomains
this.hasVerifyingDomains = hasVerifyingDomains
return
}
}
this.cancelDomains = function () {
let checkboxes = this.$refs.domainCheckbox
checkboxes.forEach(function (checkbox) {
checkbox.uncheck()
})
this.selectedAll = false
this.hasSelectedDomains = false
this.hasVerifyingDomains = false
}
this.deleteDomains = function () {
let checkboxes = this.$refs.domainCheckbox
let domainIds = []
checkboxes.forEach(function (checkbox) {
if (checkbox.isChecked()) {
let domainId = checkbox.vValue
if (typeof domainId == "number") {
domainIds.push(domainId)
}
}
})
let that = this
teaweb.confirm("确定要删除选中的域名吗?", function () {
that.$post(".deletePage")
.params({
domainIds: domainIds
})
.success(function () {
teaweb.successRefresh("删除成功")
})
})
}
this.verifyDomains = function () {
let checkboxes = this.$refs.domainCheckbox
let domainIds = []
checkboxes.forEach(function (checkbox) {
if (checkbox.isChecked()) {
let domainId = checkbox.vValue
if (typeof domainId == "number") {
domainIds.push(domainId)
}
}
})
let that = this
teaweb.confirm("html:确定要批量验证选中的域名吗?<br/>在此之前请确保这些域名已经设置了NS记录。<br/>已经验证过的域名,不再重复验证。", function () {
that.$post(".verifyPage")
.params({
domainIds: domainIds
})
.success(function () {
teaweb.successRefresh("验证成功")
})
})
}
})

View File

@@ -0,0 +1,123 @@
{$layout "layout_popup"}
<h3>创建记录</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="domainId" :value="domain.id"/>
<input type="hidden" name="addingType" :value="addingType"/>
<table class="ui table definition selectable">
<tr>
<td class="title">记录名</td>
<td>
<!-- 单个添加 -->
<div v-if="addingType == 'one'">
<div class="ui input right labeled">
<input type="text" name="name" ref="nameInput" style="width: 16em"/>
<span class="ui label">.{{domain.name}}</span>
</div>
<p class="comment"><a href="" @click.prevent="setAddingType('batch')">[批量添加]</a></p>
</div>
<!-- 批量添加 -->
<div v-if="addingType == 'batch'">
<textarea name="names" rows="10" placeholder="每行一个,不需要加顶级域名" ref="namesInput"></textarea>
<p class="comment"><a href="" @click.prevent="setAddingType('one')">[单个添加]</a></p>
</div>
</td>
</tr>
<tr>
<td>记录类型</td>
<td>
<select class="ui dropdown auto-width" name="type" v-model="type" @change="changeType">
<option v-for="t in types" :value="t.type">{{t.type}}</option>
</select>
<p class="comment">{{typeDescription}}</p>
</td>
</tr>
<tr>
<td>记录值 *</td>
<td>
<input type="text" name="value" maxlength="512"/>
</td>
</tr>
<!-- MX -->
<tr v-show="type == 'MX'">
<td class="color-border">MX优先级</td>
<td>
<input type="text" name="mxPriority" value="10" maxlength="5" size="5" style="width: 6em"/>
<p class="comment">数字越小越优先最高为0。</p>
</td>
</tr>
<!-- SRV -->
<tr v-show="type == 'SRV'">
<td class="color-border">SRV端口 *</td>
<td>
<input type="text" name="srvPort" value="" maxlength="5" size="5" style="width: 6em"/>
</td>
</tr>
<tr v-show="type == 'SRV'">
<td class="color-border">SRV优先级</td>
<td>
<input type="text" name="srvPriority" value="10" maxlength="5" size="5" style="width: 6em"/>
</td>
</tr>
<tr v-show="type == 'SRV'">
<td class="color-border">SRV权重</td>
<td>
<input type="text" name="srvWeight" value="10" maxlength="5" size="5" style="width: 6em"/>
</td>
</tr>
<!-- CAA -->
<tr v-show="type == 'CAA'">
<td class="color-border">CAA等级</td>
<td>
<input type="text" name="caaFlag" value="0" maxlength="3" size="5" style="width: 4em"/>
<p class="comment">取值在0到128之间。</p>
</td>
</tr>
<tr v-show="type == 'CAA'">
<td class="color-border">CAA标签</td>
<td>
<select class="ui dropdown auto-width" name="caaTag">
<option value="issue">issue</option>
<option value="issuewild">issuewild</option>
<option value="iodef">iodef</option>
</select>
</td>
</tr>
<tr>
<td>TTL</td>
<td>
<div class="ui input right labeled">
<input type="text" name="ttl" maxlength="10" style="width: 6em" v-model="defaultTTL"/>
<span class="ui label"></span>
</div>
</td>
</tr>
<tr>
<td>线路</td>
<td>
<ns-routes-selector></ns-routes-selector>
</td>
</tr>
<tr>
<td>权重</td>
<td>
<input type="text" name="weight" style="width: 6em" maxlength="6" value="0"/>
<p class="comment">不大于0的权重将当做10处理。</p>
</td>
</tr>
<tr>
<td>备注</td>
<td>
<textarea rows="2" name="description"></textarea>
</td>
</tr>
</table>
<submit-btn v-show="quotaCanCreate"></submit-btn>
<div class="ui message error" v-if="!quotaCanCreate">当前域名记录数已超出限额(每个域名最多{{quotaMaxRecords}}个记录数), 请升级套餐后重试。</div>
</form>

View File

@@ -0,0 +1,36 @@
Tea.context(function () {
this.type = "A"
this.typeDescription = ""
this.changeType = function () {
let that = this
this.types.forEach(function (v) {
if (v.type == that.type) {
that.typeDescription = v.description
}
})
}
this.changeType()
// 单个添加或者批量添加
this.addingType = "one"
this.$delay(function () {
this.$refs.nameInput.focus()
})
this.setAddingType = function (addingType) {
this.addingType = addingType
this.$delay(function () {
switch (addingType) {
case "one":
this.$refs.nameInput.focus()
break
case "batch":
this.$refs.namesInput.focus()
break
}
})
}
})

View File

@@ -0,0 +1,21 @@
{$layout "layout_popup"}
<h3>设置记录健康检查</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="recordId" :value="record.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">当前记录</td>
<td>
<span v-if="record.name.length > 0">{{record.name}}</span><span v-else class="disabled">[空]</span>.{{domain.name}}. {{record.type}} {{record.value}}
</td>
</tr>
</table>
<ns-record-health-check-config-box v-model="record.healthCheckConfig" :v-parent-config="domain.healthCheckConfig"></ns-record-health-check-config-box>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,21 @@
table thead th span {
font-weight: normal;
font-size: 0.8em;
}
table td.health-check-status-box {
text-align: center!important;
}
table td.health-check-status-box a {
display: none;
}
table td.health-check-status-box span.status {
display: none;
font-size: 0.8em;
}
table td.health-check-status-box:hover a {
display: inline;
}
table td.health-check-status-box:hover span.status {
display: inline;
}
/*# sourceMappingURL=index.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,KACC,MACC,GACC;EACC,mBAAA;EACA,gBAAA;;AALJ,KAUC,GAAE;EACD,4BAAA;;AAXF,KAUC,GAAE,wBAGD;EACC,aAAA;;AAdH,KAUC,GAAE,wBAOD,KAAI;EACH,aAAA;EACA,gBAAA;;AAnBH,KAuBC,GAAE,wBAAwB,MACzB;EACC,eAAA;;AAzBH,KAuBC,GAAE,wBAAwB,MAKzB,KAAI;EACH,eAAA","file":"index.css"}

View File

@@ -0,0 +1,95 @@
{$layout}
{$template "../menu_domain"}
<second-menu>
<menu-item @click.prevent="createRecord">[创建记录]</menu-item>
</second-menu>
<form class="ui form" method="get" action="/ns/domains/records" ref="recordsSearchForm">
<input type="hidden" name="domainId" :value="domain.id"/>
<div class="ui fields inline">
<div class="ui field">
<select class="ui dropdown" name="type" v-model="type">
<option value="">[记录类型]</option>
<option v-for="t in types" :value="t.type">{{t.type}}</option>
</select>
</div>
<div class="ui field">
<ns-route-selector :v-route-code="routeCode"></ns-route-selector>
</div>
<div class="ui field">
<input type="text" placeholder="记录名、备注..." name="keyword" v-model="keyword" style="width: 20em"/>
</div>
<div class="ui field">
<button type="submit" class="ui button">搜索</button>
</div>
</div>
</form>
<p class="comment" v-if="records.length == 0">暂时还没有记录。</p>
<div v-if="enableNameSearch || enableValueSearch" style="margin-top: 1em">
<a href="" v-if="enableNameSearch" class="ui label basic small blue" style="font-weight: normal" @click.prevent="searchWithName(searchingKeyword)">使用name:"{{searchingKeyword}}"精确搜索</a>
<a href="" v-if="enableValueSearch" class="ui label basic small blue" style="font-weight: normal" @click.prevent="searchWithValue(searchingKeyword)">使用value:"{{searchingKeyword}}"精确搜索</a>
</div>
<table class="ui table selectable celled" v-if="records.length > 0">
<thead>
<tr>
<th class="two wide">记录名 <sort-arrow name="nameOrder"></sort-arrow>
<br/><span>&nbsp;</span>
</th>
<th class="two wide">记录类型 <sort-arrow name="typeOrder"></sort-arrow><br/><span>&nbsp;</span></th>
<th>记录值<br/><span>&nbsp;</span></th>
<th class="two wide">TTL <sort-arrow name="ttlOrder"></sort-arrow><br/><span>&nbsp;</span></th>
<th class="two wide">线路<br/><span>&nbsp;</span></th>
<th style="width: 8em; text-align: center" v-show="domainHealthCheckIsOn">健康检查 <sort-arrow name="upOrder"></sort-arrow><br/><span>&nbsp;</span></th>
<th class="two wide">解析次数<br/><span class="grey">当前小时内</span></th>
<th class="width6">状态<br/><span>&nbsp;</span></th>
<th class="two op">操作<br/><span>&nbsp;</span></th>
</tr>
</thead>
<tr v-for="record in records">
<td><keyword :v-word="keyword">{{record.name}}&nbsp;</keyword>
<div v-if="(record.description != null && record.description.length > 0) || record.weight > 0 || (record.type == 'MX')">
<span class="grey small" v-if="record.description != null && record.description.length > 0"><keyword :v-word="keyword">{{record.description}}</keyword></span>
<span class="grey small" v-if="record.type == 'MX'">优先级:{{record.mxPriority}}</span>
<span class="grey small" v-if="record.weight > 0">权重:{{record.weight}}</span>
</div>
</td>
<td>{{record.type}}</td>
<td><keyword :v-word="keyword">{{record.value}}</keyword></td>
<td>{{formatTTL(record.ttl)}}</td>
<td>
<div v-for="route in record.routes" style="margin-top: 0.3em; margin-bottom: 0.3em">
<span class="ui label basic text tiny">{{route.name}}</span>
</div>
</td>
<td v-show="domainHealthCheckIsOn" class="health-check-status-box">
<div v-if="record.type == 'A' || record.type == 'AAAA'">
<span v-if="record.healthCheck.isOn">
<span v-if="record.isUp" title="在线"><i class="icon green circle small"></i><span class="status green">在线</span></span>
<span v-if="!record.isUp" title="离线"><i class="icon red circle small"></i><span class="status red">离线</span></span>
</span>
<span v-else class="disabled">
<i class="icon circle small"></i>
</span>
<br/>
<a href="" style="font-size: 0.9em; margin-top: 0.12em" @click.prevent="updateRecordHealthCheck(record.id)">[设置]</a>
<span v-if="!record.isUp">&nbsp; <a href="" @click.prevent="upRecord(record)">[上线]</a></span>
</div>
</td>
<td>
<a href="" @click.prevent="showStat(record.id)" title="查看统计"><i class="icon chart area small"></i></a>
<span v-if="record.stat.countRequests > 0">{{record.stat.countRequestsFormat}}</span>
<span v-else class="disabled">-</span>
</td>
<td><label-on :v-is-on="record.isOn"></label-on></td>
<td>
<a href="" @click.prevent="updateRecord(record.id)">修改</a> &nbsp;
<a href="" @click.prevent="deleteRecord(record.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,97 @@
Tea.context(function () {
this.createRecord = function () {
teaweb.popup(".createPopup?domainId=" + this.domain.id, {
height: "35em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.updateRecord = function (recordId) {
teaweb.popup(".updatePopup?recordId=" + recordId, {
height: "35em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteRecord = function (recordId) {
let that = this
teaweb.confirm("确定要删除此记录吗?", function () {
that.$post(".delete")
.params({
recordId: recordId
})
.success(function () {
teaweb.reload()
})
})
}
this.updateRecordHealthCheck = function (recordId) {
teaweb.popup(".healthCheckPopup?recordId=" + recordId, {
height: "26em",
callback: function () {
teaweb.success("设置成功", function () {
teaweb.reload()
})
}
})
}
this.upRecord = function (record) {
record.isUp = true
this.$post(".updateUp")
.params({
recordId: record.id
})
.success(function () {
teaweb.success("操作成功", function () {
teaweb.reload()
})
})
}
this.searchWithName = function (keyword) {
this.keyword = "name:\"" + keyword + "\""
this.$delay(function () {
this.$refs.recordsSearchForm.submit()
})
}
this.searchWithValue = function (keyword) {
this.keyword = "value:\"" + keyword + "\""
this.$delay(function () {
this.$refs.recordsSearchForm.submit()
})
}
this.showStat = function (recordId) {
teaweb.popup(".statPopup?recordId=" + recordId, {
})
}
this.formatTTL = function (ttl) {
if (ttl % 86400 == 0) {
let days = ttl / 86400
return days + "天"
}
if (ttl % 3600 == 0) {
let hours = ttl / 3600
return hours + "小时"
}
if (ttl % 60 == 0) {
let minutes = ttl / 60
return minutes + "分钟"
}
return ttl + "秒"
}
})

View File

@@ -0,0 +1,33 @@
table {
thead {
th {
span {
font-weight: normal;
font-size: 0.8em;
}
}
}
td.health-check-status-box {
text-align: center!important;
a {
display: none;
}
span.status {
display: none;
font-size: 0.8em;
}
}
td.health-check-status-box:hover {
a {
display: inline;
}
span.status {
display: inline;
}
}
}

View File

@@ -0,0 +1,4 @@
#stat-chart {
height: 18em;
}
/*# sourceMappingURL=statPopup.css.map */

View File

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

View File

@@ -0,0 +1,6 @@
{$layout "layout_popup"}
{$template "/echarts"}
<h3>记录"{{record.name}}"解析量统计 <span v-for="routeName in record.routeNames" class="ui label basic tiny">{{routeName}}</span></h3>
<div id="stat-chart"></div>

View File

@@ -0,0 +1,32 @@
Tea.context(function () {
this.$delay(function () {
this.reloadChart()
})
this.reloadChart = function () {
let axis = teaweb.countAxis(this.stats, function (stat){
return stat.countRequests
})
teaweb.renderLineChart({
id: "stat-chart",
name: "解析量统计",
values: this.stats,
x: function (stat) {
return stat.hour.substring(8)
},
tooltip: function (args, stats) {
let index = args.dataIndex
let year = stats[index].hour.substring(0, 4)
let month = stats[index].hour.substring(4, 6)
let day = stats[index].hour.substring(6, 8)
let hour = stats[index].hour.substring(8, 10)
return year + "-" + month + "-" + day + " " + hour + "时<br/>解析次数:" + teaweb.formatNumber(stats[index].countRequests)
},
value: function (stat) {
return stat.countRequests/axis.divider
},
axis: axis
})
}
})

View File

@@ -0,0 +1,3 @@
#stat-chart {
height: 18em;
}

View File

@@ -0,0 +1,122 @@
{$layout "layout_popup"}
<h3>修改记录</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="recordId" :value="record.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">记录名</td>
<td>
<div class="ui input right labeled">
<input type="text" name="name" ref="focus" v-model="record.name" style="width: 16em"/>
<span class="ui label">.{{domain.name}}</span>
</div>
</td>
</tr>
<tr>
<td>记录类型</td>
<td>
<select class="ui dropdown auto-width" name="type" v-model="type" @change="changeType">
<option v-for="t in types" :value="t.type">{{t.type}}</option>
</select>
<p class="comment">{{typeDescription}}</p>
</td>
</tr>
<tr>
<td>记录值 *</td>
<td>
<input type="text" name="value" maxlength="512" v-model="record.value"/>
</td>
</tr>
<!-- MX -->
<tr v-show="type == 'MX'">
<td class="color-border">MX优先级</td>
<td>
<input type="text" name="mxPriority" value="10" v-model="record.mxPriority" maxlength="5" size="5" style="width: 6em"/>
<p class="comment">数字越小越优先最高为0。</p>
</td>
</tr>
<!-- SRV -->
<tr v-show="type == 'SRV'">
<td class="color-border">SRV端口 *</td>
<td>
<input type="text" name="srvPort" value="" maxlength="5" size="5" style="width: 6em" v-model="record.srvPort"/>
</td>
</tr>
<tr v-show="type == 'SRV'">
<td class="color-border">SRV优先级</td>
<td>
<input type="text" name="srvPriority" value="10" maxlength="5" size="5" style="width: 6em" v-model="record.srvPriority"/>
</td>
</tr>
<tr v-show="type == 'SRV'">
<td class="color-border">SRV权重</td>
<td>
<input type="text" name="srvWeight" value="10" maxlength="5" size="5" style="width: 6em" v-model="record.srvWeight"/>
</td>
</tr>
<!-- CAA -->
<tr v-show="type == 'CAA'">
<td class="color-border">CAA等级</td>
<td>
<input type="text" name="caaFlag" value="0" maxlength="3" size="5" style="width: 4em" v-model="record.caaFlag"/>
<p class="comment">取值在0到128之间。</p>
</td>
</tr>
<tr v-show="type == 'CAA'">
<td class="color-border">CAA标签</td>
<td>
<select class="ui dropdown auto-width" name="caaTag" v-model="record.caaTag">
<option value="issue">issue</option>
<option value="issuewild">issuewild</option>
<option value="iodef">iodef</option>
</select>
</td>
</tr>
<tr>
<td>TTL</td>
<td>
<div class="ui input right labeled">
<input type="text" name="ttl" maxlength="10" style="width: 6em" v-model="record.ttl"/>
<span class="ui label"></span>
</div>
</td>
</tr>
<tr>
<td>线路</td>
<td>
<ns-routes-selector :v-routes="record.routes"></ns-routes-selector>
</td>
</tr>
<tr>
<td>权重</td>
<td>
<input type="text" name="weight" style="width: 6em" maxlength="6" v-model="record.weight"/>
<p class="comment">不大于0的权重将当做10处理。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>备注</td>
<td>
<textarea rows="2" name="description" v-model="record.description"></textarea>
</td>
</tr>
<tr>
<td>启用</td>
<td>
<checkbox name="isOn" v-model="record.isOn"></checkbox>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,15 @@
Tea.context(function () {
this.type = this.record.type
this.typeDescription = ""
this.changeType = function () {
let that = this
this.types.forEach(function (v) {
if (v.type == that.type) {
that.typeDescription = v.description
}
})
}
this.changeType()
})

View File

@@ -0,0 +1,34 @@
{$layout}
{$template "menu_domain"}
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="domainId" :value="domain.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">域名 *</td>
<td>
{{domain.name}}
</td>
</tr>
<tr v-show="dnsHosts != null && dnsHosts.length > 0">
<td>DNS服务器</td>
<td>
<span v-for="host in dnsHosts" class="ui basic small label">{{host}}</span>
</td>
</tr>
<tr v-if="hasGroups">
<td>所属分组</td>
<td>
<ns-domain-group-selector :v-domain-group-id="domain.groupId" ref="groupSelector"></ns-domain-group-selector>
</td>
</tr>
<tr>
<td>启用</td>
<td>
<checkbox name="isOn" value="1" v-model="domain.isOn"></checkbox>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,23 @@
Tea.context(function () {
this.$delay(function() {
this.changeUserId(this.domain.userId)
})
this.success = NotifySuccess("保存成功", "/ns/domains/domain?domainId=" + this.domain.id)
this.hasGroups = false
this.changeUserId = function (userId) {
this.$post("/ns/domains/groups/options")
.params({
userId: userId
})
.success(function (resp) {
this.hasGroups = resp.data.groups.length > 0
if (this.hasGroups) {
this.$delay(function () {
this.$refs.groupSelector.reload(userId)
})
}
})
}
})