♻️ 分离状态代码
This commit is contained in:
135
apps/state.js
135
apps/state.js
@@ -1,13 +1,8 @@
|
||||
import _ from 'lodash'
|
||||
import { createRequire } from 'module'
|
||||
import moment from 'moment'
|
||||
import os from 'os'
|
||||
import plugin from '../../../lib/plugins/plugin.js'
|
||||
import { Config, Version, Plugin_Name } from '../components/index.js'
|
||||
import { status } from '../constants/other.js'
|
||||
import { State, common, puppeteer } from '../model/index.js'
|
||||
import formatDuration from '../tools/formatDuration.js'
|
||||
const require = createRequire(import.meta.url)
|
||||
import { Config } from '../components/index.js'
|
||||
import { puppeteer } from '../model/index.js'
|
||||
import { getData, si } from '../model/State/index.js'
|
||||
import Monitor from '../model/State/Monitor.js'
|
||||
|
||||
let interval = false
|
||||
export class NewState extends plugin {
|
||||
@@ -18,8 +13,11 @@ export class NewState extends plugin {
|
||||
priority: 50,
|
||||
rule: [
|
||||
{
|
||||
reg: '^#?(椰奶)?(状态|监控)(pro)?$',
|
||||
reg: '^#?(椰奶)?状态(pro)?$',
|
||||
fnc: 'state'
|
||||
}, {
|
||||
reg: '^#椰奶监控$',
|
||||
fnc: 'monitor'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -28,7 +26,7 @@ export class NewState extends plugin {
|
||||
|
||||
async monitor (e) {
|
||||
await puppeteer.render('state/monitor', {
|
||||
chartData: JSON.stringify(State.chartData)
|
||||
chartData: JSON.stringify(Monitor.chartData)
|
||||
}, {
|
||||
e,
|
||||
scale: 1.4
|
||||
@@ -36,78 +34,15 @@ export class NewState extends plugin {
|
||||
}
|
||||
|
||||
async state (e) {
|
||||
if (e.msg.includes('监控')) return this.monitor(e)
|
||||
|
||||
if (!/椰奶/.test(e.msg) && !Config.whole.state) return false
|
||||
|
||||
if (!State.si) return e.reply('❎ 没有检测到systeminformation依赖,请运行:"pnpm add systeminformation -w"进行安装')
|
||||
if (!si) return e.reply('❎ 没有检测到systeminformation依赖,请运行:"pnpm add systeminformation -w"进行安装')
|
||||
|
||||
// 防止多次触发
|
||||
if (interval) { return false } else interval = true
|
||||
// 系统
|
||||
let otherInfo = []
|
||||
// 其他信息
|
||||
otherInfo.push({
|
||||
first: '系统',
|
||||
tail: State.osInfo?.distro
|
||||
})
|
||||
// 网络
|
||||
otherInfo.push(State.getnetwork)
|
||||
// 插件数量
|
||||
otherInfo.push(State.getPluginNum)
|
||||
let promiseTaskList = [
|
||||
State.getFastFetch(e),
|
||||
State.getFsSize()
|
||||
]
|
||||
|
||||
// 网络测试
|
||||
let psTest = []
|
||||
let { psTestSites, psTestTimeout, backdrop } = Config.state
|
||||
State.chartData.backdrop = backdrop
|
||||
psTestSites && promiseTaskList.push(...psTestSites?.map(i => State.getNetworkLatency(i.url, psTestTimeout).then(res => psTest.push({
|
||||
first: i.name,
|
||||
tail: res
|
||||
}))))
|
||||
// 执行promise任务
|
||||
let [FastFetch, HardDisk] = await Promise.all(promiseTaskList)
|
||||
// 可视化数据
|
||||
let visualData = _.compact(await Promise.all([
|
||||
// CPU板块
|
||||
State.getCpuInfo(),
|
||||
// 内存板块
|
||||
State.getMemUsage(),
|
||||
// GPU板块
|
||||
State.getGPU(),
|
||||
// Node板块
|
||||
State.getNodeInfo()
|
||||
]))
|
||||
|
||||
/** bot列表 */
|
||||
let BotList = [e.self_id]
|
||||
|
||||
if (e.msg.includes('pro')) {
|
||||
if (Array.isArray(Bot?.uin)) {
|
||||
BotList = Bot.uin
|
||||
} else if (Bot?.adapter && Bot.adapter.includes(e.self_id)) {
|
||||
BotList = Bot.adapter
|
||||
}
|
||||
}
|
||||
// 渲染数据
|
||||
let data = {
|
||||
BotStatus: await getBotState(BotList),
|
||||
chartData: JSON.stringify(common.checkIfEmpty(State.chartData, ['echarts_theme', 'cpu', 'ram']) ? undefined : State.chartData),
|
||||
// 硬盘内存
|
||||
HardDisk,
|
||||
// FastFetch
|
||||
FastFetch,
|
||||
// 硬盘速率
|
||||
fsStats: State.DiskSpeed,
|
||||
// 可视化数据
|
||||
visualData,
|
||||
// 其他数据
|
||||
otherInfo: _.compact(otherInfo),
|
||||
psTest: _.isEmpty(psTest) ? false : psTest
|
||||
}
|
||||
// 获取数据
|
||||
let data = await getData(e)
|
||||
|
||||
// 渲染图片
|
||||
await puppeteer.render('state/state', {
|
||||
@@ -120,49 +55,3 @@ export class NewState extends plugin {
|
||||
interval = false
|
||||
}
|
||||
}
|
||||
const getBotState = async (botList) => {
|
||||
const defaultAvatar = `../../../../../plugins/${Plugin_Name}/resources/state/img/default_avatar.jpg`
|
||||
const BotName = Version.name
|
||||
const systime = formatDuration(os.uptime(), 'dd天hh小时mm分', false)
|
||||
const calendar = moment().format('YYYY-MM-DD HH:mm:ss')
|
||||
|
||||
const dataPromises = botList.map(async (i) => {
|
||||
const bot = Bot[i]
|
||||
if (!bot?.uin) return ''
|
||||
|
||||
const avatar = bot.avatar || (Number(bot.uin) ? `https://q1.qlogo.cn/g?b=qq&s=0&nk=${bot.uin}` : defaultAvatar)
|
||||
const nickname = bot.nickname || '未知'
|
||||
const onlineStatus = status[bot.status] || '在线'
|
||||
const platform = bot.apk ? `${bot.apk.display} v${bot.apk.version}` : bot.version?.version || '未知'
|
||||
|
||||
const sent = await redis.get(`Yz:count:send:msg:bot:${bot.uin}:total`) || await redis.get('Yz:count:sendMsg:total')
|
||||
const recv = await redis.get(`Yz:count:receive:msg:bot:${bot.uin}:total`) || bot.stat?.recv_msg_cnt
|
||||
const screenshot = await redis.get(`Yz:count:send:image:bot:${bot.uin}:total`) || await redis.get('Yz:count:screenshot:total')
|
||||
|
||||
const friendQuantity = bot.fl?.size || 0
|
||||
const groupQuantity = bot.gl?.size || 0
|
||||
const groupMemberQuantity = Array.from(bot.gml?.values() || []).reduce((acc, curr) => acc + curr.size, 0)
|
||||
const runTime = formatDuration(Date.now() / 1000 - bot.stat?.start_time, 'dd天hh小时mm分', false)
|
||||
const botVersion = bot.version ? `${bot.version.name}(${bot.version.id})${bot.apk ? ` ${bot.version.version}` : ''}` : `ICQQ(QQ) v${require('icqq/package.json').version}`
|
||||
|
||||
return `<div class="box">
|
||||
<div class="tb">
|
||||
<div class="avatar">
|
||||
<img src="${avatar}"
|
||||
onerror="this.src= '${defaultAvatar}'; this.onerror = null;">
|
||||
</div>
|
||||
<div class="header">
|
||||
<h1>${nickname}</h1>
|
||||
<hr noshade>
|
||||
<p>${onlineStatus}(${platform}) | ${botVersion}</p>
|
||||
<p>收${recv || 0} | 发${sent || 0} | 图片${screenshot || 0} | 好友${friendQuantity} | 群${groupQuantity} | 群员${groupMemberQuantity}</p>
|
||||
<p>${BotName} 已运行 ${runTime} | 系统运行 ${systime}</p>
|
||||
<p>${calendar} | Node.js ${process.version} | ${process.platform} ${process.arch}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
|
||||
const dataArray = await Promise.all(dataPromises)
|
||||
return dataArray.join('')
|
||||
}
|
||||
|
||||
476
model/State.js
476
model/State.js
@@ -1,476 +0,0 @@
|
||||
import os from 'os'
|
||||
import _ from 'lodash'
|
||||
import fs from 'fs'
|
||||
import { Config, Data } from '../components/index.js'
|
||||
import request from '../lib/request/request.js'
|
||||
import { execSync } from '../tools/index.js'
|
||||
|
||||
export default new class {
|
||||
constructor () {
|
||||
this.si = null
|
||||
this.osInfo = null
|
||||
// 是否可以获取gpu
|
||||
this.isGPU = false
|
||||
// 网络
|
||||
this._network = null
|
||||
// 读写速率
|
||||
this._fsStats = null
|
||||
// 记录60条数据一分钟记录一次
|
||||
this.chartData = {
|
||||
network: {
|
||||
// 上行
|
||||
upload: [],
|
||||
// 下行
|
||||
download: []
|
||||
},
|
||||
fsStats: {
|
||||
// 读
|
||||
readSpeed: [],
|
||||
// 写
|
||||
writeSpeed: []
|
||||
},
|
||||
// cpu
|
||||
cpu: [],
|
||||
// 内存
|
||||
ram: [],
|
||||
// 主题
|
||||
echarts_theme: Data.readJSON('resources/state/theme_westeros.json')
|
||||
}
|
||||
|
||||
this.valueObject = {
|
||||
networkStats: 'rx_sec,tx_sec,iface',
|
||||
currentLoad: 'currentLoad',
|
||||
mem: 'active',
|
||||
fsStats: 'wx_sec,rx_sec'
|
||||
}
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
set network (value) {
|
||||
if (_.isNumber(value[0]?.tx_sec) && _.isNumber(value[0]?.rx_sec)) {
|
||||
this._network = value
|
||||
this.addData(this.chartData.network.upload, [Date.now(), value[0].tx_sec])
|
||||
this.addData(this.chartData.network.download, [Date.now(), value[0].rx_sec])
|
||||
}
|
||||
}
|
||||
|
||||
get network () {
|
||||
return this._network
|
||||
}
|
||||
|
||||
set fsStats (value) {
|
||||
if (_.isNumber(value?.wx_sec) && _.isNumber(value?.rx_sec)) {
|
||||
this._fsStats = value
|
||||
this.addData(this.chartData.fsStats.writeSpeed, [Date.now(), value.wx_sec])
|
||||
this.addData(this.chartData.fsStats.readSpeed, [Date.now(), value.rx_sec])
|
||||
}
|
||||
}
|
||||
|
||||
get fsStats () {
|
||||
return this._fsStats
|
||||
}
|
||||
|
||||
async initDependence () {
|
||||
try {
|
||||
this.si = await import('systeminformation')
|
||||
this.osInfo = await this.si.osInfo()
|
||||
return this.si
|
||||
} catch (error) {
|
||||
if (error.stack?.includes('Cannot find package')) {
|
||||
logger.warn('--------椰奶依赖缺失--------')
|
||||
logger.warn(`yenai-plugin 缺少依赖将无法使用 ${logger.yellow('椰奶状态')}`)
|
||||
logger.warn(`如需使用请运行:${logger.red('pnpm add systeminformation -w')}`)
|
||||
logger.warn('---------------------------')
|
||||
logger.debug(decodeURI(error.stack))
|
||||
} else {
|
||||
logger.error(`椰奶载入依赖错误:${logger.red('systeminformation')}`)
|
||||
logger.error(decodeURI(error.stack))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async init () {
|
||||
if (!await this.initDependence()) return
|
||||
const { controllers } = await this.si.graphics()
|
||||
// 初始化GPU获取
|
||||
if (controllers?.find(item =>
|
||||
item.memoryUsed && item.memoryFree && item.utilizationGpu)
|
||||
) {
|
||||
this.isGPU = true
|
||||
}
|
||||
// 给有问题的用户关闭定时器
|
||||
if (!Config.state.statusTask) return
|
||||
|
||||
if (Config.state.statusPowerShellStart) this.si.powerShellStart()
|
||||
this.getData()
|
||||
// 网速
|
||||
const Timer = setInterval(async () => {
|
||||
let data = await this.getData()
|
||||
if (_.isEmpty(data)) clearInterval(Timer)
|
||||
}, 60000)
|
||||
}
|
||||
|
||||
async getData () {
|
||||
let data = await this.si.get(this.valueObject)
|
||||
_.forIn(data, (value, key) => {
|
||||
if (_.isEmpty(value)) {
|
||||
logger.debug(`获取${key}数据失败,停止获取对应数据`)
|
||||
delete this.valueObject[key]
|
||||
}
|
||||
})
|
||||
let {
|
||||
fsStats,
|
||||
networkStats,
|
||||
mem: { active },
|
||||
currentLoad: { currentLoad }
|
||||
} = data
|
||||
this.fsStats = fsStats
|
||||
this.network = networkStats
|
||||
if (_.isNumber(active)) {
|
||||
this.addData(this.chartData.ram, [Date.now(), active])
|
||||
}
|
||||
if (_.isNumber(currentLoad)) {
|
||||
this.addData(this.chartData.cpu, [Date.now(), currentLoad])
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* 向数组中添加数据,如果数组长度超过允许的最大值,则删除最早添加的数据
|
||||
* @param {Array} arr - 要添加数据的数组
|
||||
* @param {*} data - 要添加的新数据
|
||||
* @param {number} [maxLen] - 数组允许的最大长度,默认值为60
|
||||
* @returns {void}
|
||||
*/
|
||||
addData (arr, data, maxLen = 60) {
|
||||
if (data === null || data === undefined) return
|
||||
// 如果数组长度超过允许的最大值,删除第一个元素
|
||||
if (arr.length >= maxLen) {
|
||||
_.pullAt(arr, 0)
|
||||
}
|
||||
// 添加新数据
|
||||
arr.push(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试获取数据,直到成功或达到最大重试次数。
|
||||
* @param {Function} fetchFunc 获取数据的函数,返回一个Promise对象。
|
||||
* @param {Array} [params] 需要执行函数的参数数组
|
||||
* @param {number} [timerId] 定时器的id,用于在获取数据失败时停止定时器
|
||||
* @param {number} [maxRetryCount] 最大重试次数。
|
||||
* @param {number} [retryInterval] 两次重试之间的等待时间,单位为毫秒。。
|
||||
* @returns {Promise} 获取到的数据。如果达到最大重试次数且获取失败,则返回null。
|
||||
*/
|
||||
async fetchDataWithRetry (fetchFunc, params = [], timerId, maxRetryCount = 3, retryInterval = 1000) {
|
||||
let retryCount = 0
|
||||
let data = null
|
||||
while (retryCount <= maxRetryCount) {
|
||||
data = await fetchFunc(...params)
|
||||
if (!_.isEmpty(data)) {
|
||||
break
|
||||
}
|
||||
retryCount++
|
||||
if (retryCount > maxRetryCount && timerId) {
|
||||
logger.debug(`获取${fetchFunc.name}数据失败,停止定时器`)
|
||||
clearInterval(timerId)
|
||||
break
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, retryInterval))
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件大小从字节转化为可读性更好的格式,例如B、KB、MB、GB、TB。
|
||||
* @param {number} size - 带转化的字节数。
|
||||
* @param {boolean} [isByte] - 如果为 true,则最终的文件大小显示保留 B 的后缀.
|
||||
* @param {boolean} [isSuffix] - 如果为 true,则在所得到的大小后面加上 kb、mb、gb、tb 等后缀.
|
||||
* @returns {string} 文件大小格式转换后的字符串.
|
||||
*/
|
||||
getFileSize (size, isByte = true, isSuffix = true) { // 把字节转换成正常文件大小
|
||||
if (size == null || size == undefined) return 0
|
||||
let num = 1024.00 // byte
|
||||
if (isByte && size < num) {
|
||||
return size.toFixed(2) + 'B'
|
||||
}
|
||||
if (size < Math.pow(num, 2)) {
|
||||
return (size / num).toFixed(2) + `K${isSuffix ? 'b' : ''}`
|
||||
} // kb
|
||||
if (size < Math.pow(num, 3)) {
|
||||
return (size / Math.pow(num, 2)).toFixed(2) + `M${isSuffix ? 'b' : ''}`
|
||||
} // M
|
||||
if (size < Math.pow(num, 4)) {
|
||||
return (size / Math.pow(num, 3)).toFixed(2) + 'G'
|
||||
} // G
|
||||
return (size / Math.pow(num, 4)).toFixed(2) + 'T' // T
|
||||
}
|
||||
|
||||
/**
|
||||
* 圆形进度条渲染
|
||||
* @param {number} res 百分比小数
|
||||
* @returns {*} css样式
|
||||
*/
|
||||
Circle (res) {
|
||||
let num = (res * 360).toFixed(0)
|
||||
let color = 'var(--low-color)'
|
||||
if (res >= 0.9) {
|
||||
color = 'var(--high-color)'
|
||||
} else if (res >= 0.8) {
|
||||
color = 'var(--medium-color)'
|
||||
}
|
||||
let leftCircle = `style="transform:rotate(-180deg);background:${color};"`
|
||||
let rightCircle = `style="transform:rotate(360deg);background:${color};"`
|
||||
if (num > 180) {
|
||||
leftCircle = `style="transform:rotate(${num}deg);background:${color};"`
|
||||
} else {
|
||||
rightCircle = `style="transform:rotate(-${180 - num}deg);background:${color};"`
|
||||
}
|
||||
return { leftCircle, rightCircle }
|
||||
}
|
||||
|
||||
/** 获取nodejs内存情况 */
|
||||
getNodeInfo () {
|
||||
let memory = process.memoryUsage()
|
||||
// 总共
|
||||
let rss = this.getFileSize(memory.rss)
|
||||
// 堆
|
||||
let heapTotal = this.getFileSize(memory.heapTotal)
|
||||
// 栈
|
||||
let heapUsed = this.getFileSize(memory.heapUsed)
|
||||
// 占用率
|
||||
let occupy = (memory.rss / (os.totalmem() - os.freemem())).toFixed(2)
|
||||
return {
|
||||
...this.Circle(occupy),
|
||||
inner: Math.round(occupy * 100) + '%',
|
||||
title: 'Node',
|
||||
info: [
|
||||
`总 ${rss}`,
|
||||
`堆 ${heapTotal}`,
|
||||
`栈 ${heapUsed}`
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取当前内存占用 */
|
||||
getMemUsage () {
|
||||
// 内存使用率
|
||||
let MemUsage = (1 - os.freemem() / os.totalmem()).toFixed(2)
|
||||
// 空闲内存
|
||||
let freemem = this.getFileSize(os.freemem())
|
||||
// 总共内存
|
||||
let totalmem = this.getFileSize(os.totalmem())
|
||||
// 使用内存
|
||||
let Usingmemory = this.getFileSize((os.totalmem() - os.freemem()))
|
||||
|
||||
return {
|
||||
...this.Circle(MemUsage),
|
||||
inner: Math.round(MemUsage * 100) + '%',
|
||||
title: 'RAM',
|
||||
info: [
|
||||
`总共 ${totalmem}`,
|
||||
`已用 ${Usingmemory}`,
|
||||
`空闲 ${freemem}`
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取CPU占用 */
|
||||
async getCpuInfo () {
|
||||
let { currentLoad: { currentLoad }, cpuCurrentSpeed } = await this.si.get({
|
||||
currentLoad: 'currentLoad',
|
||||
cpuCurrentSpeed: 'max,avg'
|
||||
})
|
||||
if (currentLoad == null || currentLoad == undefined) return false
|
||||
// 核心
|
||||
let cores = os.cpus()
|
||||
// cpu制造者
|
||||
let cpuModel = cores[0]?.model.slice(0, cores[0]?.model.indexOf(' ')) || ''
|
||||
return {
|
||||
...this.Circle(currentLoad / 100),
|
||||
inner: Math.round(currentLoad) + '%',
|
||||
title: 'CPU',
|
||||
info: [
|
||||
`${cpuModel} ${cores.length}核 ${this.osInfo?.arch}`,
|
||||
`平均${cpuCurrentSpeed.avg}GHz`,
|
||||
`最大${cpuCurrentSpeed.max}GHz`
|
||||
]
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取GPU占用 */
|
||||
async getGPU () {
|
||||
if (!this.isGPU) return false
|
||||
try {
|
||||
const { controllers } = await this.si.graphics()
|
||||
let graphics = controllers?.find(item =>
|
||||
item.memoryUsed && item.memoryFree && item.utilizationGpu
|
||||
)
|
||||
if (!graphics) {
|
||||
logger.warn('[Yenai-plugin][state]状态GPU数据异常:\n', controllers)
|
||||
return false
|
||||
}
|
||||
let {
|
||||
vendor, temperatureGpu, utilizationGpu,
|
||||
memoryTotal, memoryUsed, powerDraw
|
||||
} = graphics
|
||||
temperatureGpu && (temperatureGpu = temperatureGpu + '℃')
|
||||
powerDraw && (powerDraw = powerDraw + 'W')
|
||||
return {
|
||||
...this.Circle(utilizationGpu / 100),
|
||||
inner: Math.round(utilizationGpu) + '%',
|
||||
title: 'GPU',
|
||||
info: [
|
||||
`${vendor} ${temperatureGpu} ${powerDraw}`,
|
||||
`总共 ${(memoryTotal / 1024).toFixed(2)}G`,
|
||||
`已用 ${(memoryUsed / 1024).toFixed(2)}G`
|
||||
]
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('[Yenai-Plugin][State] 获取GPU失败')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取硬盘
|
||||
* @returns {*}
|
||||
*/
|
||||
async getFsSize () {
|
||||
// 去重
|
||||
let HardDisk = _.uniqWith(await this.si.fsSize(),
|
||||
(a, b) =>
|
||||
a.used === b.used && a.size === b.size && a.use === b.use && a.available === b.available
|
||||
)
|
||||
.filter(item => item.size && item.used && item.available && item.use)
|
||||
// 为空返回false
|
||||
if (_.isEmpty(HardDisk)) return false
|
||||
// 数值转换
|
||||
return HardDisk.map(item => {
|
||||
item.used = this.getFileSize(item.used)
|
||||
item.size = this.getFileSize(item.size)
|
||||
item.use = Math.round(item.use)
|
||||
item.color = 'var(--low-color)'
|
||||
if (item.use >= 90) {
|
||||
item.color = 'var(--high-color)'
|
||||
} else if (item.use >= 70) {
|
||||
item.color = 'var(--medium-color)'
|
||||
}
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取FastFetch
|
||||
* @param e
|
||||
*/
|
||||
async getFastFetch (e) {
|
||||
if (process.platform == 'win32' && !/pro/.test(e.msg)) return ''
|
||||
let ret = await execSync('bash plugins/yenai-plugin/resources/state/state.sh')
|
||||
if (ret.error) {
|
||||
e.reply(`❎ 请检查是否使用git bash启动Yunzai-bot\n错误信息:${ret.stderr}`)
|
||||
return ''
|
||||
}
|
||||
return ret.stdout.trim()
|
||||
}
|
||||
|
||||
// 获取读取速率
|
||||
get DiskSpeed () {
|
||||
if (!this.fsStats ||
|
||||
this.fsStats.rx_sec == null ||
|
||||
this.fsStats.wx_sec == null
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return {
|
||||
rx_sec: this.getFileSize(this.fsStats.rx_sec, false, false),
|
||||
wx_sec: this.getFileSize(this.fsStats.wx_sec, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网速
|
||||
* @returns {object}
|
||||
*/
|
||||
get getnetwork () {
|
||||
let network = _.cloneDeep(this.network)?.[0]
|
||||
if (!network || network.rx_sec == null || network.tx_sec == null) {
|
||||
return false
|
||||
}
|
||||
network.rx_sec = this.getFileSize(network.rx_sec, false, false)
|
||||
network.tx_sec = this.getFileSize(network.tx_sec, false, false)
|
||||
// return network
|
||||
return {
|
||||
first: network.iface,
|
||||
tail: `↑${network.tx_sec}/s | ↓${network.rx_sec}/s`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取插件包
|
||||
* @returns {*} 插件包数量
|
||||
*/
|
||||
get getPluginNum () {
|
||||
let str = './plugins'
|
||||
let arr = fs.readdirSync(str)
|
||||
let plugin = []
|
||||
arr.forEach((val) => {
|
||||
let ph = fs.statSync(str + '/' + val)
|
||||
if (ph.isDirectory()) {
|
||||
plugin.push(val)
|
||||
}
|
||||
})
|
||||
let del = ['example', 'genshin', 'other', 'system', 'bin']
|
||||
plugin = plugin.filter(item => !del.includes(item))
|
||||
const plugins = plugin?.length || 0
|
||||
const js = fs.readdirSync('./plugins/example')?.filter(item => item.includes('.js'))?.length || 0
|
||||
// return {
|
||||
// plugins: plugin?.length || 0,
|
||||
// js: fs.readdirSync('./plugins/example')?.filter(item => item.includes('.js'))?.length || 0
|
||||
// }
|
||||
return {
|
||||
first: '插件',
|
||||
tail: `${plugins} plugin | ${js} js`
|
||||
}
|
||||
}
|
||||
|
||||
async getNetworkLatency (url, timeoutTime = 5000) {
|
||||
const AbortController = globalThis.AbortController || await import('abort-controller')
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeout = setTimeout(() => {
|
||||
controller.abort()
|
||||
}, timeoutTime)
|
||||
try {
|
||||
const startTime = Date.now()
|
||||
let { status } = await request.get(url, { signal: controller.signal })
|
||||
const endTime = Date.now()
|
||||
let delay = endTime - startTime
|
||||
let color = ''; let statusColor = ''
|
||||
if (delay > 2000) {
|
||||
color = '#F44336'
|
||||
} else if (delay > 500) {
|
||||
color = '#d68100'
|
||||
} else {
|
||||
color = '#188038'
|
||||
}
|
||||
if (status >= 500) {
|
||||
statusColor = '#9C27B0'
|
||||
} else if (status >= 400) {
|
||||
statusColor = '#F44336'
|
||||
} else if (status >= 300) {
|
||||
statusColor = '#FF9800'
|
||||
} else if (status >= 200) {
|
||||
statusColor = '#188038'
|
||||
} else if (status >= 100) {
|
||||
statusColor = '#03A9F4'
|
||||
}
|
||||
return `<span style='color:${statusColor}'>${status}</span> | <span style='color:${color}'>${delay}ms</span>`
|
||||
} catch {
|
||||
return "<span style='color:#F44336'>timeout</span>"
|
||||
} finally {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}()
|
||||
54
model/State/BotState.js
Normal file
54
model/State/BotState.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { formatDuration } from '../../tools/index.js'
|
||||
import { Version, Plugin_Name } from '../../components/index.js'
|
||||
import moment from 'moment'
|
||||
import os from 'os'
|
||||
import { status } from '../../constants/other.js'
|
||||
import { createRequire } from 'module'
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
export default async function getBotState (botList) {
|
||||
const defaultAvatar = `../../../../../plugins/${Plugin_Name}/resources/state/img/default_avatar.jpg`
|
||||
const BotName = Version.name
|
||||
const systime = formatDuration(os.uptime(), 'dd天hh小时mm分', false)
|
||||
const calendar = moment().format('YYYY-MM-DD HH:mm:ss')
|
||||
|
||||
const dataPromises = botList.map(async (i) => {
|
||||
const bot = Bot[i]
|
||||
if (!bot?.uin) return ''
|
||||
|
||||
const avatar = bot.avatar || (Number(bot.uin) ? `https://q1.qlogo.cn/g?b=qq&s=0&nk=${bot.uin}` : defaultAvatar)
|
||||
const nickname = bot.nickname || '未知'
|
||||
const onlineStatus = status[bot.status] || '在线'
|
||||
const platform = bot.apk ? `${bot.apk.display} v${bot.apk.version}` : bot.version?.version || '未知'
|
||||
|
||||
const sent = await redis.get(`Yz:count:send:msg:bot:${bot.uin}:total`) || await redis.get('Yz:count:sendMsg:total')
|
||||
const recv = await redis.get(`Yz:count:receive:msg:bot:${bot.uin}:total`) || bot.stat?.recv_msg_cnt
|
||||
const screenshot = await redis.get(`Yz:count:send:image:bot:${bot.uin}:total`) || await redis.get('Yz:count:screenshot:total')
|
||||
|
||||
const friendQuantity = bot.fl?.size || 0
|
||||
const groupQuantity = bot.gl?.size || 0
|
||||
const groupMemberQuantity = Array.from(bot.gml?.values() || []).reduce((acc, curr) => acc + curr.size, 0)
|
||||
const runTime = formatDuration(Date.now() / 1000 - bot.stat?.start_time, 'dd天hh小时mm分', false)
|
||||
const botVersion = bot.version ? `${bot.version.name}(${bot.version.id})${bot.apk ? ` ${bot.version.version}` : ''}` : `ICQQ(QQ) v${require('icqq/package.json').version}`
|
||||
|
||||
return `<div class="box">
|
||||
<div class="tb">
|
||||
<div class="avatar">
|
||||
<img src="${avatar}"
|
||||
onerror="this.src= '${defaultAvatar}'; this.onerror = null;">
|
||||
</div>
|
||||
<div class="header">
|
||||
<h1>${nickname}</h1>
|
||||
<hr noshade>
|
||||
<p>${onlineStatus}(${platform}) | ${botVersion}</p>
|
||||
<p>收${recv || 0} | 发${sent || 0} | 图片${screenshot || 0} | 好友${friendQuantity} | 群${groupQuantity} | 群员${groupMemberQuantity}</p>
|
||||
<p>${BotName} 已运行 ${runTime} | 系统运行 ${systime}</p>
|
||||
<p>${calendar} | Node.js ${process.version} | ${process.platform} ${process.arch}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
|
||||
const dataArray = await Promise.all(dataPromises)
|
||||
return dataArray.join('')
|
||||
}
|
||||
27
model/State/CPU.js
Normal file
27
model/State/CPU.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import os from 'os'
|
||||
import { si, osInfo } from './index.js'
|
||||
import { Circle } from './utils.js'
|
||||
|
||||
/** 获取CPU占用 */
|
||||
export default async function getCpuInfo () {
|
||||
let { currentLoad: { currentLoad }, cpuCurrentSpeed } = await si.get({
|
||||
currentLoad: 'currentLoad',
|
||||
cpuCurrentSpeed: 'max,avg'
|
||||
})
|
||||
if (currentLoad == null || currentLoad == undefined) return false
|
||||
// 核心
|
||||
let cores = os.cpus()
|
||||
// cpu制造者
|
||||
let cpuModel = cores[0]?.model.slice(0, cores[0]?.model.indexOf(' ')) || ''
|
||||
return {
|
||||
...Circle(currentLoad / 100),
|
||||
inner: Math.round(currentLoad) + '%',
|
||||
title: 'CPU',
|
||||
info: [
|
||||
`${cpuModel} ${cores.length}核 ${osInfo?.arch}`,
|
||||
`平均${cpuCurrentSpeed.avg}GHz`,
|
||||
`最大${cpuCurrentSpeed.max}GHz`
|
||||
]
|
||||
|
||||
}
|
||||
}
|
||||
24
model/State/DependencyChecker.js
Normal file
24
model/State/DependencyChecker.js
Normal file
@@ -0,0 +1,24 @@
|
||||
export let si = false
|
||||
export let osInfo = null
|
||||
|
||||
export async function initDependence () {
|
||||
if (si) return si
|
||||
try {
|
||||
si = await import('systeminformation')
|
||||
osInfo = await si.osInfo()
|
||||
return si
|
||||
} catch (error) {
|
||||
if (error.stack?.includes('Cannot find package')) {
|
||||
logger.warn('--------椰奶依赖缺失--------')
|
||||
logger.warn(`yenai-plugin 缺少依赖将无法使用 ${logger.yellow('椰奶状态')}`)
|
||||
logger.warn(`如需使用请运行:${logger.red('pnpm add systeminformation -w')}`)
|
||||
logger.warn('---------------------------')
|
||||
logger.debug(decodeURI(error.stack))
|
||||
} else {
|
||||
logger.error(`椰奶载入依赖错误:${logger.red('systeminformation')}`)
|
||||
logger.error(decodeURI(error.stack))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await initDependence()
|
||||
15
model/State/FastFetch.js
Normal file
15
model/State/FastFetch.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { execSync } from '../../tools/index.js'
|
||||
|
||||
/**
|
||||
* 获取FastFetch
|
||||
* @param e
|
||||
*/
|
||||
export default async function getFastFetch (e) {
|
||||
if (process.platform == 'win32' && !/pro/.test(e.msg)) return ''
|
||||
let ret = await execSync('bash plugins/yenai-plugin/resources/state/state.sh')
|
||||
if (ret.error) {
|
||||
e.reply(`❎ 请检查是否使用git bash启动Yunzai-bot\n错误信息:${ret.stderr}`)
|
||||
return ''
|
||||
}
|
||||
return ret.stdout.trim()
|
||||
}
|
||||
31
model/State/FsSize.js
Normal file
31
model/State/FsSize.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import _ from 'lodash'
|
||||
import { getFileSize } from './utils.js'
|
||||
import { si } from './index.js'
|
||||
|
||||
/**
|
||||
* 获取硬盘
|
||||
* @returns {*}
|
||||
*/
|
||||
export default async function getFsSize () {
|
||||
// 去重
|
||||
let HardDisk = _.uniqWith(await si.fsSize(),
|
||||
(a, b) =>
|
||||
a.used === b.used && a.size === b.size && a.use === b.use && a.available === b.available
|
||||
)
|
||||
.filter(item => item.size && item.used && item.available && item.use)
|
||||
// 为空返回false
|
||||
if (_.isEmpty(HardDisk)) return false
|
||||
// 数值转换
|
||||
return HardDisk.map(item => {
|
||||
item.used = getFileSize(item.used)
|
||||
item.size = getFileSize(item.size)
|
||||
item.use = Math.round(item.use)
|
||||
item.color = 'var(--low-color)'
|
||||
if (item.use >= 90) {
|
||||
item.color = 'var(--high-color)'
|
||||
} else if (item.use >= 70) {
|
||||
item.color = 'var(--medium-color)'
|
||||
}
|
||||
return item
|
||||
})
|
||||
}
|
||||
50
model/State/GPU.js
Normal file
50
model/State/GPU.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Circle } from './utils.js'
|
||||
import { si } from './index.js'
|
||||
import { initDependence } from './DependencyChecker.js'
|
||||
|
||||
let isGPU = false;
|
||||
|
||||
(async function initGetIsGPU () {
|
||||
if (!await initDependence()) return
|
||||
const { controllers } = await si.graphics()
|
||||
// 初始化GPU获取
|
||||
if (controllers?.find(item =>
|
||||
item.memoryUsed && item.memoryFree && item.utilizationGpu)
|
||||
) {
|
||||
isGPU = true
|
||||
}
|
||||
})()
|
||||
|
||||
/** 获取GPU占用 */
|
||||
export default async function getGPU () {
|
||||
if (!isGPU) return false
|
||||
try {
|
||||
const { controllers } = await si.graphics()
|
||||
let graphics = controllers?.find(item =>
|
||||
item.memoryUsed && item.memoryFree && item.utilizationGpu
|
||||
)
|
||||
if (!graphics) {
|
||||
logger.warn('[Yenai-plugin][state]状态GPU数据异常:\n', controllers)
|
||||
return false
|
||||
}
|
||||
let {
|
||||
vendor, temperatureGpu, utilizationGpu,
|
||||
memoryTotal, memoryUsed, powerDraw
|
||||
} = graphics
|
||||
temperatureGpu && (temperatureGpu = temperatureGpu + '℃')
|
||||
powerDraw && (powerDraw = powerDraw + 'W')
|
||||
return {
|
||||
...Circle(utilizationGpu / 100),
|
||||
inner: Math.round(utilizationGpu) + '%',
|
||||
title: 'GPU',
|
||||
info: [
|
||||
`${vendor} ${temperatureGpu} ${powerDraw}`,
|
||||
`总共 ${(memoryTotal / 1024).toFixed(2)}G`,
|
||||
`已用 ${(memoryUsed / 1024).toFixed(2)}G`
|
||||
]
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('[Yenai-Plugin][State] 获取GPU失败')
|
||||
return false
|
||||
}
|
||||
}
|
||||
140
model/State/Monitor.js
Normal file
140
model/State/Monitor.js
Normal file
@@ -0,0 +1,140 @@
|
||||
import { Config, Data } from '../../components/index.js'
|
||||
import _ from 'lodash'
|
||||
import { si } from './index.js'
|
||||
import { initDependence } from './DependencyChecker.js'
|
||||
import { addData, getFileSize } from './utils.js'
|
||||
|
||||
export default new class monitor {
|
||||
constructor () {
|
||||
// 网络
|
||||
this._network = null
|
||||
// 读写速率
|
||||
this._fsStats = null
|
||||
// 记录60条数据一分钟记录一次
|
||||
this.chartData = {
|
||||
network: {
|
||||
// 上行
|
||||
upload: [],
|
||||
// 下行
|
||||
download: []
|
||||
},
|
||||
fsStats: {
|
||||
// 读
|
||||
readSpeed: [],
|
||||
// 写
|
||||
writeSpeed: []
|
||||
},
|
||||
// cpu
|
||||
cpu: [],
|
||||
// 内存
|
||||
ram: [],
|
||||
// 主题
|
||||
echarts_theme: Data.readJSON('resources/state/theme_westeros.json'),
|
||||
backdrop: Config.state.backdrop
|
||||
}
|
||||
this.valueObject = {
|
||||
networkStats: 'rx_sec,tx_sec,iface',
|
||||
currentLoad: 'currentLoad',
|
||||
mem: 'active',
|
||||
fsStats: 'wx_sec,rx_sec'
|
||||
}
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
set network (value) {
|
||||
if (_.isNumber(value[0]?.tx_sec) && _.isNumber(value[0]?.rx_sec)) {
|
||||
this._network = value
|
||||
addData(this.chartData.network.upload, [Date.now(), value[0].tx_sec])
|
||||
addData(this.chartData.network.download, [Date.now(), value[0].rx_sec])
|
||||
}
|
||||
}
|
||||
|
||||
get network () {
|
||||
return this._network
|
||||
}
|
||||
|
||||
set fsStats (value) {
|
||||
if (_.isNumber(value?.wx_sec) && _.isNumber(value?.rx_sec)) {
|
||||
this._fsStats = value
|
||||
addData(this.chartData.fsStats.writeSpeed, [Date.now(), value.wx_sec])
|
||||
addData(this.chartData.fsStats.readSpeed, [Date.now(), value.rx_sec])
|
||||
}
|
||||
}
|
||||
|
||||
get fsStats () {
|
||||
return this._fsStats
|
||||
}
|
||||
|
||||
async init () {
|
||||
if (!await initDependence()) return
|
||||
|
||||
// 给有问题的用户关闭定时器
|
||||
if (!Config.state.statusTask) return
|
||||
|
||||
if (Config.state.statusPowerShellStart) si.powerShellStart()
|
||||
this.getData()
|
||||
// 网速
|
||||
const Timer = setInterval(async () => {
|
||||
let data = await this.getData()
|
||||
if (_.isEmpty(data)) clearInterval(Timer)
|
||||
}, 60000)
|
||||
}
|
||||
|
||||
async getData () {
|
||||
let data = await si.get(this.valueObject)
|
||||
_.forIn(data, (value, key) => {
|
||||
if (_.isEmpty(value)) {
|
||||
logger.debug(`获取${key}数据失败,停止获取对应数据`)
|
||||
delete this.valueObject[key]
|
||||
}
|
||||
})
|
||||
let {
|
||||
fsStats,
|
||||
networkStats,
|
||||
mem: { active },
|
||||
currentLoad: { currentLoad }
|
||||
} = data
|
||||
this.fsStats = fsStats
|
||||
this.network = networkStats
|
||||
if (_.isNumber(active)) {
|
||||
addData(this.chartData.ram, [Date.now(), active])
|
||||
}
|
||||
if (_.isNumber(currentLoad)) {
|
||||
addData(this.chartData.cpu, [Date.now(), currentLoad])
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// 获取读取速率
|
||||
get DiskSpeed () {
|
||||
if (!this.fsStats ||
|
||||
this.fsStats.rx_sec == null ||
|
||||
this.fsStats.wx_sec == null
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return {
|
||||
rx_sec: getFileSize(this.fsStats.rx_sec, false, false),
|
||||
wx_sec: getFileSize(this.fsStats.wx_sec, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网速
|
||||
* @returns {object}
|
||||
*/
|
||||
get getNetwork () {
|
||||
let network = _.cloneDeep(this.network)?.[0]
|
||||
if (!network || network.rx_sec == null || network.tx_sec == null) {
|
||||
return false
|
||||
}
|
||||
network.rx_sec = getFileSize(network.rx_sec, false, false)
|
||||
network.tx_sec = getFileSize(network.tx_sec, false, false)
|
||||
// return network
|
||||
return {
|
||||
first: network.iface,
|
||||
tail: `↑${network.tx_sec}/s | ↓${network.rx_sec}/s`
|
||||
}
|
||||
}
|
||||
}()
|
||||
61
model/State/NetworkLatency.js
Normal file
61
model/State/NetworkLatency.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import request from '../../lib/request/request.js'
|
||||
import { Config } from '../../components/index.js'
|
||||
|
||||
export default function getNetworTestList () {
|
||||
let { psTestSites, psTestTimeout } = Config.state
|
||||
if (psTestSites) {
|
||||
let psTest = psTestSites?.map(i => getNetworkLatency(i.url, psTestTimeout).then(res => {
|
||||
return {
|
||||
first: i.name,
|
||||
tail: res
|
||||
}
|
||||
}))
|
||||
return Promise.all(psTest)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 网络测试
|
||||
* @param {string} url 测试的url
|
||||
* @param {number} [timeoutTime] 超时时间
|
||||
* @returns {string}
|
||||
*/
|
||||
async function getNetworkLatency (url, timeoutTime = 5000) {
|
||||
const AbortController = globalThis.AbortController || await import('abort-controller')
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeout = setTimeout(() => {
|
||||
controller.abort()
|
||||
}, timeoutTime)
|
||||
try {
|
||||
const startTime = Date.now()
|
||||
let { status } = await request.get(url, { signal: controller.signal })
|
||||
const endTime = Date.now()
|
||||
let delay = endTime - startTime
|
||||
let color = ''; let statusColor = ''
|
||||
if (delay > 2000) {
|
||||
color = '#F44336'
|
||||
} else if (delay > 500) {
|
||||
color = '#d68100'
|
||||
} else {
|
||||
color = '#188038'
|
||||
}
|
||||
if (status >= 500) {
|
||||
statusColor = '#9C27B0'
|
||||
} else if (status >= 400) {
|
||||
statusColor = '#F44336'
|
||||
} else if (status >= 300) {
|
||||
statusColor = '#FF9800'
|
||||
} else if (status >= 200) {
|
||||
statusColor = '#188038'
|
||||
} else if (status >= 100) {
|
||||
statusColor = '#03A9F4'
|
||||
}
|
||||
return `<span style='color:${statusColor}'>${status}</span> | <span style='color:${color}'>${delay}ms</span>`
|
||||
} catch {
|
||||
return "<span style='color:#F44336'>timeout</span>"
|
||||
} finally {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
25
model/State/NodeInfo.js
Normal file
25
model/State/NodeInfo.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { getFileSize, Circle } from './utils.js'
|
||||
import os from 'os'
|
||||
|
||||
/** 获取nodejs内存情况 */
|
||||
export default function getNodeInfo () {
|
||||
let memory = process.memoryUsage()
|
||||
// 总共
|
||||
let rss = getFileSize(memory.rss)
|
||||
// 堆
|
||||
let heapTotal = getFileSize(memory.heapTotal)
|
||||
// 栈
|
||||
let heapUsed = getFileSize(memory.heapUsed)
|
||||
// 占用率
|
||||
let occupy = (memory.rss / (os.totalmem() - os.freemem())).toFixed(2)
|
||||
return {
|
||||
...Circle(occupy),
|
||||
inner: Math.round(occupy * 100) + '%',
|
||||
title: 'Node',
|
||||
info: [
|
||||
`总 ${rss}`,
|
||||
`堆 ${heapTotal}`,
|
||||
`栈 ${heapUsed}`
|
||||
]
|
||||
}
|
||||
}
|
||||
25
model/State/PluginNum.js
Normal file
25
model/State/PluginNum.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import fs from 'fs'
|
||||
|
||||
export default function getPluginNum () {
|
||||
let str = './plugins'
|
||||
let arr = fs.readdirSync(str)
|
||||
let plugin = []
|
||||
arr.forEach((val) => {
|
||||
let ph = fs.statSync(str + '/' + val)
|
||||
if (ph.isDirectory()) {
|
||||
plugin.push(val)
|
||||
}
|
||||
})
|
||||
let del = ['example', 'genshin', 'other', 'system', 'bin']
|
||||
plugin = plugin.filter(item => !del.includes(item))
|
||||
const plugins = plugin?.length || 0
|
||||
const js = fs.readdirSync('./plugins/example')?.filter(item => item.includes('.js'))?.length || 0
|
||||
// return {
|
||||
// plugins: plugin?.length || 0,
|
||||
// js: fs.readdirSync('./plugins/example')?.filter(item => item.includes('.js'))?.length || 0
|
||||
// }
|
||||
return {
|
||||
first: '插件',
|
||||
tail: `${plugins} plugin | ${js} js`
|
||||
}
|
||||
}
|
||||
25
model/State/RAM.js
Normal file
25
model/State/RAM.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { getFileSize, Circle } from './utils.js'
|
||||
import os from 'os'
|
||||
|
||||
/** 获取当前内存占用 */
|
||||
export default function getMemUsage () {
|
||||
// 内存使用率
|
||||
let MemUsage = (1 - os.freemem() / os.totalmem()).toFixed(2)
|
||||
// 空闲内存
|
||||
let freemem = getFileSize(os.freemem())
|
||||
// 总共内存
|
||||
let totalmem = getFileSize(os.totalmem())
|
||||
// 使用内存
|
||||
let Usingmemory = getFileSize((os.totalmem() - os.freemem()))
|
||||
|
||||
return {
|
||||
...Circle(MemUsage),
|
||||
inner: Math.round(MemUsage * 100) + '%',
|
||||
title: 'RAM',
|
||||
info: [
|
||||
`总共 ${totalmem}`,
|
||||
`已用 ${Usingmemory}`,
|
||||
`空闲 ${freemem}`
|
||||
]
|
||||
}
|
||||
}
|
||||
73
model/State/index.js
Normal file
73
model/State/index.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import _ from 'lodash'
|
||||
import common from '../../lib/common/common.js'
|
||||
import getBotState from './BotState.js'
|
||||
import getCPU from './CPU.js'
|
||||
import { osInfo, si } from './DependencyChecker.js'
|
||||
import getFastFetch from './FastFetch.js'
|
||||
import getFsSize from './FsSize.js'
|
||||
import getGPU from './GPU.js'
|
||||
import Monitor from './Monitor.js'
|
||||
import getNetworTestList from './NetworkLatency.js'
|
||||
import getNodeInfo from './NodeInfo.js'
|
||||
import getPluginNum from './PluginNum.js'
|
||||
import getRAM from './RAM.js'
|
||||
|
||||
export { osInfo, si }
|
||||
|
||||
export async function getData (e) {
|
||||
// 可视化数据
|
||||
let visualData = _.compact(await Promise.all([
|
||||
// CPU板块
|
||||
getCPU(),
|
||||
// 内存板块
|
||||
getRAM(),
|
||||
// GPU板块
|
||||
getGPU(),
|
||||
// Node板块
|
||||
getNodeInfo()
|
||||
]))
|
||||
let promiseTaskList = [
|
||||
getFastFetch(e),
|
||||
getFsSize()
|
||||
]
|
||||
|
||||
let NetworTestList = getNetworTestList()
|
||||
promiseTaskList.push(NetworTestList)
|
||||
|
||||
let [FastFetch, HardDisk, psTest] = await Promise.all(promiseTaskList)
|
||||
/** bot列表 */
|
||||
let BotList = [e.self_id]
|
||||
|
||||
if (e.msg.includes('pro')) {
|
||||
if (Array.isArray(Bot?.uin)) {
|
||||
BotList = Bot.uin
|
||||
} else if (Bot?.adapter && Bot.adapter.includes(e.self_id)) {
|
||||
BotList = Bot.adapter
|
||||
}
|
||||
}
|
||||
return {
|
||||
BotStatus: await getBotState(BotList),
|
||||
chartData: JSON.stringify(common.checkIfEmpty(Monitor.chartData, ['echarts_theme', 'cpu', 'ram']) ? undefined : Monitor.chartData),
|
||||
visualData,
|
||||
otherInfo: _getOtherInfo(),
|
||||
psTest,
|
||||
FastFetch,
|
||||
HardDisk,
|
||||
// 硬盘速率
|
||||
fsStats: Monitor.DiskSpeed
|
||||
}
|
||||
}
|
||||
|
||||
function _getOtherInfo () {
|
||||
let otherInfo = []
|
||||
// 其他信息
|
||||
otherInfo.push({
|
||||
first: '系统',
|
||||
tail: osInfo?.distro
|
||||
})
|
||||
// 网络
|
||||
otherInfo.push(Monitor.getNetwork)
|
||||
// 插件数量
|
||||
otherInfo.push(getPluginNum())
|
||||
return otherInfo
|
||||
}
|
||||
66
model/State/utils.js
Normal file
66
model/State/utils.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import _ from 'lodash'
|
||||
|
||||
/**
|
||||
* 向数组中添加数据,如果数组长度超过允许的最大值,则删除最早添加的数据
|
||||
* @param {Array} arr - 要添加数据的数组
|
||||
* @param {*} data - 要添加的新数据
|
||||
* @param {number} [maxLen] - 数组允许的最大长度,默认值为60
|
||||
* @returns {void}
|
||||
*/
|
||||
export function addData (arr, data, maxLen = 60) {
|
||||
if (data === null || data === undefined) return
|
||||
// 如果数组长度超过允许的最大值,删除第一个元素
|
||||
if (arr.length >= maxLen) {
|
||||
_.pullAt(arr, 0)
|
||||
}
|
||||
// 添加新数据
|
||||
arr.push(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件大小从字节转化为可读性更好的格式,例如B、KB、MB、GB、TB。
|
||||
* @param {number} size - 带转化的字节数。
|
||||
* @param {boolean} [isByte] - 如果为 true,则最终的文件大小显示保留 B 的后缀.
|
||||
* @param {boolean} [isSuffix] - 如果为 true,则在所得到的大小后面加上 kb、mb、gb、tb 等后缀.
|
||||
* @returns {string} 文件大小格式转换后的字符串.
|
||||
*/
|
||||
export function getFileSize (size, isByte = true, isSuffix = true) { // 把字节转换成正常文件大小
|
||||
if (size == null || size == undefined) return 0
|
||||
let num = 1024.00 // byte
|
||||
if (isByte && size < num) {
|
||||
return size.toFixed(2) + 'B'
|
||||
}
|
||||
if (size < Math.pow(num, 2)) {
|
||||
return (size / num).toFixed(2) + `K${isSuffix ? 'b' : ''}`
|
||||
} // kb
|
||||
if (size < Math.pow(num, 3)) {
|
||||
return (size / Math.pow(num, 2)).toFixed(2) + `M${isSuffix ? 'b' : ''}`
|
||||
} // M
|
||||
if (size < Math.pow(num, 4)) {
|
||||
return (size / Math.pow(num, 3)).toFixed(2) + 'G'
|
||||
} // G
|
||||
return (size / Math.pow(num, 4)).toFixed(2) + 'T' // T
|
||||
}
|
||||
|
||||
/**
|
||||
* 圆形进度条渲染
|
||||
* @param {number} res 百分比小数
|
||||
* @returns {*} css样式
|
||||
*/
|
||||
export function Circle (res) {
|
||||
let num = (res * 360).toFixed(0)
|
||||
let color = 'var(--low-color)'
|
||||
if (res >= 0.9) {
|
||||
color = 'var(--high-color)'
|
||||
} else if (res >= 0.8) {
|
||||
color = 'var(--medium-color)'
|
||||
}
|
||||
let leftCircle = `style="transform:rotate(-180deg);background:${color};"`
|
||||
let rightCircle = `style="transform:rotate(360deg);background:${color};"`
|
||||
if (num > 180) {
|
||||
leftCircle = `style="transform:rotate(${num}deg);background:${color};"`
|
||||
} else {
|
||||
rightCircle = `style="transform:rotate(-${180 - num}deg);background:${color};"`
|
||||
}
|
||||
return { leftCircle, rightCircle }
|
||||
}
|
||||
@@ -26,4 +26,4 @@ async function execSync (cmd) {
|
||||
})
|
||||
}
|
||||
|
||||
export { cronValidate, formatDuration, sagiri, translateChinaNum, uploadRecord, sleep, execSync }
|
||||
export { cronValidate, formatDuration, sagiri, translateChinaNum, uploadRecord, sleep, execSync }
|
||||
|
||||
Reference in New Issue
Block a user