前端页面

This commit is contained in:
robin
2026-02-24 19:10:27 +08:00
parent 60dc87e0f2
commit 2eb32b9f1f
59 changed files with 1537 additions and 890 deletions

View File

@@ -1,116 +1,141 @@
{$layout}
{$template "menu"}
{$template "/left_menu_with_menu"}
<style>
.httpdns-settings-grid {
margin-top: 0 !important;
}
.httpdns-settings-grid .left-nav-column {
max-width: 220px;
}
.httpdns-settings-grid .right-form-column {
padding-left: .3em !important;
}
.httpdns-side-menu .item {
padding-top: .8em !important;
padding-bottom: .8em !important;
}
.httpdns-mini-action {
display: inline-block;
font-size: 12px;
color: #6b7280;
margin-left: .55em;
line-height: 1.6;
}
.httpdns-mini-action:hover {
color: #1e70bf;
}
.httpdns-mini-action .icon {
margin-right: 0 !important;
font-size: .92em !important;
}
.httpdns-note.comment {
color: #8f9aa6 !important;
font-size: 12px;
margin-top: .45em !important;
}
.httpdns-mini-action {
display: inline-block;
font-size: 12px;
color: #6b7280;
margin-left: .55em;
line-height: 1.6;
}
.httpdns-mini-action:hover {
color: #1e70bf;
}
.httpdns-mini-action .icon {
margin-right: 0 !important;
font-size: .92em !important;
}
.httpdns-note.comment {
color: #8f9aa6 !important;
font-size: 12px;
margin-top: .45em !important;
}
.httpdns-auth-table td.title {
width: 260px !important;
min-width: 260px !important;
white-space: nowrap;
word-break: keep-all;
}
.httpdns-secret-line {
display: flex;
align-items: center;
gap: .35em;
}
.httpdns-mini-icon {
color: #8c96a3 !important;
font-size: 12px;
line-height: 1;
}
.httpdns-mini-icon:hover {
color: #5e6c7b !important;
}
.httpdns-state-on {
color: #21ba45;
font-weight: 600;
}
.httpdns-state-off {
color: #8f9aa6;
font-weight: 600;
}
</style>
<div>
<div class="ui menu text blue">
<div class="item"><strong>{{app.name}}</strong> (<code>{{settings.appId}}</code>)</div>
</div>
<div class="ui divider"></div>
<div class="right-box with-menu">
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="appId" :value="app.id" />
<div class="ui stackable grid httpdns-settings-grid">
<div class="three wide computer four wide tablet sixteen wide mobile column left-nav-column">
<div class="ui fluid vertical pointing menu httpdns-side-menu">
<a href="" class="item" :class="{active: activeSection == 'basic'}" @click.prevent="activeSection='basic'">基础配置</a>
<a href="" class="item" :class="{active: activeSection == 'auth'}" @click.prevent="activeSection='auth'">认证与密钥</a>
</div>
</div>
<table class="ui table selectable definition" v-show="activeSection == 'basic'">
<tr>
<td class="title">App ID</td>
<td><code>{{settings.appId}}</code></td>
</tr>
<tr>
<td class="title">应用启用</td>
<td>
<checkbox name="appStatus" value="1" v-model="settings.appStatus"></checkbox>
</td>
</tr>
<tr>
<td class="title">SNI 防护配置</td>
<td>
<span class="green">隐匿 SNI</span>
<p class="comment httpdns-note">当前默认采用隐匿 SNI 策略,避免在 TLS 握手阶段暴露业务域名。</p>
</td>
</tr>
</table>
<div class="thirteen wide computer twelve wide tablet sixteen wide mobile column right-form-column">
<table class="ui table selectable definition" v-show="activeSection == 'basic'">
<tr>
<td class="title">App ID</td>
<td><code>{{settings.appId}}</code></td>
</tr>
<tr>
<td class="title">应用启用</td>
<td><checkbox name="appStatus" value="1" v-model="settings.appStatus"></checkbox></td>
</tr>
<table class="ui table selectable definition httpdns-auth-table" v-show="activeSection == 'auth'">
<tr>
<td class="title">请求验签</td>
<td>
<span
:class="settings.signEnabled ? 'httpdns-state-on' : 'httpdns-state-off'">{{settings.signEnabled
? "已开启" : "已关闭"}}</span>
<a href="" class="ui mini button basic" style="margin-left: .8em;"
@click.prevent="toggleSignEnabled">{{settings.signEnabled ? "关闭请求验签" : "开启请求验签"}}</a>
<p class="comment httpdns-note">打开后,服务端会对请求进行签名校验。</p>
</td>
</tr>
<tr>
<td class="title">加签 Secret</td>
<td>
<div class="httpdns-secret-line">
<code>{{signSecretVisible ? settings.signSecretPlain : settings.signSecretMasked}}</code>
<a href="" class="httpdns-mini-icon" @click.prevent="signSecretVisible = !signSecretVisible"
:title="signSecretVisible ? '隐藏明文' : '查看明文'"><i class="icon"
:class="signSecretVisible ? 'eye slash' : 'eye'"></i></a>
<a href="" class="httpdns-mini-icon" title="复制加签 Secret"
@click.prevent="copySecret(settings.signSecretPlain, '加签 Secret')"><i
class="copy outline icon"></i></a>
<a href="" class="httpdns-mini-icon" title="重置加签 Secret" @click.prevent="resetSignSecret"><i
class="redo icon"></i></a>
</div>
<p class="comment httpdns-note">最近更新:{{settings.signSecretUpdatedAt}}</p>
<p class="comment httpdns-note" v-if="!settings.signEnabled">请求验签已关闭,当前不使用加签 Secret。</p>
<p class="comment httpdns-note">用于生成鉴权接口的安全密钥。</p>
</td>
</tr>
<tr>
<td class="title">数据加密 Secret</td>
<td>
<div class="httpdns-secret-line">
<code>{{aesSecretVisible ? settings.aesSecretPlain : settings.aesSecretMasked}}</code>
<a href="" class="httpdns-mini-icon" @click.prevent="aesSecretVisible = !aesSecretVisible"
:title="aesSecretVisible ? '隐藏明文' : '查看明文'"><i class="icon"
:class="aesSecretVisible ? 'eye slash' : 'eye'"></i></a>
<a href="" class="httpdns-mini-icon" title="复制数据加密 Secret"
@click.prevent="copySecret(settings.aesSecretPlain, 'AES Secret')"><i
class="copy outline icon"></i></a>
<a href="" class="httpdns-mini-icon" title="重置数据加密 Secret" @click.prevent="resetAESSecret"><i
class="redo icon"></i></a>
</div>
<p class="comment httpdns-note">最近更新:{{settings.aesSecretUpdatedAt}}</p>
<p class="comment httpdns-note">用于解析接口数据加密的密钥。</p>
</td>
</tr>
</table>
<tr>
<td class="title">SNI 防护配置</td>
<td>
<span class="green">隐匿 SNI</span>
<p class="comment httpdns-note">当前默认采用隐匿 SNI 策略,避免在 TLS 握手阶段暴露业务域名。</p>
</td>
</tr>
</table>
<table class="ui table selectable definition" v-show="activeSection == 'auth'">
<tr>
<td class="title">请求验签</td>
<td>
<span class="ui label tiny green" v-if="settings.signEnabled">已开启</span>
<span class="ui label tiny basic" v-else>已关闭</span>
<a href="" class="ui mini button" :class="settings.signEnabled ? 'basic' : 'primary'" style="margin-left: .8em;" @click.prevent="toggleSignEnabled">{{settings.signEnabled ? "关闭请求验签" : "开启请求验签"}}</a>
<p class="comment httpdns-note">打开后,服务端会对请求进行签名校验。</p>
</td>
</tr>
<tr>
<td class="title">加签 Secret</td>
<td>
<code>{{signSecretVisible ? settings.signSecretPlain : settings.signSecretMasked}}</code>
<a href="" class="httpdns-mini-action" @click.prevent="signSecretVisible = !signSecretVisible" :title="signSecretVisible ? '隐藏明文' : '查看明文'"><i class="icon" :class="signSecretVisible ? 'eye slash' : 'eye'"></i></a>
<a href="" class="httpdns-mini-action" title="复制加签 Secret" @click.prevent="copySecret(settings.signSecretPlain, '加签 Secret')"><i class="copy outline icon"></i></a>
<a href="" class="httpdns-mini-action" @click.prevent="resetSignSecret">[重置]</a>
<p class="comment httpdns-note">最近更新:{{settings.signSecretUpdatedAt}}</p>
<p class="comment httpdns-note" v-if="!settings.signEnabled">请求验签已关闭,当前不使用加签 Secret。</p>
<p class="comment httpdns-note">用于生成鉴权接口的安全密钥。</p>
</td>
</tr>
<tr>
<td class="title">AES 数据加密 Secret</td>
<td>
<code>{{aesSecretVisible ? settings.aesSecretPlain : settings.aesSecretMasked}}</code>
<a href="" class="httpdns-mini-action" @click.prevent="aesSecretVisible = !aesSecretVisible" :title="aesSecretVisible ? '隐藏明文' : '查看明文'"><i class="icon" :class="aesSecretVisible ? 'eye slash' : 'eye'"></i></a>
<a href="" class="httpdns-mini-action" title="复制 AES 数据加密 Secret" @click.prevent="copySecret(settings.aesSecretPlain, 'AES Secret')"><i class="copy outline icon"></i></a>
<a href="" class="httpdns-mini-action" @click.prevent="resetAESSecret">[重置]</a>
<p class="comment httpdns-note">最近更新:{{settings.aesSecretUpdatedAt}}</p>
<p class="comment httpdns-note">用于解析接口数据加密的密钥。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</div>
</div>
<submit-btn></submit-btn>
</form>
</div>
</div>

View File

@@ -1,5 +1,5 @@
Tea.context(function () {
this.activeSection = "basic";
this.activeSection = this.activeSection || "basic";
this.success = NotifyReloadSuccess("保存成功");
this.signSecretVisible = false;
this.aesSecretVisible = false;
@@ -9,7 +9,7 @@ Tea.context(function () {
let targetIsOn = !this.settings.signEnabled;
if (targetIsOn) {
teaweb.confirm("html:开启后,服务端会对解析请求进行签鉴权,<span class='red'>未签名、签名无效或过期的请求都解析失败</span>,确认开启吗?", function () {
teaweb.confirm("html:开启后,服务端会对解析请求进行签鉴权,<span class='red'>未签名、签名无效或过期的请求都解析失败</span>,确认开启吗?", function () {
that.$post("/httpdns/apps/app/settings/toggleSignEnabled")
.params({
appId: that.app.id,
@@ -23,7 +23,7 @@ Tea.context(function () {
return;
}
teaweb.confirm("html:关闭后,服务端不会对解析请求进行签鉴权,可能<span class='red'>存在被刷风险</span>,确认关闭吗?", function () {
teaweb.confirm("html:关闭后,服务端不会对解析请求进行签鉴权,可能<span class='red'>存在被刷风险</span>,确认关闭吗?", function () {
that.$post("/httpdns/apps/app/settings/toggleSignEnabled")
.params({
appId: that.app.id,
@@ -109,3 +109,4 @@ Tea.context(function () {
});
};
});

View File

@@ -13,13 +13,23 @@
</td>
</tr>
<tr>
<td>所属集群 *</td>
<td>集群 *</td>
<td>
<select class="ui dropdown" name="clusterId" v-model="defaultClusterId">
<select class="ui dropdown" name="primaryClusterId" v-model="defaultPrimaryClusterId">
<option value="">[请选择集群]</option>
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
</select>
<p class="comment">应用解析请求将由所选集群下的网关节点处理。默认值来自“HTTPDNS 用户设置”</p>
<p class="comment">主集群用于优先处理应用解析请求。</p>
</td>
</tr>
<tr>
<td>备集群</td>
<td>
<select class="ui dropdown" name="backupClusterId" v-model="defaultBackupClusterId">
<option :value="0">[不设置]</option>
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
</select>
<p class="comment">当主集群不可用时,可切换到备集群。</p>
</td>
</tr>
<tr>

View File

@@ -1,66 +1,55 @@
{$layout}
{$template "menu"}
{$layout}
<style>
.httpdns-col-ttl {
width: 72px;
white-space: nowrap;
}
.httpdns-col-actions {
width: 130px;
white-space: nowrap;
}
</style>
<div>
<div class="ui menu text blue">
<div class="item"><strong>{{app.name}}</strong> (<code>{{app.appId}}</code>)</div>
</div>
<div class="ui divider"></div>
<second-menu>
<a class="item" :href="'/httpdns/apps/domains?appId=' + app.id">{{app.name}}</a>
<span class="item disabled" style="padding-left: 0; padding-right: 0">&raquo;</span>
<a class="item" :href="'/httpdns/apps/domains?appId=' + app.id">域名列表</a>
<span class="item disabled" style="padding-left: 0; padding-right: 0">&raquo;</span>
<div class="item"><strong>{{domain.name}}</strong></div>
<a href="" class="item" @click.prevent="createRecord" :class="{disabled: !domain || !domain.id}">创建规则</a>
</second-menu>
<div class="ui small message">
当前域名:<strong>{{domain.name}}</strong>
</div>
<table class="ui table selectable celled" v-if="records.length > 0" style="margin-top: .8em;">
<thead>
<tr>
<th>线路</th>
<th>规则名称</th>
<th>解析记录</th>
<th class="httpdns-col-ttl">TTL</th>
<th class="width10">状态</th>
<th class="httpdns-col-actions">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="record in records">
<td>{{record.lineText}}</td>
<td>{{record.ruleName}}</td>
<td>{{record.recordValueText}}</td>
<td class="httpdns-col-ttl">{{record.ttl}}s</td>
<td>
<span class="green" v-if="record.isOn">已启用</span>
<span class="grey" v-else>已停用</span>
</td>
<td class="httpdns-col-actions">
<a href="" @click.prevent="editRecord(record.id)">编辑</a> &nbsp;|&nbsp;
<a href="" @click.prevent="toggleRecord(record)">{{record.isOn ? "停用" : "启用"}}</a> &nbsp;|&nbsp;
<a href="" @click.prevent="deleteRecord(record.id)">删除</a>
</td>
</tr>
</tbody>
</table>
<a href="" @click.prevent="createRecord" class="ui button primary small" :class="{disabled: !domain || !domain.id}">
<i class="icon plus"></i> 新增自定义解析规则
</a>
<a :href="'/httpdns/apps/domains?appId=' + app.id" class="ui button small">返回域名管理</a>
<table class="ui table selectable celled" v-if="records.length > 0" style="margin-top: 1em;">
<thead>
<tr>
<th>线路</th>
<th>规则名称</th>
<th>SDNS 参数</th>
<th>解析记录</th>
<th class="httpdns-col-ttl">TTL</th>
<th class="width10">状态</th>
<th class="httpdns-col-actions">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="record in records">
<td>{{record.lineText}}</td>
<td>{{record.ruleName}}</td>
<td>{{record.paramsText}}</td>
<td>
{{record.recordValueText}}
</td>
<td class="httpdns-col-ttl">{{record.ttl}}s</td>
<td>
<span class="green" v-if="record.isOn">已启用</span>
<span class="grey" v-else>已停用</span>
</td>
<td class="httpdns-col-actions">
<a href="" @click.prevent="editRecord(record.id)">编辑</a> &nbsp;|&nbsp;
<a href="" @click.prevent="toggleRecord(record)">{{record.isOn ? "停用" : "启用"}}</a> &nbsp;|&nbsp;
<a href="" @click.prevent="deleteRecord(record.id)">删除</a>
</td>
</tr>
</tbody>
</table>
<p class="ui warning message" v-if="!domain || !domain.id">当前应用暂无可用域名,请先到域名管理中添加域名。</p>
<p class="ui message" v-else-if="records.length == 0">当前域名还没有自定义解析规则。</p>
</div>
<not-found-box v-if="!domain || !domain.id">当前应用暂无可用域名,请先到域名管理中添加域名。</not-found-box>
<not-found-box v-else-if="records.length == 0">当前域名还没有自定义解析规则。</not-found-box>

View File

@@ -1,4 +1,4 @@
Tea.context(function () {
Tea.context(function () {
if (typeof this.records == "undefined") {
this.records = [];
}

View File

@@ -38,7 +38,6 @@
<input type="hidden" name="domainId" :value="domain.id" />
<input type="hidden" name="domain" :value="record.domain" />
<input type="hidden" name="recordId" :value="record.id" />
<input type="hidden" name="sdnsParamsJSON" :value="JSON.stringify(sdnsParams)" />
<input type="hidden" name="recordItemsJSON" :value="JSON.stringify(recordItems)" />
<table class="ui table definition selectable">
@@ -81,26 +80,6 @@
</div>
</td>
</tr>
<tr>
<td class="title">SDNS 参数配置</td>
<td>
<div class="httpdns-row" v-for="(param, index) in sdnsParams">
<div class="field flex">
<input type="text" maxlength="64" placeholder="参数名称" v-model="param.name" />
</div>
<div class="field flex">
<input type="text" maxlength="64" placeholder="参数值" v-model="param.value" />
</div>
<a href="" @click.prevent="removeSDNSParam(index)" title="删除"><i class="icon trash alternate outline"></i></a>
</div>
<div class="httpdns-inline-actions">
<a href="" @click.prevent="addSDNSParam" :class="{disabled: sdnsParams.length >= 10}">
<i class="icon plus circle"></i>添加参数
</a>
<span class="count">{{sdnsParams.length}}/10</span>
</div>
</td>
</tr>
<tr>
<td class="title">解析记录值 *</td>
<td>

View File

@@ -75,11 +75,6 @@ Tea.context(function () {
vm.record.weightEnabled = vm.normalizeBoolean(vm.record.weightEnabled, false);
vm.record.isOn = vm.normalizeBoolean(vm.record.isOn, true);
vm.sdnsParams = vm.parseJSONList(vm.record.sdnsParamsJson);
if (vm.sdnsParams.length == 0) {
vm.sdnsParams.push({name: "", value: ""});
}
vm.recordItems = vm.parseJSONList(vm.record.recordItemsJson);
if (vm.recordItems.length == 0) {
vm.recordItems.push({type: "A", value: "", weight: 100});
@@ -139,23 +134,6 @@ Tea.context(function () {
}
};
vm.addSDNSParam = function () {
if (vm.sdnsParams.length >= 10) {
return;
}
vm.sdnsParams.push({name: "", value: ""});
};
vm.removeSDNSParam = function (index) {
if (index < 0 || index >= vm.sdnsParams.length) {
return;
}
vm.sdnsParams.splice(index, 1);
if (vm.sdnsParams.length == 0) {
vm.sdnsParams.push({name: "", value: ""});
}
};
vm.addRecordItem = function () {
if (vm.recordItems.length >= 10) {
return;

View File

@@ -0,0 +1,12 @@
{$layout}
<second-menu>
<a class="item" :href="'/httpdns/apps/domains?appId=' + app.id">{{app.name}}</a>
<span class="item disabled" style="padding-left: 0; padding-right: 0">&raquo;</span>
<div class="item"><strong>删除应用</strong></div>
</second-menu>
<div class="buttons-box">
<button class="ui button red" type="button" @click.prevent="deleteApp(app.id)">删除当前应用</button>
<p class="comment" style="margin-top: .8em;">包含{{domainCount}}域名</p>
</div>

View File

@@ -0,0 +1,16 @@
Tea.context(function () {
this.deleteApp = function (appId) {
let that = this;
teaweb.confirm("确定要删除此应用吗?", function () {
that.$post("/httpdns/apps/delete")
.params({
appId: appId
})
.success(function () {
teaweb.success("删除成功", function () {
window.location = "/httpdns/apps";
});
});
});
};
});

View File

@@ -1,35 +1,62 @@
{$layout}
{$template "menu"}
{$layout}
<div>
<div class="ui menu text blue">
<div class="item"><strong>{{app.name}}</strong> (<code>{{app.appId}}</code>)</div>
<div class="margin"></div>
<style>
.httpdns-domains-table .httpdns-domains-op {
white-space: nowrap;
}
</style>
<second-menu>
<a class="item" :href="'/httpdns/apps/domains?appId=' + app.id">{{app.name}}</a>
<span class="item disabled" style="padding-left: 0; padding-right: 0">&raquo;</span>
<div class="item"><strong>域名列表</strong></div>
<a href="" class="item" @click.prevent="bindDomain">创建域名</a>
</second-menu>
<form class="ui form small" @submit.prevent="doSearch">
<div class="ui fields inline">
<div class="ui field">
<input type="text" v-model.trim="keywordInput" placeholder="按域名筛选..." />
</div>
<div class="ui field">
<button type="submit" class="ui button small">搜索</button>
&nbsp;
<a href="" v-if="keyword.length > 0" @click.prevent="clearSearch">[清除条件]</a>
</div>
</div>
<div class="ui divider"></div>
</form>
<a href="" @click.prevent="bindDomain" class="ui button primary small"><i class="icon plus"></i>添加域名</a>
<table class="ui table selectable celled httpdns-domains-table" v-if="filteredDomains().length > 0">
<colgroup>
<col style="width:60%;" />
<col style="width:20%;" />
<col style="width:20%;" />
</colgroup>
<thead>
<tr>
<th>服务域名</th>
<th class="width10">规则策略</th>
<th class="httpdns-domains-op">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="domain in filteredDomains()">
<td>
<div><strong>{{domain.name}}</strong></div>
</td>
<td>
<a
:href="'/httpdns/apps/customRecords?appId=' + app.id + '&domainId=' + domain.id">{{domain.customRecordCount}}</a>
</td>
<td class="httpdns-domains-op">
<a :href="'/httpdns/apps/customRecords?appId=' + app.id + '&domainId=' + domain.id">自定义解析</a>
&nbsp;|&nbsp;
<a href="" @click.prevent="deleteDomain(domain.id)">解绑</a>
</td>
</tr>
</tbody>
</table>
<table class="ui table selectable celled" v-if="domains.length > 0" style="margin-top: 1em;">
<thead>
<tr>
<th>域名列表</th>
<th class="two wide">规则策略</th>
<th class="two op">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="domain in domains">
<td><strong>{{domain.name}}</strong></td>
<td>
<a :href="'/httpdns/apps/customRecords?appId=' + app.id + '&domainId=' + domain.id">{{domain.customRecordCount}}</a>
</td>
<td>
<a :href="'/httpdns/apps/customRecords?appId=' + app.id + '&domainId=' + domain.id">自定义解析</a> &nbsp;|&nbsp;
<a href="" @click.prevent="deleteDomain(domain.id)">解绑</a>
</td>
</tr>
</tbody>
</table>
<p class="ui message" v-if="domains.length == 0">该应用尚未绑定域名。</p>
</div>
<not-found-box v-if="domains.length == 0">当前应用尚未绑定域名。</not-found-box>
<not-found-box v-if="domains.length > 0 && filteredDomains().length == 0">没有匹配的域名。</not-found-box>

View File

@@ -1,8 +1,37 @@
Tea.context(function () {
Tea.context(function () {
if (typeof this.keywordInput == "undefined") {
this.keywordInput = "";
}
if (typeof this.keyword == "undefined") {
this.keyword = "";
}
if (typeof this.domains == "undefined") {
this.domains = [];
}
this.keywordInput = this.keyword;
this.doSearch = function () {
this.keyword = this.keywordInput.trim();
};
this.clearSearch = function () {
this.keywordInput = "";
this.keyword = "";
};
this.filteredDomains = function () {
let keyword = this.keyword.trim().toLowerCase();
if (keyword.length == 0) {
return this.domains;
}
return this.domains.filter(function (domain) {
let name = (domain.name || "").toLowerCase();
return name.indexOf(keyword) >= 0;
});
};
this.bindDomain = function () {
teaweb.popup("/httpdns/apps/domains/createPopup?appId=" + this.app.id, {
height: "24em",
@@ -11,6 +40,19 @@ Tea.context(function () {
});
};
this.deleteApp = function () {
let that = this;
teaweb.confirm("确定要删除当前应用吗?", function () {
that.$post("/httpdns/apps/delete")
.params({
appId: that.app.id
})
.success(function () {
window.location = "/httpdns/apps";
});
});
};
this.deleteDomain = function (domainId) {
let that = this;
teaweb.confirm("确定要解绑这个域名吗?", function () {

View File

@@ -1,6 +1,14 @@
{$layout}
{$template "menu"}
<div class="ui margin"></div>
<style>
.httpdns-apps-table .httpdns-apps-op {
white-space: nowrap;
}
</style>
<div>
<form method="get" class="ui form" action="/httpdns/apps">
<div class="ui fields inline">
@@ -16,21 +24,30 @@
<p class="ui message" v-if="apps.length == 0">暂时没有符合条件的 HTTPDNS 应用。</p>
<table class="ui table selectable celled" v-if="apps.length > 0">
<table class="ui table selectable celled httpdns-apps-table" v-if="apps.length > 0">
<colgroup>
<col style="width:34%;" />
<col style="width:28%;" />
<col style="width:12%;" />
<col style="width:10%;" />
<col style="width:16%;" />
</colgroup>
<thead>
<tr>
<th>应用名称</th>
<th>AppID</th>
<th>绑定域名数</th>
<th class="one wide center">状态</th>
<th class="tz op">操作</th>
<th class="center">绑定域名数</th>
<th class="center">状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="app in apps">
<td>
<a :href="'/httpdns/apps/app?appId=' + app.id">
<strong><keyword :v-word="keyword">{{app.name}}</keyword></strong>
<strong>
<keyword :v-word="keyword">{{app.name}}</keyword>
</strong>
</a>
</td>
<td>
@@ -41,13 +58,14 @@
<td class="center">
<label-on :v-is-on="app.isOn"></label-on>
</td>
<td>
<td class="httpdns-apps-op">
<a :href="'/httpdns/apps/app?appId=' + app.id">域名管理</a> &nbsp;|&nbsp;
<a :href="'/httpdns/apps/app/settings?appId=' + app.id">应用设置</a>
<a :href="'/httpdns/apps/app/settings?appId=' + app.id">应用设置</a> &nbsp;|&nbsp;
<a :href="'/httpdns/apps/sdk?appId=' + app.id">SDK集成</a>
</td>
</tr>
</tbody>
</table>
<div class="page" v-html="page"></div>
</div>
</div>

View File

@@ -0,0 +1,79 @@
{$layout}
<style>
.httpdns-sdk-cards {
margin-top: 1em;
}
.httpdns-sdk-cards > .card {
min-height: 168px;
}
.httpdns-sdk-desc {
margin-top: .6em;
color: #5e6c7b;
}
.httpdns-sdk-meta {
margin-top: .35em;
color: #9aa6b2;
font-size: 12px;
}
.httpdns-sdk-actions {
display: inline-flex;
align-items: center;
gap: .6em;
flex-wrap: wrap;
}
.httpdns-sdk-actions .button {
padding-left: .8em !important;
padding-right: .8em !important;
}
</style>
<second-menu>
<a class="item" :href="'/httpdns/apps/domains?appId=' + app.id">{{app.name}}</a>
<span class="item disabled" style="padding-left: 0; padding-right: 0">&raquo;</span>
<div class="item"><strong>SDK 集成</strong></div>
</second-menu>
<div class="ui three stackable cards httpdns-sdk-cards">
<div class="card">
<div class="content">
<div class="header"><i class="icon android green"></i> Android SDK</div>
<div class="httpdns-sdk-meta">Java / Kotlin</div>
<div class="description httpdns-sdk-desc">适用于 Android 客户端接入。</div>
</div>
<div class="extra content">
<div class="httpdns-sdk-actions">
<a class="ui button compact mini basic" href="javascript:;"><i class="icon download"></i> 下载 SDK</a>
<a class="ui button compact mini basic" href="javascript:;"><i class="icon book"></i> 集成文档</a>
</div>
</div>
</div>
<div class="card">
<div class="content">
<div class="header"><i class="icon apple grey"></i> iOS SDK</div>
<div class="httpdns-sdk-meta">Objective-C / Swift</div>
<div class="description httpdns-sdk-desc">适用于 iOS 客户端接入。</div>
</div>
<div class="extra content">
<div class="httpdns-sdk-actions">
<a class="ui button compact mini basic" href="javascript:;"><i class="icon download"></i> 下载 SDK</a>
<a class="ui button compact mini basic" href="javascript:;"><i class="icon book"></i> 集成文档</a>
</div>
</div>
</div>
<div class="card">
<div class="content">
<div class="header"><i class="icon mobile alternate blue"></i> Flutter SDK</div>
<div class="httpdns-sdk-meta">Dart / Plugin</div>
<div class="description httpdns-sdk-desc">适用于 Flutter 跨平台接入。</div>
</div>
<div class="extra content">
<div class="httpdns-sdk-actions">
<a class="ui button compact mini basic" href="javascript:;"><i class="icon download"></i> 下载 SDK</a>
<a class="ui button compact mini basic" href="javascript:;"><i class="icon book"></i> 集成文档</a>
</div>
</div>
</div>
</div>

View File

@@ -1,8 +1,4 @@
<second-menu>
<menu-item :href="'/httpdns/clusters/cluster?clusterId=' + cluster.id">{{cluster.name}}</menu-item>
<second-menu>
<a class="item" :href="'/httpdns/clusters/cluster?clusterId=' + cluster.id">{{cluster.name}}</a>
<span class="item disabled" style="padding-left: 0; padding-right: 0">&raquo;</span>
<menu-item :href="'/httpdns/clusters/cluster?clusterId=' + cluster.id" code="index">节点列表</menu-item>
<menu-item :href="'/httpdns/clusters/createNode?clusterId=' + cluster.id" code="createNode">创建节点</menu-item>
<menu-item :href="'/httpdns/clusters/cluster/settings?clusterId=' + cluster.id" code="settings">集群设置</menu-item>
<menu-item :href="'/httpdns/clusters/delete?clusterId=' + cluster.id" code="delete">删除集群</menu-item>
</second-menu>
</second-menu>

View File

@@ -1,10 +1,11 @@
{$layout}
{$template "cluster_menu"}
<div class="ui margin"></div>
<div class="right-menu">
<a :href="'/httpdns/clusters/createNode?clusterId=' + clusterId" class="item">[创建节点]</a>
</div>
<second-menu>
<a class="item" :href="'/httpdns/clusters/cluster?clusterId=' + cluster.id">{{cluster.name}}</a>
<span class="item disabled" style="padding-left: 0; padding-right: 0">&raquo;</span>
<div class="item"><strong>节点列表</strong></div>
<a class="item" :href="'/httpdns/clusters/createNode?clusterId=' + clusterId">创建节点</a>
</second-menu>
<form class="ui form" action="/httpdns/clusters/cluster">
<input type="hidden" name="clusterId" :value="clusterId" />
@@ -43,6 +44,9 @@
<tr>
<th>节点名称</th>
<th>IP地址</th>
<th class="center">CPU</th>
<th class="center">内存</th>
<th class="center">负载</th>
<th class="width-5 center">状态</th>
<th class="two op">操作</th>
</tr>
@@ -64,11 +68,21 @@
<div class="ui label tiny basic">{{addr.ip}}
<span class="small" v-if="addr.name.length > 0">{{addr.name}}</span>
<span class="small red" v-if="!addr.canAccess" title="不公开访问">[内]</span>
<span class="small red" v-if="!addr.isOn">[下线]</span>
<span class="small red" v-if="!addr.isUp" title="健康检查失败">[宕机]</span>
</div>
</div>
</td>
<td class="center">
<span v-if="node.status != null && node.status.isActive">{{node.status.cpuUsageText}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">
<span v-if="node.status != null && node.status.isActive">{{node.status.memUsageText}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">
<span v-if="node.status != null && node.status.isActive && node.status.load1m > 0">{{node.status.load1m}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">
<div v-if="!node.isUp">
<span class="red">宕机</span>
@@ -88,4 +102,4 @@
</tr>
</tbody>
</table>
<div class="page" v-html="page"></div>
<div class="page" v-html="page"></div>

View File

@@ -1,31 +1,57 @@
{$layout}
{$template "cluster_menu"}
<div class="ui margin"></div>
<h3 class="ui header">集群设置</h3>
<p><strong>{{cluster.name}}</strong></p>
{$var "header"}
<script src="/servers/certs/datajs" type="text/javascript"></script>
<script src="/js/sortable.min.js" type="text/javascript"></script>
{$end}
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="clusterId" :value="cluster.id" />
<table class="ui table definition selectable">
<tr>
<td class="title">集群名称 *</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus" v-model="cluster.name" />
<p class="comment">用于区分不同环境的解析节点池。</p>
</td>
</tr>
<tr>
<td>集群服务域名 *</td>
<td>
<input type="text" name="gatewayDomain" maxlength="255" v-model="settings.gatewayDomain" placeholder="例如 gw-hz.httpdns.example.com" />
<p class="comment">该集群下应用用于 SDK 接入的 HTTPDNS 服务域名。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<second-menu>
<a class="item" :href="'/httpdns/clusters/cluster?clusterId=' + cluster.id">{{cluster.name}}</a>
<span class="item disabled" style="padding-left: 0; padding-right: 0">&raquo;</span>
<div class="item"><strong>集群设置</strong></div>
</second-menu>
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="clusterId" :value="cluster.id" />
<table class="ui table definition selectable" v-show="activeSection == 'basic'">
<tr>
<td class="title">集群名称 *</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus" v-model="cluster.name" />
<p class="comment">用于在系统内部标识该 HTTPDNS 集群。</p>
</td>
</tr>
<tr>
<td>服务域名 *</td>
<td>
<input type="text" name="gatewayDomain" maxlength="255" v-model="settings.gatewayDomain"
placeholder="例如 gw-hz.httpdns.example.com" />
<p class="comment">当前集群对外提供 HTTPDNS 服务的接入域名。</p>
</td>
</tr>
<tr>
<td>节点安装根目录</td>
<td>
<input type="text" name="installDir" maxlength="100" v-model="settings.installDir" />
<p class="comment">边缘节点安装 HTTPDNS 服务的默认所在目录,此目录将被用于下发配置。通常保持默认即可。</p>
</td>
</tr>
<tr>
<td>默认解析 TTL</td>
<td>
<div class="ui input right labeled">
<input type="text" name="cacheTtl" maxlength="5" v-model="settings.cacheTtl"
style="width: 6em" />
<span class="ui label"></span>
</div>
<p class="comment">SDK 向 HTTPDNS 请求域名解析时,返回的默认缓存有效期 (TTL)。SDK 超时后将重新发起请求。</p>
</td>
</tr>
<tr>
<td>降级超时容忍度</td>
<td>
@@ -34,38 +60,46 @@
style="width: 6em" />
<span class="ui label">毫秒</span>
</div>
<p class="comment">HTTPDNS 网关请求源站超时多长时间后强制降级返回缓存兜底IP保障 P99 响应延迟)。</p>
</td>
</tr>
<tr>
<td>本地内存缓存</td>
<td>
<div class="ui input right labeled">
<input type="text" name="cacheTtl" maxlength="5" v-model="settings.cacheTtl"
style="width: 6em" />
<span class="ui label"></span>
</div>
<p class="comment">在网关节点内存中缓存解析结果的时长,缓解峰值查询压力。</p>
</td>
</tr>
<tr>
<td>节点安装根目录</td>
<td>
<input type="text" name="installDir" maxlength="100" v-model="settings.installDir" />
<p class="comment">此集群下新加网关节点的主程序默认部署路径。</p>
<p class="comment">HTTPDNS 节点在回源查询其它 DNS 时的最大等待时间。超出此时间将触发服务降级逻辑(返回上一有效缓存或错误)。</p>
</td>
</tr>
<tr>
<td>启用当前集群</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="settings.isOn" />
<input type="checkbox" name="isOn" value="1" v-model="settings.isOn"
@change="syncDefaultCluster" />
<label></label>
</div>
<p class="comment">如果取消启用,此集群下的所有 HTTPDNS 网关节点将停止处理解析请求</p>
<p class="comment">如果取消启用,整个集群的 HTTPDNS 解析服务将停止</p>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>
<tr>
<td>默认集群</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isDefaultCluster" value="1" v-model="settings.isDefaultCluster" />
<label>设置为默认部署集群</label>
</div>
<p class="comment">全局设置。如果应用未单独指定集群,将默认分配和部署到该集群中。</p>
</td>
</tr>
</table>
<table class="ui table selectable definition" v-show="activeSection == 'tls'">
<tr>
<td class="title">绑定端口 *</td>
<td>
<network-addresses-box :v-url="'/httpdns/addPortPopup'" :v-addresses="tlsConfig.listen"
:v-protocol="'tls'" :v-support-range="true"></network-addresses-box>
</td>
</tr>
</table>
<!-- SSL配置 -->
<ssl-config-box v-show="activeSection == 'tls'" :v-ssl-policy="tlsConfig.sslPolicy"
:v-protocol="'tls'"></ssl-config-box>
<submit-btn></submit-btn>
</form>
</div>

View File

@@ -1,3 +1,11 @@
Tea.context(function () {
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功");
this.activeSection = this.activeSection || "basic";
this.tlsAdvancedVisible = false;
if (!this.settings) {
this.settings = {};
}
});

View File

@@ -1,5 +1,10 @@
{$layout}
{$template "cluster_menu"}
<second-menu>
<a class="item" :href="'/httpdns/clusters/cluster?clusterId=' + cluster.id">{{cluster.name}}</a>
<span class="item disabled" style="padding-left: 0; padding-right: 0">&raquo;</span>
<div class="item"><strong>创建节点</strong></div>
</second-menu>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="clusterId" :value="clusterId" />

View File

@@ -1,6 +1,10 @@
{$layout}
{$template "cluster_menu"}
<div class="ui margin"></div>
<second-menu>
<a class="item" :href="'/httpdns/clusters/cluster?clusterId=' + cluster.id">{{cluster.name}}</a>
<span class="item disabled" style="padding-left: 0; padding-right: 0">&raquo;</span>
<div class="item"><strong>删除集群</strong></div>
</second-menu>
<div class="buttons-box">
<button class="ui button red" type="button" @click.prevent="deleteCluster(cluster.id)">删除当前集群</button>

View File

@@ -1,6 +1,8 @@
{$layout}
{$template "menu"}
<div class="ui margin"></div>
<div>
<div v-if="hasErrorLogs" class="ui icon message small error">
<i class="icon warning circle"></i>
@@ -66,3 +68,4 @@
<div class="page" v-html="page"></div>
</div>

View File

@@ -74,8 +74,9 @@
<tr>
<td>集群服务地址</td>
<td>
<code>{{selectedApp.gatewayDomain}}</code>
<a href="" class="httpdns-mini-action" title="复制服务地址" @click.prevent="copyText(selectedApp.gatewayDomain, '服务地址')"><i class="copy outline icon"></i></a>
<code>{{selectedApp.gatewayDomainDisplay}}</code>
<a href="" class="httpdns-mini-action" title="复制服务地址" @click.prevent="copyText(selectedApp.gatewayDomainDisplay, '服务地址')"><i class="copy outline icon"></i></a>
<p class="comment" v-if="selectedApp.gatewayDomains && selectedApp.gatewayDomains.length > 1">已启用主备:第一个为主集群,后续为备集群。</p>
</td>
</tr>
<tr>

View File

@@ -24,6 +24,9 @@
if (!this.selectedApp.gatewayDomain || this.selectedApp.gatewayDomain.length == 0) {
this.selectedApp.gatewayDomain = "gw.httpdns.example.com"
}
if (!this.selectedApp.gatewayDomainDisplay || this.selectedApp.gatewayDomainDisplay.length == 0) {
this.selectedApp.gatewayDomainDisplay = this.selectedApp.gatewayDomain
}
this.signSecretVisible = false
this.aesSecretVisible = false

View File

@@ -72,13 +72,6 @@
<p class="comment httpdns-policy-note">用户新建应用时默认落到此集群。</p>
</td>
</tr>
<tr>
<td class="title">启用用户域名校验</td>
<td>
<checkbox name="enableUserDomainVerify" value="1" v-model="policies.enableUserDomainVerify"></checkbox>
<p class="comment httpdns-policy-note">开启后,用户添加域名需要通过归属校验。</p>
</td>
</tr>
</table>
<table class="ui table definition selectable" v-show="activeSection == 'basic'">

View File

@@ -10,9 +10,6 @@ Tea.context(function () {
this.policies.defaultClusterId = this.availableClusters[0].id;
}
}
if (typeof this.policies.enableUserDomainVerify == "undefined") {
this.policies.enableUserDomainVerify = true;
}
if (typeof this.policies.defaultTTL == "undefined" || this.policies.defaultTTL <= 0) {
this.policies.defaultTTL = 30;
}

View File

@@ -1,3 +1,3 @@
<first-menu>
<menu-item href="/httpdns/resolveLogs" code="index">解析日志</menu-item>
<menu-item href="/httpdns/resolveLogs" code="index">访问日志</menu-item>
</first-menu>

View File

@@ -1,94 +1,103 @@
{$layout}
{$template "menu"}
<div class="margin"></div>
<style>
.httpdns-log-summary {
color: #556070;
font-size: 12px;
line-height: 1.6;
white-space: nowrap;
}
.httpdns-log-summary {
color: #556070;
font-size: 12px;
line-height: 1.6;
white-space: nowrap;
}
.httpdns-log-summary code {
font-size: 12px;
}
.httpdns-log-summary code {
font-size: 12px;
}
</style>
<form method="get" action="/httpdns/resolveLogs" class="ui form" autocomplete="off">
<div class="ui fields inline">
<div class="ui field">
<select class="ui dropdown" name="clusterId" v-model="clusterId">
<option value="">[集群]</option>
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
</select>
<div>
<form method="get" action="/httpdns/resolveLogs" class="ui form small" autocomplete="off">
<div class="ui fields inline">
<div class="ui field">
<select class="ui dropdown" name="clusterId" v-model="clusterId">
<option value="">[集群]</option>
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
</select>
</div>
<div class="ui field">
<select class="ui dropdown" name="nodeId" v-model="nodeId">
<option value="">[节点]</option>
<option v-for="node in nodes" :value="node.id"
v-if="clusterId == '' || clusterId == node.clusterId">{{node.name}}</option>
</select>
</div>
<div class="ui field">
<input type="text" name="appId" v-model="appId" placeholder="AppID" />
</div>
<div class="ui field">
<input type="text" name="domain" v-model="domain" placeholder="域名" />
</div>
<div class="ui field">
<select class="ui dropdown" name="status" v-model="status">
<option value="">[状态]</option>
<option value="success">解析成功</option>
<option value="failed">解析失败</option>
</select>
</div>
<div class="ui field">
<input type="text" name="keyword" v-model="keyword" placeholder="应用/域名/IP/结果IP" />
</div>
<div class="ui field">
<button type="submit" class="ui button small">查询</button>
</div>
<div class="ui field"
v-if="clusterId.toString().length > 0 || nodeId.toString().length > 0 || appId.length > 0 || domain.length > 0 || status.length > 0 || keyword.length > 0">
<a href="/httpdns/resolveLogs">[清除条件]</a>
</div>
</div>
<div class="ui field">
<select class="ui dropdown" name="nodeId" v-model="nodeId">
<option value="">[节点]</option>
<option v-for="node in nodes" :value="node.id" v-if="clusterId == '' || clusterId == node.clusterId">{{node.name}}</option>
</select>
</div>
<div class="ui field">
<input type="text" name="appId" v-model="appId" placeholder="AppID" style="width:10em" />
</div>
<div class="ui field">
<input type="text" name="domain" v-model="domain" placeholder="域名" style="width:13em" />
</div>
<div class="ui field">
<select class="ui dropdown" name="status" v-model="status">
<option value="">[状态]</option>
<option value="success">解析成功</option>
<option value="failed">解析失败</option>
</select>
</div>
<div class="ui field">
<input type="text" name="keyword" v-model="keyword" placeholder="应用/域名/IP/结果IP" style="width:14em" />
</div>
<div class="ui field">
<button type="submit" class="ui button">查询</button>
</div>
<div class="ui field" v-if="clusterId.toString().length > 0 || nodeId.toString().length > 0 || appId.length > 0 || domain.length > 0 || status.length > 0 || keyword.length > 0">
<a href="/httpdns/resolveLogs">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="resolveLogs.length == 0">暂时还没有解析日志。</p>
<div style="overflow-x:auto;" v-if="resolveLogs.length > 0">
<table class="ui table selectable celled">
<thead>
<tr>
<th>集群</th>
<th>节点</th>
<th>域名</th>
<th>类型</th>
<th>概要</th>
</tr>
</thead>
<tbody>
<tr v-for="log in resolveLogs">
<td>{{log.clusterName}}</td>
<td>{{log.nodeName}}</td>
<td>{{log.domain}}</td>
<td>{{log.query}}</td>
<td>
<div class="httpdns-log-summary">
{{log.time}}
| {{log.appName}}<code>{{log.appId}}</code>
| <code>{{log.clientIp}}</code>
| {{log.os}}/{{log.sdkVersion}}
| {{log.query}} {{log.domain}} ->
<code v-if="log.ips && log.ips.length > 0">{{log.ips}}</code>
<span class="grey" v-else>[无记录]</span>
|
<span class="green" v-if="log.status == 'success'"><strong>成功</strong></span>
<span class="red" v-else><strong>失败</strong></span>
<span class="grey" v-if="log.errorCode != 'none'">{{log.errorCode}}</span>
| {{log.costMs}}ms
</div>
</td>
</tr>
</tbody>
</table>
</form>
</div>
<div class="margin"></div>
<not-found-box v-if="resolveLogs.length == 0">暂时还没有访问日志。</not-found-box>
<div v-if="resolveLogs.length > 0">
<div style="overflow-x:auto;">
<table class="ui table selectable celled">
<thead>
<tr>
<th>集群</th>
<th>节点</th>
<th>域名</th>
<th>类型</th>
<th>概要</th>
</tr>
</thead>
<tbody>
<tr v-for="log in resolveLogs">
<td>{{log.clusterName}}</td>
<td>{{log.nodeName}}</td>
<td>{{log.domain}}</td>
<td>{{log.query}}</td>
<td>
<div class="httpdns-log-summary">
{{log.time}}
| {{log.appName}} (<code>{{log.appId}}</code>)
| <code>{{log.clientIp}}</code>
| {{log.os}}/{{log.sdkVersion}}
| {{log.query}} {{log.domain}} ->
<code v-if="log.ips && log.ips.length > 0">{{log.ips}}</code>
<span class="grey" v-else>[无记录]</span>
|
<span class="green" v-if="log.status == 'success'"><strong>成功</strong></span>
<span class="red" v-else><strong>失败</strong></span>
<span class="grey" v-if="log.errorCode != 'none'">({{log.errorCode}})</span>
| {{log.costMs}}ms
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -1,8 +1,16 @@
{$layout}
{$template "menu"}
<div class="margin"></div>
{$template "/datepicker"}
<form method="get" action="/httpdns/runtimeLogs" class="ui form" autocomplete="off">
<style>
.httpdns-runtime-level {
font-weight: 600;
}
</style>
<form method="get" action="/httpdns/runtimeLogs" class="ui form small" autocomplete="off">
<div class="ui fields inline">
<div class="ui field">
<select class="ui dropdown" name="clusterId" v-model="clusterId">
@@ -13,14 +21,16 @@
<div class="ui field">
<select class="ui dropdown" name="nodeId" v-model="nodeId">
<option value="">[节点]</option>
<option v-for="node in nodes" :value="node.id" v-if="clusterId == '' || clusterId == node.clusterId">{{node.name}}</option>
<option v-for="node in nodes" :value="node.id" v-if="clusterId == '' || clusterId == node.clusterId">
{{node.name}}</option>
</select>
</div>
<div class="ui field">
<input type="text" name="dayFrom" placeholder="开始日期" v-model="dayFrom" style="width:8em" id="day-from-picker"/>
<input type="text" name="dayFrom" placeholder="开始日期" v-model="dayFrom" style="width:7.8em"
id="day-from-picker" />
</div>
<div class="ui field">
<input type="text" name="dayTo" placeholder="结束日期" v-model="dayTo" style="width:8em" id="day-to-picker"/>
<input type="text" name="dayTo" placeholder="结束日期" v-model="dayTo" style="width:7.8em" id="day-to-picker" />
</div>
<div class="ui field">
<select class="ui dropdown" name="level" v-model="level">
@@ -32,49 +42,47 @@
</select>
</div>
<div class="ui field">
<input type="text" name="keyword" style="width:15em" v-model="keyword" placeholder="类型/模块/详情/节点"/>
<input type="text" name="keyword" v-model="keyword" placeholder="类型/详情/节点" />
</div>
<div class="ui field">
<button type="submit" class="ui button">查询</button>
<button type="submit" class="ui button small">查询</button>
</div>
<div class="ui field" v-if="clusterId.toString().length > 0 || nodeId.toString().length > 0 || dayFrom.length > 0 || dayTo.length > 0 || keyword.length > 0 || level.length > 0">
<div class="ui field"
v-if="clusterId.toString().length > 0 || nodeId.toString().length > 0 || dayFrom.length > 0 || dayTo.length > 0 || keyword.length > 0 || level.length > 0">
<a href="/httpdns/runtimeLogs">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="runtimeLogs.length == 0">暂时还没有运行日志。</p>
<div class="margin"></div>
<not-found-box v-if="runtimeLogs.length == 0">暂时还没有运行日志。</not-found-box>
<table class="ui table selectable celled" v-if="runtimeLogs.length > 0">
<thead>
<tr>
<th>时间</th>
<th>集群</th>
<th>节点</th>
<th>级别</th>
<th>类型</th>
<th>模块</th>
<th>详情</th>
<th>次数</th>
<th>请求ID</th>
</tr>
<tr>
<th>时间</th>
<th>集群</th>
<th>节点</th>
<th>级别</th>
<th>类型</th>
<th>详情</th>
<th>次数</th>
</tr>
</thead>
<tbody>
<tr v-for="log in runtimeLogs">
<td>{{log.createdTime}}</td>
<td>{{log.clusterName}}</td>
<td>{{log.nodeName}}</td>
<td>
<span class="ui label tiny red" v-if="log.level == 'error'">error</span>
<span class="ui label tiny orange" v-else-if="log.level == 'warning'">warning</span>
<span class="ui label tiny blue" v-else-if="log.level == 'info'">info</span>
<span class="ui label tiny green" v-else>success</span>
</td>
<td><code>{{log.tag}}</code></td>
<td>{{log.module}}</td>
<td>{{log.description}}</td>
<td>{{log.count}}</td>
<td><code>{{log.requestId}}</code></td>
</tr>
<tr v-for="log in runtimeLogs">
<td>{{log.createdTime}}</td>
<td>{{log.clusterName}}</td>
<td>{{log.nodeName}}</td>
<td>
<span
class="httpdns-runtime-level"
:class="{red:log.level == 'error', orange:log.level == 'warning', green:log.level == 'success'}">{{log.level}}</span>
</td>
<td><code>{{log.tag}}</code></td>
<td>{{log.description}}</td>
<td>{{log.count}}</td>
</tr>
</tbody>
</table>

View File

@@ -1,4 +1,4 @@
<!-- 左侧菜单由 Go Backend 自动生成注入,此处加首行子菜单使其符合标准平台样式 -->
<first-menu>
<menu-item href="/httpdns/sandbox" code="index">API 在线沙盒</menu-item>
<menu-item href="/httpdns/sandbox" code="index">解析测试</menu-item>
</first-menu>

View File

@@ -1,24 +1,6 @@
{$layout}
{$template "menu"}
<style>
.httpdns-sdns-row {
display: flex;
gap: .5em;
align-items: center;
margin-bottom: .45em;
}
.httpdns-sdns-row .field {
margin: 0 !important;
flex: 1;
}
.httpdns-sdns-actions {
margin-top: .2em;
}
.httpdns-sdns-actions .count {
color: #8f9aa6;
margin-left: .4em;
}
</style>
<div class="ui margin"></div>
<div>
<div class="ui grid stackable">
@@ -52,24 +34,6 @@
@click.prevent="request.qtype='AAAA'" type="button">AAAA</button>
</div>
</div>
<div class="field">
<label>SDNS 参数</label>
<div class="httpdns-sdns-row" v-for="(param, index) in sdnsParams">
<div class="field">
<input type="text" maxlength="64" placeholder="参数名称" v-model="param.name" />
</div>
<div class="field">
<input type="text" maxlength="64" placeholder="参数值" v-model="param.value" />
</div>
<a href="" @click.prevent="removeSDNSParam(index)" title="删除"><i class="icon trash alternate outline"></i></a>
</div>
<div class="httpdns-sdns-actions">
<a href="" @click.prevent="addSDNSParam" :class="{disabled: sdnsParams.length >= 10}">
<i class="icon plus circle"></i>添加参数
</a>
<span class="count">{{sdnsParams.length}}/10</span>
</div>
</div>
<div class="ui divider"></div>
@@ -123,7 +87,8 @@
<div class="column">
<div class="ui segment">
<div class="grey">客户端 IP</div>
<div style="margin-top:.4em;"><code>{{response.data.client_ip || request.clientIp || '-'}}</code></div>
<div style="margin-top:.4em;">
<code>{{response.data.client_ip || request.clientIp || '-'}}</code></div>
</div>
</div>
<div class="column">
@@ -155,7 +120,8 @@
</thead>
<tbody>
<tr v-for="(row, idx) in response.resultRows">
<td v-if="idx === 0" :rowspan="response.resultRows.length">{{row.domain || request.domain}}</td>
<td v-if="idx === 0" :rowspan="response.resultRows.length">{{row.domain ||
request.domain}}</td>
<td>{{row.type || request.qtype}}</td>
<td><code>{{row.ip}}</code></td>
<td>{{row.ttl}}s</td>
@@ -168,4 +134,4 @@
</div>
</div>
</div>
</div>
</div>

View File

@@ -9,7 +9,6 @@ Tea.context(function () {
}
this.request = this.newRequest()
this.sdnsParams = [{name: "", value: ""}]
this.response = {
hasResult: false,
@@ -26,39 +25,6 @@ Tea.context(function () {
this.apps = []
}
this.addSDNSParam = function () {
if (this.sdnsParams.length >= 10) {
return
}
this.sdnsParams.push({name: "", value: ""})
}
this.removeSDNSParam = function (index) {
if (index < 0 || index >= this.sdnsParams.length) {
return
}
this.sdnsParams.splice(index, 1)
if (this.sdnsParams.length == 0) {
this.sdnsParams.push({name: "", value: ""})
}
}
this.cleanSDNSParams = function () {
let list = []
this.sdnsParams.forEach(function (item) {
let name = (item.name || "").trim()
let value = (item.value || "").trim()
if (name.length == 0 && value.length == 0) {
return
}
list.push({
name: name,
value: value
})
})
return list
}
this.normalizeResultRows = function (data) {
if (typeof data == "undefined" || data == null) {
return []
@@ -106,7 +72,6 @@ Tea.context(function () {
this.response.hasResult = false
let payload = Object.assign({}, this.request)
payload.sdnsParamsJSON = JSON.stringify(this.cleanSDNSParams())
this.$post("/httpdns/sandbox/test")
.params(payload)
@@ -122,7 +87,6 @@ Tea.context(function () {
this.resetForm = function () {
this.request = this.newRequest()
this.sdnsParams = [{name: "", value: ""}]
this.response = {
hasResult: false,
code: -1,