Compare commits

...

7 Commits

Author SHA1 Message Date
hi2hi
0606ba918d 📝 更新文档 2024-12-10 17:30:28 +00:00
hi2hi
249a1463b3 🛠️ 增加git日志提取深度至20,更新发布说明内容 2024-12-10 16:39:22 +00:00
hi2hi
7d424ffa78 🚀 0.4.14 2024-12-10 16:35:30 +00:00
hi2hi
057e0f5f11 🪄 优化提取算法,单个时间单个服务的重复数据去重 2024-12-10 16:35:06 +00:00
hi2hi
247115c3c3 🪄 修改默认的10秒刷新为30秒 2024-12-10 15:59:01 +00:00
hi2hi
bd6b3c8b44 💥🐛 修系统图标错改了国别地区旗帜 2024-12-10 15:50:22 +00:00
hi2hi
a23fbe4546 🛠️ 优化工作流,release改为草稿发布 2024-12-10 15:09:47 +00:00
8 changed files with 117 additions and 64 deletions

View File

@ -12,7 +12,7 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code

View File

@ -12,22 +12,19 @@ on:
jobs: jobs:
build-and-release: build-and-release:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
fetch-depth: 10 fetch-depth: 20
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: '20' node-version: '20'
- name: Install dependencies
run: npm install
- name: Get version from package.json - name: Get version from package.json
id: get_version id: get_version
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
@ -41,6 +38,16 @@ jobs:
echo "version=${{ steps.get_version.outputs.version }}" >> $GITHUB_OUTPUT echo "version=${{ steps.get_version.outputs.version }}" >> $GITHUB_OUTPUT
fi fi
- name: Generate release notes
id: release_notes
run: |
echo "#### Changes" > release_notes.md
git log -20 --pretty=format:"- %s" >> release_notes.md
echo -e "\n-----------\n哪吒V1请下载dist.zip\n哪吒V0请下载v0-dist.zip\n哪吒V0/nazhua/子目录需求请下载v0-nazhua.zip\nv${{ steps.determine_version.outputs.version }}-all.zip是全量包\nv${{ steps.determine_version.outputs.version }}-cdn-jsdelivr.zip是jsdelivr引用版\nv${{ steps.determine_version.outputs.version }}-cdn-loli.zip是cdnjs的loli.net引用版" >> release_notes.md
- name: Install dependencies
run: npm install
- name: Create Release - name: Create Release
id: create_release id: create_release
uses: actions/create-release@v1 uses: actions/create-release@v1
@ -49,7 +56,7 @@ jobs:
with: with:
tag_name: v${{ steps.determine_version.outputs.version }} tag_name: v${{ steps.determine_version.outputs.version }}
release_name: Release v${{ steps.determine_version.outputs.version }} release_name: Release v${{ steps.determine_version.outputs.version }}
draft: false draft: true
prerelease: false prerelease: false
- name: 构建自动版 - 完整引用版本 - name: 构建自动版 - 完整引用版本
@ -174,19 +181,7 @@ jobs:
- name: Add release notes - name: Add release notes
run: | run: |
# 获取最近一次提交的变更内容
git log $(git describe --tags --abbrev=0)..HEAD --pretty=format:"%s%n%b" > change.txt
# 获取现有的发布说明
gh release view v${{ steps.determine_version.outputs.version }} --json body -q .body > body.txt
# 添加最近一次提交的变更内容
echo -e "\n## 变更内容\n$(cat change.txt)" >> body.txt
# 添加其他发布说明
echo -e "\n哪吒V1请下载dist.zip\n哪吒V0请下载v0-dist.zip\n哪吒V0/nazhua/子目录需求请下载v0-nazhua.zip\nv${{ steps.determine_version.outputs.version }}-all.zip是全量包\nv${{ steps.determine_version.outputs.version }}-cdn-jsdelivr.zip是jsdelivr引用版\nv${{ steps.determine_version.outputs.version }}-cdn-loli.zip是cdnjs的loli.net引用版" >> body.txt
# 更新发布说明 # 更新发布说明
gh release edit v${{ steps.determine_version.outputs.version }} --notes-file body.txt gh release edit v${{ steps.determine_version.outputs.version }} --notes-file release_notes.md
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

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

View File

