Commit b4ab91d6 by Torkel Ödegaard

Merge remote-tracking branch 'origin/10248_copy_panels'

parents 8fb0692d 281e519f
...@@ -27,6 +27,7 @@ import './acl/acl'; ...@@ -27,6 +27,7 @@ import './acl/acl';
import './folder_picker/folder_picker'; import './folder_picker/folder_picker';
import './move_to_folder_modal/move_to_folder'; import './move_to_folder_modal/move_to_folder';
import './settings/settings'; import './settings/settings';
import './panel_clipboard_srv';
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import { DashboardListCtrl } from './dashboard_list_ctrl'; import { DashboardListCtrl } from './dashboard_list_ctrl';
......
...@@ -22,7 +22,8 @@ export class DashboardCtrl implements PanelContainer { ...@@ -22,7 +22,8 @@ export class DashboardCtrl implements PanelContainer {
private unsavedChangesSrv, private unsavedChangesSrv,
private dashboardViewStateSrv, private dashboardViewStateSrv,
public playlistSrv, public playlistSrv,
private panelLoader private panelLoader,
private panelClipboardSrv
) { ) {
// temp hack due to way dashboards are loaded // temp hack due to way dashboards are loaded
// can't use controllerAs on route yet // can't use controllerAs on route yet
...@@ -122,6 +123,10 @@ export class DashboardCtrl implements PanelContainer { ...@@ -122,6 +123,10 @@ export class DashboardCtrl implements PanelContainer {
return this.panelLoader; return this.panelLoader;
} }
getClipboardPanel() {
return this.panelClipboardSrv.getPanel();
}
timezoneChanged() { timezoneChanged() {
this.$rootScope.$broadcast('refresh'); this.$rootScope.$broadcast('refresh');
} }
......
...@@ -2,8 +2,8 @@ import React from 'react'; ...@@ -2,8 +2,8 @@ import React from 'react';
import _ from 'lodash'; import _ from 'lodash';
import config from 'app/core/config'; import config from 'app/core/config';
import {PanelModel} from '../panel_model'; import { PanelModel } from '../panel_model';
import {PanelContainer} from './PanelContainer'; import { PanelContainer } from './PanelContainer';
import ScrollBar from 'app/core/components/ScrollBar/ScrollBar'; import ScrollBar from 'app/core/components/ScrollBar/ScrollBar';
export interface AddPanelPanelProps { export interface AddPanelPanelProps {
...@@ -14,6 +14,7 @@ export interface AddPanelPanelProps { ...@@ -14,6 +14,7 @@ export interface AddPanelPanelProps {
export interface AddPanelPanelState { export interface AddPanelPanelState {
filter: string; filter: string;
panelPlugins: any[]; panelPlugins: any[];
clipboardPanel: any;
} }
export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelPanelState> { export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelPanelState> {
...@@ -22,45 +23,77 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP ...@@ -22,45 +23,77 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP
this.state = { this.state = {
panelPlugins: this.getPanelPlugins(), panelPlugins: this.getPanelPlugins(),
clipboardPanel: this.getClipboardPanel(),
filter: '', filter: '',
}; };
this.onPanelSelected = this.onPanelSelected.bind(this); this.onPanelSelected = this.onPanelSelected.bind(this);
this.onClipboardPanelSelected = this.onClipboardPanelSelected.bind(this);
} }
getPanelPlugins() { getPanelPlugins() {
let panels = _.chain(config.panels) let panels = _.chain(config.panels)
.filter({hideFromList: false}) .filter({ hideFromList: false })
.map(item => item) .map(item => item)
.value(); .value();
// add special row type // add special row type
panels.push({id: 'row', name: 'Row', sort: 8, info: {logos: {small: 'public/img/icn-row.svg'}}}); panels.push({ id: 'row', name: 'Row', sort: 8, info: { logos: { small: 'public/img/icn-row.svg' } } });
// add sort by sort property // add sort by sort property
return _.sortBy(panels, 'sort'); return _.sortBy(panels, 'sort');
} }
getClipboardPanel() {
return this.props.getPanelContainer().getClipboardPanel();
}
onPanelSelected(panelPluginInfo) { onPanelSelected(panelPluginInfo) {
const panelContainer = this.props.getPanelContainer(); const panelContainer = this.props.getPanelContainer();
const dashboard = panelContainer.getDashboard(); const dashboard = panelContainer.getDashboard();
const {gridPos} = this.props.panel; const { gridPos } = this.props.panel;
var newPanel: any = { var newPanel: any = {
type: panelPluginInfo.id, type: panelPluginInfo.id,
title: 'Panel Title', title: 'Panel Title',
gridPos: {x: gridPos.x, y: gridPos.y, w: gridPos.w, h: gridPos.h} gridPos: { x: gridPos.x, y: gridPos.y, w: gridPos.w, h: gridPos.h },
}; };
if (panelPluginInfo.id === 'row') { if (panelPluginInfo.id === 'row') {
newPanel.title = 'Row title'; newPanel.title = 'Row title';
newPanel.gridPos = {x: 0, y: 0}; newPanel.gridPos = { x: 0, y: 0 };
} }
dashboard.addPanel(newPanel); dashboard.addPanel(newPanel);
dashboard.removePanel(this.props.panel); dashboard.removePanel(this.props.panel);
} }
onClipboardPanelSelected(panel) {
const panelContainer = this.props.getPanelContainer();
const dashboard = panelContainer.getDashboard();
const { gridPos } = this.props.panel;
panel.gridPos.x = gridPos.x;
panel.gridPos.y = gridPos.y;
dashboard.addPanel(panel);
dashboard.removePanel(this.props.panel);
}
renderClipboardPanel(copiedPanel) {
const panel = copiedPanel.panel;
const title = `Paste copied panel '${panel.title}' from '${copiedPanel.dashboard}'`;
return (
<div className="add-panel__item" onClick={() => this.onClipboardPanelSelected(panel)} title={title}>
<div className="add-panel__item-icon">
<i className="fa fa-paste fa-2x fa-fw" />
</div>
<div className="add-panel__item-name">Paste copied panel</div>
</div>
);
}
renderPanelItem(panel) { renderPanelItem(panel) {
return ( return (
<div key={panel.id} className="add-panel__item" onClick={() => this.onPanelSelected(panel)} title={panel.name}> <div key={panel.id} className="add-panel__item" onClick={() => this.onPanelSelected(panel)} title={panel.name}>
...@@ -75,11 +108,12 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP ...@@ -75,11 +108,12 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP
<div className="panel-container"> <div className="panel-container">
<div className="add-panel"> <div className="add-panel">
<div className="add-panel__header"> <div className="add-panel__header">
<i className="gicon gicon-add-panel"></i> <i className="gicon gicon-add-panel" />
<span className="add-panel__title">New Panel</span> <span className="add-panel__title">New Panel</span>
<span className="add-panel__sub-title">Select a visualization</span> <span className="add-panel__sub-title">Select a visualization</span>
</div> </div>
<ScrollBar className="add-panel__items"> <ScrollBar className="add-panel__items">
{this.state.clipboardPanel && this.renderClipboardPanel(this.state.clipboardPanel)}
{this.state.panelPlugins.map(this.renderPanelItem.bind(this))} {this.state.panelPlugins.map(this.renderPanelItem.bind(this))}
</ScrollBar> </ScrollBar>
</div> </div>
...@@ -87,4 +121,3 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP ...@@ -87,4 +121,3 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP
); );
} }
} }
...@@ -4,4 +4,5 @@ import { PanelLoader } from './PanelLoader'; ...@@ -4,4 +4,5 @@ import { PanelLoader } from './PanelLoader';
export interface PanelContainer { export interface PanelContainer {
getPanelLoader(): PanelLoader; getPanelLoader(): PanelLoader;
getDashboard(): DashboardModel; getDashboard(): DashboardModel;
getClipboardPanel(): any;
} }
import coreModule from 'app/core/core_module';
import { appEvents } from 'app/core/core';
class PanelClipboardSrv {
key = 'GrafanaDashboardClipboardPanel';
/** @ngInject **/
constructor(private $window) {
appEvents.on('copy-dashboard-panel', this.copyDashboardPanel.bind(this));
}
getPanel() {
return this.$window[this.key];
}
private copyDashboardPanel(payload) {
this.$window[this.key] = payload;
}
}
coreModule.service('panelClipboardSrv', PanelClipboardSrv);
...@@ -195,6 +195,14 @@ export class PanelCtrl { ...@@ -195,6 +195,14 @@ export class PanelCtrl {
text: 'Panel JSON', text: 'Panel JSON',
click: 'ctrl.editPanelJson(); dismiss();', click: 'ctrl.editPanelJson(); dismiss();',
}); });
menu.push({
text: 'Copy to Clipboard',
click: 'ctrl.copyPanelToClipboard()',
role: 'Editor',
directives: ['clipboard-button="ctrl.getPanelJson()"'],
});
this.events.emit('init-panel-actions', menu); this.events.emit('init-panel-actions', menu);
return menu; return menu;
} }
...@@ -270,6 +278,17 @@ export class PanelCtrl { ...@@ -270,6 +278,17 @@ export class PanelCtrl {
}); });
} }
copyPanelToClipboard() {
appEvents.emit('copy-dashboard-panel', {
dashboard: this.dashboard.title,
panel: this.panel.getSaveModel(),
});
}
getPanelJson() {
return JSON.stringify(this.panel.getSaveModel(), null, 2);
}
replacePanel(newPanel, oldPanel) { replacePanel(newPanel, oldPanel) {
let dashboard = this.dashboard; let dashboard = this.dashboard;
let index = _.findIndex(dashboard.panels, panel => { let index = _.findIndex(dashboard.panels, panel => {
......
...@@ -51,6 +51,12 @@ function renderMenuItem(item, ctrl) { ...@@ -51,6 +51,12 @@ function renderMenuItem(item, ctrl) {
html += ` href="${item.href}"`; html += ` href="${item.href}"`;
} }
if (item.directives) {
for (let directive of item.directives) {
html += ` ${directive}`;
}
}
html += `><i class="${item.icon}"></i>`; html += `><i class="${item.icon}"></i>`;
html += `<span class="dropdown-item-text">${item.text}</span>`; html += `<span class="dropdown-item-text">${item.text}</span>`;
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
vertical-align: -15%; vertical-align: -15%;
} }
.#{$fa-css-prefix}-2x { .#{$fa-css-prefix}-2x {
font-size: 2em; font-size: 2em !important;
} }
.#{$fa-css-prefix}-3x { .#{$fa-css-prefix}-3x {
font-size: 3em; font-size: 3em;
......
...@@ -65,3 +65,7 @@ ...@@ -65,3 +65,7 @@
.add-panel__item-img { .add-panel__item-img {
height: calc(100% - 15px); height: calc(100% - 15px);
} }
.add-panel__item-icon {
padding: 2px;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment