+
{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) => (
+
);
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) => (
+
);
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) => (
+
);
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 (
+
+ );
+};
+
+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) => (
+
);
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 (
+
+ );
+ };
+
+ 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
};