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
pull/10323/head
Marcus Efraimsson 8 years ago
parent e480a38dc1
commit 68457f5636
  1. 1
      public/app/features/dashboard/all.ts
  2. 7
      public/app/features/dashboard/dashboard_ctrl.ts
  3. 51
      public/app/features/dashboard/dashgrid/AddPanelPanel.tsx
  4. 1
      public/app/features/dashboard/dashgrid/PanelContainer.ts
  5. 21
      public/app/features/dashboard/panel_clipboard_srv.ts
  6. 20
      public/app/features/panel/panel_ctrl.ts
  7. 6
      public/app/features/panel/panel_header.ts
  8. 2
      public/sass/base/font-awesome/_larger.scss
  9. 4
      public/sass/components/_panel_add_panel.scss

@ -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 {
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 {
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';
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 {
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
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
<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
); );
} }
} }

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

@ -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);

@ -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;
} }
@ -263,6 +271,7 @@ export class PanelCtrl {
let editScope = this.$scope.$root.$new(); let editScope = this.$scope.$root.$new();
editScope.object = this.panel.getSaveModel(); editScope.object = this.panel.getSaveModel();
editScope.updateHandler = this.replacePanel.bind(this); editScope.updateHandler = this.replacePanel.bind(this);
editScope.enableCopy = true;
this.publishAppEvent('show-modal', { this.publishAppEvent('show-modal', {
src: 'public/app/partials/edit_json.html', 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) { 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) {
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 @@
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 @@
.add-panel__item-img { .add-panel__item-img {
height: calc(100% - 15px); height: calc(100% - 15px);
} }
.add-panel__item-icon {
padding: 2px;
}

Loading…
Cancel
Save