mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 10:20:29 -06:00
Plugins: Unifying alpha state & options for all plugins (#16530)
* app pages * app pages * workign example * started alpha support * remove app stuff * show warning on alpha/beta panels * put app back on plugin file * fix go * add enum for PluginType and PluginIncludeType * Refactoring and moving settings to plugins section fixes #16529
This commit is contained in:
parent
30dcf0f6c5
commit
3c21a121eb
@ -613,8 +613,13 @@ server_url =
|
||||
callback_url =
|
||||
|
||||
[panels]
|
||||
# here for to support old env variables, can remove after a few months
|
||||
enable_alpha = false
|
||||
disable_sanitize_html = false
|
||||
|
||||
[plugins]
|
||||
enable_alpha = false
|
||||
app_tls_skip_verify_insecure = false
|
||||
|
||||
[enterprise]
|
||||
license_path =
|
||||
|
@ -540,7 +540,10 @@ log_queries =
|
||||
;license_path =
|
||||
|
||||
[panels]
|
||||
;enable_alpha = false
|
||||
# If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities.
|
||||
;disable_sanitize_html = false
|
||||
|
||||
[plugins]
|
||||
;enable_alpha = false
|
||||
;app_tls_skip_verify_insecure = false
|
||||
|
||||
|
@ -666,11 +666,14 @@ Default setting for max attempts to sending alert notifications. Default value i
|
||||
|
||||
## [panels]
|
||||
|
||||
### enable_alpha
|
||||
Set to true if you want to test panels that are not yet ready for general usage.
|
||||
|
||||
### disable_sanitize_html
|
||||
|
||||
If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities. Default
|
||||
is false. This settings was introduced in Grafana v6.0.
|
||||
|
||||
## [plugins]
|
||||
|
||||
### enable_alpha
|
||||
|
||||
Set to true if you want to test alpha plugins that are not yet ready for general usage.
|
||||
|
||||
|
@ -1,10 +1,25 @@
|
||||
export enum PluginState {
|
||||
alpha = 'alpha', // Only included it `enable_alpha` is true
|
||||
beta = 'beta', // Will show a warning banner
|
||||
}
|
||||
|
||||
export enum PluginType {
|
||||
panel = 'panel',
|
||||
datasource = 'datasource',
|
||||
app = 'app',
|
||||
}
|
||||
|
||||
export interface PluginMeta {
|
||||
id: string;
|
||||
name: string;
|
||||
info: PluginMetaInfo;
|
||||
includes: PluginInclude[];
|
||||
module: string;
|
||||
baseUrl: string;
|
||||
includes?: PluginInclude[];
|
||||
baseUrl?: string;
|
||||
|
||||
type: PluginType;
|
||||
enabled?: boolean;
|
||||
state?: PluginState;
|
||||
|
||||
// Datasource-specific
|
||||
builtIn?: boolean;
|
||||
@ -24,8 +39,17 @@ interface PluginMetaQueryOptions {
|
||||
minInterval?: boolean;
|
||||
}
|
||||
|
||||
export enum PluginIncludeType {
|
||||
dashboard = 'dashboard',
|
||||
page = 'page',
|
||||
|
||||
// Only valid for apps
|
||||
panel = 'panel',
|
||||
datasource = 'datasource',
|
||||
}
|
||||
|
||||
export interface PluginInclude {
|
||||
type: string;
|
||||
type: PluginIncludeType;
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
macaron "gopkg.in/macaron.v1"
|
||||
)
|
||||
@ -21,7 +20,7 @@ var pluginProxyTransport *http.Transport
|
||||
func (hs *HTTPServer) initAppPluginRoutes(r *macaron.Macaron) {
|
||||
pluginProxyTransport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: setting.PluginAppsSkipVerifyTLS,
|
||||
InsecureSkipVerify: hs.Cfg.PluginsAppsSkipVerifyTLS,
|
||||
Renegotiation: tls.RenegotiateFreelyAsClient,
|
||||
},
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
|
@ -145,7 +145,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
|
||||
|
||||
panels := map[string]interface{}{}
|
||||
for _, panel := range enabledPlugins.Panels {
|
||||
if panel.State == plugins.PluginStateAlpha && !hs.Cfg.EnableAlphaPanels {
|
||||
if panel.State == plugins.PluginStateAlpha && !hs.Cfg.PluginsEnableAlpha {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -162,6 +162,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
|
||||
"hideFromList": panel.HideFromList,
|
||||
"sort": getPanelSort(panel.Id),
|
||||
"dataFormats": panel.DataFormats,
|
||||
"state": panel.State,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ func (hs *HTTPServer) GetPluginList(c *m.ReqContext) Response {
|
||||
continue
|
||||
}
|
||||
|
||||
if pluginDef.State == plugins.PluginStateAlpha && !hs.Cfg.EnableAlphaPanels {
|
||||
if pluginDef.State == plugins.PluginStateAlpha && !hs.Cfg.PluginsEnableAlpha {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -142,9 +142,6 @@ var (
|
||||
// Basic Auth
|
||||
BasicAuthEnabled bool
|
||||
|
||||
// Plugin settings
|
||||
PluginAppsSkipVerifyTLS bool
|
||||
|
||||
// Session settings.
|
||||
SessionOptions session.Options
|
||||
SessionConnMaxLifetime int64
|
||||
@ -233,7 +230,8 @@ type Cfg struct {
|
||||
MetricsEndpointEnabled bool
|
||||
MetricsEndpointBasicAuthUsername string
|
||||
MetricsEndpointBasicAuthPassword string
|
||||
EnableAlphaPanels bool
|
||||
PluginsEnableAlpha bool
|
||||
PluginsAppsSkipVerifyTLS bool
|
||||
DisableSanitizeHtml bool
|
||||
EnterpriseLicensePath string
|
||||
|
||||
@ -721,9 +719,6 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
|
||||
authBasic := iniFile.Section("auth.basic")
|
||||
BasicAuthEnabled = authBasic.Key("enabled").MustBool(true)
|
||||
|
||||
// global plugin settings
|
||||
PluginAppsSkipVerifyTLS = iniFile.Section("plugins").Key("app_tls_skip_verify_insecure").MustBool(false)
|
||||
|
||||
// Rendering
|
||||
renderSec := iniFile.Section("rendering")
|
||||
cfg.RendererUrl = renderSec.Key("server_url").String()
|
||||
@ -771,9 +766,17 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
|
||||
explore := iniFile.Section("explore")
|
||||
ExploreEnabled = explore.Key("enabled").MustBool(true)
|
||||
|
||||
panels := iniFile.Section("panels")
|
||||
cfg.EnableAlphaPanels = panels.Key("enable_alpha").MustBool(false)
|
||||
cfg.DisableSanitizeHtml = panels.Key("disable_sanitize_html").MustBool(false)
|
||||
panelsSection := iniFile.Section("panels")
|
||||
cfg.DisableSanitizeHtml = panelsSection.Key("disable_sanitize_html").MustBool(false)
|
||||
|
||||
pluginsSection := iniFile.Section("plugins")
|
||||
cfg.PluginsEnableAlpha = pluginsSection.Key("enable_alpha").MustBool(false)
|
||||
cfg.PluginsAppsSkipVerifyTLS = iniFile.Section("plugins").Key("app_tls_skip_verify_insecure").MustBool(false)
|
||||
|
||||
// check old location for this option
|
||||
if panelsSection.Key("enable_alpha").MustBool(false) {
|
||||
cfg.PluginsEnableAlpha = true
|
||||
}
|
||||
|
||||
cfg.readSessionConfig()
|
||||
cfg.readSmtpSettings()
|
||||
|
@ -7,7 +7,7 @@ import { AlertBox } from 'app/core/components/AlertBox/AlertBox';
|
||||
|
||||
// Types
|
||||
import { PanelPlugin, AppNotificationSeverity } from 'app/types';
|
||||
import { PanelProps, ReactPanelPlugin } from '@grafana/ui';
|
||||
import { PanelProps, ReactPanelPlugin, PluginType } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
pluginId: string;
|
||||
@ -45,6 +45,7 @@ export function getPanelPluginNotFound(id: string): PanelPlugin {
|
||||
id: id,
|
||||
name: id,
|
||||
sort: 100,
|
||||
type: PluginType.panel,
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
dataFormats: [],
|
||||
|
@ -18,6 +18,7 @@ import { PanelModel } from '../state';
|
||||
import { DashboardModel } from '../state';
|
||||
import { PanelPlugin } from 'app/types/plugins';
|
||||
import { VizPickerSearch } from './VizPickerSearch';
|
||||
import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
@ -238,6 +239,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
|
||||
onClose={this.onCloseVizPicker}
|
||||
/>
|
||||
</FadeIn>
|
||||
<PluginStateinfo state={plugin.state} />
|
||||
{this.renderPanelOptions()}
|
||||
</>
|
||||
</EditorTabBody>
|
||||
|
@ -24,6 +24,7 @@ import { getRouteParamsId } from 'app/core/selectors/location';
|
||||
import { NavModel, Plugin, StoreState } from 'app/types/';
|
||||
import { DataSourceSettings } from '@grafana/ui/src/types/';
|
||||
import { getDataSourceLoadingNav } from '../state/navModel';
|
||||
import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
|
||||
|
||||
export interface Props {
|
||||
navModel: NavModel;
|
||||
@ -44,11 +45,6 @@ interface State {
|
||||
testingStatus?: string;
|
||||
}
|
||||
|
||||
enum DataSourceStates {
|
||||
Alpha = 'alpha',
|
||||
Beta = 'beta',
|
||||
}
|
||||
|
||||
export class DataSourceSettingsPage extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
@ -110,32 +106,6 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
|
||||
return this.props.dataSource.readOnly === true;
|
||||
}
|
||||
|
||||
shouldRenderInfoBox() {
|
||||
const { state } = this.props.dataSourceMeta;
|
||||
|
||||
return state === DataSourceStates.Alpha || state === DataSourceStates.Beta;
|
||||
}
|
||||
|
||||
getInfoText() {
|
||||
const { dataSourceMeta } = this.props;
|
||||
|
||||
switch (dataSourceMeta.state) {
|
||||
case DataSourceStates.Alpha:
|
||||
return (
|
||||
'This plugin is marked as being in alpha state, which means it is in early development phase and updates' +
|
||||
' will include breaking changes.'
|
||||
);
|
||||
|
||||
case DataSourceStates.Beta:
|
||||
return (
|
||||
'This plugin is marked as being in a beta development state. This means it is in currently in active' +
|
||||
' development and could be missing important features.'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderIsReadOnlyMessage() {
|
||||
return (
|
||||
<div className="grafana-info-box span8">
|
||||
@ -196,7 +166,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
|
||||
<div>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
{this.isReadOnly() && this.renderIsReadOnlyMessage()}
|
||||
{this.shouldRenderInfoBox() && <div className="grafana-info-box">{this.getInfoText()}</div>}
|
||||
<PluginStateinfo state={dataSourceMeta.state} />
|
||||
|
||||
<BasicSettings
|
||||
dataSourceName={dataSource.name}
|
||||
|
@ -11,11 +11,9 @@ exports[`Render should render alpha info text 1`] = `
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="grafana-info-box"
|
||||
>
|
||||
This plugin is marked as being in alpha state, which means it is in early development phase and updates will include breaking changes.
|
||||
</div>
|
||||
<PluginStateinfo
|
||||
state="alpha"
|
||||
/>
|
||||
<BasicSettings
|
||||
dataSourceName="gdev-cloudwatch"
|
||||
isDefault={false}
|
||||
@ -49,6 +47,7 @@ exports[`Render should render alpha info text 1`] = `
|
||||
}
|
||||
dataSourceMeta={
|
||||
Object {
|
||||
"baseUrl": "path/to/plugin",
|
||||
"defaultNavUrl": "some/url",
|
||||
"enabled": false,
|
||||
"hasUpdate": false,
|
||||
@ -78,11 +77,11 @@ exports[`Render should render alpha info text 1`] = `
|
||||
"version": "1",
|
||||
},
|
||||
"latestVersion": "1",
|
||||
"module": Object {},
|
||||
"module": "path/to/module",
|
||||
"name": "pretty cool plugin 1",
|
||||
"pinned": false,
|
||||
"state": "alpha",
|
||||
"type": "",
|
||||
"type": "panel",
|
||||
}
|
||||
}
|
||||
onModelChange={[Function]}
|
||||
@ -113,11 +112,9 @@ exports[`Render should render beta info text 1`] = `
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="grafana-info-box"
|
||||
>
|
||||
This plugin is marked as being in a beta development state. This means it is in currently in active development and could be missing important features.
|
||||
</div>
|
||||
<PluginStateinfo
|
||||
state="beta"
|
||||
/>
|
||||
<BasicSettings
|
||||
dataSourceName="gdev-cloudwatch"
|
||||
isDefault={false}
|
||||
@ -151,6 +148,7 @@ exports[`Render should render beta info text 1`] = `
|
||||
}
|
||||
dataSourceMeta={
|
||||
Object {
|
||||
"baseUrl": "path/to/plugin",
|
||||
"defaultNavUrl": "some/url",
|
||||
"enabled": false,
|
||||
"hasUpdate": false,
|
||||
@ -180,11 +178,11 @@ exports[`Render should render beta info text 1`] = `
|
||||
"version": "1",
|
||||
},
|
||||
"latestVersion": "1",
|
||||
"module": Object {},
|
||||
"module": "path/to/module",
|
||||
"name": "pretty cool plugin 1",
|
||||
"pinned": false,
|
||||
"state": "beta",
|
||||
"type": "",
|
||||
"type": "panel",
|
||||
}
|
||||
}
|
||||
onModelChange={[Function]}
|
||||
@ -215,6 +213,7 @@ exports[`Render should render component 1`] = `
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<PluginStateinfo />
|
||||
<BasicSettings
|
||||
dataSourceName="gdev-cloudwatch"
|
||||
isDefault={false}
|
||||
@ -248,6 +247,7 @@ exports[`Render should render component 1`] = `
|
||||
}
|
||||
dataSourceMeta={
|
||||
Object {
|
||||
"baseUrl": "path/to/plugin",
|
||||
"defaultNavUrl": "some/url",
|
||||
"enabled": false,
|
||||
"hasUpdate": false,
|
||||
@ -277,11 +277,10 @@ exports[`Render should render component 1`] = `
|
||||
"version": "1",
|
||||
},
|
||||
"latestVersion": "1",
|
||||
"module": Object {},
|
||||
"module": "path/to/module",
|
||||
"name": "pretty cool plugin 1",
|
||||
"pinned": false,
|
||||
"state": "",
|
||||
"type": "",
|
||||
"type": "panel",
|
||||
}
|
||||
}
|
||||
onModelChange={[Function]}
|
||||
@ -317,6 +316,7 @@ exports[`Render should render is ready only message 1`] = `
|
||||
>
|
||||
This datasource was added by config and cannot be modified using the UI. Please contact your server admin to update this datasource.
|
||||
</div>
|
||||
<PluginStateinfo />
|
||||
<BasicSettings
|
||||
dataSourceName="gdev-cloudwatch"
|
||||
isDefault={false}
|
||||
@ -350,6 +350,7 @@ exports[`Render should render is ready only message 1`] = `
|
||||
}
|
||||
dataSourceMeta={
|
||||
Object {
|
||||
"baseUrl": "path/to/plugin",
|
||||
"defaultNavUrl": "some/url",
|
||||
"enabled": false,
|
||||
"hasUpdate": false,
|
||||
@ -379,11 +380,10 @@ exports[`Render should render is ready only message 1`] = `
|
||||
"version": "1",
|
||||
},
|
||||
"latestVersion": "1",
|
||||
"module": Object {},
|
||||
"module": "path/to/module",
|
||||
"name": "pretty cool plugin 1",
|
||||
"pinned": false,
|
||||
"state": "",
|
||||
"type": "",
|
||||
"type": "panel",
|
||||
}
|
||||
}
|
||||
onModelChange={[Function]}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { NavModel, NavModelItem } from 'app/types';
|
||||
import { PluginMeta, DataSourceSettings } from '@grafana/ui/src/types';
|
||||
import { PluginMeta, DataSourceSettings, PluginType } from '@grafana/ui/src/types';
|
||||
import config from 'app/core/config';
|
||||
|
||||
export function buildNavModel(dataSource: DataSourceSettings, pluginMeta: PluginMeta): NavModelItem {
|
||||
@ -67,6 +67,7 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
type: PluginType.datasource,
|
||||
name: '',
|
||||
info: {
|
||||
author: {
|
||||
@ -83,7 +84,7 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
|
||||
updated: '',
|
||||
version: '',
|
||||
},
|
||||
includes: [{ type: '', name: '', path: '' }],
|
||||
includes: [],
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
}
|
||||
|
@ -14,10 +14,11 @@ import {
|
||||
} from './actions';
|
||||
import { getMockDataSources, getMockDataSource } from '../__mocks__/dataSourcesMocks';
|
||||
import { LayoutModes } from 'app/core/components/LayoutSelector/LayoutSelector';
|
||||
import { DataSourcesState } from 'app/types';
|
||||
import { PluginMetaInfo } from '@grafana/ui';
|
||||
import { DataSourcesState, Plugin } from 'app/types';
|
||||
import { PluginMetaInfo, PluginType } from '@grafana/ui';
|
||||
|
||||
const mockPlugin = () => ({
|
||||
const mockPlugin = () =>
|
||||
({
|
||||
defaultNavUrl: 'defaultNavUrl',
|
||||
enabled: true,
|
||||
hasUpdate: true,
|
||||
@ -26,10 +27,9 @@ const mockPlugin = () => ({
|
||||
latestVersion: 'latestVersion',
|
||||
name: 'name',
|
||||
pinned: true,
|
||||
state: 'state',
|
||||
type: 'type',
|
||||
module: {},
|
||||
});
|
||||
type: PluginType.datasource,
|
||||
module: 'path/to/module',
|
||||
} as Plugin);
|
||||
|
||||
describe('dataSourcesReducer', () => {
|
||||
describe('when dataSourcesLoaded is dispatched', () => {
|
||||
|
34
public/app/features/plugins/PluginStateInfo.tsx
Normal file
34
public/app/features/plugins/PluginStateInfo.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React, { FC } from 'react';
|
||||
import { PluginState } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
state?: PluginState;
|
||||
}
|
||||
|
||||
function getPluginStateInfoText(state?: PluginState): string | null {
|
||||
switch (state) {
|
||||
case PluginState.alpha:
|
||||
return (
|
||||
'This plugin is marked as being in alpha state, which means it is in early development phase and updates' +
|
||||
' will include breaking changes.'
|
||||
);
|
||||
|
||||
case PluginState.beta:
|
||||
return (
|
||||
'This plugin is marked as being in a beta development state. This means it is in currently in active' +
|
||||
' development and could be missing important features.'
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const PluginStateinfo: FC<Props> = props => {
|
||||
const text = getPluginStateInfoText(props.state);
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div className="grafana-info-box">{text}</div>;
|
||||
};
|
||||
|
||||
export default PluginStateinfo;
|
@ -1,4 +1,5 @@
|
||||
import { Plugin, PanelPlugin, PanelDataFormat } from 'app/types';
|
||||
import { PluginType } from '@grafana/ui';
|
||||
|
||||
export const getMockPlugins = (amount: number): Plugin[] => {
|
||||
const plugins = [];
|
||||
@ -36,6 +37,7 @@ export const getMockPlugins = (amount: number): Plugin[] => {
|
||||
export const getPanelPlugin = (options: Partial<PanelPlugin>): PanelPlugin => {
|
||||
return {
|
||||
id: options.id,
|
||||
type: PluginType.panel,
|
||||
name: options.id,
|
||||
sort: options.sort || 1,
|
||||
dataFormats: [PanelDataFormat.TimeSeries],
|
||||
@ -81,9 +83,9 @@ export const getMockPlugin = () => {
|
||||
},
|
||||
latestVersion: '1',
|
||||
name: 'pretty cool plugin 1',
|
||||
baseUrl: 'path/to/plugin',
|
||||
pinned: false,
|
||||
state: '',
|
||||
type: '',
|
||||
module: {},
|
||||
};
|
||||
type: PluginType.panel,
|
||||
module: 'path/to/module',
|
||||
} as Plugin;
|
||||
};
|
||||
|
@ -15,8 +15,9 @@ exports[`Render should render component 1`] = `
|
||||
className="card-item-type"
|
||||
>
|
||||
<i
|
||||
className="icon-gf icon-gf-"
|
||||
className="icon-gf icon-gf-panel"
|
||||
/>
|
||||
panel
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@ -63,8 +64,9 @@ exports[`Render should render has plugin section 1`] = `
|
||||
className="card-item-type"
|
||||
>
|
||||
<i
|
||||
className="icon-gf icon-gf-"
|
||||
className="icon-gf icon-gf-panel"
|
||||
/>
|
||||
panel
|
||||
</div>
|
||||
<div
|
||||
className="card-item-notice"
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { AngularPanelPlugin, ReactPanelPlugin, PluginMetaInfo } from '@grafana/ui/src/types';
|
||||
import { AngularPanelPlugin, ReactPanelPlugin, PluginMetaInfo, PluginMeta } from '@grafana/ui/src/types';
|
||||
|
||||
export interface PanelPlugin {
|
||||
id: string;
|
||||
name: string;
|
||||
export interface PanelPlugin extends PluginMeta {
|
||||
hideFromList?: boolean;
|
||||
module: string;
|
||||
baseUrl: string;
|
||||
info: PluginMetaInfo;
|
||||
sort: number;
|
||||
@ -19,18 +16,11 @@ export enum PanelDataFormat {
|
||||
TimeSeries = 'time_series',
|
||||
}
|
||||
|
||||
export interface Plugin {
|
||||
export interface Plugin extends PluginMeta {
|
||||
defaultNavUrl: string;
|
||||
enabled: boolean;
|
||||
hasUpdate: boolean;
|
||||
id: string;
|
||||
info: PluginMetaInfo;
|
||||
latestVersion: string;
|
||||
name: string;
|
||||
pinned: boolean;
|
||||
state: string;
|
||||
type: string;
|
||||
module: any;
|
||||
}
|
||||
|
||||
export interface PluginDashboard {
|
||||
|
Loading…
Reference in New Issue
Block a user