mirror of
https://github.com/grafana/grafana.git
synced 2024-11-28 03:34:15 -06:00
Datasource Onboarding: Create initial data source onboarding page (#58795)
This commit is contained in:
parent
74167b4d44
commit
955bf55c6a
@ -82,6 +82,7 @@ export interface FeatureToggles {
|
||||
nestedFolders?: boolean;
|
||||
accessTokenExpirationCheck?: boolean;
|
||||
elasticsearchBackendMigration?: boolean;
|
||||
datasourceOnboarding?: boolean;
|
||||
secureSocksDatasourceProxy?: boolean;
|
||||
authnService?: boolean;
|
||||
sessionRemoteCache?: boolean;
|
||||
|
@ -373,6 +373,11 @@ var (
|
||||
Description: "Use Elasticsearch as backend data source",
|
||||
State: FeatureStateAlpha,
|
||||
},
|
||||
{
|
||||
Name: "datasourceOnboarding",
|
||||
Description: "Enable data source onboarding page",
|
||||
State: FeatureStateAlpha,
|
||||
},
|
||||
{
|
||||
Name: "secureSocksDatasourceProxy",
|
||||
Description: "Enable secure socks tunneling for supported core datasources",
|
||||
|
@ -271,6 +271,10 @@ const (
|
||||
// Use Elasticsearch as backend data source
|
||||
FlagElasticsearchBackendMigration = "elasticsearchBackendMigration"
|
||||
|
||||
// FlagDatasourceOnboarding
|
||||
// Enable data source onboarding page
|
||||
FlagDatasourceOnboarding = "datasourceOnboarding"
|
||||
|
||||
// FlagSecureSocksDatasourceProxy
|
||||
// Enable secure socks tunneling for supported core datasources
|
||||
FlagSecureSocksDatasourceProxy = "secureSocksDatasourceProxy"
|
||||
|
@ -0,0 +1,209 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { DataSourcePluginMeta, GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { Icon, useStyles2 } from '@grafana/ui';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { useAddDatasource } from 'app/features/datasources/state';
|
||||
|
||||
const topDatasources = [
|
||||
'prometheus',
|
||||
'mysql',
|
||||
'elasticsearch',
|
||||
'influxdb',
|
||||
'graphite',
|
||||
'stackdriver',
|
||||
'cloudwatch',
|
||||
'grafana-azure-monitor-datasource',
|
||||
];
|
||||
|
||||
export function DatasourceOnboarding({
|
||||
onNewDashboard,
|
||||
loading = false,
|
||||
}: {
|
||||
onNewDashboard?: () => void;
|
||||
loading?: boolean;
|
||||
}) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const { value: datasources, loading: loadingDatasources } = useAsync(async () => {
|
||||
const datasourceMeta: DataSourcePluginMeta[] = await getBackendSrv().get('/api/plugins', {
|
||||
enabled: 1,
|
||||
type: 'datasource',
|
||||
});
|
||||
|
||||
const byId = datasourceMeta.reduce<Record<string, DataSourcePluginMeta>>((prev, cur) => {
|
||||
prev[cur.id] = cur;
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
return topDatasources.map((d) => byId[d]);
|
||||
}, []);
|
||||
|
||||
const onAddDatasource = useAddDatasource();
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page
|
||||
navId="dashboards/browse"
|
||||
pageNav={{ text: t('dashboard', 'New dashboard'), url: '/dashboard/new' }}
|
||||
layout={PageLayoutType.Canvas}
|
||||
>
|
||||
<div className={styles.wrapper}>
|
||||
<h1 className={styles.title}>{t('datasource-onboarding.welcome', 'Welcome to Grafana dashboards!')}</h1>
|
||||
<div className={styles.description}>
|
||||
<h4 className={styles.explanation}>
|
||||
{t('datasource-onboarding.explanation', "To visualize your data, you'll need to connect it first.")}
|
||||
</h4>
|
||||
</div>
|
||||
{contextSrv.hasRole('Admin') ? (
|
||||
<>
|
||||
<h4 className={styles.preferredDataSource}>
|
||||
{t('datasource-onboarding.preferred', 'Connect your preferred data source:')}
|
||||
</h4>
|
||||
{!loadingDatasources && datasources !== undefined && (
|
||||
<ul className={styles.datasources}>
|
||||
{datasources.map((d) => (
|
||||
<li key={d.id}>
|
||||
<button onClick={() => onAddDatasource(d)}>
|
||||
<img
|
||||
src={d.info.logos.small}
|
||||
alt={t('datasource-onboarding.logo', 'Logo for {{datasourceName}} data source', {
|
||||
datasourceName: d.name,
|
||||
})}
|
||||
height="16"
|
||||
width="16"
|
||||
className={styles.logo}
|
||||
/>
|
||||
<span className={styles.datasourceName}>{d.name}</span>
|
||||
<Icon name="arrow-right" size="lg" className={styles.arrowIcon} />
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<a href="/datasources/new" className={styles.viewAll}>
|
||||
<span>{t('datasource-onboarding.viewAll', 'View all')}</span>
|
||||
<Icon name="arrow-right" size="lg" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<h4>
|
||||
{t('datasource-onboarding.contact-admin', 'Please contact your administrator to configure data sources.')}
|
||||
</h4>
|
||||
)}
|
||||
<button onClick={onNewDashboard} className={styles.createNew}>
|
||||
<span>{t('datasource-onboarding.sampleData', 'Or set up a new dashboard with sample data')}</span>
|
||||
<Icon name="arrow-right" size="lg" />
|
||||
</button>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
wrapper: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}),
|
||||
title: css({
|
||||
textAlign: 'center',
|
||||
fontSize: theme.typography.pxToRem(32),
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
}),
|
||||
description: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
maxWidth: '50vw',
|
||||
marginBottom: theme.spacing(8),
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
explanation: css({
|
||||
marginBottom: '0px',
|
||||
textAlign: 'center',
|
||||
fontSize: theme.typography.pxToRem(19),
|
||||
}),
|
||||
preferredDataSource: css({
|
||||
marginBottom: theme.spacing(3),
|
||||
fontSize: theme.typography.pxToRem(19),
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
}),
|
||||
datasources: css({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: `repeat(auto-fit, minmax(${theme.spacing(28)}, 1fr))`,
|
||||
gap: theme.spacing(2),
|
||||
listStyle: 'none',
|
||||
width: '100%',
|
||||
maxWidth: theme.spacing(88),
|
||||
|
||||
'> li': {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
'> button': {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: theme.spacing(7),
|
||||
gap: theme.spacing(2),
|
||||
margin: '0px',
|
||||
padding: `calc(${theme.spacing(2)} - 1px)`,
|
||||
lineHeight: theme.spacing(3),
|
||||
border: `1px solid ${theme.colors.border.weak}`,
|
||||
borderRadius: theme.shape.borderRadius(1),
|
||||
background: theme.colors.background.primary,
|
||||
fontSize: theme.typography.pxToRem(19),
|
||||
color: 'inherit',
|
||||
},
|
||||
},
|
||||
}),
|
||||
logo: css({
|
||||
width: theme.spacing(2),
|
||||
height: theme.spacing(2),
|
||||
objectFit: 'contain',
|
||||
}),
|
||||
datasourceName: css({
|
||||
marginRight: 'auto',
|
||||
}),
|
||||
arrowIcon: css({
|
||||
color: theme.colors.text.link,
|
||||
}),
|
||||
viewAll: css({
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: theme.spacing(2),
|
||||
lineHeight: theme.spacing(3),
|
||||
fontSize: theme.typography.pxToRem(19),
|
||||
color: theme.colors.text.link,
|
||||
textDecoration: 'underline',
|
||||
textUnderlinePosition: 'under',
|
||||
}),
|
||||
createNew: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
margin: '0px',
|
||||
marginTop: theme.spacing(8),
|
||||
padding: theme.spacing(0),
|
||||
border: 'none',
|
||||
background: 'inherit',
|
||||
fontSize: theme.typography.h6.fontSize,
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
};
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useEffectOnce } from 'react-use';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||
import { loadDataSources } from 'app/features/datasources/state';
|
||||
import { useDispatch, useSelector } from 'app/types';
|
||||
|
||||
import DashboardPage from './DashboardPage';
|
||||
import { DatasourceOnboarding } from './DatasourceOnboarding';
|
||||
|
||||
export default function NewDashboardPage(props: GrafanaRouteComponentProps) {
|
||||
const dispatch = useDispatch();
|
||||
useEffectOnce(() => {
|
||||
dispatch(loadDataSources());
|
||||
});
|
||||
|
||||
const { hasDatasource, loading } = useSelector((state) => ({
|
||||
hasDatasource: state.dataSources.dataSourcesCount > 0,
|
||||
loading: !state.dataSources.hasFetched,
|
||||
}));
|
||||
const [createDashboard, setCreateDashboard] = useState(false);
|
||||
const showDashboardPage = hasDatasource || createDashboard || !config.featureToggles.datasourceOnboarding;
|
||||
|
||||
return showDashboardPage ? (
|
||||
<DashboardPage {...props} />
|
||||
) : (
|
||||
<DatasourceOnboarding onNewDashboard={() => setCreateDashboard(true)} loading={loading} />
|
||||
);
|
||||
}
|
@ -88,7 +88,7 @@ export function getAppRoutes(): RouteDescriptor[] {
|
||||
pageClass: 'page-dashboard',
|
||||
routeName: DashboardRoutes.New,
|
||||
component: SafeDynamicImport(
|
||||
() => import(/* webpackChunkName: "DashboardPage" */ '../features/dashboard/containers/DashboardPage')
|
||||
() => import(/* webpackChunkName: "DashboardPage" */ '../features/dashboard/containers/NewDashboardPage')
|
||||
),
|
||||
},
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user