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"]
|
||||
],
|
||||
"packages/grafana-data/test/helpers/pluginMocks.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"packages/grafana-e2e-selectors/src/resolver.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
|
@ -465,11 +464,6 @@ exports[`better eslint`] = {
|
|||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
|
||||
],
|
||||
"packages/grafana-runtime/src/services/AngularLoader.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
|
||||
],
|
||||
"packages/grafana-runtime/src/services/EchoSrv.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
|
@ -2889,8 +2883,7 @@ exports[`better eslint`] = {
|
|||
],
|
||||
"public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"]
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"]
|
||||
],
|
||||
"public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx:5381": [
|
||||
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
|
||||
|
@ -2998,10 +2991,9 @@ exports[`better eslint`] = {
|
|||
"public/app/features/dashboard/containers/DashboardPage.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "2"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "5"]
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "4"]
|
||||
],
|
||||
"public/app/features/dashboard/containers/DashboardPageProxy.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
|
@ -3033,9 +3025,7 @@ exports[`better eslint`] = {
|
|||
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "8"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "11"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "9"]
|
||||
],
|
||||
"public/app/features/dashboard/state/DashboardMigrator.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
|
@ -3728,16 +3718,12 @@ exports[`better eslint`] = {
|
|||
[0, 0, 0, "Do not re-export imported variable (\`./remotePlugin.mock\`)", "1"],
|
||||
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"]
|
||||
],
|
||||
"public/app/features/plugins/admin/components/Badges/PluginAngularBadge.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
|
||||
],
|
||||
"public/app/features/plugins/admin/components/Badges/index.ts:5381": [
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginAngularBadge\`)", "0"],
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginDeprecatedBadge\`)", "1"],
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginDisabledBadge\`)", "2"],
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginEnterpriseBadge\`)", "3"],
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginInstallBadge\`)", "4"],
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginUpdateAvailableBadge\`)", "5"]
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginDeprecatedBadge\`)", "0"],
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginDisabledBadge\`)", "1"],
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginEnterpriseBadge\`)", "2"],
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginInstallBadge\`)", "3"],
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./PluginUpdateAvailableBadge\`)", "4"]
|
||||
],
|
||||
"public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
|
@ -3759,8 +3745,7 @@ exports[`better eslint`] = {
|
|||
[0, 0, 0, "Do not re-export imported variable (\`./InstallControlsWarning\`)", "1"]
|
||||
],
|
||||
"public/app/features/plugins/admin/components/PluginDetailsBody.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
|
||||
],
|
||||
"public/app/features/plugins/admin/components/PluginDetailsDeprecatedWarning.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
|
||||
|
@ -3799,12 +3784,6 @@ exports[`better eslint`] = {
|
|||
"public/app/features/plugins/admin/types.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/plugins/angularDeprecation/AngularDeprecationNotice.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
|
||||
],
|
||||
"public/app/features/plugins/angularDeprecation/AngularMigrationNotice.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
|
||||
],
|
||||
"public/app/features/plugins/components/PluginsErrorsInfo.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
||||
|
@ -3873,8 +3852,7 @@ exports[`better eslint`] = {
|
|||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "2"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "3"],
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "4"]
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "3"]
|
||||
],
|
||||
"public/app/features/query/components/QueryEditorRowHeader.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
|
||||
|
@ -5351,9 +5329,6 @@ exports[`better eslint`] = {
|
|||
[0, 0, 0, "Unexpected any. Specify a different type.", "16"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "17"]
|
||||
],
|
||||
"public/app/plugins/sdk.ts:5381": [
|
||||
[0, 0, 0, "Do not re-export imported variable (\`loadPluginCss\`)", "0"]
|
||||
],
|
||||
"public/app/routes/RoutesWrapper.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
|
||||
],
|
||||
|
@ -5374,13 +5349,6 @@ exports[`better eslint`] = {
|
|||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
|
||||
],
|
||||
"public/app/types/appEvent.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
|
||||
],
|
||||
"public/app/types/dashboard.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
|
@ -5416,8 +5384,7 @@ exports[`better eslint`] = {
|
|||
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "17"],
|
||||
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "18"],
|
||||
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "19"],
|
||||
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "20"],
|
||||
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "21"]
|
||||
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "20"]
|
||||
],
|
||||
"public/app/types/jquery/jquery.d.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
|
@ -5678,89 +5645,6 @@ exports[`no gf-form usage`] = {
|
|||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/angular/components/PageHeader/PageHeader.tsx:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/angular/components/code_editor/code_editor.ts:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/angular/components/form_dropdown/form_dropdown.ts:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/angular/components/info_popover.ts:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/angular/components/switch.ts:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/angular/dropdown_typeahead.ts:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/angular/metric_segment.ts:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/angular/misc.ts:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/angular/panel/partials/query_editor_row.html:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/angular/partials/tls_auth_settings.html:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/core/components/AccessControl/PermissionList.tsx:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
|
@ -5808,9 +5692,6 @@ exports[`no gf-form usage`] = {
|
|||
"public/app/features/datasources/components/DataSourceTestingStatus.tsx:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/features/plugins/admin/components/AppConfigWrapper.tsx:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/features/query/components/QueryEditorRow.tsx:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
|
@ -5977,239 +5858,6 @@ exports[`no gf-form usage`] = {
|
|||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/plugins/panel/graph/axes_editor.html:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/plugins/panel/graph/tab_display.html:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/plugins/panel/graph/tab_legend.html:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/plugins/panel/graph/tab_series_overrides.html:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/plugins/panel/graph/thresholds_form.html:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/plugins/panel/graph/time_regions_form.html:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/plugins/panel/heatmap/partials/axes_editor.html:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
|
@ -6317,123 +5965,6 @@ exports[`no gf-form usage`] = {
|
|||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/plugins/panel/table-old/column_options.html:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
],
|
||||
"public/app/plugins/panel/table-old/editor.html:5381": [
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
|
||||
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
|
||||
]
|
||||
}`
|
||||
};
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
This is directory which stores Go modules with pinned buildable package that is used within this repository, managed by https://github.com/bwplotka/bingo.
|
||||
|
||||
* Run `bingo get` to install all tools having each own module file in this directory.
|
||||
* Run `bingo get <tool>` to install <tool> that have own module file in this directory.
|
||||
* For Makefile: Make sure to put `include .bingo/Variables.mk` in your Makefile, then use $(<upper case tool name>) variable where <tool> is the .bingo/<tool>.mod.
|
||||
* For shell: Run `source .bingo/variables.env` to source all environment variable for each tool.
|
||||
* For go: Import `.bingo/variables.go` to for variable names.
|
||||
* See https://github.com/bwplotka/bingo or -h on how to add, remove or change binaries dependencies.
|
||||
- Run `bingo get` to install all tools having each own module file in this directory.
|
||||
- Run `bingo get <tool>` to install <tool> that have own module file in this directory.
|
||||
- For Makefile: Make sure to put `include .bingo/Variables.mk` in your Makefile, then use $(<upper case tool name>) variable where <tool> is the .bingo/<tool>.mod.
|
||||
- For shell: Run `source .bingo/variables.env` to source all environment variable for each tool.
|
||||
- For go: Import `.bingo/variables.go` to for variable names.
|
||||
- See https://github.com/bwplotka/bingo or -h on how to add, remove or change binaries dependencies.
|
||||
|
||||
## Requirements
|
||||
|
||||
* Go 1.14+
|
||||
- Go 1.14+
|
||||
|
|
|
@ -543,7 +543,6 @@ playwright.config.ts @grafana/plugins-platform-frontend
|
|||
/public/app/plugins/panel/datagrid/ @grafana/dataviz-squad
|
||||
/public/app/plugins/panel/gauge/ @grafana/dataviz-squad
|
||||
/public/app/plugins/panel/gettingstarted/ @grafana/grafana-frontend-platform
|
||||
/public/app/plugins/panel/graph/ @grafana/dataviz-squad
|
||||
/public/app/plugins/panel/heatmap/ @grafana/dataviz-squad
|
||||
/public/app/plugins/panel/histogram/ @grafana/dataviz-squad
|
||||
/public/app/plugins/panel/logs/ @grafana/observability-logs
|
||||
|
@ -556,7 +555,6 @@ playwright.config.ts @grafana/plugins-platform-frontend
|
|||
/public/app/plugins/panel/status-history/ @grafana/dataviz-squad
|
||||
/public/app/plugins/panel/table/ @grafana/dataviz-squad
|
||||
/public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx @grafana/dataviz-squad @grafana/app-o11y-visualizations
|
||||
/public/app/plugins/panel/table-old/ @grafana/dataviz-squad
|
||||
/public/app/plugins/panel/timeseries/ @grafana/dataviz-squad
|
||||
/public/app/plugins/panel/trend/ @grafana/dataviz-squad
|
||||
/public/app/plugins/panel/geomap/ @grafana/dataviz-squad
|
||||
|
@ -568,7 +566,6 @@ playwright.config.ts @grafana/plugins-platform-frontend
|
|||
/public/app/plugins/panel/text/ @grafana/grafana-frontend-platform
|
||||
/public/app/plugins/panel/welcome/ @grafana/grafana-frontend-platform
|
||||
/public/app/plugins/panel/xychart/ @grafana/dataviz-squad
|
||||
/public/app/plugins/sdk.ts @grafana/plugins-platform-frontend
|
||||
/public/app/routes/ @grafana/grafana-frontend-platform
|
||||
/public/app/store/ @grafana/grafana-frontend-platform
|
||||
/public/app/types/ @grafana/grafana-frontend-platform
|
||||
|
@ -601,7 +598,6 @@ playwright.config.ts @grafana/plugins-platform-frontend
|
|||
/public/api-merged.json @grafana/grafana-backend-group
|
||||
/public/api-enterprise-spec.json @grafana/grafana-backend-group
|
||||
/public/openapi3.json @grafana/grafana-backend-group
|
||||
/public/app/angular/ @torkelo
|
||||
/public/app/app.ts @grafana/frontend-ops
|
||||
/public/app/dev.ts @grafana/frontend-ops
|
||||
/public/app/core/utils/metrics.ts @grafana/plugins-platform-frontend
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
"history", // we should bump this together with react-router-dom (see https://github.com/grafana/grafana/issues/76744)
|
||||
"react-router", // we should bump this together with history and react-router-dom
|
||||
"react-router-dom", // we should bump this together with history (see https://github.com/grafana/grafana/issues/76744)
|
||||
"loader-utils", // v3 requires upstream changes in ngtemplate-loader. ignore, and remove when we remove angular.
|
||||
"monaco-editor", // due to us exposing this via @grafana/ui/CodeEditor's props bumping can break plugins
|
||||
"@fingerprintjs/fingerprintjs", // we don't want to bump to v4 due to licensing changes
|
||||
"slate", // we don't want to continue using this on the long run, use Monaco editor instead of Slate
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
.git
|
||||
.github
|
||||
.yarn
|
||||
.bingo
|
||||
build
|
||||
compiled
|
||||
data
|
||||
|
|
|
@ -91,38 +91,31 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
|
|||
|
||||
[Public preview](https://grafana.com/docs/release-life-cycle/#public-preview) features are supported by our Support teams, but might be limited to enablement, configuration, and some troubleshooting.
|
||||
|
||||
| Feature toggle name | Description |
|
||||
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `panelTitleSearch` | Search for dashboards using panel title |
|
||||
| `autoMigrateOldPanels` | Migrate old angular panels to supported versions (graph, table-old, worldmap, etc) |
|
||||
| `autoMigrateGraphPanel` | Migrate old graph panel to supported time series panel - broken out from autoMigrateOldPanels to enable granular tracking |
|
||||
| `autoMigrateTablePanel` | Migrate old table panel to supported table panel - broken out from autoMigrateOldPanels to enable granular tracking |
|
||||
| `autoMigratePiechartPanel` | Migrate old piechart panel to supported piechart panel - broken out from autoMigrateOldPanels to enable granular tracking |
|
||||
| `autoMigrateWorldmapPanel` | Migrate old worldmap panel to supported geomap panel - broken out from autoMigrateOldPanels to enable granular tracking |
|
||||
| `autoMigrateStatPanel` | Migrate old stat panel to supported stat panel - broken out from autoMigrateOldPanels to enable granular tracking |
|
||||
| `disableAngular` | Dynamic flag to disable angular at runtime. The preferred method is to set `angular_support_enabled` to `false` in the [security] settings, which allows you to change the state at runtime. |
|
||||
| `grpcServer` | Run the GRPC server |
|
||||
| `renderAuthJWT` | Uses JWT-based auth for rendering instead of relying on remote cache |
|
||||
| `refactorVariablesTimeRange` | Refactor time range variables flow to reduce number of API calls made when query variables are chained |
|
||||
| `faroDatasourceSelector` | Enable the data source selector within the Frontend Apps section of the Frontend Observability |
|
||||
| `enableDatagridEditing` | Enables the edit functionality in the datagrid panel |
|
||||
| `sqlDatasourceDatabaseSelection` | Enables previous SQL data source dataset dropdown behavior |
|
||||
| `reportingRetries` | Enables rendering retries for the reporting feature |
|
||||
| `externalServiceAccounts` | Automatic service account and token setup for plugins |
|
||||
| `cloudWatchBatchQueries` | Runs CloudWatch metrics queries as separate batches |
|
||||
| `pdfTables` | Enables generating table data as PDF in reporting |
|
||||
| `canvasPanelPanZoom` | Allow pan and zoom in canvas panel |
|
||||
| `regressionTransformation` | Enables regression analysis transformation |
|
||||
| `onPremToCloudMigrations` | Enable the Grafana Migration Assistant, which helps you easily migrate on-prem resources, such as dashboards, folders, and data source configurations, to your Grafana Cloud stack. |
|
||||
| `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage |
|
||||
| `ssoSettingsLDAP` | Use the new SSO Settings API to configure LDAP |
|
||||
| `improvedExternalSessionHandling` | Enables improved support for OAuth external sessions. After enabling this feature, users might need to re-authenticate themselves. |
|
||||
| `elasticsearchCrossClusterSearch` | Enables cross cluster search in the Elasticsearch datasource |
|
||||
| `improvedExternalSessionHandlingSAML` | Enables improved support for SAML external sessions. Ensure the NameID format is correctly configured in Grafana for SAML Single Logout to function properly. |
|
||||
| `teamHttpHeadersMimir` | Enables LBAC for datasources for Mimir to apply LBAC filtering of metrics to the client requests for users in teams |
|
||||
| `exploreMetricsUseExternalAppPlugin` | Use the externalized Grafana Metrics Drilldown (formerly known as Explore Metrics) app plugin |
|
||||
| `alertRuleRestore` | Enables the alert rule restore feature |
|
||||
| `azureMonitorLogsBuilderEditor` | Enables the logs builder mode for the Azure Monitor data source |
|
||||
| Feature toggle name | Description |
|
||||
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `panelTitleSearch` | Search for dashboards using panel title |
|
||||
| `grpcServer` | Run the GRPC server |
|
||||
| `renderAuthJWT` | Uses JWT-based auth for rendering instead of relying on remote cache |
|
||||
| `refactorVariablesTimeRange` | Refactor time range variables flow to reduce number of API calls made when query variables are chained |
|
||||
| `faroDatasourceSelector` | Enable the data source selector within the Frontend Apps section of the Frontend Observability |
|
||||
| `enableDatagridEditing` | Enables the edit functionality in the datagrid panel |
|
||||
| `sqlDatasourceDatabaseSelection` | Enables previous SQL data source dataset dropdown behavior |
|
||||
| `reportingRetries` | Enables rendering retries for the reporting feature |
|
||||
| `externalServiceAccounts` | Automatic service account and token setup for plugins |
|
||||
| `cloudWatchBatchQueries` | Runs CloudWatch metrics queries as separate batches |
|
||||
| `pdfTables` | Enables generating table data as PDF in reporting |
|
||||
| `canvasPanelPanZoom` | Allow pan and zoom in canvas panel |
|
||||
| `regressionTransformation` | Enables regression analysis transformation |
|
||||
| `onPremToCloudMigrations` | Enable the Grafana Migration Assistant, which helps you easily migrate on-prem resources, such as dashboards, folders, and data source configurations, to your Grafana Cloud stack. |
|
||||
| `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage |
|
||||
| `ssoSettingsLDAP` | Use the new SSO Settings API to configure LDAP |
|
||||
| `improvedExternalSessionHandling` | Enables improved support for OAuth external sessions. After enabling this feature, users might need to re-authenticate themselves. |
|
||||
| `elasticsearchCrossClusterSearch` | Enables cross cluster search in the Elasticsearch datasource |
|
||||
| `improvedExternalSessionHandlingSAML` | Enables improved support for SAML external sessions. Ensure the NameID format is correctly configured in Grafana for SAML Single Logout to function properly. |
|
||||
| `teamHttpHeadersMimir` | Enables LBAC for datasources for Mimir to apply LBAC filtering of metrics to the client requests for users in teams |
|
||||
| `exploreMetricsUseExternalAppPlugin` | Use the externalized Grafana Metrics Drilldown (formerly known as Explore Metrics) app plugin |
|
||||
| `alertRuleRestore` | Enables the alert rule restore feature |
|
||||
| `azureMonitorLogsBuilderEditor` | Enables the logs builder mode for the Azure Monitor data source |
|
||||
|
||||
## Experimental feature toggles
|
||||
|
||||
|
|
|
@ -9,32 +9,12 @@ describe('Auto-migrate graph panel', () => {
|
|||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
|
||||
});
|
||||
|
||||
it('Graph panel is migrated with `autoMigrateOldPanels` feature toggle', () => {
|
||||
it('Graph panel is auto-migrated', () => {
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
|
||||
cy.contains(DASHBOARD_NAME).should('be.visible');
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
|
||||
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateOldPanels': true } });
|
||||
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
|
||||
});
|
||||
|
||||
it('Graph panel is migrated with config `disableAngular` feature toggle', () => {
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
|
||||
cy.contains(DASHBOARD_NAME).should('be.visible');
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
|
||||
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.disableAngular': true } });
|
||||
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
|
||||
});
|
||||
|
||||
it('Graph panel is migrated with `autoMigrateGraphPanel` feature toggle', () => {
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
|
||||
cy.contains(DASHBOARD_NAME).should('be.visible');
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
|
||||
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateGraphPanel': true } });
|
||||
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
|
||||
});
|
||||
|
@ -44,7 +24,7 @@ describe('Auto-migrate graph panel', () => {
|
|||
cy.contains(DASHBOARD_NAME).should('be.visible');
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
|
||||
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateGraphPanel': true } });
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
|
||||
|
||||
e2e.components.Panels.Panel.title('Business Hours')
|
||||
.should('exist')
|
||||
|
|
|
@ -9,32 +9,12 @@ describe('Auto-migrate graph panel', () => {
|
|||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
|
||||
});
|
||||
|
||||
it('Graph panel is migrated with `autoMigrateOldPanels` feature toggle', () => {
|
||||
it('Graph panel is auto-migrated', () => {
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
|
||||
cy.contains(DASHBOARD_NAME).should('be.visible');
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
|
||||
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateOldPanels': true } });
|
||||
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
|
||||
});
|
||||
|
||||
it('Graph panel is migrated with config `disableAngular` feature toggle', () => {
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
|
||||
cy.contains(DASHBOARD_NAME).should('be.visible');
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
|
||||
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.disableAngular': true } });
|
||||
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
|
||||
});
|
||||
|
||||
it('Graph panel is migrated with `autoMigrateGraphPanel` feature toggle', () => {
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
|
||||
cy.contains(DASHBOARD_NAME).should('be.visible');
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
|
||||
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateGraphPanel': true } });
|
||||
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('exist');
|
||||
});
|
||||
|
@ -44,7 +24,7 @@ describe('Auto-migrate graph panel', () => {
|
|||
cy.contains(DASHBOARD_NAME).should('be.visible');
|
||||
cy.get(UPLOT_MAIN_DIV_SELECTOR).should('not.exist');
|
||||
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.autoMigrateGraphPanel': true } });
|
||||
e2e.flows.openDashboard({ uid: DASHBOARD_ID });
|
||||
|
||||
e2e.components.Panels.Panel.title('Business Hours')
|
||||
.should('exist')
|
||||
|
|
10
package.json
10
package.json
|
@ -101,8 +101,6 @@
|
|||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@testing-library/react": "16.2.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/angular": "1.8.9",
|
||||
"@types/angular-route": "1.7.6",
|
||||
"@types/babel__core": "^7",
|
||||
"@types/babel__preset-env": "^7",
|
||||
"@types/chance": "^1.1.3",
|
||||
|
@ -214,7 +212,6 @@
|
|||
"mini-css-extract-plugin": "2.9.2",
|
||||
"msw": "2.7.0",
|
||||
"mutationobserver-shim": "0.3.7",
|
||||
"ngtemplate-loader": "2.1.0",
|
||||
"node-notifier": "10.0.1",
|
||||
"nx": "20.7.1",
|
||||
"openapi-types": "^12.1.3",
|
||||
|
@ -310,10 +307,6 @@
|
|||
"@visx/shape": "3.12.0",
|
||||
"@visx/tooltip": "3.12.0",
|
||||
"@welldone-software/why-did-you-render": "8.0.3",
|
||||
"angular": "1.8.3",
|
||||
"angular-bindonce": "0.3.1",
|
||||
"angular-route": "1.8.3",
|
||||
"angular-sanitize": "1.8.3",
|
||||
"ansicolor": "2.0.3",
|
||||
"baron": "3.0.3",
|
||||
"brace": "0.11.1",
|
||||
|
@ -412,8 +405,6 @@
|
|||
"swagger-ui-react": "5.20.5",
|
||||
"symbol-observable": "4.0.0",
|
||||
"systemjs": "6.15.1",
|
||||
"systemjs-cjs-extra": "0.2.1",
|
||||
"tether-drop": "https://github.com/torkelo/drop",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tslib": "2.8.1",
|
||||
"tween-functions": "^1.2.0",
|
||||
|
@ -426,7 +417,6 @@
|
|||
"resolutions": {
|
||||
"underscore": "1.13.7",
|
||||
"@types/slate": "0.47.11",
|
||||
"ngtemplate-loader/loader-utils": "^2.0.0",
|
||||
"semver@~7.0.0": "7.5.4",
|
||||
"semver@7.3.4": "7.5.4",
|
||||
"debug@npm:^0.7.2": "2.6.9",
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { IScope } from 'angular';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { Unsubscribable, Observable, Subscriber } from 'rxjs';
|
||||
import { filter } from 'rxjs/operators';
|
||||
|
@ -66,7 +65,7 @@ export class EventBusSrv implements EventBus, LegacyEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
on<T>(event: AppEvent<T> | string, handler: LegacyEventHandler<T>, scope?: IScope) {
|
||||
on<T>(event: AppEvent<T> | string, handler: LegacyEventHandler<T>) {
|
||||
// console.log(`Deprecated emitter function used (on), use $on`);
|
||||
|
||||
// need this wrapper to make old events compatible with old handlers
|
||||
|
@ -79,13 +78,6 @@ export class EventBusSrv implements EventBus, LegacyEmitter {
|
|||
} else {
|
||||
this.emitter.on(event.name, handler.wrapper);
|
||||
}
|
||||
|
||||
if (scope) {
|
||||
const unbind = scope.$on('$destroy', () => {
|
||||
this.off(event, handler);
|
||||
unbind();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
off<T>(event: AppEvent<T> | string, handler: LegacyEventHandler<T>) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { IScope } from 'angular';
|
||||
import { Unsubscribable, Observable } from 'rxjs';
|
||||
|
||||
/**
|
||||
|
@ -129,7 +128,7 @@ export interface LegacyEmitter {
|
|||
/**
|
||||
* @deprecated use $on
|
||||
*/
|
||||
on<T>(event: AppEvent<T> | string, handler: LegacyEventHandler<T>, scope?: IScope): void;
|
||||
on<T>(event: AppEvent<T> | string, handler: LegacyEventHandler<T>): void;
|
||||
|
||||
/**
|
||||
* @deprecated use $on
|
||||
|
|
|
@ -257,6 +257,7 @@ export { toOption } from './utils/selectUtils';
|
|||
export * as arrayUtils from './utils/arrayUtils';
|
||||
export { store, Store } from './utils/store';
|
||||
export { LocalStorageValueProvider } from './utils/LocalStorageValueProvider';
|
||||
export { throwIfAngular } from './utils/throwIfAngular';
|
||||
|
||||
// Tranformations
|
||||
export { standardTransformers } from './transformations/transformers';
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { ComponentType } from 'react';
|
||||
|
||||
import { throwIfAngular } from '../utils/throwIfAngular';
|
||||
|
||||
import { KeyValue } from './data';
|
||||
import { NavModel } from './navModel';
|
||||
import { PluginMeta, GrafanaPlugin, PluginIncludeType } from './plugin';
|
||||
|
@ -85,9 +87,7 @@ export class AppPlugin<T extends KeyValue = KeyValue> extends GrafanaPlugin<AppP
|
|||
}
|
||||
|
||||
setComponentsFromLegacyExports(pluginExports: System.Module) {
|
||||
if (pluginExports.ConfigCtrl) {
|
||||
this.angularConfigCtrl = pluginExports.ConfigCtrl;
|
||||
}
|
||||
throwIfAngular(pluginExports);
|
||||
|
||||
if (this.meta && this.meta.includes) {
|
||||
for (const include of this.meta.includes) {
|
||||
|
|
|
@ -214,7 +214,6 @@ export interface GrafanaConfig {
|
|||
geomapDisableCustomBaseLayer?: boolean;
|
||||
unifiedAlertingEnabled: boolean;
|
||||
unifiedAlerting: UnifiedAlertingConfig;
|
||||
angularSupportEnabled: boolean;
|
||||
feedbackLinksEnabled: boolean;
|
||||
supportBundlesEnabled: boolean;
|
||||
secureSocksDSProxyEnabled: boolean;
|
||||
|
|
|
@ -3,7 +3,9 @@ import { Observable } from 'rxjs';
|
|||
|
||||
import { DataSourceRef } from '@grafana/schema';
|
||||
|
||||
import { deprecationWarning } from '../utils/deprecationWarning';
|
||||
import { makeClassES5Compatible } from '../utils/makeClassES5Compatible';
|
||||
import { throwIfAngular } from '../utils/throwIfAngular';
|
||||
|
||||
import { ScopedVars } from './ScopedVars';
|
||||
import { WithAccessControlMetadata } from './accesscontrol';
|
||||
|
@ -50,12 +52,16 @@ export class DataSourcePlugin<
|
|||
return this;
|
||||
}
|
||||
|
||||
/** @deprecated it will be removed in a future release */
|
||||
setConfigCtrl(ConfigCtrl: any) {
|
||||
deprecationWarning('DataSourcePlugin', 'setConfigCtrl');
|
||||
this.angularConfigCtrl = ConfigCtrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @deprecated it will be removed in a future release */
|
||||
setQueryCtrl(QueryCtrl: any) {
|
||||
deprecationWarning('DataSourcePlugin', 'setQueryCtrl');
|
||||
this.components.QueryCtrl = QueryCtrl;
|
||||
return this;
|
||||
}
|
||||
|
@ -115,7 +121,7 @@ export class DataSourcePlugin<
|
|||
}
|
||||
|
||||
setComponentsFromLegacyExports(pluginExports: System.Module) {
|
||||
this.angularConfigCtrl = pluginExports.ConfigCtrl;
|
||||
throwIfAngular(pluginExports);
|
||||
|
||||
this.components.QueryCtrl = pluginExports.QueryCtrl;
|
||||
this.components.AnnotationsQueryCtrl = pluginExports.AnnotationsQueryCtrl;
|
||||
|
@ -161,7 +167,9 @@ export interface DataSourcePluginComponents<
|
|||
TOptions extends DataSourceJsonData = DataSourceJsonData,
|
||||
TSecureOptions = {},
|
||||
> {
|
||||
/** @deprecated it will be removed in a future release */
|
||||
QueryCtrl?: any;
|
||||
/** @deprecated it will be removed in a future release */
|
||||
AnnotationsQueryCtrl?: any;
|
||||
VariableQueryEditor?: any;
|
||||
QueryEditor?: ComponentType<QueryEditorProps<DSType, TQuery, TOptions>>;
|
||||
|
|
|
@ -63,34 +63,6 @@ export interface FeatureToggles {
|
|||
*/
|
||||
correlations?: boolean;
|
||||
/**
|
||||
* Migrate old angular panels to supported versions (graph, table-old, worldmap, etc)
|
||||
*/
|
||||
autoMigrateOldPanels?: boolean;
|
||||
/**
|
||||
* Migrate old graph panel to supported time series panel - broken out from autoMigrateOldPanels to enable granular tracking
|
||||
*/
|
||||
autoMigrateGraphPanel?: boolean;
|
||||
/**
|
||||
* Migrate old table panel to supported table panel - broken out from autoMigrateOldPanels to enable granular tracking
|
||||
*/
|
||||
autoMigrateTablePanel?: boolean;
|
||||
/**
|
||||
* Migrate old piechart panel to supported piechart panel - broken out from autoMigrateOldPanels to enable granular tracking
|
||||
*/
|
||||
autoMigratePiechartPanel?: boolean;
|
||||
/**
|
||||
* Migrate old worldmap panel to supported geomap panel - broken out from autoMigrateOldPanels to enable granular tracking
|
||||
*/
|
||||
autoMigrateWorldmapPanel?: boolean;
|
||||
/**
|
||||
* Migrate old stat panel to supported stat panel - broken out from autoMigrateOldPanels to enable granular tracking
|
||||
*/
|
||||
autoMigrateStatPanel?: boolean;
|
||||
/**
|
||||
* Dynamic flag to disable angular at runtime. The preferred method is to set `angular_support_enabled` to `false` in the [security] settings, which allows you to change the state at runtime.
|
||||
*/
|
||||
disableAngular?: boolean;
|
||||
/**
|
||||
* Allow elements nesting
|
||||
*/
|
||||
canvasPanelNesting?: boolean;
|
||||
|
|
|
@ -239,6 +239,7 @@ export class GrafanaPlugin<T extends PluginMeta = PluginMeta> {
|
|||
loadError?: boolean;
|
||||
|
||||
// Config control (app/datasource)
|
||||
/** @deprecated it will be removed in a future release */
|
||||
angularConfigCtrl?: any;
|
||||
|
||||
// Show configuration tabs on the plugin page
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import { PanelPlugin, PluginMeta, PluginType } from '@grafana/data';
|
||||
|
||||
import { throwIfAngular } from './throwIfAngular';
|
||||
|
||||
const plugin: PluginMeta = {
|
||||
id: 'test',
|
||||
name: 'Test',
|
||||
type: PluginType.datasource,
|
||||
info: {
|
||||
author: { name: 'Test', url: 'https://test.com' },
|
||||
description: 'Test',
|
||||
links: [],
|
||||
logos: { large: '', small: '' },
|
||||
screenshots: [],
|
||||
updated: '2021-01-01',
|
||||
version: '1.0.0',
|
||||
},
|
||||
module: 'test',
|
||||
baseUrl: 'test',
|
||||
};
|
||||
|
||||
describe('throwIfAngular', () => {
|
||||
it('should throw if angular plugin', () => {
|
||||
const underTest = { ...plugin, angular: { detected: true, hideDeprecation: false } };
|
||||
expect(() => throwIfAngular(underTest)).toThrow('Angular plugins are not supported');
|
||||
});
|
||||
|
||||
it('should throw if angular plugin', () => {
|
||||
const underTest = { ...plugin, angularDetected: true };
|
||||
expect(() => throwIfAngular(underTest)).toThrow('Angular plugins are not supported');
|
||||
});
|
||||
|
||||
it('should throw if angular panel', () => {
|
||||
const underTest = new PanelPlugin(null);
|
||||
underTest.angularPanelCtrl = {};
|
||||
expect(() => throwIfAngular(underTest)).toThrow('Angular plugins are not supported');
|
||||
});
|
||||
|
||||
it('should throw if angular module', () => {
|
||||
const underTest: System.Module = { PanelCtrl: {} };
|
||||
expect(() => throwIfAngular(underTest)).toThrow('Angular plugins are not supported');
|
||||
});
|
||||
|
||||
it('should throw if angular module', () => {
|
||||
const underTest: System.Module = { ConfigCtrl: {} };
|
||||
expect(() => throwIfAngular(underTest)).toThrow('Angular plugins are not supported');
|
||||
});
|
||||
|
||||
it('should not throw if not angular plugin', () => {
|
||||
const underTest = { ...plugin, angular: { detected: false, hideDeprecation: false } };
|
||||
expect(() => throwIfAngular(underTest)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw if not angular panel', () => {
|
||||
const underTest = new PanelPlugin(null);
|
||||
expect(() => throwIfAngular(underTest)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw if not angular module', () => {
|
||||
const underTest: System.Module = {};
|
||||
expect(() => throwIfAngular(underTest)).not.toThrow();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import { PanelPlugin } from '../panel/PanelPlugin';
|
||||
import { PluginMeta } from '../types/plugin';
|
||||
|
||||
export function throwIfAngular(module?: System.Module): void;
|
||||
export function throwIfAngular(panel?: PanelPlugin): void;
|
||||
export function throwIfAngular(plugin?: PluginMeta): void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function throwIfAngular(data?: any): void {
|
||||
const isAngularPlugin = data?.angular?.detected ?? data?.angularDetected ?? false;
|
||||
const isAngularPanel = data?.angularPanelCtrl ?? false;
|
||||
const isAngularModule = data.PanelCtrl ?? data?.ConfigCtrl ?? false;
|
||||
if (isAngularPlugin || isAngularPanel || isAngularModule) {
|
||||
throw new Error('Angular plugins are not supported');
|
||||
}
|
||||
}
|
|
@ -37,13 +37,8 @@ export const getMockPlugins = (amount: number): PluginMeta[] => {
|
|||
return plugins;
|
||||
};
|
||||
|
||||
export function getPanelPlugin(
|
||||
options: Partial<PanelPluginMeta>,
|
||||
reactPanel?: ComponentType<PanelProps>,
|
||||
angularPanel?: any
|
||||
): PanelPlugin {
|
||||
export function getPanelPlugin(options: Partial<PanelPluginMeta>, reactPanel?: ComponentType<PanelProps>): PanelPlugin {
|
||||
const plugin = new PanelPlugin(reactPanel!);
|
||||
plugin.angularPanelCtrl = angularPanel;
|
||||
plugin.meta = {
|
||||
id: options.id!,
|
||||
type: PluginType.panel,
|
||||
|
|
|
@ -73,7 +73,6 @@
|
|||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/react": "16.2.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/angular": "1.8.9",
|
||||
"@types/history": "4.7.11",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/lodash": "4.17.15",
|
||||
|
|
|
@ -78,7 +78,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
|||
feedbackLinksEnabled = true;
|
||||
disableLoginForm = false;
|
||||
defaultDatasource = ''; // UID
|
||||
angularSupportEnabled = false;
|
||||
authProxyEnabled = false;
|
||||
exploreEnabled = false;
|
||||
queryHistoryEnabled = false;
|
||||
|
@ -240,10 +239,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
|||
overrideFeatureTogglesFromUrl(this);
|
||||
overrideFeatureTogglesFromLocalStorage(this);
|
||||
|
||||
if (this.featureToggles.disableAngular) {
|
||||
this.angularSupportEnabled = false;
|
||||
}
|
||||
|
||||
// Creating theme after applying feature toggle overrides in case we need to toggle anything
|
||||
this.theme2 = getThemeById(this.bootData.user.theme);
|
||||
this.bootData.user.lightTheme = this.theme2.isLight;
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
/**
|
||||
* Used to enable rendering of Angular components within a
|
||||
* React component without losing proper typings.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class Component extends PureComponent<Props> {
|
||||
* element: HTMLElement;
|
||||
* angularComponent: AngularComponent;
|
||||
*
|
||||
* componentDidMount() {
|
||||
* const template = '<angular-component />' // angular template here;
|
||||
* const scopeProps = { ctrl: angularController }; // angular scope properties here
|
||||
* const loader = getAngularLoader();
|
||||
* this.angularComponent = loader.load(this.element, scopeProps, template);
|
||||
* }
|
||||
*
|
||||
* componentWillUnmount() {
|
||||
* if (this.angularComponent) {
|
||||
* this.angularComponent.destroy();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* render() {
|
||||
* return (
|
||||
* <div ref={element => (this.element = element)} />
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface AngularComponent {
|
||||
/**
|
||||
* Should be called when the React component will unmount.
|
||||
*/
|
||||
destroy(): void;
|
||||
/**
|
||||
* Can be used to trigger a re-render of the Angular component.
|
||||
*/
|
||||
digest(): void;
|
||||
/**
|
||||
* Used to access the Angular scope from the React component.
|
||||
*/
|
||||
getScope(): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to load an Angular component from the context of a React component.
|
||||
* Please see the {@link AngularComponent} for a proper example.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface AngularLoader {
|
||||
/**
|
||||
*
|
||||
* @param elem - the element that the Angular component will be loaded into.
|
||||
* @param scopeProps - values that will be accessed via the Angular scope.
|
||||
* @param template - template used by the Angular component.
|
||||
*/
|
||||
load(elem: any, scopeProps: any, template: string): AngularComponent;
|
||||
}
|
||||
|
||||
let instance: AngularLoader;
|
||||
|
||||
/**
|
||||
* Used during startup by Grafana to set the AngularLoader so it is available
|
||||
* via the {@link getAngularLoader} to the rest of the application.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function setAngularLoader(v: AngularLoader) {
|
||||
instance = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to retrieve the {@link AngularLoader} that enables the use of Angular
|
||||
* components within a React component.
|
||||
*
|
||||
* Please see the {@link AngularComponent} for a proper example.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function getAngularLoader(): AngularLoader {
|
||||
return instance;
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
export * from './backendSrv';
|
||||
export * from './AngularLoader';
|
||||
export * from './dataSourceSrv';
|
||||
export * from './LocationSrv';
|
||||
export * from './EchoSrv';
|
||||
export * from './templateSrv';
|
||||
export * from './legacyAngularInjector';
|
||||
export * from './live';
|
||||
export * from './LocationService';
|
||||
export * from './appEvents';
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
import { auto } from 'angular';
|
||||
|
||||
let singleton: auto.IInjectorService;
|
||||
|
||||
/**
|
||||
* Used during startup by Grafana to temporarily expose the angular injector to
|
||||
* pure javascript plugins using {@link getLegacyAngularInjector}.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export const setLegacyAngularInjector = (instance: auto.IInjectorService) => {
|
||||
singleton = instance;
|
||||
};
|
||||
|
||||
/**
|
||||
* WARNING: this function provides a temporary way for plugins to access anything in the
|
||||
* angular injector. While the migration from angular to react continues, there are a few
|
||||
* options that do not yet have good alternatives. Note that use of this function will
|
||||
* be removed in the future.
|
||||
*
|
||||
* @beta
|
||||
*/
|
||||
export const getLegacyAngularInjector = (): auto.IInjectorService => singleton;
|
|
@ -4,7 +4,6 @@ import { useTheme2 } from '../ThemeContext';
|
|||
|
||||
import { getAccessibilityStyles } from './accessibility';
|
||||
import { getAlertingStyles } from './alerting';
|
||||
import { getAgularPanelStyles } from './angularPanelStyles';
|
||||
import { getCardStyles } from './card';
|
||||
import { getCodeStyles } from './code';
|
||||
import { getDashboardGridStyles } from './dashboardGrid';
|
||||
|
@ -39,7 +38,6 @@ export function GlobalStyles(props: GlobalStylesProps) {
|
|||
<Global
|
||||
styles={[
|
||||
getAccessibilityStyles(theme),
|
||||
getAgularPanelStyles(theme),
|
||||
getAlertingStyles(theme),
|
||||
getCodeStyles(theme),
|
||||
getDashDiffStyles(theme),
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
import { css } from '@emotion/react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
export function getAgularPanelStyles(theme: GrafanaTheme2) {
|
||||
return css({
|
||||
'.panel-options-group': {
|
||||
borderBottom: `1px solid ${theme.colors.border.weak}`,
|
||||
},
|
||||
|
||||
'.panel-options-group__header': {
|
||||
padding: theme.spacing(1, 2, 1, 1),
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
fontWeight: 500,
|
||||
color: theme.colors.text.primary,
|
||||
|
||||
'&:hover': {
|
||||
background: theme.colors.emphasize(theme.colors.background.primary, 0.03),
|
||||
},
|
||||
},
|
||||
|
||||
'.panel-options-group__icon': {
|
||||
color: theme.colors.text.secondary,
|
||||
marginRight: theme.spacing(1),
|
||||
padding: theme.spacing(0, 0.9, 0, 0.6),
|
||||
},
|
||||
|
||||
'.panel-options-group__title': {
|
||||
position: 'relative',
|
||||
},
|
||||
|
||||
'.panel-options-group__body': {
|
||||
padding: theme.spacing(1, 2, 1, 4),
|
||||
},
|
||||
});
|
||||
}
|
|
@ -208,7 +208,6 @@ type FrontendSettingsDTO struct {
|
|||
ExternalUserMngAnalytics bool `json:"externalUserMngAnalytics"`
|
||||
ExternalUserMngAnalyticsParams string `json:"externalUserMngAnalyticsParams"`
|
||||
ViewersCanEdit bool `json:"viewersCanEdit"`
|
||||
AngularSupportEnabled bool `json:"angularSupportEnabled"`
|
||||
DisableSanitizeHtml bool `json:"disableSanitizeHtml"`
|
||||
TrustedTypesDefaultPolicyEnabled bool `json:"trustedTypesDefaultPolicyEnabled"`
|
||||
CSPReportOnlyEnabled bool `json:"cspReportOnlyEnabled"`
|
||||
|
|
|
@ -230,7 +230,6 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
|||
ExternalUserMngAnalyticsParams: hs.Cfg.ExternalUserMngAnalyticsParams,
|
||||
//nolint:staticcheck // ViewersCanEdit is deprecated but still used for backward compatibility
|
||||
ViewersCanEdit: hs.Cfg.ViewersCanEdit,
|
||||
AngularSupportEnabled: hs.Cfg.AngularSupportEnabled,
|
||||
DisableSanitizeHtml: hs.Cfg.DisableSanitizeHtml,
|
||||
TrustedTypesDefaultPolicyEnabled: trustedTypesDefaultPolicyEnabled,
|
||||
CSPReportOnlyEnabled: hs.Cfg.CSPReportOnlyEnabled,
|
||||
|
|
|
@ -25,7 +25,6 @@ type PluginManagementCfg struct {
|
|||
|
||||
Features Features
|
||||
|
||||
AngularSupportEnabled bool
|
||||
HideAngularDeprecation []string
|
||||
}
|
||||
|
||||
|
@ -40,7 +39,7 @@ type Features struct {
|
|||
|
||||
// NewPluginManagementCfg returns a new PluginManagementCfg.
|
||||
func NewPluginManagementCfg(devMode bool, pluginsPath string, pluginSettings setting.PluginSettings, pluginsAllowUnsigned []string,
|
||||
pluginsCDNURLTemplate string, appURL string, features Features, angularSupportEnabled bool,
|
||||
pluginsCDNURLTemplate string, appURL string, features Features,
|
||||
grafanaComAPIURL string, disablePlugins []string, hideAngularDeprecation []string, forwardHostEnvVars []string, grafanaComAPIToken string,
|
||||
) *PluginManagementCfg {
|
||||
return &PluginManagementCfg{
|
||||
|
@ -53,7 +52,6 @@ func NewPluginManagementCfg(devMode bool, pluginsPath string, pluginSettings set
|
|||
GrafanaComAPIURL: grafanaComAPIURL,
|
||||
GrafanaAppURL: appURL,
|
||||
Features: features,
|
||||
AngularSupportEnabled: angularSupportEnabled,
|
||||
HideAngularDeprecation: hideAngularDeprecation,
|
||||
ForwardHostEnvVars: forwardHostEnvVars,
|
||||
GrafanaComAPIToken: grafanaComAPIToken,
|
||||
|
|
|
@ -109,7 +109,7 @@ func (a *AngularDetector) Validate(ctx context.Context, p *plugins.Plugin) error
|
|||
}
|
||||
|
||||
// Do not initialize plugins if they're using Angular and Angular support is disabled
|
||||
if p.Angular.Detected && !a.cfg.AngularSupportEnabled {
|
||||
if p.Angular.Detected {
|
||||
a.log.Error("Refusing to initialize plugin because it's using Angular, which has been disabled", "pluginId", p.ID)
|
||||
return (&plugins.Error{
|
||||
PluginID: p.ID,
|
||||
|
|
|
@ -136,70 +136,6 @@ func (b *FeatureFlagAPIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.AP
|
|||
},
|
||||
},
|
||||
},
|
||||
Patch: &spec3.Operation{
|
||||
OperationProps: spec3.OperationProps{
|
||||
Tags: tags,
|
||||
Summary: "Update individual toggles",
|
||||
Description: "Patch some of the toggles (keyed by the toggle name)",
|
||||
RequestBody: &spec3.RequestBody{
|
||||
RequestBodyProps: spec3.RequestBodyProps{
|
||||
Required: true,
|
||||
Description: "flags to change",
|
||||
Content: map[string]*spec3.MediaType{
|
||||
"application/json": {
|
||||
MediaTypeProps: spec3.MediaTypeProps{
|
||||
Schema: &stateSchema,
|
||||
Example: &v0alpha1.ResolvedToggleState{
|
||||
Enabled: map[string]bool{
|
||||
featuremgmt.FlagAutoMigrateOldPanels: true,
|
||||
featuremgmt.FlagAngularDeprecationUI: false,
|
||||
},
|
||||
},
|
||||
Examples: map[string]*spec3.Example{
|
||||
"enable-auto-migrate": {
|
||||
ExampleProps: spec3.ExampleProps{
|
||||
Summary: "enable auto-migrate panels",
|
||||
Description: "enable description",
|
||||
Value: &v0alpha1.ResolvedToggleState{
|
||||
Enabled: map[string]bool{
|
||||
featuremgmt.FlagAutoMigrateOldPanels: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"disable-auto-migrate": {
|
||||
ExampleProps: spec3.ExampleProps{
|
||||
Summary: "disable auto-migrate panels",
|
||||
Description: "disable description",
|
||||
Value: &v0alpha1.ResolvedToggleState{
|
||||
Enabled: map[string]bool{
|
||||
featuremgmt.FlagAutoMigrateOldPanels: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Responses: &spec3.Responses{
|
||||
ResponsesProps: spec3.ResponsesProps{
|
||||
StatusCodeResponses: map[int]*spec3.Response{
|
||||
200: {
|
||||
ResponseProps: spec3.ResponseProps{
|
||||
Content: map[string]*spec3.MediaType{
|
||||
"application/json": {},
|
||||
},
|
||||
Description: "OK",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Handler: b.handleCurrentStatus,
|
||||
},
|
||||
|
|
|
@ -92,56 +92,6 @@ var (
|
|||
Expression: "true", // enabled by default
|
||||
AllowSelfServe: true,
|
||||
},
|
||||
{
|
||||
Name: "autoMigrateOldPanels",
|
||||
Description: "Migrate old angular panels to supported versions (graph, table-old, worldmap, etc)",
|
||||
Stage: FeatureStagePublicPreview,
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaDatavizSquad,
|
||||
},
|
||||
{
|
||||
Name: "autoMigrateGraphPanel",
|
||||
Description: "Migrate old graph panel to supported time series panel - broken out from autoMigrateOldPanels to enable granular tracking",
|
||||
Stage: FeatureStagePublicPreview,
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaDatavizSquad,
|
||||
},
|
||||
{
|
||||
Name: "autoMigrateTablePanel",
|
||||
Description: "Migrate old table panel to supported table panel - broken out from autoMigrateOldPanels to enable granular tracking",
|
||||
Stage: FeatureStagePublicPreview,
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaDatavizSquad,
|
||||
},
|
||||
{
|
||||
Name: "autoMigratePiechartPanel",
|
||||
Description: "Migrate old piechart panel to supported piechart panel - broken out from autoMigrateOldPanels to enable granular tracking",
|
||||
Stage: FeatureStagePublicPreview,
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaDatavizSquad,
|
||||
},
|
||||
{
|
||||
Name: "autoMigrateWorldmapPanel",
|
||||
Description: "Migrate old worldmap panel to supported geomap panel - broken out from autoMigrateOldPanels to enable granular tracking",
|
||||
Stage: FeatureStagePublicPreview,
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaDatavizSquad,
|
||||
},
|
||||
{
|
||||
Name: "autoMigrateStatPanel",
|
||||
Description: "Migrate old stat panel to supported stat panel - broken out from autoMigrateOldPanels to enable granular tracking",
|
||||
Stage: FeatureStagePublicPreview,
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaDatavizSquad,
|
||||
},
|
||||
{
|
||||
Name: "disableAngular",
|
||||
Description: "Dynamic flag to disable angular at runtime. The preferred method is to set `angular_support_enabled` to `false` in the [security] settings, which allows you to change the state at runtime.",
|
||||
Stage: FeatureStagePublicPreview,
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaDatavizSquad,
|
||||
HideFromAdminPage: true,
|
||||
},
|
||||
{
|
||||
Name: "canvasPanelNesting",
|
||||
Description: "Allow elements nesting",
|
||||
|
|
|
@ -42,17 +42,11 @@ func ProvideManagerService(cfg *setting.Cfg) (*FeatureManager, error) {
|
|||
for key, val := range flags {
|
||||
_, ok := mgmt.flags[key]
|
||||
if !ok {
|
||||
switch key {
|
||||
// renamed the flag so it supports more panels
|
||||
case "autoMigrateGraphPanels":
|
||||
key = FlagAutoMigrateOldPanels
|
||||
default:
|
||||
mgmt.flags[key] = &FeatureFlag{
|
||||
Name: key,
|
||||
Stage: FeatureStageUnknown,
|
||||
}
|
||||
mgmt.warnings[key] = "unknown flag in config"
|
||||
mgmt.flags[key] = &FeatureFlag{
|
||||
Name: key,
|
||||
Stage: FeatureStageUnknown,
|
||||
}
|
||||
mgmt.warnings[key] = "unknown flag in config"
|
||||
}
|
||||
mgmt.startup[key] = val
|
||||
}
|
||||
|
|
|
@ -9,13 +9,6 @@ lokiExperimentalStreaming,experimental,@grafana/observability-logs,false,false,f
|
|||
featureHighlights,GA,@grafana/grafana-as-code,false,false,false
|
||||
storage,experimental,@grafana/search-and-storage,false,false,false
|
||||
correlations,GA,@grafana/dataviz-squad,false,false,false
|
||||
autoMigrateOldPanels,preview,@grafana/dataviz-squad,false,false,true
|
||||
autoMigrateGraphPanel,preview,@grafana/dataviz-squad,false,false,true
|
||||
autoMigrateTablePanel,preview,@grafana/dataviz-squad,false,false,true
|
||||
autoMigratePiechartPanel,preview,@grafana/dataviz-squad,false,false,true
|
||||
autoMigrateWorldmapPanel,preview,@grafana/dataviz-squad,false,false,true
|
||||
autoMigrateStatPanel,preview,@grafana/dataviz-squad,false,false,true
|
||||
disableAngular,preview,@grafana/dataviz-squad,false,false,true
|
||||
canvasPanelNesting,experimental,@grafana/dataviz-squad,false,false,true
|
||||
disableSecretsCompatibility,experimental,@grafana/hosted-grafana-team,false,true,false
|
||||
logRequestsInstrumentedAsUnknown,experimental,@grafana/hosted-grafana-team,false,false,false
|
||||
|
|
|
|
@ -47,34 +47,6 @@ const (
|
|||
// Correlations page
|
||||
FlagCorrelations = "correlations"
|
||||
|
||||
// FlagAutoMigrateOldPanels
|
||||
// Migrate old angular panels to supported versions (graph, table-old, worldmap, etc)
|
||||
FlagAutoMigrateOldPanels = "autoMigrateOldPanels"
|
||||
|
||||
// FlagAutoMigrateGraphPanel
|
||||
// Migrate old graph panel to supported time series panel - broken out from autoMigrateOldPanels to enable granular tracking
|
||||
FlagAutoMigrateGraphPanel = "autoMigrateGraphPanel"
|
||||
|
||||
// FlagAutoMigrateTablePanel
|
||||
// Migrate old table panel to supported table panel - broken out from autoMigrateOldPanels to enable granular tracking
|
||||
FlagAutoMigrateTablePanel = "autoMigrateTablePanel"
|
||||
|
||||
// FlagAutoMigratePiechartPanel
|
||||
// Migrate old piechart panel to supported piechart panel - broken out from autoMigrateOldPanels to enable granular tracking
|
||||
FlagAutoMigratePiechartPanel = "autoMigratePiechartPanel"
|
||||
|
||||
// FlagAutoMigrateWorldmapPanel
|
||||
// Migrate old worldmap panel to supported geomap panel - broken out from autoMigrateOldPanels to enable granular tracking
|
||||
FlagAutoMigrateWorldmapPanel = "autoMigrateWorldmapPanel"
|
||||
|
||||
// FlagAutoMigrateStatPanel
|
||||
// Migrate old stat panel to supported stat panel - broken out from autoMigrateOldPanels to enable granular tracking
|
||||
FlagAutoMigrateStatPanel = "autoMigrateStatPanel"
|
||||
|
||||
// FlagDisableAngular
|
||||
// Dynamic flag to disable angular at runtime. The preferred method is to set `angular_support_enabled` to `false` in the [security] settings, which allows you to change the state at runtime.
|
||||
FlagDisableAngular = "disableAngular"
|
||||
|
||||
// FlagCanvasPanelNesting
|
||||
// Allow elements nesting
|
||||
FlagCanvasPanelNesting = "canvasPanelNesting"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1168,15 +1168,15 @@ func TestLoader_AngularClass(t *testing.T) {
|
|||
},
|
||||
}
|
||||
// if angularDetected = true, it means that the detection has run
|
||||
l := newLoaderWithOpts(t, &config.PluginManagementCfg{AngularSupportEnabled: true}, loaderDepOpts{
|
||||
l := newLoaderWithOpts(t, &config.PluginManagementCfg{}, loaderDepOpts{
|
||||
angularInspector: angularinspector.AlwaysAngularFakeInspector,
|
||||
})
|
||||
p, err := l.Load(context.Background(), fakePluginSource)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, p, 1, "should load 1 plugin")
|
||||
if tc.expAngularDetectionRun {
|
||||
require.True(t, p[0].Angular.Detected, "angular detection should run")
|
||||
require.Empty(t, p, "plugin shouldn't have been loaded")
|
||||
} else {
|
||||
require.Len(t, p, 1, "should load 1 plugin")
|
||||
require.False(t, p[0].Angular.Detected, "angular detection should not run")
|
||||
}
|
||||
})
|
||||
|
@ -1196,8 +1196,8 @@ func TestLoader_Load_Angular(t *testing.T) {
|
|||
name string
|
||||
cfg *config.PluginManagementCfg
|
||||
}{
|
||||
{name: "angular support enabled", cfg: &config.PluginManagementCfg{AngularSupportEnabled: true}},
|
||||
{name: "angular support disabled", cfg: &config.PluginManagementCfg{AngularSupportEnabled: false}},
|
||||
{name: "angular support enabled", cfg: &config.PluginManagementCfg{}},
|
||||
{name: "angular support disabled", cfg: &config.PluginManagementCfg{}},
|
||||
} {
|
||||
t.Run(cfgTc.name, func(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
|
@ -1209,7 +1209,7 @@ func TestLoader_Load_Angular(t *testing.T) {
|
|||
name: "angular plugin",
|
||||
angularInspector: angularinspector.AlwaysAngularFakeInspector,
|
||||
// angular plugins should load only if allowed by the cfg
|
||||
shouldLoad: cfgTc.cfg.AngularSupportEnabled,
|
||||
shouldLoad: false,
|
||||
},
|
||||
{
|
||||
name: "non angular plugin",
|
||||
|
@ -1243,22 +1243,18 @@ func TestLoader_HideAngularDeprecation(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
cfg *config.PluginManagementCfg
|
||||
expHideAngularDeprecation bool
|
||||
name string
|
||||
cfg *config.PluginManagementCfg
|
||||
}{
|
||||
{name: "with plugin id in HideAngularDeprecation list", cfg: &config.PluginManagementCfg{
|
||||
AngularSupportEnabled: true,
|
||||
HideAngularDeprecation: []string{"one-app", "two-panel", "test-datasource", "three-datasource"},
|
||||
}, expHideAngularDeprecation: true},
|
||||
}},
|
||||
{name: "without plugin id in HideAngularDeprecation list", cfg: &config.PluginManagementCfg{
|
||||
AngularSupportEnabled: true,
|
||||
HideAngularDeprecation: []string{"one-app", "two-panel", "three-datasource"},
|
||||
}, expHideAngularDeprecation: false},
|
||||
}},
|
||||
{name: "with empty HideAngularDeprecation", cfg: &config.PluginManagementCfg{
|
||||
AngularSupportEnabled: true,
|
||||
HideAngularDeprecation: nil,
|
||||
}, expHideAngularDeprecation: false},
|
||||
}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
l := newLoaderWithOpts(t, tc.cfg, loaderDepOpts{
|
||||
|
@ -1266,8 +1262,7 @@ func TestLoader_HideAngularDeprecation(t *testing.T) {
|
|||
})
|
||||
p, err := l.Load(context.Background(), fakePluginSource)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, p, 1, "should load 1 plugin")
|
||||
require.Equal(t, tc.expHideAngularDeprecation, p[0].Angular.HideDeprecation)
|
||||
require.Empty(t, p, "plugin shouldn't have been loaded")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ func ProvidePluginManagementConfig(cfg *setting.Cfg, settingProvider setting.Pro
|
|||
PluginsCDNSyncLoaderEnabled: features.IsEnabledGlobally(featuremgmt.FlagPluginsCDNSyncLoader),
|
||||
LocalizationForPlugins: features.IsEnabledGlobally(featuremgmt.FlagLocalizationForPlugins),
|
||||
},
|
||||
cfg.AngularSupportEnabled,
|
||||
cfg.GrafanaComAPIURL,
|
||||
cfg.DisablePlugins,
|
||||
cfg.HideAngularDeprecation,
|
||||
|
|
|
@ -145,7 +145,6 @@ func verifyCorePluginCatalogue(t *testing.T, ctx context.Context, ps *pluginstor
|
|||
"gauge": {},
|
||||
"geomap": {},
|
||||
"gettingstarted": {},
|
||||
"graph": {},
|
||||
"heatmap": {},
|
||||
"histogram": {},
|
||||
"live": {},
|
||||
|
@ -161,7 +160,6 @@ func verifyCorePluginCatalogue(t *testing.T, ctx context.Context, ps *pluginstor
|
|||
"state-timeline": {},
|
||||
"status-history": {},
|
||||
"table": {},
|
||||
"table-old": {},
|
||||
"text": {},
|
||||
"timeseries": {},
|
||||
"trend": {},
|
||||
|
|
|
@ -176,7 +176,6 @@ type Cfg struct {
|
|||
CSPReportOnlyEnabled bool
|
||||
// CSPReportOnlyTemplate contains the Content Security Policy Report Only template.
|
||||
CSPReportOnlyTemplate string
|
||||
AngularSupportEnabled bool
|
||||
EnableFrontendSandboxForPlugins []string
|
||||
DisableGravatar bool
|
||||
DataProxyWhiteList map[string]bool
|
||||
|
@ -1578,7 +1577,6 @@ func readSecuritySettings(iniFile *ini.File, cfg *Cfg) error {
|
|||
cfg.StrictTransportSecurityMaxAge = security.Key("strict_transport_security_max_age_seconds").MustInt(86400)
|
||||
cfg.StrictTransportSecurityPreload = security.Key("strict_transport_security_preload").MustBool(false)
|
||||
cfg.StrictTransportSecuritySubDomains = security.Key("strict_transport_security_subdomains").MustBool(false)
|
||||
cfg.AngularSupportEnabled = security.Key("angular_support_enabled").MustBool(false)
|
||||
cfg.CSPEnabled = security.Key("content_security_policy").MustBool(false)
|
||||
cfg.CSPTemplate = security.Key("content_security_policy_template").MustString("")
|
||||
cfg.CSPReportOnlyEnabled = security.Key("content_security_policy_report_only").MustBool(false)
|
||||
|
|
|
@ -36,7 +36,7 @@ const (
|
|||
defaultPassword = "password"
|
||||
)
|
||||
|
||||
var updateSnapshotFlag = false
|
||||
var updateSnapshotFlag = true
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testsuite.Run(m)
|
||||
|
|
|
@ -964,52 +964,6 @@
|
|||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Graph (old)",
|
||||
"type": "panel",
|
||||
"id": "graph",
|
||||
"enabled": true,
|
||||
"pinned": false,
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Labs",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"description": "The old default graph panel",
|
||||
"links": [
|
||||
{
|
||||
"name": "Raise issue",
|
||||
"url": "https://github.com/grafana/grafana/issues/new"
|
||||
}
|
||||
],
|
||||
"logos": {
|
||||
"small": "public/app/plugins/panel/graph/img/icn-graph-panel.svg",
|
||||
"large": "public/app/plugins/panel/graph/img/icn-graph-panel.svg"
|
||||
},
|
||||
"build": {},
|
||||
"screenshots": null,
|
||||
"version": "",
|
||||
"updated": "",
|
||||
"keywords": null
|
||||
},
|
||||
"dependencies": {
|
||||
"grafanaDependency": "",
|
||||
"grafanaVersion": "*",
|
||||
"plugins": [],
|
||||
"extensions": {
|
||||
"exposedComponents": []
|
||||
}
|
||||
},
|
||||
"latestVersion": "",
|
||||
"hasUpdate": false,
|
||||
"defaultNavUrl": "/plugins/graph/",
|
||||
"category": "",
|
||||
"state": "deprecated",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Graphite",
|
||||
"type": "datasource",
|
||||
|
@ -2052,52 +2006,6 @@
|
|||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Table (old)",
|
||||
"type": "panel",
|
||||
"id": "table-old",
|
||||
"enabled": true,
|
||||
"pinned": false,
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Labs",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"description": "Table Panel for Grafana",
|
||||
"links": [
|
||||
{
|
||||
"name": "Raise issue",
|
||||
"url": "https://github.com/grafana/grafana/issues/new"
|
||||
}
|
||||
],
|
||||
"logos": {
|
||||
"small": "public/app/plugins/panel/table-old/img/icn-table-panel.svg",
|
||||
"large": "public/app/plugins/panel/table-old/img/icn-table-panel.svg"
|
||||
},
|
||||
"build": {},
|
||||
"screenshots": null,
|
||||
"version": "",
|
||||
"updated": "",
|
||||
"keywords": null
|
||||
},
|
||||
"dependencies": {
|
||||
"grafanaDependency": "",
|
||||
"grafanaVersion": "*",
|
||||
"plugins": [],
|
||||
"extensions": {
|
||||
"exposedComponents": []
|
||||
}
|
||||
},
|
||||
"latestVersion": "",
|
||||
"hasUpdate": false,
|
||||
"defaultNavUrl": "/plugins/table-old/",
|
||||
"category": "",
|
||||
"state": "deprecated",
|
||||
"signature": "internal",
|
||||
"signatureType": "",
|
||||
"signatureOrg": "",
|
||||
"angularDetected": false
|
||||
},
|
||||
{
|
||||
"name": "Tempo",
|
||||
"type": "datasource",
|
||||
|
|
|
@ -15,7 +15,6 @@ import { ErrorBoundaryAlert, PortalContainer, TimeRangeProvider } from '@grafana
|
|||
import { getAppRoutes } from 'app/routes/routes';
|
||||
import { store } from 'app/store/store';
|
||||
|
||||
import { loadAndInitAngularIfEnabled } from './angular/loadAndInitAngularIfEnabled';
|
||||
import { GrafanaApp } from './app';
|
||||
import { ExtensionSidebarContextProvider } from './core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider';
|
||||
import { GlobalStylesWrapper } from './core/components/AppChrome/ExtensionSidebar/GlobalStylesWrapper';
|
||||
|
@ -63,7 +62,6 @@ export class AppWrapper extends Component<AppWrapperProps, AppWrapperState> {
|
|||
}
|
||||
|
||||
async componentDidMount() {
|
||||
await loadAndInitAngularIfEnabled();
|
||||
this.setState({ ready: true });
|
||||
$('.preloader').remove();
|
||||
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
import 'angular';
|
||||
import 'angular-route';
|
||||
import 'angular-sanitize';
|
||||
import 'angular-bindonce';
|
||||
import 'vendor/bootstrap/bootstrap';
|
||||
|
||||
import angular from 'angular'; // eslint-disable-line no-duplicate-imports
|
||||
import { extend } from 'lodash';
|
||||
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { coreModule, angularModules } from 'app/angular/core_module';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { config } from 'app/core/config';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { DashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
|
||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { setAngularPanelReactWrapper } from 'app/features/plugins/importPanelPlugin';
|
||||
import { SystemJS } from 'app/features/plugins/loader/systemjs';
|
||||
import { buildImportMap } from 'app/features/plugins/loader/utils';
|
||||
import * as sdk from 'app/plugins/sdk';
|
||||
|
||||
import { registerAngularDirectives } from './angular_wrappers';
|
||||
import { initAngularRoutingBridge } from './bridgeReactAngularRouting';
|
||||
import { monkeyPatchInjectorWithPreAssignedBindings } from './injectorMonkeyPatch';
|
||||
import { getAngularPanelReactWrapper } from './panel/AngularPanelReactWrapper';
|
||||
import { promiseToDigest } from './promiseToDigest';
|
||||
import { registerComponents } from './registerComponents';
|
||||
|
||||
// Angular plugin dependencies map
|
||||
const importMap = {
|
||||
angular: {
|
||||
...angular,
|
||||
default: angular,
|
||||
},
|
||||
'app/core/core_module': {
|
||||
default: coreModule,
|
||||
__useDefault: true,
|
||||
},
|
||||
'app/core/core': {
|
||||
appEvents: appEvents,
|
||||
contextSrv: contextSrv,
|
||||
coreModule: coreModule,
|
||||
},
|
||||
'app/plugins/sdk': sdk,
|
||||
'app/core/utils/promiseToDigest': { promiseToDigest },
|
||||
} as Record<string, System.Module>;
|
||||
|
||||
export class AngularApp {
|
||||
ngModuleDependencies: any[];
|
||||
preBootModules: any[];
|
||||
registerFunctions: any;
|
||||
|
||||
constructor() {
|
||||
this.preBootModules = [];
|
||||
this.ngModuleDependencies = [];
|
||||
this.registerFunctions = {};
|
||||
}
|
||||
|
||||
init() {
|
||||
const app = angular.module('grafana', []);
|
||||
|
||||
setAngularPanelReactWrapper(getAngularPanelReactWrapper);
|
||||
|
||||
app.config([
|
||||
'$controllerProvider',
|
||||
'$compileProvider',
|
||||
'$filterProvider',
|
||||
'$httpProvider',
|
||||
'$provide',
|
||||
'$sceDelegateProvider',
|
||||
(
|
||||
$controllerProvider: angular.IControllerProvider,
|
||||
$compileProvider: angular.ICompileProvider,
|
||||
$filterProvider: angular.IFilterProvider,
|
||||
$httpProvider: angular.IHttpProvider,
|
||||
$provide: angular.auto.IProvideService,
|
||||
$sceDelegateProvider: angular.ISCEDelegateProvider
|
||||
) => {
|
||||
if (config.buildInfo.env !== 'development') {
|
||||
$compileProvider.debugInfoEnabled(false);
|
||||
}
|
||||
|
||||
$httpProvider.useApplyAsync(true);
|
||||
|
||||
if (Boolean(config.pluginsCDNBaseURL)) {
|
||||
$sceDelegateProvider.trustedResourceUrlList(['self', `${config.pluginsCDNBaseURL}/**`]);
|
||||
}
|
||||
|
||||
this.registerFunctions.controller = $controllerProvider.register;
|
||||
this.registerFunctions.directive = $compileProvider.directive;
|
||||
this.registerFunctions.factory = $provide.factory;
|
||||
this.registerFunctions.service = $provide.service;
|
||||
this.registerFunctions.filter = $filterProvider.register;
|
||||
|
||||
$provide.decorator('$http', [
|
||||
'$delegate',
|
||||
'$templateCache',
|
||||
($delegate: any, $templateCache: any) => {
|
||||
const get = $delegate.get;
|
||||
$delegate.get = (url: string, config: any) => {
|
||||
if (url.match(/\.html$/)) {
|
||||
// some template's already exist in the cache
|
||||
if (!$templateCache.get(url)) {
|
||||
url += '?v=' + new Date().getTime();
|
||||
}
|
||||
}
|
||||
return get(url, config);
|
||||
};
|
||||
return $delegate;
|
||||
},
|
||||
]);
|
||||
},
|
||||
]);
|
||||
|
||||
this.ngModuleDependencies = ['grafana.core', 'ngSanitize', 'grafana', 'pasvaz.bindonce', 'react'];
|
||||
|
||||
// makes it possible to add dynamic stuff
|
||||
angularModules.forEach((m: angular.IModule) => {
|
||||
this.useModule(m);
|
||||
});
|
||||
|
||||
// register react angular wrappers
|
||||
angular.module('grafana.services').service('dashboardLoaderSrv', DashboardLoaderSrv);
|
||||
|
||||
coreModule.factory('timeSrv', () => getTimeSrv());
|
||||
coreModule.factory('templateSrv', () => getTemplateSrv());
|
||||
|
||||
registerAngularDirectives();
|
||||
registerComponents();
|
||||
initAngularRoutingBridge();
|
||||
|
||||
const imports = buildImportMap(importMap);
|
||||
// pass the map of module names so systemjs can resolve them
|
||||
SystemJS.addImportMap({ imports });
|
||||
|
||||
// disable tool tip animation
|
||||
$.fn.tooltip.defaults.animation = false;
|
||||
}
|
||||
|
||||
useModule(module: angular.IModule) {
|
||||
if (this.preBootModules) {
|
||||
this.preBootModules.push(module);
|
||||
} else {
|
||||
extend(module, this.registerFunctions);
|
||||
}
|
||||
this.ngModuleDependencies.push(module.name);
|
||||
return module;
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
const injector = angular.bootstrap(document.getElementById('ngRoot')!, this.ngModuleDependencies);
|
||||
|
||||
monkeyPatchInjectorWithPreAssignedBindings(injector);
|
||||
|
||||
injector.invoke(() => {
|
||||
this.preBootModules.forEach((module) => {
|
||||
extend(module, this.registerFunctions);
|
||||
});
|
||||
|
||||
// I don't know
|
||||
return () => {};
|
||||
});
|
||||
|
||||
return injector;
|
||||
}
|
||||
}
|
|
@ -1,209 +0,0 @@
|
|||
import { HistoryWrapper, locationService, setLocationService } from '@grafana/runtime';
|
||||
|
||||
import { AngularLocationWrapper } from './AngularLocationWrapper';
|
||||
|
||||
// The methods in this file are deprecated
|
||||
// Stub the deprecation warning here to prevent polluting the test output
|
||||
jest.mock('@grafana/data', () => ({
|
||||
...jest.requireActual('@grafana/data'),
|
||||
deprecationWarning: () => {},
|
||||
}));
|
||||
|
||||
describe('AngularLocationWrapper', () => {
|
||||
const { location } = window;
|
||||
|
||||
beforeEach(() => {
|
||||
setLocationService(new HistoryWrapper());
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
// @ts-ignore
|
||||
delete window.location;
|
||||
|
||||
window.location = {
|
||||
...location,
|
||||
hash: '#hash',
|
||||
host: 'localhost:3000',
|
||||
hostname: 'localhost',
|
||||
href: 'http://www.domain.com:9877/path/b?search=a&b=c&d#hash',
|
||||
origin: 'http://www.domain.com:9877',
|
||||
pathname: '/path/b',
|
||||
port: '9877',
|
||||
protocol: 'http:',
|
||||
search: '?search=a&b=c&d',
|
||||
};
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
window.location = location;
|
||||
});
|
||||
|
||||
const wrapper = new AngularLocationWrapper();
|
||||
it('should provide common getters', () => {
|
||||
locationService.push('/path/b?search=a&b=c&d#hash');
|
||||
|
||||
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#hash');
|
||||
expect(wrapper.protocol()).toBe('http');
|
||||
expect(wrapper.host()).toBe('www.domain.com');
|
||||
expect(wrapper.port()).toBe(9877);
|
||||
expect(wrapper.path()).toBe('/path/b');
|
||||
expect(wrapper.search()).toEqual({ search: 'a', b: 'c', d: true });
|
||||
expect(wrapper.hash()).toBe('hash');
|
||||
expect(wrapper.url()).toBe('/path/b?search=a&b=c&d#hash');
|
||||
});
|
||||
|
||||
describe('path', () => {
|
||||
it('should change path', function () {
|
||||
locationService.push('/path/b?search=a&b=c&d#hash');
|
||||
wrapper.path('/new/path');
|
||||
|
||||
expect(wrapper.path()).toBe('/new/path');
|
||||
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/new/path?search=a&b=c&d#hash');
|
||||
});
|
||||
|
||||
it('should not break on numeric values', function () {
|
||||
locationService.push('/path/b?search=a&b=c&d#hash');
|
||||
wrapper.path(1);
|
||||
expect(wrapper.path()).toBe('/1');
|
||||
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/1?search=a&b=c&d#hash');
|
||||
});
|
||||
|
||||
it('should allow using 0 as path', function () {
|
||||
locationService.push('/path/b?search=a&b=c&d#hash');
|
||||
wrapper.path(0);
|
||||
expect(wrapper.path()).toBe('/0');
|
||||
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/0?search=a&b=c&d#hash');
|
||||
});
|
||||
it('should set to empty path on null value', function () {
|
||||
locationService.push('/path/b?search=a&b=c&d#hash');
|
||||
wrapper.path('/foo');
|
||||
expect(wrapper.path()).toBe('/foo');
|
||||
wrapper.path(null);
|
||||
expect(wrapper.path()).toBe('/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
it('should accept string', function () {
|
||||
locationService.push('/path/b');
|
||||
wrapper.search('x=y&c');
|
||||
expect(wrapper.search()).toEqual({ x: 'y', c: true });
|
||||
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b?x=y&c');
|
||||
});
|
||||
|
||||
it('search() should accept object', function () {
|
||||
locationService.push('/path/b');
|
||||
wrapper.search({ one: '1', two: true });
|
||||
expect(wrapper.search()).toEqual({ one: '1', two: true });
|
||||
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b?one=1&two');
|
||||
});
|
||||
|
||||
it('should copy object', function () {
|
||||
locationService.push('/path/b');
|
||||
const obj: Record<string, unknown> = { one: '1', two: true, three: null };
|
||||
wrapper.search(obj);
|
||||
expect(obj).toEqual({ one: '1', two: true, three: null });
|
||||
obj.one = 'changed';
|
||||
|
||||
expect(wrapper.search()).toEqual({ one: '1', two: true });
|
||||
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b?one=1&two');
|
||||
});
|
||||
|
||||
it('should change single parameter', function () {
|
||||
wrapper.search({ id: 'old', preserved: true });
|
||||
wrapper.search('id', 'new');
|
||||
|
||||
expect(wrapper.search()).toEqual({ id: 'new', preserved: true });
|
||||
});
|
||||
|
||||
it('should remove single parameter', function () {
|
||||
wrapper.search({ id: 'old', preserved: true });
|
||||
wrapper.search('id', null);
|
||||
|
||||
expect(wrapper.search()).toEqual({ preserved: true });
|
||||
});
|
||||
|
||||
it('should remove multiple parameters', function () {
|
||||
locationService.push('/path/b');
|
||||
wrapper.search({ one: '1', two: true });
|
||||
expect(wrapper.search()).toEqual({ one: '1', two: true });
|
||||
|
||||
wrapper.search({ one: null, two: null });
|
||||
expect(wrapper.search()).toEqual({});
|
||||
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b');
|
||||
});
|
||||
|
||||
it('should accept numeric keys', function () {
|
||||
locationService.push('/path/b');
|
||||
wrapper.search({ 1: 'one', 2: 'two' });
|
||||
expect(wrapper.search()).toEqual({ '1': 'one', '2': 'two' });
|
||||
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b?1=one&2=two');
|
||||
});
|
||||
|
||||
it('should handle multiple value', function () {
|
||||
wrapper.search('a&b');
|
||||
expect(wrapper.search()).toEqual({ a: true, b: true });
|
||||
|
||||
wrapper.search('a', null);
|
||||
|
||||
expect(wrapper.search()).toEqual({ b: true });
|
||||
|
||||
wrapper.search('b', undefined);
|
||||
expect(wrapper.search()).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle single value', function () {
|
||||
wrapper.search('ignore');
|
||||
expect(wrapper.search()).toEqual({ ignore: true });
|
||||
wrapper.search(1);
|
||||
expect(wrapper.search()).toEqual({ 1: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('url', () => {
|
||||
it('should change the path, search and hash', function () {
|
||||
wrapper.url('/some/path?a=b&c=d#hhh');
|
||||
expect(wrapper.url()).toBe('/some/path?a=b&c=d#hhh');
|
||||
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/some/path?a=b&c=d#hhh');
|
||||
expect(wrapper.path()).toBe('/some/path');
|
||||
expect(wrapper.search()).toEqual({ a: 'b', c: 'd' });
|
||||
expect(wrapper.hash()).toBe('hhh');
|
||||
});
|
||||
|
||||
it('should change only hash when no search and path specified', function () {
|
||||
locationService.push('/path/b?search=a&b=c&d');
|
||||
wrapper.url('#some-hash');
|
||||
|
||||
expect(wrapper.hash()).toBe('some-hash');
|
||||
expect(wrapper.url()).toBe('/path/b?search=a&b=c&d#some-hash');
|
||||
expect(wrapper.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#some-hash');
|
||||
});
|
||||
|
||||
it('should change only search and hash when no path specified', function () {
|
||||
locationService.push('/path/b');
|
||||
wrapper.url('?a=b');
|
||||
|
||||
expect(wrapper.search()).toEqual({ a: 'b' });
|
||||
expect(wrapper.hash()).toBe('');
|
||||
expect(wrapper.path()).toBe('/path/b');
|
||||
});
|
||||
|
||||
it('should reset search and hash when only path specified', function () {
|
||||
locationService.push('/path/b?search=a&b=c&d#hash');
|
||||
wrapper.url('/new/path');
|
||||
|
||||
expect(wrapper.path()).toBe('/new/path');
|
||||
expect(wrapper.search()).toEqual({});
|
||||
expect(wrapper.hash()).toBe('');
|
||||
});
|
||||
|
||||
it('should change path when empty string specified', function () {
|
||||
locationService.push('/path/b?search=a&b=c&d#hash');
|
||||
wrapper.url('');
|
||||
|
||||
expect(wrapper.path()).toBe('/');
|
||||
expect(wrapper.search()).toEqual({});
|
||||
expect(wrapper.hash()).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,149 +0,0 @@
|
|||
import { deprecationWarning, urlUtil } from '@grafana/data';
|
||||
import { locationSearchToObject, locationService, navigationLogger } from '@grafana/runtime';
|
||||
|
||||
// Ref: https://github.com/angular/angular.js/blob/ae8e903edf88a83fedd116ae02c0628bf72b150c/src/ng/location.js#L5
|
||||
const DEFAULT_PORTS: Record<string, number> = { http: 80, https: 443, ftp: 21 };
|
||||
|
||||
export class AngularLocationWrapper {
|
||||
constructor() {
|
||||
this.absUrl = this.wrapInDeprecationWarning(this.absUrl);
|
||||
this.hash = this.wrapInDeprecationWarning(this.hash);
|
||||
this.host = this.wrapInDeprecationWarning(this.host);
|
||||
this.path = this.wrapInDeprecationWarning(this.path);
|
||||
this.port = this.wrapInDeprecationWarning(this.port, 'window.location');
|
||||
this.protocol = this.wrapInDeprecationWarning(this.protocol, 'window.location');
|
||||
this.replace = this.wrapInDeprecationWarning(this.replace);
|
||||
this.search = this.wrapInDeprecationWarning(this.search);
|
||||
this.state = this.wrapInDeprecationWarning(this.state);
|
||||
this.url = this.wrapInDeprecationWarning(this.url);
|
||||
}
|
||||
|
||||
wrapInDeprecationWarning(fn: Function, replacement?: string) {
|
||||
let self = this;
|
||||
|
||||
return function wrapper() {
|
||||
deprecationWarning('$location', fn.name, replacement || 'locationService');
|
||||
return fn.apply(self, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
absUrl(): string {
|
||||
return `${window.location.origin}${this.url()}`;
|
||||
}
|
||||
|
||||
hash(newHash?: string | null) {
|
||||
navigationLogger('AngularLocationWrapper', false, 'Angular compat layer: hash');
|
||||
|
||||
if (!newHash) {
|
||||
return locationService.getLocation().hash.slice(1);
|
||||
} else {
|
||||
throw new Error('AngularLocationWrapper method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
host(): string {
|
||||
return new URL(window.location.href).hostname;
|
||||
}
|
||||
|
||||
path(pathname?: any) {
|
||||
navigationLogger('AngularLocationWrapper', false, 'Angular compat layer: path');
|
||||
|
||||
const location = locationService.getLocation();
|
||||
|
||||
if (pathname !== undefined && pathname !== null) {
|
||||
let parsedPath = String(pathname);
|
||||
parsedPath = parsedPath.startsWith('/') ? parsedPath : `/${parsedPath}`;
|
||||
const url = new URL(`${window.location.origin}${parsedPath}`);
|
||||
|
||||
locationService.push({
|
||||
pathname: url.pathname,
|
||||
search: url.search.length > 0 ? url.search : location.search,
|
||||
hash: url.hash.length > 0 ? url.hash : location.hash,
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
if (pathname === null) {
|
||||
locationService.push('/');
|
||||
return this;
|
||||
}
|
||||
|
||||
return location.pathname;
|
||||
}
|
||||
|
||||
port(): number | null {
|
||||
const url = new URL(window.location.href);
|
||||
return parseInt(url.port, 10) || DEFAULT_PORTS[url.protocol] || null;
|
||||
}
|
||||
|
||||
protocol(): string {
|
||||
return new URL(window.location.href).protocol.slice(0, -1);
|
||||
}
|
||||
|
||||
replace() {
|
||||
throw new Error('AngularLocationWrapper method not implemented.');
|
||||
}
|
||||
|
||||
search(search?: any, paramValue?: any) {
|
||||
navigationLogger('AngularLocationWrapper', false, 'Angular compat layer: search');
|
||||
if (!search) {
|
||||
return locationService.getSearchObject();
|
||||
}
|
||||
|
||||
if (search && arguments.length > 1) {
|
||||
locationService.partial({
|
||||
[search]: paramValue,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
let newQuery;
|
||||
|
||||
if (typeof search === 'object') {
|
||||
newQuery = { ...search };
|
||||
} else {
|
||||
newQuery = locationSearchToObject(search);
|
||||
}
|
||||
|
||||
for (const key in newQuery) {
|
||||
// removing params with null | undefined
|
||||
if (newQuery[key] === null || newQuery[key] === undefined) {
|
||||
delete newQuery[key];
|
||||
}
|
||||
}
|
||||
|
||||
const updatedUrl = urlUtil.renderUrl(locationService.getLocation().pathname, newQuery);
|
||||
locationService.push(updatedUrl);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
state(state?: any) {
|
||||
navigationLogger('AngularLocationWrapper', false, 'Angular compat layer: state');
|
||||
throw new Error('AngularLocationWrapper method not implemented.');
|
||||
}
|
||||
|
||||
url(newUrl?: any) {
|
||||
navigationLogger('AngularLocationWrapper', false, 'Angular compat layer: url');
|
||||
|
||||
if (newUrl !== undefined) {
|
||||
if (newUrl.startsWith('#')) {
|
||||
locationService.push({ ...locationService.getLocation(), hash: newUrl });
|
||||
} else if (newUrl.startsWith('?')) {
|
||||
locationService.push({ ...locationService.getLocation(), search: newUrl });
|
||||
} else if (newUrl.trim().length === 0) {
|
||||
locationService.push('/');
|
||||
} else {
|
||||
locationService.push(newUrl);
|
||||
}
|
||||
|
||||
return locationService;
|
||||
}
|
||||
|
||||
const location = locationService.getLocation();
|
||||
return `${location.pathname}${location.search}${location.hash}`;
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import { forwardRef } from 'react';
|
||||
|
||||
export const AngularRoot = forwardRef<HTMLDivElement, {}>((props, ref) => {
|
||||
return (
|
||||
<div
|
||||
id="ngRoot"
|
||||
ref={ref}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: '<grafana-app ng-cloak></grafana-app>',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
AngularRoot.displayName = 'AngularRoot';
|
|
@ -1,158 +0,0 @@
|
|||
import { IRootScopeService, IAngularEvent, auto } from 'angular';
|
||||
import $ from 'jquery';
|
||||
import _ from 'lodash'; // eslint-disable-line lodash/import-scope
|
||||
|
||||
import { AppEvent } from '@grafana/data';
|
||||
import { setLegacyAngularInjector, setAngularLoader } from '@grafana/runtime';
|
||||
import { colors } from '@grafana/ui';
|
||||
import coreModule from 'app/angular/core_module';
|
||||
import { AngularLoader } from 'app/angular/services/AngularLoader';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import config from 'app/core/config';
|
||||
import { ContextSrv } from 'app/core/services/context_srv';
|
||||
import { AppEventEmitter, AppEventConsumer } from 'app/types';
|
||||
|
||||
import { UtilSrv } from './services/UtilSrv';
|
||||
|
||||
export type GrafanaRootScope = IRootScopeService & AppEventEmitter & AppEventConsumer & { colors: string[] };
|
||||
|
||||
export class GrafanaCtrl {
|
||||
static $inject = ['$scope', 'utilSrv', '$rootScope', 'contextSrv', 'angularLoader', '$injector'];
|
||||
|
||||
constructor(
|
||||
$scope: any,
|
||||
utilSrv: UtilSrv,
|
||||
$rootScope: GrafanaRootScope,
|
||||
contextSrv: ContextSrv,
|
||||
angularLoader: AngularLoader,
|
||||
$injector: auto.IInjectorService
|
||||
) {
|
||||
// make angular loader service available to react components
|
||||
setAngularLoader(angularLoader);
|
||||
setLegacyAngularInjector($injector);
|
||||
|
||||
$scope.init = () => {
|
||||
$scope.contextSrv = contextSrv;
|
||||
$scope.appSubUrl = config.appSubUrl;
|
||||
$scope._ = _;
|
||||
utilSrv.init();
|
||||
};
|
||||
|
||||
$rootScope.colors = colors;
|
||||
|
||||
$rootScope.onAppEvent = function <T>(
|
||||
event: AppEvent<T> | string,
|
||||
callback: (event: IAngularEvent, ...args: any[]) => void,
|
||||
localScope?: any
|
||||
) {
|
||||
let unbind;
|
||||
if (typeof event === 'string') {
|
||||
unbind = $rootScope.$on(event, callback);
|
||||
} else {
|
||||
unbind = $rootScope.$on(event.name, callback);
|
||||
}
|
||||
|
||||
let callerScope = this;
|
||||
if (callerScope.$id === 1 && !localScope) {
|
||||
console.warn('warning rootScope onAppEvent called without localscope');
|
||||
}
|
||||
if (localScope) {
|
||||
callerScope = localScope;
|
||||
}
|
||||
callerScope.$on('$destroy', unbind);
|
||||
};
|
||||
|
||||
$rootScope.appEvent = <T>(event: AppEvent<T> | string, payload?: T | any) => {
|
||||
if (typeof event === 'string') {
|
||||
$rootScope.$emit(event, payload);
|
||||
appEvents.emit(event, payload);
|
||||
} else {
|
||||
$rootScope.$emit(event.name, payload);
|
||||
appEvents.emit(event, payload);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
}
|
||||
}
|
||||
|
||||
export function grafanaAppDirective() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controller: GrafanaCtrl,
|
||||
link: (scope: IRootScopeService & AppEventEmitter, elem: JQuery) => {
|
||||
const body = $('body');
|
||||
// see https://github.com/zenorocha/clipboard.js/issues/155
|
||||
$.fn.modal.Constructor.prototype.enforceFocus = () => {};
|
||||
|
||||
// handle in active view state class
|
||||
let lastActivity = new Date().getTime();
|
||||
let activeUser = true;
|
||||
const inActiveTimeLimit = 60 * 5000;
|
||||
|
||||
function checkForInActiveUser() {
|
||||
if (!activeUser) {
|
||||
return;
|
||||
}
|
||||
// only go to activity low mode on dashboard page
|
||||
if (!body.hasClass('page-dashboard')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (new Date().getTime() - lastActivity > inActiveTimeLimit) {
|
||||
activeUser = false;
|
||||
body.addClass('view-mode--inactive');
|
||||
}
|
||||
}
|
||||
|
||||
function userActivityDetected() {
|
||||
lastActivity = new Date().getTime();
|
||||
if (!activeUser) {
|
||||
activeUser = true;
|
||||
body.removeClass('view-mode--inactive');
|
||||
}
|
||||
}
|
||||
|
||||
// mouse and keyboard is user activity
|
||||
body.mousemove(userActivityDetected);
|
||||
body.keydown(userActivityDetected);
|
||||
// set useCapture = true to catch event here
|
||||
document.addEventListener('wheel', userActivityDetected, { capture: true, passive: true });
|
||||
// treat tab change as activity
|
||||
document.addEventListener('visibilitychange', userActivityDetected);
|
||||
|
||||
// check every 2 seconds
|
||||
setInterval(checkForInActiveUser, 2000);
|
||||
|
||||
// handle document clicks that should hide things
|
||||
body.click((evt) => {
|
||||
const target = $(evt.target);
|
||||
if (target.parents().length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure dropdown menu doesn't impact on z-index
|
||||
body.find('.dropdown-menu-open').removeClass('dropdown-menu-open');
|
||||
|
||||
// for stuff that animates, slides out etc, clicking it needs to
|
||||
// hide it right away
|
||||
const clickAutoHide = target.closest('[data-click-hide]');
|
||||
if (clickAutoHide.length) {
|
||||
const clickAutoHideParent = clickAutoHide.parent();
|
||||
clickAutoHide.detach();
|
||||
setTimeout(() => {
|
||||
clickAutoHideParent.append(clickAutoHide);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// hide popovers
|
||||
const popover = elem.find('.popover');
|
||||
if (popover.length > 0 && target.parents('.graph-legend').length === 0) {
|
||||
popover.hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('grafanaApp', grafanaAppDirective);
|
|
@ -1,153 +0,0 @@
|
|||
import {
|
||||
ClipboardButton,
|
||||
ColorPicker,
|
||||
DataLinksInlineEditor,
|
||||
DataSourceHttpSettings,
|
||||
GraphContextMenu,
|
||||
Icon,
|
||||
LegacyForms,
|
||||
SeriesColorPickerPopoverWithTheme,
|
||||
Spinner,
|
||||
UnitPicker,
|
||||
} from '@grafana/ui';
|
||||
import { react2AngularDirective } from 'app/angular/react2angular';
|
||||
import { OldFolderPicker } from 'app/core/components/Select/OldFolderPicker';
|
||||
import { TimePickerSettings } from 'app/features/dashboard/components/DashboardSettings/TimePickerSettings';
|
||||
import { QueryEditor as CloudMonitoringQueryEditor } from 'app/plugins/datasource/cloud-monitoring/components/QueryEditor';
|
||||
|
||||
import EmptyListCTA from '../core/components/EmptyListCTA/EmptyListCTA';
|
||||
import { Footer } from '../core/components/Footer/Footer';
|
||||
import { MetricSelect } from '../core/components/Select/MetricSelect';
|
||||
import { TagFilter } from '../core/components/TagFilter/TagFilter';
|
||||
import { HelpModal } from '../core/components/help/HelpModal';
|
||||
|
||||
import { PageHeader } from './components/PageHeader/PageHeader';
|
||||
|
||||
const { SecretFormField } = LegacyForms;
|
||||
|
||||
export function registerAngularDirectives() {
|
||||
react2AngularDirective('footer', Footer, []);
|
||||
react2AngularDirective('icon', Icon, [
|
||||
'name',
|
||||
'size',
|
||||
'type',
|
||||
['onClick', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
react2AngularDirective('spinner', Spinner, ['inline']);
|
||||
react2AngularDirective('helpModal', HelpModal, []);
|
||||
react2AngularDirective('pageHeader', PageHeader, ['model', 'noTabs']);
|
||||
react2AngularDirective('emptyListCta', EmptyListCTA, [
|
||||
'title',
|
||||
'buttonIcon',
|
||||
'buttonLink',
|
||||
'buttonTitle',
|
||||
['onClick', { watchDepth: 'reference', wrapApply: true }],
|
||||
'proTip',
|
||||
'proTipLink',
|
||||
'proTipLinkTitle',
|
||||
'proTipTarget',
|
||||
'infoBox',
|
||||
'infoBoxTitle',
|
||||
]);
|
||||
react2AngularDirective('tagFilter', TagFilter, [
|
||||
'tags',
|
||||
['onChange', { watchDepth: 'reference' }],
|
||||
['tagOptions', { watchDepth: 'reference' }],
|
||||
]);
|
||||
react2AngularDirective('colorPicker', ColorPicker, [
|
||||
'color',
|
||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopoverWithTheme, [
|
||||
'color',
|
||||
'series',
|
||||
'onColorChange',
|
||||
'onToggleAxis',
|
||||
]);
|
||||
react2AngularDirective('unitPicker', UnitPicker, [
|
||||
'value',
|
||||
'width',
|
||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
react2AngularDirective('metricSelect', MetricSelect, [
|
||||
'options',
|
||||
'onChange',
|
||||
'value',
|
||||
'isSearchable',
|
||||
'className',
|
||||
'placeholder',
|
||||
['variables', { watchDepth: 'reference' }],
|
||||
]);
|
||||
react2AngularDirective('cloudMonitoringQueryEditor', CloudMonitoringQueryEditor, [
|
||||
'target',
|
||||
'onQueryChange',
|
||||
'onExecuteQuery',
|
||||
['events', { watchDepth: 'reference' }],
|
||||
['datasource', { watchDepth: 'reference' }],
|
||||
['templateSrv', { watchDepth: 'reference' }],
|
||||
]);
|
||||
react2AngularDirective('secretFormField', SecretFormField, [
|
||||
'value',
|
||||
'isConfigured',
|
||||
'inputWidth',
|
||||
'labelWidth',
|
||||
'aria-label',
|
||||
['onReset', { watchDepth: 'reference', wrapApply: true }],
|
||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
react2AngularDirective('graphContextMenu', GraphContextMenu, [
|
||||
'x',
|
||||
'y',
|
||||
'itemsGroup',
|
||||
['onClose', { watchDepth: 'reference', wrapApply: true }],
|
||||
['getContextMenuSource', { watchDepth: 'reference', wrapApply: true }],
|
||||
['timeZone', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
|
||||
// We keep the drilldown terminology here because of as using data-* directive
|
||||
// being in conflict with HTML data attributes
|
||||
react2AngularDirective('drilldownLinksEditor', DataLinksInlineEditor, [
|
||||
'value',
|
||||
'links',
|
||||
'suggestions',
|
||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
|
||||
react2AngularDirective('datasourceHttpSettingsNext', DataSourceHttpSettings, [
|
||||
'defaultUrl',
|
||||
'showAccessOptions',
|
||||
'dataSourceConfig',
|
||||
'showForwardOAuthIdentityOption',
|
||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
react2AngularDirective('folderPicker', OldFolderPicker, [
|
||||
'labelClass',
|
||||
'rootName',
|
||||
'enableCreateNew',
|
||||
'enableReset',
|
||||
'initialTitle',
|
||||
'initialFolderId',
|
||||
'dashboardId',
|
||||
'onCreateFolder',
|
||||
['enterFolderCreation', { watchDepth: 'reference', wrapApply: true }],
|
||||
['exitFolderCreation', { watchDepth: 'reference', wrapApply: true }],
|
||||
['onLoad', { watchDepth: 'reference', wrapApply: true }],
|
||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
|
||||
react2AngularDirective('timePickerSettings', TimePickerSettings, [
|
||||
'renderCount',
|
||||
'refreshIntervals',
|
||||
'timePickerHidden',
|
||||
'nowDelay',
|
||||
'timezone',
|
||||
['onTimeZoneChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
['onRefreshIntervalChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
['onNowDelayChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
['onHideTimePickerChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
|
||||
react2AngularDirective('clipboardButton', ClipboardButton, [
|
||||
['getText', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import { isArray } from 'lodash';
|
||||
|
||||
import coreModule from './core_module';
|
||||
|
||||
export function arrayJoin() {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: (scope: any, element: any, attr: any, ngModel: any) => {
|
||||
function split_array(text: string) {
|
||||
return (text || '').split(',');
|
||||
}
|
||||
|
||||
function join_array(text: string) {
|
||||
if (isArray(text)) {
|
||||
return ((text || '') as any).join(',');
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
ngModel.$parsers.push(split_array);
|
||||
ngModel.$formatters.push(join_array);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('arrayJoin', arrayJoin);
|
|
@ -1,34 +0,0 @@
|
|||
import coreModule from './core_module';
|
||||
|
||||
export function autofillEventFix($compile: any) {
|
||||
return {
|
||||
link: ($scope: any, elem: any) => {
|
||||
const input = elem[0];
|
||||
const dispatchChangeEvent = () => {
|
||||
const event = new Event('change');
|
||||
return input.dispatchEvent(event);
|
||||
};
|
||||
const onAnimationStart = ({ animationName }: AnimationEvent) => {
|
||||
switch (animationName) {
|
||||
case 'onAutoFillStart':
|
||||
return dispatchChangeEvent();
|
||||
case 'onAutoFillCancel':
|
||||
return dispatchChangeEvent();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// const onChange = (evt: Event) => console.log(evt);
|
||||
|
||||
input.addEventListener('animationstart', onAnimationStart);
|
||||
// input.addEventListener('change', onChange);
|
||||
|
||||
$scope.$on('$destroy', () => {
|
||||
input.removeEventListener('animationstart', onAnimationStart);
|
||||
// input.removeEventListener('change', onChange);
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('autofillEventFix', ['$compile', autofillEventFix]);
|
|
@ -1,49 +0,0 @@
|
|||
import { ILocationService } from 'angular';
|
||||
|
||||
import { RouteParamsProvider } from '../core/navigation/patch/RouteParamsProvider';
|
||||
import { RouteProvider } from '../core/navigation/patch/RouteProvider';
|
||||
|
||||
import { AngularLocationWrapper } from './AngularLocationWrapper';
|
||||
import { coreModule } from './core_module';
|
||||
|
||||
// Neutralizing Angular’s location tampering
|
||||
// https://stackoverflow.com/a/19825756
|
||||
const tamperAngularLocation = () => {
|
||||
coreModule.config([
|
||||
'$provide',
|
||||
($provide: any) => {
|
||||
$provide.decorator('$browser', [
|
||||
'$delegate',
|
||||
($delegate: any) => {
|
||||
$delegate.onUrlChange = () => {};
|
||||
$delegate.url = () => '';
|
||||
|
||||
return $delegate;
|
||||
},
|
||||
]);
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
// Intercepting $location service with implementation based on history
|
||||
const interceptAngularLocation = () => {
|
||||
coreModule.config([
|
||||
'$provide',
|
||||
($provide: any) => {
|
||||
$provide.decorator('$location', [
|
||||
'$delegate',
|
||||
($delegate: ILocationService) => {
|
||||
$delegate = new AngularLocationWrapper() as unknown as ILocationService;
|
||||
return $delegate;
|
||||
},
|
||||
]);
|
||||
},
|
||||
]);
|
||||
coreModule.provider('$route', RouteProvider);
|
||||
coreModule.provider('$routeParams', RouteParamsProvider);
|
||||
};
|
||||
|
||||
export function initAngularRoutingBridge() {
|
||||
tamperAngularLocation();
|
||||
interceptAngularLocation();
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
import angular from 'angular';
|
||||
import $ from 'jquery';
|
||||
|
||||
import coreModule from './core_module';
|
||||
|
||||
coreModule.directive('bsTooltip', [
|
||||
'$parse',
|
||||
'$compile',
|
||||
function ($parse: any, $compile: any) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: true,
|
||||
link: function postLink(scope: any, element: any, attrs: any) {
|
||||
let getter = $parse(attrs.bsTooltip),
|
||||
value = getter(scope);
|
||||
scope.$watch(attrs.bsTooltip, function (newValue: any, oldValue: any) {
|
||||
if (newValue !== oldValue) {
|
||||
value = newValue;
|
||||
}
|
||||
});
|
||||
// Grafana change, always hide other tooltips
|
||||
if (true) {
|
||||
element.on('show', function (ev: any) {
|
||||
$('.tooltip.in').each(function () {
|
||||
const $this = $(this),
|
||||
tooltip = $this.data('tooltip');
|
||||
if (tooltip && !tooltip.$element.is(element)) {
|
||||
$this.tooltip('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
element.tooltip({
|
||||
title: function () {
|
||||
return angular.isFunction(value) ? value.apply(null, arguments) : value;
|
||||
},
|
||||
html: true,
|
||||
container: 'body', // Grafana change
|
||||
});
|
||||
const tooltip = element.data('tooltip');
|
||||
tooltip.show = function () {
|
||||
const r = $.fn.tooltip.Constructor.prototype.show.apply(this, arguments);
|
||||
this.tip().data('tooltip', this);
|
||||
return r;
|
||||
};
|
||||
scope._tooltip = function (event: any) {
|
||||
element.tooltip(event);
|
||||
};
|
||||
scope.hide = function () {
|
||||
element.tooltip('hide');
|
||||
};
|
||||
scope.show = function () {
|
||||
element.tooltip('show');
|
||||
};
|
||||
scope.dismiss = scope.hide;
|
||||
},
|
||||
};
|
||||
},
|
||||
]);
|
|
@ -1,63 +0,0 @@
|
|||
import angular from 'angular';
|
||||
import $ from 'jquery';
|
||||
import { isFunction } from 'lodash';
|
||||
|
||||
import coreModule from './core_module';
|
||||
|
||||
coreModule.directive('bsTypeahead', [
|
||||
'$parse',
|
||||
function ($parse: any) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function postLink(scope: any, element: any, attrs: any, controller: any) {
|
||||
let getter = $parse(attrs.bsTypeahead),
|
||||
value = getter(scope);
|
||||
scope.$watch(attrs.bsTypeahead, function (newValue: any, oldValue: any) {
|
||||
if (newValue !== oldValue) {
|
||||
value = newValue;
|
||||
}
|
||||
});
|
||||
element.attr('data-provide', 'typeahead');
|
||||
element.typeahead({
|
||||
source: function () {
|
||||
return angular.isFunction(value) ? value.apply(null, arguments) : value;
|
||||
},
|
||||
minLength: attrs.minLength || 1,
|
||||
items: attrs.item,
|
||||
updater: function (value: any) {
|
||||
if (controller) {
|
||||
scope.$apply(function () {
|
||||
controller.$setViewValue(value);
|
||||
});
|
||||
}
|
||||
scope.$emit('typeahead-updated', value);
|
||||
return value;
|
||||
},
|
||||
});
|
||||
const typeahead = element.data('typeahead');
|
||||
typeahead.lookup = function () {
|
||||
let items;
|
||||
this.query = this.$element.val() || '';
|
||||
if (this.query.length < this.options.minLength) {
|
||||
return this.shown ? this.hide() : this;
|
||||
}
|
||||
items = isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source;
|
||||
return items ? this.process(items) : this;
|
||||
};
|
||||
if (!!attrs.matchAll) {
|
||||
typeahead.matcher = function () {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
if (attrs.minLength === '0') {
|
||||
setTimeout(function () {
|
||||
element.on('focus', function () {
|
||||
element.val().length === 0 && setTimeout(element.typeahead.bind(element, 'lookup'), 200);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
]);
|
|
@ -1,22 +0,0 @@
|
|||
import { coreModule } from 'app/angular/core_module';
|
||||
|
||||
coreModule.directive('datasourceHttpSettings', () => {
|
||||
return {
|
||||
scope: {
|
||||
current: '=',
|
||||
suggestUrl: '@',
|
||||
noDirectAccess: '@',
|
||||
showForwardOAuthIdentityOption: '@',
|
||||
},
|
||||
templateUrl: 'public/app/angular/partials/http_settings_next.html',
|
||||
link: {
|
||||
pre: ($scope: any) => {
|
||||
// do not show access option if direct access is disabled
|
||||
$scope.showAccessOption = $scope.noDirectAccess !== 'true';
|
||||
$scope.onChange = (datasourceSetting: any) => {
|
||||
$scope.current = datasourceSetting;
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
|
@ -1,23 +0,0 @@
|
|||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import { NavModelItem } from '@grafana/data';
|
||||
|
||||
import { PageHeader } from './PageHeader';
|
||||
|
||||
describe('PageHeader', () => {
|
||||
describe('when the nav tree has a node with a title', () => {
|
||||
it('should render the title', async () => {
|
||||
const nav: NavModelItem = {
|
||||
icon: 'folder-open',
|
||||
id: 'node',
|
||||
subTitle: 'node subtitle',
|
||||
url: '',
|
||||
text: 'node',
|
||||
};
|
||||
|
||||
render(<PageHeader navItem={nav} />);
|
||||
|
||||
expect(screen.getByRole('heading', { name: 'node' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,165 +0,0 @@
|
|||
import { css, cx } from '@emotion/css';
|
||||
import * as React from 'react';
|
||||
|
||||
import { NavModelItem, GrafanaTheme2 } from '@grafana/data';
|
||||
import { Tab, TabsBar, Icon, useStyles2, toIconName } from '@grafana/ui';
|
||||
|
||||
import { PageInfoItem } from '../../../core/components/Page/types';
|
||||
import { PageInfo } from '../../../core/components/PageInfo/PageInfo';
|
||||
import { ProBadge } from '../../../core/components/Upgrade/ProBadge';
|
||||
|
||||
import { PanelHeaderMenuItem } from './PanelHeaderMenuItem';
|
||||
|
||||
export interface Props {
|
||||
navItem: NavModelItem;
|
||||
renderTitle?: (title: string) => React.ReactNode;
|
||||
actions?: React.ReactNode;
|
||||
info?: PageInfoItem[];
|
||||
subTitle?: React.ReactNode;
|
||||
}
|
||||
|
||||
const SelectNav = ({ children, customCss }: { children: NavModelItem[]; customCss: string }) => {
|
||||
if (!children || children.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const defaultSelectedItem = children.find((navItem) => {
|
||||
return navItem.active === true;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={`gf-form-select-wrapper width-20 ${customCss}`}>
|
||||
<div className="dropdown">
|
||||
<button
|
||||
type="button"
|
||||
className="gf-form-input dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
style={{ textAlign: 'left' }}
|
||||
>
|
||||
{defaultSelectedItem?.text}
|
||||
</button>
|
||||
<ul role="menu" className="dropdown-menu dropdown-menu--menu">
|
||||
{children.map((navItem: NavModelItem) => {
|
||||
if (navItem.hideFromTabs) {
|
||||
// TODO: Rename hideFromTabs => hideFromNav
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<PanelHeaderMenuItem
|
||||
key={navItem.url}
|
||||
iconClassName={navItem.icon}
|
||||
text={navItem.text}
|
||||
href={navItem.url}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Navigation = ({ children }: { children: NavModelItem[] }) => {
|
||||
if (!children || children.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<SelectNav customCss="page-header__select-nav">{children}</SelectNav>
|
||||
<TabsBar className="page-header__tabs" hideBorder={true}>
|
||||
{children.map((child, index) => {
|
||||
return (
|
||||
!child.hideFromTabs && (
|
||||
<Tab
|
||||
label={child.text}
|
||||
active={child.active}
|
||||
key={`${child.url}-${index}`}
|
||||
icon={child.icon}
|
||||
href={child.url}
|
||||
suffix={child.tabSuffix}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</TabsBar>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export const PageHeader = ({ navItem: model, renderTitle, actions, info, subTitle }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderHeader = (main: NavModelItem) => {
|
||||
const marginTop = main.icon === 'grafana' ? 12 : 14;
|
||||
const icon = main.icon && toIconName(main.icon);
|
||||
const sub = subTitle ?? main.subTitle;
|
||||
|
||||
return (
|
||||
<div className="page-header__inner">
|
||||
<span className="page-header__logo">
|
||||
{icon && <Icon name={icon} size="xxxl" style={{ marginTop }} />}
|
||||
{main.img && <img className="page-header__img" src={main.img} alt="" />}
|
||||
</span>
|
||||
|
||||
<div className={cx('page-header__info-block', styles.headerText)}>
|
||||
{renderTitle ? renderTitle(main.text) : renderHeaderTitle(main.text, main.highlightText)}
|
||||
{info && <PageInfo info={info} />}
|
||||
{sub && <div className="page-header__sub-title">{sub}</div>}
|
||||
{actions && <div className={styles.actions}>{actions}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.headerCanvas}>
|
||||
<div className="page-container">
|
||||
<div className="page-header">
|
||||
{renderHeader(model)}
|
||||
{model.children && model.children.length > 0 && <Navigation>{model.children}</Navigation>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function renderHeaderTitle(title: string, highlightText: NavModelItem['highlightText']) {
|
||||
if (!title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<h1 className="page-header__title">
|
||||
{title}
|
||||
{highlightText && (
|
||||
<ProBadge
|
||||
text={highlightText}
|
||||
className={css({
|
||||
verticalAlign: 'middle',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
actions: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing(1),
|
||||
}),
|
||||
headerText: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(1),
|
||||
}),
|
||||
headerCanvas: css({
|
||||
background: theme.colors.background.canvas,
|
||||
}),
|
||||
});
|
|
@ -1,96 +0,0 @@
|
|||
import { css } from '@emotion/css';
|
||||
import { useState } from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { PanelMenuItem, GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Icon, toIconName, useStyles2 } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const PanelHeaderMenuItem = (props: Props & PanelMenuItem) => {
|
||||
const [ref, setRef] = useState<HTMLLIElement | null>(null);
|
||||
const isSubMenu = props.type === 'submenu';
|
||||
const styles = useStyles2(getStyles);
|
||||
const icon = props.iconClassName ? toIconName(props.iconClassName) : undefined;
|
||||
|
||||
switch (props.type) {
|
||||
case 'divider':
|
||||
return <li className="divider" />;
|
||||
case 'group':
|
||||
return (
|
||||
<li>
|
||||
<span className={styles.groupLabel}>{props.text}</span>
|
||||
</li>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<li
|
||||
className={isSubMenu ? `dropdown-submenu ${getDropdownLocationCssClass(ref)}` : undefined}
|
||||
ref={setRef}
|
||||
data-testid={selectors.components.Panels.Panel.menuItems(props.text)}
|
||||
>
|
||||
<a onClick={props.onClick} href={props.href} role="menuitem">
|
||||
{icon && <Icon name={icon} className={styles.menuIconClassName} />}
|
||||
<span
|
||||
className="dropdown-item-text"
|
||||
data-testid={selectors.components.Panels.Panel.headerItems(props.text)}
|
||||
>
|
||||
{props.text}
|
||||
{isSubMenu && <Icon name="angle-right" className={styles.shortcutIconClassName} />}
|
||||
</span>
|
||||
|
||||
{props.shortcut && (
|
||||
<span className="dropdown-menu-item-shortcut">
|
||||
<Icon name="keyboard" className={styles.menuIconClassName} /> {props.shortcut}
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
{props.children}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function getDropdownLocationCssClass(element: HTMLElement | null) {
|
||||
if (!element) {
|
||||
return 'invisible';
|
||||
}
|
||||
|
||||
const wrapperPos = element.parentElement!.getBoundingClientRect();
|
||||
const pos = element.getBoundingClientRect();
|
||||
|
||||
if (pos.width === 0) {
|
||||
return 'invisible';
|
||||
}
|
||||
|
||||
if (wrapperPos.right + pos.width + 10 > window.innerWidth) {
|
||||
return 'pull-left';
|
||||
} else {
|
||||
return 'pull-right';
|
||||
}
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
menuIconClassName: css({
|
||||
marginRight: theme.spacing(1),
|
||||
'a::after': {
|
||||
display: 'none',
|
||||
},
|
||||
}),
|
||||
shortcutIconClassName: css({
|
||||
position: 'absolute',
|
||||
top: '7px',
|
||||
right: theme.spacing(0.5),
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
groupLabel: css({
|
||||
color: theme.colors.text.secondary,
|
||||
fontSize: theme.typography.size.sm,
|
||||
padding: theme.spacing(0.5, 1),
|
||||
}),
|
||||
};
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
import { coreModule } from 'app/angular/core_module';
|
||||
|
||||
coreModule.directive('datasourceTlsAuthSettings', () => {
|
||||
return {
|
||||
scope: {
|
||||
current: '=',
|
||||
},
|
||||
templateUrl: 'public/app/angular/partials/tls_auth_settings.html',
|
||||
};
|
||||
});
|
|
@ -1,4 +0,0 @@
|
|||
declare module 'brace/*' {
|
||||
let brace: any;
|
||||
export default brace;
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
/**
|
||||
* codeEditor directive based on Ace code editor
|
||||
* https://github.com/ajaxorg/ace
|
||||
*
|
||||
* Basic usage:
|
||||
* <code-editor content="ctrl.target.query" on-change="ctrl.panelCtrl.refresh()"
|
||||
* data-mode="sql" data-show-gutter>
|
||||
* </code-editor>
|
||||
*
|
||||
* Params:
|
||||
* content: Editor content.
|
||||
* onChange: Function called on content change (invoked on editor blur, ctrl+enter, not on every change).
|
||||
* getCompleter: Function returned external completer. Completer is an object implemented getCompletions() method,
|
||||
* see Prometheus Data Source implementation for details.
|
||||
*
|
||||
* Some Ace editor options available via data-* attributes:
|
||||
* data-mode - Language mode (text, sql, javascript, etc.). Default is 'text'.
|
||||
* data-theme - Editor theme (eg 'solarized_dark').
|
||||
* data-max-lines - Max editor height in lines. Editor grows automatically from 1 to maxLines.
|
||||
* data-show-gutter - Show gutter (contains line numbers and additional info).
|
||||
* data-tab-size - Tab size, default is 2.
|
||||
* data-behaviours-enabled - Specifies whether to use behaviors or not. "Behaviors" in this case is the auto-pairing of
|
||||
* special characters, like quotation marks, parenthesis, or brackets.
|
||||
* data-snippets-enabled - Specifies whether to use snippets or not. "Snippets" are small pieces of code that can be
|
||||
* inserted via the completion box.
|
||||
*
|
||||
* Keybindings:
|
||||
* Ctrl-Enter (Command-Enter): run onChange() function
|
||||
*/
|
||||
|
||||
import coreModule from 'app/angular/core_module';
|
||||
import config from 'app/core/config';
|
||||
|
||||
const DEFAULT_THEME_DARK = 'ace/theme/grafana-dark';
|
||||
const DEFAULT_THEME_LIGHT = 'ace/theme/textmate';
|
||||
const DEFAULT_MODE = 'text';
|
||||
const DEFAULT_MAX_LINES = 10;
|
||||
const DEFAULT_TAB_SIZE = 2;
|
||||
const DEFAULT_BEHAVIORS = true;
|
||||
const DEFAULT_SNIPPETS = true;
|
||||
|
||||
const editorTemplate = `<div></div>`;
|
||||
|
||||
async function link(scope: any, elem: any, attrs: any) {
|
||||
// Options
|
||||
const langMode = attrs.mode || DEFAULT_MODE;
|
||||
const maxLines = attrs.maxLines || DEFAULT_MAX_LINES;
|
||||
const showGutter = attrs.showGutter !== undefined;
|
||||
const tabSize = attrs.tabSize || DEFAULT_TAB_SIZE;
|
||||
const behavioursEnabled = attrs.behavioursEnabled ? attrs.behavioursEnabled === 'true' : DEFAULT_BEHAVIORS;
|
||||
const snippetsEnabled = attrs.snippetsEnabled ? attrs.snippetsEnabled === 'true' : DEFAULT_SNIPPETS;
|
||||
|
||||
// Initialize editor
|
||||
const aceElem = elem.get(0);
|
||||
const { default: ace } = await import(/* webpackChunkName: "brace" */ 'brace');
|
||||
await import('brace/ext/language_tools');
|
||||
await import('brace/theme/textmate');
|
||||
await import('brace/mode/text');
|
||||
await import('brace/snippets/text');
|
||||
await import('brace/mode/sql');
|
||||
await import('brace/snippets/sql');
|
||||
await import('brace/mode/sqlserver');
|
||||
await import('brace/snippets/sqlserver');
|
||||
await import('brace/mode/markdown');
|
||||
await import('brace/snippets/markdown');
|
||||
await import('brace/mode/json');
|
||||
await import('brace/snippets/json');
|
||||
|
||||
// @ts-ignore
|
||||
await import('./theme-grafana-dark');
|
||||
|
||||
const codeEditor = ace.edit(aceElem);
|
||||
const editorSession = codeEditor.getSession();
|
||||
|
||||
const editorOptions = {
|
||||
maxLines: maxLines,
|
||||
showGutter: showGutter,
|
||||
tabSize: tabSize,
|
||||
behavioursEnabled: behavioursEnabled,
|
||||
highlightActiveLine: false,
|
||||
showPrintMargin: false,
|
||||
autoScrollEditorIntoView: true, // this is needed if editor is inside scrollable page
|
||||
};
|
||||
|
||||
// Set options
|
||||
codeEditor.setOptions(editorOptions);
|
||||
// disable depreacation warning
|
||||
codeEditor.$blockScrolling = Infinity;
|
||||
// Padding hacks
|
||||
(codeEditor.renderer as any).setScrollMargin(10, 10);
|
||||
codeEditor.renderer.setPadding(10);
|
||||
|
||||
setThemeMode();
|
||||
setLangMode(langMode);
|
||||
setEditorContent(scope.content);
|
||||
|
||||
// Add classes
|
||||
elem.addClass('gf-code-editor');
|
||||
const textarea = elem.find('textarea');
|
||||
textarea.addClass('gf-form-input');
|
||||
|
||||
// All aria-label to be set for accessibility
|
||||
textarea.attr('aria-label', attrs.textareaLabel);
|
||||
|
||||
if (scope.codeEditorFocus) {
|
||||
setTimeout(() => {
|
||||
textarea.focus();
|
||||
const domEl = textarea[0];
|
||||
if (domEl.setSelectionRange) {
|
||||
const pos = textarea.val().length * 2;
|
||||
domEl.setSelectionRange(pos, pos);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
editorSession.on('change', (e) => {
|
||||
scope.$apply(() => {
|
||||
const newValue = codeEditor.getValue();
|
||||
scope.content = newValue;
|
||||
});
|
||||
});
|
||||
|
||||
// Sync with outer scope - update editor content if model has been changed from outside of directive.
|
||||
scope.$watch('content', (newValue: any, oldValue: any) => {
|
||||
const editorValue = codeEditor.getValue();
|
||||
if (newValue !== editorValue && newValue !== oldValue) {
|
||||
scope.$$postDigest(() => {
|
||||
setEditorContent(newValue);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
codeEditor.on('blur', () => {
|
||||
scope.onChange();
|
||||
});
|
||||
|
||||
scope.$on('$destroy', () => {
|
||||
codeEditor.destroy();
|
||||
});
|
||||
|
||||
// Keybindings
|
||||
codeEditor.commands.addCommand({
|
||||
name: 'executeQuery',
|
||||
bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' },
|
||||
exec: () => {
|
||||
scope.onChange();
|
||||
},
|
||||
});
|
||||
|
||||
function setLangMode(lang: string) {
|
||||
ace.acequire('ace/ext/language_tools');
|
||||
codeEditor.setOptions({
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true,
|
||||
enableSnippets: snippetsEnabled,
|
||||
});
|
||||
|
||||
if (scope.getCompleter()) {
|
||||
// make copy of array as ace seems to share completers array between instances
|
||||
const anyEditor = codeEditor as any;
|
||||
anyEditor.completers = anyEditor.completers.slice();
|
||||
anyEditor.completers.push(scope.getCompleter());
|
||||
}
|
||||
|
||||
const aceModeName = `ace/mode/${lang}`;
|
||||
editorSession.setMode(aceModeName);
|
||||
}
|
||||
|
||||
function setThemeMode() {
|
||||
let theme = DEFAULT_THEME_DARK;
|
||||
if (config.bootData.user.lightTheme) {
|
||||
theme = DEFAULT_THEME_LIGHT;
|
||||
}
|
||||
|
||||
codeEditor.setTheme(theme);
|
||||
}
|
||||
|
||||
function setEditorContent(value: string) {
|
||||
codeEditor.setValue(value);
|
||||
codeEditor.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
export function codeEditorDirective() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: editorTemplate,
|
||||
scope: {
|
||||
content: '=',
|
||||
datasource: '=',
|
||||
codeEditorFocus: '<',
|
||||
onChange: '&',
|
||||
getCompleter: '&',
|
||||
},
|
||||
link: link,
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('codeEditor', codeEditorDirective);
|
|
@ -1,117 +0,0 @@
|
|||
ace.define(
|
||||
'ace/theme/grafana-dark',
|
||||
['require', 'exports', 'module', 'ace/lib/dom'],
|
||||
function (acequire, exports, module) {
|
||||
'use strict';
|
||||
|
||||
exports.isDark = true;
|
||||
exports.cssClass = 'gf-code-dark';
|
||||
exports.cssText =
|
||||
'.gf-code-dark .ace_gutter {\
|
||||
background: #2f3129;\
|
||||
color: #8f908a\
|
||||
}\
|
||||
.gf-code-dark .ace_print-margin {\
|
||||
width: 1px;\
|
||||
background: #555651\
|
||||
}\
|
||||
.gf-code-dark {\
|
||||
background-color: #09090b;\
|
||||
color: #e0e0e0\
|
||||
}\
|
||||
.gf-code-dark .ace_cursor {\
|
||||
color: #f8f8f0\
|
||||
}\
|
||||
.gf-code-dark .ace_marker-layer .ace_selection {\
|
||||
background: #49483e\
|
||||
}\
|
||||
.gf-code-dark.ace_multiselect .ace_selection.ace_start {\
|
||||
box-shadow: 0 0 3px 0px #272822;\
|
||||
}\
|
||||
.gf-code-dark .ace_marker-layer .ace_step {\
|
||||
background: rgb(102, 82, 0)\
|
||||
}\
|
||||
.gf-code-dark .ace_marker-layer .ace_bracket {\
|
||||
margin: -1px 0 0 -1px;\
|
||||
border: 1px solid #49483e\
|
||||
}\
|
||||
.gf-code-dark .ace_marker-layer .ace_active-line {\
|
||||
background: #202020\
|
||||
}\
|
||||
.gf-code-dark .ace_gutter-active-line {\
|
||||
background-color: #272727\
|
||||
}\
|
||||
.gf-code-dark .ace_marker-layer .ace_selected-word {\
|
||||
border: 1px solid #49483e\
|
||||
}\
|
||||
.gf-code-dark .ace_invisible {\
|
||||
color: #52524d\
|
||||
}\
|
||||
.gf-code-dark .ace_entity.ace_name.ace_tag,\
|
||||
.gf-code-dark .ace_keyword,\
|
||||
.gf-code-dark .ace_meta.ace_tag,\
|
||||
.gf-code-dark .ace_storage {\
|
||||
color: #66d9ef\
|
||||
}\
|
||||
.gf-code-dark .ace_punctuation,\
|
||||
.gf-code-dark .ace_punctuation.ace_tag {\
|
||||
color: #fff\
|
||||
}\
|
||||
.gf-code-dark .ace_constant.ace_character,\
|
||||
.gf-code-dark .ace_constant.ace_language,\
|
||||
.gf-code-dark .ace_constant.ace_numeric,\
|
||||
.gf-code-dark .ace_constant.ace_other {\
|
||||
color: #fe85fc\
|
||||
}\
|
||||
.gf-code-dark .ace_invalid {\
|
||||
color: #f8f8f0;\
|
||||
background-color: #f92672\
|
||||
}\
|
||||
.gf-code-dark .ace_invalid.ace_deprecated {\
|
||||
color: #f8f8f0;\
|
||||
background-color: #ae81ff\
|
||||
}\
|
||||
.gf-code-dark .ace_support.ace_constant,\
|
||||
.gf-code-dark .ace_support.ace_function {\
|
||||
color: #59e6e3\
|
||||
}\
|
||||
.gf-code-dark .ace_fold {\
|
||||
background-color: #a6e22e;\
|
||||
border-color: #f8f8f2\
|
||||
}\
|
||||
.gf-code-dark .ace_storage.ace_type,\
|
||||
.gf-code-dark .ace_support.ace_class,\
|
||||
.gf-code-dark .ace_support.ace_type {\
|
||||
font-style: italic;\
|
||||
color: #66d9ef\
|
||||
}\
|
||||
.gf-code-dark .ace_entity.ace_name.ace_function,\
|
||||
.gf-code-dark .ace_entity.ace_other,\
|
||||
.gf-code-dark .ace_entity.ace_other.ace_attribute-name,\
|
||||
.gf-code-dark .ace_variable {\
|
||||
color: #a6e22e\
|
||||
}\
|
||||
.gf-code-dark .ace_variable.ace_parameter {\
|
||||
font-style: italic;\
|
||||
color: #fd971f\
|
||||
}\
|
||||
.gf-code-dark .ace_string {\
|
||||
color: #74e680\
|
||||
}\
|
||||
.gf-code-dark .ace_paren {\
|
||||
color: #f0a842\
|
||||
}\
|
||||
.gf-code-dark .ace_operator {\
|
||||
color: #FFF\
|
||||
}\
|
||||
.gf-code-dark .ace_comment {\
|
||||
color: #75715e\
|
||||
}\
|
||||
.gf-code-dark .ace_indent-guide {\
|
||||
background: url(data:image/png;base64,ivborw0kggoaaaansuheugaaaaeaaaaccayaaaczgbynaaaaekleqvqimwpq0fd0zxbzd/wpaajvaoxesgneaaaaaelftksuqmcc) right repeat-y\
|
||||
}';
|
||||
|
||||
const dom = acequire('../lib/dom');
|
||||
dom.importCssString(exports.cssText, exports.cssClass);
|
||||
}
|
||||
);
|
|
@ -1,286 +0,0 @@
|
|||
import { ISCEService } from 'angular';
|
||||
import { debounce, find, indexOf, map, isObject, escape, unescape } from 'lodash';
|
||||
|
||||
import coreModule from '../../core_module';
|
||||
import { promiseToDigest } from '../../promiseToDigest';
|
||||
|
||||
function typeaheadMatcher(this: any, item: string) {
|
||||
let str = this.query;
|
||||
if (str === '') {
|
||||
return true;
|
||||
}
|
||||
if (str[0] === '/') {
|
||||
str = str.substring(1);
|
||||
}
|
||||
if (str[str.length - 1] === '/') {
|
||||
str = str.substring(0, str.length - 1);
|
||||
}
|
||||
return item.toLowerCase().match(str.toLowerCase());
|
||||
}
|
||||
|
||||
export class FormDropdownCtrl {
|
||||
inputElement: JQLite;
|
||||
linkElement: JQLite;
|
||||
model: any;
|
||||
display: any;
|
||||
text: any;
|
||||
options: any;
|
||||
cssClass: any;
|
||||
cssClasses: any;
|
||||
allowCustom: any;
|
||||
labelMode: boolean;
|
||||
linkMode: boolean;
|
||||
cancelBlur: any;
|
||||
onChange: any;
|
||||
getOptions: any;
|
||||
optionCache: any;
|
||||
lookupText: boolean;
|
||||
placeholder: any;
|
||||
startOpen: any;
|
||||
debounce: boolean;
|
||||
|
||||
static $inject = ['$scope', '$element', '$sce', 'templateSrv'];
|
||||
|
||||
constructor(
|
||||
private $scope: any,
|
||||
$element: JQLite,
|
||||
private $sce: ISCEService,
|
||||
private templateSrv: any
|
||||
) {
|
||||
this.inputElement = $element.find('input').first();
|
||||
this.linkElement = $element.find('a').first();
|
||||
this.linkMode = true;
|
||||
this.cancelBlur = null;
|
||||
this.labelMode = false;
|
||||
this.lookupText = false;
|
||||
this.debounce = false;
|
||||
|
||||
// listen to model changes
|
||||
$scope.$watch('ctrl.model', this.modelChanged.bind(this));
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
if (this.labelMode) {
|
||||
this.cssClasses = 'gf-form-label ' + this.cssClass;
|
||||
} else {
|
||||
this.cssClasses = 'gf-form-input gf-form-input--dropdown ' + this.cssClass;
|
||||
}
|
||||
|
||||
if (this.placeholder) {
|
||||
this.inputElement.attr('placeholder', this.placeholder);
|
||||
}
|
||||
|
||||
this.inputElement.attr('data-provide', 'typeahead');
|
||||
this.inputElement.typeahead({
|
||||
source: this.typeaheadSource.bind(this),
|
||||
minLength: 0,
|
||||
items: 10000,
|
||||
updater: this.typeaheadUpdater.bind(this),
|
||||
matcher: typeaheadMatcher,
|
||||
});
|
||||
|
||||
// modify typeahead lookup
|
||||
// this = typeahead
|
||||
const typeahead = this.inputElement.data('typeahead');
|
||||
typeahead.lookup = function () {
|
||||
this.query = this.$element.val() || '';
|
||||
this.source(this.query, this.process.bind(this));
|
||||
};
|
||||
|
||||
if (this.debounce) {
|
||||
typeahead.lookup = debounce(typeahead.lookup, 500, { leading: true });
|
||||
}
|
||||
|
||||
this.linkElement.keydown((evt) => {
|
||||
// trigger typeahead on down arrow or enter key
|
||||
if (evt.keyCode === 40 || evt.keyCode === 13) {
|
||||
this.linkElement.click();
|
||||
}
|
||||
});
|
||||
|
||||
this.inputElement.keydown((evt) => {
|
||||
if (evt.keyCode === 13) {
|
||||
setTimeout(() => {
|
||||
this.inputElement.blur();
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
this.inputElement.blur(this.inputBlur.bind(this));
|
||||
|
||||
if (this.startOpen) {
|
||||
setTimeout(this.open.bind(this), 0);
|
||||
}
|
||||
}
|
||||
|
||||
getOptionsInternal(query: string) {
|
||||
return promiseToDigest(this.$scope)(Promise.resolve(this.getOptions({ $query: query })));
|
||||
}
|
||||
|
||||
isPromiseLike(obj: any) {
|
||||
return obj && typeof obj.then === 'function';
|
||||
}
|
||||
|
||||
modelChanged() {
|
||||
if (isObject(this.model)) {
|
||||
this.updateDisplay((this.model as any).text);
|
||||
} else {
|
||||
// if we have text use it
|
||||
if (this.lookupText) {
|
||||
this.getOptionsInternal('').then((options: any) => {
|
||||
const item: any = find(options, { value: this.model });
|
||||
this.updateDisplay(item ? item.text : this.model);
|
||||
});
|
||||
} else {
|
||||
this.updateDisplay(this.model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typeaheadSource(query: string, callback: (res: any) => void) {
|
||||
this.getOptionsInternal(query).then((options: any) => {
|
||||
this.optionCache = options;
|
||||
|
||||
// extract texts
|
||||
const optionTexts = map(options, (op: any) => {
|
||||
return escape(op.text);
|
||||
});
|
||||
|
||||
// add custom values
|
||||
if (this.allowCustom && this.text !== '') {
|
||||
if (indexOf(optionTexts, this.text) === -1) {
|
||||
optionTexts.unshift(this.text);
|
||||
}
|
||||
}
|
||||
|
||||
callback(optionTexts);
|
||||
});
|
||||
}
|
||||
|
||||
typeaheadUpdater(text: string) {
|
||||
if (text === this.text) {
|
||||
clearTimeout(this.cancelBlur);
|
||||
this.inputElement.focus();
|
||||
return text;
|
||||
}
|
||||
|
||||
this.inputElement.val(text);
|
||||
this.switchToLink(true);
|
||||
return text;
|
||||
}
|
||||
|
||||
switchToLink(fromClick: boolean) {
|
||||
if (this.linkMode && !fromClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.cancelBlur);
|
||||
this.cancelBlur = null;
|
||||
this.linkMode = true;
|
||||
this.inputElement.hide();
|
||||
this.linkElement.show();
|
||||
this.updateValue(this.inputElement.val() as string);
|
||||
}
|
||||
|
||||
inputBlur() {
|
||||
// happens long before the click event on the typeahead options
|
||||
// need to have long delay because the blur
|
||||
this.cancelBlur = setTimeout(this.switchToLink.bind(this), 200);
|
||||
}
|
||||
|
||||
updateValue(text: string) {
|
||||
text = unescape(text);
|
||||
|
||||
if (text === '' || this.text === text) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$scope.$apply(() => {
|
||||
const option: any = find(this.optionCache, { text: text });
|
||||
|
||||
if (option) {
|
||||
if (isObject(this.model)) {
|
||||
this.model = option;
|
||||
} else {
|
||||
this.model = option.value;
|
||||
}
|
||||
this.text = option.text;
|
||||
} else if (this.allowCustom) {
|
||||
if (isObject(this.model)) {
|
||||
(this.model as any).text = (this.model as any).value = text;
|
||||
} else {
|
||||
this.model = text;
|
||||
}
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
// needs to call this after digest so
|
||||
// property is synced with outerscope
|
||||
this.$scope.$$postDigest(() => {
|
||||
this.$scope.$apply(() => {
|
||||
this.onChange({ $option: option });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateDisplay(text: string) {
|
||||
this.text = text;
|
||||
this.display = this.$sce.trustAsHtml(this.templateSrv.highlightVariablesAsHtml(text));
|
||||
}
|
||||
|
||||
open() {
|
||||
this.inputElement.css('width', Math.max(this.linkElement.width()!, 80) + 16 + 'px');
|
||||
|
||||
this.inputElement.show();
|
||||
this.inputElement.focus();
|
||||
|
||||
this.linkElement.hide();
|
||||
this.linkMode = false;
|
||||
|
||||
const typeahead = this.inputElement.data('typeahead');
|
||||
if (typeahead) {
|
||||
this.inputElement.val('');
|
||||
typeahead.lookup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const template = `
|
||||
<input type="text"
|
||||
data-provide="typeahead"
|
||||
class="gf-form-input"
|
||||
spellcheck="false"
|
||||
style="display:none">
|
||||
</input>
|
||||
<a ng-class="ctrl.cssClasses"
|
||||
tabindex="1"
|
||||
ng-click="ctrl.open()"
|
||||
give-focus="ctrl.focus"
|
||||
ng-bind-html="ctrl.display || ' '">
|
||||
</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