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 = - '
' + - '' + - ' '; - - if (link.asDropdown) { - template += - ''; - } - - 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 @@ -
-

- Dashboard Links - New - Edit -

- -
- - New - -
- -
-
- -
- -
- - - - - - - - - - - - - - - - - -
TypeInfo
- - {{ link.type }} - -
- {{ link.title }} -
-
- {{ link.url }} -
- - {{ tag }} - -
- - - - - - - -
-
-
- -
-
-
-
- Type -
- -
-
- -
- With tags - -
- - -
- Title - -
-
-
-
  • Url
  • - -
    - -
    - Title - -
    - -
    - Tooltip - -
    - -
    - Icon -
    - -
    -
    -
    -
    - -
    -
    Include
    -
    - - - -
    -
    -
    - - -
    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 ( +
    + + + {linkInfo.title} + + +
    + ); + } +} 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 {