Admin: Add promotional page for Grafana Enterprise (#21422)
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>pull/21509/head
@ -0,0 +1,41 @@ |
|||||||
|
package licensing |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/grafana/grafana/pkg/api/dtos" |
||||||
|
"github.com/grafana/grafana/pkg/services/hooks" |
||||||
|
"github.com/grafana/grafana/pkg/setting" |
||||||
|
) |
||||||
|
|
||||||
|
type OSSLicensingService struct { |
||||||
|
Cfg *setting.Cfg `inject:""` |
||||||
|
HooksService *hooks.HooksService `inject:""` |
||||||
|
} |
||||||
|
|
||||||
|
func (*OSSLicensingService) HasLicense() bool { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func (*OSSLicensingService) Expiry() int64 { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (l *OSSLicensingService) Init() error { |
||||||
|
l.HooksService.AddIndexDataHook(func(indexData *dtos.IndexViewData) { |
||||||
|
for _, node := range indexData.NavTree { |
||||||
|
if node.Id == "admin" { |
||||||
|
node.Children = append(node.Children, &dtos.NavLink{ |
||||||
|
Text: "Upgrade", |
||||||
|
Id: "upgrading", |
||||||
|
Url: l.Cfg.AppSubUrl + "/admin/upgrading", |
||||||
|
Icon: "fa fa-fw fa-unlock-alt", |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (*OSSLicensingService) HasValidLicense() bool { |
||||||
|
return false |
||||||
|
} |
||||||
@ -0,0 +1,105 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { stylesFactory, useTheme } from '@grafana/ui'; |
||||||
|
import { css } from 'emotion'; |
||||||
|
import { GrafanaTheme } from '@grafana/data'; |
||||||
|
|
||||||
|
const title = { fontWeight: 500, fontSize: '26px', lineHeight: '123%' }; |
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme) => { |
||||||
|
const background = theme.colors.panelBg; |
||||||
|
const backgroundUrl = theme.isDark |
||||||
|
? '/public/img/licensing/header_dark.svg' |
||||||
|
: '/public/img/licensing/header_light.svg'; |
||||||
|
const footerBg = theme.isDark ? theme.colors.dark9 : theme.colors.gray6; |
||||||
|
|
||||||
|
return { |
||||||
|
container: css` |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 100%; |
||||||
|
column-gap: 20px; |
||||||
|
row-gap: 40px; |
||||||
|
padding: 42px 20px 0 77px; |
||||||
|
background-color: ${background}; |
||||||
|
@media (min-width: 1050px) { |
||||||
|
grid-template-columns: 50% 50%; |
||||||
|
} |
||||||
|
`,
|
||||||
|
footer: css` |
||||||
|
text-align: center; |
||||||
|
padding: 16px; |
||||||
|
background: ${footerBg}; |
||||||
|
`,
|
||||||
|
header: css` |
||||||
|
height: 137px; |
||||||
|
padding: 40px 0 0 79px; |
||||||
|
position: relative; |
||||||
|
background: url('${backgroundUrl}') right; |
||||||
|
`,
|
||||||
|
}; |
||||||
|
}); |
||||||
|
|
||||||
|
interface Props { |
||||||
|
header: string; |
||||||
|
subheader?: string; |
||||||
|
editionNotice?: string; |
||||||
|
} |
||||||
|
|
||||||
|
export const LicenseChrome: React.FC<Props> = ({ header, editionNotice, subheader, children }) => { |
||||||
|
const theme = useTheme(); |
||||||
|
const styles = getStyles(theme); |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className={styles.header}> |
||||||
|
<h2 style={title}>{header}</h2> |
||||||
|
{subheader && <h3>{subheader}</h3>} |
||||||
|
|
||||||
|
<Circle |
||||||
|
size="128px" |
||||||
|
style={{ |
||||||
|
boxShadow: '0px 0px 24px rgba(24, 58, 110, 0.45)', |
||||||
|
background: '#0A1C36', |
||||||
|
position: 'absolute', |
||||||
|
top: '19px', |
||||||
|
left: '71%', |
||||||
|
}} |
||||||
|
> |
||||||
|
<img |
||||||
|
className="logo-icon" |
||||||
|
src="/public/img/grafana_icon.svg" |
||||||
|
alt="Grafana" |
||||||
|
width="80px" |
||||||
|
style={{ position: 'absolute', left: '23px', top: '20px' }} |
||||||
|
/> |
||||||
|
</Circle> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className={styles.container}>{children}</div> |
||||||
|
|
||||||
|
{editionNotice && <div className={styles.footer}>{editionNotice}</div>} |
||||||
|
</> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
interface CircleProps { |
||||||
|
size: string; |
||||||
|
style?: React.CSSProperties; |
||||||
|
} |
||||||
|
|
||||||
|
export const Circle: React.FC<CircleProps> = ({ size, style, children }) => { |
||||||
|
return ( |
||||||
|
<div |
||||||
|
style={{ |
||||||
|
width: size, |
||||||
|
height: size, |
||||||
|
position: 'absolute', |
||||||
|
bottom: 0, |
||||||
|
right: 0, |
||||||
|
borderRadius: '50%', |
||||||
|
...style, |
||||||
|
}} |
||||||
|
> |
||||||
|
{children} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
@ -0,0 +1,188 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { css } from 'emotion'; |
||||||
|
import { NavModel } from '@grafana/data'; |
||||||
|
import Page from '../../core/components/Page/Page'; |
||||||
|
import { LicenseChrome } from './LicenseChrome'; |
||||||
|
import { Forms } from '@grafana/ui'; |
||||||
|
import { hot } from 'react-hot-loader'; |
||||||
|
import { StoreState } from '../../types'; |
||||||
|
import { getNavModel } from '../../core/selectors/navModel'; |
||||||
|
import { connect } from 'react-redux'; |
||||||
|
|
||||||
|
interface Props { |
||||||
|
navModel: NavModel; |
||||||
|
} |
||||||
|
|
||||||
|
export const UpgradePage: React.FC<Props> = ({ navModel }) => { |
||||||
|
return ( |
||||||
|
<Page navModel={navModel}> |
||||||
|
<Page.Contents> |
||||||
|
<UpgradeInfo |
||||||
|
editionNotice="You are running the open-source version of Grafana. |
||||||
|
You have to install the Enterprise edition in order enable Enterprise features." |
||||||
|
/> |
||||||
|
</Page.Contents> |
||||||
|
</Page> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const titleStyles = { fontWeight: 500, fontSize: '26px', lineHeight: '123%' }; |
||||||
|
|
||||||
|
interface UpgradeInfoProps { |
||||||
|
editionNotice?: string; |
||||||
|
} |
||||||
|
|
||||||
|
export const UpgradeInfo: React.FC<UpgradeInfoProps> = ({ editionNotice }) => { |
||||||
|
return ( |
||||||
|
<LicenseChrome header="Grafana Enterprise" subheader="Get your free trial" editionNotice={editionNotice}> |
||||||
|
<FeatureInfo /> |
||||||
|
<ServiceInfo /> |
||||||
|
</LicenseChrome> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const GetEnterprise: React.FC = () => { |
||||||
|
return ( |
||||||
|
<div style={{ marginTop: '60px', marginBottom: '60px' }}> |
||||||
|
<h2 style={titleStyles}>Get Grafana Enterprise</h2> |
||||||
|
<CallToAction /> |
||||||
|
<p style={{ paddingTop: '12px' }}> |
||||||
|
You can use the trial version for free for <strong>30 days</strong>. We will remind you about it{' '} |
||||||
|
<strong>5 days before the trial period ends</strong>. |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const CallToAction: React.FC = () => { |
||||||
|
return ( |
||||||
|
<Forms.LinkButton |
||||||
|
variant="primary" |
||||||
|
size="lg" |
||||||
|
href="https://grafana.com/contact?about=grafana-enterprise&utm_source=grafana-upgrade-page" |
||||||
|
> |
||||||
|
Contact us and get a free trial |
||||||
|
</Forms.LinkButton> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const ServiceInfo: React.FC = () => { |
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<h4>At your service</h4> |
||||||
|
|
||||||
|
<List> |
||||||
|
<Item title="Premium Plugins" image="/public/img/licensing/plugin_enterprise.svg" /> |
||||||
|
<Item title="Critical SLA: 2 hours" image="/public/img/licensing/sla.svg" /> |
||||||
|
<Item title="Unlimited Expert Support" image="/public/img/licensing/customer_support.svg"> |
||||||
|
24x7x365 support via |
||||||
|
<List nested={true}> |
||||||
|
<Item title="Email" /> |
||||||
|
<Item title="Private slack channel" /> |
||||||
|
<Item title="Phone" /> |
||||||
|
</List> |
||||||
|
</Item> |
||||||
|
<Item title="Hand-in-hand support" image="/public/img/licensing/handinhand_support.svg"> |
||||||
|
in the upgrade process |
||||||
|
</Item> |
||||||
|
</List> |
||||||
|
|
||||||
|
<div style={{ marginTop: '20px' }}> |
||||||
|
<strong>Also included:</strong> |
||||||
|
<br /> |
||||||
|
Indemnification, working with Grafana Labs on future prioritization, and training from the core Grafana team. |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const FeatureInfo: React.FC = () => { |
||||||
|
return ( |
||||||
|
<div style={{ paddingRight: '11px' }}> |
||||||
|
<h4>Enhanced Functionality</h4> |
||||||
|
<FeatureListing /> |
||||||
|
|
||||||
|
<GetEnterprise /> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const FeatureListing: React.FC = () => { |
||||||
|
return ( |
||||||
|
<List> |
||||||
|
<Item title="Data source permissions" /> |
||||||
|
<Item title="Reporting" /> |
||||||
|
<Item title="SAML Authentication" /> |
||||||
|
<Item title="Enhanced LDAP Integration" /> |
||||||
|
<Item title="Team Sync">LDAP, GitHub OAuth, Auth Proxy</Item> |
||||||
|
<Item title="White labeling" /> |
||||||
|
<Item title="Premium Plugins"> |
||||||
|
<List nested={true}> |
||||||
|
<Item title="Oracle" /> |
||||||
|
<Item title="Splunk" /> |
||||||
|
<Item title="Service Now" /> |
||||||
|
<Item title="Dynatrace" /> |
||||||
|
<Item title="DataDog" /> |
||||||
|
<Item title="AppDynamics" /> |
||||||
|
</List> |
||||||
|
</Item> |
||||||
|
</List> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
interface ListProps { |
||||||
|
nested?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
const List: React.FC<ListProps> = ({ children, nested }) => { |
||||||
|
const listStyle = css` |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
padding-top: 8px; |
||||||
|
|
||||||
|
> div { |
||||||
|
margin-bottom: ${nested ? 0 : 8}px; |
||||||
|
} |
||||||
|
`;
|
||||||
|
|
||||||
|
return <div className={listStyle}>{children}</div>; |
||||||
|
}; |
||||||
|
|
||||||
|
interface ItemProps { |
||||||
|
title: string; |
||||||
|
image?: string; |
||||||
|
} |
||||||
|
|
||||||
|
const Item: React.FC<ItemProps> = ({ children, title, image }) => { |
||||||
|
const imageUrl = image ? image : '/public/img/licensing/checkmark.svg'; |
||||||
|
const itemStyle = css` |
||||||
|
display: flex; |
||||||
|
|
||||||
|
> img { |
||||||
|
display: block; |
||||||
|
height: 22px; |
||||||
|
flex-grow: 0; |
||||||
|
padding-right: 12px; |
||||||
|
} |
||||||
|
`;
|
||||||
|
const titleStyle = css` |
||||||
|
font-weight: 500; |
||||||
|
line-height: 1.7; |
||||||
|
`;
|
||||||
|
|
||||||
|
return ( |
||||||
|
<div className={itemStyle}> |
||||||
|
<img src={imageUrl} /> |
||||||
|
<div> |
||||||
|
<div className={titleStyle}>{title}</div> |
||||||
|
{children} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const mapStateToProps = (state: StoreState) => ({ |
||||||
|
navModel: getNavModel(state.navIndex, 'upgrading'), |
||||||
|
}); |
||||||
|
|
||||||
|
export default hot(module)(connect(mapStateToProps)(UpgradePage)); |
||||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |