├── .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 | [![Build Status](https://travis-ci.org/pivotal-cf-experimental/xray.svg?branch=master)](https://travis-ci.org/pivotal-cf-experimental/xray) 5 | 6 | ![X-ray](../master/lattice-xray.png?raw=true) 7 | ![screenshot](../master/screenshot.png?raw=true) 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 |
65 | 66 | 67 |
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 |
    29 |
      {cells}
    30 |
    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 = ({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 |
      30 | {desiredLrps} 31 |
    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 |
    46 | 47 |

    If you are using Terraform to deploy Lattice, your username, password, and Receptor 48 | URL 49 | are printed when Lattice is successfully deployed.

    50 | 51 |

    If you are using Vagrant, your Receptor URL is: http://receptor.192.168.11.11.xip.io 52 |

    53 | 54 | 55 | 56 | 57 | 58 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 68 | 69 | 70 | 72 | 73 | 74 | 75 |
    76 | 77 | Close 78 | Launch X-Ray 79 | 80 |
    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 |
    62 | Edit Receptor 63 |
    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 |
    25 | Launch X-Ray 26 |
    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 |
    38 | 39 | toggle sidebar 40 | 41 | 42 | 43 |
    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 (); 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 (