mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: Migrates remaining Angular modals to React (#33476)
* HelpModal: Migrates to new style * Alerting: Migrates how to do alerting modal to React * ApiKeysModal: migrates to new theme * Dashboard: View dasboard json modal migrated to React and new theme * PluginPage: migrates update plugin modal to react and new theme * Chore: deprecates events and functions * Simplify help modal * Updated json modal to use Modal.ButtonRow * Tweak to api key design * Tests: updates snapshot Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
parent
86a57d17d2
commit
22ac0fc3cd
@ -1,10 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { appEvents } from 'app/core/core';
|
import { css } from '@emotion/css';
|
||||||
import { Icon } from '@grafana/ui';
|
import { GrafanaThemeV2 } from '@grafana/data';
|
||||||
|
import { Modal, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
export class HelpModal extends React.PureComponent {
|
const shortcuts = {
|
||||||
static tabIndex = 0;
|
|
||||||
static shortcuts = {
|
|
||||||
Global: [
|
Global: [
|
||||||
{ keys: ['g', 'h'], description: 'Go to Home Dashboard' },
|
{ keys: ['g', 'h'], description: 'Go to Home Dashboard' },
|
||||||
{ keys: ['g', 'p'], description: 'Go to Profile' },
|
{ keys: ['g', 'p'], description: 'Go to Profile' },
|
||||||
@ -42,60 +41,101 @@ export class HelpModal extends React.PureComponent {
|
|||||||
description: 'Move time range forward',
|
description: 'Move time range forward',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
dismiss() {
|
export interface HelpModalProps {
|
||||||
appEvents.emit('hide-modal');
|
onDismiss: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
export const HelpModal = ({ onDismiss }: HelpModalProps): JSX.Element => {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
return (
|
return (
|
||||||
<div className="modal-body">
|
<Modal title="Shortcuts" isOpen onDismiss={onDismiss} onClickBackdrop={onDismiss}>
|
||||||
<div className="modal-header">
|
<div className={styles.titleDescription}>
|
||||||
<h2 className="modal-header-title">
|
<span className={styles.shortcutTableKey}>mod</span> =<span> CTRL on windows or linux and CMD key on Mac</span>
|
||||||
<Icon name="keyboard" size="lg" />
|
|
||||||
<span className="p-l-1">Shortcuts</span>
|
|
||||||
</h2>
|
|
||||||
<a className="modal-header-close" onClick={this.dismiss}>
|
|
||||||
<Icon name="times" style={{ margin: '3px 0 0 0' }} />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.categories}>
|
||||||
<div className="modal-content help-modal">
|
{Object.entries(shortcuts).map(([category, shortcuts], i) => (
|
||||||
<p className="small" style={{ position: 'absolute', top: '13px', right: '44px' }}>
|
<div className={styles.shortcutCategory} key={i}>
|
||||||
<span className="shortcut-table-key">mod</span> =
|
<table className={styles.shortcutTable}>
|
||||||
<span className="muted"> CTRL on windows or linux and CMD key on Mac</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{Object.entries(HelpModal.shortcuts).map(([category, shortcuts], i) => (
|
|
||||||
<div className="shortcut-category" key={i}>
|
|
||||||
<table className="shortcut-table">
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="shortcut-table-category-header" colSpan={2}>
|
<th className={styles.shortcutTableCategoryHeader} colSpan={2}>
|
||||||
{category}
|
{category}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
{shortcuts.map((shortcut, j) => (
|
{shortcuts.map((shortcut, j) => (
|
||||||
<tr key={`${i}-${j}`}>
|
<tr key={`${i}-${j}`}>
|
||||||
<td className="shortcut-table-keys">
|
<td className={styles.shortcutTableKeys}>
|
||||||
{shortcut.keys.map((key, k) => (
|
{shortcut.keys.map((key, k) => (
|
||||||
<span className="shortcut-table-key" key={`${i}-${j}-${k}`}>
|
<span className={styles.shortcutTableKey} key={`${i}-${j}-${k}`}>
|
||||||
{key}
|
{key}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</td>
|
</td>
|
||||||
<td className="shortcut-table-description">{shortcut.description}</td>
|
<td className={styles.shortcutTableDescription}>{shortcut.description}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div className="clearfix" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
function getStyles(theme: GrafanaThemeV2) {
|
||||||
|
return {
|
||||||
|
titleDescription: css`
|
||||||
|
font-size: ${theme.typography.bodySmall.fontSize};
|
||||||
|
font-weight: ${theme.typography.bodySmall.fontWeight};
|
||||||
|
color: ${theme.colors.text.disabled};
|
||||||
|
padding-bottom: ${theme.spacing(2)};
|
||||||
|
`,
|
||||||
|
categories: css`
|
||||||
|
font-size: ${theme.typography.bodySmall.fontSize};
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
`,
|
||||||
|
shortcutCategory: css`
|
||||||
|
width: 50%;
|
||||||
|
font-size: ${theme.typography.bodySmall.fontSize};
|
||||||
|
`,
|
||||||
|
shortcutTable: css`
|
||||||
|
margin-bottom: ${theme.spacing(2)};
|
||||||
|
`,
|
||||||
|
shortcutTableCategoryHeader: css`
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: ${theme.typography.h6.fontSize};
|
||||||
|
text-align: left;
|
||||||
|
`,
|
||||||
|
shortcutTableDescription: css`
|
||||||
|
text-align: left;
|
||||||
|
color: ${theme.colors.text.disabled};
|
||||||
|
width: 99%;
|
||||||
|
padding: ${theme.spacing(1, 2)};
|
||||||
|
`,
|
||||||
|
shortcutTableKeys: css`
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 1%;
|
||||||
|
text-align: right;
|
||||||
|
color: ${theme.colors.text.primary};
|
||||||
|
`,
|
||||||
|
shortcutTableKey: css`
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
margin-right: ${theme.spacing(0.5)};
|
||||||
|
padding: 3px 5px;
|
||||||
|
font: 11px Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
||||||
|
line-height: 10px;
|
||||||
|
vertical-align: middle;
|
||||||
|
border: solid 1px ${theme.colors.border.medium};
|
||||||
|
border-radius: ${theme.shape.borderRadius(3)};
|
||||||
|
color: ${theme.colors.text.primary};
|
||||||
|
background-color: ${theme.colors.background.secondary};
|
||||||
|
`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ import React from 'react';
|
|||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import BottomNavLinks from './BottomNavLinks';
|
import BottomNavLinks from './BottomNavLinks';
|
||||||
import appEvents from '../../app_events';
|
import appEvents from '../../app_events';
|
||||||
import { ShowModalEvent } from '../../../types/events';
|
import { ShowModalReactEvent } from '../../../types/events';
|
||||||
|
import { HelpModal } from '../help/HelpModal';
|
||||||
|
|
||||||
jest.mock('../../app_events', () => ({
|
jest.mock('../../app_events', () => ({
|
||||||
publish: jest.fn(),
|
publish: jest.fn(),
|
||||||
@ -94,11 +95,7 @@ describe('Functions', () => {
|
|||||||
const instance = wrapper.instance() as BottomNavLinks;
|
const instance = wrapper.instance() as BottomNavLinks;
|
||||||
instance.onOpenShortcuts();
|
instance.onOpenShortcuts();
|
||||||
|
|
||||||
expect(appEvents.publish).toHaveBeenCalledWith(
|
expect(appEvents.publish).toHaveBeenCalledWith(new ShowModalReactEvent({ component: HelpModal }));
|
||||||
new ShowModalEvent({
|
|
||||||
templateHtml: '<help-modal></help-modal>',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,8 @@ import { NavModelItem } from '@grafana/data';
|
|||||||
import { Icon, IconName, Link } from '@grafana/ui';
|
import { Icon, IconName, Link } from '@grafana/ui';
|
||||||
import { OrgSwitcher } from '../OrgSwitcher';
|
import { OrgSwitcher } from '../OrgSwitcher';
|
||||||
import { getFooterLinks } from '../Footer/Footer';
|
import { getFooterLinks } from '../Footer/Footer';
|
||||||
import { ShowModalEvent } from '../../../types/events';
|
import { ShowModalReactEvent } from '../../../types/events';
|
||||||
|
import { HelpModal } from '../help/HelpModal';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
link: NavModelItem;
|
link: NavModelItem;
|
||||||
@ -23,11 +24,7 @@ export default class BottomNavLinks extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onOpenShortcuts = () => {
|
onOpenShortcuts = () => {
|
||||||
appEvents.publish(
|
appEvents.publish(new ShowModalReactEvent({ component: HelpModal }));
|
||||||
new ShowModalEvent({
|
|
||||||
templateHtml: '<help-modal></help-modal>',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleSwitcherModal = () => {
|
toggleSwitcherModal = () => {
|
||||||
|
@ -22,6 +22,7 @@ import { getDatasourceSrv } from '../../features/plugins/datasource_srv';
|
|||||||
import { getTimeSrv } from '../../features/dashboard/services/TimeSrv';
|
import { getTimeSrv } from '../../features/dashboard/services/TimeSrv';
|
||||||
import { toggleTheme } from './toggleTheme';
|
import { toggleTheme } from './toggleTheme';
|
||||||
import { withFocusedPanel } from './withFocusedPanelId';
|
import { withFocusedPanel } from './withFocusedPanelId';
|
||||||
|
import { HelpModal } from '../components/help/HelpModal';
|
||||||
|
|
||||||
export class KeybindingSrv {
|
export class KeybindingSrv {
|
||||||
modalOpen = false;
|
modalOpen = false;
|
||||||
@ -97,7 +98,7 @@ export class KeybindingSrv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private showHelpModal() {
|
private showHelpModal() {
|
||||||
appEvents.publish(new ShowModalEvent({ templateHtml: '<help-modal></help-modal>' }));
|
appEvents.publish(new ShowModalReactEvent({ component: HelpModal }));
|
||||||
}
|
}
|
||||||
|
|
||||||
private exit() {
|
private exit() {
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
ShowModalReactEvent,
|
ShowModalReactEvent,
|
||||||
} from '../../types/events';
|
} from '../../types/events';
|
||||||
import { ConfirmModal, ConfirmModalProps } from '@grafana/ui';
|
import { ConfirmModal, ConfirmModalProps } from '@grafana/ui';
|
||||||
import { textUtil } from '@grafana/data';
|
import { deprecationWarning, textUtil } from '@grafana/data';
|
||||||
|
|
||||||
export class UtilSrv {
|
export class UtilSrv {
|
||||||
modalScope: any;
|
modalScope: any;
|
||||||
@ -55,13 +55,21 @@ export class UtilSrv {
|
|||||||
this.reactModalRoot.removeChild(this.reactModalNode);
|
this.reactModalRoot.removeChild(this.reactModalNode);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use showModalReact instead that has this capability built in
|
||||||
|
*/
|
||||||
hideModal() {
|
hideModal() {
|
||||||
|
deprecationWarning('UtilSrv', 'hideModal', 'showModalReact');
|
||||||
if (this.modalScope && this.modalScope.dismiss) {
|
if (this.modalScope && this.modalScope.dismiss) {
|
||||||
this.modalScope.dismiss();
|
this.modalScope.dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use showModalReact instead
|
||||||
|
*/
|
||||||
showModal(options: any) {
|
showModal(options: any) {
|
||||||
|
deprecationWarning('UtilSrv', 'showModal', 'showModalReact');
|
||||||
if (this.modalScope && this.modalScope.dismiss) {
|
if (this.modalScope && this.modalScope.dismiss) {
|
||||||
this.modalScope.dismiss();
|
this.modalScope.dismiss();
|
||||||
}
|
}
|
||||||
|
21
public/app/features/alerting/AlertHowToModal.tsx
Normal file
21
public/app/features/alerting/AlertHowToModal.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Modal, VerticalGroup } from '@grafana/ui';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export interface AlertHowToModalProps {
|
||||||
|
onDismiss: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AlertHowToModal({ onDismiss }: AlertHowToModalProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Modal title="Adding an Alert" isOpen onDismiss={onDismiss} onClickBackdrop={onDismiss}>
|
||||||
|
<VerticalGroup spacing="sm">
|
||||||
|
<img src="public/img/alert_howto_new.png" alt="link to how to alert image" />
|
||||||
|
<p>
|
||||||
|
Alerts are added and configured in the Alert tab of any dashboard graph panel, letting you build and visualize
|
||||||
|
an alert using existing queries.
|
||||||
|
</p>
|
||||||
|
<p>Remember to save the dashboard to persist your alert rule changes.</p>
|
||||||
|
</VerticalGroup>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
@ -8,7 +8,8 @@ import { setSearchQuery } from './state/reducers';
|
|||||||
import { mockToolkitActionCreator } from 'test/core/redux/mocks';
|
import { mockToolkitActionCreator } from 'test/core/redux/mocks';
|
||||||
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
|
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
|
||||||
import { locationService } from '@grafana/runtime';
|
import { locationService } from '@grafana/runtime';
|
||||||
import { ShowModalEvent } from '../../types/events';
|
import { ShowModalReactEvent } from '../../types/events';
|
||||||
|
import { AlertHowToModal } from './AlertHowToModal';
|
||||||
|
|
||||||
jest.mock('../../core/app_events', () => ({
|
jest.mock('../../core/app_events', () => ({
|
||||||
publish: jest.fn(),
|
publish: jest.fn(),
|
||||||
@ -92,13 +93,7 @@ describe('Functions', () => {
|
|||||||
|
|
||||||
instance.onOpenHowTo();
|
instance.onOpenHowTo();
|
||||||
|
|
||||||
expect(appEvents.publish).toHaveBeenCalledWith(
|
expect(appEvents.publish).toHaveBeenCalledWith(new ShowModalReactEvent({ component: AlertHowToModal }));
|
||||||
new ShowModalEvent({
|
|
||||||
src: 'public/app/features/alerting/partials/alert_howto.html',
|
|
||||||
modalClass: 'confirm-modal',
|
|
||||||
model: {},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,7 +15,8 @@ import { setSearchQuery } from './state/reducers';
|
|||||||
import { Button, LinkButton, Select, VerticalGroup } from '@grafana/ui';
|
import { Button, LinkButton, Select, VerticalGroup } from '@grafana/ui';
|
||||||
import { AlertDefinitionItem } from './components/AlertDefinitionItem';
|
import { AlertDefinitionItem } from './components/AlertDefinitionItem';
|
||||||
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||||
import { ShowModalEvent } from '../../types/events';
|
import { ShowModalReactEvent } from '../../types/events';
|
||||||
|
import { AlertHowToModal } from './AlertHowToModal';
|
||||||
|
|
||||||
function mapStateToProps(state: StoreState) {
|
function mapStateToProps(state: StoreState) {
|
||||||
return {
|
return {
|
||||||
@ -73,13 +74,7 @@ export class AlertRuleListUnconnected extends PureComponent<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onOpenHowTo = () => {
|
onOpenHowTo = () => {
|
||||||
appEvents.publish(
|
appEvents.publish(new ShowModalReactEvent({ component: AlertHowToModal }));
|
||||||
new ShowModalEvent({
|
|
||||||
src: 'public/app/features/alerting/partials/alert_howto.html',
|
|
||||||
modalClass: 'confirm-modal',
|
|
||||||
model: {},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onSearchQueryChange = (value: string) => {
|
onSearchQueryChange = (value: string) => {
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
<div class="modal-body">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2 class="modal-header-title">
|
|
||||||
<icon name="'info-circle'"></icon>
|
|
||||||
<span class="p-l-1">Adding an Alert</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<a class="modal-header-close" ng-click="dismiss();">
|
|
||||||
<icon name="'times'"></icon>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="text-center">
|
|
||||||
<img src="public/img/alert_howto_new.png"></img>
|
|
||||||
</div>
|
|
||||||
<p class="p-a-2 text-center offset-lg-1 col-lg-10">
|
|
||||||
Alerts are added and configured in the Alert tab of any dashboard
|
|
||||||
graph panel, letting you build and visualize an alert using existing queries.
|
|
||||||
<br> <br>
|
|
||||||
Remember to save the dashboard to persist your alert rule changes.
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { ApiKeysAddedModal, Props } from './ApiKeysAddedModal';
|
|||||||
|
|
||||||
const setup = (propOverrides?: object) => {
|
const setup = (propOverrides?: object) => {
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
|
onDismiss: jest.fn(),
|
||||||
apiKey: 'api key test',
|
apiKey: 'api key test',
|
||||||
rootPath: 'test/path',
|
rootPath: 'test/path',
|
||||||
};
|
};
|
||||||
|
@ -1,47 +1,44 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Icon } from '@grafana/ui';
|
import { css } from '@emotion/css';
|
||||||
|
import { Alert, Field, Modal, useStyles2 } from '@grafana/ui';
|
||||||
|
import { GrafanaThemeV2 } from '@grafana/data';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
onDismiss: () => void;
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
rootPath: string;
|
rootPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ApiKeysAddedModal = (props: Props) => {
|
export function ApiKeysAddedModal({ onDismiss, apiKey, rootPath }: Props): JSX.Element {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
return (
|
return (
|
||||||
<div className="modal-body">
|
<Modal title="API Key Created" onDismiss={onDismiss} onClickBackdrop={onDismiss} isOpen>
|
||||||
<div className="modal-header">
|
<Field label="Key">
|
||||||
<h2 className="modal-header-title">
|
<span className={styles.label}>{apiKey}</span>
|
||||||
<Icon name="key-skeleton-alt" size="lg" />
|
</Field>
|
||||||
<span className="p-l-1">API Key Created</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<a className="modal-header-close" ng-click="dismiss();">
|
<Alert severity="info" title="You will only be able to view this key here once!">
|
||||||
<Icon name="times" />
|
It is not stored in this form, so be sure to copy it now.
|
||||||
</a>
|
</Alert>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="modal-content">
|
<p className="text-muted">You can authenticate a request using the Authorization HTTP header, example:</p>
|
||||||
<div className="gf-form-group">
|
<pre className={styles.small}>
|
||||||
<div className="gf-form">
|
curl -H "Authorization: Bearer {apiKey}" {rootPath}/api/dashboards/home
|
||||||
<span className="gf-form-label">Key</span>
|
|
||||||
<span className="gf-form-label">{props.apiKey}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grafana-info-box" style={{ border: 0 }}>
|
|
||||||
You will only be able to view this key here once! It is not stored in this form, so be sure to copy it now.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
You can authenticate a request using the Authorization HTTP header, example:
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<pre className="small">
|
|
||||||
curl -H "Authorization: Bearer {props.apiKey}" {props.rootPath}/api/dashboards/home
|
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</Modal>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ApiKeysAddedModal;
|
function getStyles(theme: GrafanaThemeV2) {
|
||||||
|
return {
|
||||||
|
label: css`
|
||||||
|
padding: ${theme.spacing(1)};
|
||||||
|
background-color: ${theme.colors.background.secondary};
|
||||||
|
border-radius: ${theme.shape.borderRadius()};
|
||||||
|
`,
|
||||||
|
small: css`
|
||||||
|
font-size: ${theme.typography.bodySmall.fontSize};
|
||||||
|
font-weight: ${theme.typography.bodySmall.fontWeight};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import ReactDOMServer from 'react-dom/server';
|
|
||||||
import { connect, ConnectedProps } from 'react-redux';
|
import { connect, ConnectedProps } from 'react-redux';
|
||||||
import { hot } from 'react-hot-loader';
|
import { hot } from 'react-hot-loader';
|
||||||
// Utils
|
// Utils
|
||||||
@ -8,7 +7,7 @@ import { getNavModel } from 'app/core/selectors/navModel';
|
|||||||
import { getApiKeys, getApiKeysCount } from './state/selectors';
|
import { getApiKeys, getApiKeysCount } from './state/selectors';
|
||||||
import { addApiKey, deleteApiKey, loadApiKeys } from './state/actions';
|
import { addApiKey, deleteApiKey, loadApiKeys } from './state/actions';
|
||||||
import Page from 'app/core/components/Page/Page';
|
import Page from 'app/core/components/Page/Page';
|
||||||
import ApiKeysAddedModal from './ApiKeysAddedModal';
|
import { ApiKeysAddedModal } from './ApiKeysAddedModal';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||||
@ -20,7 +19,7 @@ import { ApiKeysForm } from './ApiKeysForm';
|
|||||||
import { ApiKeysActionBar } from './ApiKeysActionBar';
|
import { ApiKeysActionBar } from './ApiKeysActionBar';
|
||||||
import { ApiKeysTable } from './ApiKeysTable';
|
import { ApiKeysTable } from './ApiKeysTable';
|
||||||
import { ApiKeysController } from './ApiKeysController';
|
import { ApiKeysController } from './ApiKeysController';
|
||||||
import { ShowModalEvent } from 'app/types/events';
|
import { ShowModalReactEvent } from 'app/types/events';
|
||||||
|
|
||||||
const { Switch } = LegacyForms;
|
const { Switch } = LegacyForms;
|
||||||
|
|
||||||
@ -82,11 +81,14 @@ export class ApiKeysPageUnconnected extends PureComponent<Props, State> {
|
|||||||
onAddApiKey = (newApiKey: NewApiKey) => {
|
onAddApiKey = (newApiKey: NewApiKey) => {
|
||||||
const openModal = (apiKey: string) => {
|
const openModal = (apiKey: string) => {
|
||||||
const rootPath = window.location.origin + config.appSubUrl;
|
const rootPath = window.location.origin + config.appSubUrl;
|
||||||
const modalTemplate = ReactDOMServer.renderToString(<ApiKeysAddedModal apiKey={apiKey} rootPath={rootPath} />);
|
|
||||||
|
|
||||||
appEvents.publish(
|
appEvents.publish(
|
||||||
new ShowModalEvent({
|
new ShowModalReactEvent({
|
||||||
templateHtml: modalTemplate,
|
props: {
|
||||||
|
apiKey,
|
||||||
|
rootPath,
|
||||||
|
},
|
||||||
|
component: ApiKeysAddedModal,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,71 +1,34 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Render should render component 1`] = `
|
exports[`Render should render component 1`] = `
|
||||||
<div
|
<Modal
|
||||||
className="modal-body"
|
isOpen={true}
|
||||||
|
onClickBackdrop={[MockFunction]}
|
||||||
|
onDismiss={[MockFunction]}
|
||||||
|
title="API Key Created"
|
||||||
>
|
>
|
||||||
<div
|
<Field
|
||||||
className="modal-header"
|
label="Key"
|
||||||
>
|
|
||||||
<h2
|
|
||||||
className="modal-header-title"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name="key-skeleton-alt"
|
|
||||||
size="lg"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="p-l-1"
|
|
||||||
>
|
|
||||||
API Key Created
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<a
|
|
||||||
className="modal-header-close"
|
|
||||||
ng-click="dismiss();"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name="times"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="modal-content"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="gf-form-group"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="gf-form"
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="gf-form-label"
|
className="css-ypoxjz"
|
||||||
>
|
|
||||||
Key
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="gf-form-label"
|
|
||||||
>
|
>
|
||||||
api key test
|
api key test
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</Field>
|
||||||
</div>
|
<Alert
|
||||||
<div
|
severity="info"
|
||||||
className="grafana-info-box"
|
title="You will only be able to view this key here once!"
|
||||||
style={
|
>
|
||||||
Object {
|
It is not stored in this form, so be sure to copy it now.
|
||||||
"border": 0,
|
</Alert>
|
||||||
}
|
<p
|
||||||
}
|
className="text-muted"
|
||||||
>
|
>
|
||||||
You will only be able to view this key here once! It is not stored in this form, so be sure to copy it now.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
You can authenticate a request using the Authorization HTTP header, example:
|
You can authenticate a request using the Authorization HTTP header, example:
|
||||||
<br />
|
</p>
|
||||||
<br />
|
|
||||||
<pre
|
<pre
|
||||||
className="small"
|
className="css-omfua5"
|
||||||
>
|
>
|
||||||
curl -H "Authorization: Bearer
|
curl -H "Authorization: Bearer
|
||||||
api key test
|
api key test
|
||||||
@ -73,7 +36,5 @@ exports[`Render should render component 1`] = `
|
|||||||
test/path
|
test/path
|
||||||
/api/dashboards/home
|
/api/dashboards/home
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</Modal>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
@ -4,7 +4,8 @@ import { Button, Field, Modal, Switch } from '@grafana/ui';
|
|||||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||||
import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal';
|
import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal';
|
||||||
import { appEvents } from 'app/core/core';
|
import { appEvents } from 'app/core/core';
|
||||||
import { ShowModalEvent } from 'app/types/events';
|
import { ShowModalReactEvent } from 'app/types/events';
|
||||||
|
import { ViewJsonModal } from './ViewJsonModal';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
@ -70,15 +71,12 @@ export class ShareExport extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
openJsonModal = (clone: object) => {
|
openJsonModal = (clone: object) => {
|
||||||
const model = {
|
|
||||||
object: clone,
|
|
||||||
enableCopy: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
appEvents.publish(
|
appEvents.publish(
|
||||||
new ShowModalEvent({
|
new ShowModalReactEvent({
|
||||||
src: 'public/app/partials/edit_json.html',
|
props: {
|
||||||
model,
|
json: JSON.stringify(clone, null, 2),
|
||||||
|
},
|
||||||
|
component: ViewJsonModal,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { ClipboardButton, CodeEditor, Modal } from '@grafana/ui';
|
||||||
|
|
||||||
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
import { notifyApp } from '../../../../core/actions';
|
||||||
|
import { dispatch } from '../../../../store/store';
|
||||||
|
import { createSuccessNotification } from '../../../../core/copy/appNotification';
|
||||||
|
|
||||||
|
export interface ViewJsonModalProps {
|
||||||
|
json: string;
|
||||||
|
onDismiss: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ViewJsonModal({ json, onDismiss }: ViewJsonModalProps): JSX.Element {
|
||||||
|
const getClipboardText = useCallback(() => json, [json]);
|
||||||
|
const onClipboardCopy = () => {
|
||||||
|
dispatch(notifyApp(createSuccessNotification('Content copied to clipboard')));
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Modal title="JSON" onDismiss={onDismiss} onClickBackdrop={onDismiss} isOpen>
|
||||||
|
<AutoSizer disableHeight>
|
||||||
|
{({ width }) => <CodeEditor value={json} language="json" showMiniMap={false} height="500px" width={width} />}
|
||||||
|
</AutoSizer>
|
||||||
|
<Modal.ButtonRow>
|
||||||
|
<ClipboardButton getText={getClipboardText} onClipboardCopy={onClipboardCopy}>
|
||||||
|
Copy to Clipboard
|
||||||
|
</ClipboardButton>
|
||||||
|
</Modal.ButtonRow>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
@ -17,7 +17,7 @@ import {
|
|||||||
UrlQueryMap,
|
UrlQueryMap,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { AppNotificationSeverity } from 'app/types';
|
import { AppNotificationSeverity } from 'app/types';
|
||||||
import { Alert, InfoBox, Tooltip, PluginSignatureBadge } from '@grafana/ui';
|
import { Alert, InfoBox, LinkButton, PluginSignatureBadge, Tooltip } from '@grafana/ui';
|
||||||
|
|
||||||
import Page from 'app/core/components/Page/Page';
|
import Page from 'app/core/components/Page/Page';
|
||||||
import { getPluginSettings } from './PluginSettingsCache';
|
import { getPluginSettings } from './PluginSettingsCache';
|
||||||
@ -31,8 +31,9 @@ import { config } from 'app/core/config';
|
|||||||
import { contextSrv } from '../../core/services/context_srv';
|
import { contextSrv } from '../../core/services/context_srv';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { ShowModalEvent } from 'app/types/events';
|
import { ShowModalReactEvent } from 'app/types/events';
|
||||||
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||||
|
import { UpdatePluginModal } from './UpdatePluginModal';
|
||||||
|
|
||||||
interface Props extends GrafanaRouteComponentProps<{ pluginId: string }, UrlQueryMap> {}
|
interface Props extends GrafanaRouteComponentProps<{ pluginId: string }, UrlQueryMap> {}
|
||||||
|
|
||||||
@ -142,10 +143,14 @@ class PluginPage extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showUpdateInfo = () => {
|
showUpdateInfo = () => {
|
||||||
|
const { id, name } = this.state.plugin!.meta;
|
||||||
appEvents.publish(
|
appEvents.publish(
|
||||||
new ShowModalEvent({
|
new ShowModalReactEvent({
|
||||||
src: 'public/app/features/plugins/partials/update_instructions.html',
|
props: {
|
||||||
model: this.state.plugin!.meta,
|
id,
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
component: UpdatePluginModal,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -162,9 +167,12 @@ class PluginPage extends PureComponent<Props, State> {
|
|||||||
{meta.hasUpdate && (
|
{meta.hasUpdate && (
|
||||||
<div>
|
<div>
|
||||||
<Tooltip content={meta.latestVersion!} theme="info" placement="top">
|
<Tooltip content={meta.latestVersion!} theme="info" placement="top">
|
||||||
<a href="#" onClick={this.showUpdateInfo}>
|
<LinkButton fill="text" onClick={this.showUpdateInfo}>
|
||||||
Update Available!
|
Update Available!
|
||||||
</a>
|
</LinkButton>
|
||||||
|
{/*<a href="#" onClick={this.showUpdateInfo}>*/}
|
||||||
|
{/* Update Available!*/}
|
||||||
|
{/*</a>*/}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
58
public/app/features/plugins/UpdatePluginModal.tsx
Normal file
58
public/app/features/plugins/UpdatePluginModal.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Modal, useStyles2, VerticalGroup } from '@grafana/ui';
|
||||||
|
import { GrafanaThemeV2 } from '@grafana/data';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
|
export interface UpdatePluginModalProps {
|
||||||
|
onDismiss: () => void;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UpdatePluginModal({ onDismiss, id, name }: UpdatePluginModalProps): JSX.Element {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
return (
|
||||||
|
<Modal title="Update Plugin" onDismiss={onDismiss} onClickBackdrop={onDismiss} isOpen>
|
||||||
|
<VerticalGroup spacing="md">
|
||||||
|
<VerticalGroup spacing="sm">
|
||||||
|
<p>Type the following on the command line to update {name}.</p>
|
||||||
|
<pre>
|
||||||
|
<code>grafana-cli plugins update {id}</code>
|
||||||
|
</pre>
|
||||||
|
<span className={styles.small}>
|
||||||
|
Check out {name} on <a href={`https://grafana.com/plugins/${id}`}>Grafana.com</a> for README and changelog.
|
||||||
|
If you do not have access to the command line, ask your Grafana administator.
|
||||||
|
</span>
|
||||||
|
</VerticalGroup>
|
||||||
|
<p className={styles.weak}>
|
||||||
|
<img className={styles.logo} src="public/img/grafana_icon.svg" alt="grafana logo" />
|
||||||
|
<strong>Pro tip</strong>: To update all plugins at once, type{' '}
|
||||||
|
<code className={styles.codeSmall}>grafana-cli plugins update-all</code> on the command line.
|
||||||
|
</p>
|
||||||
|
</VerticalGroup>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStyles(theme: GrafanaThemeV2) {
|
||||||
|
return {
|
||||||
|
small: css`
|
||||||
|
font-size: ${theme.typography.bodySmall.fontSize};
|
||||||
|
font-weight: ${theme.typography.bodySmall.fontWeight};
|
||||||
|
`,
|
||||||
|
weak: css`
|
||||||
|
color: ${theme.colors.text.disabled};
|
||||||
|
font-size: ${theme.typography.bodySmall.fontSize};
|
||||||
|
`,
|
||||||
|
logo: css`
|
||||||
|
vertical-align: sub;
|
||||||
|
margin-right: ${theme.spacing(0.3)};
|
||||||
|
width: ${theme.spacing(2)};
|
||||||
|
`,
|
||||||
|
codeSmall: css`
|
||||||
|
white-space: nowrap;
|
||||||
|
margin: 0 ${theme.spacing(0.25)};
|
||||||
|
padding: ${theme.spacing(0.25)};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
<div class="modal-body">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2 class="modal-header-title">
|
|
||||||
<icon name="'cloud-download'" size="'lg'"></icon>
|
|
||||||
<span class="p-l-1">Update Plugin</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<a class="modal-header-close" ng-click="dismiss();">
|
|
||||||
<icon name="'times'"></icon>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="gf-form-group">
|
|
||||||
<p>Type the following on the command line to update {{model.name}}.</p>
|
|
||||||
<pre><code>grafana-cli plugins update {{model.id}}</code></pre>
|
|
||||||
<span class="small">Check out {{model.name}} on <a href="https://grafana.com/plugins/{{model.id}}">Grafana.com</a> for README and changelog. If you do not have access to the command line, ask your Grafana administator.</span>
|
|
||||||
</div>
|
|
||||||
<p class="pluginlist-none-installed"><img class="pluginlist-inline-logo" src="public/img/grafana_icon.svg"><strong>Pro tip</strong>: To update all plugins at once, type <code class="code--small">grafana-cli plugins update-all</code> on the command line.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,24 +0,0 @@
|
|||||||
<div ng-controller="JsonEditorCtrl">
|
|
||||||
<div class="tabbed-view-header">
|
|
||||||
<h2 class="tabbed-view-title">
|
|
||||||
JSON
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<button class="tabbed-view-close-btn" ng-click="dismiss()">
|
|
||||||
<icon name="'times'"></icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tabbed-view-body">
|
|
||||||
<div class="gf-form">
|
|
||||||
<code-editor content="json" data-mode="json" data-max-lines="20"></code-editor>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="gf-form-button-row">
|
|
||||||
<button type="button" class="btn btn-primary" ng-show="canUpdate" ng-click="update(); dismiss();">Update</button>
|
|
||||||
<button class="btn btn-secondary" ng-if="canCopy" clipboard-button="getContentForClipboard()">
|
|
||||||
Copy to Clipboard
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -172,6 +172,9 @@ export class RemovePanelEvent extends BusEventWithPayload<number> {
|
|||||||
static type = 'remove-panel';
|
static type = 'remove-panel';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use ShowModalReactEvent instead that has this capability built in
|
||||||
|
*/
|
||||||
export class ShowModalEvent extends BusEventWithPayload<ShowModalPayload> {
|
export class ShowModalEvent extends BusEventWithPayload<ShowModalPayload> {
|
||||||
static type = 'show-modal';
|
static type = 'show-modal';
|
||||||
}
|
}
|
||||||
@ -184,6 +187,9 @@ export class ShowModalReactEvent extends BusEventWithPayload<ShowModalReactPaylo
|
|||||||
static type = 'show-react-modal';
|
static type = 'show-react-modal';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use ShowModalReactEvent instead that has this capability built in
|
||||||
|
*/
|
||||||
export class HideModalEvent extends BusEventBase {
|
export class HideModalEvent extends BusEventBase {
|
||||||
static type = 'hide-modal';
|
static type = 'hide-modal';
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,6 @@
|
|||||||
@import 'components/dropdown';
|
@import 'components/dropdown';
|
||||||
@import 'components/footer';
|
@import 'components/footer';
|
||||||
@import 'components/infobox';
|
@import 'components/infobox';
|
||||||
@import 'components/shortcuts';
|
|
||||||
@import 'components/drop';
|
@import 'components/drop';
|
||||||
@import 'components/query_editor';
|
@import 'components/query_editor';
|
||||||
@import 'components/tabbed_view';
|
@import 'components/tabbed_view';
|
||||||
|
@ -55,14 +55,3 @@
|
|||||||
.pluginlist-emphasis {
|
.pluginlist-emphasis {
|
||||||
font-weight: $font-weight-semi-bold;
|
font-weight: $font-weight-semi-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pluginlist-none-installed {
|
|
||||||
color: $text-color-weak;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pluginlist-inline-logo {
|
|
||||||
vertical-align: sub;
|
|
||||||
margin-right: $spacer / 3;
|
|
||||||
width: 16px;
|
|
||||||
}
|
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
.shortcut-category {
|
|
||||||
float: left;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-table {
|
|
||||||
margin-bottom: $spacer;
|
|
||||||
|
|
||||||
.shortcut-table-category-header {
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: $font-size-h6;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-table-description {
|
|
||||||
text-align: left;
|
|
||||||
color: $text-muted;
|
|
||||||
width: 99%;
|
|
||||||
padding: $space-sm $space-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-table-keys {
|
|
||||||
white-space: nowrap;
|
|
||||||
width: 1%;
|
|
||||||
text-align: right;
|
|
||||||
color: $text-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut-table-key {
|
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
|
||||||
margin-right: $space-xs;
|
|
||||||
padding: 3px 5px;
|
|
||||||
font: 11px Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
|
||||||
line-height: 10px;
|
|
||||||
vertical-align: middle;
|
|
||||||
background-color: $btn-inverse-bg;
|
|
||||||
border: solid 1px $btn-inverse-bg-hl;
|
|
||||||
border-radius: 3px;
|
|
||||||
color: $btn-inverse-text-color;
|
|
||||||
box-shadow: inset 0 -1px 0 $btn-inverse-bg-hl;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user