mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Panel/PluginList: Migrate to React (#31738)
* Panel/PluginList: Migrate to React
This commit is contained in:
parent
63746d027b
commit
3d459b556a
151
public/app/plugins/panel/pluginlist/PluginList.tsx
Normal file
151
public/app/plugins/panel/pluginlist/PluginList.tsx
Normal file
@ -0,0 +1,151 @@
|
||||
import React from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { css, cx } from 'emotion';
|
||||
import { GrafanaTheme, PanelProps, PluginMeta, PluginType } from '@grafana/data';
|
||||
import { CustomScrollbar, ModalsController, stylesFactory, Tooltip, useStyles } from '@grafana/ui';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { UpdatePluginModal } from './components/UpdatePluginModal';
|
||||
|
||||
export function PluginList(props: PanelProps) {
|
||||
const pluginState = useAsync(async () => {
|
||||
const plugins: PluginMeta[] = await getBackendSrv().get('api/plugins', { embedded: 0, core: 0 });
|
||||
return [
|
||||
{ header: 'Installed Apps', list: plugins.filter((p) => p.type === PluginType.app), type: PluginType.app },
|
||||
{ header: 'Installed Panels', list: plugins.filter((p) => p.type === PluginType.panel), type: PluginType.panel },
|
||||
{
|
||||
header: 'Installed Datasources',
|
||||
list: plugins.filter((p) => p.type === PluginType.datasource),
|
||||
type: PluginType.datasource,
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
|
||||
const styles = useStyles(getStyles);
|
||||
const isAdmin = contextSrv.user.isGrafanaAdmin;
|
||||
|
||||
if (pluginState.loading || pluginState.value === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomScrollbar autoHeightMin="100%" autoHeightMax="100%">
|
||||
<div className={styles.pluginList}>
|
||||
{pluginState.value.map((category) => (
|
||||
<div className={styles.section} key={`category-${category.type}`}>
|
||||
<h6 className={styles.sectionHeader}>{category.header}</h6>
|
||||
{category.list.map((plugin) => (
|
||||
<a className={styles.item} href={plugin.defaultNavUrl} key={`plugin-${plugin.id}`}>
|
||||
<img src={plugin.info.logos.small} className={styles.image} width="17" height="17" alt="" />
|
||||
<span className={styles.title}>{plugin.name}</span>
|
||||
<span className={styles.version}>v{plugin.info.version}</span>
|
||||
{isAdmin &&
|
||||
(plugin.hasUpdate ? (
|
||||
<ModalsController>
|
||||
{({ showModal, hideModal }) => (
|
||||
<Tooltip content={`New version: ${plugin.latestVersion}`} placement="top">
|
||||
<span
|
||||
className={cx(styles.message, styles.messageUpdate)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
showModal(UpdatePluginModal, {
|
||||
pluginID: plugin.id,
|
||||
pluginName: plugin.name,
|
||||
onDismiss: hideModal,
|
||||
isOpen: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Update available!
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</ModalsController>
|
||||
) : plugin.enabled ? (
|
||||
<span className={cx(styles.message, styles.messageNoUpdate)}>Up to date</span>
|
||||
) : (
|
||||
<span className={cx(styles.message, styles.messageEnable)}>Enable now</span>
|
||||
))}
|
||||
</a>
|
||||
))}
|
||||
|
||||
{category.list.length === 0 && (
|
||||
<a className={styles.item} href="https://grafana.com/plugins">
|
||||
<span className={styles.noneInstalled}>
|
||||
None installed. <em className={styles.emphasis}>Browse Grafana.com</em>
|
||||
</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CustomScrollbar>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
||||
pluginList: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`,
|
||||
section: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&:not(:last-of-type) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`,
|
||||
sectionHeader: css`
|
||||
color: ${theme.colors.textWeak};
|
||||
margin-bottom: ${theme.spacing.d};
|
||||
`,
|
||||
image: css`
|
||||
width: 17px;
|
||||
margin-right: ${theme.spacing.xxs};
|
||||
`,
|
||||
title: css`
|
||||
margin-right: calc(${theme.spacing.d} / 3);
|
||||
`,
|
||||
version: css`
|
||||
font-size: ${theme.typography.size.sm};
|
||||
color: ${theme.colors.textWeak};
|
||||
`,
|
||||
item: css`
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin: ${theme.spacing.xxs};
|
||||
padding: ${theme.spacing.sm};
|
||||
background: ${theme.colors.dashboardBg};
|
||||
border-radius: ${theme.border.radius.md};
|
||||
`,
|
||||
message: css`
|
||||
margin-left: auto;
|
||||
font-size: ${theme.typography.size.sm};
|
||||
`,
|
||||
messageEnable: css`
|
||||
color: ${theme.colors.linkExternal};
|
||||
&:hover {
|
||||
border-bottom: ${theme.border.width.sm} solid ${theme.colors.linkExternal};
|
||||
}
|
||||
`,
|
||||
messageUpdate: css`
|
||||
&:hover {
|
||||
border-bottom: ${theme.border.width.sm} solid ${theme.colors.text};
|
||||
}
|
||||
`,
|
||||
messageNoUpdate: css`
|
||||
color: ${theme.colors.textWeak};
|
||||
`,
|
||||
noneInstalled: css`
|
||||
color: ${theme.colors.textWeak};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
`,
|
||||
emphasis: css`
|
||||
font-weight: ${theme.typography.weight.semibold};
|
||||
font-style: normal;
|
||||
color: ${theme.colors.textWeak};
|
||||
`,
|
||||
}));
|
@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import { Modal, stylesFactory, useStyles } from '@grafana/ui';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface Props {
|
||||
pluginName: string;
|
||||
pluginID: string;
|
||||
onConfirm?: () => void;
|
||||
onDismiss?: () => void;
|
||||
}
|
||||
|
||||
export function UpdatePluginModal({ pluginName, pluginID, onDismiss }: Props) {
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
return (
|
||||
<Modal title="Update Plugin" icon="cloud-download" onDismiss={onDismiss} isOpen>
|
||||
<div className={styles.container}>
|
||||
<p>Type the following on the command line to update {pluginName}.</p>
|
||||
<pre>
|
||||
<code>grafana-cli plugins update {pluginID}</code>
|
||||
</pre>
|
||||
<span className={styles.small}>
|
||||
Check out {pluginName} on <a href={`https://grafana.com/plugins/${pluginID}`}>Grafana.com</a> for README and
|
||||
changelog. If you do not have access to the command line, ask your Grafana administator.
|
||||
</span>
|
||||
</div>
|
||||
<p className={styles.updateAllTip}>
|
||||
<img className={styles.inlineLogo} src="public/img/grafana_icon.svg" />
|
||||
<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>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
||||
small: css`
|
||||
font-size: ${theme.typography.size.sm};
|
||||
font-weight: ${theme.typography.weight.regular};
|
||||
`,
|
||||
codeSmall: css`
|
||||
font-size: ${theme.typography.size.xs};
|
||||
padding: ${theme.spacing.xxs};
|
||||
margin: 0 ${theme.spacing.xxs};
|
||||
`,
|
||||
container: css`
|
||||
margin-bottom: calc(${theme.spacing.d} * 2.5);
|
||||
`,
|
||||
updateAllTip: css`
|
||||
color: ${theme.colors.textWeak};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
`,
|
||||
inlineLogo: css`
|
||||
vertical-align: sub;
|
||||
margin-right: calc(${theme.spacing.d} / 3);
|
||||
width: ${theme.spacing.md};
|
||||
`,
|
||||
}));
|
@ -1,32 +0,0 @@
|
||||
<div class="pluginlist">
|
||||
<div class="pluginlist-section" ng-repeat="category in ctrl.viewModel">
|
||||
<h6 class="pluginlist-section-header">
|
||||
{{category.header}}
|
||||
</h6>
|
||||
<div class="pluginlist-item" ng-repeat="plugin in category.list">
|
||||
<a class="pluginlist-link pluginlist-link-{{plugin.state}} pointer" href="{{plugin.defaultNavUrl}}">
|
||||
<span>
|
||||
<img ng-src="{{plugin.info.logos.small}}" class="pluginlist-image">
|
||||
<span class="pluginlist-title">{{plugin.name}}</span>
|
||||
<span class="pluginlist-version">v{{plugin.info.version}}</span>
|
||||
</span>
|
||||
<span ng-if="ctrl.isAdmin">
|
||||
<span class="pluginlist-message pluginlist-message--update" ng-show="plugin.hasUpdate" ng-click="ctrl.updateAvailable(plugin, $event)" bs-tooltip="'New version: ' + plugin.latestVersion">
|
||||
Update available!
|
||||
</span>
|
||||
<span class="pluginlist-message pluginlist-message--enable" ng-show="!plugin.enabled && !plugin.hasUpdate">
|
||||
Enable now
|
||||
</span>
|
||||
<span class="pluginlist-message pluginlist-message--no-update" ng-show="plugin.enabled && !plugin.hasUpdate">
|
||||
Up to date
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="pluginlist-item" ng-show="category.list.length === 0">
|
||||
<a class="pluginlist-link pluginlist-link-{{plugin.state}}" href="https://grafana.com/plugins">
|
||||
<span class="pluginlist-none-installed">None installed. <span class="pluginlist-emphasis">Browse Grafana.com</span></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,79 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import { PanelCtrl } from '../../../features/panel/panel_ctrl';
|
||||
import { auto, IScope } from 'angular';
|
||||
import { ContextSrv } from '../../../core/services/context_srv';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
||||
|
||||
class PluginListCtrl extends PanelCtrl {
|
||||
static templateUrl = 'module.html';
|
||||
static scrollable = true;
|
||||
|
||||
pluginList: any[];
|
||||
viewModel: any;
|
||||
isAdmin: boolean;
|
||||
|
||||
// Set and populate defaults
|
||||
panelDefaults = {};
|
||||
|
||||
/** @ngInject */
|
||||
constructor($scope: IScope, $injector: auto.IInjectorService, contextSrv: ContextSrv) {
|
||||
super($scope, $injector);
|
||||
|
||||
_.defaults(this.panel, this.panelDefaults);
|
||||
|
||||
this.isAdmin = contextSrv.hasRole('Admin');
|
||||
this.pluginList = [];
|
||||
this.viewModel = [
|
||||
{ header: 'Installed Apps', list: [], type: 'app' },
|
||||
{ header: 'Installed Panels', list: [], type: 'panel' },
|
||||
{ header: 'Installed Datasources', list: [], type: 'datasource' },
|
||||
];
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
gotoPlugin(plugin: { id: any }, evt: any) {
|
||||
if (evt) {
|
||||
evt.stopPropagation();
|
||||
}
|
||||
this.$location.url(`plugins/${plugin.id}/edit`);
|
||||
}
|
||||
|
||||
updateAvailable(plugin: any, $event: any) {
|
||||
$event.stopPropagation();
|
||||
$event.preventDefault();
|
||||
|
||||
const modalScope = this.$scope.$new(true);
|
||||
modalScope.plugin = plugin;
|
||||
|
||||
this.publishAppEvent(CoreEvents.showModal, {
|
||||
src: 'public/app/features/plugins/partials/update_instructions.html',
|
||||
scope: modalScope,
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
promiseToDigest(this.$scope)(
|
||||
getBackendSrv()
|
||||
.get('api/plugins', { embedded: 0, core: 0 })
|
||||
.then((plugins) => {
|
||||
this.pluginList = plugins;
|
||||
this.viewModel[0].list = _.filter(plugins, { type: 'app' });
|
||||
this.viewModel[1].list = _.filter(plugins, { type: 'panel' });
|
||||
this.viewModel[2].list = _.filter(plugins, { type: 'datasource' });
|
||||
|
||||
for (const plugin of this.pluginList) {
|
||||
if (plugin.hasUpdate) {
|
||||
plugin.state = 'has-update';
|
||||
} else if (!plugin.enabled) {
|
||||
plugin.state = 'not-enabled';
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { PluginListCtrl, PluginListCtrl as PanelCtrl };
|
4
public/app/plugins/panel/pluginlist/module.tsx
Normal file
4
public/app/plugins/panel/pluginlist/module.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
import { PanelPlugin } from '@grafana/data';
|
||||
import { PluginList } from './PluginList';
|
||||
|
||||
export const plugin = new PanelPlugin(PluginList);
|
Loading…
Reference in New Issue
Block a user