improve: status-server主题agent账单信息展示 (#425)

1. 支持价格和期限单独设置
2. 账单信息附加到group分组控制
3. 一些其他优化
This commit is contained in:
nap0o 2024-10-04 11:35:44 -04:00 committed by GitHub
parent da74bf4749
commit 318da135b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 128 additions and 53 deletions

View File

@ -6,7 +6,7 @@
<th class="node-cell name center">{{tr "Name"}}</th> <th class="node-cell name center">{{tr "Name"}}</th>
<th class="node-cell os center">{{tr "Platform"}}</th> <th class="node-cell os center">{{tr "Platform"}}</th>
<th class="node-cell location center">{{tr "Location"}}</th> <th class="node-cell location center">{{tr "Location"}}</th>
<th v-if="additional && Object.values(additional).filter(item => item.price).length > 0" class="node-cell price center">{{tr "Price"}}</th> <th v-if="nodesNoTag.some(item => item.additional && item.additional.price && Object.keys(item.additional.price).length > 0)" class="node-cell price center">{{tr "Price"}}</th>
<th class="node-cell uptime center">{{tr "Uptime"}}</th> <th class="node-cell uptime center">{{tr "Uptime"}}</th>
<th class="node-cell load center">{{tr "Load"}}</th> <th class="node-cell load center">{{tr "Load"}}</th>
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th> <th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
@ -14,7 +14,7 @@
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th> <th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
<th class="node-cell memory center">{{tr "MemUsed"}}</th> <th class="node-cell memory center">{{tr "MemUsed"}}</th>
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th> <th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
<th v-if="additional && Object.values(additional).filter(item => item.remaining).length > 0" class="node-cell remaining center">{{tr "Remaining"}}</th> <th v-if="nodesNoTag.some(item => item.additional && item.additional.remaining && Object.keys(item.additional.remaining).length > 0)" class="node-cell remaining center">{{tr "Remaining"}}</th>
</tr> </tr>
</thead> </thead>
<tbody id="servers"> <tbody id="servers">
@ -38,10 +38,10 @@
<i :class="'fi fi-' + node.location"></i> <i :class="'fi fi-' + node.location"></i>
<span class="node-cell-location-text text-uppercase">@#node.location#@</span> <span class="node-cell-location-text text-uppercase">@#node.location#@</span>
</td> </td>
<td v-if="additional && Object.values(additional).filter(item => item.price).length > 0" class="node-cell price center"> <td v-if="nodesNoTag.some(item => item.additional && item.additional.price && Object.keys(item.additional.price).length > 0)" class="node-cell price center">
<template v-if="additional[node.ID] && additional[node.ID].price"> <template v-if="node.additional && node.additional.price">
<span v-if="additional[node.ID].price.amount == 0" class="node-cell-price-text">Free</span> <span v-if="node.additional.price.amount == 0" class="node-cell-price-text">Free</span>
<span v-else class="node-cell-price-text">@#additional[node.ID].price.amount#@@#(additional[node.ID].price.cycle ? '/' + additional[node.ID].price.cycle : '')#@</span> <span v-else class="node-cell-price-text">@#node.additional.price.amount#@@#(node.additional.price.cycle ? '/' + node.additional.price.cycle : '')#@</span>
</template> </template>
</td> </td>
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td> <td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td>
@ -67,16 +67,16 @@
</div> </div>
</div> </div>
</td> </td>
<td v-if="additional && Object.values(additional).filter(item => item.remaining).length > 0" class="node-cell remaining"> <td v-if="nodesNoTag.some(item => item.additional && item.additional.remaining && Object.keys(item.additional.remaining).length > 0)" class="node-cell remaining">
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']"> <div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
<template v-if="additional[node.ID] && additional[node.ID].remaining.format"> <template v-if="node.additional && node.additional.remaining.format">
<div :style="additional[node.ID].remaining.format.style" :class="additional[node.ID].remaining.days == 'lifetime' ? '' : additional[node.ID].remaining.format.class"></div> <div :style="node.additional.remaining.format.style" :class="node.additional.remaining.days == 'lifetime' ? '' : node.additional.remaining.format.class"></div>
<div class="additional"> <div class="additional">
<small v-if="additional[node.ID].remaining.days == 'lifetime'">{{tr "Lifetime"}}</small> <small v-if="node.additional.remaining.days == 'lifetime'">{{tr "Lifetime"}}</small>
<small v-else-if="additional[node.ID].remaining.days < 0">{{tr "Expired"}}</small> <small v-else-if="node.additional.remaining.days < 0">{{tr "Expired"}}</small>
<small v-else> <small v-else>
<span class="node-cell-remaining-days">@#additional[node.ID].remaining.days#@{{tr "Days"}}</span> <span class="node-cell-remaining-days">@#node.additional.remaining.days#@{{tr "Days"}}</span>
<span class="node-cell-remaining-percent">@#additional[node.ID].remaining.percent#@%</span> <span class="node-cell-remaining-percent">@#node.additional.remaining.percent#@%</span>
</small> </small>
</div> </div>
</template> </template>

View File

@ -9,7 +9,7 @@
<th class="node-cell name center">{{tr "Name"}}</th> <th class="node-cell name center">{{tr "Name"}}</th>
<th class="node-cell os center">{{tr "Platform"}}</th> <th class="node-cell os center">{{tr "Platform"}}</th>
<th class="node-cell location center">{{tr "Location"}}</th> <th class="node-cell location center">{{tr "Location"}}</th>
<th v-if="additional && Object.values(additional).filter(item => item.price).length > 0" class="node-cell price center">{{tr "Price"}}</th> <th v-if="group.data.some(item => item.additional && item.additional.price && Object.keys(item.additional.price).length > 0)" class="node-cell price center">{{tr "Price"}}</th>
<th class="node-cell uptime center">{{tr "Uptime"}}</th> <th class="node-cell uptime center">{{tr "Uptime"}}</th>
<th class="node-cell load center">{{tr "Load"}}</th> <th class="node-cell load center">{{tr "Load"}}</th>
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th> <th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
@ -17,7 +17,7 @@
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th> <th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
<th class="node-cell memory center">{{tr "MemUsed"}}</th> <th class="node-cell memory center">{{tr "MemUsed"}}</th>
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th> <th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
<th v-if="additional && Object.values(additional).filter(item => item.remaining).length > 0" class="node-cell remaining center">{{tr "Remaining"}}</th> <th v-if="group.data.some(item => item.additional && item.additional.remaining && Object.keys(item.additional.remaining).length > 0)" class="node-cell remaining center">{{tr "Remaining"}}</th>
</tr> </tr>
</thead> </thead>
<tbody id="servers"> <tbody id="servers">
@ -41,10 +41,10 @@
<i :class="'fi fi-' + node.location"></i> <i :class="'fi fi-' + node.location"></i>
<span class="node-cell-location-text text-uppercase">&nbsp;@#node.location#@</span> <span class="node-cell-location-text text-uppercase">&nbsp;@#node.location#@</span>
</td> </td>
<td v-if="additional && Object.values(additional).filter(item => item.price).length > 0" class="node-cell price center"> <td v-if="group.data.some(item => item.additional && item.additional.price && Object.keys(item.additional.price).length > 0)" class="node-cell price center">
<template v-if="additional[node.ID] && additional[node.ID].price"> <template v-if="node.additional && node.additional.price">
<span v-if="additional[node.ID].price.amount == 0" class="node-cell-price-text">Free</span> <span v-if="node.additional.price.amount == 0" class="node-cell-price-text">Free</span>
<span v-else class="node-cell-price-text">@#additional[node.ID].price.amount#@@#(additional[node.ID].price.cycle ? '/' + additional[node.ID].price.cycle : '')#@</span> <span v-else class="node-cell-price-text">@#node.additional.price.amount#@@#(node.additional.price.cycle ? '/' + node.additional.price.cycle : '')#@</span>
</template> </template>
</td> </td>
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td> <td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td>
@ -70,16 +70,16 @@
</div> </div>
</div> </div>
</td> </td>
<td v-if="additional && Object.values(additional).filter(item => item.remaining).length > 0" class="node-cell remaining"> <td v-if="group.data.some(item => item.additional && item.additional.remaining && Object.keys(item.additional.remaining).length > 0)" class="node-cell remaining">
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']"> <div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
<template v-if="additional[node.ID] && additional[node.ID].remaining.format"> <template v-if="node.additional && node.additional.remaining.format">
<div :style="additional[node.ID].remaining.format.style" :class="additional[node.ID].remaining.days == 'lifetime' ? '' : additional[node.ID].remaining.format.class"></div> <div :style="node.additional.remaining.format.style" :class="node.additional.remaining.days == 'lifetime' ? '' : node.additional.remaining.format.class"></div>
<div class="additional"> <div class="additional">
<small v-if="additional[node.ID].remaining.days == 'lifetime'">{{tr "Lifetime"}}</small> <small v-if="node.additional.remaining.days == 'lifetime'">{{tr "Lifetime"}}</small>
<small v-else-if="additional[node.ID].remaining.days < 0">{{tr "Expired"}}</small> <small v-else-if="node.additional.remaining.days < 0">{{tr "Expired"}}</small>
<small v-else> <small v-else>
<span class="node-cell-remaining-days">@#additional[node.ID].remaining.days#@{{tr "Days"}}</span> <span class="node-cell-remaining-days">@#node.additional.remaining.days#@{{tr "Days"}}</span>
<span class="node-cell-remaining-percent">@#additional[node.ID].remaining.percent#@%</span> <span class="node-cell-remaining-percent">@#node.additional.remaining.percent#@%</span>
</small> </small>
</div> </div>
</template> </template>

View File

@ -72,10 +72,25 @@
mixins: [mixinsVue], mixins: [mixinsVue],
created() { created() {
this.servers = JSON.parse('{{.Servers}}').servers; this.servers = JSON.parse('{{.Servers}}').servers;
this.additional = this.initAdditional(this.servers);
if(this.showGroup) { if(this.showGroup) {
this.nodesTag = this.groupingData(this.handleNodes(this.servers),"Tag"); this.nodesTag = this.groupingData(this.handleNodes(this.servers),"Tag");
this.nodesTag.forEach(group => {
group.data.forEach(item => {
const additionalData = this.additional[item.ID];
if (additionalData) {
item.additional = additionalData; // 添加 additional 属性
}
});
});
} else { } else {
this.nodesNoTag = this.handleNodes(this.servers); this.nodesNoTag = this.handleNodes(this.servers);
this.nodesNoTag.forEach(item => {
const additionalData = this.additional[item.ID];
if (additionalData) {
item.additional = additionalData; // 添加 additional 属性
}
});
} }
this.initCountryMap().then(countryMap => { this.initCountryMap().then(countryMap => {
this.countryMap = countryMap; this.countryMap = countryMap;
@ -83,7 +98,6 @@
this.countryServer = this.initCountryServer(); this.countryServer = this.initCountryServer();
this.countryMapChartData = this.initCountryMapChartData(); this.countryMapChartData = this.initCountryMapChartData();
}); });
this.additional = this.initAdditional(this.servers);
}, },
mounted() { mounted() {
// 初始化时建立WebSocket连接 // 初始化时建立WebSocket连接
@ -103,18 +117,42 @@
initAdditional(servers) { initAdditional(servers) {
let nodes = {}; let nodes = {};
servers?.forEach(server => { servers?.forEach(server => {
if(server.PublicNote) { if (server.PublicNote) {
const remainingFormat = this.getRemainingFormat(server.live, server.PublicNote);
const remainingDays = this.getRemainingDays(this.getNoteElementValue(server.PublicNote, "billingDataMod", "endDate"), server.PublicNote);
const remainingPercent = this.getRemainingPercent(
this.getNoteElementValue(server.PublicNote, "billingDataMod", "startDate"),
this.getNoteElementValue(server.PublicNote, "billingDataMod", "endDate"),
server.PublicNote
);
const priceAmount = this.getNoteElementValue(server.PublicNote, "billingDataMod", "amount");
const priceCycle = this.getNoteElementValue(server.PublicNote, "billingDataMod", "cycle");
// 初始化节点
nodes[server.ID] = { nodes[server.ID] = {
"remaining": { "remaining": {},
format: this.getRemainingFormat(server.live, server.PublicNote), "price": {}
days: this.getRemainingDays(this.getNoteElementValue(server.PublicNote, "billingDataMod", "endDate"),server.PublicNote),
percent: this.toFixed2(100 - this.getRemainingPercent(this.getNoteElementValue(server.PublicNote, "billingDataMod", "startDate"), this.getNoteElementValue(server.PublicNote, "billingDataMod", "endDate"), server.PublicNote))
},
"price": {
amount: this.getNoteElementValue(server.PublicNote, "billingDataMod", "amount"),
cycle: this.getNoteElementValue(server.PublicNote, "billingDataMod", "cycle")
}
}; };
if (remainingFormat) {
nodes[server.ID].remaining.format = remainingFormat;
}
if (remainingDays) {
nodes[server.ID].remaining.days = remainingDays;
}
if (remainingPercent) {
nodes[server.ID].remaining.percent = this.toFixed2(100 - remainingPercent);
}
if (priceAmount) {
nodes[server.ID].price.amount = priceAmount;
}
if (priceCycle && priceAmount) {
nodes[server.ID].price.cycle = priceCycle;
}
} }
}); });
return nodes; return nodes;
@ -416,8 +454,22 @@
} }
if(this.showGroup) { if(this.showGroup) {
this.nodesTag = this.groupingData(this.handleNodes(data.servers),"Tag"); this.nodesTag = this.groupingData(this.handleNodes(data.servers),"Tag");
this.nodesTag.forEach(group => {
group.data.forEach(item => {
const additionalData = this.additional[item.ID];
if (additionalData) {
item.additional = additionalData; // 添加 additional 属性
}
});
});
} else { } else {
this.nodesNoTag = this.handleNodes(data.servers); this.nodesNoTag = this.handleNodes(data.servers);
this.nodesNoTag.forEach(item => {
const additionalData = this.additional[item.ID];
if (additionalData) {
item.additional = additionalData; // 添加 additional 属性
}
});
} }
} }
this.ws.onclose = () => { this.ws.onclose = () => {
@ -528,7 +580,7 @@
} }
}) })
.catch(error => { .catch(error => {
console.error('Error fetching data:', error); console.log('Error fetching data:', error);
}); });
}, },
renderCharts(id, reload = false) { renderCharts(id, reload = false) {
@ -807,7 +859,7 @@
// 如果 expiration 无效,返回 null 并记录日志 // 如果 expiration 无效,返回 null 并记录日志
if (isNaN(expiration.getTime())) { if (isNaN(expiration.getTime())) {
console.error("getAutoRenewalEndDate: Invalid expiration format"); console.log("getAutoRenewalEndDate: Invalid expiration format");
} }
const result = { const result = {
@ -924,7 +976,7 @@
// 检查 startDate 格式是否有效,若无效返回 null // 检查 startDate 格式是否有效,若无效返回 null
if (isNaN(start.getTime())) { if (isNaN(start.getTime())) {
console.error("getAutoRenewalStartDate: Invalid startDate format"); console.log("getAutoRenewalStartDate: Invalid startDate format");
} }
// 如果 flag 为 0直接返回开始日期 // 如果 flag 为 0直接返回开始日期
@ -979,27 +1031,43 @@
// 检查 startDate 和 endDate 是否有效 // 检查 startDate 和 endDate 是否有效
if (!startDate || !endDate || typeof startDate !== 'string' || typeof endDate !== 'string') { if (!startDate || !endDate || typeof startDate !== 'string' || typeof endDate !== 'string') {
console.error("getRemainingFormat: Invalid startDate or endDate in note");
return null; // 如果无效,返回 null 或其他错误处理逻辑 return null; // 如果无效,返回 null 或其他错误处理逻辑
} }
//处理特殊时间格式 // 处理特殊时间格式
if (startDate.includes('0000-00-00') || endDate.includes("0000-00-00")) { if (startDate.includes('0000-00-00') || endDate.includes("0000-00-00")) {
return this.formatPercents(online, this.toFixed2(100)); return this.formatPercents(online, this.toFixed2(100));
} else { }
// 检查 startDate 和 endDate 是否为合法的Date
if (isNaN(new Date(startDate).getTime()) || isNaN(new Date(endDate).getTime())) {
return {
"class": "",
"style": "width: 0%",
"percent": "0"
}
}
//处理特殊时间格式
const percent = this.getRemainingPercent(startDate, endDate, note); const percent = this.getRemainingPercent(startDate, endDate, note);
return this.formatPercents(online, this.toFixed2(percent)); return this.formatPercents(online, this.toFixed2(percent));
}
}, },
getRemainingDays(endDate, note) { getRemainingDays(endDate, note) {
// 检查 endDate 是否有效 // 检查 endDate 是否有效
if (!endDate || typeof endDate !== 'string') { if (!endDate || typeof endDate !== 'string') {
console.error("getRemainingDays: Invalid endDate format");
return null; return null;
} }
// 处理特殊时间格式 // 处理特殊时间格式
if (endDate.includes("0000-00-00")) return "lifetime"; if (endDate.includes("0000-00-00")) {
return "lifetime";
}
// 检查 startDate 和 endDate 是否为合法的Date
if (isNaN(new Date(endDate).getTime())) {
return "NaN";
}
// 获取当前时间,并调整时区 // 获取当前时间,并调整时区
const currentTime = this.getAdjustTimezone(new Date(endDate), new Date()); const currentTime = this.getAdjustTimezone(new Date(endDate), new Date());
@ -1025,12 +1093,19 @@
}, },
getRemainingPercent(startDate, endDate, note) { getRemainingPercent(startDate, endDate, note) {
// 检查 startDate 和 endDate 是否为有效字符串并处理特殊格式 // 检查 startDate 和 endDate 是否为有效字符串并处理特殊格式
if (typeof startDate !== 'string' || typeof endDate !== 'string') { if (!startDate || !endDate || typeof startDate !== 'string' || typeof endDate !== 'string') {
console.error("getRemainingPercent: Invalid startDate or endDate format");
return null; return null;
} }
if (startDate.includes("0000-00-00") || endDate.includes("0000-00-00")) return 100; // 处理特殊时间格式
if (startDate.includes("0000-00-00") || endDate.includes("0000-00-00")) {
return 100;
}
// 检查 startDate 和 endDate 是否为合法的Date
if (isNaN(new Date(startDate).getTime()) || isNaN(new Date(endDate).getTime())) {
return "NaN";
}
// 获取当前时间并调整时区 // 获取当前时间并调整时区
const now = this.getAdjustTimezone(new Date(endDate), new Date()); const now = this.getAdjustTimezone(new Date(endDate), new Date());
@ -1056,7 +1131,7 @@
// 计算剩余百分比 // 计算剩余百分比
if (now < start) { if (now < start) {
return 0; return 0.01; //避免当条件判断!percent被过滤
} }
if (now >= end) { if (now >= end) {
return 100; return 100;