diff --git a/frontend/src/containers/home/index.js b/frontend/src/containers/home/index.js index 0ea7e3eb..8c759608 100644 --- a/frontend/src/containers/home/index.js +++ b/frontend/src/containers/home/index.js @@ -3,15 +3,16 @@ const ReactRedux = require('react-redux'); const ReactRouter = require('react-router'); const Styled = require('styled-components'); -const selectors = require('@state/selectors'); - const Container = require('@ui/components/container'); const Li = require('@ui/components/horizontal-list/li'); const Org = require('@containers/org'); +const PropTypes = require('@root/prop-types'); const Redirect = require('@components/redirect'); +const selectors = require('@state/selectors'); const Ul = require('@ui/components/horizontal-list/ul'); const NotFound = require('@containers/not-found'); + const { connect } = ReactRedux; @@ -74,14 +75,7 @@ const Home = ({ }; Home.propTypes = { - orgs: React.PropTypes.arrayOf( - React.PropTypes.shape({ - owner: React.PropTypes.string, - uuid: React.PropTypes.string, - id: React.PropTypes.string, - name: React.PropTypes.string - }) - ) + orgs: React.PropTypes.arrayOf(PropTypes.org) }; const mapStateToProps = (state) => ({ diff --git a/frontend/src/containers/org/index.js b/frontend/src/containers/org/index.js index 6a57ed40..ad09be24 100644 --- a/frontend/src/containers/org/index.js +++ b/frontend/src/containers/org/index.js @@ -4,6 +4,7 @@ const ReactRedux = require('react-redux'); const ReactRouter = require('react-router'); const NotFound = require('@containers/not-found'); +const PropTypes = require('@root/prop-types'); const Redirect = require('@components/redirect'); const selectors = require('@state/selectors'); @@ -58,15 +59,8 @@ const Org = ({ }; Org.propTypes = { - org: React.PropTypes.shape({ - owner: React.PropTypes.string, - uuid: React.PropTypes.string, - id: React.PropTypes.string, - name: React.PropTypes.string - }), - sections: React.PropTypes.arrayOf( - React.PropTypes.string - ) + org: PropTypes.org, + sections: PropTypes.sections }; const mapStateToProps = (state, ownProps) => ({ diff --git a/frontend/src/containers/org/section.js b/frontend/src/containers/org/section.js index 1a748501..5750871e 100644 --- a/frontend/src/containers/org/section.js +++ b/frontend/src/containers/org/section.js @@ -1,6 +1,7 @@ const React = require('react'); const ReactRedux = require('react-redux'); +const PropTypes = require('@root/prop-types'); const selectors = require('@state/selectors'); const Section = require('@components/section'); @@ -32,13 +33,8 @@ const OrgSection = ({ OrgSection.propTypes = { children: React.PropTypes.node, - org: React.PropTypes.shape({ - id: React.PropTypes.string, - name: React.PropTypes.string - }), - sections: React.PropTypes.arrayOf( - React.PropTypes.string - ) + org: PropTypes.org, + sections: PropTypes.sections }; const mapStateToProps = (state, ownProps) => ({ @@ -46,4 +42,6 @@ const mapStateToProps = (state, ownProps) => ({ sections: orgSectionsSelector(ownProps.params.org)(state) }); -module.exports = connect(mapStateToProps)(OrgSection); +module.exports = connect( + mapStateToProps +)(OrgSection); diff --git a/frontend/src/containers/project/index.js b/frontend/src/containers/project/index.js index 78bb1d36..64973092 100644 --- a/frontend/src/containers/project/index.js +++ b/frontend/src/containers/project/index.js @@ -2,8 +2,8 @@ const React = require('react'); const ReactRedux = require('react-redux'); const ReactRouter = require('react-router'); +const PropTypes = require('@root/prop-types'); const Redirect = require('@components/redirect'); -const Section = require('@components/section'); const selectors = require('@state/selectors'); const SectionComponents = { @@ -34,67 +34,30 @@ const Project = ({ project = {}, sections = [] }) => { - const pathname = (props) => ( - `/${props.org}/projects/${props.project}/${props.section}` - ); - - const name = `${org.name} / ${project.name}`; - - const links = sections.map((name) => ({ - pathname: pathname({ - org: org.id, - project: project.id, - section: name - }), - name - })); - - const navMatches = sections.map((name) => { - const pattern = pathname({ - org: org.id, - project: project.id, - section: name - }); - - return ( - - ); - }); - - const missPathname = pathname({ - org: org.id, - project: project.id, - section: sections[0] - }); + const navMatches = sections.map((name) => ( + + )); const missMatch = !sections.length ? null : ( - + ); return ( -
+
{navMatches} {missMatch} -
+ ); }; Project.propTypes = { - org: React.PropTypes.shape({ - id: React.PropTypes.string, - name: React.PropTypes.string - }), - project: React.PropTypes.shape({ - id: React.PropTypes.string, - name: React.PropTypes.string - }), - sections: React.PropTypes.arrayOf( - React.PropTypes.string - ) + org: PropTypes.org, + project: PropTypes.project, + sections: PropTypes.sections }; const mapStateToProps = (state, { diff --git a/frontend/src/containers/project/instances.js b/frontend/src/containers/project/instances.js index 84bda49e..dbb58acf 100644 --- a/frontend/src/containers/project/instances.js +++ b/frontend/src/containers/project/instances.js @@ -1,5 +1,9 @@ const React = require('react'); -module.exports = () => ( -

instances

+const Section = require('./section'); + +module.exports = (props) => ( +
+

instances

+
); diff --git a/frontend/src/containers/project/manifest.js b/frontend/src/containers/project/manifest.js index fbfd0ee6..f6c58d84 100644 --- a/frontend/src/containers/project/manifest.js +++ b/frontend/src/containers/project/manifest.js @@ -1,5 +1,9 @@ const React = require('react'); -module.exports = () => ( -

manifest

+const Section = require('./section'); + +module.exports = (props) => ( +
+

manifest

+
); diff --git a/frontend/src/containers/project/people.js b/frontend/src/containers/project/people.js index 042b6270..5abef63e 100644 --- a/frontend/src/containers/project/people.js +++ b/frontend/src/containers/project/people.js @@ -1,5 +1,9 @@ const React = require('react'); -module.exports = () => ( -

people

+const Section = require('./section'); + +module.exports = (props) => ( +
+

people

+
); diff --git a/frontend/src/containers/project/section.js b/frontend/src/containers/project/section.js new file mode 100644 index 00000000..ef196fe2 --- /dev/null +++ b/frontend/src/containers/project/section.js @@ -0,0 +1,63 @@ +const React = require('react'); +const ReactRedux = require('react-redux'); + +const PropTypes = require('@root/prop-types'); +const selectors = require('@state/selectors'); +const Section = require('@components/section'); + +const { + connect +} = ReactRedux; + +const { + orgByIdSelector, + projectByIdSelector, + projectSectionsSelector +} = selectors; + +const OrgSection = ({ + children, + org = {}, + project = {}, + sections = [] +}) => { + const name = `${org.name} / ${project.name}`; + + const pathname = (props) => ( + `/${props.org}/projects/${props.project}/${props.section}` + ); + + const links = sections.map((name) => ({ + pathname: pathname({ + org: org.id, + project: project.id, + section: name + }), + name + })); + + return ( +
+ {children} +
+ ); +}; + +OrgSection.propTypes = { + children: React.PropTypes.node, + org: PropTypes.org, + project: PropTypes.project, + sections: PropTypes.sections +}; + +const mapStateToProps = (state, { + params = {} +}) => ({ + org: orgByIdSelector(params.org)(state), + project: projectByIdSelector(params.projectId)(state), + sections: projectSectionsSelector(state) +}); + +module.exports = connect( + mapStateToProps +)(OrgSection); diff --git a/frontend/src/containers/project/services.js b/frontend/src/containers/project/services.js index 709df9d6..a1ccaff6 100644 --- a/frontend/src/containers/project/services.js +++ b/frontend/src/containers/project/services.js @@ -1,5 +1,32 @@ const React = require('react'); +const ReactRouter = require('react-router'); -module.exports = () => ( -

services

-); +const Section = require('./section'); +const Services = require('@containers/services'); +const Service = require('@containers/service'); + +const { + Match +} = ReactRouter; + +module.exports = () => { + const list = (props) => ( +
+ +
+ ); + + return ( +
+ + +
+ ); +}; diff --git a/frontend/src/containers/project/settings.js b/frontend/src/containers/project/settings.js index 10ce9d02..2a4631bb 100644 --- a/frontend/src/containers/project/settings.js +++ b/frontend/src/containers/project/settings.js @@ -1,5 +1,9 @@ const React = require('react'); -module.exports = () => ( -

settings

+const Section = require('./section'); + +module.exports = (props) => ( +
+

settings

+
); diff --git a/frontend/src/containers/projects/index.js b/frontend/src/containers/projects/index.js index 1c8cf07c..4e291c41 100644 --- a/frontend/src/containers/projects/index.js +++ b/frontend/src/containers/projects/index.js @@ -5,6 +5,7 @@ const ReactRouter = require('react-router'); const Button = require('@ui/components/button'); const Column = require('@ui/components/column'); +const PropTypes = require('@root/prop-types'); const Row = require('@ui/components/row'); const selectors = require('@state/selectors'); @@ -67,16 +68,8 @@ const Projects = ({ }; Projects.propTypes = { - org: React.PropTypes.shape({ - id: React.PropTypes.string, - name: React.PropTypes.string - }), - projects: React.PropTypes.arrayOf( - React.PropTypes.shape({ - id: React.PropTypes.string, - name: React.PropTypes.string - }) - ) + org: PropTypes.org, + projects: React.PropTypes.arrayOf(PropTypes.project) }; const mapStateToProps = (state, { diff --git a/frontend/src/containers/service/activity-feed.js b/frontend/src/containers/service/activity-feed.js new file mode 100644 index 00000000..0ed379fe --- /dev/null +++ b/frontend/src/containers/service/activity-feed.js @@ -0,0 +1,5 @@ +const React = require('react'); + +module.exports = () => ( +

activity-feed

+); diff --git a/frontend/src/containers/service/firewall.js b/frontend/src/containers/service/firewall.js new file mode 100644 index 00000000..d5d1dd4e --- /dev/null +++ b/frontend/src/containers/service/firewall.js @@ -0,0 +1,5 @@ +const React = require('react'); + +module.exports = () => ( +

firewall

+); diff --git a/frontend/src/containers/service/index.js b/frontend/src/containers/service/index.js new file mode 100644 index 00000000..6e920447 --- /dev/null +++ b/frontend/src/containers/service/index.js @@ -0,0 +1,109 @@ +const React = require('react'); +const ReactRedux = require('react-redux'); +const ReactRouter = require('react-router'); + +const PropTypes = require('@root/prop-types'); +const Redirect = require('@components/redirect'); +const Section = require('@components/section'); +const selectors = require('@state/selectors'); + +const SectionComponents = { + summary: require('./summary'), + instances: require('./instances'), + metrics: require('./metrics'), + networks: require('./networks'), + 'tags-metadata': require('./tags-metadata'), + 'activity-feed': require('./activity-feed'), + 'service-manifest': require('./service-manifest'), + firewall: require('./firewall') +}; + +const { + connect +} = ReactRedux; + +const { + Match, + Miss +} = ReactRouter; + +const { + orgByIdSelector, + serviceSectionsSelector, + projectByIdSelector, + serviceByIdSelector +} = selectors; + +const Service = ({ + org = {}, + project = {}, + sections = [], + service = {} +}) => { + const name = `${org.name} / ${project.name} / ${service.name}`; + + const pathname = ({ + org, + project, + service, + section + }) => ( + `/${org}/projects/${project}/services/${service}/${section}` + ); + + const links = sections.map((name) => ({ + pathname: pathname({ + org: org.id, + project: project.id, + service: service.id, + section: name + }), + name + })); + + const navMatches = sections.map((name) => ( + + )); + + const redirectHref = pathname({ + org: org.id, + project: project.id, + service: service.id, + section: 'summary' + }); + + const missMatch = !sections.length ? null : ( + + ); + + return ( +
+ {navMatches} + {missMatch} +
+ ); +}; + +Service.propTypes = { + org: PropTypes.org, + project: PropTypes.project, + sections: PropTypes.sections, + service: PropTypes.service +}; + +const mapStateToProps = (state, { + params = {} +}) => ({ + org: orgByIdSelector(params.org)(state), + project: projectByIdSelector(params.projectId)(state), + sections: serviceSectionsSelector(state), + service: serviceByIdSelector(params.serviceId)(state) +}); + +module.exports = connect( + mapStateToProps +)(Service); diff --git a/frontend/src/containers/service/instances.js b/frontend/src/containers/service/instances.js new file mode 100644 index 00000000..84bda49e --- /dev/null +++ b/frontend/src/containers/service/instances.js @@ -0,0 +1,5 @@ +const React = require('react'); + +module.exports = () => ( +

instances

+); diff --git a/frontend/src/containers/service/metrics.js b/frontend/src/containers/service/metrics.js new file mode 100644 index 00000000..66890823 --- /dev/null +++ b/frontend/src/containers/service/metrics.js @@ -0,0 +1,5 @@ +const React = require('react'); + +module.exports = () => ( +

metrics

+); diff --git a/frontend/src/containers/service/networks.js b/frontend/src/containers/service/networks.js new file mode 100644 index 00000000..cfd3a3d4 --- /dev/null +++ b/frontend/src/containers/service/networks.js @@ -0,0 +1,5 @@ +const React = require('react'); + +module.exports = () => ( +

networks

+); diff --git a/frontend/src/containers/service/service-manifest.js b/frontend/src/containers/service/service-manifest.js new file mode 100644 index 00000000..abac7706 --- /dev/null +++ b/frontend/src/containers/service/service-manifest.js @@ -0,0 +1,5 @@ +const React = require('react'); + +module.exports = () => ( +

service-manifest

+); diff --git a/frontend/src/containers/service/summary.js b/frontend/src/containers/service/summary.js new file mode 100644 index 00000000..09091bd6 --- /dev/null +++ b/frontend/src/containers/service/summary.js @@ -0,0 +1,5 @@ +const React = require('react'); + +module.exports = () => ( +

summary

+); diff --git a/frontend/src/containers/service/tags-metadata.js b/frontend/src/containers/service/tags-metadata.js new file mode 100644 index 00000000..a068afce --- /dev/null +++ b/frontend/src/containers/service/tags-metadata.js @@ -0,0 +1,5 @@ +const React = require('react'); + +module.exports = () => ( +

tags-metadata

+); diff --git a/frontend/src/containers/services/index.js b/frontend/src/containers/services/index.js new file mode 100644 index 00000000..900bb46b --- /dev/null +++ b/frontend/src/containers/services/index.js @@ -0,0 +1,95 @@ +const React = require('react'); +const ReactIntl = require('react-intl'); +const ReactRedux = require('react-redux'); +const ReactRouter = require('react-router'); + +const Column = require('@ui/components/column'); +const PropTypes = require('@root/prop-types'); +const Row = require('@ui/components/row'); +const selectors = require('@state/selectors'); + +const { + connect +} = ReactRedux; + +const { + FormattedMessage +} = ReactIntl; + +const { + orgByIdSelector, + projectByIdSelector, + servicesByProjectIdSelector +} = selectors; + +const { + Link +} = ReactRouter; + +const Services = ({ + org = {}, + project = {}, + services = [] +}) => { + const empty = services.length ? null : ( + + +

+ +

+
+
+ ); + + 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 ( +
      + {list} +
    + ); + }; + + return ( +
    + {empty} + + {serviceList(services)} + +
    + ); +}; + +Services.propTypes = { + org: PropTypes.org, + project: PropTypes.project, + services: React.PropTypes.arrayOf(PropTypes.service) +}; + +const mapStateToProps = (state, { + params = {} +}) => ({ + org: orgByIdSelector(params.org)(state), + project: projectByIdSelector(params.projectId)(state), + services: servicesByProjectIdSelector(params.projectId)(state) +}); + +module.exports = connect( + mapStateToProps +)(Services); diff --git a/frontend/src/mock-state.json b/frontend/src/mock-state.json index effb919f..b1a729c4 100644 --- a/frontend/src/mock-state.json +++ b/frontend/src/mock-state.json @@ -155,36 +155,44 @@ }, "data": [{ "uuid": "081a792c-47e0-4439-924b-2efa9788ae9e", + "id": "nginx", "name": "Nginx", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" }, { "uuid": "be227788-74f1-4e5b-a85f-b5c71cbae8d8", + "id": "wordpress", "name": "Wordpress", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" }, { "uuid": "6a0eee76-c019-413b-9d5f-44712b55b993", + "id": "nfs", "name": "NFS", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" }, { "uuid": "6d31aff4-de1e-4042-a983-fbd23d5c530c", + "id": "memcached", "name": "Memcached", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" }, { "uuid": "4ee4103e-1a52-4099-a48e-01588f597c70", + "id": "percona", "name": "Percona", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" }, { "uuid": "9572d367-c4ae-4fb1-8ad5-f5e3830e7034", + "id": "primary", "name": "Primary", - "parent": "9572d367-c4ae-4fb1-8ad5-f5e3830e7034", + "parent": "4ee4103e-1a52-4099-a48e-01588f597c70", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" }, { "uuid": "c8411ef0-ab39-42cb-a704-d20b170eff31", + "id": "secondaries", "name": "Secondaries", - "parent": "9572d367-c4ae-4fb1-8ad5-f5e3830e7034", + "parent": "4ee4103e-1a52-4099-a48e-01588f597c70", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" }, { "uuid": "97c68055-db88-45c9-ad49-f26da4264777", + "id": "consul", "name": "Consul", "project": "e0ea0c02-55cc-45fe-8064-3e5176a59401" }] diff --git a/frontend/src/prop-types.js b/frontend/src/prop-types.js new file mode 100644 index 00000000..e04d6ffb --- /dev/null +++ b/frontend/src/prop-types.js @@ -0,0 +1,31 @@ +const React = require('react'); + +const BaseObject = { + uuid: React.PropTypes.string, + id: React.PropTypes.string, + name: React.PropTypes.string +}; + +const Org = React.PropTypes.shape({ + ...BaseObject, + owner: React.PropTypes.string +}); + +const Project = React.PropTypes.shape({ + ...BaseObject +}); + +const Service = React.PropTypes.shape({ + ...BaseObject +}); + +const Sections = React.PropTypes.arrayOf( + React.PropTypes.string +); + +module.exports = { + org: Org, + project: Project, + sections: Sections, + service: Service +}; diff --git a/frontend/src/state/reducers/index.js b/frontend/src/state/reducers/index.js index 05960668..8085e6d8 100644 --- a/frontend/src/state/reducers/index.js +++ b/frontend/src/state/reducers/index.js @@ -10,6 +10,7 @@ module.exports = () => { app: require('@state/reducers/app'), intl: require('@state/reducers/intl'), orgs: require('@state/reducers/orgs'), - projects: require('@state/reducers/projects') + projects: require('@state/reducers/projects'), + services: require('@state/reducers/services') }); }; diff --git a/frontend/src/state/reducers/services.js b/frontend/src/state/reducers/services.js new file mode 100644 index 00000000..7fec06fb --- /dev/null +++ b/frontend/src/state/reducers/services.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 f504a7ea..2f267da0 100644 --- a/frontend/src/state/selectors.js +++ b/frontend/src/state/selectors.js @@ -11,17 +11,24 @@ const account = (state) => get(state, 'account.data', {}); const accountUi = (state) => get(state, 'account.ui', {}); const orgUiSections = (state) => get(state, 'orgs.ui.sections', []); const projectUiSections = (state) => get(state, 'projects.ui.sections', []); +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 projectById= (id) => createSelector( +const projectById = (projectId) => createSelector( projects, - (projects) => find(projects, ['id', id]) + (projects) => find(projects, ['id', projectId]) ); -const orgById = (id) => createSelector( +const orgById = (orgId) => createSelector( orgs, - (orgs) => find(orgs, ['id', id]) + (orgs) => find(orgs, ['id', orgId]) +); + +const serviceById = (serviceId) => createSelector( + [services], + (services) => find(services, ['id', serviceId]) ); const projectsByOrgId = (orgId) => createSelector( @@ -36,13 +43,28 @@ const orgSections = (orgId) => createSelector( ) ); +const servicesByProjectId = (projectId) => createSelector( + [services, projectById(projectId)], + (services, project) => + services.filter((s) => s.project === project.uuid) + .map((service) => ({ + ...service, + services: services.filter((s) => s.parent === service.uuid) + })) + .filter((s) => !s.parent) +); + module.exports = { accountSelector: account, accountUISelector: accountUi, orgByIdSelector: orgById, orgsSelector: orgs, + servicesSelector: services, + serviceByIdSelector: serviceById, orgSectionsSelector: orgSections, projectSectionsSelector: projectUiSections, + serviceSectionsSelector: serviceUiSections, projectsByOrgIdSelector: projectsByOrgId, - projectByIdSelector: projectById + projectByIdSelector: projectById, + servicesByProjectIdSelector: servicesByProjectId };