- 后端
- 网络
- 运维
- 数据可视化
【免费下载链接】NetAlertX
Centralized network visibility and continuous asset discovery. Monitor devices, detect change, and stay aware across distributed networks.
NetAlertX 通过一套统一的插件系统将第三方脚本、API、数据库查询等数据源接入前端 UI,并进一步触发通知与设备发现。本文以官方 Quick Start 为主线,带你从复制__template模板开始,完成config.json标识符配置、script.py数据采集实现、RUN/CMD执行设置,再到 Dev Container 与 UI 两种方式验证运行结果的全流程。读完本文,你将掌握一个可运行的 NetAlertX 插件的完整骨架,并理解其背后的结果文件数据契约、执行调度机制与源码级排查手段。
前置条件与文档地图
动手前请先确认两件事:
- 本地开发环境就绪:参考 Development Environment Setup Guide(或现成的 devcontainer)搭建可运行代码的环境。开发容器内的标准代码路径为
/workspaces/NetAlertX,容器运行时的插件目录为/app/server/plugins。 - 理解插件架构:先通读 Plugin Architecture Overview,重点把握三个核心概念——
config.json是插件的清单(manifest):描述元数据、执行时机、用户设置、数据列定义与数据库映射;- 结果文件是插件与核心的唯一数据通道:插件把结果写到
/tmp/log/plugins/last_result.<PREFIX>.log,核心读取该文件并按固定格式解析; unique_prefix是插件的身份前缀:所有自动生成的设置键(如MYPLN_RUN、MYPLN_CMD)、结果文件名、日志文件名都以此为基础。
插件运行机制一瞥:执行链与数据契约
在开始写代码前,先花一分钟理解插件被"跑起来"的底层流程,这对后面的调试至关重要。核心执行入口在 server/plugin.py:
plugin_manager.run_plugin_scripts(runType)(server/plugin.py#L67-L118)遍历所有已加载插件,读取其RUN设置值,与当前触发类型(once、schedule、always_after_scan等)比对,决定是否执行;execute_plugin()读取该插件的CMD设置,用subprocess.check_output(command, timeout=set_RUN_TIMEOUT)(server/plugin.py#L370-L384)启动脚本,RUN_TIMEOUT是整个子进程的硬性杀死计时器;- 脚本运行结束后,核心扫描
logPath/plugins/下以last_result.<PREFIX>开头的文件,逐行校验列数必须是 9 或 13(server/plugin.py#L493-L497),不符合的行直接跳过并记录错误; - 合法行随后进入
process_plugin_events()与Plugins_Objects表比对,判定new/watched-changed/watched-not-changed/missing-in-last-scan等状态,并可按mapped_to_table配置把数据写入CurrentScan等核心表。
因此,"写对结果文件"是整个插件系统的最低门槛——这也是快速上手教程把输出格式放在核心位置的原因。
Step 1:创建你的插件文件夹
所有插件都位于server/plugins/目录下,每个插件一个独立文件夹。从模板复制是最快的方式:
cd /workspaces/NetAlertX/server/plugins cp -r __template my_plugin cd my_plugin mv rename_me.py script.py当前仓库中的 server/plugins/__template 目录包含 4 个文件,含义如下:
| 文件 | 作用 |
|---|---|
config.json | 插件清单:元数据、执行设置、数据列定义(必需) |
rename_me.py | 插件逻辑脚本模板,复制后重命名为script.py |
README.md | 插件使用文档(模板占位,需按你的插件实际填写) |
ignore_plugin | 占位标记文件,用于告知扫描机制跳过该目录 |
重要约束:文件夹名必须与
config.json中的code_name完全一致(server/plugin.py 与插件加载逻辑都按此关联)。例如文件夹叫my_plugin,code_name就必须是my_plugin。
Step 2:更新config.json标识符
编辑my_plugin/config.json,把模板里的占位标识符全部换成你自己的:
{ "code_name": "my_plugin", "unique_prefix": "MYPLN", "display_name": [{"language_code": "en_us", "string": "My Plugin"}], "description": [{"language_code": "en_us", "string": "My custom plugin"}] }关键要点:
code_name必须与文件夹名一致,它是插件在系统中的唯一代码标识;unique_prefix必须全局唯一且为大写(模板默认是TMP,务必替换)。它会被用作所有自动生成设置的键前缀(如MYPLN_RUN、MYPLN_CMD)、结果文件前缀last_result.MYPLN.log以及日志文件前缀script.MYPLN.log。命名冲突会直接导致设置串台,检查现有插件(server/plugins 下每个插件的config.json)避免撞名;display_name/description采用多语言结构(language_code+string),是 Settings UI 中展示给用户的名称与帮助文本。
对照当前仓库的 server/plugins/__template/config.json,模板还包含几个快速上手阶段可以保留默认值的字段:
"enabled": true—— 插件加载开关;"data_source": "script"—— 数据来源类型,脚本插件固定为script;"mapped_to_table": "CurrentScan"—— 结果映射到CurrentScan表(如需参与设备发现);"execution_order": "Layer_0"—— 插件执行层级;"icon"—— UI 中显示的图标(Font Awesome 图标 HTML)。
容易被忽略的映射:模板的
database_column_definitions中有一条Dummy → scanSourcePlugin的静态值映射,其mapped_to_column_data.value目前是"TMP",必须同步改成"MYPLN"。从源码看,update_devices_data_from_scan()会原样读取scanSourcePlugin来构建设备更新逻辑,若该值与unique_prefix不一致,SET_ALWAYS/SET_EMPTY覆盖会静默失效(见 docs/PLUGINS_DEV.md 的 Conventions Checklist 与 test/plugins/test_plugin_conventions.py 中test_scan_source_plugin_matches_unique_prefix的强制校验)。
Step 3:实现你的数据采集脚本
编辑my_plugin/script.py,实现数据采集逻辑。以下是 Quick Start 给出的骨架,已标注每个部分的职责:
#!/usr/bin/env python3 import sys import os sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../server')) sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../server/plugins')) from plugin_helper import Plugin_Objects, mylog from helper import get_setting_value from const import logPath pluginName = "MYPLN" LOG_PATH = logPath + "/plugins" LOG_FILE = os.path.join(LOG_PATH, f"script.{pluginName}.log") RESULT_FILE = os.path.join(LOG_PATH, f"last_result.{pluginName}.log") # Initialize plugin_objects = Plugin_Objects(RESULT_FILE) try: # Your data collection logic here # For example, scan something and collect results # Add an object to results plugin_objects.add_object( objectPrimaryId="example_id", objectSecondaryId=None, DateTime="2023-01-02 15:56:30", watchedValue1="value1", watchedValue2=None, watchedValue3=None, watchedValue4=None, Extra="additional_data", ForeignKey=None ) # Write results to the log file plugin_objects.write_result_file() except Exception as e: mylog("none", f"Error: {e}") sys.exit(1)与真实模板的差异:推荐采用环境感知路径
仓库内实际模板 server/plugins/__template/rename_me.py 比 Quick Start 骨架更健壮,建议直接采用其路径处理方式:
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app') sys.path.extend([f"{INSTALL_PATH}/server/plugins", f"{INSTALL_PATH}/server"]) from const import logPath # noqa: E402 from plugin_helper import Plugin_Objects, decode_settings_base64 # noqa: E402 from logger import mylog, Logger # noqa: E402 from helper import get_setting_value # noqa: E402 import conf # noqa: E402 # 按用户设置的时区与日志级别初始化 conf.tz = timezone(get_setting_value('TIMEZONE')) Logger(get_setting_value('LOG_LEVEL'))要点:脚本必须把server/与server/plugins/加入sys.path才能import核心模块;通过NETALERTX_APP环境变量(默认/app)而非硬编码路径,可同时兼容 Dev Container(/workspaces/NetAlertX)与容器运行(/app)两种环境。真实模板的main()还示范了从设置读取值(get_setting_value('SYNC_plugins'))、批量add_object(用primaryId/secondaryId/watched1…watched4关键字)、以及write_result_file()收尾的标准模式。
输出契约:add_object与 9/13 列规则
Plugin_Objects与Plugin_Object定义在 server/plugins/plugin_helper.py:
add_object(...)(plugin_helper.py#L391-L421)把一个对象追加到内存列表;write_result_file()(plugin_helper.py#L423-L426)把每个对象按|分隔写成一行。每行实际输出 13 个字段:objectPrimaryId|objectSecondaryId|DateTime|watchedValue1|watchedValue2|watchedValue3|watchedValue4|Extra|ForeignKey|helpVal1|helpVal2|helpVal3|helpVal4(空值以字面字符串null填充)。
核心读取端只接受9 列(无 helper)或 13 列(含全部 4 个 helper)两种行(server/plugin.py#L493-L497),列数不对的行整行丢弃。数据契约的完整字段定义(哪些列必填、DateTime必须为YYYY-MM-DD HH:MM:SS、watchedValue1是触发变更通知的主观察值等)见 Plugin Data Contract。另外,plugin_helper.py的Plugin_Object.__init__对缺省/None的helpVal1-4会回退为空串,但你显式传入的真实0或False会被保留(用is not None判断而非真值判断)。
安全与规范化由框架兜底
从源码看,插件输出会被当作不可信数据做防御性处理:plugin_object_class.__init__会对每个映射列调用sanitize_plugin_text()剥离 HTML 标签定界符与控制字符(server/plugin.py#L1174-L1199,实现见 plugin_helper.py#L271-L288),MAC 形状的主键会走normalize_mac()统一为小写冒号格式。因此你的脚本只需产出符合格式的原始值即可。
Step 4:配置执行方式(RUN与CMD设置)
插件清单中的settings数组定义了用户在 Settings UI 里可见、可改的配置项。至少需要定义RUN与CMD两个保留函数设置:
{ "function": "RUN", "type": {"dataType":"string", "elements": [{"elementType": "select", "elementOptions": [], "transformers": []}]}, "default_value": "disabled", "options": ["disabled", "once", "schedule"], "localized": ["name", "description"], "name": [{"language_code":"en_us", "string": "When to run"}], "description": [{"language_code":"en_us", "string": "Enable plugin execution"}] }, { "function": "CMD", "type": {"dataType":"string", "elements": [{"elementType": "input", "elementOptions": [], "transformers": []}]}, "default_value": "python3 /app/server/plugins/my_plugin/script.py", "localized": ["name", "description"], "name": [{"language_code":"en_us", "string": "Command"}], "description": [{"language_code":"en_us", "string": "Command to execute"}] }RUN的完整取值
Quick Start 只展示了disabled/once/schedule三项,对照 server/plugins/__template/config.json 与 docs/PLUGINS_DEV.md 的保留函数表,RUN还支持更多触发时机:
| 取值 | 触发时机 |
|---|---|
disabled | 不运行(新插件的安全默认值) |
once | 手动触发一次(UI 保存后立即执行) |
schedule | 按RUN_SCHD的 cron 表达式定时执行 |
always_after_scan | 每次扫描完成后运行 |
before_name_updates | 在设备名称更新前运行 |
on_new_device | 发现新设备时运行 |
on_notification | 产生通知时运行 |
如果选schedule,还需要定义RUN_SCHD设置(cron 格式,模板默认*/5 * * * *)。此外模板还包含RUN_TIMEOUT(默认 30 秒,超时强制终止子进程)以及SET_ALWAYS/SET_EMPTY(控制映射到CurrentScan后哪些列以本插件为权威来源覆盖设备字段)。
CMD的路径替换与超时语义
- 核心执行时会做环境感知的路径替换:
execute_plugin()检测到CMD中的/app/server/plugins会替换为实际的pluginsPath(server/plugin.py#L401-L405),所以 Dev Container 里也能用/app/...写法; RUN_TIMEOUT是整个子进程的硬性杀死计时器(subprocess的timeout=),不是每个网络请求的预算。如果你的脚本在循环里多次调用外部 API,不要直接把RUN_TIMEOUT当作单次调用的超时,否则一次慢调用可能耗尽整个预算、进程在写完结果文件前被杀掉。仓库给出的两种正确做法:- 循环对象是配置声明的、长度已知的列表(如子网、IP 列表):在
config.json对应params项上标记"timeoutMultiplier": true,框架会把外层杀死超时按列表长度放大——参见 server/plugins/arp_scan/config.json 的subnets参数; - 循环对象是运行时才知道长度的集合(如通知队列):用
plugin_helper.per_item_timeout(run_timeout, item_count)(plugin_helper.py#L292-L310)把外层预算均分给每个条目。
- 循环对象是配置声明的、长度已知的列表(如子网、IP 列表):在
Step 5:测试你的插件
方式一:Dev Container 内直接运行脚本
跳过核心调度,直接以脚本方式验证数据采集逻辑本身:
# Test the script directly python3 /workspaces/NetAlertX/server/plugins/my_plugin/script.py # Check the results cat /tmp/log/plugins/last_result.MYPLN.log这是最快的第一道自检:脚本能无异常跑完、结果文件能生成且列数正确,插件的数据层就算通了。
方式二:通过 UI 走完整执行链
验证插件被核心调度器正确加载与执行:
- 重启后端:在 VS Code 任务中运行
[Dev Container] Start Backend (Python)(或参考 docs/DEV_ENV_SETUP.md 的 Maintenance > Logs > Restart server 方式); - 打开 Settings → Plugin Settings → My Plugin;
- 将
My Plugin - When to run设为once; - 点击 Save——核心的
check_and_run_user_event()会从用户事件队列读到该事件并调用handle_run("MYPLN")立即执行(server/plugin.py#L123-L197); - 检查
/tmp/log/plugins/last_result.MYPLN.log的输出。
Step 6:检查结果与排查
# Check if result file was generated ls -la /tmp/log/plugins/last_result.MYPLN.log # View contents cat /tmp/log/plugins/last_result.MYPLN.log # Check backend logs for errors tail -f /tmp/log/app.log | grep "my_plugin\|MYPLN"结合 docs/PLUGINS_DEV_DATA_CONTRACT.md 的调试手段,还可以做更严格的验证:
# 校验每行列数:应全部为 9(无 helper)或 13(含 helper) cat /tmp/log/plugins/last_result.MYPLN.log | awk -F'|' '{print NF}' | sort | uniq常见问题速查表
| 问题 | 解决方案 |
|---|---|
Module not found错误 | 确认sys.path已包含server/与server/plugins/;建议用NETALERTX_APP环境变量动态构造路径而非硬编码 |
| 设置项不出现 | 重启后端并清空浏览器缓存;确认config.json为合法 JSON 且settings结构正确 |
| 结果不显示 | 检查/tmp/log/plugins/*.log与/tmp/log/app.log;确认结果文件列数为 9 或 13(核心会整行丢弃列数错误的行) |
| 权限不足 | 插件在容器内以 NetAlertX 进程同权运行,路径请使用/app/server/plugins/...之类的容器绝对路径 |
| 插件不执行 | 检查RUN取值是否为once/schedule等有效值,schedule模式还需确认RUN_SCHDcron 表达式合法 |
下一步:从"能跑"到"产品级"
一个能输出结果文件的基础插件只是起点。按官方路线图继续深入:
- 添加用户设置:用
type/elements/default_value定义文本、布尔、列表、多选、嵌套表单等控件,让用户通过 UI 配置插件行为——见 Plugin Settings System; - 落实数据契约:严格遵循 9 列/13 列输出格式、
null字面量、DateTime格式与ForeignKey用法——见 Plugin Data Contract; - 配置 UI 展示:通过
database_column_definitions的type字段(label、device_mac、device_ip、url、threshold、replace等)控制结果呈现——见 UI Components; - 映射到数据库:
mapped_to_table: "CurrentScan"配合database_column_definitions把对象写入CurrentScan或Devices等表,参与设备发现与通知;注意CurrentScan是少数会在启动时被ensure_CurrentScan()重建的表,其最新结构以 server/db 中的实现为准; - 设置调度:用
RUN_SCHD定义 cron 计划,用RUN_TIMEOUT与timeoutMultiplier/per_item_timeout控制超时预算——见 Plugin Config Lifecycle; - 补充测试:为新增逻辑添加单元测试(参考
test/plugins/下既有插件的测试,如test_fritzbox.py的模式),并对照 test/plugins/test_plugin_conventions.py 中的约定检查项(JSON 合法性、RUN默认disabled、dataType与default_value一致性、allow_raw_text仅用于安全类型、scanSourcePlugin与unique_prefix一致等)。
参考资源
- Full Plugin Development Guide —— 插件开发全量参考(含 Conventions Checklist 与持久化建议)
- Plugin Data Contract —— 输出格式精确规范与调试命令
- Plugin Settings System —— 设置项定义与 UI 组件
- Data Sources ——
script/app-db-query/sqlite-db-query等数据源类型 - UI Components —— 结果显示与数据库映射
- Debugging Plugins —— 插件调试专题
- Plugin Import Behavior ——
scanCreatesDevice/scanNotificationMode/scanPresence导入行为契约 - 模板与参考实现:server/plugins/__template、server/plugins/plugin_helper.py、server/plugins/arp_scan(
timeoutMultiplier用法)、server/plugins/rest_import(多实例嵌套设置模式)
- 后端
- 网络
- 运维
- 数据可视化
【免费下载链接】NetAlertX
Centralized network visibility and continuous asset discovery. Monitor devices, detect change, and stay aware across distributed networks.
相关推荐
从 `__template` 脚手架到可运行的 NetAlertX 插件:插件开发实战指南
从 __template 脚手架到可运行的 NetAlertX 插件:插件开发实战指南 本文围绕 NetAlertX 仓库中的插件模板 server/plugi
后端网络运维数据可视化cobalt插件开发模板:快速构建你的第一个扩展
cobalt插件开发模板:快速构建你的第一个扩展 1. 插件基础架构 1.1 核心文件结构 cobalt plugin/ ├── manifest.json 插
后端音视频网页爬虫DirectX高性能视频渲染引擎架构解析:MPC Video Renderer技术实现原理
DirectX高性能视频渲染引擎架构解析:MPC Video Renderer技术实现原理 你是一个文章写手,你负责为开源项目写专业易懂的文章,本文将深入分析M
视频处理音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考