diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js
index ca72bba06..0c9a83916 100644
--- a/client/app/scripts/actions/app-actions.js
+++ b/client/app/scripts/actions/app-actions.js
@@ -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
});
diff --git a/client/app/scripts/components/footer.js b/client/app/scripts/components/footer.js
index e31d833d9..99f7dee2e 100644
--- a/client/app/scripts/components/footer.js
+++ b/client/app/scripts/components/footer.js
@@ -60,7 +60,7 @@ class Footer extends React.Component {
}
Version
- {version}
+ {version || '...'}
on
{hostname}
diff --git a/client/app/scripts/components/node-details/node-details-info.js b/client/app/scripts/components/node-details/node-details-info.js
index 1434c98d9..8fc72bb0d 100644
--- a/client/app/scripts/components/node-details/node-details-info.js
+++ b/client/app/scripts/components/node-details/node-details-info.js
@@ -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'),
};
}
diff --git a/client/app/scripts/components/node-details/node-details-table.js b/client/app/scripts/components/node-details/node-details-table.js
index a0a91efb0..309cfab9c 100644
--- a/client/app/scripts/components/node-details/node-details-table.js
+++ b/client/app/scripts/components/node-details/node-details-table.js
@@ -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'),
};
}
diff --git a/client/app/scripts/components/time-travel-wrapper.js b/client/app/scripts/components/time-travel-wrapper.js
index c694ea636..90379d60e 100644
--- a/client/app/scripts/components/time-travel-wrapper.js
+++ b/client/app/scripts/components/time-travel-wrapper.js
@@ -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 (
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);
}
diff --git a/client/app/scripts/utils/router-utils.js b/client/app/scripts/utils/router-utils.js
index ce35ca468..ae0c3a743 100644
--- a/client/app/scripts/utils/router-utils.js
+++ b/client/app/scripts/utils/router-utils.js
@@ -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')) {
diff --git a/client/app/scripts/utils/string-utils.js b/client/app/scripts/utils/string-utils.js
index d64123055..65788be56 100644
--- a/client/app/scripts/utils/string-utils.js
+++ b/client/app/scripts/utils/string-utils.js
@@ -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()
};
},
diff --git a/client/app/scripts/utils/web-api-utils.js b/client/app/scripts/utils/web-api-utils.js
index 6672bb847..7f9d503b4 100644
--- a/client/app/scripts/utils/web-api-utils.js
+++ b/client/app/scripts/utils/web-api-utils.js
@@ -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) => {
diff --git a/client/package.json b/client/package.json
index 84262ece0..0416b0f08 100644
--- a/client/package.json
+++ b/client/package.json
@@ -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"
},
diff --git a/client/yarn.lock b/client/yarn.lock
index cf7961609..aec961cd1 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -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"