mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 10:20:29 -06:00
Plugins: Angular deprecation: Detect Angular plugins and expose in API (#66824)
* Plugins: Angular deprecation: Detect Angular plugins and expose in API * Plugins: Angular detector: Close module.js * Plugins: Angular detector: consistent error messages * Plugins: Angular detector: Add test for missing module.js * Plugins: Angular detector: Fix integration tests * Plugins: Angular detector: Changed Angular detection patterns * Moved inMemoryFS to test_utils.go * Add different angular detectors * Plugins: Update plugins/data/expectedListResp.json * Plugins: Rename angular property to angularDetected * Plugins: Rename angular to angularDetected in Plugin and PluginDTO * Plugins: Add angularDetected to datasources, apps and plugins frontendsettings * Plugins: Add test for AngularDetected frontend settings
This commit is contained in:
parent
4310f574db
commit
16359c82a2
@ -29,22 +29,23 @@ type PluginSetting struct {
|
||||
}
|
||||
|
||||
type PluginListItem struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Id string `json:"id"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Pinned bool `json:"pinned"`
|
||||
Info plugins.Info `json:"info"`
|
||||
Dependencies plugins.Dependencies `json:"dependencies"`
|
||||
LatestVersion string `json:"latestVersion"`
|
||||
HasUpdate bool `json:"hasUpdate"`
|
||||
DefaultNavUrl string `json:"defaultNavUrl"`
|
||||
Category string `json:"category"`
|
||||
State plugins.ReleaseState `json:"state"`
|
||||
Signature plugins.SignatureStatus `json:"signature"`
|
||||
SignatureType plugins.SignatureType `json:"signatureType"`
|
||||
SignatureOrg string `json:"signatureOrg"`
|
||||
AccessControl accesscontrol.Metadata `json:"accessControl,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Id string `json:"id"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Pinned bool `json:"pinned"`
|
||||
Info plugins.Info `json:"info"`
|
||||
Dependencies plugins.Dependencies `json:"dependencies"`
|
||||
LatestVersion string `json:"latestVersion"`
|
||||
HasUpdate bool `json:"hasUpdate"`
|
||||
DefaultNavUrl string `json:"defaultNavUrl"`
|
||||
Category string `json:"category"`
|
||||
State plugins.ReleaseState `json:"state"`
|
||||
Signature plugins.SignatureStatus `json:"signature"`
|
||||
SignatureType plugins.SignatureType `json:"signatureType"`
|
||||
SignatureOrg string `json:"signatureOrg"`
|
||||
AccessControl accesscontrol.Metadata `json:"accessControl,omitempty"`
|
||||
AngularDetected bool `json:"angularDetected"`
|
||||
}
|
||||
|
||||
type PluginList []PluginListItem
|
||||
|
@ -64,16 +64,17 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
||||
}
|
||||
|
||||
panels[panel.ID] = plugins.PanelDTO{
|
||||
ID: panel.ID,
|
||||
Name: panel.Name,
|
||||
Info: panel.Info,
|
||||
Module: panel.Module,
|
||||
BaseURL: panel.BaseURL,
|
||||
SkipDataQuery: panel.SkipDataQuery,
|
||||
HideFromList: panel.HideFromList,
|
||||
ReleaseState: string(panel.State),
|
||||
Signature: string(panel.Signature),
|
||||
Sort: getPanelSort(panel.ID),
|
||||
ID: panel.ID,
|
||||
Name: panel.Name,
|
||||
Info: panel.Info,
|
||||
Module: panel.Module,
|
||||
BaseURL: panel.BaseURL,
|
||||
SkipDataQuery: panel.SkipDataQuery,
|
||||
HideFromList: panel.HideFromList,
|
||||
ReleaseState: string(panel.State),
|
||||
Signature: string(panel.Signature),
|
||||
Sort: getPanelSort(panel.ID),
|
||||
AngularDetected: panel.AngularDetected,
|
||||
}
|
||||
}
|
||||
|
||||
@ -317,6 +318,7 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug
|
||||
Module: plugin.Module,
|
||||
BaseURL: plugin.BaseURL,
|
||||
}
|
||||
dsDTO.AngularDetected = plugin.AngularDetected
|
||||
|
||||
if ds.JsonData == nil {
|
||||
dsDTO.JSONData = make(map[string]interface{})
|
||||
@ -389,6 +391,7 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug
|
||||
Module: ds.Module,
|
||||
BaseURL: ds.BaseURL,
|
||||
},
|
||||
AngularDetected: ds.AngularDetected,
|
||||
}
|
||||
if ds.Name == grafanads.DatasourceName {
|
||||
dto.ID = grafanads.DatasourceID
|
||||
@ -403,10 +406,11 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug
|
||||
|
||||
func newAppDTO(plugin plugins.PluginDTO, settings pluginsettings.InfoDTO) *plugins.AppDTO {
|
||||
app := &plugins.AppDTO{
|
||||
ID: plugin.ID,
|
||||
Version: plugin.Info.Version,
|
||||
Path: plugin.Module,
|
||||
Preload: false,
|
||||
ID: plugin.ID,
|
||||
Version: plugin.Info.Version,
|
||||
Path: plugin.Module,
|
||||
Preload: false,
|
||||
AngularDetected: plugin.AngularDetected,
|
||||
}
|
||||
|
||||
if settings.Enabled {
|
||||
|
@ -280,6 +280,41 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "angular app plugin",
|
||||
pluginStore: func() plugins.Store {
|
||||
return &plugins.FakePluginStore{
|
||||
PluginList: []plugins.PluginDTO{
|
||||
{
|
||||
Module: fmt.Sprintf("/%s/module.js", "test-app"),
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test-app",
|
||||
Info: plugins.Info{Version: "0.5.0"},
|
||||
Type: plugins.App,
|
||||
Preload: true,
|
||||
},
|
||||
AngularDetected: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
pluginSettings: func() pluginsettings.Service {
|
||||
return &pluginsettings.FakePluginSettings{
|
||||
Plugins: newAppSettings("test-app", true),
|
||||
}
|
||||
},
|
||||
expected: settings{
|
||||
Apps: map[string]*plugins.AppDTO{
|
||||
"test-app": {
|
||||
ID: "test-app",
|
||||
Preload: true,
|
||||
Path: "/test-app/module.js",
|
||||
Version: "0.5.0",
|
||||
AngularDetected: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
@ -128,18 +128,19 @@ func (hs *HTTPServer) GetPluginList(c *contextmodel.ReqContext) response.Respons
|
||||
result := make(dtos.PluginList, 0)
|
||||
for _, pluginDef := range filteredPluginDefinitions {
|
||||
listItem := dtos.PluginListItem{
|
||||
Id: pluginDef.ID,
|
||||
Name: pluginDef.Name,
|
||||
Type: string(pluginDef.Type),
|
||||
Category: pluginDef.Category,
|
||||
Info: pluginDef.Info,
|
||||
Dependencies: pluginDef.Dependencies,
|
||||
DefaultNavUrl: path.Join(hs.Cfg.AppSubURL, pluginDef.DefaultNavURL),
|
||||
State: pluginDef.State,
|
||||
Signature: pluginDef.Signature,
|
||||
SignatureType: pluginDef.SignatureType,
|
||||
SignatureOrg: pluginDef.SignatureOrg,
|
||||
AccessControl: pluginsMetadata[pluginDef.ID],
|
||||
Id: pluginDef.ID,
|
||||
Name: pluginDef.Name,
|
||||
Type: string(pluginDef.Type),
|
||||
Category: pluginDef.Category,
|
||||
Info: pluginDef.Info,
|
||||
Dependencies: pluginDef.Dependencies,
|
||||
DefaultNavUrl: path.Join(hs.Cfg.AppSubURL, pluginDef.DefaultNavURL),
|
||||
State: pluginDef.State,
|
||||
Signature: pluginDef.Signature,
|
||||
SignatureType: pluginDef.SignatureType,
|
||||
SignatureOrg: pluginDef.SignatureOrg,
|
||||
AccessControl: pluginsMetadata[pluginDef.ID],
|
||||
AngularDetected: pluginDef.AngularDetected,
|
||||
}
|
||||
|
||||
update, exists := hs.pluginsUpdateChecker.HasUpdate(c.Req.Context(), pluginDef.ID)
|
||||
|
@ -0,0 +1,82 @@
|
||||
package angulardetector
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
)
|
||||
|
||||
var (
|
||||
_ detector = &containsBytesDetector{}
|
||||
_ detector = ®exDetector{}
|
||||
)
|
||||
|
||||
// detector implements a check to see if a plugin uses Angular.
|
||||
type detector interface {
|
||||
// Detect takes the content of a moduleJs file and returns true if the plugin is using Angular.
|
||||
Detect(moduleJs []byte) bool
|
||||
}
|
||||
|
||||
// containsBytesDetector is a detector that returns true if module.js contains the "pattern" string.
|
||||
type containsBytesDetector struct {
|
||||
pattern []byte
|
||||
}
|
||||
|
||||
// Detect returns true if moduleJs contains the byte slice d.pattern.
|
||||
func (d *containsBytesDetector) Detect(moduleJs []byte) bool {
|
||||
return bytes.Contains(moduleJs, d.pattern)
|
||||
}
|
||||
|
||||
// regexDetector is a detector that returns true if the module.js content matches a regular expression.
|
||||
type regexDetector struct {
|
||||
regex *regexp.Regexp
|
||||
}
|
||||
|
||||
// Detect returns true if moduleJs matches the regular expression d.regex.
|
||||
func (d *regexDetector) Detect(moduleJs []byte) bool {
|
||||
return d.regex.Match(moduleJs)
|
||||
}
|
||||
|
||||
// angularDetectors contains all the detectors to detect Angular plugins.
|
||||
// They are executed in the specified order.
|
||||
var angularDetectors = []detector{
|
||||
&containsBytesDetector{pattern: []byte("PanelCtrl")},
|
||||
&containsBytesDetector{pattern: []byte("QueryCtrl")},
|
||||
&containsBytesDetector{pattern: []byte("app/plugins/sdk")},
|
||||
&containsBytesDetector{pattern: []byte("angular.isNumber(")},
|
||||
&containsBytesDetector{pattern: []byte("editor.html")},
|
||||
&containsBytesDetector{pattern: []byte("ctrl.annotation")},
|
||||
&containsBytesDetector{pattern: []byte("getLegacyAngularInjector")},
|
||||
|
||||
®exDetector{regex: regexp.MustCompile(`['"](app/core/utils/promiseToDigest)|(app/plugins/.*?)|(app/core/core_module)['"]`)},
|
||||
®exDetector{regex: regexp.MustCompile(`from\s+['"]grafana\/app\/`)},
|
||||
®exDetector{regex: regexp.MustCompile(`System\.register\(`)},
|
||||
}
|
||||
|
||||
// Inspect open module.js and checks if the plugin is using Angular by matching against its source code.
|
||||
// It returns true if module.js matches against any of the detectors in angularDetectors.
|
||||
func Inspect(p *plugins.Plugin) (isAngular bool, err error) {
|
||||
f, err := p.FS.Open("module.js")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("open module.js: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if closeErr := f.Close(); closeErr != nil && err == nil {
|
||||
err = fmt.Errorf("close module.js: %w", closeErr)
|
||||
}
|
||||
}()
|
||||
b, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("module.js readall: %w", err)
|
||||
}
|
||||
for _, d := range angularDetectors {
|
||||
if d.Detect(b) {
|
||||
isAngular = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package angulardetector
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAngularDetector_Inspect(t *testing.T) {
|
||||
type tc struct {
|
||||
name string
|
||||
plugin *plugins.Plugin
|
||||
exp bool
|
||||
}
|
||||
var tcs []tc
|
||||
|
||||
// Angular imports
|
||||
for i, content := range [][]byte{
|
||||
[]byte(`import { MetricsPanelCtrl } from 'grafana/app/plugins/sdk';`),
|
||||
[]byte(`define(["app/plugins/sdk"],(function(n){return function(n){var t={};function e(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return n[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}return e.m=n,e.c=t,e.d=function(n,t,r){e.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:r})},e.r=function(n){"undefined"!=typeof`),
|
||||
[]byte(`define(["app/plugins/sdk"],(function(n){return function(n){var t={};function e(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return n[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}return e.m=n,e.c=t,e.d=function(n,t,r){e.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:r})},e.r=function(n){"undefined"!=typeof Symbol&&Symbol.toSt`),
|
||||
[]byte(`define(["react","lodash","@grafana/data","@grafana/ui","@emotion/css","@grafana/runtime","moment","app/core/utils/datemath","jquery","app/plugins/sdk","app/core/core_module","app/core/core","app/core/table_model","app/core/utils/kbn","app/core/config","angular"],(function(e,t,r,n,i,a,o,s,u,l,c,p,f,h,d,m){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};retur`),
|
||||
} {
|
||||
tcs = append(tcs, tc{
|
||||
name: "angular " + strconv.Itoa(i),
|
||||
plugin: &plugins.Plugin{
|
||||
FS: plugins.NewInMemoryFS(map[string][]byte{
|
||||
"module.js": content,
|
||||
}),
|
||||
},
|
||||
exp: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Not angular
|
||||
tcs = append(tcs, tc{
|
||||
name: "not angular",
|
||||
plugin: &plugins.Plugin{
|
||||
FS: plugins.NewInMemoryFS(map[string][]byte{
|
||||
"module.js": []byte(`import { PanelPlugin } from '@grafana/data'`),
|
||||
}),
|
||||
},
|
||||
exp: false,
|
||||
})
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
isAngular, err := Inspect(tc.plugin)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.exp, isAngular)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("no module.js", func(t *testing.T) {
|
||||
p := &plugins.Plugin{FS: plugins.NewInMemoryFS(map[string][]byte{})}
|
||||
_, err := Inspect(p)
|
||||
require.ErrorIs(t, err, plugins.ErrFileNotExist)
|
||||
})
|
||||
}
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/config"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/angulardetector"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/finder"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/initializer"
|
||||
@ -153,6 +154,15 @@ func (l *Loader) loadPlugins(ctx context.Context, src plugins.PluginSource, foun
|
||||
}
|
||||
}
|
||||
|
||||
// Detect angular for external plugins
|
||||
if plugin.IsExternalPlugin() {
|
||||
var err error
|
||||
plugin.AngularDetected, err = angulardetector.Inspect(plugin)
|
||||
if err != nil {
|
||||
l.log.Warn("could not inspect plugin for angular", "pluginID", plugin.ID, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
if plugin.IsApp() {
|
||||
setDefaultNavURL(plugin)
|
||||
}
|
||||
|
@ -210,18 +210,19 @@ type PluginMetaDTO struct {
|
||||
}
|
||||
|
||||
type DataSourceDTO struct {
|
||||
ID int64 `json:"id,omitempty"`
|
||||
UID string `json:"uid,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
PluginMeta *PluginMetaDTO `json:"meta"`
|
||||
URL string `json:"url,omitempty"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
Access string `json:"access,omitempty"`
|
||||
Preload bool `json:"preload"`
|
||||
Module string `json:"module,omitempty"`
|
||||
JSONData map[string]interface{} `json:"jsonData"`
|
||||
ReadOnly bool `json:"readOnly"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
UID string `json:"uid,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
PluginMeta *PluginMetaDTO `json:"meta"`
|
||||
URL string `json:"url,omitempty"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
Access string `json:"access,omitempty"`
|
||||
Preload bool `json:"preload"`
|
||||
Module string `json:"module,omitempty"`
|
||||
JSONData map[string]interface{} `json:"jsonData"`
|
||||
ReadOnly bool `json:"readOnly"`
|
||||
AngularDetected bool `json:"angularDetected"`
|
||||
|
||||
BasicAuth string `json:"basicAuth,omitempty"`
|
||||
WithCredentials bool `json:"withCredentials,omitempty"`
|
||||
@ -241,23 +242,25 @@ type DataSourceDTO struct {
|
||||
}
|
||||
|
||||
type PanelDTO struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Info Info `json:"info"`
|
||||
HideFromList bool `json:"hideFromList"`
|
||||
Sort int `json:"sort"`
|
||||
SkipDataQuery bool `json:"skipDataQuery"`
|
||||
ReleaseState string `json:"state"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Signature string `json:"signature"`
|
||||
Module string `json:"module"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Info Info `json:"info"`
|
||||
HideFromList bool `json:"hideFromList"`
|
||||
Sort int `json:"sort"`
|
||||
SkipDataQuery bool `json:"skipDataQuery"`
|
||||
ReleaseState string `json:"state"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Signature string `json:"signature"`
|
||||
Module string `json:"module"`
|
||||
AngularDetected bool `json:"angularDetected"`
|
||||
}
|
||||
|
||||
type AppDTO struct {
|
||||
ID string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
Version string `json:"version"`
|
||||
Preload bool `json:"preload"`
|
||||
ID string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
Version string `json:"version"`
|
||||
Preload bool `json:"preload"`
|
||||
AngularDetected bool `json:"angularDetected"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -51,6 +51,8 @@ type Plugin struct {
|
||||
Module string
|
||||
BaseURL string
|
||||
|
||||
AngularDetected bool
|
||||
|
||||
Renderer pluginextensionv2.RendererPlugin
|
||||
SecretsManager secretsmanagerplugin.SecretsManagerPlugin
|
||||
client backendplugin.Plugin
|
||||
@ -80,6 +82,8 @@ type PluginDTO struct {
|
||||
// SystemJS fields
|
||||
Module string
|
||||
BaseURL string
|
||||
|
||||
AngularDetected bool
|
||||
}
|
||||
|
||||
func (p PluginDTO) SupportsStreaming() bool {
|
||||
@ -424,6 +428,7 @@ func (p *Plugin) ToDTO() PluginDTO {
|
||||
SignatureError: p.SignatureError,
|
||||
Module: p.Module,
|
||||
BaseURL: p.BaseURL,
|
||||
AngularDetected: p.AngularDetected,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Alertmanager",
|
||||
@ -74,7 +75,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Annotations list",
|
||||
@ -110,7 +112,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Azure Monitor",
|
||||
@ -168,7 +171,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Bar chart",
|
||||
@ -204,7 +208,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Bar gauge",
|
||||
@ -240,7 +245,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Candlestick",
|
||||
@ -276,7 +282,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Canvas",
|
||||
@ -312,7 +319,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "CloudWatch",
|
||||
@ -348,7 +356,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Dashboard list",
|
||||
@ -384,7 +393,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Datagrid",
|
||||
@ -420,7 +430,8 @@
|
||||
"state": "beta",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Elasticsearch",
|
||||
@ -461,7 +472,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Flame Graph",
|
||||
@ -497,7 +509,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Gauge",
|
||||
@ -533,7 +546,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Geomap",
|
||||
@ -569,7 +583,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Getting Started",
|
||||
@ -605,7 +620,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Google Cloud Monitoring",
|
||||
@ -641,7 +657,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Grafana Pyroscope",
|
||||
@ -682,7 +699,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Graph (old)",
|
||||
@ -718,7 +736,8 @@
|
||||
"state": "deprecated",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Graphite",
|
||||
@ -763,7 +782,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Heatmap",
|
||||
@ -799,7 +819,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Histogram",
|
||||
@ -835,7 +856,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "InfluxDB",
|
||||
@ -871,7 +893,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Jaeger",
|
||||
@ -916,7 +939,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Logs",
|
||||
@ -952,7 +976,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Loki",
|
||||
@ -997,7 +1022,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Microsoft SQL Server",
|
||||
@ -1033,7 +1059,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "MySQL",
|
||||
@ -1069,7 +1096,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "News",
|
||||
@ -1105,7 +1133,8 @@
|
||||
"state": "beta",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Node Graph",
|
||||
@ -1141,7 +1170,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "OpenTSDB",
|
||||
@ -1177,7 +1207,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Parca",
|
||||
@ -1218,7 +1249,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Pie chart",
|
||||
@ -1254,7 +1286,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "PostgreSQL",
|
||||
@ -1290,7 +1323,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Prometheus",
|
||||
@ -1331,7 +1365,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Stat",
|
||||
@ -1367,7 +1402,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "State timeline",
|
||||
@ -1403,7 +1439,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Status history",
|
||||
@ -1439,7 +1476,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Table",
|
||||
@ -1475,7 +1513,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Table (old)",
|
||||
@ -1511,7 +1550,8 @@
|
||||
"state": "deprecated",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Tempo",
|
||||
@ -1552,7 +1592,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "TestData",
|
||||
@ -1588,7 +1629,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Text",
|
||||
@ -1624,7 +1666,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Time series",
|
||||
@ -1660,7 +1703,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Traces",
|
||||
@ -1696,7 +1740,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Trend",
|
||||
@ -1732,7 +1777,8 @@
|
||||
"state": "beta",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Welcome",
|
||||
@ -1768,7 +1814,8 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "XY Chart",
|
||||
@ -1804,7 +1851,8 @@
|
||||
"state": "beta",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Zipkin",
|
||||
@ -1845,6 +1893,7 @@
|
||||
"state": "",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": ""
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
}
|
||||
]
|
Loading…
Reference in New Issue
Block a user