feat(cp-frontend): uniform loading statuses and titles

This commit is contained in:
Sérgio Ramos 2017-07-28 17:56:03 +01:00
parent 935e9bacca
commit 92b9f09c01
22 changed files with 238 additions and 212 deletions

View File

@ -1 +0,0 @@
export { default as DeploymentGroupsLoading } from './loading';

View File

@ -1,14 +0,0 @@
import React from 'react';
import { Col, Row } from 'react-styled-flexboxgrid';
import { Dots2 } from 'styled-text-spinners';
const LoadingRow = Row.extend`
flex: 1 1 auto;
`;
export default () =>
<LoadingRow center="xs" around="xs" middle="xs">
<Col xs={1}>
<Dots2 />
</Col>
</LoadingRow>;

View File

@ -7,13 +7,13 @@ export default Grid.extend`
${isNot('plain')`
flex: 1 1 auto;
display: flex;
display: block;
flex-flow: column;
`};
${is('center')`
display: flex;
flex-direction: row;
flex-direction: column;
flex-wrap: nowrap;
justify-content: center;
align-content: center;

View File

@ -3,11 +3,12 @@ import { Field } from 'redux-form';
import styled from 'styled-components';
import SimpleTable from 'react-simple-table';
import { Row, Col } from 'react-styled-flexboxgrid';
import { Dots2 } from 'styled-text-spinners';
import Bundle from 'react-bundle';
import remcalc from 'remcalc';
import forceArray from 'force-array';
import { Loader } from '@components/messaging';
import {
FormGroup,
FormMeta,
@ -18,7 +19,8 @@ import {
ProgressbarItem,
ProgressbarButton,
H3,
typography
typography,
StatusLoader
} from 'joyent-ui-toolkit';
const Dl = styled.dl`
@ -88,7 +90,7 @@ class ManifestEditorBundle extends Component {
}, 80);
}
return <Dots2 />;
return <Loader />;
}
render() {
if (!this.state.ManifestEditor) {
@ -161,7 +163,7 @@ export const Manifest = ({
disabled={!(dirty || !loading || defaultValue.length)}
type="submit"
>
Environment
{loading ? <StatusLoader /> : 'Environment'}
</Button>
</ButtonsRow>
</form>;
@ -180,7 +182,7 @@ const Filename = ({ name, onRemoveFile }) =>
export const Files = ({ loading, files, onRemoveFile }) => {
if (loading) {
return null;
return <Loader />;
}
const _files = files.map(({ id, name, value }) =>
@ -229,12 +231,18 @@ export const Environment = ({
disabled={!(dirty || !loading || defaultValue.length)}
type="submit"
>
{loading ? <Dots2 /> : 'Review'}
{loading ? <StatusLoader /> : 'Review'}
</Button>
</ButtonsRow>
</form>;
export const Review = ({ handleSubmit, onCancel, dirty, ...state }) => {
export const Review = ({
handleSubmit,
onCancel,
dirty,
loading,
...state
}) => {
const serviceList = forceArray(state.services).map(({ name, config }) =>
<ServiceCard key={name}>
<Dl>
@ -274,11 +282,11 @@ export const Review = ({ handleSubmit, onCancel, dirty, ...state }) => {
<form onSubmit={handleSubmit}>
{serviceList}
<ButtonsRow>
<Button onClick={onCancel} disabled={state.loading} secondary>
<Button onClick={onCancel} disabled={loading} secondary>
Cancel
</Button>
<Button disabled={state.loading} type="submit">
{state.loading ? <Dots2 /> : 'Confirm and Deploy'}
<Button disabled={loading} type="submit">
{loading ? <StatusLoader /> : 'Confirm and Deploy'}
</Button>
</ButtonsRow>
</form>

View File

@ -7,10 +7,11 @@ const Container = styled.div`
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: flex-start;
justify-content: center;
align-content: center;
align-items: center;
flex: 1 1 auto;
flex: 1 0 auto;
align-self: stretch;
`;
@ -23,6 +24,7 @@ const Msg = P.extend`
flex: 0 0 auto;
align-self: stretch;
text-align: center;
margin-bottom: 0;
`;
export default ({ msg }) =>

View File

@ -1,3 +1,4 @@
export { default as Breadcrumb } from './breadcrumb';
export { default as Menu } from './menu';
export { default as Header } from './header';
export { default as Title } from './title';

View File

@ -0,0 +1,8 @@
import { H2 } from 'joyent-ui-toolkit';
import remcalc from 'remcalc';
export default H2.extend`
margin-top: ${remcalc(2)};
flex: 0 0 auto;
align-self: stretch;
`;

View File

@ -41,7 +41,7 @@ const ServiceListItem = ({
onQuickActionsClick(evt, service);
};
const children = forceArray(service.children);
const children = sortBy(forceArray(service.children), ['slug']);
const isServiceInactive = service.status && service.status !== 'ACTIVE';
const to = `/deployment-groups/${deploymentGroup}/services/${service.slug}`;
@ -49,7 +49,7 @@ const ServiceListItem = ({
? children.reduce((count, child) => count + child.instances.length, 0)
: service.instances.length;
const childrenItems = sortBy(children, ['slug']).map(service =>
const childrenItems = children.map(service =>
<ServiceListItem
key={service.id}
deploymentGroup={deploymentGroup}

View File

@ -9,12 +9,21 @@ import { Modal } from 'joyent-ui-toolkit';
class DeploymentGroupDelete extends Component {
render() {
if (this.props.loading) {
return <Loader />;
}
if (this.props.error) {
const { loading, error } = this.props;
if (loading) {
return (
<ErrorMessage message="Oops, an error occured while loading your service." />
<Modal width={460} onCloseClick={handleCloseClick}>
<Loader />
</Modal>
);
}
if (error) {
return (
<Modal width={460} onCloseClick={handleCloseClick}>
<ErrorMessage message="Oops, an error occured while deleting your service." />
</Modal>
);
}

View File

@ -3,32 +3,11 @@ import React from 'react';
import ManifestEditOrCreate from '@containers/manifest/edit-or-create';
import { Progress } from '@components/manifest/edit-or-create';
import { LayoutContainer } from '@components/layout';
import { DeploymentGroupsLoading } from '@components/deployment-groups';
import { H2 } from 'joyent-ui-toolkit';
import { Title } from '@components/navigation';
export default ({
loading,
error,
manifest = '',
deploymentGroup = null,
match
}) => {
const stage = match.params.stage;
return (
<LayoutContainer>
<H2>Creating deployment group</H2>
{loading && <DeploymentGroupsLoading />}
{error &&
<span>
{error.toString()}
</span>}
<Progress stage={stage} create />
<ManifestEditOrCreate
manifest={manifest}
deploymentGroup={deploymentGroup}
create
/>
</LayoutContainer>
);
};
export default ({ match }) =>
<LayoutContainer>
<Title>Creating deployment group</Title>
<Progress stage={match.params.stage} create />
<ManifestEditOrCreate create />
</LayoutContainer>;

View File

@ -5,15 +5,14 @@ import intercept from 'apr-intercept';
import DeploymentGroupImportMutation from '@graphql/DeploymentGroupImport.gql';
import { LayoutContainer } from '@components/layout';
import { DeploymentGroupsLoading } from '@components/deployment-groups';
import { H2 } from 'joyent-ui-toolkit';
import { Title } from '@components/navigation';
import { ErrorMessage, Loader } from '@components/messaging';
class DeploymentGroupImport extends Component {
constructor() {
super();
this.state = {
loading: true,
error: false
};
@ -40,14 +39,21 @@ class DeploymentGroupImport extends Component {
render() {
const { loading, error } = this.state;
const _title = <Title>Importing deployment group</Title>;
if (error) {
return (
<LayoutContainer>
{_title}
<ErrorMessage message="Oops, and error occured while importing your deployment groups." />
</LayoutContainer>
);
}
return (
<LayoutContainer>
<H2>Importing deployment group</H2>
{loading && <DeploymentGroupsLoading />}
{error &&
<span>
{error.toString()}
</span>}
<LayoutContainer center>
{_title}
<Loader />
</LayoutContainer>
);
}

View File

@ -8,16 +8,12 @@ import forceArray from 'force-array';
import remcalc from 'remcalc';
import { LayoutContainer } from '@components/layout';
import { ErrorMessage } from '@components/messaging';
import { DeploymentGroupsLoading } from '@components/deployment-groups';
import { Title } from '@components/navigation';
import { ErrorMessage, Loader } from '@components/messaging';
import DeploymentGroupsQuery from '@graphql/DeploymentGroups.gql';
import DeploymentGroupsImportableQuery from '@graphql/DeploymentGroupsImportable.gql';
import { H2, H3, Small, IconButton, BinIcon } from 'joyent-ui-toolkit';
const Title = H2.extend`
margin-top: ${remcalc(2)};
`;
const DGsRows = Row.extend`
margin-top: ${remcalc(-7)};
`;
@ -109,14 +105,25 @@ const DeploymentGroupList = ({
error,
match
}) => {
const _loading = !loading ? null : <DeploymentGroupsLoading />;
const _title = <Title>Deployment groups</Title>;
// todo improve this error message style according to new designs
const _error = !error
? null
: <Row>
if (loading) {
return (
<LayoutContainer center>
{_title}
<Loader />
</LayoutContainer>
);
}
if (error) {
return (
<LayoutContainer>
{_title}
<ErrorMessage message="Oops, and error occured while loading your deployment groups." />
</Row>;
</LayoutContainer>
);
}
const groups = forceArray(deploymentGroups).map(({ slug, name }) =>
<Col xs={12} sm={4} md={3} lg={3} key={slug}>
@ -159,9 +166,7 @@ const DeploymentGroupList = ({
return (
<LayoutContainer>
<Title>Deployment groups</Title>
{_loading}
{_error}
{_title}
<DGsRows>
{groups}
{create}

View File

@ -1,52 +1,63 @@
import React, { Component } from 'react';
// Import PropTypes from 'prop-types';
import { compose, graphql } from 'react-apollo';
import InstancesQuery from '@graphql/Instances.gql';
import { Row } from 'react-styled-flexboxgrid';
import remcalc from 'remcalc';
import forceArray from 'force-array';
import sortBy from 'lodash.sortby';
import { LayoutContainer } from '@components/layout';
import { ErrorMessage } from '@components/messaging';
import { Title } from '@components/navigation';
import { Loader, ErrorMessage } from '@components/messaging';
import { InstanceListItem, EmptyInstances } from '@components/instances';
import { DeploymentGroupsLoading } from '@components/deployment-groups';
import { H2 } from 'joyent-ui-toolkit';
const Title = H2.extend`
margin-top: ${remcalc(2)};
`;
class InstanceList extends Component {
render() {
const { instances, loading, error } = this.props;
const _loading = !loading ? null : <DeploymentGroupsLoading />;
const _error = !error
? null
: <Row>
<ErrorMessage message="Oops, and error occured while loading your instances." />
</Row>;
const instanceList = instances
? instances.map((instance, index) =>
<InstanceListItem
instance={instance}
key={instance.id}
toggleCollapsed={() => null}
/>
)
: <EmptyInstances />;
const InstanceList = ({ deploymentGroup, instances = [], loading, error }) => {
const _title = <Title>Instances</Title>;
if (loading && !forceArray(instances).length) {
return (
<LayoutContainer>
<Title>Instances</Title>
{_error}
{_loading}
{instanceList}
<LayoutContainer center>
{_title}
<Loader />
</LayoutContainer>
);
}
}
if (error) {
return (
<LayoutContainer>
{_title}
<ErrorMessage message="Oops, and error occured while loading your instances." />
</LayoutContainer>
);
}
if (deploymentGroup.status === 'PROVISIONING' && !instances.length) {
return (
<LayoutContainer center>
{_title}
<Loader msg="Just a moment, were on it" />
</LayoutContainer>
);
}
const instanceList = instances.map((instance, index) =>
<InstanceListItem
instance={instance}
key={instance.id}
toggleCollapsed={() => null}
/>
);
const _instances = !instanceList.length ? <EmptyInstances /> : instanceList;
return (
<LayoutContainer>
{_title}
{_instances}
</LayoutContainer>
);
};
const InstanceListGql = graphql(InstancesQuery, {
options(props) {
@ -63,18 +74,20 @@ const InstanceListGql = graphql(InstancesQuery, {
};
},
props: ({ data: { deploymentGroup, loading, error } }) => ({
instances:
deploymentGroup && deploymentGroup.services
? deploymentGroup.services.reduce(
deploymentGroup,
instances: sortBy(
forceArray(
deploymentGroup &&
forceArray(deploymentGroup.services).reduce(
(instances, service) => instances.concat(service.instances),
[]
)
: null,
).filter(Boolean),
['name']
),
loading,
error
})
});
const InstanceListWithData = compose(InstanceListGql)(InstanceList);
export default InstanceListWithData;
export default compose(InstanceListGql)(InstanceList);

View File

@ -14,6 +14,7 @@ import DeploymentGroupProvisionMutation from '@graphql/DeploymentGroupProvision.
import DeploymentGroupConfigQuery from '@graphql/DeploymentGroupConfig.gql';
import { client } from '@state/store';
import { ErrorMessage } from '@components/messaging';
import {
Name,
Manifest,
@ -354,14 +355,10 @@ class DeploymentGroupEditOrCreate extends Component {
}
render() {
const { error, defaultStage, manifestStage } = this.state;
const { error, loading, defaultStage, manifestStage } = this.state;
if (error) {
return (
<span>
{error}
</span>
);
return <ErrorMessage message={error} />;
}
const { match, create } = this.props;

View File

@ -9,8 +9,8 @@ import DeploymentGroupBySlugQuery from '@graphql/DeploymentGroupBySlug.gql';
import ManifestEditOrCreate from '@containers/manifest/edit-or-create';
import { Progress } from '@components/manifest/edit-or-create';
import { LayoutContainer } from '@components/layout';
import { DeploymentGroupsLoading } from '@components/deployment-groups';
import { H2 } from 'joyent-ui-toolkit';
import { Title } from '@components/navigation';
import { Loader, ErrorMessage } from '@components/messaging';
const Manifest = ({
loading,
@ -21,43 +21,42 @@ const Manifest = ({
match
}) => {
const stage = match.params.stage;
const _loading = !loading ? null : <DeploymentGroupsLoading />;
const _error = !error
? null
: <span>
{error.toString()}
</span>;
const _title = <Title>Edit Manifest</Title>;
const _view =
loading || !deploymentGroup
? null
: <ManifestEditOrCreate
manifest={manifest}
environment={environment}
deploymentGroup={deploymentGroup}
edit
/>;
if (loading || !deploymentGroup) {
return (
<LayoutContainer center>
{_title}
<Loader />
</LayoutContainer>
);
}
if (error) {
return (
<LayoutContainer>
{_title}
<ErrorMessage message="Oops, and error occured while loading your services." />
</LayoutContainer>
);
}
const _notice =
!error &&
!loading &&
deploymentGroup &&
deploymentGroup.imported &&
!manifest
? <span>
Since this DeploymentGroup was imported, it doesn&#x27;t have the
initial manifest
</span>
deploymentGroup && deploymentGroup.imported && !manifest
? <ErrorMessage message="Since this DeploymentGroup was imported, it doesn&#x27;t have the initial manifest" />
: null;
return (
<LayoutContainer>
<H2>Edit Manifest</H2>
{_title}
<Progress stage={stage} edit />
{_error}
{_loading}
{_notice}
{_view}
<ManifestEditOrCreate
manifest={manifest}
environment={environment}
deploymentGroup={deploymentGroup}
edit
/>
</LayoutContainer>
);
};

View File

@ -1,30 +1,17 @@
import React from 'react';
import { graphql } from 'react-apollo';
import get from 'lodash.get';
import PortalQuery from '@graphql/Portal.gql';
import { Header as HeaderComponent } from '@components/navigation';
const Header = ({
portal = {
datacenter: {
region: ''
},
user: {
firstName: ''
}
},
loading,
error
}) =>
<HeaderComponent
datacenter={portal.datacenter.region}
username={portal.user.firstName}
/>;
const Header = ({ datacenter, username }) =>
<HeaderComponent datacenter={datacenter} username={username} />;
const HeaderWithData = graphql(PortalQuery, {
props: ({ data: { portal, loading, error } }) => ({
portal,
loading,
error
props: ({ data: { portal = {} } }) => ({
datacenter: get(portal, 'datacenter.region', ''),
username: get(portal, 'user.firstName', '')
})
})(Header);

View File

@ -9,12 +9,21 @@ import ServiceGql from './service-gql';
class ServiceDelete extends Component {
render() {
if (this.props.loading) {
return <Loader />;
}
if (this.props.error) {
const { loading, error } = this.props;
if (loading) {
return (
<ErrorMessage message="Oops, an error occured while loading your service." />
<Modal width={460} onCloseClick={handleCloseClick}>
<Loader />
</Modal>
);
}
if (error) {
return (
<Modal width={460} onCloseClick={handleCloseClick}>
<ErrorMessage message="Oops, an error occured while deleting your service." />
</Modal>
);
}
@ -56,8 +65,4 @@ const DeleteServicesGql = graphql(ServicesDeleteMutation, {
})
});
const ServiceDeleteWithData = compose(DeleteServicesGql, ServiceGql)(
ServiceDelete
);
export default ServiceDeleteWithData;
export default compose(DeleteServicesGql, ServiceGql)(ServiceDelete);

View File

@ -10,13 +10,21 @@ import ServiceGql from './service-gql';
class ServiceScale extends Component {
render() {
if (this.props.loading) {
return <Loader />;
const { loading, error } = this.props;
if (loading) {
return (
<Modal width={460} onCloseClick={handleCloseClick}>
<Loader />
</Modal>
);
}
if (this.props.error) {
if (error) {
return (
<ErrorMessage message="Oops, an error occured while loading your service." />
<Modal width={460} onCloseClick={handleCloseClick}>
<ErrorMessage message="Oops, an error occured while scaling your service." />
</Modal>
);
}
@ -85,6 +93,4 @@ const ServiceScaleGql = graphql(ServiceScaleMutation, {
})
});
const ServiceScaleWithData = compose(ServiceScaleGql, ServiceGql)(ServiceScale);
export default ServiceScaleWithData;
export default compose(ServiceScaleGql, ServiceGql)(ServiceScale);

View File

@ -47,7 +47,7 @@ class ServiceList extends Component {
startServices
} = this.props;
if (loading) {
if (loading && !forceArray(services).length) {
return (
<LayoutContainer center>
<Loader />
@ -68,7 +68,7 @@ class ServiceList extends Component {
!forceArray(services).length
) {
return (
<LayoutContainer>
<LayoutContainer center>
<Loader msg="Just a moment, were on it" />
</LayoutContainer>
);

View File

@ -4,6 +4,7 @@ import { Col, Row } from 'react-styled-flexboxgrid';
import remcalc from 'remcalc';
import unitcalc from 'unitcalc';
import { LayoutContainer } from '@components/layout';
import { Title } from '@components/navigation';
import { H2, FormGroup, Toggle, ToggleList, Legend } from 'joyent-ui-toolkit';
@ -31,7 +32,7 @@ const ServicesMenu = ({ location, history: { push } }) => {
return (
<LayoutContainer plain>
<H2>Services</H2>
<Title>Services</Title>
<PaddedRow>
<Col xs={5}>
<FormGroup name="service-view" value={toggleValue}>

View File

@ -29,6 +29,7 @@ const StyledContainer = styled.div`
const ServicesTopology = ({
url,
push,
deploymentGroup,
services,
datacenter,
loading,
@ -41,11 +42,13 @@ const ServicesTopology = ({
}) => {
if (loading) {
return (
<LayoutContainer>
<LayoutContainer center>
<Loader />
</LayoutContainer>
);
} else if (error) {
}
if (error) {
return (
<LayoutContainer>
<ErrorMessage message="Oops, and error occured while loading your services." />
@ -53,6 +56,17 @@ const ServicesTopology = ({
);
}
if (
deploymentGroup.status === 'PROVISIONING' &&
!forceArray(services).length
) {
return (
<LayoutContainer center>
<Loader msg="Just a moment, were on it" />
</LayoutContainer>
);
}
const handleQuickActionsClick = (evt, tooltipData) => {
toggleServicesQuickActions(tooltipData);
};
@ -132,7 +146,8 @@ const ServicesGql = graphql(ServicesQuery, {
}
};
},
props: ({ data: { deploymentGroup, loading, error } }) => ({
props: ({ data: { deploymentGroup = {}, loading, error } }) => ({
deploymentGroup,
services: deploymentGroup
? processServicesForTopology(deploymentGroup.services)
: null,

View File

@ -23,8 +23,8 @@ const StyledModal = styled.div`
position: absolute;
left: 50%;
top: 33.33%;
padding: ${remcalc(30)} ${remcalc(36)} ${remcalc(36)} ${remcalc(36)};
background-color: ${theme.white};
padding: ${remcalc(36)} ${remcalc(36)} ${remcalc(36)} ${remcalc(36)};
background-color: ${props => props.theme.white};
box-shadow: ${modalShadow};
width: ${props => remcalc(props.width)};