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,6 @@
<first-menu>
<menu-item href="/servers" code="index">网站列表</menu-item>
<menu-item href="/servers?auditingFlag=1" code="auditing">审核中<span :class="{red: countAuditing > 0}">({{countAuditing}})</span></menu-item>
<span class="item disabled">|</span>
<menu-item href="/servers/create" code="create">[创建网站]</menu-item>
</first-menu>

View File

@@ -0,0 +1,6 @@
<first-menu>
<menu-item code="index" href="/servers/accesslogs">策略列表</menu-item>
<menu-item @click.prevent="createPolicy">[创建策略]</menu-item>
<span class="item disabled">|</span>
<span class="item"><tip-icon content="可以利用日志策略将服务的访问日志输出到特定的媒介中。"></tip-icon></span>
</first-menu>

View File

@@ -0,0 +1,7 @@
<first-menu>
<menu-item href="/servers/accesslogs">策略列表</menu-item>
<span class="item disabled">|</span>
<menu-item :href="'policy?policyId=' + policy.id" code="policy">"{{policy.name}}"详情</menu-item>
<menu-item :href="'test?policyId=' + policy.id" code="test">测试</menu-item>
<menu-item :href="'update?policyId=' + policy.id" code="update">修改</menu-item>
</first-menu>

View File

