mirror of
https://github.com/grafana/grafana.git
synced 2025-02-09 23:16:16 -06:00
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:
parent
e480a38dc1
commit
68457f5636
@ -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';
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,4 +4,5 @@ import { PanelLoader } from './PanelLoader';
|
||||
export interface PanelContainer {
|
||||
getPanelLoader(): PanelLoader;
|
||||
getDashboard(): DashboardModel;
|
||||
getClipboardPanel(): any;
|
||||
}
|
||||
|
21
public/app/features/dashboard/panel_clipboard_srv.ts
Normal file
21
public/app/features/dashboard/panel_clipboard_srv.ts
Normal 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);
|
@ -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 => {
|
||||
|
@ -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>`;
|
||||
|
||||
|
2
public/sass/base/font-awesome/_larger.scss
vendored
2
public/sass/base/font-awesome/_larger.scss
vendored
@ -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;
|
||||
|
@ -65,3 +65,7 @@
|
||||
.add-panel__item-img {
|
||||
height: calc(100% - 15px);
|
||||
}
|
||||
|
||||
.add-panel__item-icon {
|
||||
padding: 2px;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user