@ -19,7 +19,6 @@ window.$$nazhuaConfig = {
// hideTag: false, // 隐藏标签 // hideTag: false, // 隐藏标签
// hideDotBG: true, // 隐藏框框里面的点点背景 // hideDotBG: true, // 隐藏框框里面的点点背景
// monitorRefreshTime: 10, // 监控刷新时间间隔单位s, 0为不刷新为保证不频繁请求源站最低生效值为10s // monitorRefreshTime: 10, // 监控刷新时间间隔单位s, 0为不刷新为保证不频繁请求源站最低生效值为10s
// filterWeirdGPU: true, // 过滤奇怪的GPU
// filterGPUKeywords: ['Virtual Display'], // 如果GPU名称中包含这些关键字则过滤掉 // filterGPUKeywords: ['Virtual Display'], // 如果GPU名称中包含这些关键字则过滤掉
// customCodeMap: {}, // 自定义的地图点信息 // customCodeMap: {}, // 自定义的地图点信息
// nezhaVersion: 'v1', // 哪吒版本 // nezhaVersion: 'v1', // 哪吒版本

View File

@ -84,7 +84,9 @@ Nazhua对这个支持大概在90%左右,参与数据处理了的字段如下
4-1. 分组数据v1来自公开的api接口`/api/v1/server-group`。 4-1. 分组数据v1来自公开的api接口`/api/v1/server-group`。
## 部署 ## 部署
Nazhua主题是一个纯前端项目可以部署在纯静态服务器上但需要解决`/api/v1/monitor/${id}`监控数据、`/ws`WS服务和`/`主页的跨域访问。 Nazhua主题是一个纯前端项目可以部署在纯静态服务器上
v0需要解决`/api/v1/monitor/${id}`监控数据、`/ws`WS服务和`/`主页的跨域访问。
v1需要解决`/api/xxx`等数据接口、`/api/v1/ws/server`WS服务的跨域访问。
通常来说你需要一个nginx或者caddy反代请求解决跨域问题。 通常来说你需要一个nginx或者caddy反代请求解决跨域问题。
### Docker Compose + Cloudflare Tunnels部署 ### Docker Compose + Cloudflare Tunnels部署
@ -118,8 +120,8 @@ services:
### Nginx配置示例 ### Nginx配置示例
```nginx ```nginx
map $http_upgrade $connection_upgrade { map $http_upgrade $connection_upgrade {
default upgrade; default upgrade;
'' close; '' close;
} }
server { server {
@ -172,7 +174,6 @@ server {
} }
``` ```
## 自定义配置 ## 自定义配置
可以通过修改根目录下的`config.js`文件来自定义配置 可以通过修改根目录下的`config.js`文件来自定义配置
例如:(*参考内容在文档上不一定是最新具体参考public/config.js或者[Nazhua配置生成器](https://hi2shark.github.io/nazhua-generator/)*) 例如:(*参考内容在文档上不一定是最新具体参考public/config.js或者[Nazhua配置生成器](https://hi2shark.github.io/nazhua-generator/)*)
@ -183,6 +184,7 @@ window.$$nazhuaConfig = {
infinityCycle: '无限', // 无限周期名称 infinityCycle: '无限', // 无限周期名称
buyBtnText: '购买', // 购买按钮文案 buyBtnText: '购买', // 购买按钮文案
listServerStatusType: 'progress', // 服务器状态类型--列表 listServerStatusType: 'progress', // 服务器状态类型--列表
listServerRealTimeShowLoad: false, // 列表显示服务器实时负载
detailServerStatusType: 'progress', // 服务器状态类型--详情页 detailServerStatusType: 'progress', // 服务器状态类型--详情页
disableSarasaTermSC: false, // 禁用Sarasa Term SC字体 disableSarasaTermSC: false, // 禁用Sarasa Term SC字体
hideWorldMap: false, // 隐藏地图 hideWorldMap: false, // 隐藏地图
@ -196,6 +198,8 @@ window.$$nazhuaConfig = {
hideFilter: false, // 隐藏筛选 hideFilter: false, // 隐藏筛选
hideTag: false, // 隐藏标签 hideTag: false, // 隐藏标签
hideDotBG: false, // 隐藏框框里面的点点背景 hideDotBG: false, // 隐藏框框里面的点点背景
monitorRefreshTime: 10, // 监控刷新时间间隔单位s, 0为不刷新为保证不频繁请求源站最低生效值为10s
filterGPUKeywords: ['Virtual Display'], // 如果GPU名称中包含这些关键字则过滤掉
customCodeMap: {}, // 自定义的地图点信息 customCodeMap: {}, // 自定义的地图点信息
nezhaVersion: 'v1', // 哪吒版本 nezhaVersion: 'v1', // 哪吒版本
apiMonitorPath: '/api/v1/monitor/{id}', apiMonitorPath: '/api/v1/monitor/{id}',

View File

@ -11,6 +11,21 @@
</span> </span>
</div> </div>
<div class="right-box"> <div class="right-box">
<div
class="refresh-data-group"
title="是否自动刷新"
@click="switchRefresh"
>
<div
class="switch-box"
:class="{
active: refreshData,
}"
>
<span class="switch-dot" />
</div>
<span class="label-text">刷新</span>
</div>
<div <div
class="peak-shaving-group" class="peak-shaving-group"
title="过滤太高或太低的数据" title="过滤太高或太低的数据"
@ -62,16 +77,37 @@ const props = defineProps({
}, },
}); });
const refreshData = ref(true);
const peakShaving = ref(false); const peakShaving = ref(false);
const monitorData = ref([]); const monitorData = ref([]);
const monitorChartData = computed(() => { const monitorChartData = computed(() => {
/**
* 处理监控数据以生成分类的平均延迟随时间变化的列表
*
* @returns {Object} 返回一个对象包含
* - cateList {Array}: 唯一监控名称的列表
* - dateList {Array}: 排序后的唯一时间戳列表
* - valueList {Array}: 包含以下内容的对象列表
* - name {String}: 监控名称
* - data {Array}: [时间戳, 平均延迟] 对的数组
*
* 该函数执行以下步骤
* 1. 遍历监控数据以分类和过滤平均延迟
* 2. 如果启用了削峰则应用削峰以过滤异常值
* 3. 构建监控名称到其各自时间戳和平均延迟的映射
* 4. 将映射转换为监控名称时间戳和平均延迟数据的列表
* 5. 删除重复的时间戳并对其进行排序
*/
const cateMap = {}; const cateMap = {};
const dateMap = {};
monitorData.value.forEach((i) => { monitorData.value.forEach((i) => {
const dateMap = {};
if (!cateMap[i.monitor_name]) { if (!cateMap[i.monitor_name]) {
cateMap[i.monitor_name] = []; cateMap[i.monitor_name] = {
dateMap,
avgs: [],
};
} }
const { const {
threshold, threshold,
@ -80,49 +116,47 @@ const monitorChartData = computed(() => {
min, min,
} = peakShaving.value ? getThreshold(i.avg_delay, 2) : {}; } = peakShaving.value ? getThreshold(i.avg_delay, 2) : {};
i.created_at.forEach((o, index) => { i.created_at.forEach((o, index) => {
if (!dateMap[o]) { if (dateMap[o]) {
dateMap[o] = []; return;
} }
const avgDelay = i.avg_delay[index]; const avgDelay = i.avg_delay[index];
if (peakShaving.value) { if (peakShaving.value) {
if (Math.abs(avgDelay - mean) > threshold && max / min > 2) {
return;
}
if (avgDelay === 0) { if (avgDelay === 0) {
return; return;
} }
if (Math.abs(avgDelay - mean) > threshold && max / min > 2) {
return;
}
} }
dateMap[o].push({ dateMap[o] = (avgDelay).toFixed(2) * 1;
name: i.monitor_name,
value: (avgDelay).toFixed(2) * 1,
});
}); });
}); });
const dateList = []; let dateList = [];
Object.keys(dateMap).forEach((i) => {
if (dateMap[i]?.length) {
const time = parseInt(i, 10);
dateList.push(time);
dateMap[i].forEach((o) => {
cateMap[o.name].push([time, o.value]);
});
}
});
dateList.sort((a, b) => a - b);
const cateList = []; const cateList = [];
const valueList = []; const valueList = [];
Object.keys(cateMap).forEach((i) => { Object.keys(cateMap).forEach((i) => {
if (cateMap[i]?.length) { const {
cateList.push(i); dateMap,
} avgs,
} = cateMap[i];
Object.entries(dateMap).forEach(([key, value]) => {
const time = parseInt(key, 10);
avgs.push([time, value]);
dateList.push(time);
});
valueList.push({ valueList.push({
name: i, name: i,
data: cateMap[i], data: avgs,
}); });
if (avgs.length) {
cateList.push(i);
}
}); });
//
dateList = Array.from(new Set(dateList)).sort((a, b) => a - b);
return { return {
cateList,
dateList, dateList,
cateList,
valueList, valueList,
}; };
}); });
@ -131,6 +165,10 @@ function switchPeakShaving() {
peakShaving.value = !peakShaving.value; peakShaving.value = !peakShaving.value;
} }
function switchRefresh() {
refreshData.value = !refreshData.value;
}
async function loadMonitor() { async function loadMonitor() {
await request({ await request({
url: ( url: (
@ -147,19 +185,21 @@ async function loadMonitor() {
} }
let loadMonitorTimer = null; let loadMonitorTimer = null;
async function setTimeLoadMonitor() { async function setTimeLoadMonitor(force = false) {
if (loadMonitorTimer) { if (loadMonitorTimer) {
clearTimeout(loadMonitorTimer); clearTimeout(loadMonitorTimer);
} }
await loadMonitor(); if (refreshData.value || force) {
await loadMonitor();
}
let monitorRefreshTime = parseInt(config.nazhua.monitorRefreshTime, 10); let monitorRefreshTime = parseInt(config.nazhua.monitorRefreshTime, 10);
// 0 // 0
if (monitorRefreshTime === 0) { if (monitorRefreshTime === 0) {
return; return;
} }
// 10 // 30
if (Number.isNaN(monitorRefreshTime)) { if (Number.isNaN(monitorRefreshTime)) {
monitorRefreshTime = 10; monitorRefreshTime = 30;
} }
// 10 // 10
const sTime = Math.min(monitorRefreshTime, 10); const sTime = Math.min(monitorRefreshTime, 10);
@ -169,7 +209,7 @@ async function setTimeLoadMonitor() {
} }
onMounted(() => { onMounted(() => {
setTimeLoadMonitor(); setTimeLoadMonitor(true);
}); });
onUnmounted(() => { onUnmounted(() => {
@ -197,7 +237,14 @@ onUnmounted(() => {
color: #eee; color: #eee;
} }
.peak-shaving-group { .right-box {
display: flex;
align-items: center;
gap: 10px;
}
.peak-shaving-group,
.refresh-data-group {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;

View File

@ -18,7 +18,10 @@
<span <span
class="server-flag" class="server-flag"
> >
<span :class="platformLogoIconClassName" /> <span
class="fi"
:class="'fi-' + (info?.Host?.CountryCode || 'un')"
/>
</span> </span>
<span class="server-name"> <span class="server-name">
{{ info.Name }} {{ info.Name }}
@ -29,10 +32,7 @@
v-if="cpuAndMemAndDisk" v-if="cpuAndMemAndDisk"
class="cpu-mem-group" class="cpu-mem-group"
> >
<span <span :class="platformLogoIconClassName" />
v-if="info?.Host?.Platform"
:class="'fl-' + info?.Host?.Platform"
/>
<span class="core-mem">{{ cpuAndMemAndDisk }}</span> <span class="core-mem">{{ cpuAndMemAndDisk }}</span>
</div> </div>
</div> </div>

View File

@ -8,13 +8,21 @@
* @property {number} mean - 数据的平均值 * @property {number} mean - 数据的平均值
*/ */
export function getThreshold(data, tolerance = 2) { 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) / data.length;
// 计算数据的方差
const variance = data.reduce((sum, value) => sum + (value - mean) ** 2, 0) / data.length; const variance = data.reduce((sum, value) => sum + (value - mean) ** 2, 0) / data.length;
// 计算标准差
const stdDev = Math.sqrt(variance); const stdDev = Math.sqrt(variance);
// 计算阈值
const threshold = tolerance * stdDev; const threshold = tolerance * stdDev;
// 过滤掉值为0的数据
const filteredData = data.filter((value) => value !== 0); const filteredData = data.filter((value) => value !== 0);
// 计算过滤后数据的最小值
const min = Math.min(...filteredData); const min = Math.min(...filteredData);
// 计算过滤后数据的最大值
const max = Math.max(...filteredData); const max = Math.max(...filteredData);
// 返回包含阈值、平均值、最小值和最大值的对象
return { return {
threshold, threshold,
mean, mean,