@@ -0,0 +1,244 @@
{$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" maxlength="50" ref="focus"/>
</td>
</tr>
<tr>
<td>存储类型 *</td>
<td>
<select class="ui dropdown auto-width" name="type" v-model="type">
<option value="">[选择类型]</option>
<option v-for="type in types" :value="type.code">{{type.name}}</option>
</select>
</td>
</tr>
<!-- 文件 -->
<tbody v-show="type == 'file'">
<tr>
<td>日志文件路径 *</td>
<td>
<input type="text" name="filePath"/>
<p class="comment">
存储日志的文件路径,文件名中支持变量:
<span class="ui label tiny basic">年:${year}</span>
<span class="ui label tiny basic">月:${month}</span>
<span class="ui label tiny basic">周:${week}</span>
<span class="ui label tiny basic">日:${day}</span>
<span class="ui label tiny basic">时:${hour}</span>
<span class="ui label tiny basic">分:${minute}</span>
<span class="ui label tiny basic">秒:${second}</span>
<span class="ui label tiny basic">年月日:${date}</span>,比如<span class="ui label tiny basic">/var/log/web-access-${date}.log</span>此文件会在API节点上写入。
</p>
</td>
</tr>
<tr>
<td>自动创建目录</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="fileAutoCreate" value="1" checked="checked"/>
<label></label>
</div>
<p class="comment">选中后,如果文件目录不存在时可以自动创建。</p>
</td>
</tr>
</tbody>
<!-- Elastic Search -->
<tbody v-show="type == 'es'">
<tr>
<td>Endpoint *</td>
<td>
<input type="text" name="esEndpoint"/>
<p class="comment">ES HTTP接口地址类似于192.168.1.100:9200需要能够被API节点访问。</p>
</td>
</tr>
<tr>
<td>Index *</td>
<td>
<input type="text" name="esIndex"/>
<p class="comment">Index名称支持变量
<span class="ui label tiny basic">年:${year}</span>
<span class="ui label tiny basic">月:${month}</span>
<span class="ui label tiny basic">周:${week}</span>
<span class="ui label tiny basic">日:${day}</span>
<span class="ui label tiny basic">时:${hour}</span>
<span class="ui label tiny basic">分:${minute}</span>
<span class="ui label tiny basic">秒:${second}</span>
<span class="ui label tiny basic">年月日:${date}</span></p>
</td>
</tr>
<tr>
<td>数据流模式</td>
<td>
<checkbox name="esIsDataStream" v-model="esIsDataStream"></checkbox>
<p class="comment">适用于索引是数据流Data Stream的场景时间字段为<code-label>timeISO8601</code-label></p>
</td>
</tr>
<tr v-show="!esIsDataStream">
<td>Mapping Type *</td>
<td>
<input type="text" name="esMappingType"/>
<p class="comment">Mapping名称支持变量
<span class="ui label tiny basic">年:${year}</span>
<span class="ui label tiny basic">月:${month}</span>
<span class="ui label tiny basic">周:${week}</span>
<span class="ui label tiny basic">日:${day}</span>
<span class="ui label tiny basic">时:${hour}</span>
<span class="ui label tiny basic">分:${minute}</span>
<span class="ui label tiny basic">秒:${second}</span>
<span class="ui label tiny basic">年月日:${date}</span></p>
</td>
</tr>
<tr>
<td>认证用户名</td>
<td>
<input type="text" name="esUsername"/>
<p class="comment">配置了认证后才需要填写。</p>
</td>
</tr>
<tr>
<td>认证密码</td>
<td>
<input type="text" name="esPassword"/>
<p class="comment">配置了认证后才需要填写。</p>
</td>
</tr>
</tbody>
<!-- TCP Socket -->
<tbody v-show="type == 'tcp'">
<tr>
<td>网络协议 *</td>
<td>
<select name="tcpNetwork" class="ui dropdown" style="width:10em">
<option value="tcp">TCP</option>
<option value="unix">Unix Socket</option>
</select>
</td>
</tr>
<tr>
<td>网络地址 *</td>
<td>
<input type="text" name="tcpAddr"/>
<p class="comment">接收日志的网络地址需要能够被API节点访问。</p>
</td>
</tr>
</tbody>
<!-- Syslog -->
<tbody v-show="type == 'syslog'">
<tr>
<td>网络协议</td>
<td>
<select class="ui dropdown" name="syslogProtocol" v-model="syslogProtocol" style="width:10em">
<option value="none">[无]</option>
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
<option value="socket">Unix Socket</option>
</select>
</td>
</tr>
<tr v-if="syslogProtocol == 'tcp' || syslogProtocol == 'udp'">
<td>网络地址 *</td>
<td>
<input type="text" name="syslogServerAddr"/>
<p class="comment">IP地址或主机名不包括端口需要能够被API节点访问。</p>
</td>
</tr>
<tr v-if="syslogProtocol == 'tcp' || syslogProtocol == 'udp'">
<td>端口</td>
<td>
<input type="text" name="syslogServerPort"/>
</td>
</tr>
<tr v-if="syslogProtocol == 'socket'">
<td>Socket路径 *</td>
<td>
<input type="text" name="syslogSocket"/>
<p class="comment">需要能够被API节点访问。</p>
</td>
</tr>
<tr>
<td>标签<em>(Tag)</em></td>
<td>
<input type="text" name="syslogTag"/>
<p class="comment">选填项。</p>
</td>
</tr>
<tr>
<td>优先级<em>(Priority)</em></td>
<td>
<select class="ui dropdown" name="syslogPriority" style="width:10em">
<option v-for="priority in syslogPriorities" :value="priority.value">{{priority.name}}</option>
</select>
<p class="comment">选填项。</p>
</td>
</tr>
</tbody>
<!-- 命令行输入流 -->
<tbody v-show="type == 'command'">
<tr>
<td>可执行文件 *</td>
<td>
<input type="text" name="commandCommand"/>
<p class="comment">不带参数的可执行文件地址此命令可执行文件需要部署在API节点上。</p>
</td>
</tr>
<tr>
<td>参数</td>
<td>
<input type="text" name="commandArgs"/>
<p class="comment">执行命令需要的参数</p>
</td>
</tr>
<tr>
<td>工作目录</td>
<td>
<input type="text" name="commandDir"/>
<p class="comment">命令执行所在的工作目录</p>
</td>
</tr>
</tbody>
<tr>
<td>公用</td>
<td>
<checkbox name="isPublic" value="1"></checkbox>
<p class="comment"><span class="red">选中后表示自动将此策略应用于所有集群,同时只会有一个公用策略。</span></p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>只记录WAF相关访问日志</td>
<td>
<checkbox name="firewallOnly"></checkbox>
<p class="comment">默认记录所有访问日志如果选中此项则表示只记录WAF相关访问日志。</p>
</td>
</tr>
<tr>
<td>停用默认数据库存储</td>
<td>
<checkbox name="disableDefaultDB"></checkbox>
<p class="comment">选中后,表示停止将访问日志写入到默认的数据库中。</p>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,13 @@
Tea.context(function () {
this.type = ""
/**
* syslog
*/
this.syslogProtocol = "none"
/**
* Elastic Search
*/
this.esIsDataStream = false
})

View File

@@ -0,0 +1,46 @@
{$layout}
{$template "menu"}
<p class="comment" v-if="policies.length == 0">暂时还没有访问日志策略。</p>
<table class="ui table celled selectable" v-if="policies.length > 0">
<thead>
<tr>
<th>策略名称</th>
<th class="three op">类型</th>
<th>主要参数</th>
<th class="two wide">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="policy in policies">
<td><a :href="Tea.url('.policy', {policyId: policy.id})">{{policy.name}}</a>
<div style="margin-top: 0.3em" v-if="policy.isPublic || policy.firewallOnly">
<span class="ui label olive tiny basic" v-if="policy.isPublic">公用</span>
<span class="ui label tiny basic" v-if="policy.firewallOnly">只记录WAF</span>
<span class="ui label tiny basic" v-if="policy.disableDefaultDB">停用默认数据库</span>
</div>
</td>
<td>{{policy.typeName}}</td>
<td>
<span v-if="policy.options == null" class="disabled">-</span>
<span v-else-if="policy.type == 'es'">Endpoint: {{policy.options.endpoint}}</span>
<span v-else-if="policy.type == 'file'">文件路径:{{policy.options.path}}</span>
<span v-else-if="policy.type == 'tcp'">网络地址:{{policy.options.addr}}</span>
<span v-else-if="policy.type == 'command'">可执行命令:{{policy.options.command}}</span>
<span v-else-if="policy.type == 'syslog'">
<span v-if="policy.options.protocol == 'tcp'">TCP {{policy.options.serverAddr}}</span>
<span v-if="policy.options.protocol == 'udp'">UDP {{policy.options.serverAddr}}</span>
<span v-if="policy.options.protocol == 'socket'">SOCK {{policy.options.socket}}</span>
</span>
</td>
<td>
<label-on :v-is-on="policy.isOn"></label-on>
</td>
<td>
<a :href="Tea.url('.policy', {policyId: policy.id})">详情</a> &nbsp; <a href="" @click.prevent="deletePolicy(policy.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,20 @@
Tea.context(function () {
this.createPolicy = function () {
teaweb.popup(Tea.url(".createPopup", {}), {
height: "24em",
callback: NotifyReloadSuccess("保存成功")
})
}
this.deletePolicy = function (policyId) {
teaweb.confirm("确定要删除这个日志策略吗?", function () {
this.$post(".delete")
.params({
policyId: policyId
})
.success(function () {
teaweb.successRefresh("保存成功")
})
})
}
})

View File

@@ -0,0 +1,194 @@
{$layout}
{$template "policy_menu"}
<table class="ui table definition selectable">
<tr>
<td class="title">策略名称</td>
<td>
{{policy.name}}
</td>
</tr>
<tr>
<td>状态</td>
<td>
<label-on :v-is-on="policy.isOn"></label-on>
</td>
</tr>
<tr>
<td>存储类型</td>
<td>
{{policy.typeName}}
</td>
</tr>
<!-- 文件 -->
<tbody v-if="policy.type == 'file'">
<tr>
<td>日志文件路径</td>
<td>
{{policy.options.path}}
</td>
</tr>
<tr>
<td>自动创建目录</td>
<td>
<span v-if="policy.options.autoCreate" class="green">Y</span>
<span v-else class="disabled">N</span>
</td>
</tr>
</tbody>
<!-- Elastic Search -->
<tbody v-if="policy.type == 'es'">
<tr>
<td>Endpoint</td>
<td>
{{policy.options.endpoint}}
</td>
</tr>
<tr>
<td>Index</td>
<td>
{{policy.options.index}}
</td>
</tr>
<tr v-if="policy.options.isDataStream">
<td>数据流模式</td>
<td>
<span class="green">Y</span>
</td>
</tr>
<tr v-if="!policy.options.isDataStream">
<td>Mapping Type</td>
<td>
{{policy.options.mappingType}}
</td>
</tr>
<tr>
<td>认证用户名</td>
<td>
<span v-if="policy.options.username.length > 0">{{policy.options.username}}</span>
<span v-else class="disabled">没有填写。</span>
</td>
</tr>
<tr>
<td>认证密码</td>
<td>
<span v-if="policy.options.password.length > 0">{{policy.options.password}}</span>
<span v-else class="disabled">没有填写。</span>
</td>
</tr>
</tbody>
<!-- TCP Socket -->
<tbody v-if="policy.type == 'tcp'">
<tr>
<td>网络协议</td>
<td>
{{policy.options.network.toUpperCase()}}
</td>
</tr>
<tr>
<td>网络地址</td>
<td>
{{policy.options.addr}}
</td>
</tr>
</tbody>
<!-- Syslog -->
<tbody v-if="policy.type == 'syslog'">
<tr>
<td>网络协议</td>
<td>
<span v-if="policy.options.protocol == 'none'" class="disabled">[无]</span>
<span v-if="policy.options.protocol == 'tcp'">TCP</span>
<span v-if="policy.options.protocol == 'udp'">UDP</span>
<span v-if="policy.options.protocol == 'socket'">Unix Socket</span>
</td>
</tr>
<tr v-if="policy.options.protocol == 'tcp' || policy.options.protocol == 'udp'">
<td>网络地址</td>
<td>
{{policy.options.serverAddr}}
</td>
</tr>
<tr v-if="policy.options.protocol == 'tcp' || policy.options.protocol == 'udp'">
<td>端口</td>
<td>
{{policy.options.serverPort}}
</td>
</tr>
<tr v-if="policy.options.protocol == 'socket'">
<td>Socket路径</td>
<td>
{{policy.options.socket}}
</td>
</tr>
<tr>
<td>标签<em>(Tag)</em></td>
<td>
<span v-if="policy.options.tag.length > 0">{{policy.options.tag}}</span>
<span v-else class="disabled">没有设置</span>
</td>
</tr>
<tr>
<td>优先级<em>(Priority)</em></td>
<td>
{{syslogPriorityName}}
</td>
</tr>
</tbody>
<!-- 命令行输入流 -->
<tbody v-if="policy.type == 'command'">
<tr>
<td>可执行命令</td>
<td>
{{policy.options.command}}
</td>
</tr>
<tr>
<td>参数</td>
<td>
<div v-if="policy.options.args != null && policy.options.args.length > 0">
<span v-for="arg in policy.options.args" class="ui label basic tiny">{{arg}}</span>
</div>
<span v-else class="disabled">没有设置参数。</span>
</td>
</tr>
<tr>
<td>工作目录</td>
<td>
<span v-if="policy.options.dir.length > 0">{{policy.options.dir}}</span>
<span v-else class="disabled">没有设置工作目录。</span>
</td>
</tr>
</tbody>
<tr>
<td>公用</td>
<td>
<span v-if="policy.isPublic" class="green">Y</span>
<span v-else class="disabled">N</span>
</td>
</tr>
<tr>
<td>只记录WAF相关访问日志</td>
<td>
<span v-if="policy.firewallOnly" class="green">Y</span>
<span v-else class="disabled">N</span>
</td>
</tr>
<tr>
<td>停用默认数据库存储</td>
<td>
<span v-if="policy.disableDefaultDB" class="green">Y</span>
<span v-else class="disabled">N</span>
</td>
</tr>
</table>

View File

@@ -0,0 +1,18 @@
{$layout "layout"}
{$template "/code_editor"}
{$template "policy_menu"}
<form class="ui form" data-tea-action="$" data-tea-success="success" data-tea-timeout="30" data-tea-done="done" data-tea-before="before">
<input type="hidden" name="policyId" :value="policy.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">发送内容</td>
<td>
<source-code-box id="json-data-box" type="application/json" :read-only="false" name="bodyJSON">{$.testJSON}</source-code-box>
</td>
</tr>
</table>
<submit-btn v-if="!isRequesting && policy.isOn"></submit-btn>
<p class="comment" v-if="!policy.isOn">当前策略未启用,无法执行测试</p>
<button class="ui button disabled" type="button" v-if="isRequesting">发送中...</button>
</form>

View File

@@ -0,0 +1,15 @@
Tea.context(function () {
this.isRequesting = false
this.success = function () {
teaweb.successRefresh("发送成功")
}
this.before = function () {
this.isRequesting = true
}
this.done = function () {
this.isRequesting = false
}
})

View File

@@ -0,0 +1,248 @@
{$layout}
{$template "policy_menu"}
<h3>修改策略</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="policyId" :value="policy.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">策略名称 *</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus" v-model="policy.name"/>
</td>
</tr>
<tr>
<td>存储类型 *</td>
<td>
{{policy.typeName}}
</td>
</tr>
<!-- 文件 -->
<tbody v-show="type == 'file'">
<tr>
<td>日志文件路径 *</td>
<td>
<input type="text" name="filePath" v-model="policy.options.path"/>
<p class="comment">
存储日志的文件路径,文件名中支持变量:
<span class="ui label tiny basic">年:${year}</span>
<span class="ui label tiny basic">月:${month}</span>
<span class="ui label tiny basic">周:${week}</span>
<span class="ui label tiny basic">日:${day}</span>
<span class="ui label tiny basic">时:${hour}</span>
<span class="ui label tiny basic">分:${minute}</span>
<span class="ui label tiny basic">秒:${second}</span>
<span class="ui label tiny basic">年月日:${date}</span>,比如<span class="ui label tin basic">/var/log/web-access-${date}.log</span>此文件会在API节点上写入。
</p>
</td>
</tr>
<tr>
<td>自动创建目录</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="fileAutoCreate" value="1" v-model="policy.options.autoCreate"/>
<label></label>
</div>
<p class="comment">选中后,如果文件目录不存在时可以自动创建。</p>
</td>
</tr>
</tbody>
<!-- Elastic Search -->
<tbody v-show="type == 'es'">
<tr>
<td>Endpoint *</td>
<td>
<input type="text" name="esEndpoint" v-model="policy.options.endpoint"/>
<p class="comment">ES HTTP接口地址类似于192.168.1.100:9200需要能够被API节点访问。</p>
</td>
</tr>
<tr>
<td>Index *</td>
<td>
<input type="text" name="esIndex" v-model="policy.options.index"/>
<p class="comment">Index名称支持变量
<span class="ui label tiny basic">年:${year}</span>
<span class="ui label tiny basic">月:${month}</span>
<span class="ui label tiny basic">周:${week}</span>
<span class="ui label tiny basic">日:${day}</span>
<span class="ui label tiny basic">时:${hour}</span>
<span class="ui label tiny basic">分:${minute}</span>
<span class="ui label tiny basic">秒:${second}</span>
<span class="ui label tiny basic">年月日:${date}</span></p>
</td>
</tr>
<tr>
<td>数据流模式</td>
<td>
<checkbox name="esIsDataStream" v-model="policy.options.isDataStream"></checkbox>
<p class="comment">适用于索引是数据流Data Stream的场景时间字段为<code-label>timeISO8601</code-label></p>
</td>
</tr>
<tr v-show="!policy.options.isDataStream">
<td>Mapping Type *</td>
<td>
<input type="text" name="esMappingType" v-model="policy.options.mappingType"/>
<p class="comment">Mapping名称支持变量
<span class="ui label tiny basic">年:${year}</span>
<span class="ui label tiny basic">月:${month}</span>
<span class="ui label tiny basic">周:${week}</span>
<span class="ui label tiny basic">日:${day}</span>
<span class="ui label tiny basic">时:${hour}</span>
<span class="ui label tiny basic">分:${minute}</span>
<span class="ui label tiny basic">秒:${second}</span>
<span class="ui label tiny basic">年月日:${date}</span></p>
</td>
</tr>
<tr>
<td>认证用户名</td>
<td>
<input type="text" name="esUsername" v-model="policy.options.username"/>
<p class="comment">配置了认证后才需要填写。</p>
</td>
</tr>
<tr>
<td>认证密码</td>
<td>
<input type="text" name="esPassword" v-model="policy.options.password"/>
<p class="comment">配置了认证后才需要填写。</p>
</td>
</tr>
</tbody>
<!-- TCP Socket -->
<tbody v-show="type == 'tcp'">
<tr>
<td>网络协议 *</td>
<td>
<select name="tcpNetwork" class="ui dropdown" style="width:10em" v-model="policy.options.network">
<option value="tcp">TCP</option>
<option value="unix">Unix Socket</option>
</select>
</td>
</tr>
<tr>
<td>网络地址 *</td>
<td>
<input type="text" name="tcpAddr" v-model="policy.options.addr"/>
<p class="comment">接收日志的网络地址需要能够被API节点访问。</p>
</td>
</tr>
</tbody>
<!-- Syslog -->
<tbody v-show="type == 'syslog'">
<tr>
<td>网络协议</td>
<td>
<select class="ui dropdown" name="syslogProtocol" v-model="syslogProtocol" style="width:10em">
<option value="none">[无]</option>
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
<option value="socket">Unix Socket</option>
</select>
</td>
</tr>
<tr v-if="syslogProtocol == 'tcp' || syslogProtocol == 'udp'">
<td>网络地址 *</td>
<td>
<input type="text" name="syslogServerAddr" v-model="policy.options.serverAddr"/>
<p class="comment">IP地址或主机名不包括端口需要能够被API节点访问。</p>
</td>
</tr>
<tr v-if="syslogProtocol == 'tcp' || syslogProtocol == 'udp'">
<td>端口</td>
<td>
<input type="text" name="syslogServerPort" v-model="policy.options.serverPort"/>
</td>
</tr>
<tr v-if="syslogProtocol == 'socket'">
<td>Socket路径 *</td>
<td>
<input type="text" name="syslogSocket" v-model="policy.options.socket"/>
<p class="comment">需要能够被API节点访问。</p>
</td>
</tr>
<tr>
<td>标签<em>(Tag)</em></td>
<td>
<input type="text" name="syslogTag" v-model="policy.options.tag"/>
<p class="comment">选填项。</p>
</td>
</tr>
<tr>
<td>优先级<em>(Priority)</em></td>
<td>
<select class="ui dropdown" name="syslogPriority" style="width:10em" v-model="policy.options.priority">
<option v-for="priority in syslogPriorities" :value="priority.value">{{priority.name}}</option>
</select>
<p class="comment">选填项。</p>
</td>
</tr>
</tbody>
<!-- 命令行输入流 -->
<tbody v-show="type == 'command'">
<tr>
<td>可执行文件 *</td>
<td>
<input type="text" name="commandCommand" v-model="policy.options.command"/>
<p class="comment">不带参数的可执行文件地址此命令可执行文件需要部署在API节点上。</p>
</td>
</tr>
<tr>
<td>参数</td>
<td>
<input type="text" name="commandArgs" v-model="policy.options.args"/>
<p class="comment">执行命令需要的参数</p>
</td>
</tr>
<tr>
<td>工作目录</td>
<td>
<input type="text" name="commandDir" v-model="policy.options.dir"/>
<p class="comment">命令执行所在的工作目录</p>
</td>
</tr>
</tbody>
<tr>
<td>公用</td>
<td>
<checkbox name="isPublic" v-model="policy.isPublic"></checkbox>
<p class="comment"><span class="red">选中后表示自动将此策略应用于所有集群,同时只会有一个公用策略。</span></p>
</td>
</tr>
<tr>
<td>只记录WAF相关访问日志</td>
<td>
<checkbox name="firewallOnly" v-model="policy.firewallOnly"></checkbox>
<p class="comment">默认记录所有访问日志如果选中此项则表示只记录WAF相关访问日志。</p>
</td>
</tr>
<tr>
<td>停用默认数据库存储</td>
<td>
<checkbox name="disableDefaultDB" v-model="policy.disableDefaultDB"></checkbox>
<p class="comment">选中后,表示停止将访问日志写入到默认的数据库中。</p>
</td>
</tr>
<tr>
<td>启用当前策略</td>
<td>
<checkbox name="isOn" v-model="policy.isOn"></checkbox>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,24 @@
Tea.context(function () {
this.success = NotifySuccess("html:保存成功<br/>新的配置将会在1分钟之内生效", ".policy", {policyId: this.policy.id})
this.type = this.policy.type
/**
* syslog
*/
this.syslogProtocol = this.policy.options.protocol
/**
* command
*/
if (this.policy.type == "command") {
let args = this.policy.options.args
if (args == null) {
args = ""
} else {
args = args.join(" ")
}
this.policy.options.args = args
}
})

View File

@@ -0,0 +1,90 @@
{$layout "layout_popup"}
<h3>添加源站地址</h3>
<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>
<!-- HTTP -->
<select class="ui dropdown auto-width" name="protocol" v-if="serverType == 'httpProxy' || serverType == 'httpWeb'" @change="changeProtocol" v-model="protocol">
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
<!-- 对象存储 -->
<optgroup label="对象存储" v-if="ossTypes.length > 0"></optgroup>
<option v-for="ossType in ossTypes" :value="ossType.code">{{ossType.name}}</option>
</select>
<!-- TCP -->
<select class="ui dropdown auto-width" name="protocol" v-if="serverType == 'tcpProxy'" v-model="protocol">
<option value="tcp">TCP</option>
<option value="tls">TLS</option>
</select>
<!-- UDP -->
<select class="ui dropdown auto-width" name="protocol" v-if="serverType == 'udpProxy'" v-model="protocol">
<option value="udp">UDP</option>
</select>
</td>
</tr>
<!-- 普通源站 -->
<tr v-show="!isOSS">
<td class="title">源站地址 *</td>
<td>
<div class="ui input left labeled">
<span class="ui label">{{protocol.toLowerCase()}}://</span>
<input type="text" name="addr" ref="focus" v-model="addr" @input="changeAddr" @change="detectHTTPS"/>
</div>
<p class="comment" style="padding-bottom: 0; margin-bottom: 0" v-if="addrError.length == 0 && adviceHTTPS && protocol == 'http'"><span class="red">系统检测到当前源站有HTTPS协议可用是否切换到HTTPS协议</span> <a href="" @click.prevent="switchToHTTPS">[点此切换]</a></p>
<p class="comment"><span class="red" v-if="addrError.length > 0">{{addrError}}</span>源站服务器地址通常是一个IP或域名加端口80和443端口可以省略<span v-if="serverType == 'httpProxy'">,不需要加 http:// 或 https://</span></p>
</td>
</tr>
<tr v-if="(isHTTP || protocol == 'tls') && !isOSS">
<td>回源主机名</td>
<td>
<input type="text" name="host" placeholder="比如example.com" maxlength="100"/>
<p class="comment">可选项。请求源站时的Host字段值用于设置访问源站的站点域名<span v-if="isHTTP">,支持请求变量</span>;如果用户访问的域名和源站域名不一致,请务必设置为源站域名。</p>
</td>
</tr>
<!-- OSS -->
{$ .ossForm}
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr v-if="protocol == 'https' || protocol == 'tls'">
<td>{{protocol.toUpperCase()}}证书</td>
<td>
<ssl-certs-box :v-single-mode="true" :v-view-size="'mini'" :v-description="'可以选择连接源站使用的证书;除非源站有特殊需求,否则不需要添加。'"></ssl-certs-box>
</td>
</tr>
<tr v-if="isHTTP">
<td>专属域名</td>
<td>
<domains-box></domains-box>
<p class="comment">默认不需要填写,表示支持所有域名。如果填写了专属域名,表示这些源站只会在所列的专属域名被访问时才生效。</p>
</td>
</tr>
<tr v-show="!isOSS">
<td>端口跟随</td>
<td>
<checkbox name="followPort"></checkbox>
<p class="comment">选中后,表示源站的端口保持和用户访问的服务端口保持一致;此时的源站地址中的端口号可以任意填写。</p>
</td>
</tr>
<tr v-show="!isOSS && protocol == 'https'">
<td>支持HTTP/2</td>
<td>
<checkbox name="http2Enabled"></checkbox>
<p class="comment">选中后表示源站支持HTTP/2协议。</p>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,103 @@
Tea.context(function () {
this.addr = ""
this.protocol = ""
this.isOSS = false
this.addrError = ""
// 当前网站协议
this.isHTTP = (this.serverType == "httpProxy" || this.serverType == "httpWeb")
if (this.serverType == "httpProxy") {
this.protocol = "http"
} else if (this.serverType == "tcpProxy") {
this.protocol = "tcp"
} else if (this.serverType == "udpProxy") {
this.protocol = "udp"
}
this.changeProtocol = function () {
this.isOSS = this.protocol.startsWith("oss:")
if (this.protocol == "http") {
this.detectHTTPS()
} else {
this.adviceHTTPS = false
}
this.checkPort()
}
this.changeAddr = function () {
this.adviceHTTPS = false
if (this.serverType == "httpProxy") {
if (this.addr.startsWith("http://")) {
this.protocol = "http"
} else if (this.addr.startsWith("https://")) {
this.protocol = "https"
}
}
this.checkPort()
}
this.checkPort = function () {
this.addrError = ""
// HTTP
if (this.protocol == "http") {
if (this.addr.endsWith(":443")) {
this.addrError = "443通常是HTTPS协议端口请确认源站协议选择是否正确。"
} else if (this.addr.endsWith(":8443")) {
this.addrError = "8443通常是HTTPS协议端口请确认源站协议选择是否正确。"
}
}
// HTTPS
if (this.protocol == "https") {
if (this.addr.endsWith(":80")) {
this.addrError = "80通常是HTTP协议端口请确认源站协议选择是否正确。"
} else if (this.addr.endsWith(":8080")) {
this.addrError = "8080通常是HTTP协议端口请确认源站协议选择是否正确。"
}
}
}
this.adviceHTTPS = false
var isDetectingHTTPS = false
this.detectHTTPS = function () {
if (isDetectingHTTPS) {
return
}
isDetectingHTTPS = true
this.adviceHTTPS = false
if (this.protocol == "http") {
this.$post("/servers/server/settings/origins/detectHTTPS")
.params({
addr: this.addr
})
.success(function (resp) {
this.adviceHTTPS = resp.data.isOk
if (resp.data.isOk) {
this.addr = resp.data.addr
}
})
.done(function () {
isDetectingHTTPS = false
})
} else {
isDetectingHTTPS = false
}
}
this.switchToHTTPS = function () {
this.adviceHTTPS = false
this.protocol = "https"
if (this.addr.endsWith(":80")) {
this.addr = this.addr.substring(0, this.addr.length - (":80").length)
}
}
})

View File

@@ -0,0 +1,28 @@
{$layout "layout_popup"}
<h3 v-if="!isUpdating">添加端口绑定</h3>
<h3 v-if="isUpdating">修改端口绑定</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="supportRange" :value="supportRange ? 1 : 0"/>
<table class="ui table definition selectable">
<tr>
<td>网络协议</td>
<td>
<select class="ui dropdown auto-width" name="protocol" v-model="protocol" @change="changeProtocol">
<option v-for="p in protocols" :value="p.code">{{p.name}}</option>
</select>
</td>
</tr>
<tr>
<td class="title">端口 *</td>
<td>
<input type="text" name="address" ref="focus" v-model="address"/>
<p class="comment">可以是一个数字端口通常不超过65535也可以是"地址:端口"的方式。<span v-if="supportRange">支持端口范围,形式为<code-label>port1-port2</code-label></span>
<span v-if="from.length == 0 && protocol == 'http'">HTTP常用端口为<a href="" title="点击添加" @click.prevent="addPort('80')">80</a></span>
<span v-if="from.length == 0 && protocol == 'https'">HTTPS常用端口为<a href="" title="点击添加" @click.prevent="addPort('443')">443</a></span>
</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,47 @@
Tea.context(function () {
this.success = NotifyPopup;
this.isUpdating = false
this.address = ""
this.protocol = this.protocols[0].code
// 初始化
// from 用来标记是否为特殊的节点
if (this.from.length == 0) {
if (this.protocol == "http") {
this.address = "80"
} else if (this.protocol == "https") {
this.address = "443"
}
}
if (window.parent.UPDATING_ADDR != null) {
this.isUpdating = true
let addr = window.parent.UPDATING_ADDR
this.protocol = addr.protocol
if (addr.host.length == 0) {
this.address = addr.portRange
} else {
this.address = addr.host.quoteIP() + ":" + addr.portRange
}
}
this.changeProtocol = function () {
if (this.from.length > 0) {
return
}
switch (this.protocol) {
case "http":
this.address = "80"
break
case "https":
this.address = "443"
break
}
}
this.addPort = function (port) {
this.address = port
}
});

View File

@@ -0,0 +1,24 @@
{$layout "layout_popup"}
<h3 v-if="!isUpdating">添加域名绑定</h3>
<h3 v-if="isUpdating">修改域名绑定</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="mode" :value="mode"/>
<table class="ui table selectable definition">
<tr v-if="mode == 'single'">
<td class="title">单个域名 *</td>
<td>
<input type="text" name="serverName" ref="focus" maxlength="1024" v-model="serverName.name"/>
<p class="comment">请输入单个域名,比如<code-label>example.com</code-label><code-label>www.example.com</code-label><strong>不要</strong>包含<code-label>http://</code-label><code-label>https://</code-label>或端口号;也可以使用泛域名,比如<code-label>*.example.com</code-label></p>
</td>
</tr>
<tr v-if="mode == 'multiple'">
<td class="title">多个域名 *</td>
<td>
<textarea name="serverNames" ref="serverNames" v-model="multipleServerNames"></textarea>
<p class="comment">每行一个域名,比如<code-label>example.com</code-label><code-label>www.example.com</code-label><strong>不要</strong>包含<code-label>http://</code-label><code-label>https://</code-label>或端口号;也可以使用泛域名,比如<code-label>*.example.com</code-label></p>
</td>
</tr>
</table>
<submit-btn></submit-btn> &nbsp; <a href="" v-if="mode == 'single'" @click.prevent="switchMode('multiple')">批量添加</a><a href="" v-if="mode == 'multiple'" @click.prevent="switchMode('single')">单个添加</a>
</form>

View File

@@ -0,0 +1,29 @@
Tea.context(function () {
this.success = NotifyPopup;
this.isUpdating = false
this.mode = "single" // single|multiple
this.serverName = {
name: "",
subNames: []
}
this.multipleServerNames = ""
if (window.parent.UPDATING_SERVER_NAME != null) {
this.isUpdating = true
this.serverName = window.parent.UPDATING_SERVER_NAME
if (this.serverName.subNames != null && this.serverName.subNames.length > 0) {
this.mode = "multiple"
this.multipleServerNames = this.serverName.subNames.join("\n")
}
}
this.switchMode = function (mode) {
this.mode = mode
this.$delay(function () {
if (mode == "single") {
this.$refs.focus.focus()
} else {
this.$refs.serverNames.focus()
}
})
}
});

View File

@@ -0,0 +1,8 @@
<first-menu>
<menu-item href="/servers/certs/acme" code="task">所有任务</menu-item>
<span class="disabled item">|</span>
<menu-item href="/servers/certs/acme/create" code="create">[新申请]</menu-item>
<span class="disabled item">|</span>
<menu-item href="/servers/certs/acme/users" code="user">ACME用户</menu-item>
<menu-item href="/servers/certs/acme/accounts" code="account">服务商账号</menu-item>
</first-menu>

View File

@@ -0,0 +1,42 @@
{$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>账号名称 *</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus" tabindex="1"/>
<p class="comment">为当前账号起一个容易识别的名称。</p>
</td>
</tr>
<tr>
<td class="title">证书服务商 *</td>
<td>
<select class="ui dropdown auto-width" name="providerCode" v-model="providerCode" @change="changeProvider" tabindex="2">
<option value="">[选择服务商]</option>
<option v-for="provider in providers" :value="provider.code">{{provider.name}}</option>
</select>
<p class="comment" v-if="selectedProvider != null" v-html="selectedProvider.description"></p>
</td>
</tr>
<tbody v-show="selectedProvider != null && selectedProvider.requireEAB">
<tr>
<td>EAB Kid *</td>
<td>
<input type="text" name="eabKid" maxlength="100" tabindex="3"/>
<p class="comment" v-if="selectedProvider != null" v-html="selectedProvider.eabDescription"></p>
</td>
</tr>
<tr>
<td>EAB HMAC Key *</td>
<td>
<input type="text" name="eabKey" maxlength="300" tabindex="4"/>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,16 @@
Tea.context(function () {
this.selectedProvider = null
this.changeProvider = function () {
if (this.providerCode.length == 0) {
this.selectedProvider = null
return
}
let that = this
this.selectedProvider = this.providers.$find(function (k, v) {
return v.code == that.providerCode
})
}
this.changeProvider()
})

View File

@@ -0,0 +1,46 @@
{$layout}
{$template "/left_menu_top"}
<div class="right-box without-tabbar">
{$template "../menu"}
<second-menu>
<menu-item @click.prevent="createAccount">[创建账号]</menu-item>
</second-menu>
<p class="comment" v-if="accounts.length == 0">暂时还没有服务商账号。</p>
<table class="ui table selectable celled" v-if="accounts.length > 0">
<thead>
<tr>
<th>名称</th>
<th>证书服务商</th>
<th class="four wide">EAB Kid</th>
<th class="four wide">EBA HMAC Key</th>
<th class="two wide">操作</th>
</tr>
</thead>
<tr v-for="account in accounts">
<td>
<a href="" @click.prevent="updateAccount(account.id)">{{account.name}} <i class="icon expand small"></i></a>
</td>
<td>
<span v-if="account.provider != null">{{account.provider.name}}</span>
</td>
<td>
<span v-if="account.eabKid.length > 0">{{account.eabKid}}</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="account.eabKey.length > 0">{{account.eabKey}}</span>
<span v-else class="disabled">-</span>
</td>
<td>
<a href="" @click.prevent="updateAccount(account.id)">修改</a> &nbsp;
<a href="" @click.prevent="deleteAccount(account.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>
</div>

View File

@@ -0,0 +1,33 @@
Tea.context(function () {
this.createAccount = function () {
teaweb.popup(".createPopup", {
height: "24em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.updateAccount = function (accountId) {
teaweb.popup(".updatePopup?accountId=" + accountId, {
height: "24em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteAccount = function (accountId) {
teaweb.confirm("确定要删除此账号吗?", function () {
this.$post(".delete")
.params({
accountId: accountId
})
.refresh()
})
}
})

View File

@@ -0,0 +1,41 @@
{$layout "layout_popup"}
<h3>修改账号</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="accountId" :value="account.id"/>
<input type="hidden" name="providerCode" :value="account.providerCode"/>
<table class="ui table definition selectable">
<tr>
<td>账号名称 *</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus" v-model="account.name" tabindex="1"/>
</td>
</tr>
<tr>
<td class="title">证书服务商 *</td>
<td>
<span v-if="account.provider != null">{{account.provider.name}}</span>
<span v-else class="disabled">服务商已失效</span>
<p class="comment" v-if="account.provider != null" v-html="account.provider.description"></p>
</td>
</tr>
<tbody v-show="account.provider != null && account.provider.requireEAB">
<tr>
<td>EAB Kid *</td>
<td>
<input type="text" name="eabKid" maxlength="100" v-model="account.eabKid" tabindex="3"/>
<p class="comment" v-if="account.provider != null" v-html="account.provider.eabDescription"></p>
</td>
</tr>
<tr>
<td>EAB HMAC Key *</td>
<td>
<input type="text" name="eabKey" maxlength="300" v-model="account.eabKey" tabindex="4"/>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

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

View File

@@ -0,0 +1,15 @@
.button-group {
margin-top: 4em;
z-index: 1;
}
.button-group .button.primary {
float: right;
}
.success-box {
text-align: center;
padding-top: 2em;
}
.success-box p {
font-size: 1.2em;
}
/*# sourceMappingURL=create.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["create.less"],"names":[],"mappings":"AAAA;EACC,eAAA;EACA,UAAA;;AAFD,aAIC,QAAO;EACN,YAAA;;AAIF;EACC,kBAAA;EACA,gBAAA;;AAFD,YAIC;EACC,gBAAA","file":"create.css"}

View File

@@ -0,0 +1,170 @@
{$layout}
{$template "/left_menu_top"}
<div class="right-box without-tabbar">
{$template "menu"}
<div class="margin"></div>
<form class="ui form">
<div class="ui steps fluid small">
<div class="ui step" :class="{active:step == 'prepare'}">
选择申请方式
</div>
<div class="ui step" :class="{active:step == 'user'}">
选择ACME用户
</div>
<div class="ui step" :class="{active:step == 'dns'}">
填写域名信息
</div>
<div class="ui step" :class="{active:step == 'finish'}">
完成
</div>
</div>
<!-- 准备工作 -->
<div v-show="step == 'prepare'">
<table class="ui table definition selectable">
<tr>
<td class="title">认证方式 *</td>
<td>
<div style="margin-bottom: 1em">
<radio name="authType" :v-value="'http'" v-model="authType">使用HTTP认证</radio> &nbsp;
<radio name="authType" :v-value="'dns'" v-model="authType">使用DNS认证</radio>
</div>
<div v-if="authType == 'http'">
<p class="comment">使用HTTP认证的方式请求网址<code-label>/.well-known/acme-challenge/令牌</code-label>校验,该方式要求需要实现将域名解析到集群上,之后系统会自动生成网址信息;这种方式不支持泛域名证书的申请。</p>
</div>
<div v-if="authType == 'dns'">
<p class="comment">我们在申请免费证书的过程中需要自动增加或修改相关域名的TXT记录请先确保你已经在"域名解析" -- <a href="/dns/providers" target="_blank">"DNS服务商"</a> 中已经添加了对应的DNS服务商账号。</p>
</div>
</td>
</tr>
<tr>
<td>选择平台用户</td>
<td>
<user-selector @change="changePlatformUser"></user-selector>
<p class="comment">可选项,选择证书所属用户,如果没有选择,则视为管理员所有。</p>
</td>
</tr>
<tr>
<td colspan="2">
<a href="" @click.prevent="showPrepareMoreOptionsVisible">更多选项<i class="icon angle" :class="{up: prepareMoreOptionsVisible, down: !prepareMoreOptionsVisible}"></i></a>
</td>
</tr>
<tbody v-show="prepareMoreOptionsVisible">
<tr>
<td>回调URL</td>
<td>
<input type="text" name="authURL" v-model="authURL" maxlength="200"/>
<p class="comment">将认证数据以JSON的方式POST到此URL上可以依此生成认证文件或者设置DNS域名解析仅适用于未绑定当前CDN系统的域名。</p>
</td>
</tr>
</tbody>
</table>
<div class="button-group">
<button type="button" class="ui button primary" @click.prevent="doPrepare">下一步</button>
</div>
</div>
<!-- 选择用户 -->
<div v-show="step == 'user'">
<table class="ui table definition selectable">
<tr>
<td class="title">选择证书服务商 *</td>
<td>
<select class="ui dropdown auto-width" v-model="providerCode" @change="changeProvider">
<option value="">[选择服务商]</option>
<option v-for="provider in providers" :value="provider.code">{{provider.name}}</option>
</select>
</td>
</tr>
<tr v-if="providerCode.length > 0">
<td class="title">选择ACME用户 *</td>
<td>
<div v-if="users.length > 0">
<div class="ui fields inline">
<div class="ui field">
<select class="ui dropdown" v-model="userId">
<option value="0">[请选择]</option>
<option v-for="user in users" :value="user.id" v-if="user.providerCode == providerCode">{{user.email}}{{user.description}}</option>
</select>
</div>
<div class="ui field">
<a href="" @click.prevent="createUser">[新创建]</a>
</div>
</div>
</div>
<div v-else><a href="" @click.prevent="createUser"><span v-if="platformUserId > 0">当前平台用户下</span>暂时还没有ACME用户点此创建</a></div>
<p class="comment">选择一个作为申请证书的用户。</p>
</td>
</tr>
</table>
<div class="button-group">
<button type="button" class="ui button" @click.prevent="goPrepare">上一步</button>
<button type="button" class="ui button primary" @click.prevent="doUser">下一步</button>
</div>
</div>
<!-- 设置域名解析 -->
<div v-show="step == 'dns'">
<table class="ui table definition selectable">
<tr v-show="authType == 'dns'">
<td class="title">选择DNS服务商 *</td>
<td>
<div v-if="dnsProviders.length > 0">
<select class="ui dropdown auto-width" v-model="dnsProviderId">
<option value="0">[请选择]</option>
<option v-for="provider in dnsProviders" :value="provider.id">{{provider.name}}{{provider.typeName}}</option>
</select>
</div>
<div v-else>
<span>暂时没有DNS服务商<a href="/dns/providers">[去添加]</a></span>
</div>
<p class="comment">用于自动创建域名解析记录。</p>
</td>
</tr>
<tr v-show="authType == 'dns'">
<td>顶级域名 *</td>
<td>
<input type="text" maxlength="100" v-model="dnsDomain"/>
<p class="comment">用于在DNS服务商账号中操作解析记录的域名比如 example.com不要输入二级或别的多级域名。</p>
</td>
</tr>
<tr>
<td class="title">证书域名列表 *</td>
<td>
<domains-box :v-support-wildcard="authType == 'dns'" @change="changeDomains"></domains-box>
<p class="comment">需要申请的证书中包含的域名列表<span v-if="authType == 'dns'">,所有域名必须是同一个顶级域名</span><span v-if="authType == 'http'">使用HTTP认证方式时域名中不能含有通配符</span></p>
</td>
</tr>
<tr>
<td>自动续期</td>
<td>
<checkbox v-model="autoRenew"></checkbox>
<p class="comment">选中后,表示在免费证书临近到期之前尝试自动续期。</p>
</td>
</tr>
</table>
<div class="button-group">
<button type="button" class="ui button" @click.prevent="goUser">上一步</button>
<button type="button" class="ui button primary" @click.prevent="doDNS" v-if="!isRequesting">下一步</button>
<button type="button" class="ui button primary disabled" v-if="isRequesting">提交中,要花费的时间较长,请耐心等待...</button>
</div>
</div>
<!-- 完成 -->
<div v-show="step == 'finish'">
<div class="success-box">
<p><span class="green">恭喜,证书申请成功!</span>你可以在证书列表里看到刚申请的证书,也可以 <a href="" @click.prevent="viewCert">点击这里</a> 查看证书详情。</p>
</div>
<div class="button-group">
<button type="button" class="ui button primary" @click.prevent="doFinish">返回</button>
</div>
</div>
</form>
</div>

View File

@@ -0,0 +1,165 @@
Tea.context(function () {
this.step = "prepare"
/**
* 选择平台用户
*/
this.platformUserId = 0
this.changePlatformUser = function (platformUserId) {
this.platformUserId = platformUserId
}
/**
* 准备工作
*/
this.authType = "http"
this.users = []
this.doPrepare = function () {
this.step = "user"
this.$post(".userOptions")
.params({
platformUserId: this.platformUserId
})
.success(function (resp) {
this.users = resp.data.users
})
}
this.prepareMoreOptionsVisible = false
this.authURL = ""
this.showPrepareMoreOptionsVisible = function () {
this.prepareMoreOptionsVisible = !this.prepareMoreOptionsVisible
}
/**
* 选择ACME用户
*/
this.userId = 0
this.goPrepare = function () {
this.step = "prepare"
}
this.createUser = function () {
let that = this
teaweb.popup("/servers/certs/acme/users/createPopup?providerCode=" + this.providerCode + "&platformUserId=" + this.platformUserId, {
height: "30em",
width: "44em",
callback: function (resp) {
teaweb.successToast("创建成功")
let acmeUser = resp.data.acmeUser
let description = acmeUser.description
if (description.length > 0) {
description = "" + description + ""
}
that.userId = acmeUser.id
that.users.unshift({
id: acmeUser.id,
description: description,
email: acmeUser.email,
providerCode: acmeUser.providerCode
})
}
})
}
this.providerCode = ""
this.changeProvider = function () {
this.userId = 0
}
this.doUser = function () {
if (this.providerCode.length == 0) {
teaweb.warn("请选择一个证书服务商")
return
}
if (this.userId == 0) {
teaweb.warn("请选择一个申请证书的用户")
return
}
this.step = "dns"
}
/**
* 设置DNS解析
*/
this.dnsProviderId = 0
this.dnsDomain = ""
this.autoRenew = true
this.domains = []
this.taskId = 0
this.isRequesting = false
this.goUser = function () {
this.step = "user"
}
this.changeDomains = function (v) {
this.domains = v
}
this.doDNS = function () {
this.isRequesting = true
let that = this
let taskCreated = false
this.$post("$")
.params({
platformUserId: this.platformUserId,
authType: this.authType,
acmeUserId: this.userId,
dnsProviderId: this.dnsProviderId,
dnsDomain: this.dnsDomain,
domains: this.domains,
autoRenew: this.autoRenew ? 1 : 0,
taskId: this.taskId,
authURL: this.authURL
})
.success(function (resp) {
this.taskId = resp.data.taskId
taskCreated = true
this.isRequesting = true
this.$post(".run")
.timeout(300)
.params({
taskId: this.taskId
})
.success(function (resp) {
that.certId = resp.data.certId
that.step = "finish"
})
.done(function () {
that.isRequesting = false
})
})
.done(function () {
if (!taskCreated) {
this.isRequesting = false
}
})
}
/**
* 完成
*/
this.certId = 0
this.goDNS = function () {
this.step = "dns"
}
this.doFinish = function () {
window.location = "/servers/certs/acme"
}
this.viewCert = function () {
teaweb.popup("/servers/certs/certPopup?certId=" + this.certId, {
height: "28em",
width: "48em"
})
}
})

View File

@@ -0,0 +1,17 @@
.button-group {
margin-top: 4em;
z-index: 1;
.button.primary {
float: right;
}
}
.success-box {
text-align: center;
padding-top: 2em;
p {
font-size: 1.2em;
}
}

View File

@@ -0,0 +1,108 @@
{$layout}
{$template "/left_menu_top"}
<div class="right-box without-tabbar">
{$template "menu"}
<second-menu>
<menu-item :href="'/servers/certs/acme?userType=' + userType + '&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == ''">所有任务({{countAll}})</menu-item>
<menu-item :href="'/servers/certs/acme?userType=' + userType + '&type=available&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
<menu-item :href="'/servers/certs/acme?userType=' + userType + '&type=expired&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
<menu-item :href="'/servers/certs/acme?userType=' + userType + '&type=7days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
<menu-item :href="'/servers/certs/acme?userType=' + userType + '&type=30days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '30days'">30天内过期({{count30Days}})</menu-item>
</second-menu>
<form class="ui form">
<input type="hidden" name="userType" :value="userType"/>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="keyword" placeholder="域名等关键词" style="width:12em" v-model="keyword"/>
</div>
<div class="ui field">
<user-selector :v-user-id="searchingUserId"></user-selector>
</div>
<div class="ui field"></div>
<div class="ui field">
<button type="submit" class="ui button">搜索</button>
<a :href="Tea.url('.', {userType:userType})" v-if="keyword.length > 0 || searchingUserId > 0">[清除条件]</a>
</div>
</div>
</form>
<div v-if="searchingUserId == 0">
<div class="ui menu text basic tiny blue" style="margin-bottom:0">
<a :href="'/servers/certs/acme?userType=&type=' + type + '&keyword=' + keyword + '&userId=' + searchingUserId" class="item" :class="{active: userType == ''}">管理员证书</a>
<a :href="'/servers/certs/acme?userType=user&type=' + type + '&keyword=' + keyword + '&userId=' + searchingUserId" class="item" :class="{active: userType == 'user'}">用户证书</a>
</div>
<div class="ui divider" style="margin-top:0"></div>
</div>
<p class="comment" v-if="tasks.length == 0"><span v-if="searchingUserId > 0">当前用户下</span>暂时还没有证书申请任务。</p>
<div class="ui message blue" v-if="isRunning">有任务在执行中,可能需要的时间较长,请耐心等待。</div>
<table class="ui table selectable celled" v-if="tasks.length > 0">
<thead>
<tr>
<th>ACME用户</th>
<th>证书域名</th>
<th>到期时间</th>
<th>更新时间</th>
<th class="center" style="width:6em">自动续期</th>
<th class="center" style="width:6em">关联证书</th>
<th>所属用户</th>
<th class="three op">操作</th>
</tr>
</thead>
<tr v-for="(task, index) in tasks" :class="{warning: runningIndex == index}">
<td>{{task.acmeUser.email}}
<div>
<grey-label class="olive" v-if="task.authType == 'dns'">DNS</grey-label>
<grey-label class="olive" v-if="task.authType == 'http'">HTTP</grey-label>
<grey-label v-if="task.acmeUser.provider != null">{{task.acmeUser.provider.name}}</grey-label>
<grey-label v-if="task.acmeUser.account != null">{{task.acmeUser.account.name}}</grey-label>
</div>
</td>
<td nowrap="">
<div v-for="domain in task.domains">
<span class="ui label small basic">{{domain}}</span>
</div>
</td>
<td nowrap="">
<div v-if="task.cert != null">
{{task.cert.endTime}}
</div>
<span class="disabled" v-else>-</span>
</td>
<td nowrap="">
<div v-if="task.log != null">
<span class="green" v-if="task.log.isOk">{{task.log.createdTime}}</span>
<link-red v-if="!task.log.isOk" title="任务执行失败,点击查看详情" @click.prevent="showError(task.log.error)">{{task.log.createdTime}}</link-red>
</div>
<span v-else class="disabled">尚未执行</span>
</td>
<td class="center">
<span class="green" v-if="task.autoRenew">Y</span>
<span v-else class="disabled">N</span>
</td>
<td class="center">
<div v-if="task.cert != null">
<a href="" @click.prevent="viewCert(task.cert.id)"><link-icon title="查看关联证书"></link-icon></a>
</div>
<span class="disabled" v-else="">-</span>
</td>
<td>
<span v-if="task.user != null && task.user.id > 0"><user-link :v-user="task.user"></user-link></span>
<span v-else class="disabled">管理员</span>
</td>
<td>
<a href="" @click.prevent="updateTask(task.id)" :class="{disabled: isRunning}">修改</a> &nbsp;
<a href="" @click.prevent="runTask(index, task)" :class="{disabled: isRunning}">执行</a> &nbsp;
<a href="" @click.prevent="deleteTask(task.id)" :class="{disabled: isRunning}">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>
</div>

View File

@@ -0,0 +1,63 @@
Tea.context(function () {
this.viewCert = function (certId) {
teaweb.popup("/servers/certs/certPopup?certId=" + certId, {
height: "28em",
width: "48em"
})
}
this.updateTask = function (taskId) {
teaweb.popup("/servers/certs/acme/updateTaskPopup?taskId=" + taskId, {
width: "45em",
height: "26em",
callback: function () {
teaweb.success("保存成功,如果证书域名发生了改变,请重新执行生成新证书", function () {
teaweb.reload()
})
}
})
}
this.deleteTask = function (taskId) {
let that = this
teaweb.confirm("确定要删除此任务吗?", function () {
that.$post("/servers/certs/acme/deleteTask")
.params({
taskId: taskId
})
.refresh()
})
}
this.isRunning = false
this.runningIndex = -1
this.runTask = function (index, task) {
let that = this
teaweb.confirm("html:确定要立即执行此任务吗?<br/>将会重新发起证书申请。", function () {
that.isRunning = true
that.runningIndex = index
that.$post(".run")
.timeout(300)
.params({
taskId: task.id
})
.success(function (resp) {
teaweb.success("任务执行成功", function () {
teaweb.reload()
})
})
.done(function () {
that.isRunning = false
that.runningIndex = -1
})
})
}
this.showError = function (err) {
teaweb.popupTip("任务执行失败:" + err)
}
})

View File

@@ -0,0 +1,59 @@
{$layout "layout_popup"}
<h3>修改申请任务</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="taskId" :value="task.id"/>
<input type="hidden" name="acmeUserId" :value="task.acmeUser.id"/>
<input type="hidden" name="authType" :value="task.authType"/>
<table class="ui table definition selectable">
<tr v-if="task.authType == 'dns'">
<td class="title">选择DNS服务商 *</td>
<td>
<div v-if="providers.length > 0">
<select class="ui dropdown auto-width" name="dnsProviderId" v-model="task.dnsProvider.id">
<option value="0">[请选择]</option>
<option v-for="provider in providers" :value="provider.id">{{provider.name}}{{provider.typeName}}</option>
</select>
</div>
<p class="comment">用于自动创建域名解析记录。</p>
</td>
</tr>
<tr v-if="task.authType == 'dns'">
<td class="title">顶级域名 *</td>
<td>
<input type="text" maxlength="100" name="dnsDomain" v-model="task.dnsDomain"/>
<p class="comment">用于在DNS服务商账号中操作解析记录的域名比如 example.com不要输入二级或别的多级域名。</p>
</td>
</tr>
<tr>
<td class="title">证书域名列表 *</td>
<td>
<domains-box name="domainsJSON" :v-domains="task.domains" :v-support-wildcard="task.authType == 'dns'"></domains-box>
<p class="comment">需要申请的证书中包含的域名列表<span v-if="task.authType == 'dns'">,所有域名必须是同一个顶级域名</span><span v-if="task.authType == 'http'">使用HTTP认证方式时域名中不能含有通配符</span></p>
</td>
</tr>
<tr>
<td>自动续期</td>
<td>
<checkbox name="autoRenew" v-model="task.autoRenew"></checkbox>
<p class="comment">选中后,表示在免费证书临近到期之前尝试自动续期。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>回调URL</td>
<td>
<input type="text" name="authURL" v-model="task.authURL" maxlength="200"/>
<p class="comment">将认证数据以JSON的方式POST到此URL上可以依此生成认证文件或者设置DNS域名解析仅适用于未绑定当前CDN系统的域名。</p>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,52 @@
{$layout "layout_popup"}
<h3>创建ACME用户</h3>
<form method="post" class="ui form" data-tea-success="success" data-tea-action="$">
<csrf-token></csrf-token>
<input type="hidden" name="platformUserId" :value="platformUserId"/>
<table class="ui table definition selectable">
<tr v-if="platformUser != null">
<td>所属平台用户</td>
<td><user-link :v-user="platformUser"></user-link></td>
</tr>
<tr>
<td class="title">用户邮箱 *</td>
<td>
<input type="text" name="email" maxlength="100" ref="focus"/>
<p class="comment">用于自动注册用户的邮箱。</p>
</td>
</tr>
<tr>
<td>所属证书服务商 *</td>
<td>
<select class="ui dropdown auto-width" name="providerCode" v-model="providerCode" @change="changeProvider">
<option value="">[选择服务商]</option>
<option v-for="provider in providers" :value="provider.code">{{provider.name}}</option>
</select>
</td>
</tr>
<tr v-if="selectedProvider != null">
<td>所属服务商账号 <span v-if="selectedProvider.requireEAB">*</span><em v-else>(可选)</em></td>
<td>
<div class="ui fields inline">
<div class="ui field" v-if="accounts.length > 0">
<select class="ui dropdown auto-width" name="accountId" v-model="accountId">
<option value="0">[选择账号]</option>
<option v-for="account in accounts" :value="account.id">{{account.name}}</option>
</select>
</div>
<div class="ui field">
<a href="" @click.prevent="addAccount">[添加]</a>
</div>
</div>
</td>
</tr>
<tr>
<td>备注</td>
<td>
<textarea name="description" rows="3" maxlength="100"></textarea>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,57 @@
Tea.context(function () {
this.selectedProvider = null
this.accounts = []
this.accountId = 0
this.changeProvider = function () {
this.accountId = 0
if (this.providerCode.length == 0) {
return
}
let that = this
let provider = this.providers.$find(function (k, v) {
return v.code == that.providerCode
})
if (provider == null) {
return
}
this.selectedProvider = provider
this.$post(".accountsWithCode")
.params({
code: provider.code
})
.success(function (resp) {
this.accounts = resp.data.accounts
})
}
if (this.providerCode.length > 0) {
this.changeProvider()
}
this.addAccount = function () {
let that = this
teaweb.popup("/servers/certs/acme/accounts/createPopup?providerCode=" + this.providerCode, {
height: "24em",
callback: function () {
teaweb.successToast("创建成功,已自动选中", 1500, function () {
that.$post(".accountsWithCode")
.params({
code: that.providerCode
})
.success(function (resp) {
that.accounts = resp.data.accounts
if (that.accounts.length > 0) {
that.accountId = that.accounts[0].id
}
})
})
}
})
}
})

View File

@@ -0,0 +1,42 @@
{$layout}
{$template "/left_menu_top"}
<div class="right-box without-tabbar">
{$template "../menu"}
<second-menu>
<menu-item @click.prevent="createUser">[创建用户]</menu-item>
<menu-item><tip-icon content="这里管理用于申请免费证书的用户信息"></tip-icon></menu-item>
</second-menu>
<p class="comment" v-if="users.length == 0">暂时还没有用户。</p>
<table class="ui table selectable" v-if="users.length > 0">
<thead>
<tr>
<th>用户Email</th>
<th>备注</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="user in users">
<td>
<a href="" @click.prevent="updateUser(user.id)">{{user.email}} <i class="icon expand small"></i></a>
<div>
<grey-label v-if="user.provider != null">{{user.provider.name}}</grey-label>
<grey-label v-if="user.account != null">{{user.account.name}}</grey-label>
</div>
</td>
<td>
<span v-if="user.description.length > 0">{{user.description}}</span>
<span v-else class="disabled">-</span>
</td>
<td>
<a href="" @click.prevent="updateUser(user.id)">修改</a> &nbsp;
<a href="" @click.prevent="deleteUser(user.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>
</div>

View File

@@ -0,0 +1,36 @@
Tea.context(function () {
this.createUser = function () {
teaweb.popup(Tea.url(".createPopup"), {
height: "27em",
width: "44em",
callback: function () {
teaweb.success("创建成功", function () {
teaweb.reload()
})
}
})
}
this.updateUser = function (userId) {
teaweb.popup("/servers/certs/acme/users/updatePopup?userId=" + userId, {
height: "27em",
width: "44em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteUser = function (userId) {
let that = this
teaweb.confirm("确定要删除此用户吗?", function () {
that.$post(".delete")
.params({
userId: userId
})
.refresh()
})
}
})

View File

@@ -0,0 +1,31 @@
{$layout "layout_popup"}
<h3>修改ACME用户</h3>
<form method="post" class="ui form" data-tea-success="success" data-tea-action="$">
<csrf-token></csrf-token>
<input type="hidden" name="userId" :value="user.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">用户邮箱 *</td>
<td>
{{user.email}}
<p class="comment">用于自动注册用户的邮箱,不允许修改。</p>
</td>
</tr>
<tr v-if="user.provider != null">
<td>所属证书服务商</td>
<td>{{user.provider.name}}</td>
</tr>
<tr v-if="user.account != null">
<td>所属服务商账号</td>
<td>{{user.account.name}}</td>
</tr>
<tr>
<td>备注</td>
<td>
<textarea name="description" rows="3" maxlength="100" v-model="user.description" ref="focus"></textarea>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,15 @@
.pre-box {
padding: 1em;
margin: 0;
line-height: 1.7;
-ms-word-break: break-all;
word-break: break-all;
font-size: 0.9em;
background: rgba(0, 0, 0, 0.05);
overflow-y: auto;
max-height: 20em;
}
.pre-box::-webkit-scrollbar {
width: 6px;
}
/*# sourceMappingURL=certPopup.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["certPopup.less"],"names":[],"mappings":"AAAA;EACC,YAAA;EACA,SAAA;EACA,gBAAA;EACA,yBAAA;EACA,qBAAA;EACA,gBAAA;EACA,+BAAA;EACA,gBAAA;EACA,gBAAA;;AAGD,QAAQ;EACP,UAAA","file":"certPopup.css"}

View File

@@ -0,0 +1,74 @@
{$layout "layout_popup"}
<h3>证书详情</h3>
<table class="ui table definition selectable">
<tr>
<td class="title">证书说明</td>
<td>{{info.name}}</td>
</tr>
<tr v-if="info.description.length > 0">
<td>详细说明</td>
<td>{{info.description}}</td>
</tr>
<tr>
<td>证书状态</td>
<td>
<span class="ui label small green basic" v-if="info.isAvailable">有效中</span>
<span class="ui label small red basic" v-else>已过期</span>
</td>
</tr>
<tr>
<td>发行信息</td>
<td>
<div v-if="info.commonNames != null">
<div v-for="(commonName, index) in info.commonNames">
<span v-html="indent(index)"></span>{{commonName}}
</div>
</div>
</td>
</tr>
<tr>
<td>域名</td>
<td>
<span class="ui label small basic" v-for="dnsName in info.dnsNames">{{dnsName}}</span>
</td>
</tr>
<tr>
<td>有效期</td>
<td>{{info.beginTime}} - {{info.endTime}}</td>
</tr>
<tr>
<td>引用网站</td>
<td>
<span class="disabled" v-if="servers.length == 0">暂时没有引用此证书的网站。</span>
<div v-if="servers.length > 0">
<a v-for="server in servers" :href="'/servers/server/settings/https?serverId=' + server.id" target="_blank" class="ui label small basic">{{server.name}}</a>
</div>
</td>
</tr>
<tr>
<td>证书文件下载</td>
<td>
<a :href="'/servers/certs/downloadZip?certId=' + info.id" target="_blank">[ZIP下载]</a> &nbsp;
<a :href="'/servers/certs/downloadCert?certId=' + info.id" target="_blank">[证书下载]</a> &nbsp;
<a :href="'/servers/certs/downloadKey?certId=' + info.id" v-if="!info.isCA" target="_blank">[私钥下载]</a>
</td>
</tr>
<tr>
<td>证书预览</td>
<td>
<pre class="pre-box" style="font-family: Menlo, Monaco, 'Courier New', monospace !important">{{info.certString}}</pre>
<div style="margin-top:1em">
<a :href="'/servers/certs/viewCert?certId=' + info.id" target="_blank">[浏览器新窗口打开]</a>
</div>
</td>
</tr>
<tr v-if="!info.isCA">
<td>私钥预览</td>
<td><pre class="pre-box" style="font-family: Menlo, Monaco, 'Courier New', monospace !important">{{info.keyString}}</pre>
<div style="margin-top: 1em">
<a :href="'/servers/certs/viewKey?certId=' + info.id" target="_blank">[浏览器新窗口打开]</a>
</div>
</td>
</tr>
</table>

View File

@@ -0,0 +1,10 @@
Tea.context(function () {
// 打印缩进
this.indent = function (index) {
let indent = ""
for (let i = 0; i < index; i++) {
indent += " &nbsp; &nbsp; "
}
return indent
}
})

View File

@@ -0,0 +1,15 @@
.pre-box {
padding: 1em;
margin: 0;
line-height: 1.7;
-ms-word-break: break-all;
word-break: break-all;
font-size: 0.9em;
background: rgba(0, 0, 0, 0.05);
overflow-y: auto;
max-height: 20em;
}
.pre-box::-webkit-scrollbar {
width: 6px;
}

View File

@@ -0,0 +1,99 @@
{$layout}
{$template "/left_menu_top"}
<div class="right-box without-tabbar">
<second-menu>
<menu-item :href="'/servers/certs?userType=' + userType + '&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == ''">所有证书({{countAll}})</menu-item>
<menu-item :href="'/servers/certs?userType=' + userType + '&type=ca&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'ca'">CA证书({{countCA}})</menu-item>
<menu-item :href="'/servers/certs?userType=' + userType + '&type=available&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
<menu-item :href="'/servers/certs?userType=' + userType + '&type=expired&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
<menu-item :href="'/servers/certs?userType=' + userType + '&type=7days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
<menu-item :href="'/servers/certs?userType=' + userType + '&type=30days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '30days'">30天内过期({{count30Days}})</menu-item>
<span class="item disabled">|</span>
<a href="" class="item" @click.prevent="uploadCert">[上传证书]</a>
<a href="" class="item" @click.prevent="uploadBatch">[批量上传]</a>
</second-menu>
<form class="ui form">
<input type="hidden" name="type" :value="type"/>
<input type="hidden" name="userType" :value="userType"/>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="keyword" placeholder="域名、说明等关键词" style="width:12em" v-model="keyword"/>
</div>
<div class="ui field">
<user-selector :v-user-id="searchingUserId"></user-selector>
</div>
<div class="ui field">
<button type="submit" class="ui button">搜索</button>
&nbsp;
<a :href="Tea.url('.', { 'userType':userType, 'type':type })" v-if="keyword.length > 0 || searchingUserId > 0">[清除条件]</a>
</div>
</div>
</form>
<div v-if="searchingUserId == 0">
<div class="ui menu text basic tiny blue" style="margin-bottom:0">
<a :href="'/servers/certs?userType=&type=' + type + '&keyword=' + keyword + '&userId=' + searchingUserId" class="item" :class="{active: userType == ''}">管理员证书</a>
<a :href="'/servers/certs?userType=user&type=' + type + '&keyword=' + keyword + '&userId=' + searchingUserId" class="item" :class="{active: userType == 'user'}">用户证书</a>
</div>
<div class="ui divider" style="margin-top:0"></div>
</div>
<p class="comment" v-if="certs.length == 0"><span v-if="searchingUserId > 0">当前用户下</span>暂时还没有相关的证书。</p>
<table class="ui table selectable celled" v-if="certs.length > 0">
<thead>
<tr>
<th>证书说明</th>
<th>顶级发行组织</th>
<th>域名</th>
<th>生效日期</th>
<th>过期日期</th>
<th class="center">引用网站</th>
<th>所属用户</th>
<th class="center">状态</th>
<th class="three op">操作</th>
</tr>
</thead>
<tr v-for="(cert, index) in certs">
<td><keyword :v-word="keyword">{{cert.name}}</keyword>
<div v-if="cert.isCA" style="margin-top:0.5em">
<micro-basic-label class="olive">CA</micro-basic-label>
</div>
<div v-if="cert.isACME" style="margin-top: 0.5em">
<micro-basic-label class="olive" title="通过ACME协议免费申请">ACME</micro-basic-label>
</div>
</td>
<td>
<span v-if="cert.commonNames != null && cert.commonNames.length > 0">{{cert.commonNames[cert.commonNames.length-1]}}</span>
</td>
<td>
<div v-for="dnsName in cert.dnsNames" style="margin-bottom:0.4em">
<span class="ui label tiny basic"><keyword :v-word="keyword">{{dnsName}}</keyword></span>
</div>
</td>
<td>{{certInfos[index].beginDay}}</td>
<td>{{certInfos[index].endDay}}</td>
<td class="center">{{certInfos[index].countServers}}</td>
<td>
<span v-if="certInfos[index].user != null && certInfos[index].user.id > 0"><user-link :v-user="certInfos[index].user"></user-link></span>
<span v-else class="disabled">管理员</span>
</td>
<td nowrap="" class="center">
<span class="ui label red tiny basic" v-if="!certInfos[index].isOn">未启用</span>
<span class="ui label red tiny basic" v-else-if="certInfos[index].isExpired">已过期</span>
<span class="ui label green tiny basic" v-else>有效中</span>
</td>
<td>
<a href="" @click.prevent="viewCert(cert.id)">详情</a> &nbsp;
<a href="" @click.prevent="updateCert(cert.id)">修改</a> &nbsp;
<a href="" @click.prevent="deleteCert(cert.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>
</div>

View File

@@ -0,0 +1,54 @@
Tea.context(function () {
// 上传证书
this.uploadCert = function () {
teaweb.popup("/servers/certs/uploadPopup?userId=" + this.searchingUserId, {
height: "30em",
callback: function () {
teaweb.success("上传成功", function () {
window.location.reload()
})
}
})
}
// 批量上传证书
this.uploadBatch = function () {
teaweb.popup("/servers/certs/uploadBatchPopup?userId=" + this.searchingUserId, {
callback: function () {
window.location.reload()
}
})
}
// 删除证书
this.deleteCert = function (certId) {
let that = this
teaweb.confirm("确定要删除此证书吗?", function () {
that.$post("/servers/certs/delete")
.params({
certId: certId
})
.refresh()
})
}
// 查看证书
this.viewCert = function (certId) {
teaweb.popup("/servers/certs/certPopup?certId=" + certId, {
height: "28em",
width: "48em"
})
}
// 修改证书
this.updateCert = function (certId) {
teaweb.popup("/servers/certs/updatePopup?certId=" + certId, {
height: "30em",
callback: function () {
teaweb.success("修改成功", function () {
window.location.reload()
})
}
})
}
})

View File

@@ -0,0 +1,84 @@
{$layout}
{$template "/left_menu_top"}
<div class="right-box without-tabbar">
<div class="margin"></div>
<form class="ui form" method="get" action="/servers/certs/ocsp">
<div class="ui fields inline">
<div class="ui field">
<input type="text" placeholder="关键词" style="width: 10em" name="keyword" v-model="keyword"/>
</div>
<div class="ui field">
<button class="ui button small">搜索</button>
&nbsp; <a href="/servers/certs/ocsp" v-if="keyword.length > 0">[清除条件]</a>
</div>
</div>
</form>
<div class="margin"></div>
<form class="ui form" v-if="certs.length > 0">
<div class="ui divider"></div>
<div class="ui fields inline">
<div class="ui field" v-if="certIds.length == 0" @click.prevent="resetAllCerts">
<button class="ui button small basic">重试所有证书</button>
</div>
<div class="ui field" v-if="certIds.length > 0">
<button class="ui button small basic" @click.prevent="resetCerts">重试选中证书</button>
</div>
<div class="ui field" v-if="certIds.length > 0">
<button class="ui button small basic" @click.prevent="ignoreCerts">忽略选中证书</button>
</div>
</div>
</form>
<p class="comment" v-if="certs.length == 0">暂时没有OCSP日志。</p>
<table class="ui table selectable celled" v-if="certs.length > 0">
<thead>
<tr>
<th style="width: 1em"><checkbox v-model="allChecked"></checkbox></th>
<th>证书说明</th>
<th>顶级发行组织</th>
<th>域名</th>
<th class="six wide">更新错误信息</th>
<th class="center">状态</th>
<th class="one op">操作</th>
</tr>
</thead>
<tbody v-for="(cert, index) in certs">
<tr>
<td><checkbox :id="'cert_' + cert.id" ref="certCheckboxes" @input="changeCerts"></checkbox></td>
<td><keyword :v-word="keyword">{{cert.name}}</keyword>
<div v-if="cert.isCA" style="margin-top:0.5em">
<micro-basic-label class="olive">CA</micro-basic-label>
</div>
<div v-if="cert.isACME" style="margin-top: 0.5em">
<micro-basic-label class="olive" title="通过ACME协议免费申请">ACME</micro-basic-label>
</div>
</td>
<td>
<span v-if="cert.commonNames != null && cert.commonNames.length > 0">{{cert.commonNames[cert.commonNames.length-1]}}</span>
</td>
<td>
<div v-for="dnsName in cert.dnsNames" style="margin-bottom:0.4em">
<span class="ui label tiny basic"><keyword :v-word="keyword">{{dnsName}}</keyword></span>
</div>
</td>
<td style="word-break: break-all">
<span class="red">{{cert.ocspError}}</span>
</td>
<td nowrap="" class="center">
<span class="ui label red tiny basic" v-if="!cert.isOn">未启用</span>
<span class="ui label red tiny basic" v-else-if="cert.isExpired">已过期</span>
<span class="ui label green tiny basic" v-else>有效中</span>
</td>
<td>
<a href="" @click.prevent="viewCert(cert.id)">详情</a> &nbsp;
</td>
</tr>
</tbody>
</table>
<div class="page" v-html="page"></div>
</div>

View File

@@ -0,0 +1,62 @@
Tea.context(function () {
this.certIds = []
this.allChecked = false
this.$delay(function () {
let that = this
this.$watch("allChecked", function (b) {
let boxes = that.$refs.certCheckboxes
boxes.forEach(function (box) {
if (b) {
box.check()
} else {
box.uncheck()
}
that.changeCerts()
})
})
})
this.changeCerts = function () {
let boxes = this.$refs.certCheckboxes
let that = this
this.certIds = []
boxes.forEach(function (box) {
if (box.isChecked()) {
let boxId = box.id
that.certIds.push(parseInt(boxId.split("_")[1]))
}
})
}
this.resetAllCerts = function () {
this.$post(".resetAll")
.success(function () {
teaweb.successRefresh("操作成功,将很快开始重试")
})
}
this.resetCerts = function () {
this.$post(".reset")
.params({ certIds: this.certIds })
.success(function () {
teaweb.successRefresh("操作成功,将很快开始重试")
})
}
this.ignoreCerts = function () {
this.$post(".ignore")
.params({ certIds: this.certIds })
.success(function () {
teaweb.successRefresh("忽略成功")
})
}
// 查看证书详情
this.viewCert = function (certId) {
teaweb.popup("/servers/certs/certPopup?certId=" + certId, {
height: "28em",
width: "48em"
})
}
})

View File

@@ -0,0 +1,92 @@
{$layout "layout_popup"}
<h3>选择证书 <span v-if="searchingDomains.length > 0">(当前网站域名:{{searchingDomains[0]}}<var style="font-style: normal" v-if="searchingDomains.length > 1">等{{searchingDomains.length}}个域名</var></span></h3>
<!-- 搜索表单 -->
<form class="ui form" action="/servers/certs/selectPopup" ref="searchForm">
<input type="hidden" name="selectedCertIds" :value="selectedCertIds"/>
<input type="hidden" name="searchingType" :value="searchingType"/>
<input type="hidden" name="searchingDomains" :value="searchingDomains.join(',')"/>
<div class="ui fields inline">
<div class="ui field">
<user-selector :v-user-id="userId" ref="userSelector"></user-selector>
</div>
<div class="ui field">
<input type="text" name="keyword" v-model="keyword" placeholder="域名、说明文字等" size="30"/>
</div>
<div class="ui field">
<button class="ui button" type="submit">搜索</button> &nbsp;
<a :href="'/servers/certs/selectPopup?selectedCertIds=' + selectedCertIds + '&searchingType=' + searchingType + '&searchingDomains=' + encodeURL(searchingDomains.join(','))" v-if="keyword.length > 0">[清除条件]</a>
</div>
</div>
</form>
<!-- 选项卡 -->
<div v-if="searchingDomains.length > 0">
<div class="ui divider" style="margin-bottom: 0"></div>
<second-menu>
<span class="item"><span v-if="searchingUserId > 0">当前用户</span><span v-else>未指定用户</span></span>
<raquo-item></raquo-item>
<menu-item :active="searchingType == 'all'" :href="baseURL + '&searchingType=all'">所有证书 <span class="small">({{totalAll}})</span></menu-item>
<span class="disabled item">|</span>
<menu-item :active="searchingType == 'match'" :href="baseURL + '&searchingType=match'">域名匹配证书 <span class="small"> ({{totalMatch}})</span></menu-item>
</second-menu>
</div>
<!-- 全选 -->
<div v-if="countChecked > 0">
<div class="margin"></div>
<button class="ui button small basic" type="button" @click.prevent="confirmChecked">使用选中的{{countChecked}}个证书</button>
</div>
<!-- 证书列表 -->
<p class="comment" v-if="certs.length == 0">
<span v-if="searchingUserId > 0">当前用户下</span>暂时还没有<span v-if="searchingType == 'match'">跟所添加域名匹配的</span>相关证书<span v-if="searchingUserId > 0"><a href="" @click.prevent="searchNoneUserCerts">[尝试搜索管理员上传和申请的证书]</a></span>
</p>
<table class="ui table selectable celled" v-if="certs.length > 0">
<thead>
<tr>
<th style="width:1em"><checkbox @input="changeAll"></checkbox></th>
<th>证书说明</th>
<th>域名</th>
<th>过期日期</th>
<th v-if="viewSize == 'normal'">引用网站</th>
<th>状态</th>
<th class="one op">操作</th>
</tr>
</thead>
<tr v-for="(cert, index) in certs">
<td>
<checkbox v-model="cert.isChecked" ref="certCheckboxes" @input="changeCertChecked" v-if="!certInfos[index].isSelected"></checkbox>
</td>
<td>
<a href="" @click.prevent="selectCert(cert)" v-if="!certInfos[index].isSelected"><keyword :v-word="keyword">{{cert.name}}</keyword></a>
<span v-if="certInfos[index].isSelected">{{cert.name}}</span>
<div v-if="cert.commonNames != null && cert.commonNames.length > 0" style="margin-top:0.5em">
<span class="grey small">{{cert.commonNames[cert.commonNames.length-1]}}</span>
</div>
<div v-if="cert.isCA" style="margin-top:0.5em">
<span class="ui label olive tiny">CA</span>
</div>
</td>
<td>
<div v-for="dnsName in cert.dnsNames" style="margin-bottom:0.4em">
<span class="ui label tiny basic"><keyword :v-word="keyword">{{dnsName}}</keyword></span>
</div>
</td>
<td>{{certInfos[index].endDay}}</td>
<td v-if="viewSize == 'normal'">{{certInfos[index].countServers}}</td>
<td nowrap="">
<span class="ui label red tiny basic" v-if="certInfos[index].isExpired">已过期</span>
<span class="ui label green tiny basic" v-else>有效中</span>
</td>
<td>
<a href="" @click.prevent="selectCert(cert)" v-if="!certInfos[index].isSelected">选择</a>
<span v-else>已选</span>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,84 @@
Tea.context(function () {
this.selectCert = function (cert) {
NotifyPopup({
code: 200,
data: {
cert: cert,
certRef: {
isOn: true,
certId: cert.id
}
}
})
}
this.encodeURL = function (arg) {
return window.encodeURIComponent(arg)
}
/**
* 复选框
*/
this.countChecked = 0
this.certs.forEach(function (cert) {
cert.isChecked = false
})
this.changeAll = function (b) {
let that = this
this.certs.forEach(function (cert) {
cert.isChecked = b
})
if (b) {
let countChecked = 0
this.certs.forEach(function (cert, index) {
if (cert.isChecked && !that.certInfos[index].isSelected) {
countChecked++
}
})
this.countChecked = countChecked
} else {
this.countChecked = 0
}
}
this.changeCertChecked = function () {
let countChecked = 0
this.certs.forEach(function (cert) {
if (cert.isChecked) {
countChecked++
}
})
this.countChecked = countChecked
}
this.confirmChecked = function () {
let resultCerts = []
let resultCertRefs = []
this.certs.forEach(function (cert) {
if (cert.isChecked) {
resultCerts.push(cert)
resultCertRefs.push({
isOn: true,
certId: cert.id
})
}
})
NotifyPopup({
code: 200,
data: {
certs: resultCerts,
certRefs: resultCertRefs
}
})
}
this.searchNoneUserCerts = function () {
this.$refs.userSelector.clear()
this.$delay(function () {
this.$refs.searchForm.submit()
}, 10)
}
})

View File

@@ -0,0 +1,63 @@
{$layout "layout_popup"}
<h3>修改证书<span><span v-if="certConfig.dnsNames != null && certConfig.dnsNames.length > 0">&nbsp; (包含{{certConfig.dnsNames[0]}}<var v-if="certConfig.dnsNames.length > 1" style="font-style: normal">等{{certConfig.dnsNames.length}}个</var>域名)</span></span></h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="certId" :value="certConfig.id"/>
<input type="hidden" name="textMode" :value="textMode ? 1 : 0"/>
<table class="ui table definition selectable">
<tr>
<td class="title">证书说明 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus" v-model="certConfig.name"/>
<p class="comment">可以简单说明证书的用途。</p>
</td>
</tr>
<tr>
<td>证书类型</td>
<td>
<select class="ui dropdown auto-width" name="isCA" v-model="isCA">
<option value="0">加密证书</option>
<option value="1">CA证书</option>
</select>
</td>
</tr>
<tr>
<td><span v-if="textMode">输入证书内容</span><span v-else>选择证书文件</span></td>
<td>
<input type="file" name="certFile" accept="application/x-pem-file, application/pkcs10, application/x-pkcs12, application/x-x509-user-cert, application/x-x509-ca-cert, application/pkix-cert, .pem" v-if="!textMode"/>
<file-textarea class="wide-code" ref="certTextField" spellcheck="false" name="certText" placeholder="-----BEGIN CERTIFICATE-----" v-if="textMode" style="font-size: 0.7em"></file-textarea>
<p class="comment"><a href="" @click.prevent="switchTextMode">[<span v-if="!textMode">输入内容</span><span v-else>上传文件</span>]</a>。文件内容中通常含有"-----BEGIN CERTIFICATE-----"类似的信息<span v-if="textMode">,可以直接拖动证书文件到输入框,留空表示不修改</span></p>
</td>
</tr>
<tr v-show="isCA == 0">
<td><span v-if="textMode">输入私钥内容</span><span v-else>选择私钥文件</span></td>
<td>
<input type="file" name="keyFile" accept="application/pkcs8, .key" v-if="!textMode"/>
<file-textarea class="wide-code" spellcheck="false" name="keyText" placeholder="-----BEGIN RSA PRIVATE KEY-----" v-if="textMode" style="font-size: 0.7em"></file-textarea>
<p class="comment"><a href="" @click.prevent="switchTextMode">[<span v-if="!textMode">输入内容</span><span v-else>上传文件</span>]</a>。文件内容中通常含有"-----BEGIN RSA PRIVATE KEY-----"类似的信息<span v-if="textMode">,可以直接拖动证书文件到输入框,留空表示不修改</span></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="3" name="description" maxlength="200" v-model="certConfig.description"></textarea>
</td>
</tr>
<tr>
<td>启用当前证书</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="certConfig.isOn"/>
<label></label>
</div>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,14 @@
Tea.context(function () {
this.success = NotifyPopup
this.isCA = this.certConfig.isCA ? 1 : 0
this.textMode = false
this.switchTextMode = function () {
this.textMode = !this.textMode
if (this.textMode) {
this.$delay(function () {
this.$refs.certTextField.focus()
})
}
}
})

View File

@@ -0,0 +1,25 @@
{$layout "layout_popup"}
<h3>批量上传证书</h3>
<form class="ui form" data-tea-success="successUpload" data-tea-action="$" data-tea-before="before" data-tea-done="done" data-tea-timeout="600">
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">选择要上传的证书和私钥文件 *</td>
<td>
<input type="file" name="certFiles" accept="application/x-pem-file, application/pkcs10, application/x-pkcs12, application/x-x509-user-cert, application/x-x509-ca-cert, application/pkix-cert, application/pkcs8, .key, .pem" multiple="multiple"/>
<p class="comment">点击后在弹出的文件选择框中支持多选;不用担心文件名和顺序问题,系统会自动匹配<span v-if="!teaIsPlus">;免费版本限制每次最多上传{{maxFiles}}个文件</span></p>
</td>
</tr>
<tr>
<td>所属用户</td>
<td>
<user-selector @change="changeUserId" :v-user-id="userId"></user-selector>
<p class="comment">可选项,指定证书所属的用户;指定用户后,上传的证书管理员无法在管理系统查看,只能在用户系统查看。</p>
</td>
</tr>
</table>
<submit-btn v-show="!isRequesting">上传</submit-btn>
<button class="ui button disabled " type="button" v-if="isRequesting">上传中...</button>
</form>

View File

@@ -0,0 +1,25 @@
Tea.context(function () {
this.isRequesting = false
this.before = function () {
this.isRequesting = true
}
this.done = function () {
this.isRequesting = false
}
this.successUpload = function (resp) {
let msg = "html:成功上传" + resp.data.count + "个证书"
if (this.userId > 0) {
msg += "<br/>由于你选择了证书用户,所以只有此用户才能在用户系统中查看到这些证书。"
}
teaweb.success(msg, function () {
NotifyPopup(resp)
})
}
this.changeUserId = function (userId) {
this.userId = userId
}
})

View File

@@ -0,0 +1,69 @@
{$layout "layout_popup"}
<h3>上传证书</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="textMode" :value="textMode ? 1 : 0"/>
<table class="ui table definition selectable">
<tr>
<td class="title">证书说明 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus"/>
<p class="comment">可以简单说明证书的用途。</p>
</td>
</tr>
<tr>
<td>所属用户</td>
<td>
<user-selector :v-user-id="userId"></user-selector>
<p class="comment">可选项,当前证书所属用户。</p>
</td>
</tr>
<tr>
<td>证书类型 *</td>
<td>
<select class="ui dropdown auto-width" name="isCA" v-model="isCA">
<option value="0">加密证书</option>
<option value="1">CA证书</option>
</select>
</td>
</tr>
<tr>
<td><span v-if="textMode">输入证书内容</span><span v-else>选择证书文件</span> *</td>
<td>
<input type="file" name="certFile" accept="application/x-pem-file, application/pkcs10, application/x-pkcs12, application/x-x509-user-cert, application/x-x509-ca-cert, application/pkix-cert, .pem" v-if="!textMode"/>
<file-textarea class="wide-code" ref="certTextField" spellcheck="false" name="certText" placeholder="-----BEGIN CERTIFICATE-----" v-if="textMode" style="font-size: 0.7em"></file-textarea>
<p class="comment"><a href="" @click.prevent="switchTextMode">[<span v-if="!textMode">输入内容</span><span v-else>上传文件</span>]</a>。文件内容中通常含有"-----BEGIN CERTIFICATE-----"类似的信息<span v-if="textMode">,可以直接拖动证书文件到输入框</span></p>
</td>
</tr>
<tr v-show="isCA == 0">
<td><span v-if="textMode">输入私钥内容</span><span v-else>选择私钥文件</span> *</td>
<td>
<input type="file" name="keyFile" accept="application/pkcs8, application/x-pem-file, .key, .pem" v-if="!textMode"/>
<file-textarea class="wide-code" spellcheck="false" name="keyText" placeholder="-----BEGIN RSA PRIVATE KEY-----" v-if="textMode" style="font-size: 0.7em"></file-textarea>
<p class="comment"><a href="" @click.prevent="switchTextMode">[<span v-if="!textMode">输入内容</span><span v-else>上传文件</span>]</a>。文件内容中通常含有"-----BEGIN RSA PRIVATE KEY-----"类似的信息<span v-if="textMode">,可以直接拖动私钥文件到输入框</span></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="3" name="description" maxlength="200"></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,14 @@
Tea.context(function () {
this.success = NotifyPopup
this.isCA = 0
this.textMode = false
this.switchTextMode = function () {
this.textMode = !this.textMode
if (this.textMode) {
this.$delay(function () {
this.$refs.certTextField.focus()
})
}
}
})

View File

@@ -0,0 +1,11 @@
<second-menu>
<menu-item href="/servers/components/cache">策略列表</menu-item>
<span class="item">|</span>
<menu-item :href="'/servers/components/cache/policy?cachePolicyId=' + cachePolicyId" code="index">{{cachePolicyName}}</menu-item>
<menu-item :href="'/servers/components/cache/test?cachePolicyId=' + cachePolicyId" code="test">测试</menu-item>
<menu-item :href="'/servers/components/cache/stat?cachePolicyId=' + cachePolicyId" code="stat">统计</menu-item>
<menu-item :href="'/servers/components/cache/clean?cachePolicyId=' + cachePolicyId" code="clean">清理</menu-item>
<menu-item :href="'/servers/components/cache/purge?cachePolicyId=' + cachePolicyId" code="purge">刷新</menu-item>
<menu-item :href="'/servers/components/cache/fetch?cachePolicyId=' + cachePolicyId" code="fetch">预热</menu-item>
<menu-item :href="'/servers/components/cache/update?cachePolicyId=' + cachePolicyId" code="update">修改</menu-item>
</second-menu>

View File

@@ -0,0 +1,6 @@
<first-menu>
<menu-item href="." code="purge">刷新缓存</menu-item>
<menu-item href=".fetch" code="fetch">预热缓存</menu-item>
<span class="item disabled">|</span>
<menu-item href=".tasks" code="task">所有任务<span v-if="countDoingTasks > 0" class="grey">({{countDoingTasks}})</span></menu-item>
</first-menu>

View File

@@ -0,0 +1,31 @@
{$layout}
{$template "menu"}
<div class="margin"></div>
<div><span class="grey">预热缓存指的是预先从源站读取最新内容当用户访问预热后的URL时直接从缓存中返回内容不需要再次回源。</span></div>
<form method="post" class="ui form" data-tea-action="$" data-tea-before="before" data-tea-success="success" data-tea-fail="fail" data-tea-done="done" data-tea-timeout="3600">
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td>要预热的URL列表</td>
<td>
<textarea name="keys" rows="20" ref="focus"></textarea>
<p class="comment">每行一个URL。</p>
</td>
</tr>
<tr>
<td class="title">操作结果</td>
<td>
<div v-if="isRequesting">数据发送中...</div>
<span class="red" v-if="!isRequesting && !isOk && message.length > 0">失败:{{message}}</span>
<div v-if="!isRequesting && !isOk && failKeys.length > 0" class="fail-keys-box">
<div v-for="failKey in failKeys">
<span class="red">{{failKey.key}}: {{failKey.reason}}</span>
</div>
</div>
</td>
</tr>
</table>
<submit-btn v-if="!isRequesting">提交</submit-btn>
</form>

View File

@@ -0,0 +1,32 @@
Tea.context(function () {
this.isRequesting = false
this.isOk = false
this.message = ""
this.failKeys = []
this.before = function () {
this.isRequesting = true
this.isOk = false
this.message = ""
this.failKeys = []
}
this.success = function (resp) {
this.isOk = true
let f = NotifyReloadSuccess("任务提交成功")
f()
}
this.fail = function (resp) {
this.message = resp.message
if (resp.data.failKeys != null) {
this.failKeys = resp.data.failKeys
}
}
this.done = function () {
this.isRequesting = false
}
});

View File

@@ -0,0 +1,8 @@
.fail-keys-box {
max-height: 10em;
overflow-y: auto;
}
.fail-keys-box::-webkit-scrollbar {
width: 6px;
}
/*# sourceMappingURL=index.css.map */

View File

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

View File

@@ -0,0 +1,43 @@
{$layout}
{$template "menu"}
<div class="margin"></div>
<div><span class="grey">刷新缓存指的是标记URL列表或目录为失效状态当有新的用户请求这些URL时会再次从源站读取最新的内容。</span></div>
<form method="post" class="ui form" data-tea-action="$" data-tea-before="before" data-tea-success="success" data-tea-fail="fail" data-tea-done="done" data-tea-timeout="300">
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">URL类型</td>
<td>
<radio name="keyType" :v-value="'key'" v-model="keyType">URL</radio> &nbsp;
<radio name="keyType" :v-value="'prefix'" v-model="keyType">目录</radio>
</td>
</tr>
<tr>
<td>
<span v-if="keyType == 'key'">要刷新的URL列表</span>
<span v-if="keyType == 'prefix'">要刷新的URL目录列表</span>
</td>
<td>
<textarea name="keys" rows="20" ref="keysBox"></textarea>
<p class="comment" v-if="keyType == 'key'">每行一个URL比如<code-label>https://example.com/hello/world.html</code-label></p>
<p class="comment" v-if="keyType == 'prefix'">每行一个URL目录比如<code-label>https://example.com/hello/</code-label>;如果只填写域名部分,表示清理全站,比如<code-label>https://example.com/</code-label></p>
</td>
</tr>
<tr>
<td>操作结果</td>
<td>
<div v-if="isRequesting">数据发送中...</div>
<span class="red" v-if="!isRequesting && !isOk && message.length > 0">失败:{{message}}</span>
<div v-if="!isRequesting && !isOk && failKeys.length > 0" class="fail-keys-box">
<div v-for="failKey in failKeys">
<span class="red">{{failKey.key}}: {{failKey.reason}}</span>
</div>
</div>
</td>
</tr>
</table>
<submit-btn v-if="!isRequesting">提交</submit-btn>
</form>

View File

@@ -0,0 +1,47 @@
Tea.context(function () {
this.isRequesting = false
this.isOk = false
this.message = ""
this.failKeys = []
this.$delay(function () {
this.$refs.keysBox.focus()
this.$watch("keyType", function () {
this.$refs.keysBox.focus()
})
})
this.before = function () {
this.isRequesting = true
this.isOk = false
this.message = ""
this.failKeys = []
}
this.success = function () {
this.isOk = true
let that = this
teaweb.success("任务提交成功", function () {
window.location = window.location.pathname + "?keyType=" + that.keyType
})
}
this.fail = function (resp) {
this.message = resp.message
if (resp.data.failKeys != null) {
this.failKeys = resp.data.failKeys
}
}
this.done = function () {
this.isRequesting = false
}
/**
* 操作类型
*/
if (this.keyType == null || this.keyType.length == 0) {
this.keyType = "key" // key | prefix
}
})

View File

@@ -0,0 +1,8 @@
.fail-keys-box {
max-height: 10em;
overflow-y: auto;
}
.fail-keys-box::-webkit-scrollbar {
width: 6px;
}

View File

@@ -0,0 +1,100 @@
{$layout}
{$template "menu"}
<second-menu>
<menu-item href=".tasks">所有任务</menu-item>
<span class="disabled item" style="padding: 0">&raquo;</span>
<span class="item">任务详情</span>
</second-menu>
<table class="ui table definition selectable">
<tr>
<td class="title">任务编号</td>
<td>{{task.id}}</td>
</tr>
<tr>
<td>任务类型</td>
<td>
<span v-if="task.type == 'purge'">刷新</span>
<span v-if="task.type == 'fetch'">预热</span>
</td>
</tr>
<tr>
<td>缓存URL类型</td>
<td>
<span v-if="task.keyType == 'key'">URL</span>
<span v-if="task.keyType == 'prefix'">目录</span>
</td>
</tr>
<tr>
<td>创建时间</td>
<td>
{{task.createdTime}}
</td>
</tr>
<tr>
<td>完成时间</td>
<td>
<span v-if="task.isDone">{{task.doneTime}}</span>
<span v-else class="disabled">尚未完成</span>
</td>
</tr>
<tr>
<td>所属用户</td>
<td>
<span v-if="task.user != null && task.user.id > 0"><user-link :v-user="task.user"></user-link></span>
<span v-else="" class="disabled">-</span>
</td>
</tr>
<tr>
<td>任务状态</td>
<td>
<span v-if="task.isOk" class="green">已完成</span>
<a :href="'/servers/components/cache/batch/task?taskId=' + task.id" v-else-if="task.isDone" class="red"><span class="red">失败</span></a>
<span v-else-if="!task.isDone" class="grey">等待执行</span>
</td>
</tr>
<tr>
<td>操作</td>
<td>
<a href="" @click.prevent="deleteTask(task.id)">[删除]</a> &nbsp; &nbsp;
<a href="" @click.prevent="resetTask(task.id)">[重置状态]</a>
</td>
</tr>
</table>
<h4>要操作的缓存Key</h4>
<table class="ui table selectable celled">
<thead>
<tr>
<th>
<span v-if="task.keyType == 'key'">URL</span>
<span v-if="task.keyType == 'prefix'">前缀</span>
</th>
<th class="two wide">所属集群</th>
<th class="width6">状态</th>
</tr>
</thead>
<tbody v-for="key in task.keys">
<tr>
<td>
{{key.key}}
<div v-if="key.errors.length > 0" style="margin-top: 0.5em">
<a :href="'/clusters/cluster/node?nodeId=' + err.nodeId" v-for="err in key.errors" class="ui label basic tiny red">
节点{{err.nodeId}}{{err.error}}
</a>
</div>
</td>
<td>
<span v-if="key.cluster != null && key.cluster.id > 0">{{key.cluster.name}}</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="key.isDone && key.errors.length == 0" class="green">完成</span>
<span v-if="key.isDone && key.errors.length > 0" class="red">失败</span>
<span v-if="!key.isDone && !key.isDoing" class="grey">未执行</span>
<span v-if="!key.isDone && key.isDoing" class="grey">部分完成</span>
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,42 @@
Tea.context(function () {
this.$delay(function () {
this.reload()
})
this.deleteTask = function (taskId) {
teaweb.confirm("确定要删除此任务吗?", function () {
this.$post(".deleteTask")
.params({
taskId: taskId
})
.success(function () {
window.location = Tea.url(".tasks")
})
})
}
this.resetTask = function (taskId) {
teaweb.confirm("确定要重置任务状态吗?", function () {
this.$post(".resetTask")
.params({
taskId: taskId
})
.refresh()
})
}
this.reload = function () {
this.$post("$")
.params({
taskId: this.task.id
})
.success(function (resp) {
this.task = resp.data.task
})
.done(function () {
this.$delay(function () {
this.reload()
}, 10000)
})
}
})

View File

@@ -0,0 +1,48 @@
{$layout}
{$template "menu"}
<p class="comment" v-if="tasks.length == 0">暂时还没有任务。</p>
<table class="ui table selectable celled" v-if="tasks.length > 0">
<thead>
<tr>
<th style="width: 7em">任务编号</th>
<th>任务类型</th>
<th>URL类型</th>
<th>创建时间</th>
<th class="four wide">所属用户</th>
<th class="two wide">任务状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tbody v-for="task in tasks">
<tr>
<td><a :href="'/servers/components/cache/batch/task?taskId=' + task.id">{{task.id}}</a></td>
<td>
<span v-if="task.type == 'purge'">刷新</span>
<span v-if="task.type == 'fetch'">预热</span>
</td>
<td>
<span v-if="task.keyType == 'key'">URL</span>
<span v-if="task.keyType == 'prefix'">目录</span>
</td>
<td>{{task.createdTime}}</td>
<td>
<span v-if="task.user != null && task.user.id > 0"><user-link :v-user="task.user"></user-link></span>
<span v-if="task.description.length > 0" class="grey">{{task.description}}</span>
<span v-else="" class="disabled">-</span>
</td>
<td>
<span v-if="task.isOk" class="green">已完成</span>
<a :href="'/servers/components/cache/batch/task?taskId=' + task.id" v-else-if="task.isDone" class="red"><span class="red">失败</span></a>
<span v-else-if="!task.isDone" class="grey">等待执行</span>
</td>
<td>
<a :href="'/servers/components/cache/batch/task?taskId=' + task.id">详情</a> &nbsp;
<a href="" @click.prevent="deleteTask(task.id)">删除</a>
</td>
</tr>
</tbody>
</table>
<page-box></page-box>

View File

@@ -0,0 +1,11 @@
Tea.context(function () {
this.deleteTask = function (taskId) {
teaweb.confirm("确定要删除此任务吗?", function () {
this.$post(".deleteTask")
.params({
taskId: taskId
})
.refresh()
})
}
})

View File

@@ -0,0 +1,74 @@
{$layout}
{$template "policy_menu"}
<h3>选择集群</h3>
<select class="ui dropdown auto-width" v-model="clusterId">
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
</select>
<div class="ui divider"></div>
<h3>清理</h3>
<p class=""><span class="red">严重警告:该操作将清理集群所有节点上的所有当前策略产生的缓存;如果只是想清理单个缓存,请使用“刷新预热”功能。</span></p>
<form method="post" data-tea-action="$" data-tea-before="before" data-tea-success="success" data-tea-fail="fail" data-tea-done="done" data-tea-timeout="300">
<input type="hidden" name="cachePolicyId" :value="cachePolicyId"/>
<input type="hidden" name="clusterId" :value="clusterId"/>
<table class="ui table definition selectable">
<tr>
<td class="title">操作原因 *</td>
<td>
<p class="comment">为了防止新手滥用此功能、导致节点崩溃,你需要选择操作原因,才能继续操作:</p>
<div>
<radio v-model="reason" :v-value="REASON_NEW_PIE">我是一名新手,我不知道怎么清除缓存</radio>
</div>
<div style="margin-top: 0.6em">
<radio v-model="reason" :v-value="REASON_BATCH_DELETE">要清理的域名太多,我比较懒,我不想用“刷新预热”功能</radio>
</div>
<div style="margin-top: 0.6em">
<radio v-model="reason" :v-value="REASON_MAINTAINS">因节点硬盘空间不足或以往缓存设置错误或其他原因,想清除所有缓存,重新开始</radio>
</div>
<div style="margin-top: 0.6em">
<radio v-model="reason" :v-value="REASON_ISSUE_REPORT">我怀疑“刷新预热”功能不起作用</radio>
</div>
</td>
</tr>
<tr v-show="!isReasonable()">
<td>新手操作建议</td>
<td>
请使用左侧主菜单中的 <a href="/servers/components/cache/batch?keyType=prefix" target="_blank">[刷新预热]</a> 功能来清理你的缓存。
</td>
</tr>
<tr v-show="reason == REASON_ISSUE_REPORT">
<td>操作建议</td>
<td>
当前系统提供的 <a href="/servers/components/cache/batch?keyType=prefix" target="_blank">[刷新预热]</a> 功能运行非常可靠如果你确认发现功能异常且能100%几率重现问题,请及时报告给软件开发者。
</td>
</tr>
<tr v-show="reason == REASON_BATCH_DELETE">
<td>操作建议</td>
<td>
请尽可能使用 <a href="/servers/components/cache/batch?keyType=prefix" target="_blank">[刷新预热]</a> 功能,避免影响当前系统上的别的网站。
</td>
</tr>
<tr v-show="reason == REASON_MAINTAINS">
<td>操作建议</td>
<td>
缓存文件较多时,系统需要消耗更多系统资源和更多时间删除缓存文件,硬盘使用空间会逐渐恢复,操作后,请耐心等待。
</td>
</tr>
<tr v-show="isReasonable()">
<td class="title">操作结果</td>
<td>
<div v-if="isRequesting">数据发送中...</div>
<span class="red" v-if="!isRequesting && !isOk && message.length > 0">失败:{{message}}</span>
<div v-if="!isRequesting && isOk">
<span v-if="results.length == 0" class="red">此集群下没有任何可用的节点。</span>
<div class="ui label tiny" v-for="one in results" :class="{green:one.isOk, red:!one.isOk}" style="margin-bottom: 0.5em">{{one.nodeName}}{{one.message}}</div>
</div>
</td>
</tr>
</table>
<submit-btn v-if="!isRequesting && isReasonable()">提交</submit-btn>
<button class="ui button disabled" type="button" v-if="!isReasonable()">提交</button>
</form>

View File

@@ -0,0 +1,45 @@
Tea.context(function () {
this.reason = 0
this.REASON_NEW_PIE = 0
this.REASON_ISSUE_REPORT = 1
this.REASON_BATCH_DELETE = 2
this.REASON_MAINTAINS = 3
this.isReasonable = function () {
return this.reason == this.REASON_ISSUE_REPORT || this.reason == this.REASON_BATCH_DELETE || this.reason == this.REASON_MAINTAINS
}
if (this.clusterId == null) {
if (this.clusters.length > 0) {
this.clusterId = this.clusters[0].id
} else {
this.clusterId = 0
}
}
this.isRequesting = false
this.isOk = false
this.message = ""
this.results = []
this.before = function () {
this.isRequesting = true
this.isOk = false
this.message = ""
this.results = []
}
this.success = function (resp) {
this.isOk = true
this.results = resp.data.results
}
this.fail = function (resp) {
this.message = resp.message
}
this.done = function () {
this.isRequesting = false
}
});

View File

@@ -0,0 +1,135 @@
{$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 class="title">策略名称 *</td>
<td><input type="text" name="name" maxlength="100" ref="focus"/> </td>
</tr>
<tr>
<td class="color-border">缓存类型 *</td>
<td>
<select class="ui dropdown auto-width" name="type" v-model="policyType">
<option v-for="type in types" :value="type.type">{{type.name}}</option>
</select>
</td>
</tr>
<!-- 文件缓存选项 -->
<tbody v-if="policyType == 'file'">
<tr>
<td class="color-border">缓存目录 *</td>
<td>
<input type="text" name="fileDir" maxlength="500" value="/opt/cache"/>
<p class="comment">存放文件缓存的目录,通常填写绝对路径。</p>
</td>
</tr>
<tr>
<td class="color-border">缓存硬盘最大用量 *</td>
<td>
<size-capacity-box :v-name="'capacityJSON'" :v-count="128" :v-unit="'gb'" :key="'capacityJSON1'"></size-capacity-box>
<p class="comment">单个节点上缓存所在硬盘的最大用量超出此用量或硬盘接近用尽时将会自动尝试清理旧数据如果为0表示没有限制。</p>
</td>
</tr>
<tr>
<td class="color-border">内存最大容量</td>
<td>
<size-capacity-box :v-name="'fileMemoryCapacityJSON'" :v-count="1" :v-unit="'gb'" :key="'fileMemoryCapacityJSON'"></size-capacity-box>
<p class="comment">单个节点上作为一级缓存的内存最大容量可以作为硬盘缓冲区和存储热点缓存内容如果为0表示不使用内存作为一级缓存。</p>
</td>
</tr>
</tbody>
<tbody v-if="policyType == 'memory'">
<tr>
<td>内存最大容量</td>
<td>
<size-capacity-box :key="'a'" :v-name="'capacityJSON'" :v-count="2" :v-unit="'gb'" :key="'capacityJSON2'"></size-capacity-box>
<p class="comment">单个节点上允许缓存的最大容量如果为0表示没有限制。</p>
</td>
</tr>
</tbody>
<tr>
<td>最大内容尺寸</td>
<td>
<size-capacity-box :v-name="'maxSizeJSON'" :v-count="256" :v-unit="'mb'" :key="'maxSizeJSON'"></size-capacity-box>
<p class="comment">允许缓存的单个内容最大尺寸如果为0表示没有限制。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible && policyType == 'file'">
<tr>
<td class="color-border">缓存硬盘最小空余空间</td>
<td>
<size-capacity-box :v-name="'fileMinFreeSizeJSON'" :v-count="0" :v-unit="'gb'" :key="'minFreeSizeJSON'"></size-capacity-box>
<p class="comment">缓存硬盘保留的最小空余空间如果为0表示自动限制目前默认保留5GiB</p>
</td>
</tr>
<tr>
<td class="color-border">缓存文件句柄缓存</td>
<td>
<input type="text" name="fileOpenFileCacheMax" maxlength="6" value="0" style="width: 10em"/>
<p class="comment"><pro-warning-label></pro-warning-label>保持在内存中的缓存文件句柄的数量提升缓存文件打开速度同时也会占用系统更多的内存建议数量不超过缓存文件数量的百分之一0表示不启用。</p>
</td>
</tr>
<tr v-show="teaIsPlus">
<td class="color-border">开启Sendfile</td>
<td>
<checkbox name="fileEnableSendfile"></checkbox>
<p class="comment"><pro-warning-label></pro-warning-label><plus-label></plus-label>使用sendfile提升发送缓存文件的效率。</p>
</td>
</tr>
<tr>
<td class="color-border">允许读取不完整的Partial Content</td>
<td>
<checkbox name="enableIncompletePartialContent" checked="checked"></checkbox>
<p class="comment">允许在有一部分内容缓存的情况下读取分区内容,剩余的部分将会自动回源读取。</p>
</td>
</tr>
<!-- <tr v-show="moreOptionsVisible && policyType == 'file'">
<td class="color-border">启用MMAP</td>
<td>
<checkbox name="enableMMAP"></checkbox>
<p class="comment">选中后表示允许系统自动利用MMAP提升缓存读取性能。</p>
</td>
</tr>-->
</tbody>
<tbody v-show="moreOptionsVisible">
<tr>
<td>同步写入压缩缓存</td>
<td>
<checkbox name="syncCompressionCache"></checkbox>
<p class="comment">选中后在压缩设置开启的情况下在缓存源站内容的同时也会同步写入压缩缓存不选中表示在源站内容缓存后下一次调用才会缓存压缩内容防止同一时间内硬盘IO负载过高。</p>
</td>
</tr>
<tr>
<td>预热超时时间</td>
<td>
<time-duration-box :v-name="'fetchTimeoutJSON'" :v-unit="'minute'" maxlength="6"></time-duration-box>
<p class="comment">预热读取源站的超时时间默认20分钟。</p>
</td>
</tr>
<tr>
<td>描述</td>
<td>
<textarea maxlength="200" name="description" 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,5 @@
Tea.context(function () {
this.success = NotifyPopup
this.policyType = this.types[0].type
})

View File

@@ -0,0 +1,28 @@
{$layout}
{$template "policy_menu"}
<form method="post" class="ui form" data-tea-action="$" data-tea-before="before" data-tea-success="success" data-tea-fail="fail" data-tea-done="done" data-tea-timeout="3600">
<input type="hidden" name="cachePolicyId" :value="cachePolicyId"/>
<table class="ui table definition selectable">
<tr>
<td>URL列表</td>
<td>
<textarea name="keys" rows="10" ref="focus"></textarea>
<p class="comment">每行一个URL。</p>
</td>
</tr>
<tr>
<td class="title">操作结果</td>
<td>
<div v-if="isRequesting">数据发送中...</div>
<span class="red" v-if="!isRequesting && !isOk && message.length > 0">失败:{{message}}</span>
<div v-if="!isRequesting && !isOk && failKeys.length > 0" class="fail-keys-box">
<div v-for="failKey in failKeys">
<span class="red">{{failKey.key}}: {{failKey.reason}}</span>
</div>
</div>
</td>
</tr>
</table>
<submit-btn v-if="!isRequesting">提交</submit-btn>
</form>

View File

@@ -0,0 +1,32 @@
Tea.context(function () {
this.isRequesting = false
this.isOk = false
this.message = ""
this.failKeys = []
this.before = function () {
this.isRequesting = true
this.isOk = false
this.message = ""
this.failKeys = []
}
this.success = function (resp) {
this.isOk = true
let f = NotifyReloadSuccess("任务提交成功")
f()
}
this.fail = function (resp) {
this.message = resp.message
if (resp.data.failKeys != null) {
this.failKeys = resp.data.failKeys
}
}
this.done = function () {
this.isRequesting = false
}
});

View File

@@ -0,0 +1,71 @@
{$layout}
<first-menu>
<menu-item href="/servers/components/cache" code="index">列表</menu-item>
<span class="item">|</span>
<a href="" class="item" @click.prevent="createPolicy()">[创建]</a>
</first-menu>
<!-- 搜索 -->
<div class="margin"></div>
<form class="ui form" method="get" action="/servers/components/cache">
<div class="ui fields inline">
<div class="ui field">
<node-cluster-combo-box :v-cluster-id="clusterId"></node-cluster-combo-box>
</div>
<div class="ui field">
<input type="text" name="keyword" v-model="keyword" placeholder="策略名称..."/>
</div>
<div class="ui field">
<select class="ui dropdown" name="storageType" v-model="storageType">
<option value="">[存储类型]</option>
<option v-for="storageType in storageTypes" :value="storageType.type">{{storageType.name}}</option>
</select>
</div>
<div class="ui field">
<button type="submit" class="ui button">搜索</button>
&nbsp;
<a :href="Tea.url('.')" v-if="keyword.length > 0 || clusterId > 0 || storageType.length > 0">[清除条件]</a>
</div>
</div>
</form>
<!-- 列表 -->
<p class="comment" v-if="cachePolicies == null || cachePolicies.length == 0">暂时还没有缓存策略。</p>
<table class="ui table selectable celled" v-if="cachePolicies != null && cachePolicies.length > 0">
<thead>
<tr>
<th>策略名称</th>
<th>策略类型</th>
<th>容量</th>
<th class="center">集群数</th>
<th class="center">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="(policy, index) in cachePolicies">
<td>
<a :href="'/servers/components/cache/policy?cachePolicyId=' + policy.id"><keyword :v-word="keyword">{{policy.name}}</keyword>
&nbsp;
<i class="icon disk small blue" v-if="policy.type == 'file'" title="硬盘"></i>
</a>
</td>
<td>{{infos[index].typeName}} <span class="small">{{policy.type}}</span></td>
<td>
<span v-if="policy.capacity != null && policy.capacity.count > 0">
{{policy.capacity.count}}{{policy.capacity.unit.toUpperCase().replace(/(.)B/, "$1iB")}}
<span v-if="policy.type == 'file' && policy.options.memoryPolicy != null && policy.options.memoryPolicy.capacity != null && policy.options.memoryPolicy.capacity.count > 0" class="small grey" title="内存容量">
(内存:{{policy.options.memoryPolicy.capacity.count}}{{policy.options.memoryPolicy.capacity.unit.toUpperCase().replace(/(.)B/, "$1iB")}}
</span>
</span>
<span v-else class="disabled">不限</span>
</td>
<td class="center">{{infos[index].countClusters}}</td>
<td class="center"><label-on :v-is-on="policy.isOn"></label-on></td>
<td>
<a :href="'/servers/components/cache/policy?cachePolicyId=' + policy.id">详情</a> &nbsp; <a href="" @click.prevent="deletePolicy(policy.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,25 @@
Tea.context(function () {
// 创建策略
this.createPolicy = function () {
teaweb.popup("/servers/components/cache/createPopup", {
height: "27em",
callback: function () {
teaweb.success("保存成功", function () {
window.location.reload()
})
}
})
}
// 删除策略
this.deletePolicy = function (policyId) {
let that = this
teaweb.confirm("确定要删除此缓存策略吗?", function () {
that.$post("/servers/components/cache/delete")
.params({
cachePolicyId: policyId
})
.refresh()
})
}
})

View File

@@ -0,0 +1,128 @@
{$layout}
{$template "policy_menu"}
<table class="ui table definition selectable">
<tr>
<td class="title">策略名称</td>
<td>{{cachePolicy.name}}</td>
</tr>
<tr>
<td>状态</td>
<td><label-on :v-is-on="cachePolicy.isOn"></label-on></td>
</tr>
<tr>
<td class="color-border">缓存类型</td>
<td>
{{typeName}}<span class="small">{{cachePolicy.type}}</span>
</td>
</tr>
<!-- 文件缓存选项 -->
<tbody v-if="cachePolicy.type == 'file'">
<tr>
<td class="color-border">缓存目录</td>
<td>
{{cachePolicy.options.dir}}
<p class="comment">存放文件缓存的目录,通常填写绝对路径。</p>
</td>
</tr>
<tr>
<td class="color-border">缓存硬盘最大用量</td>
<td>
<size-capacity-view :v-value="cachePolicy.capacity" :v-default-text="'不限'"></size-capacity-view>
<p class="comment">单个节点上缓存所在硬盘的最大用量如果为0表示没有限制。</p>
</td>
</tr>
<tr v-if="cachePolicy.options.memoryPolicy != null && cachePolicy.options.memoryPolicy.capacity != null && cachePolicy.options.memoryPolicy.capacity.count > 0">
<td class="color-border">内存最大容量</td>
<td>
<size-capacity-view :v-value="cachePolicy.options.memoryPolicy.capacity"></size-capacity-view>
<p class="comment">作为一级缓存的内存最大容量可以作为硬盘缓冲区和存储热点缓存内容如果为0表示不使用内存作为一级缓存。</p>
</td>
</tr>
<tr v-else>
<td class="color-border">内存最大容量</td>
<td>
<a :href="Tea.url('.update', {cachePolicyId:cachePolicy.id})"><span class="red">请设置一个内存容量作为缓冲区,以提升缓存写入性能。</span></a>
</td>
</tr>
<tr v-if="cachePolicy.options.openFileCache != null && cachePolicy.options.openFileCache.isOn && cachePolicy.options.openFileCache.max > 0">
<td class="color-border">缓存文件句柄缓存</td>
<td>{{cachePolicy.options.openFileCache.max}}</td>
</tr>
<tr v-if="cachePolicy.options.enableSendfile">
<td class="color-border">开启Sendfile</td>
<td><span v-if="cachePolicy.options.enableSendfile" class="green">Y</span><span v-else class="disabled">N</span></td>
</tr>
<tr v-if="cachePolicy.options.minFreeSize != null && cachePolicy.options.minFreeSize.count > 0">
<td class="color-border">缓存硬盘最小空余空间</td>
<td><size-capacity-view :v-value="cachePolicy.options.minFreeSize"></size-capacity-view></td>
</tr>
<tr v-if="cachePolicy.options.enableIncompletePartialContent">
<td class="color-border">允许读取不完整的Partial Content</td>
<td>
<span class="green">Y</span>
</td>
</tr>
<!--<tr v-if="cachePolicy.options.enableMMAP">
<td class="color-border">启用MMAP</td>
<td>
<span>Y</span>
</td>
</tr>-->
</tbody>
<tbody v-if="cachePolicy.type != 'file'">
<tr>
<td>缓存最大容量</td>
<td>
<size-capacity-view :v-value="cachePolicy.capacity" :v-default-text="'不限'"></size-capacity-view>
<p class="comment">允许缓存的最大容量如果为0表示没有限制。</p>
</td>
</tr>
</tbody>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>最大内容长度</td>
<td>
<size-capacity-view :v-value="cachePolicy.maxSize" :v-default-text="'不限'"></size-capacity-view>
<p class="comment">允许缓存的单个内容最大尺寸如果为0表示没有限制。</p>
</td>
</tr>
<tr>
<td>同步写入压缩缓存</td>
<td>
<span v-if="cachePolicy.syncCompressionCache" class="green">Y</span>
<span v-else class="disabled">N</span>
</td>
</tr>
<tr>
<td>预热超时时间</td>
<td><span v-if="fetchTimeoutString.length > 0">{{fetchTimeoutString}}</span><span v-else class="disabled">使用默认</span></td>
</tr>
<tr>
<td>描述</td>
<td>
<span v-if="cachePolicy.description.length > 0">{{cachePolicy.description}}</span>
<span v-else class="disabled">暂时还没有描述。</span>
</td>
</tr>
</tbody>
</table>
<!-- 默认缓存条件 -->
<h4>默认缓存条件</h4>
<http-cache-refs-box :v-cache-refs="cachePolicy.cacheRefs"></http-cache-refs-box>
<!-- 使用此策略的集群 -->
<h4>使用此策略的集群</h4>
<p class="comment" v-if="clusters.length == 0">暂时还没有集群使用此策略。</p>
<table class="ui table selectable" v-if="clusters.length > 0">
<tr v-for="cluster in clusters">
<td>{{cluster.name}}<link-icon :href="'/clusters/cluster?clusterId=' + cluster.id"></link-icon></td>
</tr>
</table>

View File

@@ -0,0 +1,40 @@
{$layout}
{$template "policy_menu"}
<p class="comment">可以在这里批量刷新一组URL。</p>
<form method="post" class="ui form" data-tea-action="$" data-tea-before="before" data-tea-success="success" data-tea-fail="fail" data-tea-done="done" data-tea-timeout="300">
<input type="hidden" name="cachePolicyId" :value="cachePolicyId"/>
<table class="ui table definition selectable">
<tr>
<td class="title">URL类型</td>
<td>
<radio name="keyType" :v-value="'key'" v-model="keyType">URL</radio> &nbsp;
<radio name="keyType" :v-value="'prefix'" v-model="keyType">目录</radio>
</td>
</tr>
<tr>
<td>
<span v-if="keyType == 'key'">URL</span>
<span v-if="keyType == 'prefix'">目录</span>
</td>
<td>
<textarea name="keys" rows="10" ref="keysBox"></textarea>
<p class="comment" v-if="keyType == 'key'">每行一个URL比如<code-label>https://example.com/hello/world.html</code-label></p>
<p class="comment" v-if="keyType == 'prefix'">每行一个URL目录比如<code-label>https://example.com/hello/</code-label>;如果只填写域名部分,表示清理全站,比如<code-label>https://example.com/</code-label></p>
</td>
</tr>
<tr>
<td>操作结果</td>
<td>
<div v-if="isRequesting">数据发送中...</div>
<span class="red" v-if="!isRequesting && !isOk && message.length > 0">失败:{{message}}</span>
<div v-if="!isRequesting && !isOk && failKeys.length > 0" class="fail-keys-box">
<div v-for="failKey in failKeys">
<span class="red">{{failKey.key}}: {{failKey.reason}}</span>
</div>
</div>
</td>
</tr>
</table>
<submit-btn v-if="!isRequesting">提交</submit-btn>
</form>

View File

@@ -0,0 +1,43 @@
Tea.context(function () {
this.isRequesting = false
this.isOk = false
this.message = ""
this.failKeys = []
this.$delay(function () {
this.$refs.keysBox.focus()
this.$watch("keyType", function () {
this.$refs.keysBox.focus()
})
})
this.before = function () {
this.isRequesting = true
this.isOk = false
this.message = ""
this.failKeys = []
}
this.success = function () {
this.isOk = true
let f = NotifyReloadSuccess("任务提交成功")
f()
}
this.fail = function (resp) {
this.message = resp.message
if (resp.data.failKeys != null) {
this.failKeys = resp.data.failKeys
}
}
this.done = function () {
this.isRequesting = false
}
/**
* 操作类型
*/
this.keyType = "key" // key | prefix
})

View File

@@ -0,0 +1,24 @@
{$layout "layout_popup"}
<h3>选择缓存策略</h3>
<table class="ui table selectable">
<thead>
<tr>
<th>策略名称</th>
<th style="width: 7em">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="cachePolicy in cachePolicies">
<td>{{cachePolicy.name}}</td>
<td>
<label-on :v-is-on="cachePolicy.isOn"></label-on>
</td>
<td>
<span v-if="cachePolicy.isOn">
<a href="" @click.prevent="selectPolicy(cachePolicy)">选择</a>
</span>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,11 @@
Tea.context(function () {
this.selectPolicy = function (cachePolicy) {
NotifyPopup({
code: 200,
data: {
cachePolicy: cachePolicy
},
message: ""
})
}
})

View File

@@ -0,0 +1,31 @@
{$layout}
{$template "policy_menu"}
<h3>选择集群</h3>
<select class="ui dropdown auto-width" v-model="clusterId">
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
</select>
<div class="ui divider"></div>
<h3>统计</h3>
<form method="post" data-tea-action="$" data-tea-before="before" data-tea-success="success" data-tea-fail="fail" data-tea-done="done">
<input type="hidden" name="cachePolicyId" :value="cachePolicyId"/>
<input type="hidden" name="clusterId" :value="clusterId"/>
<table class="ui table definition selectable">
<tr>
<td class="title">操作结果</td>
<td>
<div v-if="isRequesting">数据发送中...</div>
<span class="red" v-if="!isRequesting && !isOk && message.length > 0">失败:{{message}}</span>
<div v-if="!isRequesting && isOk">
<span v-if="results.length == 0" class="red">此集群下没有任何可用的节点。</span>
<div class="ui label tiny" v-for="one in results" :class="{green:one.isOk, red:!one.isOk}" style="margin-bottom: 0.5em">{{one.nodeName}}{{one.message}}</div>
</div>
</td>
</tr>
</table>
<submit-btn v-if="!isRequesting">提交</submit-btn>
</form>

View File

@@ -0,0 +1,34 @@
Tea.context(function () {
if (this.clusterId == null) {
if (this.clusters.length > 0) {
this.clusterId = this.clusters[0].id
} else {
this.clusterId = 0
}
}
this.isRequesting = false
this.isOk = false
this.message = ""
this.results = []
this.before = function () {
this.isRequesting = true
this.isOk = false
this.message = ""
this.results = []
}
this.success = function (resp) {
this.isOk = true
this.results = resp.data.results
}
this.fail = function (resp) {
this.message = resp.message
}
this.done = function () {
this.isRequesting = false
}
});

View File

@@ -0,0 +1,70 @@
{$layout}
{$template "policy_menu"}
<h3>选择集群</h3>
<select class="ui dropdown auto-width" v-model="clusterId">
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
</select>
<div class="ui divider"></div>
<h3>测试写入</h3>
<form method="post" class="ui form" data-tea-action=".testWrite" data-tea-before="beforeWrite" data-tea-done="doneWrite" data-tea-success="successWrite" data-tea-fail="failWrite">
<input type="hidden" name="clusterId" :value="clusterId"/>
<input type="hidden" name="cachePolicyId" :value="cachePolicyId"/>
<table class="ui table selectable definition">
<tr>
<td class="title">Key</td>
<td>
<input type="text" name="key" value="my-key"/>
</td>
</tr>
<tr>
<td>Value</td>
<td>
<textarea name="value" rows="3">my-value</textarea>
</td>
</tr>
<tr>
<td>操作结果</td>
<td>
<div v-if="isRequestingWrite">数据发送中...</div>
<span class="red" v-if="!isRequestingWrite && !writeOk && writeMessage.length > 0">失败:{{writeMessage}}</span>
<div v-if="!isRequestingWrite && writeOk">
<span v-if="writeResults.length == 0" class="red">此集群下没有任何可用的节点。</span>
<div class="ui label tiny" v-for="result in writeResults" :class="{green:result.isOk, red:!result.isOk}" style="margin-bottom:0.5em">节点{{result.nodeName}}{{result.message}}</div>
</div>
</td>
</tr>
</table>
<submit-btn v-if="!isRequestingWrite">提交</submit-btn>
</form>
<div class="ui divider"></div>
<h3>测试读取</h3>
<form method="post" class="ui form" data-tea-action=".testRead" data-tea-before="beforeRead" data-tea-done="doneRead" data-tea-success="successRead" data-tea-fail="failRead">
<input type="hidden" name="clusterId" :value="clusterId"/>
<input type="hidden" name="cachePolicyId" :value="cachePolicyId"/>
<table class="ui table selectable definition">
<tr>
<td class="title">Key</td>
<td>
<input type="text" name="key" value="my-key"/>
</td>
</tr>
<tr>
<td>操作结果</td>
<td>
<div v-if="isRequestingRead">数据发送中...</div>
<span class="red" v-if="!isRequestingRead && !readOk && readMessage.length > 0">失败:{{readMessage}}</span>
<div v-if="!isRequestingRead && readOk">
<span v-if="readResults.length == 0" class="red">此集群下没有任何可用的节点。</span>
<div class="ui label tiny" v-for="result in readResults" :class="{green:result.isOk, red:!result.isOk}" style="margin-bottom: 0.5em">节点{{result.nodeName}}{{result.message}}</div>
</div>
</td>
</tr>
</table>
<submit-btn v-if="!isRequestingRead">提交</submit-btn>
</form>

View File

@@ -0,0 +1,65 @@
Tea.context(function () {
if (this.clusterId == null) {
if (this.clusters.length > 0) {
this.clusterId = this.clusters[0].id
} else {
this.clusterId = 0
}
}
this.isRequestingWrite = false
this.writeOk = false
this.writeMessage = ""
this.writeIsAllOk = false
this.writeResults = []
this.beforeWrite = function () {
this.isRequestingWrite = true
this.writeOk = false
this.writeMessage = ""
this.writeResult = {}
}
this.failWrite = function (resp) {
this.writeOk = false
this.writeMessage = resp.message
}
this.successWrite = function (resp) {
this.writeOk = true
this.writeIsAllOk = resp.data.isAllOk
this.writeResults = resp.data.results
}
this.doneWrite = function () {
this.isRequestingWrite = false
}
this.isRequestingRead = false
this.readOk = false
this.readMessage = ""
this.readIsAllOk = false
this.readResults = []
this.beforeRead = function () {
this.isRequestingRead = true
this.readOk = false
this.readMessage = ""
this.readResult = {}
}
this.failRead = function (resp) {
this.readOk = false
this.readMessage = resp.message
};
this.successRead = function (resp) {
this.readOk = true;
this.readIsAllOk = resp.data.isAllOk
this.readResults = resp.data.results
}
this.doneRead = function () {
this.isRequestingRead = false
}
});

View File

@@ -0,0 +1,146 @@
{$layout}
{$template "policy_menu"}
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="cachePolicyId" :value="cachePolicy.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">策略名称 *</td>
<td><input type="text" name="name" maxlength="100" ref="focus" v-model="cachePolicy.name"/> </td>
</tr>
<tr>
<td class="color-border">缓存类型 *</td>
<td>
<select class="ui dropdown auto-width" name="type" v-model="policyType" @change="changePolicyType">
<option v-for="type in types" :value="type.type">{{type.name}}</option>
</select>
</td>
</tr>
<!-- 文件缓存选项 -->
<tbody v-if="policyType == 'file'">
<tr>
<td class="color-border">缓存目录 *</td>
<td>
<input type="text" name="fileDir" maxlength="500" v-model="cachePolicy.options.dir"/>
<p class="comment">存放文件缓存的目录,通常填写绝对路径。</p>
</td>
</tr>
<tr>
<td class="color-border">缓存硬盘最大用量</td>
<td>
<size-capacity-box :v-name="'capacityJSON'" :v-value="cachePolicy.capacity" :v-count="0" :v-unit="'gb'"></size-capacity-box>
<p class="comment">单个节点上缓存所在硬盘的最大用量超出此用量或硬盘接近用尽时将会自动尝试清理旧数据如果为0表示没有限制。</p>
</td>
</tr>
<tr v-if="cachePolicy.options.memoryPolicy != null && cachePolicy.options.memoryPolicy.capacity != null">
<td class="color-border">内存最大容量</td>
<td>
<size-capacity-box :v-name="'fileMemoryCapacityJSON'" :v-value="cachePolicy.options.memoryPolicy.capacity" :v-count="1" :v-unit="'gb'"></size-capacity-box>
<p class="comment">单个节点上作为一级缓存的内存最大容量可以作为硬盘缓冲区和存储热点缓存内容如果为0表示不使用内存作为一级缓存。</p>
</td>
</tr>
<tr v-if="cachePolicy.options.memoryPolicy == null || cachePolicy.options.memoryPolicy.capacity == null">
<td class="color-border">内存最大容量</td>
<td>
<size-capacity-box :v-name="'fileMemoryCapacityJSON'" :v-count="0" :v-unit="'gb'"></size-capacity-box>
<p class="comment">作为一级缓存的内存最大容量可以作为硬盘缓冲区和存储热点缓存内容如果为0表示不使用内存作为一级缓存。</p>
</td>
</tr>
</tbody>
<tbody v-if="policyType != 'file'">
<tr>
<td>缓存最大容量</td>
<td>
<size-capacity-box :v-name="'capacityJSON'" :v-value="cachePolicy.capacity" :v-count="0" :v-unit="'gb'"></size-capacity-box>
<p class="comment">单个节点上允许缓存的最大容量如果为0表示没有限制。</p>
</td>
</tr>
</tbody>
<tr>
<td>最大内容长度</td>
<td>
<size-capacity-box :v-value="cachePolicy.maxSize" :v-name="'maxSizeJSON'" :v-count="32" :v-unit="'mb'"></size-capacity-box>
<p class="comment">允许缓存的单个内容最大尺寸如果为0表示没有限制。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible && policyType == 'file'">
<tr>
<td class="color-border">缓存硬盘最小空余空间</td>
<td>
<size-capacity-box :v-name="'fileMinFreeSizeJSON'" :v-value="cachePolicy.options.minFreeSize" :key="'minFreeSizeJSON'"></size-capacity-box>
<p class="comment">缓存硬盘保留的最小空余空间如果为0表示使用默认目前默认保留5GiB</p>
</td>
</tr>
<tr>
<td class="color-border">缓存文件句柄缓存</td>
<td>
<input type="text" name="fileOpenFileCacheMax" v-model="fileOpenFileCacheMax" maxlength="6" value="0" style="width: 10em"/>
<p class="comment"><pro-warning-label></pro-warning-label>保持在内存中的缓存文件句柄的数量提升缓存文件打开速度同时也会占用系统更多的内存建议数量不超过缓存文件数量的百分之一0表示不启用。</p>
</td>
</tr>
<tr v-show="teaIsPlus">
<td class="color-border">开启Sendfile</td>
<td>
<checkbox name="fileEnableSendfile" v-model="cachePolicy.options.enableSendfile"></checkbox>
<p class="comment"><pro-warning-label></pro-warning-label><plus-label></plus-label>使用sendfile提升发送缓存文件的效率。</p>
</td>
</tr>
<tr>
<td class="color-border">允许读取不完整的Partial Content</td>
<td>
<checkbox name="enableIncompletePartialContent" v-model="cachePolicy.options.enableIncompletePartialContent"></checkbox>
<p class="comment">允许在有一部分内容缓存的情况下读取分区内容,剩余的部分将会自动回源读取。</p>
</td>
</tr>
<tr v-show="false">
<td class="color-border">启用MMAP</td>
<td>
<checkbox name="enableMMAP" v-model="cachePolicy.options.enableMMAP"></checkbox>
<p class="comment">选中后表示允许系统自动利用MMAP提升缓存读取性能。</p>
</td>
</tr>
</tbody>
<tbody v-show="moreOptionsVisible">
<tr>
<td>同步写入压缩缓存</td>
<td>
<checkbox name="syncCompressionCache" v-model="cachePolicy.syncCompressionCache"></checkbox>
<p class="comment">选中后在压缩设置开启的情况下在缓存源站内容的同时也会同步写入压缩缓存不选中表示在源站内容缓存后下一次调用才会缓存压缩内容防止同一时间内硬盘IO负载过高。</p>
</td>
</tr>
<tr>
<td>预热超时时间</td>
<td>
<time-duration-box :v-name="'fetchTimeoutJSON'" :v-value="cachePolicy.fetchTimeout" maxlength="6"></time-duration-box>
<p class="comment">预热读取源站的超时时间默认20分钟。</p>
</td>
</tr>
<tr>
<td>描述</td>
<td>
<textarea maxlength="200" name="description" rows="3" v-model="cachePolicy.description"></textarea>
</td>
</tr>
<tr>
<td>启用当前策略</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="cachePolicy.isOn"/>
<label></label>
</div>
</td>
</tr>
</tbody>
</table>
<h4>默认缓存条件</h4>
<http-cache-refs-config-box :v-cache-refs="cachePolicy.cacheRefs" :v-cache-policy-id="cachePolicy.id" :v-max-bytes="cachePolicy.maxSize"></http-cache-refs-config-box>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,27 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功 ")
this.policyType = this.cachePolicy.type
this.fileOpenFileCacheMax = 0
if (this.cachePolicy.type == "file" && this.cachePolicy.options.openFileCache != null && this.cachePolicy.options.openFileCache.isOn && this.cachePolicy.options.openFileCache.max > 0) {
this.fileOpenFileCacheMax = this.cachePolicy.options.openFileCache.max
}
this.changePolicyType = function () {
if (this.policyType == "file") {
let options = this.cachePolicy.options
if (options != null && typeof options == "object" && typeof options["dir"] === "undefined") {
options["enableMMAP"] = false
options["enableIncompletePartialContent"] = true
options["dir"] = "/opt/cache"
options["memoryPolicy"] = {
capacity: {
unit: "gb",
count: 2
}
}
}
}
}
})

View File

@@ -0,0 +1,6 @@
{$layout}
{$template "/left_menu_top"}
<div class="right-box without-tabbar">
<p class="ui message">此功能暂未开放敬请期待。</p>
</div>

View File

@@ -0,0 +1,14 @@
<second-menu>
<menu-item href="/servers/components/waf">列表</menu-item>
<span class="item">|</span>
<menu-item :href="'/servers/components/waf/policy?firewallPolicyId=' + firewallPolicyId" code="index">{{firewallPolicyName}}</menu-item>
<menu-item :href="'/servers/components/waf/groups?firewallPolicyId=' + firewallPolicyId + '&type=inbound'" code="inbound">入站规则({{countInboundGroups}})</menu-item>
<menu-item :href="'/servers/components/waf/groups?firewallPolicyId=' + firewallPolicyId + '&type=outbound'" code="outbound">出站规则({{countOutboundGroups}})</menu-item>
<menu-item :href="'/servers/components/waf/ipadmin?firewallPolicyId=' + firewallPolicyId" code="ipadmin">IP管理</menu-item>
<menu-item :href="'/servers/components/waf/log?firewallPolicyId=' + firewallPolicyId" code="log">拦截日志</menu-item>
<!-- TODO -->
<!--<menu-item :href="'/servers/components/waf/test?firewallPolicyId=' + firewallPolicyId" code="test">测试</menu-item>-->
<menu-item :href="'/servers/components/waf/import?firewallPolicyId=' + firewallPolicyId" code="import">导入</menu-item>
<menu-item :href="'/servers/components/waf/export?firewallPolicyId=' + firewallPolicyId" code="export">导出</menu-item>
<menu-item :href="'/servers/components/waf/update?firewallPolicyId=' + firewallPolicyId" code="update">修改</menu-item>
</second-menu>

View File

@@ -0,0 +1,46 @@
{$layout "layout_popup"}
<h3>添加分组</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="firewallPolicyId" :value="firewallPolicyId"/>
<input type="hidden" name="type" :value="type"/>
<table class="ui table definition selectable">
<tr>
<td class="title">分组名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus"/>
<p class="comment">给分组起一个容易识别的名称</p>
</td>
</tr>
<tr>
<td>代号</td>
<td>
<input type="text" name="code" maxlength="100"/>
<p class="comment">可选项,在导入时可以合并相同代号的分组。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<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,44 @@
{$layout "layout_popup"}
<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="name" maxlength="100" ref="focus"/>
<p class="comment">给策略起一个容易识别的名字。</p>
</td>
</tr>
<tr>
<td>启用预置的规则</td>
<td>
<div class="ui checkbox" v-for="group in groups" style="width:10em;margin-bottom:0.5em">
<input type="checkbox" name="groupCodes" :value="group.code" :id="'group-checkbox-' + group.code" v-model="group.isOn"/>
<label :for="'group-checkbox-' + group.code">{{group.name}}</label>
</div>
<p class="comment">可以启用一些我们预置的规则组。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>描述</td>
<td>
<textarea name="description" 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,234 @@
{$layout "layout_popup"}
<h3 v-if="!isUpdating">添加规则</h3>
<h3 v-if="isUpdating">修改规则</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="ruleId" :value="rule.id"/>
<input type="hidden" name="optionsJSON" v-if="checkpoint != null && checkpoint.options != null" :value="JSON.stringify(checkpoint.options)"/>
<table class="ui table definition selectable">
<tr>
<td class="title">参数 *</td>
<td>
<select name="prefix" class="ui dropdown auto-width" @change="changeCheckpoint()" v-model="rule.checkpointPrefix">
<option value="">[选择参数]</option>
<optgroup label="特殊参数"></optgroup>
<option v-for="cp in checkpoints" v-if="cp.isComposed" :value="cp.prefix">{{cp.name}} - [{{cp.prefix}}]</option>
<optgroup label="通用参数"></optgroup>
<option v-for="cp in checkpoints" v-if="!cp.isComposed" :value="cp.prefix">{{cp.name}} - [{{cp.prefix}}]</option>
</select>
<p class="comment" v-if="checkpoint != null"><span class="ui label tiny basic">${<em style="font-style: normal;">{{checkpoint.prefix}}</em>}</span>{{checkpoint.description}}</p>
</td>
</tr>
<!-- 参数名 -->
<tr v-if="checkpoint != null && checkpoint.hasParams">
<td>参数名</td>
<td>
<select name="param" v-model="rule.checkpointParam" class="ui dropdown auto-width" v-if="checkpoint.params != null">
<option v-for="o in checkpoint.params" :value="o.value">{{o.name}}</option>
</select>
<input type="text" name="param" maxlength="100" v-model="rule.checkpointParam" v-if="checkpoint.params == null"/>
</td>
</tr>
<!-- 组合规则的选项 -->
<tbody v-if="checkpoint != null && checkpoint.isComposed">
<tr>
<td colspan="2">配置选项</td>
</tr>
<tr>
<td colspan="2">
<!-- 通用header -->
<http-cond-general-header-length v-if="checkpoint.prefix == 'requestGeneralHeaderLength' || checkpoint.prefix == 'responseGeneralHeaderLength'" :v-checkpoint="checkpoint"></http-cond-general-header-length>
<!-- 防盗链 -->
<http-firewall-checkpoint-referer-block v-if="checkpoint.prefix == 'refererBlock'" :v-checkpoint="checkpoint"></http-firewall-checkpoint-referer-block>
<!-- CC -->
<http-firewall-checkpoint-cc v-if="checkpoint.prefix == 'cc2'" :v-checkpoint="checkpoint"></http-firewall-checkpoint-cc>
</td>
</tr>
</tbody>
<!-- 选项 -->
<tbody v-if="checkpoint != null && !checkpoint.isComposed && checkpoint.options != null && checkpoint.options.length > 0">
<tr v-for="option in checkpoint.options">
<td>{{option.name}}</td>
<td>
<div class="ui fields inline" v-if="option.type == 'field'">
<div class="ui field">
<input type="text" name="" :placeholder="option.placeholder" :maxlength="(option.maxLength > 0)?option.maxLength:1024" :size="option.size" v-model="option.value"/>
</div>
<div class="ui field">
{{option.rightLabel}}
</div>
</div>
<div class="ui fields inline" v-if="option.type == 'options'">
<div class="ui field">
<select class="ui dropdown" :style="'width:' + option.size + 'em'" name="" v-model="option.value">
<option v-for="opt in option.options" :value="opt.value">{{opt.name}}</option>
</select>
</div>
<div class="ui field">
{{option.rightLabel}}
</div>
</div>
<p class="comment" v-if="option.comment != null && option.comment.length > 0">{{option.comment}}</p>
</td>
</tr>
</tbody>
<tbody v-show="checkpoint != null && !checkpoint.isComposed">
<tr>
<td>操作符 *</td>
<td>
<select class="ui dropdown auto-width" name="operator" v-model="rule.operator" @change="changeOperator()">
<option v-for="op in operators" :value="op.code">{{op.name}}</option>
</select>
<p class="comment" v-if="operator != null" v-html="operator.description"></p>
</td>
</tr>
<tr v-show="operator != null && operator.dataType != 'none'">
<td>
<span v-if="operator != null && operator.dataType == 'regexp'">匹配正则表达式</span>
<span v-else-if="operator != null && operator.dataType == 'wildcard'">匹配单个通配符</span>
<span v-else-if="operator != null && operator.dataType == 'number'">对比数字</span>
<span v-else-if="operator != null && operator.dataType == 'string'">对比单个字符串</span>
<span v-else-if="operator != null && operator.dataType == 'strings'">对比一组字符串</span>
<span v-else-if="operator != null && operator.dataType == 'string|number'">对比单个字符串或数字</span>
<span v-else-if="operator != null && operator.dataType == 'version'">对比单个版本号</span>
<span v-else-if="operator != null && operator.dataType == 'versionRange'">对比版本号范围</span>
<span v-else-if="operator != null && operator.dataType == 'ip'">对比单个IP</span>
<span v-else-if="operator != null && operator.dataType == 'ips'">一组对比IP</span>
<span v-else>对比值</span>
</td>
<td>
<!-- 二进制数据 -->
<div v-if="rule.operator == 'contains binary'">
<textarea rows="3" maxlength="4096" name="value" v-model="rule.value"></textarea>
<p class="comment">将二进制进行Base64Encode后放在这里比如<code-label>Hello</code-label>对应<code-label>SGVsbG8=</code-label></p>
</div>
<div v-else-if="rule.operator == 'not contains binary'">
<textarea rows="3" maxlength="4096" name="value" v-model="rule.value"></textarea>
<p class="comment">将二进制进行Base64Encode后放在这里比如<code-label>Hello</code-label>对应<code-label>SGVsbG8=</code-label></p>
</div>
<!-- bool数据 -->
<div v-else-if="checkpoint != null && checkpoint.dataType == 'bool'">
<select name="value" class="ui selectable auto-width" v-model="rule.value" @change="changeRuleValue">
<option value="">[请选择]</option>
<option value="1">是(1)</option>
<option value="0">否(0)</option>
</select>
</div>
<!-- wildcard -->
<div v-else-if="operator != null && operator.dataType == 'wildcard'">
<textarea rows="3" maxlength="4096" name="value" v-model="rule.value" @input="changeRuleValue"></textarea>
<p class="comment">输入包含通配符的字符串,比如<code-label>Chrome/*</code-label><code-label>192.168.*.*</code-label></p>
</div>
<!-- number -->
<div v-else-if="operator != null && operator.dataType == 'number'">
<input type="number" name="value" v-model="rule.value" @input="changeRuleValue" style="width: 16em" maxlength="30" placeholder="对比数字" step="any"/>
<p class="comment">输入和参数对比的数字,比如<code-label>123</code-label><code-label>123.456</code-label></p>
</div>
<!-- string|number -->
<div v-else-if="operator != null && operator.dataType == 'string|number'">
<input type="text" name="value" v-model="rule.value" @input="changeRuleValue" style="width: 16em" maxlength="30" placeholder="对比字符串或数字"/>
<p class="comment">输入和参数对比的字符串或数字,比如<code-label>123</code-label><code-label>abc</code-label></p>
</div>
<!-- version -->
<div v-else-if="operator != null && operator.dataType == 'version'">
<input type="text" name="value" v-model="rule.value" @input="changeRuleValue" style="width: 10em" maxlength="100" placeholder="对比版本号"/>
<p class="comment">输入和参数对比的版本号,比如<code-label>1.2.7</code-label></p>
</div>
<!-- versionRange -->
<div v-else-if="operator != null && operator.dataType == 'versionRange'">
<input type="text" name="value" v-model="rule.value" @input="changeRuleValue" style="width: 10em" maxlength="100" placeholder="版本号范围"/>
<p class="comment">输入和参数对比的版本号范围,比如<code-label>1.2.7,1.3.7</code-label></p>
</div>
<!-- IP -->
<div v-else-if="operator != null && operator.dataType == 'ip'">
<input type="text" name="value" v-model="rule.value" @input="changeRuleValue" style="width: 16em" maxlength="100" placeholder="对比IP"/>
<p class="comment">输入和参数对比的IP比如<code-label>192.168.2.100</code-label></p>
</div>
<!-- 其余数据 -->
<div v-else>
<textarea rows="3" maxlength="4096" name="value" v-model="rule.value" @input="changeRuleValue"></textarea>
<p class="comment" v-if="operator != null && operator.dataType == 'strings'">每行一个数据。</p>
<p class="comment" v-if="operator != null && operator.dataType == 'regexp'">正则表达式中对于要匹配的内容中的特殊字符需要转义处理(即在字符前面加入反斜杠\),比如<code-label>.?*+()[]{}^$\</code-label>等符号要变成<code-label>\.\?\*\+\(\)\[\]\{\}\^\$\\</code-label></p>
</div>
<p class="comment" v-if="operator != null && operator.dataType == 'regexp' && rule.value.match(/\n/)"><span class="red">警告:发现你填写的正则表达式中包含了换行符,如果你的意图是每行都表示不同的选项,那么请使用竖杠(<code-label>|</code-label>)符号代替换行符,比如把<code-label>a换行b换行c换行</code-label>改成<code-label>a|b|c</code-label><a href="" @click.prevent="convertValueLine">[帮我转换]</a></span></p>
<!-- 特殊规则 -->
<div style="margin-top: 1em">
<!-- geo country name -->
<div v-if="checkpoint != null && checkpoint.prefix == 'geoCountryName'" >
<combo-box title="已添加" width="14em" data-url="/ui/countryOptions" data-key="countries" placeholder="点这里选择国家/地区" @change="selectGeoCountryName" ref="countryComboBox" key="combo-box-country"></combo-box>
</div>
<!-- geo province name -->
<div v-if="checkpoint != null && checkpoint.prefix == 'geoProvinceName'" >
<combo-box title="已添加" data-url="/ui/provinceOptions" data-key="provinces" placeholder="点这里选择省份名称" @change="selectGeoProvinceName" ref="provinceComboBox" key="combo-box-province"></combo-box>
</div>
<!-- geo city name -->
<div v-if="checkpoint != null && checkpoint.prefix == 'geoCityName'" >
<combo-box title="已添加" data-url="/ui/cityOptions" data-key="cities" placeholder="点这里选择城市名称" @change="selectGeoCityName" ref="cityComboBox" key="combo-box-city"></combo-box>
</div>
<!-- ISP Name -->
<div v-if="checkpoint != null && checkpoint.prefix == 'ispName'" >
<combo-box title="已添加" data-url="/ui/providerOptions" data-key="providers" placeholder="点这里选择ISP名称" @change="selectISPName" ref="ispComboBox" key="combo-box-isp"></combo-box>
</div>
</div>
</td>
</tr>
<tr v-if="operator != null && operator.dataType == 'regexp'">
<td>正则表达式测试</td>
<td>
<a href="" v-if="!regexpTestIsOn" @click.prevent="changeRegexpTestIsOn">[输入测试字符串]</a>
<div v-if="regexpTestIsOn">
<textarea placeholder="输入要测试的内容" rows="3" v-model="regexpTestBody" ref="regexpTestBody" @input="changeRegexpTestBody"></textarea>
<p class="comment">
<span v-if="regexpTestResult.isOk" class="green">{{regexpTestResult.message}}</span>
<span v-if="!regexpTestResult.isOk" class="red">{{regexpTestResult.message}}</span>
&nbsp; <a href="" @click.prevent="changeRegexpTestIsOn">[结束测试]</a>
</p>
</div>
</td>
</tr>
<tr v-if="(checkpoint == null || checkpoint.dataType != 'bool') && operator != null && operator.case != 'none'">
<td>不区分大小写</td>
<td>
<div class="ui checkbox">
<input name="case" type="checkbox" value="1" v-model="rule.isCaseInsensitive" @change="changeCaseInsensitive"/>
<label></label>
</div>
<p class="comment">开启后忽略英文字母大小写</p>
</td>
</tr>
<!-- 参数过滤器 -->
<tr v-if="checkpoint != null && !checkpoint.isComposed">
<td>编解码</td>
<td>
<http-firewall-param-filters-box :v-filters="rule.paramFilters"></http-firewall-param-filters-box>
</td>
</tr>
<tr>
<td>备注</td>
<td><input type="text" name="description" v-model="rule.description"/></td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,231 @@
Tea.context(function () {
this.success = NotifyPopup
this.isUpdating = (window.parent.UPDATING_RULE != null)
this.rule = {
id: 0,
param: "",
paramFilters: [],
checkpointPrefix: "",
checkpointParam: "",
value: "",
isCaseInsensitive: false,
operator: "match",
checkpointOptions: null,
description: "",
isOn: true
}
if (window.parent.UPDATING_RULE != null) {
this.rule = window.parent.UPDATING_RULE
let param = this.rule.param.substring(this.rule.param.indexOf("${") + 2, this.rule.param.indexOf("}"))
let index = param.indexOf(".")
if (index > 0) {
this.rule.checkpointPrefix = param.substring(0, index)
this.rule.checkpointParam = param.substring(index + 1)
} else {
this.rule.checkpointPrefix = param
}
this.$delay(function () {
this.loadCheckpoint()
if (this.rule.checkpointOptions != null && this.checkpoint != null && this.checkpoint.options != null) {
let that = this
this.checkpoint.options.forEach(function (option) {
if (typeof (that.rule.checkpointOptions[option.code]) != "undefined") {
option.value = that.rule.checkpointOptions[option.code]
}
})
}
})
}
/**
* checkpoint
*/
this.checkpoint = null
this.loadCheckpoint = function () {
if (this.rule.checkpointPrefix.length == 0) {
this.checkpoint = null
return
}
let that = this
this.checkpoint = this.checkpoints.$find(function (k, v) {
return v.prefix == that.rule.checkpointPrefix
})
}
this.changeCheckpoint = function () {
if (this.rule.checkpointPrefix.length == 0) {
this.checkpoint = null
return
}
let that = this
this.checkpoint = this.checkpoints.$find(function (k, v) {
return v.prefix == that.rule.checkpointPrefix
})
if (this.checkpoint == null) {
return
}
switch (this.checkpoint.dataType) {
case "bool":
this.rule.operator = "eq"
break
case "number":
this.rule.operator = "eq"
break
default:
this.rule.operator = "match"
}
}
/**
* operator
*/
this.changeOperator = function () {
let that = this;
this.operator = this.operators.$find(function (k, v) {
return v.code == that.rule.operator
})
if (this.operator == null) {
return
}
if (!this.isUpdating) {
this.rule.isCaseInsensitive = (this.operator.case == "yes")
}
};
this.changeOperator()
/**
* caseInsensitive
*/
this.changeCaseInsensitive = function () {
if (this.rule.operator == "match" || this.rule.operator == "not match") {
if (this.regexpTestIsOn) {
this.changeRegexpTestBody()
}
}
}
/**
* value
*/
this.changeRuleValue = function () {
if (this.rule.operator == "match" || this.rule.operator == "not match") {
if (this.regexpTestIsOn) {
this.changeRegexpTestBody()
}
} else {
this.regexpTestIsOn = false
this.regexpTestResult = {isOk: false, message: ""}
}
}
this.convertValueLine = function () {
let value = this.rule.value
if (value != null && value.length > 0) {
let lines = value.split(/\n/)
let resultLines = []
lines.forEach(function (line) {
line = line.trim()
if (line.length > 0) {
resultLines.push(line)
}
})
this.rule.value = resultLines.join("|")
}
}
/**
* 正则测试
*/
this.regexpTestIsOn = false
this.regexpTestBody = ""
this.regexpTestResult = {isOk: false, message: ""}
this.changeRegexpTestIsOn = function () {
this.regexpTestIsOn = !this.regexpTestIsOn
if (this.regexpTestIsOn) {
this.$delay(function () {
this.$refs.regexpTestBody.focus()
})
}
}
this.changeRegexpTestBody = function () {
this.$post(".testRegexp")
.params({
"regexp": this.rule.value,
"body": this.regexpTestBody,
"isCaseInsensitive": this.rule.isCaseInsensitive
})
.success(function (resp) {
this.regexpTestResult = resp.data.result
})
}
// isp
this.selectISPName = function (isp) {
if (isp == null) {
return
}
let ispName = isp.name
this.$refs.ispComboBox.clear()
if (this.rule.value.length == 0) {
this.rule.value = ispName
} else {
this.rule.value += "|" + ispName
}
}
// country
this.selectGeoCountryName = function (country) {
if (country == null) {
return
}
let countryName = country.name
this.$refs.countryComboBox.clear()
if (this.rule.value.length == 0) {
this.rule.value = countryName
} else {
this.rule.value += "|" + countryName
}
}
// province
this.selectGeoProvinceName = function (province) {
if (province == null) {
return
}
let provinceName = province.name
this.$refs.provinceComboBox.clear()
if (this.rule.value.length == 0) {
this.rule.value = provinceName
} else {
this.rule.value += "|" + provinceName
}
}
// city
this.selectGeoCityName = function (city) {
if (city == null) {
return
}
let cityName = city.name
this.$refs.cityComboBox.clear()
if (this.rule.value.length == 0) {
this.rule.value = cityName
} else {
this.rule.value += "|" + cityName
}
}
})

View File

@@ -0,0 +1,69 @@
{$layout "layout_popup"}
<h3>添加规则集</h3>
<p class="ui message" v-if="isGlobalPolicy">当前设置的规则集为WAF策略的规则集将会应用于对应集群已经启用WAF的网站如果你只是想设置某个网站相关的规则集请到对应网站WAF功能中设置。</p>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="groupId" :value="groupId"/>
<input type="hidden" name="formType" :value="useCode ? 'code' : 'normal'"/>
<table class="ui table definition selectable">
<tr>
<td class="title">规则集名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus"/>
<p class="comment">可以用来描述当前规则集用途。<a href="" @click.prevent="switchToCode"><span v-if="!useCode">[使用代码]</span><span v-else>[切换到常规表单]</span></a></p>
</td>
</tr>
<!-- usual form -->
<tbody v-show="!useCode">
<tr>
<td>规则 *</td>
<td>
<http-firewall-rules-box :v-rules="rules" :v-type="type"></http-firewall-rules-box>
</td>
</tr>
<tr v-show="rules.length > 1">
<td>规则之间的关系 *</td>
<td>
<select class="ui dropdown" name="connector" style="width:10em" @change="changeConnector()" v-model="selectedConnector">
<option v-for="connector in connectors" :value="connector.value">{{connector.name}}</option>
</select>
<p class="comment">{{selectedConnectorDescription}}</p>
</td>
</tr>
<tr>
<td>执行动作 *</td>
<td>
<http-firewall-actions-box :v-actions="actions" :v-firewall-policy="firewallPolicy" :v-group-type="type"></http-firewall-actions-box>
</td>
</tr>
<tr>
<td>允许局域网IP</td>
<td>
<checkbox name="ignoreLocal"></checkbox>
<p class="comment">选中后表示如果请求来自局域网IP则直接跳过当前规则集。</p>
</td>
</tr>
<tr>
<td>允许搜索引擎</td>
<td>
<checkbox name="ignoreSearchEngine"></checkbox>
<p class="comment">选中后表示如果请求来自常见搜索引擎的IP则直接跳过当前规则集。</p>
</td>
</tr>
</tbody>
<!-- code form -->
<tbody v-show="useCode">
<tr>
<td>规则集代码 *</td>
<td>
<textarea name="code" ref="codeInput" placeholder="{ ... 规则集代码 ... }" rows="20"></textarea>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,55 @@
Tea.context(function () {
this.success = NotifyPopup
// rules
this.rules = []
// connector
this.selectedConnector = this.connectors[1].value
this.selectedConnectorDescription = ""
this.changeConnector = function () {
let that = this
this.selectedConnectorDescription = this.connectors.$find(function (k, v) {
return v.value == that.selectedConnector
}).description
}
this.changeConnector()
// action
this.action = "block"
// action:go_group
this.actionGroupId = 0
// action:go_set
this.actionSetId = 0
this.groupSets = function (groupId) {
let group = null
this.firewallPolicy.inbound.groups.forEach(function (v) {
if (v.id == groupId) {
group = v
}
})
this.firewallPolicy.outbound.groups.forEach(function (v) {
if (v.id == groupId) {
group = v
}
})
if (group == null) {
return []
}
return group.sets
}
// 使用代码
this.useCode = false
this.switchToCode = function () {
this.useCode = !this.useCode
if (this.useCode) {
this.$delay(function () {
this.$refs.codeInput.focus()
})
}
}
})

View File

@@ -0,0 +1,7 @@
.groups-box .group-box {
float: left;
width: 11em;
margin-top: 0.1em;
margin-bottom: 0.6em;
}
/*# sourceMappingURL=export.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["export.less"],"names":[],"mappings":"AAAA,WACC;EACC,WAAA;EACA,WAAA;EACA,iBAAA;EACA,oBAAA","file":"export.css"}

View File

@@ -0,0 +1,53 @@
{$layout}
{$template "waf_menu"}
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="firewallPolicyId" :value="firewallPolicyId"/>
<table class="ui table definition selectable">
<tr>
<td class="title">启用的入站规则</td>
<td>
<span v-if="enabledInboundGroups.length == 0" class="disabled">暂时还没有入站规则。</span>
<div class="groups-box" v-show="enabledInboundGroups.length > 0">
<div v-for="g in enabledInboundGroups" class="group-box">
<checkbox name="inboundGroupIds" :value="true" :v-value="g.id">{{g.name}}</checkbox>
</div>
</div>
</td>
</tr>
<tr v-if="disabledInboundGroups.length > 0">
<td class="title">停用的入站规则</td>
<td>
<div class="groups-box" v-show="disabledInboundGroups.length > 0">
<div v-for="g in disabledInboundGroups" class="group-box">
<checkbox name="inboundGroupIds" :value="false" :v-value="g.id">{{g.name}} &nbsp;<sup><span class="red">停用</span></sup></checkbox>
</div>
</div>
</td>
</tr>
<tr>
<td>启用的出站规则</td>
<td>
<span v-if="enabledOutboundGroups.length == 0" class="disabled">暂时还没有出站规则。</span>
<div class="groups-box" v-show="enabledOutboundGroups.length > 0">
<div v-for="g in enabledOutboundGroups" class="group-box">
<checkbox name="outboundGroupIds" :value="true" :v-value="g.id">{{g.name}}</checkbox>
</div>
</div>
</td>
</tr>
<tr v-if="disabledOutboundGroups.length > 0">
<td>停用的出站规则</td>
<td>
<div class="groups-box" v-show="disabledOutboundGroups.length > 0">
<div v-for="g in disabledOutboundGroups" class="group-box">
<checkbox name="outboundGroupIds" :value="false" :v-value="g.id">{{g.name}} &nbsp;<sup><span class="red">停用</span></sup></checkbox>
</div>
</div>
</td>
</tr>
</table>
<submit-btn>导出</submit-btn>
</form>

View File

@@ -0,0 +1,5 @@
Tea.context(function () {
this.success = function (resp) {
window.location = "/servers/components/waf/exportDownload?key=" + resp.data.key + "&policyId=" + resp.data.id
}
})

View File

@@ -0,0 +1,8 @@
.groups-box {
.group-box {
float: left;
width: 11em;
margin-top: 0.1em;
margin-bottom: 0.6em;
}
}

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