mirror of
https://github.com/yldio/copilot.git
synced 2024-12-01 07:30:07 +02:00
Rock and roll
This commit is contained in:
parent
131923ca59
commit
7e2bfce707
50
frontend-technical-prototype/README.md
Normal file
50
frontend-technical-prototype/README.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
[![Docker Repository on Quay](https://quay.io/repository/yldio/joyent-dashboard-frontend/status?token=ab124f42-c6c5-4023-9841-17ef74c8b7f1 "Docker Repository on Quay")](https://quay.io/repository/yldio/joyent-dashboard-frontend)
|
||||||
|
# Joyent Dashboard Frontend
|
||||||
|
|
||||||
|
## start
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
|
## test
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
## structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── src
|
||||||
|
│ ├── containers
|
||||||
|
│ ├── index.js
|
||||||
|
│ ├── root.js
|
||||||
|
│ └── state
|
||||||
|
│ ├── actions.js
|
||||||
|
│ ├── reducers
|
||||||
|
│ ├── store.js
|
||||||
|
│ └── thunks
|
||||||
|
├── static
|
||||||
|
├── locales
|
||||||
|
├── scripts
|
||||||
|
├── test
|
||||||
|
├── webpack
|
||||||
|
├── .babelrc
|
||||||
|
└── .eslintrc
|
||||||
|
```
|
||||||
|
|
||||||
|
- **src/index.js**: Renders `src/root.js` and bootstraps hot module reloading.
|
||||||
|
- **src/root.js**: The main component that wraps `react-redux`, `react-router` and `react-hot-loader`.
|
||||||
|
- **src/state/store.js**: Exports a function that creates a `redux` store instance with all the middlewares and reducers configured.
|
||||||
|
- **src/state/actions.js**: Not only exports all the actions available (declared in the file), but also goes through all the thunks and exports them.
|
||||||
|
- **src/state/thunks**: Directory to place thunks so that actions or reducers don't get too confusing.
|
||||||
|
- **src/state/reducers**: Each file here represents a reducer scope. So, `state.app` will be controlled in `reducers/app.js`.
|
||||||
|
- **locales**: Translation definitions for each locale supported.
|
||||||
|
- **scripts**: Utility scripts (e.g. building localizations).
|
||||||
|
- **test**: Self explanatory.
|
||||||
|
- **webpack**: Webpack configuration for multiple enviroments. Development configuration includes a dev-server and hot module replacement support.
|
||||||
|
- **.babelrc**: This babel configuration outputs ES2015 code, so it will produce code only for modern browsers.
|
||||||
|
Also, async/await is supported.
|
||||||
|
- **.eslintrc**:ESLint configuration. It's basically [semistandard](https://github.com/Flet/semistandard) with `space-before-function-paren` probited;
|
134
frontend-technical-prototype/package.json
Normal file
134
frontend-technical-prototype/package.json
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
{
|
||||||
|
"name": "joyent-portal-frontend",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"private": true,
|
||||||
|
"license": "private",
|
||||||
|
"main": "server/index.js",
|
||||||
|
"directories": {
|
||||||
|
"test": "test",
|
||||||
|
"lib": "src"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "webpack-dev-server --define process.env.BASELINE_GRID=false --open --config webpack/index.js ",
|
||||||
|
"production": "node server",
|
||||||
|
"lint": "make lint",
|
||||||
|
"lint-fix": "make lint-fix",
|
||||||
|
"test": "make test",
|
||||||
|
"open": "nyc report --reporter=html & open coverage/index.html",
|
||||||
|
"coverage": "nyc check-coverage --statements 100 --functions 100 --lines 100 --branches 100",
|
||||||
|
"build-locales": " CONFIG=$(pwd)/webpack/index.js babel-node scripts/build-locales",
|
||||||
|
"clean-static": "git check-ignore static/** | xargs rm"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tomgco/joyent-portal-ui": "alpha",
|
||||||
|
"constant-case": "^2.0.0",
|
||||||
|
"force-array": "^3.1.0",
|
||||||
|
"hapi": "^16.1.0",
|
||||||
|
"inert": "^4.1.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"lodash.find": "^4.6.0",
|
||||||
|
"lodash.flatten": "^4.4.0",
|
||||||
|
"lodash.get": "^4.4.2",
|
||||||
|
"lodash.isempty": "^4.4.0",
|
||||||
|
"lodash.template": "^4.4.0",
|
||||||
|
"moment": "^2.17.1",
|
||||||
|
"param-case": "^2.1.0",
|
||||||
|
"portal-api": "^1.0.0",
|
||||||
|
"querystring": "^0.2.0",
|
||||||
|
"react": "^15.4.2",
|
||||||
|
"react-a11y": "^0.3.3",
|
||||||
|
"react-dom": "^15.4.2",
|
||||||
|
"react-intl": "^2.2.3",
|
||||||
|
"react-intl-redux": "^0.4.1",
|
||||||
|
"react-redux": "^5.0.3",
|
||||||
|
"react-router": "4.0.0-beta.6",
|
||||||
|
"react-router-dom": "4.0.0-beta.6",
|
||||||
|
"react-select": "^1.0.0-rc.3",
|
||||||
|
"reduce-reducers": "^0.1.2",
|
||||||
|
"redux": "^3.6.0",
|
||||||
|
"redux-actions": "^1.2.2",
|
||||||
|
"redux-batched-actions": "^0.1.5",
|
||||||
|
"redux-form": "^6.5.0",
|
||||||
|
"redux-logger": "^2.8.1",
|
||||||
|
"redux-promise-middleware": "^4.2.0",
|
||||||
|
"redux-thunk": "^2.2.0",
|
||||||
|
"reselect": "^2.5.4",
|
||||||
|
"simple-statistics": "^2.5.0",
|
||||||
|
"styled-components": "^1.4.4",
|
||||||
|
"understood": "^1.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"ava": "^0.18.2",
|
||||||
|
"babel-cli": "^6.23.0",
|
||||||
|
"babel-core": "^6.23.1",
|
||||||
|
"babel-eslint": "^7.1.1",
|
||||||
|
"babel-loader": "^6.3.2",
|
||||||
|
"babel-plugin-inline-react-svg": "^0.2.0",
|
||||||
|
"babel-plugin-styled-components": "^1.0.0",
|
||||||
|
"babel-plugin-transform-class-properties": "^6.23.0",
|
||||||
|
"babel-plugin-transform-es2015-modules-commonjs": "^6.23.0",
|
||||||
|
"babel-plugin-transform-es2015-parameters": "^6.23.0",
|
||||||
|
"babel-plugin-transform-object-rest-spread": "^6.23.0",
|
||||||
|
"babel-plugin-transform-react-constant-elements": "^6.23.0",
|
||||||
|
"babel-plugin-transform-react-jsx": "^6.23.0",
|
||||||
|
"babel-plugin-transform-react-jsx-self": "^6.22.0",
|
||||||
|
"babel-plugin-transform-react-jsx-source": "^6.22.0",
|
||||||
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
|
"babel-plugin-webpack-alias": "^2.1.2",
|
||||||
|
"babel-plugin-webpack-loaders": "^0.9.0",
|
||||||
|
"babel-preset-env": "^1.1.10",
|
||||||
|
"babel-preset-react": "^6.23.0",
|
||||||
|
"babel-register": "^6.23.0",
|
||||||
|
"case-sensitive-paths-webpack-plugin": "^1.1.4",
|
||||||
|
"css-loader": "^0.26.2",
|
||||||
|
"enzyme": "^2.7.1",
|
||||||
|
"eslint": "^3.16.1",
|
||||||
|
"eslint-config-semistandard": "^7.0.0",
|
||||||
|
"eslint-config-standard": "^7.0.0",
|
||||||
|
"eslint-plugin-babel": "^4.1.0",
|
||||||
|
"eslint-plugin-import": "^2.2.0",
|
||||||
|
"eslint-plugin-jsx-a11y": "^4.0.0",
|
||||||
|
"eslint-plugin-promise": "^3.5.0",
|
||||||
|
"eslint-plugin-react": "^6.10.0",
|
||||||
|
"eslint-plugin-standard": "^2.0.1",
|
||||||
|
"file-loader": "^0.10.1",
|
||||||
|
"jsdom": "^9.11.0",
|
||||||
|
"json-loader": "^0.5.4",
|
||||||
|
"ncp": "^2.0.0",
|
||||||
|
"node-hook": "^0.4.0",
|
||||||
|
"nyc": "^10.1.2",
|
||||||
|
"pre-commit": "^1.2.2",
|
||||||
|
"react-addons-perf": "^15.4.2",
|
||||||
|
"react-addons-test-utils": "^15.4.2",
|
||||||
|
"react-dev-utils": "^0.5.1",
|
||||||
|
"react-perf": "^1.0.1",
|
||||||
|
"redux-ava": "^2.2.0",
|
||||||
|
"redux-perf-middleware": "^1.2.2",
|
||||||
|
"require-hacker": "^2.1.4",
|
||||||
|
"simple-mock": "^0.7.3",
|
||||||
|
"style-loader": "^0.13.2",
|
||||||
|
"stylelint": "^7.9.0",
|
||||||
|
"stylelint-config-standard": "^16.0.0",
|
||||||
|
"stylelint-processor-styled-components": "^0.0.4",
|
||||||
|
"tap-xunit": "^1.7.0",
|
||||||
|
"thenify": "^3.2.1",
|
||||||
|
"url-loader": "^0.5.8",
|
||||||
|
"webpack": "^2.2.1",
|
||||||
|
"webpack-dev-server": "^2.4.1",
|
||||||
|
"webpack-manifest-plugin": "^1.1.0",
|
||||||
|
"webpack-shell-plugin": "^0.5.0"
|
||||||
|
},
|
||||||
|
"ava": {
|
||||||
|
"failFast": true,
|
||||||
|
"cache": false,
|
||||||
|
"require": [
|
||||||
|
"./test/_hook.js"
|
||||||
|
],
|
||||||
|
"babel": "inherit"
|
||||||
|
},
|
||||||
|
"pre-commit": [
|
||||||
|
"lint",
|
||||||
|
"test",
|
||||||
|
"coverage"
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
import Container from '@ui/components/container';
|
||||||
|
import { breakpoints } from '@ui/shared/constants';
|
||||||
|
|
||||||
|
const LayoutContainer = styled(Container)`
|
||||||
|
padding: 2rem;
|
||||||
|
|
||||||
|
${breakpoints.large`
|
||||||
|
padding: 0;
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default LayoutContainer;
|
@ -0,0 +1 @@
|
|||||||
|
export { default as LayoutContainer } from './container';
|
@ -0,0 +1,79 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import flatten from 'lodash.flatten';
|
||||||
|
|
||||||
|
import Container from '@ui/components/container';
|
||||||
|
import Row from '@ui/components/row';
|
||||||
|
import Column from '@ui/components/column';
|
||||||
|
import { H2 } from '@ui/components/base-elements';
|
||||||
|
import NavLink from '@ui/components/nav-link';
|
||||||
|
import PropTypes from '@root/prop-types';
|
||||||
|
import { remcalc, unitcalc } from '@ui/shared/functions';
|
||||||
|
import { colors } from '@ui/shared/constants';
|
||||||
|
|
||||||
|
// Main Contonent Wrapper Styles
|
||||||
|
const StyledDiv = styled.div`
|
||||||
|
border-bottom: solid ${remcalc(1)} ${colors.base.grey};
|
||||||
|
padding: ${unitcalc(4.5)} 0 ${unitcalc(4.5)} 0;
|
||||||
|
margin-bottom: ${remcalc(18)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BreadcrumbA = styled(NavLink)`
|
||||||
|
text-decoration: none;
|
||||||
|
color: ${colors.base.primary};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BreadcrumbSpan = styled.span`
|
||||||
|
color: ${colors.base.text};
|
||||||
|
`;
|
||||||
|
|
||||||
|
function getNameLink(name) {
|
||||||
|
return flatten(name.map((part, i) => {
|
||||||
|
if (!part.name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const link = (
|
||||||
|
<BreadcrumbA key={part.pathname} to={part.pathname}>
|
||||||
|
{part.name}
|
||||||
|
</BreadcrumbA>
|
||||||
|
);
|
||||||
|
|
||||||
|
const key = `${part.pathname}${i}`;
|
||||||
|
const slash = (
|
||||||
|
<BreadcrumbSpan key={key}> / </BreadcrumbSpan>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (i === 0) ? link : [
|
||||||
|
slash,
|
||||||
|
link
|
||||||
|
];
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledH2 = styled(H2)`
|
||||||
|
color: ${colors.base.primary};
|
||||||
|
margin: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Breadcrumb = ({
|
||||||
|
name = []
|
||||||
|
}) => (
|
||||||
|
<Container>
|
||||||
|
<Row>
|
||||||
|
<Column xs={12}>
|
||||||
|
<StyledDiv>
|
||||||
|
<StyledH2>
|
||||||
|
{getNameLink(name)}
|
||||||
|
</StyledH2>
|
||||||
|
</StyledDiv>
|
||||||
|
</Column>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
Breadcrumb.propTypes = {
|
||||||
|
name: React.PropTypes.arrayOf(PropTypes.link)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Breadcrumb;
|
@ -0,0 +1,2 @@
|
|||||||
|
export { default as Breadcrumb } from './breadcrumb';
|
||||||
|
export { default as Menu } from './menu';
|
@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import Li from '@ui/components/horizontal-list/li';
|
||||||
|
import NavLink from '@ui/components/nav-link';
|
||||||
|
import { breakpoints } from '@ui/shared/constants';
|
||||||
|
import { remcalc } from '@ui/shared/functions';
|
||||||
|
import PropTypes from '@root/prop-types';
|
||||||
|
import Ul from '@ui/components/horizontal-list/ul';
|
||||||
|
import { LayoutContainer } from '@components/layout';
|
||||||
|
|
||||||
|
const StyledHorizontalList = styled(Ul)`
|
||||||
|
padding: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledHorizontalListItem = styled(Li)`
|
||||||
|
${breakpoints.smallOnly`
|
||||||
|
display: block;
|
||||||
|
`}
|
||||||
|
|
||||||
|
& + li {
|
||||||
|
margin-left: ${remcalc(21)};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Menu = ({
|
||||||
|
links
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const navLinks = links.map((link) => {
|
||||||
|
return (
|
||||||
|
<StyledHorizontalListItem key={link.name}>
|
||||||
|
<NavLink activeClassName='active' to={link.pathname}>
|
||||||
|
<FormattedMessage id={link.name} />
|
||||||
|
</NavLink>
|
||||||
|
</StyledHorizontalListItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO this could be any kind of nav, not just 'project-'...
|
||||||
|
return (
|
||||||
|
<LayoutContainer>
|
||||||
|
<StyledHorizontalList name='project-nav'>
|
||||||
|
{navLinks}
|
||||||
|
</StyledHorizontalList>
|
||||||
|
</LayoutContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Menu.propTypes = {
|
||||||
|
links: React.PropTypes.arrayOf(PropTypes.link)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Menu;
|
@ -0,0 +1,72 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
orgByIdSelector,
|
||||||
|
projectByIdSelector,
|
||||||
|
serviceByIdSelector
|
||||||
|
} from '@root/state/selectors';
|
||||||
|
import { Breadcrumb as BreadcrumbComponent } from '@components/navigation';
|
||||||
|
|
||||||
|
const Breadcrumb = ({
|
||||||
|
location,
|
||||||
|
match,
|
||||||
|
org,
|
||||||
|
project,
|
||||||
|
service
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const path = location.pathname.split('/');
|
||||||
|
|
||||||
|
const links = [{
|
||||||
|
name: org.name,
|
||||||
|
pathname: path.slice(0, 2).join('/')
|
||||||
|
}];
|
||||||
|
|
||||||
|
if(project) {
|
||||||
|
links.push({
|
||||||
|
name: project.name,
|
||||||
|
pathname: path.slice(0, 4).join('/')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(service) {
|
||||||
|
links.push({
|
||||||
|
name: service.name,
|
||||||
|
pathname: path.slice(0, 6).join('/')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add people etc
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BreadcrumbComponent name={links} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Breadcrumb.propTypes = {
|
||||||
|
location: React.PropTypes.object.isRequired,
|
||||||
|
match: React.PropTypes.object.isRequired,
|
||||||
|
org: React.PropTypes.object.isRequired,
|
||||||
|
project: React.PropTypes.object,
|
||||||
|
service: React.PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state, {
|
||||||
|
location,
|
||||||
|
match = {
|
||||||
|
params: {}
|
||||||
|
}
|
||||||
|
}) => ({
|
||||||
|
location,
|
||||||
|
match,
|
||||||
|
org: orgByIdSelector(match.params.org)(state),
|
||||||
|
project: projectByIdSelector(match.params.project)(state),
|
||||||
|
service: serviceByIdSelector(match.params.service)(state)
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(Breadcrumb);
|
@ -0,0 +1,3 @@
|
|||||||
|
export { default as Breadcrumb } from './breadcrumb';
|
||||||
|
export { default as Menu } from './menu';
|
||||||
|
export { default as Org } from './org';
|
@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
orgSectionsByIdSelector,
|
||||||
|
projectSectionsSelector,
|
||||||
|
serviceSectionsSelector
|
||||||
|
} from '@root/state/selectors';
|
||||||
|
import { Menu as MenuComponent } from '@components/navigation';
|
||||||
|
|
||||||
|
const Menu = (props) => {
|
||||||
|
|
||||||
|
const {
|
||||||
|
match,
|
||||||
|
sections
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const links = sections.map((section) => ({
|
||||||
|
name: section,
|
||||||
|
pathname: `${match.url}/${section}`
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuComponent links={links} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Menu.propTypes = {
|
||||||
|
match: React.PropTypes.object.isRequired,
|
||||||
|
sections: React.PropTypes.array.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state, {
|
||||||
|
match = {
|
||||||
|
params: {}
|
||||||
|
}
|
||||||
|
}) => ({
|
||||||
|
location,
|
||||||
|
match,
|
||||||
|
sections: match.params.service ?
|
||||||
|
serviceSectionsSelector(state) :
|
||||||
|
match.params.project ?
|
||||||
|
projectSectionsSelector(state) :
|
||||||
|
orgSectionsByIdSelector(match.params.org)(state)
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(Menu);
|
@ -0,0 +1,8 @@
|
|||||||
|
export { default as ServiceActivityFeed } from './activity-feed';
|
||||||
|
export { default as ServiceFirewall } from './firewall';
|
||||||
|
export { default as ServiceInstances } from './instances';
|
||||||
|
export { default as ServiceMetrics } from './metrics';
|
||||||
|
export { default as ServiceNetworks } from './networks';
|
||||||
|
export { default as ServiceManifest } from './service-manifest';
|
||||||
|
export { default as ServiceSummary } from './summary';
|
||||||
|
export { default as ServiceMetadata } from './tags-metadata';
|
@ -0,0 +1,46 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { addMetric, metricDurationChange } from '@state/actions';
|
||||||
|
import Metrics from '@containers/metrics';
|
||||||
|
|
||||||
|
import {
|
||||||
|
metricsByServiceIdSelector,
|
||||||
|
metricTypesSelector,
|
||||||
|
serviceByIdSelector
|
||||||
|
} from '@state/selectors';
|
||||||
|
|
||||||
|
const mapStateToProps = (state, {
|
||||||
|
match = {
|
||||||
|
params: {}
|
||||||
|
}
|
||||||
|
}) => ({
|
||||||
|
datasets: metricsByServiceIdSelector(match.params.service)(state),
|
||||||
|
metricTypes: metricTypesSelector(state),
|
||||||
|
service: serviceByIdSelector(match.params.service)(state)
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
addMetric: (service) => (metric) =>
|
||||||
|
dispatch(addMetric({
|
||||||
|
metric: metric,
|
||||||
|
service: service.uuid
|
||||||
|
})),
|
||||||
|
metricDurationChange: (service) => (duration, dataset) =>
|
||||||
|
dispatch(metricDurationChange({
|
||||||
|
duration,
|
||||||
|
dataset
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
|
||||||
|
...stateProps,
|
||||||
|
...dispatchProps,
|
||||||
|
...ownProps,
|
||||||
|
addMetric: dispatchProps.addMetric(stateProps.service),
|
||||||
|
metricDurationChange: dispatchProps.metricDurationChange(stateProps.service)
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
mergeProps
|
||||||
|
)(Metrics);
|
@ -0,0 +1,3 @@
|
|||||||
|
export { default as ServicesTopology } from './topology';
|
||||||
|
export { default as ServicesList } from './list';
|
||||||
|
export { default as ServicesView } from './view';
|
138
frontend-technical-prototype/src/containers/services/list.js
Normal file
138
frontend-technical-prototype/src/containers/services/list.js
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import PropTypes from '@root/prop-types';
|
||||||
|
import { LayoutContainer } from '@components/layout';
|
||||||
|
import ServiceItem from '@components/service/item';
|
||||||
|
import UnmanagedInstances from '@components/services/unmanaged-instances';
|
||||||
|
import { toggleTooltip } from '@state/actions';
|
||||||
|
import ServicesTooltip from '@components/services/tooltip';
|
||||||
|
import { subscribeMetric } from '@state/thunks';
|
||||||
|
|
||||||
|
import {
|
||||||
|
orgByIdSelector,
|
||||||
|
projectByIdSelector,
|
||||||
|
servicesByProjectIdSelector,
|
||||||
|
serviceUiTooltipSelector
|
||||||
|
} from '@state/selectors';
|
||||||
|
|
||||||
|
const StyledContainer = styled.div`
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// TMP - single source of truth
|
||||||
|
const duration = '5 minutes';
|
||||||
|
const interval = '15 seconds';
|
||||||
|
|
||||||
|
class Services extends React.Component {
|
||||||
|
// we DON'T want to unsubscribe once we started going
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.subscribeMetric(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
ref(name) {
|
||||||
|
this._refs = this._refs || {};
|
||||||
|
|
||||||
|
return (el) => {
|
||||||
|
this._refs[name] = el;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
org = {},
|
||||||
|
project = {},
|
||||||
|
services = [],
|
||||||
|
toggleTooltip = () => ({}),
|
||||||
|
uiTooltip = {}
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const onQuickActions = (evt, service) => {
|
||||||
|
const list = this._refs.container;
|
||||||
|
const listRect = list.getBoundingClientRect();
|
||||||
|
const button = evt.currentTarget;
|
||||||
|
const buttonRect = button.getBoundingClientRect();
|
||||||
|
|
||||||
|
const position = {
|
||||||
|
left: buttonRect.left - listRect.left
|
||||||
|
+ (buttonRect.right - buttonRect.left)/2,
|
||||||
|
top: buttonRect.bottom - listRect.top
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleTooltip({
|
||||||
|
service: service,
|
||||||
|
position: position,
|
||||||
|
data: {
|
||||||
|
serviceId: service.id,
|
||||||
|
orgId: org.id,
|
||||||
|
projectId: project.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTooltipBlur = (evt) => onQuickActions(evt, uiTooltip.service);
|
||||||
|
|
||||||
|
const serviceList = services.map((service) => (
|
||||||
|
<ServiceItem
|
||||||
|
key={service.uuid}
|
||||||
|
onQuickActions={onQuickActions}
|
||||||
|
org={org.id}
|
||||||
|
project={project.id}
|
||||||
|
service={service}
|
||||||
|
uiTooltip={uiTooltip}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
// TODO replace `false` with a check for existence unmanaged instances
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
const unmanagedInstances = false ? (
|
||||||
|
<UnmanagedInstances instances={0} />
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LayoutContainer>
|
||||||
|
{unmanagedInstances}
|
||||||
|
<StyledContainer>
|
||||||
|
<div ref={this.ref('container')}>
|
||||||
|
{serviceList}
|
||||||
|
<ServicesTooltip {...uiTooltip} onBlur={handleTooltipBlur} />
|
||||||
|
</div>
|
||||||
|
</StyledContainer>
|
||||||
|
</LayoutContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Services.propTypes = {
|
||||||
|
org: PropTypes.org,
|
||||||
|
project: PropTypes.project,
|
||||||
|
services: React.PropTypes.arrayOf(PropTypes.service),
|
||||||
|
toggleTooltip: React.PropTypes.func,
|
||||||
|
uiTooltip: React.PropTypes.object,
|
||||||
|
subscribeMetric: React.PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state, {
|
||||||
|
match = {
|
||||||
|
params: {}
|
||||||
|
},
|
||||||
|
push
|
||||||
|
}) => ({
|
||||||
|
org: orgByIdSelector(match.params.org)(state),
|
||||||
|
project: projectByIdSelector(match.params.project)(state),
|
||||||
|
services: servicesByProjectIdSelector(match.params.project, {
|
||||||
|
duration,
|
||||||
|
interval
|
||||||
|
})(state),
|
||||||
|
uiTooltip: serviceUiTooltipSelector(state)
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
toggleTooltip: (data) => dispatch(toggleTooltip(data)),
|
||||||
|
subscribeMetric: (payload) => dispatch(subscribeMetric(payload))
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(Services);
|
120
frontend-technical-prototype/src/index.js
Normal file
120
frontend-technical-prototype/src/index.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { IntlProvider } from 'react-intl-redux';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import qs from 'querystring';
|
||||||
|
import a11y from 'react-a11y';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import React from 'react';
|
||||||
|
import Perf from 'react-addons-perf';
|
||||||
|
|
||||||
|
import MockStateTesting from '@mock-states/testing';
|
||||||
|
import MockState from '@mock-states';
|
||||||
|
import LeakDatasets from './dataset-leak.json';
|
||||||
|
import NormalDatasets from './dataset-normal.json';
|
||||||
|
import Store from '@state/store';
|
||||||
|
import { isProduction } from '@utils';
|
||||||
|
import Router from '@root/routing';
|
||||||
|
|
||||||
|
if ( !isProduction() ) {
|
||||||
|
a11y(React, {
|
||||||
|
ReactDOM
|
||||||
|
});
|
||||||
|
|
||||||
|
window.Perf = Perf;
|
||||||
|
}
|
||||||
|
|
||||||
|
const states = {
|
||||||
|
all: MockState,
|
||||||
|
testing: MockStateTesting
|
||||||
|
};
|
||||||
|
|
||||||
|
const query = qs.parse(window.location.search.replace(/^\?/, ''));
|
||||||
|
const mockState = states[query.mock || 'all'];
|
||||||
|
|
||||||
|
// node_memory_rss_bytes
|
||||||
|
// node_memory_heap_total_bytes
|
||||||
|
// node_memory_heap_used_bytes
|
||||||
|
// process_heap_bytes
|
||||||
|
// process_resident_memory_bytes
|
||||||
|
// process_virtual_memory_bytes
|
||||||
|
// process_cpu_seconds_total
|
||||||
|
// process_cpu_system_seconds_total
|
||||||
|
// process_cpu_user_seconds_total
|
||||||
|
// node_lag_duration_milliseconds
|
||||||
|
// http_request_duration_milliseconds
|
||||||
|
|
||||||
|
// node_memory_rss_bytes
|
||||||
|
// node_memory_heap_total_bytes
|
||||||
|
// node_memory_heap_used_bytes
|
||||||
|
// process_heap_bytes
|
||||||
|
// process_resident_memory_bytes
|
||||||
|
// process_virtual_memory_bytes
|
||||||
|
// process_cpu_seconds_total
|
||||||
|
// process_cpu_system_seconds_total
|
||||||
|
// process_cpu_user_seconds_total
|
||||||
|
// node_lag_duration_milliseconds
|
||||||
|
// http_request_duration_milliseconds
|
||||||
|
|
||||||
|
// TMP - ensure datasets are at least 2 hrs long - START
|
||||||
|
import getTwoHourDatasets from './utils/two-hour-metric-datasets';
|
||||||
|
const leakTwoHourLongDatasets = getTwoHourDatasets(LeakDatasets);
|
||||||
|
const normalTwoHourLongDatasets = getTwoHourDatasets(NormalDatasets);
|
||||||
|
// TMP - ensure datasets are at least 2 hrs long - END
|
||||||
|
|
||||||
|
// TMP - plug fake metric data - START
|
||||||
|
const isCrazy = (uuid) => uuid === 'crazy-cpu' ||
|
||||||
|
uuid === 'crazy-disk' || uuid === 'crazy-memory';
|
||||||
|
|
||||||
|
const isCPU = (uuid) => uuid === 'crazy-cpu'
|
||||||
|
|| uuid === '3e6ee79a-7453-4fc6-b9da-7ae1e41138ec';
|
||||||
|
|
||||||
|
const isDisk = (uuid) => uuid === 'crazy-disk'
|
||||||
|
|| uuid === '4e6ee79a-7453-4fc6-b9da-7ae1e41138ed';
|
||||||
|
|
||||||
|
const isMemory = (uuid) => uuid === 'crazy-memory'
|
||||||
|
|| uuid === '6e6ee79a-7453-4fc6-b9da-7ae1e41138ed';
|
||||||
|
|
||||||
|
const getDataset = (twoHourLongDatasets, uuid) => {
|
||||||
|
if(isCPU(uuid)) {
|
||||||
|
return twoHourLongDatasets.process_cpu_seconds_total;
|
||||||
|
}
|
||||||
|
if(isDisk(uuid)) {
|
||||||
|
return twoHourLongDatasets.process_heap_bytes.map((sample) =>
|
||||||
|
[
|
||||||
|
sample[0],
|
||||||
|
sample[1]/1024/1024
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if(isMemory(uuid)) {
|
||||||
|
return twoHourLongDatasets.node_memory_heap_used_bytes.map((sample) =>
|
||||||
|
[
|
||||||
|
sample[0],
|
||||||
|
sample[1]/1024/1024
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const datasets = MockState.metrics.data.datasets.map((dataset, index) => {
|
||||||
|
|
||||||
|
const data = isCrazy(dataset.uuid) && dataset.uuid !== 'crazy-cpu' ?
|
||||||
|
getDataset(leakTwoHourLongDatasets, dataset.uuid) :
|
||||||
|
getDataset(normalTwoHourLongDatasets, dataset.uuid);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...dataset,
|
||||||
|
data: data
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
mockState.metrics.data.datasets = datasets;
|
||||||
|
// TMP - plug fake metric data - END
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={Store(mockState)}>
|
||||||
|
<IntlProvider>
|
||||||
|
{Router}
|
||||||
|
</IntlProvider>
|
||||||
|
</Provider>,
|
||||||
|
document.getElementById('root')
|
||||||
|
);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user