mirror of
https://github.com/hamster1963/nezha-dash.git
synced 2025-04-24 21:10:45 +08:00
Compare commits
5 Commits
b21f4288a7
...
2f058d8777
Author | SHA1 | Date | |
---|---|---|---|
|
2f058d8777 | ||
|
24d038b914 | ||
|
5f7892c837 | ||
|
0ac6235422 | ||
|
a19da63fce |
@ -160,12 +160,48 @@ export const NetworkChart = React.memo(function NetworkChart({
|
|||||||
return activeChart === defaultChart ? formattedData : chartData[activeChart]
|
return activeChart === defaultChart ? formattedData : chartData[activeChart]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果开启了削峰,对数据进行处理
|
|
||||||
const data = (
|
const data = (
|
||||||
activeChart === defaultChart ? formattedData : chartData[activeChart]
|
activeChart === defaultChart ? formattedData : chartData[activeChart]
|
||||||
) as ResultItem[]
|
) as ResultItem[]
|
||||||
const windowSize = 7 // 增加到7个点的移动平均
|
|
||||||
const weights = [0.1, 0.1, 0.15, 0.3, 0.15, 0.1, 0.1] // 加权平均的权重
|
const windowSize = 11 // 增加窗口大小以获取更好的统计效果
|
||||||
|
const alpha = 0.3 // EWMA平滑因子
|
||||||
|
|
||||||
|
// 辅助函数:计算中位数
|
||||||
|
const getMedian = (arr: number[]) => {
|
||||||
|
const sorted = [...arr].sort((a, b) => a - b)
|
||||||
|
const mid = Math.floor(sorted.length / 2)
|
||||||
|
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数:异常值处理
|
||||||
|
const processValues = (values: number[]) => {
|
||||||
|
if (values.length === 0) return null
|
||||||
|
|
||||||
|
const median = getMedian(values)
|
||||||
|
const deviations = values.map((v) => Math.abs(v - median))
|
||||||
|
const medianDeviation = getMedian(deviations) * 1.4826 // MAD估计器
|
||||||
|
|
||||||
|
// 使用中位数绝对偏差(MAD)进行异常值检测
|
||||||
|
const validValues = values.filter(
|
||||||
|
(v) =>
|
||||||
|
Math.abs(v - median) <= 3 * medianDeviation && // 更严格的异常值判定
|
||||||
|
v <= median * 3, // 限制最大值不超过中位数的3倍
|
||||||
|
)
|
||||||
|
|
||||||
|
if (validValues.length === 0) return median // 如果没有有效值,返回中位数
|
||||||
|
|
||||||
|
// 计算EWMA
|
||||||
|
let ewma = validValues[0]
|
||||||
|
for (let i = 1; i < validValues.length; i++) {
|
||||||
|
ewma = alpha * validValues[i] + (1 - alpha) * ewma
|
||||||
|
}
|
||||||
|
|
||||||
|
return ewma
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化EWMA历史值
|
||||||
|
const ewmaHistory: { [key: string]: number } = {}
|
||||||
|
|
||||||
return data.map((point, index) => {
|
return data.map((point, index) => {
|
||||||
if (index < windowSize - 1) return point
|
if (index < windowSize - 1) return point
|
||||||
@ -174,22 +210,40 @@ export const NetworkChart = React.memo(function NetworkChart({
|
|||||||
const smoothed = { ...point } as ResultItem
|
const smoothed = { ...point } as ResultItem
|
||||||
|
|
||||||
if (activeChart === defaultChart) {
|
if (activeChart === defaultChart) {
|
||||||
// 处理所有线路的数据
|
|
||||||
chartDataKey.forEach((key) => {
|
chartDataKey.forEach((key) => {
|
||||||
const values = window
|
const values = window
|
||||||
.map((w) => w[key])
|
.map((w) => w[key])
|
||||||
.filter((v) => v !== undefined && v !== null) as number[]
|
.filter((v) => v !== undefined && v !== null) as number[]
|
||||||
if (values.length === windowSize) {
|
|
||||||
smoothed[key] = values.reduce((acc, val, idx) => acc + val * weights[idx], 0)
|
if (values.length > 0) {
|
||||||
|
const processed = processValues(values)
|
||||||
|
if (processed !== null) {
|
||||||
|
// 应用EWMA平滑
|
||||||
|
if (ewmaHistory[key] === undefined) {
|
||||||
|
ewmaHistory[key] = processed
|
||||||
|
} else {
|
||||||
|
ewmaHistory[key] = alpha * processed + (1 - alpha) * ewmaHistory[key]
|
||||||
|
}
|
||||||
|
smoothed[key] = ewmaHistory[key]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// 处理单条线路的数据
|
|
||||||
const values = window
|
const values = window
|
||||||
.map((w) => w.avg_delay)
|
.map((w) => w.avg_delay)
|
||||||
.filter((v) => v !== undefined && v !== null) as number[]
|
.filter((v) => v !== undefined && v !== null) as number[]
|
||||||
if (values.length === windowSize) {
|
|
||||||
smoothed.avg_delay = values.reduce((acc, val, idx) => acc + val * weights[idx], 0)
|
if (values.length > 0) {
|
||||||
|
const processed = processValues(values)
|
||||||
|
if (processed !== null) {
|
||||||
|
// 应用EWMA平滑
|
||||||
|
if (ewmaHistory["current"] === undefined) {
|
||||||
|
ewmaHistory["current"] = processed
|
||||||
|
} else {
|
||||||
|
ewmaHistory["current"] = alpha * processed + (1 - alpha) * ewmaHistory["current"]
|
||||||
|
}
|
||||||
|
smoothed.avg_delay = ewmaHistory["current"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,11 +141,11 @@ export default function ServerOverviewClient() {
|
|||||||
</div>
|
</div>
|
||||||
{data?.result ? (
|
{data?.result ? (
|
||||||
<>
|
<>
|
||||||
<section className="flex items-start flex-row pr-0 gap-1">
|
<section className="flex flex-col sm:flex-row items-start pr-0 gap-1">
|
||||||
<p className="sm:text-[12px] text-[10px] text-blue-800 dark:text-blue-400 text-nowrap font-medium">
|
<p className="text-[12px] text-blue-800 dark:text-blue-400 text-nowrap font-medium">
|
||||||
↑{formatBytes(data?.total_out_bandwidth)}
|
↑{formatBytes(data?.total_out_bandwidth)}
|
||||||
</p>
|
</p>
|
||||||
<p className="sm:text-[12px] text-[10px] text-purple-800 dark:text-purple-400 text-nowrap font-medium">
|
<p className="text-[12px] text-purple-800 dark:text-purple-400 text-nowrap font-medium">
|
||||||
↓{formatBytes(data?.total_in_bandwidth)}
|
↓{formatBytes(data?.total_in_bandwidth)}
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
28
package.json
28
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nezha-dash",
|
"name": "nezha-dash",
|
||||||
"version": "1.8.2",
|
"version": "1.8.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3040",
|
"dev": "next dev -p 3040",
|
||||||
@ -15,22 +15,22 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ducanh2912/next-pwa": "^10.2.9",
|
"@ducanh2912/next-pwa": "^10.2.9",
|
||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
"@radix-ui/react-dialog": "^1.1.3",
|
"@radix-ui/react-dialog": "^1.1.4",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.3",
|
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
||||||
"@radix-ui/react-label": "^2.1.1",
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.2",
|
"@radix-ui/react-navigation-menu": "^1.2.3",
|
||||||
"@radix-ui/react-popover": "^1.1.3",
|
"@radix-ui/react-popover": "^1.1.4",
|
||||||
"@radix-ui/react-progress": "^1.1.1",
|
"@radix-ui/react-progress": "^1.1.1",
|
||||||
"@radix-ui/react-separator": "^1.1.1",
|
"@radix-ui/react-separator": "^1.1.1",
|
||||||
"@radix-ui/react-slot": "^1.1.1",
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
"@radix-ui/react-switch": "^1.1.2",
|
"@radix-ui/react-switch": "^1.1.2",
|
||||||
"@radix-ui/react-tooltip": "^1.1.5",
|
"@radix-ui/react-tooltip": "^1.1.6",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^5.2.0",
|
"@trivago/prettier-plugin-sort-imports": "^5.2.0",
|
||||||
"@turf/turf": "^7.1.0",
|
"@turf/turf": "^7.1.0",
|
||||||
"@types/d3-geo": "^3.1.0",
|
"@types/d3-geo": "^3.1.0",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.18.0",
|
"@typescript-eslint/eslint-plugin": "^8.18.1",
|
||||||
"caniuse-lite": "^1.0.30001688",
|
"caniuse-lite": "^1.0.30001689",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"country-flag-icons": "^1.5.13",
|
"country-flag-icons": "^1.5.13",
|
||||||
@ -42,7 +42,7 @@
|
|||||||
"lucide-react": "^0.454.0",
|
"lucide-react": "^0.454.0",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"maxmind": "^4.3.23",
|
"maxmind": "^4.3.23",
|
||||||
"next": "^15.1.0",
|
"next": "^15.1.1",
|
||||||
"next-auth": "^5.0.0-beta.25",
|
"next-auth": "^5.0.0-beta.25",
|
||||||
"next-intl": "^3.26.1",
|
"next-intl": "^3.26.1",
|
||||||
"next-runtime-env": "^3.2.2",
|
"next-runtime-env": "^3.2.2",
|
||||||
@ -57,22 +57,22 @@
|
|||||||
"swr": "^2.2.6-beta.5",
|
"swr": "^2.2.6-beta.5",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript-eslint": "^8.18.0"
|
"typescript-eslint": "^8.18.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@next/bundle-analyzer": "^15.1.0",
|
"@next/bundle-analyzer": "^15.1.1",
|
||||||
"@tailwindcss/postcss": "^4.0.0-beta.7",
|
"@tailwindcss/postcss": "^4.0.0-beta.8",
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.2",
|
||||||
"@types/react": "^19.0.1",
|
"@types/react": "^19.0.1",
|
||||||
"@types/react-dom": "^19.0.2",
|
"@types/react-dom": "^19.0.2",
|
||||||
"eslint": "^9.17.0",
|
"eslint": "^9.17.0",
|
||||||
"eslint-config-next": "^15.1.0",
|
"eslint-config-next": "^15.1.1",
|
||||||
"eslint-plugin-turbo": "^2.3.3",
|
"eslint-plugin-turbo": "^2.3.3",
|
||||||
"eslint-plugin-unused-imports": "^4.1.4",
|
"eslint-plugin-unused-imports": "^4.1.4",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.9",
|
"prettier-plugin-tailwindcss": "^0.6.9",
|
||||||
"tailwindcss": "^4.0.0-beta.7",
|
"tailwindcss": "^4.0.0-beta.8",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"vercel": "^39.2.2"
|
"vercel": "^39.2.2"
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user