diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/package.json | 1 | ||||
-rw-r--r-- | web/src/js/actions.js | 44 | ||||
-rw-r--r-- | web/src/js/app.jsx | 8 | ||||
-rw-r--r-- | web/src/js/backends/websocket.js | 68 | ||||
-rw-r--r-- | web/src/js/components/ProxyApp.jsx | 59 | ||||
-rw-r--r-- | web/src/js/dispatcher.js | 18 | ||||
-rw-r--r-- | web/src/js/ducks/app.js | 27 | ||||
-rw-r--r-- | web/src/js/ducks/eventLog.js | 65 | ||||
-rw-r--r-- | web/src/js/ducks/flowView.js | 25 | ||||
-rw-r--r-- | web/src/js/ducks/flows.js | 108 | ||||
-rw-r--r-- | web/src/js/ducks/msgQueue.js | 113 | ||||
-rw-r--r-- | web/src/js/ducks/settings.js | 50 | ||||
-rw-r--r-- | web/src/js/ducks/ui/index.js | 1 | ||||
-rw-r--r-- | web/src/js/ducks/utils/list.js | 12 | ||||
-rw-r--r-- | web/src/js/ducks/websocket.js | 93 | ||||
-rw-r--r-- | web/src/js/urlState.js | 78 |
16 files changed, 176 insertions, 594 deletions
diff --git a/web/package.json b/web/package.json index 59b031b9..238d64b9 100644 --- a/web/package.json +++ b/web/package.json @@ -19,7 +19,6 @@ "bootstrap": "^3.3.6", "classnames": "^2.2.5", "flux": "^2.1.1", - "history": "^3.0.0", "lodash": "^4.11.2", "react": "^15.1.0", "react-codemirror": "^0.2.6", diff --git a/web/src/js/actions.js b/web/src/js/actions.js deleted file mode 100644 index 51b180ce..00000000 --- a/web/src/js/actions.js +++ /dev/null @@ -1,44 +0,0 @@ -import {AppDispatcher} from "./dispatcher.js"; - -export var ActionTypes = { - // Connection - CONNECTION_OPEN: "connection_open", - CONNECTION_CLOSE: "connection_close", - CONNECTION_ERROR: "connection_error", - - // Stores - SETTINGS_STORE: "settings", - EVENT_STORE: "events", - FLOW_STORE: "flows" -}; - -export var StoreCmds = { - ADD: "add", - UPDATE: "update", - REMOVE: "remove", - RESET: "reset" -}; - -export var ConnectionActions = { - open: function () { - AppDispatcher.dispatchViewAction({ - type: ActionTypes.CONNECTION_OPEN - }); - }, - close: function () { - AppDispatcher.dispatchViewAction({ - type: ActionTypes.CONNECTION_CLOSE - }); - }, - error: function () { - AppDispatcher.dispatchViewAction({ - type: ActionTypes.CONNECTION_ERROR - }); - } -}; - -export var Query = { - SEARCH: "s", - HIGHLIGHT: "h", - SHOW_EVENTLOG: "e" -}; diff --git a/web/src/js/app.jsx b/web/src/js/app.jsx index f04baea0..a94d2ef6 100644 --- a/web/src/js/app.jsx +++ b/web/src/js/app.jsx @@ -7,6 +7,9 @@ import thunk from 'redux-thunk' import ProxyApp from './components/ProxyApp' import rootReducer from './ducks/index' import { add as addLog } from './ducks/eventLog' +import useUrlState from './urlState' +import WebSocketBackend from './backends/websocket' + const middlewares = [thunk]; @@ -21,12 +24,13 @@ const store = createStore( applyMiddleware(...middlewares) ) -// @todo move to ProxyApp +useUrlState(store) +window.backend = new WebSocketBackend(store) + window.addEventListener('error', msg => { store.dispatch(addLog(msg)) }) -// @todo remove this document.addEventListener('DOMContentLoaded', () => { render( <Provider store={store}> diff --git a/web/src/js/backends/websocket.js b/web/src/js/backends/websocket.js new file mode 100644 index 00000000..aa890bb7 --- /dev/null +++ b/web/src/js/backends/websocket.js @@ -0,0 +1,68 @@ +import { fetchApi } from "../utils" + +export const CMD_RESET = 'reset' + +export default class WebsocketBackend { + constructor(store) { + this.activeFetches = {} + this.store = store + this.connect() + } + + connect() { + this.socket = new WebSocket(location.origin.replace('http', 'ws') + '/updates') + this.socket.addEventListener('open', () => this.onOpen()) + this.socket.addEventListener('close', () => this.onClose()) + this.socket.addEventListener('message', msg => this.onMessage(JSON.parse(msg.data))) + this.socket.addEventListener('error', error => this.onError(error)) + } + + onOpen() { + this.fetchData("settings") + this.fetchData("flows") + this.fetchData("events") + } + + fetchData(resource) { + let queue = [] + this.activeFetches[resource] = queue + fetchApi(`/${resource}`) + .then(res => res.json()) + .then(json => { + // Make sure that we are not superseded yet by the server sending a RESET. + if (this.activeFetches[resource] === queue) + this.receive(resource, json) + }) + } + + onMessage(msg) { + + if (msg.cmd === CMD_RESET) { + return this.fetchData(msg.resource) + } + if (msg.resource in this.activeFetches) { + this.activeFetches[msg.resource].push(msg) + } else { + let type = `${msg.resource}_${msg.cmd}`.toUpperCase() + this.store.dispatch({ type, ...msg }) + } + } + + receive(resource, msg) { + let type = `${resource}_RECEIVE`.toUpperCase() + this.store.dispatch({ type, [resource]: msg }) + let queue = this.activeFetches[resource] + delete this.activeFetches[resource] + queue.forEach(msg => this.onMessage(msg)) + } + + onClose() { + // FIXME + console.error("onClose", arguments) + } + + onError() { + // FIXME + console.error("onError", arguments) + } +} diff --git a/web/src/js/components/ProxyApp.jsx b/web/src/js/components/ProxyApp.jsx index d76816e5..18976de0 100644 --- a/web/src/js/components/ProxyApp.jsx +++ b/web/src/js/components/ProxyApp.jsx @@ -1,13 +1,7 @@ import React, { Component, PropTypes } from 'react' import { connect } from 'react-redux' -import { createHashHistory, useQueries } from 'history' -import { init as appInit, destruct as appDestruct } from '../ducks/app' import { onKeyDown } from '../ducks/ui/keyboard' -import { updateFilter, updateHighlight } from '../ducks/flowView' -import { selectTab } from '../ducks/ui/flow' -import { select as selectFlow } from '../ducks/flows' -import { Query } from '../actions' import MainView from './MainView' import Header from './Header' import EventLog from './EventLog' @@ -15,57 +9,14 @@ import Footer from './Footer' class ProxyAppMain extends Component { - flushToStore(location) { - const components = location.pathname.split('/').filter(v => v) - const query = location.query || {} - - if (components.length > 2) { - this.props.selectFlow(components[1]) - this.props.selectTab(components[2]) - } else { - this.props.selectFlow(null) - this.props.selectTab(null) - } - - this.props.updateFilter(query[Query.SEARCH]) - this.props.updateHighlight(query[Query.HIGHLIGHT]) - } - - flushToHistory(props) { - const query = { ...query } - - if (props.filter) { - query[Query.SEARCH] = props.filter - } - - if (props.highlight) { - query[Query.HIGHLIGHT] = props.highlight - } - - if (props.selectedFlowId) { - this.history.push({ pathname: `/flows/${props.selectedFlowId}/${props.tab}`, query }) - } else { - this.history.push({ pathname: '/flows', query }) - } - } - componentWillMount() { - this.props.appInit() - this.history = useQueries(createHashHistory)() - this.unlisten = this.history.listen(location => this.flushToStore(location)) window.addEventListener('keydown', this.props.onKeyDown); } componentWillUnmount() { - this.props.appDestruct() - this.unlisten() window.removeEventListener('keydown', this.props.onKeyDown); } - componentWillReceiveProps(nextProps) { - this.flushToHistory(nextProps) - } - render() { const { showEventLog, location, filter, highlight } = this.props return ( @@ -84,18 +35,8 @@ class ProxyAppMain extends Component { export default connect( state => ({ showEventLog: state.eventLog.visible, - filter: state.flowView.filter, - highlight: state.flowView.highlight, - tab: state.ui.flow.tab, - selectedFlowId: state.flows.selected[0] }), { - appInit, - appDestruct, onKeyDown, - updateFilter, - updateHighlight, - selectTab, - selectFlow } )(ProxyAppMain) diff --git a/web/src/js/dispatcher.js b/web/src/js/dispatcher.js deleted file mode 100644 index b4e22ed9..00000000 --- a/web/src/js/dispatcher.js +++ /dev/null @@ -1,18 +0,0 @@ - -import flux from "flux"; - -const PayloadSources = { - VIEW: "view", - SERVER: "server" -}; - - -export var AppDispatcher = new flux.Dispatcher(); -AppDispatcher.dispatchViewAction = function (action) { - action.source = PayloadSources.VIEW; - this.dispatch(action); -}; -AppDispatcher.dispatchServerAction = function (action) { - action.source = PayloadSources.SERVER; - this.dispatch(action); -};
\ No newline at end of file diff --git a/web/src/js/ducks/app.js b/web/src/js/ducks/app.js deleted file mode 100644 index f1dcb490..00000000 --- a/web/src/js/ducks/app.js +++ /dev/null @@ -1,27 +0,0 @@ -import { connect as wsConnect, disconnect as wsDisconnect } from './websocket' - -export const INIT = 'APP_INIT' - -const defaultState = {} - -export function reduce(state = defaultState, action) { - switch (action.type) { - - default: - return state - } -} - -export function init() { - return dispatch => { - dispatch(wsConnect()) - dispatch({ type: INIT }) - } -} - -export function destruct() { - return dispatch => { - dispatch(wsDisconnect()) - dispatch({ type: DESTRUCT }) - } -} diff --git a/web/src/js/ducks/eventLog.js b/web/src/js/ducks/eventLog.js index f72d7bd6..76572a5d 100644 --- a/web/src/js/ducks/eventLog.js +++ b/web/src/js/ducks/eventLog.js @@ -1,17 +1,12 @@ import reduceList, * as listActions from './utils/list' import reduceView, * as viewActions from './utils/view' -import * as websocketActions from './websocket' -import * as msgQueueActions from './msgQueue' -export const MSG_TYPE = 'UPDATE_EVENTLOG' -export const DATA_URL = '/events' - -export const ADD = 'EVENTLOG_ADD' -export const RECEIVE = 'EVENTLOG_RECEIVE' -export const TOGGLE_VISIBILITY = 'EVENTLOG_TOGGLE_VISIBILITY' -export const TOGGLE_FILTER = 'EVENTLOG_TOGGLE_FILTER' -export const UNKNOWN_CMD = 'EVENTLOG_UNKNOWN_CMD' -export const FETCH_ERROR = 'EVENTLOG_FETCH_ERROR' +export const ADD = 'EVENTS_ADD' +export const RECEIVE = 'EVENTS_RECEIVE' +export const TOGGLE_VISIBILITY = 'EVENTS_TOGGLE_VISIBILITY' +export const TOGGLE_FILTER = 'EVENTS_TOGGLE_FILTER' +export const UNKNOWN_CMD = 'EVENTS_UNKNOWN_CMD' +export const FETCH_ERROR = 'EVENTS_FETCH_ERROR' const defaultState = { logId: 0, @@ -54,8 +49,8 @@ export default function reduce(state = defaultState, action) { case RECEIVE: return { ...state, - list: reduceList(state.list, listActions.receive(action.list)), - view: reduceView(state.view, viewActions.receive(action.list, log => state.filters[log.level])), + list: reduceList(state.list, listActions.receive(action.events)), + view: reduceView(state.view, viewActions.receive(action.events, log => state.filters[log.level])), } default: @@ -63,58 +58,14 @@ export default function reduce(state = defaultState, action) { } } -/** - * @public - */ export function toggleFilter(filter) { return { type: TOGGLE_FILTER, filter } } -/** - * @public - * - * @todo move to ui? - */ export function toggleVisibility() { return { type: TOGGLE_VISIBILITY } } -/** - * @public - */ export function add(message, level = 'web') { return { type: ADD, message, level } } - -/** - * This action creater takes all WebSocket events - * - * @public websocket - */ -export function handleWsMsg(msg) { - switch (msg.cmd) { - - case websocketActions.CMD_ADD: - return add(msg.data.message, msg.data.level) - - case websocketActions.CMD_RESET: - return fetchData() - - default: - return { type: UNKNOWN_CMD, msg } - } -} - -/** - * @public websocket - */ -export function fetchData() { - return msgQueueActions.fetchData(MSG_TYPE) -} - -/** - * @public msgQueue - */ -export function receiveData(list) { - return { type: RECEIVE, list } -} diff --git a/web/src/js/ducks/flowView.js b/web/src/js/ducks/flowView.js index dd5bea41..1af96573 100644 --- a/web/src/js/ducks/flowView.js +++ b/web/src/js/ducks/flowView.js @@ -121,6 +121,16 @@ export default function reduce(state = defaultState, action) { } case flowActions.REMOVE: + /* FIXME: Implement select switch on remove + return (dispatch, getState) => { + let currentIndex = getState().flowView.indexOf[getState().flows.selected[0]] + let maxIndex = getState().flowView.data.length - 1 + let deleteLastEntry = maxIndex == 0 + if (deleteLastEntry) + dispatch(select()) + else + dispatch(selectRelative(currentIndex == maxIndex ? -1 : 1) ) + */ return { ...reduceView( state, @@ -135,7 +145,7 @@ export default function reduce(state = defaultState, action) { ...reduceView( state, viewActions.receive( - action.list, + action.flows, makeFilter(state.filter), makeSort(state.sort) ) @@ -149,33 +159,20 @@ export default function reduce(state = defaultState, action) { } } -/** - * @public - */ export function updateFilter(filter) { return (dispatch, getState) => { dispatch({ type: UPDATE_FILTER, filter, flows: getState().flows.data }) } } -/** - * @public - */ export function updateHighlight(highlight) { return { type: UPDATE_HIGHLIGHT, highlight } } -/** - * @public - */ export function updateSort(column, desc) { return { type: UPDATE_SORT, column, desc } } - -/** - * @public - */ export function selectRelative(shift) { return (dispatch, getState) => { let currentSelectionIndex = getState().flowView.indexOf[getState().flows.selected[0]] diff --git a/web/src/js/ducks/flows.js b/web/src/js/ducks/flows.js index 404db0d1..519b5e7d 100644 --- a/web/src/js/ducks/flows.js +++ b/web/src/js/ducks/flows.js @@ -2,12 +2,6 @@ import { fetchApi } from '../utils' import reduceList, * as listActions from './utils/list' import { selectRelative } from './flowView' -import * as msgQueueActions from './msgQueue' -import * as websocketActions from './websocket' - -export const MSG_TYPE = 'UPDATE_FLOWS' -export const DATA_URL = '/flows' - export const ADD = 'FLOWS_ADD' export const UPDATE = 'FLOWS_UPDATE' export const REMOVE = 'FLOWS_REMOVE' @@ -47,7 +41,7 @@ export default function reduce(state = defaultState, action) { case RECEIVE: return { ...state, - ...reduceList(state, listActions.receive(action.list)), + ...reduceList(state, listActions.receive(action.flows)), } case SELECT: @@ -64,51 +58,30 @@ export default function reduce(state = defaultState, action) { } } -/** - * @public - */ export function accept(flow) { return dispatch => fetchApi(`/flows/${flow.id}/accept`, { method: 'POST' }) } -/** - * @public - */ export function acceptAll() { return dispatch => fetchApi('/flows/accept', { method: 'POST' }) } -/** - * @public - */ export function remove(flow) { return dispatch => fetchApi(`/flows/${flow.id}`, { method: 'DELETE' }) } -/** - * @public - */ export function duplicate(flow) { return dispatch => fetchApi(`/flows/${flow.id}/duplicate`, { method: 'POST' }) } -/** - * @public - */ export function replay(flow) { return dispatch => fetchApi(`/flows/${flow.id}/replay`, { method: 'POST' }) } -/** - * @public - */ export function revert(flow) { return dispatch => fetchApi(`/flows/${flow.id}/revert`, { method: 'POST' }) } -/** - * @public - */ export function update(flow, data) { return dispatch => fetchApi.put(`/flows/${flow.id}`, data) } @@ -121,24 +94,15 @@ export function uploadContent(flow, file, type) { } -/** - * @public - */ export function clear() { return dispatch => fetchApi('/clear', { method: 'POST' }) } -/** - * @public - */ export function download() { window.location = '/flows/dump' return { type: REQUEST_ACTION } } -/** - * @public - */ export function upload(file) { const body = new FormData() body.append('file', file) @@ -152,73 +116,3 @@ export function select(id) { flowIds: id ? [id] : [] } } - - -/** - * This action creater takes all WebSocket events - * - * @public websocket - */ -export function handleWsMsg(msg) { - switch (msg.cmd) { - - case websocketActions.CMD_ADD: - return addFlow(msg.data) - - case websocketActions.CMD_UPDATE: - return updateFlow(msg.data) - - case websocketActions.CMD_REMOVE: - return removeFlow(msg.data.id) - - case websocketActions.CMD_RESET: - return fetchFlows() - - default: - return { type: UNKNOWN_CMD, msg } - } -} - -/** - * @public websocket - */ -export function fetchFlows() { - return msgQueueActions.fetchData(MSG_TYPE) -} - -/** - * @public msgQueue - */ -export function receiveData(list) { - return { type: RECEIVE, list } -} - -/** - * @private - */ -export function addFlow(item) { - return { type: ADD, item } -} - -/** - * @private - */ -export function updateFlow(item) { - return { type: UPDATE, item } -} - -/** - * @private - */ -export function removeFlow(id) { - return (dispatch, getState) => { - let currentIndex = getState().flowView.indexOf[getState().flows.selected[0]] - let maxIndex = getState().flowView.data.length - 1 - let deleteLastEntry = maxIndex == 0 - if (deleteLastEntry) - dispatch(select()) - else - dispatch(selectRelative(currentIndex == maxIndex ? -1 : 1) ) - dispatch({ type: REMOVE, id }) - } -} diff --git a/web/src/js/ducks/msgQueue.js b/web/src/js/ducks/msgQueue.js deleted file mode 100644 index 6d82f4c2..00000000 --- a/web/src/js/ducks/msgQueue.js +++ /dev/null @@ -1,113 +0,0 @@ -import { fetchApi } from '../utils' -import * as websocketActions from './websocket' -import * as eventLogActions from './eventLog' -import * as flowsActions from './flows' -import * as settingsActions from './settings' - -export const INIT = 'MSG_QUEUE_INIT' -export const ENQUEUE = 'MSG_QUEUE_ENQUEUE' -export const CLEAR = 'MSG_QUEUE_CLEAR' -export const FETCH_ERROR = 'MSG_QUEUE_FETCH_ERROR' - -const handlers = { - [eventLogActions.MSG_TYPE] : eventLogActions, - [flowsActions.MSG_TYPE] : flowsActions, - [settingsActions.MSG_TYPE] : settingsActions, -} - -const defaultState = {} - -export default function reduce(state = defaultState, action) { - switch (action.type) { - - case INIT: - return { - ...state, - [action.queue]: [], - } - - case ENQUEUE: - return { - ...state, - [action.queue]: [...state[action.queue], action.msg], - } - - case CLEAR: - return { - ...state, - [action.queue]: null, - } - - default: - return state - } -} - -/** - * @public websocket - */ -export function handleWsMsg(msg) { - return (dispatch, getState) => { - const handler = handlers[msg.type] - if (msg.cmd === websocketActions.CMD_RESET) { - return dispatch(fetchData(handler.MSG_TYPE)) - } - if (getState().msgQueue[handler.MSG_TYPE]) { - return dispatch({ type: ENQUEUE, queue: handler.MSG_TYPE, msg }) - } - return dispatch(handler.handleWsMsg(msg)) - } -} - -/** - * @public - */ -export function fetchData(type) { - return dispatch => { - const handler = handlers[type] - - dispatch(init(handler.MSG_TYPE)) - - fetchApi(handler.DATA_URL) - .then(res => res.json()) - .then(json => dispatch(receive(type, json))) - .catch(error => dispatch(fetchError(type, error))) - } -} - -/** - * @private - */ -export function receive(type, res) { - return (dispatch, getState) => { - const handler = handlers[type] - const queue = getState().msgQueue[handler.MSG_TYPE] || [] - - dispatch(clear(handler.MSG_TYPE)) - dispatch(handler.receiveData(res.data)) - for (const msg of queue) { - dispatch(handler.handleWsMsg(msg)) - } - } -} - -/** - * @private - */ -export function init(queue) { - return { type: INIT, queue } -} - -/** - * @private - */ -export function clear(queue) { - return { type: CLEAR, queue } -} - -/** - * @private - */ -export function fetchError(type, error) { - return { type: FETCH_ERROR, type, error } -} diff --git a/web/src/js/ducks/settings.js b/web/src/js/ducks/settings.js index 6b21baec..32afe3be 100644 --- a/web/src/js/ducks/settings.js +++ b/web/src/js/ducks/settings.js @@ -1,12 +1,7 @@ import { fetchApi } from '../utils' -import * as websocketActions from './websocket' -import * as msgQueueActions from './msgQueue' -export const MSG_TYPE = 'UPDATE_SETTINGS' -export const DATA_URL = '/settings' - -export const RECEIVE = 'RECEIVE' -export const UPDATE = 'UPDATE' +export const RECEIVE = 'SETTINGS_RECEIVE' +export const UPDATE = 'SETTINGS_UPDATE' export const REQUEST_UPDATE = 'REQUEST_UPDATE' export const UNKNOWN_CMD = 'SETTINGS_UNKNOWN_CMD' @@ -23,7 +18,7 @@ export default function reducer(state = defaultState, action) { case UPDATE: return { ...state, - ...action.settings, + ...action.data, } default: @@ -31,46 +26,7 @@ export default function reducer(state = defaultState, action) { } } -/** - * @public msgQueue - */ -export function handleWsMsg(msg) { - switch (msg.cmd) { - - case websocketActions.CMD_UPDATE: - return updateSettings(msg.data) - - default: - console.error('unknown settings update', msg) - return { type: UNKNOWN_CMD, msg } - } -} - -/** - * @public - */ export function update(settings) { fetchApi.put('/settings', settings) return { type: REQUEST_UPDATE } } - -/** - * @public websocket - */ -export function fetchData() { - return msgQueueActions.fetchData(MSG_TYPE) -} - -/** - * @public msgQueue - */ -export function receiveData(settings) { - return { type: RECEIVE, settings } -} - -/** - * @private - */ -export function updateSettings(settings) { - return { type: UPDATE, settings } -} diff --git a/web/src/js/ducks/ui/index.js b/web/src/js/ducks/ui/index.js index f3c5f59e..1d989eb1 100644 --- a/web/src/js/ducks/ui/index.js +++ b/web/src/js/ducks/ui/index.js @@ -2,6 +2,7 @@ import { combineReducers } from 'redux' import flow from './flow' import header from './header' +// TODO: Just move ducks/ui/* into ducks/? export default combineReducers({ flow, header, diff --git a/web/src/js/ducks/utils/list.js b/web/src/js/ducks/utils/list.js index fdeb5856..339fb921 100644 --- a/web/src/js/ducks/utils/list.js +++ b/web/src/js/ducks/utils/list.js @@ -76,30 +76,18 @@ export default function reduce(state = defaultState, action) { } } -/** - * @public - */ export function add(item) { return { type: ADD, item } } -/** - * @public - */ export function update(item) { return { type: UPDATE, item } } -/** - * @public - */ export function remove(id) { return { type: REMOVE, id } } -/** - * @public - */ export function receive(list) { return { type: RECEIVE, list } } diff --git a/web/src/js/ducks/websocket.js b/web/src/js/ducks/websocket.js deleted file mode 100644 index 21400bb5..00000000 --- a/web/src/js/ducks/websocket.js +++ /dev/null @@ -1,93 +0,0 @@ -import { ConnectionActions } from '../actions.js' -import { AppDispatcher } from '../dispatcher.js' - -import * as msgQueueActions from './msgQueue' -import * as eventLogActions from './eventLog' -import * as flowsActions from './flows' -import * as settingsActions from './settings' - -export const CMD_ADD = 'add' -export const CMD_UPDATE = 'update' -export const CMD_REMOVE = 'remove' -export const CMD_RESET = 'reset' - -export const SYM_SOCKET = Symbol('WEBSOCKET_SYM_SOCKET') - -export const CONNECT = 'WEBSOCKET_CONNECT' -export const CONNECTED = 'WEBSOCKET_CONNECTED' -export const DISCONNECT = 'WEBSOCKET_DISCONNECT' -export const DISCONNECTED = 'WEBSOCKET_DISCONNECTED' -export const ERROR = 'WEBSOCKET_ERROR' -export const MESSAGE = 'WEBSOCKET_MESSAGE' - -/* we may want to have an error message attribute here at some point */ -const defaultState = { connected: false, socket: null } - -export default function reduce(state = defaultState, action) { - switch (action.type) { - - case CONNECT: - return { ...state, [SYM_SOCKET]: action.socket } - - case CONNECTED: - return { ...state, connected: true } - - case DISCONNECT: - return { ...state, connected: false } - - case DISCONNECTED: - return { ...state, [SYM_SOCKET]: null, connected: false } - - default: - return state - } -} - -export function connect() { - return dispatch => { - const socket = new WebSocket(location.origin.replace('http', 'ws') + '/updates') - - socket.addEventListener('open', () => dispatch(onConnect())) - socket.addEventListener('close', () => dispatch(onDisconnect())) - socket.addEventListener('message', msg => dispatch(onMessage(JSON.parse(msg.data)))) - socket.addEventListener('error', error => dispatch(onError(error))) - - dispatch({ type: CONNECT, socket }) - } -} - -export function disconnect() { - return (dispatch, getState) => { - getState().settings[SYM_SOCKET].close() - dispatch({ type: DISCONNECT }) - } -} - -export function onConnect() { - // workaround to make sure that our state is already available. - return dispatch => { - dispatch({ type: CONNECTED }) - dispatch(settingsActions.fetchData()) - dispatch(flowsActions.fetchFlows()) - dispatch(eventLogActions.fetchData()) - } -} - -export function onMessage(msg) { - return msgQueueActions.handleWsMsg(msg) -} - -export function onDisconnect() { - return dispatch => { - dispatch(eventLogActions.add('WebSocket connection closed.')) - dispatch({ type: DISCONNECTED }) - } -} - -export function onError(error) { - // @todo let event log subscribe WebSocketActions.ERROR - return dispatch => { - dispatch(eventLogActions.add('WebSocket connection error.')) - dispatch({ type: ERROR, error }) - } -} diff --git a/web/src/js/urlState.js b/web/src/js/urlState.js new file mode 100644 index 00000000..118211db --- /dev/null +++ b/web/src/js/urlState.js @@ -0,0 +1,78 @@ +import { select } from "./ducks/flows" +import { selectTab } from "./ducks/ui/flow" +import { updateFilter, updateHighlight } from "./ducks/flowView" +import { toggleVisibility } from "./ducks/eventLog" + +const Query = { + SEARCH: "s", + HIGHLIGHT: "h", + SHOW_EVENTLOG: "e" +}; + +function updateStoreFromUrl(store) { + const [path, query] = window.location.hash.substr(1).split("?", 2) + const path_components = path.substr(1).split("/") + + if (path_components[0] === "flows") { + if (path_components.length == 3) { + const [flowId, tab] = path_components.slice(1) + store.dispatch(select(flowId)) + store.dispatch(selectTab(tab)) + } + } + + if (query) { + query + .split("&") + .forEach((x) => { + const [key, value] = x.split("=", 2) + switch (key) { + case Query.SEARCH: + store.dispatch(updateFilter(value)) + break + case Query.HIGHLIGHT: + store.dispatch(updateHighlight(value)) + break + case Query.SHOW_EVENTLOG: + if(!store.getState().eventLog.visible) + store.dispatch(toggleVisibility(value)) + break + default: + console.error(`unimplemented query arg: ${x}`) + } + }) + } +} + +function updateUrlFromStore(store) { + const state = store.getState() + let query = { + [Query.SEARCH]: state.flowView.filter, + [Query.HIGHLIGHT]: state.flowView.highlight, + [Query.SHOW_EVENTLOG]: state.eventLog.visible, + } + const queryStr = Object.keys(query) + .filter(k => query[k]) + .map(k => `${k}=${query[k]}`) + .join("&") + + let url + if (state.flows.selected.length > 0) { + url = `/flows/${state.flows.selected[0]}/${state.ui.flow.tab}` + } else { + url = "/flows" + } + + if (queryStr) { + url += "?" + queryStr + } + if (window.location.hash !== url) { + // FIXME: replace state + window.location.hash = url + } +} + +export default function initialize(store) { + updateStoreFromUrl(store) + store.subscribe(() => updateUrlFromStore(store)) +} |