
# 使用nvm管理Node版本
nvm install 16.20.2
npm install -g @grafana/toolkit@latest # 官方CLI工具
# 创建插件模板(选择类型)
npx @grafana/create-plugin@latest
? What type of plugin are you building?
❯ Data Source
Panel
Appmy-plugin/
├── src/ # 核心代码
│ ├── datasource.ts # 数据源逻辑
│ └── module.ts # 插件入口
├── plugin.json # 元数据配置
├── README.md
└── package.json// datasource.ts
class MyDataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
super(instanceSettings);
}
// 必须实现的三个方法
async testDatasource() {
return {
status: 'success',
message: 'Data source is working',
};
}
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
const { range } = options;
const queries = options.targets
.filter(t => !t.hide)
.map(t => ({
refId: t.refId,
queryText: t.queryText,
}));
// 调用后端API
const data = await this.doRequest(queries, range);
return { data };
}
async doRequest(queries: Query[], range: TimeRange) {
// 实现具体请求逻辑
}
}// 转换原始数据为Grafana数据帧
function transformToDataFrames(rawData: any[], query: MyQuery) {
return rawData.map(item => {
return new MutableDataFrame({
refId: query.refId,
fields: [
{ name: 'time', type: FieldType.time, values: item.timestamps },
{ name: 'value', type: FieldType.number, values: item.values },
],
});
});
}// ConfigEditor.tsx
export const ConfigEditor: React.FC<Props> = ({ options, onOptionsChange }) => {
return (
<div className="gf-form-group">
<InlineField label="API Endpoint" labelWidth={12}>
<Input
value={options.jsonData.endpoint}
onChange={e => onOptionsChange({
...options,
jsonData: { ...options.jsonData, endpoint: e.currentTarget.value }
})}
/>
</InlineField>
</div>
);
};// SimplePanel.tsx
export const SimplePanel: React.FC<Props> = ({ data, width, height }) => {
const theme = useTheme2();
// 数据转换逻辑
const series = data.series.map(frame => {
return {
x: frame.fields[0].values.toArray(),
y: frame.fields[1].values.toArray()
};
});
return (
<div style={{ position: 'relative', width, height }}>
<Canvas>
<LineChart
data={series}
width={width}
height={height}
theme={theme}
/>
</Canvas>
</div>
);
};// PanelOptions.ts
export interface PanelOptions {
showLegend: boolean;
lineWidth: number;
fillOpacity: number;
}
// OptionsEditor.tsx
export const OptionsEditor: React.FC<Props<PanelOptions>> = ({ options, onChange }) => {
return (
<>
<Field label="Line width">
<Input
type="number"
value={options.lineWidth}
onChange={e => onChange({ ...options, lineWidth: e.currentTarget.valueAsNumber })}
/>
</Field>
<Switch
label="Show legend"
checked={options.showLegend}
onChange={e => onChange({ ...options, showLegend: e.currentTarget.checked })}
/>
</>
);
};// 前端调用后端接口
const response = await getBackendSrv().post('/api/plugins/my-plugin/resources', {
query: options.targets[0].queryText,
range: options.range
});
// 后端路由注册(plugin.json)
"routes": [{
"path": "resources",
"method": "POST",
"reqRole": "Viewer",
"handler": "onCustomRequest"
}]// 在数据源中实现metricFindQuery
async metricFindQuery(query: MyVariableQuery) {
const results = await this.fetchMetricOptions(query);
return results.map(text => ({ text, value: text }));
}// 使用流式数据处理
function* streamDataGenerator() {
while (hasMoreData) {
const chunk = fetchNextChunk();
yield new StreamingDataFrame(chunk, {
refId: 'A',
fields: [{ name: 'time' }, { name: 'value' }]
});
}
}npm run dev # 自动监控文件变化
# 访问 http://localhost:3000// datasource.test.ts
describe('MyDataSource', () => {
it('should correctly parse query response', async () => {
const ds = new MyDataSource(mockSettings);
const res = await ds.query(mockRequest);
expect(res.data.length).toBe(2);
});
});# 开启Grafana调试日志
GF_LOG_LEVEL=debug ./bin/grafana-servernpm run build
npx @grafana/sign-plugin@latest # 需要先申请证书# grafana.ini
[plugins]
allow_loading_unsigned_plugins = my-plugingrafana-plugins-webpack
注意事项:
通过以上步骤,可以开发出符合企业级标准的自定义插件。建议从简单面板开始,逐步增加复杂度,利用TypeScript的类型系统提高代码质量,结合Storybook进行组件化开发。