mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
App Plugins: support react pages and tabs (#16586)
This commit is contained in:
62
packages/grafana-ui/src/types/app.ts
Normal file
62
packages/grafana-ui/src/types/app.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { ComponentClass } from 'react';
|
||||
import { NavModel } from './navModel';
|
||||
import { PluginMeta, PluginIncludeType, GrafanaPlugin } from './plugin';
|
||||
|
||||
export interface AppRootProps {
|
||||
meta: AppPluginMeta;
|
||||
|
||||
path: string; // The URL path to this page
|
||||
query: { [s: string]: any }; // The URL query parameters
|
||||
|
||||
/**
|
||||
* Pass the nav model to the container... is there a better way?
|
||||
*/
|
||||
onNavChanged: (nav: NavModel) => void;
|
||||
}
|
||||
|
||||
export interface AppPluginMeta extends PluginMeta {
|
||||
// TODO anything specific to apps?
|
||||
}
|
||||
|
||||
export class AppPlugin extends GrafanaPlugin<AppPluginMeta> {
|
||||
// Content under: /a/${plugin-id}/*
|
||||
root?: ComponentClass<AppRootProps>;
|
||||
rootNav?: NavModel; // Initial navigation model
|
||||
|
||||
// Old style pages
|
||||
angularPages?: { [component: string]: any };
|
||||
|
||||
/**
|
||||
* Set the component displayed under:
|
||||
* /a/${plugin-id}/*
|
||||
*/
|
||||
setRootPage(root: ComponentClass<AppRootProps>, rootNav?: NavModel) {
|
||||
this.root = root;
|
||||
this.rootNav = rootNav;
|
||||
return this;
|
||||
}
|
||||
|
||||
setComponentsFromLegacyExports(pluginExports: any) {
|
||||
if (pluginExports.ConfigCtrl) {
|
||||
this.angularConfigCtrl = pluginExports.ConfigCtrl;
|
||||
}
|
||||
|
||||
const { meta } = this;
|
||||
if (meta && meta.includes) {
|
||||
for (const include of meta.includes) {
|
||||
const { type, component } = include;
|
||||
if (type === PluginIncludeType.page && component) {
|
||||
const exp = pluginExports[component];
|
||||
if (!exp) {
|
||||
console.warn('App Page uses unknown component: ', component, meta);
|
||||
continue;
|
||||
}
|
||||
if (!this.angularPages) {
|
||||
this.angularPages = {};
|
||||
}
|
||||
this.angularPages[component] = exp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,6 @@ export class DataSourcePlugin<TOptions = {}, TQuery extends DataQuery = DataQuer
|
||||
return this;
|
||||
}
|
||||
|
||||
setConfigCtrl(ConfigCtrl: any) {
|
||||
this.components.ConfigCtrl = ConfigCtrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
setQueryCtrl(QueryCtrl: any) {
|
||||
this.components.QueryCtrl = QueryCtrl;
|
||||
return this;
|
||||
@@ -60,14 +55,15 @@ export class DataSourcePlugin<TOptions = {}, TQuery extends DataQuery = DataQuer
|
||||
return this;
|
||||
}
|
||||
|
||||
setComponentsFromLegacyExports(exports: any) {
|
||||
this.components.ConfigCtrl = exports.ConfigCtrl;
|
||||
this.components.QueryCtrl = exports.QueryCtrl;
|
||||
this.components.AnnotationsQueryCtrl = exports.AnnotationsQueryCtrl;
|
||||
this.components.ExploreQueryField = exports.ExploreQueryField;
|
||||
this.components.ExploreStartPage = exports.ExploreStartPage;
|
||||
this.components.QueryEditor = exports.QueryEditor;
|
||||
this.components.VariableQueryEditor = exports.VariableQueryEditor;
|
||||
setComponentsFromLegacyExports(pluginExports: any) {
|
||||
this.angularConfigCtrl = pluginExports.ConfigCtrl;
|
||||
|
||||
this.components.QueryCtrl = pluginExports.QueryCtrl;
|
||||
this.components.AnnotationsQueryCtrl = pluginExports.AnnotationsQueryCtrl;
|
||||
this.components.ExploreQueryField = pluginExports.ExploreQueryField;
|
||||
this.components.ExploreStartPage = pluginExports.ExploreStartPage;
|
||||
this.components.QueryEditor = pluginExports.QueryEditor;
|
||||
this.components.VariableQueryEditor = pluginExports.VariableQueryEditor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +87,6 @@ interface PluginMetaQueryOptions {
|
||||
|
||||
export interface DataSourcePluginComponents<TOptions = {}, TQuery extends DataQuery = DataQuery> {
|
||||
QueryCtrl?: any;
|
||||
ConfigCtrl?: any;
|
||||
AnnotationsQueryCtrl?: any;
|
||||
VariableQueryEditor?: any;
|
||||
QueryEditor?: ComponentClass<QueryEditorProps<DataSourceApi, TQuery>>;
|
||||
|
||||
@@ -2,6 +2,7 @@ export * from './data';
|
||||
export * from './time';
|
||||
export * from './panel';
|
||||
export * from './plugin';
|
||||
export * from './app';
|
||||
export * from './datasource';
|
||||
export * from './theme';
|
||||
export * from './graph';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ComponentClass } from 'react';
|
||||
|
||||
export enum PluginState {
|
||||
alpha = 'alpha', // Only included it `enable_alpha` is true
|
||||
beta = 'beta', // Will show a warning banner
|
||||
@@ -21,8 +23,12 @@ export interface PluginMeta {
|
||||
module: string;
|
||||
baseUrl: string;
|
||||
|
||||
// Define plugin requirements
|
||||
dependencies?: PluginDependencies;
|
||||
|
||||
// Filled in by the backend
|
||||
jsonData?: { [str: string]: any };
|
||||
secureJsonData?: { [str: string]: any };
|
||||
enabled?: boolean;
|
||||
defaultNavUrl?: string;
|
||||
hasUpdate?: boolean;
|
||||
@@ -30,6 +36,18 @@ export interface PluginMeta {
|
||||
pinned?: boolean;
|
||||
}
|
||||
|
||||
interface PluginDependencyInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
type: PluginType;
|
||||
}
|
||||
|
||||
export interface PluginDependencies {
|
||||
grafanaVersion: string;
|
||||
plugins: PluginDependencyInfo[];
|
||||
}
|
||||
|
||||
export enum PluginIncludeType {
|
||||
dashboard = 'dashboard',
|
||||
page = 'page',
|
||||
@@ -44,6 +62,10 @@ export interface PluginInclude {
|
||||
name: string;
|
||||
path?: string;
|
||||
icon?: string;
|
||||
|
||||
role?: string; // "Viewer", Admin, editor???
|
||||
addToNav?: boolean; // Show in the sidebar... only if type=page?
|
||||
|
||||
// Angular app pages
|
||||
component?: string;
|
||||
}
|
||||
@@ -69,44 +91,35 @@ export interface PluginMetaInfo {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export class GrafanaPlugin<T extends PluginMeta> {
|
||||
export interface PluginConfigTabProps<T extends PluginMeta> {
|
||||
meta: T;
|
||||
query: { [s: string]: any }; // The URL query parameters
|
||||
}
|
||||
|
||||
export interface PluginConfigTab<T extends PluginMeta> {
|
||||
title: string; // Display
|
||||
icon?: string;
|
||||
id: string; // Unique, in URL
|
||||
|
||||
body: ComponentClass<PluginConfigTabProps<T>>;
|
||||
}
|
||||
|
||||
export class GrafanaPlugin<T extends PluginMeta = PluginMeta> {
|
||||
// Meta is filled in by the plugin loading system
|
||||
meta?: T;
|
||||
|
||||
// Soon this will also include common config options
|
||||
}
|
||||
// Config control (app/datasource)
|
||||
angularConfigCtrl?: any;
|
||||
|
||||
export class AppPlugin extends GrafanaPlugin<PluginMeta> {
|
||||
angular?: {
|
||||
ConfigCtrl?: any;
|
||||
pages: { [component: string]: any };
|
||||
};
|
||||
// Show configuration tabs on the plugin page
|
||||
configTabs?: Array<PluginConfigTab<T>>;
|
||||
|
||||
setComponentsFromLegacyExports(pluginExports: any) {
|
||||
const legacy = {
|
||||
ConfigCtrl: undefined,
|
||||
pages: {} as any,
|
||||
};
|
||||
|
||||
if (pluginExports.ConfigCtrl) {
|
||||
legacy.ConfigCtrl = pluginExports.ConfigCtrl;
|
||||
this.angular = legacy;
|
||||
}
|
||||
|
||||
const { meta } = this;
|
||||
if (meta && meta.includes) {
|
||||
for (const include of meta.includes) {
|
||||
const { type, component } = include;
|
||||
if (type === PluginIncludeType.page && component) {
|
||||
const exp = pluginExports[component];
|
||||
if (!exp) {
|
||||
console.warn('App Page uses unknown component: ', component, meta);
|
||||
continue;
|
||||
}
|
||||
legacy.pages[component] = exp;
|
||||
this.angular = legacy;
|
||||
}
|
||||
}
|
||||
// Tabs on the plugin page
|
||||
addConfigTab(tab: PluginConfigTab<T>) {
|
||||
if (!this.configTabs) {
|
||||
this.configTabs = [];
|
||||
}
|
||||
this.configTabs.push(tab);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user