mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #13984 from grafana/12759-panel-header-standard-menu-only
12759 panel header menu in React (standard options only)
This commit is contained in:
@@ -4,13 +4,13 @@ import coreModule from '../core_module';
|
||||
export class JsonEditorCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope) {
|
||||
$scope.json = angular.toJson($scope.object, true);
|
||||
$scope.canUpdate = $scope.updateHandler !== void 0 && $scope.contextSrv.isEditor;
|
||||
$scope.canCopy = $scope.enableCopy;
|
||||
$scope.json = angular.toJson($scope.model.object, true);
|
||||
$scope.canUpdate = $scope.model.updateHandler !== void 0 && $scope.contextSrv.isEditor;
|
||||
$scope.canCopy = $scope.model.enableCopy;
|
||||
|
||||
$scope.update = () => {
|
||||
const newObject = angular.fromJson($scope.json);
|
||||
$scope.updateHandler(newObject, $scope.object);
|
||||
$scope.model.updateHandler(newObject, $scope.model.object);
|
||||
};
|
||||
|
||||
$scope.getContentForClipboard = () => $scope.json;
|
||||
|
||||
@@ -23,7 +23,9 @@ export const locationReducer = (state = initialState, action: Action): LocationS
|
||||
return {
|
||||
url: renderUrl(path || state.path, query),
|
||||
path: path || state.path,
|
||||
query: query,
|
||||
query: {
|
||||
...query,
|
||||
},
|
||||
routeParams: routeParams || state.routeParams,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { store } from 'app/store/configureStore';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
|
||||
// Services that handles angular -> mobx store sync & other react <-> angular sync
|
||||
// Services that handles angular -> redux store sync & other react <-> angular sync
|
||||
export class BridgeSrv {
|
||||
private fullPageReloadRoutes;
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
import config from 'app/core/config';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { removePanel } from 'app/features/dashboard/utils/panel';
|
||||
|
||||
// Services
|
||||
import { AnnotationsSrv } from '../annotations/annotations_srv';
|
||||
|
||||
// Types
|
||||
import { DashboardModel } from './dashboard_model';
|
||||
import { PanelModel } from './panel_model';
|
||||
|
||||
export class DashboardCtrl {
|
||||
dashboard: DashboardModel;
|
||||
@@ -19,7 +19,6 @@ export class DashboardCtrl {
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private $scope,
|
||||
private $rootScope,
|
||||
private keybindingSrv,
|
||||
private timeSrv,
|
||||
private variableSrv,
|
||||
@@ -112,12 +111,14 @@ export class DashboardCtrl {
|
||||
}
|
||||
|
||||
showJsonEditor(evt, options) {
|
||||
const editScope = this.$rootScope.$new();
|
||||
editScope.object = options.object;
|
||||
editScope.updateHandler = options.updateHandler;
|
||||
const model = {
|
||||
object: options.object,
|
||||
updateHandler: options.updateHandler,
|
||||
};
|
||||
|
||||
this.$scope.appEvent('show-dash-editor', {
|
||||
src: 'public/app/partials/edit_json.html',
|
||||
scope: editScope,
|
||||
model: model,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -136,34 +137,7 @@ export class DashboardCtrl {
|
||||
}
|
||||
|
||||
const panelInfo = this.dashboard.getPanelInfoById(options.panelId);
|
||||
this.removePanel(panelInfo.panel, true);
|
||||
}
|
||||
|
||||
removePanel(panel: PanelModel, ask: boolean) {
|
||||
// confirm deletion
|
||||
if (ask !== false) {
|
||||
let text2, confirmText;
|
||||
|
||||
if (panel.alert) {
|
||||
text2 = 'Panel includes an alert rule, removing panel will also remove alert rule';
|
||||
confirmText = 'YES';
|
||||
}
|
||||
|
||||
this.$scope.appEvent('confirm-modal', {
|
||||
title: 'Remove Panel',
|
||||
text: 'Are you sure you want to remove this panel?',
|
||||
text2: text2,
|
||||
icon: 'fa-trash',
|
||||
confirmText: confirmText,
|
||||
yesText: 'Remove',
|
||||
onConfirm: () => {
|
||||
this.removePanel(panel, false);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.dashboard.removePanel(panel);
|
||||
removePanel(this.dashboard, panelInfo.panel, true);
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
|
||||
@@ -232,11 +232,6 @@ export class DashboardModel {
|
||||
return this.meta.fullscreen && !panel.fullscreen;
|
||||
}
|
||||
|
||||
changePanelType(panel: PanelModel, pluginId: string) {
|
||||
panel.changeType(pluginId);
|
||||
this.events.emit('panel-type-changed', panel);
|
||||
}
|
||||
|
||||
private ensureListExist(data) {
|
||||
if (!data) {
|
||||
data = {};
|
||||
|
||||
@@ -83,7 +83,6 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
|
||||
dashboard.on('view-mode-changed', this.onViewModeChanged.bind(this));
|
||||
dashboard.on('row-collapsed', this.triggerForceUpdate.bind(this));
|
||||
dashboard.on('row-expanded', this.triggerForceUpdate.bind(this));
|
||||
dashboard.on('panel-type-changed', this.triggerForceUpdate.bind(this));
|
||||
}
|
||||
|
||||
buildLayout() {
|
||||
@@ -176,7 +175,12 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
|
||||
const panelClasses = classNames({ panel: true, 'panel--fullscreen': panel.fullscreen });
|
||||
panelElements.push(
|
||||
<div key={panel.id.toString()} className={panelClasses} id={`panel-${panel.id}`}>
|
||||
<DashboardPanel panel={panel} dashboard={this.props.dashboard} panelType={panel.type} />
|
||||
<DashboardPanel
|
||||
panel={panel}
|
||||
dashboard={this.props.dashboard}
|
||||
isEditing={panel.isEditing}
|
||||
isFullscreen={panel.fullscreen}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import config from 'app/core/config';
|
||||
import { PanelModel } from '../panel_model';
|
||||
import { DashboardModel } from '../dashboard_model';
|
||||
@@ -11,16 +11,17 @@ import { PanelChrome } from './PanelChrome';
|
||||
import { PanelEditor } from './PanelEditor';
|
||||
|
||||
export interface Props {
|
||||
panelType: string;
|
||||
panel: PanelModel;
|
||||
dashboard: DashboardModel;
|
||||
isEditing: boolean;
|
||||
isFullscreen: boolean;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
pluginExports: PluginExports;
|
||||
}
|
||||
|
||||
export class DashboardPanel extends React.Component<Props, State> {
|
||||
export class DashboardPanel extends PureComponent<Props, State> {
|
||||
element: any;
|
||||
angularPanel: AngularComponent;
|
||||
pluginInfo: any;
|
||||
@@ -113,9 +114,8 @@ export class DashboardPanel extends React.Component<Props, State> {
|
||||
|
||||
renderReactPanel() {
|
||||
const { pluginExports } = this.state;
|
||||
const containerClass = this.props.panel.isEditing ? 'panel-editor-container' : 'panel-height-helper';
|
||||
const panelWrapperClass = this.props.panel.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper';
|
||||
|
||||
const containerClass = this.props.isEditing ? 'panel-editor-container' : 'panel-height-helper';
|
||||
const panelWrapperClass = this.props.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper';
|
||||
// this might look strange with these classes that change when edit, but
|
||||
// I want to try to keep markup (parents) for panel the same in edit mode to avoide unmount / new mount of panel
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, { ComponentClass, PureComponent } from 'react';
|
||||
import { getTimeSrv } from '../time_srv';
|
||||
|
||||
// Components
|
||||
import { PanelHeader } from './PanelHeader';
|
||||
import { PanelHeader } from './PanelHeader/PanelHeader';
|
||||
import { DataPanel } from './DataPanel';
|
||||
|
||||
// Types
|
||||
@@ -49,17 +49,19 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
const timeSrv = getTimeSrv();
|
||||
const timeRange = timeSrv.timeRange();
|
||||
|
||||
this.setState({
|
||||
this.setState(prevState => ({
|
||||
...prevState,
|
||||
refreshCounter: this.state.refreshCounter + 1,
|
||||
timeRange: timeRange,
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
onRender = () => {
|
||||
console.log('onRender');
|
||||
this.setState({
|
||||
this.setState(prevState => ({
|
||||
...prevState,
|
||||
renderCounter: this.state.renderCounter + 1,
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
get isVisible() {
|
||||
@@ -68,12 +70,12 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
|
||||
render() {
|
||||
const { panel, dashboard } = this.props;
|
||||
const { refreshCounter, timeRange, renderCounter } = this.state;
|
||||
|
||||
const { datasource, targets } = panel;
|
||||
const { timeRange, renderCounter, refreshCounter } = this.state;
|
||||
const PanelComponent = this.props.component;
|
||||
|
||||
console.log('Panel chrome render');
|
||||
|
||||
console.log('panelChrome render');
|
||||
return (
|
||||
<div className="panel-container">
|
||||
<PanelHeader panel={panel} dashboard={dashboard} />
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { PanelModel } from '../panel_model';
|
||||
import { DashboardModel } from '../dashboard_model';
|
||||
import { store } from 'app/store/configureStore';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
|
||||
interface PanelHeaderProps {
|
||||
panel: PanelModel;
|
||||
dashboard: DashboardModel;
|
||||
}
|
||||
|
||||
export class PanelHeader extends React.Component<PanelHeaderProps, any> {
|
||||
onEditPanel = () => {
|
||||
store.dispatch(
|
||||
updateLocation({
|
||||
query: {
|
||||
panelId: this.props.panel.id,
|
||||
edit: true,
|
||||
fullscreen: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
onViewPanel = () => {
|
||||
store.dispatch(
|
||||
updateLocation({
|
||||
query: {
|
||||
panelId: this.props.panel.id,
|
||||
edit: false,
|
||||
fullscreen: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const isFullscreen = false;
|
||||
const isLoading = false;
|
||||
const panelHeaderClass = classNames({ 'panel-header': true, 'grid-drag-handle': !isFullscreen });
|
||||
|
||||
return (
|
||||
<div className={panelHeaderClass}>
|
||||
<span className="panel-info-corner">
|
||||
<i className="fa" />
|
||||
<span className="panel-info-corner-inner" />
|
||||
</span>
|
||||
|
||||
{isLoading && (
|
||||
<span className="panel-loading">
|
||||
<i className="fa fa-spinner fa-spin" />
|
||||
</span>
|
||||
)}
|
||||
|
||||
<div className="panel-title-container">
|
||||
<span className="panel-title">
|
||||
<span className="icon-gf panel-alert-icon" />
|
||||
<span className="panel-title-text">{this.props.panel.title}</span>
|
||||
<span className="panel-menu-container dropdown">
|
||||
<span className="fa fa-caret-down panel-menu-toggle" data-toggle="dropdown" />
|
||||
<ul className="dropdown-menu dropdown-menu--menu panel-menu" role="menu">
|
||||
<li>
|
||||
<a onClick={this.onEditPanel}>
|
||||
<i className="fa fa-fw fa-edit" /> Edit
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a onClick={this.onViewPanel}>
|
||||
<i className="fa fa-fw fa-eye" /> View
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<span className="panel-time-info">
|
||||
<i className="fa fa-clock-o" /> 4m
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { PanelHeaderMenu } from './PanelHeaderMenu';
|
||||
|
||||
import { DashboardModel } from 'app/features/dashboard/dashboard_model';
|
||||
import { PanelModel } from 'app/features/dashboard/panel_model';
|
||||
|
||||
export interface Props {
|
||||
panel: PanelModel;
|
||||
dashboard: DashboardModel;
|
||||
}
|
||||
|
||||
export class PanelHeader extends PureComponent<Props> {
|
||||
render() {
|
||||
const isFullscreen = false;
|
||||
const isLoading = false;
|
||||
const panelHeaderClass = classNames({ 'panel-header': true, 'grid-drag-handle': !isFullscreen });
|
||||
const { panel, dashboard } = this.props;
|
||||
|
||||
return (
|
||||
<div className={panelHeaderClass}>
|
||||
<span className="panel-info-corner">
|
||||
<i className="fa" />
|
||||
<span className="panel-info-corner-inner" />
|
||||
</span>
|
||||
|
||||
{isLoading && (
|
||||
<span className="panel-loading">
|
||||
<i className="fa fa-spinner fa-spin" />
|
||||
</span>
|
||||
)}
|
||||
|
||||
<div className="panel-title-container">
|
||||
<div className="panel-title">
|
||||
<span className="icon-gf panel-alert-icon" />
|
||||
<span className="panel-title-text" data-toggle="dropdown">
|
||||
{panel.title} <span className="fa fa-caret-down panel-menu-toggle" />
|
||||
</span>
|
||||
|
||||
<PanelHeaderMenu panel={panel} dashboard={dashboard} />
|
||||
|
||||
<span className="panel-time-info">
|
||||
<i className="fa fa-clock-o" /> 4m
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { DashboardModel } from 'app/features/dashboard/dashboard_model';
|
||||
import { PanelModel } from 'app/features/dashboard/panel_model';
|
||||
import { PanelHeaderMenuItem } from './PanelHeaderMenuItem';
|
||||
import { getPanelMenu } from 'app/features/dashboard/utils/getPanelMenu';
|
||||
import { PanelMenuItem } from 'app/types/panel';
|
||||
|
||||
export interface Props {
|
||||
panel: PanelModel;
|
||||
dashboard: DashboardModel;
|
||||
}
|
||||
|
||||
export class PanelHeaderMenu extends PureComponent<Props> {
|
||||
renderItems = (menu: PanelMenuItem[], isSubMenu = false) => {
|
||||
return (
|
||||
<ul className="dropdown-menu dropdown-menu--menu panel-menu" role={isSubMenu ? '' : 'menu'}>
|
||||
{menu.map((menuItem, idx: number) => {
|
||||
return (
|
||||
<PanelHeaderMenuItem
|
||||
key={`${menuItem.text}${idx}`}
|
||||
type={menuItem.type}
|
||||
text={menuItem.text}
|
||||
iconClassName={menuItem.iconClassName}
|
||||
onClick={menuItem.onClick}
|
||||
shortcut={menuItem.shortcut}
|
||||
>
|
||||
{menuItem.subMenu && this.renderItems(menuItem.subMenu, true)}
|
||||
</PanelHeaderMenuItem>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dashboard, panel } = this.props;
|
||||
const menu = getPanelMenu(dashboard, panel);
|
||||
return <div className="panel-menu-container dropdown">{this.renderItems(menu)}</div>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import React, { SFC } from 'react';
|
||||
import { PanelMenuItem } from 'app/types/panel';
|
||||
|
||||
interface Props {
|
||||
children: any;
|
||||
}
|
||||
|
||||
export const PanelHeaderMenuItem: SFC<Props & PanelMenuItem> = props => {
|
||||
const isSubMenu = props.type === 'submenu';
|
||||
const isDivider = props.type === 'divider';
|
||||
return isDivider ? (
|
||||
<li className="divider" />
|
||||
) : (
|
||||
<li className={isSubMenu ? 'dropdown-submenu' : null}>
|
||||
<a onClick={props.onClick}>
|
||||
{props.iconClassName && <i className={props.iconClassName} />}
|
||||
<span className="dropdown-item-text">{props.text}</span>
|
||||
{props.shortcut && <span className="dropdown-menu-item-shortcut">{props.shortcut}</span>}
|
||||
</a>
|
||||
{props.children}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
@@ -48,14 +48,15 @@ export class DashExportCtrl {
|
||||
saveAs(blob, dash.title + '-' + new Date().getTime() + '.json');
|
||||
}
|
||||
|
||||
private openJsonModal(clone: any) {
|
||||
const editScope = this.$rootScope.$new();
|
||||
editScope.object = clone;
|
||||
editScope.enableCopy = true;
|
||||
private openJsonModal(clone: object) {
|
||||
const model = {
|
||||
object: clone,
|
||||
enableCopy: true,
|
||||
};
|
||||
|
||||
this.$rootScope.appEvent('show-modal', {
|
||||
src: 'public/app/partials/edit_json.html',
|
||||
scope: editScope,
|
||||
model: model,
|
||||
});
|
||||
|
||||
this.dismiss();
|
||||
|
||||
@@ -12,6 +12,8 @@ export function ShareModalCtrl($scope, $rootScope, $location, $timeout, timeSrv,
|
||||
$scope.editor = { index: $scope.tabIndex || 0 };
|
||||
|
||||
$scope.init = () => {
|
||||
$scope.panel = $scope.model && $scope.model.panel ? $scope.model.panel : $scope.panel; // React pass panel and dashboard in the "model" property
|
||||
$scope.dashboard = $scope.model && $scope.model.dashboard ? $scope.model.dashboard : $scope.dashboard; // ^
|
||||
$scope.modeSharePanel = $scope.panel ? true : false;
|
||||
|
||||
$scope.tabs = [{ title: 'Link', src: 'shareLink.html' }];
|
||||
|
||||
120
public/app/features/dashboard/utils/getPanelMenu.ts
Normal file
120
public/app/features/dashboard/utils/getPanelMenu.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
import { store } from 'app/store/configureStore';
|
||||
|
||||
import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel';
|
||||
import { PanelModel } from 'app/features/dashboard/panel_model';
|
||||
import { DashboardModel } from 'app/features/dashboard/dashboard_model';
|
||||
import { PanelMenuItem } from 'app/types/panel';
|
||||
|
||||
export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => {
|
||||
const onViewPanel = () => {
|
||||
store.dispatch(
|
||||
updateLocation({
|
||||
query: {
|
||||
panelId: panel.id,
|
||||
edit: false,
|
||||
fullscreen: true,
|
||||
},
|
||||
partial: true,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onEditPanel = () => {
|
||||
store.dispatch(
|
||||
updateLocation({
|
||||
query: {
|
||||
panelId: panel.id,
|
||||
edit: true,
|
||||
fullscreen: true,
|
||||
},
|
||||
partial: true,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onSharePanel = () => {
|
||||
sharePanel(dashboard, panel);
|
||||
};
|
||||
|
||||
const onDuplicatePanel = () => {
|
||||
duplicatePanel(dashboard, panel);
|
||||
};
|
||||
|
||||
const onCopyPanel = () => {
|
||||
copyPanel(panel);
|
||||
};
|
||||
|
||||
const onEditPanelJson = () => {
|
||||
editPanelJson(dashboard, panel);
|
||||
};
|
||||
|
||||
const onRemovePanel = () => {
|
||||
removePanel(dashboard, panel, true);
|
||||
};
|
||||
|
||||
const menu: PanelMenuItem[] = [];
|
||||
|
||||
menu.push({
|
||||
text: 'View',
|
||||
iconClassName: 'fa fa-fw fa-eye',
|
||||
onClick: onViewPanel,
|
||||
shortcut: 'v',
|
||||
});
|
||||
|
||||
if (dashboard.meta.canEdit) {
|
||||
menu.push({
|
||||
text: 'Edit',
|
||||
iconClassName: 'fa fa-fw fa-edit',
|
||||
onClick: onEditPanel,
|
||||
shortcut: 'e',
|
||||
});
|
||||
}
|
||||
|
||||
menu.push({
|
||||
text: 'Share',
|
||||
iconClassName: 'fa fa-fw fa-share',
|
||||
onClick: onSharePanel,
|
||||
shortcut: 'p s',
|
||||
});
|
||||
|
||||
const subMenu: PanelMenuItem[] = [];
|
||||
|
||||
if (!panel.fullscreen && dashboard.meta.canEdit) {
|
||||
subMenu.push({
|
||||
text: 'Duplicate',
|
||||
onClick: onDuplicatePanel,
|
||||
shortcut: 'p d',
|
||||
});
|
||||
|
||||
subMenu.push({
|
||||
text: 'Copy',
|
||||
onClick: onCopyPanel,
|
||||
});
|
||||
}
|
||||
|
||||
subMenu.push({
|
||||
text: 'Panel JSON',
|
||||
onClick: onEditPanelJson,
|
||||
});
|
||||
|
||||
menu.push({
|
||||
type: 'submenu',
|
||||
text: 'More...',
|
||||
iconClassName: 'fa fa-fw fa-cube',
|
||||
subMenu: subMenu,
|
||||
});
|
||||
|
||||
if (dashboard.meta.canEdit) {
|
||||
menu.push({ type: 'divider' });
|
||||
|
||||
menu.push({
|
||||
text: 'Remove',
|
||||
iconClassName: 'fa fa-fw fa-trash',
|
||||
onClick: onRemovePanel,
|
||||
shortcut: 'p r',
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
};
|
||||
86
public/app/features/dashboard/utils/panel.ts
Normal file
86
public/app/features/dashboard/utils/panel.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { DashboardModel } from 'app/features/dashboard/dashboard_model';
|
||||
import { PanelModel } from 'app/features/dashboard/panel_model';
|
||||
import store from 'app/core/store';
|
||||
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
|
||||
|
||||
export const removePanel = (dashboard: DashboardModel, panel: PanelModel, ask: boolean) => {
|
||||
// confirm deletion
|
||||
if (ask !== false) {
|
||||
const text2 = panel.alert ? 'Panel includes an alert rule, removing panel will also remove alert rule' : null;
|
||||
const confirmText = panel.alert ? 'YES' : null;
|
||||
|
||||
appEvents.emit('confirm-modal', {
|
||||
title: 'Remove Panel',
|
||||
text: 'Are you sure you want to remove this panel?',
|
||||
text2: text2,
|
||||
icon: 'fa-trash',
|
||||
confirmText: confirmText,
|
||||
yesText: 'Remove',
|
||||
onConfirm: () => removePanel(dashboard, panel, false),
|
||||
});
|
||||
return;
|
||||
}
|
||||
dashboard.removePanel(panel);
|
||||
};
|
||||
|
||||
export const duplicatePanel = (dashboard: DashboardModel, panel: PanelModel) => {
|
||||
dashboard.duplicatePanel(panel);
|
||||
};
|
||||
|
||||
export const copyPanel = (panel: PanelModel) => {
|
||||
store.set(LS_PANEL_COPY_KEY, JSON.stringify(panel.getSaveModel()));
|
||||
appEvents.emit('alert-success', ['Panel copied. Open Add Panel to paste']);
|
||||
};
|
||||
|
||||
const replacePanel = (dashboard: DashboardModel, newPanel: PanelModel, oldPanel: PanelModel) => {
|
||||
const index = dashboard.panels.findIndex(panel => {
|
||||
return panel.id === oldPanel.id;
|
||||
});
|
||||
|
||||
const deletedPanel = dashboard.panels.splice(index, 1);
|
||||
dashboard.events.emit('panel-removed', deletedPanel);
|
||||
|
||||
newPanel = new PanelModel(newPanel);
|
||||
newPanel.id = oldPanel.id;
|
||||
|
||||
dashboard.panels.splice(index, 0, newPanel);
|
||||
dashboard.sortPanelsByGridPos();
|
||||
dashboard.events.emit('panel-added', newPanel);
|
||||
};
|
||||
|
||||
export const editPanelJson = (dashboard: DashboardModel, panel: PanelModel) => {
|
||||
const model = {
|
||||
object: panel.getSaveModel(),
|
||||
updateHandler: (newPanel: PanelModel, oldPanel: PanelModel) => {
|
||||
replacePanel(dashboard, newPanel, oldPanel);
|
||||
},
|
||||
enableCopy: true,
|
||||
};
|
||||
|
||||
appEvents.emit('show-modal', {
|
||||
src: 'public/app/partials/edit_json.html',
|
||||
model: model,
|
||||
});
|
||||
};
|
||||
|
||||
export const sharePanel = (dashboard: DashboardModel, panel: PanelModel) => {
|
||||
appEvents.emit('show-modal', {
|
||||
src: 'public/app/features/dashboard/partials/shareModal.html',
|
||||
model: {
|
||||
dashboard: dashboard,
|
||||
panel: panel,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const refreshPanel = (panel: PanelModel) => {
|
||||
panel.refresh();
|
||||
};
|
||||
|
||||
export const toggleLegend = (panel: PanelModel) => {
|
||||
console.log('Toggle legend is not implemented yet');
|
||||
// We need to set panel.legend defaults first
|
||||
// panel.legend.show = !panel.legend.show;
|
||||
refreshPanel(panel);
|
||||
};
|
||||
@@ -1,11 +1,15 @@
|
||||
import config from 'app/core/config';
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import { appEvents, profiler } from 'app/core/core';
|
||||
import { PanelModel } from 'app/features/dashboard/panel_model';
|
||||
import { profiler } from 'app/core/core';
|
||||
import {
|
||||
duplicatePanel,
|
||||
copyPanel as copyPanelUtil,
|
||||
editPanelJson as editPanelJsonUtil,
|
||||
sharePanel as sharePanelUtil,
|
||||
} from 'app/features/dashboard/utils/panel';
|
||||
import Remarkable from 'remarkable';
|
||||
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, LS_PANEL_COPY_KEY } from 'app/core/constants';
|
||||
import store from 'app/core/store';
|
||||
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
|
||||
|
||||
const TITLE_HEIGHT = 27;
|
||||
const PANEL_BORDER = 2;
|
||||
@@ -241,7 +245,7 @@ export class PanelCtrl {
|
||||
}
|
||||
|
||||
duplicate() {
|
||||
this.dashboard.duplicatePanel(this.panel);
|
||||
duplicatePanel(this.dashboard, this.panel);
|
||||
}
|
||||
|
||||
removePanel() {
|
||||
@@ -251,48 +255,15 @@ export class PanelCtrl {
|
||||
}
|
||||
|
||||
editPanelJson() {
|
||||
const 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',
|
||||
scope: editScope,
|
||||
});
|
||||
editPanelJsonUtil(this.dashboard, this.panel);
|
||||
}
|
||||
|
||||
copyPanel() {
|
||||
store.set(LS_PANEL_COPY_KEY, JSON.stringify(this.panel.getSaveModel()));
|
||||
appEvents.emit('alert-success', ['Panel copied. Open Add Panel to paste']);
|
||||
}
|
||||
|
||||
replacePanel(newPanel, oldPanel) {
|
||||
const dashboard = this.dashboard;
|
||||
const index = _.findIndex(dashboard.panels, panel => {
|
||||
return panel.id === oldPanel.id;
|
||||
});
|
||||
|
||||
const deletedPanel = dashboard.panels.splice(index, 1);
|
||||
this.dashboard.events.emit('panel-removed', deletedPanel);
|
||||
|
||||
newPanel = new PanelModel(newPanel);
|
||||
newPanel.id = oldPanel.id;
|
||||
|
||||
dashboard.panels.splice(index, 0, newPanel);
|
||||
dashboard.sortPanelsByGridPos();
|
||||
dashboard.events.emit('panel-added', newPanel);
|
||||
copyPanelUtil(this.panel);
|
||||
}
|
||||
|
||||
sharePanel() {
|
||||
const shareScope = this.$scope.$new();
|
||||
shareScope.panel = this.panel;
|
||||
shareScope.dashboard = this.dashboard;
|
||||
|
||||
this.publishAppEvent('show-modal', {
|
||||
src: 'public/app/features/dashboard/partials/shareModal.html',
|
||||
scope: shareScope,
|
||||
});
|
||||
sharePanelUtil(this.dashboard, this.panel);
|
||||
}
|
||||
|
||||
getInfoMode() {
|
||||
|
||||
@@ -16,9 +16,7 @@ export class VizTabCtrl {
|
||||
$scope.ctrl = this;
|
||||
}
|
||||
|
||||
onTypeChanged = (plugin: PanelPlugin) => {
|
||||
this.dashboard.changePanelType(this.panelCtrl.panel, plugin.id);
|
||||
};
|
||||
onTypeChanged = (plugin: PanelPlugin) => {};
|
||||
}
|
||||
|
||||
const template = `
|
||||
|
||||
@@ -12,3 +12,12 @@ export interface PanelOptionsProps<T = any> {
|
||||
options: T;
|
||||
onChange: (options: T) => void;
|
||||
}
|
||||
|
||||
export interface PanelMenuItem {
|
||||
type?: 'submenu' | 'divider';
|
||||
text?: string;
|
||||
iconClassName?: string;
|
||||
onClick?: () => void;
|
||||
shortcut?: string;
|
||||
subMenu?: PanelMenuItem[];
|
||||
}
|
||||
|
||||
@@ -183,6 +183,11 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
& > .dropdown > .dropdown-menu {
|
||||
// Panel menu. TODO: See if we can merge this with above
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.cascade-open {
|
||||
.dropdown-menu {
|
||||
display: block;
|
||||
|
||||
@@ -138,7 +138,6 @@ div.flot-text {
|
||||
padding: 3px 5px;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
left: 1px;
|
||||
|
||||
Reference in New Issue
Block a user