mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-09-29 00:32:06 +08:00
* bugfix:修复内存泄露和信号量饥饿问题 * 修复图片渲染按高度截断问题 * 优化图片渲染速度 * 权限检查去掉无效引用代码 * 添加uv支持 * 🚨 auto fix by pre-commit hooks * bugfix:修改gitignore换行 * bugfix:修复测试没有新生成uv.lock * 修复导入错误 * bugfix:移除重复调用 * 🚨 auto fix by pre-commit hooks * 清理残余poetry引用 * 更新uv安装方式 * 修复阿里云获取问题 * 增加资源下载提示 * 🚨 auto fix by pre-commit hooks * 修改资源下载为流式 * 🚨 auto fix by pre-commit hooks * 提高启动速度 * 移除bot.py支持 * 🚨 auto fix by pre-commit hooks * 优化win脚本逻辑 * 🚨 auto fix by pre-commit hooks * 清理残余无效逻辑 * 代码改进 * 🚨 auto fix by pre-commit hooks * 增加数据库迁移存在性检查 * 🚨 auto fix by pre-commit hooks * chore(test): 添加pytest超时控制和优雅关闭机制 - 在GitHub Actions工作流中添加作业级和步骤级超时限制,防止测试无限期挂起 - 添加pytest-timeout依赖并配置全局超时为120秒 - 在send_queue服务添加关闭钩子,确保worker任务正确取消 - 在priority_manager添加on_shutdown钩子,支持优先级生命周期的关闭阶段 * chore(lint): 禁用超长行的lint警告 * Modify restart logic for Windows platform * 🚨 auto fix by pre-commit hooks * bugfix:修复sys导入问题 * 清理无效结构 * bugfix:修复路径问题 * bugfix:修复shell语法传递给git导致资源获取失败问题 * 优化关闭显示 * bugfix:修复路径问题 * bugfix:增加路径安全 * bugfix:修复orm绕过问题 * 放宽numpy版本限制 * 修改重启方案 * bugfix:修复循环导入 * 优化逻辑 * Enhance disconnect function with error handling Added error handling for disconnect function and imported ConfigurationError. * Implement emergency restart mechanism Added emergency restart mechanism using atexit to ensure process restart even on severe exceptions during shutdown. * 🚨 auto fix by pre-commit hooks * 重启行为归一化 * 修复测试检测问题 * bugfix:修复测试侧类型报错问题 * 引入launcher机制 * 移除重启测试 * 收紧缓存调用路径 * 类型注解收敛 * 优化浏览器回收行为 * 优化浏览器渲染 * bugfix:解决重复关闭浏览器问题 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: ManyManyTomato <93612024+ATTomatoo@users.noreply.github.com> Co-authored-by: AkashiCoin <l1040186796@gmail.com>
459 lines
15 KiB
Python
459 lines
15 KiB
Python
from typing import Any
|
|
|
|
from nonebot.adapters import Bot
|
|
|
|
from zhenxun import ui
|
|
from zhenxun.models.group_console import GroupConsole
|
|
from zhenxun.models.plugin_info import PluginInfo
|
|
from zhenxun.models.task_info import TaskInfo
|
|
from zhenxun.ui.models import LayoutData, StatusBadgeCell, TextCell
|
|
from zhenxun.utils.enum import PluginType
|
|
from zhenxun.utils.exception import GroupConsoleNotFound
|
|
from zhenxun.utils.platform import PlatformUtils
|
|
|
|
from .strategy import get_strategy
|
|
|
|
|
|
async def build_plugin() -> bytes:
|
|
"""构造插件状态图片"""
|
|
column_name = [
|
|
"ID",
|
|
"模块",
|
|
"名称",
|
|
"全局状态",
|
|
"禁用类型",
|
|
"加载状态",
|
|
"菜单分类",
|
|
"作者",
|
|
"版本",
|
|
"金币花费",
|
|
]
|
|
plugin_list = await PluginInfo.get_plugins(
|
|
load_status=None,
|
|
filter_parent=False,
|
|
plugin_type__not=PluginType.HIDDEN,
|
|
)
|
|
rows = []
|
|
for plugin in plugin_list:
|
|
status_cell = StatusBadgeCell(
|
|
text="开启" if plugin.status else "关闭",
|
|
status_type="ok" if plugin.status else "error",
|
|
)
|
|
load_cell = StatusBadgeCell(
|
|
text="SUCCESS" if plugin.load_status else "ERROR",
|
|
status_type="ok" if plugin.load_status else "error",
|
|
)
|
|
rows.append(
|
|
[
|
|
plugin.id,
|
|
plugin.module,
|
|
plugin.name,
|
|
status_cell,
|
|
plugin.block_type.value if plugin.block_type else "-",
|
|
load_cell,
|
|
plugin.menu_type or "-",
|
|
plugin.author or "-",
|
|
plugin.version or "-",
|
|
plugin.cost_gold,
|
|
]
|
|
)
|
|
|
|
table = ui.table("Plugin List", "插件状态概览")
|
|
table.set_headers(column_name)
|
|
table.add_rows(rows)
|
|
table.set_column_widths(
|
|
[
|
|
"60px",
|
|
"150px",
|
|
"150px",
|
|
"80px",
|
|
"100px",
|
|
"100px",
|
|
"100px",
|
|
"100px",
|
|
"80px",
|
|
"80px",
|
|
]
|
|
)
|
|
return await ui.render(table, viewport={"width": 1400, "height": 10})
|
|
|
|
|
|
async def build_task(group_id: str | None) -> bytes:
|
|
"""构造被动技能状态图片"""
|
|
task_list = await TaskInfo.get_tasks(load_status=None)
|
|
column_name = ["ID", "模块", "名称", "群组状态", "全局状态", "运行时间"]
|
|
group = None
|
|
if group_id:
|
|
group = await GroupConsole.get_group_db(group_id=group_id)
|
|
if not group:
|
|
raise GroupConsoleNotFound()
|
|
else:
|
|
column_name.remove("群组状态")
|
|
rows = []
|
|
for task in task_list:
|
|
global_status_cell = StatusBadgeCell(
|
|
text="开启" if task.status else "关闭",
|
|
status_type="ok" if task.status else "error",
|
|
)
|
|
row = [task.id, task.module, task.name]
|
|
if group:
|
|
is_group_open = f"<{task.module}," not in group.block_task
|
|
group_status_cell = StatusBadgeCell(
|
|
text="开启" if is_group_open else "关闭",
|
|
status_type="ok" if is_group_open else "error",
|
|
)
|
|
row.append(group_status_cell)
|
|
row.extend([global_status_cell, task.run_time or "-"])
|
|
rows.append(row)
|
|
|
|
table = ui.table("Task List", "被动技能状态概览")
|
|
table.set_headers(column_name)
|
|
table.add_rows(rows)
|
|
if group:
|
|
table.set_column_widths(["60px", "150px", "150px", "100px", "100px", "auto"])
|
|
viewport_width = 1200
|
|
else:
|
|
table.set_column_widths(["60px", "150px", "150px", "100px", "auto"])
|
|
viewport_width = 1000
|
|
|
|
return await ui.render(table, viewport={"width": viewport_width, "height": 10})
|
|
|
|
|
|
async def render_global_status(name: str, is_task: bool, bot: Bot) -> bytes:
|
|
"""渲染全局状态报表,含差异化过滤和双栏展示"""
|
|
strategy = get_strategy(is_task)
|
|
info = await strategy.get_entity(name)
|
|
if not info:
|
|
raise ValueError(f"未找到{strategy.entity_type_name}: {name}")
|
|
|
|
module = info.module
|
|
default_status = info.status
|
|
|
|
online_groups, _ = await PlatformUtils.get_group_list(bot)
|
|
valid_keys = {(str(g.group_id), g.channel_id) for g in online_groups}
|
|
|
|
all_db_groups = await GroupConsole.all()
|
|
target_groups = [
|
|
g for g in all_db_groups if (str(g.group_id), g.channel_id) in valid_keys
|
|
]
|
|
|
|
total_count = len(target_groups)
|
|
status_data = []
|
|
for group in target_groups:
|
|
gid = str(group.group_id)
|
|
is_su_blocked, is_norm_blocked = await strategy.check_block_status(gid, module)
|
|
is_open = bool(default_status) and not is_su_blocked and not is_norm_blocked
|
|
|
|
if not default_status:
|
|
status_text, badge_color = "全局关闭", "error"
|
|
elif is_su_blocked:
|
|
status_text, badge_color = "系统禁用", "error"
|
|
elif is_norm_blocked:
|
|
status_text, badge_color = "群内关闭", "warning"
|
|
else:
|
|
status_text, badge_color = "开启", "success"
|
|
|
|
status_data.append(
|
|
{
|
|
"id": str(group.group_id),
|
|
"name": group.group_name,
|
|
"status": is_open,
|
|
"status_text": status_text,
|
|
"badge_color": badge_color,
|
|
}
|
|
)
|
|
|
|
open_list = [item for item in status_data if item["status"]]
|
|
close_list = [item for item in status_data if not item["status"]]
|
|
open_count = len(open_list)
|
|
open_rate = open_count / total_count if total_count > 0 else 0
|
|
|
|
global_alert = None
|
|
if not default_status:
|
|
global_alert = ui.alert(
|
|
"全局已禁用",
|
|
f"{strategy.entity_type_name} [{name}] 当前处于全局关闭状态。",
|
|
type="error",
|
|
)
|
|
|
|
display_list = []
|
|
list_title = "群组状态详情"
|
|
if total_count > 0 and default_status:
|
|
if open_rate > 0.9:
|
|
display_list, list_title = (
|
|
close_list,
|
|
f"异常状态列表 (其余 {open_count} 个群均正常开启)",
|
|
)
|
|
elif open_rate < 0.1:
|
|
display_list, list_title = (
|
|
open_list,
|
|
f"异常状态列表 (其余 {len(close_list)} 个群均已禁用)",
|
|
)
|
|
else:
|
|
display_list = sorted(status_data, key=lambda x: not x["status"])
|
|
|
|
return await build_dashboard_report(
|
|
page_title=f"{strategy.entity_type_name}状态报告: {name}",
|
|
total_count=total_count,
|
|
active_count=open_count,
|
|
inactive_count=len(close_list),
|
|
active_rate=open_rate,
|
|
active_label="已开启",
|
|
active_color="var(--color-accent-green)",
|
|
inactive_label="已关闭",
|
|
inactive_color="var(--color-accent-red)",
|
|
progress_label=f"功能 [{name}] 全局覆盖率",
|
|
summary_tip=(
|
|
f"总群数: {total_count} | 🟢 开启: {open_count} | "
|
|
f"🔴 关闭: {len(close_list)}"
|
|
),
|
|
display_list=display_list,
|
|
list_title=list_title,
|
|
global_alert=global_alert,
|
|
perfect_state_alert=ui.alert(
|
|
"状态完美", f"所有 {total_count} 个群组状态一致。", type="success"
|
|
)
|
|
if not global_alert
|
|
else None,
|
|
)
|
|
|
|
|
|
async def render_group_active_status(bot: Bot) -> bytes:
|
|
"""渲染群组醒来/休眠状态报表"""
|
|
online_groups, _ = await PlatformUtils.get_group_list(bot)
|
|
valid_keys = {(str(g.group_id), g.channel_id) for g in online_groups}
|
|
all_db_groups = await GroupConsole.all()
|
|
target_groups = [
|
|
g for g in all_db_groups if (str(g.group_id), g.channel_id) in valid_keys
|
|
]
|
|
|
|
total_count = len(target_groups)
|
|
status_data = [
|
|
{
|
|
"id": str(group.group_id),
|
|
"name": group.group_name,
|
|
"status": group.status,
|
|
"status_text": "工作中" if group.status else "休息中",
|
|
"badge_color": "success" if group.status else "info",
|
|
}
|
|
for group in target_groups
|
|
]
|
|
|
|
wake_list = [item for item in status_data if item["status"]]
|
|
sleep_list = [item for item in status_data if not item["status"]]
|
|
wake_rate = len(wake_list) / total_count if total_count > 0 else 0
|
|
|
|
display_list, list_title = status_data, "群组状态详情"
|
|
if wake_rate > 0.9:
|
|
display_list, list_title = (
|
|
sleep_list,
|
|
f"休息中的群组 (其余 {len(wake_list)} 个群正常工作中)",
|
|
)
|
|
elif wake_rate < 0.1:
|
|
display_list, list_title = (
|
|
wake_list,
|
|
f"工作中/已醒来的群组 (其余 {len(sleep_list)} 个群休息中)",
|
|
)
|
|
|
|
return await build_dashboard_report(
|
|
page_title="真寻工作状态统计",
|
|
total_count=total_count,
|
|
active_count=len(wake_list),
|
|
inactive_count=len(sleep_list),
|
|
active_rate=wake_rate,
|
|
active_label="当前工作中",
|
|
active_color="var(--color-accent-green)",
|
|
inactive_label="当前休息中",
|
|
inactive_color="var(--color-text-muted)",
|
|
progress_label="全服群组活跃覆盖率",
|
|
display_list=display_list,
|
|
list_title=list_title,
|
|
no_record_alert=ui.alert("无记录", "当前没有已加入的群组记录。", type="info"),
|
|
perfect_state_alert=ui.alert(
|
|
"状态统一",
|
|
(
|
|
f"所有 {total_count} 个群组当前均处于 "
|
|
f"{'工作中' if wake_rate > 0.5 else '休息中'} 状态。"
|
|
),
|
|
type="success",
|
|
),
|
|
)
|
|
|
|
|
|
async def build_dashboard_report(
|
|
page_title: str,
|
|
total_count: int,
|
|
active_count: int,
|
|
inactive_count: int,
|
|
active_rate: float,
|
|
active_label: str,
|
|
active_color: str,
|
|
inactive_label: str,
|
|
inactive_color: str,
|
|
progress_label: str,
|
|
display_list: list[dict],
|
|
list_title: str,
|
|
summary_tip: str = "",
|
|
global_alert: Any = None,
|
|
no_record_alert: Any = None,
|
|
perfect_state_alert: Any = None,
|
|
) -> bytes:
|
|
"""通用的 Dashboard 报表构建器,用于替代原先冗余的 UI 代码"""
|
|
|
|
kpi_row = LayoutData.row(gap="12px", align_items="stretch")
|
|
|
|
def _build_kpi_card(title: str, value: str, val_color: str):
|
|
header = LayoutData.row(justify_content="space-between", width="100%")
|
|
header.add_item(
|
|
ui.text(title, font_size="13px", color="var(--color-text-muted)")
|
|
)
|
|
if title != "总群数" and title != "管理群总数":
|
|
rate_str = (
|
|
f"{active_rate:.1%}"
|
|
if "已开启" in title or "当前工作" in title
|
|
else f"{(1 - active_rate):.1%}"
|
|
)
|
|
header.add_item(
|
|
ui.text(rate_str, font_size="13px", bold=True, color=val_color)
|
|
)
|
|
|
|
content = ui.vstack(
|
|
[
|
|
header.build()
|
|
if "已开启" in title or "已关闭" in title
|
|
else ui.text(title, font_size="13px", color="var(--color-text-muted)"),
|
|
ui.text(value, font_size="24px", bold=True, color=val_color),
|
|
],
|
|
gap="2px",
|
|
align_items="start" if "总" in title else "stretch",
|
|
padding="0",
|
|
)
|
|
|
|
return ui.card(content).with_inline_style({"--card-padding": "12px 16px"})
|
|
|
|
kpi_row.add_item(
|
|
_build_kpi_card(
|
|
"总群数" if "功能" in progress_label else "管理群总数",
|
|
str(total_count),
|
|
"var(--color-text-dark)",
|
|
),
|
|
metadata={"flex": True},
|
|
)
|
|
kpi_row.add_item(
|
|
_build_kpi_card(active_label, str(active_count), active_color),
|
|
metadata={"flex": True},
|
|
)
|
|
kpi_row.add_item(
|
|
_build_kpi_card(inactive_label, str(inactive_count), inactive_color),
|
|
metadata={"flex": True},
|
|
)
|
|
|
|
progress_scheme = "primary" if "功能" in progress_label else "success"
|
|
progress_section = ui.vstack(
|
|
[
|
|
ui.text(progress_label, font_size="14px", color="var(--color-text-muted)"),
|
|
ui.progress_bar(
|
|
progress=active_rate * 100,
|
|
label=f"{active_count}/{total_count}",
|
|
color_scheme=progress_scheme,
|
|
),
|
|
],
|
|
gap="8px",
|
|
)
|
|
|
|
content_area = None
|
|
if not display_list:
|
|
if total_count == 0 and no_record_alert:
|
|
content_area = no_record_alert
|
|
elif global_alert and "功能" in progress_label:
|
|
content_area = global_alert
|
|
elif perfect_state_alert:
|
|
content_area = perfect_state_alert
|
|
elif len(display_list) <= 15:
|
|
rows = []
|
|
for item in display_list:
|
|
status_cell = StatusBadgeCell(
|
|
text=item["status_text"], status_type=item["badge_color"]
|
|
)
|
|
rows.append(
|
|
[
|
|
TextCell(content=str(item["id"])),
|
|
TextCell(content=str(item["name"])),
|
|
status_cell,
|
|
]
|
|
)
|
|
content_area = (
|
|
ui.table(list_title, None)
|
|
.set_headers(["群号", "群名", "状态"])
|
|
.set_column_widths(["160px", "auto", "100px"])
|
|
.add_rows(rows)
|
|
)
|
|
else:
|
|
grid = LayoutData.grid(columns=3, gap="15px")
|
|
MAX_SHOW = 60
|
|
for item in display_list[:MAX_SHOW]:
|
|
card_content = ui.vstack(
|
|
[
|
|
ui.text(str(item["name"]), bold=True, font_size="15px"),
|
|
LayoutData.row(justify_content="space-between", width="100%")
|
|
.add_item(ui.text(str(item["id"]), font_size="12px", color="#999"))
|
|
.add_item(
|
|
ui.badge(item["status_text"], color_scheme=item["badge_color"])
|
|
),
|
|
],
|
|
gap="8px",
|
|
align_items="start",
|
|
)
|
|
grid.add_item(ui.card(card_content))
|
|
|
|
container = LayoutData.column(gap="10px")
|
|
container.add_item(grid.build())
|
|
if len(display_list) > MAX_SHOW:
|
|
container.add_item(
|
|
ui.text(
|
|
f"... 还有 {len(display_list) - MAX_SHOW} 个群组未显示 ...",
|
|
align="center",
|
|
color="#ccc",
|
|
)
|
|
)
|
|
content_area = container.build()
|
|
|
|
main_layout = LayoutData.column(padding="40px", gap="30px")
|
|
main_layout.add_item(
|
|
ui.text(
|
|
page_title,
|
|
font_size="32px",
|
|
bold=True,
|
|
align="center",
|
|
color="var(--color-primary)",
|
|
)
|
|
)
|
|
|
|
stats_items = []
|
|
if global_alert and "功能" in progress_label:
|
|
stats_items.append(global_alert)
|
|
|
|
stats_items.extend(
|
|
[
|
|
kpi_row.build(),
|
|
ui.divider(margin="15px 0"),
|
|
progress_section,
|
|
]
|
|
)
|
|
|
|
if summary_tip:
|
|
stats_items.append(
|
|
ui.text(
|
|
summary_tip,
|
|
font_size="13px",
|
|
color="var(--color-text-muted)",
|
|
align="center",
|
|
)
|
|
)
|
|
|
|
main_layout.add_item(ui.card(ui.vstack(stats_items)))
|
|
if content_area:
|
|
main_layout.add_item(content_area)
|
|
|
|
return await ui.render(main_layout.build(), viewport={"width": 900, "height": 10})
|