mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-02 17:50:39 +00:00
Store the pausedAt state in the app URL.
This commit is contained in:
@@ -345,6 +345,7 @@ export function pauseTimeAtNow() {
|
||||
dispatch({
|
||||
type: ActionTypes.PAUSE_TIME_AT_NOW
|
||||
});
|
||||
updateRoute(getState);
|
||||
if (!getState().get('nodesLoaded')) {
|
||||
getNodes(getState, dispatch);
|
||||
if (isResourceViewModeSelector(getState())) {
|
||||
@@ -582,6 +583,7 @@ export function resumeTime() {
|
||||
dispatch({
|
||||
type: ActionTypes.RESUME_TIME
|
||||
});
|
||||
updateRoute(getState);
|
||||
// After unpausing, all of the following calls will re-activate polling.
|
||||
getTopologies(getState, dispatch);
|
||||
getNodes(getState, dispatch, true);
|
||||
@@ -592,11 +594,13 @@ export function resumeTime() {
|
||||
};
|
||||
}
|
||||
|
||||
export function startTimeTravel() {
|
||||
export function startTimeTravel(timestamp = null) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ActionTypes.START_TIME_TRAVEL
|
||||
type: ActionTypes.START_TIME_TRAVEL,
|
||||
timestamp,
|
||||
});
|
||||
updateRoute(getState);
|
||||
if (!getState().get('nodesLoaded')) {
|
||||
getNodes(getState, dispatch);
|
||||
if (isResourceViewModeSelector(getState())) {
|
||||
@@ -623,6 +627,7 @@ export function jumpToTime(timestamp) {
|
||||
type: ActionTypes.JUMP_TO_TIME,
|
||||
timestamp,
|
||||
});
|
||||
updateRoute(getScopeState);
|
||||
getNodes(getScopeState, dispatch);
|
||||
getTopologies(getScopeState, dispatch);
|
||||
if (isResourceViewModeSelector(getScopeState())) {
|
||||
@@ -661,13 +666,32 @@ export function receiveTopologies(topologies) {
|
||||
}
|
||||
|
||||
export function receiveApiDetails(apiDetails) {
|
||||
return {
|
||||
type: ActionTypes.RECEIVE_API_DETAILS,
|
||||
capabilities: fromJS(apiDetails.capabilities),
|
||||
hostname: apiDetails.hostname,
|
||||
version: apiDetails.version,
|
||||
newVersion: apiDetails.newVersion,
|
||||
plugins: apiDetails.plugins,
|
||||
return (dispatch, getState) => {
|
||||
const isFirstTime = !getState().get('version');
|
||||
const pausedAt = getState().get('pausedAt');
|
||||
|
||||
dispatch({
|
||||
type: ActionTypes.RECEIVE_API_DETAILS,
|
||||
capabilities: fromJS(apiDetails.capabilities || {}),
|
||||
hostname: apiDetails.hostname,
|
||||
version: apiDetails.version,
|
||||
newVersion: apiDetails.newVersion,
|
||||
plugins: apiDetails.plugins,
|
||||
});
|
||||
|
||||
// On initial load either start time travelling at the pausedAt timestamp
|
||||
// (if it was given as URL param) if time travelling is enabled, otherwise
|
||||
// simply pause at the present time which is arguably the next best thing
|
||||
// we could do.
|
||||
// NOTE: We can't make this decision before API details are received because
|
||||
// we have no prior info on whether time travel would be available.
|
||||
if (isFirstTime && pausedAt) {
|
||||
if (apiDetails.capabilities && apiDetails.capabilities.historic_reports) {
|
||||
dispatch(startTimeTravel(pausedAt));
|
||||
} else {
|
||||
dispatch(pauseTimeAtNow());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -806,10 +830,6 @@ export function shutdown() {
|
||||
return (dispatch) => {
|
||||
stopPolling();
|
||||
teardownWebsockets();
|
||||
// Exit the time travel mode before unmounting the app.
|
||||
dispatch({
|
||||
type: ActionTypes.RESUME_TIME
|
||||
});
|
||||
dispatch({
|
||||
type: ActionTypes.SHUTDOWN
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ class Footer extends React.Component {
|
||||
</a>
|
||||
}
|
||||
<span className="footer-label">Version</span>
|
||||
{version}
|
||||
{version || '...'}
|
||||
<span className="footer-label">on</span>
|
||||
{hostname}
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Map as makeMap } from 'immutable';
|
||||
import MatchedText from '../matched-text';
|
||||
import ShowMore from '../show-more';
|
||||
import { formatDataType } from '../../utils/string-utils';
|
||||
import { getSerializedTimeTravelTimestamp } from '../../utils/web-api-utils';
|
||||
|
||||
|
||||
class NodeDetailsInfo extends React.Component {
|
||||
@@ -68,7 +67,7 @@ class NodeDetailsInfo extends React.Component {
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
timestamp: getSerializedTimeTravelTimestamp(state),
|
||||
timestamp: state.get('pausedAt'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import NodeDetailsTableRow from './node-details-table-row';
|
||||
import NodeDetailsTableHeaders from './node-details-table-headers';
|
||||
import { ipToPaddedString } from '../../utils/string-utils';
|
||||
import { moveElement, insertElement } from '../../utils/array-utils';
|
||||
import { getSerializedTimeTravelTimestamp } from '../../utils/web-api-utils';
|
||||
import {
|
||||
isIP, isNumber, defaultSortDesc, getTableColumnsStyles
|
||||
} from '../../utils/node-details-utils';
|
||||
@@ -305,7 +304,7 @@ NodeDetailsTable.defaultProps = {
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
timestamp: getSerializedTimeTravelTimestamp(state),
|
||||
timestamp: state.get('pausedAt'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class TimeTravelWrapper extends React.Component {
|
||||
}
|
||||
|
||||
changeTimestamp(timestamp) {
|
||||
this.props.jumpToTime(timestamp);
|
||||
this.props.jumpToTime(moment(timestamp).utc());
|
||||
}
|
||||
|
||||
trackTimestampEdit() {
|
||||
@@ -61,7 +61,7 @@ class TimeTravelWrapper extends React.Component {
|
||||
return (
|
||||
<TimeTravel
|
||||
visible={visible}
|
||||
timestamp={timestamp || moment()}
|
||||
timestamp={timestamp}
|
||||
earliestTimestamp={this.props.earliestTimestamp}
|
||||
onChangeTimestamp={this.changeTimestamp}
|
||||
onTimestampInputEdit={this.trackTimestampEdit}
|
||||
@@ -75,6 +75,7 @@ class TimeTravelWrapper extends React.Component {
|
||||
|
||||
function mapStateToProps(state, { params }) {
|
||||
const scopeState = state.scope || state;
|
||||
const pausedAt = scopeState.get('pausedAt');
|
||||
let firstSeenConnectedAt;
|
||||
|
||||
// If we're in the Weave Cloud context, use firstSeeConnectedAt as the earliest timestamp.
|
||||
@@ -89,8 +90,8 @@ function mapStateToProps(state, { params }) {
|
||||
visible: scopeState.get('showingTimeTravel'),
|
||||
topologyViewMode: scopeState.get('topologyViewMode'),
|
||||
currentTopology: scopeState.get('currentTopology'),
|
||||
earliestTimestamp: firstSeenConnectedAt,
|
||||
timestamp: scopeState.get('pausedAt'),
|
||||
earliestTimestamp: firstSeenConnectedAt && firstSeenConnectedAt.utc().format(),
|
||||
timestamp: pausedAt && pausedAt.utc().format(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax, import/no-unresolved */
|
||||
import debug from 'debug';
|
||||
import moment from 'moment';
|
||||
import { size, each, includes, isEqual } from 'lodash';
|
||||
import {
|
||||
fromJS,
|
||||
@@ -9,8 +10,6 @@ import {
|
||||
OrderedMap as makeOrderedMap,
|
||||
} from 'immutable';
|
||||
|
||||
import { nowInSecondsPrecision } from 'weaveworks-ui-components/lib/utils/time';
|
||||
|
||||
import ActionTypes from '../constants/action-types';
|
||||
import {
|
||||
GRAPH_VIEW_MODE,
|
||||
@@ -24,6 +23,7 @@ import { isPausedSelector } from '../selectors/time-travel';
|
||||
import { activeTopologyZoomCacheKeyPathSelector } from '../selectors/zooming';
|
||||
import { timestampsEqual } from '../utils/time-utils';
|
||||
import { applyPinnedSearches } from '../utils/search-utils';
|
||||
import { deserializeTimestamp } from '../utils/web-api-utils';
|
||||
import {
|
||||
findTopologyById,
|
||||
setTopologyUrlsById,
|
||||
@@ -86,7 +86,7 @@ export const initialState = makeMap({
|
||||
topologyOptions: makeOrderedMap(), // topologyId -> options
|
||||
topologyUrlsById: makeOrderedMap(), // topologyId -> topologyUrl
|
||||
topologyViewMode: GRAPH_VIEW_MODE,
|
||||
version: '...',
|
||||
version: null,
|
||||
versionUpdate: null,
|
||||
// Set some initial numerical values to prevent NaN in case of edgy race conditions.
|
||||
viewport: makeMap({ width: 0, height: 0 }),
|
||||
@@ -381,13 +381,13 @@ export function rootReducer(state = initialState, action) {
|
||||
case ActionTypes.PAUSE_TIME_AT_NOW: {
|
||||
state = state.set('showingTimeTravel', false);
|
||||
state = state.set('timeTravelTransitioning', false);
|
||||
return state.set('pausedAt', nowInSecondsPrecision());
|
||||
return state.set('pausedAt', moment().utc());
|
||||
}
|
||||
|
||||
case ActionTypes.START_TIME_TRAVEL: {
|
||||
state = state.set('showingTimeTravel', true);
|
||||
state = state.set('timeTravelTransitioning', false);
|
||||
return state.set('pausedAt', nowInSecondsPrecision());
|
||||
return state.set('pausedAt', action.timestamp || moment().utc());
|
||||
}
|
||||
|
||||
case ActionTypes.JUMP_TO_TIME: {
|
||||
@@ -694,6 +694,9 @@ export function rootReducer(state = initialState, action) {
|
||||
pinnedMetricType: action.state.pinnedMetricType
|
||||
});
|
||||
state = state.set('topologyViewMode', action.state.topologyViewMode);
|
||||
if (action.state.pausedAt) {
|
||||
state = state.set('pausedAt', deserializeTimestamp(action.state.pausedAt));
|
||||
}
|
||||
if (action.state.gridSortedBy) {
|
||||
state = state.set('gridSortedBy', action.state.gridSortedBy);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { each } from 'lodash';
|
||||
|
||||
import { route } from '../actions/app-actions';
|
||||
import { storageGet, storageSet } from './storage-utils';
|
||||
import { serializeTimestamp } from './web-api-utils';
|
||||
|
||||
//
|
||||
// page.js won't match the routes below if ":state" has a slash in it, so replace those before we
|
||||
@@ -50,6 +51,7 @@ export function getUrlState(state) {
|
||||
const urlState = {
|
||||
controlPipe: cp ? cp.toJS() : null,
|
||||
nodeDetails: nodeDetails.toJS(),
|
||||
pausedAt: serializeTimestamp(state.get('pausedAt')),
|
||||
topologyViewMode: state.get('topologyViewMode'),
|
||||
pinnedMetricType: state.get('pinnedMetricType'),
|
||||
pinnedSearches: state.get('pinnedSearches').toJS(),
|
||||
@@ -59,7 +61,7 @@ export function getUrlState(state) {
|
||||
gridSortedDesc: state.get('gridSortedDesc'),
|
||||
topologyId: state.get('currentTopologyId'),
|
||||
topologyOptions: state.get('topologyOptions').toJS(), // all options,
|
||||
contrastMode: state.get('contrastMode')
|
||||
contrastMode: state.get('contrastMode'),
|
||||
};
|
||||
|
||||
if (state.get('showingNetworks')) {
|
||||
|
||||
@@ -104,13 +104,12 @@ export function humanizedRoundedDownDuration(duration) {
|
||||
// that matches the `dataType` of the field. You must return an Object
|
||||
// with the keys `value` and `title` defined.
|
||||
// `referenceTimestamp` is the timestamp we've time-travelled to.
|
||||
export function formatDataType(field, referenceTimestampStr = null) {
|
||||
export function formatDataType(field, referenceTimestamp = null) {
|
||||
const formatters = {
|
||||
datetime(timestampString) {
|
||||
const timestamp = moment(timestampString);
|
||||
const referenceTimestamp = referenceTimestampStr ? moment(referenceTimestampStr) : moment();
|
||||
return {
|
||||
value: timestamp.from(referenceTimestamp),
|
||||
value: timestamp.from(referenceTimestamp ? moment(referenceTimestamp) : moment()),
|
||||
title: timestamp.utc().toISOString()
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import debug from 'debug';
|
||||
import moment from 'moment';
|
||||
import reqwest from 'reqwest';
|
||||
import { defaults } from 'lodash';
|
||||
import { Map as makeMap, List } from 'immutable';
|
||||
@@ -49,16 +50,17 @@ let firstMessageOnWebsocketAt = null;
|
||||
let continuePolling = true;
|
||||
|
||||
|
||||
export function getSerializedTimeTravelTimestamp(state) {
|
||||
// The timestamp parameter will be used only if it's in the past.
|
||||
if (!isPausedSelector(state)) return null;
|
||||
export function serializeTimestamp(timestamp) {
|
||||
return timestamp ? timestamp.toISOString() : null;
|
||||
}
|
||||
|
||||
return state.get('pausedAt').toISOString();
|
||||
export function deserializeTimestamp(str) {
|
||||
return str ? moment(str) : null;
|
||||
}
|
||||
|
||||
export function buildUrlQuery(params = makeMap(), state) {
|
||||
// Attach the time travel timestamp to every request to the backend.
|
||||
params = params.set('timestamp', getSerializedTimeTravelTimestamp(state));
|
||||
params = params.set('timestamp', serializeTimestamp(state.get('pausedAt')));
|
||||
|
||||
// Ignore the entries with values `null` or `undefined`.
|
||||
return params.map((value, param) => {
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"reselect": "3.0.1",
|
||||
"reselect-map": "1.0.3",
|
||||
"styled-components": "^2.2.1",
|
||||
"weaveworks-ui-components": "git+https://github.com/weaveworks/ui-components.git#v0.1.51",
|
||||
"weaveworks-ui-components": "git+https://github.com/weaveworks/ui-components.git#v0.1.52",
|
||||
"whatwg-fetch": "2.0.3",
|
||||
"xterm": "2.9.2"
|
||||
},
|
||||
|
||||
@@ -6666,9 +6666,9 @@ wd@^0.4.0:
|
||||
underscore.string "~3.0.3"
|
||||
vargs "~0.1.0"
|
||||
|
||||
"weaveworks-ui-components@git+https://github.com/weaveworks/ui-components.git#v0.1.51":
|
||||
version "0.1.51"
|
||||
resolved "git+https://github.com/weaveworks/ui-components.git#a08ea6a026f4cd58c66d4965838cf04d074604a4"
|
||||
"weaveworks-ui-components@git+https://github.com/weaveworks/ui-components.git#v0.1.52":
|
||||
version "0.1.52"
|
||||
resolved "git+https://github.com/weaveworks/ui-components.git#48978545233bfb0d1ac87795f332221cdaa58fc9"
|
||||
dependencies:
|
||||
babel-cli "^6.18.0"
|
||||
babel-plugin-transform-export-extensions "6.8.0"
|
||||
|
||||
Reference in New Issue
Block a user