├── .babelrc
├── .cfignore
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .travis.yml
├── LICENSE
├── NOTICE
├── Procfile
├── Procfile.dev
├── README.md
├── app
├── api
│ ├── actual_lrps_api.js
│ ├── authorization_api.js
│ ├── base_api.js
│ ├── cells_api.js
│ ├── desired_lrps_api.js
│ └── receptor_api.js
├── components
│ ├── actual_lrp.js
│ ├── actual_lrp_list.js
│ ├── application.js
│ ├── canvas.js
│ ├── cell.js
│ ├── cells.js
│ ├── container.js
│ ├── desired_lrp.js
│ ├── desired_lrp_detail.js
│ ├── desired_lrp_info.js
│ ├── desired_lrp_list.js
│ ├── filter.js
│ ├── footer.js
│ ├── form_group.js
│ ├── header.js
│ ├── launch_modal.js
│ ├── page.js
│ ├── scaling.js
│ ├── setup.js
│ ├── sidebar.js
│ ├── sidebar_container.js
│ ├── sidebar_header.js
│ ├── svg.js
│ ├── timeago.js
│ └── zones.js
├── helpers
│ ├── application_helper.js
│ ├── array_helper.js
│ ├── lrp_helper.js
│ ├── string_helper.js
│ └── url_helper.js
├── images
│ └── favicon.ico
├── mixins
│ ├── google_analytics_mixin.js
│ ├── hover_actual_lrp_mixin.js
│ ├── hover_desired_lrp_mixin.js
│ ├── receptor_mixin.js
│ └── receptor_stream_mixin.js
├── stylesheets
│ ├── _animations.scss
│ ├── _backgrounds.scss
│ ├── _cell.scss
│ ├── _containers.scss
│ ├── _errors.scss
│ ├── _forms.scss
│ ├── _layout.scss
│ ├── _links.scss
│ ├── _lists.scss
│ ├── _mixins.scss
│ ├── _scaling.scss
│ ├── _scrim.scss
│ ├── _setup.scss
│ ├── _sidebar.scss
│ ├── _variables.scss
│ ├── _zones.scss
│ └── application.scss
├── svg
│ ├── brand.svg
│ └── logo.svg
└── vendor
│ └── google_analytics.js
├── config
├── application.json
├── colors.json
├── deploy.json
├── development.json
├── production.json
├── test.json
├── webpack.js
└── webpack
│ ├── development.js
│ ├── production.js
│ └── test.js
├── gulpfile.js
├── helpers
└── application_helper.js
├── index.js
├── lattice-xray.png
├── manifest-ketchup.yml
├── manifest-staging.yml
├── manifest.yml
├── package.json
├── public
└── .gitkeep
├── screenshot.png
├── scripts
├── blue-green-deploy.sh
└── open_spec_or_impl.sh
├── server
├── app.js
├── bootstrap.js
├── components
│ └── layout.js
├── config.js
├── helpers
│ └── asset_helper.js
└── middleware
│ ├── application_middleware.js
│ ├── auth_middleware.js
│ ├── component.js
│ ├── fake_api.js
│ ├── receptor_url.js
│ └── setup_middleware.js
├── spec
├── app
│ ├── api
│ │ ├── actual_lrps_api_spec.js
│ │ ├── authorization_api_spec.js
│ │ ├── base_api_spec.js
│ │ ├── cells_api_spec.js
│ │ ├── desired_lrps_api_spec.js
│ │ └── receptor_api_spec.js
│ ├── components
│ │ ├── actual_lrp_list_spec.js
│ │ ├── actual_lrp_spec.js
│ │ ├── application_spec.js
│ │ ├── cell_spec.js
│ │ ├── cells_spec.js
│ │ ├── container_spec.js
│ │ ├── desired_lrp_detail_spec.js
│ │ ├── desired_lrp_info_spec.js
│ │ ├── desired_lrp_list_spec.js
│ │ ├── desired_lrp_spec.js
│ │ ├── filter_spec.js
│ │ ├── form_group_spec.js
│ │ ├── launch_modal_spec.js
│ │ ├── page_spec.js
│ │ ├── scaling_spec.js
│ │ ├── setup_spec.js
│ │ ├── sidebar_container_spec.js
│ │ ├── sidebar_header_spec.js
│ │ ├── sidebar_spec.js
│ │ ├── svg_spec.js
│ │ └── zones_spec.js
│ ├── feature
│ │ ├── edit_receptor_url_spec.js
│ │ ├── filtering_spec.js
│ │ └── hover_spec.js
│ ├── helpers
│ │ ├── application_helper_spec.js
│ │ ├── array_helper_spec.js
│ │ ├── lrp_helper_spec.js
│ │ ├── string_helper_spec.js
│ │ └── url_helper_spec.js
│ ├── mixins
│ │ ├── hover_desired_lrp_mixin_spec.js
│ │ ├── receptor_mixin_spec.js
│ │ └── receptor_stream_mixin_spec.js
│ ├── spec_helper.js
│ └── support
│ │ ├── matchers.js
│ │ └── react_helper.js
├── factories
│ ├── actual_lrp.js
│ ├── cell.js
│ ├── desired_lrp.js
│ ├── modification_tag.js
│ └── route.js
├── server
│ ├── app_spec.js
│ ├── helpers
│ │ └── asset_helper_spec.js
│ ├── middleware
│ │ └── receptor_url_spec.js
│ ├── spec_helper.js
│ └── support
│ │ └── supertest_promisified.js
├── spec.js
├── spec_helper.js
└── support
│ ├── deferred.js
│ ├── jasmine.css
│ └── mock_throttle.js
├── tasks
├── assets.js
├── deploy.js
├── foreman.js
├── gulptasks.js
├── jasmine.js
├── lint.js
└── server.js
└── vendor
└── pui-v1.4.0
└── pui-variables.scss
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 1
3 | }
--------------------------------------------------------------------------------
/.cfignore:
--------------------------------------------------------------------------------
1 | .env
2 | .idea/*
3 | /log/*
4 | /node_modules
5 | /spec/app/*
6 | /spec/server/*
7 | /spec/support/*
8 | /tmp/*
9 | /tasks/*
10 | /vendor/*
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | app/canvas/**/*.js
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "phantomjs": true,
5 | "node": true,
6 | "jasmine": true
7 | },
8 |
9 | "ecmaFeatures": {
10 | "arrowFunctions": true,
11 | "blockBindings": true,
12 | "binaryLiterals": true,
13 | "classes": true,
14 | "defaultParams": true,
15 | "destructuring": true,
16 | "forOf": true,
17 | "generators": true,
18 | "jsx": true,
19 | "objectLiteralComputedProperties": true,
20 | "objectLiteralDuplicateProperties": true,
21 | "objectLiteralShorthandMethods": true,
22 | "objectLiteralShorthandProperties": true,
23 | "octalLiterals": true,
24 | "regexYFlag": true,
25 | "regexUFlag": true,
26 | "restParams": true,
27 | "spread": true,
28 | "superInFunctions": true,
29 | "templateStrings": true,
30 | "unicodeCodePointEscapes": true
31 | },
32 |
33 | "globals": {
34 | "xray": true,
35 | "root": true
36 | },
37 |
38 | "parser": "babel-eslint",
39 |
40 | "plugins": [
41 | "react"
42 | ],
43 |
44 | "rules": {
45 | "camelcase": 0,
46 | "curly": 0,
47 | "eol-last": 0,
48 | "no-empty": 0,
49 | "no-undef": 0,
50 | "no-path-concat": 0,
51 | "no-shadow": 0,
52 | "no-underscore-dangle": 0,
53 | "no-unused-expressions": 0,
54 | "quotes": [1, "single"],
55 | "react/jsx-uses-vars": 1,
56 | "react/jsx-no-undef": 1,
57 | "react/jsx-quotes": [1, "double", "avoid-escape"],
58 | "react/no-did-mount-set-state": 0,
59 | "react/no-did-update-set-state": 1,
60 | "react/prop-types": [1, {ignore: ['children', 'className', 'id', 'style']}],
61 | "react/self-closing-comp": 1,
62 | "react/wrap-multilines": 1,
63 | "strict": 0
64 | }
65 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/*
2 | public/*
3 | node_modules
4 | .env
5 | tmp
6 | npm-debug.log
7 | *.iml
8 | .ruby-version
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | - 0.12
5 | env:
6 | - DEVELOPMENT=true
7 | before_install:
8 | - wget https://s3.amazonaws.com/travis-phantomjs/phantomjs-2.0.0-ubuntu-12.04.tar.bz2
9 | - tar xjf phantomjs-2.0.0-ubuntu-12.04.tar.bz2
10 | - sudo rm -rf /usr/local/phantomjs/bin/phantomjs
11 | - sudo mv phantomjs /usr/local/phantomjs/bin/phantomjs
12 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | xray 1.0.0
2 |
3 | Copyright (c) 2015 Pivotal Software, Inc. All Rights Reserved.
4 |
5 | This product is licensed to you under the Apache License, Version 2.0 (the "License").
6 | You may not use this product except in compliance with the License.
7 |
8 | This product may include a number of subcomponents with separate copyright notices
9 | and license terms. Your use of these subcomponents is subject to the terms and
10 | conditions of the subcomponent's license, as noted in the LICENSE file.
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm start
--------------------------------------------------------------------------------
/Procfile.dev:
--------------------------------------------------------------------------------
1 | web: gulp s
2 | jasmine: gulp jasmine
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #XRAY
2 | Peer into the Lattice
3 |
4 | [](https://travis-ci.org/pivotal-cf-experimental/xray)
5 |
6 | 
7 | 
8 |
9 | ##Installation
10 | To get started with xray:
11 | ```sh
12 | git clone git@github.com:pivotal-cf-experimental/xray.git
13 | cd xray
14 | npm install
15 | RECEPTOR_URL='http://receptor.example.com' node_modules/.bin/gulp s
16 | ```
17 | There should now be a server listening on port 3000 that will visualize the state of the lattice instance at `RECEPTOR_URL`
18 |
19 | If you would like basic auth, use
20 | ```sh
21 | XRAY_USER='user' XRAY_PASSWORD='password' RECEPTOR_URL='http://receptor.example.com' node_module/.bin/gulp s
22 | ```
23 | where you are free to choose user and password.
24 |
25 | ##Deployment
26 |
27 | To deploy to your own CF, follow the steps in `Installation`, make sure your cf cli is logged in, then:
28 | ```
29 | NODE_ENV=production gulp assets
30 | cf push
31 | ```
32 |
33 | For a blue-green deployment, you can add an entry to `config/deploy.json` and use
34 | ```
35 | ENV=KEY_IN_DEPLOY.JSON node_modules/.bin/gulp deploy
36 | ```
37 |
38 | ##Development
39 |
40 | To run server:
41 | ```sh
42 | gulp s
43 | ```
44 |
45 | To run tests:
46 | ```sh
47 | npm test
48 | ```
49 |
50 | This assumes you have phantomjs installed in your environment.
51 |
52 | (c) Copyright 2015 Pivotal Software, Inc. All Rights Reserved.
53 |
--------------------------------------------------------------------------------
/app/api/actual_lrps_api.js:
--------------------------------------------------------------------------------
1 | var BaseApi = require('./base_api');
2 |
3 | var ActualLrpsApi = {
4 | fetch() {
5 | return BaseApi.fetch('actual_lrps').then(actualLrps => ({actualLrps}));
6 | }
7 | };
8 |
9 | module.exports = ActualLrpsApi;
--------------------------------------------------------------------------------
/app/api/authorization_api.js:
--------------------------------------------------------------------------------
1 | var BaseApi = require('./base_api');
2 |
3 | var AuthorizationApi = {
4 | create() {
5 | return BaseApi.fetch('auth_cookie', {method: 'post'});
6 | }
7 | };
8 |
9 | module.exports = AuthorizationApi;
--------------------------------------------------------------------------------
/app/api/base_api.js:
--------------------------------------------------------------------------------
1 | var request = require('superagent');
2 | var {getCredentials} = require('../helpers/url_helper');
3 | var join = require('url-join');
4 |
5 | var baseApiUrl = null;
6 |
7 | var BaseApi = {
8 | get baseUrl() { return baseApiUrl; },
9 |
10 | set baseUrl(u) { baseApiUrl = u; },
11 |
12 | fetch(route, options = {}) {
13 | var {method = 'get'} = options;
14 | var {user, password, url} = getCredentials(this.baseUrl);
15 | return new Promise(function(resolve, reject) {
16 | request[method](join(url, 'v1', route))
17 | .auth(user, password)
18 | .withCredentials()
19 | .accept('json')
20 | .end(function(err, res) {
21 | if (err) return reject(err);
22 | resolve(res.body);
23 | }
24 | );
25 | });
26 | }
27 | };
28 |
29 | module.exports = BaseApi;
--------------------------------------------------------------------------------
/app/api/cells_api.js:
--------------------------------------------------------------------------------
1 | var BaseApi = require('./base_api');
2 |
3 | var CellsApi = {
4 | fetch() {
5 | return BaseApi.fetch('cells').then(cells => ({cells}));
6 | }
7 | };
8 |
9 | module.exports = CellsApi;
--------------------------------------------------------------------------------
/app/api/desired_lrps_api.js:
--------------------------------------------------------------------------------
1 | var BaseApi = require('./base_api');
2 |
3 | var DesiredLrpsApi = {
4 | fetch() {
5 | return BaseApi.fetch('desired_lrps').then(function(desiredLrps) {
6 | return {desiredLrps: desiredLrps.sort((a, b) => a.process_guid < b.process_guid ? -1 : 1)};
7 | });
8 | }
9 | };
10 |
11 | module.exports = DesiredLrpsApi;
--------------------------------------------------------------------------------
/app/api/receptor_api.js:
--------------------------------------------------------------------------------
1 | var ActualLrpsApi = require('./actual_lrps_api');
2 | var CellsApi = require('./cells_api');
3 | var DesiredLrpsApi = require('./desired_lrps_api');
4 |
5 | var ReceptorApi = {
6 | fetch() {
7 | return Promise.all([ActualLrpsApi.fetch(), CellsApi.fetch(), DesiredLrpsApi.fetch()]).then(function(responses) {
8 | var [{actualLrps}, {cells}, {desiredLrps}] = responses;
9 | return {actualLrps, cells, desiredLrps};
10 | });
11 | }
12 | };
13 |
14 | module.exports = ReceptorApi;
--------------------------------------------------------------------------------
/app/components/actual_lrp.js:
--------------------------------------------------------------------------------
1 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
2 | var HoverActualLrpMixin = require('../mixins/hover_actual_lrp_mixin');
3 | var React = require('react');
4 | var Timeago = require('./timeago');
5 | var classnames = require('classnames');
6 |
7 | var types = React.PropTypes;
8 |
9 | var ActualLrp = React.createClass({
10 | mixins: [PureRenderMixin, HoverActualLrpMixin],
11 |
12 | propTypes: {
13 | actualLrp: types.object.isRequired,
14 | $hoverActualLrp: types.object.isRequired,
15 | $hoverSidebarActualLrp: types.object.isRequired
16 | },
17 |
18 | ignorePureRenderProps: ['$hoverActualLrp', '$hoverSidebarActualLrp'],
19 |
20 | renderLrpState({state, cellId, placementError}) {
21 | state = state && state.toLowerCase();
22 | if(placementError) {
23 | return (
24 |
{state}: {placementError}
25 | );
26 | }
27 | return [
28 | ({state} ),
29 | ({cellId} )
30 | ];
31 | },
32 |
33 | render() {
34 | var {actualLrp, className} = this.props;
35 | var {cell_id: cellId, index, since, state, placement_error: placementError} = actualLrp;
36 | var claimed = state === 'CLAIMED';
37 | var faded = state === 'UNCLAIMED' && !placementError;
38 | var crashed = state === 'CRASHED' || placementError;
39 |
40 | var classes = classnames(
41 | className,
42 | {
43 | 'actual-lrp': true,
44 | 'error': crashed,
45 | faded, claimed
46 | }
47 | );
48 |
49 | return (
50 |
51 | {index}
52 | {this.renderLrpState({state, cellId, placementError})}
53 |
54 |
55 | );
56 | }
57 | });
58 |
59 | module.exports = ActualLrp;
--------------------------------------------------------------------------------
/app/components/actual_lrp_list.js:
--------------------------------------------------------------------------------
1 | var ActualLrp = require('./actual_lrp');
2 | var classnames = require('classnames');
3 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
4 | var React = require('react');
5 |
6 | var types = React.PropTypes;
7 |
8 | var ActualLrpList = React.createClass({
9 | mixins: [PureRenderMixin],
10 |
11 | propTypes: {
12 | actualLrps: types.array.isRequired,
13 | $hoverActualLrp: types.object.isRequired,
14 | $hoverSidebarActualLrp: types.object.isRequired
15 | },
16 |
17 | renderActualLrps() {
18 | var {$hoverActualLrp, $hoverSidebarActualLrp} = this.props;
19 | return this.props.actualLrps.map(function(actualLrp) {
20 | var className = classnames({hover: $hoverSidebarActualLrp.get() === actualLrp});
21 | return ( );
22 | });
23 | },
24 |
25 | render() {
26 | return (
27 |
28 |
29 | {this.renderActualLrps()}
30 |
31 |
32 | );
33 | }
34 | });
35 |
36 | module.exports = ActualLrpList;
--------------------------------------------------------------------------------
/app/components/application.js:
--------------------------------------------------------------------------------
1 | require('babel/polyfill');
2 | var Cursor = require('pui-cursor');
3 | var googleAnalyticsMixin = require('../mixins/google_analytics_mixin');
4 | var Layout = require('../../server/components/layout');
5 | var Page = require('./page');
6 | var React = require('react');
7 | var {PortalDestination} = require('pui-react-portals');
8 |
9 | var types = React.PropTypes;
10 |
11 | var Application = React.createClass({
12 | mixins: [googleAnalyticsMixin],
13 |
14 | propTypes: {
15 | config: types.object.isRequired
16 | },
17 |
18 | childContextTypes: {
19 | colors: types.array.isRequired
20 | },
21 |
22 | getChildContext: function() {
23 | return {colors: this.props.config.colors};
24 | },
25 |
26 | getInitialState() {
27 | return {
28 | receptor: {
29 | cells: [],
30 | desiredLrps: [],
31 | actualLrps: [],
32 | actualLrpsByProcessGuid: {},
33 | actualLrpsByCellId: {},
34 | desiredLrpsByProcessGuid: {}
35 | },
36 | scaling: 'memory_mb',
37 | selection: {
38 | hoverDesiredLrp: null,
39 | selectedDesiredLrp: null,
40 | hoverActualLrp: null,
41 | filteredLrps: {}
42 | },
43 | sidebar: {
44 | filter: '',
45 | sidebarCollapsed: false,
46 | hoverActualLrp: null
47 | },
48 | receptorUrl: this.props.config.receptorUrl
49 | };
50 | },
51 |
52 | componentDidUpdate() {
53 | var {receptor, selection, sidebar} = this.state;
54 | Object.assign(xray, {receptor, selection, sidebar});
55 | },
56 |
57 | render() {
58 | var {receptorUrl, receptor, sidebar, selection, scaling} = this.state;
59 | var $receptor = new Cursor(receptor, receptor => this.setState({receptor}));
60 | var $sidebar = new Cursor(sidebar, sidebar => this.setState({sidebar}));
61 | var $selection = new Cursor(selection, selection => this.setState({selection}));
62 | var $scaling = new Cursor(scaling, scaling => this.setState({scaling}));
63 | return (
64 |
68 | );
69 | }
70 | });
71 |
72 | Layout.init(Application);
73 |
74 | module.exports = Application;
75 |
--------------------------------------------------------------------------------
/app/components/canvas.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var types = React.PropTypes;
3 |
4 | var Canvas = React.createClass({
5 | propTypes: {
6 | src: types.func.isRequired,
7 | height: types.number.isRequired,
8 | width: types.number.isRequired
9 | },
10 |
11 | componentDidMount() {
12 | var {src, width, height} = this.props;
13 | var {canvas} = this.refs;
14 | var canvasNode = canvas.getDOMNode();
15 | var {pixelRatio = 1} = window;
16 | canvasNode.width = width * pixelRatio;
17 | canvasNode.height = height * pixelRatio;
18 | var ctx = canvasNode.getContext('2d');
19 | ctx.scale(pixelRatio, pixelRatio);
20 | src(ctx);
21 | },
22 |
23 | render() {
24 | var {height, width, className} = this.props;
25 | var style = {width: `${width}px`, height: `${height}px`};
26 | return ;
27 | }
28 | });
29 |
30 | module.exports = Canvas;
--------------------------------------------------------------------------------
/app/components/cell.js:
--------------------------------------------------------------------------------
1 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
2 | var Container = require('./container');
3 | var React = require('react');
4 | var classnames = require('classnames');
5 |
6 | var types = React.PropTypes;
7 |
8 | var Cell = React.createClass({
9 | mixins: [PureRenderMixin],
10 |
11 | propTypes: {
12 | actualLrps: types.array,
13 | cell: types.object.isRequired,
14 | className: types.string,
15 | scaling: types.string.isRequired,
16 | $receptor: types.object.isRequired,
17 | $selection: types.object.isRequired,
18 | $sidebar: types.object.isRequired
19 | },
20 |
21 | getDesiredLrp(actualLrp) {
22 | var {$receptor} = this.props;
23 | var {desiredLrpsByProcessGuid} = $receptor.get();
24 | return desiredLrpsByProcessGuid && desiredLrpsByProcessGuid[actualLrp.process_guid];
25 | },
26 |
27 | getSelectionClasses({actualLrp, desiredLrp}) {
28 | var {$selection} = this.props;
29 | var {selectedDesiredLrp, hoverDesiredLrp, hoverActualLrp, filteredLrps} = $selection.get();
30 | var isFiltered = !selectedDesiredLrp && !hoverDesiredLrp && filteredLrps && !!filteredLrps[actualLrp.process_guid];
31 | return {
32 | selected: isFiltered || selectedDesiredLrp === desiredLrp,
33 | hover: hoverDesiredLrp === desiredLrp,
34 | highlight: hoverActualLrp === actualLrp
35 | };
36 | },
37 |
38 | render() {
39 | var {cell, actualLrps, scaling, style, $selection, $sidebar} = this.props;
40 | var denominator = scaling === 'containers' ? 50 : cell.capacity[scaling];
41 | var containers = actualLrps && actualLrps.map(function(actualLrp) {
42 | var desiredLrp = this.getDesiredLrp(actualLrp);
43 | var className = classnames(this.getSelectionClasses({actualLrp, desiredLrp}));
44 | var props = {actualLrp, denominator, desiredLrp, scaling, $selection, $sidebar, className};
45 | return ();
46 | }, this);
47 |
48 | return (
49 |
50 | {containers}
51 |
52 | );
53 | }
54 | });
55 |
56 | module.exports = Cell;
--------------------------------------------------------------------------------
/app/components/cells.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var Cell = require('./cell');
3 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
4 |
5 | var types = React.PropTypes;
6 |
7 | var Cells = React.createClass({
8 | mixins: [PureRenderMixin],
9 |
10 | propTypes: {
11 | cells: types.array,
12 | scaling: types.string.isRequired,
13 | $receptor: types.object,
14 | $selection: types.object,
15 | $sidebar: types.object
16 | },
17 |
18 | render() {
19 | var {cells, scaling, $receptor, $selection, $sidebar} = this.props;
20 | cells = cells && cells.map(function(cell) {
21 | var key = cell.cell_id;
22 | var actualLrps = $receptor.get('actualLrpsByCellId', key) || [];
23 | var props = {actualLrps, cell, scaling, $receptor, $selection, $sidebar, key};
24 | return ( | );
25 | }, this);
26 |
27 | return (
28 |
31 | );
32 | }
33 | });
34 |
35 | module.exports = Cells;
--------------------------------------------------------------------------------
/app/components/container.js:
--------------------------------------------------------------------------------
1 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
2 | var {getHostname} = require('../helpers/lrp_helper');
3 | var HoverDesiredLrpMixin = require('../mixins/hover_desired_lrp_mixin');
4 | var {pickColor} = require('../helpers/application_helper');
5 | var classnames = require('classnames');
6 | var React = require('react');
7 | var {OverlayTrigger} = require('pui-react-overlay-trigger');
8 | var {Tooltip} = require('pui-react-tooltip');
9 | var DesiredLrpInfo = require('./desired_lrp_info');
10 |
11 | var types = React.PropTypes;
12 |
13 | var Container = React.createClass({
14 | mixins: [PureRenderMixin, HoverDesiredLrpMixin],
15 |
16 | propTypes: {
17 | actualLrp: types.object.isRequired,
18 | desiredLrp: types.object,
19 | denominator: types.number.isRequired,
20 | scaling: types.string.isRequired,
21 | $selection: types.object.isRequired,
22 | $sidebar: types.object.isRequired
23 | },
24 |
25 | contextTypes: {
26 | colors: types.array.isRequired
27 | },
28 |
29 | ignorePureRenderProps: ['$selection', '$sidebar'],
30 |
31 | renderToolTip() {
32 | var {actualLrp, desiredLrp} = this.props;
33 | if(!desiredLrp) { return ( ); }
34 | return ( );
35 | },
36 |
37 | render() {
38 | var {denominator, desiredLrp, className, scaling, actualLrp} = this.props;
39 | var {state, instance_guid: instanceGuid, modification_tag: {epoch: key}, process_guid: processGuid} = actualLrp;
40 |
41 | var flex;
42 | var undesired;
43 | var backgroundColor;
44 | var width;
45 |
46 | if (!desiredLrp) {
47 | undesired = true;
48 | backgroundColor = null;
49 | } else {
50 | if (scaling !== 'containers') {
51 | var numerator = desiredLrp[scaling];
52 | var percentWidth = numerator / denominator;
53 | width = `${percentWidth * 100}%`;
54 | flex = numerator === 0;
55 | }
56 | backgroundColor = pickColor(this.context.colors, getHostname(desiredLrp) || processGuid);
57 | }
58 |
59 | var style = {width, backgroundColor};
60 | className = classnames(
61 | className,
62 | {
63 | 'app-container': true,
64 | claimed: state === 'CLAIMED',
65 | flex,
66 | undesired
67 | });
68 | var props = {className, role: 'button', style, key, 'data-instance-guid': instanceGuid, onClick: this.onClick, onMouseEnter: this.onMouseEnter, onMouseLeave: this.onMouseLeave};
69 | return (
70 |
71 | {processGuid}
72 |
73 | );
74 | }
75 | });
76 |
77 | module.exports = Container;
--------------------------------------------------------------------------------
/app/components/desired_lrp.js:
--------------------------------------------------------------------------------
1 | var classnames = require('classnames');
2 | var HoverDesiredLrpMixin = require('../mixins/hover_desired_lrp_mixin');
3 | var Media = require('pui-react-media').Media;
4 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
5 | var React = require('react');
6 | var SidebarContainer = require('./sidebar_container');
7 | var DesiredLrpInfo = require('./desired_lrp_info');
8 |
9 | var types = React.PropTypes;
10 |
11 | var DesiredLrp = React.createClass({
12 | mixins: [PureRenderMixin, HoverDesiredLrpMixin],
13 |
14 | propTypes: {
15 | desiredLrp: types.object.isRequired,
16 | actualLrps: types.array.isRequired,
17 | sidebarCollapsed: types.bool,
18 | tag: types.string,
19 | $selection: types.object.isRequired,
20 | $sidebar: types.object.isRequired
21 | },
22 |
23 | getDefaultProps() {
24 | return {tag: 'div'};
25 | },
26 |
27 | contextTypes: {
28 | colors: types.array.isRequired
29 | },
30 |
31 | ignorePureRenderProps: ['$selection'],
32 |
33 | render() {
34 | var {actualLrps, desiredLrp, className, sidebarCollapsed, tag: Tag} = this.props;
35 |
36 | var {process_guid: processGuid} = desiredLrp;
37 | var claimed = actualLrps.some(({state}) => state === 'CLAIMED');
38 | var instancesRunning = actualLrps.filter(({state}) => state === 'RUNNING').length;
39 | var instancesError = instancesRunning < desiredLrp.instances;
40 | var desiredLrpInfo = ( );
41 | className = classnames(className, 'desired-lrp', {error: instancesError});
42 | return (
43 |
44 | } key={processGuid} className="man">
45 | {desiredLrpInfo}
46 |
47 |
48 | );
49 | }
50 | });
51 |
52 | module.exports = DesiredLrp;
53 |
--------------------------------------------------------------------------------
/app/components/desired_lrp_detail.js:
--------------------------------------------------------------------------------
1 | var ActualLrpList = require('./actual_lrp_list');
2 | var DesiredLrp = require('./desired_lrp');
3 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
4 | var React = require('react');
5 |
6 | var types = React.PropTypes;
7 |
8 | var DesiredLrpDetail = React.createClass({
9 | mixins: [PureRenderMixin],
10 |
11 | propTypes: {
12 | $receptor: types.object.isRequired,
13 | $selection: types.object.isRequired,
14 | $sidebar: types.object.isRequired
15 | },
16 |
17 | render() {
18 | var {$receptor, $selection, $sidebar} = this.props;
19 | var {actualLrpsByProcessGuid = {}, desiredLrpsByProcessGuid = {}} = $receptor.get();
20 | var {selectedDesiredLrp} = $selection.get();
21 | var desiredLrp = selectedDesiredLrp && desiredLrpsByProcessGuid[selectedDesiredLrp.process_guid];
22 | var isDeleted = false;
23 | if(!desiredLrp) {
24 | if(selectedDesiredLrp) {
25 | desiredLrp = selectedDesiredLrp;
26 | isDeleted = true;
27 | } else {
28 | return null;
29 | }
30 | }
31 |
32 | var actualLrps = actualLrpsByProcessGuid[desiredLrp.process_guid] || [];
33 | var $hoverActualLrp = $selection.refine('hoverActualLrp');
34 | var $hoverSidebarActualLrp = $sidebar.refine('hoverActualLrp');
35 | return (
36 |
37 |
38 | {isDeleted &&
This process has been deleted. Information in this panel is out of date. }
39 |
40 |
41 | );
42 | }
43 | });
44 |
45 | module.exports = DesiredLrpDetail;
--------------------------------------------------------------------------------
/app/components/desired_lrp_info.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var prettyBytes = require('pretty-bytes');
3 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
4 | var types = React.PropTypes;
5 | var {getRoutes} = require('../helpers/lrp_helper');
6 |
7 | function stopPropagation(e) {
8 | e.stopPropagation();
9 | }
10 |
11 | function links(array) {
12 | return array && array.map((hostname, i) => {hostname} );
13 | }
14 |
15 | var Routes = React.createClass({
16 | mixins: [PureRenderMixin],
17 |
18 | propTypes: {
19 | routes: types.array.isRequired
20 | },
21 |
22 | render() {
23 | var {routes} = this.props;
24 | if (routes.length === 1) {
25 | routes = ({links(routes[0].hostnames)}
);
26 | } else {
27 | routes = routes.map(function({port, hostnames}, i) {
28 | return (
29 |
30 | {port}:
31 | {links(hostnames)}
32 | );
33 | });
34 | routes = ();
35 | }
36 | return ({routes}
);
37 | }
38 | });
39 |
40 | var DesiredLrpInfo = React.createClass({
41 | propTypes: {
42 | actualLrps: types.array.isRequired,
43 | desiredLrp: types.object.isRequired
44 | },
45 |
46 | render() {
47 | var {actualLrps, desiredLrp} = this.props;
48 |
49 | var routes = getRoutes(desiredLrp);
50 | var {disk_mb: disk, memory_mb: memory, process_guid: processGuid} = desiredLrp;
51 | disk = prettyBytes(disk * 1000000);
52 | memory = prettyBytes(memory * 1000000);
53 | var instancesRunning = actualLrps.filter(({state}) => state === 'RUNNING').length;
54 | var instances = `${instancesRunning}/${desiredLrp.instances}`;
55 |
56 | return (
57 |
58 | {processGuid}
59 | {routes && }
60 |
61 | {instances}
62 | (M: {memory} D: {disk})
63 |
64 |
65 | );
66 | }
67 | });
68 |
69 | module.exports = DesiredLrpInfo;
--------------------------------------------------------------------------------
/app/components/desired_lrp_list.js:
--------------------------------------------------------------------------------
1 | var DesiredLrp = require('./desired_lrp');
2 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
3 | var React = require('react');
4 | var SidebarHeader = require('./sidebar_header');
5 | var classnames = require('classnames');
6 |
7 | var types = React.PropTypes;
8 |
9 | var DesiredLrpList = React.createClass({
10 | mixins: [PureRenderMixin],
11 |
12 | propTypes: {
13 | $receptor: types.object.isRequired,
14 | $selection: types.object.isRequired,
15 | $sidebar: types.object.isRequired
16 | },
17 |
18 | renderDesiredLrps(desiredLrps) {
19 | var {$receptor, $selection, $sidebar} = this.props;
20 | var {actualLrpsByProcessGuid = {}} = $receptor.get();
21 | var sidebarCollapsed = $sidebar.get('sidebarCollapsed');
22 |
23 | if(!desiredLrps.length && !sidebarCollapsed) {
24 | return No filtered processes found.
;
25 | }
26 |
27 | desiredLrps = desiredLrps.map(this.renderDesiredLrp.bind(this, {actualLrpsByProcessGuid, sidebarCollapsed, $selection, $sidebar}));
28 | return (
29 |
32 | );
33 | },
34 |
35 | renderDesiredLrp({actualLrpsByProcessGuid, sidebarCollapsed, $selection, $sidebar}, desiredLrp) {
36 | var key = desiredLrp.process_guid;
37 | var className = classnames('clickable', {hover: $sidebar.get('filter') || $selection.get('hoverDesiredLrp') === desiredLrp});
38 | var filtered = actualLrpsByProcessGuid[desiredLrp.process_guid] || [];
39 | return (
40 |
41 | );
42 | },
43 |
44 | render() {
45 | var {$receptor, $selection, $sidebar} = this.props;
46 | var {desiredLrps = []} = $receptor.get();
47 | var {filter} = $sidebar.get();
48 | if (filter) {
49 | var {filteredLrps} = $selection.get();
50 | desiredLrps = desiredLrps.filter(({process_guid: processGuid}) => processGuid in filteredLrps);
51 | }
52 | return (
53 |
54 |
55 |
56 | {this.renderDesiredLrps(desiredLrps)}
57 |
58 |
59 | );
60 | }
61 | });
62 |
63 | module.exports = DesiredLrpList;
--------------------------------------------------------------------------------
/app/components/filter.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | var types = React.PropTypes;
4 |
5 | var Filter = React.createClass({
6 | propTypes: {
7 | onFilter: types.func.isRequired,
8 | value: types.string,
9 | className: types.string,
10 | placeholder: types.string,
11 | style: types.object
12 | },
13 |
14 | render() {
15 | var {className, id, onFilter, placeholder, style, value} = this.props;
16 | return (
17 |
18 |
19 |
20 | );
21 | }
22 | });
23 |
24 | module.exports = Filter;
--------------------------------------------------------------------------------
/app/components/footer.js:
--------------------------------------------------------------------------------
1 | var {InlineList, ListItem} = require('pui-react-lists');
2 | var {Flag} = require('pui-react-media');
3 | var React = require('react');
4 | var Svg = require('./svg');
5 |
6 | var Footer = React.createClass({
7 | render() {
8 | var {className} = this.props;
9 | return (
10 |
21 | );
22 | }
23 | });
24 |
25 | module.exports = Footer;
--------------------------------------------------------------------------------
/app/components/form_group.js:
--------------------------------------------------------------------------------
1 | var classnames = require('classnames');
2 | var React = require('react');
3 | var types = React.PropTypes;
4 |
5 | var FormGroup = React.createClass({
6 | propTypes: {
7 | onValidate: types.func.isRequired,
8 | helpBlock: types.oneOfType([types.object, types.string])
9 | },
10 |
11 | getInitialState() {
12 | return {valid: true};
13 | },
14 |
15 | validate() {
16 | var input = this.getDOMNode().querySelector('input');
17 | var valid = input && this.props.onValidate(input);
18 | this.setState({valid});
19 | return valid;
20 | },
21 |
22 | render() {
23 | var {valid} = this.state;
24 | var {className, children, helpBlock} = this.props;
25 | var helpBlockClassName = classnames('help-block', {'has-error': !valid});
26 | return (
27 |
28 | {children}
29 | {helpBlock && !valid && {helpBlock} }
30 |
31 | );
32 | }
33 | });
34 |
35 | module.exports = FormGroup;
--------------------------------------------------------------------------------
/app/components/header.js:
--------------------------------------------------------------------------------
1 | var {Flag} = require('pui-react-media');
2 | var React = require('react');
3 | var Svg = require('./svg');
4 |
5 | var Header = React.createClass({
6 | render() {
7 | var {className, children} = this.props;
8 | return (
9 |
10 | } rightImage={children}>
11 | X-Ray
12 |
13 |
14 | );
15 | }
16 | });
17 |
18 | module.exports = Header;
--------------------------------------------------------------------------------
/app/components/launch_modal.js:
--------------------------------------------------------------------------------
1 | var camelCase = require('lodash.camelcase');
2 | var {Col, Row} = require('pui-react-grids');
3 | var {Divider} = require('pui-react-dividers');
4 | var {getCredentials} = require('../helpers/url_helper');
5 | var {LowlightButton, HighlightButton} = require('pui-react-buttons');
6 | var {Modal, ModalBody, ModalFooter} = require('pui-react-modals');
7 | var {PortalSource} = require('pui-react-portals');
8 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
9 | var React = require('react');
10 |
11 | var types = React.PropTypes;
12 |
13 | var LaunchModal = React.createClass({
14 | mixins: [PureRenderMixin],
15 |
16 | propTypes: {
17 | receptorUrl: types.string,
18 | title: types.string
19 | },
20 |
21 | getInitialState() {
22 | var {receptorUrl: rawReceptorUrl = ''} = this.props;
23 | var {user, password, url: receptorUrl} = getCredentials(rawReceptorUrl);
24 | return {user, password, receptorUrl};
25 | },
26 |
27 | change({target: {value: value, name}}) {
28 | this.setState({[camelCase(name)]: value});
29 | },
30 |
31 | openModal() {
32 | this.refs.modal.open();
33 | },
34 |
35 | closeModal() {
36 | this.refs.modal.close();
37 | },
38 |
39 | renderModal() {
40 | var {title} = this.props;
41 | var {password, receptorUrl, user} = this.state;
42 | var disabled = !receptorUrl.length;
43 | return (
44 |
45 |
81 |
82 | );
83 | },
84 |
85 | render() {
86 | return (
87 |
88 |
{this.props.children}
89 |
90 | {this.renderModal()}
91 |
92 |
93 | );
94 | }
95 | });
96 |
97 | module.exports = LaunchModal;
--------------------------------------------------------------------------------
/app/components/page.js:
--------------------------------------------------------------------------------
1 | var BaseApi = require('../api/base_api');
2 | var Header = require('./header');
3 | var Footer = require('./footer');
4 | var LaunchModal = require('./launch_modal');
5 | var Scaling = require('./scaling');
6 | var React = require('react');
7 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
8 | var ReceptorMixin = require('../mixins/receptor_mixin');
9 | var ReceptorStreamMixin = require('../mixins/receptor_stream_mixin');
10 | var Zones = require('./zones');
11 | var Sidebar = require('./sidebar');
12 | var classnames = require('classnames');
13 |
14 | var types = React.PropTypes;
15 |
16 | var Page = React.createClass({
17 | mixins: [PureRenderMixin, ReceptorMixin, ReceptorStreamMixin],
18 |
19 | propTypes: {
20 | receptorUrl: types.string.isRequired,
21 | $receptor: types.object.isRequired,
22 | $scaling: types.object.isRequired,
23 | $selection: types.object.isRequired,
24 | $sidebar: types.object.isRequired
25 | },
26 |
27 | componentDidMount() {
28 | this.componentWillReceiveProps(this.props);
29 | },
30 |
31 | componentWillReceiveProps(nextProps) {
32 | if (nextProps.receptorUrl && !BaseApi.baseUrl) {
33 | BaseApi.baseUrl = nextProps.receptorUrl;
34 | this.updateReceptor();
35 | this.pollReceptor();
36 | this.streamSSE(nextProps.receptorUrl);
37 | }
38 | },
39 |
40 | onScrimClick() {
41 | this.props.$selection.merge({selectedDesiredLrp: null, hoverDesiredLrp: null});
42 | },
43 |
44 | render() {
45 | var {receptorUrl, $receptor, $scaling, $sidebar, $selection} = this.props;
46 | var selection = !!($selection.get('hoverDesiredLrp') || $selection.get('selectedDesiredLrp')) || $sidebar.get('filter');
47 | var sidebarCollapsed = $sidebar.get('sidebarCollapsed');
48 |
49 | var classes = classnames(
50 | 'page',
51 | {
52 | 'sidebar-collapsed': sidebarCollapsed,
53 | 'sidebar-open': !sidebarCollapsed,
54 | 'filtered': $sidebar.get('filter'),
55 | selection
56 | }
57 | );
58 |
59 | return (
60 |
61 |
64 |
65 |
66 |
67 |
68 | {$selection.get('selectedDesiredLrp') && }
69 |
70 |
71 |
72 |
73 | {this.props.children}
74 |
75 | );
76 | }
77 | });
78 |
79 | module.exports = Page;
80 |
--------------------------------------------------------------------------------
/app/components/scaling.js:
--------------------------------------------------------------------------------
1 | var Icon = require('pui-react-iconography').Icon;
2 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
3 | var Radio = require('pui-react-radio').Radio;
4 | var RadioGroup = require('pui-react-radio-group').RadioGroup;
5 | var React = require('react');
6 | var ReceptorMixin = require('../mixins/receptor_mixin');
7 |
8 | var types = React.PropTypes;
9 |
10 | var Scaling = React.createClass({
11 | mixins: [PureRenderMixin, ReceptorMixin],
12 |
13 | propTypes: {
14 | $scaling: types.object.isRequired
15 | },
16 |
17 | changeScale(scaling) {
18 | this.props.$scaling.set(scaling);
19 | },
20 |
21 | render() {
22 | var scaling = this.props.$scaling.get();
23 | return (
24 |
25 |
26 |
27 | memory
28 | containers
29 |
30 |
31 | );
32 | }
33 | });
34 |
35 | module.exports = Scaling;
--------------------------------------------------------------------------------
/app/components/setup.js:
--------------------------------------------------------------------------------
1 | require('babel/polyfill');
2 | var {Col, Row} = require('pui-react-grids');
3 | var Footer = require('./footer');
4 | var googleAnalyticsMixin = require('../mixins/google_analytics_mixin');
5 | var Header = require('./header');
6 | var LaunchModal = require('./launch_modal');
7 | var Layout = require('../../server/components/layout');
8 | var React = require('react');
9 | var {PortalDestination} = require('pui-react-portals');
10 | var types = React.PropTypes;
11 |
12 | var Setup = React.createClass({
13 | mixins: [googleAnalyticsMixin],
14 |
15 | propTypes: {
16 | config: types.object.isRequired
17 | },
18 |
19 | render() {
20 | var {config: {receptorUrl}} = this.props;
21 | return (
22 |
23 |
24 |
27 |
28 |
29 |
30 |
31 | Explore the Lattice
32 | X-Ray is an easy to use dashboard for visualizing Lattice clusters. Point X-Ray at your Lattice deployment to view the distribution and status of your containers
33 | X-Ray needs a working Lattice environment. Read More
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | }
44 | });
45 |
46 | Layout.init(Setup);
47 |
48 | module.exports = Setup;
49 |
--------------------------------------------------------------------------------
/app/components/sidebar.js:
--------------------------------------------------------------------------------
1 | var DesiredLrpDetail = require('./desired_lrp_detail');
2 | var DesiredLrpList = require('./desired_lrp_list');
3 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
4 | var React = require('react');
5 |
6 | var types = React.PropTypes;
7 |
8 | var Sidebar = React.createClass({
9 | mixins: [PureRenderMixin],
10 |
11 | propTypes: {
12 | $receptor: types.object.isRequired,
13 | $selection: types.object.isRequired,
14 | $sidebar: types.object.isRequired
15 | },
16 |
17 | render() {
18 | var {$selection} = this.props;
19 | var hasDetails = !!$selection.get('selectedDesiredLrp');
20 | return (
21 |
22 | {!hasDetails && }
23 | {hasDetails && }
24 |
25 | );
26 | }
27 | });
28 |
29 | module.exports = Sidebar;
--------------------------------------------------------------------------------
/app/components/sidebar_container.js:
--------------------------------------------------------------------------------
1 | var classnames = require('classnames');
2 | var Icon = require('pui-react-iconography').Icon;
3 | var React = require('react');
4 | var OverlayTrigger = require('pui-react-overlay-trigger').OverlayTrigger;
5 | var Tooltip = require('pui-react-tooltip').Tooltip;
6 | var {getHostname} = require('../helpers/lrp_helper');
7 | var {pickColor} = require('../helpers/application_helper');
8 |
9 | var types = React.PropTypes;
10 |
11 | var SidebarContainer = React.createClass({
12 | propTypes: {
13 | claimed: types.bool,
14 | desiredLrp: types.object.isRequired,
15 | instancesError: types.bool.isRequired,
16 | tooltip: types.oneOfType([types.object, types.bool])
17 | },
18 |
19 | contextTypes: {
20 | colors: types.array.isRequired
21 | },
22 |
23 | render() {
24 | var {desiredLrp, tooltip, instancesError, claimed} = this.props;
25 | var {process_guid: processGuid} = desiredLrp;
26 | var containerColor = pickColor(this.context.colors, getHostname(desiredLrp) || processGuid);
27 | var imageStyle = {backgroundColor: containerColor};
28 |
29 | var container = (
30 |
31 | {instancesError && }
32 |
33 | );
34 |
35 | if (!tooltip) {
36 | return container;
37 | }
38 | return (
39 | {tooltip}}>
40 | {container}
41 |
42 | );
43 | }
44 | });
45 |
46 | module.exports = SidebarContainer;
--------------------------------------------------------------------------------
/app/components/sidebar_header.js:
--------------------------------------------------------------------------------
1 | var Filter = require('./filter');
2 | var Icon = require('pui-react-iconography').Icon;
3 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
4 | var React = require('react');
5 | var helper = require('../helpers/lrp_helper');
6 |
7 | var types = React.PropTypes;
8 |
9 | var SidebarHeader = React.createClass({
10 | mixins: [PureRenderMixin],
11 |
12 | propTypes: {
13 | $receptor: types.object.isRequired,
14 | $selection: types.object.isRequired,
15 | $sidebar: types.object.isRequired
16 | },
17 |
18 | filter(e) {
19 | var {$receptor, $selection, $sidebar} = this.props;
20 | var desiredLrps = $receptor.get().desiredLrps;
21 | var filter = e.target.value;
22 | $sidebar.merge({filter: filter});
23 | var filteredLrps = filter.length ?
24 | helper.filterDesiredLrps(desiredLrps, filter) : {};
25 | $selection.merge({filteredLrps});
26 | },
27 |
28 | toggleSidebar() {
29 | var {$sidebar} = this.props;
30 | $sidebar.merge({sidebarCollapsed: !$sidebar.get('sidebarCollapsed')});
31 | },
32 |
33 | render() {
34 | var {$sidebar} = this.props;
35 | var filter = $sidebar.get('filter');
36 | return (
37 |
44 | );
45 | }
46 | });
47 |
48 | module.exports = SidebarHeader;
--------------------------------------------------------------------------------
/app/components/svg.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | var types = React.PropTypes;
4 |
5 | var Svg = React.createClass({
6 | propTypes: {
7 | src: types.string.isRequired
8 | },
9 |
10 | getInitialState() {
11 | return {attributes: null, content: null};
12 | },
13 |
14 | componentDidMount() {
15 | var {attributes, content} = require(`../svg/${this.props.src}.svg`);
16 | this.setState({attributes, content});
17 | },
18 |
19 | render() {
20 | var {attributes, content} = this.state;
21 | if (!content) return null;
22 | return ;
23 | }
24 | });
25 |
26 | module.exports = Svg;
27 |
--------------------------------------------------------------------------------
/app/components/timeago.js:
--------------------------------------------------------------------------------
1 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
2 | var React = require('react');
3 | var timeago = require('timeago');
4 |
5 | timeago.settings.strings = {
6 | prefixAgo: null,
7 | prefixFromNow: null,
8 | suffixAgo: '',
9 | suffixFromNow: '',
10 | seconds: '%ds',
11 | minute: '1m',
12 | minutes: '%dm',
13 | hour: '1h',
14 | hours: '%dh',
15 | day: '1d',
16 | days: '%dd',
17 | month: '1mo',
18 | months: '%dmo',
19 | year: '1yr',
20 | years: '%dyr',
21 | wordSeparator: ' ',
22 | numbers: []
23 | };
24 |
25 | var types = React.PropTypes;
26 |
27 | var Timeago = React.createClass({
28 | mixins: [PureRenderMixin],
29 |
30 | propTypes: {
31 | dateTime: types.object.isRequired
32 | },
33 |
34 | render() {
35 | var {dateTime} = this.props;
36 | return ({timeago(dateTime)} );
37 | }
38 | });
39 |
40 | module.exports = Timeago;
--------------------------------------------------------------------------------
/app/components/zones.js:
--------------------------------------------------------------------------------
1 | var Cells = require('./cells');
2 | var PureRenderMixin = require('pui-cursor/mixins/pure-render-mixin');
3 | var React = require('react');
4 | var groupBy = require('lodash.groupby');
5 |
6 | var types = React.PropTypes;
7 |
8 | var Zones = React.createClass({
9 | mixins: [PureRenderMixin],
10 |
11 | propTypes: {
12 | $receptor: types.object,
13 | scaling: types.string.isRequired,
14 | $selection: types.object,
15 | $sidebar: types.object
16 | },
17 |
18 | renderZones: function() {
19 | var {scaling, $receptor, $selection, $sidebar} = this.props;
20 | var cells = $receptor.get('cells');
21 | if (!cells) return null;
22 |
23 | var zones = groupBy(cells, 'zone');
24 | return Object.keys(zones).sort().map(function(zone) {
25 | var cells = zones[zone];
26 | return (
27 |
28 | {`Zone ${zone} - ${cells.length} Cells`}
29 |
30 |
31 | );
32 | });
33 | },
34 |
35 | render() {
36 | return (
37 |
38 | {this.renderZones()}
39 |
40 | );
41 | }
42 | });
43 |
44 | module.exports = Zones;
--------------------------------------------------------------------------------
/app/helpers/application_helper.js:
--------------------------------------------------------------------------------
1 | function hashCode(str) {
2 | return str.split('').reduce(function(a, b) {
3 | a = ((a << 5) - a) + b.charCodeAt(0);
4 | return a & a;
5 | }, 0);
6 | }
7 |
8 | module.exports = {
9 | pickColor(colors, str) {
10 | return colors[(Math.abs(hashCode(str + '')) % colors.length)];
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/app/helpers/array_helper.js:
--------------------------------------------------------------------------------
1 | function diff(oldArr, newArr, id, changeCallback) {
2 | oldArr = oldArr || [];
3 | newArr = newArr || [];
4 |
5 | var oldMap = oldArr.reduce((memo, e) => (memo[e[id]] = e, memo), {});
6 | var newMap = newArr.reduce((memo, e) => (memo[e[id]] = e, memo), {});
7 | var oldIds = Object.keys(oldMap);
8 | var newIds = Object.keys(newMap);
9 |
10 | var added = newArr.filter(newEl => !oldIds.includes(newEl[id]));
11 | var removed = oldArr.filter(oldEl => !newIds.includes(oldEl[id]));
12 | var changed = changeCallback && oldArr.filter(function(oldEl) {
13 | var match = newMap[oldEl[id]];
14 | if (!match) return false;
15 | return changeCallback(oldEl, match);
16 | }).map(oldEl => [oldEl, newMap[oldEl[id]]]);
17 |
18 | return {added, removed, changed};
19 | }
20 |
21 | module.exports = {diff};
--------------------------------------------------------------------------------
/app/helpers/lrp_helper.js:
--------------------------------------------------------------------------------
1 | var max = require('lodash.max');
2 | var {lpad} = require('./string_helper');
3 | var flatten = require('lodash.flatten');
4 |
5 | function getRoutes(desiredLrp) {
6 | var routes = desiredLrp.routes;
7 | if (!routes) return [];
8 | var routers = Object.keys(routes);
9 | if (!routers.length) return [];
10 | return routes[routers[0]];
11 | }
12 |
13 | module.exports = {
14 | findLrp(lrps, {modification_tag: {epoch}}) {
15 | //TODO: desiredLrps could be a hash for O(1) lookup instead of a find
16 | lrps = lrps.reduce((memo, lrp) => epoch === lrp.modification_tag.epoch ? memo.concat(lrp) : memo, []);
17 | if (!lrps.length) return null;
18 | return max(lrps, ({modification_tag: {index}}) => index);
19 | },
20 |
21 | actualLrpIndex: lrp => lrp.process_guid + lpad(lrp.index, '0', 5),
22 |
23 | decorateDesiredLrp(lrp) {
24 | var routes = getRoutes(lrp);
25 | var hostnames = flatten(routes.map(route => route.hostnames));
26 | lrp.filterString = [lrp.process_guid, ...hostnames].join('|');
27 | },
28 | getRoutes: getRoutes,
29 |
30 | getHostname(desiredLrp) {
31 | var routes = getRoutes(desiredLrp);
32 | if (!routes.length) return null;
33 | return routes[0].hostnames && routes[0].hostnames[0];
34 | },
35 |
36 | filterDesiredLrps(desiredLrps, filter) {
37 | return desiredLrps
38 | .filter(desiredLrp => desiredLrp.filterString.includes(filter))
39 | .reduce((memo, lrp) => Object.assign(memo, {[lrp.process_guid]: lrp}), {});
40 | }
41 | };
--------------------------------------------------------------------------------
/app/helpers/string_helper.js:
--------------------------------------------------------------------------------
1 | var StringHelper = {
2 | lpad(thing, character, count) {
3 | var string = thing.toString();
4 | return (character.repeat(count) + string).slice(-Math.max(count, string.length));
5 | }
6 | };
7 |
8 | module.exports = StringHelper;
--------------------------------------------------------------------------------
/app/helpers/url_helper.js:
--------------------------------------------------------------------------------
1 | var Url = require('url');
2 | var UrlHelper = {
3 | getCredentials(url) {
4 | var parsedUrl = Url.parse(url);
5 | var [user, password] = parsedUrl.auth ? parsedUrl.auth.split(':').map(decodeURIComponent) : [];
6 | return {user, password, url: Url.format(Object.assign(parsedUrl, {auth: null}))};
7 | }
8 | };
9 |
10 | module.exports = UrlHelper;
--------------------------------------------------------------------------------
/app/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vmware-archive/xray/83f174aabb405a2cc54e7720080bf9207ba19ba3/app/images/favicon.ico
--------------------------------------------------------------------------------
/app/mixins/google_analytics_mixin.js:
--------------------------------------------------------------------------------
1 | var googleAnalytics = require('../vendor/google_analytics');
2 |
3 | module.exports = {
4 | componentDidMount() {
5 | googleAnalytics.init(window, document);
6 | }
7 | };
--------------------------------------------------------------------------------
/app/mixins/hover_actual_lrp_mixin.js:
--------------------------------------------------------------------------------
1 | var React = require('react/addons');
2 | var types = React.PropTypes;
3 |
4 | var HoverActualLrpMixin = {
5 | propTypes: {
6 | actualLrp: types.object.isRequired,
7 | $hoverActualLrp: types.object,
8 | $hoverSidebarActualLrp: types.object
9 | },
10 |
11 | onMouseEnter() {
12 | var {actualLrp, $hoverActualLrp, $hoverSidebarActualLrp} = this.props;
13 | [$hoverActualLrp, $hoverSidebarActualLrp].filter(Boolean).forEach(cursor => cursor.set(actualLrp));
14 | },
15 |
16 | onMouseLeave() {
17 | var {$hoverActualLrp, $hoverSidebarActualLrp} = this.props;
18 | [$hoverActualLrp, $hoverSidebarActualLrp].filter(Boolean).forEach(cursor => cursor.set(null));
19 | }
20 | };
21 |
22 | module.exports = HoverActualLrpMixin;
--------------------------------------------------------------------------------
/app/mixins/hover_desired_lrp_mixin.js:
--------------------------------------------------------------------------------
1 | var React = require('react/addons');
2 | var types = React.PropTypes;
3 | var throttle = require('lodash.throttle');
4 |
5 | var privates = new WeakMap();
6 |
7 | function withFilter(callback) {
8 | return function(...args) {
9 | var {desiredLrp, $selection, $sidebar} = this.props;
10 | if (!$sidebar.get('filter') || desiredLrp.process_guid in $selection.get('filteredLrps')) callback.apply(this, args);
11 | };
12 | }
13 |
14 |
15 | var HoverDesiredLrpMixin = {
16 | propTypes: {
17 | desiredLrp: types.object.isRequired,
18 | $selection: types.object.isRequired,
19 | $sidebar: types.object.isRequired
20 | },
21 |
22 | componentDidMount() {
23 | var addHover = throttle(function updateHover($selection, desiredLrp) {
24 | $selection.merge({hoverDesiredLrp: desiredLrp});
25 | }, 16);
26 |
27 | var removeHover = throttle(function updateHover($selection) {
28 | $selection.merge({hoverDesiredLrp: null});
29 | }, 16);
30 |
31 | privates.set(this, {addHover, removeHover});
32 | },
33 |
34 | onMouseEnter: withFilter(function() {
35 | var {addHover} = privates.get(this);
36 | var {desiredLrp, $selection} = this.props;
37 | addHover($selection, desiredLrp);
38 | }),
39 |
40 | onMouseLeave: withFilter(function() {
41 | var {removeHover} = privates.get(this);
42 | var {$selection} = this.props;
43 | removeHover($selection);
44 | }),
45 |
46 | onClick: withFilter(function(e) {
47 | e.stopPropagation();
48 | var {desiredLrp, $selection, $sidebar} = this.props;
49 | $selection.merge({selectedDesiredLrp: desiredLrp});
50 | $sidebar.merge({sidebarCollapsed: false});
51 | })
52 | };
53 |
54 | module.exports = HoverDesiredLrpMixin;
--------------------------------------------------------------------------------
/app/mixins/receptor_mixin.js:
--------------------------------------------------------------------------------
1 | const CELL_POLL_INTERVAL = 10000;
2 | const RECEPTOR_POLL_INTERVAL = 30000;
3 |
4 | var ReceptorApi = require('../api/receptor_api');
5 | var CellsApi = require('../api/cells_api');
6 | var sortedIndex = require('lodash.sortedindex');
7 | var {actualLrpIndex, decorateDesiredLrp} = require('../helpers/lrp_helper');
8 | var {diff} = require('../helpers/array_helper');
9 | var {setCorrectingInterval} = require('correcting-interval');
10 | var groupBy = require('lodash.groupby');
11 |
12 | var React = require('react/addons');
13 | var types = React.PropTypes;
14 |
15 | function applyUpdate(newArr, id, options = {}) {
16 | return {
17 | $apply: function(oldArr) {
18 | var {added, removed, changed} = diff(oldArr, newArr, id, options.change);
19 | var results = oldArr.filter(x => !removed.includes(x));
20 | if (changed && changed.length) {
21 | /*eslint-disable no-unused-vars*/
22 | var currentChanged = changed.map(([current, next]) => current);
23 | var nextChanged = changed.map(([current, next]) => next);
24 | /*eslint-enable no-unused-vars*/
25 | results = results.map(x => currentChanged.includes(x) ? nextChanged[currentChanged.indexOf(x)] : x);
26 | }
27 |
28 | if (options.sortBy) {
29 | added.forEach(function(obj) {
30 | var index = sortedIndex(results, obj, options.sortBy);
31 | results.splice(index, 0, obj);
32 | });
33 | return results;
34 | }
35 | return results.concat(added);
36 | }
37 | };
38 | }
39 |
40 | function setIndex(newArr, id) {
41 | return {
42 | $set: newArr.reduce((memo, obj) => (memo[obj[id]] = obj, memo), {})
43 | };
44 | }
45 |
46 | function setIndexArray(newArr, id) {
47 | return {
48 | $set: groupBy(newArr, id)
49 | };
50 | }
51 |
52 | var ReceptorMixin = {
53 | statics: {
54 | CELL_POLL_INTERVAL,
55 | RECEPTOR_POLL_INTERVAL
56 | },
57 |
58 | propTypes: {
59 | $receptor: types.object.isRequired
60 | },
61 |
62 | async updateReceptor() {
63 | var {actualLrps, cells, desiredLrps} = await ReceptorApi.fetch();
64 | var {$receptor} = this.props;
65 | desiredLrps.forEach(decorateDesiredLrp.bind(this));
66 |
67 | var change = (a, b) => a.modification_tag.index < b.modification_tag.index;
68 | var updateDesiredLrps = applyUpdate(desiredLrps, 'process_guid', {change}).$apply;
69 | var newDesiredLrps = updateDesiredLrps($receptor.get('desiredLrps'));
70 |
71 | var newActualLrps = applyUpdate(actualLrps, 'instance_guid', {change, sortBy: actualLrpIndex})
72 | .$apply($receptor.get('actualLrps'));
73 |
74 | $receptor.update({
75 | cells: applyUpdate(cells, 'cell_id', {sortBy: 'cell_id'}),
76 | actualLrps: {$set: newActualLrps},
77 | desiredLrps: {$set: newDesiredLrps},
78 | desiredLrpsByProcessGuid: setIndex(newDesiredLrps, 'process_guid'),
79 | actualLrpsByProcessGuid: setIndexArray(newActualLrps, 'process_guid'),
80 | actualLrpsByCellId: setIndexArray(newActualLrps, 'cell_id')
81 | });
82 | },
83 |
84 | async updateCells() {
85 | var {cells} = await CellsApi.fetch();
86 | var {$receptor} = this.props;
87 | $receptor.refine('cells').update(applyUpdate(cells, 'cell_id'));
88 | },
89 |
90 | pollReceptor() {
91 | setCorrectingInterval(this.updateCells, CELL_POLL_INTERVAL);
92 | setCorrectingInterval(this.updateReceptor, RECEPTOR_POLL_INTERVAL);
93 | }
94 | };
95 |
96 | module.exports = ReceptorMixin;
--------------------------------------------------------------------------------
/app/mixins/receptor_stream_mixin.js:
--------------------------------------------------------------------------------
1 | var AuthorizationApi = require('../api/authorization_api');
2 | var EventSource = require('pui-event-source');
3 | var join = require('url-join');
4 | var sortedIndex = require('lodash.sortedindex');
5 | var {actualLrpIndex, decorateDesiredLrp} = require('../helpers/lrp_helper');
6 |
7 | var privates = new WeakMap();
8 |
9 | /*eslint-disable no-unused-vars*/
10 | function createResource(cursorName, resourceKey, options = {}) {
11 | return function({[resourceKey]: resource}) {
12 | var {$receptor} = this.props;
13 | var oldResource = $receptor.get(cursorName).find(({modification_tag: {epoch}}) => epoch === resource.modification_tag.epoch);
14 | if (oldResource) return;
15 |
16 | if(options.decorate) {
17 | options.decorate(resource);
18 | }
19 | $receptor.apply(function(receptor) {
20 | (options.indexBy || []).forEach(function(config) {
21 | var indexBy = receptor[config.name];
22 | var indexKey = resource[config.key];
23 | if (config.array) {
24 | indexBy[indexKey] = indexBy[indexKey] || [];
25 | indexBy[indexKey].push(resource);
26 | } else {
27 | indexBy[indexKey] = resource;
28 | }
29 | });
30 |
31 | if (options.sortBy) {
32 | var index = sortedIndex(receptor[cursorName], resource, options.sortBy);
33 | receptor[cursorName].splice(index, 0, resource);
34 | } else {
35 | receptor[cursorName].push(resource);
36 | }
37 |
38 | return receptor;
39 | });
40 | };
41 | }
42 |
43 | function removeResource(cursorName, resourceKey, options = {}) {
44 | return function({[resourceKey]: resource}) {
45 | var {$receptor} = this.props;
46 | var oldResource = $receptor.get(cursorName).find(({modification_tag: {epoch}}) => epoch === resource.modification_tag.epoch);
47 | if (!oldResource) return;
48 | $receptor.apply(function(receptor) {
49 | (options.indexBy || []).forEach(function(config) {
50 | var indexBy = $receptor.get(config.name);
51 | var indexKey = resource[config.key];
52 | if (config.array) {
53 | var position;
54 | if (indexBy[indexKey] && (position = indexBy[indexKey].indexOf(oldResource)) !== -1) {
55 | indexBy[indexKey].splice(position, 1);
56 | } else {
57 | var oldKey = oldResource[config.key];
58 | if (indexBy[oldKey]) {
59 | position = indexBy[oldKey].indexOf(oldResource);
60 | position !== -1 && indexBy[oldKey].splice(position, 1);
61 | }
62 | }
63 | } else {
64 | delete indexBy[indexKey];
65 | }
66 | });
67 |
68 | var index = receptor[cursorName].indexOf(oldResource);
69 | index !== -1 && receptor[cursorName].splice(index, 1);
70 | return receptor;
71 | });
72 | };
73 | }
74 |
75 | function changeResource(cursorName, resourceKey, options = {}) {
76 | return function({[resourceKey]: resource}) {
77 | var {$receptor} = this.props;
78 | var oldResource = $receptor.get(cursorName).find(({modification_tag: {epoch}}) => epoch === resource.modification_tag.epoch);
79 |
80 | if(options.decorate) {
81 | options.decorate(resource);
82 | }
83 | $receptor.apply(function(receptor) {
84 | if (!oldResource) {
85 | receptor[cursorName].push(resource);
86 | } else {
87 | var index = receptor[cursorName].indexOf(oldResource);
88 | receptor[cursorName][index] = resource;
89 | }
90 |
91 | (options.indexBy || []).forEach(function(config) {
92 | var indexBy = receptor[config.name];
93 | var indexKey = resource[config.key];
94 | if (config.array) {
95 | if (oldResource) {
96 | var position;
97 | if (indexBy[indexKey] && (position = indexBy[indexKey].indexOf(oldResource)) !== -1) {
98 | indexBy[indexKey].splice(position, 1, resource);
99 | } else {
100 | var oldKey = oldResource[config.key];
101 | if (indexBy[oldKey]) {
102 | position = indexBy[oldKey].indexOf(oldResource);
103 | position !== -1 && indexBy[oldKey].splice(position, 1);
104 | }
105 | (indexBy[indexKey] || (indexBy[indexKey] = [])).push(resource);
106 | }
107 | } else {
108 | (indexBy[indexKey] || (indexBy[indexKey] = [])).push(resource);
109 | }
110 | } else {
111 | indexBy[indexKey] = resource;
112 | }
113 | });
114 |
115 | return receptor;
116 | });
117 | };
118 | }
119 | /*eslint-enable no-unused-vars*/
120 |
121 | async function connectSSE(receptorUrl, promise) {
122 | try {
123 | await promise;
124 | } catch(e) {
125 | } finally {
126 | var sse = new EventSource(join(receptorUrl, 'v1', 'events'), {withCredentials: true});
127 | privates.set(this, {sse});
128 | }
129 | }
130 |
131 | var ReceptorStreamMixin = {
132 | componentWillUnmount() {
133 | this.destroySSE();
134 | },
135 |
136 | createSSE(receptorUrl) {
137 | var promise = AuthorizationApi.create();
138 | connectSSE.call(this, receptorUrl, promise);
139 | return promise;
140 | },
141 |
142 | destroySSE() {
143 | var {sse} = privates.get(this) || {};
144 | if (sse) sse.off();
145 | },
146 |
147 | streamActualLrps() {
148 | var {sse} = privates.get(this) || {};
149 | if (!sse) return;
150 |
151 | var options = {
152 | indexBy: [
153 | {key: 'process_guid', name: 'actualLrpsByProcessGuid', array: true},
154 | {key: 'cell_id', name: 'actualLrpsByCellId', array: true}
155 | ]
156 | };
157 |
158 | sse
159 | .on('actual_lrp_created', createResource('actualLrps', 'actual_lrp', Object.assign({sortBy: actualLrpIndex}, options)).bind(this))
160 | .on('actual_lrp_changed', changeResource('actualLrps', 'actual_lrp_after', options).bind(this))
161 | .on('actual_lrp_removed', removeResource('actualLrps', 'actual_lrp', options).bind(this));
162 | },
163 |
164 | streamDesiredLrps() {
165 | var {sse} = privates.get(this) || {};
166 | if (!sse) return;
167 |
168 | var options = {
169 | indexBy: [{
170 | key: 'process_guid',
171 | name: 'desiredLrpsByProcessGuid'
172 | }],
173 | decorate: decorateDesiredLrp.bind(this)
174 | };
175 | sse
176 | .on('desired_lrp_created', createResource('desiredLrps', 'desired_lrp', options).bind(this))
177 | .on('desired_lrp_changed', changeResource('desiredLrps', 'desired_lrp_after', options).bind(this))
178 | .on('desired_lrp_removed', removeResource('desiredLrps', 'desired_lrp', options).bind(this));
179 | },
180 |
181 | async streamSSE(receptorUrl) {
182 | this.destroySSE();
183 | try {
184 | await this.createSSE(receptorUrl);
185 | } catch(e) {
186 | } finally {
187 | this.streamActualLrps();
188 | this.streamDesiredLrps();
189 | }
190 | }
191 | };
192 |
193 | module.exports = ReceptorStreamMixin;
--------------------------------------------------------------------------------
/app/stylesheets/_animations.scss:
--------------------------------------------------------------------------------
1 | @keyframes fade-in {
2 | 0% { opacity: 0; }
3 | 100% { opacity: 1; }
4 | }
5 |
6 | @keyframes almost-fade-in {
7 | 0% { opacity: 0.33; }
8 | 100% { opacity: 1; }
9 | }
--------------------------------------------------------------------------------
/app/stylesheets/_backgrounds.scss:
--------------------------------------------------------------------------------
1 |
2 | .bg-isometric {
3 | background:
4 | linear-gradient(to bottom, $isometric-bg 1%, $isometric-bg 45%, rgba(23, 35, 42, 0) 100%),
5 | radial-gradient(ellipse at bottom, rgba(23, 35, 42, 0) 0%, $isometric-bg 85%),
6 | repeating-linear-gradient(150deg, $isometric-grid-color, $isometric-grid-color 1px, transparent 2px, transparent 80px) 0 0,
7 | repeating-linear-gradient(30deg, $isometric-grid-color, $isometric-grid-color 1px, transparent 2px, transparent 80px) 0 0;
8 | background-size:100%, 100%,160px 93px,160px 93px;
9 | background-color: $isometric-bg;
10 | }
--------------------------------------------------------------------------------
/app/stylesheets/_cell.scss:
--------------------------------------------------------------------------------
1 | .cell {
2 | display: flex;
3 | flex-direction: row;
4 | padding: 0;
5 | background-color: $glow-1;
6 | margin-top: 8px;
7 | height: $app-container-size;
8 | }
9 |
--------------------------------------------------------------------------------
/app/stylesheets/_containers.scss:
--------------------------------------------------------------------------------
1 | $app-container-shadow-bottom: inset 0 $app-container-shadow-height * -1 0 rgba(0,0,0,0.2);
2 | $app-container-gutter-right: inset -2px 0 0 $neutral-2;
3 |
4 | .app-container, .app-container-sidebar {
5 | box-shadow: $app-container-shadow-bottom;
6 | }
7 |
8 | .app-container {
9 | box-sizing: content-box;
10 | display: block;
11 | height: 100%;
12 | width: $app-container-size;
13 | margin: 0;
14 | transition-timing-function: ease-out;
15 | transition-duration: 200ms;
16 | box-shadow: $app-container-gutter-right, $app-container-shadow-bottom;
17 |
18 | &.flex {
19 | flex: 1;
20 | }
21 |
22 | &.undesired {
23 | background-color: transparent;
24 | outline: 3px solid $gray-11;
25 | box-shadow: $app-container-shadow-bottom;
26 | z-index: 2;
27 | }
28 |
29 | &.highlight {
30 | outline: 3px solid $gray-11;
31 | z-index: 2;
32 | box-shadow: $app-container-shadow-bottom;
33 | transform: scale(1.25);
34 | }
35 | }
36 |
37 | .claimed {
38 | animation: fade-in 1s linear infinite alternate;
39 | }
40 |
41 | .selection .app-container {
42 | opacity: .2;
43 |
44 | &.selected, &.hover {
45 | opacity: 1;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/stylesheets/_errors.scss:
--------------------------------------------------------------------------------
1 | .error {
2 | color: $red-3;
3 | }
--------------------------------------------------------------------------------
/app/stylesheets/_forms.scss:
--------------------------------------------------------------------------------
1 | .form-control-inverse {
2 | color: $neutral-11;
3 | border-color: $neutral-3;
4 | background-color: transparent;
5 | }
--------------------------------------------------------------------------------
/app/stylesheets/_layout.scss:
--------------------------------------------------------------------------------
1 | @-ms-viewport{
2 | width: device-width;
3 | }
4 |
5 | html, body, #root, .xray {
6 | min-height: 100%;
7 | @include full-height;
8 | }
9 |
10 | body {
11 | background-color: $isometric-bg;
12 | }
13 |
14 | // fancy full width layout
15 | @media(min-width: $screen-sm-min) {
16 | .page {
17 | @include full;
18 | overflow: hidden;
19 | display: flex;
20 | flex-direction: column;
21 | }
22 |
23 | .main-content {
24 | flex: 1;
25 | display: flex;
26 | @include full;
27 | }
28 | }
29 |
30 | .main-panel {
31 | flex: 1;
32 | display: flex;
33 | margin-right: 0;
34 | flex-direction: column;
35 | position: relative;
36 | }
37 |
38 | .sidebar-panel {
39 | position: relative;
40 | background-color: $shadow-2;
41 |
42 | @media(min-width: $screen-md-min){
43 | width: $sidebar-width;
44 | }
45 | }
46 |
47 | .main-header, .main-footer {
48 | background-color: $dark-1;
49 | }
50 |
51 | .left-header {
52 | padding: 15px;
53 | }
54 |
55 | .main-header {
56 | .logo {
57 | margin: 20px 0 20px 15px;
58 | }
59 |
60 | h1 {
61 | margin: 0;
62 | font-size: 18px;
63 | line-height: 1;
64 | font-weight: 400;
65 | }
66 |
67 | .receptor-url {
68 | cursor: pointer;
69 | width: 175px;
70 | background-color: $shadow-2;
71 |
72 | @media(min-width: $screen-md-min) {
73 | width: $sidebar-width;
74 | }
75 |
76 | label {
77 | cursor: pointer;
78 | display: block;
79 |
80 | .active-label, .inactive-label, input {
81 | padding: 0 $desired-lrp-gutter;
82 | }
83 | }
84 |
85 | label .active-label {
86 | color: $link-color;
87 | display: none;
88 | }
89 |
90 | label .inactive-label {
91 | text-transform: uppercase;
92 | }
93 |
94 | &:hover label {
95 | .active-label {
96 | display: block;
97 | }
98 | .inactive-label {
99 | display: none;
100 | }
101 | }
102 |
103 | input {
104 | height: auto;
105 | border: none;
106 | box-shadow: none;
107 | outline: none;
108 | }
109 | }
110 | }
111 |
112 | .main-footer {
113 | padding: 15px;
114 |
115 | &, .list-inline a {
116 | color: $gray-4;
117 | }
118 |
119 | .brand {
120 | display: block;
121 | }
122 |
123 | p, .list-inline {
124 | margin: 0;
125 | line-height: 1;
126 | }
127 |
128 | .list-inline {
129 | margin-left: 10px;
130 |
131 | > li {
132 | & + li:before {
133 | content: '·';
134 | margin-right: 10px;
135 | }
136 | }
137 | }
138 | }
139 |
140 | .main-panel {
141 | transition: margin-right $sidebar-animation-duration ease-in;
142 | .sidebar-collapsed & {
143 | margin-right: $sidebar-collapsed-width - $sidebar-width;
144 | }
145 | }
146 |
147 | @media(min-width: $screen-md-min) {
148 | .sidebar-panel {
149 | transform: translate3d(0, 0, 0);
150 | transition: transform $sidebar-animation-duration ease-in;
151 | .sidebar-toggle .fa-angle-double-right {
152 | transform: rotateZ(0deg);
153 | transition: transform $sidebar-animation-duration linear;
154 | }
155 | .media-body, .sidebar-header {
156 | opacity: 1;
157 | transition: opacity $sidebar-animation-duration linear;
158 | }
159 | .sidebar-collapsed & {
160 | transform: translate3d($sidebar-width - $sidebar-collapsed-width, 0, 0);
161 | .media-body {
162 | display: none;
163 | }
164 | .sidebar-toggle .fa-angle-double-right {
165 | transform: rotateZ(180deg);
166 | }
167 | }
168 | }
169 | }
170 |
171 |
172 |
173 | .footer {
174 | background-color: $shadow-2;
175 |
176 | @media(min-width: $screen-md-min) {
177 | align-items: center;
178 | display: flex;
179 | height: $footer-height;
180 | .radio-group {
181 | flex: 1;
182 | }
183 | }
184 | }
--------------------------------------------------------------------------------
/app/stylesheets/_links.scss:
--------------------------------------------------------------------------------
1 | // makes a non link show link-like behavior
2 | .clickable {
3 | cursor: pointer;
4 | }
--------------------------------------------------------------------------------
/app/stylesheets/_lists.scss:
--------------------------------------------------------------------------------
1 | // remove when we bump to PUI 1.7
2 | .list-group-inverse > li {
3 | border-top: 1px solid $neutral-1;
4 | }
--------------------------------------------------------------------------------
/app/stylesheets/_mixins.scss:
--------------------------------------------------------------------------------
1 | @mixin full-width {
2 | width: 100%
3 | }
4 |
5 | @mixin full-height {
6 | height: 100%
7 | }
8 |
9 | @mixin fade {
10 | transition: opacity 200ms ease-in;
11 | }
12 |
13 | @mixin full {
14 | @include full-width;
15 | @include full-height;
16 | }
17 |
18 | @mixin scrollable {
19 | overflow: auto;
20 | }
21 |
22 | @mixin scrollable-x {
23 | overflow-x: auto;
24 | overflow-y: hidden;
25 | }
26 |
27 | @mixin scrollable-y {
28 | overflow-x: hidden;
29 | overflow-y: auto;
30 | }
31 |
32 | @mixin zero {
33 | position: absolute;
34 | top: 0;
35 | left: 0;
36 | right: 0;
37 | bottom: 0;
38 | }
39 |
--------------------------------------------------------------------------------
/app/stylesheets/_scaling.scss:
--------------------------------------------------------------------------------
1 | .footer {
2 | .fa-undo, label {
3 | color: $gray-11;
4 | }
5 | .radio-group {
6 | text-align: center;
7 | input[type="radio"] {
8 | margin-right: 10px;
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/app/stylesheets/_scrim.scss:
--------------------------------------------------------------------------------
1 | .scrim {
2 | @include zero;
3 | z-index: 1000;
4 | }
5 |
--------------------------------------------------------------------------------
/app/stylesheets/_setup.scss:
--------------------------------------------------------------------------------
1 | @media(min-width: $screen-md-min) {
2 | .main-content.setup {
3 | @include scrollable;
4 | }
5 | }
6 |
7 | .main-content.setup {
8 | position: relative;
9 | align-items: center;
10 | }
11 |
--------------------------------------------------------------------------------
/app/stylesheets/_sidebar.scss:
--------------------------------------------------------------------------------
1 | .app-container-sidebar {
2 | width: $app-container-size;
3 | height: $app-container-size;
4 | display: block;
5 | }
6 |
7 | .desired-lrp-list, .desired-lrp-detail {
8 | @include full-height;
9 | }
10 |
11 | @media(min-width: $screen-md-min) {
12 | .sidebar {
13 | @include zero;
14 | table {
15 | @include full-width;
16 | table-layout: fixed;
17 | }
18 | }
19 |
20 | .desired-lrps, .actual-lrps {
21 | @include scrollable-y;
22 | flex: 1;
23 | }
24 | }
25 |
26 | .sidebar-header {
27 | display: flex;
28 |
29 | .filter-processes {
30 | flex: 1;
31 | }
32 |
33 | .form-control {
34 | height: $app-container-size + $app-container-shadow-height;
35 | }
36 |
37 | .sidebar-toggle {
38 | color: $neutral-11;
39 | border: 1px solid $neutral-3;
40 | border-radius: 3px;
41 | width: $app-container-size;
42 | margin: 0 $desired-lrp-gutter 0 $desired-lrp-gutter - 7;
43 | }
44 | }
45 |
46 | .desired-lrp-list, .desired-lrp-detail {
47 | display: flex;
48 | flex-direction: column;
49 |
50 | .media, .media-body {
51 | overflow: hidden;
52 | }
53 | .media-body, .media-left, .media-right {
54 | display: block;
55 | }
56 | .media-body {
57 | width: auto;
58 | margin: $desired-lrp-gutter $desired-lrp-gutter $desired-lrp-gutter 0;
59 | }
60 |
61 | .error .media-left {
62 | margin-top: $desired-lrp-gutter - $error-gutter;
63 | }
64 |
65 | .media-left {
66 | float: left;
67 | padding: $desired-lrp-gutter - $error-gutter;
68 | }
69 | }
70 |
71 | .desired-lrp-list {
72 | .port {
73 | width: 45px;
74 | }
75 | .list-group-inverse {
76 | display: flex;
77 | flex-direction: column;
78 |
79 | > li {
80 | display: block;
81 | order: 2;
82 | &.error {
83 | order: 1;
84 | }
85 | }
86 | }
87 |
88 | .error {
89 | .metadata, .routes a {
90 | @extend .error;
91 | }
92 | }
93 | }
94 |
95 | .desired-lrp {
96 | @include fade;
97 |
98 | .selection & {
99 | opacity: 0.2;
100 | }
101 | &.hover {
102 | opacity: 1.0
103 | }
104 |
105 | .filtered & {
106 | background-color: transparent;
107 | }
108 |
109 | .routes a {
110 | display: block
111 | }
112 |
113 | .app-container-sidebar {
114 | outline: $error-gutter solid transparent;
115 | box-sizing: content-box;
116 | margin: $error-gutter;
117 | line-height: $app-container-size;
118 | text-align: center;
119 | color: $gray-11;
120 | }
121 |
122 | &.error .app-container-sidebar {
123 | outline-color: $red-3;
124 | }
125 | }
126 |
127 | .desired-lrp-info .metadata {
128 | color: $gray-4;
129 | }
130 |
131 | .desired-lrp-detail {
132 | .desired-lrp {
133 | opacity: 1;
134 | }
135 |
136 | .desired-lrp-info {
137 | font-size: 14px;
138 | line-height: 1.2
139 | }
140 |
141 | .actual-lrps {
142 | cursor: default;
143 | font-size: 16px;
144 |
145 | .table > tbody > tr > td {
146 | border-color: $gray-2;
147 | }
148 |
149 | .index, .since {
150 | width: 15%;
151 | }
152 | .state {
153 | text-transform: capitalize;
154 | }
155 | .faded {
156 | opacity: 0.35;
157 | }
158 |
159 | .table {
160 | &:hover .actual-lrp {
161 | opacity: 0.2;
162 | }
163 |
164 | .actual-lrp {
165 | @include fade;
166 |
167 | &.claimed {
168 | animation-name: almost-fade-in;
169 | }
170 | border: 2px solid $shadow-3;
171 |
172 | &.hover {
173 | border-color: $gray-11;
174 | outline: 2px solid $gray-11;
175 | opacity: 1;
176 | }
177 | }
178 | }
179 | }
180 | }
--------------------------------------------------------------------------------
/app/stylesheets/_variables.scss:
--------------------------------------------------------------------------------
1 | $gutter: 5px;
2 | $app-container-size: 36px;
3 | $sidebar-width: 400px;
4 | $desired-lrp-gutter: 15px;
5 | $error-gutter: 5px;
6 |
7 | $app-container-shadow-height: 4px;
8 |
9 | $sidebar-animation-duration: 120ms;
10 | $sidebar-collapsed-width: $app-container-size + $desired-lrp-gutter * 2;
11 |
12 | $footer-height: 35px;
13 |
14 | // Backgrounds
15 |
16 | $isometric-bg: #162229;
17 | $isometric-grid-color: $gray-3;
18 |
19 |
--------------------------------------------------------------------------------
/app/stylesheets/_zones.scss:
--------------------------------------------------------------------------------
1 | .zones {
2 | flex: 1;
3 | padding: 0 8px;
4 | @include scrollable-y;
5 | }
6 |
--------------------------------------------------------------------------------
/app/stylesheets/application.scss:
--------------------------------------------------------------------------------
1 | @import '../../vendor/pui-v1.4.0/pui-variables';
2 | @import '_variables';
3 | @import '_mixins';
4 | @import '_animations';
5 |
6 | @import '_backgrounds';
7 | @import '_cell';
8 | @import '_containers';
9 | @import '_forms';
10 | @import '_errors';
11 | @import '_scaling';
12 | @import '_layout';
13 | @import '_lists';
14 | @import '_links';
15 | @import '_scrim';
16 | @import '_setup';
17 | @import '_sidebar';
18 | @import '_zones';
19 |
--------------------------------------------------------------------------------
/app/svg/brand.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
19 |
22 |
25 |
26 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/svg/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
--------------------------------------------------------------------------------
/app/vendor/google_analytics.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | init(window, document) {
3 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
4 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
5 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
6 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
7 |
8 | ga('create', 'UA-22181585-32', 'auto');
9 | ga('send', 'pageview');
10 | }
11 | };
--------------------------------------------------------------------------------
/config/application.json:
--------------------------------------------------------------------------------
1 | {
2 | }
--------------------------------------------------------------------------------
/config/colors.json:
--------------------------------------------------------------------------------
1 | [
2 | "#dc5547",
3 | "#d5b1ad",
4 | "#ff8570",
5 | "#ff7155",
6 | "#ffa88d",
7 | "#ffc1ae",
8 | "#ff9b6b",
9 | "#d57845",
10 | "#fad2ba",
11 | "#ff9251",
12 | "#fdc594",
13 | "#d7c0a3",
14 | "#ffc267",
15 | "#fdd08a",
16 | "#87847d",
17 | "#f6b740",
18 | "#ca9d3d",
19 | "#cac5b8",
20 | "#817a66",
21 | "#aba384",
22 | "#f5e293",
23 | "#f9e165",
24 | "#b9b05e",
25 | "#c4ba38",
26 | "#dde04b",
27 | "#dde15e",
28 | "#a9b83e",
29 | "#dae894",
30 | "#c4dd69",
31 | "#c1df8c",
32 | "#bdc5b5",
33 | "#85b251",
34 | "#addc99",
35 | "#95db7b",
36 | "#9db8a0",
37 | "#4f9258",
38 | "#0ac66e",
39 | "#55d4a5",
40 | "#6cdab9",
41 | "#009d73",
42 | "#447065",
43 | "#00c699",
44 | "#00a07e",
45 | "#86d9d0",
46 | "#00cfbb",
47 | "#006866",
48 | "#8ed2da",
49 | "#007f92",
50 | "#82b8c4",
51 | "#00b2dc",
52 | "#008ac9",
53 | "#008fd1",
54 | "#5989a1",
55 | "#336698",
56 | "#659fdd",
57 | "#3c5e92",
58 | "#55647d",
59 | "#5957c4",
60 | "#bfb9da",
61 | "#9786d8",
62 | "#6943b8",
63 | "#703e9b",
64 | "#6a5e71",
65 | "#a139b8",
66 | "#c36cc8",
67 | "#b955a8",
68 | "#d091c3",
69 | "#e26cb4",
70 | "#b5799b",
71 | "#e74d9c",
72 | "#d1307e",
73 | "#af3971",
74 | "#f0c4d7",
75 | "#f683ae",
76 | "#f99dba",
77 | "#fa7499",
78 | "#f14772",
79 | "#ff91a2",
80 | "#ff6474",
81 | "#b24550",
82 | "#c44a4e",
83 | "#ffaeb0",
84 | "#fe5b5e",
85 | "#85163a",
86 | "#b01f2a",
87 | "#00a79b",
88 | "#808184",
89 | "#a6a8ab",
90 | "#d0d2d3"
91 | ]
92 |
--------------------------------------------------------------------------------
/config/deploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "staging": {
3 | "api": "api.run.pivotal.io",
4 | "name": "xray-staging",
5 | "manifest": "manifest-staging.yml",
6 | "org": "pivotal",
7 | "routes": ["cfapps.io -n xray-staging"],
8 | "space": "pivotal-ui"
9 | },
10 | "pws": {
11 | "api": "api.run.pivotal.io",
12 | "name": "xray",
13 | "manifest": "manifest.yml",
14 | "org": "pivotal",
15 | "routes": ["cfapps.io -n xray","xray.cf"],
16 | "space": "pivotal-ui"
17 | }
18 | }
--------------------------------------------------------------------------------
/config/development.json:
--------------------------------------------------------------------------------
1 | {
2 | "assetHost": "localhost",
3 | "assetPort": 3001
4 | }
--------------------------------------------------------------------------------
/config/production.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | }
--------------------------------------------------------------------------------
/config/test.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | }
--------------------------------------------------------------------------------
/config/webpack.js:
--------------------------------------------------------------------------------
1 | var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
2 | var commonsChunkPlugin = new CommonsChunkPlugin({
3 | name: 'common',
4 | filename: 'common.js'
5 | });
6 |
7 | module.exports = function(env = null) {
8 | return Object.assign({}, {
9 | entry: {
10 | application: './app/components/application.js',
11 | setup: './app/components/setup.js'
12 | },
13 | externals: {
14 | react: 'React',
15 | 'react/addons': 'React'
16 | },
17 | module: {
18 | loaders: [
19 | {test: /\.js$/, exclude: [/node_modules/], loader: 'babel-loader'},
20 | {test: /\.svg$/, exclude: [/node_modules/], loader: 'svg-loader'}
21 | ]
22 | },
23 | output: {
24 | filename: '[name].js',
25 | chunkFilename: '[id].js',
26 | pathinfo: true
27 | },
28 | plugins: [commonsChunkPlugin]
29 | }, env && require(`./webpack/${env}`));
30 | };
--------------------------------------------------------------------------------
/config/webpack/development.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | devtool: 'inline-source-map',
3 | watch: true
4 | };
--------------------------------------------------------------------------------
/config/webpack/production.js:
--------------------------------------------------------------------------------
1 | var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
2 | var UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
3 | var commonsChunkPlugin = new CommonsChunkPlugin({
4 | name: 'common',
5 | filename: 'common.js'
6 | });
7 | var uglifyPlugin = new UglifyJsPlugin({compress: {warnings: false}});
8 |
9 | module.exports = {
10 | plugins: [commonsChunkPlugin, uglifyPlugin]
11 | };
--------------------------------------------------------------------------------
/config/webpack/test.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | devtool: 'eval',
3 | entry: {
4 | spec: `./spec/spec.js`
5 | },
6 | plugins: null,
7 | resolve: {
8 | alias: {
9 | 'lodash.throttle': `${__dirname}/../../spec/support/mock_throttle.js`
10 | }
11 | },
12 | quiet: true,
13 | watch: true
14 | };
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | require('babel/register');
2 | process.env.NODE_ENV = process.env.NODE_ENV || 'development';
3 | var requireDir = require('require-dir');
4 | requireDir('./tasks');
5 |
--------------------------------------------------------------------------------
/helpers/application_helper.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | compact(array) {
3 | return array.filter(Boolean);
4 | }
5 | };
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var recluster = require('recluster'),
2 | path = require('path');
3 |
4 | var cluster = recluster(path.join(__dirname, 'server', 'bootstrap.js'), {readyWhen: 'ready', workers: 1});
5 | cluster.run();
6 |
7 | process.on('SIGUSR2', function() {
8 | console.log('Got SIGUSR2, reloading cluster...');
9 | cluster.reload();
10 | });
11 |
12 | console.log('spawned cluster, kill -s SIGUSR2', process.pid, 'to reload');
--------------------------------------------------------------------------------
/lattice-xray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vmware-archive/xray/83f174aabb405a2cc54e7720080bf9207ba19ba3/lattice-xray.png
--------------------------------------------------------------------------------
/manifest-ketchup.yml:
--------------------------------------------------------------------------------
1 | ---
2 | applications:
3 | - name: xray
4 | buildpack: nodejs_buildpack
5 | env:
6 | NODE_ENV: production
--------------------------------------------------------------------------------
/manifest-staging.yml:
--------------------------------------------------------------------------------
1 | ---
2 | applications:
3 | - name: xray-staging
4 | buildpack: nodejs_buildpack
5 | env:
6 | NODE_ENV: production
--------------------------------------------------------------------------------
/manifest.yml:
--------------------------------------------------------------------------------
1 | ---
2 | applications:
3 | - name: xray
4 | buildpack: nodejs_buildpack
5 | env:
6 | NODE_ENV: production
7 | instances: 4
8 | memory: 256M
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "xray",
3 | "version": "1.1.0",
4 | "description": "Peer into the Lattice",
5 | "main": "index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/pivotal-cf-experimental/xray.git"
9 | },
10 | "scripts": {
11 | "test": "./node_modules/.bin/gulp spec",
12 | "start": "node index.js"
13 | },
14 | "author": "pivotal",
15 | "license": "ISC",
16 | "devDependencies": {
17 | "babel-eslint": "^4.0.5",
18 | "babel-loader": "^5.1.4",
19 | "del": "^1.2.0",
20 | "dr-frankenstyle": "^0.1.6",
21 | "es6-promise": "^2.1.1",
22 | "eslint-plugin-react": "^3.2.0",
23 | "font-awesome": "^4.3.0",
24 | "foreman": "^1.4.0",
25 | "fs-extra": "^0.18.4",
26 | "gulp": "^3.9.0",
27 | "gulp-autoprefixer": "^2.3.1",
28 | "gulp-cached": "^1.1.0",
29 | "gulp-clone": "^1.0.0",
30 | "gulp-eslint": "^1.0.0",
31 | "gulp-file": "^0.2.0",
32 | "gulp-gzip": "^1.1.0",
33 | "gulp-header": "^1.2.2",
34 | "gulp-if": "^1.2.5",
35 | "gulp-jasmine": "^2.0.1",
36 | "gulp-jasmine-browser": "^0.1.4",
37 | "gulp-load-plugins": "^0.10.0",
38 | "gulp-loader": "^1.1.2",
39 | "gulp-minify-css": "^1.1.5",
40 | "gulp-plumber": "^1.0.1",
41 | "gulp-rename": "^1.2.2",
42 | "gulp-rev": "^4.0.0",
43 | "gulp-rev-css-url": "0.0.12",
44 | "gulp-sass": "^2.0.1",
45 | "gulp-sass-graph-abs": "^1.0.2",
46 | "gulp-sourcemaps": "^1.5.2",
47 | "gulp-util": "^3.0.5",
48 | "gulp-watch": "^4.2.4",
49 | "gulp-webserver": "git://github.com/rdy/gulp-webserver.git#8eb2b7ea1ed23945cc96612fe8b5e1ba8a86da76",
50 | "jasmine-ajax": "^3.1.1",
51 | "jasmine_dom_matchers": "^0.2.1",
52 | "jquery": "^2.1.4",
53 | "merge-stream": "^0.1.7",
54 | "methods": "^1.1.1",
55 | "mock-promises": "^0.3.0",
56 | "supertest": "^1.0.1",
57 | "svg-loader": "0.0.2",
58 | "through2": "^0.6.5",
59 | "vinyl": "^0.4.6",
60 | "webpack": "^1.9.10",
61 | "webpack-stream": "^2.1.0"
62 | },
63 | "dependencies": {
64 | "babel": "^5.5.5",
65 | "babel-core": "^5.5.5",
66 | "basic-auth": "^1.0.3",
67 | "body-parser": "^1.12.4",
68 | "classnames": "^2.1.3",
69 | "connect-gzip-static": "^1.0.0",
70 | "cookie-parser": "^1.3.5",
71 | "correcting-interval": "^2.0.0",
72 | "express": "^4.12.4",
73 | "faker": "^2.1.3",
74 | "lodash.camelcase": "^3.0.1",
75 | "lodash.compact": "^3.0.0",
76 | "lodash.flatten": "^3.0.2",
77 | "lodash.groupby": "^3.1.1",
78 | "lodash.max": "^3.3.1",
79 | "lodash.sortedindex": "^3.1.1",
80 | "lodash.throttle": "^3.0.3",
81 | "lodash.times": "^3.0.2",
82 | "node-event-emitter": "0.0.1",
83 | "pretty-bytes": "^2.0.1",
84 | "pui-css-dividers": "^1.10.0",
85 | "pui-css-ellipsis": "^1.10.0",
86 | "pui-css-links": "^1.10.0",
87 | "pui-css-typography": "^1.10.0",
88 | "pui-css-whitespace": "^1.10.0",
89 | "pui-cursor": "^1.3.2",
90 | "pui-event-source": "0.0.4",
91 | "pui-react-buttons": "^1.10.0",
92 | "pui-react-dividers": "^1.10.0",
93 | "pui-react-grids": "^1.10.0",
94 | "pui-react-iconography": "^1.10.0",
95 | "pui-react-lists": "^1.10.0",
96 | "pui-react-media": "^1.10.0",
97 | "pui-react-modals": "^1.10.0",
98 | "pui-react-overlay-trigger": "^1.10.0",
99 | "pui-react-portals": "^1.10.0",
100 | "pui-react-radio": "^1.10.0",
101 | "pui-react-radio-group": "^1.10.0",
102 | "pui-react-tooltip": "^1.10.0",
103 | "react": "^0.13.3",
104 | "react-bootstrap": "^0.23.1",
105 | "recluster": "^0.4.0",
106 | "require-dir": "^0.3.0",
107 | "rosie": "^0.4.1",
108 | "run-sequence": "^1.1.0",
109 | "serve-favicon": "^2.2.1",
110 | "superagent": "^1.2.0",
111 | "timeago": "git://github.com/rdy/node-timeago.git#75cb5efe9006217988b1c1809c74c71b2ba0d69d",
112 | "url-join": "0.0.1"
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/public/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vmware-archive/xray/83f174aabb405a2cc54e7720080bf9207ba19ba3/public/.gitkeep
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vmware-archive/xray/83f174aabb405a2cc54e7720080bf9207ba19ba3/screenshot.png
--------------------------------------------------------------------------------
/scripts/blue-green-deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | cf api $API
5 | cf app $NAME || cf login -o $ORG -s $SPACE
6 | BLUENAME=$NAME-blue
7 |
8 | cf push $BLUENAME -f $MANIFEST
9 |
10 | ROUTES=("${ROUTES[@]}")
11 |
12 | OIFS=$IFS
13 | IFS=','
14 | for ROUTE in ${ROUTES[@]}; do
15 | eval "cf map-route $BLUENAME $ROUTE"
16 | eval "cf unmap-route $NAME $ROUTE"
17 | done
18 |
19 | cf push $NAME -f $MANIFEST
20 |
21 | for ROUTE in ${ROUTES[@]}; do
22 | eval "cf map-route $NAME $ROUTE"
23 | eval "cf unmap-route $BLUENAME $ROUTE"
24 | done
25 | IFS=$OIFS
26 |
27 | cf stop $BLUENAME
--------------------------------------------------------------------------------
/scripts/open_spec_or_impl.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ -z "$2" ]; then
4 | echo "This script must be called with file path and project root" >&2
5 | exit 1
6 | fi
7 |
8 | if [[ "$1" =~ .*spec\.js ]]; then
9 | #in a spec file
10 | if [ -d "src" ]; then
11 | # source files are in src/, spec files are in spec/
12 | file_to_open_part1=${1/spec\//src\//}
13 | else
14 | # source files are in root/, spec files are in root/spec/
15 | file_to_open_part1=$2${1#$2"/spec"}
16 | fi
17 | file_to_open=${file_to_open_part1/_spec\.js/\.js}
18 | else
19 | if [ -d "src" ]; then
20 | # source files are in src/, spec files are in spec/
21 | file_to_open_part1=${1/src\//spec\//}
22 | else
23 | # source files are in root, spec files are in root/spec
24 | file_to_open_part1=$2"/spec"${1#$2}
25 | fi
26 | file_to_open=${file_to_open_part1/\.js/_spec\.js}
27 | fi
28 |
29 | touch $file_to_open
30 |
31 | wstorm $file_to_open
32 |
33 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | var application = require('./middleware/application_middleware');
2 | var bodyParser = require('body-parser');
3 | const {authenticate} = require('./middleware/auth_middleware');
4 | var cookieParser = require('cookie-parser');
5 | var express = require('express');
6 | var favicon = require('serve-favicon');
7 | var gzipStatic = require('connect-gzip-static');
8 | var receptorUrl = require('./middleware/receptor_url');
9 | var setup = require('./middleware/setup_middleware');
10 | var app = express();
11 |
12 | app.use(cookieParser());
13 | app.use(bodyParser.json());
14 | app.use(bodyParser.urlencoded({extended: true}));
15 | app.use(favicon(`${__dirname}/../app/images/favicon.ico`));
16 | app.use(gzipStatic(`${__dirname}/../public`, {maxAge: process.env.NODE_ENV === 'production' && 604800000}));
17 |
18 | app.get('/', authenticate, receptorUrl, ...application.show);
19 | app.get('/setup', authenticate, receptorUrl, ...setup.show);
20 | app.post('/setup', authenticate, receptorUrl, ...setup.create);
21 |
22 | var fakeApi = require('./middleware/fake_api');
23 | app.post('/demo/v1/auth_cookie', ...fakeApi.demo.authCookie.show);
24 | app.get('/demo/v1/cells', ...fakeApi.demo.cells.index);
25 | app.get('/demo/v1/actual_lrps', ...fakeApi.demo.actualLrps.index);
26 | app.get('/demo/v1/desired_lrps', ...fakeApi.demo.desiredLrps.index);
27 |
28 | if(process.env.NODE_ENV === 'development') {
29 | app.post('/perf/v1/auth_cookie', ...fakeApi.perf.authCookie.show);
30 | app.get('/perf/v1/cells', ...fakeApi.perf.cells.index);
31 | app.get('/perf/v1/actual_lrps', ...fakeApi.perf.actualLrps.index);
32 | app.get('/perf/v1/desired_lrps', ...fakeApi.perf.desiredLrps.index);
33 | }
34 | module.exports = app;
--------------------------------------------------------------------------------
/server/bootstrap.js:
--------------------------------------------------------------------------------
1 | require('babel/register');
2 | var app = require('./app');
3 | app.listen(process.env.PORT || 3000, function() {
4 | process.send && process.send({cmd: 'ready'});
5 | });
6 | module.exports = app;
--------------------------------------------------------------------------------
/server/components/layout.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | var types = React.PropTypes;
4 |
5 | var Body = React.createClass({
6 | propTypes: {
7 | config: types.object.isRequired,
8 | entry: types.func.isRequired,
9 | scripts: types.array.isRequired
10 | },
11 |
12 | render() {
13 | var {config, entry, scripts, className} = this.props;
14 | scripts = scripts.map(function(src, i) {
15 | return (