refactor: restructure plugins per MTSS standard, consolidate assets to res/

Per docs/plugin-audit-report.md (plugins normalized to
Trigger(handlers) → Service(services) → Model(repository/models) + utils):

- Split monolithic __init__.py into handlers/services/utils across
  dailywife, deer_pipe, dice, galgame_card, helldivers_tools,
  huoziyinshua, learning_chat, makeaquote, mc_server_status,
  ncm_saying, picfinder_take, picstatus, random_jm_code, regif,
  steam_info, video_analysis, group_tools
- Move static assets under res/: deer_pipe font/img, makeaquote font,
  helldivers img/templates, huoziyinshua HuoZiYinShua
- Add config.py + register_config_items to ncm_saying, random_jm_code,
  group_tools; learning_chat unified config bridge
- Remove deprecated: voice_trans plugin, bf_bot/test.py, dead code in
  dailywife/deer_pipe, empty dirs, debug scripts under helldivers temp
- Disable brash_general_supercredits_tools (stub comment only)
- bot.py: optional stdout/stderr redirect to log file for Web log viewer,
  force ANSI colorize on non-TTY sinks
- Move runtime data (jm_code.json) out of plugin dir into hexi/data
- Docs: plugin-audit-report.md; README reflects removed plugins

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-09-03 00:44:38 +08:00
co-authored by Claude
parent 25bc30611f
commit 9371a28e35
2389 changed files with 5100 additions and 10127 deletions
+171 -47
View File
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { Avatar, Button, Card } from '@heroui/react'
import { hubDashboard } from '../../api/client'
import { Avatar, Card } from '@heroui/react'
import { hubDashboard, hubDashboardStream } from '../../api/client'
interface BotInfo {
self_id: string
@@ -52,6 +52,29 @@ interface DiskInfo {
used: number
total: number
}
interface NicInfo {
name: string
up: boolean
sent: number
recv: number
ip: string
}
interface NetworkInfo {
sent: number
recv: number
sent_rate: number
recv_rate: number
packets_sent: number
packets_recv: number
interfaces: NicInfo[]
}
interface BackendInfo {
nonebot_version: string
plugin_count: number
python: string
driver: string
driver_type: string
}
interface DashboardData {
ok: boolean
bots: BotInfo[]
@@ -60,6 +83,8 @@ interface DashboardData {
memory: MemInfo
process: ProcessInfo
disk: DiskInfo[]
network: NetworkInfo
backend: BackendInfo
}
function fmtBytes(n: number): string {
@@ -71,6 +96,11 @@ function fmtBytes(n: number): string {
return v.toFixed(1) + ' ' + units[i]
}
function fmtRate(n: number): string {
if (!Number.isFinite(n) || n < 0) return '0 B/s'
return fmtBytes(n) + '/s'
}
function fmtUptime(sec: number): string {
if (!Number.isFinite(sec)) return '—'
const d = Math.floor(sec / 86400)
@@ -100,6 +130,24 @@ function coreColor(v: number): string {
return 'bg-emerald-500'
}
function InfoRow({ label, value }: { label: string; value: string }) {
return (
<div className="flex items-center justify-between border-b border-slate-100 pb-1.5">
<span className="text-xs text-slate-400">{label}</span>
<span className="text-sm font-medium text-slate-700 text-right">{value || '—'}</span>
</div>
)
}
function SectionTitle({ title, desc }: { title: string; desc: string }) {
return (
<div className="mb-1">
<h3 className="text-base font-semibold text-slate-800">{title}</h3>
<p className="text-xs text-slate-400">{desc}</p>
</div>
)
}
export default function HomePage() {
const [data, setData] = useState<DashboardData | null>(null)
const [error, setError] = useState('')
@@ -124,53 +172,93 @@ export default function HomePage() {
}, [])
useEffect(() => {
refresh(true) // 初始静默加载
const timer = setInterval(() => {
if (document.visibilityState !== 'hidden') refresh(true)
}, 15000)
const onVisible = () => {
if (document.visibilityState === 'visible') refresh(true)
}
document.addEventListener('visibilitychange', onVisible)
return () => {
clearInterval(timer)
document.removeEventListener('visibilitychange', onVisible)
}
refresh(true) // 初始静默加载,快速出首屏
const dispose = hubDashboardStream({
onData: (d) => {
if (d && d.ok !== false) { setData(d); setError('') }
},
})
return () => { dispose() }
}, [refresh])
const cpu = data?.cpu
const mem = data?.memory
const proc = data?.process
const sys = data?.system
const hostSubtitle = [
cpu?.brand,
cpu?.logical && cpu.logical + ' 核',
(data ? sys?.python : ''),
data ? 'NoneBot ' + sys?.nonebot : '',
].filter(Boolean).join(' · ')
const net = data?.network
const backend = data?.backend
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-semibold text-slate-800">总览</h2>
<p className="text-sm text-slate-400">主机与服务状态</p>
<p className="text-sm text-slate-400">主机与 BOT 综合状态(每 2 秒刷新)</p>
</div>
<Button size="sm" isDisabled={loading} onPress={() => refresh(false)}>{loading ? '刷新中…' : '刷新'}</Button>
</div>
{error && !data && <Card className="border border-red-200 bg-red-50"><Card.Content><p className="text-sm text-red-600">{error}</p></Card.Content></Card>}
{/* 主机资源 */}
{/* 1. 主机规格信息:系统 / 处理器 / 内存 / 硬盘 */}
<Card className="border border-slate-200 bg-white">
<Card.Header>
<div className="flex w-full items-center justify-between">
<div>
<Card.Title>主机资源</Card.Title>
<p className="text-xs text-slate-400">{hostSubtitle || '加载中…'}</p>
<SectionTitle title="主机规格信息" desc="系统 / 处理器 / 内存 / 硬盘" />
</Card.Header>
<Card.Content>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<div className="rounded-xl border border-slate-200 p-4">
<div className="mb-2 text-sm font-medium text-slate-700">系统</div>
<div className="space-y-1.5">
<InfoRow label="主机名" value={sys?.hostname || ''} />
<InfoRow label="操作系统" value={sys?.os || ''} />
<InfoRow label="运行时长" value={sys ? fmtUptime(sys.system_uptime_seconds) : '—'} />
</div>
</div>
<div className="rounded-xl border border-slate-200 p-4">
<div className="mb-2 text-sm font-medium text-slate-700">处理器</div>
<div className="space-y-1.5">
<InfoRow label="型号" value={cpu?.brand || ''} />
<InfoRow label="逻辑核心" value={cpu?.logical != null ? cpu.logical + ' 核' : '—'} />
<InfoRow label="物理核心" value={cpu?.physical != null ? cpu.physical + ' 核' : '—'} />
</div>
</div>
<div className="rounded-xl border border-slate-200 p-4">
<div className="mb-2 text-sm font-medium text-slate-700">内存</div>
<div className="space-y-1.5">
<InfoRow label="总量" value={mem ? fmtBytes(mem.total) : '—'} />
<InfoRow label="可用" value={mem ? fmtBytes(mem.available) : '—'} />
<InfoRow label="本进程占用" value={mem ? fmtBytes(mem.process_rss) : '—'} />
</div>
</div>
</div>
<div className="mt-4">
<div className="mb-2 text-sm font-medium text-slate-700">硬盘</div>
{!data ? (
<p className="text-sm text-slate-400">加载中…</p>
) : !data.disk.length ? (
<p className="text-sm text-slate-400">未获取到磁盘信息</p>
) : (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{data.disk.map((d) => (
<div key={d.mount} className="rounded-xl border border-slate-200 p-4">
<div className="flex items-center justify-between">
<span className="truncate text-xs font-medium text-slate-600" title={d.mount}>{d.mount}</span>
<span className="text-xs text-slate-500">{d.percent}%</span>
</div>
<div className="mt-2"><Bar value={d.percent} /></div>
<p className="mt-2 text-xs text-slate-400">{fmtBytes(d.used)} / {fmtBytes(d.total)}</p>
</div>
))}
</div>
)}
</div>
</Card.Content>
</Card>
{/* 2. 资源占用信息:CPU / 内存 / 网络 */}
<Card className="border border-slate-200 bg-white">
<Card.Header>
<SectionTitle title="资源占用信息" desc="CPU / 内存 / 网络实时占用" />
</Card.Header>
<Card.Content>
<div className="grid gap-4 md:grid-cols-3">
@@ -181,9 +269,7 @@ export default function HomePage() {
<span className="text-sm font-semibold text-slate-800">{cpu ? cpu.percent + '%' : '—'}</span>
</div>
<div className="mt-3"><Bar value={cpu?.percent || 0} /></div>
<p className="mt-2 text-xs text-slate-400">
负载: {cpu?.load_avg ? cpu.load_avg.join(' / ') : '—'}
</p>
<p className="mt-2 text-xs text-slate-400">负载: {cpu?.load_avg ? cpu.load_avg.join(' / ') : '—'}</p>
{cpu?.per_core?.length ? (
<div className="mt-3 grid grid-cols-4 gap-2">
{cpu.per_core.map((v, i) => (
@@ -204,35 +290,72 @@ export default function HomePage() {
<span className="text-sm font-semibold text-slate-800">{mem ? mem.percent + '%' : '—'}</span>
</div>
<div className="mt-3"><Bar value={mem?.percent || 0} /></div>
<p className="mt-2 text-xs text-slate-400">
本进程 {mem ? fmtBytes(mem.process_rss) : '—'} · 其他已用 {mem ? fmtBytes(Math.max(0, mem.used - mem.process_rss)) : '—'} · 空间 {mem ? fmtBytes(Math.max(0, mem.total - mem.used)) : '—'}
</p>
<p className="mt-2 text-xs text-slate-400">已用 {mem ? fmtBytes(mem.used) : '—'} · 可用 {mem ? fmtBytes(mem.available) : '—'}</p>
<p className="mt-1 text-xs text-slate-400">共 {mem ? fmtBytes(mem.total) : '—'}</p>
</div>
{/* 进程 */}
{/* 网络 */}
<div className="rounded-xl border border-slate-200 p-4">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-slate-700">运行进程</span>
<span className="text-xs text-slate-500">PID {proc?.pid ?? '—'}</span>
<span className="text-sm font-medium text-slate-700">网络流量</span>
{net?.interfaces?.length ? <span className="text-xs text-slate-400">{net.interfaces.length} 网卡</span> : null}
</div>
<dl className="mt-3 space-y-2 text-xs">
<div className="flex justify-between"><dt className="text-slate-400">RSS</dt><dd className="text-slate-700">{proc ? fmtBytes(proc.rss) : '—'}</dd></div>
<div className="flex justify-between"><dt className="text-slate-400">进程</dt><dd className="text-slate-700">{proc?.name || '—'}</dd></div>
<div className="flex justify-between"><dt className="text-slate-400">CPU</dt><dd className="text-slate-700">{proc?.cpu_percent != null ? proc.cpu_percent + '%' : '—'}</dd></div>
<div className="flex justify-between"><dt className="text-slate-400">内存占用</dt><dd className="text-slate-700">{proc?.memory_percent != null ? proc.memory_percent + '%' : '—'}</dd></div>
<div className="flex justify-between"><dt className="text-slate-400">启动时长</dt><dd className="text-slate-700">{sys ? fmtUptime(sys.bot_uptime_seconds) : '—'}</dd></div>
</dl>
<div className="mt-3 space-y-2 text-xs">
<div className="flex justify-between"><dt className="text-slate-400">↓ 下行速率</dt><dd className="text-slate-700">{net ? fmtRate(net.recv_rate) : '—'}</dd></div>
<div className="flex justify-between"><dt className="text-slate-400">↑ 上行速率</dt><dd className="text-slate-700">{net ? fmtRate(net.sent_rate) : '—'}</dd></div>
<div className="flex justify-between"><dt className="text-slate-400">累计下行</dt><dd className="text-slate-700">{net ? fmtBytes(net.recv) : '—'}</dd></div>
<div className="flex justify-between"><dt className="text-slate-400">累计上行</dt><dd className="text-slate-700">{net ? fmtBytes(net.sent) : '—'}</dd></div>
</div>
</div>
</div>
{/* 本进程 */}
<div className="mt-4 rounded-xl border border-slate-200 p-4">
<div className="mb-2 text-sm font-medium text-slate-700">运行进程(bot)</div>
<dl className="grid grid-cols-2 gap-2 text-xs sm:grid-cols-3 lg:grid-cols-5">
<div className="flex flex-col"><dt className="text-slate-400">PID</dt><dd className="text-slate-700">{proc?.pid ?? '—'}</dd></div>
<div className="flex flex-col"><dt className="text-slate-400">进程名</dt><dd className="text-slate-700">{proc?.name || '—'}</dd></div>
<div className="flex flex-col"><dt className="text-slate-400">CPU</dt><dd className="text-slate-700">{proc?.cpu_percent != null ? proc.cpu_percent + '%' : '—'}</dd></div>
<div className="flex flex-col"><dt className="text-slate-400">内存占用</dt><dd className="text-slate-700">{proc?.memory_percent != null ? proc.memory_percent + '%' : '—'}</dd></div>
<div className="flex flex-col"><dt className="text-slate-400">RSS</dt><dd className="text-slate-700">{proc ? fmtBytes(proc.rss) : '—'}</dd></div>
</dl>
</div>
</Card.Content>
</Card>
{/* 3. BOT 后端信息:NoneBot 版本 / 驱动方式 / 装载插件数 */}
<Card className="border border-slate-200 bg-white">
<Card.Header>
<SectionTitle title="BOT 后端信息" desc="NoneBot 版本 / 驱动方式 / 装载插件数" />
</Card.Header>
<Card.Content>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<div className="rounded-xl border border-slate-200 p-4">
<div className="text-xs text-slate-400">NoneBot 版本</div>
<div className="mt-1 text-lg font-semibold text-slate-800">{backend?.nonebot_version || '—'}</div>
</div>
<div className="rounded-xl border border-slate-200 p-4">
<div className="text-xs text-slate-400">驱动方式</div>
<div className="mt-1 truncate text-sm font-medium text-slate-700" title={backend?.driver}>{backend?.driver || '—'}</div>
<div className="text-xs text-slate-400">{backend?.driver_type || ''}</div>
</div>
<div className="rounded-xl border border-slate-200 p-4">
<div className="text-xs text-slate-400">装载插件数</div>
<div className="mt-1 text-lg font-semibold text-slate-800">{backend?.plugin_count ?? '—'} 个</div>
</div>
<div className="rounded-xl border border-slate-200 p-4">
<div className="text-xs text-slate-400">Python</div>
<div className="mt-1 truncate text-sm font-medium text-slate-700" title={backend?.python}>{backend?.python || '—'}</div>
<div className="text-xs text-slate-400">bot 运行 {sys ? fmtUptime(sys.bot_uptime_seconds) : '—'}</div>
</div>
</div>
</Card.Content>
</Card>
{/* 在线会话 */}
{/* 4. BOT 协议端信息:登录账号昵称 / 账号 / 适配器类型 */}
<Card className="border border-slate-200 bg-white">
<Card.Header>
<Card.Title>在线会话</Card.Title>
<p className="text-xs text-slate-400">当前已接入并完成登录的 QQ 账号</p>
<SectionTitle title="BOT 协议端信息" desc="当前登录的账户昵称 / 账号 / 适配器类型" />
</Card.Header>
<Card.Content>
{!data ? (
@@ -254,6 +377,7 @@ export default function HomePage() {
<div className="flex items-center gap-3">
{bot.msg_recv != null && <span className="text-xs text-slate-400">收 {bot.msg_recv}</span>}
{bot.msg_sent != null && <span className="text-xs text-slate-400">发 {bot.msg_sent}</span>}
{bot.online != null && <span className="text-xs text-slate-400">{bot.online ? '在线' : '离线'}</span>}
<span className="inline-flex items-center gap-1.5 text-xs">
<span className={'size-2 rounded-full ' + (bot.ws_connected ? 'bg-emerald-500' : 'bg-slate-300')} />
<span className={bot.ws_connected ? 'text-emerald-600' : 'text-slate-400'}>{bot.ws_connected ? '已连接' : '未连接'}</span>