Compare commits

..

No commits in common. "015ce405868f0583b4ed36515983bd0825a25a41" and "2d331823b98a2e6fa96496fd8505c919cca58e3a" have entirely different histories.

6 changed files with 55 additions and 211 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "nazhua", "name": "nazhua",
"version": "0.8.0", "version": "0.7.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@ -77,12 +77,6 @@ const codeMaps = {
name: '吉隆坡', name: '吉隆坡',
country: '马来西亚', country: '马来西亚',
}, },
BKK: {
name: '曼谷',
country: '泰国',
x: 985,
y: 296,
},
HAN: { HAN: {
x: 998, x: 998,
y: 274, y: 274,
@ -95,24 +89,6 @@ const codeMaps = {
name: '胡志明市', name: '胡志明市',
country: '越南', country: '越南',
}, },
BOM: {
name: '孟买',
country: '印度',
x: 874,
y: 284,
},
DEL: {
name: '新德里',
country: '印度',
x: 886,
y: 246,
},
DXB: {
name: '迪拜',
country: '阿联酋',
x: 794.5,
y: 252,
},
LAX: { LAX: {
x: 95, x: 95,
y: 207, y: 207,
@ -161,12 +137,6 @@ const codeMaps = {
name: '纽约', name: '纽约',
country: '美国', country: '美国',
}, },
IAD: {
name: '阿什本',
country: 'US',
x: 265,
y: 186,
},
DFW: { DFW: {
x: 172, x: 172,
y: 211, y: 211,
@ -281,30 +251,6 @@ const codeMaps = {
name: '布加勒斯特', name: '布加勒斯特',
country: '罗马尼亚', country: '罗马尼亚',
}, },
SOF: {
name: '索菲亚',
country: '保加利亚',
x: 662.5,
y: 167,
},
VNO: {
name: '维尔纽斯',
country: '立陶宛',
x: 657.5,
y: 110.5,
},
OSL: {
name: '奥斯陆',
country: '挪威',
x: 615.5,
y: 93,
},
RBA: {
name: '拉巴特',
country: '摩洛哥',
x: 545,
y: 212,
},
IST: { IST: {
x: 676, x: 676,
y: 176, y: 176,
@ -329,7 +275,6 @@ export const aliasMapping = {
HK: 'HKG', HK: 'HKG',
MO: 'MFM', MO: 'MFM',
TW: 'TPE', TW: 'TPE',
ASH: 'IAD',
}; };
export const countryCodeMapping = { export const countryCodeMapping = {
@ -339,9 +284,6 @@ export const countryCodeMapping = {
KR: 'SEL', KR: 'SEL',
MY: 'KUL', MY: 'KUL',
VN: 'HAN', VN: 'HAN',
IN: 'DEL',
TH: 'BKK',
AE: 'DXB',
TR: 'IST', TR: 'IST',
RO: 'OTP', RO: 'OTP',
LU: 'LUX', LU: 'LUX',
@ -360,10 +302,6 @@ export const countryCodeMapping = {
IT: 'MXP', IT: 'MXP',
ES: 'MAD', ES: 'MAD',
PL: 'WAW', PL: 'WAW',
BG: 'SOF',
LT: 'VNO',
NO: 'OSL',
MA: 'RBA',
}; };
export default codeMaps; export default codeMaps;

View File

@ -54,7 +54,6 @@ const store = useStore();
const footerSlogan = computed(() => decodeURIComponent(config.nazhua?.footerSlogan || '')); const footerSlogan = computed(() => decodeURIComponent(config.nazhua?.footerSlogan || ''));
const dynamicContentRef = ref(); const dynamicContentRef = ref();
const executedScripts = ref(new Set()); //
const dynamicContent = computed(() => { const dynamicContent = computed(() => {
if (store.state.setting?.config?.custom_code) { if (store.state.setting?.config?.custom_code) {
@ -70,69 +69,24 @@ const dynamicContent = computed(() => {
const executeScripts = () => { const executeScripts = () => {
nextTick(() => { nextTick(() => {
if (!dynamicContentRef.value) return; if (!dynamicContentRef.value) return;
const scripts = dynamicContentRef.value.querySelectorAll('script'); const scripts = dynamicContentRef.value.querySelectorAll('script');
scripts.forEach((script) => { scripts.forEach((script) => {
try { const newScript = document.createElement('script');
// newScript.type = 'text/javascript';
const scriptIdentifier = script.src || script.textContent || ''; if (script.src) {
if (!scriptIdentifier || executedScripts.value.has(scriptIdentifier)) { newScript.src = script.src; // src
return; } else {
} newScript.textContent = script.textContent; //
const newScript = document.createElement('script');
newScript.type = script.type || 'text/javascript';
//
if (script.async !== undefined) newScript.async = script.async;
if (script.defer !== undefined) newScript.defer = script.defer;
if (script.crossOrigin) newScript.crossOrigin = script.crossOrigin;
if (script.integrity) newScript.integrity = script.integrity;
if (script.noModule !== undefined) newScript.noModule = script.noModule;
if (script.referrerPolicy) newScript.referrerPolicy = script.referrerPolicy;
if (script.src) {
//
newScript.src = script.src;
newScript.onload = () => {
executedScripts.value.add(scriptIdentifier);
};
newScript.onerror = (error) => {
console.error('Failed to load external script:', script.src, error);
};
document.body.appendChild(newScript);
} else {
//
newScript.textContent = script.textContent;
document.body.appendChild(newScript);
executedScripts.value.add(scriptIdentifier);
//
document.body.removeChild(newScript);
}
} catch (error) {
console.error('Error executing dynamic script:', error);
} }
document.body.appendChild(newScript);
document.body.removeChild(newScript); //
}); });
}); });
}; };
// watch(dynamicContent, () => {
const cleanupScripts = () => { if (dynamicContent.value) {
executedScripts.value.clear(); executeScripts();
};
watch(dynamicContent, (newVal, oldVal) => {
//
if (newVal !== oldVal) {
cleanupScripts();
}
if (newVal) {
// DOM
nextTick(() => {
executeScripts();
});
} }
}); });

View File

@ -280,6 +280,13 @@ const monitorChartData = computed(() => {
* - valueList {Array}: 包含以下内容的对象列表 * - valueList {Array}: 包含以下内容的对象列表
* - name {String}: 监控名称 * - name {String}: 监控名称
* - data {Array}: [时间戳, 平均延迟] 对的数组 * - data {Array}: [时间戳, 平均延迟] 对的数组
*
* 该函数执行以下步骤
* 1. 遍历监控数据以分类和过滤平均延迟
* 2. 如果启用了削峰则应用削峰以过滤异常值
* 3. 构建监控名称到其各自时间戳和平均延迟的映射
* 4. 将映射转换为监控名称时间戳和平均延迟数据的列表
* 5. 删除重复的时间戳并对其进行排序
*/ */
const cateMap = {}; const cateMap = {};
monitorData.value.forEach((i) => { monitorData.value.forEach((i) => {
@ -312,30 +319,27 @@ const monitorChartData = computed(() => {
} }
} }
const { const {
median, threshold,
tolerancePercent, mean,
} = peakShaving.value ? getThreshold(showAvgDelay) : {}; max,
min,
} = peakShaving.value ? getThreshold(showAvgDelay, 2) : {};
showCreateTime.forEach((o, index) => { showCreateTime.forEach((o, index) => {
if (Object.prototype.hasOwnProperty.call(dateMap, o)) { if (Object.prototype.hasOwnProperty.call(dateMap, o)) {
return; return;
} }
const avgDelay = showAvgDelay[index]; const avgDelay = showAvgDelay[index];
// 0
if (avgDelay === null || avgDelay === 0) {
dateMap[o] = undefined;
return;
}
//
if (peakShaving.value) { if (peakShaving.value) {
// if (avgDelay === 0) {
const threshold = median * tolerancePercent; dateMap[o] = null;
// return;
if (Math.abs(avgDelay - median) > threshold) { }
dateMap[o] = undefined; if (Math.abs(avgDelay - mean) > threshold && max / min > 2) {
dateMap[o] = null;
return; return;
} }
} }
dateMap[o] = (avgDelay).toFixed(2) * 1; dateMap[o] = avgDelay ? (avgDelay).toFixed(2) * 1 : null;
}); });
}); });
let dateList = []; let dateList = [];
@ -358,11 +362,9 @@ const monitorChartData = computed(() => {
showCates.value[id] = true; showCates.value[id] = true;
} }
// //
// (undefined) const validAvgs = avgs.filter((a) => a[1] !== 0 && a[1] !== null);
const realAvgs = avgs.filter((a) => a[1] !== undefined);
const validAvgs = realAvgs.filter((a) => a[1] !== 0 && a[1] !== null);
const avg = validAvgs.reduce((a, b) => a + b[1], 0) / validAvgs.length; const avg = validAvgs.reduce((a, b) => a + b[1], 0) / validAvgs.length;
const over = validAvgs.length / realAvgs.length; const over = avgs.filter((a) => a[1] !== 0 && a[1] !== null).length / avgs.length;
const cateItem = { const cateItem = {
id, id,
name: i, name: i,
@ -566,12 +568,6 @@ onUnmounted(() => {
justify-content: space-between; justify-content: space-between;
gap: 10px; gap: 10px;
@media screen and (min-width: 768px) {
position: sticky;
top: var(--layout-header-height);
z-index: 1000;
}
.module-title { .module-title {
width: max-content; width: max-content;
height: 30px; height: 30px;

View File

@ -134,13 +134,11 @@ const {
.item-content-sub-label { .item-content-sub-label {
height: var(--real-time-label-line-height); height: var(--real-time-label-line-height);
line-height: var(--real-time-label-line-height); line-height: var(--real-time-label-line-height);
white-space: nowrap;
} }
.item-content-sub-content { .item-content-sub-content {
display: flex; display: flex;
align-items: center; align-items: center;
white-space: nowrap;
} }
.item-value, .item-value,

View File

@ -1,75 +1,33 @@
import uniqolor from 'uniqolor'; import uniqolor from 'uniqolor';
/** /**
* 计算数据的统计信息使用截尾中位数作为基准值 * 计算数据的阈值和平均值
* 根据平均延迟的不同范围使用不同的容差百分比进行削峰
* *
* @param {number[]} data - 要计算的数据数组 * @param {number[]} data - 要计算的数据数组
* @returns {{median: number, tolerancePercent: number, min: number, max: number}} * @param {number} [tolerance=2] - 容差倍数默认值为2
* 返回包含统计信息的对象 * @returns {{threshold: number, mean: number}} 返回包含阈值和平均值的对象
* @property {number} median - 截尾中位数(去掉极端值后的中位数) * @property {number} threshold - 计算得到的阈值
* @property {number} tolerancePercent - 根据中位数计算的容差百分比 * @property {number} mean - 数据的平均值
* @property {number} min - 最小值
* @property {number} max - 最大值
*/ */
export function getThreshold(data) { export function getThreshold(data, tolerance = 2) {
// 过滤掉null和0的数据只对有效延迟值计算统计量 // 计算数据的平均值
const mean = data.reduce((sum, value) => sum + (value || 0), 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 && value !== null); const filteredData = data.filter((value) => value !== 0 && value !== null);
// 计算过滤后数据的最小值
if (filteredData.length === 0) { const min = Math.min(...filteredData);
return { // 计算过滤后数据的最大值
median: 0, const max = Math.max(...filteredData);
tolerancePercent: 0.2, // 返回包含阈值、平均值、最小值和最大值的对象
min: 0,
max: 0,
};
}
// 排序数据
const sortedData = [...filteredData].sort((a, b) => Math.ceil(a) - Math.ceil(b));
const len = sortedData.length;
// 计算需要裁剪的数量10%
const trimCount = Math.floor(len * 0.1);
// 用于计算中位数的数据如果10%的数量>=1则去掉最大和最小的10%
let dataForMedian;
if (trimCount >= 1) {
// 截尾去掉最小的10%和最大的10%
dataForMedian = sortedData.slice(trimCount, len - trimCount);
} else {
// 数据量太少,不裁剪
dataForMedian = sortedData;
}
// 计算截尾中位数
const medianLen = dataForMedian.length;
const median = medianLen % 2 === 0
? (dataForMedian[medianLen / 2 - 1] + dataForMedian[medianLen / 2]) / 2
: dataForMedian[Math.floor(medianLen / 2)];
// 根据中位数确定容差百分比,延迟越小容差越大
let tolerancePercent;
if (median <= 10) {
tolerancePercent = 0.50; // 50%
} else if (median <= 30) {
tolerancePercent = 0.35; // 35%
} else if (median <= 50) {
tolerancePercent = 0.25; // 25%
} else if (median <= 100) {
tolerancePercent = 0.20; // 20%
} else {
tolerancePercent = 0.15; // 15%
}
const min = sortedData[0];
const max = sortedData[len - 1];
// console.log(min, max, median, sortedData);
return { return {
median, threshold,
tolerancePercent, mean,
min, min,
max, max,
}; };