From 3d459b556a96f719b0f6df8fbd2d4dd099fbe509 Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Tue, 9 Mar 2021 12:22:59 +0000 Subject: [PATCH] Panel/PluginList: Migrate to React (#31738) * Panel/PluginList: Migrate to React --- .../plugins/panel/pluginlist/PluginList.tsx | 151 ++++++++++++++++++ .../components/UpdatePluginModal.tsx | 59 +++++++ .../app/plugins/panel/pluginlist/module.html | 32 ---- public/app/plugins/panel/pluginlist/module.ts | 79 --------- .../app/plugins/panel/pluginlist/module.tsx | 4 + 5 files changed, 214 insertions(+), 111 deletions(-) create mode 100644 public/app/plugins/panel/pluginlist/PluginList.tsx create mode 100644 public/app/plugins/panel/pluginlist/components/UpdatePluginModal.tsx delete mode 100644 public/app/plugins/panel/pluginlist/module.html delete mode 100644 public/app/plugins/panel/pluginlist/module.ts create mode 100644 public/app/plugins/panel/pluginlist/module.tsx diff --git a/public/app/plugins/panel/pluginlist/PluginList.tsx b/public/app/plugins/panel/pluginlist/PluginList.tsx new file mode 100644 index 00000000000..44726ac04f1 --- /dev/null +++ b/public/app/plugins/panel/pluginlist/PluginList.tsx @@ -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 ( + +
+ {pluginState.value.map((category) => ( +
+
{category.header}
+ {category.list.map((plugin) => ( + + + {plugin.name} + v{plugin.info.version} + {isAdmin && + (plugin.hasUpdate ? ( + + {({ showModal, hideModal }) => ( + + { + e.preventDefault(); + + showModal(UpdatePluginModal, { + pluginID: plugin.id, + pluginName: plugin.name, + onDismiss: hideModal, + isOpen: true, + }); + }} + > + Update available! + + + )} + + ) : plugin.enabled ? ( + Up to date + ) : ( + Enable now + ))} + + ))} + + {category.list.length === 0 && ( + + + None installed. Browse Grafana.com + + + )} +
+ ))} +
+
+ ); +} + +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}; + `, +})); diff --git a/public/app/plugins/panel/pluginlist/components/UpdatePluginModal.tsx b/public/app/plugins/panel/pluginlist/components/UpdatePluginModal.tsx new file mode 100644 index 00000000000..ab523789b6e --- /dev/null +++ b/public/app/plugins/panel/pluginlist/components/UpdatePluginModal.tsx @@ -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 ( + +
+

Type the following on the command line to update {pluginName}.

+
+          grafana-cli plugins update {pluginID}
+        
+ + Check out {pluginName} on Grafana.com for README and + changelog. If you do not have access to the command line, ask your Grafana administator. + +
+

+ + Pro tip: To update all plugins at once, type{' '} + grafana-cli plugins update-all on the command line. +

+
+ ); +} + +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}; + `, +})); diff --git a/public/app/plugins/panel/pluginlist/module.html b/public/app/plugins/panel/pluginlist/module.html deleted file mode 100644 index 1ab69bbc62a..00000000000 --- a/public/app/plugins/panel/pluginlist/module.html +++ /dev/null @@ -1,32 +0,0 @@ -
-
-
- {{category.header}} -
- - -
-
diff --git a/public/app/plugins/panel/pluginlist/module.ts b/public/app/plugins/panel/pluginlist/module.ts deleted file mode 100644 index 76f9f2a0d98..00000000000 --- a/public/app/plugins/panel/pluginlist/module.ts +++ /dev/null @@ -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 }; diff --git a/public/app/plugins/panel/pluginlist/module.tsx b/public/app/plugins/panel/pluginlist/module.tsx new file mode 100644 index 00000000000..86f97e04b5d --- /dev/null +++ b/public/app/plugins/panel/pluginlist/module.tsx @@ -0,0 +1,4 @@ +import { PanelPlugin } from '@grafana/data'; +import { PluginList } from './PluginList'; + +export const plugin = new PanelPlugin(PluginList);