dashboard: copy panel to clipboard

Adds a new menu item to panels, Copy to Clipboard, that will both
copy the panel json to the clipboard and temporarily store the panel object
in the browsers window object. The temporarily stored panel object are
available in Add Panel from any dashboard for as long you don't refresh
the browser.
Fixes #10248, #1004
This commit is contained in:
Marcus Efraimsson 2017-12-21 18:10:24 +01:00
parent e480a38dc1
commit 68457f5636
9 changed files with 102 additions and 11 deletions

View File

@ -27,6 +27,7 @@ import './acl/acl';
import './folder_picker/folder_picker';
import './move_to_folder_modal/move_to_folder';
import './settings/settings';
import './panel_clipboard_srv';
import coreModule from 'app/core/core_module';
import { DashboardListCtrl } from './dashboard_list_ctrl';

View File

@ -22,7 +22,8 @@ export class DashboardCtrl implements PanelContainer {
private unsavedChangesSrv,
private dashboardViewStateSrv,
public playlistSrv,
private panelLoader
private panelLoader,
private panelClipboardSrv
) {
// temp hack due to way dashboards are loaded
// can't use controllerAs on route yet
@ -122,6 +123,10 @@ export class DashboardCtrl implements PanelContainer {
return this.panelLoader;
}
getClipboardPanel() {
return this.panelClipboardSrv.getPanel();
}
timezoneChanged() {
this.$rootScope.$broadcast('refresh');
}

View File

@ -2,8 +2,8 @@ import React from 'react';
import _ from 'lodash';
import config from 'app/core/config';
import {PanelModel} from '../panel_model';
import {PanelContainer} from './PanelContainer';
import { PanelModel } from '../panel_model';
import { PanelContainer } from './PanelContainer';
import ScrollBar from 'app/core/components/ScrollBar/ScrollBar';
export interface AddPanelPanelProps {
@ -14,6 +14,7 @@ export interface AddPanelPanelProps {
export interface AddPanelPanelState {
filter: string;
panelPlugins: any[];
clipboardPanel: any;
}
export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelPanelState> {
@ -22,45 +23,77 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP
this.state = {
panelPlugins: this.getPanelPlugins(),
clipboardPanel: this.getClipboardPanel(),
filter: '',
};
this.onPanelSelected = this.onPanelSelected.bind(this);
this.onClipboardPanelSelected = this.onClipboardPanelSelected.bind(this);
}
getPanelPlugins() {
let panels = _.chain(config.panels)
.filter({hideFromList: false})
.filter({ hideFromList: false })
.map(item => item)
.value();
// 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
return _.sortBy(panels, 'sort');
}
getClipboardPanel() {
return this.props.getPanelContainer().getClipboardPanel();
}
onPanelSelected(panelPluginInfo) {
const panelContainer = this.props.getPanelContainer();
const dashboard = panelContainer.getDashboard();
const {gridPos} = this.props.panel;
const { gridPos } = this.props.panel;
var newPanel: any = {
type: panelPluginInfo.id,
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') {
newPanel.title = 'Row title';
newPanel.gridPos = {x: 0, y: 0};
newPanel.gridPos = { x: 0, y: 0 };
}
dashboard.addPanel(newPanel);
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) {
return (
<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
<div className="panel-container">
<div className="add-panel">
<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__sub-title">Select a visualization</span>
</div>
<ScrollBar className="add-panel__items">
{this.state.clipboardPanel && this.renderClipboardPanel(this.state.clipboardPanel)}
{this.state.panelPlugins.map(this.renderPanelItem.bind(this))}
</ScrollBar>
</div>
@ -87,4 +121,3 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP
);
}
}

View File

@ -4,4 +4,5 @@ import { PanelLoader } from './PanelLoader';
export interface PanelContainer {
getPanelLoader(): PanelLoader;
getDashboard(): DashboardModel;
getClipboardPanel(): any;
}

View File

@ -0,0 +1,21 @@
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);

View File

@ -195,6 +195,14 @@ export class PanelCtrl {
text: 'Panel JSON',
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);
return menu;
}
@ -263,6 +271,7 @@ export class PanelCtrl {
let editScope = this.$scope.$root.$new();
editScope.object = this.panel.getSaveModel();
editScope.updateHandler = this.replacePanel.bind(this);
editScope.enableCopy = true;
this.publishAppEvent('show-modal', {
src: 'public/app/partials/edit_json.html',
@ -270,6 +279,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) {
let dashboard = this.dashboard;
let index = _.findIndex(dashboard.panels, panel => {

View File

@ -51,6 +51,12 @@ function renderMenuItem(item, ctrl) {
html += ` href="${item.href}"`;
}
if (item.directives) {
for (let directive of item.directives) {
html += ` ${directive}`;
}
}
html += `><i class="${item.icon}"></i>`;
html += `<span class="dropdown-item-text">${item.text}</span>`;

View File

@ -8,7 +8,7 @@
vertical-align: -15%;
}
.#{$fa-css-prefix}-2x {
font-size: 2em;
font-size: 2em !important;
}
.#{$fa-css-prefix}-3x {
font-size: 3em;

View File

@ -65,3 +65,7 @@
.add-panel__item-img {
height: calc(100% - 15px);
}
.add-panel__item-icon {
padding: 2px;
}