From ccc2d409297fa0e805d5743c7c5d2c3a6a4a0e90 Mon Sep 17 00:00:00 2001 From: "Joshua F. Rountree" Date: Sun, 4 Nov 2018 23:49:02 -0500 Subject: [PATCH] PRE-BUILD COMMANDS! NOW IN CONSOLE! --- app/actions/eon_detail_actions.js | 106 +------------- app/actions/ui_actions.js | 8 + app/actions/zmq_actions.js | 7 +- app/app.global.scss | 9 +- app/background/zmq.js | 34 +++-- app/components/EonDetail/DriveViewer.js | 80 ---------- .../EonDetail/StateList/StateListToolbar.js | 97 ++++++++++++ app/components/EonDetail/StateList/index.js | 52 ++++--- app/components/EonDetail/index.js | 48 +++++- app/components/Terminal.js | 90 +++++------- app/components/commands/backup_openpilot.js | 25 ++++ app/components/commands/command.js | 138 ++++++++++++++++++ app/components/commands/get_fingerprint.js | 24 +++ app/components/commands/index.js | 15 ++ app/components/commands/install_openpilot.js | 33 +++++ .../commands/launch_android_settings.js | 22 +++ app/components/commands/reboot.js | 24 +++ app/components/commands/take_screenshot.js | 21 +++ app/constants/eon_detail_action_types.js | 10 +- app/constants/ui_action_types.js | 3 +- app/constants/zmq_action_types.js | 3 +- app/containers/App.js | 4 +- app/containers/EonDetailPage.js | 1 + app/main.dev.js | 4 +- app/package.json | 2 +- app/reducers/eon_detail_reducer.js | 20 +++ app/reducers/ui_reducer.js | 11 +- app/reducers/zmq_reducer.js | 25 ++-- app/sagas/eon_sagas.js | 7 + app/styles/command-bar.scss | 86 +++++++++++ app/styles/is-mac.scss | 2 + app/styles/state-list.scss | 14 ++ app/styles/tab-list.scss | 14 +- package.json | 1 + yarn.lock | 90 ++++++++---- 35 files changed, 805 insertions(+), 325 deletions(-) delete mode 100644 app/components/EonDetail/DriveViewer.js create mode 100644 app/components/EonDetail/StateList/StateListToolbar.js create mode 100644 app/components/commands/backup_openpilot.js create mode 100644 app/components/commands/command.js create mode 100644 app/components/commands/get_fingerprint.js create mode 100644 app/components/commands/index.js create mode 100644 app/components/commands/install_openpilot.js create mode 100644 app/components/commands/launch_android_settings.js create mode 100644 app/components/commands/reboot.js create mode 100644 app/components/commands/take_screenshot.js create mode 100644 app/styles/command-bar.scss create mode 100644 app/styles/state-list.scss diff --git a/app/actions/eon_detail_actions.js b/app/actions/eon_detail_actions.js index c50eb7e..f72ce91 100644 --- a/app/actions/eon_detail_actions.js +++ b/app/actions/eon_detail_actions.js @@ -1,51 +1,5 @@ import * as types from '../constants/eon_detail_action_types'; -// ACTION CREATORS -let pollerId; - -export function BEGIN_install(eon) { - return { - type: types.INSTALL, - payload: eon - }; -} - -export function SUCCESS_install() { - return { - type: types.INSTALL_SUCCESS - }; -} - -export function FAIL_install(err) { - return { - type: types.INSTALL_FAIL, - payload: { - err - } - }; -} - -export function BEGIN_uninstall() { - return { - type: types.UNINSTALL - }; -} - -export function SUCCESS_uninstall() { - return { - type: types.UNINSTALL_SUCCESS - }; -} - -export function FAIL_uninstall(err) { - return { - type: types.UNINSTALL_FAIL, - payload: { - err - } - }; -} - export function CHANGE_TAB(tab) { return { type: types.CHANGE_TAB, @@ -53,66 +7,22 @@ export function CHANGE_TAB(tab) { }; } -// SSH ACTION CREATORS -export function BEGIN_connectSSH() { +export function SHOW_COMMAND(tab) { return { - type: types.CONNECT_SSH - }; -} - - -export function SUCCESS_connectSSH() { - return { - type: types.CONNECT_SSH_SUCCESS - }; -} - -export function FAIL_connectSSH(err) { - return { - type: types.CONNECT_SSH_FAIL, - payload: { - err - } - }; -} - -export function BEGIN_sshCommand() { - return { - type: types.SSH_COMMAND - }; -} - -export function SUCCESS_sshCommand() { - return { - type: types.SSH_COMMAND_SUCCESS - }; -} - -export function RESPONSE_sshCommand(stdout,stderr) { - return { - type: types.SSH_COMMAND_RESPONSE, - payload: { - stderr, - stdout - } + type: types.SHOW_COMMAND, + payload: tab }; } -export function STOP_POLLING() { +export function HIDE_COMMAND() { return { - type: types.STOP_POLLING + type: types.HIDE_COMMAND }; } -export function FAIL_sshCommand(err) { +export function RUN_COMMAND(command) { return { - type: types.SSH_COMMAND_FAIL, - payload: { - err - } + type: types.RUN_COMMAND, + payload: command }; -} - -export function stopPolling() { - dispatch(STOP_POLLING); } \ No newline at end of file diff --git a/app/actions/ui_actions.js b/app/actions/ui_actions.js index ef0245c..2788135 100644 --- a/app/actions/ui_actions.js +++ b/app/actions/ui_actions.js @@ -1,7 +1,15 @@ import * as types from '../constants/ui_action_types'; + export function SET_TERMINAL_FONT_SIZE(fontSize) { return { type: types.SET_TERMINAL_FONT_SIZE, payload: fontSize }; +} +export function SET_STATE_LIST_DEPTH(depth) { + console.warn("Setting depth to: ", depth); + return { + type: types.SET_STATE_LIST_DEPTH, + payload: depth + }; } \ No newline at end of file diff --git a/app/actions/zmq_actions.js b/app/actions/zmq_actions.js index 15907f3..c12857a 100644 --- a/app/actions/zmq_actions.js +++ b/app/actions/zmq_actions.js @@ -22,4 +22,9 @@ export function ERROR(err) { type: types.ERROR, payload: err }; -} \ No newline at end of file +} +export function TOGGLE_PAUSE() { + return { + type: types.TOGGLE_PAUSE + }; +} diff --git a/app/app.global.scss b/app/app.global.scss index f3432e4..1ec1e60 100644 --- a/app/app.global.scss +++ b/app/app.global.scss @@ -17,6 +17,11 @@ $right-content-width: calc(100% - #{$left-bar-width}); body { background-color:#000; } +.react-json-view { + * { + opacity:1!important; + } +} .app-wrapper { $darker_col: 6; padding-top:0; @@ -37,6 +42,8 @@ body { @import "styles/tab-list"; @import "styles/tab-content"; @import "styles/loading-overlay"; + @import "styles/state-list"; + @import "styles/command-bar"; @import "styles/is-mac"; .container-fluid { @@ -69,7 +76,7 @@ body { position:relative; height:100%; width:100%; - padding:15px; + padding:0; .json-pretty { background-color:#000; } diff --git a/app/background/zmq.js b/app/background/zmq.js index ef0007b..c1fa72f 100644 --- a/app/background/zmq.js +++ b/app/background/zmq.js @@ -26,20 +26,22 @@ function onMessage(sender, event_message, service, cb) { if (service.id === "logMessage") { jsonData = JSON.parse(jsonData); } - newData = { - fields: Object.keys(jsonData), - latestMessage: jsonData - }; - if (!data[service.id]) { - data[service.id] = {}; - } + // newData = { + // , + // latestMessage: jsonData + // }; + data[service.id] = { - ...data[service.id], - ...newData + fields: Object.keys(jsonData), + latestMessage: jsonData, + messages: [ + ...data[service.id].messages, + jsonData + ] }; - + writeLog("message count: " + data[service.id].messages.length); let msgResp = {}; - msgResp[service.id] = data[service.id] + msgResp[service.id] = data[service.id]; sender.send(types.MESSAGE, msgResp); } @@ -52,6 +54,16 @@ export function startZmq() { ipcMain.on(types.CONNECT, (evt, ip, service) => { const { sender } = evt; const addr = `tcp://${ip}:${service.port}`; + if (!data[service.id]) { + data[service.id] = { + latestMessage: {}, + messages: [] + }; + } + // if (!data[service.id].messages) { + // data[service.id].messages = []; + // } + data[service.id].messages = []; msgHandler = throttle((msg) => { return onMessage(sender, msg, service); }, 200, { leading: true }); sock.on('message', msgHandler); writeLog(`Connecting to ${addr}`, service.key); diff --git a/app/components/EonDetail/DriveViewer.js b/app/components/EonDetail/DriveViewer.js deleted file mode 100644 index e0d6806..0000000 --- a/app/components/EonDetail/DriveViewer.js +++ /dev/null @@ -1,80 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; -import ReactPlayer from 'react-player'; -import moment from 'moment'; -import * as commaEndpoints from '../../constants/comma_endpoints.json'; -import * as EonActions from '../../actions/eon_detail_actions'; -import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; -const propTypes = { - activeDrive: PropTypes.number, - driveData: PropTypes.object, - driveVideoUrl: PropTypes.string -}; - -class DriveViewer extends Component { - close = () => { - this.props.CLOSE_DRIVE(); - } - - render() { - const { activeDrive, driveData, driveVideoUrl } = this.props; - console.warn("Drive:",driveData); - console.warn("Drive Video:", driveVideoUrl); - if (driveData) { - const start_time = moment(driveData.start_time); - return ( -
- - {driveData.start_geocode} to {driveData.end_geocode} on {driveData.start_time} - - {activeDrive && driveVideoUrl && - - } - - - - - -
- ); - } else { - return (
); - } - } -}; - -function mapStateToProps(state) { - const { eonDetail } = state; - const { activeDrive } = eonDetail; - let activeDriveData; - let driveVideoUrl; - if (activeDrive) { - activeDriveData = state.eonDetail.drives[activeDrive]; - driveVideoUrl = `${commaEndpoints.Video.Base}${commaEndpoints.Video.Endpoint.hls.replace(':segment_string',activeDriveData.sig_path)}`; - } - - - return { - driveData: activeDriveData, - activeDrive, - driveVideoUrl - }; -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators(EonActions, dispatch); -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(DriveViewer); \ No newline at end of file diff --git a/app/components/EonDetail/StateList/StateListToolbar.js b/app/components/EonDetail/StateList/StateListToolbar.js new file mode 100644 index 0000000..99055df --- /dev/null +++ b/app/components/EonDetail/StateList/StateListToolbar.js @@ -0,0 +1,97 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import * as ZmqActions from '../../../actions/zmq_actions'; +import * as UiActions from '../../../actions/ui_actions'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { Nav, NavItem, Dropdown, DropdownItem, DropdownToggle, DropdownMenu, NavLink } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +const propTypes = { + paused: PropTypes.bool, + depth: PropTypes.number, + messageCount: PropTypes.number +}; + +class StateListToolbar extends Component { + constructor(props) { + super(props); + + this.state = { + dropdownOpen: false + }; + } + + toggle = () => { + this.setState({ + dropdownOpen: !this.state.dropdownOpen + }); + } + setDepth = (depth) => { + this.props.SET_STATE_LIST_DEPTH(depth); + } + togglePause = () => { + this.props.TOGGLE_PAUSE(); + } + + render() { + const { depth, messageCount, paused } = this.props; + return ( + + ) + } +} + +StateListToolbar.propTypes = propTypes; + +function mapDispatchToProps(dispatch) { + return bindActionCreators({...ZmqActions,...UiActions}, dispatch); +} + +const mapStateToProps = (state, {type}) => { + let messageCount = 0; + let depth = 1; + let paused = false; + + if (state.zmq.data && state.zmq.data[type]) { + if (state.zmq.data[type].messages) { + messageCount = state.zmq.data[type].messages.length; + } + } + + if (state.ui && state.ui.stateListDepth) { + depth = state.ui.stateListDepth; + } + paused = state.zmq.paused; + return { + messageCount, + depth, + paused + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(StateListToolbar); \ No newline at end of file diff --git a/app/components/EonDetail/StateList/index.js b/app/components/EonDetail/StateList/index.js index 8736128..3032b8e 100644 --- a/app/components/EonDetail/StateList/index.js +++ b/app/components/EonDetail/StateList/index.js @@ -6,8 +6,10 @@ import StateListItem from './StateListItem'; import LoadingOverlay from '../../LoadingOverlay'; import services from '../../../constants/service_list.yaml'; import JSONPretty from 'react-json-pretty'; +import ReactJson from 'react-json-view'; import * as ZmqActions from '../../../actions/zmq_actions'; -import { ListGroup, Card, CardHeader, CardBody, } from 'reactstrap' +// import { ListGroup, Card, CardHeader, CardBody } from 'reactstrap' +import StateListToolbar from './StateListToolbar'; const propTypes = { // messageCount: PropTypes.number, // messages: PropTypes.array, @@ -15,22 +17,13 @@ const propTypes = { // latestMessage: PropTypes.object, // sampling: PropTypes.bool, data: PropTypes.any, - type: PropTypes.string + type: PropTypes.string, + depth: PropTypes.number }; class StateList extends React.PureComponent { service = services[this.props.type] - clearSampling = () => { - this.setState({ - messages: [] - }); - } - - toggleSampling = () => { - this.setState({ - sampling: !this.state.sampling - }); - } + componentDidMount(props) { this.props.CONNECT(this.props.type); } @@ -38,12 +31,8 @@ class StateList extends React.PureComponent { this.props.DISCONNECT(this.props.type); } render() { - let { type, data } = this.props; - // let { fields } = this.service; - - // let items = fields.map((field) => { - // return - // }); + let { type, data, depth } = this.props; + let loadingMessage = "Waiting for messages..."; if (!data) { @@ -51,6 +40,24 @@ class StateList extends React.PureComponent { } if (data) { + return ( +
+
+ +
+
+ +
+
+ ); return (
); } else { return (
); @@ -66,12 +73,17 @@ function mapDispatchToProps(dispatch) { const mapStateToProps = (state, {type}) => { let data; + let depth = 1; if (state.zmq.data && state.zmq.data[type] && state.zmq.data[type].latestMessage) { data = state.zmq.data[type].latestMessage; } + if (state.ui && state.ui.stateListDepth >= 0) { + depth = state.ui.stateListDepth; + } return { - data + data, + depth }; }; diff --git a/app/components/EonDetail/index.js b/app/components/EonDetail/index.js index 4c83918..ba24da4 100644 --- a/app/components/EonDetail/index.js +++ b/app/components/EonDetail/index.js @@ -8,7 +8,9 @@ import Layout from '../Layout'; import StateList from './StateList'; import { TabContent, Nav, NavItem, NavLink, TabPane } from 'reactstrap'; import Terminal from '../Terminal'; - +import commands from '../commands'; +import inflection from 'inflection'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; const propTypes = { activeTab: PropTypes.string, eon: PropTypes.object, @@ -18,16 +20,38 @@ const propTypes = { services: PropTypes.object }; -class EonDetail extends Component { +class EonDetail extends React.PureComponent { setTab = (tab) => { this.props.CHANGE_TAB(tab); } + showCommand = (tab) => { + this.props.SHOW_COMMAND(tab); + } + runCommand = (command) => { + this.props.RUN_COMMAND(command); + } render() { - const { activeTab, network, devices, currentStateKeys, eon, services, serviceIds } = this.props; + const { activeTab, activeCommand, network, devices, currentStateKeys, eon, services, serviceIds } = this.props; + const commandKeys = Object.keys(commands); + if (network === 'disconnected' || eon == null) { return (); } let stateTabs, statePanes; + let commandTabs = commandKeys.map((key,index) => { + return ( + { this.showCommand(key); }}> + {inflection.titleize(inflection.underscore(key)).replace('Eon','EON')} + + + ); + }); stateTabs = serviceIds.map((key) => { const service = services[key]; @@ -50,7 +74,7 @@ class EonDetail extends Component { statePanes = serviceIds.map((key) => { const service = services[key]; return ( - + {activeTab === key && } ); @@ -58,10 +82,15 @@ class EonDetail extends Component { const contextActions = [ - + + + ]; - + let CommandPane; + if (activeCommand) { + CommandPane = commands[activeCommand]; + } return ( - - + + {statePanes} diff --git a/app/components/Terminal.js b/app/components/Terminal.js index ed0aa2e..5c866e4 100644 --- a/app/components/Terminal.js +++ b/app/components/Terminal.js @@ -3,9 +3,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Terminal } from 'xterm'; import { remote, clipboard } from 'electron'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; const { app } = remote; import path from 'path'; - // import * as attach from 'xterm/lib/addons/attach/attach'; import * as attach from '../addons/attach'; import * as fit from 'xterm/lib/addons/fit/fit'; @@ -26,7 +27,6 @@ class ReactTerminal extends React.Component { super(props); this.HOST = `127.0.0.1:9788`; this.SOCKET_URL = `ws://${this.HOST}/terminals/`; - // this.elementId = `terminal_${ getId() }`; this.failures = 0; this.interval = null; this.fontSize = 16; @@ -47,39 +47,18 @@ class ReactTerminal extends React.Component { this.termRef = component; } onOpen(termOptions) { - // we need to delay one frame so that styles - // get applied and we can make an accurate measurement - // of the container width and height requestAnimationFrame(() => { - // at this point it would make sense for character - // measurement to have taken place but it seems that - // xterm.js might be doing this asynchronously, so - // we force it instead - // eslint-disable-next-line no-debugger - //debugger; - // this.term.charMeasure.measure(termOptions); this.fitResize(); }); } getTermDocument() { - // eslint-disable-next-line no-console - // console.warn( - // 'The underlying terminal engine of Workbench no longer ' + - // 'uses iframes with individual `document` objects for each ' + - // 'terminal instance. This method call is retained for ' + - // "backwards compatibility reasons. It's ok to attach directly" + - // 'to the `document` object of the main `window`.' - // ); return document; } onWindowResize() { this.fitResize(); } - - // intercepting paste event for any necessary processing of - // clipboard data, if result is falsy, paste event continues onWindowPaste(e) { if (!this.props.isTermActive) return; @@ -90,7 +69,6 @@ class ReactTerminal extends React.Component { this.term.send(processed); } } - onMouseUp(e) { if (this.props.quickEdit && e.button === 2) { if (this.term.hasSelection()) { @@ -106,27 +84,21 @@ class ReactTerminal extends React.Component { write(data) { this.term.write(data); } - focus() { this.term.focus(); } - clear() { this.term.clear(); } - reset() { this.term.reset(); } - resize(cols, rows) { this.term.resize(cols, rows); } - selectAll() { this.term.selectAll(); } - fitResize() { if (!this.termWrapperRef) { return; @@ -182,14 +154,7 @@ class ReactTerminal extends React.Component { if (props.onCursorMove) { this.term.on('cursormove', () => { - const cursorFrame = { - x: this.term.buffer.x * this.term.renderer.dimensions.actualCellWidth, - y: this.term.buffer.y * this.term.renderer.dimensions.actualCellHeight, - width: this.term.renderer.dimensions.actualCellWidth, - height: this.term.renderer.dimensions.actualCellHeight, - col: this.term.buffer.y, - row: this.term.buffer.x - }; + e props.onCursorMove(cursorFrame); }); } @@ -274,11 +239,19 @@ class ReactTerminal extends React.Component { }); } render() { + const { activeCommand, CommandPane } = this.props; return ( -
-
+
+ {activeCommand && CommandPane && +
+ { console.warn("Sending command: ",command); this.sendCommand (command); }} /> +
+ } +
+
+
); } @@ -351,23 +324,26 @@ class ReactTerminal extends React.Component { ReactTerminal.propTypes = { eonIp: PropTypes.string, - options: PropTypes.object + options: PropTypes.object, + activeCommand: PropTypes.string }; -function listenToWindowResize(callback) { - var resizeTimeout; +function mapDispatchToProps(dispatch) { + // return bindActionCreators(EonActions, dispatch); +} - function resizeThrottler() { - // ignore resize events as long as an actualResizeHandler execution is in the queue - if (!resizeTimeout) { - resizeTimeout = setTimeout(function() { - resizeTimeout = null; - callback(); - }, 66); - } - } +const mapStateToProps = (state, ownProps) => { + let activeCommand; - window.addEventListener('resize', resizeThrottler, false); -} + if (state.eonDetail.activeCommand) { + activeCommand = state.eonDetail.activeCommand; + } + return { + activeCommand + } +}; -export default ReactTerminal; +export default connect( + mapStateToProps, + // mapDispatchToProps +)(ReactTerminal); \ No newline at end of file diff --git a/app/components/commands/backup_openpilot.js b/app/components/commands/backup_openpilot.js new file mode 100644 index 0000000..d961132 --- /dev/null +++ b/app/components/commands/backup_openpilot.js @@ -0,0 +1,25 @@ +import React, { PureComponent } from 'react'; +import TerminalCommand from './command'; + +class BackupOpenpilot extends PureComponent { + constructor(props) { + super(props); + this.name = "Backup Openpilot"; + this.description = "Copies your current openpilot installation to openpilot.bak."; + + this.commands = [ + "cd /data", + "cp -rf ./openpilot ./openpilot.bak" + ]; + } + + render() { return( + ); + } +} + +export default BackupOpenpilot; \ No newline at end of file diff --git a/app/components/commands/command.js b/app/components/commands/command.js new file mode 100644 index 0000000..06c5387 --- /dev/null +++ b/app/components/commands/command.js @@ -0,0 +1,138 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { Button, ButtonGroup, Form, FormGroup, Label, Input } from 'reactstrap'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import * as EonActions from '../../actions/eon_detail_actions'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +const propTypes = { + name: PropTypes.string, + description: PropTypes.string, + commands: PropTypes.array, + fields: PropTypes.array, + runCommand: PropTypes.func +}; + +class TerminalCommand extends React.PureComponent { + constructor(props) { + super(props); + this.onEntering = this.onEntering.bind(this); + this.onEntered = this.onEntered.bind(this); + this.onExiting = this.onExiting.bind(this); + this.onExited = this.onExited.bind(this); + this.toggle = this.toggle.bind(this); + this.state = { collapse: false, status: 'Closed' }; + } + + onEntering() { + this.setState({ status: 'Opening...' }); + } + + onEntered() { + this.setState({ status: 'Opened' }); + } + + onExiting() { + this.setState({ status: 'Closing...' }); + } + + onExited() { + this.setState({ status: 'Closed' }); + } + + toggle() { + this.setState({ collapse: !this.state.collapse }); + } + + buildCommand = () => { + let fullCommand = this.props.commands.map((command) => { + if (this.props.fields) { + this.props.fields.forEach((field) => { + const fieldName = field[0]; + const fieldLabel = field[1]; + const fieldType = field[2]; + const fieldPlaceholder = field[3]; + let matchToReplace = `%${fieldName}%`; + let valueToSet = fieldPlaceholder; + + if (this[fieldName]) { + valueToSet = this[fieldName]; + } + if (valueToSet) { + command = command.replace(matchToReplace,valueToSet); + } + }); + } + return command; + }).join("; ") + "\r"; + + return fullCommand; + } + + runCommand = () => { + let command = this.buildCommand(); + if (command) { + if (this.props.onRunCommand) { + this.props.onRunCommand(command); + } + this.props.RUN_COMMAND(command); + } + } + hideCommand = () => { + this.props.HIDE_COMMAND(); + } + setFieldValue = (field, value) => { + console.warn(`${field}: ${value}`); + this[field] = value; + this.buildCommand(); + } + + render() { + const { name, description, commands, fields, children } = this.props; + let fieldTags; + if (fields && fields.length) { + fieldTags = fields.map((field, index) => { + const fieldName = field[0]; + const fieldLabel = field[1]; + const fieldType = field[2]; + const fieldPlaceholder = field[3]; + return ( + + + { this.setFieldValue(fieldName,evt.target.value);}} defaultValue={fieldPlaceholder} /> + + ); + }); + } + + return (
+

{name}

+

{description}

+
+ {this.fullCommand} + {fieldTags} + {children} + + + + +
+
); + } +} + +TerminalCommand.propTypes = propTypes; + +function mapDispatchToProps(dispatch,ownProps) { + return bindActionCreators(EonActions, dispatch); +} + +const mapStateToProps = (state, ownProps) => { + return {}; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(TerminalCommand); \ No newline at end of file diff --git a/app/components/commands/get_fingerprint.js b/app/components/commands/get_fingerprint.js new file mode 100644 index 0000000..785ee64 --- /dev/null +++ b/app/components/commands/get_fingerprint.js @@ -0,0 +1,24 @@ +import React, { PureComponent } from 'react'; +import TerminalCommand from './command'; + +class GetFingerprint extends PureComponent { + constructor(props) { + super(props); + this.name = "Get Fingerprint"; + this.description = "Openpilot needs to successfully match the car's CAN messages. This is called the fingerprint. Executing this command can help you figure out if Openpilot sees your vehicle."; + this.commands = [ + "cd /data/openpilot/selfdrive", + "PYTHONPATH=/data/openpilot PREPAREONLY=1 /data/openpilot/selfdrive/debug/get_fingerprint.py" + ]; + } + + render() { return( + ); + } +} + +export default GetFingerprint; \ No newline at end of file diff --git a/app/components/commands/index.js b/app/components/commands/index.js new file mode 100644 index 0000000..9bbe143 --- /dev/null +++ b/app/components/commands/index.js @@ -0,0 +1,15 @@ +import GetFingerprint from './get_fingerprint'; +import InstallOpenpilot from './install_openpilot'; +import BackupOpenpilot from './backup_openpilot'; +import RebootEon from './reboot'; +import LaunchAndroidSettings from './launch_android_settings'; +import TakeScreenshot from './take_screenshot'; + +export default { + InstallOpenpilot, + BackupOpenpilot, + GetFingerprint, + // TakeScreenshot, + LaunchAndroidSettings, + RebootEon +}; \ No newline at end of file diff --git a/app/components/commands/install_openpilot.js b/app/components/commands/install_openpilot.js new file mode 100644 index 0000000..a7b0508 --- /dev/null +++ b/app/components/commands/install_openpilot.js @@ -0,0 +1,33 @@ +import React, { PureComponent } from 'react'; +import TerminalCommand from './command'; + +class InstallOpenpilot extends PureComponent { + constructor(props) { + super(props); + this.name = "Install Openpilot"; + this.description = "It's not that difficult, but its nice sometimes to have some automation!"; + this.fields = [ + ["gitUrl", "Git URL", "textarea", "https://github.com/commaai/openpilot.git"], + ["gitBranch", "Branch", "text", "release2"] + ]; + this.commands = [ + "cd /data", + "cp -rf ./openpilot ./openpilot.bak", + "rm -rf ./openpilot", + "git clone %gitUrl% openpilot", + "cd openpilot", + "git checkout %gitBranch%" + ]; + } + + render() { return( + ); + } +} + +export default InstallOpenpilot; \ No newline at end of file diff --git a/app/components/commands/launch_android_settings.js b/app/components/commands/launch_android_settings.js new file mode 100644 index 0000000..b101eae --- /dev/null +++ b/app/components/commands/launch_android_settings.js @@ -0,0 +1,22 @@ +import React, { PureComponent } from 'react'; +import TerminalCommand from './command'; + +class LaunchAndroidSettings extends PureComponent { + constructor(props) { + super(props); + this.name = "Launch Android Settings"; + this.description = "This opens the base-Android system settings. Allows you to change things that the EON interfaces do not."; + this.commands = [ + "am start -a android.settings.SETTINGS" + ]; + } + render() { return( + ); + } +} + +export default LaunchAndroidSettings; \ No newline at end of file diff --git a/app/components/commands/reboot.js b/app/components/commands/reboot.js new file mode 100644 index 0000000..a0f8d75 --- /dev/null +++ b/app/components/commands/reboot.js @@ -0,0 +1,24 @@ +import React, { PureComponent } from 'react'; +import TerminalCommand from './command'; + +class Reboot extends PureComponent { + constructor(props) { + super(props); + this.name = "Reboot EON"; + this.description = "Will tell your EON to reboot."; + this.fields = []; + this.commands = [ + "reboot" + ]; + } + + render() { return( + ); + } +} + +export default Reboot; \ No newline at end of file diff --git a/app/components/commands/take_screenshot.js b/app/components/commands/take_screenshot.js new file mode 100644 index 0000000..69c6c89 --- /dev/null +++ b/app/components/commands/take_screenshot.js @@ -0,0 +1,21 @@ +import React, { PureComponent } from 'react'; +import TerminalCommand from './command'; + +class TakeScreenshot extends PureComponent { + constructor(props) { + super(props); + this.name = "Take Screenshot"; + this.description = "Snap a picture of the EON screen and save it to your computer."; + this.commands = []; + } + + render() { return( + ); + } +} + +export default TakeScreenshot; \ No newline at end of file diff --git a/app/constants/eon_detail_action_types.js b/app/constants/eon_detail_action_types.js index e3b48c4..b8413d7 100644 --- a/app/constants/eon_detail_action_types.js +++ b/app/constants/eon_detail_action_types.js @@ -42,9 +42,11 @@ export const CONNECT_SSH = 'eon/CONNECT_SSH'; export const CONNECT_SSH_SUCCESS = 'eon/CONNECT_SSH_SUCCESS'; export const CONNECT_SSH_FAIL = 'eon/CONNECT_SSH_FAIL'; -export const SSH_COMMAND = 'eon/SSH_COMMAND'; -export const SSH_COMMAND_SUCCESS = 'eon/SSH_COMMAND_SUCCESS'; -export const SSH_COMMAND_RESPONSE = 'eon/SSH_COMMAND_RESPONSE'; -export const SSH_COMMAND_FAIL = 'eon/SSH_COMMAND_FAIL'; +export const SHOW_COMMAND = 'eon/SHOW_COMMAND'; +export const HIDE_COMMAND = 'eon/HIDE_COMMAND'; +export const RUN_COMMAND = 'eon/RUN_COMMAND'; +export const RUN_COMMAND_SUCCESS = 'eon/RUN_COMMAND_SUCCESS'; +export const RUN_COMMAND_RESPONSE = 'eon/RUN_COMMAND_RESPONSE'; +export const RUN_COMMAND_FAIL = 'eon/RUN_COMMAND_FAIL'; export const STOP_POLLING = 'eon/STOP_POLLING'; \ No newline at end of file diff --git a/app/constants/ui_action_types.js b/app/constants/ui_action_types.js index 66af2c9..c9d3432 100644 --- a/app/constants/ui_action_types.js +++ b/app/constants/ui_action_types.js @@ -1 +1,2 @@ -export const SET_TERMINAL_FONT_SIZE = 'ui/SET_TERMINAL_FONT_SIZE'; \ No newline at end of file +export const SET_TERMINAL_FONT_SIZE = 'ui/SET_TERMINAL_FONT_SIZE'; +export const SET_STATE_LIST_DEPTH = 'ui/SET_STATE_LIST_DEPTH'; \ No newline at end of file diff --git a/app/constants/zmq_action_types.js b/app/constants/zmq_action_types.js index a307077..656d7ac 100644 --- a/app/constants/zmq_action_types.js +++ b/app/constants/zmq_action_types.js @@ -1,4 +1,5 @@ export const CONNECT = 'zmq/CONNECT'; export const DISCONNECT = 'zmq/DISCONNECT'; export const MESSAGE = 'zmq/MESSAGE'; -export const ERROR = 'zmq/ERROR'; \ No newline at end of file +export const ERROR = 'zmq/ERROR'; +export const TOGGLE_PAUSE = 'zmq/TOGGLE_PAUSE'; \ No newline at end of file diff --git a/app/containers/App.js b/app/containers/App.js index a7d3c24..0dbf2dd 100644 --- a/app/containers/App.js +++ b/app/containers/App.js @@ -5,10 +5,10 @@ import EonDetail from '../components/EonDetail'; import * as NetworkConnectionActions from '../actions/network_connection_actions'; import { library } from '@fortawesome/fontawesome-svg-core'; import { fab } from '@fortawesome/free-brands-svg-icons'; -import { faPause, faSpinnerThird, faUndo, faCheck, faCircle, faTimesOctagon, faSync, faChevronLeft, faChevronRight, faPlus } from '@fortawesome/pro-solid-svg-icons'; +import { faPause, faPlay, faSpinnerThird, faUndo, faCheck, faCircle, faTimesOctagon, faSync, faChevronLeft, faChevronRight, faPlus } from '@fortawesome/pro-solid-svg-icons'; import { faGithub } from '@fortawesome/free-brands-svg-icons'; -library.add(faPause,faUndo,faSpinnerThird, faCheck,faCircle, faGithub, faTimesOctagon, faSync, faChevronLeft, faPlus, faChevronRight); +library.add(faPause,faUndo,faPlay,faSpinnerThird, faCheck,faCircle, faGithub, faTimesOctagon, faSync, faChevronLeft, faPlus, faChevronRight); function mapStateToProps(state) { return {}; diff --git a/app/containers/EonDetailPage.js b/app/containers/EonDetailPage.js index 00bdfe3..d92c63a 100644 --- a/app/containers/EonDetailPage.js +++ b/app/containers/EonDetailPage.js @@ -6,6 +6,7 @@ import services from '../constants/service_list.yaml'; function mapStateToProps(state) { return { activeTab: state.eonDetail.activeTab, + activeCommand: state.eonDetail.activeCommand, selectedEon: state.eonList.selectedEon, eon: state.eonList.eons[state.eonList.selectedEon], network: state.networkConnection.status, diff --git a/app/main.dev.js b/app/main.dev.js index 77be365..b074c86 100644 --- a/app/main.dev.js +++ b/app/main.dev.js @@ -100,8 +100,8 @@ app.on('ready', async () => { frame: (process.platform !== 'darwin') ? true : false, titleBarStyle: (process.platform !== 'darwin') ? null : "hiddenInset", backgroundColor: "#000000", - minWidth: 540, - minHeight: 640 + minWidth: 320, + minHeight: 240 }); let webContents = mainWindow.webContents; diff --git a/app/package.json b/app/package.json index 7d3cf98..321afa1 100644 --- a/app/package.json +++ b/app/package.json @@ -1,7 +1,7 @@ { "name": "workbench", "productName": "Workbench", - "version": "0.0.32", + "version": "0.0.34", "description": "The Openpilot Workbench is to help people fix problems with EON, Openpilot, etc.", "main": "./main.prod.js", "author": { diff --git a/app/reducers/eon_detail_reducer.js b/app/reducers/eon_detail_reducer.js index 1d364ea..6627d5a 100644 --- a/app/reducers/eon_detail_reducer.js +++ b/app/reducers/eon_detail_reducer.js @@ -1,11 +1,14 @@ /* reducer for managing state for individual eon */ import * as types from '../constants/eon_detail_action_types'; import settings from 'electron-settings'; +import { min } from 'gl-matrix/src/gl-matrix/vec2'; const defaultTmuxLogLength = 300; const initialState = { activeTab: 'console', + activeCommand: null, + lastRunCommand: null, previousTab: null, updated: null, vehicleConnection: null, @@ -32,9 +35,26 @@ export default function eonDetailReducer(state = initialState, action) { case types.CHANGE_TAB: return { ...state, + activeCommand: null, previousTab: state.activeTab, activeTab: action.payload }; + case types.RUN_COMMAND: + return { + ...state, + lastRunCommand: action.payload, + activeCommand: null + }; + case types.SHOW_COMMAND: + return { + ...state, + activeCommand: action.payload + }; + case types.HIDE_COMMAND: + return { + ...state, + activeCommand: null + }; case types.CONNECT: return { ...state, diff --git a/app/reducers/ui_reducer.js b/app/reducers/ui_reducer.js index 883294b..b8d18f4 100644 --- a/app/reducers/ui_reducer.js +++ b/app/reducers/ui_reducer.js @@ -1,8 +1,9 @@ /* reducer for managing list of eons and scanning for eons */ -import * as types from '../constants/network_scanner_action_types'; +import * as types from '../constants/ui_action_types'; const initialState = { - terminalFontSize: '16px' + terminalFontSize: '16px', + stateListDepth: 1 }; const deleteProperty = ({[key]: _, ...newObj}, key) => newObj; @@ -13,6 +14,12 @@ export default function uiReducer(state = initialState, action) { ...state, terminalFontSize: action.payload } + case types.SET_STATE_LIST_DEPTH: + console.warn("Set depth to: ", action.payload); + return { + ...state, + stateListDepth: action.payload + } default: return state; } diff --git a/app/reducers/zmq_reducer.js b/app/reducers/zmq_reducer.js index 14c92a4..cb1d7fb 100644 --- a/app/reducers/zmq_reducer.js +++ b/app/reducers/zmq_reducer.js @@ -10,18 +10,23 @@ const deleteProperty = ({[key]: _, ...newObj}, key) => newObj; export default function zmqReducer(state = initialState, action) { switch (action.type) { case types.MESSAGE: + if (!state.paused) { + return { + ...state, + data: { + ...state.data, + ...action.payload + } + }; + } + return { + ...state + }; + case types.TOGGLE_PAUSE: return { ...state, - data: { - ...state.data, - ...action.payload - } - } - // case types.SET_TERMINAL_FONT_SIZE: - // return { - // ...state, - // terminalFontSize: action.payload - // } + paused: !state.paused + }; default: return state; } diff --git a/app/sagas/eon_sagas.js b/app/sagas/eon_sagas.js index 48e69c0..762734e 100644 --- a/app/sagas/eon_sagas.js +++ b/app/sagas/eon_sagas.js @@ -80,7 +80,13 @@ function* addEonListError() { ) ); } +function* handleRunCommand(action) { + const command = action.payload; + if (command === 'reboot') { + yield put(push(routes.EON_LIST)); + } +} function* handleSelectEon(action) { const { eonDetail } = yield select(); const { activeTab } = eonDetail; @@ -94,6 +100,7 @@ export function* eonSagas() { // console.warn("types:",types); yield all([ + takeLatest(types.RUN_COMMAND, handleRunCommand), takeLatest(eonListTypes.SELECT_EON, handleSelectEon), // takeLatest('@@router/LOCATION_CHANGE', routeWatcher), takeEvery(types.CONNECT_FAILED, addEonListError), diff --git a/app/styles/command-bar.scss b/app/styles/command-bar.scss new file mode 100644 index 0000000..ef335d8 --- /dev/null +++ b/app/styles/command-bar.scss @@ -0,0 +1,86 @@ +.command-box { + position: absolute; + right: 0; + top: 0; + width:250px; + height:100%; + z-index: 1000; + background-color:rgba(#000,0.8); + color:#FFF; + border-left:1px solid rgba(#FFF,0.1); + .form-group { + margin:0; + padding:15px; + label { + font-size:12px; + } + input,textarea { + border-radius:0; + font-size:12px; + padding:10px; + line-height:1em; + height:initial; + } + } + h3,p { + padding:5px 10px; + } + + h3 { + color: rgba(255, 255, 255, 0.8); + font-size: 14px; + background-color: rgba(18, 18, 18, 0.9); + margin-bottom: 0; + padding: 10px; + } + + .btn-group { + display:flex; + position:absolute; + bottom:0; + left:0; + width:100%; + .btn { + flex:1; + border-radius:0; + background-color:#212121; + border:0; + position:relative; + font-size:12px; + &:first-child { + background-color:var(--blue); + &:before { + content: ""; + background-color: rgba(0, 0, 0, 0.15); + width: 1px; + top: 0; + height: 100%; + position: absolute; + right: 0; + z-index: 10000; + } + } + } + } + p { + color:rgba(#FFF,0.8); + font-size:12px; + padding-top:5px; + } + > div > .btn { + display:block; + width:100%; + border-radius:0; + background-color:#212121; + border:0; + outline: none; + margin:0; + font-size:12px; + box-shadow:none; + + &:active,&:focus,&:hover { + box-shadow:none; + outline:none; + } + } +} \ No newline at end of file diff --git a/app/styles/is-mac.scss b/app/styles/is-mac.scss index da0bbd3..56e7c12 100644 --- a/app/styles/is-mac.scss +++ b/app/styles/is-mac.scss @@ -10,6 +10,8 @@ @import "styles/tab-list"; @import "styles/tab-content"; @import "styles/loading-overlay"; + @import "styles/state-list"; + @import "styles/command-bar"; .top-bar { .title { diff --git a/app/styles/state-list.scss b/app/styles/state-list.scss new file mode 100644 index 0000000..16b80e9 --- /dev/null +++ b/app/styles/state-list.scss @@ -0,0 +1,14 @@ +.state-toolbar { + background-color:rgba(#000,0.3335); + + .btn-toolbar { + .btn-group { + .btn { + border-radius:0; + } + } + } +} +.state-data { + padding:15px; +} \ No newline at end of file diff --git a/app/styles/tab-list.scss b/app/styles/tab-list.scss index da3df57..1c7f2c9 100644 --- a/app/styles/tab-list.scss +++ b/app/styles/tab-list.scss @@ -16,6 +16,18 @@ overflow-x: hidden; overflow-y: auto; .nav-item { + width:100%; + > .nav { + .nav-item { + .nav-link { + padding-left:30px; + &.active { + font-weight:normal; + background-color:initial; + } + } + } + } .nav-link { position:relative; line-height: 21px; @@ -26,7 +38,7 @@ text-align:left; background-color:darken(#03111C,2); color:rgba(#FFF,0.5); - + &:before { position: absolute; bottom: -1px; diff --git a/package.json b/package.json index 36405e7..62a1971 100644 --- a/package.json +++ b/package.json @@ -267,6 +267,7 @@ "react-hot-loader": "^4.3.4", "react-input-mask": "^2.0.4", "react-json-pretty": "^1.7.9", + "react-json-view": "^1.19.1", "react-lazy-load": "^3.0.13", "react-moment": "^0.8.1", "react-redux": "^5.0.7", diff --git a/yarn.lock b/yarn.lock index a0eb165..e7552ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2658,6 +2658,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base16@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" + integrity sha1-4pf2DX7BAUp6lxo568ipjAtoHnA= + base64-js@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1" @@ -4978,18 +4983,6 @@ electron-rebuild@^1.8.2: spawn-rx "^2.0.10" yargs "^7.0.2" -electron-remote@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/electron-remote/-/electron-remote-1.3.0.tgz#5c4bd278bd86d8aca0a9215c8d31440f1a0f133a" - integrity sha512-i00MD42fzlmyhsYRUDrMM104OQTT/soEmBmZ707CZ3k/nwa0rrB3a3mpxvR0EI2Q+Xw2VBdhWbk2gYmyg0PS0g== - dependencies: - debug "^2.5.1" - hashids "^1.1.1" - lodash.get "^4.4.2" - pify "^2.3.0" - rxjs "^5.0.0-beta.12" - xmlhttprequest "^1.8.0" - electron-settings@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/electron-settings/-/electron-settings-3.2.0.tgz#01461e153f95b6f18adbe0c360c70898eb0f43c3" @@ -5922,6 +5915,13 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" +fbemitter@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-2.1.1.tgz#523e14fdaf5248805bb02f62efc33be703f51865" + integrity sha1-Uj4U/a9SSIBbsC9i78M75wP1GGU= + dependencies: + fbjs "^0.8.4" + fbjs-scripts@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/fbjs-scripts/-/fbjs-scripts-0.8.3.tgz#b854de7a11e62a37f72dab9aaf4d9b53c4a03174" @@ -5938,7 +5938,7 @@ fbjs-scripts@^0.8.3: semver "^5.1.0" through2 "^2.0.0" -fbjs@^0.8.9: +fbjs@^0.8.0, fbjs@^0.8.4, fbjs@^0.8.9: version "0.8.17" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= @@ -6137,6 +6137,14 @@ flush-write-stream@^1.0.0: inherits "^2.0.1" readable-stream "^2.0.4" +flux@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/flux/-/flux-3.1.3.tgz#d23bed515a79a22d933ab53ab4ada19d05b2f08a" + integrity sha1-0jvtUVp5oi2TOrU6tK2hnQWy8Io= + dependencies: + fbemitter "^2.0.0" + fbjs "^0.8.0" + follow-redirects@^1.0.0: version "1.5.9" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.9.tgz#c9ed9d748b814a39535716e531b9196a845d89c6" @@ -6795,11 +6803,6 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hashids@^1.1.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/hashids/-/hashids-1.2.2.tgz#28635c7f2f7360ba463686078eee837479e8eafb" - integrity sha512-dEHCG2LraR6PNvSGxosZHIRgxF5sNLOIBFEHbj8lfP9WWmu/PWPMzsip1drdVSOFi51N2pU7gZavrgn7sbGFuw== - hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" @@ -8791,6 +8794,11 @@ lodash.clonedeep@^4.3.2, lodash.clonedeep@^4.5.0, lodash.clonedeep@~4.5.0: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash.curry@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" + integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA= + lodash.debounce@^4.0.0, lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -8826,6 +8834,11 @@ lodash.flattendeep@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= +lodash.flow@^3.3.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" + integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o= + lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -10779,7 +10792,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -pify@^2.0.0, pify@^2.3.0: +pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= @@ -11545,6 +11558,11 @@ pupa@^1.0.0: resolved "https://registry.yarnpkg.com/pupa/-/pupa-1.0.0.tgz#9a9568a5af7e657b8462a6e9d5328743560ceff6" integrity sha1-mpVopa9+ZXuEYqbp1TKHQ1YM7/Y= +pure-color@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" + integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4= + q@1.x, q@^1.1.2, q@~1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -11686,6 +11704,16 @@ rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.2.1, rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-base16-styling@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c" + integrity sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw= + dependencies: + base16 "^1.0.0" + lodash.curry "^4.0.1" + lodash.flow "^3.3.0" + pure-color "^1.2.0" + react-chartkick@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/react-chartkick/-/react-chartkick-0.3.0.tgz#692461eb808e18e7a2860129247476d55295e677" @@ -11735,6 +11763,16 @@ react-json-pretty@^1.7.9: dependencies: create-react-class "^15.5.2" +react-json-view@^1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.19.1.tgz#95d8e59e024f08a25e5dc8f076ae304eed97cf5c" + integrity sha512-u5e0XDLIs9Rj43vWkKvwL8G3JzvXSl6etuS5G42a8klMohZuYFQzSN6ri+/GiBptDqlrXPTdExJVU7x9rrlXhg== + dependencies: + flux "^3.1.3" + react-base16-styling "^0.6.0" + react-lifecycles-compat "^3.0.4" + react-textarea-autosize "^6.1.0" + react-lazy-load@^3.0.13: version "3.0.13" resolved "https://registry.yarnpkg.com/react-lazy-load/-/react-lazy-load-3.0.13.tgz#3b0a92d336d43d3f0d73cbe6f35b17050b08b824" @@ -11835,6 +11873,13 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.4.1: react-is "^16.5.2" schedule "^0.5.0" +react-textarea-autosize@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz#df91387f8a8f22020b77e3833c09829d706a09a5" + integrity sha512-F6bI1dgib6fSvG8so1HuArPUv+iVEfPliuLWusLF+gAKz0FbB4jLrWUrTAeq1afnPT2c9toEZYUdz/y1uKMy4A== + dependencies: + prop-types "^15.6.0" + react-textarea-autosize@^7.0.4: version "7.0.4" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.0.4.tgz#4e4be649b544a88713e7b5043f76950f35d3d503" @@ -12621,7 +12666,7 @@ rx@2.3.24: resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7" integrity sha1-FPlQpCF9fjXapxu8vljv9o6ksrc= -rxjs@^5.0.0-beta.12, rxjs@^5.1.1: +rxjs@^5.1.1: version "5.5.12" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw== @@ -15113,11 +15158,6 @@ xmldom@0.1.x: resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk= -xmlhttprequest@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" - integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw= - xregexp@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020"