Grafana(三)插件开发实战

该文章已生成可运行项目,

一、插件概述

1. 插件类型与结构简介

Grafana 插件主要分为三类:

  • 数据源插件(Data Source Plugin):扩展数据源类型,实现自定义数据查询。
  • 面板插件(Panel Plugin):扩展可视化图表类型,如自定义饼图、地图等。
  • 应用插件(App Plugin):集成数据源、面板、路由等,适合完整业务集成。

插件目录结构示例:

my-plugin/
├── src/                # 源码目录
│   ├── module.tsx      # 主入口
│   ├── ...             # 其它组件/逻辑
├── plugin.json         # 插件声明配置
├── README.md
├── package.json
├── tsconfig.json
└── ...

2. 开发环境准备

  • Node.js(建议 v18+)
  • yarn 或 npm
  • Grafana 本地环境(建议用 Docker 快速搭建)

安装官方插件开发工具:

npm install -g @grafana/toolkit

3. 插件项目初始化

使用官方脚手架快速创建:

npx @grafana/create-plugin my-plugin
cd my-plugin
yarn install

根据提示选择插件类型(panel/data source/app)。


4. plugin.json 配置详解

plugin.json 是插件的声明文件,决定插件类型、入口、支持的功能等。

面板插件示例:

{
  "type": "panel",
  "name": "My Custom Panel",
  "id": "myorg-my-custom-panel",
  "info": {
    "description": "A custom panel plugin",
    "author": {
      "name": "Your Name"
    },
    "keywords": ["panel", "custom"],
    "version": "1.0.0"
  },
  "dependencies": {
    "grafanaDependency": ">=10.0.0",
    "plugins": []
  },
  "includes": [
    {
      "type": "panel",
      "name": "My Custom Panel"
    }
  ]
}

数据源插件示例:

{
  "type": "datasource",
  "name": "My Data Source",
  "id": "myorg-my-datasource",
  "info": {
    "description": "A custom data source plugin",
    "author": { "name": "Your Name" },
    "version": "1.0.0"
  },
  "dependencies": {
    "grafanaDependency": ">=10.0.0"
  },
  "includes": [
    { "type": "datasource", "name": "My Data Source" }
  ]
}

主要字段说明:

  • type:插件类型(panel/datasource/app)
  • name/id:名称与唯一ID(发布到市场必须唯一)
  • info:描述、作者、版本
  • dependencies:Grafana 版本依赖
  • includes:声明包含的功能

5. 前端与后端开发配置

前端(React/TypeScript)

  • src/module.tsx 是主入口,导出插件组件。
  • 面板插件需实现 PanelPlugin,数据源插件需实现 DataSourcePlugin
  • 配置项通过 PanelOptions 或 DataSourceOptions 实现,支持自定义表单控件。

后端(可选,Go)


6. 插件本地调试与打包

本地开发调试

  1. 在 Grafana 配置文件 grafana.ini 中启用开发模式:
    [plugins]
    allow_loading_unsigned_plugins = myorg-my-custom-panel
    
  2. 启动 Grafana 服务(推荐用 Docker):
    docker run -d -p 3000:3000 \
      -v $(pwd)/my-plugin/dist:/var/lib/grafana/plugins/myorg-my-custom-panel \
      -e "GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=myorg-my-custom-panel" \
      grafana/grafana:latest
    
  3. 启动插件开发服务器:
    yarn dev
    
    或使用 toolkit:
    grafana-toolkit plugin:dev
    

打包发布

yarn build

打包后的文件在 dist/ 目录,将其拷贝到 Grafana 的插件目录即可。


7. 插件安装与部署

  • 将 dist 目录拷贝到 Grafana 的插件目录(如 /var/lib/grafana/plugins/)。
  • 修改配置文件允许加载未签名插件(生产环境建议签名)。
  • 重启 Grafana 服务。

二、开发案例实战

1、面板插件开发详细代码案例(TypeScript + React)

1.1. 快速初始化项目

npx @grafana/create-plugin my-panel-plugin
cd my-panel-plugin
yarn install

选择 panel 类型。

1.2. plugin.json 配置(核心字段)

{
  "type": "panel",
  "name": "Demo Panel",
  "id": "myorg-demo-panel",
  "info": {
    "description": "A demo panel plugin",
    "author": { "name": "Your Name" },
    "version": "1.0.0"
  },
  "dependencies": {
    "grafanaDependency": ">=10.0.0"
  },
  "includes": [
    { "type": "panel", "name": "Demo Panel" }
  ]
}

1.3. 主入口代码(src/module.tsx)

import { PanelProps } from '@grafana/data';
import { SimpleOptions } from './types';
import React from 'react';

interface Props extends PanelProps<SimpleOptions> {}

export const DemoPanel: React.FC<Props> = ({ options, data, width, height }) => {
  // 获取数据
  const series = data.series[0];
  const values = series ? series.fields[1].values.toArray() : [];

  // 简单渲染
  return (
    <div style={{ width, height, padding: 20 }}>
      <h2>{options.title || 'Demo Panel'}</h2>
      <ul>
        {values.map((v, i) => (
          <li key={i}>{v}</li>
        ))}
      </ul>
    </div>
  );
};

