import { css } from '@emotion/css'; import { memo } from 'react'; import { GrafanaTheme2, LinkTarget } from '@grafana/data'; import { t } from '@grafana/i18n'; import { config } from '@grafana/runtime'; import { Icon, IconName, useStyles2 } from '@grafana/ui'; export interface FooterLink { target: LinkTarget; text: string; id: string; icon?: IconName; url?: string; } export let getFooterLinks = (): FooterLink[] => { return [ { target: '_blank', id: 'documentation', text: t('nav.help/documentation', 'Documentation'), icon: 'document-info', url: 'https://grafana.com/docs/grafana/latest/?utm_source=grafana_footer', }, { target: '_blank', id: 'support', text: t('nav.help/support', 'Support'), icon: 'question-circle', url: 'https://grafana.com/products/enterprise/?utm_source=grafana_footer', }, { target: '_blank', id: 'community', text: t('nav.help/community', 'Community'), icon: 'comments-alt', url: 'https://community.grafana.com/?utm_source=grafana_footer', }, ]; }; export function getVersionMeta(version: string) { const isBeta = version.includes('-beta'); return { hasReleaseNotes: true, isBeta, }; } export function getVersionLinks(hideEdition?: boolean): FooterLink[] { const { buildInfo, licenseInfo } = config; const links: FooterLink[] = []; const stateInfo = licenseInfo.stateInfo ? ` (${licenseInfo.stateInfo})` : ''; if (!hideEdition) { links.push({ target: '_blank', id: 'license', text: `${buildInfo.edition}${stateInfo}`, url: licenseInfo.licenseUrl, }); } if (buildInfo.hideVersion) { return links; } const { hasReleaseNotes } = getVersionMeta(buildInfo.version); links.push({ target: '_blank', id: 'version', text: buildInfo.versionString, url: hasReleaseNotes ? `https://github.com/grafana/grafana/blob/main/CHANGELOG.md` : undefined, }); if (buildInfo.hasUpdate) { links.push({ target: '_blank', id: 'updateVersion', text: `New version available!`, icon: 'download-alt', url: 'https://grafana.com/grafana/download?utm_source=grafana_footer', }); } return links; } export function setFooterLinksFn(fn: typeof getFooterLinks) { getFooterLinks = fn; } export interface Props { /** Link overrides to show specific links in the UI */ customLinks?: FooterLink[] | null; hideEdition?: boolean; } export const Footer = memo(({ customLinks, hideEdition }: Props) => { const links = (customLinks || getFooterLinks()).concat(getVersionLinks(hideEdition)); const styles = useStyles2(getStyles); return ( ); }); Footer.displayName = 'Footer'; function FooterItem({ item }: { item: FooterLink }) { const content = item.url ? ( {item.text} ) : ( item.text ); return ( <> {item.icon && } {content} ); } const getStyles = (theme: GrafanaTheme2) => ({ footer: css({ ...theme.typography.bodySmall, color: theme.colors.text.primary, display: 'block', padding: theme.spacing(2, 0), position: 'relative', width: '98%', 'a:hover': { color: theme.colors.text.maxContrast, textDecoration: 'underline', }, [theme.breakpoints.down('md')]: { display: 'none', }, }), list: css({ listStyle: 'none', }), listItem: css({ display: 'inline-block', '&:after': { content: "' | '", padding: theme.spacing(0, 1), }, '&:last-child:after': { content: "''", paddingLeft: 0, }, }), });