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:
Ryan McKinley 2019-04-12 04:46:42 -07:00 committed by Torkel Ödegaard
parent 30dcf0f6c5
commit 3c21a121eb
18 changed files with 154 additions and 114 deletions

View File

@ -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 =

View File

@ -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

View File

@ -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.

View File

@ -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;
}

View File

@ -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,

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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()

View File

@ -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: [],

View File

@ -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>

View File

@ -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}

View File

@ -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]}

View File

@ -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: '',
}

View File

@ -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', () => {

View 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;

View File

@ -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;
};

View File

@ -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"

View File

@ -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 {