Commit c913263b by Torkel Ödegaard Committed by GitHub

Merge pull request #14875 from grafana/page-layout-component

CustomScrollbar on all-React-pages
parents 9ecfc39b bfadcd29
......@@ -8,6 +8,7 @@ interface Props {
autoHideDuration?: number;
autoMaxHeight?: string;
hideTracksWhenNotNeeded?: boolean;
autoHeightMin?: number | string;
}
/**
......@@ -21,6 +22,7 @@ export class CustomScrollbar extends PureComponent<Props> {
autoHideDuration: 200,
autoMaxHeight: '100%',
hideTracksWhenNotNeeded: false,
autoHeightMin: '0'
};
render() {
......@@ -32,7 +34,6 @@ export class CustomScrollbar extends PureComponent<Props> {
autoHeight={true}
// These autoHeightMin & autoHeightMax options affect firefox and chrome differently.
// Before these where set to inhert but that caused problems with cut of legends in firefox
autoHeightMin={'0'}
autoHeightMax={autoMaxHeight}
renderTrackHorizontal={props => <div {...props} className="track-horizontal" />}
renderTrackVertical={props => <div {...props} className="track-vertical" />}
......
......@@ -7,7 +7,7 @@ exports[`CustomScrollbar renders correctly 1`] = `
Object {
"height": "auto",
"maxHeight": "100%",
"minHeight": "0",
"minHeight": 0,
"overflow": "hidden",
"position": "relative",
"width": "100%",
......@@ -24,7 +24,7 @@ exports[`CustomScrollbar renders correctly 1`] = `
"marginBottom": 0,
"marginRight": 0,
"maxHeight": "calc(100% + 0px)",
"minHeight": "calc(0 + 0px)",
"minHeight": 0,
"overflow": "scroll",
"position": "relative",
"right": undefined,
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
import Transition from 'react-transition-group/Transition';
interface Props {
......@@ -8,7 +8,7 @@ interface Props {
unmountOnExit?: boolean;
}
export const FadeIn: SFC<Props> = props => {
export const FadeIn: FC<Props> = props => {
const defaultStyle = {
transition: `opacity ${props.duration}ms linear`,
opacity: 0,
......
import React, { FC } from 'react';
import { Tooltip } from '@grafana/ui';
interface Props {
appName: string;
buildVersion: string;
buildCommit: string;
newGrafanaVersionExists: boolean;
newGrafanaVersion: string;
}
export const Footer: FC<Props> = React.memo(({appName, buildVersion, buildCommit, newGrafanaVersionExists, newGrafanaVersion}) => {
return (
<footer className="footer">
<div className="text-center">
<ul>
<li>
<a href="http://docs.grafana.org" target="_blank">
<i className="fa fa-file-code-o" /> Docs
</a>
</li>
<li>
<a href="https://grafana.com/services/support" target="_blank">
<i className="fa fa-support" /> Support Plans
</a>
</li>
<li>
<a href="https://community.grafana.com/" target="_blank">
<i className="fa fa-comments-o" /> Community
</a>
</li>
<li>
<a href="https://grafana.com" target="_blank">{appName}</a> <span>v{buildVersion} (commit: {buildCommit})</span>
</li>
{newGrafanaVersionExists && (
<li>
<Tooltip placement="auto" content={newGrafanaVersion}>
<a href="https://grafana.com/get" target="_blank">
New version available!
</a>
</Tooltip>
</li>
)}
</ul>
</div>
</footer>
);
});
export default Footer;
import React, { SFC } from 'react';
import React, { FC } from 'react';
export type LayoutMode = LayoutModes.Grid | LayoutModes.List;
......@@ -12,7 +12,7 @@ interface Props {
onLayoutModeChanged: (mode: LayoutMode) => {};
}
const LayoutSelector: SFC<Props> = props => {
const LayoutSelector: FC<Props> = props => {
const { mode, onLayoutModeChanged } = props;
return (
<div className="layout-selector">
......
// Libraries
import React, { Component } from 'react';
import config from 'app/core/config';
import { NavModel } from 'app/types';
import { getTitleFromNavModel } from 'app/core/selectors/navModel';
// Components
import PageHeader from '../PageHeader/PageHeader';
import Footer from '../Footer/Footer';
import PageContents from './PageContents';
import { CustomScrollbar } from '@grafana/ui';
interface Props {
title?: string;
children: JSX.Element[] | JSX.Element;
navModel: NavModel;
}
class Page extends Component<Props> {
private bodyClass = 'is-react';
private body = document.body;
static Header = PageHeader;
static Contents = PageContents;
componentDidMount() {
this.body.classList.add(this.bodyClass);
this.updateTitle();
}
componentDidUpdate(prevProps: Props) {
if (prevProps.title !== this.props.title) {
this.updateTitle();
}
}
componentWillUnmount() {
this.body.classList.remove(this.bodyClass);
}
updateTitle = () => {
const title = this.getPageTitle;
document.title = title ? title + ' - Grafana' : 'Grafana';
}
get getPageTitle () {
const { navModel } = this.props;
if (navModel) {
return getTitleFromNavModel(navModel) || undefined;
}
return undefined;
}
render() {
const { navModel } = this.props;
const { buildInfo } = config;
return (
<div className="page-scrollbar-wrapper">
<CustomScrollbar autoHeightMin={'100%'}>
<div className="page-scrollbar-content">
<PageHeader model={navModel} />
{this.props.children}
<Footer
appName="Grafana"
buildCommit={buildInfo.commit}
buildVersion={buildInfo.version}
newGrafanaVersion={buildInfo.latestVersion}
newGrafanaVersionExists={buildInfo.hasUpdate} />
</div>
</CustomScrollbar>
</div>
);
}
}
export default Page;
// Libraries
import React, { Component } from 'react';
// Components
import PageLoader from '../PageLoader/PageLoader';
interface Props {
isLoading?: boolean;
children: JSX.Element[] | JSX.Element;
}
class PageContents extends Component<Props> {
render() {
const { isLoading } = this.props;
return (
<div className="page-container page-body">
{isLoading && <PageLoader />}
{this.props.children}
</div>
);
}
}
export default PageContents;
import React from 'react';
import React, { FormEvent } from 'react';
import { NavModel, NavModelItem } from 'app/types';
import classNames from 'classnames';
import appEvents from 'app/core/app_events';
......@@ -12,8 +12,8 @@ const SelectNav = ({ main, customCss }: { main: NavModelItem; customCss: string
return navItem.active === true;
});
const gotoUrl = evt => {
const element = evt.target;
const gotoUrl = (evt: FormEvent) => {
const element = evt.target as HTMLSelectElement;
const url = element.options[element.selectedIndex].value;
appEvents.emit('location-change', { href: url });
};
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
interface Props {
pageName: string;
pageName?: string;
}
const PageLoader: SFC<Props> = ({ pageName }) => {
const PageLoader: FC<Props> = ({ pageName }) => {
const loadingText = `Loading ${pageName}...`;
return (
<div className="page-loader-wrapper">
......
import React, { SFC, ReactNode, PureComponent } from 'react';
import React, { FC, ReactNode, PureComponent } from 'react';
import { Tooltip } from '@grafana/ui';
interface ToggleButtonGroupProps {
......@@ -29,7 +29,7 @@ interface ToggleButtonProps {
tooltip?: string;
}
export const ToggleButton: SFC<ToggleButtonProps> = ({
export const ToggleButton: FC<ToggleButtonProps> = ({
children,
selected,
className = '',
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
export interface Props {
child: any;
}
const DropDownChild: SFC<Props> = props => {
const DropDownChild: FC<Props> = props => {
const { child } = props;
const listItemClassName = child.divider ? 'divider' : '';
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
import DropDownChild from './DropDownChild';
interface Props {
link: any;
}
const SideMenuDropDown: SFC<Props> = props => {
const SideMenuDropDown: FC<Props> = props => {
const { link } = props;
return (
<ul className="dropdown-menu dropdown-menu--sidemenu" role="menu">
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
const SignIn: SFC<any> = () => {
const SignIn: FC<any> = () => {
const loginUrl = `login?redirect=${encodeURIComponent(window.location.pathname)}`;
return (
<div className="sidemenu-item">
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
import _ from 'lodash';
import TopSectionItem from './TopSectionItem';
import config from '../../config';
const TopSection: SFC<any> = () => {
const TopSection: FC<any> = () => {
const navTree = _.cloneDeep(config.bootData.navTree);
const mainLinks = _.filter(navTree, item => !item.hideFromMenu);
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
import SideMenuDropDown from './SideMenuDropDown';
export interface Props {
link: any;
}
const TopSectionItem: SFC<Props> = props => {
const TopSectionItem: FC<Props> = props => {
const { link } = props;
return (
<div className="sidemenu-item dropdown">
......
......@@ -6,6 +6,8 @@ export interface BuildInfo {
commit: string;
isEnterprise: boolean;
env: string;
latestVersion: string;
hasUpdate: boolean;
}
export class Settings {
......
......@@ -41,3 +41,7 @@ export function getNavModel(navIndex: NavIndex, id: string, fallback?: NavModel)
return getNotFoundModel();
}
export const getTitleFromNavModel = (navModel: NavModel) => {
return `${navModel.main.text}${navModel.node.text ? ': ' + navModel.node.text : '' }`;
};
......@@ -6,7 +6,14 @@ import { getMultipleMockKeys, getMockKey } from './__mocks__/apiKeysMock';
const setup = (propOverrides?: object) => {
const props: Props = {
navModel: {} as NavModel,
navModel: {
main: {
text: 'Configuration'
},
node: {
text: 'Api Keys'
}
} as NavModel,
apiKeys: [] as ApiKey[],
searchQuery: '',
hasFetched: false,
......
......@@ -6,8 +6,7 @@ import { NavModel, ApiKey, NewApiKey, OrgRole } from 'app/types';
import { getNavModel } from 'app/core/selectors/navModel';
import { getApiKeys, getApiKeysCount } from './state/selectors';
import { loadApiKeys, deleteApiKey, setSearchQuery, addApiKey } from './state/actions';
import PageHeader from 'app/core/components/PageHeader/PageHeader';
import PageLoader from 'app/core/components/PageLoader/PageLoader';
import Page from 'app/core/components/Page/Page';
import SlideDown from 'app/core/components/Animations/SlideDown';
import ApiKeysAddedModal from './ApiKeysAddedModal';
import config from 'app/core/config';
......@@ -240,18 +239,17 @@ export class ApiKeysPage extends PureComponent<Props, any> {
const { hasFetched, navModel, apiKeysCount } = this.props;
return (
<div>
<PageHeader model={navModel} />
{hasFetched ? (
apiKeysCount > 0 ? (
this.renderApiKeyList()
) : (
this.renderEmptyList()
)
) : (
<PageLoader pageName="Api keys" />
)}
</div>
<Page navModel={navModel}>
<Page.Contents isLoading={!hasFetched}>
{hasFetched && (
apiKeysCount > 0 ? (
this.renderApiKeyList()
) : (
this.renderEmptyList()
)
)}
</Page.Contents>
</Page>
);
}
}
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render API keys table if there are any keys 1`] = `
<div>
<PageHeader
model={Object {}}
<Page
navModel={
Object {
"main": Object {
"text": "Configuration",
},
"node": Object {
"text": "Api Keys",
},
}
}
>
<PageContents
isLoading={true}
/>
<PageLoader
pageName="Api keys"
/>
</div>
</Page>
`;
exports[`Render should render CTA if there are no API keys 1`] = `
<div>
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
<Page
navModel={
Object {
"main": Object {
"text": "Configuration",
},
"node": Object {
"text": "Api Keys",
},
}
}
>
<PageContents
isLoading={false}
>
<EmptyListCTA
model={
Object {
"buttonIcon": "fa fa-plus",
"buttonLink": "#",
"buttonTitle": " New API Key",
"onClick": [Function],
"proTip": "Remember you can provide view-only API access to other applications.",
"proTipLink": "",
"proTipLinkTitle": "",
"proTipTarget": "_blank",
"title": "You haven't added any API Keys yet.",
}
}
/>
<Component
in={false}
<div
className="page-container page-body"
>
<div
className="cta-form"
<EmptyListCTA
model={
Object {
"buttonIcon": "fa fa-plus",
"buttonLink": "#",
"buttonTitle": " New API Key",
"onClick": [Function],
"proTip": "Remember you can provide view-only API access to other applications.",
"proTipLink": "",
"proTipLinkTitle": "",
"proTipTarget": "_blank",
"title": "You haven't added any API Keys yet.",
}
}
/>
<Component
in={false}
>
<button
className="cta-form__close btn btn-transparent"
onClick={[Function]}
>
<i
className="fa fa-close"
/>
</button>
<h5>
Add API Key
</h5>
<form
className="gf-form-group"
onSubmit={[Function]}
<div
className="cta-form"
>
<div
className="gf-form-inline"
<button
className="cta-form__close btn btn-transparent"
onClick={[Function]}
>
<i
className="fa fa-close"
/>
</button>
<h5>
Add API Key
</h5>
<form
className="gf-form-group"
onSubmit={[Function]}
>
<div
className="gf-form max-width-21"
>
<span
className="gf-form-label"
>
Key name
</span>
<input
className="gf-form-input"
onChange={[Function]}
placeholder="Name"
type="text"
value=""
/>
</div>
<div
className="gf-form"
className="gf-form-inline"
>
<span
className="gf-form-label"
<div
className="gf-form max-width-21"
>
Role
</span>
<span
className="gf-form-select-wrapper"
>
<select
className="gf-form-input gf-size-auto"
<span
className="gf-form-label"
>
Key name
</span>
<input
className="gf-form-input"
onChange={[Function]}
value="Viewer"
placeholder="Name"
type="text"
value=""
/>
</div>
<div
className="gf-form"
>
<span
className="gf-form-label"
>
<option
key="Viewer"
label="Viewer"
Role
</span>
<span
className="gf-form-select-wrapper"
>
<select
className="gf-form-input gf-size-auto"
onChange={[Function]}
value="Viewer"
>
Viewer
</option>
<option
key="Editor"
label="Editor"
value="Editor"
>
Editor
</option>
<option
key="Admin"
label="Admin"
value="Admin"
>
Admin
</option>
</select>
</span>
</div>
<div
className="gf-form"
>
<button
className="btn gf-form-btn btn-success"
<option
key="Viewer"
label="Viewer"
value="Viewer"
>
Viewer
</option>
<option
key="Editor"
label="Editor"
value="Editor"
>
Editor
</option>
<option
key="Admin"
label="Admin"
value="Admin"
>
Admin
</option>
</select>
</span>
</div>
<div
className="gf-form"
>
Add
</button>
<button
className="btn gf-form-btn btn-success"
>
Add
</button>
</div>
</div>
</div>
</form>
</div>
</Component>
</div>
</div>
</form>
</div>
</Component>
</div>
</PageContents>
</Page>
`;
import React, { SFC } from 'react';
import React, { FC } from 'react';
import { PanelMenuItem } from '@grafana/ui';
interface Props {
children: any;
}
export const PanelHeaderMenuItem: SFC<Props & PanelMenuItem> = props => {
export const PanelHeaderMenuItem: FC<Props & PanelMenuItem> = props => {
const isSubMenu = props.type === 'submenu';
const isDivider = props.type === 'divider';
return isDivider ? (
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
import { Tooltip } from '@grafana/ui';
interface Props {
......@@ -10,7 +10,7 @@ interface Props {
tooltipInfo?: any;
}
export const DataSourceOptions: SFC<Props> = ({ label, placeholder, name, value, onChange, tooltipInfo }) => {
export const DataSourceOptions: FC<Props> = ({ label, placeholder, name, value, onChange, tooltipInfo }) => {
const dsOption = (
<div className="gf-form gf-form--flex-end">
<label className="gf-form-label">{label}</label>
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
import { PluginDashboard } from '../../types';
export interface Props {
......@@ -7,7 +7,7 @@ export interface Props {
onRemove: (dashboard) => void;
}
const DashboardsTable: SFC<Props> = ({ dashboards, onImport, onRemove }) => {
const DashboardsTable: FC<Props> = ({ dashboards, onImport, onRemove }) => {
function buttonText(dashboard: PluginDashboard) {
return dashboard.revision !== dashboard.importedRevision ? 'Update' : 'Re-import';
}
......
......@@ -10,7 +10,14 @@ const setup = (propOverrides?: object) => {
dataSources: [] as DataSource[],
layoutMode: LayoutModes.Grid,
loadDataSources: jest.fn(),
navModel: {} as NavModel,
navModel: {
main: {
text: 'Configuration'
},
node: {
text: 'Data Sources'
}
} as NavModel,
dataSourcesCount: 0,
searchQuery: '',
setDataSourcesSearchQuery: jest.fn(),
......
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { hot } from 'react-hot-loader';
import PageHeader from '../../core/components/PageHeader/PageHeader';
import PageLoader from 'app/core/components/PageLoader/PageLoader';
import OrgActionBar from '../../core/components/OrgActionBar/OrgActionBar';
import EmptyListCTA from '../../core/components/EmptyListCTA/EmptyListCTA';
import Page from 'app/core/components/Page/Page';
import OrgActionBar from 'app/core/components/OrgActionBar/OrgActionBar';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import DataSourcesList from './DataSourcesList';
import { DataSource, NavModel } from 'app/types';
import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
import { DataSource, NavModel, StoreState } from 'app/types';
import { LayoutMode } from 'app/core/components/LayoutSelector/LayoutSelector';
import { loadDataSources, setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/actions';
import { getNavModel } from '../../core/selectors/navModel';
import { getNavModel } from 'app/core/selectors/navModel';
import {
getDataSources,
getDataSourcesCount,
......@@ -67,30 +67,30 @@ export class DataSourcesListPage extends PureComponent<Props> {
};
return (
<div>
<PageHeader model={navModel} />
<div className="page-container page-body">
{!hasFetched && <PageLoader pageName="Data sources" />}
{hasFetched && dataSourcesCount === 0 && <EmptyListCTA model={emptyListModel} />}
{hasFetched &&
dataSourcesCount > 0 && [
<OrgActionBar
layoutMode={layoutMode}
searchQuery={searchQuery}
onSetLayoutMode={mode => setDataSourcesLayoutMode(mode)}
setSearchQuery={query => setDataSourcesSearchQuery(query)}
linkButton={linkButton}
key="action-bar"
/>,
<DataSourcesList dataSources={dataSources} layoutMode={layoutMode} key="list" />,
]}
</div>
</div>
<Page navModel={navModel}>
<Page.Contents isLoading={!hasFetched}>
<>
{hasFetched && dataSourcesCount === 0 && <EmptyListCTA model={emptyListModel} />}
{hasFetched &&
dataSourcesCount > 0 && [
<OrgActionBar
layoutMode={layoutMode}
searchQuery={searchQuery}
onSetLayoutMode={mode => setDataSourcesLayoutMode(mode)}
setSearchQuery={query => setDataSourcesSearchQuery(query)}
linkButton={linkButton}
key="action-bar"
/>,
<DataSourcesList dataSources={dataSources} layoutMode={layoutMode} key="list" />,
]}
</>
</Page.Contents>
</Page>
);
}
}
function mapStateToProps(state) {
function mapStateToProps(state: StoreState) {
return {
navModel: getNavModel(state.navIndex, 'datasources'),
dataSources: getDataSources(state.dataSources),
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render action bar and datasources 1`] = `
<div>
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
<Page
navModel={
Object {
"main": Object {
"text": "Configuration",
},
"node": Object {
"text": "Data Sources",
},
}
}
>
<PageContents
isLoading={false}
>
<OrgActionBar
key="action-bar"
......@@ -143,21 +151,25 @@ exports[`Render should render action bar and datasources 1`] = `
key="list"
layoutMode="grid"
/>
</div>
</div>
</PageContents>
</Page>
`;
exports[`Render should render component 1`] = `
<div>
<PageHeader
model={Object {}}
<Page
navModel={
Object {
"main": Object {
"text": "Configuration",
},
"node": Object {
"text": "Data Sources",
},
}
}
>
<PageContents
isLoading={true}
/>
<div
className="page-container page-body"
>
<PageLoader
pageName="Data sources"
/>
</div>
</div>
</Page>
`;
import React, { SFC } from 'react';
import React, { FC } from 'react';
import { FormLabel } from '@grafana/ui';
import { Switch } from '../../../core/components/Switch/Switch';
......@@ -9,7 +9,7 @@ export interface Props {
onDefaultChange: (value: boolean) => void;
}
const BasicSettings: SFC<Props> = ({ dataSourceName, isDefault, onDefaultChange, onNameChange }) => {
const BasicSettings: FC<Props> = ({ dataSourceName, isDefault, onDefaultChange, onNameChange }) => {
return (
<div className="gf-form-group">
<div className="gf-form-inline">
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
export interface Props {
isReadOnly: boolean;
......@@ -6,7 +6,7 @@ export interface Props {
onSubmit: (event) => void;
}
const ButtonRow: SFC<Props> = ({ isReadOnly, onDelete, onSubmit }) => {
const ButtonRow: FC<Props> = ({ isReadOnly, onDelete, onSubmit }) => {
return (
<div className="gf-form-button-row">
<button type="submit" className="btn btn-success" disabled={isReadOnly} onClick={event => onSubmit(event)}>
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
interface Props {
message: any;
}
export const Alert: SFC<Props> = props => {
export const Alert: FC<Props> = props => {
const { message } = props;
return (
<div className="gf-form-group section">
......
......@@ -6,7 +6,14 @@ import { NavModel, Organization } from '../../types';
const setup = (propOverrides?: object) => {
const props: Props = {
organization: {} as Organization,
navModel: {} as NavModel,
navModel: {
main: {
text: 'Configuration'
},
node: {
text: 'Org details'
}
} as NavModel,
loadOrganization: jest.fn(),
setOrganizationName: jest.fn(),
updateOrganization: jest.fn(),
......
import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import PageHeader from '../../core/components/PageHeader/PageHeader';
import PageLoader from '../../core/components/PageLoader/PageLoader';
import Page from 'app/core/components/Page/Page';
import OrgProfile from './OrgProfile';
import SharedPreferences from 'app/core/components/SharedPreferences/SharedPreferences';
import { loadOrganization, setOrganizationName, updateOrganization } from './state/actions';
import { NavModel, Organization, StoreState } from 'app/types';
import { getNavModel } from '../../core/selectors/navModel';
import { getNavModel } from 'app/core/selectors/navModel';
export interface Props {
navModel: NavModel;
......@@ -35,22 +34,22 @@ export class OrgDetailsPage extends PureComponent<Props> {
const isLoading = Object.keys(organization).length === 0;
return (
<div>
<PageHeader model={navModel} />
<div className="page-container page-body">
{isLoading && <PageLoader pageName="Organization" />}
{!isLoading && (
<div>
<OrgProfile
onOrgNameChange={name => this.onOrgNameChange(name)}
onSubmit={this.onUpdateOrganization}
orgName={organization.name}
/>
<SharedPreferences resourceUri="org" />
<Page navModel={navModel}>
<Page.Contents isLoading={isLoading}>
<div className="page-container page-body">
{!isLoading && (
<div>
<OrgProfile
onOrgNameChange={name => this.onOrgNameChange(name)}
onSubmit={this.onUpdateOrganization}
orgName={organization.name}
/>
<SharedPreferences resourceUri="org" />
</div>
)}
</div>
)}
</div>
</div>
</Page.Contents>
</Page>
);
}
}
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
export interface Props {
orgName: string;
......@@ -6,7 +6,7 @@ export interface Props {
onOrgNameChange: (orgName: string) => void;
}
const OrgProfile: SFC<Props> = ({ onSubmit, onOrgNameChange, orgName }) => {
const OrgProfile: FC<Props> = ({ onSubmit, onOrgNameChange, orgName }) => {
return (
<div>
<h3 className="page-sub-heading">Organization profile</h3>
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render component 1`] = `
<div>
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
<Page
navModel={
Object {
"main": Object {
"text": "Configuration",
},
"node": Object {
"text": "Org details",
},
}
}
>
<PageContents
isLoading={true}
>
<PageLoader
pageName="Organization"
<div
className="page-container page-body"
/>
</div>
</div>
</PageContents>
</Page>
`;
exports[`Render should render organization and preferences 1`] = `
<div>
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
<Page
navModel={
Object {
"main": Object {
"text": "Configuration",
},
"node": Object {
"text": "Org details",
},
}
}
>
<PageContents
isLoading={false}
>
<div>
<OrgProfile
onOrgNameChange={[Function]}
onSubmit={[Function]}
orgName="Cool org"
/>
<SharedPreferences
resourceUri="org"
/>
<div
className="page-container page-body"
>
<div>
<OrgProfile
onOrgNameChange={[Function]}
onSubmit={[Function]}
orgName="Cool org"
/>
<SharedPreferences
resourceUri="org"
/>
</div>
</div>
</div>
</div>
</PageContents>
</Page>
`;
import React, { SFC } from 'react';
import React, { FC } from 'react';
import classNames from 'classnames';
import PluginListItem from './PluginListItem';
import { Plugin } from 'app/types';
......@@ -9,7 +9,7 @@ interface Props {
layoutMode: LayoutMode;
}
const PluginList: SFC<Props> = props => {
const PluginList: FC<Props> = props => {
const { plugins, layoutMode } = props;
const listStyle = classNames({
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
import { Plugin } from 'app/types';
interface Props {
plugin: Plugin;
}
const PluginListItem: SFC<Props> = props => {
const PluginListItem: FC<Props> = props => {
const { plugin } = props;
return (
......
......@@ -6,7 +6,14 @@ import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector
const setup = (propOverrides?: object) => {
const props: Props = {
navModel: {} as NavModel,
navModel: {
main: {
text: 'Configuration'
},
node: {
text: 'Plugins'
}
} as NavModel,
plugins: [] as Plugin[],
searchQuery: '',
setPluginsSearchQuery: jest.fn(),
......
import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import PageHeader from 'app/core/components/PageHeader/PageHeader';
import Page from 'app/core/components/Page/Page';
import OrgActionBar from 'app/core/components/OrgActionBar/OrgActionBar';
import PageLoader from 'app/core/components/PageLoader/PageLoader';
import PluginList from './PluginList';
import { NavModel, Plugin } from 'app/types';
import { loadPlugins, setPluginsLayoutMode, setPluginsSearchQuery } from './state/actions';
import { getNavModel } from '../../core/selectors/navModel';
import { getNavModel } from 'app/core/selectors/navModel';
import { getLayoutMode, getPlugins, getPluginsSearchQuery } from './state/selectors';
import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
import { LayoutMode } from 'app/core/components/LayoutSelector/LayoutSelector';
export interface Props {
navModel: NavModel;
......@@ -48,23 +47,22 @@ export class PluginListPage extends PureComponent<Props> {
};
return (
<div>
<PageHeader model={navModel} />
<div className="page-container page-body">
<OrgActionBar
searchQuery={searchQuery}
layoutMode={layoutMode}
onSetLayoutMode={mode => setPluginsLayoutMode(mode)}
setSearchQuery={query => setPluginsSearchQuery(query)}
linkButton={linkButton}
/>
{hasFetched ? (
plugins && <PluginList plugins={plugins} layoutMode={layoutMode} />
) : (
<PageLoader pageName="Plugins" />
)}
</div>
</div>
<Page navModel={navModel}>
<Page.Contents isLoading={!hasFetched}>
<>
<OrgActionBar
searchQuery={searchQuery}
layoutMode={layoutMode}
onSetLayoutMode={mode => setPluginsLayoutMode(mode)}
setSearchQuery={query => setPluginsSearchQuery(query)}
linkButton={linkButton}
/>
{hasFetched && plugins && (
plugins && <PluginList plugins={plugins} layoutMode={layoutMode} />
)}
</>
</Page.Contents>
</Page>
);
}
}
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render component 1`] = `
<div>
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
<Page
navModel={
Object {
"main": Object {
"text": "Configuration",
},
"node": Object {
"text": "Plugins",
},
}
}
>
<PageContents
isLoading={true}
>
<OrgActionBar
layoutMode="grid"
......@@ -20,20 +28,25 @@ exports[`Render should render component 1`] = `
searchQuery=""
setSearchQuery={[Function]}
/>
<PageLoader
pageName="Plugins"
/>
</div>
</div>
</PageContents>
</Page>
`;
exports[`Render should render list 1`] = `
<div>
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
<Page
navModel={
Object {
"main": Object {
"text": "Configuration",
},
"node": Object {
"text": "Plugins",
},
}
}
>
<PageContents
isLoading={false}
>
<OrgActionBar
layoutMode="grid"
......@@ -51,6 +64,6 @@ exports[`Render should render list 1`] = `
layoutMode="grid"
plugins={Array []}
/>
</div>
</div>
</PageContents>
</Page>
`;
......@@ -6,7 +6,14 @@ import { getMockTeam, getMultipleMockTeams } from './__mocks__/teamMocks';
const setup = (propOverrides?: object) => {
const props: Props = {
navModel: {} as NavModel,
navModel: {
main: {
text: 'Configuration'
},
node: {
text: 'Team List'
}
} as NavModel,
teams: [] as Team[],
loadTeams: jest.fn(),
deleteTeam: jest.fn(),
......
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { hot } from 'react-hot-loader';
import PageHeader from 'app/core/components/PageHeader/PageHeader';
import Page from 'app/core/components/Page/Page';
import { DeleteButton } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import PageLoader from 'app/core/components/PageLoader/PageLoader';
import { NavModel, Team } from '../../types';
import { NavModel, Team } from 'app/types';
import { loadTeams, deleteTeam, setSearchQuery } from './state/actions';
import { getSearchQuery, getTeams, getTeamsCount } from './state/selectors';
import { getNavModel } from 'app/core/selectors/navModel';
......@@ -141,10 +140,11 @@ export class TeamList extends PureComponent<Props, any> {
const { hasFetched, navModel } = this.props;
return (
<div>
<PageHeader model={navModel} />
{hasFetched ? this.renderList() : <PageLoader pageName="Teams" />}
</div>
<Page navModel={navModel}>
<Page.Contents isLoading={!hasFetched}>
{hasFetched && this.renderList()}
</Page.Contents>
</Page>
);
}
}
......
......@@ -11,7 +11,14 @@ jest.mock('../../core/app_events', () => ({
const setup = (propOverrides?: object) => {
const props: Props = {
navModel: {} as NavModel,
navModel: {
main: {
text: 'Configuration'
},
node: {
text: 'Users'
}
} as NavModel,
users: [] as OrgUser[],
invitees: [] as Invitee[],
searchQuery: '',
......
......@@ -2,15 +2,14 @@ import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import Remarkable from 'remarkable';
import PageHeader from 'app/core/components/PageHeader/PageHeader';
import PageLoader from 'app/core/components/PageLoader/PageLoader';
import Page from 'app/core/components/Page/Page';
import UsersActionBar from './UsersActionBar';
import UsersTable from './UsersTable';
import InviteesTable from './InviteesTable';
import { Invitee, NavModel, OrgUser } from 'app/types';
import appEvents from 'app/core/app_events';
import { loadUsers, loadInvitees, setUsersSearchQuery, updateUser, removeUser } from './state/actions';
import { getNavModel } from '../../core/selectors/navModel';
import { getNavModel } from 'app/core/selectors/navModel';
import { getInvitees, getUsers, getUsersSearchQuery } from './state/selectors';
export interface Props {
......@@ -105,16 +104,17 @@ export class UsersListPage extends PureComponent<Props, State> {
const externalUserMngInfoHtml = this.externalUserMngInfoHtml;
return (
<div>
<PageHeader model={navModel} />
<div className="page-container page-body">
<Page navModel={navModel}>
<Page.Contents isLoading={!hasFetched}>
<>
<UsersActionBar onShowInvites={this.onShowInvites} showInvites={this.state.showInvites} />
{externalUserMngInfoHtml && (
<div className="grafana-info-box" dangerouslySetInnerHTML={{ __html: externalUserMngInfoHtml }} />
)}
{hasFetched ? this.renderTable() : <PageLoader pageName="Users" />}
</div>
</div>
{hasFetched && this.renderTable()}
</>
</Page.Contents>
</Page>
);
}
}
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
import { OrgUser } from 'app/types';
export interface Props {
......@@ -7,7 +7,7 @@ export interface Props {
onRemoveUser: (user: OrgUser) => void;
}
const UsersTable: SFC<Props> = props => {
const UsersTable: FC<Props> = props => {
const { users, onRoleChange, onRemoveUser } = props;
return (
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render List page 1`] = `
<div>
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
<Page
navModel={
Object {
"main": Object {
"text": "Configuration",
},
"node": Object {
"text": "Users",
},
}
}
>
<PageContents
isLoading={false}
>
<Connect(UsersActionBar)
onShowInvites={[Function]}
......@@ -17,25 +25,30 @@ exports[`Render should render List page 1`] = `
onRoleChange={[Function]}
users={Array []}
/>
</div>
</div>
</PageContents>
</Page>
`;
exports[`Render should render component 1`] = `
<div>
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
<Page
navModel={
Object {
"main": Object {
"text": "Configuration",
},
"node": Object {
"text": "Users",
},
}
}
>
<PageContents
isLoading={true}
>
<Connect(UsersActionBar)
onShowInvites={[Function]}
showInvites={false}
/>
<PageLoader
pageName="Users"
/>
</div>
</div>
</PageContents>
</Page>
`;
import React, { SFC } from 'react';
import React, { FC } from 'react';
import _ from 'lodash';
import kbn from 'app/core/utils/kbn';
......@@ -14,7 +14,7 @@ export interface Props {
usedAlignmentPeriod: string;
}
export const AlignmentPeriods: SFC<Props> = ({
export const AlignmentPeriods: FC<Props> = ({
alignmentPeriod,
templateSrv,
onChange,
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
import _ from 'lodash';
import { MetricSelect } from 'app/core/components/Select/MetricSelect';
......@@ -12,7 +12,7 @@ export interface Props {
perSeriesAligner: string;
}
export const Alignments: SFC<Props> = ({ perSeriesAligner, templateSrv, onChange, alignOptions }) => {
export const Alignments: FC<Props> = ({ perSeriesAligner, templateSrv, onChange, alignOptions }) => {
return (
<>
<div className="gf-form-group">
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
export const AnnotationsHelp: SFC = () => {
export const AnnotationsHelp: FC = () => {
return (
<div className="gf-form grafana-info-box" style={{ padding: 0 }}>
<pre className="gf-form-pre alert alert-info" style={{ marginRight: 0 }}>
......
import React, { SFC } from 'react';
import React, { FC } from 'react';
interface Props {
onValueChange: (e) => void;
......@@ -7,7 +7,7 @@ interface Props {
label: string;
}
const SimpleSelect: SFC<Props> = props => {
const SimpleSelect: FC<Props> = props => {
const { label, onValueChange, value, options } = props;
return (
<div className="gf-form max-width-21">
......
......@@ -38,6 +38,14 @@
}
}
.is-react .footer {
display: none;
}
.is-react .custom-scrollbars .footer {
display: block;
}
// Keeping footer inside the graphic on Login screen
.login-page {
.footer {
......
......@@ -20,7 +20,23 @@
}
}
.page-scrollbar-wrapper {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
.page-scrollbar-content {
display: flex;
min-height: 100%;
flex-direction: column;
width: 100%;
}
.page-container {
flex-grow: 1;
width: 100%;
margin-left: auto;
margin-right: auto;
padding-left: $spacer*2;
......@@ -78,7 +94,6 @@
.page-body {
padding-top: $spacer*2;
min-height: 500px;
}
.page-heading {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment