Merge pull request #22 from status-im/refactor/websocket
Refactor Process logs to use saga for websocket
This commit is contained in:
commit
028fb217c9
|
@ -63,6 +63,8 @@ export const RECEIVE_PROCESSES_ERROR = 'RECEIVE_PROCESSES_ERROR';
|
|||
// Process logs
|
||||
export const FETCH_PROCESS_LOGS = 'FETCH_PROCESS_LOGS';
|
||||
export const RECEIVE_PROCESS_LOGS = 'RECEIVE_PROCESS_LOGS';
|
||||
export const WATCH_NEW_PROCESS_LOGS = 'WATCH_NEW_PROCESS_LOGS';
|
||||
export const RECEIVE_NEW_PROCESS_LOG = 'RECEIVE_NEW_PROCESS_LOG';
|
||||
export const RECEIVE_PROCESS_LOGS_ERROR = 'RECEIVE_PROCESS_LOGS_ERROR';
|
||||
// BlockHeader
|
||||
export const INIT_BLOCK_HEADER = 'INIT_BLOCK_HEADER';
|
||||
|
@ -94,6 +96,13 @@ export function fetchProcessLogs(processName) {
|
|||
};
|
||||
}
|
||||
|
||||
export function listenToProcessLogs(processName) {
|
||||
return {
|
||||
type: WATCH_NEW_PROCESS_LOGS,
|
||||
processName
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveProcessLogs(processName, logs) {
|
||||
return {
|
||||
type: RECEIVE_PROCESS_LOGS,
|
||||
|
|
|
@ -43,6 +43,10 @@ export function fetchProcessLogs(processName) {
|
|||
return axios.get(`${constants.httpEndpoint}/process-logs/${processName}`);
|
||||
}
|
||||
|
||||
export function webSocketProcess(processName) {
|
||||
return new WebSocket(constants.wsEndpoint + '/process-logs/' + processName);
|
||||
}
|
||||
|
||||
export function webSocketBlockHeader() {
|
||||
return new WebSocket(`${constants.wsEndpoint}/blockchain/blockHeader`);
|
||||
}
|
||||
|
|
|
@ -1,86 +1,30 @@
|
|||
import React, {Component} from 'react';
|
||||
import connect from "react-redux/es/connect/connect";
|
||||
import {fetchProcessLogs} from "../actions";
|
||||
import constants from '../constants';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Page} from "tabler-react";
|
||||
import Loading from "./Loading";
|
||||
|
||||
class Process extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
logs: []
|
||||
};
|
||||
this.gotOriginalLogs = false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const self = this;
|
||||
|
||||
this.props.fetchProcessLogs(self.props.processName);
|
||||
|
||||
this.ws = new WebSocket(constants.wsEndpoint + '/process-logs/' + self.props.processName);
|
||||
|
||||
this.ws.onmessage = function(evt) {
|
||||
const log = JSON.parse(evt.data);
|
||||
const logs = self.state.logs;
|
||||
logs.push(log);
|
||||
self.setState({
|
||||
logs
|
||||
});
|
||||
};
|
||||
|
||||
this.ws.onclose = function() {
|
||||
console.log(self.props.processName + "Log process connection is closed");
|
||||
};
|
||||
|
||||
window.onbeforeunload = function(_event) {
|
||||
this.ws.close();
|
||||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, _nextState) {
|
||||
if (!this.gotOriginalLogs && nextProps.logs && nextProps.logs[this.props.processName]) {
|
||||
const logs = nextProps.logs[this.props.processName].concat(this.state.logs);
|
||||
this.gotOriginalLogs = true;
|
||||
this.setState({
|
||||
logs
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const logs = this.props.logs;
|
||||
return (
|
||||
<div>
|
||||
State: {this.props.state}
|
||||
<Page.Content title={this.props.processName.charAt(0).toUpperCase() + this.props.processName.slice(1)}>
|
||||
<p className="capitalize">State: {this.props.state}</p>
|
||||
{!logs &&
|
||||
<Loading/>}
|
||||
{logs &&
|
||||
<div className="logs">
|
||||
{
|
||||
this.state.logs.map((item, i) => <p key={i} className={item.logLevel}>{item.msg_clear || item.msg}</p>)
|
||||
logs.map((item, i) => <p key={i} className={item.logLevel}>{item.msg_clear || item.msg}</p>)
|
||||
}
|
||||
</div>
|
||||
</div>);
|
||||
</div>}
|
||||
</Page.Content>);
|
||||
}
|
||||
}
|
||||
|
||||
Process.propTypes = {
|
||||
processName: PropTypes.string.isRequired,
|
||||
state: PropTypes.string.isRequired,
|
||||
fetchProcessLogs: PropTypes.func,
|
||||
logs: PropTypes.object
|
||||
logs: PropTypes.array
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {logs: state.processes.logs};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
fetchProcessLogs
|
||||
}
|
||||
)(Process);
|
||||
export default Process;
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import PropTypes from "prop-types";
|
||||
import React, {Component} from 'react';
|
||||
import connect from "react-redux/es/connect/connect";
|
||||
import {NavLink, Route, Switch, withRouter} from 'react-router-dom';
|
||||
import {
|
||||
Page,
|
||||
Grid,
|
||||
List
|
||||
} from "tabler-react";
|
||||
|
||||
import ProcessesContainer from '../containers/ProcessesContainer';
|
||||
import Loading from "./Loading";
|
||||
|
||||
const routePrefix = '/embark/processes';
|
||||
|
||||
class ProcessesLayout extends Component {
|
||||
|
||||
|
||||
render() {
|
||||
if (!this.props.processes || !this.props.processes.data) {
|
||||
return <Loading />;
|
||||
}
|
||||
const processNames = Object.keys(this.props.processes.data) || [];
|
||||
return (<Grid.Row>
|
||||
<Grid.Col md={3}>
|
||||
<Page.Title className="my-5">Processes</Page.Title>
|
||||
<div>
|
||||
<List.Group transparent={true}>
|
||||
{processNames.map((processName, index) => {
|
||||
return (<List.GroupItem
|
||||
className="d-flex align-items-center capitalize"
|
||||
to={`${routePrefix}/${processName}`}
|
||||
key={'process-' + processName}
|
||||
active={index === 0 && this.props.match.isExact === true}
|
||||
RootComponent={withRouter(NavLink)}
|
||||
>
|
||||
{processName}
|
||||
</List.GroupItem>);
|
||||
})}
|
||||
|
||||
</List.Group>
|
||||
</div>
|
||||
</Grid.Col>
|
||||
<Grid.Col md={9}>
|
||||
<Switch>
|
||||
<Route exact path={`${routePrefix}/`} component={ProcessesContainer} />
|
||||
{processNames.map((processName, index) => {
|
||||
return (<Route key={'procesRoute-' + index} exact path={`${routePrefix}/${processName}`} component={ProcessesContainer}/>);
|
||||
})}
|
||||
</Switch>
|
||||
</Grid.Col>
|
||||
</Grid.Row>);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessesLayout.propTypes = {
|
||||
processes: PropTypes.object,
|
||||
match: PropTypes.object
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {processes: state.processes};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps
|
||||
)(ProcessesLayout);
|
||||
|
|
@ -6,11 +6,12 @@ import React, {Component} from 'react';
|
|||
import history from '../history';
|
||||
import Layout from '../components/Layout';
|
||||
import routes from '../routes';
|
||||
import {initBlockHeader} from '../actions';
|
||||
import {initBlockHeader, fetchProcesses} from '../actions';
|
||||
|
||||
class AppContainer extends Component {
|
||||
componentDidMount() {
|
||||
this.props.initBlockHeader();
|
||||
this.props.fetchProcesses();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -25,12 +26,14 @@ class AppContainer extends Component {
|
|||
}
|
||||
|
||||
AppContainer.propTypes = {
|
||||
initBlockHeader: PropTypes.func
|
||||
initBlockHeader: PropTypes.func,
|
||||
fetchProcesses: PropTypes.func
|
||||
};
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
{
|
||||
initBlockHeader
|
||||
initBlockHeader,
|
||||
fetchProcesses
|
||||
},
|
||||
)(AppContainer);
|
||||
|
|
|
@ -1,48 +1,48 @@
|
|||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {Tabs, Tab} from 'tabler-react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {fetchProcessLogs, listenToProcessLogs} from '../actions';
|
||||
|
||||
import {fetchProcesses} from '../actions';
|
||||
import Loading from '../components/Loading';
|
||||
|
||||
import "./css/processContainer.css";
|
||||
import Process from "../components/Process";
|
||||
|
||||
class ProcessesContainer extends Component {
|
||||
componentDidMount() {
|
||||
this.props.fetchProcesses();
|
||||
// Get correct process name
|
||||
const pathParts = this.props.match.path.split('/');
|
||||
this.processName = pathParts[pathParts.length - 1];
|
||||
// If we are not in a specific process page (eg: processes/ root), get first process
|
||||
if (Object.keys(this.props.processes.data).indexOf(this.processName) < 0) {
|
||||
this.processName = Object.keys(this.props.processes.data)[0];
|
||||
}
|
||||
|
||||
// Fetch logs for the process
|
||||
this.props.fetchProcessLogs(this.processName);
|
||||
|
||||
// Only start watching if we are not already watching
|
||||
if (!this.props.processes.data[this.processName].isListening) {
|
||||
this.props.listenToProcessLogs(this.processName);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {processes} = this.props;
|
||||
if (!processes.data) {
|
||||
return <Loading />;
|
||||
if (!this.processName) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const processNames = Object.keys(processes.data);
|
||||
return (
|
||||
<div className="processes-container">
|
||||
{processes.error && <h1>
|
||||
<i>Error: {processes.error.message || processes.error}</i>
|
||||
</h1>}
|
||||
|
||||
{processNames && processNames.length && <Tabs initialTab={processNames[0]}>
|
||||
{processNames.map(processName => {
|
||||
return (<Tab key={processName} title={processName}>
|
||||
<Process processName={processName} state={processes.data[processName].state}/>
|
||||
</Tab>);
|
||||
})}
|
||||
</Tabs>}
|
||||
|
||||
<Process processName={this.processName}
|
||||
state={this.props.processes.data[this.processName].state}
|
||||
logs={this.props.processes.data[this.processName].logs}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessesContainer.propTypes = {
|
||||
match: PropTypes.object,
|
||||
processes: PropTypes.object,
|
||||
fetchProcesses: PropTypes.func
|
||||
fetchProcessLogs: PropTypes.func,
|
||||
listenToProcessLogs: PropTypes.func
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
@ -52,6 +52,7 @@ function mapStateToProps(state) {
|
|||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
fetchProcesses
|
||||
fetchProcessLogs,
|
||||
listenToProcessLogs
|
||||
}
|
||||
)(ProcessesContainer);
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
|
||||
.processes-container .nav-link {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.processes-container .logs {
|
||||
.logs {
|
||||
margin: 10px 0;
|
||||
background-color: #333333;
|
||||
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
||||
|
@ -13,15 +8,19 @@
|
|||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.processes-container .logs .error {
|
||||
.logs .error {
|
||||
color: #dc3546;
|
||||
}
|
||||
.processes-container .logs .warn {
|
||||
.logs .warn {
|
||||
color: #fec107;
|
||||
}
|
||||
.processes-container .logs .debug {
|
||||
.logs .debug {
|
||||
color: #b7c1cc;
|
||||
}
|
||||
.processes-container .logs .trace {
|
||||
.logs .trace {
|
||||
color: #8f98a2;
|
||||
}
|
||||
|
||||
.capitalize {
|
||||
text-transform: capitalize;
|
||||
}
|
|
@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
|
|||
import {Provider} from 'react-redux';
|
||||
|
||||
import "tabler-react/dist/Tabler.css";
|
||||
import "./general.css";
|
||||
|
||||
import AppContainer from './containers/AppContainer';
|
||||
import registerServiceWorker from './registerServiceWorker';
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import {RECEIVE_PROCESSES, RECEIVE_PROCESSES_ERROR, RECEIVE_PROCESS_LOGS, RECEIVE_PROCESS_LOGS_ERROR} from "../actions";
|
||||
import {
|
||||
RECEIVE_PROCESSES,
|
||||
RECEIVE_PROCESSES_ERROR,
|
||||
RECEIVE_PROCESS_LOGS,
|
||||
RECEIVE_PROCESS_LOGS_ERROR,
|
||||
RECEIVE_NEW_PROCESS_LOG,
|
||||
WATCH_NEW_PROCESS_LOGS
|
||||
} from "../actions";
|
||||
|
||||
export default function processes(state = {}, action) {
|
||||
switch (action.type) {
|
||||
|
@ -9,11 +16,40 @@ export default function processes(state = {}, action) {
|
|||
case RECEIVE_PROCESS_LOGS:
|
||||
return {
|
||||
...state,
|
||||
logs: {
|
||||
...state.logs,
|
||||
[action.processName]: action.logs.data
|
||||
data: {
|
||||
...state.data,
|
||||
[action.processName]: {
|
||||
...state.data[action.processName],
|
||||
logs: action.logs.data
|
||||
}
|
||||
}
|
||||
};
|
||||
case RECEIVE_NEW_PROCESS_LOG: {
|
||||
const logs = state.data[action.processName].logs || [];
|
||||
logs.push(action.log);
|
||||
return {
|
||||
...state,
|
||||
data: {
|
||||
...state.data,
|
||||
[action.processName]: {
|
||||
...state.data[action.processName],
|
||||
logs: logs
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
case WATCH_NEW_PROCESS_LOGS: {
|
||||
return {
|
||||
...state,
|
||||
data: {
|
||||
...state.data,
|
||||
[action.processName]: {
|
||||
...state.data[action.processName],
|
||||
isListening: true
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
case RECEIVE_PROCESS_LOGS_ERROR:
|
||||
return Object.assign({}, state, {error: action.error});
|
||||
default:
|
||||
|
|
|
@ -4,15 +4,14 @@ import {Route, Switch} from 'react-router-dom';
|
|||
import Home from './components/Home';
|
||||
import NoMatch from './components/NoMatch';
|
||||
import ExplorerLayout from './components/ExplorerLayout';
|
||||
|
||||
import ProcessesContainer from './containers/ProcessesContainer';
|
||||
import ProcessesLayout from './components/ProcessesLayout';
|
||||
|
||||
const routes = (
|
||||
<React.Fragment>
|
||||
<Switch>
|
||||
<Route exact path="/embark/" component={Home} />
|
||||
<Route path="/embark/explorer/" component={ExplorerLayout} />
|
||||
<Route path="/embark/processes/" component={ProcessesContainer} />
|
||||
<Route path="/embark/processes/" component={ProcessesLayout} />
|
||||
<Route component={NoMatch} />
|
||||
</Switch>
|
||||
</React.Fragment>
|
||||
|
|
|
@ -96,6 +96,19 @@ export function *watchInitBlockHeader() {
|
|||
yield takeEvery(actions.INIT_BLOCK_HEADER, initBlockHeader);
|
||||
}
|
||||
|
||||
export function *listenToProcessLogs(action) {
|
||||
const socket = api.webSocketProcess(action.processName);
|
||||
const channel = yield call(createChannel, socket);
|
||||
while (true) {
|
||||
const log = yield take(channel);
|
||||
yield put({type: actions.RECEIVE_NEW_PROCESS_LOG, processName: action.processName, log});
|
||||
}
|
||||
}
|
||||
|
||||
export function *watchListenToProcessLogs() {
|
||||
yield takeEvery(actions.WATCH_NEW_PROCESS_LOGS, listenToProcessLogs);
|
||||
}
|
||||
|
||||
export default function *root() {
|
||||
yield all([
|
||||
fork(watchInitBlockHeader),
|
||||
|
@ -103,6 +116,7 @@ export default function *root() {
|
|||
fork(watchFetchAccount),
|
||||
fork(watchFetchProcesses),
|
||||
fork(watchFetchProcessLogs),
|
||||
fork(watchListenToProcessLogs),
|
||||
fork(watchFetchBlocks),
|
||||
fork(watchFetchBlock),
|
||||
fork(watchFetchTransactions),
|
||||
|
|
Loading…
Reference in New Issue