mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* feat(Plugins/Catalog): start adding necessary apis * feat(PLugins/Catalog): add extra helpers for merging local & remote plugins * feat(Plugins/Catalog): add plugin details as an optional field of CatalogPlugin * feat(PLugins/Catalog): add scaffolding for the new redux model * feat(PLugins/Catalog): export reducers based on a feature-flag * refactor(Plugins/Admin): rename api methods * feat(Plugin/Catalog): add an api method for fetching a single plugin * feat(Plugins/Admin): try cleaning stuff around plugin fetching * ffeat(Plugins/Catalog): return the catalog reducer when the feature flag is set * refactor(Plugins/Admin): fix typings * feat(Plugins/Admin): use the new reducer for the browse page * feat(catalog): introduce selectors to search and filter plugins list * refactor(Plugins/Details): rename page prop type * refactor(Plugins/Admin): add a const for a state prefix * refactor(Plugins/Admin): use the state prefix in the actions * feat(Plugins/Admin): add types for the requests * refactor(Plugins/Admin): add request info to the reducer * refactor(Plugins/Admin): add request handling to the hooks & selectors * refactor(Plugins/Details): start using the data stored in Redux * refactor(Plugins/Admin): rename selector to start with "select" * fix(Plugins/Admin): only fetch plugins once * refactor(Plugins/Admin): make the tab selection work in details * refactor(catalog): put back loading and error states in plugin list * refactor(Plugins/Admin): use CatalogPlugin for <PluginDetailsSignature /> * feat(Plugins/Admin): add an api method for fetching plugin details * refactor(Plugins/Admin): add action for updating the details * irefactor(Plugins/Admin): show basic plugin details info * refactor(Plugin Details): migrate the plugin details header * refactor(Plugins/Admin): make the config and dashboards tabs work * refactor(Plugins/Admin): add old reducer state to the new one * feat(catalog): introduce actions, reducers and hooks for install & uninstall * refactor(catalog): wire up InstallControls component to redux * refactor(catalog): move parentUrl inside PluginDetailsHeader and uncomment InstallControls * feat(catalog): introduce code for plugin updates to install action * refactor(Plugins/Admin): add backward compatible actions * test(catalog): update PluginDetails and Browse tests to work with catalog store * refactor(Plugins/Admin): make the dashboards and panels work again * refactor(Plugins/Admin): fix linter and typescript errors * fix(Plugins/Admin): put the local-only plugins to the beginning of the list * fix(Plugins/Admin): fix the mocks in the tests for PluginDetails * refactor(Plugins/Admin): remove unecessary hook usePluginsByFilter() * refactor(Plugins/Admin): extract the useTabs() hook to its own file * refactor(Plugins/Admin): remove unused helpers and types * fix(Plugins/Admin): show the first tab when uninstalling an app plugin This can cause the user to find themselves on a dissappeared tab, as the config and dashboards tabs are removed. * fix(catalog): correct logic for checking if activeTabIndex is greater than total tabs * fix(Plugins/Admin): fix race-condition between fetching plugin details and all plugins * fix(Plugins): fix strict type errors * chore(catalog): remove todos * feat(catalog): render an alert in PluginDetails when a plugin cannot be found * feat(catalog): use the proper store state * refactor(Plugins/Admin): fetch local and remote plugins in parallell Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com> * style(catalog): fix prettier error in api * fix(catalog): prevent throwing error if InstallControlsButton is unmounted during install * refactor(Plugins/Admin): add a separate hook for filtering & sorting plugins Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com> Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>
139 lines
3.4 KiB
TypeScript
139 lines
3.4 KiB
TypeScript
import React from 'react';
|
|
import { css } from '@emotion/css';
|
|
import { GrafanaTheme2 } from '@grafana/data';
|
|
import { useStyles2, Icon } from '@grafana/ui';
|
|
|
|
import { InstallControls } from './InstallControls';
|
|
import { PluginDetailsHeaderSignature } from './PluginDetailsHeaderSignature';
|
|
import { PluginLogo } from './PluginLogo';
|
|
import { CatalogPlugin } from '../types';
|
|
|
|
type Props = {
|
|
currentUrl: string;
|
|
parentUrl: string;
|
|
plugin: CatalogPlugin;
|
|
};
|
|
|
|
export function PluginDetailsHeader({ plugin, currentUrl, parentUrl }: Props): React.ReactElement {
|
|
const styles = useStyles2(getStyles);
|
|
|
|
return (
|
|
<div className={styles.headerContainer}>
|
|
<PluginLogo
|
|
alt={`${plugin.name} logo`}
|
|
src={plugin.info.logos.small}
|
|
className={css`
|
|
object-fit: contain;
|
|
width: 100%;
|
|
height: 68px;
|
|
max-width: 68px;
|
|
`}
|
|
/>
|
|
|
|
<div className={styles.headerWrapper}>
|
|
{/* Title & navigation */}
|
|
<nav className={styles.breadcrumb} aria-label="Breadcrumb">
|
|
<ol>
|
|
<li>
|
|
<a className={styles.textUnderline} href={parentUrl}>
|
|
Plugins
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href={currentUrl} aria-current="page">
|
|
{plugin.name}
|
|
</a>
|
|
</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<div className={styles.headerInformation}>
|
|
{/* Org name */}
|
|
<span>{plugin.orgName}</span>
|
|
|
|
{/* Links */}
|
|
{plugin.details?.links.map((link: any) => (
|
|
<a key={link.name} href={link.url}>
|
|
{link.name}
|
|
</a>
|
|
))}
|
|
|
|
{/* Downloads */}
|
|
{plugin.downloads > 0 && (
|
|
<span>
|
|
<Icon name="cloud-download" />
|
|
{` ${new Intl.NumberFormat().format(plugin.downloads)}`}{' '}
|
|
</span>
|
|
)}
|
|
|
|
{/* Latest version */}
|
|
{plugin.version && <span>{plugin.version}</span>}
|
|
|
|
{/* Signature information */}
|
|
<PluginDetailsHeaderSignature plugin={plugin} />
|
|
</div>
|
|
|
|
<p>{plugin.description}</p>
|
|
|
|
<InstallControls plugin={plugin} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export const getStyles = (theme: GrafanaTheme2) => {
|
|
return {
|
|
headerContainer: css`
|
|
display: flex;
|
|
margin-bottom: ${theme.spacing(3)};
|
|
margin-top: ${theme.spacing(3)};
|
|
min-height: 120px;
|
|
`,
|
|
headerWrapper: css`
|
|
margin-left: ${theme.spacing(3)};
|
|
`,
|
|
breadcrumb: css`
|
|
font-size: ${theme.typography.h2.fontSize};
|
|
li {
|
|
display: inline;
|
|
list-style: none;
|
|
&::after {
|
|
content: '/';
|
|
padding: 0 0.25ch;
|
|
}
|
|
&:last-child::after {
|
|
content: '';
|
|
}
|
|
}
|
|
`,
|
|
headerInformation: css`
|
|
display: flex;
|
|
align-items: center;
|
|
margin-top: ${theme.spacing()};
|
|
margin-bottom: ${theme.spacing(3)};
|
|
|
|
& > * {
|
|
&::after {
|
|
content: '|';
|
|
padding: 0 ${theme.spacing()};
|
|
}
|
|
&:last-child::after {
|
|
content: '';
|
|
padding-right: 0;
|
|
}
|
|
}
|
|
font-size: ${theme.typography.h4.fontSize};
|
|
`,
|
|
headerOrgName: css`
|
|
font-size: ${theme.typography.h4.fontSize};
|
|
`,
|
|
signature: css`
|
|
margin: ${theme.spacing(3)};
|
|
margin-bottom: 0;
|
|
`,
|
|
textUnderline: css`
|
|
text-decoration: underline;
|
|
`,
|
|
};
|
|
};
|