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,101 +1,141 @@
|
||||
import React from 'react';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { Icon } from '@grafana/ui';
|
||||
import { css } from '@emotion/css';
|
||||
import { GrafanaThemeV2 } from '@grafana/data';
|
||||
import { Modal, useStyles2 } from '@grafana/ui';
|
||||
|
||||
export class HelpModal extends React.PureComponent {
|
||||
static tabIndex = 0;
|
||||
static shortcuts = {
|
||||
Global: [
|
||||
{ keys: ['g', 'h'], description: 'Go to Home Dashboard' },
|
||||
{ keys: ['g', 'p'], description: 'Go to Profile' },
|
||||
{ keys: ['s', 'o'], description: 'Open search' },
|
||||
{ keys: ['esc'], description: 'Exit edit/setting views' },
|
||||
],
|
||||
Dashboard: [
|
||||
{ keys: ['mod+s'], description: 'Save dashboard' },
|
||||
{ keys: ['d', 'r'], description: 'Refresh all panels' },
|
||||
{ keys: ['d', 's'], description: 'Dashboard settings' },
|
||||
{ keys: ['d', 'v'], description: 'Toggle in-active / view mode' },
|
||||
{ keys: ['d', 'k'], description: 'Toggle kiosk mode (hides top nav)' },
|
||||
{ keys: ['d', 'E'], description: 'Expand all rows' },
|
||||
{ keys: ['d', 'C'], description: 'Collapse all rows' },
|
||||
{ keys: ['d', 'a'], description: 'Toggle auto fit panels (experimental feature)' },
|
||||
{ keys: ['mod+o'], description: 'Toggle shared graph crosshair' },
|
||||
{ keys: ['d', 'l'], description: 'Toggle all panel legends' },
|
||||
],
|
||||
'Focused Panel': [
|
||||
{ keys: ['e'], description: 'Toggle panel edit view' },
|
||||
{ keys: ['v'], description: 'Toggle panel fullscreen view' },
|
||||
{ keys: ['p', 's'], description: 'Open Panel Share Modal' },
|
||||
{ keys: ['p', 'd'], description: 'Duplicate Panel' },
|
||||
{ keys: ['p', 'r'], description: 'Remove Panel' },
|
||||
{ keys: ['p', 'l'], description: 'Toggle panel legend' },
|
||||
],
|
||||
'Time Range': [
|
||||
{ keys: ['t', 'z'], description: 'Zoom out time range' },
|
||||
{
|
||||
keys: ['t', '←'],
|
||||
description: 'Move time range back',
|
||||
},
|
||||
{
|
||||
keys: ['t', '→'],
|
||||
description: 'Move time range forward',
|
||||
},
|
||||
],
|
||||
};
|
||||
const shortcuts = {
|
||||
Global: [
|
||||
{ keys: ['g', 'h'], description: 'Go to Home Dashboard' },
|
||||
{ keys: ['g', 'p'], description: 'Go to Profile' },
|
||||
{ keys: ['s', 'o'], description: 'Open search' },
|
||||
{ keys: ['esc'], description: 'Exit edit/setting views' },
|
||||
],
|
||||
Dashboard: [
|
||||
{ keys: ['mod+s'], description: 'Save dashboard' },
|
||||
{ keys: ['d', 'r'], description: 'Refresh all panels' },
|
||||
{ keys: ['d', 's'], description: 'Dashboard settings' },
|
||||
{ keys: ['d', 'v'], description: 'Toggle in-active / view mode' },
|
||||
{ keys: ['d', 'k'], description: 'Toggle kiosk mode (hides top nav)' },
|
||||
{ keys: ['d', 'E'], description: 'Expand all rows' },
|
||||
{ keys: ['d', 'C'], description: 'Collapse all rows' },
|
||||
{ keys: ['d', 'a'], description: 'Toggle auto fit panels (experimental feature)' },
|
||||
{ keys: ['mod+o'], description: 'Toggle shared graph crosshair' },
|
||||
{ keys: ['d', 'l'], description: 'Toggle all panel legends' },
|
||||
],
|
||||
'Focused Panel': [
|
||||
{ keys: ['e'], description: 'Toggle panel edit view' },
|
||||
{ keys: ['v'], description: 'Toggle panel fullscreen view' },
|
||||
{ keys: ['p', 's'], description: 'Open Panel Share Modal' },
|
||||
{ keys: ['p', 'd'], description: 'Duplicate Panel' },
|
||||
{ keys: ['p', 'r'], description: 'Remove Panel' },
|
||||
{ keys: ['p', 'l'], description: 'Toggle panel legend' },
|
||||
],
|
||||
'Time Range': [
|
||||
{ keys: ['t', 'z'], description: 'Zoom out time range' },
|
||||
{
|
||||
keys: ['t', '←'],
|
||||
description: 'Move time range back',
|
||||
},
|
||||
{
|
||||
keys: ['t', '→'],
|
||||
description: 'Move time range forward',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
dismiss() {
|
||||
appEvents.emit('hide-modal');
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="modal-body">
|
||||
<div className="modal-header">
|
||||
<h2 className="modal-header-title">
|
||||
<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 className="modal-content help-modal">
|
||||
<p className="small" style={{ position: 'absolute', top: '13px', right: '44px' }}>
|
||||
<span className="shortcut-table-key">mod</span> =
|
||||
<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>
|
||||
<tr>
|
||||
<th className="shortcut-table-category-header" colSpan={2}>
|
||||
{category}
|
||||
</th>
|
||||
</tr>
|
||||
{shortcuts.map((shortcut, j) => (
|
||||
<tr key={`${i}-${j}`}>
|
||||
<td className="shortcut-table-keys">
|
||||
{shortcut.keys.map((key, k) => (
|
||||
<span className="shortcut-table-key" key={`${i}-${j}-${k}`}>
|
||||
{key}
|
||||
</span>
|
||||
))}
|
||||
</td>
|
||||
<td className="shortcut-table-description">{shortcut.description}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="clearfix" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export interface HelpModalProps {
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export const HelpModal = ({ onDismiss }: HelpModalProps): JSX.Element => {
|
||||
const styles = useStyles2(getStyles);
|
||||
return (
|
||||
<Modal title="Shortcuts" isOpen onDismiss={onDismiss} onClickBackdrop={onDismiss}>
|
||||
<div className={styles.titleDescription}>
|
||||
<span className={styles.shortcutTableKey}>mod</span> =<span> CTRL on windows or linux and CMD key on Mac</span>
|
||||
</div>
|
||||
<div className={styles.categories}>
|
||||
{Object.entries(shortcuts).map(([category, shortcuts], i) => (
|
||||
<div className={styles.shortcutCategory} key={i}>
|
||||
<table className={styles.shortcutTable}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className={styles.shortcutTableCategoryHeader} colSpan={2}>
|
||||
{category}
|
||||
</th>
|
||||
</tr>
|
||||
{shortcuts.map((shortcut, j) => (
|
||||
<tr key={`${i}-${j}`}>
|
||||
<td className={styles.shortcutTableKeys}>
|
||||
{shortcut.keys.map((key, k) => (
|
||||
<span className={styles.shortcutTableKey} key={`${i}-${j}-${k}`}>
|
||||
{key}
|
||||
</span>
|
||||
))}
|
||||
</td>
|
||||
<td className={styles.shortcutTableDescription}>{shortcut.description}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</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 BottomNavLinks from './BottomNavLinks';
|
||||
import appEvents from '../../app_events';
|
||||
import { ShowModalEvent } from '../../../types/events';
|
||||
import { ShowModalReactEvent } from '../../../types/events';
|
||||
import { HelpModal } from '../help/HelpModal';
|
||||
|
||||
jest.mock('../../app_events', () => ({
|
||||
publish: jest.fn(),
|
||||
@ -94,11 +95,7 @@ describe('Functions', () => {
|
||||
const instance = wrapper.instance() as BottomNavLinks;
|
||||
instance.onOpenShortcuts();
|
||||
|
||||
expect(appEvents.publish).toHaveBeenCalledWith(
|
||||
new ShowModalEvent({
|
||||
templateHtml: '<help-modal></help-modal>',
|
||||
})
|
||||
);
|
||||
expect(appEvents.publish).toHaveBeenCalledWith(new ShowModalReactEvent({ component: HelpModal }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -6,7 +6,8 @@ import { NavModelItem } from '@grafana/data';
|
||||
import { Icon, IconName, Link } from '@grafana/ui';
|
||||
import { OrgSwitcher } from '../OrgSwitcher';
|
||||
import { getFooterLinks } from '../Footer/Footer';
|
||||
import { ShowModalEvent } from '../../../types/events';
|
||||
import { ShowModalReactEvent } from '../../../types/events';
|
||||
import { HelpModal } from '../help/HelpModal';
|
||||
|
||||
export interface Props {
|
||||
link: NavModelItem;
|
||||
@ -23,11 +24,7 @@ export default class BottomNavLinks extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
onOpenShortcuts = () => {
|
||||
appEvents.publish(
|
||||
new ShowModalEvent({
|
||||
templateHtml: '<help-modal></help-modal>',
|
||||
})
|
||||
);
|
||||
appEvents.publish(new ShowModalReactEvent({ component: HelpModal }));
|
||||
};
|
||||
|
||||
toggleSwitcherModal = () => {
|
||||
|
@ -22,6 +22,7 @@ import { getDatasourceSrv } from '../../features/plugins/datasource_srv';
|
||||
import { getTimeSrv } from '../../features/dashboard/services/TimeSrv';
|
||||
import { toggleTheme } from './toggleTheme';
|
||||
import { withFocusedPanel } from './withFocusedPanelId';
|
||||
import { HelpModal } from '../components/help/HelpModal';
|
||||
|
||||
export class KeybindingSrv {
|
||||
modalOpen = false;
|
||||
@ -97,7 +98,7 @@ export class KeybindingSrv {
|
||||
}
|
||||
|
||||
private showHelpModal() {
|
||||
appEvents.publish(new ShowModalEvent({ templateHtml: '<help-modal></help-modal>' }));
|
||||
appEvents.publish(new ShowModalReactEvent({ component: HelpModal }));
|
||||
}
|
||||
|
||||
private exit() {
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
ShowModalReactEvent,
|
||||
} from '../../types/events';
|
||||
import { ConfirmModal, ConfirmModalProps } from '@grafana/ui';
|
||||
import { textUtil } from '@grafana/data';
|
||||
import { deprecationWarning, textUtil } from '@grafana/data';
|
||||
|
||||
export class UtilSrv {
|
||||
modalScope: any;
|
||||
@ -55,13 +55,21 @@ export class UtilSrv {
|
||||
this.reactModalRoot.removeChild(this.reactModalNode);
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated use showModalReact instead that has this capability built in
|
||||
*/
|
||||
hideModal() {
|
||||
deprecationWarning('UtilSrv', 'hideModal', 'showModalReact');
|
||||
if (this.modalScope && this.modalScope.dismiss) {
|
||||
this.modalScope.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use showModalReact instead
|
||||
*/
|
||||
showModal(options: any) {
|
||||
deprecationWarning('UtilSrv', 'showModal', 'showModalReact');
|
||||
if (this.modalScope && 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 { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { ShowModalEvent } from '../../types/events';
|
||||
import { ShowModalReactEvent } from '../../types/events';
|
||||
import { AlertHowToModal } from './AlertHowToModal';
|
||||
|
||||
jest.mock('../../core/app_events', () => ({
|
||||
publish: jest.fn(),
|
||||
@ -92,13 +93,7 @@ describe('Functions', () => {
|
||||
|
||||
instance.onOpenHowTo();
|
||||
|
||||
expect(appEvents.publish).toHaveBeenCalledWith(
|
||||
new ShowModalEvent({
|
||||
src: 'public/app/features/alerting/partials/alert_howto.html',
|
||||
modalClass: 'confirm-modal',
|
||||
model: {},
|
||||
})
|
||||
);
|
||||
expect(appEvents.publish).toHaveBeenCalledWith(new ShowModalReactEvent({ component: AlertHowToModal }));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -15,7 +15,8 @@ import { setSearchQuery } from './state/reducers';
|
||||
import { Button, LinkButton, Select, VerticalGroup } from '@grafana/ui';
|
||||
import { AlertDefinitionItem } from './components/AlertDefinitionItem';
|
||||
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) {
|
||||
return {
|
||||
@ -73,13 +74,7 @@ export class AlertRuleListUnconnected extends PureComponent<Props> {
|
||||
};
|
||||
|
||||
onOpenHowTo = () => {
|
||||
appEvents.publish(
|
||||
new ShowModalEvent({
|
||||
src: 'public/app/features/alerting/partials/alert_howto.html',
|
||||
modalClass: 'confirm-modal',
|
||||
model: {},
|
||||
})
|
||||
);
|
||||
appEvents.publish(new ShowModalReactEvent({ component: AlertHowToModal }));
|
||||
};
|
||||
|
||||
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 props: Props = {
|
||||
onDismiss: jest.fn(),
|
||||
apiKey: 'api key test',
|
||||
rootPath: 'test/path',
|
||||
};
|
||||
|
@ -1,47 +1,44 @@
|
||||
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 {
|
||||
onDismiss: () => void;
|
||||
apiKey: string;
|
||||
rootPath: string;
|
||||
}
|
||||
|
||||
export const ApiKeysAddedModal = (props: Props) => {
|
||||
export function ApiKeysAddedModal({ onDismiss, apiKey, rootPath }: Props): JSX.Element {
|
||||
const styles = useStyles2(getStyles);
|
||||
return (
|
||||
<div className="modal-body">
|
||||
<div className="modal-header">
|
||||
<h2 className="modal-header-title">
|
||||
<Icon name="key-skeleton-alt" size="lg" />
|
||||
<span className="p-l-1">API Key Created</span>
|
||||
</h2>
|
||||
<Modal title="API Key Created" onDismiss={onDismiss} onClickBackdrop={onDismiss} isOpen>
|
||||
<Field label="Key">
|
||||
<span className={styles.label}>{apiKey}</span>
|
||||
</Field>
|
||||
|
||||
<a className="modal-header-close" ng-click="dismiss();">
|
||||
<Icon name="times" />
|
||||
</a>
|
||||
</div>
|
||||
<Alert severity="info" title="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.
|
||||
</Alert>
|
||||
|
||||
<div className="modal-content">
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-muted">You can authenticate a request using the Authorization HTTP header, example:</p>
|
||||
<pre className={styles.small}>
|
||||
curl -H "Authorization: Bearer {apiKey}" {rootPath}/api/dashboards/home
|
||||
</pre>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
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 ReactDOMServer from 'react-dom/server';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import { hot } from 'react-hot-loader';
|
||||
// Utils
|
||||
@ -8,7 +7,7 @@ import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { getApiKeys, getApiKeysCount } from './state/selectors';
|
||||
import { addApiKey, deleteApiKey, loadApiKeys } from './state/actions';
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import ApiKeysAddedModal from './ApiKeysAddedModal';
|
||||
import { ApiKeysAddedModal } from './ApiKeysAddedModal';
|
||||
import config from 'app/core/config';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
@ -20,7 +19,7 @@ import { ApiKeysForm } from './ApiKeysForm';
|
||||
import { ApiKeysActionBar } from './ApiKeysActionBar';
|
||||
import { ApiKeysTable } from './ApiKeysTable';
|
||||
import { ApiKeysController } from './ApiKeysController';
|
||||
import { ShowModalEvent } from 'app/types/events';
|
||||
import { ShowModalReactEvent } from 'app/types/events';
|
||||
|
||||
const { Switch } = LegacyForms;
|
||||
|
||||
@ -82,11 +81,14 @@ export class ApiKeysPageUnconnected extends PureComponent<Props, State> {
|
||||
onAddApiKey = (newApiKey: NewApiKey) => {
|
||||
const openModal = (apiKey: string) => {
|
||||
const rootPath = window.location.origin + config.appSubUrl;
|
||||
const modalTemplate = ReactDOMServer.renderToString(<ApiKeysAddedModal apiKey={apiKey} rootPath={rootPath} />);
|
||||
|
||||
appEvents.publish(
|
||||
new ShowModalEvent({
|
||||
templateHtml: modalTemplate,
|
||||
new ShowModalReactEvent({
|
||||
props: {
|
||||
apiKey,
|
||||
rootPath,
|
||||
},
|
||||
component: ApiKeysAddedModal,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
@ -1,79 +1,40 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<div
|
||||
className="modal-body"
|
||||
<Modal
|
||||
isOpen={true}
|
||||
onClickBackdrop={[MockFunction]}
|
||||
onDismiss={[MockFunction]}
|
||||
title="API Key Created"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
<Field
|
||||
label="Key"
|
||||
>
|
||||
<h2
|
||||
className="modal-header-title"
|
||||
<span
|
||||
className="css-ypoxjz"
|
||||
>
|
||||
<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"
|
||||
api key test
|
||||
</span>
|
||||
</Field>
|
||||
<Alert
|
||||
severity="info"
|
||||
title="You will only be able to view this key here once!"
|
||||
>
|
||||
<div
|
||||
className="gf-form-group"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<span
|
||||
className="gf-form-label"
|
||||
>
|
||||
Key
|
||||
</span>
|
||||
<span
|
||||
className="gf-form-label"
|
||||
>
|
||||
api key test
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="grafana-info-box"
|
||||
style={
|
||||
Object {
|
||||
"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
|
||||
api key test
|
||||
"
|
||||
test/path
|
||||
/api/dashboards/home
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
It is not stored in this form, so be sure to copy it now.
|
||||
</Alert>
|
||||
<p
|
||||
className="text-muted"
|
||||
>
|
||||
You can authenticate a request using the Authorization HTTP header, example:
|
||||
</p>
|
||||
<pre
|
||||
className="css-omfua5"
|
||||
>
|
||||
curl -H "Authorization: Bearer
|
||||
api key test
|
||||
"
|
||||
test/path
|
||||
/api/dashboards/home
|
||||
</pre>
|
||||
</Modal>
|
||||
`;
|
||||
|
@ -4,7 +4,8 @@ import { Button, Field, Modal, Switch } from '@grafana/ui';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { ShowModalEvent } from 'app/types/events';
|
||||
import { ShowModalReactEvent } from 'app/types/events';
|
||||
import { ViewJsonModal } from './ViewJsonModal';
|
||||
|
||||
interface Props {
|
||||
dashboard: DashboardModel;
|
||||
@ -70,15 +71,12 @@ export class ShareExport extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
openJsonModal = (clone: object) => {
|
||||
const model = {
|
||||
object: clone,
|
||||
enableCopy: true,
|
||||
};
|
||||
|
||||
appEvents.publish(
|
||||
new ShowModalEvent({
|
||||
src: 'public/app/partials/edit_json.html',
|
||||
model,
|
||||
new ShowModalReactEvent({
|
||||
props: {
|
||||
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,
|
||||
} from '@grafana/data';
|
||||
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 { getPluginSettings } from './PluginSettingsCache';
|
||||
@ -31,8 +31,9 @@ import { config } from 'app/core/config';
|
||||
import { contextSrv } from '../../core/services/context_srv';
|
||||
import { css } from '@emotion/css';
|
||||
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 { UpdatePluginModal } from './UpdatePluginModal';
|
||||
|
||||
interface Props extends GrafanaRouteComponentProps<{ pluginId: string }, UrlQueryMap> {}
|
||||
|
||||
@ -142,10 +143,14 @@ class PluginPage extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
showUpdateInfo = () => {
|
||||
const { id, name } = this.state.plugin!.meta;
|
||||
appEvents.publish(
|
||||
new ShowModalEvent({
|
||||
src: 'public/app/features/plugins/partials/update_instructions.html',
|
||||
model: this.state.plugin!.meta,
|
||||
new ShowModalReactEvent({
|
||||
props: {
|
||||
id,
|
||||
name,
|
||||
},
|
||||
component: UpdatePluginModal,
|
||||
})
|
||||
);
|
||||
};
|
||||
@ -162,9 +167,12 @@ class PluginPage extends PureComponent<Props, State> {
|
||||
{meta.hasUpdate && (
|
||||
<div>
|
||||
<Tooltip content={meta.latestVersion!} theme="info" placement="top">
|
||||
<a href="#" onClick={this.showUpdateInfo}>
|
||||
<LinkButton fill="text" onClick={this.showUpdateInfo}>
|
||||
Update Available!
|
||||
</a>
|
||||
</LinkButton>
|
||||
{/*<a href="#" onClick={this.showUpdateInfo}>*/}
|
||||
{/* Update Available!*/}
|
||||
{/*</a>*/}
|
||||
</Tooltip>
|
||||
</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';
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use ShowModalReactEvent instead that has this capability built in
|
||||
*/
|
||||
export class ShowModalEvent extends BusEventWithPayload<ShowModalPayload> {
|
||||
static type = 'show-modal';
|
||||
}
|
||||
@ -184,6 +187,9 @@ export class ShowModalReactEvent extends BusEventWithPayload<ShowModalReactPaylo
|
||||
static type = 'show-react-modal';
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use ShowModalReactEvent instead that has this capability built in
|
||||
*/
|
||||
export class HideModalEvent extends BusEventBase {
|
||||
static type = 'hide-modal';
|
||||
}
|
||||
|
@ -68,7 +68,6 @@
|
||||
@import 'components/dropdown';
|
||||
@import 'components/footer';
|
||||
@import 'components/infobox';
|
||||
@import 'components/shortcuts';
|
||||
@import 'components/drop';
|
||||
@import 'components/query_editor';
|
||||
@import 'components/tabbed_view';
|
||||
|
@ -55,14 +55,3 @@
|
||||
.pluginlist-emphasis {
|
||||
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