Initial commit (code only without large binaries)

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

View File

@@ -0,0 +1,26 @@
.grid {
margin-top: 2em !important;
margin-left: 2em !important;
}
.grid .column {
margin-bottom: 2em;
border-right: 1px #eee solid;
}
.grid .column div.value {
margin-top: 1.5em;
}
.grid .column div.value span {
font-size: 2em;
margin-right: 0.2em;
}
.grid .column.no-border {
border-right: 0;
}
.chart-box {
height: 20em;
}
h4 .color-span {
font-size: 0.6em;
padding: 2px 4px;
}
/*# sourceMappingURL=index.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,0BAAA;EACA,2BAAA;;AAFD,KAIC;EACC,kBAAA;EACA,4BAAA;;AANF,KAIC,QAIC,IAAG;EACF,iBAAA;;AATH,KAIC,QAIC,IAAG,MAGF;EACC,cAAA;EACA,mBAAA;;AAbJ,KAkBC,QAAO;EACN,eAAA;;AAIF;EACC,YAAA;;AAGD,EAAG;EACF,gBAAA;EACA,gBAAA","file":"index.css"}

View File

@@ -0,0 +1,60 @@
{$layout}
{$var "header"}
<!-- echart -->
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
{$end}
<div class="margin"></div>
<form method="get" class="ui form small" :action="path" autocomplete="off">
<first-menu style="margin-top: -1em">
<div class="item" v-if="servers.length > 0">
<select class="ui dropdown" name="serverId" v-model="serverId">
<option value="0">[选择域名]</option>
<option v-for="server in servers" :value="server.id">{{server.serverName}}</option>
</select>
</div>
<div class="item">
<button class="ui button small" type="submit">查找</button>
</div>
</first-menu>
</form>
<div class="ui three columns grid">
<div class="ui column">
<h4>今日拦截</h4>
<div class="value"><span>{{countDailyBlock}}</span></div>
</div>
<div class="ui column">
<h4>今日验证码验证</h4>
<div class="value"><span>{{countDailyCaptcha}}</span></div>
</div>
<div class="ui column no-border">
<h4>今日记录</h4>
<div class="value"><span>{{countDailyLog}}</span></div>
</div>
<div class="ui column">
<h4>本周拦截</h4>
<div class="value"><span>{{countWeeklyBlock}}</span></div>
</div>
<div class="ui column no-border">
<h4>本月拦截</h4>
<div class="value"><span>{{countMonthlyBlock}}</span></div>
</div>
</div>
<h4>近15日趋势
<span class="color-span" style="background: #9DD3E8">全部</span>
<span class="color-span" style="background: #F39494">拦截</span>
<span class="color-span" style="background: #FBD88A">验证码</span>
<span class="color-span" style="background: #879BD7">记录</span>
</h4>
<div class="chart-box" id="daily-chart"></div>
<h4>拦截类型分布</h4>
<div class="chart-box" id="group-chart"></div>

View File

@@ -0,0 +1,198 @@
Tea.context(function () {
this.$delay(function () {
let that = this
this.totalDailyStats = this.logDailyStats.map(function (v, k) {
return {
day: v.day,
count: that.logDailyStats[k].count + that.blockDailyStats[k].count + that.captchaDailyStats[k].count
}
})
let dailyUnit = this.processMaxUnit(this.totalDailyStats)
this.reloadLineChart("daily-chart", "规则分组", this.totalDailyStats, function (v) {
return v.day.substring(4, 6) + "-" + v.day.substring(6)
}, function (args) {
return that.logDailyStats[args.dataIndex].day.substring(4, 6) + "-" + that.logDailyStats[args.dataIndex].day.substring(6) + ": 拦截: "
+ teaweb.formatNumber(that.blockDailyStats[args.dataIndex].count) + ", 验证码: " + teaweb.formatNumber(that.captchaDailyStats[args.dataIndex].count) + ", 记录: " + teaweb.formatNumber(that.logDailyStats[args.dataIndex].count)
}, dailyUnit)
let groupUnit = this.processMaxUnit(this.groupStats)
let total = this.groupStats.$sum(function (k, v) {
return v.rawCount
})
this.reloadBarChart("group-chart", "规则分组", this.groupStats, function (v) {
return v.group.name
}, function (args) {
let percent = ""
if (total > 0) {
percent = ", 占比: " + (Math.round(that.groupStats[args.dataIndex].rawCount * 100 * 100 / total) / 100) + "%"
}
return that.groupStats[args.dataIndex].group.name + ": " + teaweb.formatNumber(that.groupStats[args.dataIndex].rawCount) + percent
}, groupUnit)
window.addEventListener("resize", function () {
that.resizeChart("daily-chart")
that.resizeChart("group-chart")
})
})
this.reloadLineChart = function (chartId, name, stats, xFunc, tooltipFunc, unit) {
let chartBox = document.getElementById(chartId)
if (chartBox == null) {
return
}
let that = this
let chart = echarts.init(chartBox)
let option = {
xAxis: {
data: stats.map(xFunc)
},
yAxis: {
axisLabel: {
formatter: function (value) {
return value + unit
}
}
},
tooltip: {
show: true,
trigger: "item",
formatter: tooltipFunc
},
grid: {
left: 40,
top: 10,
right: 20,
bottom: 20
},
series: [
{
name: name,
type: "line",
data: this.totalDailyStats.map(function (v, index) {
return that.totalDailyStats[index].count;
}),
areaStyle: {},
itemStyle: {
color: "#9DD3E8"
}
},
{
name: name,
type: "line",
data: this.logDailyStats.map(function (v) {
return v.count;
}),
itemStyle: {
color: "#879BD7"
}
},
{
name: name,
type: "line",
data: this.blockDailyStats.map(function (v) {
return v.count;
}),
itemStyle: {
color: "#F39494"
}
},
{
name: name,
type: "line",
data: this.captchaDailyStats.map(function (v) {
return v.count;
}),
itemStyle: {
color: "#FBD88A"
}
}
],
animation: true
}
chart.setOption(option)
chart.resize()
}
this.reloadBarChart = function (chartId, name, stats, xFunc, tooltipFunc, unit) {
let chartBox = document.getElementById(chartId)
if (chartBox == null) {
return
}
let chart = echarts.init(chartBox)
let option = {
xAxis: {
data: stats.map(xFunc)
},
yAxis: {
axisLabel: {
formatter: function (value) {
return value + unit
}
}
},
tooltip: {
show: true,
trigger: "item",
formatter: tooltipFunc
},
grid: {
left: 40,
top: 10,
right: 20,
bottom: 20
},
series: [
{
name: name,
type: "bar",
data: stats.map(function (v) {
return v.count;
}),
itemStyle: {
color: "#9DD3E8"
},
barWidth: "20em"
}
],
animation: true
}
chart.setOption(option)
chart.resize()
}
this.resizeChart = function (chartId) {
let chartBox = document.getElementById(chartId)
if (chartBox == null) {
return
}
let chart = echarts.init(chartBox)
chart.resize()
}
this.processMaxUnit = function (stats) {
let max = stats.$map(function (k, v) {
return v.count
}).$max()
let divider = 0
let unit = ""
if (max >= 1000 * 1000 * 1000) {
unit = "B"
divider = 1000 * 1000 * 1000
} else if (max >= 1000 * 1000) {
unit = "M"
divider = 1000 * 1000
} else if (max >= 1000) {
unit = "K"
divider = 1000
}
stats.forEach(function (v) {
v.rawCount = v.count
if (divider > 0) {
v.count /= divider
}
})
return unit
}
})

View File

@@ -0,0 +1,31 @@
.grid {
margin-top: 2em !important;
margin-left: 2em !important;
.column {
margin-bottom: 2em;
border-right: 1px #eee solid;
div.value {
margin-top: 1.5em;
span {
font-size: 2em;
margin-right: 0.2em;
}
}
}
.column.no-border {
border-right: 0;
}
}
.chart-box {
height: 20em;
}
h4 .color-span {
font-size: 0.6em;
padding: 2px 4px;
}

View File

@@ -0,0 +1,6 @@
<first-menu>
<menu-item :href="'/waf/iplists'" code="import">所有</menu-item>
<span class="disabled item">|</span>
<menu-item :href="'/waf/iplists/list?listId=' + list.id" code="list">"{{list.name}}"详情</menu-item>
<menu-item :href="'/waf/iplists/items?listId=' + list.id" code="item">IP({{list.countItems}})</menu-item>
</first-menu>

View File

@@ -0,0 +1,55 @@
{$layout}
<div class="margin"></div>
<form class="ui form" action="/waf/iplists" method="get">
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="keyword" placeholder="x.x.x.x" v-model="keyword"/>
</div>
<div class="ui field">
<select class="ui dropdown auto-width" name="listType" v-model="listType">
<option value="">[所有类型]</option>
<option value="black">黑名单</option>
<option value="white">白名单</option>
<option value="grey">灰名单</option>
</select>
</div>
<div class="ui field">
<select class="ui dropdown" name="eventLevel" v-model="eventLevel">
<option value="">[所有级别]</option>
<option v-for="level in eventLevels" :value="level.code">{{level.name}}</option>
</select>
</div>
<div class="ui field">
<div class="ui checkbox">
<input type="checkbox" name="globalOnly" value="1" v-model="globalOnly" id="global-only-checkbox"/>
<label for="global-only-checkbox">系统自动拦截</label>
</div>
</div>
<div class="ui field" v-if="countUnread > 0">
<div class="ui checkbox">
<input type="checkbox" name="unread" value="1" v-model="unread"/>
<label><span class="red">New</span></label>
</div>
</div>
<div class="ui field">
<button class="ui button" type="submit">搜索</button>
&nbsp;
<a href="/waf/iplists" v-if="keyword.length > 0 || globalOnly || unread || eventLevel.length > 0">[清除条件]</a>
</div>
<div class="ui field" v-if="countUnread > 0">
<span class="disabled">|</span>
</div>
<div class="ui field" v-if="countUnread > 0">
<a href="" @click.prevent="readAllItems" title="消除未读标记New">[全部设为已读]</a>
</div>
</div>
</form>
<div class="margin"></div>
<p class="comment" v-if="items.length == 0">暂时还没有IP。</p>
<ip-list-table v-if="items.length > 0" :v-items="items" @update-item="updateItem" @delete-item="deleteItem" :v-keyword="keyword" :v-show-search-button="true"></ip-list-table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,30 @@
Tea.context(function () {
this.updateItem = function (itemId) {
teaweb.popup(Tea.url(".updateIPPopup", {itemId: itemId}), {
height: "30em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteItem = function (itemId) {
let that = this
teaweb.confirm("确定要删除这个IP吗", function () {
that.$post(".deleteIP")
.params({
"itemId": itemId
})
.refresh()
})
}
this.readAllItems = function () {
this.$post(".readAll")
.success(function () {
teaweb.reload()
})
}
})

View File

@@ -0,0 +1,35 @@
{$layout}
{$template "list_menu"}
<!--<second-menu>
<menu-item @click.prevent="createIP">[创建IP]</menu-item>
</second-menu>-->
<div class="margin"></div>
<form class="ui form" action="/waf/iplists/items">
<input type="hidden" name="listId" :value="list.id"/>
<div class="ui fields inline">
<div class="ui field">
<input type="text" size="20" placeholder="x.x.x.x" name="keyword" v-model="keyword"/>
</div>
<div class="ui field">
<select class="ui dropdown" name="eventLevel" v-model="eventLevel">
<option value="">[级别]</option>
<option v-for="level in eventLevels" :value="level.code">{{level.name}}</option>
</select>
</div>
<div class="ui field">
<button class="ui button" type="submit">搜索</button>
&nbsp;
<a :href="'/waf/iplists/items?listId=' + list.id" v-if="keyword.length > 0 || eventLevel.length > 0">[清除条件]</a>
</div>
</div>
<div class="margin"></div>
</form>
<p class="comment" v-if="items.length == 0">暂时还没有IP。</p>
<ip-list-table v-if="items.length > 0" :v-items="items" @update-item="updateItem" @delete-item="deleteItem" :v-keyword="keyword"></ip-list-table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,37 @@
Tea.context(function () {
this.updateItem = function (itemId) {
teaweb.popup(Tea.url(".updateIPPopup", {itemId: itemId}), {
height: "30em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteItem = function (itemId) {
let that = this
teaweb.confirm("确定要删除这个IP吗", function () {
that.$post(".deleteIP")
.params({
"itemId": itemId
})
.refresh()
})
}
/**
* 添加IP名单菜单
*/
this.createIP = function () {
teaweb.popup(Tea.url(".createIPPopup", {listId: this.list.id}), {
height: "30em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
})

View File

@@ -0,0 +1,31 @@
{$layout}
{$template "list_menu"}
<table class="ui table definition selectable">
<tr>
<td class="title">名称</td>
<td>
{{list.name}}
</td>
</tr>
<tr>
<td>类型</td>
<td>
{{list.typeName}}
</td>
</tr>
<tr>
<td>全局有效</td>
<td>
<span v-if="list.isGlobal" class="green">Y</span>
<span v-else class="disabled">N</span>
</td>
</tr>
<tr>
<td>备注</td>
<td>
<span v-if="list.description.length > 0">{{list.description}}</span>
<span v-else class="disabled">-</span>
</td>
</tr>
</table>

View File

@@ -0,0 +1,49 @@
{$layout}
{$var "header"}
<!-- datepicker -->
<script type="text/javascript" src="/js/moment.min.js"></script>
<script type="text/javascript" src="/js/pikaday.js"></script>
<link rel="stylesheet" href="/js/pikaday.css"/>
<link rel="stylesheet" href="/js/pikaday.theme.css"/>
<link rel="stylesheet" href="/js/pikaday.triangle.css"/>
{$end}
<div class="margin"></div>
<form method="get" class="ui form small" :action="path" autocomplete="off">
<first-menu style="margin-top: -1em">
<div class="item" v-if="servers.length > 0">
<select class="ui dropdown" name="serverId" v-model="serverId">
<option value="0">[选择域名]</option>
<option v-for="server in servers" :value="server.id">{{server.serverName}}</option>
</select>
</div>
<div class="item right">
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="day" maxlength="10" placeholder="选择日期" style="width:7.8em" id="day-input" v-model="day"/>
</div>
<div class="ui field">
<button class="ui button small" type="submit">查找</button>
</div>
</div>
</div>
</first-menu>
</form>
<p class="comment" v-if="accessLogs.length == 0">暂时还没有日志。</p>
<table class="ui table selectable" v-if="accessLogs.length > 0">
<!-- 这里之所以需要添加 :key是因为要不然不会刷新显示 -->
<tr v-for="accessLog in accessLogs" :key="accessLog.requestId">
<td><http-access-log-box :v-access-log="accessLog"></http-access-log-box></td>
</tr>
</table>
<div v-if="accessLogs.length > 0">
<a :href="path + '?requestId=' + lastRequestId + '&day=' + day + '&groupId=' + groupId + '&serverId=' + serverId" v-if="hasPrev">上一页</a>
<span v-else class="disabled">上一页</span>
<span class="disabled">&nbsp; | &nbsp;</span>
<a :href="path + '?requestId=' + nextRequestId + '&day=' + day + '&groupId=' + groupId + '&serverId=' + serverId" v-if="hasMore">下一页</a>
<span v-else class="disabled">下一页</span>
</div>

View File

@@ -0,0 +1,17 @@
Tea.context(function () {
this.$delay(function () {
let that = this
teaweb.datepicker("day-input", function (day) {
that.day = day
})
})
let that = this
this.accessLogs.forEach(function (accessLog) {
if (typeof (that.regions[accessLog.remoteAddr]) == "string") {
accessLog.region = that.regions[accessLog.remoteAddr]
} else {
accessLog.region = ""
}
})
})