2022-11-29 06:45:03 -06:00
|
|
|
import { css } from '@emotion/css';
|
2024-06-25 06:43:47 -05:00
|
|
|
import * as React from 'react';
|
2024-08-23 01:54:13 -05:00
|
|
|
import { useLocation } from 'react-router-dom-v5-compat';
|
2022-11-29 06:45:03 -06:00
|
|
|
|
|
|
|
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
2023-06-28 02:58:45 -05:00
|
|
|
import { config } from '@grafana/runtime';
|
2024-07-23 07:16:26 -05:00
|
|
|
import { Alert, Box, Stack, TabContent, useStyles2 } from '@grafana/ui';
|
2022-11-29 06:45:03 -06:00
|
|
|
import { Page } from 'app/core/components/Page/Page';
|
|
|
|
import { AppNotificationSeverity } from 'app/types';
|
|
|
|
|
2023-07-24 10:25:36 -05:00
|
|
|
import { AngularDeprecationPluginNotice } from '../../angularDeprecation/AngularDeprecationPluginNotice';
|
2022-11-29 06:45:03 -06:00
|
|
|
import { Loader } from '../components/Loader';
|
|
|
|
import { PluginDetailsBody } from '../components/PluginDetailsBody';
|
|
|
|
import { PluginDetailsDisabledError } from '../components/PluginDetailsDisabledError';
|
2024-08-13 04:55:30 -05:00
|
|
|
import { PluginDetailsRightPanel } from '../components/PluginDetailsRightPanel';
|
2022-11-29 06:45:03 -06:00
|
|
|
import { PluginDetailsSignature } from '../components/PluginDetailsSignature';
|
|
|
|
import { usePluginDetailsTabs } from '../hooks/usePluginDetailsTabs';
|
|
|
|
import { usePluginPageExtensions } from '../hooks/usePluginPageExtensions';
|
|
|
|
import { useGetSingle, useFetchStatus, useFetchDetailsStatus } from '../state/hooks';
|
|
|
|
import { PluginTabIds } from '../types';
|
|
|
|
|
2023-09-12 05:49:10 -05:00
|
|
|
import { PluginDetailsDeprecatedWarning } from './PluginDetailsDeprecatedWarning';
|
|
|
|
|
2022-11-29 06:45:03 -06:00
|
|
|
export type Props = {
|
|
|
|
// The ID of the plugin
|
|
|
|
pluginId: string;
|
|
|
|
// The navigation ID used for displaying the sidebar navigation
|
|
|
|
navId?: string;
|
|
|
|
// Can be used to customise the title & subtitle for the not found page
|
|
|
|
notFoundNavModel?: NavModelItem;
|
|
|
|
// Can be used to customise the content shown when a plugin with the given ID cannot be found
|
|
|
|
notFoundComponent?: React.ReactElement;
|
|
|
|
};
|
|
|
|
|
|
|
|
export function PluginDetailsPage({
|
|
|
|
pluginId,
|
|
|
|
navId = 'plugins',
|
|
|
|
notFoundComponent = <NotFoundPlugin />,
|
|
|
|
notFoundNavModel = {
|
|
|
|
text: 'Unknown plugin',
|
|
|
|
subTitle: 'The requested ID does not belong to any plugin',
|
|
|
|
active: true,
|
|
|
|
},
|
|
|
|
}: Props) {
|
|
|
|
const location = useLocation();
|
|
|
|
const queryParams = new URLSearchParams(location.search);
|
|
|
|
const plugin = useGetSingle(pluginId); // fetches the plugin settings for this Grafana instance
|
|
|
|
const { navModel, activePageId } = usePluginDetailsTabs(plugin, queryParams.get('page') as PluginTabIds);
|
|
|
|
const { actions, info, subtitle } = usePluginPageExtensions(plugin);
|
|
|
|
const { isLoading: isFetchLoading } = useFetchStatus();
|
|
|
|
const { isLoading: isFetchDetailsLoading } = useFetchDetailsStatus();
|
|
|
|
const styles = useStyles2(getStyles);
|
|
|
|
|
|
|
|
if (isFetchLoading || isFetchDetailsLoading) {
|
|
|
|
return (
|
|
|
|
<Page
|
|
|
|
navId={navId}
|
|
|
|
pageNav={{
|
|
|
|
text: '',
|
|
|
|
active: true,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Loader />
|
|
|
|
</Page>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!plugin) {
|
|
|
|
return (
|
|
|
|
<Page navId={navId} pageNav={notFoundNavModel}>
|
|
|
|
{notFoundComponent}
|
|
|
|
</Page>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-08-13 04:55:30 -05:00
|
|
|
const conditionalProps = !config.featureToggles.pluginsDetailsRightPanel ? { info: info } : {};
|
|
|
|
|
2022-11-29 06:45:03 -06:00
|
|
|
return (
|
2024-08-13 04:55:30 -05:00
|
|
|
<Page navId={navId} pageNav={navModel} actions={actions} subTitle={subtitle} {...conditionalProps}>
|
|
|
|
<Stack gap={4} justifyContent="space-between" direction={{ xs: 'column-reverse', sm: 'row' }}>
|
|
|
|
<Page.Contents>
|
|
|
|
<TabContent className={styles.tabContent}>
|
|
|
|
{plugin.angularDetected && (
|
|
|
|
<AngularDeprecationPluginNotice
|
|
|
|
className={styles.alert}
|
|
|
|
angularSupportEnabled={config?.angularSupportEnabled}
|
|
|
|
pluginId={plugin.id}
|
|
|
|
pluginType={plugin.type}
|
|
|
|
showPluginDetailsLink={false}
|
|
|
|
interactionElementId="plugin-details-page"
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
<PluginDetailsSignature plugin={plugin} className={styles.alert} />
|
|
|
|
<PluginDetailsDisabledError plugin={plugin} className={styles.alert} />
|
|
|
|
<PluginDetailsDeprecatedWarning plugin={plugin} className={styles.alert} />
|
|
|
|
<PluginDetailsBody queryParams={Object.fromEntries(queryParams)} plugin={plugin} pageId={activePageId} />
|
|
|
|
</TabContent>
|
|
|
|
</Page.Contents>
|
|
|
|
{config.featureToggles.pluginsDetailsRightPanel && <PluginDetailsRightPanel info={info} plugin={plugin} />}
|
|
|
|
</Stack>
|
2022-11-29 06:45:03 -06:00
|
|
|
</Page>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export const getStyles = (theme: GrafanaTheme2) => {
|
|
|
|
return {
|
2024-07-17 07:48:47 -05:00
|
|
|
alert: css({
|
|
|
|
marginBottom: theme.spacing(2),
|
|
|
|
}),
|
|
|
|
subtitle: css({
|
|
|
|
display: 'flex',
|
|
|
|
flexDirection: 'column',
|
|
|
|
gap: theme.spacing(1),
|
|
|
|
}),
|
2022-11-29 06:45:03 -06:00
|
|
|
// Needed due to block formatting context
|
2024-09-24 07:23:18 -05:00
|
|
|
tabContent: css({
|
|
|
|
paddingLeft: '5px',
|
2024-11-13 07:30:29 -06:00
|
|
|
width: '100%',
|
2024-09-24 07:23:18 -05:00
|
|
|
}),
|
2022-11-29 06:45:03 -06:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
function NotFoundPlugin() {
|
|
|
|
return (
|
2024-07-23 07:16:26 -05:00
|
|
|
<Stack justifyContent="center" alignItems="center" height="100%">
|
|
|
|
<Box>
|
|
|
|
<Alert severity={AppNotificationSeverity.Warning} title="Plugin not found">
|
|
|
|
That plugin cannot be found. Please check the url is correct or <br />
|
|
|
|
go to the <a href="/plugins">plugin catalog</a>.
|
|
|
|
</Alert>
|
|
|
|
</Box>
|
|
|
|
</Stack>
|
2022-11-29 06:45:03 -06:00
|
|
|
);
|
|
|
|
}
|