diff --git a/docs/Makefile b/docs/Makefile
index 84bd02b6089..f7766a2c26d 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -1,6 +1,6 @@
.PHONY: docs docs-test
-IMAGE = grafana/docs-base:latest
+IMAGE = grafana/docs-base:latest
docs:
docker pull ${IMAGE}
diff --git a/packages/grafana-ui/src/types/icon.ts b/packages/grafana-ui/src/types/icon.ts
index cb1b6d3e9d5..d319687565b 100644
--- a/packages/grafana-ui/src/types/icon.ts
+++ b/packages/grafana-ui/src/types/icon.ts
@@ -113,7 +113,8 @@ export type IconName =
| 'ellipsis-v'
| 'favorite'
| 'line-alt'
- | 'sort-amount-down';
+ | 'sort-amount-down'
+ | 'cloud';
export const getAvailableIcons = (): IconName[] => [
'fa fa-spinner',
@@ -226,4 +227,5 @@ export const getAvailableIcons = (): IconName[] => [
'ellipsis-v',
'favorite',
'sort-amount-down',
+ 'cloud',
];
diff --git a/public/app/features/dashboard/components/DashLinks/DashLinksContainerCtrl.ts b/public/app/features/dashboard/components/DashLinks/DashLinksContainerCtrl.ts
deleted file mode 100644
index 61fb378b621..00000000000
--- a/public/app/features/dashboard/components/DashLinks/DashLinksContainerCtrl.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-import angular from 'angular';
-import _ from 'lodash';
-import { iconMap } from './DashLinksEditorCtrl';
-import { LinkSrv } from 'app/features/panel/panellinks/link_srv';
-import { backendSrv } from 'app/core/services/backend_srv';
-import { DashboardSrv } from '../../services/DashboardSrv';
-import { PanelEvents } from '@grafana/data';
-import { CoreEvents } from 'app/types';
-import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
-import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
-
-export type DashboardLink = { tags: any; target: string; keepTime: any; includeVars: any };
-
-function dashLinksContainer() {
- return {
- scope: {
- links: '=',
- dashboard: '=',
- },
- restrict: 'E',
- controller: 'DashLinksContainerCtrl',
- template: '',
- link: () => {},
- };
-}
-
-/** @ngInject */
-function dashLink($compile: any, $sanitize: any, linkSrv: LinkSrv) {
- return {
- restrict: 'E',
- link: (scope: any, elem: JQuery) => {
- const link = scope.link;
- const dashboard = scope.dashboard;
-
- let template =
- '
';
-
- elem.html(template);
- $compile(elem.contents())(scope);
-
- function update() {
- const linkInfo = linkSrv.getAnchorInfo(link);
-
- const anchor = elem.find('a');
- const span = elem.find('span');
- span.text(linkInfo.title);
-
- if (!link.asDropdown) {
- anchor.attr('href', linkInfo.href);
- sanitizeAnchor();
- }
- anchor.attr('data-placement', 'bottom');
- // tooltip
- anchor.tooltip({
- title: $sanitize(scope.link.tooltip),
- html: true,
- container: 'body',
- });
- }
-
- function sanitizeAnchor() {
- const anchor = elem.find('a');
- const anchorSanitized = $sanitize(anchor.parent().html());
- anchor.parent().html(anchorSanitized);
- }
-
- elem.find('i').attr('class', 'fa fa-fw ' + scope.link.icon);
- elem.find('a').attr('target', scope.link.target);
-
- // fix for menus on the far right
- if (link.asDropdown && scope.$last) {
- elem.find('.dropdown-menu').addClass('pull-right');
- }
-
- update();
- dashboard.events.on(PanelEvents.refresh, update, scope);
- },
- };
-}
-
-export class DashLinksContainerCtrl {
- /** @ngInject */
- constructor($scope: any, $rootScope: GrafanaRootScope, dashboardSrv: DashboardSrv, linkSrv: LinkSrv) {
- const currentDashId = dashboardSrv.getCurrent().id;
-
- function buildLinks(linkDef: any) {
- if (linkDef.type === 'dashboards') {
- if (!linkDef.tags) {
- console.log('Dashboard link missing tag');
- return Promise.resolve([]);
- }
-
- if (linkDef.asDropdown) {
- return Promise.resolve([
- {
- title: linkDef.title,
- tags: linkDef.tags,
- keepTime: linkDef.keepTime,
- includeVars: linkDef.includeVars,
- target: linkDef.targetBlank ? '_blank' : '_self',
- icon: 'bars',
- asDropdown: true,
- },
- ]);
- }
-
- return $scope.searchDashboards(linkDef, 7);
- }
-
- if (linkDef.type === 'link') {
- return Promise.resolve([
- {
- url: linkDef.url,
- title: linkDef.title,
- // @ts-ignore
- icon: iconMap[linkDef.icon],
- tooltip: linkDef.tooltip,
- target: linkDef.targetBlank ? '_blank' : '_self',
- keepTime: linkDef.keepTime,
- includeVars: linkDef.includeVars,
- },
- ]);
- }
-
- return Promise.resolve([]);
- }
-
- function updateDashLinks() {
- const promises = _.map($scope.links, buildLinks);
-
- Promise.all(promises).then(results => {
- $scope.generatedLinks = _.flatten(results);
- });
- }
-
- $scope.searchDashboards = (link: DashboardLink, limit: any) => {
- return promiseToDigest($scope)(
- backendSrv.search({ tag: link.tags, limit: limit }).then(results => {
- return _.reduce(
- results,
- (memo, dash) => {
- // do not add current dashboard
- if (dash.id !== currentDashId) {
- memo.push({
- title: dash.title,
- url: dash.url,
- target: link.target === '_self' ? '' : link.target,
- icon: 'table',
- keepTime: link.keepTime,
- includeVars: link.includeVars,
- });
- }
- return memo;
- },
- []
- );
- })
- );
- };
-
- $scope.fillDropdown = (link: { searchHits: any }) => {
- $scope.searchDashboards(link, 100).then((results: any) => {
- _.each(results, hit => {
- hit.url = linkSrv.getLinkUrl(hit);
- });
- link.searchHits = results;
- });
- };
-
- updateDashLinks();
- $rootScope.onAppEvent(CoreEvents.dashLinksUpdated, updateDashLinks, $scope);
- }
-}
-
-angular.module('grafana.directives').directive('dashLinksContainer', dashLinksContainer);
-angular.module('grafana.directives').directive('dashLink', dashLink);
-angular.module('grafana.directives').controller('DashLinksContainerCtrl', DashLinksContainerCtrl);
diff --git a/public/app/features/dashboard/components/DashLinks/DashLinksEditorCtrl.ts b/public/app/features/dashboard/components/DashLinks/DashLinksEditorCtrl.ts
index fd2d60969f4..a25726c5ace 100644
--- a/public/app/features/dashboard/components/DashLinks/DashLinksEditorCtrl.ts
+++ b/public/app/features/dashboard/components/DashLinks/DashLinksEditorCtrl.ts
@@ -4,14 +4,14 @@ import { DashboardModel } from 'app/features/dashboard/state';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { CoreEvents } from 'app/types';
-export let iconMap = {
- 'external link': 'fa-external-link',
- dashboard: 'fa-th-large',
- question: 'fa-question',
- info: 'fa-info',
- bolt: 'fa-bolt',
- doc: 'fa-file-text-o',
- cloud: 'fa-cloud',
+export let iconMap: { [key: string]: string } = {
+ 'external link': 'external-link-alt',
+ dashboard: 'apps',
+ question: 'question-circle',
+ info: 'info-circle',
+ bolt: 'bolt',
+ doc: 'file-alt',
+ cloud: 'cloud',
};
export class DashLinksEditorCtrl {
@@ -62,7 +62,6 @@ export class DashLinksEditorCtrl {
editLink(link: any) {
this.link = link;
this.mode = 'edit';
- console.log(this.link);
}
saveLink() {
diff --git a/public/app/features/dashboard/components/DashLinks/editor.html b/public/app/features/dashboard/components/DashLinks/editor.html
deleted file mode 100644
index 0aa73c8192d..00000000000
--- a/public/app/features/dashboard/components/DashLinks/editor.html
+++ /dev/null
@@ -1,185 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- | Type |
- Info |
- |
-
-
-
-
- |
-
- {{ link.type }}
- |
-
-
- {{ link.title }}
-
-
- {{ link.url }}
-
-
- {{ tag }}
-
- |
-
-
- |
-
-
- |
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/app/features/dashboard/components/DashLinks/index.ts b/public/app/features/dashboard/components/DashLinks/index.ts
index ef118d4a84c..61585668048 100644
--- a/public/app/features/dashboard/components/DashLinks/index.ts
+++ b/public/app/features/dashboard/components/DashLinks/index.ts
@@ -1,2 +1 @@
-export { DashLinksContainerCtrl } from './DashLinksContainerCtrl';
export { DashLinksEditorCtrl } from './DashLinksEditorCtrl';
diff --git a/public/app/features/dashboard/components/SubMenu/AngularDashboardLinks.tsx b/public/app/features/dashboard/components/SubMenu/AngularDashboardLinks.tsx
deleted file mode 100644
index 82e4159d500..00000000000
--- a/public/app/features/dashboard/components/SubMenu/AngularDashboardLinks.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React, { PureComponent } from 'react';
-import { AngularComponent, getAngularLoader } from '@grafana/runtime';
-
-import { DashboardModel } from '../../state/DashboardModel';
-
-export interface Props {
- dashboard: DashboardModel | null;
-}
-
-export class AngularDashboardLinks extends PureComponent {
- element: HTMLElement;
- angularCmp: AngularComponent;
-
- componentDidMount() {
- if (!this.hasLinks()) {
- return;
- }
-
- const loader = getAngularLoader();
- const template = '';
- const scopeProps = {
- dashboard: this.props.dashboard,
- links: this.props.dashboard.links,
- };
-
- this.angularCmp = loader.load(this.element, scopeProps, template);
- }
-
- componentWillUnmount() {
- if (this.angularCmp) {
- this.angularCmp.destroy();
- }
- }
-
- hasLinks = () => this.props.dashboard.links.length > 0;
-
- render() {
- if (!this.hasLinks()) {
- return null;
- }
- return (this.element = element)} />;
- }
-}
diff --git a/public/app/features/dashboard/components/SubMenu/AngularSubMenu.tsx b/public/app/features/dashboard/components/SubMenu/AngularSubMenu.tsx
deleted file mode 100644
index 4faca44be2c..00000000000
--- a/public/app/features/dashboard/components/SubMenu/AngularSubMenu.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-// Libaries
-import React, { PureComponent } from 'react';
-
-// Utils & Services
-import { AngularComponent, getAngularLoader } from '@grafana/runtime';
-
-// Types
-import { DashboardModel } from '../../state/DashboardModel';
-
-export interface Props {
- dashboard: DashboardModel | null;
-}
-
-export class AngularSubMenu extends PureComponent
{
- element: HTMLElement;
- angularCmp: AngularComponent;
-
- componentDidMount() {
- const loader = getAngularLoader();
-
- const template = '';
- const scopeProps = { dashboard: this.props.dashboard };
-
- this.angularCmp = loader.load(this.element, scopeProps, template);
- }
-
- componentWillUnmount() {
- if (this.angularCmp) {
- this.angularCmp.destroy();
- }
- }
-
- render() {
- return (this.element = element)} />;
- }
-}
diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx
new file mode 100644
index 00000000000..39092e48806
--- /dev/null
+++ b/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx
@@ -0,0 +1,43 @@
+import React, { FC } from 'react';
+import { Icon, IconName, Tooltip } from '@grafana/ui';
+import { sanitize, sanitizeUrl } from '@grafana/data/src/text/sanitize';
+import { DashboardsDropdown } from './DashboardsDropdown';
+import { getLinkSrv } from '../../../panel/panellinks/link_srv';
+
+import { DashboardModel } from '../../state';
+import { DashboardLink } from '../../state/DashboardModel';
+import { iconMap } from '../DashLinks/DashLinksEditorCtrl';
+
+export interface Props {
+ dashboard: DashboardModel;
+}
+
+export const DashboardLinks: FC
= ({ dashboard }) => {
+ return (
+ dashboard.links.length > 0 && (
+ <>
+ {dashboard.links.map((link: DashboardLink, index: number) => {
+ const linkInfo = getLinkSrv().getAnchorInfo(link);
+ const key = `${link.title}-$${index}`;
+
+ if (link.asDropdown) {
+ return ;
+ }
+
+ const linkElement = (
+
+
+ {sanitize(linkInfo.title)}
+
+ );
+
+ return (
+
+ {link.tooltip ? {linkElement} : linkElement}
+
+ );
+ })}
+ >
+ )
+ );
+};
diff --git a/public/app/features/dashboard/components/SubMenu/DashboardsDropdown.tsx b/public/app/features/dashboard/components/SubMenu/DashboardsDropdown.tsx
new file mode 100644
index 00000000000..08fc46d0324
--- /dev/null
+++ b/public/app/features/dashboard/components/SubMenu/DashboardsDropdown.tsx
@@ -0,0 +1,71 @@
+import React, { PureComponent } from 'react';
+import { Icon } from '@grafana/ui';
+import { sanitize, sanitizeUrl } from '@grafana/data/src/text/sanitize';
+import { getBackendSrv } from 'app/core/services/backend_srv';
+import { DashboardSearchHit } from 'app/features/search/types';
+import { getLinkSrv } from '../../../panel/panellinks/link_srv';
+import { DashboardLink } from '../../state/DashboardModel';
+
+interface Props {
+ link: DashboardLink;
+ linkInfo: { title: string; href: string };
+ dashboardId: any;
+}
+
+interface State {
+ searchHits: DashboardSearchHit[];
+}
+
+export class DashboardsDropdown extends PureComponent {
+ state = { searchHits: [] as DashboardSearchHit[] };
+ onDropDownClick = async () => {
+ const { dashboardId, link } = this.props;
+
+ const limit = 7;
+ const dashboards = await getBackendSrv().search({ tag: link.tags, limit });
+ const processed = dashboards
+ .filter(dash => dash.id !== dashboardId)
+ .map(dash => {
+ return {
+ ...dash,
+ url: getLinkSrv().getLinkUrl(dash),
+ };
+ });
+
+ this.setState({
+ searchHits: processed,
+ });
+ };
+
+ render() {
+ const { link, linkInfo } = this.props;
+ const { searchHits } = this.state;
+
+ return (
+
+ );
+ }
+}
diff --git a/public/app/features/dashboard/components/SubMenu/SubMenu.tsx b/public/app/features/dashboard/components/SubMenu/SubMenu.tsx
index a3a3f92ab5f..45ce7ea9c31 100644
--- a/public/app/features/dashboard/components/SubMenu/SubMenu.tsx
+++ b/public/app/features/dashboard/components/SubMenu/SubMenu.tsx
@@ -4,7 +4,7 @@ import { StoreState } from '../../../../types';
import { getVariables } from '../../../variables/state/selectors';
import { VariableHide, VariableModel } from '../../../templating/types';
import { DashboardModel } from '../../state';
-import { AngularDashboardLinks } from './AngularDashboardLinks';
+import { DashboardLinks } from './DashboardLinks';
import { Annotations } from './Annotations';
import { SubMenuItems } from './SubMenuItems';
@@ -49,19 +49,17 @@ class SubMenuUnConnected extends PureComponent {
};
render() {
+ const { dashboard, variables } = this.props;
if (!this.isSubMenuVisible()) {
return null;
}
return (
-
-
+
+
-
+ {dashboard &&
}
);
diff --git a/public/app/features/dashboard/components/SubMenu/SubMenuCtrl.ts b/public/app/features/dashboard/components/SubMenu/SubMenuCtrl.ts
deleted file mode 100644
index 8a93d67a502..00000000000
--- a/public/app/features/dashboard/components/SubMenu/SubMenuCtrl.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import angular, { ILocationService } from 'angular';
-import _ from 'lodash';
-import { selectors } from '@grafana/e2e-selectors';
-import { VariableSrv } from 'app/features/templating/all';
-import { CoreEvents } from '../../../../types';
-
-export class SubMenuCtrl {
- annotations: any;
- variables: any;
- dashboard: any;
- submenuEnabled: boolean;
- selectors: typeof selectors.pages.Dashboard.SubMenu;
-
- /** @ngInject */
- constructor(private variableSrv: VariableSrv, private $location: ILocationService) {
- this.annotations = this.dashboard.templating.list;
- this.variables = this.variableSrv.variables;
- this.submenuEnabled = this.dashboard.meta.submenuEnabled;
- this.dashboard.events.on(CoreEvents.submenuVisibilityChanged, (enabled: boolean) => {
- this.submenuEnabled = enabled;
- });
- this.selectors = selectors.pages.Dashboard.SubMenu;
- }
-
- annotationStateChanged() {
- this.dashboard.startRefresh();
- }
-
- variableUpdated(variable: any) {
- this.variableSrv.variableUpdated(variable, true);
- }
-
- openEditView(editview: any) {
- const search = _.extend(this.$location.search(), { editview: editview });
- this.$location.search(search);
- }
-}
-
-export function submenuDirective() {
- return {
- restrict: 'E',
- templateUrl: 'public/app/features/dashboard/components/SubMenu/template.html',
- controller: SubMenuCtrl,
- bindToController: true,
- controllerAs: 'ctrl',
- scope: {
- dashboard: '=',
- },
- };
-}
-
-angular.module('grafana.directives').directive('dashboardSubmenu', submenuDirective);
diff --git a/public/app/features/dashboard/components/SubMenu/index.ts b/public/app/features/dashboard/components/SubMenu/index.ts
deleted file mode 100644
index 4148083a2e2..00000000000
--- a/public/app/features/dashboard/components/SubMenu/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { SubMenuCtrl } from './SubMenuCtrl';
-export { AngularSubMenu } from './AngularSubMenu';
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx
index 2c3ec361d66..ed840e350dd 100644
--- a/public/app/features/dashboard/containers/DashboardPage.tsx
+++ b/public/app/features/dashboard/containers/DashboardPage.tsx
@@ -12,7 +12,6 @@ import { Branding } from 'app/core/components/Branding/Branding';
// Components
import { DashboardGrid } from '../dashgrid/DashboardGrid';
import { DashNav } from '../components/DashNav';
-import { AngularSubMenu } from '../components/SubMenu';
import { DashboardSettings } from '../components/DashboardSettings';
import { PanelEditor } from '../components/PanelEditor/PanelEditor';
import { Alert, CustomScrollbar, Icon } from '@grafana/ui';
@@ -297,7 +296,7 @@ export class DashboardPage extends PureComponent {
{initError && this.renderInitFailedState()}
- {!editPanel && !featureToggles.newVariables &&
}
+ {!featureToggles.newVariables &&
}
{!editPanel && featureToggles.newVariables &&
}
(link: DataLink, scopedVars: ScopedVars, origin: T) => LinkModel;
+ getAnchorInfo: (link: any) => any;
+ getLinkUrl: (link: any) => string;
}
export class LinkSrv implements LinkService {