grafana前端源码学习笔记
Author:zhoulujun Date:
Grafana基本概念
Organization:有些场景是一个grafana服务商与多个客户合作,客户之间是隔离的,所以grafana也就支持多组织。但在更多的场景下,grafana会被单独部署到某个客户环境中。在grafana中的组织下的每个用户,都可以使用属于这个组织的DataSoure和Dashboard。
User:一个用户可以属于一个或多个组织。在不通组织中可以被设置不同角色。Grafana也支持多样的认证方式。比如集成database,或来自外部的SQL server,或是一个LDAP server。
Dashboard:面板组件。panel和row汇总到了dashboard中。右上角的time picker可以控制panel的时间。dashboard能被方便的共享。可以使用Snapshot的特性,将当前的所有视图数据导出到静态的JSON文档中。dashboard也可以被打标签。
Row:在一个Dashboard,“行“是用来组合各个Panel的。一行有12个单元,可以将panel设置成不同的单位宽度。更不错的是,grafana在所有分辨率的屏幕下,都能适应的很好。
Panel:在grafana中,Panel是基础呈现块。每个Panel都提供了查询编辑器Query Editor,辅助我们从DataSource提取所展示的信息。每种Panel都有多样的配置和展现方式,它可以在Dashboard上拖拽和重绘大小。官方提供的Panel有如下:
Graph:可以提供折线图、柱状图等能力,可以通过Plugin添加其它图表。Singlestat如同其名,是用来展示单个字段的状态。Dashlist和Text是特殊的panel,它不连接任何Data Source。
Singlestat
Dashlist
Table
Text
Query Editor:查询编辑器也是数据源间不同的。编辑器可以引用Dashboard的变量,来达到动态控制panel的目的。QE也可以添加多次Query,来获取多个series。每次Query的结果,可以通过输入#xxx给下一次引用,进行更高级的查询。
Data Source
Data Source
Graphite
InfluxDB
OpenTSDB
Prometheus
Elasticsearch
CloudWatch
Grafana所涉及的知识点
TypeScript 语言
AngularJS 框架
SystemJS 模块化解决方案
Sass css预处理语言
Grunt 构建工具
NodeJS基础
Grunt 能不能换成别的?
TypeScript 能不能换成 JavaScript?
AngularJS 能不能换成更高版本的 Angular 或者使用其他框架?
前两个的答案是可以,不过作为非配置工程师,懒得折腾这么多,至于 AngularJS 1.x,涉及到了整个 Grafana 的 View 层问题,在某个论坛看到关于能不能升级的提议,大致是:「你们能不能升级到 Angular 4 啊?」「我们还在升级 Angular 2 的路上」——总之,路漫漫其修远兮,一句话就是,我们现在还得用 AngularJS 来搞定 Template。
从《Grafana的发展历史》可知,grafana一直在迭代,但是现在感觉不会有大的改动了。
Grafana核心源码目录结构
public/app
core
components # 通用组件
controllers # 控制器
directives # 指令
filters # 过滤器
live # websocket
routers # 路由
services # 服务
utils # 工具
features # 功能模块
headers # typescript头文件
partials # 页面片段(相当于传统意义上的页面
plugins # 插件 (app/datasource/panel)
app.ts # app入口
boot.js # 启动app
system.conf.js # systemjs配置
APP插件工程架构
目录 | 描述 |
---|---|
dist / build | 存放编译打包后的插件代码 |
src | 插件源码目录 |
src/components | 界面文件, 可选 |
src/sass | css样式文件 |
src/dashboard | 仪表板json文件 |
src/datasource | 数据源插件 |
src/img | 图像文件 |
src/panel | 仪表板插件 |
src/module.ts | 唯一入口文件 |
src/plugin.json | 插件描述文件 |
Gruntfile.js | Grunt任务描述 |
package.json | 依赖包说明 |
README.md | 说明文档 |
grafana6 7 8,每个版本目录结构相差迥异,但是文件名代表的含义大体相同,只是目录结构做了调整。
架构详解
dist
grafana引入插件主要在插件目录下的dist中读取入口文件,然后将对应的插件配置引入到grafana中;
前面说到的编译运行就是把src的文件以及readme复制到dist中,然后将ts文件编译生成js文件;
如果有后端服务程序,也会通过makefile编译生成到该目录下;
src/components
该目录下存放着对应的ts文件和html文件,定义前端的基本操作;
html定义页面显示,可使用grafana原有的样式,详情请参考官方开发指南;
ts文件定义页面的页面具体操作,并可将页面通过描述嵌入到grafana的页面中;
ts文件中的类定义templateUrl可导入HTML文件,然后该类需在module.ts入口文件进行声明,然后在plugin.json对页面进行对应描述即可。
src/css
该目录存放一些自定义css样式;
src/dashboard
该目录存放着插件自带的仪表板;可以通过grafana制定,然后保存生成json文件,不过应注意要把json文件中的id项改为null,不然会引发Import Dashboard Error;
src/datasource
该目录是APP附属的数据源配置, 相当于将数据源插件嵌入到APP中;
src/img
该目录存放图像文件,比如图标;
src/panel
该目录是APP附属的仪表插件, 相当于将仪表插件嵌入到APP中;待添加
src/module.ts
必要文件, 文件内容解析如下(以kubernetes app为例):
import {KubernetesConfigCtrl} from './components/config/config'; import {ClustersCtrl} from './components/clusters/clusters'; import {loadPluginCss} from 'app/plugins/sdk'; loadPluginCss({ dark: 'plugins/grafana-kubernetes-app/css/dark.css', light: 'plugins/grafana-kubernetes-app/css/light.css' }); export { KubernetesConfigCtrl as ConfigCtrl, ClustersCtrl, };
首先导入component目录下的类
导入加载自定义css的类
声明自定义的css样式
声明APP使用到的类
ConfigCtrl是grafana在配置插件时使用的控制器,将KubernetesConfigCtrl作为ConfigCtrl,该页面会在插件配置的页面中显示。
src中的module.js文件非常重要,它是插件加载的起点文件,但是里面内容不多,主要是导入controller.js,然后导出为Ctrl。controller.js里面放了主要的js代码。
src/plugin.json
必要文件, 文件内容解析如下:
{ "type": "app", //声明类型:app, datasource, panel "name": "kubernetes", //插件名称 "id": "grafana-kubernetes-app", //插件ID,必须唯一 "info": { //相关信息 "description": "Kubernetes app. shows data collected by Prometheus.", "author": { "name": "Grafana Labs", "url": "https://grafana.com/" }, "keywords": ["raintank", "kubernetes", "Prometheus"], "logos": { //声明图标 "small": "img/logo.svg", "large": "img/logo.svg" }, "links": [ //链接跳转 {"name": "Grafana Labs", "url": "https://grafana.com/"}, ], "version": "1.0.1", //版本信息 "updated": "2018-01-18" }, "includes": [ //对插件的各部分进行引入 { "type": "page", //页面类型 "name": "Clusters", //显示的名称 "component": "ClustersCtrl", //该页面的类 "role": "Viewer", //该页面的功能 "addToNav": true, //是否加入插件导航栏 "defaultNav": true //是否作为插件使能后的默认页面 }, { "type": "datasource", //数据源类型 "name": "kubernetes DS" //数据源名称 }, { "type": "dashboard", //仪表板类型 "name": "K8s Node", //仪表板的名称 "path": "dashboards/k8s-node.json", //仪表板的文职 "addToNav": false }, { "type": "panel", //仪表类型 "name": "Kubernetes Node Info" }, ], "dependencies": { //依赖的grafana版本以及插件 "grafanaVersion": "5.0+", "plugins": [] } }
Gruntfile.js
Grunt任务配置文件,主要架构是引入依赖插件,定义任务信息,配置需要执行的任务;
package.json
声明该插件需要使用到的插件和依赖,便于npm安装环境;
Readme.md
要求Markdown格式, 可在插件配置的页面的Readme选项卡中显示;
Grafana插件开发
官方插件合集:https://github.com/grafana/grafana/tree/main/public/app/plugins
具体可以参看 https://github.com/grafana/simple-json-datasource,最简单的 Datasource 主要有以下模块
在Grafana中,如果dist文件夹存在,grafana会从dist文件夹加载代码,忽略src文件夹内容。先忽略dist文件。
插件目录
├── src
├── css
├── img
│ └── server-logo.png
├── module
│ ├── annotationsQueryCtrl.js
│ ├── configCtrl.js
│ ├── datasource.js
│ ├── queryCtrl.js
│ └── queryOptionsCtrl.js
├── module.js
├── page
│ ├── annotationsQuery.html
│ ├── config.html
│ ├── query.html
│ └── queryOptions.html
└── plugin.json
├── Gruntfile.js
├── README.md
├── package.json
plugin.json文件
在plugin.json文件中,有两个关于datasource特定的设置,其中一个必须为true
"metrics": true, // 是否在panel中支持metrics
"annotations": false, // 在dashboard中支持annotations查询
plugin.json保存plugin的元数据,Grafana在扫描插件目录时查找plugin.json文件,并自动注册插件,文件中的内容会被提取并封装成对象使用。
module.js
module.js文件非常重要,它是插件加载的起点文件。与Grafana的其余部分进行交互,插件文件需要导出以下5个模块:
Datasource // Required
QueryCtrl // Required
ConfigCtrl // Required
QueryOptionsCtrl //
AnnotationsQueryCtrl //
所以在module中,负责导出这五个模块。 module.js文件代码示例:
import GenericAnnotationsQueryCtrl from './module/annotationsQueryCtrl'; import GenericConfigCtrl from './module/configCtrl'; import GenericDatasource from './module/datasource'; import GenericQueryCtrl from './module/queryCtrl'; import GenericQueryOptionsCtrl from './module/queryOptionsCtrl'; export { GenericAnnotationsQueryCtrl as AnnotationsQueryCtrl, GenericConfigCtrl as ConfigCtrl, GenericDatasource as Datasource, GenericQueryCtrl as QueryCtrl, GenericQueryOptionsCtrl as QueryOptionsCtrl };
module文件夹
├── annotationsQueryCtrl.js
├── configCtrl.js
├── datasource.js
├── queryCtrl.js
└── queryOptionsCtrl.js
这五个文件对应module.js需要导出的五个模块,将来会被转换为五个angular的控制器。
page文件夹
├── annotationsQuery.html
├── config.html
├── query.html
└── queryOptions.html
这四个文件对应module文件夹下annotationsQueryCtrl、configCtrl、queryCtrl、queryOptionsCtrl四个模块需要绑定的页面模板, datasource模块不需要页面模板。
Datasource插件-数据源模件
与Grafana的其余部分进行交互,插件文件需要导出以下5个模块:
Datasource // Required
QueryCtrl // Required
ConfigCtrl // Required
QueryOptionsCtrl //
AnnotationsQueryCtrl //
constructor函数
datasource插件与数据源通信,并将数据转换为时间序列。
数据源有以下功能:
query(options) // 用于panel查询数据
testDatasource() // 用于自定义数据源的页面,测试当前配置的数据源是可用的
annotationQuery(options) // 用于dashboard获取注释信息
metricFindQuery(options) // used by query editor to get metric suggestions.
constructor函数传入的参数有:
constructor(instanceSettings, $q, backendSrv, templateSrv) {} // instanceSettings对象为: { id: 5, jsonData: { keepCookies: [], tlsAuth: false, tlsAuthWithCACert: false, tlsSkipVerify: false, }, meta: { alerting: false, annotations: true, baseUrl: "public/plugins/grafana-server-datasource", dependencies: { grafanaVersion: "3.x.x", plugins: [], }, id: "grafana-server-datasource", includes: null, info: { author: { name:"liuchunhui", url:"https://grafana.com", }, description: "代理服务端作为数据源", links: [ {name: "Github", url: ""}, {name: "MIT License", url: ""} ], logos: { large:"public/plugins/grafana-server-datasource/img/server-logo.png", small:"public/plugins/grafana-server-datasource/img/server-logo.png" }, screenshots:null updated:"2018-04-23" version:"1.0.0" }, metrics: true, module: "plugins/grafana-server-datasource/module", name: "代理服务端", routes: null, type: "datasource", } name:"代理服务端数据源", type:"grafana-server-datasource", url:"/api/datasources/proxy/5", } // $q 是个函数 // backendSrv对象为: { $http: ƒ p(e), $q: ƒ M(t), $timeout: ƒ o(o,s,u), HTTP_REQUEST_CANCELLED: -1, alertSrv: { $rootScope: object, $timeout: ƒ o(o,s,u) list: [] } contextSrv: { isEditor: true, isGrafanaAdmin: true, isSignedIn: true, sidemenu: true, sidemenuSmallBreakpoint: false, user: { // 当前登录的用户信息 email:"admin@localhost", gravatarUrl:"/avatar/46d229b033af06a191ff2267bca9ae56", helpFlags1:0, id:1, isGrafanaAdmin:true, isSignedIn:true, lightTheme:true, locale:"zh-CN", login:"admin", name:"admin", orgCount:1, orgId:1, orgName:"Main Org.", orgRole:"Admin", timezone:"browser", }, version:"5.0.1", }, inFlightRequests:{}, noBackendCache:true, } // templateSrv对象为: { builtIns: { __interval:{text: "1s", value: "1s"}, __interval_ms:{text: "100", value: "100"} }, grafanaVariables: {}, index:{}, regex:/\$(\w+)|\[\[([\s\S]+?)(?::(\w+))?\]\]|\${(\w+)(?::(\w+))?}/g }
query函数
真正去查询数据时要调用的函数。官方提供的数据源有两种不同的结果,time series和table,目前grafana官方所有数据源和面板都支持time series格式,table格式仅由InfluxDB数据源和表格面板支持。
我们开发插件时可以自定义类型值,但是要做到开发的datasource插件也适配官方自带的panel插件,那么定义datasource返回的数据格式和grafana的一致。
datasource.query的time series类型响应格式:
[{ "target":"upper_75", "datapoints":[ [622, 1450754160000], [365, 1450754220000] ] }, { "target":"upper_90", "datapoints":[ [861, 1450754160000], [767, 1450754220000] ] }]
datasource.query的table类型响应格式:
[{ "columns": [{ "text": "Time", "type": "time", "sort": true, "desc": true, }, { "text": "mean", }, { "text": "sum", }], "rows": [[ 1457425380000, null, null ], [ 1457425370000, 1002.76215352, 1002.76215352 ]], "type": "table" }]
传递给datasource.query函数的请求对象:
{ "range": { "from": moment, // 全局时间筛选起始日期 "raw": {from: "now-7d", to: "now"}, "to": moment, // 全局时间筛选结束日期 }, "rangeRaw": { "from": "now-7d", "to": "now", }, "interval": "15m", "intervalMs": 900000, "targets": [{ // 定义的查询条件们 "refId": "A", "target": "upper_75" }, { "refId": "B", "target": "upper_90" }], "format": "json", "maxDataPoints": 2495, //decided by the panel "cacheTimeout": undefined, "panelId": 2, "timezone": "browser" }
query函数示例:
query(options) { // 返回的结果数据,panel插件会通过监听'data-received'获取到 const params = { // 封装http请求参数 from: options.range.from.format('x'), to: options.range.to.format('x'), targets: options.queries, }; return this.doRequest({ // 发起http请求并返回结果 url: this.url + '/card-data', // 要制定公共接口规范 method: 'POST', data: params }); } doRequest(options) { options.withCredentials = this.withCredentials; options.headers = this.headers; return this.backendSrv.datasourceRequest(options); // datasourceRequest()是grafana提供的请求datasource函数 }
testDatasource函数
当用户在添加新数据源时点击Save&Test按钮,详细信息首先保存到数据库,然后testDatasource调用数据源插件中定义的函数。
此函数向数据源发出查询,验证数据源是否配置的正确可用,确保当用户在新仪表板中编写查询时,数据源已正确配置。
函数示例:
/** * 当保存并测试数据源时会调用该函数 * 测试数据源是否正常工 * return { status: "", message: "", title: "" } */ testDatasource() { return this.doRequest({ url: this.url + '/', method: 'GET', }).then(response => { if (response.status === 200) { return { status: "success", message: "Data source is working", title: "Success" }; } }); }
annotationQuery函数
注释查询,传递给datasource.annotationQuery函数的请求对象:
{ "range": { "from": "2016-03-04T04:07:55.144Z", "to": "2016-03-04T07:07:55.144Z" }, "rangeRaw": { "from": "now-3h", "to": "now" }, "annotation": { "datasource": "generic datasource", "enable": true, "name": "annotation name" } }
datasource.annotationQuery的预期结果:
[{ "annotation": { "name": "annotation name", //should match the annotation name in grafana "enabled": true, "datasource": "generic datasource", }, "title": "Cluster outage", "time": 1457075272576, "text": "Joe causes brain split", "tags": "joe, cluster, failure" }]
可以使用grafana平台的数据库存取'注释'
metricFindQuery 函数
暂时不知
ConfigCtrl模块
当用户编辑或创建此类型的新数据源时,该类将被实例化并视为Angular控制器。 该类需要一个静态模板或templateUrl变量,该模板或变量将被渲染为此控制器的视图。 ConfigCtrl类中的this对象内容为:
{ current: { name: "代理服务端数据源", // 数据源名称 isDefault: true, // 是否是默认 type: "grafana-server-datasource", // 数据源插件的id值 url: "http://localhost:3001", // HTTP:数据源对应的url access: "proxy", // HTTP:连接数据源类型,有'direct'和'proxy'两种类型可选 basicAuth: true, // Auth:Basic Auth选项 basicAuthUser: "basic auth user", // Basic Auth Details:User选项,当basicAuth为true时有效 basicAuthPassword:"basic auth password", // Basic Auth Details:Password选项,当basicAuth为true时有效 withCredentials: true, // Auth:With Credentials选项 jsonData: { tlsAuth: true, // Auth:TLS Client Auth选项 tlsAuthWithCACert: true, // Auth: With CA Cert选项 tlsSkipVerify: true, // Auth: Skip TLS Verification (Insecure)选项值 keepCookies: ["Advanced Cookoe"], // Advanced HTTP Settings: cookie的白名单 }, secureJsonData: { tlsCACert: "TLS Auth CA Cert", // TLS Auth Details:CA Cert选项,当jsonData下的tlsAuthWithCACert值为true时有效 tlsClientCert: "TLS Auth Client Cert", // TLS Auth Details:Client Cert选项,当jsonData下的tlsAuth值为true时有效 tlsClientKey: "TLS Auth Client Key", // TLS Auth Details:Client Key选项,当jsonData下的tlsAuth值为true时有效 }, secureJsonFields: {}, }, meta: { baseUrl: "public/plugins/grafana-server-datasource", defaultNavUrl: "", dependencies: { grafanaVersion: "3.x.x", plugins: [], }, enabled: false, hasUpdate: false, id: "grafana-server-datasource", includes: null, info: { // plugin.json中配置的信息 author: { name: "liuchunhui", url: "https://grafana.com" }, description: "代理服务端作为数据源", links: [ {name: "Github", url: ""}, {name: "MIT License", url: ""} ], logos: { large:"public/plugins/grafana-server-datasource/img/server-logo.png", small:"public/plugins/grafana-server-datasource/img/server-logo.png" }, screenshots:null, updated:"2018-04-23", version:"1.0.0" }, jsonData: null, latestVersion: "", module: "plugins/grafana-server-datasource/module", name: "代理服务端", pinned: false state: "", type: "datasource", }
模板页面中使用grafana封装的angular组件,传入this的current对象,实现HTTP、Auth两个模块的定义:
<datasource-http-settings current="ctrl.current"></datasource-http-settings>
QueryCtrl模块
一个JavaScript类,当用户在面板中编辑指标时,它将被实例化并作为Angular控制器处理。
该类必须从app/plugins/sdk.QueryCtrl类继承。
grafana提供的sdk中包含三种插件共我们使用:
import { PanelCtrl } from 'app/features/panel/panel_ctrl';
import { MetricsPanelCtrl } from 'app/features/panel/metrics_panel_ctrl'; // 自动添加"Metrics"度量标准查询编辑
import { QueryCtrl } from 'app/features/panel/query_ctrl';
该类需要一个静态模板或templateUrl变量,该模板或变量将被渲染为此控制器的视图。
当用户在panel面板下,切回到Metrics模块时,会初始化该类。所以我们可以在构造函数中做一些我们自己的事情。比如获取指标维度列表,作为用户筛选的展示条件。
import { QueryCtrl } from 'app/plugins/sdk'; export default class GenericQueryCtrl extends QueryCtrl { constructor($scope, $injector) { super($scope, $injector); // 获取参数列表请求 this.requestParams().then(response => { const targets = response.data.target; this.options = response.data.options; this.text = response.data.text; this.keys = Object.keys(targets); for (let key in targets) { this.target[key] = this.target[key] || targets[key]; } }); } requestParams() { // 请求获取参数列表 const params = { header: { 'Content-Type': 'application/json' }, method: 'GET', retry: 0, url: this.datasource.url + '/param-list' }; return this.datasource.backendSrv.datasourceRequest(params); // 使用grafana提供的http请求函数 } onChangeInternal() { // 刷新面板 this.panelCtrl.refresh(); // grafana自带方法使面板更新数据 } toggleEditorMode() { // 是否开启编辑模式 this.target.rawQuery = !this.target.rawQuery; } } GenericQueryCtrl.templateUrl = './page/query.html';
QueryCtrl控制器的query.html模板:
<query-editor-row query-ctrl="ctrl" has-text-edit-mode="true"> <div class="gf-form" ng-if="!ctrl.target.rawQuery" ng-repeat="key in ctrl.keys"> <span class="gf-form-label width-7"> {{ctrl.text[key]}} </span> <select class="gf-form-input width-25" ng-model="ctrl.target[key]" ng-change="ctrl.onChangeInternal()"> <option ng-repeat="option in ctrl.options[key]" value="{{option.name}}"> {{option.desc}} </option> </select> </div> <div ng-if="ctrl.target.rawQuery"> <textarea class="gf-form-input" rows="5" spellcheck="false" ng-blur="ctrl.onChangeInternal()" /> </div> </query-editor-row>
<query-editor-row query-ctrl="ctrl">标签的内容会加入到Add Query模板中,标签中的has-text-edit-mode="true"属性,能开启Toggle Edit Mode功能。
QueryCtrl控制器中的target.rawQuery参数,标记着两种编辑模式的切换,但是这两种模式需要写代码定义。
AnnotationsQueryCtrl模块
当用户在datasource的模板菜单中选择这种类型的数据源时,将被实例化并作为Angular控制器处理的JavaScript类。
此类需要一个静态模板或templateUrl变量,该模板或变量将被渲染为此控制器的视图。
绑定到此控制器的字段随后会发送到数据库对象annotationQuery函数。
在开发插件时能自定义dashboard的Built in query的条件。
AnnotationQueryCtrl代码:
export default class GenericAnnotationsQueryCtrl {} GenericAnnotationsQueryCtrl.templateUrl = './page/annotationsQuery.html';
annotationsQuery.html代码:
<h5 class="section-heading">注解查询条件设定</h5> <div class="gf-form-group"> <div class="gf-form"> <input type="text" class="gf-form-input" ng-model='ctrl.annotation.query' placeholder="" /> </div> </div>
Panel插件-面板模块
和上面数据源模块一样,这么就简单过一下
plugin.json文件格式
{ id: '', // 重要!!!插件的唯一名称,不能重复!!!id命名约定为 [github username/org]-[plugin name]-[datasource|app|panel] type: 'panel', // 插件类型,有panel/datasource/app name: 'Clock', // 插件名称 info: { description: 'Clock panel for grafana', author: { "name": "Raintank Inc.", "url": "http://raintank.io" }, keywords: ["clock", "panel"], logos: '', // 项目的logo version: '1.0.0', updated: '2015-03-24', }, dependencies: { grafanaVersion: '4.x.x', // 该插件需要的grafana后端版本 plugins: [], // 该插件需要的插件的版本 } }
grafana提供的监听函数有:
init-edit-mode: // panel的进入编辑状态时的钩子函数
panel-teardown: // clean up时的钩子函数
data-received: //在panel接收到数据或更细数据时的钩子函数
data-snapshot-load: // 在快照模式下载入数据时的钩子函数
参考资料:
Grafana学习笔记 https://www.jianshu.com/p/648069dd3ad1
Grafana插件开发------APP插件开发 https://blog.csdn.net/mr_black_man/article/details/102831891
Grafana的Datasource插件开发实践二 https://juejin.cn/post/6844903598858436615
Grafana Panel插件开发实践 https://juejin.cn/post/6844903589119262728
Grafana 插件开发入门 - 前端视角(React) https://blog.csdn.net/Candy_home/article/details/102665934
转载本站文章《grafana前端源码学习笔记》,
请注明出处:https://www.zhoulujun.cn/html/OS/Linux/monitor/8738.html