diff options
Diffstat (limited to 'web/src/js/ducks')
-rw-r--r-- | web/src/js/ducks/eventLog.js | 178 | ||||
-rw-r--r-- | web/src/js/ducks/flows.js | 274 | ||||
-rw-r--r-- | web/src/js/ducks/utils/list.js | 209 | ||||
-rwxr-xr-x[-rw-r--r--] | web/src/js/ducks/utils/view.js | 243 | ||||
-rwxr-xr-x | web/src/js/ducks/views.js | 40 | ||||
-rwxr-xr-x | web/src/js/ducks/views/main.js | 199 | ||||
-rw-r--r-- | web/src/js/ducks/websocket.js | 15 |
7 files changed, 769 insertions, 389 deletions
diff --git a/web/src/js/ducks/eventLog.js b/web/src/js/ducks/eventLog.js index 44b67b2c..0c875689 100644 --- a/web/src/js/ducks/eventLog.js +++ b/web/src/js/ducks/eventLog.js @@ -1,80 +1,152 @@ -import makeList from "./utils/list" -import {updateViewFilter, updateViewList} from "./utils/view" +import { fetchApi } from '../utils' +import reduceList, * as listActions from './utils/list' +import reduceView, * as viewActions from './utils/view' +import * as websocketActions from './websocket' -const TOGGLE_FILTER = 'TOGGLE_EVENTLOG_FILTER' -const TOGGLE_VISIBILITY = 'TOGGLE_EVENTLOG_VISIBILITY' -export const UPDATE_LOG = "UPDATE_EVENTLOG" - -const { - reduceList, - updateList, - fetchList, - addItem, -} = makeList(UPDATE_LOG, "/events") +export const WS_MSG_TYPE = 'UPDATE_EVENTLOG' +export const ADD = 'EVENTLOG_ADD' +export const REQUEST = 'EVENTLOG_REQUEST' +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' const defaultState = { + logId: 0, visible: false, - filter: { - "debug": false, - "info": true, - "web": true - }, - events: reduceList(), - filteredEvents: [], + filters: { debug: false, info: true, web: true }, + list: undefined, + view: undefined, } -export default function reducer(state = defaultState, action) { +export default function reduce(state = defaultState, action) { switch (action.type) { + + case TOGGLE_VISIBILITY: + return { + ...state, + visible: !state.visible + } + case TOGGLE_FILTER: - const filter = { - ...state.filter, - [action.filter]: !state.filter[action.filter] + const filters = { ...state.filters, [action.filter]: !state.filters[action.filter] } + return { + ...state, + filters, + view: reduceView(state.view, viewActions.updateFilter(state.list, log => filters[log.level])), + } + + case ADD: + const item = { + id: `log-${state.logId}`, + message: action.message, + level: action.level, } return { ...state, - filter, - filteredEvents: updateViewFilter( - state.events, - x => filter[x.level] - ) + logId: state.logId + 1, + list: reduceList(state.list, listActions.add(item)), + view: reduceView(state.view, viewActions.add(item, log => state.filters[log.level])), } - case TOGGLE_VISIBILITY: + + case REQUEST: return { ...state, - visible: !state.visible + list: reduceList(state.list, listActions.request()), + } + + case RECEIVE: + const list = reduceList(state.list, listActions.receive(action.list)) + return { + ...state, + list, + view: reduceView(state.view, viewActions.receive(list, log => state.filters[log.level])), } - case UPDATE_LOG: - const events = reduceList(state.events, action) + + default: return { ...state, - events, - filteredEvents: updateViewList( - state.filteredEvents, - state.events, - events, - action, - x => state.filter[x.level] - ) + list: reduceList(state.list, action), + view: reduceView(state.view, 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 state + return { type: UNKNOWN_CMD, msg } } } +/** + * @public websocket + */ +export function fetchData() { + return dispatch => { + dispatch(request()) -export function toggleEventLogFilter(filter) { - return {type: TOGGLE_FILTER, filter} + return fetchApi('/events') + .then(res => res.json()) + .then(json => dispatch(receive(json.data))) + .catch(error => dispatch(fetchError(error))) + } } -export function toggleEventLogVisibility() { - return {type: TOGGLE_VISIBILITY} + +/** + * @private + */ +export function request() { + return { type: REQUEST } +} + +/** + * @private + */ +export function receive(list) { + return { type: RECEIVE, list } } -let id = 0 -export function addLogEntry(message, level = "web") { - return addItem({ - message, - level, - id: `log-${id++}` - }) + +/** + * @private + */ +export function fetchError(error) { + return { type: FETCH_ERROR, error } } -export {updateList as updateLogEntries, fetchList as fetchLogEntries}
\ No newline at end of file diff --git a/web/src/js/ducks/flows.js b/web/src/js/ducks/flows.js index b877d3e4..f732f536 100644 --- a/web/src/js/ducks/flows.js +++ b/web/src/js/ducks/flows.js @@ -1,114 +1,230 @@ -import makeList from "./utils/list" -import Filt from "../filt/filt" -import {updateViewFilter, updateViewList, updateViewSort} from "./utils/view" -import {reverseString} from "../utils.js"; -import * as columns from "../components/FlowTable/FlowColumns"; +import { fetchApi } from '../utils' +import reduceList, * as listActions from './utils/list' +import reduceViews, * as viewsActions from './views' +import * as websocketActions from './websocket' -export const UPDATE_FLOWS = "UPDATE_FLOWS" -export const SET_FILTER = "SET_FLOW_FILTER" -export const SET_HIGHLIGHT = "SET_FLOW_HIGHLIGHT" -export const SET_SORT = "SET_FLOW_SORT" -export const SELECT_FLOW = "SELECT_FLOW" - -const { - reduceList, - updateList, - fetchList, -} = makeList(UPDATE_FLOWS, "/flows") +export const WS_MSG_TYPE = 'UPDATE_FLOWS' +export const ADD = 'FLOWS_ADD' +export const UPDATE = 'FLOWS_UPDATE' +export const REMOVE = 'FLOWS_REMOVE' +export const REQUEST = 'FLOWS_REQUEST' +export const RECEIVE = 'FLOWS_RECEIVE' +export const REQUEST_ACTION = 'FLOWS_REQUEST_ACTION' +export const UNKNOWN_CMD = 'FLOWS_UNKNOWN_CMD' +export const FETCH_ERROR = 'FLOWS_FETCH_ERROR' const defaultState = { - all: reduceList(), - selected: [], - view: [], - filter: undefined, - highlight: undefined, - sort: {sortColumn: undefined, sortDesc: false}, -} - -function makeFilterFn(filter) { - return filter ? Filt.parse(filter) : () => true; -} - - -function makeSortFn(sort){ - let column = columns[sort.sortColumn]; - if (!column) return; - - let sortKeyFun = column.sortKeyFun; - if (sort.sortDesc) { - sortKeyFun = sortKeyFun && function (flow) { - const k = column.sortKeyFun(flow); - return _.isString(k) ? reverseString("" + k) : -k; - }; - } - return sortKeyFun; + list: undefined, + views: undefined, } -export default function reducer(state = defaultState, action) { +export default function reduce(state = defaultState, action) { switch (action.type) { - case UPDATE_FLOWS: - let all = reduceList(state.all, action) + + case ADD: return { ...state, - all, - view: updateViewList(state.view, state.all, all, action, makeFilterFn(action.filter), makeSortFn(state.sort)) + list: reduceList(state.list, listActions.add(action.item)), + views: reduceViews(state.views, viewsActions.add(action.item)), } - case SET_FILTER: + + case UPDATE: return { ...state, - filter: action.filter, - view: updateViewFilter(state.all, makeFilterFn(action.filter), makeSortFn(state.sort)) + list: reduceList(state.list, listActions.update(action.id, action.item)), + views: reduceViews(state.views, viewsActions.update(action.id, action.item)), } - case SET_HIGHLIGHT: + + case REMOVE: return { ...state, - highlight: action.highlight + list: reduceList(state.list, listActions.remove(action.item.id)), + views: reduceViews(state.views, viewsActions.remove(action.item.id)), } - case SET_SORT: + + case REQUEST: return { ...state, - sort: action.sort, - view: updateViewSort(state.view, makeSortFn(action.sort)) + list: reduceList(state.list, listActions.request()), } - case SELECT_FLOW: + + case RECEIVE: + const list = reduceList(state.list, listActions.receive(action.list)) return { ...state, - selected: [action.flowId] + list, + views: reduceViews(state.views, viewsActions.receive(list)), } + default: - return state + return { + ...state, + list: reduceList(state.list, action), + views: reduceViews(state.views, action), + } } } +/** + * @public + */ +export function accept(flow) { + fetchApi(`/flows/${flow.id}/accept`, { method: 'POST' }) + return { type: REQUEST_ACTION } +} -export function setFilter(filter) { - return { - type: SET_FILTER, - filter - } +/** + * @public + */ +export function acceptAll() { + fetchApi('/flows/accept', { method: 'POST' }) + return { type: REQUEST_ACTION } } -export function setHighlight(highlight) { - return { - type: SET_HIGHLIGHT, - highlight - } + +/** + * @public + */ +export function remove(flow) { + fetchApi(`/flows/${flow.id}`, { method: 'DELETE' }) + return { type: REQUEST_ACTION } +} + +/** + * @public + */ +export function duplicate(flow) { + fetchApi(`/flows/${flow.id}/duplicate`, { method: 'POST' }) + return { type: REQUEST_ACTION } +} + +/** + * @public + */ +export function replay(flow) { + fetchApi(`/flows/${flow.id}/replay`, { method: 'POST' }) + return { type: REQUEST_ACTION } +} + +/** + * @public + */ +export function revert(flow) { + fetchApi(`/flows/${flow.id}/revert`, { method: 'POST' }) + return { type: REQUEST_ACTION } } -export function setSort(sort){ - return { - type: SET_SORT, - sort + +/** + * @public + */ +export function update(flow, body) { + fetchApi(`/flows/${flow.id}`, { method: 'PUT', body }) + return { type: REQUEST_ACTION } +} + +/** + * @public + */ +export function clear() { + fetchApi('/clear', { method: 'POST' }) + return { type: REQUEST_ACTION } +} + +/** + * @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) + fetchApi('/flows/dump', { method: 'post', body }) + return { type: REQUEST_ACTION } +} + +/** + * This action creater takes all WebSocket events + * + * @public websocket + */ +export function handleWsMsg(msg) { + switch (msg.cmd) { + + case websocketActions.CMD_ADD: + return add(msg.data) + + case websocketActions.CMD_UPDATE: + return update(msg.data.id, msg.data) + + case websocketActions.CMD_REMOVE: + return remove(msg.data.id) + + case websocketActions.CMD_RESET: + return fetchData() + + default: + return { type: UNKNOWN_CMD, msg } } } -export function selectFlow(flowId) { - return (dispatch, getState) => { - dispatch({ - type: SELECT_FLOW, - currentSelection: getState().flows.selected[0], - flowId - }) + +/** + * @public websocket + */ +export function fetchData() { + return dispatch => { + dispatch(request()) + + return fetchApi('/flows') + .then(res => res.json()) + .then(json => dispatch(receive(json.data))) + .catch(error => dispatch(fetchError(error))) } } +/** + * @private + */ +export function add(item) { + return { type: ADD, item } +} -export {updateList as updateFlows, fetchList as fetchFlows} +/** + * @private + */ +export function update(id, item) { + return { type: UPDATE, id, item } +} + +/** + * @private + */ +export function remove(id) { + return { type: REMOVE, id } +} + +/** + * @private + */ +export function request() { + return { type: REQUEST } +} + +/** + * @private + */ +export function receive(list) { + return { type: RECEIVE, list } +} + +/** + * @private + */ +export function fetchError(error) { + return { type: FETCH_ERROR, error } +} diff --git a/web/src/js/ducks/utils/list.js b/web/src/js/ducks/utils/list.js index a830fe99..e66a8549 100644 --- a/web/src/js/ducks/utils/list.js +++ b/web/src/js/ducks/utils/list.js @@ -1,166 +1,91 @@ -import {fetchApi} from "../../utils" - -export const ADD = "ADD" -export const UPDATE = "UPDATE" -export const REMOVE = "REMOVE" -export const REQUEST_LIST = "REQUEST_LIST" -export const RECEIVE_LIST = "RECEIVE_LIST" +import _ from 'lodash' +export const SET = 'LIST_SET' +export const CLEAR = 'LIST_CLEAR' +export const REQUEST = 'LIST_REQUEST' +export const RECEIVE = 'LIST_RECEIVE' const defaultState = { - list: [], - isFetching: false, - actionsDuringFetch: [], - byId: {}, - indexOf: {}, + data: {}, + pendingActions: null, } -export default function makeList(actionType, fetchURL) { - function reduceList(state = defaultState, action = {}) { - - if (action.type !== actionType) { - return state - } +export default function reduce(state = defaultState, action) { + switch (action.type) { - // Handle cases where we finished fetching or are still fetching. - if (action.cmd === RECEIVE_LIST) { - let s = { - isFetching: false, - actionsDuringFetch: [], - list: action.list, - byId: {}, - indexOf: {} - } - for (let i = 0; i < action.list.length; i++) { - let item = action.list[i] - s.byId[item.id] = item - s.indexOf[item.id] = i - } - for (action of state.actionsDuringFetch) { - s = reduceList(s, action) + case SET: + if (state.pendingActions) { + return { + ...state, + pendingActions: [...state.pendingActions, action] + } } - return s - } else if (state.isFetching) { return { ...state, - actionsDuringFetch: [...state.actionsDuringFetch, action] + data: { ...state.data, [action.id]: null, [action.item.id]: action.item } } - } - - let list, itemIndex - switch (action.cmd) { - case ADD: - return { - list: [...state.list, action.item], - byId: {...state.byId, [action.item.id]: action.item}, - indexOf: {...state.indexOf, [action.item.id]: state.list.length}, - } - - case UPDATE: - list = [...state.list] - itemIndex = state.indexOf[action.item.id] - list[itemIndex] = action.item + case CLEAR: + if (state.pendingActions) { return { ...state, - list, - byId: {...state.byId, [action.item.id]: action.item}, + pendingActions: [...state.pendingActions, action] } + } + return { + ...state, + data: { ...state.data, [action.id]: null } + } - case REMOVE: - list = [...state.list] - itemIndex = state.indexOf[action.item.id] - list.splice(itemIndex, 1) - return { - ...state, - list, - byId: {...state.byId, [action.item.id]: undefined}, - indexOf: {...state.indexOf, [action.item.id]: undefined}, - } - - case REQUEST_LIST: - return { - ...state, - isFetching: true - } - - default: - console.debug("unknown action", action) - return state - } - } - - function addItem(item) { - return { - type: actionType, - cmd: ADD, - item - } - } - - function updateItem(item) { - return { - type: actionType, - cmd: UPDATE, - item - } - } - - function removeItem(item) { - return { - type: actionType, - cmd: REMOVE, - item - } - } - - - function updateList(event) { - /* This action creater takes all WebSocket events */ - return dispatch => { - switch (event.cmd) { - case "add": - return dispatch(addItem(event.data)) - case "update": - return dispatch(updateItem(event.data)) - case "remove": - return dispatch(removeItem(event.data)) - case "reset": - return dispatch(fetchList()) - default: - console.error("unknown list update", event) + case REQUEST: + return { + ...state, + pendingActions: [] } - } - } - function requestList() { - return { - type: actionType, - cmd: REQUEST_LIST, - } - } + case RECEIVE: + return state.pendingActions.reduce(reduce, { + ...state, + pendingActions: null, + data: _.fromPairs(action.list.map(item => [item.id, item])), + }) - function receiveList(list) { - return { - type: actionType, - cmd: RECEIVE_LIST, - list - } + default: + return state } +} - function fetchList() { - return dispatch => { +/** + * @public + */ +export function add(item) { + return { type: SET, id: item.id, item } +} - dispatch(requestList()) +/** + * @public + */ +export function update(id, item) { + return { type: SET, id, item } +} - return fetchApi(fetchURL).then(response => { - return response.json().then(json => { - dispatch(receiveList(json.data)) - }) - }) - } - } +/** + * @public + */ +export function remove(id) { + return { type: CLEAR, id } +} +/** + * @public + */ +export function request() { + return { type: REQUEST } +} - return {reduceList, updateList, fetchList, addItem, updateItem, removeItem,} -}
\ No newline at end of file +/** + * @public + */ +export function receive(list) { + return { type: RECEIVE, list } +} diff --git a/web/src/js/ducks/utils/view.js b/web/src/js/ducks/utils/view.js index 01d57b17..3b552378 100644..100755 --- a/web/src/js/ducks/utils/view.js +++ b/web/src/js/ducks/utils/view.js @@ -1,134 +1,157 @@ -import {ADD, UPDATE, REMOVE, REQUEST_LIST, RECEIVE_LIST} from "./list" - -const defaultFilterFn = x => true -const defaultSortFn = false - -const makeCompareFn = sortFn => { - let compareFn = (a, b) => { - let akey = sortFn(a), - bkey = sortFn(b) - if (akey < bkey) { - return -1 - } else if (akey > bkey) { - return 1 - } else { - return 0 - } - } - // need to adjust sortedIndexOf as well - // if (sortFn.reverse) - // return (a, b) => compareFn(b, a) - return compareFn -} +import _ from 'lodash' -const sortedInsert = (list, sortFn, item) => { - let l = [...list, item] - l.indexOf = x => sortedIndexOf(l, x, sortFn) - let compareFn = makeCompareFn(sortFn) - - // only sort if sorting order is not correct yet - if (sortFn && compareFn(list[list.length - 1], item) > 0) { - // TODO: This is untested - console.debug("sorting view...") - l.sort(compareFn) - } - return l -} +export const UPDATE_FILTER = 'VIEW_UPDATE_FILTER' +export const UPDATE_SORTER = 'VIEW_UPDATE_SORTER' +export const ADD = 'VIEW_ADD' +export const UPDATE = 'VIEW_UPDATE' +export const REMOVE = 'VIEW_REMOVE' +export const RECEIVE = 'VIEW_RECEIVE' -const sortedRemove = (list, sortFn, item) => { - let itemId = item.id - let l = list.filter(x => x.id !== itemId) - l.indexOf = x => sortedIndexOf(l, x, sortFn) - return l +const defaultState = { + data: [], + indexOf: {}, } -export function sortedIndexOf(list, value, sortFn) { - if (!sortFn) { - sortFn = x => 0 // This triggers the linear search for flows that have the same sort value. - } +export default function reduce(state = defaultState, action) { + switch (action.type) { - let low = 0, - high = list.length, - val = sortFn(value), - mid; - while (low < high) { - mid = (low + high) >>> 1; - if (sortFn(list[mid]) < val) { - low = mid + 1 - } else { - high = mid + case UPDATE_FILTER: { + const data = _.values(action.list.data).filter(action.filter).sort(action.sorter) + return { + ...state, + data, + indexOf: _.fromPairs(data.map((item, index) => [item.id, index])), + } } - } - // Two flows may have the same sort value. - // we previously determined the leftmost flow with the same sort value, - // so no we need to scan linearly - while (list[low].id !== value.id && sortFn(list[low + 1]) === val) { - low++ - } - return low; -} + case UPDATE_SORTER: { + const data = [...state.data].sort(action.sorter) + return { + ...state, + data, + indexOf: _.fromPairs(data.map((item, index) => [item.id, index])) + } + } -// for when the list changes -export function updateViewList(currentView, currentList, nextList, action, filterFn = defaultFilterFn, sortFn = defaultSortFn) { - switch (action.cmd) { - case REQUEST_LIST: - return currentView - case RECEIVE_LIST: - return updateViewFilter(nextList, filterFn, sortFn) case ADD: - if (filterFn(action.item)) { - return sortedInsert(currentView, sortFn, action.item) + if (state.indexOf[action.item.id] != null || !action.filter(action.item)) { + return state } - return currentView - case UPDATE: - // let's determine if it's in the view currently and if it should be in the view. - let currentItemState = currentList.byId[action.item.id], - nextItemState = action.item, - isInView = filterFn(currentItemState), - shouldBeInView = filterFn(nextItemState) - - if (!isInView && shouldBeInView) - return sortedInsert(currentView, sortFn, action.item) - if (isInView && !shouldBeInView) - return sortedRemove(currentView, sortFn, action.item) - if (isInView && shouldBeInView) { - let s = [...currentView] - s.indexOf = x => sortedIndexOf(s, x, sortFn) - s[s.indexOf(currentItemState)] = nextItemState - if (sortFn && sortFn(currentItemState) !== sortFn(nextItemState)) - s.sort(makeCompareFn(sortFn)) - return s + return { + ...state, + ...sortedInsert(state, action.item, action.sorter), } - return currentView + case REMOVE: - let isInView_ = filterFn(currentList.byId[action.item.id]) - if (isInView_) { - return sortedRemove(currentView, sortFn, action.item) + if (state.indexOf[action.item.id] == null) { + return state + } + return { + ...state, + ...sortedRemove(state, action.id), + } + + case UPDATE: { + if (state.indexOf[action.item.id] == null) { + return + } + const nextState = { + ...state, + ...sortedRemove(state, action.id), } - return currentView + if (!action.filter(action.item)) { + return nextState + } + return { + ...nextState, + ...sortedInsert(nextState, action.item, action.sorter) + } + } + + case RECEIVE: { + const data = _.values(action.list.data).filter(action.filter).sort(action.sorter) + return { + ...state, + data, + indexOf: _.fromPairs(data.map((item, index) => [item.id, index])), + } + } + default: - console.error("Unknown list action: ", action) - return currentView + return state } } -export function updateViewFilter(list, filterFn = defaultFilterFn, sortFn = defaultSortFn) { - let filtered = list.list.filter(filterFn) - if (sortFn){ - filtered.sort(makeCompareFn(sortFn)) +export function updateFilter(list, filter = defaultFilter, sorter = defaultSorter) { + return { type: UPDATE_FILTER, list, filter, sorter } +} + +export function updateSorter(sorter = defaultSorter) { + return { type: UPDATE_SORTER, sorter } +} + +export function add(item, filter = defaultFilter, sorter = defaultSorter) { + return { type: ADD, item, filter, sorter } +} + +export function update(id, item, filter = defaultFilter, sorter = defaultSorter) { + return { type: UPDATE, id, item, filter, sorter } +} + +export function remove(id) { + return { type: REMOVE, id } +} + +export function receive(list, filter = defaultFilter, sorter = defaultSorter) { + return { type: RECEIVE, list, filter, sorter } +} + +function sortedInsert(state, item, sorter) { + const index = sortedIndex(state.data, item, sorter) + const data = [...state.data] + const indexOf = { ...state.indexOf } + + data.splice(index, 0, item) + for (let i = data.length - 1; i >= index; i--) { + indexOf[data[i].id] = i } - filtered.indexOf = x => sortedIndexOf(filtered, x, sortFn) - return filtered + return { data, indexOf } } -export function updateViewSort(list, sortFn = defaultSortFn) { - let sorted = [...list] - if (sortFn) { - sorted.sort(makeCompareFn(sortFn)) +function sortedRemove(state, id) { + const index = state.indexOf[id] + const data = [...state.data] + const indexOf = { ...state.indexOf, [id]: null } + + data.splice(index, 1) + for (let i = data.length - 1; i >= index; i--) { + indexOf[data[i].id] = i } - sorted.indexOf = x => sortedIndexOf(sorted, x, sortFn) - return sorted + return { data, indexOf } +} + +function sortedIndex(list, item, sorter) { + let low = 0 + let high = list.length + + while (low < high) { + const middle = (low + high) >>> 1 + if (sorter(item, list[middle]) > 0) { + low = middle + 1 + } else { + high = middle + } + } + + return low +} + +function defaultFilter() { + return true +} + +function defaultSorter(a, b) { + return 0 } diff --git a/web/src/js/ducks/views.js b/web/src/js/ducks/views.js new file mode 100755 index 00000000..e1e46c2e --- /dev/null +++ b/web/src/js/ducks/views.js @@ -0,0 +1,40 @@ +import { combineReducers } from 'redux' +import * as viewActions from './utils/view' +import main from './views/main.js' + +export const ADD = 'FLOW_VIEWS_ADD' +export const UPDATE = 'FLOW_VIEWS_UPDATE' +export const REMOVE = 'FLOW_VIEWS_REMOVE' +export const RECEIVE = 'FLOW_VIEWS_RECEIVE' + +export default combineReducers({ + main, +}) + +/** + * @public + */ +export function add(item) { + return { type: ADD, item } +} + +/** + * @public + */ +export function update(id, item) { + return { type: UPDATE, id, 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/views/main.js b/web/src/js/ducks/views/main.js new file mode 100755 index 00000000..74dc2606 --- /dev/null +++ b/web/src/js/ducks/views/main.js @@ -0,0 +1,199 @@ +import { RequestUtils } from '../../flow/utils' +import reduceView, * as viewActions from '../utils/view' +import * as viewsActions from '../views' + +export const UPDATE_FILTER = 'FLOW_VIEWS_MAIN_UPDATE_FILTER' +export const UPDATE_SORTER = 'FLOW_VIEWS_MAIN_UPDATE_SORTER' +export const UPDATE_HIGHLIGHT = 'FLOW_VIEWS_MAIN_UPDATE_HIGHLIGHT' +export const SELECT = 'FLOW_VIEWS_MAIN_SELECT' + +const sortKeyFuns = { + + TLSColumn: flow => flow.request.scheme, + + PathColumn: flow => RequestUtils.pretty_url(flow.request), + + MethodColumn: flow => flow.request.method, + + StatusColumn: flow => flow.response && flow.response.status_code, + + TimeColumn: flow => flow.response && flow.response.timestamp_end - flow.request.timestamp_start, + + SizeColumn: flow => { + let total = flow.request.contentLength + if (flow.response) { + total += flow.response.contentLength || 0 + } + return total + }, +} + +const defaultState = { + highlight: null, + selected: [], + filter: null, + sorter: { column: null, desc: false }, + view: undefined, +} + +export default function reduce(state = defaultState, action) { + switch (action.type) { + + case UPDATE_HIGHLIGHT: + return { + ...state, + highlight: action.highlight, + } + + case SELECT: + return { + ...state, + selected: [action.id] + } + + case UPDATE_FILTER: + return { + ...state, + filter: action.filter, + view: reduceView( + state.view, + viewActions.updateFilter( + action.list, + makeFilter(action.filter), + makeSorter(state.sorter) + ) + ), + } + + case UPDATE_SORTER: + const sorter = { column: action.column, desc: action.desc } + return { + ...state, + sorter, + view: reduceView( + state.view, + viewActions.updateSorter( + makeSorter(sorter) + ) + ), + } + + case viewsActions.ADD: + return { + ...state, + view: reduceView( + state.view, + viewActions.add( + action.item, + makeFilter(state.filter), + makeSorter(state.sorter) + ) + ), + } + + case viewsActions.UPDATE: + return { + ...state, + view: reduceView( + state.view, + viewActions.update( + action.id, + action.item, + makeFilter(state.filter), + makeSorter(state.sorter) + ) + ), + } + + case viewsActions.REMOVE: + return { + ...state, + view: reduceView( + state.view, + viewActions.remove( + action.id + ) + ), + } + + case viewsActions.RECEIVE: + return { + ...state, + view: reduceView( + state.view, + viewActions.receive( + action.list, + makeFilter(state.filter), + makeSorter(state.sorter) + ) + ), + } + + default: + return { + ...state, + view: reduceView(state.view, action) + } + } +} + +/** + * @public + */ +export function updateFilter(filter) { + return (dispatch, getState) => { + return { type: UPDATE_FILTER, filter, list: getState().flows.list } + } +} + +/** + * @public + */ +export function updateHighlight(highlight) { + return { type: UPDATE_HIGHLIGHT, highlight } +} + +/** + * @public + */ +export function updateSorter(column, desc) { + return { type: UPDATE_SORTER, column, desc } +} + +/** + * @public + */ +export function select(id) { + return { type: SELECT, currentSelection: getState().flows.views.main.selected[0], id } +} + +/** + * @private + */ +function makeFilter(filter) { + if (!filter) { + return + } + return Filt.parse(filter) +} + +/** + * @private + */ +function makeSorter({ column, desc }) { + const sortKeyFun = sortKeyFuns[column] + if (!sortKeyFun) { + return + } + return (a, b) => { + const ka = sortKeyFun(a) + const kb = sortKeyFun(b) + if (ka > kb) { + return desc ? -1 : 1 + } + if (ka < kb) { + return desc ? 1 : -1 + } + return 0 + } +} diff --git a/web/src/js/ducks/websocket.js b/web/src/js/ducks/websocket.js index f38124c9..c79d887a 100644 --- a/web/src/js/ducks/websocket.js +++ b/web/src/js/ducks/websocket.js @@ -4,6 +4,11 @@ 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' @@ -78,14 +83,14 @@ export function onMessage(msg) { switch (data.type) { - case eventLogActions.UPDATE_LOG: - return dispatch(eventLogActions.updateLogEntries(data)) + case eventLogActions.WS_MSG_TYPE: + return dispatch(eventLogActions.handleWsMsg(data)) - case flowsActions.UPDATE_FLOWS: - return dispatch(flowsActions.updateFlows(data)) + case flowsActions.WS_MSG_TYPE: + return dispatch(flowsActions.handleWsMsg(data)) case settingsActions.UPDATE_SETTINGS: - return dispatch(settingsActions.updateSettings(message)) + return dispatch(settingsActions.handleWsMsg(data)) default: console.warn('unknown message', data) |