Theme: Page styles move to emotion global styles and design tweaks (#33529)

* Theme: Page styles move to emotion global styles and design tweaks

* More style tweaks

* tweaks

* Updating snapshots

* Another fix

* Another fix

* minor fix

* More style tweaks to page toolbar and alert rule page

* minor polish
This commit is contained in:
Torkel Ödegaard 2021-04-30 10:04:01 +02:00 committed by GitHub
parent aaca022df6
commit f6ecded86b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 367 additions and 433 deletions

View File

@ -135,7 +135,7 @@ const getStyles = (theme: GrafanaThemeV2) => {
background: ${theme.colors.background.canvas}; background: ${theme.colors.background.canvas};
justify-content: flex-end; justify-content: flex-end;
flex-wrap: wrap; flex-wrap: wrap;
padding: ${theme.spacing(0, 1, 1, 2)}; padding: ${theme.spacing(1, 2, 0, 2)};
`, `,
toolbarLeft: css` toolbarLeft: css`
display: flex; display: flex;

View File

@ -4,10 +4,15 @@ import { useTheme2 } from '..';
import { getElementStyles } from './elements'; import { getElementStyles } from './elements';
import { getCardStyles } from './card'; import { getCardStyles } from './card';
import { getAgularPanelStyles } from './angularPanelStyles'; import { getAgularPanelStyles } from './angularPanelStyles';
import { getPageStyles } from './page';
/** @internal */ /** @internal */
export function GlobalStyles() { export function GlobalStyles() {
const theme = useTheme2(); const theme = useTheme2();
return <Global styles={[getElementStyles(theme), getCardStyles(theme), getAgularPanelStyles(theme)]} />; return (
<Global
styles={[getElementStyles(theme), getPageStyles(theme), getCardStyles(theme), getAgularPanelStyles(theme)]}
/>
);
} }

View File

@ -0,0 +1,114 @@
import { css } from '@emotion/react';
import { GrafanaThemeV2 } from '@grafana/data';
export function getPageStyles(theme: GrafanaThemeV2) {
return css`
.grafana-app {
display: flex;
align-items: stretch;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.main-view {
position: relative;
flex-grow: 1;
}
.page-scrollbar-wrapper {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
.page-scrollbar-content {
display: flex;
min-height: 100%;
flex-direction: column;
width: 100%;
height: 100%;
}
.page-container {
flex-grow: 1;
flex-basis: 100%;
padding-left: ${theme.spacing(2)};
padding-right: ${theme.spacing(2)};
${theme.breakpoints.up('sm')} {
margin: ${theme.spacing(0, 1)};
}
${theme.breakpoints.up('md')} {
margin: ${theme.spacing(0, 2)};
}
@media (min-width: ${theme.breakpoints.values.xxl + theme.spacing.gridSize * 2}px) {
max-width: ${theme.breakpoints.values.xxl}px;
margin-left: auto;
margin-right: auto;
width: 100%;
}
}
.page-full {
margin-left: ${theme.spacing(2)};
padding-left: ${theme.spacing(2)};
padding-right: ${theme.spacing(2)};
}
.page-body {
padding: ${theme.spacing(1)};
background: ${theme.components.panel.background};
border: 1px solid ${theme.components.panel.borderColor};
margin-bottom: 32px;
${theme.breakpoints.up('md')} {
padding: ${theme.spacing(2)};
}
${theme.breakpoints.up('lg')} {
padding: ${theme.spacing(3)};
}
}
.page-heading {
font-size: ${theme.typography.h4.fontSize};
margin-top: 0;
margin-bottom: ${theme.spacing(2)};
}
.page-action-bar {
margin-bottom: ${theme.spacing(2)};
display: flex;
align-items: flex-start;
> a,
> button {
margin-left: ${theme.spacing(2)};
}
}
.page-action-bar--narrow {
margin-bottom: 0;
}
.page-action-bar__spacer {
width: ${theme.spacing(2)};
flex-grow: 1;
}
.page-sub-heading {
margin-bottom: ${theme.spacing(2)};
}
.page-sub-heading-icon {
margin-left: ${theme.spacing(1)};
margin-top: ${theme.spacing(0.5)};
}
`;
}

View File

@ -372,7 +372,7 @@ $panel-editor-viz-item-shadow: 0 0 4px $gray-3;
$panel-editor-viz-item-border: 1px solid $gray-3; $panel-editor-viz-item-border: 1px solid $gray-3;
$panel-editor-viz-item-shadow-hover: 0 0 4px $blue-light; $panel-editor-viz-item-shadow-hover: 0 0 4px $blue-light;
$panel-editor-viz-item-border-hover: 1px solid $blue-light; $panel-editor-viz-item-border-hover: 1px solid $blue-light;
$panel-editor-viz-item-bg: $white; $panel-editor-viz-item-bg: $card-background;
$panel-editor-tabs-line-color: $dark-2; $panel-editor-tabs-line-color: $dark-2;
$panel-editor-viz-item-bg-hover: lighten($blue-base, 45%); $panel-editor-viz-item-bg-hover: lighten($blue-base, 45%);

View File

@ -23,7 +23,7 @@ export const FilterInput: FC<Props> = ({ value, placeholder, width, onChange, au
autoFocus={autoFocus ?? false} autoFocus={autoFocus ?? false}
prefix={<Icon name="search" />} prefix={<Icon name="search" />}
suffix={suffix} suffix={suffix}
width={width ?? 40} width={width}
type="text" type="text"
value={value ? unEscapeStringFromRegex(value) : ''} value={value ? unEscapeStringFromRegex(value) : ''}
onChange={(event) => onChange(escapeStringForRegex(event.currentTarget.value))} onChange={(event) => onChange(escapeStringForRegex(event.currentTarget.value))}

View File

@ -7,14 +7,13 @@ import PageHeader from '../PageHeader/PageHeader';
import { Footer } from '../Footer/Footer'; import { Footer } from '../Footer/Footer';
import { PageContents } from './PageContents'; import { PageContents } from './PageContents';
import { CustomScrollbar, useStyles2 } from '@grafana/ui'; import { CustomScrollbar, useStyles2 } from '@grafana/ui';
import { GrafanaThemeV2, NavModel, ThemeBreakpointsKey } from '@grafana/data'; import { GrafanaThemeV2, NavModel } from '@grafana/data';
import { Branding } from '../Branding/Branding'; import { Branding } from '../Branding/Branding';
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
interface Props extends HTMLAttributes<HTMLDivElement> { interface Props extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode; children: React.ReactNode;
navModel: NavModel; navModel: NavModel;
contentWidth?: ThemeBreakpointsKey;
} }
export interface PageType extends FC<Props> { export interface PageType extends FC<Props> {
@ -22,7 +21,7 @@ export interface PageType extends FC<Props> {
Contents: typeof PageContents; Contents: typeof PageContents;
} }
export const Page: PageType = ({ navModel, children, className, contentWidth, ...otherProps }) => { export const Page: PageType = ({ navModel, children, className, ...otherProps }) => {
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
useEffect(() => { useEffect(() => {
@ -31,10 +30,7 @@ export const Page: PageType = ({ navModel, children, className, contentWidth, ..
}, [navModel]); }, [navModel]);
return ( return (
<div <div {...otherProps} className={cx(styles.wrapper, className)}>
{...otherProps}
className={cx(styles.wrapper, className, contentWidth ? styles.contentWidth(contentWidth) : undefined)}
>
<CustomScrollbar autoHeightMin={'100%'}> <CustomScrollbar autoHeightMin={'100%'}>
<div className="page-scrollbar-content"> <div className="page-scrollbar-content">
<PageHeader model={navModel} /> <PageHeader model={navModel} />
@ -53,15 +49,9 @@ export default Page;
const getStyles = (theme: GrafanaThemeV2) => ({ const getStyles = (theme: GrafanaThemeV2) => ({
wrapper: css` wrapper: css`
background: ${theme.colors.background.primary};
bottom: 0; bottom: 0;
position: absolute; position: absolute;
top: 0; top: 0;
width: 100%; width: 100%;
`, `,
contentWidth: (size: ThemeBreakpointsKey) => css`
.page-container {
max-width: ${theme.breakpoints.values[size]}px;
}
`,
}); });

View File

@ -24,7 +24,6 @@ export default class PageActionBar extends PureComponent<Props> {
<div className="gf-form gf-form--grow"> <div className="gf-form gf-form--grow">
<FilterInput value={searchQuery} onChange={setSearchQuery} placeholder={placeholder} /> <FilterInput value={searchQuery} onChange={setSearchQuery} placeholder={placeholder} />
</div> </div>
<div className="page-action-bar__spacer" />
{linkButton && <LinkButton {...linkProps}>{linkButton.title}</LinkButton>} {linkButton && <LinkButton {...linkProps}>{linkButton.title}</LinkButton>}
</div> </div>
); );

View File

@ -13,9 +13,6 @@ exports[`Render should render component 1`] = `
value="" value=""
/> />
</div> </div>
<div
className="page-action-bar__spacer"
/>
<LinkButton <LinkButton
href="some/url" href="some/url"
target="_blank" target="_blank"

View File

@ -140,7 +140,6 @@ function renderTitle(title: string, breadcrumbs: NavModelBreadcrumb[]) {
const getStyles = (theme: GrafanaThemeV2) => ({ const getStyles = (theme: GrafanaThemeV2) => ({
headerCanvas: css` headerCanvas: css`
background: ${theme.colors.background.canvas}; background: ${theme.colors.background.canvas};
border-bottom: 1px solid ${theme.colors.border.weak};
`, `,
}); });

View File

@ -3,13 +3,14 @@ import { css, cx } from '@emotion/css';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
import { NavModel } from '@grafana/data'; import { NavModel } from '@grafana/data';
import { Pagination, Tooltip, HorizontalGroup, stylesFactory, LinkButton, Input, Icon } from '@grafana/ui'; import { Pagination, Tooltip, stylesFactory, LinkButton, Icon } from '@grafana/ui';
import { AccessControlAction, StoreState, UserDTO } from '../../types'; import { AccessControlAction, StoreState, UserDTO } from '../../types';
import Page from 'app/core/components/Page/Page'; import Page from 'app/core/components/Page/Page';
import { getNavModel } from '../../core/selectors/navModel'; import { getNavModel } from '../../core/selectors/navModel';
import { fetchUsers, changeQuery, changePage } from './state/actions'; import { fetchUsers, changeQuery, changePage } from './state/actions';
import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
import { contextSrv } from 'app/core/core'; import { contextSrv } from 'app/core/core';
import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
interface OwnProps {} interface OwnProps {}
@ -42,27 +43,21 @@ const UserListAdminPageUnConnected: React.FC<Props> = (props) => {
<Page navModel={navModel}> <Page navModel={navModel}>
<Page.Contents> <Page.Contents>
<> <>
<div> <div className="page-action-bar">
<HorizontalGroup justify="space-between"> <div className="gf-form gf-form--grow">
<Input <FilterInput
width={40}
type="text"
placeholder="Search user by login, email, or name." placeholder="Search user by login, email, or name."
tabIndex={1}
autoFocus={true} autoFocus={true}
value={query} value={query}
spellCheck={false} onChange={(value) => changeQuery(value)}
onChange={(event) => changeQuery(event.currentTarget.value)}
prefix={<Icon name="search" />}
/> />
{contextSrv.hasPermission(AccessControlAction.UsersCreate) && ( </div>
<LinkButton href="admin/users/create" variant="primary"> {contextSrv.hasPermission(AccessControlAction.UsersCreate) && (
New user <LinkButton href="admin/users/create" variant="primary">
</LinkButton> New user
)} </LinkButton>
</HorizontalGroup> )}
</div> </div>
<div className={cx(styles.table, 'admin-list-table')}> <div className={cx(styles.table, 'admin-list-table')}>
<table className="filter-table form-inline filter-table--hover"> <table className="filter-table form-inline filter-table--hover">
<thead> <thead>

View File

@ -16,7 +16,7 @@ export const AlertingPageWrapper: FC<Props> = ({ children, pageId, isLoading })
); );
return ( return (
<Page navModel={navModel} contentWidth="xxl"> <Page navModel={navModel}>
<Page.Contents isLoading={isLoading}>{children}</Page.Contents> <Page.Contents isLoading={isLoading}>{children}</Page.Contents>
</Page> </Page>
); );

View File

@ -1,6 +1,6 @@
import React, { FC, useMemo } from 'react'; import React, { FC, useMemo } from 'react';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaThemeV2 } from '@grafana/data';
import { PageToolbar, ToolbarButton, useStyles, CustomScrollbar, Spinner, Alert } from '@grafana/ui'; import { PageToolbar, Button, useStyles2, CustomScrollbar, Spinner, Alert } from '@grafana/ui';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { AlertTypeStep } from './AlertTypeStep'; import { AlertTypeStep } from './AlertTypeStep';
@ -24,7 +24,7 @@ type Props = {
}; };
export const AlertRuleForm: FC<Props> = ({ existing }) => { export const AlertRuleForm: FC<Props> = ({ existing }) => {
const styles = useStyles(getStyles); const styles = useStyles2(getStyles);
const dispatch = useDispatch(); const dispatch = useDispatch();
const defaultValues: RuleFormValues = useMemo(() => { const defaultValues: RuleFormValues = useMemo(() => {
@ -73,22 +73,22 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
return ( return (
<FormProvider {...formAPI}> <FormProvider {...formAPI}>
<form onSubmit={handleSubmit((values) => submit(values, false))} className={styles.form}> <form onSubmit={handleSubmit((values) => submit(values, false))} className={styles.form}>
<PageToolbar title="Create alert rule" pageIcon="bell" className={styles.toolbar}> <PageToolbar title="Create alert rule" pageIcon="bell">
<Link to="/alerting/list"> <Link to="/alerting/list">
<ToolbarButton variant="default" disabled={submitState.loading} type="button"> <Button variant="secondary" disabled={submitState.loading} type="button" fill="outline">
Cancel Cancel
</ToolbarButton> </Button>
</Link> </Link>
<ToolbarButton <Button
variant="primary" variant="secondary"
type="button" type="button"
onClick={handleSubmit((values) => submit(values, false))} onClick={handleSubmit((values) => submit(values, false))}
disabled={submitState.loading} disabled={submitState.loading}
> >
{submitState.loading && <Spinner className={styles.buttonSpinner} inline={true} />} {submitState.loading && <Spinner className={styles.buttonSpinner} inline={true} />}
Save Save
</ToolbarButton> </Button>
<ToolbarButton <Button
variant="primary" variant="primary"
type="button" type="button"
onClick={handleSubmit((values) => submit(values, true))} onClick={handleSubmit((values) => submit(values, true))}
@ -96,7 +96,7 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
> >
{submitState.loading && <Spinner className={styles.buttonSpinner} inline={true} />} {submitState.loading && <Spinner className={styles.buttonSpinner} inline={true} />}
Save and exit Save and exit
</ToolbarButton> </Button>
</PageToolbar> </PageToolbar>
<div className={styles.contentOuter}> <div className={styles.contentOuter}>
<CustomScrollbar autoHeightMin="100%" hideHorizontalTrack={true}> <CustomScrollbar autoHeightMin="100%" hideHorizontalTrack={true}>
@ -128,15 +128,10 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
); );
}; };
const getStyles = (theme: GrafanaTheme) => { const getStyles = (theme: GrafanaThemeV2) => {
return { return {
buttonSpinner: css` buttonSpinner: css`
margin-right: ${theme.spacing.sm}; margin-right: ${theme.spacing(1)};
`,
toolbar: css`
padding-top: ${theme.spacing.sm};
padding-bottom: ${theme.spacing.md};
border-bottom: solid 1px ${theme.colors.border2};
`, `,
form: css` form: css`
width: 100%; width: 100%;
@ -146,19 +141,16 @@ const getStyles = (theme: GrafanaTheme) => {
`, `,
contentInner: css` contentInner: css`
flex: 1; flex: 1;
padding: ${theme.spacing.md}; padding: ${theme.spacing(2)};
`, `,
contentOuter: css` contentOuter: css`
background: ${theme.colors.panelBg}; background: ${theme.colors.background.primary};
border: 1px solid ${theme.colors.border.weak};
border-radius: ${theme.shape.borderRadius()};
margin: ${theme.spacing(2)};
overflow: hidden; overflow: hidden;
flex: 1; flex: 1;
`, `,
formInput: css`
width: 400px;
& + & {
margin-left: ${theme.spacing.sm};
}
`,
flexRow: css` flexRow: css`
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -15,8 +15,6 @@ export const ApiKeysActionBar: FC<Props> = ({ searchQuery, disabled, onAddClick,
<div className="gf-form gf-form--grow"> <div className="gf-form gf-form--grow">
<FilterInput placeholder="Search keys" value={searchQuery} onChange={onSearchChange} /> <FilterInput placeholder="Search keys" value={searchQuery} onChange={onSearchChange} />
</div> </div>
<div className="page-action-bar__spacer" />
<Button className="pull-right" onClick={onAddClick} disabled={disabled}> <Button className="pull-right" onClick={onAddClick} disabled={disabled}>
Add API key Add API key
</Button> </Button>

View File

@ -11,7 +11,7 @@ import { ApiKeysAddedModal } from './ApiKeysAddedModal';
import config from 'app/core/config'; import config from 'app/core/config';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { InlineField, InlineSwitch } from '@grafana/ui'; import { InlineField, InlineSwitch, VerticalGroup } from '@grafana/ui';
import { rangeUtil } from '@grafana/data'; import { rangeUtil } from '@grafana/data';
import { getTimeZone } from 'app/features/profile/state/selectors'; import { getTimeZone } from 'app/features/profile/state/selectors';
import { setSearchQuery } from './state/reducers'; import { setSearchQuery } from './state/reducers';
@ -150,13 +150,12 @@ export class ApiKeysPageUnconnected extends PureComponent<Props, State> {
) : null} ) : null}
<ApiKeysForm show={isAdding} onClose={toggleIsAdding} onKeyAdded={this.onAddApiKey} /> <ApiKeysForm show={isAdding} onClose={toggleIsAdding} onKeyAdded={this.onAddApiKey} />
{showTable ? ( {showTable ? (
<> <VerticalGroup>
<h3 className="page-heading">Existing API keys</h3>
<InlineField label="Show expired"> <InlineField label="Show expired">
<InlineSwitch id="showExpired" value={includeExpired} onChange={this.onIncludeExpiredChange} /> <InlineSwitch id="showExpired" value={includeExpired} onChange={this.onIncludeExpiredChange} />
</InlineField> </InlineField>
<ApiKeysTable apiKeys={apiKeys} timeZone={timeZone} onDelete={this.onDeleteApiKey} /> <ApiKeysTable apiKeys={apiKeys} timeZone={timeZone} onDelete={this.onDeleteApiKey} />
</> </VerticalGroup>
) : null} ) : null}
</> </>
); );

View File

@ -74,7 +74,7 @@ const getStyles = (theme: GrafanaTheme) => {
min-height: 0; min-height: 0;
`, `,
vizButtonWrapper: css` vizButtonWrapper: css`
padding: 0 ${theme.spacing.sm} ${theme.spacing.md} 0; padding: 0 ${theme.spacing.md} ${theme.spacing.md} 0;
`, `,
legacyOptions: css` legacyOptions: css`
label: legacy-options; label: legacy-options;

View File

@ -40,7 +40,7 @@ export const SnapshotListTable: FC = () => {
); );
return ( return (
<div className="page-container page-body"> <div>
<table className="filter-table"> <table className="filter-table">
<thead> <thead>
<tr> <tr>

View File

@ -38,7 +38,7 @@ export class OrgDetailsPage extends PureComponent<Props> {
<Page navModel={navModel}> <Page navModel={navModel}>
<Page.Contents isLoading={isLoading}> <Page.Contents isLoading={isLoading}>
{!isLoading && ( {!isLoading && (
<VerticalGroup> <VerticalGroup spacing="lg">
<OrgProfile onSubmit={this.onUpdateOrganization} orgName={organization.name} /> <OrgProfile onSubmit={this.onUpdateOrganization} orgName={organization.name} />
<SharedPreferences resourceUri="org" /> <SharedPreferences resourceUri="org" />
</VerticalGroup> </VerticalGroup>

View File

@ -35,7 +35,9 @@ exports[`Render should render organization and preferences 1`] = `
<PageContents <PageContents
isLoading={false} isLoading={false}
> >
<VerticalGroup> <VerticalGroup
spacing="lg"
>
<OrgProfile <OrgProfile
onSubmit={[Function]} onSubmit={[Function]}
orgName="Cool org" orgName="Cool org"

View File

@ -17,7 +17,7 @@ import {
UrlQueryMap, UrlQueryMap,
} from '@grafana/data'; } from '@grafana/data';
import { AppNotificationSeverity } from 'app/types'; import { AppNotificationSeverity } from 'app/types';
import { Alert, InfoBox, LinkButton, PluginSignatureBadge, Tooltip } from '@grafana/ui'; import { Alert, LinkButton, PluginSignatureBadge, Tooltip } from '@grafana/ui';
import Page from 'app/core/components/Page/Page'; import Page from 'app/core/components/Page/Page';
import { getPluginSettings } from './PluginSettingsCache'; import { getPluginSettings } from './PluginSettingsCache';
@ -170,9 +170,6 @@ class PluginPage extends PureComponent<Props, State> {
<LinkButton fill="text" onClick={this.showUpdateInfo}> <LinkButton fill="text" onClick={this.showUpdateInfo}>
Update Available! Update Available!
</LinkButton> </LinkButton>
{/*<a href="#" onClick={this.showUpdateInfo}>*/}
{/* Update Available!*/}
{/*</a>*/}
</Tooltip> </Tooltip>
</div> </div>
)} )}
@ -283,11 +280,10 @@ class PluginPage extends PureComponent<Props, State> {
} }
return ( return (
<InfoBox <Alert
aria-label={selectors.pages.PluginPage.signatureInfo} aria-label={selectors.pages.PluginPage.signatureInfo}
severity={plugin.meta.signature !== PluginSignatureStatus.valid ? 'warning' : 'info'} severity={plugin.meta.signature !== PluginSignatureStatus.valid ? 'warning' : 'info'}
urlTitle="Read more about plugins signing" title="Plugin signature"
url="https://grafana.com/docs/grafana/latest/plugins/plugin-signatures/"
> >
<PluginSignatureBadge <PluginSignatureBadge
status={plugin.meta.signature} status={plugin.meta.signature}
@ -303,7 +299,15 @@ class PluginPage extends PureComponent<Props, State> {
{plugin.meta.signature !== PluginSignatureStatus.valid && {plugin.meta.signature !== PluginSignatureStatus.valid &&
'Grafana Labs cant guarantee the integrity of this unsigned plugin. Ask the plugin author to request it to be signed.'} 'Grafana Labs cant guarantee the integrity of this unsigned plugin. Ask the plugin author to request it to be signed.'}
</p> </p>
</InfoBox> <a
href="https://grafana.com/docs/grafana/latest/plugins/plugin-signatures/"
className="external-link"
target="_blank"
rel="noreferrer"
>
Read more about plugins signing.
</a>
</Alert>
); );
} }

View File

@ -62,7 +62,7 @@ export const ChangePasswordForm: FC<Props> = ({ user, onChangePassword, isSaving
<Button variant="primary" disabled={isSaving}> <Button variant="primary" disabled={isSaving}>
Change Password Change Password
</Button> </Button>
<LinkButton variant="secondary" href={`${config.appSubUrl}/profile`}> <LinkButton variant="secondary" href={`${config.appSubUrl}/profile`} fill="outline">
Cancel Cancel
</LinkButton> </LinkButton>
</HorizontalGroup> </HorizontalGroup>

View File

@ -27,7 +27,7 @@ export const ChangePasswordPage: FC<Props> = ({ navModel }) => (
) => { ) => {
return ( return (
<Page.Contents> <Page.Contents>
<h3 className="page-sub-heading">Change Your Password</h3> <h3 className="page-heading">Change Your Password</h3>
{states.loadUser ? ( {states.loadUser ? (
<LoadingPlaceholder text="Loading user profile..." /> <LoadingPlaceholder text="Loading user profile..." />
) : ( ) : (

View File

@ -22,52 +22,52 @@ export class UserOrganizations extends PureComponent<Props> {
return <LoadingPlaceholder text="Loading organizations..." />; return <LoadingPlaceholder text="Loading organizations..." />;
} }
if (orgs.length === 0) {
return null;
}
return ( return (
<> <div>
{orgs.length > 0 && ( <h3 className="page-sub-heading">Organizations</h3>
<> <div className="gf-form-group">
<h3 className="page-sub-heading">Organizations</h3> <table className="filter-table form-inline">
<div className="gf-form-group"> <thead>
<table className="filter-table form-inline"> <tr>
<thead> <th>Name</th>
<tr> <th>Role</th>
<th>Name</th> <th />
<th>Role</th> </tr>
<th /> </thead>
<tbody>
{orgs.map((org: UserOrg, index) => {
return (
<tr key={index}>
<td>{org.name}</td>
<td>{org.role}</td>
<td className="text-right">
{org.orgId === user.orgId ? (
<Button variant="secondary" size="sm" disabled>
Current
</Button>
) : (
<Button
variant="secondary"
size="sm"
onClick={() => {
this.props.setUserOrg(org);
}}
>
Select
</Button>
)}
</td>
</tr> </tr>
</thead> );
<tbody> })}
{orgs.map((org: UserOrg, index) => { </tbody>
return ( </table>
<tr key={index}> </div>
<td>{org.name}</td> </div>
<td>{org.role}</td>
<td className="text-right">
{org.orgId === user.orgId ? (
<Button variant="secondary" size="sm" disabled>
Current
</Button>
) : (
<Button
variant="secondary"
size="sm"
onClick={() => {
this.props.setUserOrg(org);
}}
>
Select
</Button>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</>
)}
</>
); );
} }
} }

View File

@ -1,7 +1,7 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { LoadingPlaceholder } from '@grafana/ui'; import { LoadingPlaceholder, VerticalGroup } from '@grafana/ui';
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { NavModel } from '@grafana/data'; import { NavModel } from '@grafana/data';
import { UserProvider, UserAPI, LoadingStates } from 'app/core/utils/UserProvider'; import { UserProvider, UserAPI, LoadingStates } from 'app/core/utils/UserProvider';
@ -34,16 +34,15 @@ export const UserProfileEdit: FC<Props> = ({ navModel }) => (
{states.loadUser ? ( {states.loadUser ? (
<LoadingPlaceholder text="Loading user profile..." /> <LoadingPlaceholder text="Loading user profile..." />
) : ( ) : (
<UserProfileEditForm <VerticalGroup spacing="md">
updateProfile={api.updateUserProfile} <UserProfileEditForm
isSavingUser={states.updateUserProfile} updateProfile={api.updateUserProfile}
user={user!} isSavingUser={states.updateUserProfile}
/> user={user!}
)} />
<SharedPreferences resourceUri="user" />
<UserTeams isLoading={states.loadTeams} loadTeams={api.loadTeams} teams={teams} /> <SharedPreferences resourceUri="user" />
{!states.loadUser && ( <UserTeams isLoading={states.loadTeams} loadTeams={api.loadTeams} teams={teams} />
<>
<UserOrganizations <UserOrganizations
isLoading={states.loadOrgs} isLoading={states.loadOrgs}
setUserOrg={api.setUserOrg} setUserOrg={api.setUserOrg}
@ -58,7 +57,7 @@ export const UserProfileEdit: FC<Props> = ({ navModel }) => (
sessions={sessions} sessions={sessions}
user={user!} user={user!}
/> />
</> </VerticalGroup>
)} )}
</Page.Contents> </Page.Contents>
); );

View File

@ -23,7 +23,7 @@ export class UserSessions extends PureComponent<Props> {
} }
return ( return (
<> <div>
{sessions.length > 0 && ( {sessions.length > 0 && (
<> <>
<h3 className="page-sub-heading">Sessions</h3> <h3 className="page-sub-heading">Sessions</h3>
@ -59,7 +59,7 @@ export class UserSessions extends PureComponent<Props> {
</div> </div>
</> </>
)} )}
</> </div>
); );
} }
} }

View File

@ -20,40 +20,40 @@ export class UserTeams extends PureComponent<Props> {
return <LoadingPlaceholder text="Loading teams..." />; return <LoadingPlaceholder text="Loading teams..." />;
} }
if (teams.length === 0) {
return null;
}
return ( return (
<> <div>
{teams.length > 0 && ( <h3 className="page-sub-heading">Teams</h3>
<> <div className="gf-form-group">
<h3 className="page-sub-heading">Teams</h3> <table className="filter-table form-inline">
<div className="gf-form-group"> <thead>
<table className="filter-table form-inline"> <tr>
<thead> <th />
<tr> <th>Name</th>
<th /> <th>Email</th>
<th>Name</th> <th>Members</th>
<th>Email</th> </tr>
<th>Members</th> </thead>
<tbody>
{teams.map((team: Team, index) => {
return (
<tr key={index}>
<td className="width-4 text-center">
<img className="filter-table__avatar" src={team.avatarUrl} />
</td>
<td>{team.name}</td>
<td>{team.email}</td>
<td>{team.memberCount}</td>
</tr> </tr>
</thead> );
<tbody> })}
{teams.map((team: Team, index) => { </tbody>
return ( </table>
<tr key={index}> </div>
<td className="width-4 text-center"> </div>
<img className="filter-table__avatar" src={team.avatarUrl} />
</td>
<td>{team.name}</td>
<td>{team.email}</td>
<td>{team.memberCount}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</>
)}
</>
); );
} }
} }

View File

@ -19,10 +19,12 @@ export const DashboardActions: FC<Props> = ({ folderId, isEditor, canEdit }) =>
}; };
return ( return (
<HorizontalGroup spacing="md" align="center"> <div>
{canEdit && <LinkButton href={actionUrl('new')}>New Dashboard</LinkButton>} <HorizontalGroup spacing="md" align="center">
{!folderId && isEditor && <LinkButton href="dashboards/folder/new">New Folder</LinkButton>} {canEdit && <LinkButton href={actionUrl('new')}>New Dashboard</LinkButton>}
{canEdit && <LinkButton href={actionUrl('import')}>Import</LinkButton>} {!folderId && isEditor && <LinkButton href="dashboards/folder/new">New Folder</LinkButton>}
</HorizontalGroup> {canEdit && <LinkButton href={actionUrl('import')}>Import</LinkButton>}
</HorizontalGroup>
</div>
); );
}; };

View File

@ -1,6 +1,6 @@
import React, { FC, memo, useState } from 'react'; import React, { FC, memo, useState } from 'react';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { HorizontalGroup, stylesFactory, useTheme, Spinner } from '@grafana/ui'; import { stylesFactory, useTheme, Spinner } from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
import { contextSrv } from 'app/core/services/context_srv'; import { contextSrv } from 'app/core/services/context_srv';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
@ -93,11 +93,11 @@ export const ManageDashboards: FC<Props> = memo(({ folder }) => {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div> <div className="page-action-bar">
<HorizontalGroup justify="space-between"> <div className="gf-form gf-form--grow m-r-2">
<FilterInput value={query.query} onChange={onQueryChange} placeholder={'Search dashboards by name'} /> <FilterInput value={query.query} onChange={onQueryChange} placeholder={'Search dashboards by name'} />
<DashboardActions isEditor={isEditor} canEdit={hasEditPermissionInFolders || canSave} folderId={folderId} /> </div>
</HorizontalGroup> <DashboardActions isEditor={isEditor} canEdit={hasEditPermissionInFolders || canSave} folderId={folderId} />
</div> </div>
<div className={styles.results}> <div className={styles.results}>

View File

@ -101,8 +101,6 @@ export class TeamList extends PureComponent<Props, any> {
<FilterInput placeholder="Search teams" value={searchQuery} onChange={this.onSearchQueryChange} /> <FilterInput placeholder="Search teams" value={searchQuery} onChange={this.onSearchQueryChange} />
</div> </div>
<div className="page-action-bar__spacer" />
<LinkButton className={disabledClass} href={newTeamHref}> <LinkButton className={disabledClass} href={newTeamHref}>
New Team New Team
</LinkButton> </LinkButton>

View File

@ -78,8 +78,6 @@ export class TeamMembers extends PureComponent<Props, State> {
<div className="gf-form gf-form--grow"> <div className="gf-form gf-form--grow">
<FilterInput placeholder="Search members" value={searchMemberQuery} onChange={this.onSearchQueryChange} /> <FilterInput placeholder="Search members" value={searchMemberQuery} onChange={this.onSearchQueryChange} />
</div> </div>
<div className="page-action-bar__spacer" />
<Button className="pull-right" onClick={this.onToggleAdding} disabled={isAdding || !isTeamAdmin}> <Button className="pull-right" onClick={this.onToggleAdding} disabled={isAdding || !isTeamAdmin}>
Add member Add member
</Button> </Button>

View File

@ -47,9 +47,6 @@ exports[`Render should render teams table 1`] = `
value="" value=""
/> />
</div> </div>
<div
className="page-action-bar__spacer"
/>
<LinkButton <LinkButton
className="" className=""
href="org/teams/new" href="org/teams/new"
@ -380,9 +377,6 @@ exports[`Render when feature toggle editorsCanAdmin is turned on and signedin us
value="" value=""
/> />
</div> </div>
<div
className="page-action-bar__spacer"
/>
<LinkButton <LinkButton
className=" disabled" className=" disabled"
href="#" href="#"
@ -505,9 +499,6 @@ exports[`Render when feature toggle editorsCanAdmin is turned on and signedin us
value="" value=""
/> />
</div> </div>
<div
className="page-action-bar__spacer"
/>
<LinkButton <LinkButton
className="" className=""
href="org/teams/new" href="org/teams/new"

View File

@ -14,9 +14,6 @@ exports[`Render should render component 1`] = `
value="" value=""
/> />
</div> </div>
<div
className="page-action-bar__spacer"
/>
<Button <Button
className="pull-right" className="pull-right"
disabled={false} disabled={false}
@ -101,9 +98,6 @@ exports[`Render should render team members 1`] = `
value="" value=""
/> />
</div> </div>
<div
className="page-action-bar__spacer"
/>
<Button <Button
className="pull-right" className="pull-right"
disabled={false} disabled={false}

View File

@ -44,19 +44,18 @@ export class UsersActionBar extends PureComponent<Props> {
onChange={setUsersSearchQuery} onChange={setUsersSearchQuery}
placeholder="Search user by login, email or name" placeholder="Search user by login, email or name"
/> />
{pendingInvitesCount > 0 && (
<div style={{ marginLeft: '1rem' }}>
<RadioButtonGroup value={showInvites ? 'invites' : 'users'} options={options} onChange={onShowInvites} />
</div>
)}
<div className="page-action-bar__spacer" />
{canInvite && canAddToOrg && <LinkButton href="org/users/invite">Invite</LinkButton>}
{externalUserMngLinkUrl && (
<LinkButton href={externalUserMngLinkUrl} target="_blank" rel="noopener">
{externalUserMngLinkName}
</LinkButton>
)}
</div> </div>
{pendingInvitesCount > 0 && (
<div style={{ marginLeft: '1rem' }}>
<RadioButtonGroup value={showInvites ? 'invites' : 'users'} options={options} onChange={onShowInvites} />
</div>
)}
{canInvite && canAddToOrg && <LinkButton href="org/users/invite">Invite</LinkButton>}
{externalUserMngLinkUrl && (
<LinkButton href={externalUserMngLinkUrl} target="_blank" rel="noopener">
{externalUserMngLinkName}
</LinkButton>
)}
</div> </div>
); );
} }

View File

@ -12,9 +12,6 @@ exports[`Render should render component 1`] = `
placeholder="Search user by login, email or name" placeholder="Search user by login, email or name"
value="" value=""
/> />
<div
className="page-action-bar__spacer"
/>
</div> </div>
</div> </div>
`; `;
@ -31,32 +28,29 @@ exports[`Render should render pending invites button 1`] = `
placeholder="Search user by login, email or name" placeholder="Search user by login, email or name"
value="" value=""
/> />
<div </div>
style={ <div
Object { style={
"marginLeft": "1rem", Object {
} "marginLeft": "1rem",
} }
> }
<RadioButtonGroup >
onChange={[MockFunction]} <RadioButtonGroup
options={ onChange={[MockFunction]}
Array [ options={
Object { Array [
"label": "Users", Object {
"value": "users", "label": "Users",
}, "value": "users",
Object { },
"label": "Pending Invites (5)", Object {
"value": "invites", "label": "Pending Invites (5)",
}, "value": "invites",
] },
} ]
value="users" }
/> value="users"
</div>
<div
className="page-action-bar__spacer"
/> />
</div> </div>
</div> </div>
@ -74,15 +68,12 @@ exports[`Render should show external user management button 1`] = `
placeholder="Search user by login, email or name" placeholder="Search user by login, email or name"
value="" value=""
/> />
<div
className="page-action-bar__spacer"
/>
<LinkButton
href="some/url"
rel="noopener"
target="_blank"
/>
</div> </div>
<LinkButton
href="some/url"
rel="noopener"
target="_blank"
/>
</div> </div>
`; `;
@ -98,14 +89,11 @@ exports[`Render should show invite button 1`] = `
placeholder="Search user by login, email or name" placeholder="Search user by login, email or name"
value="" value=""
/> />
<div
className="page-action-bar__spacer"
/>
<LinkButton
href="org/users/invite"
>
Invite
</LinkButton>
</div> </div>
<LinkButton
href="org/users/invite"
>
Invite
</LinkButton>
</div> </div>
`; `;

View File

@ -29,7 +29,6 @@
// LAYOUTS // LAYOUTS
@import 'layout/lists'; @import 'layout/lists';
@import 'layout/page';
// COMPONENTS // COMPONENTS
@import '../app/features/dashboard/components/AddPanelWidget/AddPanelWidget'; @import '../app/features/dashboard/components/AddPanelWidget/AddPanelWidget';

View File

@ -374,7 +374,7 @@ $panel-editor-viz-item-shadow: 0 0 4px $gray-3;
$panel-editor-viz-item-border: 1px solid $gray-3; $panel-editor-viz-item-border: 1px solid $gray-3;
$panel-editor-viz-item-shadow-hover: 0 0 4px $blue-light; $panel-editor-viz-item-shadow-hover: 0 0 4px $blue-light;
$panel-editor-viz-item-border-hover: 1px solid $blue-light; $panel-editor-viz-item-border-hover: 1px solid $blue-light;
$panel-editor-viz-item-bg: $white; $panel-editor-viz-item-bg: $card-background;
$panel-editor-tabs-line-color: $dark-2; $panel-editor-tabs-line-color: $dark-2;
$panel-editor-viz-item-bg-hover: lighten($blue-base, 45%); $panel-editor-viz-item-bg-hover: lighten($blue-base, 45%);

View File

@ -32,6 +32,15 @@ mark,
background: $alert-warning-bg; background: $alert-warning-bg;
} }
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 500;
}
// Lists // Lists
// -------------------------------------------------- // --------------------------------------------------

View File

@ -1,6 +1,4 @@
.page-header { .page-header {
padding: $space-xl 0 0 0;
.btn { .btn {
float: right; float: right;
margin-left: $space-md; margin-left: $space-md;
@ -11,12 +9,18 @@
top: 1px; top: 1px;
} }
} }
margin-top: $space-md;
@include media-breakpoint-up(md) {
margin-top: 0;
}
} }
.page-header__inner { .page-header__inner {
flex-grow: 1; flex-grow: 1;
display: flex; display: flex;
margin-bottom: $space-xl; padding: $space-lg 0;
} }
.page-header__title { .page-header__title {
@ -36,6 +40,7 @@
width: 50px; width: 50px;
height: 50px; height: 50px;
position: relative; position: relative;
color: $text-color-weak;
&.fa { &.fa {
top: 10px; top: 10px;
@ -51,7 +56,8 @@
} }
.page-header__logo { .page-header__logo {
margin: -1px $spacer; margin: -1px $spacer -1px 0;
color: $text-color-weak;
} }
.page-header__sub-title { .page-header__sub-title {

View File

@ -23,7 +23,6 @@ $mobile-menu-breakpoint: md;
@include media-breakpoint-up($mobile-menu-breakpoint) { @include media-breakpoint-up($mobile-menu-breakpoint) {
background: $side-menu-bg; background: $side-menu-bg;
height: auto; height: auto;
box-shadow: $side-menu-shadow;
position: relative; position: relative;
z-index: $zindex-sidemenu; z-index: $zindex-sidemenu;
} }

View File

@ -1,165 +0,0 @@
.grafana-app {
display: flex;
align-items: stretch;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.main-view {
position: relative;
flex-grow: 1;
background: $page-bg;
}
.page-alerting,
.page-explore,
.page-dashboard {
.main-view {
background: $dashboard-bg;
}
}
.page-scrollbar-wrapper {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
.page-scrollbar-content {
display: flex;
min-height: 100%;
flex-direction: column;
width: 100%;
height: 100%;
}
.page-container {
flex-grow: 1;
width: 100%;
flex-basis: 100%;
margin-left: auto;
margin-right: auto;
padding-left: $spacer * 2;
padding-right: $spacer * 2;
max-width: 980px;
@include clearfix();
}
.page-full {
margin-left: $page-sidebar-margin;
padding-left: $spacer;
padding-right: $spacer;
@include clearfix();
}
.scroll-canvas {
position: absolute;
width: 100%;
overflow: auto;
height: 100%;
-webkit-overflow-scrolling: touch;
display: flex;
flex-direction: column;
&--dashboard {
height: calc(100% - 56px);
}
> div {
flex-grow: 1;
}
> .footer {
flex-shrink: 0;
}
// Render in correct position even ng-view div is not rendered yet
> .footer:first-child {
flex-grow: 1;
display: flex;
> * {
width: 100%;
align-self: flex-end;
}
}
}
.page-body {
padding-top: $spacer * 2;
padding-bottom: $spacer * 4;
}
.page-heading {
font-size: $font-size-h4;
margin-top: 0;
margin-bottom: $spacer;
}
.page-action-bar {
margin-bottom: $spacer * 2;
display: flex;
align-items: flex-start;
> a,
> button {
margin-left: $spacer;
}
}
.page-action-bar--narrow {
margin-bottom: 0;
}
.page-action-bar__spacer {
width: $spacer * 2;
flex-grow: 1;
}
.sidebar-content {
width: calc(100% - #{$page-sidebar-width + $page-sidebar-margin}); // sidebar width + margin
}
.sidebar-container {
@include media-breakpoint-up(md) {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
}
.page-sidebar {
@include media-breakpoint-up(md) {
width: $page-sidebar-width;
margin-left: $page-sidebar-margin;
}
}
.page-sub-heading {
margin-bottom: $spacer;
}
.page-sub-heading-icon {
margin-left: $spacer;
margin-top: $space-xs;
}
.page-sidebar {
color: $text-color-weak;
h4 {
font-size: $font-size-base;
font-weight: $font-weight-semi-bold;
}
h5 {
font-size: $font-size-base;
font-weight: $font-weight-semi-bold;
}
}
.page-sidebar-section {
margin-bottom: $spacer * 2;
}

View File

@ -1,3 +1,26 @@
.sidebar-content {
width: calc(100% - #{$page-sidebar-width + $page-sidebar-margin}); // sidebar width + margin
}
.sidebar-container {
@include media-breakpoint-up(md) {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
}
.page-sidebar {
@include media-breakpoint-up(md) {
width: $page-sidebar-width;
margin-left: $page-sidebar-margin;
}
}
.page-sidebar-section {
margin-bottom: $spacer * 2;
}
.get-more-plugins-link { .get-more-plugins-link {
color: $gray-3; color: $gray-3;
font-size: $font-size-sm; font-size: $font-size-sm;