Frontend: Remove Angular (#99760)

* chore(angularsupport): delete feature toggle to disable angular

* feat(angular-support): remove config.angularSupportEnabled

* chore(jest): remove angular from setup file

* chore(angular): delete angular deprecation ui components

* refactor(angular): move migration featureflags into migration notice

* chore(dashboard): remove angular deprecation notices

* chore(annotations): remove angular editor loader

* feat(appwrapper): no more angular app loading

* feat(pluginscatalog): clean up angular plugin warnings and logic

* chore(angular): delete angular app and associated files

* feat(plugins): delete old angular graph plugin

* feat(plugins): delete old angular table panel

* feat(frontend): remove unused appEvent type

* feat(dashboards): clean up angular from panel options and menu

* feat(plugins): remove graph and table-old from built in plugins and delete sdk

* feat(frontend): remove angular related imports in routes and explore graph

* feat(theme): remove angular panel styles from global styles

* chore(i18n): run make i18n-extract

* test(api_plugins_test): refresh snapshot due to deleting old graph and table plugins

* chore(angulardeprecation): delete angular migration notice components and usage

* test(frontend): clean up tests that assert rendering angular deprecation notices

* chore(backend): remove autoMigrateOldPanels feature flag

* chore(config): remove angularSupportEnabled from config preventing loading angular plugins

* chore(graphpanel): remove autoMigrateGraphPanel from feature toggles

* chore(tablepanel): delete autoMigrateTablePanel feature flag

* chore(piechart): delete autoMigratePiechartPanel feature flag

* chore(worldmappanel): remove autoMigrateWorldmapPanel feature toggle

* chore(statpanel): remove autoMigrateStatPanel feature flag

* feat(dashboards): remove automigrate feature flags and always auto migrate angular panels

* test(pluginsintegration): fix failing loader test

* test(frontend): wip: fix failures and skip erroring migration tests

* chore(codeowners): remove deleted angular related files and directories

* test(graphite): remove angular mock from test file

* test(dashboards): skip failing exporter test, remove angularSupportEnabled flags

* test(dashbaord): skip another failing panel menu test

* Tests: fixes pkg/services/pluginsintegration/loader/loader_test.go (#100505)

* Tests: fixes pkg/services/pluginsintegration/plugins_integration_test.go

* Trigger Build

* chore(dashboards): remove angularComponent from getPanelMenu, update test

* feat(dashboards): remove all usage of AngularComponent and getAngularLoader

* chore(betterer): refresh results file

* feat(plugins): remove PluginAngularBadge component and usage

* feat(datasource_srv): remove usage of getLegacyAngularInjector

* feat(queryeditor): delete AngularQueryComponentScope type

* Chore: removes Angular from plugin_loader

* Chore: remove angular from getPlugin

* Chore: fix i18n

* Trigger Build

* Chore: remove more Angular from importPanelPlugin

* Chore: remove search options warning

* Chore: remove and deprecate Angular related

* chore(angular): remove angular dependencies from core and runtime

* chore(runtime): delete angular injector

* chore(data): delete angular scope from event bus

* chore(plugin-catalog): remove code pushing app plugins angular config page

* chore(yarn): refresh lock file

* chore(frontend): remove ng-loader from webpack configs, remove systemjs cjs plugin

* chore(navigation): remove tether-drop cleanup from GrafanaRouter, delete dependency

* chore(runtime): delete AngularLoader

* chore(betterer): refresh results file

* chore(betterer): fix out of sync results file

* feat(query): fix type and import errors in QueryEditorRow

* test(dashboards): delete skipped angular related tests

* Tests: add back tests and fix betterer

* Tests: fix broken test

* Trigger build

* chore(i18n): remove angular deprecation related strings

* test: clean up connections and plugins catalog tests

* chore(betterer): update results file

---------

Co-authored-by: Hugo Häggmark <hugo.haggmark@gmail.com>
pull/103443/head
Jack Westbrook 4 months ago committed by GitHub
parent b1198b92c6
commit f96e4e9ad2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 495
      .betterer.results
  2. 14
      .bingo/README.md
  3. 4
      .github/CODEOWNERS
  4. 1
      .github/renovate.json5
  5. 1
      .prettierignore
  6. 57
      docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
  7. 24
      e2e/old-arch/various-suite/graph-auto-migrate.spec.ts
  8. 24
      e2e/various-suite/graph-auto-migrate.spec.ts
  9. 10
      package.json
  10. 10
      packages/grafana-data/src/events/EventBus.ts
  11. 3
      packages/grafana-data/src/events/types.ts
  12. 1
      packages/grafana-data/src/index.ts
  13. 6
      packages/grafana-data/src/types/app.ts
  14. 1
      packages/grafana-data/src/types/config.ts
  15. 10
      packages/grafana-data/src/types/datasource.ts
  16. 28
      packages/grafana-data/src/types/featureToggles.gen.ts
  17. 1
      packages/grafana-data/src/types/plugin.ts
  18. 63
      packages/grafana-data/src/utils/throwIfAngular.test.ts
  19. 15
      packages/grafana-data/src/utils/throwIfAngular.ts
  20. 7
      packages/grafana-data/test/helpers/pluginMocks.ts
  21. 1
      packages/grafana-runtime/package.json
  22. 5
      packages/grafana-runtime/src/config.ts
  23. 87
      packages/grafana-runtime/src/services/AngularLoader.ts
  24. 2
      packages/grafana-runtime/src/services/index.ts
  25. 23
      packages/grafana-runtime/src/services/legacyAngularInjector.ts
  26. 2
      packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx
  27. 39
      packages/grafana-ui/src/themes/GlobalStyles/angularPanelStyles.ts
  28. 1
      pkg/api/dtos/frontend_settings.go
  29. 1
      pkg/api/frontendsettings.go
  30. 4
      pkg/plugins/config/config.go
  31. 2
      pkg/plugins/manager/pipeline/validation/steps.go
  32. 64
      pkg/registry/apis/featuretoggle/register.go
  33. 50
      pkg/services/featuremgmt/registry.go
  34. 14
      pkg/services/featuremgmt/service.go
  35. 7
      pkg/services/featuremgmt/toggles_gen.csv
  36. 28
      pkg/services/featuremgmt/toggles_gen.go
  37. 2227
      pkg/services/featuremgmt/toggles_gen.json
  38. 29
      pkg/services/pluginsintegration/loader/loader_test.go
  39. 1
      pkg/services/pluginsintegration/pluginconfig/config.go
  40. 2
      pkg/services/pluginsintegration/plugins_integration_test.go
  41. 2
      pkg/setting/setting.go
  42. 2
      pkg/tests/api/plugins/api_plugins_test.go
  43. 92
      pkg/tests/api/plugins/data/expectedListResp.json
  44. 2
      public/app/AppWrapper.tsx
  45. 166
      public/app/angular/AngularApp.ts
  46. 209
      public/app/angular/AngularLocationWrapper.test.ts
  47. 149
      public/app/angular/AngularLocationWrapper.ts
  48. 15
      public/app/angular/AngularRoot.tsx
  49. 158
      public/app/angular/GrafanaCtrl.ts
  50. 153
      public/app/angular/angular_wrappers.ts
  51. 30
      public/app/angular/array_join.ts
  52. 34
      public/app/angular/autofill_event_fix.ts
  53. 49
      public/app/angular/bridgeReactAngularRouting.ts
  54. 59
      public/app/angular/bsTooltip.ts
  55. 63
      public/app/angular/bsTypeahead.ts
  56. 22
      public/app/angular/components/HttpSettingsCtrl.ts
  57. 23
      public/app/angular/components/PageHeader/PageHeader.test.tsx
  58. 165
      public/app/angular/components/PageHeader/PageHeader.tsx
  59. 96
      public/app/angular/components/PageHeader/PanelHeaderMenuItem.tsx
  60. 10
      public/app/angular/components/TlsAuthSettingsCtrl.ts
  61. 4
      public/app/angular/components/code_editor/brace.d.ts
  62. 200
      public/app/angular/components/code_editor/code_editor.ts
  63. 117
      public/app/angular/components/code_editor/theme-grafana-dark.js
  64. 286
      public/app/angular/components/form_dropdown/form_dropdown.ts
  65. 69
      public/app/angular/components/info_popover.ts
  66. 29
      public/app/angular/components/jsontree.ts
  67. 249
      public/app/angular/components/plugin_component.ts
  68. 186
      public/app/angular/components/query_part_editor.ts
  69. 50
      public/app/angular/components/scroll.ts
  70. 24
      public/app/angular/components/spectrum_picker.ts
  71. 74
      public/app/angular/components/sql_part/sql_part.ts
  72. 196
      public/app/angular/components/sql_part/sql_part_editor.ts
  73. 94
      public/app/angular/components/switch.ts
  74. 18
      public/app/angular/core_module.ts
  75. 80
      public/app/angular/diff-view.ts
  76. 273
      public/app/angular/dropdown_typeahead.ts
  77. 61
      public/app/angular/filters/filters.ts
  78. 29
      public/app/angular/give_focus.ts
  79. 43
      public/app/angular/index.ts
  80. 50
      public/app/angular/injectorMonkeyPatch.ts
  81. 52
      public/app/angular/jquery_extended.ts
  82. 23
      public/app/angular/lazyBootAngular.ts
  83. 92
      public/app/angular/loadAndInitAngularIfEnabled.ts
  84. 266
      public/app/angular/metric_segment.ts
  85. 189
      public/app/angular/misc.ts
  86. 60
      public/app/angular/ng_model_on_blur.ts
  87. 127
      public/app/angular/panel/AngularPanelReactWrapper.tsx
  88. 4
      public/app/angular/panel/all.ts
  89. 244
      public/app/angular/panel/metrics_panel_ctrl.ts
  90. 119
      public/app/angular/panel/panel_ctrl.ts
  91. 127
      public/app/angular/panel/panel_directive.ts
  92. 41
      public/app/angular/panel/panel_editor_tab.ts
  93. 2
      public/app/angular/panel/partials/query_editor_row.html
  94. 27
      public/app/angular/panel/query_ctrl.ts
  95. 43
      public/app/angular/panel/query_editor_row.ts
  96. 57
      public/app/angular/panel/specs/metrics_panel_ctrl.test.ts
  97. 4
      public/app/angular/partials.ts
  98. 8
      public/app/angular/partials/http_settings_next.html
  99. 81
      public/app/angular/partials/tls_auth_settings.html
  100. 28
      public/app/angular/promiseToDigest.test.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -393,8 +393,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "8"]
],
"packages/grafana-data/test/helpers/pluginMocks.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"packages/grafana-e2e-selectors/src/resolver.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
@ -465,11 +464,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
],
"packages/grafana-runtime/src/services/AngularLoader.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
],
"packages/grafana-runtime/src/services/EchoSrv.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
@ -2889,8 +2883,7 @@ exports[`better eslint`] = {
],
"public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"]
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"]
],
"public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
@ -2998,10 +2991,9 @@ exports[`better eslint`] = {
"public/app/features/dashboard/containers/DashboardPage.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "5"]
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "4"]
],
"public/app/features/dashboard/containers/DashboardPageProxy.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
@ -3033,9 +3025,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"],
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
[0, 0, 0, "Unexpected any. Specify a different type.", "11"]
[0, 0, 0, "Unexpected any. Specify a different type.", "9"]
],
"public/app/features/dashboard/state/DashboardMigrator.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
@ -3728,16 +3718,12 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not re-export imported variable (\`./remotePlugin.mock\`)", "1"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"]
],
"public/app/features/plugins/admin/components/Badges/PluginAngularBadge.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
],
"public/app/features/plugins/admin/components/Badges/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./PluginAngularBadge\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./PluginDeprecatedBadge\`)", "1"],
[0, 0, 0, "Do not re-export imported variable (\`./PluginDisabledBadge\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`./PluginEnterpriseBadge\`)", "3"],
[0, 0, 0, "Do not re-export imported variable (\`./PluginInstallBadge\`)", "4"],
[0, 0, 0, "Do not re-export imported variable (\`./PluginUpdateAvailableBadge\`)", "5"]
[0, 0, 0, "Do not re-export imported variable (\`./PluginDeprecatedBadge\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./PluginDisabledBadge\`)", "1"],
[0, 0, 0, "Do not re-export imported variable (\`./PluginEnterpriseBadge\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`./PluginInstallBadge\`)", "3"],
[0, 0, 0, "Do not re-export imported variable (\`./PluginUpdateAvailableBadge\`)", "4"]
],
"public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
@ -3759,8 +3745,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not re-export imported variable (\`./InstallControlsWarning\`)", "1"]
],
"public/app/features/plugins/admin/components/PluginDetailsBody.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/features/plugins/admin/components/PluginDetailsDeprecatedWarning.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
@ -3799,12 +3784,6 @@ exports[`better eslint`] = {
"public/app/features/plugins/admin/types.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/plugins/angularDeprecation/AngularDeprecationNotice.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
],
"public/app/features/plugins/angularDeprecation/AngularMigrationNotice.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
],
"public/app/features/plugins/components/PluginsErrorsInfo.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
@ -3873,8 +3852,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"],
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "4"]
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "3"]
],
"public/app/features/query/components/QueryEditorRowHeader.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
@ -5351,9 +5329,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "16"],
[0, 0, 0, "Unexpected any. Specify a different type.", "17"]
],
"public/app/plugins/sdk.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`loadPluginCss\`)", "0"]
],
"public/app/routes/RoutesWrapper.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
],
@ -5374,13 +5349,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
],
"public/app/types/appEvent.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
],
"public/app/types/dashboard.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
@ -5416,8 +5384,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "17"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "18"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "19"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "20"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "21"]
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "20"]
],
"public/app/types/jquery/jquery.d.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
@ -5678,89 +5645,6 @@ exports[`no gf-form usage`] = {
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/angular/components/PageHeader/PageHeader.tsx:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/angular/components/code_editor/code_editor.ts:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/angular/components/form_dropdown/form_dropdown.ts:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/angular/components/info_popover.ts:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/angular/components/switch.ts:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/angular/dropdown_typeahead.ts:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/angular/metric_segment.ts:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/angular/misc.ts:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/angular/panel/partials/query_editor_row.html:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/angular/partials/tls_auth_settings.html:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/core/components/AccessControl/PermissionList.tsx:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
@ -5808,9 +5692,6 @@ exports[`no gf-form usage`] = {
"public/app/features/datasources/components/DataSourceTestingStatus.tsx:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/features/plugins/admin/components/AppConfigWrapper.tsx:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/features/query/components/QueryEditorRow.tsx:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
@ -5977,239 +5858,6 @@ exports[`no gf-form usage`] = {
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/plugins/panel/graph/axes_editor.html:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/plugins/panel/graph/tab_display.html:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/plugins/panel/graph/tab_legend.html:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/plugins/panel/graph/tab_series_overrides.html:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/plugins/panel/graph/thresholds_form.html:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/plugins/panel/graph/time_regions_form.html:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/plugins/panel/heatmap/partials/axes_editor.html:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
@ -6317,123 +5965,6 @@ exports[`no gf-form usage`] = {
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/plugins/panel/table-old/column_options.html:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/plugins/panel/table-old/editor.html:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
]
}`
};

@ -2,13 +2,13 @@
This is directory which stores Go modules with pinned buildable package that is used within this repository, managed by https://github.com/bwplotka/bingo.
* Run `bingo get` to install all tools having each own module file in this directory.
* Run `bingo get <tool>` to install <tool> that have own module file in this directory.
* For Makefile: Make sure to put `include .bingo/Variables.mk` in your Makefile, then use $(<upper case tool name>) variable where <tool> is the .bingo/<tool>.mod.
* For shell: Run `source .bingo/variables.env` to source all environment variable for each tool.
* For go: Import `.bingo/variables.go` to for variable names.
* See https://github.com/bwplotka/bingo or -h on how to add, remove or change binaries dependencies.
- Run `bingo get` to install all tools having each own module file in this directory.
- Run `bingo get <tool>` to install <tool> that have own module file in this directory.
- For Makefile: Make sure to put `include .bingo/Variables.mk` in your Makefile, then use $(<upper case tool name>) variable where <tool> is the .bingo/<tool>.mod.
- For shell: Run `source .bingo/variables.env` to source all environment variable for each tool.
- For go: Import `.bingo/variables.go` to for variable names.
- See https://github.com/bwplotka/bingo or -h on how to add, remove or change binaries dependencies.
## Requirements
* Go 1.14+
- Go 1.14+

@ -543,7 +543,6 @@ playwright.config.ts @grafana/plugins-platform-frontend
/public/app/plugins/panel/datagrid/ @grafana/dataviz-squad
/public/app/plugins/panel/gauge/ @grafana/dataviz-squad
/public/app/plugins/panel/gettingstarted/ @grafana/grafana-frontend-platform
/public/app/plugins/panel/graph/ @grafana/dataviz-squad
/public/app/plugins/panel/heatmap/ @grafana/dataviz-squad
/public/app/plugins/panel/histogram/ @grafana/dataviz-squad
/public/app/plugins/panel/logs/ @grafana/observability-logs
@ -556,7 +555,6 @@ playwright.config.ts @grafana/plugins-platform-frontend
/public/app/plugins/panel/status-history/ @grafana/dataviz-squad
/public/app/plugins/panel/table/ @grafana/dataviz-squad
/public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx @grafana/dataviz-squad @grafana/app-o11y-visualizations
/public/app/plugins/panel/table-old/ @grafana/dataviz-squad
/public/app/plugins/panel/timeseries/ @grafana/dataviz-squad
/public/app/plugins/panel/trend/ @grafana/dataviz-squad
/public/app/plugins/panel/geomap/ @grafana/dataviz-squad
@ -568,7 +566,6 @@ playwright.config.ts @grafana/plugins-platform-frontend
/public/app/plugins/panel/text/ @grafana/grafana-frontend-platform
/public/app/plugins/panel/welcome/ @grafana/grafana-frontend-platform
/public/app/plugins/panel/xychart/ @grafana/dataviz-squad
/public/app/plugins/sdk.ts @grafana/plugins-platform-frontend
/public/app/routes/ @grafana/grafana-frontend-platform
/public/app/store/ @grafana/grafana-frontend-platform
/public/app/types/ @grafana/grafana-frontend-platform
@ -601,7 +598,6 @@ playwright.config.ts @grafana/plugins-platform-frontend
/public/api-merged.json @grafana/grafana-backend-group
/public/api-enterprise-spec.json @grafana/grafana-backend-group
/public/openapi3.json @grafana/grafana-backend-group
/public/app/angular/ @torkelo
/public/app/app.ts @grafana/frontend-ops
/public/app/dev.ts @grafana/frontend-ops
/public/app/core/utils/metrics.ts @grafana/plugins-platform-frontend

@ -6,7 +6,6 @@
"history", // we should bump this together with react-router-dom (see https://github.com/grafana/grafana/issues/76744)
"react-router", // we should bump this together with history and react-router-dom
"react-router-dom", // we should bump this together with history (see https://github.com/grafana/grafana/issues/76744)
"loader-utils", // v3 requires upstream changes in ngtemplate-loader. ignore, and remove when we remove angular.
"monaco-editor", // due to us exposing this via @grafana/ui/CodeEditor's props bumping can break plugins
"@fingerprintjs/fingerprintjs", // we don't want to bump to v4 due to licensing changes
"slate", // we don't want to continue using this on the long run, use Monaco editor instead of Slate

@ -1,7 +1,6 @@
.git
.github
.yarn
.bingo
build
compiled
data

