mirror of
https://github.com/hi2shark/nazhua.git
synced 2026-01-18 18:20:44 +08:00
Compare commits
5 Commits
51197a1c05
...
2d331823b9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d331823b9 | ||
|
|
0a33582541 | ||
|
|
9aaa5b0cc3 | ||
|
|
eed7be4b1b | ||
|
|
1b20505ef2 |
@ -1,4 +1,4 @@
|
||||
FROM nginx:1.27.3
|
||||
FROM nginx:1.27-alpine
|
||||
|
||||
COPY ./dist /home/wwwroot/html
|
||||
COPY ./nginx-default.conf.template /etc/nginx/templates/default.conf.template
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nazhua",
|
||||
"version": "0.6.6",
|
||||
"version": "0.7.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@ -42,10 +42,9 @@ provide('currentTime', currentTime);
|
||||
*/
|
||||
function refreshTime() {
|
||||
currentTime.value = Date.now();
|
||||
setTimeout(() => {
|
||||
refreshTime();
|
||||
}, 1000);
|
||||
window.requestAnimationFrame(refreshTime);
|
||||
}
|
||||
refreshTime();
|
||||
|
||||
// 是否为Windows系统
|
||||
const isWindows = /windows|win32/i.test(navigator.userAgent);
|
||||
|
||||
@ -16,12 +16,18 @@
|
||||
|
||||
--world-map-point-color: #fff143;
|
||||
|
||||
--duration-color: #cbf1f5;
|
||||
--duration-color: #89c3eb;
|
||||
--transfer-color: #f9ed69;
|
||||
--transfer-in-color: var(--transfer-color);
|
||||
--transfer-out-color: #90f2ff;
|
||||
--net-speed-color: #90f2ff;
|
||||
--net-speed-in-color: #f5b199;
|
||||
--net-speed-out-color: #89c3eb;
|
||||
--conn-color: #90f2ff;
|
||||
--conn-tcp-color: #89c3eb;
|
||||
--conn-udp-color: #2ca9e1;
|
||||
--load-color: #90f2ff;
|
||||
--process-color: #f5b199;
|
||||
|
||||
--list-item-price-color: #eee;
|
||||
--list-item-buy-link-color: #ffc300;
|
||||
|
||||
@ -18,11 +18,13 @@ use([
|
||||
DataZoomComponent,
|
||||
]);
|
||||
|
||||
export default (
|
||||
dateList,
|
||||
valueList,
|
||||
mode = 'dark',
|
||||
) => {
|
||||
export default (options) => {
|
||||
const {
|
||||
dateList,
|
||||
valueList,
|
||||
mode = 'dark',
|
||||
connectNulls = true,
|
||||
} = options || {};
|
||||
const fontFamily = config.nazhua.disableSarasaTermSC === true ? undefined : 'Sarasa Term SC';
|
||||
const option = {
|
||||
darkMode: mode === 'dark',
|
||||
@ -36,7 +38,7 @@ export default (
|
||||
let res = `<p style="font-weight: bold; color: #ff6;">${time}</p>`;
|
||||
if (params.length < 10) {
|
||||
params.forEach((i) => {
|
||||
res += `${i.marker} ${i.seriesName}: ${i.value[1]}ms<br>`;
|
||||
res += i.value[1] ? `${i.marker} ${i.seriesName}: ${i.value[1]}ms<br>` : '';
|
||||
});
|
||||
} else {
|
||||
res += '<table>';
|
||||
@ -45,7 +47,9 @@ export default (
|
||||
if (index % 2 === 0) {
|
||||
res += '<tr>';
|
||||
}
|
||||
res += `<td style="padding: 0 4px;">${i.marker} ${i.seriesName}: ${i.value[1]}ms</td>`;
|
||||
res += i.value[1]
|
||||
? `<td style="padding: 0 4px;">${i.marker} ${i.seriesName}: ${i.value[1]}ms</td>`
|
||||
: '<td style="padding: 0 4px;"></td>';
|
||||
if (index % 2 === 1) {
|
||||
res += '</tr>';
|
||||
trEnd = true;
|
||||
@ -108,7 +112,7 @@ export default (
|
||||
...i,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
connectNulls: true,
|
||||
connectNulls,
|
||||
legendHoverLink: false,
|
||||
symbol: 'none',
|
||||
})),
|
||||
|
||||
@ -38,15 +38,20 @@ const props = defineProps({
|
||||
type: [Number, String],
|
||||
default: null,
|
||||
},
|
||||
connectNulls: {
|
||||
type: [Boolean, String],
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const chartRef = ref();
|
||||
const option = computed(() => {
|
||||
if (props.dateList && props.valueList) {
|
||||
return lineChart(
|
||||
props.dateList,
|
||||
props.valueList,
|
||||
);
|
||||
return lineChart({
|
||||
dateList: props.dateList,
|
||||
valueList: props.valueList,
|
||||
connectNulls: props.connectNulls,
|
||||
});
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
@ -118,10 +118,10 @@
|
||||
{{ cateItem.avg }}ms
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="cate-avg-ms"
|
||||
v-if="cateItem.over !== 0"
|
||||
class="cate-over-rate"
|
||||
>
|
||||
-ms
|
||||
{{ cateItem.over }}%
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@ -131,6 +131,7 @@
|
||||
:date-list="monitorChartData.dateList"
|
||||
:value-list="[monitorChartData.valueList[index]]"
|
||||
:size="240"
|
||||
:connect-nulls="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -168,12 +169,6 @@
|
||||
>
|
||||
{{ cateItem.avg }}ms
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="cate-avg-ms"
|
||||
>
|
||||
-ms
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</popover>
|
||||
@ -183,6 +178,7 @@
|
||||
<line-chart
|
||||
:date-list="monitorChartData.dateList"
|
||||
:value-list="monitorChartData.valueList"
|
||||
:connect-nulls="false"
|
||||
/>
|
||||
</template>
|
||||
</dot-dot-box>
|
||||
@ -263,8 +259,9 @@ const monitorChartType = computed(() => {
|
||||
return config.nazhua.monitorChartType;
|
||||
});
|
||||
|
||||
const now = ref(Date.now());
|
||||
const accpetShowTime = computed(() => now.value - (minute.value * 60 * 1000));
|
||||
// 服务器时间(后面来自接口)
|
||||
const nowServerTime = computed(() => store.state.serverTime || Date.now());
|
||||
const accpetShowTime = computed(() => (Math.floor(nowServerTime.value / 60000) - minute.value) * 60000);
|
||||
|
||||
const minuteActiveArrowStyle = computed(() => {
|
||||
const index = minutes.findIndex((i) => i.value === minute.value);
|
||||
@ -302,13 +299,25 @@ const monitorChartData = computed(() => {
|
||||
};
|
||||
}
|
||||
const showAvgDelay = [];
|
||||
const showCreateTime = i.created_at.filter((o, index) => {
|
||||
const showCreateTime = [];
|
||||
const accpeTimeMap = {};
|
||||
i.created_at.forEach((o, index) => {
|
||||
const status = o >= accpetShowTime.value;
|
||||
if (status) {
|
||||
showAvgDelay.push(i.avg_delay[index]);
|
||||
accpeTimeMap[o] = i.avg_delay[index];
|
||||
}
|
||||
return status;
|
||||
});
|
||||
const allMintues = Math.floor((Date.now() - accpetShowTime.value) / 60000);
|
||||
for (let j = 0; j < allMintues; j += 1) {
|
||||
const time = accpetShowTime.value + j * 60000;
|
||||
showCreateTime.push(time);
|
||||
const timeProp = accpeTimeMap[time];
|
||||
if (timeProp) {
|
||||
showAvgDelay.push(timeProp);
|
||||
} else {
|
||||
showAvgDelay.push(null);
|
||||
}
|
||||
}
|
||||
const {
|
||||
threshold,
|
||||
mean,
|
||||
@ -316,19 +325,21 @@ const monitorChartData = computed(() => {
|
||||
min,
|
||||
} = peakShaving.value ? getThreshold(showAvgDelay, 2) : {};
|
||||
showCreateTime.forEach((o, index) => {
|
||||
if (dateMap[o]) {
|
||||
if (Object.prototype.hasOwnProperty.call(dateMap, o)) {
|
||||
return;
|
||||
}
|
||||
const avgDelay = showAvgDelay[index];
|
||||
if (peakShaving.value) {
|
||||
if (avgDelay === 0) {
|
||||
dateMap[o] = null;
|
||||
return;
|
||||
}
|
||||
if (Math.abs(avgDelay - mean) > threshold && max / min > 2) {
|
||||
dateMap[o] = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
dateMap[o] = (avgDelay).toFixed(2) * 1;
|
||||
dateMap[o] = avgDelay ? (avgDelay).toFixed(2) * 1 : null;
|
||||
});
|
||||
});
|
||||
let dateList = [];
|
||||
@ -351,9 +362,9 @@ const monitorChartData = computed(() => {
|
||||
showCates.value[id] = true;
|
||||
}
|
||||
// 计算平均延迟和成功率
|
||||
const validAvgs = avgs.filter((a) => a[1] !== 0);
|
||||
const validAvgs = avgs.filter((a) => a[1] !== 0 && a[1] !== null);
|
||||
const avg = validAvgs.reduce((a, b) => a + b[1], 0) / validAvgs.length;
|
||||
const over = avgs.filter((a) => a[1] !== 0).length / avgs.length;
|
||||
const over = avgs.filter((a) => a[1] !== 0 && a[1] !== null).length / avgs.length;
|
||||
const cateItem = {
|
||||
id,
|
||||
name: i,
|
||||
@ -384,8 +395,7 @@ const monitorChartData = computed(() => {
|
||||
});
|
||||
}
|
||||
});
|
||||
// 去重
|
||||
dateList = Array.from(new Set(dateList)).sort((a, b) => a - b);
|
||||
dateList = dateList.sort((a, b) => a - b);
|
||||
valueList = valueList.filter((i) => showCates.value[i.id]);
|
||||
return {
|
||||
dateList,
|
||||
@ -410,7 +420,6 @@ function switchChartType() {
|
||||
}
|
||||
|
||||
function toggleMinute(value) {
|
||||
now.value = store.state.serverTime || Date.now();
|
||||
minute.value = value;
|
||||
}
|
||||
|
||||
@ -454,7 +463,6 @@ async function loadMonitor() {
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
now.value = store.state.serverTime || Date.now();
|
||||
}
|
||||
|
||||
let loadMonitorTimer = null;
|
||||
@ -539,8 +547,15 @@ onUnmounted(() => {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.cate-over-rate {
|
||||
height: var(--cate-item-height);
|
||||
line-height: calc(var(--cate-item-height) + 2px);
|
||||
text-align: right;
|
||||
color: #fffbd8;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
filter: grayscale(1);
|
||||
filter: grayscale(1) brightness(0.8);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ const platformLogoIconClassName = computed(() => hostUtils.getPlatformLogoIconCl
|
||||
|
||||
const serverRealTimeListTpls = computed(() => {
|
||||
if (config.nazhua?.listServerRealTimeShowLoad) {
|
||||
return 'duration,load,transfer,speeds';
|
||||
return 'D-A-T,T-A-U,L-A-P,I-A-O';
|
||||
}
|
||||
return 'duration,transfer,inSpeed,outSpeed';
|
||||
});
|
||||
|
||||
@ -23,7 +23,10 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="item-text item-value">{{ value }}</span>
|
||||
<span class="item-text item-unit">{{ unit }}</span>
|
||||
<span
|
||||
v-if="unit"
|
||||
class="item-text item-unit"
|
||||
>{{ unit }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@ -184,6 +187,12 @@ const columnStyle = computed(() => {
|
||||
}
|
||||
}
|
||||
|
||||
&--speeds {
|
||||
.item-value {
|
||||
color: var(--net-speed-color);
|
||||
}
|
||||
}
|
||||
|
||||
&--inSpeed {
|
||||
.item-value {
|
||||
color: var(--net-speed-in-color);
|
||||
@ -207,5 +216,23 @@ const columnStyle = computed(() => {
|
||||
color: var(--list-item-price-color);
|
||||
}
|
||||
}
|
||||
|
||||
&--tcp {
|
||||
.item-value {
|
||||
color: var(--conn-tcp-color);
|
||||
}
|
||||
}
|
||||
|
||||
&--udp {
|
||||
.item-value {
|
||||
color: var(--conn-udp-color);
|
||||
}
|
||||
}
|
||||
|
||||
&--conns {
|
||||
.item-value {
|
||||
color: var(--conn-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
<server-list-item-real-time
|
||||
v-if="$config.nazhua.hideListItemStat !== true"
|
||||
:info="info"
|
||||
server-real-time-list-tpls="load,inSpeed,outSpeed,transfer,duration"
|
||||
server-real-time-list-tpls="load,conns,speeds,transfer,duration"
|
||||
/>
|
||||
<server-list-item-bill
|
||||
v-if="$config.nazhua.hideListItemBill !== true"
|
||||
|
||||
@ -22,13 +22,19 @@
|
||||
</span>
|
||||
<span class="item-content-sub-content">
|
||||
<span class="item-value">{{ subItem.show ? subItem?.value : '-' }}</span>
|
||||
<span class="item-unit item-text">{{ subItem.show ? subItem?.unit : '' }}</span>
|
||||
<span
|
||||
v-if="subItem.show"
|
||||
class="item-unit item-text"
|
||||
>{{ subItem?.unit }}</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<template v-else>
|
||||
<span class="item-value">{{ item.show ? item?.value : '-' }}</span>
|
||||
<span class="item-unit item-text">{{ item.show ? item?.unit : '' }}</span>
|
||||
<span
|
||||
v-if="item.show"
|
||||
class="item-unit item-text"
|
||||
>{{ item?.unit }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<span
|
||||
@ -120,7 +126,14 @@ const {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
gap: 0.2em;
|
||||
}
|
||||
|
||||
--real-time-label-line-height: calc(var(--real-time-label-font-size, 14px) * 1.8);
|
||||
|
||||
.item-content-sub-label {
|
||||
height: var(--real-time-label-line-height);
|
||||
line-height: var(--real-time-label-line-height);
|
||||
}
|
||||
|
||||
.item-content-sub-content {
|
||||
@ -128,21 +141,36 @@ const {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
line-height: 1em;
|
||||
font-size: var(--real-time-label-font-size, 14px);
|
||||
}
|
||||
|
||||
.item-text {
|
||||
line-height: 1em;
|
||||
font-size: var(--real-time-label-font-size, 14px);
|
||||
}
|
||||
|
||||
.item-value,
|
||||
.item-text,
|
||||
.item-label {
|
||||
line-height: 1em;
|
||||
height: var(--real-time-label-line-height);
|
||||
line-height: var(--real-time-label-line-height);
|
||||
font-size: var(--real-time-label-font-size, 14px);
|
||||
}
|
||||
|
||||
.item-content-sub-item--L-A-P-load {
|
||||
.item-value {
|
||||
color: var(--load-color);
|
||||
}
|
||||
}
|
||||
.item-content-sub-item--L-A-P-process {
|
||||
.item-value {
|
||||
color: var(--process-color);
|
||||
}
|
||||
}
|
||||
|
||||
.item-content-sub-item--D-A-T-duration {
|
||||
.item-value {
|
||||
color: var(--duration-color);
|
||||
}
|
||||
}
|
||||
.item-content-sub-item--D-A-T-transfer {
|
||||
.item-value {
|
||||
color: var(--transfer-color);
|
||||
}
|
||||
}
|
||||
|
||||
.item-content-sub-item--speeds-in {
|
||||
.item-value {
|
||||
color: var(--net-speed-in-color);
|
||||
@ -153,6 +181,17 @@ const {
|
||||
color: var(--net-speed-out-color);
|
||||
}
|
||||
}
|
||||
|
||||
.item-content-sub-item--conn-tcp {
|
||||
.item-value {
|
||||
color: var(--conn-tcp-color);
|
||||
}
|
||||
}
|
||||
.item-content-sub-item--conn-udp {
|
||||
.item-value {
|
||||
color: var(--conn-udp-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,15 +11,15 @@ import uniqolor from 'uniqolor';
|
||||
*/
|
||||
export function getThreshold(data, tolerance = 2) {
|
||||
// 计算数据的平均值
|
||||
const mean = data.reduce((sum, value) => sum + value, 0) / data.length;
|
||||
const mean = data.reduce((sum, value) => sum + (value || 0), 0) / data.length;
|
||||
// 计算数据的方差
|
||||
const variance = data.reduce((sum, value) => sum + (value - mean) ** 2, 0) / data.length;
|
||||
const variance = data.reduce((sum, value) => sum + ((value || 0) - mean) ** 2, 0) / data.length;
|
||||
// 计算标准差
|
||||
const stdDev = Math.sqrt(variance);
|
||||
// 计算阈值
|
||||
const threshold = tolerance * stdDev;
|
||||
// 过滤掉值为0的数据
|
||||
const filteredData = data.filter((value) => value !== 0);
|
||||
const filteredData = data.filter((value) => value !== 0 && value !== null);
|
||||
// 计算过滤后数据的最小值
|
||||
const min = Math.min(...filteredData);
|
||||
// 计算过滤后数据的最大值
|
||||
|
||||
@ -255,6 +255,44 @@ export default (params) => {
|
||||
show: validate.isSet(netOutSpeed.value?.value),
|
||||
};
|
||||
case 'speeds':
|
||||
return {
|
||||
key,
|
||||
label: '网速',
|
||||
value: [
|
||||
`${netInSpeed.value?.value}${netInSpeed.value?.unit}`,
|
||||
`${netOutSpeed.value?.value}${netOutSpeed.value?.unit}`,
|
||||
].join('|'),
|
||||
show: validate.isSet(netInSpeed.value?.value) && validate.isSet(netOutSpeed.value?.value),
|
||||
};
|
||||
case 'load':
|
||||
return {
|
||||
key,
|
||||
label: '负载',
|
||||
value: (props.info.State?.Load1 || 0).toFixed(2),
|
||||
show: validate.isSet(props.info.State?.Load1),
|
||||
};
|
||||
case 'conns':
|
||||
return {
|
||||
key,
|
||||
label: '连接',
|
||||
value: `${props.info.State?.TcpConnCount || 0}|${props.info.State?.UdpConnCount || 0}`,
|
||||
show: true,
|
||||
};
|
||||
case 'tcp':
|
||||
return {
|
||||
key,
|
||||
label: 'TCP',
|
||||
value: props.info.State?.TcpConnCount || 0,
|
||||
show: validate.isSet(props.info.State?.TcpConnCount),
|
||||
};
|
||||
case 'udp':
|
||||
return {
|
||||
key,
|
||||
label: 'UDP',
|
||||
value: props.info.State?.UdpConnCount || 0,
|
||||
show: validate.isSet(props.info.State?.UdpConnCount),
|
||||
};
|
||||
case 'I-A-O':
|
||||
return {
|
||||
key,
|
||||
label: '网速',
|
||||
@ -276,13 +314,68 @@ export default (params) => {
|
||||
],
|
||||
show: validate.isSet(netInSpeed.value?.value) && validate.isSet(netOutSpeed.value?.value),
|
||||
};
|
||||
case 'load':
|
||||
case 'L-A-P':
|
||||
return {
|
||||
key,
|
||||
label: '负载',
|
||||
value: (props.info.State?.Load1 || 0).toFixed(2),
|
||||
unit: '',
|
||||
show: validate.isSet(props.info.State?.Load1),
|
||||
values: [
|
||||
{
|
||||
key: 'load',
|
||||
label: '负载',
|
||||
value: (props.info.State?.Load1 || 0).toFixed(2),
|
||||
show: validate.isSet(props.info.State?.Load1),
|
||||
},
|
||||
{
|
||||
key: 'process',
|
||||
label: '进程',
|
||||
value: props.info.State?.ProcessCount || 0,
|
||||
show: validate.isSet(props.info.State?.ProcessCount),
|
||||
},
|
||||
],
|
||||
show: validate.isSet(props.info.State?.Load1) || validate.isSet(props.info.State?.ProcessCount),
|
||||
};
|
||||
case 'T-A-U':
|
||||
return {
|
||||
key,
|
||||
label: '连接',
|
||||
values: [
|
||||
{
|
||||
key: 'tcp',
|
||||
label: 'TCP',
|
||||
value: (props.info.State?.TcpConnCount || 0).toString().padEnd(3, ' '),
|
||||
show: validate.isSet(props.info.State?.TcpConnCount),
|
||||
},
|
||||
{
|
||||
key: 'udp',
|
||||
label: 'UDP',
|
||||
value: (props.info.State?.UdpConnCount || 0).toString().padEnd(3, ' '),
|
||||
show: validate.isSet(props.info.State?.UdpConnCount),
|
||||
},
|
||||
],
|
||||
show: validate.isSet(props.info.State?.TcpConnCount) || validate.isSet(props.info.State?.UdpConnCount),
|
||||
};
|
||||
case 'D-A-T':
|
||||
return {
|
||||
key,
|
||||
label: '统计',
|
||||
values: [
|
||||
{
|
||||
key: 'duration',
|
||||
label: '在线',
|
||||
value: duration.value?.value,
|
||||
unit: duration.value?.unit,
|
||||
show: validate.isSet(duration.value?.value),
|
||||
},
|
||||
{
|
||||
key: 'transfer',
|
||||
label: '流量',
|
||||
title: `${transfer.value.statTypeLabel}流量`,
|
||||
value: transfer.value?.value,
|
||||
unit: transfer.value?.unit,
|
||||
show: validate.isSet(transfer.value?.value),
|
||||
},
|
||||
],
|
||||
show: validate.isSet(duration.value?.value) || validate.isSet(transfer.value?.value),
|
||||
};
|
||||
default:
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user