mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into ui-new-red-green-blue
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
|
||||
export class AdHocFiltersCtrl {
|
||||
segments: any;
|
||||
variable: any;
|
||||
dashboard: DashboardModel;
|
||||
removeTagFilterSegment: any;
|
||||
|
||||
/** @ngInject */
|
||||
@@ -14,14 +16,13 @@ export class AdHocFiltersCtrl {
|
||||
private $q,
|
||||
private variableSrv,
|
||||
$scope,
|
||||
private $rootScope
|
||||
) {
|
||||
this.removeTagFilterSegment = uiSegmentSrv.newSegment({
|
||||
fake: true,
|
||||
value: '-- remove filter --',
|
||||
});
|
||||
this.buildSegmentModel();
|
||||
this.$rootScope.onAppEvent('template-variable-value-updated', this.buildSegmentModel.bind(this), $scope);
|
||||
this.dashboard.events.on('template-variable-value-updated', this.buildSegmentModel.bind(this), $scope);
|
||||
}
|
||||
|
||||
buildSegmentModel() {
|
||||
@@ -171,6 +172,7 @@ export function adHocFiltersComponent() {
|
||||
controllerAs: 'ctrl',
|
||||
scope: {
|
||||
variable: '=',
|
||||
dashboard: '=',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { AddPanelWidget, Props } from './AddPanelWidget';
|
||||
import { DashboardModel, PanelModel } from '../../state';
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
const props: Props = {
|
||||
dashboard: {} as DashboardModel,
|
||||
panel: {} as PanelModel,
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
||||
return shallow(<AddPanelWidget {...props} />);
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
it('should render component', () => {
|
||||
const wrapper = setup();
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,20 @@
|
||||
// Libraries
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
// Utils
|
||||
import config from 'app/core/config';
|
||||
import { PanelModel } from '../../state/PanelModel';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import store from 'app/core/store';
|
||||
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
|
||||
// Store
|
||||
import { store as reduxStore } from 'app/store/store';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
|
||||
// Types
|
||||
import { PanelModel } from '../../state';
|
||||
import { DashboardModel } from '../../state';
|
||||
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
|
||||
import { LocationUpdate } from 'app/types';
|
||||
|
||||
export interface Props {
|
||||
panel: PanelModel;
|
||||
@@ -46,6 +54,7 @@ export class AddPanelWidget extends React.Component<Props, State> {
|
||||
copiedPanels.push(pluginCopy);
|
||||
}
|
||||
}
|
||||
|
||||
return _.sortBy(copiedPanels, 'sort');
|
||||
}
|
||||
|
||||
@@ -54,28 +63,7 @@ export class AddPanelWidget extends React.Component<Props, State> {
|
||||
this.props.dashboard.removePanel(this.props.dashboard.panels[0]);
|
||||
}
|
||||
|
||||
copyButton(panel) {
|
||||
return (
|
||||
<button className="btn-inverse btn" onClick={() => this.onPasteCopiedPanel(panel)} title={panel.name}>
|
||||
Paste copied Panel
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
moveToEdit(panel) {
|
||||
reduxStore.dispatch(
|
||||
updateLocation({
|
||||
query: {
|
||||
panelId: panel.id,
|
||||
edit: true,
|
||||
fullscreen: true,
|
||||
},
|
||||
partial: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
onCreateNewPanel = () => {
|
||||
onCreateNewPanel = (tab = 'queries') => {
|
||||
const dashboard = this.props.dashboard;
|
||||
const { gridPos } = this.props.panel;
|
||||
|
||||
@@ -88,7 +76,21 @@ export class AddPanelWidget extends React.Component<Props, State> {
|
||||
dashboard.addPanel(newPanel);
|
||||
dashboard.removePanel(this.props.panel);
|
||||
|
||||
this.moveToEdit(newPanel);
|
||||
const location: LocationUpdate = {
|
||||
query: {
|
||||
panelId: newPanel.id,
|
||||
edit: true,
|
||||
fullscreen: true,
|
||||
},
|
||||
partial: true,
|
||||
};
|
||||
|
||||
if (tab === 'visualization') {
|
||||
location.query.tab = 'visualization';
|
||||
location.query.openVizPicker = true;
|
||||
}
|
||||
|
||||
reduxStore.dispatch(updateLocation(location));
|
||||
};
|
||||
|
||||
onPasteCopiedPanel = panelPluginInfo => {
|
||||
@@ -125,30 +127,50 @@ export class AddPanelWidget extends React.Component<Props, State> {
|
||||
dashboard.removePanel(this.props.panel);
|
||||
};
|
||||
|
||||
render() {
|
||||
let addCopyButton;
|
||||
renderOptionLink = (icon, text, onClick) => {
|
||||
return (
|
||||
<div>
|
||||
<a href="#" onClick={onClick} className="add-panel-widget__link btn btn-inverse">
|
||||
<div className="add-panel-widget__icon">
|
||||
<i className={`gicon gicon-${icon}`} />
|
||||
</div>
|
||||
<span>{text}</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (this.state.copiedPanelPlugins.length === 1) {
|
||||
addCopyButton = this.copyButton(this.state.copiedPanelPlugins[0]);
|
||||
}
|
||||
render() {
|
||||
const { copiedPanelPlugins } = this.state;
|
||||
|
||||
return (
|
||||
<div className="panel-container add-panel-widget-container">
|
||||
<div className="add-panel-widget">
|
||||
<div className="add-panel-widget__header grid-drag-handle">
|
||||
<i className="gicon gicon-add-panel" />
|
||||
<span className="add-panel-widget__title">New Panel</span>
|
||||
<button className="add-panel-widget__close" onClick={this.handleCloseAddPanel}>
|
||||
<i className="fa fa-close" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="add-panel-widget__btn-container">
|
||||
<button className="btn-primary btn btn-large" onClick={this.onCreateNewPanel}>
|
||||
Edit Panel
|
||||
</button>
|
||||
{addCopyButton}
|
||||
<button className="btn-inverse btn" onClick={this.onCreateNewRow}>
|
||||
Add Row
|
||||
</button>
|
||||
<div className="add-panel-widget__create">
|
||||
{this.renderOptionLink('queries', 'Add Query', this.onCreateNewPanel)}
|
||||
{this.renderOptionLink('visualization', 'Choose Visualization', () =>
|
||||
this.onCreateNewPanel('visualization')
|
||||
)}
|
||||
</div>
|
||||
<div className="add-panel-widget__actions">
|
||||
<button className="btn btn-inverse add-panel-widget__action" onClick={this.onCreateNewRow}>Convert to row</button>
|
||||
{copiedPanelPlugins.length === 1 && (
|
||||
<button
|
||||
className="btn btn-inverse add-panel-widget__action"
|
||||
onClick={() => this.onPasteCopiedPanel(copiedPanelPlugins[0])}
|
||||
>
|
||||
Paste copied panel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
cursor: move;
|
||||
background: $page-header-bg;
|
||||
box-shadow: $page-header-shadow;
|
||||
border-bottom: 1px solid $page-header-border-color;
|
||||
|
||||
.gicon {
|
||||
font-size: 30px;
|
||||
@@ -26,6 +29,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
.add-panel-widget__title {
|
||||
font-size: $font-size-md;
|
||||
font-weight: $font-weight-semi-bold;
|
||||
margin-right: $spacer*2;
|
||||
}
|
||||
|
||||
.add-panel-widget__link {
|
||||
margin: 0 8px;
|
||||
width: 154px;
|
||||
}
|
||||
|
||||
.add-panel-widget__icon {
|
||||
margin-bottom: 8px;
|
||||
|
||||
.gicon {
|
||||
color: white;
|
||||
height: 44px;
|
||||
width: 53px;
|
||||
position: relative;
|
||||
left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-panel-widget__close {
|
||||
margin-left: auto;
|
||||
background-color: transparent;
|
||||
@@ -34,14 +60,25 @@
|
||||
margin-right: -10px;
|
||||
}
|
||||
|
||||
.add-panel-widget__create {
|
||||
display: inherit;
|
||||
margin-bottom: 24px;
|
||||
// this is to have the big button appear centered
|
||||
margin-top: 55px;
|
||||
}
|
||||
|
||||
.add-panel-widget__actions {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.add-panel-widget__action {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.add-panel-widget__btn-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
.btn {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<div
|
||||
className="panel-container add-panel-widget-container"
|
||||
>
|
||||
<div
|
||||
className="add-panel-widget"
|
||||
>
|
||||
<div
|
||||
className="add-panel-widget__header grid-drag-handle"
|
||||
>
|
||||
<i
|
||||
className="gicon gicon-add-panel"
|
||||
/>
|
||||
<span
|
||||
className="add-panel-widget__title"
|
||||
>
|
||||
New Panel
|
||||
</span>
|
||||
<button
|
||||
className="add-panel-widget__close"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-close"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="add-panel-widget__btn-container"
|
||||
>
|
||||
<div
|
||||
className="add-panel-widget__create"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
className="add-panel-widget__link btn btn-inverse"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div
|
||||
className="add-panel-widget__icon"
|
||||
>
|
||||
<i
|
||||
className="gicon gicon-queries"
|
||||
/>
|
||||
</div>
|
||||
<span>
|
||||
Add Query
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
className="add-panel-widget__link btn btn-inverse"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div
|
||||
className="add-panel-widget__icon"
|
||||
>
|
||||
<i
|
||||
className="gicon gicon-visualization"
|
||||
/>
|
||||
</div>
|
||||
<span>
|
||||
Choose Visualization
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="add-panel-widget__actions"
|
||||
>
|
||||
<button
|
||||
className="btn btn-inverse add-panel-widget__action"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Convert to row
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,5 +1,6 @@
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
|
||||
export let iconMap = {
|
||||
'external link': 'fa-external-link',
|
||||
@@ -12,7 +13,7 @@ export let iconMap = {
|
||||
};
|
||||
|
||||
export class DashLinksEditorCtrl {
|
||||
dashboard: any;
|
||||
dashboard: DashboardModel;
|
||||
iconMap: any;
|
||||
mode: any;
|
||||
link: any;
|
||||
@@ -40,6 +41,7 @@ export class DashLinksEditorCtrl {
|
||||
addLink() {
|
||||
this.dashboard.links.push(this.link);
|
||||
this.mode = 'list';
|
||||
this.dashboard.updateSubmenuVisibility();
|
||||
}
|
||||
|
||||
editLink(link) {
|
||||
|
||||
253
public/app/features/dashboard/components/DashNav/DashNav.tsx
Normal file
253
public/app/features/dashboard/components/DashNav/DashNav.tsx
Normal file
@@ -0,0 +1,253 @@
|
||||
// Libaries
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
// Utils & Services
|
||||
import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader';
|
||||
import { appEvents } from 'app/core/app_events';
|
||||
import { PlaylistSrv } from 'app/features/playlist/playlist_srv';
|
||||
|
||||
// Components
|
||||
import { DashNavButton } from './DashNavButton';
|
||||
|
||||
// State
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
|
||||
// Types
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
|
||||
export interface Props {
|
||||
dashboard: DashboardModel;
|
||||
editview: string;
|
||||
isEditing: boolean;
|
||||
isFullscreen: boolean;
|
||||
$injector: any;
|
||||
updateLocation: typeof updateLocation;
|
||||
onAddPanel: () => void;
|
||||
}
|
||||
|
||||
export class DashNav extends PureComponent<Props> {
|
||||
timePickerEl: HTMLElement;
|
||||
timepickerCmp: AngularComponent;
|
||||
playlistSrv: PlaylistSrv;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.playlistSrv = this.props.$injector.get('playlistSrv');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const loader = getAngularLoader();
|
||||
|
||||
const template =
|
||||
'<gf-time-picker class="gf-timepicker-nav" dashboard="dashboard" ng-if="!dashboard.timepicker.hidden" />';
|
||||
const scopeProps = { dashboard: this.props.dashboard };
|
||||
|
||||
this.timepickerCmp = loader.load(this.timePickerEl, scopeProps, template);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.timepickerCmp) {
|
||||
this.timepickerCmp.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
onOpenSearch = () => {
|
||||
appEvents.emit('show-dash-search');
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
if (this.props.editview) {
|
||||
this.props.updateLocation({
|
||||
query: { editview: null },
|
||||
partial: true,
|
||||
});
|
||||
} else {
|
||||
this.props.updateLocation({
|
||||
query: { panelId: null, edit: null, fullscreen: null },
|
||||
partial: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onToggleTVMode = () => {
|
||||
appEvents.emit('toggle-kiosk-mode');
|
||||
};
|
||||
|
||||
onSave = () => {
|
||||
const { $injector } = this.props;
|
||||
const dashboardSrv = $injector.get('dashboardSrv');
|
||||
dashboardSrv.saveDashboard();
|
||||
};
|
||||
|
||||
onOpenSettings = () => {
|
||||
this.props.updateLocation({
|
||||
query: { editview: 'settings' },
|
||||
partial: true,
|
||||
});
|
||||
};
|
||||
|
||||
onStarDashboard = () => {
|
||||
const { dashboard, $injector } = this.props;
|
||||
const dashboardSrv = $injector.get('dashboardSrv');
|
||||
|
||||
dashboardSrv.starDashboard(dashboard.id, dashboard.meta.isStarred).then(newState => {
|
||||
dashboard.meta.isStarred = newState;
|
||||
this.forceUpdate();
|
||||
});
|
||||
};
|
||||
|
||||
onPlaylistPrev = () => {
|
||||
this.playlistSrv.prev();
|
||||
};
|
||||
|
||||
onPlaylistNext = () => {
|
||||
this.playlistSrv.next();
|
||||
};
|
||||
|
||||
onPlaylistStop = () => {
|
||||
this.playlistSrv.stop();
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onOpenShare = () => {
|
||||
const $rootScope = this.props.$injector.get('$rootScope');
|
||||
const modalScope = $rootScope.$new();
|
||||
modalScope.tabIndex = 0;
|
||||
modalScope.dashboard = this.props.dashboard;
|
||||
|
||||
appEvents.emit('show-modal', {
|
||||
src: 'public/app/features/dashboard/components/ShareModal/template.html',
|
||||
scope: modalScope,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dashboard, isFullscreen, editview, onAddPanel } = this.props;
|
||||
const { canStar, canSave, canShare, folderTitle, showSettings, isStarred } = dashboard.meta;
|
||||
const { snapshot } = dashboard;
|
||||
|
||||
const haveFolder = dashboard.meta.folderId > 0;
|
||||
const snapshotUrl = snapshot && snapshot.originalUrl;
|
||||
|
||||
return (
|
||||
<div className="navbar">
|
||||
<div>
|
||||
<a className="navbar-page-btn" onClick={this.onOpenSearch}>
|
||||
<i className="gicon gicon-dashboard" />
|
||||
{haveFolder && <span className="navbar-page-btn--folder">{folderTitle} / </span>}
|
||||
{dashboard.title}
|
||||
<i className="fa fa-caret-down" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="navbar__spacer" />
|
||||
|
||||
{this.playlistSrv.isPlaying && (
|
||||
<div className="navbar-buttons navbar-buttons--playlist">
|
||||
<DashNavButton
|
||||
tooltip="Go to previous dashboard"
|
||||
classSuffix="tight"
|
||||
icon="fa fa-step-backward"
|
||||
onClick={this.onPlaylistPrev}
|
||||
/>
|
||||
<DashNavButton
|
||||
tooltip="Stop playlist"
|
||||
classSuffix="tight"
|
||||
icon="fa fa-stop"
|
||||
onClick={this.onPlaylistStop}
|
||||
/>
|
||||
<DashNavButton
|
||||
tooltip="Go to next dashboard"
|
||||
classSuffix="tight"
|
||||
icon="fa fa-forward"
|
||||
onClick={this.onPlaylistNext}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="navbar-buttons navbar-buttons--actions">
|
||||
{canSave && (
|
||||
<DashNavButton
|
||||
tooltip="Add panel"
|
||||
classSuffix="add-panel"
|
||||
icon="gicon gicon-add-panel"
|
||||
onClick={onAddPanel}
|
||||
/>
|
||||
)}
|
||||
|
||||
{canStar && (
|
||||
<DashNavButton
|
||||
tooltip="Mark as favorite"
|
||||
classSuffix="star"
|
||||
icon={`${isStarred ? 'fa fa-star' : 'fa fa-star-o'}`}
|
||||
onClick={this.onStarDashboard}
|
||||
/>
|
||||
)}
|
||||
|
||||
{canShare && (
|
||||
<DashNavButton
|
||||
tooltip="Share dashboard"
|
||||
classSuffix="share"
|
||||
icon="fa fa-share-square-o"
|
||||
onClick={this.onOpenShare}
|
||||
/>
|
||||
)}
|
||||
|
||||
{canSave && (
|
||||
<DashNavButton tooltip="Save dashboard" classSuffix="save" icon="fa fa-save" onClick={this.onSave} />
|
||||
)}
|
||||
|
||||
{snapshotUrl && (
|
||||
<DashNavButton
|
||||
tooltip="Open original dashboard"
|
||||
classSuffix="snapshot-origin"
|
||||
icon="fa fa-link"
|
||||
href={snapshotUrl}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showSettings && (
|
||||
<DashNavButton
|
||||
tooltip="Dashboard settings"
|
||||
classSuffix="settings"
|
||||
icon="fa fa-cog"
|
||||
onClick={this.onOpenSettings}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="navbar-buttons navbar-buttons--tv">
|
||||
<DashNavButton
|
||||
tooltip="Cycke view mode"
|
||||
classSuffix="tv"
|
||||
icon="fa fa-desktop"
|
||||
onClick={this.onToggleTVMode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="gf-timepicker-nav" ref={element => (this.timePickerEl = element)} />
|
||||
|
||||
{(isFullscreen || editview) && (
|
||||
<div className="navbar-buttons navbar-buttons--close">
|
||||
<DashNavButton
|
||||
tooltip="Back to dashboard"
|
||||
classSuffix="primary"
|
||||
icon="fa fa-reply"
|
||||
onClick={this.onClose}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = () => ({});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
updateLocation,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DashNav);
|
||||
@@ -0,0 +1,33 @@
|
||||
// Libraries
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { Tooltip } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
icon: string;
|
||||
tooltip: string;
|
||||
classSuffix: string;
|
||||
onClick?: () => void;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
export const DashNavButton: FunctionComponent<Props> = ({ icon, tooltip, classSuffix, onClick, href }) => {
|
||||
if (onClick) {
|
||||
return (
|
||||
<Tooltip content={tooltip}>
|
||||
<button className={`btn navbar-button navbar-button--${classSuffix}`} onClick={onClick}>
|
||||
<i className={icon} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip content={tooltip}>
|
||||
<a className={`btn navbar-button navbar-button--${classSuffix}`} href={href}>
|
||||
<i className={icon} />
|
||||
</a>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -1,119 +0,0 @@
|
||||
import moment from 'moment';
|
||||
import angular from 'angular';
|
||||
import { appEvents, NavModel } from 'app/core/core';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
|
||||
export class DashNavCtrl {
|
||||
dashboard: DashboardModel;
|
||||
navModel: NavModel;
|
||||
titleTooltip: string;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $scope, private dashboardSrv, private $location, public playlistSrv) {
|
||||
appEvents.on('save-dashboard', this.saveDashboard.bind(this), $scope);
|
||||
|
||||
if (this.dashboard.meta.isSnapshot) {
|
||||
const meta = this.dashboard.meta;
|
||||
this.titleTooltip = 'Created: ' + moment(meta.created).calendar();
|
||||
if (meta.expires) {
|
||||
this.titleTooltip += '<br>Expires: ' + moment(meta.expires).fromNow() + '<br>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleSettings() {
|
||||
const search = this.$location.search();
|
||||
if (search.editview) {
|
||||
delete search.editview;
|
||||
} else {
|
||||
search.editview = 'settings';
|
||||
}
|
||||
this.$location.search(search);
|
||||
}
|
||||
|
||||
toggleViewMode() {
|
||||
appEvents.emit('toggle-kiosk-mode');
|
||||
}
|
||||
|
||||
close() {
|
||||
const search = this.$location.search();
|
||||
if (search.editview) {
|
||||
delete search.editview;
|
||||
} else if (search.fullscreen) {
|
||||
delete search.fullscreen;
|
||||
delete search.edit;
|
||||
delete search.tab;
|
||||
delete search.panelId;
|
||||
}
|
||||
this.$location.search(search);
|
||||
}
|
||||
|
||||
starDashboard() {
|
||||
this.dashboardSrv.starDashboard(this.dashboard.id, this.dashboard.meta.isStarred).then(newState => {
|
||||
this.dashboard.meta.isStarred = newState;
|
||||
});
|
||||
}
|
||||
|
||||
shareDashboard(tabIndex) {
|
||||
const modalScope = this.$scope.$new();
|
||||
modalScope.tabIndex = tabIndex;
|
||||
modalScope.dashboard = this.dashboard;
|
||||
|
||||
appEvents.emit('show-modal', {
|
||||
src: 'public/app/features/dashboard/components/ShareModal/template.html',
|
||||
scope: modalScope,
|
||||
});
|
||||
}
|
||||
|
||||
hideTooltip(evt) {
|
||||
angular.element(evt.currentTarget).tooltip('hide');
|
||||
}
|
||||
|
||||
saveDashboard() {
|
||||
return this.dashboardSrv.saveDashboard();
|
||||
}
|
||||
|
||||
showSearch() {
|
||||
if (this.dashboard.meta.fullscreen) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
appEvents.emit('show-dash-search');
|
||||
}
|
||||
|
||||
addPanel() {
|
||||
appEvents.emit('dash-scroll', { animate: true, evt: 0 });
|
||||
|
||||
if (this.dashboard.panels.length > 0 && this.dashboard.panels[0].type === 'add-panel') {
|
||||
return; // Return if the "Add panel" exists already
|
||||
}
|
||||
|
||||
this.dashboard.addPanel({
|
||||
type: 'add-panel',
|
||||
gridPos: { x: 0, y: 0, w: 12, h: 8 },
|
||||
title: 'Panel Title',
|
||||
});
|
||||
}
|
||||
|
||||
navItemClicked(navItem, evt) {
|
||||
if (navItem.clickHandler) {
|
||||
navItem.clickHandler();
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function dashNavDirective() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'public/app/features/dashboard/components/DashNav/template.html',
|
||||
controller: DashNavCtrl,
|
||||
bindToController: true,
|
||||
controllerAs: 'ctrl',
|
||||
transclude: true,
|
||||
scope: { dashboard: '=' },
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('grafana.directives').directive('dashnav', dashNavDirective);
|
||||
@@ -1 +1,2 @@
|
||||
export { DashNavCtrl } from './DashNavCtrl';
|
||||
import DashNav from './DashNav';
|
||||
export { DashNav };
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
<div class="navbar">
|
||||
|
||||
<div>
|
||||
<a class="navbar-page-btn" ng-click="ctrl.showSearch()">
|
||||
<i class="gicon gicon-dashboard"></i>
|
||||
<span ng-if="ctrl.dashboard.meta.folderId > 0" class="navbar-page-btn--folder">{{ctrl.dashboard.meta.folderTitle}} / </span>{{ctrl.dashboard.title}}
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="navbar__spacer"></div>
|
||||
|
||||
<div class="navbar-buttons navbar-buttons--playlist" ng-if="ctrl.playlistSrv.isPlaying">
|
||||
<a class="navbar-button navbar-button--tight" ng-click="ctrl.playlistSrv.prev()"><i class="fa fa-step-backward"></i></a>
|
||||
<a class="navbar-button navbar-button--tight" ng-click="ctrl.playlistSrv.stop()"><i class="fa fa-stop"></i></a>
|
||||
<a class="navbar-button navbar-button--tight" ng-click="ctrl.playlistSrv.next()"><i class="fa fa-step-forward"></i></a>
|
||||
</div>
|
||||
|
||||
<div class="navbar-buttons navbar-buttons--actions">
|
||||
<button class="btn navbar-button navbar-button--add-panel" ng-show="::ctrl.dashboard.meta.canSave" bs-tooltip="'Add panel'" data-placement="bottom" ng-click="ctrl.addPanel()">
|
||||
<i class="gicon gicon-add-panel"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn navbar-button navbar-button--star" ng-show="::ctrl.dashboard.meta.canStar" ng-click="ctrl.starDashboard()" bs-tooltip="'Mark as favorite'" data-placement="bottom">
|
||||
<i class="fa" ng-class="{'fa-star-o': !ctrl.dashboard.meta.isStarred, 'fa-star': ctrl.dashboard.meta.isStarred}"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn navbar-button navbar-button--share" ng-show="::ctrl.dashboard.meta.canShare" ng-click="ctrl.shareDashboard(0)" bs-tooltip="'Share dashboard'" data-placement="bottom">
|
||||
<i class="fa fa-share-square-o"></i></a>
|
||||
</button>
|
||||
|
||||
<button class="btn navbar-button navbar-button--save" ng-show="ctrl.dashboard.meta.canSave" ng-click="ctrl.saveDashboard()" bs-tooltip="'Save dashboard <br> CTRL+S'" data-placement="bottom">
|
||||
<i class="fa fa-save"></i>
|
||||
</button>
|
||||
|
||||
<a class="btn navbar-button navbar-button--snapshot-origin" ng-if="::ctrl.dashboard.snapshot.originalUrl" href="{{ctrl.dashboard.snapshot.originalUrl}}" bs-tooltip="'Open original dashboard'" data-placement="bottom">
|
||||
<i class="fa fa-link"></i>
|
||||
</a>
|
||||
|
||||
<button class="btn navbar-button navbar-button--settings" ng-click="ctrl.toggleSettings()" bs-tooltip="'Dashboard Settings'" data-placement="bottom" ng-show="ctrl.dashboard.meta.showSettings">
|
||||
<i class="fa fa-cog"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="navbar-buttons navbar-buttons--tv">
|
||||
<button class="btn navbar-button navbar-button--tv" ng-click="ctrl.toggleViewMode()" bs-tooltip="'Cycle view mode'" data-placement="bottom">
|
||||
<i class="fa fa-desktop"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<gf-time-picker class="gf-timepicker-nav" dashboard="ctrl.dashboard" ng-if="!ctrl.dashboard.timepicker.hidden"></gf-time-picker>
|
||||
|
||||
<div class="navbar-buttons navbar-buttons--close">
|
||||
<button class="btn navbar-button navbar-button--primary" ng-click="ctrl.close()" bs-tooltip="'Back to dashboard'" data-placement="bottom">
|
||||
<i class="fa fa-reply"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<dashboard-search></dashboard-search>
|
||||
@@ -9,6 +9,7 @@ describe('DashboardRow', () => {
|
||||
beforeEach(() => {
|
||||
dashboardMock = {
|
||||
toggleRow: jest.fn(),
|
||||
on: jest.fn(),
|
||||
meta: {
|
||||
canEdit: true,
|
||||
},
|
||||
|
||||
@@ -18,11 +18,11 @@ export class DashboardRow extends React.Component<DashboardRowProps, any> {
|
||||
collapsed: this.props.panel.collapsed,
|
||||
};
|
||||
|
||||
appEvents.on('template-variable-value-updated', this.onVariableUpdated);
|
||||
this.props.dashboard.on('template-variable-value-updated', this.onVariableUpdated);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
appEvents.off('template-variable-value-updated', this.onVariableUpdated);
|
||||
this.props.dashboard.off('template-variable-value-updated', this.onVariableUpdated);
|
||||
}
|
||||
|
||||
onVariableUpdated = () => {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Libaries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Utils & Services
|
||||
import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader';
|
||||
|
||||
// Types
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
|
||||
export interface Props {
|
||||
dashboard: DashboardModel | null;
|
||||
}
|
||||
|
||||
export class DashboardSettings extends PureComponent<Props> {
|
||||
element: HTMLElement;
|
||||
angularCmp: AngularComponent;
|
||||
|
||||
componentDidMount() {
|
||||
const loader = getAngularLoader();
|
||||
|
||||
const template = '<dashboard-settings dashboard="dashboard" class="dashboard-settings" />';
|
||||
const scopeProps = { dashboard: this.props.dashboard };
|
||||
|
||||
this.angularCmp = loader.load(this.element, scopeProps, template);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.angularCmp) {
|
||||
this.angularCmp.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className="panel-height-helper" ref={element => this.element = element} />;
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export { SettingsCtrl } from './SettingsCtrl';
|
||||
export { DashboardSettings } from './DashboardSettings';
|
||||
|
||||
36
public/app/features/dashboard/components/SubMenu/SubMenu.tsx
Normal file
36
public/app/features/dashboard/components/SubMenu/SubMenu.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
// Libaries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Utils & Services
|
||||
import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader';
|
||||
|
||||
// Types
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
|
||||
export interface Props {
|
||||
dashboard: DashboardModel | null;
|
||||
}
|
||||
|
||||
export class SubMenu extends PureComponent<Props> {
|
||||
element: HTMLElement;
|
||||
angularCmp: AngularComponent;
|
||||
|
||||
componentDidMount() {
|
||||
const loader = getAngularLoader();
|
||||
|
||||
const template = '<dashboard-submenu dashboard="dashboard" />';
|
||||
const scopeProps = { dashboard: this.props.dashboard };
|
||||
|
||||
this.angularCmp = loader.load(this.element, scopeProps, template);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.angularCmp) {
|
||||
this.angularCmp.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={element => this.element = element} />;
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export { SubMenuCtrl } from './SubMenuCtrl';
|
||||
export { SubMenu } from './SubMenu';
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<value-select-dropdown ng-if="variable.type !== 'adhoc' && variable.type !== 'textbox'" variable="variable" on-updated="ctrl.variableUpdated(variable)"></value-select-dropdown>
|
||||
<input type="text" ng-if="variable.type === 'textbox'" ng-model="variable.query" class="gf-form-input width-12" ng-blur="variable.current.value != variable.query && variable.updateOptions() && ctrl.variableUpdated(variable);" ng-keydown="$event.keyCode === 13 && variable.current.value != variable.query && variable.updateOptions() && ctrl.variableUpdated(variable);" ></input>
|
||||
</div>
|
||||
<ad-hoc-filters ng-if="variable.type === 'adhoc'" variable="variable"></ad-hoc-filters>
|
||||
<ad-hoc-filters ng-if="variable.type === 'adhoc'" variable="variable" dashboard="ctrl.dashboard"></ad-hoc-filters>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.dashboard.annotations.list.length > 0">
|
||||
|
||||
Reference in New Issue
Block a user