@ -91,38 +91,31 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
[Public preview](https://grafana.com/docs/release-life-cycle/#public-preview) features are supported by our Support teams, but might be limited to enablement, configuration, and some troubleshooting.
| Feature toggle name | Description |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `panelTitleSearch` | Search for dashboards using panel title |
| `autoMigrateOldPanels` | Migrate old angular panels to supported versions (graph, table-old, worldmap, etc) |
| `autoMigrateGraphPanel` | Migrate old graph panel to supported time series panel - broken out from autoMigrateOldPanels to enable granular tracking |
| `autoMigrateTablePanel` | Migrate old table panel to supported table panel - broken out from autoMigrateOldPanels to enable granular tracking |
| `autoMigratePiechartPanel` | Migrate old piechart panel to supported piechart panel - broken out from autoMigrateOldPanels to enable granular tracking |
| `autoMigrateWorldmapPanel` | Migrate old worldmap panel to supported geomap panel - broken out from autoMigrateOldPanels to enable granular tracking |
| `autoMigrateStatPanel` | Migrate old stat panel to supported stat panel - broken out from autoMigrateOldPanels to enable granular tracking |
| `disableAngular` | Dynamic flag to disable angular at runtime. The preferred method is to set `angular_support_enabled` to `false` in the [security] settings, which allows you to change the state at runtime. |
| `grpcServer` | Run the GRPC server |
| `renderAuthJWT` | Uses JWT-based auth for rendering instead of relying on remote cache |
| `refactorVariablesTimeRange` | Refactor time range variables flow to reduce number of API calls made when query variables are chained |
| `faroDatasourceSelector` | Enable the data source selector within the Frontend Apps section of the Frontend Observability |
| `enableDatagridEditing` | Enables the edit functionality in the datagrid panel |
| `sqlDatasourceDatabaseSelection` | Enables previous SQL data source dataset dropdown behavior |
| `reportingRetries` | Enables rendering retries for the reporting feature |
| `externalServiceAccounts` | Automatic service account and token setup for plugins |
| `cloudWatchBatchQueries` | Runs CloudWatch metrics queries as separate batches |
| `pdfTables` | Enables generating table data as PDF in reporting |
| `canvasPanelPanZoom` | Allow pan and zoom in canvas panel |
| `regressionTransformation` | Enables regression analysis transformation |
| `onPremToCloudMigrations` | Enable the Grafana Migration Assistant, which helps you easily migrate on-prem resources, such as dashboards, folders, and data source configurations, to your Grafana Cloud stack. |
| `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage |
| `ssoSettingsLDAP` | Use the new SSO Settings API to configure LDAP |
| `improvedExternalSessionHandling` | Enables improved support for OAuth external sessions. After enabling this feature, users might need to re-authenticate themselves. |
| `elasticsearchCrossClusterSearch` | Enables cross cluster search in the Elasticsearch datasource |
| `improvedExternalSessionHandlingSAML` | Enables improved support for SAML external sessions. Ensure the NameID format is correctly configured in Grafana for SAML Single Logout to function properly. |
| `teamHttpHeadersMimir` | Enables LBAC for datasources for Mimir to apply LBAC filtering of metrics to the client requests for users in teams |
| `exploreMetricsUseExternalAppPlugin` | Use the externalized Grafana Metrics Drilldown (formerly known as Explore Metrics) app plugin |
| `alertRuleRestore` | Enables the alert rule restore feature |
| `azureMonitorLogsBuilderEditor` | Enables the logs builder mode for the Azure Monitor data source |
| Feature toggle name | Description |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `panelTitleSearch` | Search for dashboards using panel title |
| `grpcServer` | Run the GRPC server |
| `renderAuthJWT` | Uses JWT-based auth for rendering instead of relying on remote cache |
| `refactorVariablesTimeRange` | Refactor time range variables flow to reduce number of API calls made when query variables are chained |
| `faroDatasourceSelector` | Enable the data source selector within the Frontend Apps section of the Frontend Observability |
| `enableDatagridEditing` | Enables the edit functionality in the datagrid panel |
| `sqlDatasourceDatabaseSelection` | Enables previous SQL data source dataset dropdown behavior |
| `reportingRetries` | Enables rendering retries for the reporting feature |
| `externalServiceAccounts` | Automatic service account and token setup for plugins |
| `cloudWatchBatchQueries` | Runs CloudWatch metrics queries as separate batches |
| `pdfTables` | Enables generating table data as PDF in reporting |
| `canvasPanelPanZoom` | Allow pan and zoom in canvas panel |
| `regressionTransformation` | Enables regression analysis transformation |
| `onPremToCloudMigrations` | Enable the Grafana Migration Assistant, which helps you easily migrate on-prem resources, such as dashboards, folders, and data source configurations, to your Grafana Cloud stack. |
| `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage |
| `ssoSettingsLDAP` | Use the new SSO Settings API to configure LDAP |
| `improvedExternalSessionHandling` | Enables improved support for OAuth external sessions. After enabling this feature, users might need to re-authenticate themselves. |
| `elasticsearchCrossClusterSearch` | Enables cross cluster search in the Elasticsearch datasource |
| `improvedExternalSessionHandlingSAML` | Enables improved support for SAML external sessions. Ensure the NameID format is correctly configured in Grafana for SAML Single Logout to function properly. |
| `teamHttpHeadersMimir` | Enables LBAC for datasources for Mimir to apply LBAC filtering of metrics to the client requests for users in teams |
| `exploreMetricsUseExternalAppPlugin` | Use the externalized Grafana Metrics Drilldown (formerly known as Explore Metrics) app plugin |
| `alertRuleRestore` | Enables the alert rule restore feature |
| `azureMonitorLogsBuilderEditor` | Enables the logs builder mode for the Azure Monitor data source |
## Experimental feature toggles

@ -9,32 +9,12 @@ describe('Auto-migrate graph panel', () => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Graph panel is migrated with `autoMigrateOldPanels` feature toggle', () => {
it('Graph panel is auto-migrated', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
cy.contains(DASHBOARD_NAME).should('be.visible');
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateOldPanels': true } });
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
});
it('Graph panel is migrated with config `disableAngular` feature toggle', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
cy.contains(DASHBOARD_NAME).should('be.visible');
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.disableAngular': true } });
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
});
it('Graph panel is migrated with `autoMigrateGraphPanel` feature toggle', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
cy.contains(DASHBOARD_NAME).should('be.visible');
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateGraphPanel': true } });
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
});
@ -44,7 +24,7 @@ describe('Auto-migrate graph panel', () => {
cy.contains(DASHBOARD_NAME).should('be.visible');
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateGraphPanel': true } });
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
e2e.components.Panels.Panel.title('Business Hours')
.should('exist')

@ -9,32 +9,12 @@ describe('Auto-migrate graph panel', () => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('Graph panel is migrated with `autoMigrateOldPanels` feature toggle', () => {
it('Graph panel is auto-migrated', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
cy.contains(DASHBOARD_NAME).should('be.visible');
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateOldPanels': true } });
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
});
it('Graph panel is migrated with config `disableAngular` feature toggle', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
cy.contains(DASHBOARD_NAME).should('be.visible');
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.disableAngular': true } });
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
});
it('Graph panel is migrated with `autoMigrateGraphPanel` feature toggle', () => {
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
cy.contains(DASHBOARD_NAME).should('be.visible');
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateGraphPanel': true } });
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
});
@ -44,7 +24,7 @@ describe('Auto-migrate graph panel', () => {
cy.contains(DASHBOARD_NAME).should('be.visible');
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateGraphPanel': true } });
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
e2e.components.Panels.Panel.title('Business Hours')
.should('exist')

@ -101,8 +101,6 @@
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.2.0",
"@testing-library/user-event": "14.6.1",
"@types/angular": "1.8.9",
"@types/angular-route": "1.7.6",
"@types/babel__core": "^7",
"@types/babel__preset-env": "^7",
"@types/chance": "^1.1.3",
@ -214,7 +212,6 @@
"mini-css-extract-plugin": "2.9.2",
"msw": "2.7.0",
"mutationobserver-shim": "0.3.7",
"ngtemplate-loader": "2.1.0",
"node-notifier": "10.0.1",
"nx": "20.7.1",
"openapi-types": "^12.1.3",
@ -310,10 +307,6 @@
"@visx/shape": "3.12.0",
"@visx/tooltip": "3.12.0",
"@welldone-software/why-did-you-render": "8.0.3",
"angular": "1.8.3",
"angular-bindonce": "0.3.1",
"angular-route": "1.8.3",
"angular-sanitize": "1.8.3",
"ansicolor": "2.0.3",
"baron": "3.0.3",
"brace": "0.11.1",
@ -412,8 +405,6 @@
"swagger-ui-react": "5.20.5",
"symbol-observable": "4.0.0",
"systemjs": "6.15.1",
"systemjs-cjs-extra": "0.2.1",
"tether-drop": "https://github.com/torkelo/drop",
"tinycolor2": "1.6.0",
"tslib": "2.8.1",
"tween-functions": "^1.2.0",
@ -426,7 +417,6 @@
"resolutions": {
"underscore": "1.13.7",
"@types/slate": "0.47.11",
"ngtemplate-loader/loader-utils": "^2.0.0",
"semver@~7.0.0": "7.5.4",
"semver@7.3.4": "7.5.4",
"debug@npm:^0.7.2": "2.6.9",

@ -1,4 +1,3 @@
import { IScope } from 'angular';
import EventEmitter from 'eventemitter3';
import { Unsubscribable, Observable, Subscriber } from 'rxjs';
import { filter } from 'rxjs/operators';
@ -66,7 +65,7 @@ export class EventBusSrv implements EventBus, LegacyEmitter {
}
}
on<T>(event: AppEvent<T> | string, handler: LegacyEventHandler<T>, scope?: IScope) {
on<T>(event: AppEvent<T> | string, handler: LegacyEventHandler<T>) {
// console.log(`Deprecated emitter function used (on), use $on`);
// need this wrapper to make old events compatible with old handlers
@ -79,13 +78,6 @@ export class EventBusSrv implements EventBus, LegacyEmitter {
} else {
this.emitter.on(event.name, handler.wrapper);
}
if (scope) {
const unbind = scope.$on('$destroy', () => {
this.off(event, handler);
unbind();
});
}
}
off<T>(event: AppEvent<T> | string, handler: LegacyEventHandler<T>) {

@ -1,4 +1,3 @@
import { IScope } from 'angular';
import { Unsubscribable, Observable } from 'rxjs';
/**
@ -129,7 +128,7 @@ export interface LegacyEmitter {
/**
* @deprecated use $on
*/
on<T>(event: AppEvent<T> | string, handler: LegacyEventHandler<T>, scope?: IScope): void;
on<T>(event: AppEvent<T> | string, handler: LegacyEventHandler<T>): void;
/**
* @deprecated use $on

@ -257,6 +257,7 @@ export { toOption } from './utils/selectUtils';
export * as arrayUtils from './utils/arrayUtils';
export { store, Store } from './utils/store';
export { LocalStorageValueProvider } from './utils/LocalStorageValueProvider';
export { throwIfAngular } from './utils/throwIfAngular';
// Tranformations
export { standardTransformers } from './transformations/transformers';

@ -1,5 +1,7 @@
import { ComponentType } from 'react';
import { throwIfAngular } from '../utils/throwIfAngular';
import { KeyValue } from './data';
import { NavModel } from './navModel';
import { PluginMeta, GrafanaPlugin, PluginIncludeType } from './plugin';
@ -85,9 +87,7 @@ export class AppPlugin<T extends KeyValue = KeyValue> extends GrafanaPlugin<AppP
}
setComponentsFromLegacyExports(pluginExports: System.Module) {
if (pluginExports.ConfigCtrl) {
this.angularConfigCtrl = pluginExports.ConfigCtrl;
}
throwIfAngular(pluginExports);
if (this.meta && this.meta.includes) {
for (const include of this.meta.includes) {

@ -214,7 +214,6 @@ export interface GrafanaConfig {
geomapDisableCustomBaseLayer?: boolean;
unifiedAlertingEnabled: boolean;
unifiedAlerting: UnifiedAlertingConfig;
angularSupportEnabled: boolean;
feedbackLinksEnabled: boolean;
supportBundlesEnabled: boolean;
secureSocksDSProxyEnabled: boolean;

@ -3,7 +3,9 @@ import { Observable } from 'rxjs';
import { DataSourceRef } from '@grafana/schema';
import { deprecationWarning } from '../utils/deprecationWarning';
import { makeClassES5Compatible } from '../utils/makeClassES5Compatible';
import { throwIfAngular } from '../utils/throwIfAngular';
import { ScopedVars } from './ScopedVars';
import { WithAccessControlMetadata } from './accesscontrol';
@ -50,12 +52,16 @@ export class DataSourcePlugin<
return this;
}
/** @deprecated it will be removed in a future release */
setConfigCtrl(ConfigCtrl: any) {
deprecationWarning('DataSourcePlugin', 'setConfigCtrl');
this.angularConfigCtrl = ConfigCtrl;
return this;
}
/** @deprecated it will be removed in a future release */
setQueryCtrl(QueryCtrl: any) {
deprecationWarning('DataSourcePlugin', 'setQueryCtrl');
this.components.QueryCtrl = QueryCtrl;
return this;
}
@ -115,7 +121,7 @@ export class DataSourcePlugin<
}
setComponentsFromLegacyExports(pluginExports: System.Module) {
this.angularConfigCtrl = pluginExports.ConfigCtrl;
throwIfAngular(pluginExports);
this.components.QueryCtrl = pluginExports.QueryCtrl;
this.components.AnnotationsQueryCtrl = pluginExports.AnnotationsQueryCtrl;
@ -161,7 +167,9 @@ export interface DataSourcePluginComponents<
TOptions extends DataSourceJsonData = DataSourceJsonData,
TSecureOptions = {},
> {
/** @deprecated it will be removed in a future release */
QueryCtrl?: any;
/** @deprecated it will be removed in a future release */
AnnotationsQueryCtrl?: any;
VariableQueryEditor?: any;
QueryEditor?: ComponentType<QueryEditorProps<DSType, TQuery, TOptions>>;

@ -63,34 +63,6 @@ export interface FeatureToggles {
*/
correlations?: boolean;
/**
* Migrate old angular panels to supported versions (graph, table-old, worldmap, etc)
*/
autoMigrateOldPanels?: boolean;
/**
* Migrate old graph panel to supported time series panel - broken out from autoMigrateOldPanels to enable granular tracking
*/
autoMigrateGraphPanel?: boolean;
/**
* Migrate old table panel to supported table panel - broken out from autoMigrateOldPanels to enable granular tracking
*/
autoMigrateTablePanel?: boolean;
/**
* Migrate old piechart panel to supported piechart panel - broken out from autoMigrateOldPanels to enable granular tracking
*/
autoMigratePiechartPanel?: boolean;
/**
* Migrate old worldmap panel to supported geomap panel - broken out from autoMigrateOldPanels to enable granular tracking
*/
autoMigrateWorldmapPanel?: boolean;
/**
* Migrate old stat panel to supported stat panel - broken out from autoMigrateOldPanels to enable granular tracking
*/
autoMigrateStatPanel?: boolean;
/**
* Dynamic flag to disable angular at runtime. The preferred method is to set `angular_support_enabled` to `false` in the [security] settings, which allows you to change the state at runtime.
*/
disableAngular?: boolean;
/**
* Allow elements nesting
*/
canvasPanelNesting?: boolean;

@ -239,6 +239,7 @@ export class GrafanaPlugin<T extends PluginMeta = PluginMeta> {
loadError?: boolean;
// Config control (app/datasource)
/** @deprecated it will be removed in a future release */
angularConfigCtrl?: any;
// Show configuration tabs on the plugin page

@ -0,0 +1,63 @@
import { PanelPlugin, PluginMeta, PluginType } from '@grafana/data';
import { throwIfAngular } from './throwIfAngular';
const plugin: PluginMeta = {
id: 'test',
name: 'Test',
type: PluginType.datasource,
info: {
author: { name: 'Test', url: 'https://test.com' },
description: 'Test',
links: [],
logos: { large: '', small: '' },
screenshots: [],
updated: '2021-01-01',
version: '1.0.0',
},
module: 'test',
baseUrl: 'test',
};
describe('throwIfAngular', () => {
it('should throw if angular plugin', () => {
const underTest = { ...plugin, angular: { detected: true, hideDeprecation: false } };
expect(() => throwIfAngular(underTest)).toThrow('Angular plugins are not supported');
});
it('should throw if angular plugin', () => {
const underTest = { ...plugin, angularDetected: true };
expect(() => throwIfAngular(underTest)).toThrow('Angular plugins are not supported');
});
it('should throw if angular panel', () => {
const underTest = new PanelPlugin(null);
underTest.angularPanelCtrl = {};
expect(() => throwIfAngular(underTest)).toThrow('Angular plugins are not supported');
});
it('should throw if angular module', () => {
const underTest: System.Module = { PanelCtrl: {} };
expect(() => throwIfAngular(underTest)).toThrow('Angular plugins are not supported');
});
it('should throw if angular module', () => {
const underTest: System.Module = { ConfigCtrl: {} };
expect(() => throwIfAngular(underTest)).toThrow('Angular plugins are not supported');
});
it('should not throw if not angular plugin', () => {
const underTest = { ...plugin, angular: { detected: false, hideDeprecation: false } };
expect(() => throwIfAngular(underTest)).not.toThrow();
});
it('should not throw if not angular panel', () => {
const underTest = new PanelPlugin(null);
expect(() => throwIfAngular(underTest)).not.toThrow();
});
it('should not throw if not angular module', () => {
const underTest: System.Module = {};
expect(() => throwIfAngular(underTest)).not.toThrow();
});
});

@ -0,0 +1,15 @@
import { PanelPlugin } from '../panel/PanelPlugin';
import { PluginMeta } from '../types/plugin';
export function throwIfAngular(module?: System.Module): void;
export function throwIfAngular(panel?: PanelPlugin): void;
export function throwIfAngular(plugin?: PluginMeta): void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function throwIfAngular(data?: any): void {
const isAngularPlugin = data?.angular?.detected ?? data?.angularDetected ?? false;
const isAngularPanel = data?.angularPanelCtrl ?? false;
const isAngularModule = data.PanelCtrl ?? data?.ConfigCtrl ?? false;
if (isAngularPlugin || isAngularPanel || isAngularModule) {
throw new Error('Angular plugins are not supported');
}
}

@ -37,13 +37,8 @@ export const getMockPlugins = (amount: number): PluginMeta[] => {
return plugins;
};
export function getPanelPlugin(
options: Partial<PanelPluginMeta>,
reactPanel?: ComponentType<PanelProps>,
angularPanel?: any
): PanelPlugin {
export function getPanelPlugin(options: Partial<PanelPluginMeta>, reactPanel?: ComponentType<PanelProps>): PanelPlugin {
const plugin = new PanelPlugin(reactPanel!);
plugin.angularPanelCtrl = angularPanel;
plugin.meta = {
id: options.id!,
type: PluginType.panel,

@ -73,7 +73,6 @@
"@testing-library/dom": "10.4.0",
"@testing-library/react": "16.2.0",
"@testing-library/user-event": "14.6.1",
"@types/angular": "1.8.9",
"@types/history": "4.7.11",
"@types/jest": "29.5.14",
"@types/lodash": "4.17.15",

@ -78,7 +78,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
feedbackLinksEnabled = true;
disableLoginForm = false;
defaultDatasource = ''; // UID
angularSupportEnabled = false;
authProxyEnabled = false;
exploreEnabled = false;
queryHistoryEnabled = false;
@ -240,10 +239,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
overrideFeatureTogglesFromUrl(this);
overrideFeatureTogglesFromLocalStorage(this);
if (this.featureToggles.disableAngular) {
this.angularSupportEnabled = false;
}
// Creating theme after applying feature toggle overrides in case we need to toggle anything
this.theme2 = getThemeById(this.bootData.user.theme);
this.bootData.user.lightTheme = this.theme2.isLight;

@ -1,87 +0,0 @@
/**
* Used to enable rendering of Angular components within a
* React component without losing proper typings.
*
* @example
* ```typescript
* class Component extends PureComponent<Props> {
* element: HTMLElement;
* angularComponent: AngularComponent;
*
* componentDidMount() {
* const template = '<angular-component />' // angular template here;
* const scopeProps = { ctrl: angularController }; // angular scope properties here
* const loader = getAngularLoader();
* this.angularComponent = loader.load(this.element, scopeProps, template);
* }
*
* componentWillUnmount() {
* if (this.angularComponent) {
* this.angularComponent.destroy();
* }
* }
*
* render() {
* return (
* <div ref={element => (this.element = element)} />
* );
* }
* }
* ```
*
* @public
*/
export interface AngularComponent {
/**
* Should be called when the React component will unmount.
*/
destroy(): void;
/**
* Can be used to trigger a re-render of the Angular component.
*/
digest(): void;
/**
* Used to access the Angular scope from the React component.
*/
getScope(): any;
}
/**
* Used to load an Angular component from the context of a React component.
* Please see the {@link AngularComponent} for a proper example.
*
* @public
*/
export interface AngularLoader {
/**
*
* @param elem - the element that the Angular component will be loaded into.
* @param scopeProps - values that will be accessed via the Angular scope.
* @param template - template used by the Angular component.
*/
load(elem: any, scopeProps: any, template: string): AngularComponent;
}
let instance: AngularLoader;
/**
* Used during startup by Grafana to set the AngularLoader so it is available
* via the {@link getAngularLoader} to the rest of the application.
*
* @internal
*/
export function setAngularLoader(v: AngularLoader) {
instance = v;
}
/**
* Used to retrieve the {@link AngularLoader} that enables the use of Angular
* components within a React component.
*
* Please see the {@link AngularComponent} for a proper example.
*
* @public
*/
export function getAngularLoader(): AngularLoader {
return instance;
}

@ -1,10 +1,8 @@
export * from './backendSrv';
export * from './AngularLoader';
export * from './dataSourceSrv';
export * from './LocationSrv';
export * from './EchoSrv';
export * from './templateSrv';
export * from './legacyAngularInjector';
export * from './live';
export * from './LocationService';
export * from './appEvents';

@ -1,23 +0,0 @@
import { auto } from 'angular';
let singleton: auto.IInjectorService;
/**
* Used during startup by Grafana to temporarily expose the angular injector to
* pure javascript plugins using {@link getLegacyAngularInjector}.
*
* @internal
*/
export const setLegacyAngularInjector = (instance: auto.IInjectorService) => {
singleton = instance;
};
/**
* WARNING: this function provides a temporary way for plugins to access anything in the
* angular injector. While the migration from angular to react continues, there are a few
* options that do not yet have good alternatives. Note that use of this function will
* be removed in the future.
*
* @beta
*/
export const getLegacyAngularInjector = (): auto.IInjectorService => singleton;

@ -4,7 +4,6 @@ import { useTheme2 } from '../ThemeContext';
import { getAccessibilityStyles } from './accessibility';
import { getAlertingStyles } from './alerting';
import { getAgularPanelStyles } from './angularPanelStyles';
import { getCardStyles } from './card';
import { getCodeStyles } from './code';
import { getDashboardGridStyles } from './dashboardGrid';
@ -39,7 +38,6 @@ export function GlobalStyles(props: GlobalStylesProps) {
<Global
styles={[
getAccessibilityStyles(theme),
getAgularPanelStyles(theme),
getAlertingStyles(theme),
getCodeStyles(theme),
getDashDiffStyles(theme),

@ -1,39 +0,0 @@
import { css } from '@emotion/react';
import { GrafanaTheme2 } from '@grafana/data';
export function getAgularPanelStyles(theme: GrafanaTheme2) {
return css({
'.panel-options-group': {
borderBottom: `1px solid ${theme.colors.border.weak}`,
},
'.panel-options-group__header': {
padding: theme.spacing(1, 2, 1, 1),
position: 'relative',
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
fontWeight: 500,
color: theme.colors.text.primary,
'&:hover': {
background: theme.colors.emphasize(theme.colors.background.primary, 0.03),
},
},
'.panel-options-group__icon': {
color: theme.colors.text.secondary,
marginRight: theme.spacing(1),
padding: theme.spacing(0, 0.9, 0, 0.6),
},
'.panel-options-group__title': {
position: 'relative',
},
'.panel-options-group__body': {
padding: theme.spacing(1, 2, 1, 4),
},
});
}

@ -208,7 +208,6 @@ type FrontendSettingsDTO struct {
ExternalUserMngAnalytics bool `json:"externalUserMngAnalytics"`
ExternalUserMngAnalyticsParams string `json:"externalUserMngAnalyticsParams"`
ViewersCanEdit bool `json:"viewersCanEdit"`
AngularSupportEnabled bool `json:"angularSupportEnabled"`
DisableSanitizeHtml bool `json:"disableSanitizeHtml"`
TrustedTypesDefaultPolicyEnabled bool `json:"trustedTypesDefaultPolicyEnabled"`
CSPReportOnlyEnabled bool `json:"cspReportOnlyEnabled"`

@ -230,7 +230,6 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
ExternalUserMngAnalyticsParams: hs.Cfg.ExternalUserMngAnalyticsParams,
//nolint:staticcheck // ViewersCanEdit is deprecated but still used for backward compatibility
ViewersCanEdit: hs.Cfg.ViewersCanEdit,
AngularSupportEnabled: hs.Cfg.AngularSupportEnabled,
DisableSanitizeHtml: hs.Cfg.DisableSanitizeHtml,
TrustedTypesDefaultPolicyEnabled: trustedTypesDefaultPolicyEnabled,
CSPReportOnlyEnabled: hs.Cfg.CSPReportOnlyEnabled,

@ -25,7 +25,6 @@ type PluginManagementCfg struct {
Features Features
AngularSupportEnabled bool
HideAngularDeprecation []string
}
@ -40,7 +39,7 @@ type Features struct {
// NewPluginManagementCfg returns a new PluginManagementCfg.
func NewPluginManagementCfg(devMode bool, pluginsPath string, pluginSettings setting.PluginSettings, pluginsAllowUnsigned []string,
pluginsCDNURLTemplate string, appURL string, features Features, angularSupportEnabled bool,
pluginsCDNURLTemplate string, appURL string, features Features,
grafanaComAPIURL string, disablePlugins []string, hideAngularDeprecation []string, forwardHostEnvVars []string, grafanaComAPIToken string,
) *PluginManagementCfg {
return &PluginManagementCfg{
@ -53,7 +52,6 @@ func NewPluginManagementCfg(devMode bool, pluginsPath string, pluginSettings set
GrafanaComAPIURL: grafanaComAPIURL,
GrafanaAppURL: appURL,
Features: features,
AngularSupportEnabled: angularSupportEnabled,
HideAngularDeprecation: hideAngularDeprecation,
ForwardHostEnvVars: forwardHostEnvVars,
GrafanaComAPIToken: grafanaComAPIToken,

@ -109,7 +109,7 @@ func (a *AngularDetector) Validate(ctx context.Context, p *plugins.Plugin) error
}
// Do not initialize plugins if they're using Angular and Angular support is disabled
if p.Angular.Detected && !a.cfg.AngularSupportEnabled {
if p.Angular.Detected {
a.log.Error("Refusing to initialize plugin because it's using Angular, which has been disabled", "pluginId", p.ID)
return (&plugins.Error{
PluginID: p.ID,

@ -136,70 +136,6 @@ func (b *FeatureFlagAPIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.AP
},
},
},
Patch: &spec3.Operation{
OperationProps: spec3.OperationProps{
Tags: tags,
Summary: "Update individual toggles",
Description: "Patch some of the toggles (keyed by the toggle name)",
RequestBody: &spec3.RequestBody{
RequestBodyProps: spec3.RequestBodyProps{
Required: true,
Description: "flags to change",
Content: map[string]*spec3.MediaType{
"application/json": {
MediaTypeProps: spec3.MediaTypeProps{
Schema: &stateSchema,
Example: &v0alpha1.ResolvedToggleState{
Enabled: map[string]bool{
featuremgmt.FlagAutoMigrateOldPanels: true,
featuremgmt.FlagAngularDeprecationUI: false,
},
},
Examples: map[string]*spec3.Example{
"enable-auto-migrate": {
ExampleProps: spec3.ExampleProps{
Summary: "enable auto-migrate panels",
Description: "enable description",
Value: &v0alpha1.ResolvedToggleState{
Enabled: map[string]bool{
featuremgmt.FlagAutoMigrateOldPanels: true,
},
},
},
},
"disable-auto-migrate": {
ExampleProps: spec3.ExampleProps{
Summary: "disable auto-migrate panels",
Description: "disable description",
Value: &v0alpha1.ResolvedToggleState{
Enabled: map[string]bool{
featuremgmt.FlagAutoMigrateOldPanels: false,
},
},
},
},
},
},
},
},
},
},
Responses: &spec3.Responses{
ResponsesProps: spec3.ResponsesProps{
StatusCodeResponses: map[int]*spec3.Response{
200: {
ResponseProps: spec3.ResponseProps{
Content: map[string]*spec3.MediaType{
"application/json": {},
},
Description: "OK",
},
},
},
},
},
},
},
},
Handler: b.handleCurrentStatus,
},

@ -92,56 +92,6 @@ var (
Expression: "true", // enabled by default
AllowSelfServe: true,
},
{
Name: "autoMigrateOldPanels",
Description: "Migrate old angular panels to supported versions (graph, table-old, worldmap, etc)",
Stage: FeatureStagePublicPreview,
FrontendOnly: true,
Owner: grafanaDatavizSquad,
},
{
Name: "autoMigrateGraphPanel",
Description: "Migrate old graph panel to supported time series panel - broken out from autoMigrateOldPanels to enable granular tracking",
Stage: FeatureStagePublicPreview,
FrontendOnly: true,
Owner: grafanaDatavizSquad,
},
{
Name: "autoMigrateTablePanel",
Description: "Migrate old table panel to supported table panel - broken out from autoMigrateOldPanels to enable granular tracking",
Stage: FeatureStagePublicPreview,
FrontendOnly: true,
Owner: grafanaDatavizSquad,
},
{
Name: "autoMigratePiechartPanel",
Description: "Migrate old piechart panel to supported piechart panel - broken out from autoMigrateOldPanels to enable granular tracking",
Stage: FeatureStagePublicPreview,
FrontendOnly: true,
Owner: grafanaDatavizSquad,
},
{
Name: "autoMigrateWorldmapPanel",
Description: "Migrate old worldmap panel to supported geomap panel - broken out from autoMigrateOldPanels to enable granular tracking",
Stage: FeatureStagePublicPreview,
FrontendOnly: true,
Owner: grafanaDatavizSquad,
},
{
Name: "autoMigrateStatPanel",
Description: "Migrate old stat panel to supported stat panel - broken out from autoMigrateOldPanels to enable granular tracking",
Stage: FeatureStagePublicPreview,
FrontendOnly: true,
Owner: grafanaDatavizSquad,
},
{
Name: "disableAngular",
Description: "Dynamic flag to disable angular at runtime. The preferred method is to set `angular_support_enabled` to `false` in the [security] settings, which allows you to change the state at runtime.",
Stage: FeatureStagePublicPreview,
FrontendOnly: true,
Owner: grafanaDatavizSquad,
HideFromAdminPage: true,
},
{
Name: "canvasPanelNesting",
Description: "Allow elements nesting",

@ -42,17 +42,11 @@ func ProvideManagerService(cfg *setting.Cfg) (*FeatureManager, error) {
for key, val := range flags {
_, ok := mgmt.flags[key]
if !ok {
switch key {
// renamed the flag so it supports more panels
case "autoMigrateGraphPanels":
key = FlagAutoMigrateOldPanels
default:
mgmt.flags[key] = &FeatureFlag{
Name: key,
Stage: FeatureStageUnknown,
}
mgmt.warnings[key] = "unknown flag in config"
mgmt.flags[key] = &FeatureFlag{
Name: key,
Stage: FeatureStageUnknown,
}
mgmt.warnings[key] = "unknown flag in config"
}
mgmt.startup[key] = val
}

@ -9,13 +9,6 @@ lokiExperimentalStreaming,experimental,@grafana/observability-logs,false,false,f
featureHighlights,GA,@grafana/grafana-as-code,false,false,false
storage,experimental,@grafana/search-and-storage,false,false,false
correlations,GA,@grafana/dataviz-squad,false,false,false
autoMigrateOldPanels,preview,@grafana/dataviz-squad,false,false,true
autoMigrateGraphPanel,preview,@grafana/dataviz-squad,false,false,true
autoMigrateTablePanel,preview,@grafana/dataviz-squad,false,false,true
autoMigratePiechartPanel,preview,@grafana/dataviz-squad,false,false,true
autoMigrateWorldmapPanel,preview,@grafana/dataviz-squad,false,false,true
autoMigrateStatPanel,preview,@grafana/dataviz-squad,false,false,true
disableAngular,preview,@grafana/dataviz-squad,false,false,true
canvasPanelNesting,experimental,@grafana/dataviz-squad,false,false,true
disableSecretsCompatibility,experimental,@grafana/hosted-grafana-team,false,true,false
logRequestsInstrumentedAsUnknown,experimental,@grafana/hosted-grafana-team,false,false,false

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
9 featureHighlights GA @grafana/grafana-as-code false false false
10 storage experimental @grafana/search-and-storage false false false
11 correlations GA @grafana/dataviz-squad false false false
autoMigrateOldPanels preview @grafana/dataviz-squad false false true
autoMigrateGraphPanel preview @grafana/dataviz-squad false false true
autoMigrateTablePanel preview @grafana/dataviz-squad false false true
autoMigratePiechartPanel preview @grafana/dataviz-squad false false true
autoMigrateWorldmapPanel preview @grafana/dataviz-squad false false true
autoMigrateStatPanel preview @grafana/dataviz-squad false false true
disableAngular preview @grafana/dataviz-squad false false true
12 canvasPanelNesting experimental @grafana/dataviz-squad false false true
13 disableSecretsCompatibility experimental @grafana/hosted-grafana-team false true false
14 logRequestsInstrumentedAsUnknown experimental @grafana/hosted-grafana-team false false false

@ -47,34 +47,6 @@ const (
// Correlations page
FlagCorrelations = "correlations"
// FlagAutoMigrateOldPanels
// Migrate old angular panels to supported versions (graph, table-old, worldmap, etc)
FlagAutoMigrateOldPanels = "autoMigrateOldPanels"
// FlagAutoMigrateGraphPanel
// Migrate old graph panel to supported time series panel - broken out from autoMigrateOldPanels to enable granular tracking
FlagAutoMigrateGraphPanel = "autoMigrateGraphPanel"
// FlagAutoMigrateTablePanel
// Migrate old table panel to supported table panel - broken out from autoMigrateOldPanels to enable granular tracking
FlagAutoMigrateTablePanel = "autoMigrateTablePanel"
// FlagAutoMigratePiechartPanel
// Migrate old piechart panel to supported piechart panel - broken out from autoMigrateOldPanels to enable granular tracking
FlagAutoMigratePiechartPanel = "autoMigratePiechartPanel"
// FlagAutoMigrateWorldmapPanel
// Migrate old worldmap panel to supported geomap panel - broken out from autoMigrateOldPanels to enable granular tracking
FlagAutoMigrateWorldmapPanel = "autoMigrateWorldmapPanel"
// FlagAutoMigrateStatPanel
// Migrate old stat panel to supported stat panel - broken out from autoMigrateOldPanels to enable granular tracking
FlagAutoMigrateStatPanel = "autoMigrateStatPanel"
// FlagDisableAngular
// Dynamic flag to disable angular at runtime. The preferred method is to set `angular_support_enabled` to `false` in the [security] settings, which allows you to change the state at runtime.
FlagDisableAngular = "disableAngular"
// FlagCanvasPanelNesting
// Allow elements nesting
FlagCanvasPanelNesting = "canvasPanelNesting"

File diff suppressed because it is too large Load Diff

@ -1168,15 +1168,15 @@ func TestLoader_AngularClass(t *testing.T) {
},
}
// if angularDetected = true, it means that the detection has run
l := newLoaderWithOpts(t, &config.PluginManagementCfg{AngularSupportEnabled: true}, loaderDepOpts{
l := newLoaderWithOpts(t, &config.PluginManagementCfg{}, loaderDepOpts{
angularInspector: angularinspector.AlwaysAngularFakeInspector,
})
p, err := l.Load(context.Background(), fakePluginSource)
require.NoError(t, err)
require.Len(t, p, 1, "should load 1 plugin")
if tc.expAngularDetectionRun {
require.True(t, p[0].Angular.Detected, "angular detection should run")
require.Empty(t, p, "plugin shouldn't have been loaded")
} else {
require.Len(t, p, 1, "should load 1 plugin")
require.False(t, p[0].Angular.Detected, "angular detection should not run")
}
})
@ -1196,8 +1196,8 @@ func TestLoader_Load_Angular(t *testing.T) {
name string
cfg *config.PluginManagementCfg
}{
{name: "angular support enabled", cfg: &config.PluginManagementCfg{AngularSupportEnabled: true}},
{name: "angular support disabled", cfg: &config.PluginManagementCfg{AngularSupportEnabled: false}},
{name: "angular support enabled", cfg: &config.PluginManagementCfg{}},
{name: "angular support disabled", cfg: &config.PluginManagementCfg{}},
} {
t.Run(cfgTc.name, func(t *testing.T) {
for _, tc := range []struct {
@ -1209,7 +1209,7 @@ func TestLoader_Load_Angular(t *testing.T) {
name: "angular plugin",
angularInspector: angularinspector.AlwaysAngularFakeInspector,
// angular plugins should load only if allowed by the cfg
shouldLoad: cfgTc.cfg.AngularSupportEnabled,
shouldLoad: false,
},
{
name: "non angular plugin",
@ -1243,22 +1243,18 @@ func TestLoader_HideAngularDeprecation(t *testing.T) {
},
}
for _, tc := range []struct {
name string
cfg *config.PluginManagementCfg
expHideAngularDeprecation bool
name string
cfg *config.PluginManagementCfg
}{
{name: "with plugin id in HideAngularDeprecation list", cfg: &config.PluginManagementCfg{
AngularSupportEnabled: true,
HideAngularDeprecation: []string{"one-app", "two-panel", "test-datasource", "three-datasource"},
}, expHideAngularDeprecation: true},
}},
{name: "without plugin id in HideAngularDeprecation list", cfg: &config.PluginManagementCfg{
AngularSupportEnabled: true,
HideAngularDeprecation: []string{"one-app", "two-panel", "three-datasource"},
}, expHideAngularDeprecation: false},
}},
{name: "with empty HideAngularDeprecation", cfg: &config.PluginManagementCfg{
AngularSupportEnabled: true,
HideAngularDeprecation: nil,
}, expHideAngularDeprecation: false},
}},
} {
t.Run(tc.name, func(t *testing.T) {
l := newLoaderWithOpts(t, tc.cfg, loaderDepOpts{
@ -1266,8 +1262,7 @@ func TestLoader_HideAngularDeprecation(t *testing.T) {
})
p, err := l.Load(context.Background(), fakePluginSource)
require.NoError(t, err)
require.Len(t, p, 1, "should load 1 plugin")
require.Equal(t, tc.expHideAngularDeprecation, p[0].Angular.HideDeprecation)
require.Empty(t, p, "plugin shouldn't have been loaded")
})
}
}

@ -36,7 +36,6 @@ func ProvidePluginManagementConfig(cfg *setting.Cfg, settingProvider setting.Pro
PluginsCDNSyncLoaderEnabled: features.IsEnabledGlobally(featuremgmt.FlagPluginsCDNSyncLoader),
LocalizationForPlugins: features.IsEnabledGlobally(featuremgmt.FlagLocalizationForPlugins),
},
cfg.AngularSupportEnabled,
cfg.GrafanaComAPIURL,
cfg.DisablePlugins,
cfg.HideAngularDeprecation,

@ -145,7 +145,6 @@ func verifyCorePluginCatalogue(t *testing.T, ctx context.Context, ps *pluginstor
"gauge": {},
"geomap": {},
"gettingstarted": {},
"graph": {},
"heatmap": {},
"histogram": {},
"live": {},
@ -161,7 +160,6 @@ func verifyCorePluginCatalogue(t *testing.T, ctx context.Context, ps *pluginstor
"state-timeline": {},
"status-history": {},
"table": {},
"table-old": {},
"text": {},
"timeseries": {},
"trend": {},

@ -176,7 +176,6 @@ type Cfg struct {
CSPReportOnlyEnabled bool
// CSPReportOnlyTemplate contains the Content Security Policy Report Only template.
CSPReportOnlyTemplate string
AngularSupportEnabled bool
EnableFrontendSandboxForPlugins []string
DisableGravatar bool
DataProxyWhiteList map[string]bool
@ -1578,7 +1577,6 @@ func readSecuritySettings(iniFile *ini.File, cfg *Cfg) error {
cfg.StrictTransportSecurityMaxAge = security.Key("strict_transport_security_max_age_seconds").MustInt(86400)
cfg.StrictTransportSecurityPreload = security.Key("strict_transport_security_preload").MustBool(false)
cfg.StrictTransportSecuritySubDomains = security.Key("strict_transport_security_subdomains").MustBool(false)
cfg.AngularSupportEnabled = security.Key("angular_support_enabled").MustBool(false)
cfg.CSPEnabled = security.Key("content_security_policy").MustBool(false)
cfg.CSPTemplate = security.Key("content_security_policy_template").MustString("")
cfg.CSPReportOnlyEnabled = security.Key("content_security_policy_report_only").MustBool(false)

@ -36,7 +36,7 @@ const (
defaultPassword = "password"
)
var updateSnapshotFlag = false
var updateSnapshotFlag = true
func TestMain(m *testing.M) {
testsuite.Run(m)

@ -964,52 +964,6 @@
"signatureOrg": "",
"angularDetected": false
},
{
"name": "Graph (old)",
"type": "panel",
"id": "graph",
"enabled": true,
"pinned": false,
"info": {
"author": {
"name": "Grafana Labs",
"url": "https://grafana.com"
},
"description": "The old default graph panel",
"links": [
{
"name": "Raise issue",
"url": "https://github.com/grafana/grafana/issues/new"
}
],
"logos": {
"small": "public/app/plugins/panel/graph/img/icn-graph-panel.svg",
"large": "public/app/plugins/panel/graph/img/icn-graph-panel.svg"
},
"build": {},
"screenshots": null,
"version": "",
"updated": "",
"keywords": null
},
"dependencies": {
"grafanaDependency": "",
"grafanaVersion": "*",
"plugins": [],
"extensions": {
"exposedComponents": []
}
},
"latestVersion": "",
"hasUpdate": false,
"defaultNavUrl": "/plugins/graph/",
"category": "",
"state": "deprecated",
"signature": "internal",
"signatureType": "",
"signatureOrg": "",
"angularDetected": false
},
{
"name": "Graphite",
"type": "datasource",
@ -2052,52 +2006,6 @@
"signatureOrg": "",
"angularDetected": false
},
{
"name": "Table (old)",
"type": "panel",
"id": "table-old",
"enabled": true,
"pinned": false,
"info": {
"author": {
"name": "Grafana Labs",
"url": "https://grafana.com"
},
"description": "Table Panel for Grafana",
"links": [
{
"name": "Raise issue",
"url": "https://github.com/grafana/grafana/issues/new"
}
],
"logos": {
"small": "public/app/plugins/panel/table-old/img/icn-table-panel.svg",
"large": "public/app/plugins/panel/table-old/img/icn-table-panel.svg"
},
"build": {},
"screenshots": null,
"version": "",
"updated": "",
"keywords": null
},
"dependencies": {
"grafanaDependency": "",
"grafanaVersion": "*",
"plugins": [],
"extensions": {
"exposedComponents": []
}
},
"latestVersion": "",
"hasUpdate": false,
"defaultNavUrl": "/plugins/table-old/",
"category": "",
"state": "deprecated",
"signature": "internal",
"signatureType": "",
"signatureOrg": "",
"angularDetected": false
},
{
"name": "Tempo",
"type": "datasource",

@ -15,7 +15,6 @@ import { ErrorBoundaryAlert, PortalContainer, TimeRangeProvider } from '@grafana
import { getAppRoutes } from 'app/routes/routes';
import { store } from 'app/store/store';
import { loadAndInitAngularIfEnabled } from './angular/loadAndInitAngularIfEnabled';
import { GrafanaApp } from './app';
import { ExtensionSidebarContextProvider } from './core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider';
import { GlobalStylesWrapper } from './core/components/AppChrome/ExtensionSidebar/GlobalStylesWrapper';
@ -63,7 +62,6 @@ export class AppWrapper extends Component<AppWrapperProps, AppWrapperState> {
}
async componentDidMount() {
await loadAndInitAngularIfEnabled();
this.setState({ ready: true });
$('.preloader').remove();

@ -1,166 +0,0 @@
import 'angular';
import 'angular-route';
import 'angular-sanitize';
import 'angular-bindonce';
import 'vendor/bootstrap/bootstrap';
import angular from 'angular'; // eslint-disable-line no-duplicate-imports
import { extend } from 'lodash';
import { getTemplateSrv } from '@grafana/runtime';
import { coreModule, angularModules } from 'app/angular/core_module';
import appEvents from 'app/core/app_events';
import { config } from 'app/core/config';
import { contextSrv } from 'app/core/services/context_srv';
import { DashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { setAngularPanelReactWrapper } from 'app/features/plugins/importPanelPlugin';
import { SystemJS } from 'app/features/plugins/loader/systemjs';
import { buildImportMap } from 'app/features/plugins/loader/utils';
import * as sdk from 'app/plugins/sdk';
import { registerAngularDirectives } from './angular_wrappers';
import { initAngularRoutingBridge } from './bridgeReactAngularRouting';
import { monkeyPatchInjectorWithPreAssignedBindings } from './injectorMonkeyPatch';
import { getAngularPanelReactWrapper } from './panel/AngularPanelReactWrapper';
import { promiseToDigest } from './promiseToDigest';
import { registerComponents } from './registerComponents';
// Angular plugin dependencies map
const importMap = {
angular: {
...angular,
default: angular,
},
'app/core/core_module': {
default: coreModule,
__useDefault: true,
},
'app/core/core': {
appEvents: appEvents,
contextSrv: contextSrv,
coreModule: coreModule,
},
'app/plugins/sdk': sdk,
'app/core/utils/promiseToDigest': { promiseToDigest },
} as Record<string, System.Module>;
export class AngularApp {
ngModuleDependencies: any[];
preBootModules: any[];
registerFunctions: any;
constructor() {
this.preBootModules = [];
this.ngModuleDependencies = [];
this.registerFunctions = {};
}
init() {
const app = angular.module('grafana', []);
setAngularPanelReactWrapper(getAngularPanelReactWrapper);
app.config([
'$controllerProvider',
'$compileProvider',
'$filterProvider',
'$httpProvider',
'$provide',
'$sceDelegateProvider',
(
$controllerProvider: angular.IControllerProvider,
$compileProvider: angular.ICompileProvider,
$filterProvider: angular.IFilterProvider,
$httpProvider: angular.IHttpProvider,
$provide: angular.auto.IProvideService,
$sceDelegateProvider: angular.ISCEDelegateProvider
) => {
if (config.buildInfo.env !== 'development') {
$compileProvider.debugInfoEnabled(false);
}
$httpProvider.useApplyAsync(true);
if (Boolean(config.pluginsCDNBaseURL)) {
$sceDelegateProvider.trustedResourceUrlList(['self', `${config.pluginsCDNBaseURL}/**`]);
}
this.registerFunctions.controller = $controllerProvider.register;
this.registerFunctions.directive = $compileProvider.directive;
this.registerFunctions.factory = $provide.factory;
this.registerFunctions.service = $provide.service;
this.registerFunctions.filter = $filterProvider.register;
$provide.decorator('$http', [
'$delegate',
'$templateCache',
($delegate: any, $templateCache: any) => {
const get = $delegate.get;
$delegate.get = (url: string, config: any) => {
if (url.match(/\.html$/)) {
// some template's already exist in the cache
if (!$templateCache.get(url)) {
url += '?v=' + new Date().getTime();
}
}
return get(url, config);
};
return $delegate;
},
]);
},
]);
this.ngModuleDependencies = ['grafana.core', 'ngSanitize', 'grafana', 'pasvaz.bindonce', 'react'];
// makes it possible to add dynamic stuff
angularModules.forEach((m: angular.IModule) => {
this.useModule(m);
});
// register react angular wrappers
angular.module('grafana.services').service('dashboardLoaderSrv', DashboardLoaderSrv);
coreModule.factory('timeSrv', () => getTimeSrv());
coreModule.factory('templateSrv', () => getTemplateSrv());
registerAngularDirectives();
registerComponents();
initAngularRoutingBridge();
const imports = buildImportMap(importMap);
// pass the map of module names so systemjs can resolve them
SystemJS.addImportMap({ imports });
// disable tool tip animation
$.fn.tooltip.defaults.animation = false;
}
useModule(module: angular.IModule) {
if (this.preBootModules) {
this.preBootModules.push(module);
} else {
extend(module, this.registerFunctions);
}
this.ngModuleDependencies.push(module.name);
return module;
}
bootstrap() {
const injector = angular.bootstrap(document.getElementById('ngRoot')!, this.ngModuleDependencies);
monkeyPatchInjectorWithPreAssignedBindings(injector);
injector.invoke(() => {
this.preBootModules.forEach((module) => {
extend(module, this.registerFunctions);
});
// I don't know
return () => {};
});
return injector;
}
}

@ -1,209 +0,0 @@
import { HistoryWrapper, locationService, setLocationService } from '@grafana/runtime';
import { AngularLocationWrapper } from './AngularLocationWrapper';
// The methods in this file are deprecated
// Stub the deprecation warning here to prevent polluting the test output
jest.mock('@grafana/data', () => ({
...jest.requireActual('@grafana/data'),
deprecationWarning: () => {},
}));
describe('AngularLocationWrapper', () => {
const { location } = window;
beforeEach(() => {
setLocationService(new HistoryWrapper());
});
beforeAll(() => {
// @ts-ignore
delete window.location;
window.location = {
...location,
hash: '#hash',
host: 'localhost:3000',
hostname: 'localhost',
href: 'http://www.domain.com:9877/path/b?search=a&b=c&d#hash',
origin: 'http://www.domain.com:9877',
pathname: '/path/b',
port: '9877',
protocol: 'http:',
search: '?search=a&b=c&d',
};
});
afterAll(() => {
window.location = location;
});
const wrapper = new AngularLocationWrapper();
it('should provide common getters', () => {
locationService.push('/path/b?search=a&b=c&d#hash');
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#hash');
expect(wrapper.protocol()).toBe('http');
expect(wrapper.host()).toBe('www.domain.com');
expect(wrapper.port()).toBe(9877);
expect(wrapper.path()).toBe('/path/b');
expect(wrapper.search()).toEqual({ search: 'a', b: 'c', d: true });
expect(wrapper.hash()).toBe('hash');
expect(wrapper.url()).toBe('/path/b?search=a&b=c&d#hash');
});
describe('path', () => {
it('should change path', function () {
locationService.push('/path/b?search=a&b=c&d#hash');
wrapper.path('/new/path');
expect(wrapper.path()).toBe('/new/path');
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/new/path?search=a&b=c&d#hash');
});
it('should not break on numeric values', function () {
locationService.push('/path/b?search=a&b=c&d#hash');
wrapper.path(1);
expect(wrapper.path()).toBe('/1');
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/1?search=a&b=c&d#hash');
});
it('should allow using 0 as path', function () {
locationService.push('/path/b?search=a&b=c&d#hash');
wrapper.path(0);
expect(wrapper.path()).toBe('/0');
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/0?search=a&b=c&d#hash');
});
it('should set to empty path on null value', function () {
locationService.push('/path/b?search=a&b=c&d#hash');
wrapper.path('/foo');
expect(wrapper.path()).toBe('/foo');
wrapper.path(null);
expect(wrapper.path()).toBe('/');
});
});
describe('search', () => {
it('should accept string', function () {
locationService.push('/path/b');
wrapper.search('x=y&c');
expect(wrapper.search()).toEqual({ x: 'y', c: true });
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b?x=y&c');
});
it('search() should accept object', function () {
locationService.push('/path/b');
wrapper.search({ one: '1', two: true });
expect(wrapper.search()).toEqual({ one: '1', two: true });
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b?one=1&two');
});
it('should copy object', function () {
locationService.push('/path/b');
const obj: Record<string, unknown> = { one: '1', two: true, three: null };
wrapper.search(obj);
expect(obj).toEqual({ one: '1', two: true, three: null });
obj.one = 'changed';
expect(wrapper.search()).toEqual({ one: '1', two: true });
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b?one=1&two');
});
it('should change single parameter', function () {
wrapper.search({ id: 'old', preserved: true });
wrapper.search('id', 'new');
expect(wrapper.search()).toEqual({ id: 'new', preserved: true });
});
it('should remove single parameter', function () {
wrapper.search({ id: 'old', preserved: true });
wrapper.search('id', null);
expect(wrapper.search()).toEqual({ preserved: true });
});
it('should remove multiple parameters', function () {
locationService.push('/path/b');
wrapper.search({ one: '1', two: true });
expect(wrapper.search()).toEqual({ one: '1', two: true });
wrapper.search({ one: null, two: null });
expect(wrapper.search()).toEqual({});
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b');
});
it('should accept numeric keys', function () {
locationService.push('/path/b');
wrapper.search({ 1: 'one', 2: 'two' });
expect(wrapper.search()).toEqual({ '1': 'one', '2': 'two' });
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b?1=one&2=two');
});
it('should handle multiple value', function () {
wrapper.search('a&b');
expect(wrapper.search()).toEqual({ a: true, b: true });
wrapper.search('a', null);
expect(wrapper.search()).toEqual({ b: true });
wrapper.search('b', undefined);
expect(wrapper.search()).toEqual({});
});
it('should handle single value', function () {
wrapper.search('ignore');
expect(wrapper.search()).toEqual({ ignore: true });
wrapper.search(1);
expect(wrapper.search()).toEqual({ 1: true });
});
});
describe('url', () => {
it('should change the path, search and hash', function () {
wrapper.url('/some/path?a=b&c=d#hhh');
expect(wrapper.url()).toBe('/some/path?a=b&c=d#hhh');
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/some/path?a=b&c=d#hhh');
expect(wrapper.path()).toBe('/some/path');
expect(wrapper.search()).toEqual({ a: 'b', c: 'd' });
expect(wrapper.hash()).toBe('hhh');
});
it('should change only hash when no search and path specified', function () {
locationService.push('/path/b?search=a&b=c&d');
wrapper.url('#some-hash');
expect(wrapper.hash()).toBe('some-hash');
expect(wrapper.url()).toBe('/path/b?search=a&b=c&d#some-hash');
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#some-hash');
});
it('should change only search and hash when no path specified', function () {
locationService.push('/path/b');
wrapper.url('?a=b');
expect(wrapper.search()).toEqual({ a: 'b' });
expect(wrapper.hash()).toBe('');
expect(wrapper.path()).toBe('/path/b');
});
it('should reset search and hash when only path specified', function () {
locationService.push('/path/b?search=a&b=c&d#hash');
wrapper.url('/new/path');
expect(wrapper.path()).toBe('/new/path');
expect(wrapper.search()).toEqual({});
expect(wrapper.hash()).toBe('');
});
it('should change path when empty string specified', function () {
locationService.push('/path/b?search=a&b=c&d#hash');
wrapper.url('');
expect(wrapper.path()).toBe('/');
expect(wrapper.search()).toEqual({});
expect(wrapper.hash()).toBe('');
});
});
});

@ -1,149 +0,0 @@
import { deprecationWarning, urlUtil } from '@grafana/data';
import { locationSearchToObject, locationService, navigationLogger } from '@grafana/runtime';
// Ref: https://github.com/angular/angular.js/blob/ae8e903edf88a83fedd116ae02c0628bf72b150c/src/ng/location.js#L5
const DEFAULT_PORTS: Record<string, number> = { http: 80, https: 443, ftp: 21 };
export class AngularLocationWrapper {
constructor() {
this.absUrl = this.wrapInDeprecationWarning(this.absUrl);
this.hash = this.wrapInDeprecationWarning(this.hash);
this.host = this.wrapInDeprecationWarning(this.host);
this.path = this.wrapInDeprecationWarning(this.path);
this.port = this.wrapInDeprecationWarning(this.port, 'window.location');
this.protocol = this.wrapInDeprecationWarning(this.protocol, 'window.location');
this.replace = this.wrapInDeprecationWarning(this.replace);
this.search = this.wrapInDeprecationWarning(this.search);
this.state = this.wrapInDeprecationWarning(this.state);
this.url = this.wrapInDeprecationWarning(this.url);
}
wrapInDeprecationWarning(fn: Function, replacement?: string) {
let self = this;
return function wrapper() {
deprecationWarning('$location', fn.name, replacement || 'locationService');
return fn.apply(self, arguments);
};
}
absUrl(): string {
return `${window.location.origin}${this.url()}`;
}
hash(newHash?: string | null) {
navigationLogger('AngularLocationWrapper', false, 'Angular compat layer: hash');
if (!newHash) {
return locationService.getLocation().hash.slice(1);
} else {
throw new Error('AngularLocationWrapper method not implemented.');
}
}
host(): string {
return new URL(window.location.href).hostname;
}
path(pathname?: any) {
navigationLogger('AngularLocationWrapper', false, 'Angular compat layer: path');
const location = locationService.getLocation();
if (pathname !== undefined && pathname !== null) {
let parsedPath = String(pathname);
parsedPath = parsedPath.startsWith('/') ? parsedPath : `/${parsedPath}`;
const url = new URL(`${window.location.origin}${parsedPath}`);
locationService.push({
pathname: url.pathname,
search: url.search.length > 0 ? url.search : location.search,
hash: url.hash.length > 0 ? url.hash : location.hash,
});
return this;
}
if (pathname === null) {
locationService.push('/');
return this;
}
return location.pathname;
}
port(): number | null {
const url = new URL(window.location.href);
return parseInt(url.port, 10) || DEFAULT_PORTS[url.protocol] || null;
}
protocol(): string {
return new URL(window.location.href).protocol.slice(0, -1);
}
replace() {
throw new Error('AngularLocationWrapper method not implemented.');
}
search(search?: any, paramValue?: any) {
navigationLogger('AngularLocationWrapper', false, 'Angular compat layer: search');
if (!search) {
return locationService.getSearchObject();
}
if (search && arguments.length > 1) {
locationService.partial({
[search]: paramValue,
});
return this;
}
if (search) {
let newQuery;
if (typeof search === 'object') {
newQuery = { ...search };
} else {
newQuery = locationSearchToObject(search);
}
for (const key in newQuery) {
// removing params with null | undefined
if (newQuery[key] === null || newQuery[key] === undefined) {
delete newQuery[key];
}
}
const updatedUrl = urlUtil.renderUrl(locationService.getLocation().pathname, newQuery);
locationService.push(updatedUrl);
}
return this;
}
state(state?: any) {
navigationLogger('AngularLocationWrapper', false, 'Angular compat layer: state');
throw new Error('AngularLocationWrapper method not implemented.');
}
url(newUrl?: any) {
navigationLogger('AngularLocationWrapper', false, 'Angular compat layer: url');
if (newUrl !== undefined) {
if (newUrl.startsWith('#')) {
locationService.push({ ...locationService.getLocation(), hash: newUrl });
} else if (newUrl.startsWith('?')) {
locationService.push({ ...locationService.getLocation(), search: newUrl });
} else if (newUrl.trim().length === 0) {
locationService.push('/');
} else {
locationService.push(newUrl);
}
return locationService;
}
const location = locationService.getLocation();
return `${location.pathname}${location.search}${location.hash}`;
}
}

@ -1,15 +0,0 @@
import { forwardRef } from 'react';
export const AngularRoot = forwardRef<HTMLDivElement, {}>((props, ref) => {
return (
<div
id="ngRoot"
ref={ref}
dangerouslySetInnerHTML={{
__html: '<grafana-app ng-cloak></grafana-app>',
}}
/>
);
});
AngularRoot.displayName = 'AngularRoot';

@ -1,158 +0,0 @@
import { IRootScopeService, IAngularEvent, auto } from 'angular';
import $ from 'jquery';
import _ from 'lodash'; // eslint-disable-line lodash/import-scope
import { AppEvent } from '@grafana/data';
import { setLegacyAngularInjector, setAngularLoader } from '@grafana/runtime';
import { colors } from '@grafana/ui';
import coreModule from 'app/angular/core_module';
import { AngularLoader } from 'app/angular/services/AngularLoader';
import appEvents from 'app/core/app_events';
import config from 'app/core/config';
import { ContextSrv } from 'app/core/services/context_srv';
import { AppEventEmitter, AppEventConsumer } from 'app/types';
import { UtilSrv } from './services/UtilSrv';
export type GrafanaRootScope = IRootScopeService & AppEventEmitter & AppEventConsumer & { colors: string[] };
export class GrafanaCtrl {
static $inject = ['$scope', 'utilSrv', '$rootScope', 'contextSrv', 'angularLoader', '$injector'];
constructor(
$scope: any,
utilSrv: UtilSrv,
$rootScope: GrafanaRootScope,
contextSrv: ContextSrv,
angularLoader: AngularLoader,
$injector: auto.IInjectorService
) {
// make angular loader service available to react components
setAngularLoader(angularLoader);
setLegacyAngularInjector($injector);
$scope.init = () => {
$scope.contextSrv = contextSrv;
$scope.appSubUrl = config.appSubUrl;
$scope._ = _;
utilSrv.init();
};
$rootScope.colors = colors;
$rootScope.onAppEvent = function <T>(
event: AppEvent<T> | string,
callback: (event: IAngularEvent, ...args: any[]) => void,
localScope?: any
) {
let unbind;
if (typeof event === 'string') {
unbind = $rootScope.$on(event, callback);
} else {
unbind = $rootScope.$on(event.name, callback);
}
let callerScope = this;
if (callerScope.$id === 1 && !localScope) {
console.warn('warning rootScope onAppEvent called without localscope');
}
if (localScope) {
callerScope = localScope;
}
callerScope.$on('$destroy', unbind);
};
$rootScope.appEvent = <T>(event: AppEvent<T> | string, payload?: T | any) => {
if (typeof event === 'string') {
$rootScope.$emit(event, payload);
appEvents.emit(event, payload);
} else {
$rootScope.$emit(event.name, payload);
appEvents.emit(event, payload);
}
};
$scope.init();
}
}
export function grafanaAppDirective() {
return {
restrict: 'E',
controller: GrafanaCtrl,
link: (scope: IRootScopeService & AppEventEmitter, elem: JQuery) => {
const body = $('body');
// see https://github.com/zenorocha/clipboard.js/issues/155
$.fn.modal.Constructor.prototype.enforceFocus = () => {};
// handle in active view state class
let lastActivity = new Date().getTime();
let activeUser = true;
const inActiveTimeLimit = 60 * 5000;
function checkForInActiveUser() {
if (!activeUser) {
return;
}
// only go to activity low mode on dashboard page
if (!body.hasClass('page-dashboard')) {
return;
}
if (new Date().getTime() - lastActivity > inActiveTimeLimit) {
activeUser = false;
body.addClass('view-mode--inactive');
}
}
function userActivityDetected() {
lastActivity = new Date().getTime();
if (!activeUser) {
activeUser = true;
body.removeClass('view-mode--inactive');
}
}
// mouse and keyboard is user activity
body.mousemove(userActivityDetected);
body.keydown(userActivityDetected);
// set useCapture = true to catch event here
document.addEventListener('wheel', userActivityDetected, { capture: true, passive: true });
// treat tab change as activity
document.addEventListener('visibilitychange', userActivityDetected);
// check every 2 seconds
setInterval(checkForInActiveUser, 2000);
// handle document clicks that should hide things
body.click((evt) => {
const target = $(evt.target);
if (target.parents().length === 0) {
return;
}
// ensure dropdown menu doesn't impact on z-index
body.find('.dropdown-menu-open').removeClass('dropdown-menu-open');
// for stuff that animates, slides out etc, clicking it needs to
// hide it right away
const clickAutoHide = target.closest('[data-click-hide]');
if (clickAutoHide.length) {
const clickAutoHideParent = clickAutoHide.parent();
clickAutoHide.detach();
setTimeout(() => {
clickAutoHideParent.append(clickAutoHide);
}, 100);
}
// hide popovers
const popover = elem.find('.popover');
if (popover.length > 0 && target.parents('.graph-legend').length === 0) {
popover.hide();
}
});
},
};
}
coreModule.directive('grafanaApp', grafanaAppDirective);

@ -1,153 +0,0 @@
import {
ClipboardButton,
ColorPicker,
DataLinksInlineEditor,
DataSourceHttpSettings,
GraphContextMenu,
Icon,
LegacyForms,
SeriesColorPickerPopoverWithTheme,
Spinner,
UnitPicker,
} from '@grafana/ui';
import { react2AngularDirective } from 'app/angular/react2angular';
import { OldFolderPicker } from 'app/core/components/Select/OldFolderPicker';
import { TimePickerSettings } from 'app/features/dashboard/components/DashboardSettings/TimePickerSettings';
import { QueryEditor as CloudMonitoringQueryEditor } from 'app/plugins/datasource/cloud-monitoring/components/QueryEditor';
import EmptyListCTA from '../core/components/EmptyListCTA/EmptyListCTA';
import { Footer } from '../core/components/Footer/Footer';
import { MetricSelect } from '../core/components/Select/MetricSelect';
import { TagFilter } from '../core/components/TagFilter/TagFilter';
import { HelpModal } from '../core/components/help/HelpModal';
import { PageHeader } from './components/PageHeader/PageHeader';
const { SecretFormField } = LegacyForms;
export function registerAngularDirectives() {
react2AngularDirective('footer', Footer, []);
react2AngularDirective('icon', Icon, [
'name',
'size',
'type',
['onClick', { watchDepth: 'reference', wrapApply: true }],
]);
react2AngularDirective('spinner', Spinner, ['inline']);
react2AngularDirective('helpModal', HelpModal, []);
react2AngularDirective('pageHeader', PageHeader, ['model', 'noTabs']);
react2AngularDirective('emptyListCta', EmptyListCTA, [
'title',
'buttonIcon',
'buttonLink',
'buttonTitle',
['onClick', { watchDepth: 'reference', wrapApply: true }],
'proTip',
'proTipLink',
'proTipLinkTitle',
'proTipTarget',
'infoBox',
'infoBoxTitle',
]);
react2AngularDirective('tagFilter', TagFilter, [
'tags',
['onChange', { watchDepth: 'reference' }],
['tagOptions', { watchDepth: 'reference' }],
]);
react2AngularDirective('colorPicker', ColorPicker, [
'color',
['onChange', { watchDepth: 'reference', wrapApply: true }],
]);
react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopoverWithTheme, [
'color',
'series',
'onColorChange',
'onToggleAxis',
]);
react2AngularDirective('unitPicker', UnitPicker, [
'value',
'width',
['onChange', { watchDepth: 'reference', wrapApply: true }],
]);
react2AngularDirective('metricSelect', MetricSelect, [
'options',
'onChange',
'value',
'isSearchable',
'className',
'placeholder',
['variables', { watchDepth: 'reference' }],
]);
react2AngularDirective('cloudMonitoringQueryEditor', CloudMonitoringQueryEditor, [
'target',
'onQueryChange',
'onExecuteQuery',
['events', { watchDepth: 'reference' }],
['datasource', { watchDepth: 'reference' }],
['templateSrv', { watchDepth: 'reference' }],
]);
react2AngularDirective('secretFormField', SecretFormField, [
'value',
'isConfigured',
'inputWidth',
'labelWidth',
'aria-label',
['onReset', { watchDepth: 'reference', wrapApply: true }],
['onChange', { watchDepth: 'reference', wrapApply: true }],
]);
react2AngularDirective('graphContextMenu', GraphContextMenu, [
'x',
'y',
'itemsGroup',
['onClose', { watchDepth: 'reference', wrapApply: true }],
['getContextMenuSource', { watchDepth: 'reference', wrapApply: true }],
['timeZone', { watchDepth: 'reference', wrapApply: true }],
]);
// We keep the drilldown terminology here because of as using data-* directive
// being in conflict with HTML data attributes
react2AngularDirective('drilldownLinksEditor', DataLinksInlineEditor, [
'value',
'links',
'suggestions',
['onChange', { watchDepth: 'reference', wrapApply: true }],
]);
react2AngularDirective('datasourceHttpSettingsNext', DataSourceHttpSettings, [
'defaultUrl',
'showAccessOptions',
'dataSourceConfig',
'showForwardOAuthIdentityOption',
['onChange', { watchDepth: 'reference', wrapApply: true }],
]);
react2AngularDirective('folderPicker', OldFolderPicker, [
'labelClass',
'rootName',
'enableCreateNew',
'enableReset',
'initialTitle',
'initialFolderId',
'dashboardId',
'onCreateFolder',
['enterFolderCreation', { watchDepth: 'reference', wrapApply: true }],
['exitFolderCreation', { watchDepth: 'reference', wrapApply: true }],
['onLoad', { watchDepth: 'reference', wrapApply: true }],
['onChange', { watchDepth: 'reference', wrapApply: true }],
]);
react2AngularDirective('timePickerSettings', TimePickerSettings, [
'renderCount',
'refreshIntervals',
'timePickerHidden',
'nowDelay',
'timezone',
['onTimeZoneChange', { watchDepth: 'reference', wrapApply: true }],
['onRefreshIntervalChange', { watchDepth: 'reference', wrapApply: true }],
['onNowDelayChange', { watchDepth: 'reference', wrapApply: true }],
['onHideTimePickerChange', { watchDepth: 'reference', wrapApply: true }],
]);
react2AngularDirective('clipboardButton', ClipboardButton, [
['getText', { watchDepth: 'reference', wrapApply: true }],
]);
}

@ -1,30 +0,0 @@
import { isArray } from 'lodash';
import coreModule from './core_module';
export function arrayJoin() {
'use strict';
return {
restrict: 'A',
require: 'ngModel',
link: (scope: any, element: any, attr: any, ngModel: any) => {
function split_array(text: string) {
return (text || '').split(',');
}
function join_array(text: string) {
if (isArray(text)) {
return ((text || '') as any).join(',');
} else {
return text;
}
}
ngModel.$parsers.push(split_array);
ngModel.$formatters.push(join_array);
},
};
}
coreModule.directive('arrayJoin', arrayJoin);

@ -1,34 +0,0 @@
import coreModule from './core_module';
export function autofillEventFix($compile: any) {
return {
link: ($scope: any, elem: any) => {
const input = elem[0];
const dispatchChangeEvent = () => {
const event = new Event('change');
return input.dispatchEvent(event);
};
const onAnimationStart = ({ animationName }: AnimationEvent) => {
switch (animationName) {
case 'onAutoFillStart':
return dispatchChangeEvent();
case 'onAutoFillCancel':
return dispatchChangeEvent();
}
return null;
};
// const onChange = (evt: Event) => console.log(evt);
input.addEventListener('animationstart', onAnimationStart);
// input.addEventListener('change', onChange);
$scope.$on('$destroy', () => {
input.removeEventListener('animationstart', onAnimationStart);
// input.removeEventListener('change', onChange);
});
},
};
}
coreModule.directive('autofillEventFix', ['$compile', autofillEventFix]);

@ -1,49 +0,0 @@
import { ILocationService } from 'angular';
import { RouteParamsProvider } from '../core/navigation/patch/RouteParamsProvider';
import { RouteProvider } from '../core/navigation/patch/RouteProvider';
import { AngularLocationWrapper } from './AngularLocationWrapper';
import { coreModule } from './core_module';
// Neutralizing Angular’s location tampering
// https://stackoverflow.com/a/19825756
const tamperAngularLocation = () => {
coreModule.config([
'$provide',
($provide: any) => {
$provide.decorator('$browser', [
'$delegate',
($delegate: any) => {
$delegate.onUrlChange = () => {};
$delegate.url = () => '';
return $delegate;
},
]);
},
]);
};
// Intercepting $location service with implementation based on history
const interceptAngularLocation = () => {
coreModule.config([
'$provide',
($provide: any) => {
$provide.decorator('$location', [
'$delegate',
($delegate: ILocationService) => {
$delegate = new AngularLocationWrapper() as unknown as ILocationService;
return $delegate;
},
]);
},
]);
coreModule.provider('$route', RouteProvider);
coreModule.provider('$routeParams', RouteParamsProvider);
};
export function initAngularRoutingBridge() {
tamperAngularLocation();
interceptAngularLocation();
}

@ -1,59 +0,0 @@
import angular from 'angular';
import $ from 'jquery';
import coreModule from './core_module';
coreModule.directive('bsTooltip', [
'$parse',
'$compile',
function ($parse: any, $compile: any) {
return {
restrict: 'A',
scope: true,
link: function postLink(scope: any, element: any, attrs: any) {
let getter = $parse(attrs.bsTooltip),
value = getter(scope);
scope.$watch(attrs.bsTooltip, function (newValue: any, oldValue: any) {
if (newValue !== oldValue) {
value = newValue;
}
});
// Grafana change, always hide other tooltips
if (true) {
element.on('show', function (ev: any) {
$('.tooltip.in').each(function () {
const $this = $(this),
tooltip = $this.data('tooltip');
if (tooltip && !tooltip.$element.is(element)) {
$this.tooltip('hide');
}
});
});
}
element.tooltip({
title: function () {
return angular.isFunction(value) ? value.apply(null, arguments) : value;
},
html: true,
container: 'body', // Grafana change
});
const tooltip = element.data('tooltip');
tooltip.show = function () {
const r = $.fn.tooltip.Constructor.prototype.show.apply(this, arguments);
this.tip().data('tooltip', this);
return r;
};
scope._tooltip = function (event: any) {
element.tooltip(event);
};
scope.hide = function () {
element.tooltip('hide');
};
scope.show = function () {
element.tooltip('show');
};
scope.dismiss = scope.hide;
},
};
},
]);

@ -1,63 +0,0 @@
import angular from 'angular';
import $ from 'jquery';
import { isFunction } from 'lodash';
import coreModule from './core_module';
coreModule.directive('bsTypeahead', [
'$parse',
function ($parse: any) {
return {
restrict: 'A',
require: '?ngModel',
link: function postLink(scope: any, element: any, attrs: any, controller: any) {
let getter = $parse(attrs.bsTypeahead),
value = getter(scope);
scope.$watch(attrs.bsTypeahead, function (newValue: any, oldValue: any) {
if (newValue !== oldValue) {
value = newValue;
}
});
element.attr('data-provide', 'typeahead');
element.typeahead({
source: function () {
return angular.isFunction(value) ? value.apply(null, arguments) : value;
},
minLength: attrs.minLength || 1,
items: attrs.item,
updater: function (value: any) {
if (controller) {
scope.$apply(function () {
controller.$setViewValue(value);
});
}
scope.$emit('typeahead-updated', value);
return value;
},
});
const typeahead = element.data('typeahead');
typeahead.lookup = function () {
let items;
this.query = this.$element.val() || '';
if (this.query.length < this.options.minLength) {
return this.shown ? this.hide() : this;
}
items = isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source;
return items ? this.process(items) : this;
};
if (!!attrs.matchAll) {
typeahead.matcher = function () {
return true;
};
}
if (attrs.minLength === '0') {
setTimeout(function () {
element.on('focus', function () {
element.val().length === 0 && setTimeout(element.typeahead.bind(element, 'lookup'), 200);
});
});
}
},
};
},
]);

@ -1,22 +0,0 @@
import { coreModule } from 'app/angular/core_module';
coreModule.directive('datasourceHttpSettings', () => {
return {
scope: {
current: '=',
suggestUrl: '@',
noDirectAccess: '@',
showForwardOAuthIdentityOption: '@',
},
templateUrl: 'public/app/angular/partials/http_settings_next.html',
link: {
pre: ($scope: any) => {
// do not show access option if direct access is disabled
$scope.showAccessOption = $scope.noDirectAccess !== 'true';
$scope.onChange = (datasourceSetting: any) => {
$scope.current = datasourceSetting;
};
},
},
};
});

@ -1,23 +0,0 @@
import { render, screen } from '@testing-library/react';
import { NavModelItem } from '@grafana/data';
import { PageHeader } from './PageHeader';
describe('PageHeader', () => {
describe('when the nav tree has a node with a title', () => {
it('should render the title', async () => {
const nav: NavModelItem = {
icon: 'folder-open',
id: 'node',
subTitle: 'node subtitle',
url: '',
text: 'node',
};
render(<PageHeader navItem={nav} />);
expect(screen.getByRole('heading', { name: 'node' })).toBeInTheDocument();
});
});
});

@ -1,165 +0,0 @@
import { css, cx } from '@emotion/css';
import * as React from 'react';
import { NavModelItem, GrafanaTheme2 } from '@grafana/data';
import { Tab, TabsBar, Icon, useStyles2, toIconName } from '@grafana/ui';
import { PageInfoItem } from '../../../core/components/Page/types';
import { PageInfo } from '../../../core/components/PageInfo/PageInfo';
import { ProBadge } from '../../../core/components/Upgrade/ProBadge';
import { PanelHeaderMenuItem } from './PanelHeaderMenuItem';
export interface Props {
navItem: NavModelItem;
renderTitle?: (title: string) => React.ReactNode;
actions?: React.ReactNode;
info?: PageInfoItem[];
subTitle?: React.ReactNode;
}
const SelectNav = ({ children, customCss }: { children: NavModelItem[]; customCss: string }) => {
if (!children || children.length === 0) {
return null;
}
const defaultSelectedItem = children.find((navItem) => {
return navItem.active === true;
});
return (
<div className={`gf-form-select-wrapper width-20 ${customCss}`}>
<div className="dropdown">
<button
type="button"
className="gf-form-input dropdown-toggle"
data-toggle="dropdown"
style={{ textAlign: 'left' }}
>
{defaultSelectedItem?.text}
</button>
<ul role="menu" className="dropdown-menu dropdown-menu--menu">
{children.map((navItem: NavModelItem) => {
if (navItem.hideFromTabs) {
// TODO: Rename hideFromTabs => hideFromNav
return null;
}
return (
<PanelHeaderMenuItem
key={navItem.url}
iconClassName={navItem.icon}
text={navItem.text}
href={navItem.url}
/>
);
})}
</ul>
</div>
</div>
);
};
const Navigation = ({ children }: { children: NavModelItem[] }) => {
if (!children || children.length === 0) {
return null;
}
return (
<nav>
<SelectNav customCss="page-header__select-nav">{children}</SelectNav>
<TabsBar className="page-header__tabs" hideBorder={true}>
{children.map((child, index) => {
return (
!child.hideFromTabs && (
<Tab
label={child.text}
active={child.active}
key={`${child.url}-${index}`}
icon={child.icon}
href={child.url}
suffix={child.tabSuffix}
/>
)
);
})}
</TabsBar>
</nav>
);
};
export const PageHeader = ({ navItem: model, renderTitle, actions, info, subTitle }: Props) => {
const styles = useStyles2(getStyles);
if (!model) {
return null;
}
const renderHeader = (main: NavModelItem) => {
const marginTop = main.icon === 'grafana' ? 12 : 14;
const icon = main.icon && toIconName(main.icon);
const sub = subTitle ?? main.subTitle;
return (
<div className="page-header__inner">
<span className="page-header__logo">
{icon && <Icon name={icon} size="xxxl" style={{ marginTop }} />}
{main.img && <img className="page-header__img" src={main.img} alt="" />}
</span>
<div className={cx('page-header__info-block', styles.headerText)}>
{renderTitle ? renderTitle(main.text) : renderHeaderTitle(main.text, main.highlightText)}
{info && <PageInfo info={info} />}
{sub && <div className="page-header__sub-title">{sub}</div>}
{actions && <div className={styles.actions}>{actions}</div>}
</div>
</div>
);
};
return (
<div className={styles.headerCanvas}>
<div className="page-container">
<div className="page-header">
{renderHeader(model)}
{model.children && model.children.length > 0 && <Navigation>{model.children}</Navigation>}
</div>
</div>
</div>
);
};
function renderHeaderTitle(title: string, highlightText: NavModelItem['highlightText']) {
if (!title) {
return null;
}
return (
<h1 className="page-header__title">
{title}
{highlightText && (
<ProBadge
text={highlightText}
className={css({
verticalAlign: 'middle',
})}
/>
)}
</h1>
);
}
const getStyles = (theme: GrafanaTheme2) => ({
actions: css({
display: 'flex',
flexDirection: 'row',
gap: theme.spacing(1),
}),
headerText: css({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
}),
headerCanvas: css({
background: theme.colors.background.canvas,
}),
});

@ -1,96 +0,0 @@
import { css } from '@emotion/css';
import { useState } from 'react';
import * as React from 'react';
import { PanelMenuItem, GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Icon, toIconName, useStyles2 } from '@grafana/ui';
interface Props {
children?: React.ReactNode;
}
export const PanelHeaderMenuItem = (props: Props & PanelMenuItem) => {
const [ref, setRef] = useState<HTMLLIElement | null>(null);
const isSubMenu = props.type === 'submenu';
const styles = useStyles2(getStyles);
const icon = props.iconClassName ? toIconName(props.iconClassName) : undefined;
switch (props.type) {
case 'divider':
return <li className="divider" />;
case 'group':
return (
<li>
<span className={styles.groupLabel}>{props.text}</span>
</li>
);
default:
return (
<li
className={isSubMenu ? `dropdown-submenu ${getDropdownLocationCssClass(ref)}` : undefined}
ref={setRef}
data-testid={selectors.components.Panels.Panel.menuItems(props.text)}
>
<a onClick={props.onClick} href={props.href} role="menuitem">
{icon && <Icon name={icon} className={styles.menuIconClassName} />}
<span
className="dropdown-item-text"
data-testid={selectors.components.Panels.Panel.headerItems(props.text)}
>
{props.text}
{isSubMenu && <Icon name="angle-right" className={styles.shortcutIconClassName} />}
</span>
{props.shortcut && (
<span className="dropdown-menu-item-shortcut">
<Icon name="keyboard" className={styles.menuIconClassName} /> {props.shortcut}
</span>
)}
</a>
{props.children}
</li>
);
}
};
function getDropdownLocationCssClass(element: HTMLElement | null) {
if (!element) {
return 'invisible';
}
const wrapperPos = element.parentElement!.getBoundingClientRect();
const pos = element.getBoundingClientRect();
if (pos.width === 0) {
return 'invisible';
}
if (wrapperPos.right + pos.width + 10 > window.innerWidth) {
return 'pull-left';
} else {
return 'pull-right';
}
}
function getStyles(theme: GrafanaTheme2) {
return {
menuIconClassName: css({
marginRight: theme.spacing(1),
'a::after': {
display: 'none',
},
}),
shortcutIconClassName: css({
position: 'absolute',
top: '7px',
right: theme.spacing(0.5),
color: theme.colors.text.secondary,
}),
groupLabel: css({
color: theme.colors.text.secondary,
fontSize: theme.typography.size.sm,
padding: theme.spacing(0.5, 1),
}),
};
}

@ -1,10 +0,0 @@
import { coreModule } from 'app/angular/core_module';
coreModule.directive('datasourceTlsAuthSettings', () => {
return {
scope: {
current: '=',
},
templateUrl: 'public/app/angular/partials/tls_auth_settings.html',
};
});

@ -1,4 +0,0 @@
declare module 'brace/*' {
let brace: any;
export default brace;
}

@ -1,200 +0,0 @@
/**
* codeEditor directive based on Ace code editor
* https://github.com/ajaxorg/ace
*
* Basic usage:
* <code-editor content="ctrl.target.query" on-change="ctrl.panelCtrl.refresh()"
* data-mode="sql" data-show-gutter>
* </code-editor>
*
* Params:
* content: Editor content.
* onChange: Function called on content change (invoked on editor blur, ctrl+enter, not on every change).
* getCompleter: Function returned external completer. Completer is an object implemented getCompletions() method,
* see Prometheus Data Source implementation for details.
*
* Some Ace editor options available via data-* attributes:
* data-mode - Language mode (text, sql, javascript, etc.). Default is 'text'.
* data-theme - Editor theme (eg 'solarized_dark').
* data-max-lines - Max editor height in lines. Editor grows automatically from 1 to maxLines.
* data-show-gutter - Show gutter (contains line numbers and additional info).
* data-tab-size - Tab size, default is 2.
* data-behaviours-enabled - Specifies whether to use behaviors or not. "Behaviors" in this case is the auto-pairing of
* special characters, like quotation marks, parenthesis, or brackets.
* data-snippets-enabled - Specifies whether to use snippets or not. "Snippets" are small pieces of code that can be
* inserted via the completion box.
*
* Keybindings:
* Ctrl-Enter (Command-Enter): run onChange() function
*/
import coreModule from 'app/angular/core_module';
import config from 'app/core/config';
const DEFAULT_THEME_DARK = 'ace/theme/grafana-dark';
const DEFAULT_THEME_LIGHT = 'ace/theme/textmate';
const DEFAULT_MODE = 'text';
const DEFAULT_MAX_LINES = 10;
const DEFAULT_TAB_SIZE = 2;
const DEFAULT_BEHAVIORS = true;
const DEFAULT_SNIPPETS = true;
const editorTemplate = `<div></div>`;
async function link(scope: any, elem: any, attrs: any) {
// Options
const langMode = attrs.mode || DEFAULT_MODE;
const maxLines = attrs.maxLines || DEFAULT_MAX_LINES;
const showGutter = attrs.showGutter !== undefined;
const tabSize = attrs.tabSize || DEFAULT_TAB_SIZE;
const behavioursEnabled = attrs.behavioursEnabled ? attrs.behavioursEnabled === 'true' : DEFAULT_BEHAVIORS;
const snippetsEnabled = attrs.snippetsEnabled ? attrs.snippetsEnabled === 'true' : DEFAULT_SNIPPETS;
// Initialize editor
const aceElem = elem.get(0);
const { default: ace } = await import(/* webpackChunkName: "brace" */ 'brace');
await import('brace/ext/language_tools');
await import('brace/theme/textmate');
await import('brace/mode/text');
await import('brace/snippets/text');
await import('brace/mode/sql');
await import('brace/snippets/sql');
await import('brace/mode/sqlserver');
await import('brace/snippets/sqlserver');
await import('brace/mode/markdown');
await import('brace/snippets/markdown');
await import('brace/mode/json');
await import('brace/snippets/json');
// @ts-ignore
await import('./theme-grafana-dark');
const codeEditor = ace.edit(aceElem);
const editorSession = codeEditor.getSession();
const editorOptions = {
maxLines: maxLines,
showGutter: showGutter,
tabSize: tabSize,
behavioursEnabled: behavioursEnabled,
highlightActiveLine: false,
showPrintMargin: false,
autoScrollEditorIntoView: true, // this is needed if editor is inside scrollable page
};
// Set options
codeEditor.setOptions(editorOptions);
// disable depreacation warning
codeEditor.$blockScrolling = Infinity;
// Padding hacks
(codeEditor.renderer as any).setScrollMargin(10, 10);
codeEditor.renderer.setPadding(10);
setThemeMode();
setLangMode(langMode);
setEditorContent(scope.content);
// Add classes
elem.addClass('gf-code-editor');
const textarea = elem.find('textarea');
textarea.addClass('gf-form-input');
// All aria-label to be set for accessibility
textarea.attr('aria-label', attrs.textareaLabel);
if (scope.codeEditorFocus) {
setTimeout(() => {
textarea.focus();
const domEl = textarea[0];
if (domEl.setSelectionRange) {
const pos = textarea.val().length * 2;
domEl.setSelectionRange(pos, pos);
}
}, 100);
}
// Event handlers
editorSession.on('change', (e) => {
scope.$apply(() => {
const newValue = codeEditor.getValue();
scope.content = newValue;
});
});
// Sync with outer scope - update editor content if model has been changed from outside of directive.
scope.$watch('content', (newValue: any, oldValue: any) => {
const editorValue = codeEditor.getValue();
if (newValue !== editorValue && newValue !== oldValue) {
scope.$$postDigest(() => {
setEditorContent(newValue);
});
}
});
codeEditor.on('blur', () => {
scope.onChange();
});
scope.$on('$destroy', () => {
codeEditor.destroy();
});
// Keybindings
codeEditor.commands.addCommand({
name: 'executeQuery',
bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' },
exec: () => {
scope.onChange();
},
});
function setLangMode(lang: string) {
ace.acequire('ace/ext/language_tools');
codeEditor.setOptions({
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: snippetsEnabled,
});
if (scope.getCompleter()) {
// make copy of array as ace seems to share completers array between instances
const anyEditor = codeEditor as any;
anyEditor.completers = anyEditor.completers.slice();
anyEditor.completers.push(scope.getCompleter());
}
const aceModeName = `ace/mode/${lang}`;
editorSession.setMode(aceModeName);
}
function setThemeMode() {
let theme = DEFAULT_THEME_DARK;
if (config.bootData.user.lightTheme) {
theme = DEFAULT_THEME_LIGHT;
}
codeEditor.setTheme(theme);
}
function setEditorContent(value: string) {
codeEditor.setValue(value);
codeEditor.clearSelection();
}
}
export function codeEditorDirective() {
return {
restrict: 'E',
template: editorTemplate,
scope: {
content: '=',
datasource: '=',
codeEditorFocus: '<',
onChange: '&',
getCompleter: '&',
},
link: link,
};
}
coreModule.directive('codeEditor', codeEditorDirective);

@ -1,117 +0,0 @@
ace.define(
'ace/theme/grafana-dark',
['require', 'exports', 'module', 'ace/lib/dom'],
function (acequire, exports, module) {
'use strict';
exports.isDark = true;
exports.cssClass = 'gf-code-dark';
exports.cssText =
'.gf-code-dark .ace_gutter {\
background: #2f3129;\
color: #8f908a\
}\
.gf-code-dark .ace_print-margin {\
width: 1px;\
background: #555651\
}\
.gf-code-dark {\
background-color: #09090b;\
color: #e0e0e0\
}\
.gf-code-dark .ace_cursor {\
color: #f8f8f0\
}\
.gf-code-dark .ace_marker-layer .ace_selection {\
background: #49483e\
}\
.gf-code-dark.ace_multiselect .ace_selection.ace_start {\
box-shadow: 0 0 3px 0px #272822;\
}\
.gf-code-dark .ace_marker-layer .ace_step {\
background: rgb(102, 82, 0)\
}\
.gf-code-dark .ace_marker-layer .ace_bracket {\
margin: -1px 0 0 -1px;\
border: 1px solid #49483e\
}\
.gf-code-dark .ace_marker-layer .ace_active-line {\
background: #202020\
}\
.gf-code-dark .ace_gutter-active-line {\
background-color: #272727\
}\
.gf-code-dark .ace_marker-layer .ace_selected-word {\
border: 1px solid #49483e\
}\
.gf-code-dark .ace_invisible {\
color: #52524d\
}\
.gf-code-dark .ace_entity.ace_name.ace_tag,\
.gf-code-dark .ace_keyword,\
.gf-code-dark .ace_meta.ace_tag,\
.gf-code-dark .ace_storage {\
color: #66d9ef\
}\
.gf-code-dark .ace_punctuation,\
.gf-code-dark .ace_punctuation.ace_tag {\
color: #fff\
}\
.gf-code-dark .ace_constant.ace_character,\
.gf-code-dark .ace_constant.ace_language,\
.gf-code-dark .ace_constant.ace_numeric,\
.gf-code-dark .ace_constant.ace_other {\
color: #fe85fc\
}\
.gf-code-dark .ace_invalid {\
color: #f8f8f0;\
background-color: #f92672\
}\
.gf-code-dark .ace_invalid.ace_deprecated {\
color: #f8f8f0;\
background-color: #ae81ff\
}\
.gf-code-dark .ace_support.ace_constant,\
.gf-code-dark .ace_support.ace_function {\
color: #59e6e3\
}\
.gf-code-dark .ace_fold {\
background-color: #a6e22e;\
border-color: #f8f8f2\
}\
.gf-code-dark .ace_storage.ace_type,\
.gf-code-dark .ace_support.ace_class,\
.gf-code-dark .ace_support.ace_type {\
font-style: italic;\
color: #66d9ef\
}\
.gf-code-dark .ace_entity.ace_name.ace_function,\
.gf-code-dark .ace_entity.ace_other,\
.gf-code-dark .ace_entity.ace_other.ace_attribute-name,\
.gf-code-dark .ace_variable {\
color: #a6e22e\
}\
.gf-code-dark .ace_variable.ace_parameter {\
font-style: italic;\
color: #fd971f\
}\
.gf-code-dark .ace_string {\
color: #74e680\
}\
.gf-code-dark .ace_paren {\
color: #f0a842\
}\
.gf-code-dark .ace_operator {\
color: #FFF\
}\
.gf-code-dark .ace_comment {\
color: #75715e\
}\
.gf-code-dark .ace_indent-guide {\
background: url(data:image/png;base64,ivborw0kggoaaaansuheugaaaaeaaaaccayaaaczgbynaaaaekleqvqimwpq0fd0zxbzd/wpaajvaoxesgneaaaaaelftksuqmcc) right repeat-y\
}';
const dom = acequire('../lib/dom');
dom.importCssString(exports.cssText, exports.cssClass);
}
);

@ -1,286 +0,0 @@
import { ISCEService } from 'angular';
import { debounce, find, indexOf, map, isObject, escape, unescape } from 'lodash';
import coreModule from '../../core_module';
import { promiseToDigest } from '../../promiseToDigest';
function typeaheadMatcher(this: any, item: string) {
let str = this.query;
if (str === '') {
return true;
}
if (str[0] === '/') {
str = str.substring(1);
}
if (str[str.length - 1] === '/') {
str = str.substring(0, str.length - 1);
}
return item.toLowerCase().match(str.toLowerCase());
}
export class FormDropdownCtrl {
inputElement: JQLite;
linkElement: JQLite;
model: any;
display: any;
text: any;
options: any;
cssClass: any;
cssClasses: any;
allowCustom: any;
labelMode: boolean;
linkMode: boolean;
cancelBlur: any;
onChange: any;
getOptions: any;
optionCache: any;
lookupText: boolean;
placeholder: any;
startOpen: any;
debounce: boolean;
static $inject = ['$scope', '$element', '$sce', 'templateSrv'];
constructor(
private $scope: any,
$element: JQLite,
private $sce: ISCEService,
private templateSrv: any
) {
this.inputElement = $element.find('input').first();
this.linkElement = $element.find('a').first();
this.linkMode = true;
this.cancelBlur = null;
this.labelMode = false;
this.lookupText = false;
this.debounce = false;
// listen to model changes
$scope.$watch('ctrl.model', this.modelChanged.bind(this));
}
$onInit() {
if (this.labelMode) {
this.cssClasses = 'gf-form-label ' + this.cssClass;
} else {
this.cssClasses = 'gf-form-input gf-form-input--dropdown ' + this.cssClass;
}
if (this.placeholder) {
this.inputElement.attr('placeholder', this.placeholder);
}
this.inputElement.attr('data-provide', 'typeahead');
this.inputElement.typeahead({
source: this.typeaheadSource.bind(this),
minLength: 0,
items: 10000,
updater: this.typeaheadUpdater.bind(this),
matcher: typeaheadMatcher,
});
// modify typeahead lookup
// this = typeahead
const typeahead = this.inputElement.data('typeahead');
typeahead.lookup = function () {
this.query = this.$element.val() || '';
this.source(this.query, this.process.bind(this));
};
if (this.debounce) {
typeahead.lookup = debounce(typeahead.lookup, 500, { leading: true });
}
this.linkElement.keydown((evt) => {
// trigger typeahead on down arrow or enter key
if (evt.keyCode === 40 || evt.keyCode === 13) {
this.linkElement.click();
}
});
this.inputElement.keydown((evt) => {
if (evt.keyCode === 13) {
setTimeout(() => {
this.inputElement.blur();
}, 300);
}
});
this.inputElement.blur(this.inputBlur.bind(this));
if (this.startOpen) {
setTimeout(this.open.bind(this), 0);
}
}
getOptionsInternal(query: string) {
return promiseToDigest(this.$scope)(Promise.resolve(this.getOptions({ $query: query })));
}
isPromiseLike(obj: any) {
return obj && typeof obj.then === 'function';
}
modelChanged() {
if (isObject(this.model)) {
this.updateDisplay((this.model as any).text);
} else {
// if we have text use it
if (this.lookupText) {
this.getOptionsInternal('').then((options: any) => {
const item: any = find(options, { value: this.model });
this.updateDisplay(item ? item.text : this.model);
});
} else {
this.updateDisplay(this.model);
}
}
}
typeaheadSource(query: string, callback: (res: any) => void) {
this.getOptionsInternal(query).then((options: any) => {
this.optionCache = options;
// extract texts
const optionTexts = map(options, (op: any) => {
return escape(op.text);
});
// add custom values
if (this.allowCustom && this.text !== '') {
if (indexOf(optionTexts, this.text) === -1) {
optionTexts.unshift(this.text);
}
}
callback(optionTexts);
});
}
typeaheadUpdater(text: string) {
if (text === this.text) {
clearTimeout(this.cancelBlur);
this.inputElement.focus();
return text;
}
this.inputElement.val(text);
this.switchToLink(true);
return text;
}
switchToLink(fromClick: boolean) {
if (this.linkMode && !fromClick) {
return;
}
clearTimeout(this.cancelBlur);
this.cancelBlur = null;
this.linkMode = true;
this.inputElement.hide();
this.linkElement.show();
this.updateValue(this.inputElement.val() as string);
}
inputBlur() {
// happens long before the click event on the typeahead options
// need to have long delay because the blur
this.cancelBlur = setTimeout(this.switchToLink.bind(this), 200);
}
updateValue(text: string) {
text = unescape(text);
if (text === '' || this.text === text) {
return;
}
this.$scope.$apply(() => {
const option: any = find(this.optionCache, { text: text });
if (option) {
if (isObject(this.model)) {
this.model = option;
} else {
this.model = option.value;
}
this.text = option.text;
} else if (this.allowCustom) {
if (isObject(this.model)) {
(this.model as any).text = (this.model as any).value = text;
} else {
this.model = text;
}
this.text = text;
}
// needs to call this after digest so
// property is synced with outerscope
this.$scope.$$postDigest(() => {
this.$scope.$apply(() => {
this.onChange({ $option: option });
});
});
});
}
updateDisplay(text: string) {
this.text = text;
this.display = this.$sce.trustAsHtml(this.templateSrv.highlightVariablesAsHtml(text));
}
open() {
this.inputElement.css('width', Math.max(this.linkElement.width()!, 80) + 16 + 'px');
this.inputElement.show();
this.inputElement.focus();
this.linkElement.hide();
this.linkMode = false;
const typeahead = this.inputElement.data('typeahead');
if (typeahead) {
this.inputElement.val('');
typeahead.lookup();
}
}
}
const template = `
<input type="text"
data-provide="typeahead"
class="gf-form-input"
spellcheck="false"
style="display:none">
</input>
<a ng-class="ctrl.cssClasses"
tabindex="1"
ng-click="ctrl.open()"
give-focus="ctrl.focus"
ng-bind-html="ctrl.display || '&nbsp;'">
</a>
`;
export function formDropdownDirective() {
return {
restrict: 'E',
template: template,
controller: FormDropdownCtrl,
bindToController: true,
controllerAs: 'ctrl',
scope: {
model: '=',
getOptions: '&',
onChange: '&',
cssClass: '@',
allowCustom: '@',
labelMode: '@',
lookupText: '@',
placeholder: '@',
startOpen: '@',
debounce: '@',
},
};
}
coreModule.directive('gfFormDropdown', formDropdownDirective);

@ -1,69 +0,0 @@
import { each } from 'lodash';
// @ts-ignore
import Drop from 'tether-drop';
import coreModule from 'app/angular/core_module';
export function infoPopover() {
return {
restrict: 'E',
template: `<icon name="'info-circle'" style="margin-left: 10px;" size="'xs'"></icon>`,
transclude: true,
link: (scope: any, elem: any, attrs: any, ctrl: any, transclude: any) => {
const offset = attrs.offset || '0 -10px';
const position = attrs.position || 'right middle';
let classes = 'drop-help drop-hide-out-of-bounds';
const openOn = 'hover';
elem.addClass('gf-form-help-icon');
if (attrs.wide) {
classes += ' drop-wide';
}
if (attrs.mode) {
elem.addClass('gf-form-help-icon--' + attrs.mode);
}
transclude((clone: any, newScope: any) => {
const content = document.createElement('div');
content.className = 'markdown-html';
each(clone, (node) => {
content.appendChild(node);
});
const dropOptions = {
target: elem[0],
content: content,
position: position,
classes: classes,
openOn: openOn,
hoverOpenDelay: 400,
tetherOptions: {
offset: offset,
constraints: [
{
to: 'window',
attachment: 'together',
pin: true,
},
],
},
};
// Create drop in next digest after directive content is rendered.
scope.$applyAsync(() => {
const drop = new Drop(dropOptions);
const unbind = scope.$on('$destroy', () => {
drop.destroy();
unbind();
});
});
});
},
};
}
coreModule.directive('infoPopover', infoPopover);

@ -1,29 +0,0 @@
import { JsonExplorer } from '@grafana/ui';
import coreModule from 'app/angular/core_module';
coreModule.directive('jsonTree', [
function jsonTreeDirective() {
return {
restrict: 'E',
scope: {
object: '=',
startExpanded: '@',
rootName: '@',
},
link: (scope: any, elem) => {
let expansionLevel = scope.startExpanded;
if (scope.startExpanded === 'true') {
expansionLevel = 2;
} else if (scope.startExpanded === 'false') {
expansionLevel = 1;
}
const jsonObject = { [scope.rootName]: scope.object };
const jsonExp = new JsonExplorer(jsonObject, expansionLevel, {
animateOpen: true,
});
const html = jsonExp.render(true);
elem.append(html);
},
};
},
]);

@ -1,249 +0,0 @@
import angular, { ILocationService } from 'angular';
import { each } from 'lodash';
import { DataSourceApi, PanelEvents } from '@grafana/data';
import coreModule from 'app/angular/core_module';
import config from 'app/core/config';
import { importPanelPlugin } from '../../features/plugins/importPanelPlugin';
import { importDataSourcePlugin, importAppPlugin } from '../../features/plugins/plugin_loader';
coreModule.directive('pluginComponent', ['$compile', '$http', '$templateCache', '$location', pluginDirectiveLoader]);
function pluginDirectiveLoader($compile: any, $http: any, $templateCache: any, $location: ILocationService) {
function getTemplate(component: { template: any; templateUrl: any }) {
if (component.template) {
return Promise.resolve(component.template);
}
const cached = $templateCache.get(component.templateUrl);
if (cached) {
return Promise.resolve(cached);
}
return $http.get(component.templateUrl).then((res: any) => {
return res.data;
});
}
function relativeTemplateUrlToAbs(templateUrl: string, baseUrl: string) {
if (!templateUrl) {
return undefined;
}
if (templateUrl.indexOf('public') === 0) {
return templateUrl;
}
return baseUrl + '/' + templateUrl;
}
function getPluginComponentDirective(options: any) {
// handle relative template urls for plugin templates
options.Component.templateUrl = relativeTemplateUrlToAbs(options.Component.templateUrl, options.baseUrl);
return () => {
return {
templateUrl: options.Component.templateUrl,
template: options.Component.template,
restrict: 'E',
controller: options.Component,
controllerAs: 'ctrl',
bindToController: true,
scope: options.bindings,
link: (scope: any, elem: any, attrs: any, ctrl: any) => {
if (ctrl.link) {
ctrl.link(scope, elem, attrs, ctrl);
}
if (ctrl.init) {
ctrl.init();
}
},
};
};
}
function loadPanelComponentInfo(scope: any, attrs: any) {
const componentInfo: any = {
name: 'panel-plugin-' + scope.panel.type,
bindings: { dashboard: '=', panel: '=', row: '=' },
attrs: {
dashboard: 'dashboard',
panel: 'panel',
class: 'panel-height-helper',
},
};
const panelInfo = config.panels[scope.panel.type];
return importPanelPlugin(panelInfo.id).then((panelPlugin) => {
const PanelCtrl = panelPlugin.angularPanelCtrl;
componentInfo.Component = PanelCtrl;
if (!PanelCtrl || PanelCtrl.registered) {
return componentInfo;
}
if (PanelCtrl.templatePromise) {
return PanelCtrl.templatePromise.then((res: any) => {
return componentInfo;
});
}
if (panelInfo) {
PanelCtrl.templateUrl = relativeTemplateUrlToAbs(PanelCtrl.templateUrl, panelInfo.baseUrl);
}
PanelCtrl.templatePromise = getTemplate(PanelCtrl).then((template: any) => {
PanelCtrl.templateUrl = null;
PanelCtrl.template = `<grafana-panel ctrl="ctrl" class="panel-height-helper">${template}</grafana-panel>`;
return { ...componentInfo, baseUrl: panelInfo.baseUrl };
});
return PanelCtrl.templatePromise;
});
}
function getModule(scope: any, attrs: any): any {
switch (attrs.type) {
// QueryCtrl
case 'query-ctrl': {
const ds: DataSourceApi = scope.ctrl.datasource as DataSourceApi;
return Promise.resolve({
baseUrl: ds.meta.baseUrl,
name: 'query-ctrl-' + ds.meta.id,
bindings: { target: '=', panelCtrl: '=', datasource: '=' },
attrs: {
target: 'ctrl.target',
'panel-ctrl': 'ctrl',
datasource: 'ctrl.datasource',
},
Component: ds.components!.QueryCtrl,
});
}
// Annotations
case 'annotations-query-ctrl': {
const baseUrl = scope.ctrl.currentDatasource.meta.baseUrl;
const pluginId = scope.ctrl.currentDatasource.meta.id;
return importDataSourcePlugin(scope.ctrl.currentDatasource.meta).then((dsPlugin) => {
return {
baseUrl,
name: 'annotations-query-ctrl-' + pluginId,
bindings: { annotation: '=', datasource: '=' },
attrs: {
annotation: 'ctrl.currentAnnotation',
datasource: 'ctrl.currentDatasource',
},
Component: dsPlugin.components.AnnotationsQueryCtrl,
};
});
}
// Datasource ConfigCtrl
case 'datasource-config-ctrl': {
const dsMeta = scope.ctrl.datasourceMeta;
const angularUrl = $location.url();
return importDataSourcePlugin(dsMeta).then((dsPlugin) => {
scope.$watch(
'ctrl.current',
() => {
// This watcher can trigger when we navigate away due to late digests
// This check is to stop onModelChanged from being called when navigating away
// as it triggers a redux action which comes before the angular $routeChangeSucces and
// This makes the bridgeSrv think location changed from redux before detecting it was actually
// changed from angular.
if (angularUrl === $location.url()) {
scope.onModelChanged(scope.ctrl.current);
}
},
true
);
return {
baseUrl: dsMeta.baseUrl,
name: 'ds-config-' + dsMeta.id,
bindings: { meta: '=', current: '=' },
attrs: { meta: 'ctrl.datasourceMeta', current: 'ctrl.current' },
Component: dsPlugin.angularConfigCtrl,
};
});
}
// AppConfigCtrl
case 'app-config-ctrl': {
const model = scope.ctrl.model;
return importAppPlugin(model).then((appPlugin) => {
return {
baseUrl: model.baseUrl,
name: 'app-config-' + model.id,
bindings: { appModel: '=', appEditCtrl: '=' },
attrs: { 'app-model': 'ctrl.model', 'app-edit-ctrl': 'ctrl' },
Component: appPlugin.angularConfigCtrl,
};
});
}
// Panel
case 'panel': {
return loadPanelComponentInfo(scope, attrs);
}
default: {
return Promise.reject({
message: 'Could not find component type: ' + attrs.type,
});
}
}
}
function appendAndCompile(scope: any, elem: JQuery, componentInfo: any) {
const child = angular.element(document.createElement(componentInfo.name));
each(componentInfo.attrs, (value, key) => {
child.attr(key, value);
});
$compile(child)(scope);
elem.empty();
// let a binding digest cycle complete before adding to dom
setTimeout(() => {
scope.$applyAsync(() => {
elem.append(child);
setTimeout(() => {
scope.$applyAsync(() => {
scope.$broadcast(PanelEvents.componentDidMount.name);
});
});
});
});
}
function registerPluginComponent(scope: any, elem: JQuery, attrs: any, componentInfo: any) {
if (componentInfo.notFound) {
elem.empty();
return;
}
if (!componentInfo.Component) {
throw {
message: 'Failed to find exported plugin component for ' + componentInfo.name,
};
}
if (!componentInfo.Component.registered) {
const directiveName = attrs.$normalize(componentInfo.name);
const directiveFn = getPluginComponentDirective(componentInfo);
coreModule.directive(directiveName, directiveFn);
componentInfo.Component.registered = true;
}
appendAndCompile(scope, elem, componentInfo);
}
return {
restrict: 'E',
link: (scope: any, elem: JQuery, attrs: any) => {
getModule(scope, attrs)
.then((componentInfo: any) => {
registerPluginComponent(scope, elem, attrs, componentInfo);
})
.catch((err: any) => {
console.error('Plugin component error', err);
});
},
};
}

@ -1,186 +0,0 @@
import $ from 'jquery';
import { debounce, each, map, partial, escape, unescape } from 'lodash';
import coreModule from 'app/angular/core_module';
import { promiseToDigest } from '../promiseToDigest';
const template = `
<div class="dropdown cascade-open">
<a ng-click="showActionsMenu()" class="query-part-name pointer dropdown-toggle" data-toggle="dropdown">{{part.def.type}}</a>
<span>(</span><span class="query-part-parameters"></span><span>)</span>
<ul class="dropdown-menu">
<li ng-repeat="action in partActions">
<a ng-click="triggerPartAction(action)">{{action.text}}</a>
</li>
</ul>
`;
coreModule.directive('queryPartEditor', ['templateSrv', queryPartEditorDirective]);
export function queryPartEditorDirective(templateSrv: any) {
const paramTemplate = '<input type="text" class="hide input-mini tight-form-func-param"></input>';
return {
restrict: 'E',
template: template,
scope: {
part: '=',
handleEvent: '&',
debounce: '@',
},
link: function postLink($scope: any, elem: any) {
const part = $scope.part;
const partDef = part.def;
const $paramsContainer = elem.find('.query-part-parameters');
const debounceLookup = $scope.debounce;
$scope.partActions = [];
function clickFuncParam(this: any, paramIndex: number) {
const $link = $(this);
const $input = $link.next();
$input.val(part.params[paramIndex]);
$input.css('width', $link.width()! + 16 + 'px');
$link.hide();
$input.show();
$input.focus();
$input.select();
const typeahead = $input.data('typeahead');
if (typeahead) {
$input.val('');
typeahead.lookup();
}
}
function inputBlur(this: any, paramIndex: number) {
const $input = $(this);
const $link = $input.prev();
const newValue = $input.val();
if (newValue !== '' || part.def.params[paramIndex].optional) {
$link.html(templateSrv.highlightVariablesAsHtml(newValue));
part.updateParam($input.val(), paramIndex);
$scope.$apply(() => {
$scope.handleEvent({ $event: { name: 'part-param-changed' } });
});
}
$input.hide();
$link.show();
}
function inputKeyPress(this: any, paramIndex: number, e: any) {
if (e.which === 13) {
inputBlur.call(this, paramIndex);
}
}
function inputKeyDown(this: any) {
this.style.width = (3 + this.value.length) * 8 + 'px';
}
function addTypeahead($input: JQuery, param: any, paramIndex: number) {
if (!param.options && !param.dynamicLookup) {
return;
}
const typeaheadSource = (query: string, callback: any) => {
if (param.options) {
let options = param.options;
if (param.type === 'int') {
options = map(options, (val) => {
return val.toString();
});
}
return options;
}
$scope.$apply(() => {
$scope.handleEvent({ $event: { name: 'get-param-options' } }).then((result: any) => {
const dynamicOptions = map(result, (op) => {
return escape(op.value);
});
callback(dynamicOptions);
});
});
};
$input.attr('data-provide', 'typeahead');
$input.typeahead({
source: typeaheadSource,
minLength: 0,
items: 1000,
updater: (value: string) => {
value = unescape(value);
setTimeout(() => {
inputBlur.call($input[0], paramIndex);
}, 0);
return value;
},
});
const typeahead = $input.data('typeahead');
typeahead.lookup = function () {
this.query = this.$element.val() || '';
const items = this.source(this.query, $.proxy(this.process, this));
return items ? this.process(items) : items;
};
if (debounceLookup) {
typeahead.lookup = debounce(typeahead.lookup, 500, { leading: true });
}
}
$scope.showActionsMenu = () => {
promiseToDigest($scope)(
$scope.handleEvent({ $event: { name: 'get-part-actions' } }).then((res: any) => {
$scope.partActions = res;
})
);
};
$scope.triggerPartAction = (action: string) => {
$scope.handleEvent({ $event: { name: 'action', action: action } });
};
function addElementsAndCompile() {
each(partDef.params, (param: any, index: number) => {
if (param.optional && part.params.length <= index) {
return;
}
if (index > 0) {
$('<span>, </span>').appendTo($paramsContainer);
}
const paramValue = templateSrv.highlightVariablesAsHtml(part.params[index]);
const $paramLink = $('<a class="graphite-func-param-link pointer">' + paramValue + '</a>');
const $input = $(paramTemplate);
$paramLink.appendTo($paramsContainer);
$input.appendTo($paramsContainer);
$input.blur(partial(inputBlur, index));
$input.keyup(inputKeyDown);
$input.keypress(partial(inputKeyPress, index));
$paramLink.click(partial(clickFuncParam, index));
addTypeahead($input, param, index);
});
}
function relink() {
$paramsContainer.empty();
addElementsAndCompile();
}
relink();
},
};
}

@ -1,50 +0,0 @@
// @ts-ignore
import baron from 'baron';
import $ from 'jquery';
import coreModule from 'app/angular/core_module';
const scrollBarHTML = `
<div class="baron__track">
<div class="baron__bar"></div>
</div>
`;
const scrollRootClass = 'baron baron__root';
const scrollerClass = 'baron__scroller';
export function geminiScrollbar() {
return {
restrict: 'A',
link: (scope: any, elem: any, attrs: any) => {
let scrollRoot = elem.parent();
const scroller = elem;
if (attrs.grafanaScrollbar && attrs.grafanaScrollbar === 'scrollonroot') {
scrollRoot = scroller;
}
scrollRoot.addClass(scrollRootClass);
$(scrollBarHTML).appendTo(scrollRoot);
elem.addClass(scrollerClass);
const scrollParams = {
root: scrollRoot[0],
scroller: scroller[0],
bar: '.baron__bar',
barOnCls: '_scrollbar',
scrollingCls: '_scrolling',
track: '.baron__track',
direction: 'v',
};
const scrollbar = baron(scrollParams);
scope.$on('$destroy', () => {
scrollbar.dispose();
});
},
};
}
coreModule.directive('grafanaScrollbar', geminiScrollbar);

@ -1,24 +0,0 @@
/**
* Wrapper for the new ngReact <color-picker> directive for backward compatibility.
* Allows remaining <spectrum-picker> untouched in outdated plugins.
* Technically, it's just a wrapper for react component with two-way data binding support.
*/
import coreModule from '../core_module';
coreModule.directive('spectrumPicker', spectrumPicker);
export function spectrumPicker() {
return {
restrict: 'E',
require: 'ngModel',
scope: true,
replace: true,
template: '<color-picker color="ngModel.$viewValue" on-change="onColorChange"></color-picker>',
link: (scope: any, element: any, attrs: any, ngModel: any) => {
scope.ngModel = ngModel;
scope.onColorChange = (color: string) => {
ngModel.$setViewValue(color);
};
},
};
}

@ -1,74 +0,0 @@
import { clone } from 'lodash';
export class SqlPartDef {
type: string;
style: string;
label: string;
params: any[];
defaultParams: any[];
wrapOpen: string;
wrapClose: string;
separator: string;
constructor(options: any) {
this.type = options.type;
if (options.label) {
this.label = options.label;
} else {
this.label = this.type[0].toUpperCase() + this.type.substring(1) + ':';
}
this.style = options.style;
if (this.style === 'function') {
this.wrapOpen = '(';
this.wrapClose = ')';
this.separator = ', ';
} else {
this.wrapOpen = ' ';
this.wrapClose = ' ';
this.separator = ' ';
}
this.params = options.params;
this.defaultParams = options.defaultParams;
}
}
export class SqlPart {
part: any;
def: SqlPartDef;
params: any[];
label: string;
name: string;
datatype: string;
constructor(part: any, def: any) {
this.part = part;
this.def = def;
if (!this.def) {
throw { message: 'Could not find sql part ' + part.type };
}
this.datatype = part.datatype;
if (part.name) {
this.name = part.name;
this.label = def.label + ' ' + part.name;
} else {
this.name = '';
this.label = def.label;
}
part.params = part.params || clone(this.def.defaultParams);
this.params = part.params;
}
updateParam(strValue: string, index: number) {
// handle optional parameters
if (strValue === '' && this.def.params[index].optional) {
this.params.splice(index, 1);
} else {
this.params[index] = strValue;
}
this.part.params = this.params;
}
}

@ -1,196 +0,0 @@
import $ from 'jquery';
import { debounce, each, indexOf, map, partial, escape, unescape } from 'lodash';
import coreModule from 'app/angular/core_module';
const template = `
<div class="dropdown cascade-open">
<a ng-click="showActionsMenu()" class="query-part-name pointer dropdown-toggle" data-toggle="dropdown">{{part.label}}</a>
<span>{{part.def.wrapOpen}}</span><span class="query-part-parameters"></span><span>{{part.def.wrapClose}}</span>
<ul class="dropdown-menu">
<li ng-repeat="action in partActions">
<a ng-click="triggerPartAction(action)">{{action.text}}</a>
</li>
</ul>
`;
coreModule.directive('sqlPartEditor', ['templateSrv', sqlPartEditorDirective]);
export function sqlPartEditorDirective(templateSrv: any) {
const paramTemplate = '<input type="text" class="hide input-mini"></input>';
return {
restrict: 'E',
template: template,
scope: {
part: '=',
handleEvent: '&',
debounce: '@',
},
link: function postLink($scope: any, elem: any) {
const part = $scope.part;
const partDef = part.def;
const $paramsContainer = elem.find('.query-part-parameters');
const debounceLookup = $scope.debounce;
let cancelBlur: any = null;
$scope.partActions = [];
function clickFuncParam(this: any, paramIndex: number) {
const $link = $(this);
const $input = $link.next();
$input.val(part.params[paramIndex]);
$input.css('width', $link.width()! + 16 + 'px');
$link.hide();
$input.show();
$input.focus();
$input.select();
const typeahead = $input.data('typeahead');
if (typeahead) {
$input.val('');
typeahead.lookup();
}
}
function inputBlur($input: JQuery, paramIndex: number) {
cancelBlur = setTimeout(() => {
switchToLink($input, paramIndex);
}, 200);
}
function switchToLink($input: JQuery, paramIndex: number) {
const $link = $input.prev();
const newValue = $input.val();
if (newValue !== '' || part.def.params[paramIndex].optional) {
$link.html(templateSrv.highlightVariablesAsHtml(newValue));
part.updateParam($input.val(), paramIndex);
$scope.$apply(() => {
$scope.handleEvent({ $event: { name: 'part-param-changed' } });
});
}
$input.hide();
$link.show();
}
function inputKeyPress(this: any, paramIndex: number, e: any) {
if (e.which === 13) {
switchToLink($(this), paramIndex);
}
}
function inputKeyDown(this: any) {
this.style.width = (3 + this.value.length) * 8 + 'px';
}
function addTypeahead($input: JQuery, param: any, paramIndex: number) {
if (!param.options && !param.dynamicLookup) {
return;
}
const typeaheadSource = (query: string, callback: any) => {
if (param.options) {
let options = param.options;
if (param.type === 'int') {
options = map(options, (val) => {
return val.toString();
});
}
return options;
}
$scope.$apply(() => {
$scope.handleEvent({ $event: { name: 'get-param-options', param: param } }).then((result: any) => {
const dynamicOptions = map(result, (op) => {
return escape(op.value);
});
// add current value to dropdown if it's not in dynamicOptions
if (indexOf(dynamicOptions, part.params[paramIndex]) === -1) {
dynamicOptions.unshift(escape(part.params[paramIndex]));
}
callback(dynamicOptions);
});
});
};
$input.attr('data-provide', 'typeahead');
$input.typeahead({
source: typeaheadSource,
minLength: 0,
items: 1000,
updater: (value: string) => {
value = unescape(value);
if (value === part.params[paramIndex]) {
clearTimeout(cancelBlur);
$input.focus();
return value;
}
return value;
},
});
const typeahead = $input.data('typeahead');
typeahead.lookup = function () {
this.query = this.$element.val() || '';
const items = this.source(this.query, $.proxy(this.process, this));
return items ? this.process(items) : items;
};
if (debounceLookup) {
typeahead.lookup = debounce(typeahead.lookup, 500, { leading: true });
}
}
$scope.showActionsMenu = () => {
$scope.handleEvent({ $event: { name: 'get-part-actions' } }).then((res: any) => {
$scope.partActions = res;
});
};
$scope.triggerPartAction = (action: string) => {
$scope.handleEvent({ $event: { name: 'action', action: action } });
};
function addElementsAndCompile() {
each(partDef.params, (param: any, index: number) => {
if (param.optional && part.params.length <= index) {
return;
}
if (index > 0) {
$('<span>' + partDef.separator + '</span>').appendTo($paramsContainer);
}
const paramValue = templateSrv.highlightVariablesAsHtml(part.params[index]);
const $paramLink = $('<a class="query-part__link">' + paramValue + '</a>');
const $input = $(paramTemplate);
$paramLink.appendTo($paramsContainer);
$input.appendTo($paramsContainer);
$input.blur(partial(inputBlur, $input, index));
$input.keyup(inputKeyDown);
$input.keypress(partial(inputKeyPress, index));
$paramLink.click(partial(clickFuncParam, index));
addTypeahead($input, param, index);
});
}
function relink() {
$paramsContainer.empty();
addElementsAndCompile();
}
relink();
},
};
}

@ -1,94 +0,0 @@
import coreModule from 'app/angular/core_module';
const template = `
<label for="check-{{ctrl.id}}" class="gf-form-switch-container">
<div class="gf-form-label {{ctrl.labelClass}}" ng-show="ctrl.label">
{{ctrl.label}}
<info-popover mode="right-normal" ng-if="ctrl.tooltip" position="top center">
{{ctrl.tooltip}}
</info-popover>
</div>
<div class="gf-form-switch {{ctrl.switchClass}}" ng-if="ctrl.show">
<input id="check-{{ctrl.id}}" type="checkbox" ng-model="ctrl.checked" ng-change="ctrl.internalOnChange()">
<span class="gf-form-switch__slider"></span>
</div>
</label>
`;
const checkboxTemplate = `
<label for="check-{{ctrl.id}}" class="gf-form-switch-container">
<div class="gf-form-label {{ctrl.labelClass}}" ng-show="ctrl.label">
{{ctrl.label}}
<info-popover mode="right-normal" ng-if="ctrl.tooltip" position="top center">
{{ctrl.tooltip}}
</info-popover>
</div>
<div class="gf-form-checkbox {{ctrl.switchClass}}" ng-if="ctrl.show">
<input id="check-{{ctrl.id}}" type="checkbox" ng-model="ctrl.checked" ng-change="ctrl.internalOnChange()">
<span class="gf-form-switch__checkbox"></span>
</div>
</label>
`;
export class SwitchCtrl {
onChange: any;
checked: any;
show: any;
id: any;
label?: string;
static $inject = ['$scope', '$timeout'];
constructor(
$scope: any,
private $timeout: any
) {
this.show = true;
this.id = $scope.$id;
}
internalOnChange() {
return this.$timeout(() => {
return this.onChange();
});
}
}
export function switchDirective() {
return {
restrict: 'E',
controller: SwitchCtrl,
controllerAs: 'ctrl',
bindToController: true,
scope: {
checked: '=',
label: '@',
labelClass: '@',
tooltip: '@',
switchClass: '@',
onChange: '&',
},
template: template,
};
}
export function checkboxDirective() {
return {
restrict: 'E',
controller: SwitchCtrl,
controllerAs: 'ctrl',
bindToController: true,
scope: {
checked: '=',
label: '@',
labelClass: '@',
tooltip: '@',
switchClass: '@',
onChange: '&',
},
template: checkboxTemplate,
};
}
coreModule.directive('gfFormSwitch', switchDirective);
coreModule.directive('gfFormCheckbox', checkboxDirective);

@ -1,18 +0,0 @@
import angular from 'angular';
const coreModule = angular.module('grafana.core', ['ngRoute']);
// legacy modules
const angularModules = [
coreModule,
angular.module('grafana.controllers', []),
angular.module('grafana.directives', []),
angular.module('grafana.factories', []),
angular.module('grafana.services', []),
angular.module('grafana.filters', []),
angular.module('grafana.routes', []),
];
export { angularModules, coreModule };
export default coreModule;

@ -1,80 +0,0 @@
import angular from 'angular';
import { GrafanaRootScope } from 'app/angular/GrafanaCtrl';
import coreModule from './core_module';
export class DeltaCtrl {
observer: any;
constructor() {
const waitForCompile = () => {};
this.observer = new MutationObserver(waitForCompile);
const observerConfig = {
attributes: true,
attributeFilter: ['class'],
characterData: false,
childList: true,
subtree: false,
};
this.observer.observe(angular.element('.delta-html')[0], observerConfig);
}
$onDestroy() {
this.observer.disconnect();
}
}
export function delta() {
return {
controller: DeltaCtrl,
replace: false,
restrict: 'A',
};
}
coreModule.directive('diffDelta', delta);
// Link to JSON line number
export class LinkJSONCtrl {
static $inject = ['$scope', '$rootScope', '$anchorScroll'];
constructor(
private $scope: any,
private $rootScope: GrafanaRootScope,
private $anchorScroll: any
) {}
goToLine(line: number) {
let unbind: () => void;
const scroll = () => {
this.$anchorScroll(`l${line}`);
unbind();
};
this.$scope.switchView().then(() => {
unbind = this.$rootScope.$on('json-diff-ready', scroll.bind(this));
});
}
}
export function linkJson() {
return {
controller: LinkJSONCtrl,
controllerAs: 'ctrl',
replace: true,
restrict: 'E',
scope: {
line: '@lineDisplay',
link: '@lineLink',
switchView: '&',
},
template: `<a class="diff-linenum btn btn-inverse btn-small" ng-click="ctrl.goToLine(link)">Line {{ line }}</a>`,
};
}
coreModule.directive('diffLinkJson', linkJson);

@ -1,273 +0,0 @@
import $ from 'jquery';
import { each, reduce } from 'lodash';
import coreModule from './core_module';
export function dropdownTypeahead($compile: any) {
const inputTemplate =
'<input type="text"' +
' class="gf-form-input input-medium tight-form-input"' +
' spellcheck="false" style="display:none"></input>';
const buttonTemplate =
'<a class="gf-form-label tight-form-func dropdown-toggle"' +
' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
' ><i class="fa fa-plus"></i></a>';
return {
scope: {
menuItems: '=dropdownTypeahead',
dropdownTypeaheadOnSelect: '&dropdownTypeaheadOnSelect',
model: '=ngModel',
},
link: ($scope: any, elem: any, attrs: any) => {
const $input = $(inputTemplate);
const $button = $(buttonTemplate);
$input.appendTo(elem);
$button.appendTo(elem);
if (attrs.linkText) {
$button.html(attrs.linkText);
}
if (attrs.ngModel) {
$scope.$watch('model', (newValue: any) => {
each($scope.menuItems, (item) => {
each(item.submenu, (subItem) => {
if (subItem.value === newValue) {
$button.html(subItem.text);
}
});
});
});
}
const typeaheadValues = reduce(
$scope.menuItems,
(memo: any[], value, index) => {
if (!value.submenu) {
value.click = 'menuItemSelected(' + index + ')';
memo.push(value.text);
} else {
each(value.submenu, (item, subIndex) => {
item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
memo.push(value.text + ' ' + item.text);
});
}
return memo;
},
[]
);
const closeDropdownMenu = () => {
$input.hide();
$input.val('');
$button.show();
$button.focus();
elem.removeClass('open');
};
$scope.menuItemSelected = (index: number, subIndex: number) => {
const menuItem = $scope.menuItems[index];
const payload: any = { $item: menuItem };
if (menuItem.submenu && subIndex !== void 0) {
payload.$subItem = menuItem.submenu[subIndex];
}
$scope.dropdownTypeaheadOnSelect(payload);
closeDropdownMenu();
};
$input.attr('data-provide', 'typeahead');
$input.typeahead({
source: typeaheadValues,
minLength: 1,
items: 10,
updater: (value: string) => {
const result: any = {};
each($scope.menuItems, (menuItem) => {
each(menuItem.submenu, (submenuItem) => {
if (value === menuItem.text + ' ' + submenuItem.text) {
result.$subItem = submenuItem;
result.$item = menuItem;
}
});
});
if (result.$item) {
$scope.$apply(() => {
$scope.dropdownTypeaheadOnSelect(result);
});
}
$input.trigger('blur');
return '';
},
});
$button.click(() => {
$button.hide();
$input.show();
$input.focus();
});
$input.keyup(() => {
elem.toggleClass('open', $input.val() === '');
});
elem.mousedown((evt: Event) => {
evt.preventDefault();
});
$input.blur(() => {
$input.hide();
$input.val('');
$button.show();
$button.focus();
// clicking the function dropdown menu won't
// work if you remove class at once
setTimeout(() => {
elem.removeClass('open');
}, 200);
});
$compile(elem.contents())($scope);
},
};
}
export function dropdownTypeahead2($compile: any) {
const inputTemplate =
'<input type="text"' + ' class="gf-form-input"' + ' spellcheck="false" style="display:none"></input>';
const buttonTemplate =
'<a class="{{buttonTemplateClass}} dropdown-toggle"' +
' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
' ><i class="fa fa-plus"></i></a>';
return {
scope: {
menuItems: '=dropdownTypeahead2',
dropdownTypeaheadOnSelect: '&dropdownTypeaheadOnSelect',
model: '=ngModel',
buttonTemplateClass: '@',
},
link: ($scope: any, elem: any, attrs: any) => {
const $input = $(inputTemplate);
if (!$scope.buttonTemplateClass) {
$scope.buttonTemplateClass = 'gf-form-input';
}
const $button = $(buttonTemplate);
const timeoutId = {
blur: null as any,
};
$input.appendTo(elem);
$button.appendTo(elem);
if (attrs.linkText) {
$button.html(attrs.linkText);
}
if (attrs.ngModel) {
$scope.$watch('model', (newValue: any) => {
each($scope.menuItems, (item) => {
each(item.submenu, (subItem) => {
if (subItem.value === newValue) {
$button.html(subItem.text);
}
});
});
});
}
const typeaheadValues = reduce(
$scope.menuItems,
(memo: any[], value, index) => {
if (!value.submenu) {
value.click = 'menuItemSelected(' + index + ')';
memo.push(value.text);
} else {
each(value.submenu, (item, subIndex) => {
item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
memo.push(value.text + ' ' + item.text);
});
}
return memo;
},
[]
);
const closeDropdownMenu = () => {
$input.hide();
$input.val('');
$button.show();
$button.focus();
elem.removeClass('open');
};
$scope.menuItemSelected = (index: number, subIndex: number) => {
const menuItem = $scope.menuItems[index];
const payload: any = { $item: menuItem };
if (menuItem.submenu && subIndex !== void 0) {
payload.$subItem = menuItem.submenu[subIndex];
}
$scope.dropdownTypeaheadOnSelect(payload);
closeDropdownMenu();
};
$input.attr('data-provide', 'typeahead');
$input.typeahead({
source: typeaheadValues,
minLength: 1,
items: 10,
updater: (value: string) => {
const result: any = {};
each($scope.menuItems, (menuItem) => {
each(menuItem.submenu, (submenuItem) => {
if (value === menuItem.text + ' ' + submenuItem.text) {
result.$subItem = submenuItem;
result.$item = menuItem;
}
});
});
if (result.$item) {
$scope.$apply(() => {
$scope.dropdownTypeaheadOnSelect(result);
});
}
$input.trigger('blur');
return '';
},
});
$button.click(() => {
$button.hide();
$input.show();
$input.focus();
});
$input.keyup(() => {
elem.toggleClass('open', $input.val() === '');
});
elem.mousedown((evt: Event) => {
evt.preventDefault();
timeoutId.blur = null;
});
$input.blur(() => {
timeoutId.blur = setTimeout(() => {
closeDropdownMenu();
}, 1);
});
$compile(elem.contents())($scope);
},
};
}
coreModule.directive('dropdownTypeahead', ['$compile', dropdownTypeahead]);
coreModule.directive('dropdownTypeahead2', ['$compile', dropdownTypeahead2]);

@ -1,61 +0,0 @@
import angular from 'angular';
import { isArray, isNull, isObject, isUndefined } from 'lodash';
import { dateTime } from '@grafana/data';
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
import coreModule from '../core_module';
coreModule.filter('stringSort', () => {
return (input: any) => {
return input.sort();
};
});
coreModule.filter('slice', () => {
return (arr: any[], start: any, end: any) => {
if (!isUndefined(arr)) {
return arr.slice(start, end);
}
return arr;
};
});
coreModule.filter('stringify', () => {
return (arr: any[]) => {
if (isObject(arr) && !isArray(arr)) {
return angular.toJson(arr);
} else {
return isNull(arr) ? null : arr.toString();
}
};
});
coreModule.filter('moment', () => {
return (date: string, mode: string) => {
switch (mode) {
case 'ago':
return dateTime(date).fromNow();
}
return dateTime(date).fromNow();
};
});
function interpolateTemplateVars(templateSrv: TemplateSrv = getTemplateSrv()) {
const filterFunc: any = (text: string, scope: any) => {
let scopedVars;
if (scope.ctrl) {
scopedVars = (scope.ctrl.panel || scope.ctrl.row).scopedVars;
} else {
scopedVars = scope.row.scopedVars;
}
return templateSrv.replaceWithText(text, scopedVars);
};
filterFunc.$stateful = true;
return filterFunc;
}
coreModule.filter('interpolateTemplateVars', interpolateTemplateVars);
export default {};

@ -1,29 +0,0 @@
import coreModule from './core_module';
coreModule.directive('giveFocus', () => {
return (scope: any, element: any, attrs: any) => {
element.click((e: any) => {
e.stopPropagation();
});
scope.$watch(
attrs.giveFocus,
(newValue: any) => {
if (!newValue) {
return;
}
setTimeout(() => {
element.focus();
const domEl: any = element[0];
if (domEl.setSelectionRange) {
const pos = element.val().length * 2;
domEl.setSelectionRange(pos, pos);
}
}, 200);
},
true
);
};
});
export default {};

@ -1,43 +0,0 @@
import './panel/all';
import './partials';
import './filters/filters';
import './services/alert_srv';
import './services/dynamic_directive_srv';
import './services/ng_react';
import './services/segment_srv';
import './services/popover_srv';
import './services/timer';
import './services/AngularLoader';
import '../angular/jquery_extended';
import './dropdown_typeahead';
import './autofill_event_fix';
import './metric_segment';
import './misc';
import './bsTooltip';
import './bsTypeahead';
import './ng_model_on_blur';
import './tags';
import './rebuild_on_change';
import './give_focus';
import './diff-view';
import './array_join';
import './angular_wrappers';
// components
import './components/query_part_editor';
import './components/form_dropdown/form_dropdown';
import './components/scroll';
import './components/jsontree';
import './components/switch';
import './components/info_popover';
import './components/spectrum_picker';
import './components/code_editor/code_editor';
import './components/sql_part/sql_part_editor';
import './components/HttpSettingsCtrl';
import './components/TlsAuthSettingsCtrl';
import './components/plugin_component';
import './GrafanaCtrl';
export { AngularApp } from './AngularApp';
export { coreModule } from './core_module';

@ -1,50 +0,0 @@
export function monkeyPatchInjectorWithPreAssignedBindings(injector: any) {
injector.oldInvoke = injector.invoke;
injector.invoke = (fn: any, self: any, locals: any, serviceName: any) => {
const parentScope = locals?.$scope?.$parent;
if (parentScope) {
// PanelCtrl
if (parentScope.panel) {
self.panel = parentScope.panel;
}
// Panels & dashboard SettingsCtrl
if (parentScope.dashboard) {
self.dashboard = parentScope.dashboard;
}
// Query editors
if (parentScope.ctrl?.target) {
self.panelCtrl = parentScope.ctrl;
self.datasource = parentScope.ctrl.datasource;
self.target = parentScope.ctrl.target;
}
// Data source ConfigCtrl
if (parentScope.ctrl?.datasourceMeta) {
self.meta = parentScope.ctrl.datasourceMeta;
self.current = parentScope.ctrl.current;
}
// Data source AnnotationsQueryCtrl
if (parentScope.ctrl?.currentAnnotation) {
self.annotation = parentScope.ctrl.currentAnnotation;
self.datasource = parentScope.ctrl.currentDatasource;
}
// App config ctrl
if (parentScope.isAppConfigCtrl) {
self.appEditCtrl = parentScope.ctrl;
self.appModel = parentScope.ctrl.model;
}
// App page ctrl
if (parentScope.$parent?.$parent?.ctrl?.appModel) {
self.appModel = parentScope.$parent?.$parent?.ctrl?.appModel;
}
}
return injector.oldInvoke(fn, self, locals, serviceName);
};
}

@ -1,52 +0,0 @@
import angular from 'angular';
import $ from 'jquery';
import { extend } from 'lodash';
const $win = $(window);
$.fn.place_tt = (() => {
const defaults = {
offset: 5,
};
return function (this: any, x: number, y: number, opts: any) {
opts = $.extend(true, {}, defaults, opts);
return this.each(() => {
const $tooltip = $(this);
let width, height;
$tooltip.addClass('grafana-tooltip');
$('#tooltip').remove();
$tooltip.appendTo(document.body);
if (opts.compile) {
angular
.element(document)
.injector()
.invoke([
'$compile',
'$rootScope',
($compile, $rootScope) => {
const tmpScope = $rootScope.$new(true);
extend(tmpScope, opts.scopeData);
$compile($tooltip)(tmpScope);
tmpScope.$digest();
tmpScope.$destroy();
},
]);
}
width = $tooltip.outerWidth(true)!;
height = $tooltip.outerHeight(true)!;
const left = x + opts.offset + width > $win.width()! ? x - opts.offset - width : x + opts.offset;
const top = y + opts.offset + height > $win.height()! ? y - opts.offset - height : y + opts.offset;
$tooltip.css('left', left > 0 ? left : 0);
$tooltip.css('top', top > 0 ? top : 0);
});
};
})();

@ -1,23 +0,0 @@
import { auto } from 'angular';
let injector: auto.IInjectorService | undefined;
/**
* Future poc to lazy load angular app, not yet used
*/
export async function getAngularInjector(): Promise<auto.IInjectorService> {
if (injector) {
return injector;
}
const { AngularApp } = await import(/* webpackChunkName: "AngularApp" */ './index');
if (injector) {
return injector;
}
const app = new AngularApp();
app.init();
injector = app.bootstrap();
return injector;
}

@ -1,92 +0,0 @@
import { deprecationWarning } from '@grafana/data';
import {
config,
setAngularLoader,
setLegacyAngularInjector,
getDataSourceSrv,
getBackendSrv,
getTemplateSrv,
} from '@grafana/runtime';
import { contextSrv } from 'app/core/core';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv';
import { getLinkSrv } from 'app/features/panel/panellinks/link_srv';
export async function loadAndInitAngularIfEnabled() {
if (config.angularSupportEnabled) {
const { AngularApp } = await import(/* webpackChunkName: "AngularApp" */ './index');
const app = new AngularApp();
app.init();
app.bootstrap();
} else {
// Register a dummy loader that does nothing
setAngularLoader({
load: (elem, scopeProps, template) => {
return {
destroy: () => {},
digest: () => {},
getScope: () => {
return {};
},
};
},
});
// Temporary path to allow access to services exposed directly by the angular injector
setLegacyAngularInjector({
get: (key: string) => {
switch (key) {
case 'backendSrv': {
deprecationWarning('getLegacyAngularInjector', 'backendSrv', 'use getBackendSrv() in @grafana/runtime');
return getBackendSrv();
}
case 'contextSrv': {
deprecationWarning('getLegacyAngularInjector', 'contextSrv');
return contextSrv;
}
case 'dashboardSrv': {
// we do not yet have a public interface for this
deprecationWarning('getLegacyAngularInjector', 'getDashboardSrv');
return getDashboardSrv();
}
case 'datasourceSrv': {
deprecationWarning(
'getLegacyAngularInjector',
'datasourceSrv',
'use getDataSourceSrv() in @grafana/runtime'
);
return getDataSourceSrv();
}
case 'linkSrv': {
// we do not yet have a public interface for this
deprecationWarning('getLegacyAngularInjector', 'linkSrv');
return getLinkSrv();
}
case 'validationSrv': {
// we do not yet have a public interface for this
deprecationWarning('getLegacyAngularInjector', 'validationSrv');
return validationSrv;
}
case 'timeSrv': {
// we do not yet have a public interface for this
deprecationWarning('getLegacyAngularInjector', 'timeSrv');
return getTimeSrv();
}
case 'templateSrv': {
deprecationWarning('getLegacyAngularInjector', 'templateSrv', 'use getTemplateSrv() in @grafana/runtime');
return getTemplateSrv();
}
}
throw 'Angular is disabled. Unable to expose: ' + key;
},
} as angular.auto.IInjectorService);
}
}

@ -1,266 +0,0 @@
import $ from 'jquery';
import { debounce, find, indexOf, map, escape, unescape } from 'lodash';
import { TemplateSrv } from 'app/features/templating/template_srv';
import coreModule from './core_module';
export function metricSegment($compile: any, $sce: any, templateSrv: TemplateSrv) {
const inputTemplate =
'<input type="text" data-provide="typeahead" ' +
' class="gf-form-input input-medium"' +
' spellcheck="false" style="display:none"></input>';
const linkTemplate =
'<a class="gf-form-label" ng-class="segment.cssClass" ' +
'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
const selectTemplate =
'<a class="gf-form-input gf-form-input--dropdown" ng-class="segment.cssClass" ' +
'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
return {
scope: {
segment: '=',
getOptions: '&',
onChange: '&',
debounce: '@',
},
link: ($scope: any, elem: any) => {
const $input = $(inputTemplate);
const segment = $scope.segment;
const $button = $(segment.selectMode ? selectTemplate : linkTemplate);
let options = null;
let cancelBlur: any = null;
let linkMode = true;
const debounceLookup = $scope.debounce;
$input.appendTo(elem);
$button.appendTo(elem);
$scope.updateVariableValue = (value: string) => {
if (value === '' || segment.value === value) {
return;
}
$scope.$apply(() => {
const selected: any = find($scope.altSegments, { value: value });
if (selected) {
segment.value = selected.value;
segment.html = selected.html || $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(selected.value));
segment.fake = false;
segment.expandable = selected.expandable;
if (selected.type) {
segment.type = selected.type;
}
} else if (segment.custom !== 'false') {
segment.value = value;
segment.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(value));
segment.expandable = true;
segment.fake = false;
}
$scope.onChange();
});
};
$scope.switchToLink = (fromClick: boolean) => {
if (linkMode && !fromClick) {
return;
}
clearTimeout(cancelBlur);
cancelBlur = null;
linkMode = true;
$input.hide();
$button.show();
$scope.updateVariableValue($input.val());
};
$scope.inputBlur = () => {
// happens long before the click event on the typeahead options
// need to have long delay because the blur
cancelBlur = setTimeout($scope.switchToLink, 200);
};
$scope.source = (query: string, callback: any) => {
$scope.$apply(() => {
$scope.getOptions({ $query: query }).then((altSegments: any) => {
$scope.altSegments = altSegments;
options = map($scope.altSegments, (alt) => {
return escape(alt.value);
});
// add custom values
if (segment.custom !== 'false') {
if (!segment.fake && indexOf(options, segment.value) === -1) {
options.unshift(escape(segment.value));
}
}
callback(options);
});
});
};
$scope.updater = (value: string) => {
value = unescape(value);
if (value === segment.value) {
clearTimeout(cancelBlur);
$input.focus();
return value;
}
$input.val(value);
$scope.switchToLink(true);
return value;
};
$scope.matcher = function (item: string) {
if (linkMode) {
return false;
}
let str = this.query;
if (str[0] === '/') {
str = str.substring(1);
}
if (str[str.length - 1] === '/') {
str = str.substring(0, str.length - 1);
}
try {
return item.toLowerCase().match(str.toLowerCase());
} catch (e) {
return false;
}
};
$input.attr('data-provide', 'typeahead');
$input.typeahead({
source: $scope.source,
minLength: 0,
items: 10000,
updater: $scope.updater,
matcher: $scope.matcher,
});
const typeahead = $input.data('typeahead');
typeahead.lookup = function () {
this.query = this.$element.val() || '';
const items = this.source(this.query, $.proxy(this.process, this));
return items ? this.process(items) : items;
};
if (debounceLookup) {
typeahead.lookup = debounce(typeahead.lookup, 500, { leading: true });
}
$button.keydown((evt) => {
// trigger typeahead on down arrow or enter key
if (evt.keyCode === 40 || evt.keyCode === 13) {
$button.click();
}
});
$button.click(() => {
options = null;
$input.css('width', Math.max($button.width()!, 80) + 16 + 'px');
$button.hide();
$input.show();
$input.focus();
linkMode = false;
const typeahead = $input.data('typeahead');
if (typeahead) {
$input.val('');
typeahead.lookup();
}
});
$input.blur($scope.inputBlur);
$compile(elem.contents())($scope);
},
};
}
export function metricSegmentModel(uiSegmentSrv: any) {
return {
template:
'<metric-segment segment="segment" get-options="getOptionsInternal()" on-change="onSegmentChange()"></metric-segment>',
restrict: 'E',
scope: {
property: '=',
options: '=',
getOptions: '&',
onChange: '&',
},
link: {
pre: function postLink($scope: any, elem: any, attrs: any) {
let cachedOptions: any;
$scope.valueToSegment = (value: any) => {
const option: any = find($scope.options, { value: value });
const segment = {
cssClass: attrs.cssClass,
custom: attrs.custom,
value: option ? option.text : value,
selectMode: attrs.selectMode,
};
return uiSegmentSrv.newSegment(segment);
};
$scope.getOptionsInternal = () => {
if ($scope.options) {
cachedOptions = $scope.options;
return Promise.resolve(
map($scope.options, (option) => {
return { value: option.text };
})
);
} else {
return $scope.getOptions().then((options: any) => {
cachedOptions = options;
return map(options, (option) => {
if (option.html) {
return option;
}
return { value: option.text };
});
});
}
};
$scope.onSegmentChange = () => {
if (cachedOptions) {
const option: any = find(cachedOptions, { text: $scope.segment.value });
if (option && option.value !== $scope.property) {
$scope.property = option.value;
} else if (attrs.custom !== 'false') {
$scope.property = $scope.segment.value;
}
} else {
$scope.property = $scope.segment.value;
}
// needs to call this after digest so
// property is synced with outerscope
$scope.$$postDigest(() => {
$scope.$apply(() => {
$scope.onChange();
});
});
};
$scope.segment = $scope.valueToSegment($scope.property);
},
},
};
}
coreModule.directive('metricSegment', ['$compile', '$sce', 'templateSrv', metricSegment]);
coreModule.directive('metricSegmentModel', ['uiSegmentSrv', metricSegmentModel]);

@ -1,189 +0,0 @@
import angular from 'angular';
import coreModule from './core_module';
coreModule.directive('tip', ['$compile', tip]);
function tip($compile: any) {
return {
restrict: 'E',
link: (scope: any, elem: any, attrs: any) => {
let _t =
'<i class="grafana-tip fa fa-' +
(attrs.icon || 'question-circle') +
'" bs-tooltip="\'' +
// here we double-html-encode any special characters in the source string
// this is needed so that the final html contains the encoded entities as they
// will be decoded when _t is parsed by angular
elem.text().replace(/[\'\"\\{}<>&]/g, (m: string) => '&amp;#' + m.charCodeAt(0) + ';') +
'\'"></i>';
elem.replaceWith($compile(angular.element(_t))(scope));
},
};
}
coreModule.directive('compile', ['$compile', compile]);
function compile($compile: any) {
return {
restrict: 'A',
link: (scope: any, element: any, attrs: any) => {
scope.$watch(
(scope: any) => {
return scope.$eval(attrs.compile);
},
(value: any) => {
element.html(value);
$compile(element.contents())(scope);
}
);
},
};
}
coreModule.directive('watchChange', watchChange);
function watchChange() {
return {
scope: { onchange: '&watchChange' },
link: (scope: any, element: any) => {
element.on('input', () => {
scope.$apply(() => {
scope.onchange({ inputValue: element.val() });
});
});
},
};
}
coreModule.directive('editorOptBool', ['$compile', editorOptBool]);
function editorOptBool($compile: any) {
return {
restrict: 'E',
link: (scope: any, elem: any, attrs: any) => {
const ngchange = attrs.change ? ' ng-change="' + attrs.change + '"' : '';
const tip = attrs.tip ? ' <tip>' + attrs.tip + '</tip>' : '';
const showIf = attrs.showIf ? ' ng-show="' + attrs.showIf + '" ' : '';
const template =
'<div class="editor-option gf-form-checkbox text-center"' +
showIf +
'>' +
' <label for="' +
attrs.model +
'" class="small">' +
attrs.text +
tip +
'</label>' +
'<input class="cr1" id="' +
attrs.model +
'" type="checkbox" ' +
' ng-model="' +
attrs.model +
'"' +
ngchange +
' ng-checked="' +
attrs.model +
'"></input>' +
' <label for="' +
attrs.model +
'" class="cr1"></label>';
elem.replaceWith($compile(angular.element(template))(scope));
},
};
}
coreModule.directive('editorCheckbox', ['$compile, $interpolate', editorCheckbox]);
function editorCheckbox($compile: any, $interpolate: any) {
return {
restrict: 'E',
link: (scope: any, elem: any, attrs: any) => {
const text = $interpolate(attrs.text)(scope);
const model = $interpolate(attrs.model)(scope);
const ngchange = attrs.change ? ' ng-change="' + attrs.change + '"' : '';
const tip = attrs.tip ? ' <tip>' + attrs.tip + '</tip>' : '';
const label = '<label for="' + scope.$id + model + '" class="checkbox-label">' + text + tip + '</label>';
let template =
'<input class="cr1" id="' +
scope.$id +
model +
'" type="checkbox" ' +
' ng-model="' +
model +
'"' +
ngchange +
' ng-checked="' +
model +
'"></input>' +
' <label for="' +
scope.$id +
model +
'" class="cr1"></label>';
template = template + label;
elem.addClass('gf-form-checkbox');
elem.html($compile(angular.element(template))(scope));
},
};
}
coreModule.directive('gfDropdown', ['$parse', '$compile', '$timeout', gfDropdown]);
function gfDropdown($parse: any, $compile: any, $timeout: any) {
function buildTemplate(items: any, placement?: any) {
const upclass = placement === 'top' ? 'dropup' : '';
const ul = ['<ul class="dropdown-menu ' + upclass + '" role="menu" aria-labelledby="drop1">', '</ul>'];
for (let index = 0; index < items.length; index++) {
const item = items[index];
if (item.divider) {
ul.splice(index + 1, 0, '<li class="divider"></li>');
continue;
}
let li =
'<li' +
(item.submenu && item.submenu.length ? ' class="dropdown-submenu"' : '') +
'>' +
'<a tabindex="-1" ng-href="' +
(item.href || '') +
'"' +
(item.click ? ' ng-click="' + item.click + '"' : '') +
(item.target ? ' target="' + item.target + '"' : '') +
(item.method ? ' data-method="' + item.method + '"' : '') +
'>' +
(item.text || '') +
'</a>';
if (item.submenu && item.submenu.length) {
li += buildTemplate(item.submenu).join('\n');
}
li += '</li>';
ul.splice(index + 1, 0, li);
}
return ul;
}
return {
restrict: 'EA',
scope: true,
link: function postLink(scope: any, iElement: any, iAttrs: any) {
const getter = $parse(iAttrs.gfDropdown),
items = getter(scope);
$timeout(() => {
const placement = iElement.data('placement');
const dropdown = angular.element(buildTemplate(items, placement).join(''));
dropdown.insertAfter(iElement);
$compile(iElement.next('ul.dropdown-menu'))(scope);
});
iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown');
},
};
}

@ -1,60 +0,0 @@
import { rangeUtil } from '@grafana/data';
import coreModule from './core_module';
function ngModelOnBlur() {
return {
restrict: 'A',
priority: 1,
require: 'ngModel',
link: (scope: any, elm: any, attr: any, ngModelCtrl: any) => {
if (attr.type === 'radio' || attr.type === 'checkbox') {
return;
}
elm.off('input keydown change');
elm.bind('blur', () => {
scope.$apply(() => {
ngModelCtrl.$setViewValue(elm.val());
});
});
},
};
}
function emptyToNull() {
return {
restrict: 'A',
require: 'ngModel',
link: (scope: any, elm: any, attrs: any, ctrl: any) => {
ctrl.$parsers.push((viewValue: any) => {
if (viewValue === '') {
return null;
}
return viewValue;
});
},
};
}
function validTimeSpan() {
return {
require: 'ngModel',
link: (scope: any, elm: any, attrs: any, ctrl: any) => {
ctrl.$validators.integer = (modelValue: any, viewValue: any) => {
if (ctrl.$isEmpty(modelValue)) {
return true;
}
if (viewValue.indexOf('$') === 0 || viewValue.indexOf('+$') === 0) {
return true; // allow template variable
}
const info = rangeUtil.describeTextRange(viewValue);
return info.invalid !== true;
};
},
};
}
coreModule.directive('ngModelOnblur', ngModelOnBlur);
coreModule.directive('emptyToNull', emptyToNull);
coreModule.directive('validTimeSpan', validTimeSpan);

@ -1,127 +0,0 @@
import { ComponentType, useEffect, useRef } from 'react';
import { Observable, ReplaySubject } from 'rxjs';
import { EventBusSrv, PanelData, PanelPlugin, PanelProps, FieldConfigSource } from '@grafana/data';
import { AngularComponent, getAngularLoader, RefreshEvent } from '@grafana/runtime';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { DashboardModelCompatibilityWrapper } from 'app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper';
import { GetDataOptions } from 'app/features/query/state/PanelQueryRunner';
import { RenderEvent } from 'app/types/events';
interface AngularScopeProps {
panel: PanelModelCompatibilityWrapper;
dashboard: DashboardModelCompatibilityWrapper;
queryRunner: FakeQueryRunner;
size: {
height: number;
width: number;
};
}
export function getAngularPanelReactWrapper(plugin: PanelPlugin): ComponentType<PanelProps> {
return function AngularWrapper(props: PanelProps) {
const divRef = useRef<HTMLDivElement>(null);
const angularState = useRef<AngularScopeProps | undefined>();
const angularComponent = useRef<AngularComponent | undefined>();
useEffect(() => {
if (!divRef.current) {
return;
}
const loader = getAngularLoader();
const template = '<plugin-component type="panel" class="panel-height-helper"></plugin-component>';
const queryRunner = new FakeQueryRunner();
const fakePanel = new PanelModelCompatibilityWrapper(plugin, props, queryRunner);
angularState.current = {
// @ts-ignore
panel: fakePanel,
// @ts-ignore
dashboard: getDashboardSrv().getCurrent(),
size: { width: props.width, height: props.height },
queryRunner: queryRunner,
};
angularComponent.current = loader.load(divRef.current, angularState.current, template);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Re-render angular panel when dimensions change
useEffect(() => {
if (!angularComponent.current) {
return;
}
angularState.current!.size.height = props.height;
angularState.current!.size.width = props.width;
angularState.current!.panel.events.publish(new RenderEvent());
}, [props.width, props.height]);
// Pass new data to angular panel
useEffect(() => {
if (!angularState.current?.panel) {
return;
}
angularState.current.queryRunner.forwardNewData(props.data);
}, [props.data]);
return <div ref={divRef} className="panel-height-helper" />;
};
}
class PanelModelCompatibilityWrapper {
id: number;
type: string;
title: string;
plugin: PanelPlugin;
events: EventBusSrv;
queryRunner: FakeQueryRunner;
fieldConfig: FieldConfigSource;
options: Record<string, unknown>;
constructor(plugin: PanelPlugin, props: PanelProps, queryRunner: FakeQueryRunner) {
// Assign legacy "root" level options
if (props.options.angularOptions) {
Object.assign(this, props.options.angularOptions);
}
this.id = props.id;
this.type = plugin.meta.id;
this.title = props.title;
this.fieldConfig = props.fieldConfig;
this.options = props.options;
this.plugin = plugin;
this.events = new EventBusSrv();
this.queryRunner = queryRunner;
}
refresh() {
this.events.publish(new RefreshEvent());
}
render() {
this.events.publish(new RenderEvent());
}
getQueryRunner() {
return this.queryRunner;
}
}
class FakeQueryRunner {
private subject = new ReplaySubject<PanelData>(1);
getData(options: GetDataOptions): Observable<PanelData> {
return this.subject;
}
forwardNewData(data: PanelData) {
this.subject.next(data);
}
run() {}
}

@ -1,4 +0,0 @@
import './panel_directive';
import './query_ctrl';
import './panel_editor_tab';
import './query_editor_row';

@ -1,244 +0,0 @@
import { isArray } from 'lodash';
import { Unsubscribable } from 'rxjs';
import {
DataFrame,
DataQueryResponse,
DataSourceApi,
LegacyResponseData,
LoadingState,
PanelData,
PanelEvents,
TimeRange,
toDataFrameDTO,
toLegacyResponseData,
} from '@grafana/data';
import { PanelCtrl } from 'app/angular/panel/panel_ctrl';
import { ContextSrv } from 'app/core/services/context_srv';
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
import { PanelQueryRunner } from '../../features/query/state/PanelQueryRunner';
class MetricsPanelCtrl extends PanelCtrl {
declare datasource: DataSourceApi;
declare range: TimeRange;
contextSrv: ContextSrv;
datasourceSrv: any;
timeSrv: any;
templateSrv: any;
interval: any;
intervalMs: any;
resolution: any;
timeInfo?: string;
skipDataOnInit = false;
dataList: LegacyResponseData[] = [];
querySubscription?: Unsubscribable | null;
useDataFrames = false;
panelData?: PanelData;
constructor($scope: any, $injector: any) {
super($scope, $injector);
this.contextSrv = $injector.get('contextSrv');
this.datasourceSrv = $injector.get('datasourceSrv');
this.timeSrv = $injector.get('timeSrv');
this.templateSrv = $injector.get('templateSrv');
this.panel.datasource = this.panel.datasource || null;
this.events.on(PanelEvents.refresh, this.onMetricsPanelRefresh.bind(this));
this.events.on(PanelEvents.panelTeardown, this.onPanelTearDown.bind(this));
this.events.on(PanelEvents.componentDidMount, this.onMetricsPanelMounted.bind(this));
}
private onMetricsPanelMounted() {
const queryRunner = this.panel.getQueryRunner() as PanelQueryRunner;
this.querySubscription = queryRunner
.getData({ withTransforms: true, withFieldConfig: true })
.subscribe(this.panelDataObserver);
}
private onPanelTearDown() {
if (this.querySubscription) {
this.querySubscription.unsubscribe();
this.querySubscription = null;
}
}
private onMetricsPanelRefresh() {
// ignore fetching data if another panel is in fullscreen
if (this.otherPanelInFullscreenMode()) {
return;
}
// if we have snapshot data use that
if (this.panel.snapshotData) {
this.updateTimeRange();
let data = this.panel.snapshotData;
// backward compatibility
if (!isArray(data)) {
data = data.data;
}
this.panelData = {
state: LoadingState.Done,
series: data,
timeRange: this.range,
};
// Defer panel rendering till the next digest cycle.
// For some reason snapshot panels don't init at this time, so this helps to avoid rendering issues.
return this.$timeout(() => {
this.events.emit(PanelEvents.dataSnapshotLoad, data);
});
}
// clear loading/error state
delete this.error;
this.loading = true;
// load datasource service
return this.datasourceSrv
.get(this.panel.datasource, this.panel.scopedVars)
.then(this.issueQueries.bind(this))
.catch((err: any) => {
this.processDataError(err);
});
}
processDataError(err: any) {
// if canceled keep loading set to true
if (err.cancelled) {
console.log('Panel request cancelled', err);
return;
}
this.error = err.message || 'Request Error';
if (err.data) {
if (err.data.message) {
this.error = err.data.message;
} else if (err.data.error) {
this.error = err.data.error;
}
}
this.angularDirtyCheck();
}
angularDirtyCheck() {
if (!this.$scope.$root.$$phase) {
this.$scope.$digest();
}
}
// Updates the response with information from the stream
panelDataObserver = {
next: (data: PanelData) => {
this.panelData = data;
if (data.state === LoadingState.Error) {
this.loading = false;
this.processDataError(data.error);
}
// Ignore data in loading state
if (data.state === LoadingState.Loading) {
this.loading = true;
this.angularDirtyCheck();
return;
}
if (data.request) {
const { timeInfo } = data.request;
if (timeInfo) {
this.timeInfo = timeInfo;
}
}
if (data.timeRange) {
this.range = data.timeRange;
}
if (this.useDataFrames) {
this.handleDataFrames(data.series);
} else {
// Make the results look as if they came directly from a <6.2 datasource request
const legacy = data.series.map((v) => toLegacyResponseData(v));
this.handleQueryResult({ data: legacy });
}
this.angularDirtyCheck();
},
};
updateTimeRange(datasource?: DataSourceApi) {
this.datasource = datasource || this.datasource;
this.range = this.timeSrv.timeRange();
const newTimeData = applyPanelTimeOverrides(this.panel, this.range);
this.timeInfo = newTimeData.timeInfo;
this.range = newTimeData.timeRange;
}
issueQueries(datasource: DataSourceApi) {
this.updateTimeRange(datasource);
this.datasource = datasource;
const panel = this.panel as PanelModel;
const queryRunner = panel.getQueryRunner();
return queryRunner.run({
datasource: panel.datasource,
queries: panel.targets,
panelId: panel.id,
dashboardUID: this.dashboard.uid,
timezone: this.dashboard.getTimezone(),
timeInfo: this.timeInfo,
timeRange: this.range,
maxDataPoints: panel.maxDataPoints || this.width,
minInterval: panel.interval,
scopedVars: panel.scopedVars,
cacheTimeout: panel.cacheTimeout,
queryCachingTTL: panel.queryCachingTTL,
transformations: panel.transformations,
});
}
handleDataFrames(data: DataFrame[]) {
this.loading = false;
if (this.dashboard && this.dashboard.snapshot) {
this.panel.snapshotData = data.map((frame) => toDataFrameDTO(frame));
}
try {
this.events.emit(PanelEvents.dataFramesReceived, data);
} catch (err) {
this.processDataError(err);
}
}
handleQueryResult(result: DataQueryResponse) {
this.loading = false;
if (this.dashboard.snapshot) {
this.panel.snapshotData = result.data;
}
if (!result || !result.data) {
console.log('Data source query result invalid, missing data field:', result);
result = { data: [] };
}
try {
this.events.emit(PanelEvents.dataReceived, result.data);
} catch (err) {
this.processDataError(err);
}
}
}
export { MetricsPanelCtrl };

@ -1,119 +0,0 @@
import { auto } from 'angular';
import { isString } from 'lodash';
import {
AppEvent,
PanelEvents,
PanelPluginMeta,
AngularPanelMenuItem,
EventBusExtended,
EventBusSrv,
} from '@grafana/data';
import { AngularLocationWrapper } from 'app/angular/AngularLocationWrapper';
import config from 'app/core/config';
import { profiler } from 'app/core/core';
import { DashboardModel } from '../../features/dashboard/state/DashboardModel';
export class PanelCtrl {
panel: any;
error: any;
declare dashboard: DashboardModel;
pluginName = '';
pluginId = '';
editorTabs: any;
$scope: any;
$injector: auto.IInjectorService;
$timeout: any;
editModeInitiated = false;
declare height: number;
declare width: number;
containerHeight: any;
events: EventBusExtended;
loading = false;
timing: any;
$location: AngularLocationWrapper;
constructor($scope: any, $injector: auto.IInjectorService) {
this.panel = this.panel ?? $scope.$parent.panel;
this.dashboard = this.dashboard ?? $scope.$parent.dashboard;
this.$injector = $injector;
this.$scope = $scope;
this.$timeout = $injector.get('$timeout');
this.editorTabs = [];
this.$location = new AngularLocationWrapper();
this.events = new EventBusSrv();
this.timing = {}; // not used but here to not break plugins
const plugin = config.panels[this.panel.type];
if (plugin) {
this.pluginId = plugin.id;
this.pluginName = plugin.name;
}
$scope.$on(PanelEvents.componentDidMount.name, () => this.panelDidMount());
}
panelDidMount() {
this.events.emit(PanelEvents.componentDidMount);
this.events.emit(PanelEvents.initialized);
this.dashboard.panelInitialized(this.panel);
}
renderingCompleted() {
profiler.renderingCompleted();
}
refresh() {
this.panel.refresh();
}
publishAppEvent<T>(event: AppEvent<T>, payload?: T) {
this.$scope.$root.appEvent(event, payload);
}
initEditMode() {
if (!this.editModeInitiated) {
this.editModeInitiated = true;
this.events.emit(PanelEvents.editModeInitialized);
}
}
addEditorTab(title: string, directiveFn: any, index?: number, icon?: any) {
const editorTab = { title, directiveFn, icon };
if (isString(directiveFn)) {
editorTab.directiveFn = () => {
return { templateUrl: directiveFn };
};
}
if (index) {
this.editorTabs.splice(index, 0, editorTab);
} else {
this.editorTabs.push(editorTab);
}
}
getExtendedMenu() {
const menu: AngularPanelMenuItem[] = [];
this.events.emit(PanelEvents.initPanelActions, menu);
return menu;
}
// Override in sub-class to add items before extended menu
async getAdditionalMenuItems(): Promise<any[]> {
return [];
}
otherPanelInFullscreenMode() {
return this.dashboard.otherPanelInFullscreen(this.panel);
}
render(payload?: any) {
this.events.emit(PanelEvents.render, payload);
}
// overriden from react
onPluginTypeChange = (plugin: PanelPluginMeta) => {};
}

@ -1,127 +0,0 @@
// @ts-ignore
import baron from 'baron';
import { Subscription } from 'rxjs';
import { PanelEvents } from '@grafana/data';
import { RefreshEvent } from '@grafana/runtime';
import { coreModule } from 'app/angular/core_module';
import { PanelDirectiveReadyEvent, RenderEvent } from 'app/types/events';
import { PanelModel } from '../../features/dashboard/state/PanelModel';
import { PanelCtrl } from './panel_ctrl';
const panelTemplate = `
<ng-transclude class="panel-height-helper"></ng-transclude>
`;
coreModule.directive('grafanaPanel', [
'$timeout',
($timeout) => {
return {
restrict: 'E',
template: panelTemplate,
transclude: true,
scope: { ctrl: '=' },
link: (scope: any, elem) => {
const ctrl: PanelCtrl = scope.ctrl;
const panel: PanelModel = scope.ctrl.panel;
const subs = new Subscription();
let panelScrollbar: any;
function resizeScrollableContent() {
if (panelScrollbar) {
panelScrollbar.update();
}
}
ctrl.events.on(PanelEvents.componentDidMount, () => {
if ((ctrl as any).__proto__.constructor.scrollable) {
const scrollRootClass = 'baron baron__root baron__clipper panel-content--scrollable';
const scrollerClass = 'baron__scroller';
const scrollBarHTML = `
<div class="baron__track">
<div class="baron__bar"></div>
</div>
`;
const scrollRoot = elem;
const scroller = elem.find(':first').find(':first');
scrollRoot.addClass(scrollRootClass);
$(scrollBarHTML).appendTo(scrollRoot);
scroller.addClass(scrollerClass);
panelScrollbar = baron({
root: scrollRoot[0],
scroller: scroller[0],
bar: '.baron__bar',
barOnCls: '_scrollbar',
scrollingCls: '_scrolling',
});
panelScrollbar.scroll();
}
});
function updateDimensionsFromParentScope() {
ctrl.height = scope.$parent.$parent.size.height;
ctrl.width = scope.$parent.$parent.size.width;
}
updateDimensionsFromParentScope();
// Pass PanelModel events down to angular controller event emitter
subs.add(
panel.events.subscribe(RefreshEvent, () => {
updateDimensionsFromParentScope();
ctrl.events.emit('refresh');
})
);
subs.add(
panel.events.subscribe(RenderEvent, (event) => {
// this event originated from angular so no need to bubble it back
if (event.payload?.fromAngular) {
return;
}
updateDimensionsFromParentScope();
$timeout(() => {
resizeScrollableContent();
ctrl.events.emit('render');
});
})
);
subs.add(
ctrl.events.subscribe(RenderEvent, (event) => {
// this event originated from angular so bubble it to react so the PanelChromeAngular can update the panel header alert state
if (event.payload) {
event.payload.fromAngular = true;
panel.events.publish(event);
}
})
);
scope.$on('$destroy', () => {
elem.off();
// Remove PanelModel.event subs
subs.unsubscribe();
// Remove Angular controller event subs
ctrl.events.emit(PanelEvents.panelTeardown);
ctrl.events.removeAllListeners();
if (panelScrollbar) {
panelScrollbar.dispose();
}
});
panel.events.publish(PanelDirectiveReadyEvent);
},
};
},
]);

@ -1,41 +0,0 @@
import angular from 'angular';
const directiveModule = angular.module('grafana.directives');
const directiveCache: any = {};
directiveModule.directive('panelEditorTab', ['dynamicDirectiveSrv', panelEditorTab]);
function panelEditorTab(dynamicDirectiveSrv: any) {
return dynamicDirectiveSrv.create({
scope: {
ctrl: '=',
editorTab: '=',
},
directive: (scope: any) => {
const pluginId = scope.ctrl.pluginId;
const tabName = scope.editorTab.title
.toLowerCase()
.replace(' ', '-')
.replace('&', '')
.replace(' ', '')
.replace(' ', '-');
if (directiveCache[pluginId]) {
if (directiveCache[pluginId][tabName]) {
return directiveCache[pluginId][tabName];
}
} else {
directiveCache[pluginId] = [];
}
const result = {
fn: () => scope.editorTab.directiveFn(),
name: `panel-editor-tab-${pluginId}${tabName}`,
};
directiveCache[pluginId][tabName] = result;
return result;
},
});
}

@ -1,2 +0,0 @@
<div ng-transclude class="gf-form-query-content"></div>

@ -1,27 +0,0 @@
import { auto } from 'angular';
import { indexOf } from 'lodash';
export class QueryCtrl<T = any> {
target!: T;
datasource!: any;
panelCtrl!: any;
panel: any;
hasRawMode!: boolean;
error?: string | null;
isLastQuery: boolean;
constructor(
public $scope: any,
public $injector: auto.IInjectorService
) {
this.panelCtrl = this.panelCtrl ?? $scope.ctrl.panelCtrl;
this.target = this.target ?? $scope.ctrl.target;
this.datasource = this.datasource ?? $scope.ctrl.datasource;
this.panel = this.panelCtrl?.panel ?? $scope.ctrl.panelCtrl.panel;
this.isLastQuery = indexOf(this.panel.targets, this.target) === this.panel.targets.length - 1;
}
refresh() {
this.panelCtrl.refresh();
}
}

@ -1,43 +0,0 @@
import { coreModule } from 'app/angular/core_module';
export class QueryRowCtrl {
target: any;
queryCtrl: any;
panelCtrl: any;
panel: any;
hasTextEditMode = false;
$onInit() {
this.panelCtrl = this.queryCtrl.panelCtrl;
this.target = this.queryCtrl.target;
this.panel = this.panelCtrl.panel;
if (this.hasTextEditMode && this.queryCtrl.toggleEditorMode) {
// expose this function to react parent component
this.panelCtrl.toggleEditorMode = this.queryCtrl.toggleEditorMode.bind(this.queryCtrl);
}
if (this.queryCtrl.getCollapsedText) {
// expose this function to react parent component
this.panelCtrl.getCollapsedText = this.queryCtrl.getCollapsedText.bind(this.queryCtrl);
}
}
}
coreModule.directive('queryEditorRow', queryEditorRowDirective);
function queryEditorRowDirective() {
return {
restrict: 'E',
controller: QueryRowCtrl,
bindToController: true,
controllerAs: 'ctrl',
templateUrl: 'public/app/angular/panel/partials/query_editor_row.html',
transclude: true,
scope: {
queryCtrl: '=',
canCollapse: '=',
hasTextEditMode: '=',
},
};
}

@ -1,57 +0,0 @@
jest.mock('app/core/core', () => ({}));
jest.mock('app/core/config', () => {
return {
...jest.requireActual('app/core/config'),
panels: {
test: {
id: 'test',
name: 'test',
},
},
};
});
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
import { MetricsPanelCtrl } from '../metrics_panel_ctrl';
describe('MetricsPanelCtrl', () => {
describe('can setup', () => {
it('should return controller', async () => {
const ctrl = setupController({ hasAccessToExplore: true });
expect((await ctrl.getAdditionalMenuItems()).length).toBe(0);
});
});
});
function setupController({ hasAccessToExplore } = { hasAccessToExplore: false }) {
const injectorStub = {
get: (type: any) => {
switch (type) {
case 'contextSrv': {
return { hasAccessToExplore: () => hasAccessToExplore };
}
case 'timeSrv': {
return { timeRangeForUrl: () => {} };
}
default: {
return jest.fn();
}
}
},
};
const scope: any = {
panel: { events: [] },
appEvent: jest.fn(),
onAppEvent: jest.fn(),
$on: jest.fn(),
colors: [],
$parent: {
panel: new PanelModel({ type: 'test' }),
dashboard: {},
},
};
return new MetricsPanelCtrl(scope, injectorStub);
}

@ -1,4 +0,0 @@
let templates = (require as any).context('../', true, /\.html$/);
templates.keys().forEach((key: string) => {
templates(key);
});

@ -1,8 +0,0 @@
<datasource-http-settings-next
on-change="onChange"
dataSourceConfig="current"
showAccessOptions="showAccessOption"
defaultUrl="suggestUrl"
showForwardOAuthIdentityOption="showForwardOAuthIdentityOption"
secureSocksDSProxyEnabled="secureSocksDSProxyEnabled"
/>

@ -1,81 +0,0 @@
<div class="gf-form-group">
<div class="gf-form">
<h6>TLS/SSL Auth Details</h6>
<info-popover mode="header">TLS/SSL certificates are encrypted and stored in the Grafana database.</info-popover>
</div>
<div ng-if="current.jsonData.tlsAuthWithCACert">
<div class="gf-form-inline">
<div class="gf-form gf-form--v-stretch"><label class="gf-form-label width-7">CA Cert</label></div>
<div class="gf-form gf-form--grow" ng-if="!current.secureJsonFields.tlsCACert">
<textarea
rows="7"
class="gf-form-input gf-form-textarea"
ng-model="current.secureJsonData.tlsCACert"
placeholder="Begins with -----BEGIN CERTIFICATE-----"
></textarea>
</div>
<div class="gf-form" ng-if="current.secureJsonFields.tlsCACert">
<input type="text" class="gf-form-input max-width-12" disabled="disabled" value="configured" />
<button
type="reset"
aria-label="Reset CA Cert"
class="btn btn-secondary gf-form-btn"
ng-click="current.secureJsonFields.tlsCACert = false"
>
reset
</button>
</div>
</div>
</div>
<div ng-if="current.jsonData.tlsAuth">
<div class="gf-form-inline">
<div class="gf-form gf-form--v-stretch"><label class="gf-form-label width-7">Client Cert</label></div>
<div class="gf-form gf-form--grow" ng-if="!current.secureJsonFields.tlsClientCert">
<textarea
rows="7"
class="gf-form-input gf-form-textarea"
ng-model="current.secureJsonData.tlsClientCert"
placeholder="Begins with -----BEGIN CERTIFICATE-----"
required
></textarea>
</div>
<div class="gf-form" ng-if="current.secureJsonFields.tlsClientCert">
<input type="text" class="gf-form-input max-width-12" disabled="disabled" value="configured" />
<button
class="btn btn-secondary gf-form-btn"
aria-label="Reset Client Cert"
type="reset"
ng-click="current.secureJsonFields.tlsClientCert = false"
>
reset
</button>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form gf-form--v-stretch"><label class="gf-form-label width-7">Client Key</label></div>
<div class="gf-form gf-form--grow" ng-if="!current.secureJsonFields.tlsClientKey">
<textarea
rows="7"
class="gf-form-input gf-form-textarea"
ng-model="current.secureJsonData.tlsClientKey"
placeholder="Begins with -----BEGIN RSA PRIVATE KEY-----"
required
></textarea>
</div>
<div class="gf-form" ng-if="current.secureJsonFields.tlsClientKey">
<input type="text" class="gf-form-input max-width-12" disabled="disabled" value="configured" />
<button
class="btn btn-secondary gf-form-btn"
type="reset"
aria-label="Reset Client Key"
ng-click="current.secureJsonFields.tlsClientKey = false"
>
reset
</button>
</div>
</div>
</div>
</div>

@ -1,28 +0,0 @@
import { IScope } from 'angular';
import { promiseToDigest } from './promiseToDigest';
describe('promiseToDigest', () => {
describe('when called with a promise that resolves', () => {
it('then evalAsync should be called on $scope', async () => {
const $scope = { $evalAsync: jest.fn() } as jest.MockedObject<IScope>;
await promiseToDigest($scope)(Promise.resolve(123));
expect($scope.$evalAsync).toHaveBeenCalledTimes(1);
});
});
describe('when called with a promise that rejects', () => {
it('then evalAsync should be called on $scope', async () => {
const $scope = { $evalAsync: jest.fn() } as jest.MockedObject<IScope>;
try {
await promiseToDigest($scope)(Promise.reject(123));
} catch (error) {
expect(error).toEqual(123);
expect($scope.$evalAsync).toHaveBeenCalledTimes(1);
}
});
});
});

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save