debug-toolbar.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. /* eslint react/jsx-no-bind: "off" */
  2. import React from 'react';
  3. import { connect } from 'react-redux';
  4. import {
  5. sampleSize, sample, random, range, flattenDeep, times
  6. } from 'lodash';
  7. import { fromJS, Set as makeSet } from 'immutable';
  8. import { hsl } from 'd3-color';
  9. import debug from 'debug';
  10. import ActionTypes from '../constants/action-types';
  11. import { receiveNodesDelta } from '../actions/app-actions';
  12. import { getNodeColor, getNodeColorDark, text2degree } from '../utils/color-utils';
  13. import { availableMetricsSelector } from '../selectors/node-metric';
  14. const SHAPES = ['square', 'hexagon', 'heptagon', 'circle'];
  15. const STACK_VARIANTS = [false, true];
  16. const METRIC_FILLS = [0, 0.1, 50, 99.9, 100];
  17. const NETWORKS = [
  18. 'be', 'fe', 'zb', 'db', 're', 'gh', 'jk', 'lol', 'nw'
  19. ].map(n => ({ colorKey: n, id: n, label: n }));
  20. const INTERNET = 'the-internet';
  21. const LOREM = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
  22. incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
  23. ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
  24. voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
  25. proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`;
  26. const sampleArray = (collection, n = 4) => sampleSize(collection, random(n));
  27. const log = debug('scope:debug-panel');
  28. const shapeTypes = {
  29. circle: ['Host', 'Hosts'],
  30. heptagon: ['Pod', 'Pods'],
  31. hexagon: ['Container', 'Containers'],
  32. square: ['Process', 'Processes']
  33. };
  34. const LABEL_PREFIXES = range('A'.charCodeAt(), 'Z'.charCodeAt() + 1)
  35. .map(n => String.fromCharCode(n));
  36. const deltaAdd = (name, adjacency = [], shape = 'circle', stack = false, networks = NETWORKS) => ({
  37. adjacency,
  38. controls: {},
  39. id: name,
  40. label: name,
  41. labelMinor: name,
  42. latest: {},
  43. networks,
  44. origins: [],
  45. rank: name,
  46. shape,
  47. stack
  48. });
  49. function addMetrics(availableMetrics, node, v) {
  50. const metrics = availableMetrics.size > 0 ? availableMetrics : fromJS([
  51. { id: 'host_cpu_usage_percent', label: 'CPU' }
  52. ]);
  53. return Object.assign({}, node, {
  54. metrics: metrics.map(m => Object.assign({}, m, {
  55. id: 'zing', label: 'zing', max: 100, value: v
  56. })).toJS()
  57. });
  58. }
  59. function label(shape, stacked) {
  60. const type = shapeTypes[shape];
  61. return stacked ? `Group of ${type[1]}` : type[0];
  62. }
  63. function addAllVariants(dispatch) {
  64. const newNodes = flattenDeep(STACK_VARIANTS.map(stack => (SHAPES.map((s) => {
  65. if (!stack) return [deltaAdd(label(s, stack), [], s, stack)];
  66. return times(3).map(() => deltaAdd(label(s, stack), [], s, stack));
  67. }))));
  68. dispatch(receiveNodesDelta({
  69. add: newNodes
  70. }));
  71. }
  72. function addAllMetricVariants(availableMetrics) {
  73. const newNodes = flattenDeep(METRIC_FILLS.map((v, i) => (
  74. SHAPES.map(s => [addMetrics(availableMetrics, deltaAdd(label(s) + i, [], s), v)])
  75. )));
  76. return (dispatch) => {
  77. dispatch(receiveNodesDelta({
  78. add: newNodes
  79. }));
  80. };
  81. }
  82. export function showingDebugToolbar() {
  83. return (('debugToolbar' in localStorage && JSON.parse(localStorage.debugToolbar))
  84. || window.location.pathname.indexOf('debug') > -1);
  85. }
  86. export function toggleDebugToolbar() {
  87. if ('debugToolbar' in localStorage) {
  88. localStorage.debugToolbar = !showingDebugToolbar();
  89. }
  90. }
  91. function enableLog(ns) {
  92. debug.enable(`scope:${ns}`);
  93. window.location.reload();
  94. }
  95. function disableLog() {
  96. debug.disable();
  97. window.location.reload();
  98. }
  99. function setAppState(fn) {
  100. return (dispatch) => {
  101. dispatch({
  102. fn,
  103. type: ActionTypes.DEBUG_TOOLBAR_INTERFERING
  104. });
  105. };
  106. }
  107. class DebugToolbar extends React.Component {
  108. constructor(props, context) {
  109. super(props, context);
  110. this.onChange = this.onChange.bind(this);
  111. this.toggleColors = this.toggleColors.bind(this);
  112. this.addNodes = this.addNodes.bind(this);
  113. this.intermittentTimer = null;
  114. this.intermittentNodes = makeSet();
  115. this.shortLivedTimer = null;
  116. this.shortLivedNodes = makeSet();
  117. this.state = {
  118. nodesToAdd: 30,
  119. showColors: false
  120. };
  121. }
  122. onChange(ev) {
  123. this.setState({ nodesToAdd: parseInt(ev.target.value, 10) });
  124. }
  125. toggleColors() {
  126. this.setState(prevState => ({
  127. showColors: !prevState.showColors
  128. }));
  129. }
  130. asyncDispatch(v) {
  131. setTimeout(() => this.props.dispatch(v), 0);
  132. }
  133. setLoading(loading) {
  134. this.asyncDispatch(setAppState(state => state.set('topologiesLoaded', !loading)));
  135. }
  136. setIntermittent() {
  137. // simulate epheremal nodes
  138. if (this.intermittentTimer) {
  139. clearInterval(this.intermittentTimer);
  140. this.intermittentTimer = null;
  141. } else {
  142. this.intermittentTimer = setInterval(() => {
  143. // add new node
  144. this.addNodes(1);
  145. // remove random node
  146. const ns = this.props.nodes;
  147. const nodeNames = ns.keySeq().toJS();
  148. const randomNode = sample(nodeNames);
  149. this.asyncDispatch(receiveNodesDelta({
  150. remove: [randomNode]
  151. }));
  152. }, 1000);
  153. }
  154. }
  155. setShortLived() {
  156. // simulate nodes with same ID popping in and out
  157. if (this.shortLivedTimer) {
  158. clearInterval(this.shortLivedTimer);
  159. this.shortLivedTimer = null;
  160. } else {
  161. this.shortLivedTimer = setInterval(() => {
  162. // filter random node
  163. const ns = this.props.nodes;
  164. const nodeNames = ns.keySeq().toJS();
  165. const randomNode = sample(nodeNames);
  166. if (randomNode) {
  167. let nextNodes = ns.setIn([randomNode, 'filtered'], true);
  168. this.shortLivedNodes = this.shortLivedNodes.add(randomNode);
  169. // bring nodes back after a bit
  170. if (this.shortLivedNodes.size > 5) {
  171. const returningNode = this.shortLivedNodes.first();
  172. this.shortLivedNodes = this.shortLivedNodes.rest();
  173. nextNodes = nextNodes.setIn([returningNode, 'filtered'], false);
  174. }
  175. this.asyncDispatch(setAppState(state => state.set('nodes', nextNodes)));
  176. }
  177. }, 1000);
  178. }
  179. }
  180. updateAdjacencies() {
  181. const ns = this.props.nodes;
  182. const nodeNames = ns.keySeq().toJS();
  183. this.asyncDispatch(receiveNodesDelta({
  184. add: this.createRandomNodes(7),
  185. remove: this.randomExistingNode(),
  186. update: sampleArray(nodeNames).map(n => ({
  187. adjacency: sampleArray(nodeNames),
  188. id: n,
  189. }), nodeNames.length),
  190. }));
  191. }
  192. createRandomNodes(n, prefix = 'zing') {
  193. const ns = this.props.nodes;
  194. const nodeNames = ns.keySeq().toJS();
  195. const newNodeNames = range(ns.size, ns.size + n).map(i => (
  196. // `${randomLetter()}${randomLetter()}-zing`
  197. `${prefix}${i}`
  198. ));
  199. const allNodes = nodeNames.concat(newNodeNames);
  200. return newNodeNames.map(name => deltaAdd(
  201. name,
  202. sampleArray(allNodes),
  203. sample(SHAPES),
  204. sample(STACK_VARIANTS),
  205. sampleArray(NETWORKS, 10)
  206. ));
  207. }
  208. addInternetNode() {
  209. setTimeout(() => {
  210. this.asyncDispatch(receiveNodesDelta({
  211. add: [{
  212. id: INTERNET, label: INTERNET, labelMinor: 'Outgoing packets', pseudo: true, shape: 'cloud'
  213. }]
  214. }));
  215. }, 0);
  216. }
  217. addNodes(n, prefix = 'zing') {
  218. setTimeout(() => {
  219. this.asyncDispatch(receiveNodesDelta({
  220. add: this.createRandomNodes(n, prefix)
  221. }));
  222. log('added nodes', n);
  223. }, 0);
  224. }
  225. randomExistingNode() {
  226. const ns = this.props.nodes;
  227. const nodeNames = ns.keySeq().toJS();
  228. return [nodeNames[random(nodeNames.length - 1)]];
  229. }
  230. removeNode() {
  231. this.asyncDispatch(receiveNodesDelta({
  232. remove: this.randomExistingNode()
  233. }));
  234. }
  235. render() {
  236. const { availableMetrics } = this.props;
  237. return (
  238. <div className="debug-panel">
  239. <div>
  240. <strong>Add nodes </strong>
  241. <button type="button" onClick={() => this.addNodes(1)}>+1</button>
  242. <button type="button" onClick={() => this.addNodes(10)}>+10</button>
  243. <input type="number" onChange={this.onChange} value={this.state.nodesToAdd} />
  244. <button type="button" onClick={() => this.addNodes(this.state.nodesToAdd)}>+</button>
  245. <button type="button" onClick={() => this.asyncDispatch(addAllVariants)}>
  246. Variants
  247. </button>
  248. <button
  249. type="button"
  250. onClick={() => this.asyncDispatch(addAllMetricVariants(availableMetrics))}>
  251. Metric Variants
  252. </button>
  253. <button type="button" onClick={() => this.addNodes(1, LOREM)}>Long name</button>
  254. <button type="button" onClick={() => this.addInternetNode()}>Internet</button>
  255. <button type="button" onClick={() => this.removeNode()}>Remove node</button>
  256. <button type="button" onClick={() => this.updateAdjacencies()}>Update adj.</button>
  257. </div>
  258. <div>
  259. <strong>Logging </strong>
  260. <button type="button" onClick={() => enableLog('*')}>scope:*</button>
  261. <button type="button" onClick={() => enableLog('dispatcher')}>scope:dispatcher</button>
  262. <button type="button" onClick={() => enableLog('app-key-press')}>
  263. scope:app-key-press
  264. </button>
  265. <button type="button" onClick={() => enableLog('terminal')}>scope:terminal</button>
  266. <button type="button" onClick={() => disableLog()}>Disable log</button>
  267. </div>
  268. <div>
  269. <strong>Colors </strong>
  270. <button type="button" onClick={this.toggleColors}>toggle</button>
  271. </div>
  272. {this.state.showColors
  273. && (
  274. <table>
  275. <tbody>
  276. {LABEL_PREFIXES.map(r => (
  277. <tr key={r}>
  278. <td
  279. title={`${r}`}
  280. style={{ backgroundColor: hsl(text2degree(r), 0.5, 0.5).toString() }} />
  281. </tr>
  282. ))}
  283. </tbody>
  284. </table>
  285. )}
  286. {this.state.showColors && [getNodeColor, getNodeColorDark].map(fn => (
  287. <table key={fn}>
  288. <tbody>
  289. {LABEL_PREFIXES.map(r => (
  290. <tr key={r}>
  291. {LABEL_PREFIXES.map(c => (
  292. <td key={c} title={`(${r}, ${c})`} style={{ backgroundColor: fn(r, c) }} />
  293. ))}
  294. </tr>
  295. ))}
  296. </tbody>
  297. </table>
  298. ))}
  299. <div>
  300. <strong>State </strong>
  301. <button type="button" onClick={() => this.setLoading(true)}>
  302. Set doing initial load
  303. </button>
  304. <button type="button" onClick={() => this.setLoading(false)}>Stop</button>
  305. </div>
  306. <div>
  307. <strong>Short-lived nodes </strong>
  308. <button type="button" onClick={() => this.setShortLived()}>
  309. Toggle short-lived nodes
  310. </button>
  311. <button type="button" onClick={() => this.setIntermittent()}>
  312. Toggle intermittent nodes
  313. </button>
  314. </div>
  315. </div>
  316. );
  317. }
  318. }
  319. function mapStateToProps(state) {
  320. return {
  321. availableMetrics: availableMetricsSelector(state),
  322. nodes: state.get('nodes'),
  323. };
  324. }
  325. export default connect(mapStateToProps)(DebugToolbar);