// 注册插件
import { PanelPlugin } from '@grafana/data';
export const plugin = new PanelPlugin<SimpleOptions>(DemoPanel)
  .setPanelOptions(builder => {
    builder
      .addTextInput({
        path: 'title',
        name: 'Panel Title',
        defaultValue: 'Demo Panel',
      });
  });

1.4. 配置项 types(src/types.ts)

export interface SimpleOptions {
  title?: string;
}

1.5. 开发调试

yarn dev

Grafana 配置 allow_loading_unsigned_plugins = myorg-demo-panel,插件目录映射到 dist


2、数据源插件开发流程与代码模板

2.1. 初始化项目

npx @grafana/create-plugin my-datasource-plugin
cd my-datasource-plugin
yarn install

选择 datasource 类型。

2.2. plugin.json 示例

{
  "type": "datasource",
  "name": "Demo DataSource",
  "id": "myorg-demo-datasource",
  "info": {
    "description": "A demo data source plugin",
    "author": { "name": "Your Name" },
    "version": "1.0.0"
  },
  "dependencies": {
    "grafanaDependency": ">=10.0.0"
  },
  "includes": [
    { "type": "datasource", "name": "Demo DataSource" }
  ]
}

2.3. 主入口代码(src/DataSource.ts)

import { DataSourceApi, DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings } from '@grafana/data';
import { DemoQuery, DemoDataSourceOptions } from './types';

export class DemoDataSource extends DataSourceApi<DemoQuery, DemoDataSourceOptions> {
  constructor(instanceSettings: DataSourceInstanceSettings<DemoDataSourceOptions>) {
    super(instanceSettings);
  }

  async query(request: DataQueryRequest<DemoQuery>): Promise<DataQueryResponse> {
    // 这里可以调用外部API,或返回模拟数据
    const data = [
      {
        fields: [
          { name: 'time', type: 'time', values: [Date.now()] },
          { name: 'value', type: 'number', values: [Math.random() * 100] }
        ],
        length: 1
      }
    ];
    return { data };
  }

  async testDatasource() {
    // 测试连接
    return { status: 'success', message: 'Demo connection OK' };
  }
}

2.4. 配置表单(src/ConfigEditor.tsx)

import React from 'react';
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { DemoDataSourceOptions } from './types';

export const ConfigEditor: React.FC<DataSourcePluginOptionsEditorProps<DemoDataSourceOptions>> = ({ onOptionsChange, options }) => {
  return (
    <div>
      <label>API URL</label>
      <input
        type="text"
        value={options.apiUrl || ''}
        onChange={e => onOptionsChange({ ...options, apiUrl: e.target.value })}
      />
    </div>
  );
};

2.5. 类型定义(src/types.ts)

export interface DemoQuery {
  queryText?: string;
}

export interface DemoDataSourceOptions {
  apiUrl?: string;
}

2.6. 注册插件(src/module.ts)

import { DataSourcePlugin } from '@grafana/data';
import { DemoDataSource } from './DataSource';
import { ConfigEditor } from './ConfigEditor';
export const plugin = new DataSourcePlugin(DemoDataSource)
  .setConfigEditor(ConfigEditor);

3、后端安全代理实现(Go)

3.1. 后端插件目录结构示例

my-datasource-plugin/
├── src/
├── plugin.json
├── backend/
│   └── main.go

3.2. Go 后端代码(backend/main.go)

package main

import (
    "context"
    "encoding/json"
    "net/http"

    "github.com/grafana/grafana-plugin-sdk-go/backend"
    "github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
    "github.com/grafana/grafana-plugin-sdk-go/backend/log"
)

func main() {
    datasource.Serve(newDemoDatasource)
}

func newDemoDatasource(_ backend.DataSourceInstanceSettings) (backend.DataSource, error) {
    return &DemoDatasource{}, nil
}

type DemoDatasource struct{}

func (ds *DemoDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
    res := backend.NewQueryDataResponse()
    for _, q := range req.Queries {
        // 这里可以安全地访问外部API
        log.DefaultLogger.Info("Demo backend query", "query", string(q.JSON))
        frame := backend.NewFrame("response")
        frame.Fields = append(frame.Fields,
            backend.NewField("time", nil, []int64{backend.TimeToEpochMilliseconds(ctx)}),
            backend.NewField("value", nil, []float64{42.0}),
        )
        res.Responses[q.RefID] = backend.DataResponse{Frames: backend.Frames{frame}}
    }
    return res, nil
}

3.3. plugin.json 后端声明

{
  ...
  "backend": true,
  ...
}

3.4. 本地运行与调试

  • 后端插件需用 Go 编译,参考 官方后端SDK
  • 前端与后端可一起开发,Grafana 会自动加载

4、实践建议与资源

  • 开发调试建议:用 Docker 启 Grafana,插件目录映射本地,yarn dev 热更新前端。
  • 代码结构建议:前端/后端分离,类型定义清晰,表单与主逻辑分离。
  • 安全建议:后端代理用于对接私有API、加密认证信息,避免前端泄露密钥。
  • 发布建议:生产环境建议插件签名,详见官方签名流程
本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猩火燎猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值