对服务增加基础的数据统计

This commit is contained in:
GoEdgeLab
2021-01-25 16:40:49 +08:00
parent a9ea18ef3f
commit ddf5493575
27 changed files with 1047 additions and 272 deletions

View File

@@ -5,7 +5,7 @@ Vue.component("http-stat-config-box", {
if (stat == null) {
stat = {
isPrior: false,
isOn: true
isOn: false
}
}
return {

View File

@@ -14,7 +14,7 @@ Vue.component("http-websocket-box", {
if (websocketConfig == null) {
websocketConfig = {
id: 0,
isOn: true,
isOn: false,
handshakeTimeout: {
count: 30,
unit: "second"

View File

@@ -1,261 +1,263 @@
window.teaweb = {
set: function (key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
get: function (key) {
var item = localStorage.getItem(key);
if (item == null || item.length == 0) {
return null;
}
set: function (key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
get: function (key) {
var item = localStorage.getItem(key);
if (item == null || item.length == 0) {
return null;
}
return JSON.parse(item);
},
getString: function (key) {
var value = this.get(key);
if (typeof (value) == "string") {
return value;
}
return "";
},
getBool: function (key) {
return Boolean(this.get(key));
},
remove: function (key) {
localStorage.removeItem(key)
},
match: function (source, keyword) {
if (source == null) {
return false;
}
if (keyword == null) {
return true;
}
source = source.trim();
keyword = keyword.trim();
if (keyword.length == 0) {
return true;
}
if (source.length == 0) {
return false;
}
var pieces = keyword.split(/\s+/);
for (var i = 0; i < pieces.length; i++) {
var pattern = pieces[i];
pattern = pattern.replace(/(\+|\*|\?|[|]|{|}|\||\\|\(|\)|\.)/g, "\\$1");
var reg = new RegExp(pattern, "i");
if (!reg.test(source)) {
return false;
}
}
return true;
},
return JSON.parse(item);
},
getString: function (key) {
var value = this.get(key);
if (typeof (value) == "string") {
return value;
}
return "";
},
getBool: function (key) {
return Boolean(this.get(key));
},
remove: function (key) {
localStorage.removeItem(key)
},
match: function (source, keyword) {
if (source == null) {
return false;
}
if (keyword == null) {
return true;
}
source = source.trim();
keyword = keyword.trim();
if (keyword.length == 0) {
return true;
}
if (source.length == 0) {
return false;
}
var pieces = keyword.split(/\s+/);
for (var i = 0; i < pieces.length; i++) {
var pattern = pieces[i];
pattern = pattern.replace(/(\+|\*|\?|[|]|{|}|\||\\|\(|\)|\.)/g, "\\$1");
var reg = new RegExp(pattern, "i");
if (!reg.test(source)) {
return false;
}
}
return true;
},
datepicker: function (element, callback) {
if (typeof (element) == "string") {
element = document.getElementById(element);
}
var year = new Date().getFullYear();
var picker = new Pikaday({
field: element,
firstDay: 1,
minDate: new Date(year - 1, 0, 1),
maxDate: new Date(year + 10, 11, 31),
yearRange: [year - 1, year + 10],
format: "YYYY-MM-DD",
i18n: {
previousMonth: '上月',
nextMonth: '下月',
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
weekdays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
weekdaysShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
},
theme: 'triangle-theme',
onSelect: function () {
if (typeof (callback) == "function") {
callback.call(Tea.Vue, picker.toString());
}
}
});
},
datepicker: function (element, callback) {
if (typeof (element) == "string") {
element = document.getElementById(element);
}
var year = new Date().getFullYear();
var picker = new Pikaday({
field: element,
firstDay: 1,
minDate: new Date(year - 1, 0, 1),
maxDate: new Date(year + 10, 11, 31),
yearRange: [year - 1, year + 10],
format: "YYYY-MM-DD",
i18n: {
previousMonth: '上月',
nextMonth: '下月',
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
weekdays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
weekdaysShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
},
theme: 'triangle-theme',
onSelect: function () {
if (typeof (callback) == "function") {
callback.call(Tea.Vue, picker.toString());
}
}
});
},
formatBytes: function (bytes) {
bytes = Math.ceil(bytes);
if (bytes < 1024) {
return bytes + " bytes";
}
if (bytes < 1024 * 1024) {
return (Math.ceil(bytes * 100 / 1024) / 100) + " k";
}
return (Math.ceil(bytes * 100 / 1024 / 1024) / 100) + " m";
},
formatBytes: function (bytes) {
bytes = Math.ceil(bytes);
if (bytes < 1024) {
return bytes + " bytes";
}
if (bytes < 1024 * 1024) {
return (Math.ceil(bytes * 100 / 1024) / 100) + " k";
}
return (Math.ceil(bytes * 100 / 1024 / 1024) / 100) + " m";
},
formatNumber: function (x) {
return x.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ", ");
},
popup: function (url, options) {
if (options == null) {
options = {};
}
var width = "40em";
var height = "20em";
window.POPUP_CALLBACK = function () {
Swal.close();
};
popup: function (url, options) {
if (options == null) {
options = {};
}
var width = "40em";
var height = "20em";
window.POPUP_CALLBACK = function () {
Swal.close();
};
if (options["width"] != null) {
width = options["width"];
}
if (options["height"] != null) {
height = options["height"];
}
if (typeof (options["callback"]) == "function") {
window.POPUP_CALLBACK = function () {
Swal.close();
options["callback"].apply(Tea.Vue, arguments);
};
}
if (options["width"] != null) {
width = options["width"];
}
if (options["height"] != null) {
height = options["height"];
}
if (typeof (options["callback"]) == "function") {
window.POPUP_CALLBACK = function () {
Swal.close();
options["callback"].apply(Tea.Vue, arguments);
};
}
Swal.fire({
html: '<iframe src="' + url + '#popup-' + width + '" style="border:0; width: 100%; height:' + height + '"></iframe>',
width: width,
padding: "0.5em",
showConfirmButton: false,
showCloseButton: true,
focusConfirm: false,
onClose: function (popup) {
if (typeof (options["onClose"]) == "function") {
options["onClose"].apply(Tea.Vue, arguments)
}
}
});
},
popupFinish: function () {
if (window.POPUP_CALLBACK != null) {
window.POPUP_CALLBACK.apply(window, arguments);
}
},
popupTip: function (html) {
Swal.fire({
html: '<i class="icon question circle"></i><span style="line-height: 1.7">' + html + "</span>",
width: "30em",
padding: "5em",
showConfirmButton: false,
showCloseButton: true,
focusConfirm: false
});
},
isPopup: function () {
var hash = window.location.hash;
return hash != null && hash.startsWith("#popup");
},
Swal: function () {
return this.isPopup() ? window.parent.Swal : window.Swal;
},
success: function (message, callback) {
var width = "20em";
if (message.length > 30) {
width = "30em";
}
Swal.fire({
html: '<iframe src="' + url + '#popup-' + width + '" style="border:0; width: 100%; height:' + height + '"></iframe>',
width: width,
padding: "0.5em",
showConfirmButton: false,
showCloseButton: true,
focusConfirm: false,
onClose: function (popup) {
if (typeof (options["onClose"]) == "function") {
options["onClose"].apply(Tea.Vue, arguments)
}
}
});
},
popupFinish: function () {
if (window.POPUP_CALLBACK != null) {
window.POPUP_CALLBACK.apply(window, arguments);
}
},
popupTip: function (html) {
Swal.fire({
html: '<i class="icon question circle"></i><span style="line-height: 1.7">' + html + "</span>",
width: "30em",
padding: "5em",
showConfirmButton: false,
showCloseButton: true,
focusConfirm: false
});
},
isPopup: function () {
var hash = window.location.hash;
return hash != null && hash.startsWith("#popup");
},
Swal: function () {
return this.isPopup() ? window.parent.Swal : window.Swal;
},
success: function (message, callback) {
var width = "20em";
if (message.length > 30) {
width = "30em";
}
let config = {
confirmButtonText: "确定",
buttonsStyling: false,
icon: "success",
customClass: {
closeButton: "ui button",
cancelButton: "ui button",
confirmButton: "ui button primary"
},
width: width,
onAfterClose: function () {
if (typeof (callback) == "function") {
setTimeout(function () {
callback();
});
} else if (typeof (callback) == "string") {
window.location = callback
}
}
}
let config = {
confirmButtonText: "确定",
buttonsStyling: false,
icon: "success",
customClass: {
closeButton: "ui button",
cancelButton: "ui button",
confirmButton: "ui button primary"
},
width: width,
onAfterClose: function () {
if (typeof (callback) == "function") {
setTimeout(function () {
callback();
});
} else if (typeof (callback) == "string") {
window.location = callback
}
}
}
if (message.startsWith("html:")) {
config.html = message.substring(5)
} else {
config.text = message
}
if (message.startsWith("html:")) {
config.html = message.substring(5)
} else {
config.text = message
}
Swal.fire(config);
},
successToast: function (message, timeout) {
if (timeout == null) {
timeout = 2000
}
var width = "20em";
if (message.length > 30) {
width = "30em";
}
Swal.fire({
text: message,
icon: "success",
width: width,
timer: timeout,
showConfirmButton: false
});
},
warn: function (message, callback) {
var width = "20em";
if (message.length > 30) {
width = "30em";
}
Swal.fire({
text: message,
confirmButtonText: "确定",
buttonsStyling: false,
customClass: {
closeButton: "ui button",
cancelButton: "ui button",
confirmButton: "ui button primary"
},
icon: "warning",
width: width,
onAfterClose: function () {
if (typeof (callback) == "function") {
setTimeout(function () {
callback();
});
}
}
});
},
confirm: function (message, callback) {
let width = "20em";
if (message.length > 30) {
width = "30em";
}
let config = {
confirmButtonText: "确定",
cancelButtonText: "取消",
showCancelButton: true,
showCloseButton: false,
buttonsStyling: false,
customClass: {
closeButton: "ui button",
cancelButton: "ui button",
confirmButton: "ui button primary"
},
icon: "warning",
width: width,
preConfirm: function () {
if (typeof (callback) == "function") {
callback.call(Tea.Vue);
}
}
}
if (message.startsWith("html:")) {
config.html = message.substring(5)
} else {
config.text = message
}
Swal.fire(config);
},
reload: function () {
window.location.reload()
}
Swal.fire(config);
},
successToast: function (message, timeout) {
if (timeout == null) {
timeout = 2000
}
var width = "20em";
if (message.length > 30) {
width = "30em";
}
Swal.fire({
text: message,
icon: "success",
width: width,
timer: timeout,
showConfirmButton: false
});
},
warn: function (message, callback) {
var width = "20em";
if (message.length > 30) {
width = "30em";
}
Swal.fire({
text: message,
confirmButtonText: "确定",
buttonsStyling: false,
customClass: {
closeButton: "ui button",
cancelButton: "ui button",
confirmButton: "ui button primary"
},
icon: "warning",
width: width,
onAfterClose: function () {
if (typeof (callback) == "function") {
setTimeout(function () {
callback();
});
}
}
});
},
confirm: function (message, callback) {
let width = "20em";
if (message.length > 30) {
width = "30em";
}
let config = {
confirmButtonText: "确定",
cancelButtonText: "取消",
showCancelButton: true,
showCloseButton: false,
buttonsStyling: false,
customClass: {
closeButton: "ui button",
cancelButton: "ui button",
confirmButton: "ui button primary"
},
icon: "warning",
width: width,
preConfirm: function () {
if (typeof (callback) == "function") {
callback.call(Tea.Vue);
}
}
}
if (message.startsWith("html:")) {
config.html = message.substring(5)
} else {
config.text = message
}
Swal.fire(config);
},
reload: function () {
window.location.reload()
}
};

View File

@@ -1,6 +1,5 @@
{$layout}
{$var "header"}
<!-- echart -->
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>

View File

@@ -0,0 +1,4 @@
.chart-box {
height: 20em;
}
/*# sourceMappingURL=clients.css.map */

View File

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

View File

@@ -0,0 +1,21 @@
{$layout}
{$var "header"}
<!-- echart -->
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
{$end}
{$template "/left_menu"}
<div class="right-box">
{$ if eq .statIsOn false}
<p class="ui message">
要想查看统计数据,需要先开启统计功能,<a :href="'/servers/server/settings/stat?serverId=' + serverId">[点击这里]</a>修改配置。
</p>
{$else}
<h4>操作系统排行</h4>
<div class="chart-box" id="system-chart"></div>
<h4>浏览器排行</h4>
<div class="chart-box" id="browser-chart"></div>
{$end}
</div>

View File

@@ -0,0 +1,108 @@
Tea.context(function () {
this.$delay(function () {
let that = this
let systemUnit = this.processMaxUnit(this.systemStats)
this.reloadChart("system-chart", "操作系统", this.systemStats, function (v) {
return v.system.name
}, function (args) {
return that.systemStats[args.dataIndex].system.name + ": " + teaweb.formatNumber(that.systemStats[args.dataIndex].rawCount)
}, systemUnit)
window.addEventListener("resize", function () {
that.resizeChart("system-chart")
})
let browserUnit = this.processMaxUnit(this.browserStats)
this.reloadChart("browser-chart", "浏览器", this.browserStats, function (v) {
return v.browser.name
}, function (args) {
return that.browserStats[args.dataIndex].browser.name + ": " + teaweb.formatNumber(that.browserStats[args.dataIndex].rawCount)
}, browserUnit)
window.addEventListener("resize", function () {
that.resizeChart("system-chart")
that.resizeChart("browser-chart")
})
})
this.reloadChart = 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,3 @@
.chart-box {
height: 20em;
}

View File

@@ -0,0 +1,4 @@
.chart-box {
height: 20em;
}
/*# sourceMappingURL=index.css.map */

View File

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

View File

@@ -1,6 +1,30 @@
{$layout}
{$var "header"}
<!-- echart -->
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
{$end}
{$template "/left_menu"}
<div class="right-box">
<div class="ui message">此功能暂未开放,敬请期待。</div>
{$ if eq .statIsOn false}
<p class="ui message">
要想查看统计数据,需要先开启统计功能,<a :href="'/servers/server/settings/stat?serverId=' + serverId">[点击这里]</a>修改配置。
</p>
{$else}
<h4>地区排行</h4>
<div class="chart-box" id="country-chart">
</div>
<h4>省市排行</h4>
<div class="chart-box" id="province-chart">
</div>
<h4>城市排行</h4>
<div class="chart-box" id="city-chart">
</div>
{$end}
</div>

View File

@@ -0,0 +1,113 @@
Tea.context(function () {
this.$delay(function () {
let that = this
let countryUnit = this.processMaxUnit(this.countryStats)
this.reloadChart("country-chart", "地区", this.countryStats, function (v) {
return v.country.name
}, function (args) {
return that.countryStats[args.dataIndex].country.name + ": " + teaweb.formatNumber(that.countryStats[args.dataIndex].rawCount)
}, countryUnit)
let provinceUnit = this.processMaxUnit(this.provinceStats)
this.reloadChart("province-chart", "省市", this.provinceStats, function (v) {
return v.province.name
}, function (args) {
return that.provinceStats[args.dataIndex].country.name + ": " + that.provinceStats[args.dataIndex].province.name + " " + teaweb.formatNumber(that.provinceStats[args.dataIndex].rawCount)
}, provinceUnit)
let cityUnit = this.processMaxUnit(this.cityStats)
this.reloadChart("city-chart", "城市", this.cityStats, function (v) {
return v.city.name
}, function (args) {
return that.cityStats[args.dataIndex].country.name + ": " + that.cityStats[args.dataIndex].province.name + " " + that.cityStats[args.dataIndex].city.name + " " + teaweb.formatNumber(that.cityStats[args.dataIndex].rawCount)
}, cityUnit)
window.addEventListener("resize", function () {
that.resizeChart("country-chart")
that.resizeChart("province-chart")
that.resizeChart("city-chart")
})
})
this.reloadChart = 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,3 @@
.chart-box {
height: 20em;
}

View File

@@ -0,0 +1,4 @@
.chart-box {
height: 20em;
}
/*# sourceMappingURL=providers.css.map */

View File

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

View File

@@ -0,0 +1,18 @@
{$layout}
{$var "header"}
<!-- echart -->
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
{$end}
{$template "/left_menu"}
<div class="right-box">
{$ if eq .statIsOn false}
<p class="ui message">
要想查看统计数据,需要先开启统计功能,<a :href="'/servers/server/settings/stat?serverId=' + serverId">[点击这里]</a>修改配置。
</p>
{$else}
<h4>运营商排行</h4>
<div class="chart-box" id="provider-chart"></div>
{$end}
</div>

View File

@@ -0,0 +1,96 @@
Tea.context(function () {
this.$delay(function () {
let that = this
let providerUnit = this.processMaxUnit(this.providerStats)
this.reloadChart("provider-chart", "运营商", this.providerStats, function (v) {
return v.provider.name
}, function (args) {
return that.providerStats[args.dataIndex].provider.name + ": " + teaweb.formatNumber(that.providerStats[args.dataIndex].rawCount)
}, providerUnit)
window.addEventListener("resize", function () {
that.resizeChart("provider-chart")
})
})
this.reloadChart = 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,3 @@
.chart-box {
height: 20em;
}