mirror of https://github.com/grafana/grafana.git
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>
This commit is contained in:
parent
b1198b92c6
commit
f96e4e9ad2
|
@ -393,8 +393,7 @@ exports[`better eslint`] = {
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "8"]
|
[0, 0, 0, "Unexpected any. Specify a different type.", "8"]
|
||||||
],
|
],
|
||||||
"packages/grafana-data/test/helpers/pluginMocks.ts:5381": [
|
"packages/grafana-data/test/helpers/pluginMocks.ts:5381": [
|
||||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
|
||||||
],
|
],
|
||||||
"packages/grafana-e2e-selectors/src/resolver.ts:5381": [
|
"packages/grafana-e2e-selectors/src/resolver.ts:5381": [
|
||||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
[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.", "3"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
|
[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": [
|
"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.", "0"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
[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": [
|
"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()", "0"],
|
||||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
[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"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx:5381": [
|
"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"]
|
[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": [
|
"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.", "0"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
[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.", "3"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
|
[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", "5"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/dashboard/containers/DashboardPageProxy.tsx:5381": [
|
"public/app/features/dashboard/containers/DashboardPageProxy.tsx:5381": [
|
||||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
[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.", "6"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
|
[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.", "8"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
|
[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"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/dashboard/state/DashboardMigrator.ts:5381": [
|
"public/app/features/dashboard/state/DashboardMigrator.ts:5381": [
|
||||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
[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 re-export imported variable (\`./remotePlugin.mock\`)", "1"],
|
||||||
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"]
|
[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": [
|
"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\`)", "0"],
|
||||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginDeprecatedBadge\`)", "1"],
|
[0, 0, 0, "Do not re-export imported variable (\`./PluginDisabledBadge\`)", "1"],
|
||||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginDisabledBadge\`)", "2"],
|
[0, 0, 0, "Do not re-export imported variable (\`./PluginEnterpriseBadge\`)", "2"],
|
||||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginEnterpriseBadge\`)", "3"],
|
[0, 0, 0, "Do not re-export imported variable (\`./PluginInstallBadge\`)", "3"],
|
||||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginInstallBadge\`)", "4"],
|
[0, 0, 0, "Do not re-export imported variable (\`./PluginUpdateAvailableBadge\`)", "4"]
|
||||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginUpdateAvailableBadge\`)", "5"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx:5381": [
|
"public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx:5381": [
|
||||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
[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"]
|
[0, 0, 0, "Do not re-export imported variable (\`./InstallControlsWarning\`)", "1"]
|
||||||
],
|
],
|
||||||
"public/app/features/plugins/admin/components/PluginDetailsBody.tsx:5381": [
|
"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 />", "0"]
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/plugins/admin/components/PluginDetailsDeprecatedWarning.tsx:5381": [
|
"public/app/features/plugins/admin/components/PluginDetailsDeprecatedWarning.tsx:5381": [
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
|
[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": [
|
"public/app/features/plugins/admin/types.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[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": [
|
"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 in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
[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.", "0"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
[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.", "2"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "3"],
|
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "3"]
|
||||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "4"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/query/components/QueryEditorRowHeader.tsx:5381": [
|
"public/app/features/query/components/QueryEditorRowHeader.tsx:5381": [
|
||||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
|
[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.", "16"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "17"]
|
[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": [
|
"public/app/routes/RoutesWrapper.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()", "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.", "3"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
|
[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": [
|
"public/app/types/dashboard.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[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 ...\`)", "17"],
|
||||||
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "18"],
|
[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 ...\`)", "19"],
|
||||||
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "20"],
|
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "20"]
|
||||||
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "21"]
|
|
||||||
],
|
],
|
||||||
"public/app/types/jquery/jquery.d.ts:5381": [
|
"public/app/types/jquery/jquery.d.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
[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"],
|
||||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or 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": [
|
"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"]
|
[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": [
|
"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"]
|
[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": [
|
"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"]
|
[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"],
|
||||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or 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": [
|
"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"],
|
||||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or 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"],
|
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or 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.
|
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` 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.
|
- 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 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 shell: Run `source .bingo/variables.env` to source all environment variable for each tool.
|
||||||
* For go: Import `.bingo/variables.go` to for variable names.
|
- 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.
|
- See https://github.com/bwplotka/bingo or -h on how to add, remove or change binaries dependencies.
|
||||||
|
|
||||||
## Requirements
|
## 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/datagrid/ @grafana/dataviz-squad
|
||||||
/public/app/plugins/panel/gauge/ @grafana/dataviz-squad
|
/public/app/plugins/panel/gauge/ @grafana/dataviz-squad
|
||||||
/public/app/plugins/panel/gettingstarted/ @grafana/grafana-frontend-platform
|
/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/heatmap/ @grafana/dataviz-squad
|
||||||
/public/app/plugins/panel/histogram/ @grafana/dataviz-squad
|
/public/app/plugins/panel/histogram/ @grafana/dataviz-squad
|
||||||
/public/app/plugins/panel/logs/ @grafana/observability-logs
|
/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/status-history/ @grafana/dataviz-squad
|
||||||
/public/app/plugins/panel/table/ @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/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/timeseries/ @grafana/dataviz-squad
|
||||||
/public/app/plugins/panel/trend/ @grafana/dataviz-squad
|
/public/app/plugins/panel/trend/ @grafana/dataviz-squad
|
||||||
/public/app/plugins/panel/geomap/ @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/text/ @grafana/grafana-frontend-platform
|
||||||
/public/app/plugins/panel/welcome/ @grafana/grafana-frontend-platform
|
/public/app/plugins/panel/welcome/ @grafana/grafana-frontend-platform
|
||||||
/public/app/plugins/panel/xychart/ @grafana/dataviz-squad
|
/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/routes/ @grafana/grafana-frontend-platform
|
||||||
/public/app/store/ @grafana/grafana-frontend-platform
|
/public/app/store/ @grafana/grafana-frontend-platform
|
||||||
/public/app/types/ @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-merged.json @grafana/grafana-backend-group
|
||||||
/public/api-enterprise-spec.json @grafana/grafana-backend-group
|
/public/api-enterprise-spec.json @grafana/grafana-backend-group
|
||||||
/public/openapi3.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/app.ts @grafana/frontend-ops
|
||||||
/public/app/dev.ts @grafana/frontend-ops
|
/public/app/dev.ts @grafana/frontend-ops
|
||||||
/public/app/core/utils/metrics.ts @grafana/plugins-platform-frontend
|
/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)
|
"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", // 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)
|
"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
|
"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
|
"@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
|
"slate", // we don't want to continue using this on the long run, use Monaco editor instead of Slate
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
.yarn
|
.yarn
|
||||||
.bingo
|
|
||||||
build
|
build
|
||||||
compiled
|
compiled
|
||||||
data
|
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.
|
[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 |
|
| Feature toggle name | Description |
|
||||||
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `panelTitleSearch` | Search for dashboards using panel title |
|
| `panelTitleSearch` | Search for dashboards using panel title |
|
||||||
| `autoMigrateOldPanels` | Migrate old angular panels to supported versions (graph, table-old, worldmap, etc) |
|
| `grpcServer` | Run the GRPC server |
|
||||||
| `autoMigrateGraphPanel` | Migrate old graph panel to supported time series panel - broken out from autoMigrateOldPanels to enable granular tracking |
|
| `renderAuthJWT` | Uses JWT-based auth for rendering instead of relying on remote cache |
|
||||||
| `autoMigrateTablePanel` | Migrate old table panel to supported table panel - broken out from autoMigrateOldPanels to enable granular tracking |
|
| `refactorVariablesTimeRange` | Refactor time range variables flow to reduce number of API calls made when query variables are chained |
|
||||||
| `autoMigratePiechartPanel` | Migrate old piechart panel to supported piechart panel - broken out from autoMigrateOldPanels to enable granular tracking |
|
| `faroDatasourceSelector` | Enable the data source selector within the Frontend Apps section of the Frontend Observability |
|
||||||
| `autoMigrateWorldmapPanel` | Migrate old worldmap panel to supported geomap panel - broken out from autoMigrateOldPanels to enable granular tracking |
|
| `enableDatagridEditing` | Enables the edit functionality in the datagrid panel |
|
||||||
| `autoMigrateStatPanel` | Migrate old stat panel to supported stat panel - broken out from autoMigrateOldPanels to enable granular tracking |
|
| `sqlDatasourceDatabaseSelection` | Enables previous SQL data source dataset dropdown behavior |
|
||||||
| `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. |
|
| `reportingRetries` | Enables rendering retries for the reporting feature |
|
||||||
| `grpcServer` | Run the GRPC server |
|
| `externalServiceAccounts` | Automatic service account and token setup for plugins |
|
||||||
| `renderAuthJWT` | Uses JWT-based auth for rendering instead of relying on remote cache |
|
| `cloudWatchBatchQueries` | Runs CloudWatch metrics queries as separate batches |
|
||||||
| `refactorVariablesTimeRange` | Refactor time range variables flow to reduce number of API calls made when query variables are chained |
|
| `pdfTables` | Enables generating table data as PDF in reporting |
|
||||||
| `faroDatasourceSelector` | Enable the data source selector within the Frontend Apps section of the Frontend Observability |
|
| `canvasPanelPanZoom` | Allow pan and zoom in canvas panel |
|
||||||
| `enableDatagridEditing` | Enables the edit functionality in the datagrid panel |
|
| `regressionTransformation` | Enables regression analysis transformation |
|
||||||
| `sqlDatasourceDatabaseSelection` | Enables previous SQL data source dataset dropdown behavior |
|
| `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. |
|
||||||
| `reportingRetries` | Enables rendering retries for the reporting feature |
|
| `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage |
|
||||||
| `externalServiceAccounts` | Automatic service account and token setup for plugins |
|
| `ssoSettingsLDAP` | Use the new SSO Settings API to configure LDAP |
|
||||||
| `cloudWatchBatchQueries` | Runs CloudWatch metrics queries as separate batches |
|
| `improvedExternalSessionHandling` | Enables improved support for OAuth external sessions. After enabling this feature, users might need to re-authenticate themselves. |
|
||||||
| `pdfTables` | Enables generating table data as PDF in reporting |
|
| `elasticsearchCrossClusterSearch` | Enables cross cluster search in the Elasticsearch datasource |
|
||||||
| `canvasPanelPanZoom` | Allow pan and zoom in canvas panel |
|
| `improvedExternalSessionHandlingSAML` | Enables improved support for SAML external sessions. Ensure the NameID format is correctly configured in Grafana for SAML Single Logout to function properly. |
|
||||||
| `regressionTransformation` | Enables regression analysis transformation |
|
| `teamHttpHeadersMimir` | Enables LBAC for datasources for Mimir to apply LBAC filtering of metrics to the client requests for users in teams |
|
||||||
| `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. |
|
| `exploreMetricsUseExternalAppPlugin` | Use the externalized Grafana Metrics Drilldown (formerly known as Explore Metrics) app plugin |
|
||||||
| `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage |
|
| `alertRuleRestore` | Enables the alert rule restore feature |
|
||||||
| `ssoSettingsLDAP` | Use the new SSO Settings API to configure LDAP |
|
| `azureMonitorLogsBuilderEditor` | Enables the logs builder mode for the Azure Monitor data source |
|
||||||
| `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
|
## Experimental feature toggles
|
||||||
|
|
||||||
|
|
|
@ -9,32 +9,12 @@ describe('Auto-migrate graph panel', () => {
|
||||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
|
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 });
|
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
|
||||||
cy.contains(DASHBOARD_NAME).should('be.visible');
|
cy.contains(DASHBOARD_NAME).should('be.visible');
|
||||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
|
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 });
|
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');
|
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.contains(DASHBOARD_NAME).should('be.visible');
|
||||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
|
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')
|
e2e.components.Panels.Panel.title('Business Hours')
|
||||||
.should('exist')
|
.should('exist')
|
||||||
|
|
|
@ -9,32 +9,12 @@ describe('Auto-migrate graph panel', () => {
|
||||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
|
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 });
|
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
|
||||||
cy.contains(DASHBOARD_NAME).should('be.visible');
|
cy.contains(DASHBOARD_NAME).should('be.visible');
|
||||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
|
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 });
|
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');
|
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.contains(DASHBOARD_NAME).should('be.visible');
|
||||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
|
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')
|
e2e.components.Panels.Panel.title('Business Hours')
|
||||||
.should('exist')
|
.should('exist')
|
||||||
|
|
10
package.json
10
package.json
|
@ -101,8 +101,6 @@
|
||||||
"@testing-library/jest-dom": "6.6.3",
|
"@testing-library/jest-dom": "6.6.3",
|
||||||
"@testing-library/react": "16.2.0",
|
"@testing-library/react": "16.2.0",
|
||||||
"@testing-library/user-event": "14.6.1",
|
"@testing-library/user-event": "14.6.1",
|
||||||
"@types/angular": "1.8.9",
|
|
||||||
"@types/angular-route": "1.7.6",
|
|
||||||
"@types/babel__core": "^7",
|
"@types/babel__core": "^7",
|
||||||
"@types/babel__preset-env": "^7",
|
"@types/babel__preset-env": "^7",
|
||||||
"@types/chance": "^1.1.3",
|
"@types/chance": "^1.1.3",
|
||||||
|
@ -214,7 +212,6 @@
|
||||||
"mini-css-extract-plugin": "2.9.2",
|
"mini-css-extract-plugin": "2.9.2",
|
||||||
"msw": "2.7.0",
|
"msw": "2.7.0",
|
||||||
"mutationobserver-shim": "0.3.7",
|
"mutationobserver-shim": "0.3.7",
|
||||||
"ngtemplate-loader": "2.1.0",
|
|
||||||
"node-notifier": "10.0.1",
|
"node-notifier": "10.0.1",
|
||||||
"nx": "20.7.1",
|
"nx": "20.7.1",
|
||||||
"openapi-types": "^12.1.3",
|
"openapi-types": "^12.1.3",
|
||||||
|
@ -310,10 +307,6 @@
|
||||||
"@visx/shape": "3.12.0",
|
"@visx/shape": "3.12.0",
|
||||||
"@visx/tooltip": "3.12.0",
|
"@visx/tooltip": "3.12.0",
|
||||||
"@welldone-software/why-did-you-render": "8.0.3",
|
"@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",
|
"ansicolor": "2.0.3",
|
||||||
"baron": "3.0.3",
|
"baron": "3.0.3",
|
||||||
"brace": "0.11.1",
|
"brace": "0.11.1",
|
||||||
|
@ -412,8 +405,6 @@
|
||||||
"swagger-ui-react": "5.20.5",
|
"swagger-ui-react": "5.20.5",
|
||||||
"symbol-observable": "4.0.0",
|
"symbol-observable": "4.0.0",
|
||||||
"systemjs": "6.15.1",
|
"systemjs": "6.15.1",
|
||||||
"systemjs-cjs-extra": "0.2.1",
|
|
||||||
"tether-drop": "https://github.com/torkelo/drop",
|
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tslib": "2.8.1",
|
"tslib": "2.8.1",
|
||||||
"tween-functions": "^1.2.0",
|
"tween-functions": "^1.2.0",
|
||||||
|
@ -426,7 +417,6 @@
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"underscore": "1.13.7",
|
"underscore": "1.13.7",
|
||||||
"@types/slate": "0.47.11",
|
"@types/slate": "0.47.11",
|
||||||
"ngtemplate-loader/loader-utils": "^2.0.0",
|
|
||||||
"semver@~7.0.0": "7.5.4",
|
"semver@~7.0.0": "7.5.4",
|
||||||
"semver@7.3.4": "7.5.4",
|
"semver@7.3.4": "7.5.4",
|
||||||
"debug@npm:^0.7.2": "2.6.9",
|
"debug@npm:^0.7.2": "2.6.9",
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { IScope } from 'angular';
|
|
||||||
import EventEmitter from 'eventemitter3';
|
import EventEmitter from 'eventemitter3';
|
||||||
import { Unsubscribable, Observable, Subscriber } from 'rxjs';
|
import { Unsubscribable, Observable, Subscriber } from 'rxjs';
|
||||||
import { filter } from 'rxjs/operators';
|
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`);
|
// console.log(`Deprecated emitter function used (on), use $on`);
|
||||||
|
|
||||||
// need this wrapper to make old events compatible with old handlers
|
// need this wrapper to make old events compatible with old handlers
|
||||||
|
@ -79,13 +78,6 @@ export class EventBusSrv implements EventBus, LegacyEmitter {
|
||||||
} else {
|
} else {
|
||||||
this.emitter.on(event.name, handler.wrapper);
|
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>) {
|
off<T>(event: AppEvent<T> | string, handler: LegacyEventHandler<T>) {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { IScope } from 'angular';
|
|
||||||
import { Unsubscribable, Observable } from 'rxjs';
|
import { Unsubscribable, Observable } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -129,7 +128,7 @@ export interface LegacyEmitter {
|
||||||
/**
|
/**
|
||||||
* @deprecated use $on
|
* @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
|
* @deprecated use $on
|
||||||
|
|
|
@ -257,6 +257,7 @@ export { toOption } from './utils/selectUtils';
|
||||||
export * as arrayUtils from './utils/arrayUtils';
|
export * as arrayUtils from './utils/arrayUtils';
|
||||||
export { store, Store } from './utils/store';
|
export { store, Store } from './utils/store';
|
||||||
export { LocalStorageValueProvider } from './utils/LocalStorageValueProvider';
|
export { LocalStorageValueProvider } from './utils/LocalStorageValueProvider';
|
||||||
|
export { throwIfAngular } from './utils/throwIfAngular';
|
||||||
|
|
||||||
// Tranformations
|
// Tranformations
|
||||||
export { standardTransformers } from './transformations/transformers';
|
export { standardTransformers } from './transformations/transformers';
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { ComponentType } from 'react';
|
import { ComponentType } from 'react';
|
||||||
|
|
||||||
|
import { throwIfAngular } from '../utils/throwIfAngular';
|
||||||
|
|
||||||
import { KeyValue } from './data';
|
import { KeyValue } from './data';
|
||||||
import { NavModel } from './navModel';
|
import { NavModel } from './navModel';
|
||||||
import { PluginMeta, GrafanaPlugin, PluginIncludeType } from './plugin';
|
import { PluginMeta, GrafanaPlugin, PluginIncludeType } from './plugin';
|
||||||
|
@ -85,9 +87,7 @@ export class AppPlugin<T extends KeyValue = KeyValue> extends GrafanaPlugin<AppP
|
||||||
}
|
}
|
||||||
|
|
||||||
setComponentsFromLegacyExports(pluginExports: System.Module) {
|
setComponentsFromLegacyExports(pluginExports: System.Module) {
|
||||||
if (pluginExports.ConfigCtrl) {
|
throwIfAngular(pluginExports);
|
||||||
this.angularConfigCtrl = pluginExports.ConfigCtrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.meta && this.meta.includes) {
|
if (this.meta && this.meta.includes) {
|
||||||
for (const include of this.meta.includes) {
|
for (const include of this.meta.includes) {
|
||||||
|
|
|
@ -214,7 +214,6 @@ export interface GrafanaConfig {
|
||||||
geomapDisableCustomBaseLayer?: boolean;
|
geomapDisableCustomBaseLayer?: boolean;
|
||||||
unifiedAlertingEnabled: boolean;
|
unifiedAlertingEnabled: boolean;
|
||||||
unifiedAlerting: UnifiedAlertingConfig;
|
unifiedAlerting: UnifiedAlertingConfig;
|
||||||
angularSupportEnabled: boolean;
|
|
||||||
feedbackLinksEnabled: boolean;
|
feedbackLinksEnabled: boolean;
|
||||||
supportBundlesEnabled: boolean;
|
supportBundlesEnabled: boolean;
|
||||||
secureSocksDSProxyEnabled: boolean;
|
secureSocksDSProxyEnabled: boolean;
|
||||||
|
|
|
@ -3,7 +3,9 @@ import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { DataSourceRef } from '@grafana/schema';
|
import { DataSourceRef } from '@grafana/schema';
|
||||||
|
|
||||||
|
import { deprecationWarning } from '../utils/deprecationWarning';
|
||||||
import { makeClassES5Compatible } from '../utils/makeClassES5Compatible';
|
import { makeClassES5Compatible } from '../utils/makeClassES5Compatible';
|
||||||
|
import { throwIfAngular } from '../utils/throwIfAngular';
|
||||||
|
|
||||||
import { ScopedVars } from './ScopedVars';
|
import { ScopedVars } from './ScopedVars';
|
||||||
import { WithAccessControlMetadata } from './accesscontrol';
|
import { WithAccessControlMetadata } from './accesscontrol';
|
||||||
|
@ -50,12 +52,16 @@ export class DataSourcePlugin<
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated it will be removed in a future release */
|
||||||
setConfigCtrl(ConfigCtrl: any) {
|
setConfigCtrl(ConfigCtrl: any) {
|
||||||
|
deprecationWarning('DataSourcePlugin', 'setConfigCtrl');
|
||||||
this.angularConfigCtrl = ConfigCtrl;
|
this.angularConfigCtrl = ConfigCtrl;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated it will be removed in a future release */
|
||||||
setQueryCtrl(QueryCtrl: any) {
|
setQueryCtrl(QueryCtrl: any) {
|
||||||
|
deprecationWarning('DataSourcePlugin', 'setQueryCtrl');
|
||||||
this.components.QueryCtrl = QueryCtrl;
|
this.components.QueryCtrl = QueryCtrl;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -115,7 +121,7 @@ export class DataSourcePlugin<
|
||||||
}
|
}
|
||||||
|
|
||||||
setComponentsFromLegacyExports(pluginExports: System.Module) {
|
setComponentsFromLegacyExports(pluginExports: System.Module) {
|
||||||
this.angularConfigCtrl = pluginExports.ConfigCtrl;
|
throwIfAngular(pluginExports);
|
||||||
|
|
||||||
this.components.QueryCtrl = pluginExports.QueryCtrl;
|
this.components.QueryCtrl = pluginExports.QueryCtrl;
|
||||||
this.components.AnnotationsQueryCtrl = pluginExports.AnnotationsQueryCtrl;
|
this.components.AnnotationsQueryCtrl = pluginExports.AnnotationsQueryCtrl;
|
||||||
|
@ -161,7 +167,9 @@ export interface DataSourcePluginComponents<
|
||||||
TOptions extends DataSourceJsonData = DataSourceJsonData,
|
TOptions extends DataSourceJsonData = DataSourceJsonData,
|
||||||
TSecureOptions = {},
|
TSecureOptions = {},
|
||||||
> {
|
> {
|
||||||
|
/** @deprecated it will be removed in a future release */
|
||||||
QueryCtrl?: any;
|
QueryCtrl?: any;
|
||||||
|
/** @deprecated it will be removed in a future release */
|
||||||
AnnotationsQueryCtrl?: any;
|
AnnotationsQueryCtrl?: any;
|
||||||
VariableQueryEditor?: any;
|
VariableQueryEditor?: any;
|
||||||
QueryEditor?: ComponentType<QueryEditorProps<DSType, TQuery, TOptions>>;
|
QueryEditor?: ComponentType<QueryEditorProps<DSType, TQuery, TOptions>>;
|
||||||
|
|
|
@ -63,34 +63,6 @@ export interface FeatureToggles {
|
||||||
*/
|
*/
|
||||||
correlations?: boolean;
|
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
|
* Allow elements nesting
|
||||||
*/
|
*/
|
||||||
canvasPanelNesting?: boolean;
|
canvasPanelNesting?: boolean;
|
||||||
|
|
|
@ -239,6 +239,7 @@ export class GrafanaPlugin<T extends PluginMeta = PluginMeta> {
|
||||||
loadError?: boolean;
|
loadError?: boolean;
|
||||||
|
|
||||||
// Config control (app/datasource)
|
// Config control (app/datasource)
|
||||||
|
/** @deprecated it will be removed in a future release */
|
||||||
angularConfigCtrl?: any;
|
angularConfigCtrl?: any;
|
||||||
|
|
||||||
// Show configuration tabs on the plugin page
|
// 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;
|
return plugins;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getPanelPlugin(
|
export function getPanelPlugin(options: Partial<PanelPluginMeta>, reactPanel?: ComponentType<PanelProps>): PanelPlugin {
|
||||||
options: Partial<PanelPluginMeta>,
|
|
||||||
reactPanel?: ComponentType<PanelProps>,
|
|
||||||
angularPanel?: any
|
|
||||||
): PanelPlugin {
|
|
||||||
const plugin = new PanelPlugin(reactPanel!);
|
const plugin = new PanelPlugin(reactPanel!);
|
||||||
plugin.angularPanelCtrl = angularPanel;
|
|
||||||
plugin.meta = {
|
plugin.meta = {
|
||||||
id: options.id!,
|
id: options.id!,
|
||||||
type: PluginType.panel,
|
type: PluginType.panel,
|
||||||
|
|
|
@ -73,7 +73,6 @@
|
||||||
"@testing-library/dom": "10.4.0",
|
"@testing-library/dom": "10.4.0",
|
||||||
"@testing-library/react": "16.2.0",
|
"@testing-library/react": "16.2.0",
|
||||||
"@testing-library/user-event": "14.6.1",
|
"@testing-library/user-event": "14.6.1",
|
||||||
"@types/angular": "1.8.9",
|
|
||||||
"@types/history": "4.7.11",
|
"@types/history": "4.7.11",
|
||||||
"@types/jest": "29.5.14",
|
"@types/jest": "29.5.14",
|
||||||
"@types/lodash": "4.17.15",
|
"@types/lodash": "4.17.15",
|
||||||
|
|
|
@ -78,7 +78,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||||
feedbackLinksEnabled = true;
|
feedbackLinksEnabled = true;
|
||||||
disableLoginForm = false;
|
disableLoginForm = false;
|
||||||
defaultDatasource = ''; // UID
|
defaultDatasource = ''; // UID
|
||||||
angularSupportEnabled = false;
|
|
||||||
authProxyEnabled = false;
|
authProxyEnabled = false;
|
||||||
exploreEnabled = false;
|
exploreEnabled = false;
|
||||||
queryHistoryEnabled = false;
|
queryHistoryEnabled = false;
|
||||||
|
@ -240,10 +239,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||||
overrideFeatureTogglesFromUrl(this);
|
overrideFeatureTogglesFromUrl(this);
|
||||||
overrideFeatureTogglesFromLocalStorage(this);
|
overrideFeatureTogglesFromLocalStorage(this);
|
||||||
|
|
||||||
if (this.featureToggles.disableAngular) {
|
|
||||||
this.angularSupportEnabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creating theme after applying feature toggle overrides in case we need to toggle anything
|
// Creating theme after applying feature toggle overrides in case we need to toggle anything
|
||||||
this.theme2 = getThemeById(this.bootData.user.theme);
|
this.theme2 = getThemeById(this.bootData.user.theme);
|
||||||
this.bootData.user.lightTheme = this.theme2.isLight;
|
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 './backendSrv';
|
||||||
export * from './AngularLoader';
|
|
||||||
export * from './dataSourceSrv';
|
export * from './dataSourceSrv';
|
||||||
export * from './LocationSrv';
|
export * from './LocationSrv';
|
||||||
export * from './EchoSrv';
|
export * from './EchoSrv';
|
||||||
export * from './templateSrv';
|
export * from './templateSrv';
|
||||||
export * from './legacyAngularInjector';
|
|
||||||
export * from './live';
|
export * from './live';
|
||||||
export * from './LocationService';
|
export * from './LocationService';
|
||||||
export * from './appEvents';
|
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 { getAccessibilityStyles } from './accessibility';
|
||||||
import { getAlertingStyles } from './alerting';
|
import { getAlertingStyles } from './alerting';
|
||||||
import { getAgularPanelStyles } from './angularPanelStyles';
|
|
||||||
import { getCardStyles } from './card';
|
import { getCardStyles } from './card';
|
||||||
import { getCodeStyles } from './code';
|
import { getCodeStyles } from './code';
|
||||||
import { getDashboardGridStyles } from './dashboardGrid';
|
import { getDashboardGridStyles } from './dashboardGrid';
|
||||||
|
@ -39,7 +38,6 @@ export function GlobalStyles(props: GlobalStylesProps) {
|
||||||
<Global
|
<Global
|
||||||
styles={[
|
styles={[
|
||||||
getAccessibilityStyles(theme),
|
getAccessibilityStyles(theme),
|
||||||
getAgularPanelStyles(theme),
|
|
||||||
getAlertingStyles(theme),
|
getAlertingStyles(theme),
|
||||||
getCodeStyles(theme),
|
getCodeStyles(theme),
|
||||||
getDashDiffStyles(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"`
|
ExternalUserMngAnalytics bool `json:"externalUserMngAnalytics"`
|
||||||
ExternalUserMngAnalyticsParams string `json:"externalUserMngAnalyticsParams"`
|
ExternalUserMngAnalyticsParams string `json:"externalUserMngAnalyticsParams"`
|
||||||
ViewersCanEdit bool `json:"viewersCanEdit"`
|
ViewersCanEdit bool `json:"viewersCanEdit"`
|
||||||
AngularSupportEnabled bool `json:"angularSupportEnabled"`
|
|
||||||
DisableSanitizeHtml bool `json:"disableSanitizeHtml"`
|
DisableSanitizeHtml bool `json:"disableSanitizeHtml"`
|
||||||
TrustedTypesDefaultPolicyEnabled bool `json:"trustedTypesDefaultPolicyEnabled"`
|
TrustedTypesDefaultPolicyEnabled bool `json:"trustedTypesDefaultPolicyEnabled"`
|
||||||
CSPReportOnlyEnabled bool `json:"cspReportOnlyEnabled"`
|
CSPReportOnlyEnabled bool `json:"cspReportOnlyEnabled"`
|
||||||
|
|
|
@ -230,7 +230,6 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
||||||
ExternalUserMngAnalyticsParams: hs.Cfg.ExternalUserMngAnalyticsParams,
|
ExternalUserMngAnalyticsParams: hs.Cfg.ExternalUserMngAnalyticsParams,
|
||||||
//nolint:staticcheck // ViewersCanEdit is deprecated but still used for backward compatibility
|
//nolint:staticcheck // ViewersCanEdit is deprecated but still used for backward compatibility
|
||||||
ViewersCanEdit: hs.Cfg.ViewersCanEdit,
|
ViewersCanEdit: hs.Cfg.ViewersCanEdit,
|
||||||
AngularSupportEnabled: hs.Cfg.AngularSupportEnabled,
|
|
||||||
DisableSanitizeHtml: hs.Cfg.DisableSanitizeHtml,
|
DisableSanitizeHtml: hs.Cfg.DisableSanitizeHtml,
|
||||||
TrustedTypesDefaultPolicyEnabled: trustedTypesDefaultPolicyEnabled,
|
TrustedTypesDefaultPolicyEnabled: trustedTypesDefaultPolicyEnabled,
|
||||||
CSPReportOnlyEnabled: hs.Cfg.CSPReportOnlyEnabled,
|
CSPReportOnlyEnabled: hs.Cfg.CSPReportOnlyEnabled,
|
||||||
|
|
|
@ -25,7 +25,6 @@ type PluginManagementCfg struct {
|
||||||
|
|
||||||
Features Features
|
Features Features
|
||||||
|
|
||||||
AngularSupportEnabled bool
|
|
||||||
HideAngularDeprecation []string
|
HideAngularDeprecation []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@ type Features struct {
|
||||||
|
|
||||||
// NewPluginManagementCfg returns a new PluginManagementCfg.
|
// NewPluginManagementCfg returns a new PluginManagementCfg.
|
||||||
func NewPluginManagementCfg(devMode bool, pluginsPath string, pluginSettings setting.PluginSettings, pluginsAllowUnsigned []string,
|
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,
|
grafanaComAPIURL string, disablePlugins []string, hideAngularDeprecation []string, forwardHostEnvVars []string, grafanaComAPIToken string,
|
||||||
) *PluginManagementCfg {
|
) *PluginManagementCfg {
|
||||||
return &PluginManagementCfg{
|
return &PluginManagementCfg{
|
||||||
|
@ -53,7 +52,6 @@ func NewPluginManagementCfg(devMode bool, pluginsPath string, pluginSettings set
|
||||||
GrafanaComAPIURL: grafanaComAPIURL,
|
GrafanaComAPIURL: grafanaComAPIURL,
|
||||||
GrafanaAppURL: appURL,
|
GrafanaAppURL: appURL,
|
||||||
Features: features,
|
Features: features,
|
||||||
AngularSupportEnabled: angularSupportEnabled,
|
|
||||||
HideAngularDeprecation: hideAngularDeprecation,
|
HideAngularDeprecation: hideAngularDeprecation,
|
||||||
ForwardHostEnvVars: forwardHostEnvVars,
|
ForwardHostEnvVars: forwardHostEnvVars,
|
||||||
GrafanaComAPIToken: grafanaComAPIToken,
|
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
|
// 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)
|
a.log.Error("Refusing to initialize plugin because it's using Angular, which has been disabled", "pluginId", p.ID)
|
||||||
return (&plugins.Error{
|
return (&plugins.Error{
|
||||||
PluginID: p.ID,
|
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,
|
Handler: b.handleCurrentStatus,
|
||||||
},
|
},
|
||||||
|
|
|
@ -92,56 +92,6 @@ var (
|
||||||
Expression: "true", // enabled by default
|
Expression: "true", // enabled by default
|
||||||
AllowSelfServe: true,
|
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",
|
Name: "canvasPanelNesting",
|
||||||
Description: "Allow elements nesting",
|
Description: "Allow elements nesting",
|
||||||
|
|
|
@ -42,17 +42,11 @@ func ProvideManagerService(cfg *setting.Cfg) (*FeatureManager, error) {
|
||||||
for key, val := range flags {
|
for key, val := range flags {
|
||||||
_, ok := mgmt.flags[key]
|
_, ok := mgmt.flags[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
switch key {
|
mgmt.flags[key] = &FeatureFlag{
|
||||||
// renamed the flag so it supports more panels
|
Name: key,
|
||||||
case "autoMigrateGraphPanels":
|
Stage: FeatureStageUnknown,
|
||||||
key = FlagAutoMigrateOldPanels
|
|
||||||
default:
|
|
||||||
mgmt.flags[key] = &FeatureFlag{
|
|
||||||
Name: key,
|
|
||||||
Stage: FeatureStageUnknown,
|
|
||||||
}
|
|
||||||
mgmt.warnings[key] = "unknown flag in config"
|
|
||||||
}
|
}
|
||||||
|
mgmt.warnings[key] = "unknown flag in config"
|
||||||
}
|
}
|
||||||
mgmt.startup[key] = val
|
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
|
featureHighlights,GA,@grafana/grafana-as-code,false,false,false
|
||||||
storage,experimental,@grafana/search-and-storage,false,false,false
|
storage,experimental,@grafana/search-and-storage,false,false,false
|
||||||
correlations,GA,@grafana/dataviz-squad,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
|
canvasPanelNesting,experimental,@grafana/dataviz-squad,false,false,true
|
||||||
disableSecretsCompatibility,experimental,@grafana/hosted-grafana-team,false,true,false
|
disableSecretsCompatibility,experimental,@grafana/hosted-grafana-team,false,true,false
|
||||||
logRequestsInstrumentedAsUnknown,experimental,@grafana/hosted-grafana-team,false,false,false
|
logRequestsInstrumentedAsUnknown,experimental,@grafana/hosted-grafana-team,false,false,false
|
||||||
|
|
|
|
@ -47,34 +47,6 @@ const (
|
||||||
// Correlations page
|
// Correlations page
|
||||||
FlagCorrelations = "correlations"
|
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
|
// FlagCanvasPanelNesting
|
||||||
// Allow elements nesting
|
// Allow elements nesting
|
||||||
FlagCanvasPanelNesting = "canvasPanelNesting"
|
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
|
// 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,
|
angularInspector: angularinspector.AlwaysAngularFakeInspector,
|
||||||
})
|
})
|
||||||
p, err := l.Load(context.Background(), fakePluginSource)
|
p, err := l.Load(context.Background(), fakePluginSource)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, p, 1, "should load 1 plugin")
|
|
||||||
if tc.expAngularDetectionRun {
|
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 {
|
} else {
|
||||||
|
require.Len(t, p, 1, "should load 1 plugin")
|
||||||
require.False(t, p[0].Angular.Detected, "angular detection should not run")
|
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
|
name string
|
||||||
cfg *config.PluginManagementCfg
|
cfg *config.PluginManagementCfg
|
||||||
}{
|
}{
|
||||||
{name: "angular support enabled", cfg: &config.PluginManagementCfg{AngularSupportEnabled: true}},
|
{name: "angular support enabled", cfg: &config.PluginManagementCfg{}},
|
||||||
{name: "angular support disabled", cfg: &config.PluginManagementCfg{AngularSupportEnabled: false}},
|
{name: "angular support disabled", cfg: &config.PluginManagementCfg{}},
|
||||||
} {
|
} {
|
||||||
t.Run(cfgTc.name, func(t *testing.T) {
|
t.Run(cfgTc.name, func(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
|
@ -1209,7 +1209,7 @@ func TestLoader_Load_Angular(t *testing.T) {
|
||||||
name: "angular plugin",
|
name: "angular plugin",
|
||||||
angularInspector: angularinspector.AlwaysAngularFakeInspector,
|
angularInspector: angularinspector.AlwaysAngularFakeInspector,
|
||||||
// angular plugins should load only if allowed by the cfg
|
// angular plugins should load only if allowed by the cfg
|
||||||
shouldLoad: cfgTc.cfg.AngularSupportEnabled,
|
shouldLoad: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non angular plugin",
|
name: "non angular plugin",
|
||||||
|
@ -1243,22 +1243,18 @@ func TestLoader_HideAngularDeprecation(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
cfg *config.PluginManagementCfg
|
cfg *config.PluginManagementCfg
|
||||||
expHideAngularDeprecation bool
|
|
||||||
}{
|
}{
|
||||||
{name: "with plugin id in HideAngularDeprecation list", 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"},
|
HideAngularDeprecation: []string{"one-app", "two-panel", "test-datasource", "three-datasource"},
|
||||||
}, expHideAngularDeprecation: true},
|
}},
|
||||||
{name: "without plugin id in HideAngularDeprecation list", cfg: &config.PluginManagementCfg{
|
{name: "without plugin id in HideAngularDeprecation list", cfg: &config.PluginManagementCfg{
|
||||||
AngularSupportEnabled: true,
|
|
||||||
HideAngularDeprecation: []string{"one-app", "two-panel", "three-datasource"},
|
HideAngularDeprecation: []string{"one-app", "two-panel", "three-datasource"},
|
||||||
}, expHideAngularDeprecation: false},
|
}},
|
||||||
{name: "with empty HideAngularDeprecation", cfg: &config.PluginManagementCfg{
|
{name: "with empty HideAngularDeprecation", cfg: &config.PluginManagementCfg{
|
||||||
AngularSupportEnabled: true,
|
|
||||||
HideAngularDeprecation: nil,
|
HideAngularDeprecation: nil,
|
||||||
}, expHideAngularDeprecation: false},
|
}},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
l := newLoaderWithOpts(t, tc.cfg, loaderDepOpts{
|
l := newLoaderWithOpts(t, tc.cfg, loaderDepOpts{
|
||||||
|
@ -1266,8 +1262,7 @@ func TestLoader_HideAngularDeprecation(t *testing.T) {
|
||||||
})
|
})
|
||||||
p, err := l.Load(context.Background(), fakePluginSource)
|
p, err := l.Load(context.Background(), fakePluginSource)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, p, 1, "should load 1 plugin")
|
require.Empty(t, p, "plugin shouldn't have been loaded")
|
||||||
require.Equal(t, tc.expHideAngularDeprecation, p[0].Angular.HideDeprecation)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,6 @@ func ProvidePluginManagementConfig(cfg *setting.Cfg, settingProvider setting.Pro
|
||||||
PluginsCDNSyncLoaderEnabled: features.IsEnabledGlobally(featuremgmt.FlagPluginsCDNSyncLoader),
|
PluginsCDNSyncLoaderEnabled: features.IsEnabledGlobally(featuremgmt.FlagPluginsCDNSyncLoader),
|
||||||
LocalizationForPlugins: features.IsEnabledGlobally(featuremgmt.FlagLocalizationForPlugins),
|
LocalizationForPlugins: features.IsEnabledGlobally(featuremgmt.FlagLocalizationForPlugins),
|
||||||
},
|
},
|
||||||
cfg.AngularSupportEnabled,
|
|
||||||
cfg.GrafanaComAPIURL,
|
cfg.GrafanaComAPIURL,
|
||||||
cfg.DisablePlugins,
|
cfg.DisablePlugins,
|
||||||
cfg.HideAngularDeprecation,
|
cfg.HideAngularDeprecation,
|
||||||
|
|
|
@ -145,7 +145,6 @@ func verifyCorePluginCatalogue(t *testing.T, ctx context.Context, ps *pluginstor
|
||||||
"gauge": {},
|
"gauge": {},
|
||||||
"geomap": {},
|
"geomap": {},
|
||||||
"gettingstarted": {},
|
"gettingstarted": {},
|
||||||
"graph": {},
|
|
||||||
"heatmap": {},
|
"heatmap": {},
|
||||||
"histogram": {},
|
"histogram": {},
|
||||||
"live": {},
|
"live": {},
|
||||||
|
@ -161,7 +160,6 @@ func verifyCorePluginCatalogue(t *testing.T, ctx context.Context, ps *pluginstor
|
||||||
"state-timeline": {},
|
"state-timeline": {},
|
||||||
"status-history": {},
|
"status-history": {},
|
||||||
"table": {},
|
"table": {},
|
||||||
"table-old": {},
|
|
||||||
"text": {},
|
"text": {},
|
||||||
"timeseries": {},
|
"timeseries": {},
|
||||||
"trend": {},
|
"trend": {},
|
||||||
|
|
|
@ -176,7 +176,6 @@ type Cfg struct {
|
||||||
CSPReportOnlyEnabled bool
|
CSPReportOnlyEnabled bool
|
||||||
// CSPReportOnlyTemplate contains the Content Security Policy Report Only template.
|
// CSPReportOnlyTemplate contains the Content Security Policy Report Only template.
|
||||||
CSPReportOnlyTemplate string
|
CSPReportOnlyTemplate string
|
||||||
AngularSupportEnabled bool
|
|
||||||
EnableFrontendSandboxForPlugins []string
|
EnableFrontendSandboxForPlugins []string
|
||||||
DisableGravatar bool
|
DisableGravatar bool
|
||||||
DataProxyWhiteList map[string]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.StrictTransportSecurityMaxAge = security.Key("strict_transport_security_max_age_seconds").MustInt(86400)
|
||||||
cfg.StrictTransportSecurityPreload = security.Key("strict_transport_security_preload").MustBool(false)
|
cfg.StrictTransportSecurityPreload = security.Key("strict_transport_security_preload").MustBool(false)
|
||||||
cfg.StrictTransportSecuritySubDomains = security.Key("strict_transport_security_subdomains").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.CSPEnabled = security.Key("content_security_policy").MustBool(false)
|
||||||
cfg.CSPTemplate = security.Key("content_security_policy_template").MustString("")
|
cfg.CSPTemplate = security.Key("content_security_policy_template").MustString("")
|
||||||
cfg.CSPReportOnlyEnabled = security.Key("content_security_policy_report_only").MustBool(false)
|
cfg.CSPReportOnlyEnabled = security.Key("content_security_policy_report_only").MustBool(false)
|
||||||
|
|
|
@ -36,7 +36,7 @@ const (
|
||||||
defaultPassword = "password"
|
defaultPassword = "password"
|
||||||
)
|
)
|
||||||
|
|
||||||
var updateSnapshotFlag = false
|
var updateSnapshotFlag = true
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
testsuite.Run(m)
|
testsuite.Run(m)
|
||||||
|
|
|
@ -964,52 +964,6 @@
|
||||||
"signatureOrg": "",
|
"signatureOrg": "",
|
||||||
"angularDetected": false
|
"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",
|
"name": "Graphite",
|
||||||
"type": "datasource",
|
"type": "datasource",
|
||||||
|
@ -2052,52 +2006,6 @@
|
||||||
"signatureOrg": "",
|
"signatureOrg": "",
|
||||||
"angularDetected": false
|
"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",
|
"name": "Tempo",
|
||||||
"type": "datasource",
|
"type": "datasource",
|
||||||
|
|
|
@ -15,7 +15,6 @@ import { ErrorBoundaryAlert, PortalContainer, TimeRangeProvider } from '@grafana
|
||||||
import { getAppRoutes } from 'app/routes/routes';
|
import { getAppRoutes } from 'app/routes/routes';
|
||||||
import { store } from 'app/store/store';
|
import { store } from 'app/store/store';
|
||||||
|
|
||||||
import { loadAndInitAngularIfEnabled } from './angular/loadAndInitAngularIfEnabled';
|
|
||||||
import { GrafanaApp } from './app';
|
import { GrafanaApp } from './app';
|
||||||
import { ExtensionSidebarContextProvider } from './core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider';
|
import { ExtensionSidebarContextProvider } from './core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider';
|
||||||
import { GlobalStylesWrapper } from './core/components/AppChrome/ExtensionSidebar/GlobalStylesWrapper';
|
import { GlobalStylesWrapper } from './core/components/AppChrome/ExtensionSidebar/GlobalStylesWrapper';
|
||||||
|
@ -63,7 +62,6 @@ export class AppWrapper extends Component<AppWrapperProps, AppWrapperState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
await loadAndInitAngularIfEnabled();
|
|
||||||
this.setState({ ready: true });
|
this.setState({ ready: true });
|
||||||
$('.preloader').remove();
|
$('.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 || ' '">
|
|
||||||
</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) => '&#' + 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…
Reference in New Issue