diff --git a/frontend/src/components/empty/instances.js b/frontend/src/components/empty/instances.js new file mode 100644 index 00000000..739406c1 --- /dev/null +++ b/frontend/src/components/empty/instances.js @@ -0,0 +1,19 @@ +const React = require('react'); +const ReactIntl = require('react-intl'); + +const Column = require('@ui/components/column'); +const Row = require('@ui/components/row'); + +const { + FormattedMessage +} = ReactIntl; + +module.exports = () => ( + + +

+ +

+
+
+); diff --git a/frontend/src/containers/service/instances.js b/frontend/src/containers/service/instances.js index 84bda49e..c062a507 100644 --- a/frontend/src/containers/service/instances.js +++ b/frontend/src/containers/service/instances.js @@ -1,5 +1,65 @@ const React = require('react'); +const ReactRedux = require('react-redux'); -module.exports = () => ( -

instances

-); +const EmptyInstances = require('@components/empty/instances'); +const PropTypes = require('@root/prop-types'); +const List = require('@ui/components/list'); +const selectors = require('@state/selectors'); + +const { + connect +} = ReactRedux; + +const { + instancesByServiceIdSelector +} = selectors; + +const { + ListItem, + ListItemView, + ListItemMeta, + ListItemTitle, + ListItemOptions +} = List; + +const Instances = ({ + instances = [] +}) => { + const empty = instances.length ? null : ( + + ); + + const instanceList = instances.map((service) => ( + + + + {service.name} + + + + … + + + )); + + return ( +
+ {empty} + {instanceList} +
+ ); +}; + +Instances.propTypes = { + instances: React.PropTypes.arrayOf(PropTypes.instance) +}; + +const mapStateToProps = (state, { + params = {} +}) => ({ + instances: instancesByServiceIdSelector(params.serviceId)(state) +}); + +module.exports = connect( + mapStateToProps +)(Instances); diff --git a/frontend/src/containers/services/index.js b/frontend/src/containers/services/index.js index 244cae8f..94fcfe90 100644 --- a/frontend/src/containers/services/index.js +++ b/frontend/src/containers/services/index.js @@ -1,10 +1,9 @@ const React = require('react'); const ReactRedux = require('react-redux'); -const ReactRouter = require('react-router'); const EmptyServices = require('@components/empty/services'); const PropTypes = require('@root/prop-types'); -const Row = require('@ui/components/row'); +const Service = require('./service'); const selectors = require('@state/selectors'); const { @@ -17,10 +16,6 @@ const { servicesByProjectIdSelector } = selectors; -const { - Link -} = ReactRouter; - const Services = ({ org = {}, project = {}, @@ -30,37 +25,19 @@ const Services = ({ ); - const serviceList = (services) => { - if (!services || !services.length) { - return null; - } - - const list = services.map((service) => { - const to = `/${org.id}/projects/${project.id}/services/${service.id}`; - - return ( -
  • - - {service.name} - - {serviceList(service.services)} -
  • - ); - }); - - return ( - - ); - }; + const serviceList = services.map((service) => ( + + )); return (
    {empty} - - {serviceList(services)} - + {serviceList}
    ); }; diff --git a/frontend/src/containers/services/service.js b/frontend/src/containers/services/service.js new file mode 100644 index 00000000..c6fd4e5a --- /dev/null +++ b/frontend/src/containers/services/service.js @@ -0,0 +1,84 @@ +const React = require('react'); +// const ReactRouter = require('react-router'); + +const List = require('@ui/components/list'); +const PropTypes = require('@root/prop-types'); + +// const { +// Link +// } = ReactRouter; + +const { + ListItem, + ListItemView, + ListItemMeta, + ListItemTitle, + ListItemSubTitle, + ListItemDescription, + ListItemGroupView, + ListItemOutlet, + ListItemOptions, + ListItemHeader +} = List; + +const Service = ({ + org = '', + project = '', + service = {} +}) => { + // const to = `/${org}/projects/${project}/services/${service.id}`; + + const childs = service.services.map((service) => ( + 1} + > + + + {service.name} + {service.instances} instances + + + Metrics + + + + )); + + const view = childs.length ? ( + + {childs} + + ) : ( + + + Flags + + + Metrics + + + ); + + return ( + + + + {service.name} + {service.instances} instance + + + + {view} + + ); +}; + +Service.propTypes = { + org: React.PropTypes.string, + project: React.PropTypes.string, + service: PropTypes.service +}; + +module.exports = Service; diff --git a/frontend/src/mock-state.json b/frontend/src/mock-state.json index 940a4c12..ffa3b7c7 100644 --- a/frontend/src/mock-state.json +++ b/frontend/src/mock-state.json @@ -175,44 +175,52 @@ "uuid": "081a792c-47e0-4439-924b-2efa9788ae9e", "id": "nginx", "name": "Nginx", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "instances": 1 }, { "uuid": "be227788-74f1-4e5b-a85f-b5c71cbae8d8", "id": "wordpress", "name": "Wordpress", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "instances": 1 }, { "uuid": "6a0eee76-c019-413b-9d5f-44712b55b993", "id": "nfs", "name": "NFS", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "instances": 1 }, { "uuid": "6d31aff4-de1e-4042-a983-fbd23d5c530c", "id": "memcached", "name": "Memcached", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "instances": 1 }, { "uuid": "4ee4103e-1a52-4099-a48e-01588f597c70", "id": "percona", "name": "Percona", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "instances": 5 }, { "uuid": "9572d367-c4ae-4fb1-8ad5-f5e3830e7034", "id": "primary", "name": "Primary", "parent": "4ee4103e-1a52-4099-a48e-01588f597c70", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "instances": 1 }, { "uuid": "c8411ef0-ab39-42cb-a704-d20b170eff31", "id": "secondaries", "name": "Secondaries", "parent": "4ee4103e-1a52-4099-a48e-01588f597c70", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "instances": 4 }, { "uuid": "97c68055-db88-45c9-ad49-f26da4264777", "id": "consul", "name": "Consul", - "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" + "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401", + "instances": 1 }] }, "instances": { diff --git a/frontend/src/prop-types.js b/frontend/src/prop-types.js index 1a5b45ef..0edf6fcb 100644 --- a/frontend/src/prop-types.js +++ b/frontend/src/prop-types.js @@ -28,6 +28,13 @@ const Service = React.PropTypes.shape({ ...BaseObject }); +const Instance = React.PropTypes.shape({ + ...BaseObject, + datacenter: React.PropTypes.string, + service: React.PropTypes.string, + project: React.PropTypes.string +}); + const Sections = React.PropTypes.arrayOf( React.PropTypes.string ); @@ -38,5 +45,6 @@ module.exports = { org: Org, project: Project, sections: Sections, - service: Service + service: Service, + instance: Instance }; diff --git a/frontend/src/state/reducers/index.js b/frontend/src/state/reducers/index.js index e064169a..9f428409 100644 --- a/frontend/src/state/reducers/index.js +++ b/frontend/src/state/reducers/index.js @@ -8,6 +8,7 @@ module.exports = () => { return combineReducers({ account: require('@state/reducers/account'), app: require('@state/reducers/app'), + instances: require('@state/reducers/instances'), intl: require('@state/reducers/intl'), metrics: require('@state/reducers/metrics'), orgs: require('@state/reducers/orgs'), diff --git a/frontend/src/state/reducers/instances.js b/frontend/src/state/reducers/instances.js new file mode 100644 index 00000000..7fec06fb --- /dev/null +++ b/frontend/src/state/reducers/instances.js @@ -0,0 +1,9 @@ +const ReduxActions = require('redux-actions'); + +const { + handleActions +} = ReduxActions; + +module.exports = handleActions({ + 'x': (state) => state // somehow handleActions needs at least one reducer +}, {}); diff --git a/frontend/src/state/selectors.js b/frontend/src/state/selectors.js index 2f267da0..be168d92 100644 --- a/frontend/src/state/selectors.js +++ b/frontend/src/state/selectors.js @@ -15,6 +15,7 @@ const serviceUiSections = (state) => get(state, 'services.ui.sections', []); const orgs = (state) => get(state, 'orgs.data', []); const projects = (state) => get(state, 'projects.data', []); const services = (state) => get(state, 'services.data', []); +const instances = (state) => get(state, 'instances.data', []); const projectById = (projectId) => createSelector( projects, @@ -54,6 +55,12 @@ const servicesByProjectId = (projectId) => createSelector( .filter((s) => !s.parent) ); +const instancesByServiceId = (serviceId) => createSelector( + [instances, serviceById(serviceId)], + (instances, service) => instances.filter((i) => i.service === service.uuid) +); + + module.exports = { accountSelector: account, accountUISelector: accountUi, @@ -66,5 +73,6 @@ module.exports = { serviceSectionsSelector: serviceUiSections, projectsByOrgIdSelector: projectsByOrgId, projectByIdSelector: projectById, - servicesByProjectIdSelector: servicesByProjectId + servicesByProjectIdSelector: servicesByProjectId, + instancesByServiceIdSelector: instancesByServiceId }; diff --git a/ui/src/components/base/index.js b/ui/src/components/base/index.js index b999bc8e..10fbdf90 100644 --- a/ui/src/components/base/index.js +++ b/ui/src/components/base/index.js @@ -1,4 +1,6 @@ const constants = require('../../shared/constants'); +const fncs = require('../../shared/functions'); + const Styled = require('styled-components'); const { @@ -9,15 +11,27 @@ const { } = constants; const { - default: styled + default: styled, } = Styled; -module.exports = styled.div` - @font-face { - font-family: 'LibreFranklin'; - src: url('../../shared/fonts/LibreFranklin.ttf') format('truetype') - } +const { + generateFonts +} = fncs; + +// The name that will be used in the 'font-family' property +const fontFamilies = [ + 'LibreFranklin' +]; + +// The name the font file without the extension +const fontFilenames = [ + 'librefranklin-webfont' +]; + +module.exports = styled.div` + ${generateFonts(fontFamilies, fontFilenames)}; + font-family: 'LibreFranklin', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 1rem; diff --git a/ui/src/components/list/collapsed.js b/ui/src/components/list/collapsed.js deleted file mode 100644 index c5824212..00000000 --- a/ui/src/components/list/collapsed.js +++ /dev/null @@ -1,18 +0,0 @@ -const React = require('react'); - -module.exports = (Component) => (props) => { - // eslint-disable-next-line react/prop-types - const _children = React.Children.map(props.children, (c) => { - return React.cloneElement(c, { - ...c.props, - // eslint-disable-next-line react/prop-types - collapsed: props.collapsed - }); - }); - - return ( - - {_children} - - ); -}; diff --git a/ui/src/components/list/description.js b/ui/src/components/list/description.js index 35ba6cf8..80240ff2 100644 --- a/ui/src/components/list/description.js +++ b/ui/src/components/list/description.js @@ -1,6 +1,6 @@ -const Title = require('./title'); -const Styled = require('styled-components'); const React = require('react'); +const Styled = require('styled-components'); +const Title = require('./title'); const { default: styled diff --git a/ui/src/components/list/group-view.js b/ui/src/components/list/group-view.js new file mode 100644 index 00000000..460c7961 --- /dev/null +++ b/ui/src/components/list/group-view.js @@ -0,0 +1,25 @@ +const constants = require('../../shared/constants'); +const fns = require('../../shared/functions'); +const View = require('./view').raw; +const Styled = require('styled-components'); + +const { + colors +} = constants; + +const { + remcalc +} = fns; + +const { + default: styled +} = Styled; + +module.exports = styled(View)` + display: block; + padding-top: ${remcalc(62)}; + padding-left: ${remcalc(23)}; + padding-right: ${remcalc(23)}; + padding-bottom: ${remcalc(15)}; + background-color: ${colors.brandInactive}; +`; diff --git a/ui/src/components/list/header.js b/ui/src/components/list/header.js new file mode 100644 index 00000000..3d022475 --- /dev/null +++ b/ui/src/components/list/header.js @@ -0,0 +1,55 @@ +const constants = require('../../shared/constants'); +const fns = require('../../shared/functions'); +const Item = require('./item'); +const React = require('react'); +const Styled = require('styled-components'); + +const { + colors +} = constants; + +const { + remcalc +} = fns; + +const { + default: styled +} = Styled; + +const StyledItem = styled(Item)` + position: absolute; + + background-color: ${colors.brandPrimary}; + border: solid 1px ${colors.borderPrimary}; + box-shadow: none; + + width: calc(100% + ${remcalc(2)}); + margin: 0; + + top: ${remcalc(-1)}; + left: ${remcalc(-1)}; + right: ${remcalc(-1)}; +`; + +const addFromHeader = (children) => React.Children.map(children, (c) => { + return React.cloneElement(c, { + ...c.props, + fromHeader: true + }); +}); + +const Header = (props) => ( + + {addFromHeader(props.children)} + +); + +Header.propTypes = { + children: React.PropTypes.node +}; + +module.exports = Header; diff --git a/ui/src/components/list/index.js b/ui/src/components/list/index.js index 0d79549b..758a7fbb 100644 --- a/ui/src/components/list/index.js +++ b/ui/src/components/list/index.js @@ -1,10 +1,12 @@ module.exports = { - ListItem: require('./item'), - ListItemView: require('./view'), - ListItemTitle: require('./title'), - ListItemSubTitle: require('./subtitle'), ListItemDescription: require('./description'), + ListItemHeader: require('./header'), + ListItemGroupView: require('./group-view'), + ListItem: require('./item'), ListItemMeta: require('./meta'), + ListItemOptions: require('./options'), ListItemOutlet: require('./outlet'), - ListItemOptions: require('./options') + ListItemSubTitle: require('./subtitle'), + ListItemTitle: require('./title'), + ListItemView: require('./view') }; diff --git a/ui/src/components/list/item.js b/ui/src/components/list/item.js index e6c11391..112552f2 100644 --- a/ui/src/components/list/item.js +++ b/ui/src/components/list/item.js @@ -1,9 +1,9 @@ -const Collapsed = require('./collapsed'); const constants = require('../../shared/constants'); const fns = require('../../shared/functions'); const React = require('react'); const Row = require('../row'); const Styled = require('styled-components'); +const transferProps = require('./transfer-props'); const { boxes, @@ -18,16 +18,45 @@ const { default: styled } = Styled; -const height = (props) => props.collapsed ? remcalc(48) : remcalc(126); - -const Item = styled(Row)` - height: ${height} - box-shadow: ${boxes.bottomShaddow}; - border: 1px solid ${colors.borderSecondary}; - background-color: ${colors.brandSecondary}; +const paper = ` + 0 8px 0 -5px #fafafa, + 0 8px 1px -4px ${colors.borderSecondary}, + 0 16px 0 -10px #fafafa, + 0 16px 1px -9px ${colors.borderSecondary}; `; -module.exports = Collapsed((props) => ( +const height = (props) => props.collapsed + ? remcalc(48) + : 'auto'; + +const minHeight = (props) => props.collapsed + ? 'auto' + : remcalc(126); + +// remcalc(126) +const shadow = (props) => props.stacked + ? paper + : props.flat + ? 'none' + : props.collapsed && props.headed + ? boxes.bottomShaddowDarker + : boxes.bottomShaddow; + +const Item = styled(Row)` + position: relative; + + height: ${height}; + min-height: ${minHeight}; + box-shadow: ${shadow}; + border: 1px solid ${colors.borderSecondary}; + background-color: ${colors.brandSecondary}; + margin-bottom: ${remcalc(10)}; +`; + +module.exports = transferProps([ + 'collapsed', + 'headed' +], (props) => ( {props.children} diff --git a/ui/src/components/list/meta.js b/ui/src/components/list/meta.js index 1a2bfda6..df41aa5b 100644 --- a/ui/src/components/list/meta.js +++ b/ui/src/components/list/meta.js @@ -1,8 +1,9 @@ -const Collapsed = require('./collapsed'); const Column = require('../column'); -const Styled = require('styled-components'); const React = require('react'); const Row = require('../row'); +const Styled = require('styled-components'); +const transferProps = require('./transfer-props'); +const View = require('./view'); const { default: styled @@ -14,14 +15,26 @@ const InnerRow = styled(Row)` height: 100%; `; -module.exports = Collapsed((props) => ( - - - {props.children} - - -)); +module.exports = transferProps([ + 'collapsed', + 'headed', + 'fromHeader' +], (props) => { + const meta = ( + + + {props.children} + + + ); + + return !props.fromHeader ? meta : ( + + {meta} + + ); +}); diff --git a/ui/src/components/list/options.js b/ui/src/components/list/options.js index a04c3303..6f8f0f90 100644 --- a/ui/src/components/list/options.js +++ b/ui/src/components/list/options.js @@ -2,6 +2,7 @@ const Button = require('../button'); const constants = require('../../shared/constants'); const fns = require('../../shared/functions'); const React = require('react'); +const transferProps = require('./transfer-props'); const Styled = require('styled-components'); const { @@ -16,11 +17,17 @@ const { default: styled } = Styled; -const height = (props) => props.collapsed ? remcalc(46) : remcalc(124); +const height = (props) => props.collapsed + ? remcalc(46) + : remcalc(124); + +const borderLeftColor = (props) => !props.fromHeader + ? colors.borderSecondary + : colors.borderPrimary; const Nav = styled.nav` flex: 0 0 ${remcalc(47)}; - border-left: 1px solid ${colors.borderSecondary}; + border-left: 1px solid ${borderLeftColor}; `; const StyledButton = styled(Button)` @@ -44,17 +51,24 @@ const StyledButton = styled(Button)` } `; -const Options = (props) => ( -