mirror of https://github.com/grafana/grafana
Onboarding: New getting started panel (#23826)
* image and card component * change height of getting started panel * progress * setup basic step * advanced steps * step forward and backward * do checks * fix button size * minor styling on butttons * add correct links * save tutorial click in localstorage * types and gradients * fix gradients * use spacing variable * lots of responsiveness * add links to help * Getting started work * redo according to split panel design * minor touch ups * new background images * split up docs card to different hrefs * welcome bar touch ups * hide icon on small screens * transparent false on welcome banner * fix urls * source tag in welcome urls * move images to panel dir, removed unused images * Nicer loading message * make the cards look nicer on wide screens * append utm tag on render instead * replace width with margin * new background image for light * remove target on a element * removing buttonselect, add tag to href * more polishing Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>pull/24605/head
parent
cab066f8ce
commit
36fa54a288
@ -0,0 +1,61 @@ |
||||
import React, { FC } from 'react'; |
||||
import { Card } from '../types'; |
||||
import { Icon, stylesFactory, useTheme } from '@grafana/ui'; |
||||
import { GrafanaTheme } from '@grafana/data'; |
||||
import { css } from 'emotion'; |
||||
import { cardContent, cardStyle, iconStyle } from './sharedStyles'; |
||||
|
||||
interface Props { |
||||
card: Card; |
||||
} |
||||
|
||||
export const DocsCard: FC<Props> = ({ card }) => { |
||||
const theme = useTheme(); |
||||
const styles = getStyles(theme, card.done); |
||||
|
||||
return ( |
||||
<div className={styles.card}> |
||||
<div className={cardContent}> |
||||
<a href={`${card.href}?utm_source=grafana_gettingstarted`}> |
||||
<div className={styles.heading}>{card.done ? 'complete' : card.heading}</div> |
||||
<h4 className={styles.title}>{card.title}</h4> |
||||
<div> |
||||
<Icon className={iconStyle(theme, card.done)} name={card.icon} size="xxl" /> |
||||
</div> |
||||
</a> |
||||
</div> |
||||
<a href={`${card.learnHref}?utm_source=grafana_gettingstarted`} className={styles.url} target="_blank"> |
||||
Learn how in the docs <Icon name="external-link-alt" /> |
||||
</a> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme, complete: boolean) => { |
||||
return { |
||||
card: css` |
||||
${cardStyle(theme, complete)} |
||||
|
||||
min-width: 230px; |
||||
|
||||
@media only screen and (max-width: ${theme.breakpoints.md}) { |
||||
min-width: 192px; |
||||
} |
||||
`,
|
||||
heading: css` |
||||
text-transform: uppercase; |
||||
color: ${complete ? theme.palette.blue95 : '#FFB357'}; |
||||
margin-bottom: ${theme.spacing.md}; |
||||
`,
|
||||
title: css` |
||||
margin-bottom: 48px; |
||||
`,
|
||||
url: css` |
||||
border-top: 1px solid ${theme.colors.border1}; |
||||
position: absolute; |
||||
bottom: 0; |
||||
padding: 8px 16px; |
||||
width: 100%; |
||||
`,
|
||||
}; |
||||
}); |
||||
@ -0,0 +1,68 @@ |
||||
import React, { FC } from 'react'; |
||||
import { css } from 'emotion'; |
||||
import { GrafanaTheme } from '@grafana/data'; |
||||
import { stylesFactory, useTheme } from '@grafana/ui'; |
||||
import { TutorialCard } from './TutorialCard'; |
||||
import { Card, SetupStep } from '../types'; |
||||
import { DocsCard } from './DocsCard'; |
||||
|
||||
interface Props { |
||||
step: SetupStep; |
||||
} |
||||
|
||||
export const Step: FC<Props> = ({ step }) => { |
||||
const theme = useTheme(); |
||||
const styles = getStyles(theme); |
||||
|
||||
return ( |
||||
<div className={styles.setup}> |
||||
<div className={styles.info}> |
||||
<h2 className={styles.title}>{step.title}</h2> |
||||
<p>{step.info}</p> |
||||
</div> |
||||
<div className={styles.cards}> |
||||
{step.cards.map((card: Card, index: number) => { |
||||
const key = `${card.title}-${index}`; |
||||
if (card.type === 'tutorial') { |
||||
return <TutorialCard key={key} card={card} />; |
||||
} |
||||
return <DocsCard key={key} card={card} />; |
||||
})} |
||||
</div> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => { |
||||
return { |
||||
setup: css` |
||||
display: flex; |
||||
width: 95%; |
||||
`,
|
||||
info: css` |
||||
width: 172px; |
||||
margin-right: 5%; |
||||
|
||||
@media only screen and (max-width: ${theme.breakpoints.xxl}) { |
||||
margin-right: ${theme.spacing.xl}; |
||||
} |
||||
@media only screen and (max-width: ${theme.breakpoints.sm}) { |
||||
display: none; |
||||
} |
||||
`,
|
||||
title: css` |
||||
color: ${theme.palette.blue95}; |
||||
`,
|
||||
cards: css` |
||||
overflow-x: scroll; |
||||
overflow-y: hidden; |
||||
width: 100%; |
||||
display: flex; |
||||
justify-content: center; |
||||
|
||||
@media only screen and (max-width: ${theme.breakpoints.xxl}) { |
||||
justify-content: flex-start; |
||||
} |
||||
`,
|
||||
}; |
||||
}); |
||||
@ -0,0 +1,72 @@ |
||||
import React, { FC, MouseEvent } from 'react'; |
||||
import { GrafanaTheme } from '@grafana/data'; |
||||
import { Icon, stylesFactory, useTheme } from '@grafana/ui'; |
||||
import { css } from 'emotion'; |
||||
import store from 'app/core/store'; |
||||
import { cardContent, cardStyle, iconStyle } from './sharedStyles'; |
||||
import { Card } from '../types'; |
||||
|
||||
interface Props { |
||||
card: Card; |
||||
} |
||||
|
||||
export const TutorialCard: FC<Props> = ({ card }) => { |
||||
const theme = useTheme(); |
||||
const styles = getStyles(theme, card.done); |
||||
|
||||
return ( |
||||
<a className={styles.card} onClick={(event: MouseEvent<HTMLAnchorElement>) => handleTutorialClick(event, card)}> |
||||
<div className={cardContent}> |
||||
<div className={styles.type}>{card.type}</div> |
||||
<div className={styles.heading}>{card.done ? 'complete' : card.heading}</div> |
||||
<h4>{card.title}</h4> |
||||
<div className={styles.info}>{card.info}</div> |
||||
<Icon className={iconStyle(theme, card.done)} name={card.icon} size="xxl" /> |
||||
</div> |
||||
</a> |
||||
); |
||||
}; |
||||
|
||||
const handleTutorialClick = (event: MouseEvent<HTMLAnchorElement>, card: Card) => { |
||||
event.preventDefault(); |
||||
const isSet = store.get(card.key); |
||||
if (!isSet) { |
||||
store.set(card.key, true); |
||||
} |
||||
window.open(`${card.href}?utm_source=grafana_gettingstarted`, '_blank'); |
||||
}; |
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme, complete: boolean) => { |
||||
const textColor = `${complete ? theme.palette.blue95 : '#FFB357'}`; |
||||
return { |
||||
card: css` |
||||
${cardStyle(theme, complete)} |
||||
width: 460px; |
||||
min-width: 460px; |
||||
|
||||
@media only screen and (max-width: ${theme.breakpoints.xl}) { |
||||
min-width: 368px; |
||||
} |
||||
|
||||
@media only screen and (max-width: ${theme.breakpoints.lg}) { |
||||
min-width: 272px; |
||||
} |
||||
`,
|
||||
type: css` |
||||
color: ${textColor}; |
||||
text-transform: uppercase; |
||||
`,
|
||||
heading: css` |
||||
text-transform: uppercase; |
||||
color: ${textColor}; |
||||
margin-bottom: ${theme.spacing.sm}; |
||||
`,
|
||||
info: css` |
||||
margin-bottom: ${theme.spacing.md}; |
||||
`,
|
||||
status: css` |
||||
display: flex; |
||||
justify-content: flex-end; |
||||
`,
|
||||
}; |
||||
}); |
||||
@ -0,0 +1,49 @@ |
||||
import { GrafanaTheme } from '@grafana/data'; |
||||
import { css } from 'emotion'; |
||||
import { stylesFactory } from '@grafana/ui'; |
||||
|
||||
export const cardStyle = stylesFactory((theme: GrafanaTheme, complete: boolean) => { |
||||
const completeGradient = 'linear-gradient(to right, #5182CC 0%, #245BAF 100%)'; |
||||
const darkThemeGradients = complete ? completeGradient : 'linear-gradient(to right, #f05a28 0%, #fbca0a 100%)'; |
||||
const lightThemeGradients = complete ? completeGradient : 'linear-gradient(to right, #FBCA0A 0%, #F05A28 100%)'; |
||||
|
||||
const borderGradient = theme.isDark ? darkThemeGradients : lightThemeGradients; |
||||
|
||||
return ` |
||||
background-color: ${theme.colors.bg1}; |
||||
margin-right: ${theme.spacing.xl}; |
||||
border: 1px solid ${theme.colors.border1}; |
||||
border-bottom-left-radius: ${theme.border.radius.md}; |
||||
border-bottom-right-radius: ${theme.border.radius.md}; |
||||
position: relative; |
||||
max-height: 230px; |
||||
|
||||
@media only screen and (max-width: ${theme.breakpoints.xxl}) { |
||||
margin-right: ${theme.spacing.md}; |
||||
} |
||||
&::before { |
||||
display: block; |
||||
content: ' '; |
||||
position: absolute; |
||||
left: 0; |
||||
right: 0; |
||||
height: 2px; |
||||
top: 0; |
||||
background-image: ${borderGradient}; |
||||
} |
||||
`;
|
||||
}); |
||||
|
||||
export const iconStyle = stylesFactory( |
||||
(theme: GrafanaTheme, complete: boolean) => css` |
||||
color: ${complete ? theme.palette.blue95 : theme.colors.textWeak}; |
||||
|
||||
@media only screen and (max-width: ${theme.breakpoints.sm}) { |
||||
display: none; |
||||
} |
||||
` |
||||
); |
||||
|
||||
export const cardContent = css` |
||||
padding: 24px 16px; |
||||
`;
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,107 @@ |
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; |
||||
import { getBackendSrv } from 'app/core/services/backend_srv'; |
||||
import store from 'app/core/store'; |
||||
import { SetupStep } from './types'; |
||||
|
||||
const step1TutorialTitle = 'Grafana fundamentals'; |
||||
const step2TutorialTitle = 'Create users and teams'; |
||||
const keyPrefix = 'getting.started.'; |
||||
const step1Key = `${keyPrefix}${step1TutorialTitle |
||||
.replace(' ', '-') |
||||
.trim() |
||||
.toLowerCase()}`;
|
||||
const step2Key = `${keyPrefix}${step2TutorialTitle |
||||
.replace(' ', '-') |
||||
.trim() |
||||
.toLowerCase()}`;
|
||||
|
||||
export const getSteps = (): SetupStep[] => [ |
||||
{ |
||||
heading: 'Welcome to Grafana', |
||||
subheading: 'The steps below will guide you to quickly finish setting up your Grafana installation.', |
||||
title: 'Basic', |
||||
info: 'The steps below will guide you to quickly finish setting up your Grafana installation.', |
||||
done: false, |
||||
cards: [ |
||||
{ |
||||
type: 'tutorial', |
||||
heading: 'Data source and dashboards', |
||||
title: step1TutorialTitle, |
||||
info: |
||||
'Set up and understand Grafana if you have no prior experience. This tutorial guides you through the entire process and covers the “Data source” and “Dashboards” steps to the right.', |
||||
href: 'https://grafana.com/tutorials/grafana-fundamentals', |
||||
icon: 'grafana', |
||||
check: () => Promise.resolve(store.get(step1Key)), |
||||
key: step1Key, |
||||
done: false, |
||||
}, |
||||
{ |
||||
type: 'docs', |
||||
title: 'Add your first data source', |
||||
heading: 'data sources', |
||||
icon: 'database', |
||||
learnHref: 'https://grafana.com/docs/grafana/latest/features/datasources/add-a-data-source', |
||||
href: 'datasources/new', |
||||
check: () => { |
||||
return new Promise(resolve => { |
||||
resolve( |
||||
getDatasourceSrv() |
||||
.getMetricSources() |
||||
.filter(item => { |
||||
return item.meta.builtIn !== true; |
||||
}).length > 0 |
||||
); |
||||
}); |
||||
}, |
||||
done: false, |
||||
}, |
||||
{ |
||||
type: 'docs', |
||||
heading: 'dashboards', |
||||
title: 'Create your first dashboard', |
||||
icon: 'apps', |
||||
href: 'dashboard/new', |
||||
learnHref: 'https://grafana.com/docs/grafana/latest/guides/getting_started/#create-a-dashboard', |
||||
check: async () => { |
||||
const result = await getBackendSrv().search({ limit: 1 }); |
||||
return result.length > 0; |
||||
}, |
||||
done: false, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
heading: 'Setup complete!', |
||||
subheading: |
||||
'All necessary steps to use Grafana are done. Now tackle advanced steps or make the best use of this home dashboard – it is, after all, a fully customizable dashboard – and remove this panel.', |
||||
title: 'Advanced', |
||||
info: ' Manage your users and teams and add plugins. These steps are optional', |
||||
done: false, |
||||
cards: [ |
||||
{ |
||||
type: 'tutorial', |
||||
heading: 'Users', |
||||
title: 'Create users and teams', |
||||
info: 'Learn to organize your users in teams and manage resource access and roles.', |
||||
href: 'https://grafana.com/tutorials/create-users-and-teams', |
||||
icon: 'users-alt', |
||||
key: step2Key, |
||||
check: () => Promise.resolve(store.get(step2Key)), |
||||
done: false, |
||||
}, |
||||
{ |
||||
type: 'docs', |
||||
heading: 'plugins', |
||||
title: 'Find and install plugins', |
||||
learnHref: 'https://grafana.com/docs/grafana/latest/plugins/installation', |
||||
href: 'plugins', |
||||
icon: 'plug', |
||||
check: async () => { |
||||
const plugins = await getBackendSrv().get('/api/plugins', { embedded: 0, core: 0 }); |
||||
return Promise.resolve(plugins.length > 0); |
||||
}, |
||||
done: false, |
||||
}, |
||||
], |
||||
}, |
||||
]; |
||||
@ -0,0 +1,26 @@ |
||||
import { IconName } from '@grafana/ui'; |
||||
|
||||
export type CardType = 'tutorial' | 'docs' | 'other'; |
||||
|
||||
export interface Card { |
||||
title: string; |
||||
type: CardType; |
||||
icon: IconName; |
||||
href: string; |
||||
check: () => Promise<boolean>; |
||||
done: boolean; |
||||
heading: string; |
||||
info?: string; |
||||
// For local storage
|
||||
key?: string; |
||||
learnHref?: string; |
||||
} |
||||
|
||||
export interface SetupStep { |
||||
heading: string; |
||||
subheading: string; |
||||
title: string; |
||||
info: string; |
||||
cards: Card[]; |
||||
done: boolean; |
||||
} |
||||
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 44 KiB |
Loading…
Reference in new issue