@koishi-ce/plugin-status 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2 -2
- package/dist/style.css +1 -1
- package/lib/index.d.ts +8 -8
- package/locales/de-DE.yml +9 -9
- package/locales/en-US.yml +8 -8
- package/locales/fr-FR.yml +9 -9
- package/locales/ja-JP.yml +6 -6
- package/locales/ru-RU.yml +9 -9
- package/package.json +4 -4
- package/src/envinfo.ts +23 -7
- package/src/index.test.ts +58 -18
- package/src/profile.ts +22 -10
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Schema as e,Universal as t,capitalize as n,icons as r,message as i,router as a,store as o,useConfig as ee,withProxy as s}from"@koishi-ce/client";import{Fragment as c,computed as l,createBlock as u,createCommentVNode as d,createElementBlock as f,createElementVNode as p,createTextVNode as m,createVNode as h,defineComponent as g,inject as _,normalizeClass as v,normalizeStyle as y,openBlock as b,renderList as x,resolveComponent as S,toDisplayString as C,unref as w,withCtx as T}from"vue";var te=g({__name:`analytics`,setup(e){let t=_(`component:analytic-number`),n=l(()=>Object.values(o.status.bots).reduce((e,t)=>e+t.messageReceived,0)/60),r=l(()=>Object.values(o.analytics.messageByDate).slice(-7).reduce((e,t)=>e+t.receive,0)/7/24/60/60);return(e,i)=>(b(),u(w(t),{icon:`analytic:pulse`,title:`当前 QPS`},{default:T(()=>[m(C(+n.value.toFixed(2)),1)]),"footer-left":T(()=>[...i[0]||=[m(`近期 QPS`,-1)]]),"footer-right":T(()=>[m(C(+r.value.toFixed(2)),1)]),_:1}))}}),E=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n},D={},O={class:`status-light`};function ne(e,t){return b(),f(`span`,O)}var k=E(D,[[`render`,ne],[`__scopeId`,`data-v-bf35056f`]]),A={OFFLINE:0,ONLINE:1,CONNECT:2,DISCONNECT:3,RECONNECT:4};function j(e){switch(e){case A.OFFLINE:return`offline`;case A.ONLINE:return`online`;case A.CONNECT:return`connect`;case A.DISCONNECT:return`disconnect`;case A.RECONNECT:return`reconnect`}}var M={class:`bot-view`},N={class:`info`},P=[`title`],F=[`title`],I={class:`truncate cur-frequency`},L={style:{"margin-right":`8px`}},R=E(g({__name:`preview`,props:{data:{}},setup(e){let n={[t.Status.ONLINE]:`运行中`,[t.Status.OFFLINE]:`离线`,[t.Status.CONNECT]:`正在连接`,[t.Status.RECONNECT]:`正在重连`,[t.Status.DISCONNECT]:`正在断开`};return(t,r)=>{let i=S(`el-tooltip`),a=S(`k-icon`);return b(),f(`section`,M,[p(`div`,{class:`avatar`,style:y({backgroundImage:`url(${w(s)(e.data.user.avatar)})`}),onClick:r[0]||=e=>t.$emit(`avatar-click`)},[h(i,{content:n[e.data.status],placement:`right`},{default:T(()=>[h(k,{class:v(w(j)(e.data.status))},null,8,[`class`])]),_:1},8,[`content`])],4),p(`div`,N,[p(`div`,{class:`truncate`,title:e.data.user.name},[h(a,{name:`robot`}),m(C(e.data.user.name),1)],8,P),p(`div`,{class:`truncate`,title:e.data.platform},[h(a,{name:`platform`}),m(C(e.data.platform),1)],8,F),p(`div`,I,[p(`span`,L,[h(a,{name:`arrow-up`}),p(`span`,null,C(e.data.messageSent)+`/min`,1)]),p(`span`,null,[h(a,{name:`arrow-down`}),p(`span`,null,C(e.data.messageReceived)+`/min`,1)])])])])}}}),[[`__scopeId`,`data-v-0d87ef44`]]),z={key:0,class:`el-popper__empty`},B={class:`count`},V=E(g({__name:`index`,setup(e){let t=ee(),n=l(()=>{let e={};for(let t of Object.values(o.status.bots)){let n=j(t.status);e[n]=(e[n]||0)+1}return Object.fromEntries(Object.entries(e).sort((e,t)=>e[0].localeCompare(t[0])))}),r=l(()=>Object.values(o.status.bots).reduce((e,t)=>e+t.messageSent,0)),i=l(()=>Object.values(o.status.bots).reduce((e,t)=>e+t.messageReceived,0));return(e,ee)=>{let s=S(`k-icon`),l=S(`k-status`);return w(o).status?(b(),u(l,{key:0},{tooltip:T(()=>[Object.values(w(o).status.bots).length?d(`v-if`,!0):(b(),f(`span`,z)),(b(!0),f(c,null,x(w(o).status.bots,(e,t)=>(b(),u(R,{key:t,data:e,class:v({"has-link":e.paths?.length}),onClick:t=>w(a).push(`/plugins/`+e.paths[0].replace(/\./,`/`))},null,8,[`data`,`class`,`onClick`]))),128))]),default:T(()=>[(b(!0),f(c,null,x(n.value,(e,n)=>(b(),f(c,{key:n},[e>(w(t).mergeThreshold??10)?(b(),f(c,{key:0},[h(k,{class:v(n)},null,8,[`class`]),p(`span`,B,`×`+C(e),1)],64)):(b(!0),f(c,{key:1},x(Array(e),(e,t)=>(b(),u(k,{key:t,class:v(n)},null,8,[`class`]))),128))],64))),128)),h(s,{name:`arrow-up`}),p(`span`,null,C(r.value)+`/min`,1),h(s,{name:`arrow-down`}),p(`span`,null,C(i.value)+`/min`,1)]),_:1})):d(`v-if`,!0)}}}),[[`__scopeId`,`data-v-
|
|
1
|
+
import{Schema as e,Universal as t,capitalize as n,icons as r,message as i,router as a,store as o,useConfig as ee,withProxy as s}from"@koishi-ce/client";import{Fragment as c,computed as l,createBlock as u,createCommentVNode as d,createElementBlock as f,createElementVNode as p,createTextVNode as m,createVNode as h,defineComponent as g,inject as _,normalizeClass as v,normalizeStyle as y,openBlock as b,renderList as x,resolveComponent as S,toDisplayString as C,unref as w,withCtx as T}from"vue";var te=g({__name:`analytics`,setup(e){let t=_(`component:analytic-number`),n=l(()=>Object.values(o.status.bots).reduce((e,t)=>e+t.messageReceived,0)/60),r=l(()=>Object.values(o.analytics.messageByDate).slice(-7).reduce((e,t)=>e+t.receive,0)/7/24/60/60);return(e,i)=>(b(),u(w(t),{icon:`analytic:pulse`,title:`当前 QPS`},{default:T(()=>[m(C(+n.value.toFixed(2)),1)]),"footer-left":T(()=>[...i[0]||=[m(`近期 QPS`,-1)]]),"footer-right":T(()=>[m(C(+r.value.toFixed(2)),1)]),_:1}))}}),E=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n},D={},O={class:`status-light`};function ne(e,t){return b(),f(`span`,O)}var k=E(D,[[`render`,ne],[`__scopeId`,`data-v-bf35056f`]]),A={OFFLINE:0,ONLINE:1,CONNECT:2,DISCONNECT:3,RECONNECT:4};function j(e){switch(e){case A.OFFLINE:return`offline`;case A.ONLINE:return`online`;case A.CONNECT:return`connect`;case A.DISCONNECT:return`disconnect`;case A.RECONNECT:return`reconnect`}}var M={class:`bot-view`},N={class:`info`},P=[`title`],F=[`title`],I={class:`truncate cur-frequency`},L={style:{"margin-right":`8px`}},R=E(g({__name:`preview`,props:{data:{}},setup(e){let n={[t.Status.ONLINE]:`运行中`,[t.Status.OFFLINE]:`离线`,[t.Status.CONNECT]:`正在连接`,[t.Status.RECONNECT]:`正在重连`,[t.Status.DISCONNECT]:`正在断开`};return(t,r)=>{let i=S(`el-tooltip`),a=S(`k-icon`);return b(),f(`section`,M,[p(`div`,{class:`avatar`,style:y({backgroundImage:`url(${w(s)(e.data.user.avatar)})`}),onClick:r[0]||=e=>t.$emit(`avatar-click`)},[h(i,{content:n[e.data.status],placement:`right`},{default:T(()=>[h(k,{class:v(w(j)(e.data.status))},null,8,[`class`])]),_:1},8,[`content`])],4),p(`div`,N,[p(`div`,{class:`truncate`,title:e.data.user.name},[h(a,{name:`robot`}),m(C(e.data.user.name),1)],8,P),p(`div`,{class:`truncate`,title:e.data.platform},[h(a,{name:`platform`}),m(C(e.data.platform),1)],8,F),p(`div`,I,[p(`span`,L,[h(a,{name:`arrow-up`}),p(`span`,null,C(e.data.messageSent)+`/min`,1)]),p(`span`,null,[h(a,{name:`arrow-down`}),p(`span`,null,C(e.data.messageReceived)+`/min`,1)])])])])}}}),[[`__scopeId`,`data-v-0d87ef44`]]),z={key:0,class:`el-popper__empty`},B={class:`count`},V=E(g({__name:`index`,setup(e){let t=ee(),n=l(()=>{let e={};for(let t of Object.values(o.status.bots)){let n=j(t.status);e[n]=(e[n]||0)+1}return Object.fromEntries(Object.entries(e).sort((e,t)=>e[0].localeCompare(t[0])))}),r=l(()=>Object.values(o.status.bots).reduce((e,t)=>e+t.messageSent,0)),i=l(()=>Object.values(o.status.bots).reduce((e,t)=>e+t.messageReceived,0));return(e,ee)=>{let s=S(`k-icon`),l=S(`k-status`);return w(o).status?(b(),u(l,{key:0},{tooltip:T(()=>[Object.values(w(o).status.bots).length?d(`v-if`,!0):(b(),f(`span`,z)),(b(!0),f(c,null,x(w(o).status.bots,(e,t)=>(b(),u(R,{key:t,data:e,class:v({"has-link":e.paths?.length}),onClick:t=>w(a).push(`/plugins/`+e.paths[0].replace(/\./,`/`))},null,8,[`data`,`class`,`onClick`]))),128))]),default:T(()=>[(b(!0),f(c,null,x(n.value,(e,n)=>(b(),f(c,{key:n},[e>(w(t).mergeThreshold??10)?(b(),f(c,{key:0},[h(k,{class:v(n)},null,8,[`class`]),p(`span`,B,`×`+C(e),1)],64)):(b(!0),f(c,{key:1},x(Array(e),(e,t)=>(b(),u(k,{key:t,class:v(n)},null,8,[`class`]))),128))],64))),128)),h(s,{name:`arrow-up`}),p(`span`,null,C(r.value)+`/min`,1),h(s,{name:`arrow-down`}),p(`span`,null,C(i.value)+`/min`,1)]),_:1})):d(`v-if`,!0)}}}),[[`__scopeId`,`data-v-a2e0e762`]]),H=e=>{e.slot({type:`status-right`,component:V})},U={class:`bots-container flex flex-wrap gap-4`},W=E(g({__name:`config`,setup(e){let t=_(`manager.settings.current`),n=l(()=>Object.values(o.status?.bots||{}).filter(e=>e.paths?.includes(t.value.path)));return(e,t)=>n.value?.length?(b(),f(c,{key:0},[t[0]||=p(`h2`,{class:`k-schema-header`},` 机器人 `,-1),p(`div`,U,[(b(!0),f(c,null,x(n.value,(e,t)=>(b(),u(R,{key:t,data:e},null,8,[`data`]))),128))])],64)):d(`v-if`,!0)}}),[[`__scopeId`,`data-v-3117e871`]]);async function G(e){try{return navigator.clipboard.writeText(e)}catch{let t=document.createElement(`textarea`),n=document.activeElement;t.value=e,t.setAttribute(`readonly`,``),t.style.contain=`strict`,t.style.position=`absolute`,t.style.left=`-9999px`,t.style.fontSize=`12pt`;let r=document.getSelection(),i=r?r.rangeCount>0&&r.getRangeAt(0):null;document.body.appendChild(t),t.select(),t.selectionStart=0,t.selectionEnd=e.length,document.execCommand(`copy`),document.body.removeChild(t),i&&r&&(r.removeAllRanges(),r.addRange(i)),n&&n.focus()}}var K={class:`title`},q=E(g({__name:`envinfo`,setup(e){async function t(){await G(Object.entries(o.envinfo).map(([e,t])=>`${n(e)}:\n`+Object.entries(t).map(([e,t])=>` ${e}: ${t}`).join(`
|
|
2
2
|
`)).join(`
|
|
3
3
|
|
|
4
|
-
`)),i.success(`已复制环境信息!`)}return(e,r)=>{let i=S(`k-status`);return w(o).envinfo?(b(),u(i,{key:0,class:`version`,onClick:t},{tooltip:T(()=>[(b(!0),f(c,null,x(w(o).envinfo,(e,t)=>(b(),f(`div`,{class:`section`,key:t},[p(`p`,K,C(w(n)(t))+`:`,1),(b(!0),f(c,null,x(e,(e,t)=>(b(),f(`p`,{class:`info`,key:t},C(t)+`: `+C(e),1))),128))]))),128))]),default:T(()=>[m(` Koishi v`+C(w(o).envinfo.koishi.Core)+` `,1)]),_:1})):d(`v-if`,!0)}}}),[[`__scopeId`,`data-v-
|
|
4
|
+
`)),i.success(`已复制环境信息!`)}return(e,r)=>{let i=S(`k-status`);return w(o).envinfo?(b(),u(i,{key:0,class:`version`,onClick:t},{tooltip:T(()=>[(b(!0),f(c,null,x(w(o).envinfo,(e,t)=>(b(),f(`div`,{class:`section`,key:t},[p(`p`,K,C(w(n)(t))+`:`,1),(b(!0),f(c,null,x(e,(e,t)=>(b(),f(`p`,{class:`info`,key:t},C(t)+`: `+C(e),1))),128))]))),128))]),default:T(()=>[m(` Koishi v`+C(w(o).envinfo.koishi.Core)+` `,1)]),_:1})):d(`v-if`,!0)}}}),[[`__scopeId`,`data-v-52da28fa`]]),J={class:`load-bar`},Y={class:`title`},X={class:`body`},Z=E(g({__name:`load-bar`,props:{rate:{},title:{}},setup(e){let t=e;function n(e,t=1){return`${+(e*100).toFixed(t)}%`}let r=[`used`,`app`,`free`],i=l(()=>[t.rate[1]-t.rate[0],t.rate[0],1-t.rate[1]]),a=l(()=>i.value.findIndex(e=>e>=.5)),o=l(()=>`${n(t.rate[0],1)} / ${n(t.rate[1],1)}`);return(t,ee)=>(b(),f(`div`,J,[p(`span`,Y,C(e.title),1),p(`span`,X,[(b(),f(c,null,x(r,(e,t)=>p(`span`,{key:e,class:v([e,`bar`]),style:y({width:n(i.value[t])})},[t===a.value?(b(),f(c,{key:0},[m(C(o.value),1)],64)):a.value===-1&&i.value[t]>=.2?(b(),f(c,{key:1},[m(C(n(i.value[t])),1)],64)):d(`v-if`,!0)],6)),64))])]))}}),[[`__scopeId`,`data-v-c6c8a320`]]);function Q(e,t=3){return`${(e*100).toFixed(t)}%`}var re={class:`load`},ie=E(g({__name:`index`,setup(e){return(e,t)=>{let n=S(`k-status`);return w(o).status?(b(),u(n,{key:0},{tooltip:T(()=>[p(`div`,re,[h(Z,{title:`CPU`,rate:w(o).status.cpu},null,8,[`rate`]),h(Z,{title:`内存`,rate:w(o).status.memory},null,8,[`rate`])])]),default:T(()=>[m(` CPU: `+C(w(Q)(w(o).status.cpu[1],0))+` 内存: `+C(w(Q)(w(o).status.memory[1],0)),1)]),_:1})):d(`v-if`,!0)}}}),[[`__scopeId`,`data-v-7fdf3e68`]]),ae=e=>{e.slot({type:`status-right`,component:ie})},oe={},se={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 384 512`};function ce(e,t){return b(),f(`svg`,se,[...t[0]||=[p(`path`,{fill:`currentColor`,d:`M377.4 296.6l-168 176C204.8 477.3 198.6 480 192 480s-12.84-2.688-17.38-7.438l-168-176C-2.5 286.1-2.156 271.8 7.438 262.6c9.5-9.156 24.75-8.812 33.94 .8125L168 396.1V56.02c0-13.25 10.75-24.01 23.1-24.01S216 42.77 216 56.02v340.1l126.6-132.7c9.156-9.625 24.41-9.969 33.94-.8125C386.2 271.8 386.5 286.1 377.4 296.6z`},null,-1)]])}var le=E(oe,[[`render`,ce]]),ue={},de={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 384 512`};function fe(e,t){return b(),f(`svg`,de,[...t[0]||=[p(`path`,{fill:`currentColor`,d:`M6.625 215.5l168-176C179.2 34.7 185.4 32.02 192 32.02s12.84 2.688 17.38 7.438l168 176c9.125 9.594 8.781 24.78-.8125 33.94c-9.5 9.156-24.75 8.812-33.94-.8125L216 115.9V456c0 13.25-10.75 23.1-23.1 23.1S168 469.3 168 456V115.9l-126.6 132.7C32.22 258.2 16.97 258.5 7.438 249.4C-2.156 240.2-2.5 225 6.625 215.5z`},null,-1)]])}var pe=E(ue,[[`render`,fe]]),me={},he={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 512 512`};function ge(e,t){return b(),f(`svg`,he,[...t[0]||=[p(`path`,{fill:`currentColor`,d:`M12.41 148.02l232.94 105.67c6.8 3.09 14.49 3.09 21.29 0l232.94-105.67c16.55-7.51 16.55-32.52 0-40.03L266.65 2.31a25.607 25.607 0 0 0-21.29 0L12.41 107.98c-16.55 7.51-16.55 32.53 0 40.04zm487.18 88.28l-58.09-26.33-161.64 73.27c-7.56 3.43-15.59 5.17-23.86 5.17s-16.29-1.74-23.86-5.17L70.51 209.97l-58.1 26.33c-16.55 7.5-16.55 32.5 0 40l232.94 105.59c6.8 3.08 14.49 3.08 21.29 0L499.59 276.3c16.55-7.5 16.55-32.5 0-40zm0 127.8l-57.87-26.23-161.86 73.37c-7.56 3.43-15.59 5.17-23.86 5.17s-16.29-1.74-23.86-5.17L70.29 337.87 12.41 364.1c-16.55 7.5-16.55 32.5 0 40l232.94 105.59c6.8 3.08 14.49 3.08 21.29 0L499.59 404.1c16.55-7.5 16.55-32.5 0-40z`},null,-1)]])}var _e=E(me,[[`render`,ge]]),ve={},ye={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 640 512`};function be(e,t){return b(),f(`svg`,ye,[...t[0]||=[p(`path`,{fill:`currentColor`,d:`M640 240C640 248.8 632.8 256 624 256h-133.1l-60 150c-2.625 6.375-8.375 10.25-16 9.1C408 415.5 402.1 410.6 400.5 403.9l-78.75-315l-82 410.3C238.3 506.5 231.9 511.8 224.4 512H224c-7.25 0-13.75-5-15.5-12.12L147.5 256H16C7.201 256 0 248.8 0 240s7.201-15.1 16-15.1H160c7.375 0 13.75 4.993 15.5 12.12l46.75 187l82-410.3C305.8 5.5 312 .25 319.5 0c8.125 .125 14.25 4.875 16 12.12l84 336l45.63-114.1C467.5 228 473.5 224 480 224h144C632.8 224 640 231.2 640 240z`},null,-1)]])}var $=E(ve,[[`render`,be]]),xe={},Se={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 640 512`};function Ce(e,t){return b(),f(`svg`,Se,[...t[0]||=[p(`path`,{fill:`currentColor`,d:`M32,224H64V416H32A31.96166,31.96166,0,0,1,0,384V256A31.96166,31.96166,0,0,1,32,224Zm512-48V448a64.06328,64.06328,0,0,1-64,64H160a64.06328,64.06328,0,0,1-64-64V176a79.974,79.974,0,0,1,80-80H288V32a32,32,0,0,1,64,0V96H464A79.974,79.974,0,0,1,544,176ZM264,256a40,40,0,1,0-40,40A39.997,39.997,0,0,0,264,256Zm-8,128H192v32h64Zm96,0H288v32h64ZM456,256a40,40,0,1,0-40,40A39.997,39.997,0,0,0,456,256Zm-8,128H384v32h64ZM640,256V384a31.96166,31.96166,0,0,1-32,32H576V224h32A31.96166,31.96166,0,0,1,640,256Z`},null,-1)]])}var we=E(xe,[[`render`,Ce]]);r.register(`arrow-up`,pe),r.register(`arrow-down`,le),r.register(`platform`,_e),r.register(`analytic:pulse`,$),r.register(`robot`,we);var Te=t=>{t.plugin(H),t.plugin(ae),t.slot({type:`status-left`,component:q}),t.slot({type:`analytic-number`,component:te}),t.slot({type:`plugin-details`,component:W,order:-500}),t.settings({id:`status`,schema:e.object({mergeThreshold:e.number().default(10).description(`当机器人的数量超过这个值时将合并显示状态指示灯。`)}).description(`机器人设置`)})};export{Te as default};
|
package/dist/style.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
.status-light[data-v-bf35056f]{transition:var(--color-transition);border-radius:.625rem;width:.625rem;height:.625rem}.status-light.online[data-v-bf35056f]{background-color:var(--k-color-success)}.status-light.connect[data-v-bf35056f],.status-light.reconnect[data-v-bf35056f]{background-color:var(--k-color-warning)}.status-light.error[data-v-bf35056f]{background-color:var(--k-color-danger)!important}.status-light.offline[data-v-bf35056f]{background-color:var(--k-color-disabled)}.status-light+.status-light[data-v-bf35056f]{margin-left:4px}.bot-view[data-v-0d87ef44]{width:15rem;padding:.75rem 1rem;font-size:14px;transition:all .3s;display:flex}.bot-view+.bot-view[data-v-0d87ef44]{border-top:1px solid var(--k-color-divider)}.bot-view.active>div.avatar[data-v-0d87ef44]{border-color:var(--active)}.bot-view>div.avatar[data-v-0d87ef44]{box-sizing:content-box;border:1px solid var(--k-color-divider);background-repeat:no-repeat;background-size:100%;border-radius:100%;flex-shrink:0;width:4rem;height:4rem;transition:all .1s;position:relative}.bot-view>div.avatar .status-light[data-v-0d87ef44]{border:1px solid var(--k-color-divider);width:.875rem;height:.875rem;position:absolute;bottom:-1px;right:-1px}.bot-view>div.info[data-v-0d87ef44]{flex-direction:column;flex-grow:1;justify-content:space-around;margin-left:1.25rem;display:flex;overflow:hidden}.bot-view>div.info .k-icon[data-v-0d87ef44]{text-align:center;vertical-align:-2px;width:20px;margin-right:6px}.bot-view.has-link[data-v-0d87ef44]{cursor:pointer}.bot-view.has-link[data-v-0d87ef44]:hover{background-color:var(--bg1)}.k-status .k-icon[data-v-
|
|
1
|
+
.status-light[data-v-bf35056f]{transition:var(--color-transition);border-radius:.625rem;width:.625rem;height:.625rem}.status-light.online[data-v-bf35056f]{background-color:var(--k-color-success)}.status-light.connect[data-v-bf35056f],.status-light.reconnect[data-v-bf35056f]{background-color:var(--k-color-warning)}.status-light.error[data-v-bf35056f]{background-color:var(--k-color-danger)!important}.status-light.offline[data-v-bf35056f]{background-color:var(--k-color-disabled)}.status-light+.status-light[data-v-bf35056f]{margin-left:4px}.bot-view[data-v-0d87ef44]{width:15rem;padding:.75rem 1rem;font-size:14px;transition:all .3s;display:flex}.bot-view+.bot-view[data-v-0d87ef44]{border-top:1px solid var(--k-color-divider)}.bot-view.active>div.avatar[data-v-0d87ef44]{border-color:var(--active)}.bot-view>div.avatar[data-v-0d87ef44]{box-sizing:content-box;border:1px solid var(--k-color-divider);background-repeat:no-repeat;background-size:100%;border-radius:100%;flex-shrink:0;width:4rem;height:4rem;transition:all .1s;position:relative}.bot-view>div.avatar .status-light[data-v-0d87ef44]{border:1px solid var(--k-color-divider);width:.875rem;height:.875rem;position:absolute;bottom:-1px;right:-1px}.bot-view>div.info[data-v-0d87ef44]{flex-direction:column;flex-grow:1;justify-content:space-around;margin-left:1.25rem;display:flex;overflow:hidden}.bot-view>div.info .k-icon[data-v-0d87ef44]{text-align:center;vertical-align:-2px;width:20px;margin-right:6px}.bot-view.has-link[data-v-0d87ef44]{cursor:pointer}.bot-view.has-link[data-v-0d87ef44]:hover{background-color:var(--bg1)}.k-status .k-icon[data-v-a2e0e762]{margin-right:4px}.k-status *+.k-icon[data-v-a2e0e762]{margin-left:6px}.k-status .count[data-v-a2e0e762]{letter-spacing:1px;margin:0 4px}.bots-container .bot-view[data-v-3117e871]{background-color:var(--bg0);border-radius:.5rem}p[data-v-52da28fa]{margin:8px;padding:0 6px;font-size:12px;line-height:1}.info[data-v-52da28fa]{padding-left:20px}.load-bar[data-v-c6c8a320]{-webkit-user-select:none;user-select:none;align-items:center;margin:.5rem 0;font-size:.9em;display:flex}.load-bar .title[data-v-c6c8a320]{min-width:3em}.load-bar .body[data-v-c6c8a320]{background-color:var(--k-c-divisor);width:10rem;height:.8rem;transition:var(--color-transition);color:var(--fg1);border-radius:1rem;font-size:10px;display:inline;position:relative;overflow:hidden}.load-bar .bar[data-v-c6c8a320]{float:left;white-space:pre;justify-content:center;align-items:center;height:100%;display:flex;position:relative}.load-bar .used[data-v-c6c8a320]{background-color:var(--primary);color:#fff;transition:color .3s,background-color .3s}.load-bar .used[data-v-c6c8a320]:hover{background-color:var(--primary-tint,var(--primary))}.load-bar .app[data-v-c6c8a320]{background-color:var(--k-color-warning);transition:color .3s,background-color .3s}.load-bar .app[data-v-c6c8a320]:hover{background-color:var(--k-color-warning-tint,var(--k-color-warning))}.load[data-v-7fdf3e68]{padding:.25rem 1rem}.absolute{position:absolute}.relative{position:relative}.inline{display:inline}.hidden{display:none}.h2{height:.5rem}.flex{display:flex}.flex-shrink{flex-shrink:1}.flex-grow{flex-grow:1}.flex-wrap{flex-wrap:wrap}.gap-4{gap:1rem}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.border{border-width:1px}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease{transition-timing-function:cubic-bezier(.4,0,.2,1)}
|
|
2
2
|
/*$vite$:1*/
|
package/lib/index.d.ts
CHANGED
|
@@ -45,7 +45,7 @@ declare class TickCounter {
|
|
|
45
45
|
static initialize(bot: Bot, ctx: Context): void;
|
|
46
46
|
}
|
|
47
47
|
/** 负载率二元组:[app, total],app 为本进程占比,total 为整机占比,取值 0~1。 */
|
|
48
|
-
type LoadRate = [app: number, total: number];
|
|
48
|
+
export type LoadRate = [app: number, total: number];
|
|
49
49
|
/**
|
|
50
50
|
* 性能数据服务:以 DataService 形式向前端广播 Payload,
|
|
51
51
|
* 前端经 store.status 读取(键名即服务名 "status")。
|
|
@@ -84,7 +84,7 @@ declare namespace ProfileProvider {
|
|
|
84
84
|
//#endregion
|
|
85
85
|
//#region src/index.d.ts
|
|
86
86
|
/** 频道活跃度记录:键为小时数(0~23),值为该小时内触发频道的次数。 */
|
|
87
|
-
type Activity = Record<number, number>;
|
|
87
|
+
export type Activity = Record<number, number>;
|
|
88
88
|
declare module "@koishi-ce/koishi" {
|
|
89
89
|
interface Channel {
|
|
90
90
|
name: string;
|
|
@@ -99,10 +99,10 @@ declare module "@koishi-ce/console" {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
declare const name = "status";
|
|
103
|
-
declare const inject: string[];
|
|
104
|
-
interface Config extends ProfileProvider.Config, EnvInfoProvider.Config {}
|
|
105
|
-
declare const Config: Schema<Config>;
|
|
102
|
+
export declare const name = "status";
|
|
103
|
+
export declare const inject: string[];
|
|
104
|
+
export interface Config extends ProfileProvider.Config, EnvInfoProvider.Config {}
|
|
105
|
+
export declare const Config: Schema<Config>;
|
|
106
106
|
/**
|
|
107
107
|
* 插件入口:注册控制台前端资源(dev 指向 client 源码,prod 指向构建产物),
|
|
108
108
|
* 再将环境信息与性能采集两个服务作为子插件挂载,共享同一份配置。
|
|
@@ -110,6 +110,6 @@ declare const Config: Schema<Config>;
|
|
|
110
110
|
* @param ctx 应用上下文
|
|
111
111
|
* @param config 插件配置(两个子服务配置的交集合并)
|
|
112
112
|
*/
|
|
113
|
-
declare function apply(ctx: Context, config: Config): void;
|
|
113
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
114
114
|
//#endregion
|
|
115
|
-
export {
|
|
115
|
+
export { EnvInfoProvider, ProfileProvider };
|
package/locales/de-DE.yml
CHANGED
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
commands:
|
|
6
6
|
status:
|
|
7
|
-
description:
|
|
7
|
+
description: Status anzeigen
|
|
8
8
|
messages:
|
|
9
9
|
status:
|
|
10
|
-
1:
|
|
11
|
-
0:
|
|
12
|
-
2:
|
|
13
|
-
4:
|
|
14
|
-
3:
|
|
15
|
-
bot: <p>{user.name}
|
|
10
|
+
1: Läuft
|
|
11
|
+
0: Offline
|
|
12
|
+
2: Verbindung wird hergestellt
|
|
13
|
+
4: Verbindung wird wiederhergestellt
|
|
14
|
+
3: Verbindung wird getrennt
|
|
15
|
+
bot: '<p>{user.name}: <i18n path={"commands.status.messages.status." + status}/></p>'
|
|
16
16
|
epilog: |-
|
|
17
17
|
==========
|
|
18
|
-
CPU
|
|
19
|
-
|
|
18
|
+
CPU-Auslastung: { (cpu[0] * 100).toFixed() }% / { (cpu[1] * 100).toFixed() }%
|
|
19
|
+
Speicherauslastung: { (memory[0] * 100).toFixed() }% / { (memory[1] * 100).toFixed() }%
|
package/locales/en-US.yml
CHANGED
|
@@ -7,13 +7,13 @@ commands:
|
|
|
7
7
|
description: Display the status
|
|
8
8
|
messages:
|
|
9
9
|
status:
|
|
10
|
-
1:
|
|
11
|
-
0:
|
|
12
|
-
2:
|
|
13
|
-
4:
|
|
14
|
-
3:
|
|
15
|
-
bot: <p>{user.name}
|
|
10
|
+
1: Running
|
|
11
|
+
0: Offline
|
|
12
|
+
2: Connecting
|
|
13
|
+
4: Reconnecting
|
|
14
|
+
3: Disconnecting
|
|
15
|
+
bot: '<p>{user.name}: <i18n path={"commands.status.messages.status." + status}/></p>'
|
|
16
16
|
epilog: |-
|
|
17
17
|
==========
|
|
18
|
-
CPU
|
|
19
|
-
|
|
18
|
+
CPU usage: { (cpu[0] * 100).toFixed() }% / { (cpu[1] * 100).toFixed() }%
|
|
19
|
+
Memory usage: { (memory[0] * 100).toFixed() }% / { (memory[1] * 100).toFixed() }%
|
package/locales/fr-FR.yml
CHANGED
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
commands:
|
|
6
6
|
status:
|
|
7
|
-
description:
|
|
7
|
+
description: Afficher le statut
|
|
8
8
|
messages:
|
|
9
9
|
status:
|
|
10
|
-
1:
|
|
11
|
-
0:
|
|
12
|
-
2:
|
|
13
|
-
4:
|
|
14
|
-
3:
|
|
15
|
-
bot: <p>{user.name}
|
|
10
|
+
1: En cours d'exécution
|
|
11
|
+
0: Hors ligne
|
|
12
|
+
2: Connexion en cours
|
|
13
|
+
4: Reconnexion en cours
|
|
14
|
+
3: Déconnexion en cours
|
|
15
|
+
bot: '<p>{user.name} : <i18n path={"commands.status.messages.status." + status}/></p>'
|
|
16
16
|
epilog: |-
|
|
17
17
|
==========
|
|
18
|
-
CPU
|
|
19
|
-
|
|
18
|
+
Utilisation du CPU : { (cpu[0] * 100).toFixed() }% / { (cpu[1] * 100).toFixed() }%
|
|
19
|
+
Utilisation de la mémoire : { (memory[0] * 100).toFixed() }% / { (memory[1] * 100).toFixed() }%
|
package/locales/ja-JP.yml
CHANGED
|
@@ -7,13 +7,13 @@ commands:
|
|
|
7
7
|
description: 実行状態を確認する
|
|
8
8
|
messages:
|
|
9
9
|
status:
|
|
10
|
-
1:
|
|
11
|
-
0:
|
|
12
|
-
2:
|
|
13
|
-
4:
|
|
14
|
-
3:
|
|
10
|
+
1: 実行中
|
|
11
|
+
0: オフライン
|
|
12
|
+
2: 接続中
|
|
13
|
+
4: 再接続中
|
|
14
|
+
3: 切断中
|
|
15
15
|
bot: <p>{user.name}:<i18n path={"commands.status.messages.status." + status}/></p>
|
|
16
16
|
epilog: |-
|
|
17
17
|
==========
|
|
18
18
|
CPU 使用率:{ (cpu[0] * 100).toFixed() }% / { (cpu[1] * 100).toFixed() }%
|
|
19
|
-
|
|
19
|
+
メモリ使用率:{ (memory[0] * 100).toFixed() }% / { (memory[1] * 100).toFixed() }%
|
package/locales/ru-RU.yml
CHANGED
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
commands:
|
|
6
6
|
status:
|
|
7
|
-
description:
|
|
7
|
+
description: Показать статус
|
|
8
8
|
messages:
|
|
9
9
|
status:
|
|
10
|
-
1:
|
|
11
|
-
0:
|
|
12
|
-
2:
|
|
13
|
-
4:
|
|
14
|
-
3:
|
|
15
|
-
bot: <p>{user.name}
|
|
10
|
+
1: Работает
|
|
11
|
+
0: Не в сети
|
|
12
|
+
2: Подключение
|
|
13
|
+
4: Переподключение
|
|
14
|
+
3: Отключение
|
|
15
|
+
bot: '<p>{user.name}: <i18n path={"commands.status.messages.status." + status}/></p>'
|
|
16
16
|
epilog: |-
|
|
17
17
|
==========
|
|
18
|
-
CPU
|
|
19
|
-
|
|
18
|
+
Использование CPU: { (cpu[0] * 100).toFixed() }% / { (cpu[1] * 100).toFixed() }%
|
|
19
|
+
Использование памяти: { (memory[0] * 100).toFixed() }% / { (memory[1] * 100).toFixed() }%
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koishi-ce/plugin-status",
|
|
3
3
|
"description": "Status view for Koishi",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.mjs",
|
|
7
7
|
"typings": "lib/index.d.ts",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"@koishi-ce/koishi": "^1.0.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@koishi-ce/client": "^1.0
|
|
54
|
-
"@koishi-ce/koishi": "^1.0.
|
|
55
|
-
"@koishi-ce/plugin-analytics": "^1.0
|
|
53
|
+
"@koishi-ce/client": "^1.1.0",
|
|
54
|
+
"@koishi-ce/koishi": "^1.0.8",
|
|
55
|
+
"@koishi-ce/plugin-analytics": "^1.1.0"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@koishi-ce/console": "^1.0.0"
|
package/src/envinfo.ts
CHANGED
|
@@ -13,16 +13,26 @@
|
|
|
13
13
|
import { readFile } from "node:fs/promises";
|
|
14
14
|
import { arch, cpus, platform, release } from "node:os";
|
|
15
15
|
import { DataService } from "@koishi-ce/console";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
type Context,
|
|
18
|
+
type Dict,
|
|
19
|
+
Schema,
|
|
20
|
+
version,
|
|
21
|
+
} from "@koishi-ce/koishi";
|
|
17
22
|
|
|
18
|
-
class EnvInfoProvider extends DataService<
|
|
23
|
+
class EnvInfoProvider extends DataService<
|
|
24
|
+
Dict<Dict<string>>
|
|
25
|
+
> {
|
|
19
26
|
/** 采集任务的缓存:get() 首次调用时创建,之后始终复用同一 Promise。 */
|
|
20
27
|
private task!: Promise<Dict<Dict<string>>>;
|
|
21
28
|
|
|
22
29
|
// 基类链(cordis Service)上已有 config 成员,故需 override
|
|
23
30
|
override config: EnvInfoProvider.Config;
|
|
24
31
|
|
|
25
|
-
constructor(
|
|
32
|
+
constructor(
|
|
33
|
+
ctx: Context,
|
|
34
|
+
config: EnvInfoProvider.Config,
|
|
35
|
+
) {
|
|
26
36
|
super(ctx, "envinfo");
|
|
27
37
|
this.config = config;
|
|
28
38
|
}
|
|
@@ -42,15 +52,20 @@ class EnvInfoProvider extends DataService<Dict<Dict<string>>> {
|
|
|
42
52
|
};
|
|
43
53
|
// 不直接 require package.json 是为了避免其被模块缓存固定住,
|
|
44
54
|
// 热更新后读取到的仍是旧版本号
|
|
45
|
-
const metapath = require.resolve(
|
|
46
|
-
|
|
55
|
+
const metapath = require.resolve(
|
|
56
|
+
"@koishi-ce/console/package.json",
|
|
57
|
+
);
|
|
58
|
+
const meta = await readFile(metapath, "utf8").then(
|
|
59
|
+
JSON.parse,
|
|
60
|
+
);
|
|
47
61
|
const koishi: Dict<string> = {
|
|
48
62
|
Core: version,
|
|
49
63
|
Console: meta.version,
|
|
50
64
|
};
|
|
51
65
|
// 宿主代理(如 koishi-plugin-browser)可通过该环境变量上报自身名称与版本
|
|
52
66
|
if (process.env["KOISHI_AGENT"]) {
|
|
53
|
-
const [name, agentVersion] =
|
|
67
|
+
const [name, agentVersion] =
|
|
68
|
+
process.env["KOISHI_AGENT"].split("/");
|
|
54
69
|
if (name && agentVersion) {
|
|
55
70
|
koishi[name] = agentVersion;
|
|
56
71
|
}
|
|
@@ -66,7 +81,8 @@ class EnvInfoProvider extends DataService<Dict<Dict<string>>> {
|
|
|
66
81
|
|
|
67
82
|
// erasableSyntaxOnly 禁止含运行时值的 namespace,
|
|
68
83
|
// 原 namespace 内的 Config 常量移到此处的静态字段,对外形状不变
|
|
69
|
-
static Config: Schema<EnvInfoProvider.Config> =
|
|
84
|
+
static Config: Schema<EnvInfoProvider.Config> =
|
|
85
|
+
Schema.object({});
|
|
70
86
|
}
|
|
71
87
|
|
|
72
88
|
namespace EnvInfoProvider {
|
package/src/index.test.ts
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
3
|
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
afterAll,
|
|
7
|
+
beforeAll,
|
|
8
|
+
describe,
|
|
9
|
+
expect,
|
|
10
|
+
it,
|
|
11
|
+
} from "bun:test";
|
|
6
12
|
import { Console, type Entry } from "@koishi-ce/console";
|
|
7
13
|
import { App, type Plugin } from "@koishi-ce/koishi";
|
|
8
14
|
import mock from "@koishi-ce/plugin-mock";
|
|
@@ -16,7 +22,10 @@ import {
|
|
|
16
22
|
|
|
17
23
|
/** 控制台服务桩:仅实现入口登记所需的最小面。 */
|
|
18
24
|
class FakeConsole extends Console {
|
|
19
|
-
protected resolveEntry(
|
|
25
|
+
protected resolveEntry(
|
|
26
|
+
_files: Entry.Files,
|
|
27
|
+
_key: string,
|
|
28
|
+
): string[] {
|
|
20
29
|
return [];
|
|
21
30
|
}
|
|
22
31
|
}
|
|
@@ -27,15 +36,19 @@ const app = new App();
|
|
|
27
36
|
app.plugin(memory as unknown as typeof memory.default);
|
|
28
37
|
// Console 基类的 static inject 是 cordis 3 旧形态({ optional: [...] }),
|
|
29
38
|
// 与 Plugin.Constructor 期待的 Dict<Meta> 索引签名不兼容,仅做类型层转型
|
|
30
|
-
app.plugin(
|
|
39
|
+
app.plugin(
|
|
40
|
+
FakeConsole as unknown as Plugin.Constructor<App>,
|
|
41
|
+
);
|
|
31
42
|
// tickInterval 取小值,驱动 ready 定时器尽快完成首次 CPU 采样与刷新
|
|
32
43
|
app.plugin(status, { tickInterval: 50 });
|
|
33
44
|
app.plugin(mock);
|
|
34
45
|
|
|
35
46
|
const client = app.mock.client("123", "321");
|
|
36
47
|
|
|
37
|
-
const profile = () =>
|
|
38
|
-
|
|
48
|
+
const profile = () =>
|
|
49
|
+
app.get("console.services.status") as ProfileProviderType;
|
|
50
|
+
const envinfo = () =>
|
|
51
|
+
app.get("console.services.envinfo") as EnvInfoProvider;
|
|
39
52
|
|
|
40
53
|
beforeAll(async () => {
|
|
41
54
|
await app.start();
|
|
@@ -49,24 +62,39 @@ afterAll(async () => {
|
|
|
49
62
|
|
|
50
63
|
describe("status 插件", () => {
|
|
51
64
|
it("加载后同时挂载 envinfo 与 status 两个数据服务", () => {
|
|
52
|
-
expect(
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
expect(
|
|
66
|
+
app.get("console.services.envinfo"),
|
|
67
|
+
).toBeDefined();
|
|
68
|
+
expect(
|
|
69
|
+
app.get("console.services.status"),
|
|
70
|
+
).toBeDefined();
|
|
71
|
+
expect(
|
|
72
|
+
Object.keys(app.console.entries).length,
|
|
73
|
+
).toBeGreaterThan(0);
|
|
55
74
|
});
|
|
56
75
|
|
|
57
76
|
it("envinfo 采集系统 / 运行时 / Koishi 三组信息并缓存", async () => {
|
|
58
77
|
const data = await envinfo().get();
|
|
59
|
-
expect(Object.keys(data)).toEqual([
|
|
78
|
+
expect(Object.keys(data)).toEqual([
|
|
79
|
+
"system",
|
|
80
|
+
"binaries",
|
|
81
|
+
"koishi",
|
|
82
|
+
]);
|
|
60
83
|
// 索引签名属性走方括号访问,undefined 安全由断言本身兜底
|
|
61
84
|
expect(data["system"]?.["OS"]).toBeTruthy();
|
|
62
|
-
expect(data["binaries"]?.["Node"]).toBe(
|
|
85
|
+
expect(data["binaries"]?.["Node"]).toBe(
|
|
86
|
+
process.versions.node,
|
|
87
|
+
);
|
|
63
88
|
expect(data["koishi"]?.["Core"]).toBeTruthy();
|
|
64
89
|
expect(data["koishi"]?.["Console"]).toBeTruthy();
|
|
65
90
|
|
|
66
91
|
// 首次采集后缓存:内部 task 不再更换
|
|
67
|
-
const before =
|
|
92
|
+
const before =
|
|
93
|
+
envinfo()["task" as keyof EnvInfoProvider];
|
|
68
94
|
await envinfo().get();
|
|
69
|
-
expect(envinfo()["task" as keyof EnvInfoProvider]).toBe(
|
|
95
|
+
expect(envinfo()["task" as keyof EnvInfoProvider]).toBe(
|
|
96
|
+
before,
|
|
97
|
+
);
|
|
70
98
|
});
|
|
71
99
|
|
|
72
100
|
it("envinfo 上报 KOISHI_AGENT 宿主代理信息", async () => {
|
|
@@ -81,7 +109,9 @@ describe("status 插件", () => {
|
|
|
81
109
|
|
|
82
110
|
it("profile 采集内存 / CPU 负载与机器人收发速率", async () => {
|
|
83
111
|
// 等待首个 tick 完成 CPU 差值采样
|
|
84
|
-
await new Promise((resolve) =>
|
|
112
|
+
await new Promise((resolve) =>
|
|
113
|
+
setTimeout(resolve, 120),
|
|
114
|
+
);
|
|
85
115
|
const data = await profile().get();
|
|
86
116
|
expect(data.memory).toHaveLength(2);
|
|
87
117
|
expect(data.cpu).toHaveLength(2);
|
|
@@ -90,7 +120,9 @@ describe("status 插件", () => {
|
|
|
90
120
|
expect(Number.isFinite(rate)).toBe(true);
|
|
91
121
|
}
|
|
92
122
|
// mock 机器人不隐藏,应出现在列表中
|
|
93
|
-
expect(Object.keys(data.bots).length).toBeGreaterThan(
|
|
123
|
+
expect(Object.keys(data.bots).length).toBeGreaterThan(
|
|
124
|
+
0,
|
|
125
|
+
);
|
|
94
126
|
});
|
|
95
127
|
|
|
96
128
|
it("get 每次重算且结构稳定(cached 恒为 undefined 的死分支)", async () => {
|
|
@@ -100,7 +132,9 @@ describe("status 插件", () => {
|
|
|
100
132
|
// 源码从不写入 cached 字段,非强制调用同样重算
|
|
101
133
|
expect(svc.cached).toBeUndefined();
|
|
102
134
|
expect(Object.keys(second)).toEqual(Object.keys(first));
|
|
103
|
-
expect(Object.keys(second.bots)).toEqual(
|
|
135
|
+
expect(Object.keys(second.bots)).toEqual(
|
|
136
|
+
Object.keys(first.bots),
|
|
137
|
+
);
|
|
104
138
|
const forced = await svc.get(true);
|
|
105
139
|
expect(Object.keys(forced)).toEqual(Object.keys(first));
|
|
106
140
|
});
|
|
@@ -112,7 +146,9 @@ describe("status 插件", () => {
|
|
|
112
146
|
const receivedBefore = bot?._messageReceived.get() ?? 0;
|
|
113
147
|
// 收到一条用户消息(触发 message 事件),机器人无回复
|
|
114
148
|
await client.receive("ping-should-not-reply");
|
|
115
|
-
expect(bot?._messageReceived.get()).toBe(
|
|
149
|
+
expect(bot?._messageReceived.get()).toBe(
|
|
150
|
+
receivedBefore + 1,
|
|
151
|
+
);
|
|
116
152
|
expect(bot?._messageSent.get()).toBe(before);
|
|
117
153
|
|
|
118
154
|
const data = await profile().get(true);
|
|
@@ -126,7 +162,9 @@ describe("status 插件", () => {
|
|
|
126
162
|
bot?._messageSent.add(1);
|
|
127
163
|
expect(bot?._messageSent.get()).toBe(1);
|
|
128
164
|
// 等待一次以上的窗口滑动(计数移入历史槽位),新计数继续记入队头
|
|
129
|
-
await new Promise((resolve) =>
|
|
165
|
+
await new Promise((resolve) =>
|
|
166
|
+
setTimeout(resolve, 1100),
|
|
167
|
+
);
|
|
130
168
|
bot?._messageSent.add(2);
|
|
131
169
|
expect(bot?._messageSent.get()).toBe(3);
|
|
132
170
|
});
|
|
@@ -153,7 +191,9 @@ describe("status 插件", () => {
|
|
|
153
191
|
});
|
|
154
192
|
|
|
155
193
|
it("两个子服务的 Config 均可解析", () => {
|
|
156
|
-
expect(ProfileProvider.Config({})).toEqual({
|
|
194
|
+
expect(ProfileProvider.Config({})).toEqual({
|
|
195
|
+
tickInterval: 5000,
|
|
196
|
+
});
|
|
157
197
|
expect(EnvInfoProvider.Config({})).toEqual({});
|
|
158
198
|
});
|
|
159
199
|
});
|
package/src/profile.ts
CHANGED
|
@@ -52,7 +52,10 @@ class TickCounter {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
constructor(ctx: Context) {
|
|
55
|
-
this.stop = ctx.setInterval(
|
|
55
|
+
this.stop = ctx.setInterval(
|
|
56
|
+
() => this.tick(),
|
|
57
|
+
Time.second,
|
|
58
|
+
);
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
/** 在当前秒的槽位上累加计数。 */
|
|
@@ -83,7 +86,10 @@ let usedRate: number;
|
|
|
83
86
|
/** 采集内存负载率:[进程 RSS / 总内存, 1 - 空闲内存 / 总内存]。 */
|
|
84
87
|
async function memoryRate(): Promise<LoadRate> {
|
|
85
88
|
const total = totalmem();
|
|
86
|
-
return [
|
|
89
|
+
return [
|
|
90
|
+
process.memoryUsage().rss / total,
|
|
91
|
+
1 - freemem() / total,
|
|
92
|
+
];
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
/**
|
|
@@ -139,7 +145,10 @@ class ProfileProvider extends DataService<ProfileProvider.Payload> {
|
|
|
139
145
|
// 基类链(cordis Service)上已有 config 成员,故需 override
|
|
140
146
|
override config: ProfileProvider.Config;
|
|
141
147
|
|
|
142
|
-
constructor(
|
|
148
|
+
constructor(
|
|
149
|
+
ctx: Context,
|
|
150
|
+
config: ProfileProvider.Config,
|
|
151
|
+
) {
|
|
143
152
|
super(ctx, "status");
|
|
144
153
|
|
|
145
154
|
this.config = config;
|
|
@@ -213,7 +222,9 @@ class ProfileProvider extends DataService<ProfileProvider.Payload> {
|
|
|
213
222
|
const bots: Dict<ProfileProvider.BotData> = {};
|
|
214
223
|
for (const bot of this.ctx.bots) {
|
|
215
224
|
if (bot.hidden) continue;
|
|
216
|
-
const paths = this.ctx
|
|
225
|
+
const paths = this.ctx
|
|
226
|
+
.get("loader")
|
|
227
|
+
?.paths(bot.ctx.scope);
|
|
217
228
|
bots[bot.sid] = {
|
|
218
229
|
...bot.toJSON(),
|
|
219
230
|
error: bot.error?.message,
|
|
@@ -228,12 +239,13 @@ class ProfileProvider extends DataService<ProfileProvider.Payload> {
|
|
|
228
239
|
|
|
229
240
|
// erasableSyntaxOnly 禁止含运行时值的 namespace,
|
|
230
241
|
// 原 namespace 内的 Config 常量移到此处的静态字段,对外形状不变
|
|
231
|
-
static Config: Schema<ProfileProvider.Config> =
|
|
232
|
-
|
|
233
|
-
.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
242
|
+
static Config: Schema<ProfileProvider.Config> =
|
|
243
|
+
Schema.object({
|
|
244
|
+
tickInterval: Schema.natural()
|
|
245
|
+
.role("ms")
|
|
246
|
+
.description("性能数据推送的时间间隔。")
|
|
247
|
+
.default(Time.second * 5),
|
|
248
|
+
});
|
|
237
249
|
}
|
|
238
250
|
|
|
239
251
|
namespace ProfileProvider {
|