mirror of
https://github.com/weaveworks/scope.git
synced 2026-08-19 04:16:21 +00:00
@@ -1,3 +1,64 @@
|
||||
## Release 1.5.0
|
||||
|
||||
Highlights:
|
||||
- More accurate and cheaper connection tracking with eBPF, which now is enabled by default.
|
||||
- Bug fixes and performance improvements.
|
||||
|
||||
New features and enhancements:
|
||||
- Enable eBPF tracking by default
|
||||
[#2535](https://github.com/weaveworks/scope/pull/2535)
|
||||
- Elide url passwords in cli arguments
|
||||
[#2568](https://github.com/weaveworks/scope/pull/2568)
|
||||
|
||||
Performance improvements:
|
||||
- drop addr and port from Endpoint.Latest map
|
||||
[#2581](https://github.com/weaveworks/scope/pull/2581)
|
||||
- parallel reduce
|
||||
[#2561](https://github.com/weaveworks/scope/pull/2561)
|
||||
- don't read all of /proc when probe.proc.spy=false
|
||||
[#2557](https://github.com/weaveworks/scope/pull/2557)
|
||||
- optimise: don't sort in NodeSet.ForEach
|
||||
[#2548](https://github.com/weaveworks/scope/pull/2548)
|
||||
- encode empty ps.Maps as nil
|
||||
[#2547](https://github.com/weaveworks/scope/pull/2547)
|
||||
|
||||
Bug fixes:
|
||||
- re-target app clients when name resolution changes
|
||||
[#2579](https://github.com/weaveworks/scope/pull/2579)
|
||||
- correct type for "Observed Gen."
|
||||
[#2572](https://github.com/weaveworks/scope/pull/2572)
|
||||
- Back off upon errored kubernetes api requests
|
||||
[#2562](https://github.com/weaveworks/scope/pull/2562)
|
||||
- Close eBPF tracker cleanly
|
||||
[#2541](https://github.com/weaveworks/scope/pull/2541)
|
||||
- Simplify connection tracker init and fix procfs scan fallback
|
||||
[#2539](https://github.com/weaveworks/scope/pull/2539)
|
||||
- Guard against null DaemonSet store
|
||||
[#2538](https://github.com/weaveworks/scope/pull/2538)
|
||||
|
||||
Internal improvements and cleanup:
|
||||
- es6ify server.js and include in eslint
|
||||
[#2560](https://github.com/weaveworks/scope/pull/2560)
|
||||
- Fix prog/main_test.go
|
||||
[#2567](https://github.com/weaveworks/scope/pull/2567)
|
||||
- Fix incomplete dependencies for `make scope/prog`
|
||||
[#2563](https://github.com/weaveworks/scope/pull/2563)
|
||||
- bump package.json version to current scope version
|
||||
[#2555](https://github.com/weaveworks/scope/pull/2555)
|
||||
- simplify connection join
|
||||
[#2559](https://github.com/weaveworks/scope/pull/2559)
|
||||
- Use map helpers
|
||||
[#2546](https://github.com/weaveworks/scope/pull/2546)
|
||||
- add copyreport utility
|
||||
[#2542](https://github.com/weaveworks/scope/pull/2542)
|
||||
|
||||
Weave Cloud related changes:
|
||||
- Time travel control
|
||||
[#2524](https://github.com/weaveworks/scope/pull/2524)
|
||||
- Add app capabilities to /api endpoint
|
||||
[#2575](https://github.com/weaveworks/scope/pull/2575)
|
||||
|
||||
|
||||
## Release 1.4.0
|
||||
|
||||
Highlights:
|
||||
|
||||
@@ -635,13 +635,15 @@ function updateFromNodesDeltaBuffer(dispatch, state) {
|
||||
}
|
||||
|
||||
export function clickResumeUpdate() {
|
||||
return (dispatch, getServiceState) => {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ActionTypes.CLICK_RESUME_UPDATE
|
||||
});
|
||||
// TODO: Find a better way to do this (see the comment above).
|
||||
const state = getState().scope || getState();
|
||||
// Periodically merge buffered nodes deltas until the buffer is emptied.
|
||||
nodesDeltaBufferUpdateTimer = setInterval(
|
||||
() => updateFromNodesDeltaBuffer(dispatch, getServiceState().scope),
|
||||
() => updateFromNodesDeltaBuffer(dispatch, state),
|
||||
NODES_DELTA_BUFFER_FEED_INTERVAL,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Plugins from './plugins';
|
||||
import PauseButton from './pause-button';
|
||||
import { trackMixpanelEvent } from '../utils/tracking-utils';
|
||||
import {
|
||||
clickDownloadGraph,
|
||||
@@ -65,6 +66,7 @@ class Footer extends React.Component {
|
||||
</div>
|
||||
|
||||
<div className="footer-tools">
|
||||
<PauseButton />
|
||||
<a
|
||||
className="footer-icon"
|
||||
onClick={this.handleRelayoutClick}
|
||||
|
||||
@@ -11,7 +11,6 @@ import { Loading, getNodeType } from './loading';
|
||||
import {
|
||||
isTopologyNodeCountZero,
|
||||
isNodesDisplayEmpty,
|
||||
isTopologyEmpty,
|
||||
} from '../utils/topology-utils';
|
||||
import {
|
||||
isGraphViewModeSelector,
|
||||
@@ -24,11 +23,12 @@ import { TOPOLOGY_LOADER_DELAY } from '../constants/timer';
|
||||
|
||||
// TODO: The information that we already have available on the frontend should enable
|
||||
// us to determine which of these cases exactly is preventing us from seeing the nodes.
|
||||
const NODE_COUNT_ZERO_CAUSES = [
|
||||
const NODES_STATS_COUNT_ZERO_CAUSES = [
|
||||
'We haven\'t received any reports from probes recently. Are the probes properly connected?',
|
||||
'Containers view only: you\'re not running Docker, or you don\'t have any containers',
|
||||
];
|
||||
const NODES_DISPLAY_EMPTY_CAUSES = [
|
||||
const NODES_NOT_DISPLAYED_CAUSES = [
|
||||
'There are nodes, but they\'ve been filtered out by pinned searches in the top-left corner.',
|
||||
'There are nodes, but they\'re currently hidden. Check the view options in the bottom-left if they allow for showing hidden nodes.',
|
||||
'There are no nodes for this particular moment in time. Use the time travel feature at the bottom-right corner to explore different times.',
|
||||
];
|
||||
@@ -39,13 +39,14 @@ const renderCauses = causes => (
|
||||
|
||||
class Nodes extends React.Component {
|
||||
renderConditionalEmptyTopologyError() {
|
||||
const { topologyNodeCountZero, nodesDisplayEmpty, topologyEmpty } = this.props;
|
||||
const { topologyNodeCountZero, nodesDisplayEmpty } = this.props;
|
||||
|
||||
return (
|
||||
<NodesError faIconClass="fa-circle-thin" hidden={!topologyEmpty}>
|
||||
<NodesError faIconClass="fa-circle-thin" hidden={!nodesDisplayEmpty}>
|
||||
<div className="heading">Nothing to show. This can have any of these reasons:</div>
|
||||
{topologyNodeCountZero && renderCauses(NODE_COUNT_ZERO_CAUSES)}
|
||||
{!topologyNodeCountZero && nodesDisplayEmpty && renderCauses(NODES_DISPLAY_EMPTY_CAUSES)}
|
||||
{topologyNodeCountZero ?
|
||||
renderCauses(NODES_STATS_COUNT_ZERO_CAUSES) :
|
||||
renderCauses(NODES_NOT_DISPLAYED_CAUSES)}
|
||||
</NodesError>
|
||||
);
|
||||
}
|
||||
@@ -84,7 +85,6 @@ function mapStateToProps(state) {
|
||||
isResourceViewMode: isResourceViewModeSelector(state),
|
||||
topologyNodeCountZero: isTopologyNodeCountZero(state),
|
||||
nodesDisplayEmpty: isNodesDisplayEmpty(state),
|
||||
topologyEmpty: isTopologyEmpty(state),
|
||||
websocketTransitioning: state.get('websocketTransitioning'),
|
||||
currentTopology: state.get('currentTopology'),
|
||||
nodesLoaded: state.get('nodesLoaded'),
|
||||
|
||||
@@ -60,14 +60,14 @@ class PauseButton extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps({ scope }) {
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
hasUpdates: !scope.get('nodesDeltaBuffer').isEmpty(),
|
||||
updateCount: scope.get('nodesDeltaBuffer').size,
|
||||
updatePausedAt: scope.get('updatePausedAt'),
|
||||
topologyViewMode: scope.get('topologyViewMode'),
|
||||
currentTopology: scope.get('currentTopology'),
|
||||
isPaused: isPausedSelector(scope),
|
||||
hasUpdates: !state.get('nodesDeltaBuffer').isEmpty(),
|
||||
updateCount: state.get('nodesDeltaBuffer').size,
|
||||
updatePausedAt: state.get('updatePausedAt'),
|
||||
topologyViewMode: state.get('topologyViewMode'),
|
||||
currentTopology: state.get('currentTopology'),
|
||||
isPaused: isPausedSelector(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { searchMatchCountByTopologySelector } from '../selectors/search';
|
||||
import { isResourceViewModeSelector } from '../selectors/topology';
|
||||
import { slugify } from '../utils/string-utils';
|
||||
import { parseQuery } from '../utils/search-utils';
|
||||
import { isTopologyEmpty } from '../utils/topology-utils';
|
||||
import { isTopologyNodeCountZero } from '../utils/topology-utils';
|
||||
import { trackMixpanelEvent } from '../utils/tracking-utils';
|
||||
import SearchItem from './search-item';
|
||||
import { ENTER_KEY_CODE } from '../constants/key-codes';
|
||||
@@ -129,7 +129,7 @@ class Search extends React.Component {
|
||||
const { nodes, pinnedSearches, searchFocused, searchMatchCountByTopology,
|
||||
isResourceViewMode, searchQuery, topologiesLoaded, inputId = 'search' } = this.props;
|
||||
const hidden = !topologiesLoaded || isResourceViewMode;
|
||||
const disabled = this.props.isTopologyEmpty && !hidden;
|
||||
const disabled = this.props.isTopologyNodeCountZero && !hidden;
|
||||
const matchCount = searchMatchCountByTopology
|
||||
.reduce((count, topologyMatchCount) => count + topologyMatchCount, 0);
|
||||
const showPinnedSearches = pinnedSearches.size > 0;
|
||||
@@ -180,7 +180,7 @@ export default connect(
|
||||
nodes: state.get('nodes'),
|
||||
topologyViewMode: state.get('topologyViewMode'),
|
||||
isResourceViewMode: isResourceViewModeSelector(state),
|
||||
isTopologyEmpty: isTopologyEmpty(state),
|
||||
isTopologyNodeCountZero: isTopologyNodeCountZero(state),
|
||||
currentTopology: state.get('currentTopology'),
|
||||
topologiesLoaded: state.get('topologiesLoaded'),
|
||||
pinnedSearches: state.get('pinnedSearches'),
|
||||
|
||||
@@ -5,7 +5,6 @@ import classNames from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import PauseButton from './pause-button';
|
||||
import TimeTravelTimestamp from './time-travel-timestamp';
|
||||
import { trackMixpanelEvent } from '../utils/tracking-utils';
|
||||
import {
|
||||
@@ -260,7 +259,6 @@ class TimeTravel extends React.Component {
|
||||
selected={showSliderPanel}
|
||||
/>
|
||||
{!isCurrent && this.renderJumpToNowButton()}
|
||||
<PauseButton />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -271,7 +269,7 @@ function mapStateToProps({ scope, root }, { params }) {
|
||||
const cloudInstance = root.instances[params.orgId] || {};
|
||||
const featureFlags = cloudInstance.featureFlags || [];
|
||||
return {
|
||||
hasTimeTravel: featureFlags.includes('timeline-control'),
|
||||
hasTimeTravel: featureFlags.includes('time-travel'),
|
||||
websocketTransitioning: scope.get('websocketTransitioning'),
|
||||
topologyViewMode: scope.get('topologyViewMode'),
|
||||
currentTopology: scope.get('currentTopology'),
|
||||
|
||||
@@ -16,7 +16,8 @@ describe('RootReducer', () => {
|
||||
// TODO maybe extract those to topology-utils tests?
|
||||
const activeTopologyOptionsSelector = topologySelectors.activeTopologyOptionsSelector;
|
||||
const getAdjacentNodes = topologyUtils.getAdjacentNodes;
|
||||
const isTopologyEmpty = topologyUtils.isTopologyEmpty;
|
||||
const isNodesDisplayEmpty = topologyUtils.isNodesDisplayEmpty;
|
||||
const isTopologyNodeCountZero = topologyUtils.isTopologyNodeCountZero;
|
||||
const getUrlState = require('../../utils/router-utils').getUrlState;
|
||||
|
||||
// fixtures
|
||||
@@ -534,24 +535,36 @@ describe('RootReducer', () => {
|
||||
|
||||
// empty topology
|
||||
|
||||
it('detects that the topology is empty', () => {
|
||||
it('detects that the nodes display is empty', () => {
|
||||
let nextState = initialState;
|
||||
nextState = reducer(nextState, ReceiveTopologiesAction);
|
||||
nextState = reducer(nextState, ClickTopologyAction);
|
||||
expect(isTopologyEmpty(nextState)).toBeTruthy();
|
||||
expect(isNodesDisplayEmpty(nextState)).toBeTruthy();
|
||||
|
||||
nextState = reducer(nextState, ReceiveNodesDeltaAction);
|
||||
expect(isTopologyEmpty(nextState)).toBeFalsy();
|
||||
expect(isNodesDisplayEmpty(nextState)).toBeFalsy();
|
||||
|
||||
nextState = reducer(nextState, ClickTopology2Action);
|
||||
expect(isNodesDisplayEmpty(nextState)).toBeTruthy();
|
||||
|
||||
nextState = reducer(nextState, ReceiveNodesDeltaAction);
|
||||
expect(isTopologyEmpty(nextState)).toBeTruthy();
|
||||
expect(isNodesDisplayEmpty(nextState)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('detects that the topo stats are empty', () => {
|
||||
let nextState = initialState;
|
||||
nextState = reducer(nextState, ReceiveTopologiesAction);
|
||||
nextState = reducer(nextState, ClickTopologyAction);
|
||||
expect(isTopologyNodeCountZero(nextState)).toBeFalsy();
|
||||
|
||||
nextState = reducer(nextState, ReceiveNodesDeltaAction);
|
||||
expect(isTopologyNodeCountZero(nextState)).toBeFalsy();
|
||||
|
||||
nextState = reducer(nextState, ClickTopology2Action);
|
||||
expect(isTopologyNodeCountZero(nextState)).toBeTruthy();
|
||||
|
||||
nextState = reducer(nextState, ClickTopologyAction);
|
||||
expect(isTopologyEmpty(nextState)).toBeTruthy();
|
||||
|
||||
nextState = reducer(nextState, ReceiveNodesDeltaAction);
|
||||
expect(isTopologyEmpty(nextState)).toBeFalsy();
|
||||
expect(isTopologyNodeCountZero(nextState)).toBeFalsy();
|
||||
});
|
||||
|
||||
// selection of relatives
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Set as makeSet, List as makeList } from 'immutable';
|
||||
import { isWebsocketQueryingCurrentSelector } from '../selectors/time-travel';
|
||||
import { isResourceViewModeSelector } from '../selectors/topology';
|
||||
import { pinnedMetricSelector } from '../selectors/node-metric';
|
||||
import { shownNodesSelector } from '../selectors/node-filters';
|
||||
|
||||
//
|
||||
// top priority first
|
||||
@@ -145,11 +146,7 @@ export function isNodesDisplayEmpty(state) {
|
||||
return !pinnedMetricSelector(state);
|
||||
}
|
||||
// Otherwise (in graph and table view), we only look at the nodes content.
|
||||
return state.get('nodes').isEmpty();
|
||||
}
|
||||
|
||||
export function isTopologyEmpty(state) {
|
||||
return isTopologyNodeCountZero(state) || isNodesDisplayEmpty(state);
|
||||
return shownNodesSelector(state).isEmpty();
|
||||
}
|
||||
|
||||
export function getAdjacentNodes(state, originNodeId) {
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
&-label {
|
||||
&-label, .pause-text {
|
||||
text-transform: uppercase;
|
||||
margin: 0 0.25em;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "weave-scope",
|
||||
"version": "1.4.0",
|
||||
"version": "1.5.0",
|
||||
"description": "SPA JS app for Weave Scope visualising the application network.",
|
||||
"repository": "weaveworks/scope",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
+4
-4
@@ -125,7 +125,7 @@ After it’s been launched, open your browser to `http://localhost:4040`.
|
||||
**Docker Compose Format Version 1:**
|
||||
|
||||
scope:
|
||||
image: weaveworks/scope:1.3.0
|
||||
image: weaveworks/scope:1.5.0
|
||||
net: "host"
|
||||
pid: "host"
|
||||
privileged: true
|
||||
@@ -141,7 +141,7 @@ After it’s been launched, open your browser to `http://localhost:4040`.
|
||||
version: '2'
|
||||
services:
|
||||
scope:
|
||||
image: weaveworks/scope:1.3.0
|
||||
image: weaveworks/scope:1.5.0
|
||||
network_mode: "host"
|
||||
pid: "host"
|
||||
privileged: true
|
||||
@@ -165,7 +165,7 @@ Version 2 of this YAML file supports networks and volumes as defined by any plug
|
||||
**Docker Compose Format Version 1:**
|
||||
|
||||
probe:
|
||||
image: weaveworks/scope:1.3.0
|
||||
image: weaveworks/scope:1.5.0
|
||||
net: "host"
|
||||
pid: "host"
|
||||
privileged: true
|
||||
@@ -182,7 +182,7 @@ Version 2 of this YAML file supports networks and volumes as defined by any plug
|
||||
version: '2'
|
||||
services:
|
||||
probe:
|
||||
image: weaveworks/scope:1.3.0
|
||||
image: weaveworks/scope:1.5.0
|
||||
network_mode: "host"
|
||||
pid: "host"
|
||||
privileged: true
|
||||
|
||||
Reference in New Issue
Block a user