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) => (
-