PluginDetails.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import { css } from '@emotion/css';
  2. import React, { useEffect } from 'react';
  3. import { usePrevious } from 'react-use';
  4. import { GrafanaTheme2 } from '@grafana/data';
  5. import { locationService } from '@grafana/runtime';
  6. import { useStyles2, TabsBar, TabContent, Tab, Alert, IconName } from '@grafana/ui';
  7. import { Layout } from '@grafana/ui/src/components/Layout/Layout';
  8. import { Page } from 'app/core/components/Page/Page';
  9. import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
  10. import { AppNotificationSeverity } from 'app/types';
  11. import { Loader } from '../components/Loader';
  12. import { PluginDetailsBody } from '../components/PluginDetailsBody';
  13. import { PluginDetailsDisabledError } from '../components/PluginDetailsDisabledError';
  14. import { PluginDetailsHeader } from '../components/PluginDetailsHeader';
  15. import { PluginDetailsSignature } from '../components/PluginDetailsSignature';
  16. import { usePluginDetailsTabs } from '../hooks/usePluginDetailsTabs';
  17. import { useGetSingle, useFetchStatus, useFetchDetailsStatus } from '../state/hooks';
  18. import { PluginTabLabels, PluginTabIds, PluginDetailsTab } from '../types';
  19. type Props = GrafanaRouteComponentProps<{ pluginId?: string }>;
  20. export default function PluginDetails({ match, queryParams }: Props): JSX.Element | null {
  21. const {
  22. params: { pluginId = '' },
  23. url,
  24. } = match;
  25. const parentUrl = url.substring(0, url.lastIndexOf('/'));
  26. const defaultTabs: PluginDetailsTab[] = [
  27. {
  28. label: PluginTabLabels.OVERVIEW,
  29. icon: 'file-alt',
  30. id: PluginTabIds.OVERVIEW,
  31. href: `${url}?page=${PluginTabIds.OVERVIEW}`,
  32. },
  33. ];
  34. const plugin = useGetSingle(pluginId); // fetches the localplugin settings
  35. const { tabs, defaultTab } = usePluginDetailsTabs(plugin, defaultTabs);
  36. const { isLoading: isFetchLoading } = useFetchStatus();
  37. const { isLoading: isFetchDetailsLoading } = useFetchDetailsStatus();
  38. const styles = useStyles2(getStyles);
  39. const prevTabs = usePrevious(tabs);
  40. const pageId = (queryParams.page as PluginTabIds) || defaultTab;
  41. // If an app plugin is uninstalled we need to reset the active tab when the config / dashboards tabs are removed.
  42. useEffect(() => {
  43. const hasUninstalledWithConfigPages = prevTabs && prevTabs.length > tabs.length;
  44. const isViewingAConfigPage = pageId !== PluginTabIds.OVERVIEW && pageId !== PluginTabIds.VERSIONS;
  45. if (hasUninstalledWithConfigPages && isViewingAConfigPage) {
  46. locationService.replace(`${url}?page=${PluginTabIds.OVERVIEW}`);
  47. }
  48. }, [pageId, url, tabs, prevTabs]);
  49. if (isFetchLoading || isFetchDetailsLoading) {
  50. return (
  51. <Page>
  52. <Loader />
  53. </Page>
  54. );
  55. }
  56. if (!plugin) {
  57. return (
  58. <Layout justify="center" align="center">
  59. <Alert severity={AppNotificationSeverity.Warning} title="Plugin not found">
  60. That plugin cannot be found. Please check the url is correct or <br />
  61. go to the <a href={parentUrl}>plugin catalog</a>.
  62. </Alert>
  63. </Layout>
  64. );
  65. }
  66. return (
  67. <Page>
  68. <PluginDetailsHeader currentUrl={`${url}?page=${pageId}`} parentUrl={parentUrl} plugin={plugin} />
  69. {/* Tab navigation */}
  70. <div>
  71. <div className="page-container">
  72. <TabsBar hideBorder>
  73. {tabs.map((tab: PluginDetailsTab) => {
  74. return (
  75. <Tab
  76. key={tab.label}
  77. label={tab.label}
  78. href={tab.href}
  79. icon={tab.icon as IconName}
  80. active={tab.id === pageId}
  81. />
  82. );
  83. })}
  84. </TabsBar>
  85. </div>
  86. </div>
  87. <Page.Contents>
  88. {/* Active tab */}
  89. <TabContent className={styles.tabContent}>
  90. <PluginDetailsSignature plugin={plugin} className={styles.alert} />
  91. <PluginDetailsDisabledError plugin={plugin} className={styles.alert} />
  92. <PluginDetailsBody queryParams={queryParams} plugin={plugin} pageId={pageId} />
  93. </TabContent>
  94. </Page.Contents>
  95. </Page>
  96. );
  97. }
  98. export const getStyles = (theme: GrafanaTheme2) => {
  99. return {
  100. alert: css`
  101. margin: ${theme.spacing(3)};
  102. margin-bottom: 0;
  103. `,
  104. // Needed due to block formatting context
  105. tabContent: css`
  106. overflow: auto;
  107. `,
  108. };
  109. };