aboutsummaryrefslogtreecommitdiffstats
path: root/web/src
diff options
context:
space:
mode:
Diffstat (limited to 'web/src')
-rw-r--r--web/src/js/actions.js44
-rw-r--r--web/src/js/app.jsx8
-rw-r--r--web/src/js/backends/websocket.js68
-rw-r--r--web/src/js/components/ProxyApp.jsx59
-rw-r--r--web/src/js/dispatcher.js18
-rw-r--r--web/src/js/ducks/app.js27
-rw-r--r--web/src/js/ducks/eventLog.js65
-rw-r--r--web/src/js/ducks/flowView.js25
-rw-r--r--web/src/js/ducks/flows.js108
-rw-r--r--web/src/js/ducks/msgQueue.js113
-rw-r--r--web/src/js/ducks/settings.js50
-rw-r--r--web/src/js/ducks/ui/index.js1
-rw-r--r--web/src/js/ducks/utils/list.js12
-rw-r--r--web/src/js/ducks/websocket.js93
-rw-r--r--web/src/js/urlState.js78
15 files changed, 176 insertions, 593 deletions
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))
+}