ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Ajenti Dashboard Widget 开发指南:从 Widget 接口到 Angular 模板的完整实战

Ajenti Dashboard Widget 开发指南:从 Widget 接口到 Angular 模板的完整实战 后端运维【免费下载链接】ajentiAjenti Core and stock plugins项目地址https://gitcode.com/gh_mirrors/aj/ajenti点击查看免费下载本文是一份面向 Ajenti 插件开发者的实战指南讲解如何通过扩展aj.plugins.dashboard.api.Widget抽象类为 Ajenti 控制面板Dashboard提供可配置、可动态刷新的自定义 Widget。读完本文你将掌握 Widget 的类定义、模板与配置对话框的编写、Angular 控制器的数据流以及 Ajenti 内置 Widget 的实现范式可直接动手写出自己的第一个面板组件。说明本文基于当前仓库Ajenti Core and stock plugins实际源码撰写。原文档docs/source/dev/dash-widgets.rst中的示例代码来自官方 demo 插件仓库demo_5_widget本仓库不包含该示例插件但仓库内置的 5 个 Dashboard 组件提供了完全对等的参考实现可对照阅读。Ajenti Dashboard 面板用户在此添加、配置并查看各类 Widget 的实时数值一、Dashboard 插件与 Widget 接口概览Ajenti 的 Dashboard 插件位于 plugins/dashboard其职责在 plugins/dashboard/README.md 中有明确说明This plugin manages and displays all available widgets registered in the plugins. It defines a widget interfaceWidgetin order to facilitate the definition and integration of a new widget.即Dashboard 插件负责统一管理并展示所有插件注册的 Widget并定义了Widget接口以简化新 Widget 的定义与集成。任何插件包括第三方插件都可以注册自己的 Widget 上架到 Dashboard 页面用户可以在面板中自由添加、配置并实时查看数据。Widget 的抽象基类定义在 plugins/dashboard/api.pyfrom jadi import interface interface class Widget(): Base interface for dashboard widgets. id None name None Display name template None Angular view template URL config_template None Configuration dialog template URL def __init__(self, context): self.context context def get_value(self, config): Override this to return the widget value for the given config dict. raise NotImplementedError可见Widget是一个通过interface声明的 jadi 接口需要开发者实现的核心部分有三块类属性id、name、template、config_template与方法__init__、get_value。get_value的默认实现直接抛出NotImplementedError因此任何自定义 Widget 都必须覆写它。二、定义你的第一个 Widget类结构与注册插件通过继承Widget抽象类并提供给 jadi 容器来注册 Widget。原文档给出了完整示例其结构如下component(Widget) class RandomWidget(Widget): id random # display name name Random # template of the widget template /demo_5_widget:resources/partial/widget.html # template of the configuration dialog config_template /demo_5_widget:resources/partial/widget.config.html def __init__(self, context): Widget.__init__(self, context) def get_value(self, config): # generate value based on widgets config if bytes not in config: return Not configured return os.urandom(int(config[bytes])).encode(hex)其中几个关键点需要展开说明component(Widget)来自jadi的装饰器将RandomWidget注册为Widget接口的实现。Dashboard 插件启动时会通过Widget.all(self.context)枚举全部注册项见 plugins/dashboard/views.py因此无需任何额外的注册表或配置文件声明即注册。idWidget 的唯一标识用于前端区分不同类型的 Widget。必须唯一且在 API 请求中作为typeId传递见下文第三节。name显示名称将出现在 Dashboard 的添加菜单与 Widget 头部。templateWidget 主体视图的 Angular 模板 URL采用/{插件名}:resources/...的资源定位语法。config_template配置对话框的模板 URL。该属性是可选的如果未提供则用户添加该 Widget 时不会弹出配置界面内置 Widget 大多未提供配置模板见第六节。__init__(self, context)构造时接收当前请求上下文context必须显式调用父类构造器Widget.__init__(self, context)。注意get_value(config)的入参config这是一个Python dict由用户在配置对话框中填写、随每次刷新请求提交见第四节。示例中RandomWidget根据配置中的bytes字段生成随机字节串若配置中不含bytes则返回字符串Not configured——这展示了配置驱动取值的典型模式。三、数据获取契约get_value 与 HTTP API 的调用链get_value(config)是 Widget 唯一必须实现的方法Dashboard 前端会周期性调用它来刷新数值。其底层调用链可以从 plugins/dashboard/views.py 中完整还原GET /api/dashboard/widgets返回全部可用 Widget 的元数据id、name、template、config_template前端据此渲染添加 Widget菜单return [ { id: w.id, name: w.name, template: w.template, config_template: w.config_template, } for w in self.widgets.values() ]POST /api/dashboard/widgets-values接收前端提交的请求列表每项含id、typeId、config对每个请求调用对应 Widget 的get_value(rq[config])并返回结果data http_context.json_body() return [ { id: rq[id], data: self.widgets[rq[typeId]].get_value(rq[config]), } for rq in data if rq[typeId] in self.widgets ]从这段实现可以提炼出两条重要的设计约束Widget 必须无状态Handler.__init__中self.widgets {x.id: x for x in Widget.all(self.context)}表明每个类型的 Widget只实例化一次服务所有同类型 Widget 的请求原文档也强调If user creates multiple widgets of same type, a single instance will be created to service their requests。因此你的 Widget 类不应保留任何跨请求状态所有可变数据都应存放在config中或每次在get_value内重新获取。get_value的返回值自由但需自洽它可以是标量如 uptime 的秒数、列表如 cpu 的每核百分比、或 dict如 memory 的used/free/total只要与你提供的模板中的渲染逻辑一致即可。四、模板编写CSS 类与 Angular 控制器Widget 主体模板template使用 AngularJS 编写。原文档给出了标准样式的模板骨架并指出有一些现成的 CSS 类可供使用以保持标准外观div ng:controllerDemo5WidgetController div classwidget-header Random /div div classwidget-value {{value || Unknown}} /div /div可用的标准 CSS 类来自 Dashboard 插件的前端样式包括CSS 类用途widget-headerWidget 标题栏widget-valueWidget 主体数值区域widget-container/widget等容器布局由 Dashboard 页面统一提供模板对应的 Angular 控制器需要监听widget-update事件以接收后端推送的数据angular.module(ajenti.demo5).controller Demo5WidgetController, ($scope) - # $scope.widget is our widget descriptor here $scope.$on widget-update, ($event, id, data) - if id ! $scope.widget.id return $scope.value data这里的事件契约非常关键$scope.widget当前 Widget 的描述对象含id等元数据由 Dashboard 框架注入widget-update框架周期性广播的事件回调收到(id, data)控制器必须按id过滤只有id与自身$scope.widget.id一致时才更新$scope.value。因为页面上可能同时存在多个不同类型的 Widget它们共享同一事件总线。五、配置对话框config_template 与初始化流程若你的 Widget 需要用户配置如RandomWidget的bytes参数则需提供config_template并配套实现配置控制器angular.module(ajenti.demo5).controller Demo5WidgetConfigController, ($scope) - # $scope.configuredWidget is our widget descriptor here # some defaults $scope.configuredWidget.config.bytes ? 4配置对话框的控制器的关键点是$scope.configuredWidget正在被配置的 Widget 描述对象其config属性是一个 dict直接与后端get_value收到的config参数对应在控制器中可为未设置的配置项提供默认值如$scope.configuredWidget.config.bytes ? 4CoffeeScript 的?仅在值为空时赋值。原文档还明确了整个 Widget 的生命周期起始流程Initially, dashboard will create your widget with an empty ({}) config and show the configuration dialog you provided.即当用户在 Dashboard 添加一个 Widget 时框架会以空配置{}创建它并立即弹出你提供的配置对话框。这意味着你的get_value必须能够安全处理config缺失某个键甚至为空 dict的情况——RandomWidget中if bytes not in config: return Not configured正是为此设计的健壮写法。六、内置 Widget 实现范式直接可读的参考源码如果你希望看到一个真实、完整的 Widget 实现仓库内置的 5 个 Widget 是最佳范本它们全部位于 plugins/dashboard/widgets均遵循继承Widgetcomponent注册 覆写get_value的统一范式1. 主机名 Widgethostname.py——最简单的标量型 Widgetcomponent(Widget) class HostnameWidget(Widget): id hostname name _(Hostname) template /dashboard:resources/partial/widgets/hostname.html def __init__(self, context): Widget.__init__(self, context) def get_value(self, config): return platform.node()2. 运行时长 Widgetuptime.py——同样返回标量def get_value(self, config): return time.time() - psutil.boot_time()3. CPU 使用率 Widgetcpu.py——返回列表每核百分比归一化到 0~1def get_value(self, config): return [x / 100.0 for x in psutil.cpu_percent(interval0, percpuTrue)]4. 内存使用率 Widgetmemory.py——返回 dict便于模板分别渲染多项数据def get_value(self, config): v psutil.virtual_memory() return { used: v.total - v.available, free: v.available, total: v.total }5. 负载均值 Widgetloadavg.py——仓库中唯一消费config的内置 Widget是理解配置驱动取值的最佳案例def get_value(self, config): k 1.0 if config and config.get(divide, False): k / multiprocessing.cpu_count() if os.path.exists(/proc/loadavg): return [float(open(/proc/loadavg).read().split()[x]) * k for x in range(3)] tokens subprocess.check_output([uptime]).decode().split() return [float(x.strip(,).replace(,, .)) * k for x in tokens[-3:]]它通过config.get(divide, False)决定是否将负载均值除以 CPU 核数同时兼容/proc/loadavg与uptime两种取值来源。注意它对config的防御性写法if config and config.get(divide, False)——空 dict 也能安全通过。七、前端资源注册plugin.yml 中的声明Widget 的模板、控制器与依赖服务需要通过插件的 plugin.yml 声明为资源Ajenti 前端才会加载它们。以 Dashboard 插件自身的配置为模板name: dashboard version: 0.42 title: Dashboard icon: bar-chart dependencies: - !PluginDependency { plugin_name: core } resources: - resources/js/module.es - resources/js/routing.es - resources/js/controllers/index.controller.es - resources/js/controllers/cpuWidget.controller.es - resources/js/controllers/hostnameWidget.controller.es - resources/js/controllers/loadAverageWidget.controller.es - resources/js/controllers/memoryWidget.controller.es - resources/js/controllers/uptimeWidget.controller.es - resources/js/services/dashboard.service.es - resources/css/styles.less - resources/partial/index.html - resources/partial/widget.html - resources/partial/widgets/cpu.html - resources/partial/widgets/hostname.html - resources/partial/widgets/loadavg.html - resources/partial/widgets/memory.html - resources/partial/widgets/uptime.html - ng:ajenti.dashboard从中可以总结出编写自定义 Widget 插件时需遵循的资源声明规范每个 Widget 的模板文件resources/partial/widgets/*.html都要列入resources这解释了template属性中/dashboard:resources/partial/widgets/cpu.html路径的由来每个 Widget 的Angular 控制器resources/js/controllers/*Widget.controller.es同样需要注册通过ng:ajenti.dashboard声明依赖的 Angular 模块如果你的 Widget 使用独立模块则在template/config_template中引用对应插件的模块路径如示例中的/demo_5_widget:resources/...。八、开发 Checklist编写一个可用的 Dashboard Widget综合原文档与仓库源码开发一个自定义 Widget 的完整步骤为继承并注册创建类继承aj.plugins.dashboard.api.Widget用component(Widget)注册定义唯一的id与显示用的name实现get_value(config)从config读取配置、获取实时数据并返回标量/列表/dict 均可务必对空配置{}与缺失键做防御不得在类中保留跨请求状态编写主体模板使用widget-header、widget-value等标准 CSS 类通过ng:controller挂载控制器将template指向/{插件名}:resources/partial/xxx.html编写控制器在widget-update事件中按id过滤后更新$scope.value可选提供配置能力编写config_template对应的配置对话框控制器在$scope.configuredWidget.config上设置默认值声明资源在插件的plugin.yml中注册模板与控制器资源在面板中验证进入/view/dashboard通过侧边栏的 Dashboard 入口见 plugins/dashboard/main.py 的SidebarItemProvider添加你的 Widget 并观察周期性刷新是否正常。完成上述步骤后你的 Widget 就会与内置的 CPU、内存、负载等组件一样出现在 Dashboard 的添加菜单中供用户随时添加与配置。赞分享后端运维【免费下载链接】ajentiAjenti Core and stock plugins项目地址https://gitcode.com/gh_mirrors/aj/ajenti点击查看免费下载相关推荐Ajenti Dashboard Widget API 深度解析从内置组件到自定义插件的完整开发指南Ajenti Dashboard Widget API 深度解析从内置组件到自定义插件的完整开发指南 Dashboard 是 Ajenti 管理面板默认的落地后端运维gridstack.js 官方 Angular 封装指南从动态 Widget 到 ngFor 模板的双轨实战gridstack.js 官方 Angular 封装指南从动态 Widget 到 ngFor 模板的双轨实战 Gridstack.js 是快速构建交互式仪表盘前端UI组件Seelen UI Widget 开发指南从 metadata.yml 到运行时 JS API 的完整实战Seelen UI Widget 开发指南从 metadata.yml 到运行时 JS API 的完整实战 Seelen UI 的 Widget部件本质上桌面应用前端插件系统创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表