conjur-asset-ui-beta 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. checksums.yaml +7 -0
  2. data/.git-hooks/pre_commit/trailing_whitespace.rb +26 -0
  3. data/.gitignore +23 -0
  4. data/.project +18 -0
  5. data/CHANGELOG.md +14 -0
  6. data/Gemfile +10 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +72 -0
  9. data/Rakefile +76 -0
  10. data/TODO.md +31 -0
  11. data/app/.csscomb.json +304 -0
  12. data/app/.jshintrc +46 -0
  13. data/app/build/css/bootstrap.css +6906 -0
  14. data/app/build/fonts/glyphicons-halflings-regular.eot +0 -0
  15. data/app/build/fonts/glyphicons-halflings-regular.svg +288 -0
  16. data/app/build/fonts/glyphicons-halflings-regular.ttf +0 -0
  17. data/app/build/fonts/glyphicons-halflings-regular.woff +0 -0
  18. data/app/build/fonts/glyphicons-halflings-regular.woff2 +0 -0
  19. data/app/build/images/conjur-logo.svg +26 -0
  20. data/app/build/images/icon-client-pc.svg +12 -0
  21. data/app/build/images/icon-environment.png +0 -0
  22. data/app/build/images/icon-person.svg +12 -0
  23. data/app/build/images/icon-policy.png +0 -0
  24. data/app/build/images/icon-resource.png +0 -0
  25. data/app/build/images/icon-service-dots.svg +13 -0
  26. data/app/build/images/icon-variable.png +0 -0
  27. data/app/build/index.html +26 -0
  28. data/app/build/js/app.js +78070 -0
  29. data/app/build/js/pace.js +2 -0
  30. data/app/config/preprocessor.js +9 -0
  31. data/app/config/webpack.js +84 -0
  32. data/app/gulpfile.js +144 -0
  33. data/app/package.json +83 -0
  34. data/app/src/actions.js +493 -0
  35. data/app/src/app.js +76 -0
  36. data/app/src/clients/audit.js +54 -0
  37. data/app/src/clients/generic.js +87 -0
  38. data/app/src/clients/layer_members.js +36 -0
  39. data/app/src/clients/list.js +82 -0
  40. data/app/src/clients/members.js +37 -0
  41. data/app/src/clients/search.js +19 -0
  42. data/app/src/components/app/__tests__/app-test.js +22 -0
  43. data/app/src/components/app/app.js +66 -0
  44. data/app/src/components/audit/__tests__/table_header-test.js +40 -0
  45. data/app/src/components/audit/box.js +11 -0
  46. data/app/src/components/audit/constants.js +7 -0
  47. data/app/src/components/audit/entry.js +107 -0
  48. data/app/src/components/audit/fields_mixin.js +13 -0
  49. data/app/src/components/audit/humanize_event.js +216 -0
  50. data/app/src/components/audit/table.js +100 -0
  51. data/app/src/components/audit/table_header.js +38 -0
  52. data/app/src/components/audit/timestamp.js +30 -0
  53. data/app/src/components/chart/chart.js +539 -0
  54. data/app/src/components/chart/chart_helper_mixin.js +79 -0
  55. data/app/src/components/custom/list.js +5 -0
  56. data/app/src/components/custom/view.js +71 -0
  57. data/app/src/components/dashboard/activity.js +113 -0
  58. data/app/src/components/dashboard/dashboard.js +47 -0
  59. data/app/src/components/flash/flash.js +17 -0
  60. data/app/src/components/generic/__tests__/time-test.js +43 -0
  61. data/app/src/components/generic/annotations.js +41 -0
  62. data/app/src/components/generic/breadcrumbs.js +59 -0
  63. data/app/src/components/generic/foldable_audit_section.js +252 -0
  64. data/app/src/components/generic/list.js +144 -0
  65. data/app/src/components/generic/list_factory.js +42 -0
  66. data/app/src/components/generic/resource_link.js +65 -0
  67. data/app/src/components/generic/role_link.js +65 -0
  68. data/app/src/components/generic/tab_mixin.js +148 -0
  69. data/app/src/components/generic/time.js +34 -0
  70. data/app/src/components/group/list.js +5 -0
  71. data/app/src/components/group/view.js +137 -0
  72. data/app/src/components/host/activity.js +93 -0
  73. data/app/src/components/host/details.js +30 -0
  74. data/app/src/components/host/host_link.js +20 -0
  75. data/app/src/components/host/list.js +5 -0
  76. data/app/src/components/host/view.js +113 -0
  77. data/app/src/components/layer/list.js +5 -0
  78. data/app/src/components/layer/view.js +180 -0
  79. data/app/src/components/navbar/__tests__/navbar-test.js +21 -0
  80. data/app/src/components/navbar/nav_search_form.js +41 -0
  81. data/app/src/components/navbar/navbar.js +71 -0
  82. data/app/src/components/owned_resources/owned_resources.js +86 -0
  83. data/app/src/components/owned_resources/owned_resources_box.js +106 -0
  84. data/app/src/components/permissions/permissions.js +143 -0
  85. data/app/src/components/permissions/permissions_table.js +104 -0
  86. data/app/src/components/policy/list.js +5 -0
  87. data/app/src/components/policy/view.js +98 -0
  88. data/app/src/components/refresh/refresh.js +30 -0
  89. data/app/src/components/refresh/refresh.less +15 -0
  90. data/app/src/components/search/group.js +45 -0
  91. data/app/src/components/search/group_heading.js +50 -0
  92. data/app/src/components/search/group_title.js +38 -0
  93. data/app/src/components/search/result_item.js +57 -0
  94. data/app/src/components/search/search.js +103 -0
  95. data/app/src/components/user/activity.js +92 -0
  96. data/app/src/components/user/details.js +30 -0
  97. data/app/src/components/user/list.js +5 -0
  98. data/app/src/components/user/pubkeys.js +116 -0
  99. data/app/src/components/user/pubkeys.less +56 -0
  100. data/app/src/components/user/view.js +123 -0
  101. data/app/src/components/variable/activity.js +83 -0
  102. data/app/src/components/variable/details.js +48 -0
  103. data/app/src/components/variable/fetchers.js +83 -0
  104. data/app/src/components/variable/list.js +5 -0
  105. data/app/src/components/variable/updaters.js +83 -0
  106. data/app/src/components/variable/view.js +105 -0
  107. data/app/src/constants.js +35 -0
  108. data/app/src/images/conjur-logo.svg +26 -0
  109. data/app/src/images/icon-client-pc.svg +12 -0
  110. data/app/src/images/icon-environment.png +0 -0
  111. data/app/src/images/icon-person.svg +12 -0
  112. data/app/src/images/icon-policy.png +0 -0
  113. data/app/src/images/icon-resource.png +0 -0
  114. data/app/src/images/icon-service-dots.svg +13 -0
  115. data/app/src/images/icon-variable.png +0 -0
  116. data/app/src/pages/index.html +26 -0
  117. data/app/src/routes.js +57 -0
  118. data/app/src/stores/app_store.js +29 -0
  119. data/app/src/stores/audit_store.js +77 -0
  120. data/app/src/stores/group_store.js +105 -0
  121. data/app/src/stores/host_store.js +98 -0
  122. data/app/src/stores/layer_store.js +115 -0
  123. data/app/src/stores/policy_store.js +89 -0
  124. data/app/src/stores/resources_store.js +118 -0
  125. data/app/src/stores/route_store.js +24 -0
  126. data/app/src/stores/search_store.js +73 -0
  127. data/app/src/stores/user_store.js +111 -0
  128. data/app/src/stores/variable_store.js +94 -0
  129. data/app/src/styles/bootstrap.less +56 -0
  130. data/app/src/styles/styles.less +634 -0
  131. data/app/src/utils.js +43 -0
  132. data/app/src/vendor/pace.js +2 -0
  133. data/conjur-asset-ui.gemspec +36 -0
  134. data/features/navigation_bar.feature +31 -0
  135. data/features/step_definitions/custom_step.rb +32 -0
  136. data/features/support/env.rb +38 -0
  137. data/features/support/hooks.rb +30 -0
  138. data/features/support/world.rb +17 -0
  139. data/lib/conjur-asset-ui-version.rb +7 -0
  140. data/lib/conjur-asset-ui.rb +7 -0
  141. data/lib/conjur/command/ui.rb +54 -0
  142. data/lib/conjur/webserver/api_proxy.rb +94 -0
  143. data/lib/conjur/webserver/authorize.rb +28 -0
  144. data/lib/conjur/webserver/conjur_info.rb +33 -0
  145. data/lib/conjur/webserver/home.rb +42 -0
  146. data/lib/conjur/webserver/login.rb +57 -0
  147. data/lib/conjur/webserver/renderer.rb +34 -0
  148. data/lib/conjur/webserver/server.rb +130 -0
  149. data/public/js/views/roleGraph.js +91 -0
  150. metadata +373 -0
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ var React = require('react'),
4
+ map = require('lodash/collection/map');
5
+
6
+ var FieldsMixin = require('./fields_mixin');
7
+
8
+ module.exports = React.createClass({
9
+ displayName: 'AuditTableHeader',
10
+
11
+ mixins: [FieldsMixin],
12
+
13
+ propTypes: {
14
+ compact: React.PropTypes.bool
15
+ },
16
+
17
+ getDefaultProps() {
18
+ return {
19
+ compact: false
20
+ };
21
+ },
22
+
23
+ render() {
24
+ var items = map(this.fields(), (field) => {
25
+ return (
26
+ <th key={field}>{field.replace('auditview_', '').replace('_', ' ')}</th>
27
+ );
28
+ });
29
+
30
+ return (
31
+ <thead>
32
+ <tr>
33
+ {items}
34
+ </tr>
35
+ </thead>
36
+ );
37
+ }
38
+ });
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ var React = require('react'),
4
+ moment = require('moment');
5
+
6
+ module.exports = React.createClass({
7
+ displayName: 'AuditTimestamp',
8
+
9
+ propTypes: {
10
+ time: React.PropTypes.string
11
+ },
12
+
13
+ getDefaultProps() {
14
+ return {
15
+ time: '1970-01-01T00:00:00.000Z'
16
+ };
17
+ },
18
+
19
+ render() {
20
+ var ts = moment(this.props.time);
21
+
22
+ return (
23
+ <time className="timestamp"
24
+ dateTime={ts.format()}
25
+ title={ts.calendar()}>
26
+ {ts.fromNow()}
27
+ </time>
28
+ );
29
+ }
30
+ });
@@ -0,0 +1,539 @@
1
+ 'use strict';
2
+
3
+ var React = require('react'),
4
+ d3 = require('d3');
5
+
6
+ var merge = require('lodash/object/merge');
7
+
8
+ var bisector = d3.bisector(function(d) { return d.date; }).left,
9
+ oneDay = 86400000;
10
+
11
+ function _translate(x, y) {
12
+ return 'translate(' + x + ',' + y + ')';
13
+ }
14
+
15
+ module.exports = React.createClass({
16
+ displayName: 'Chart',
17
+
18
+ getInitialState() {
19
+ return {ready: false};
20
+ },
21
+
22
+ getDefaultProps() {
23
+ return {
24
+ defaultOptions: {
25
+ legend: {},
26
+ axis: {
27
+ x: {
28
+ label: 'X axis'
29
+ },
30
+ y: {
31
+ label: 'Y axis'
32
+ }
33
+ },
34
+ margin: {
35
+ top: 20,
36
+ right: 145,
37
+ bottom: 30,
38
+ left: 50
39
+ }
40
+ }
41
+ };
42
+ },
43
+
44
+ el: undefined,
45
+ svg: undefined,
46
+
47
+ componentDidMount() {
48
+ // should be better way to do this
49
+ this.el = this.getDOMNode();
50
+ this.svg = {};
51
+
52
+ this._calculateInitialState(this.props);
53
+ },
54
+
55
+ componentWillReceiveProps(nextProps) {
56
+ this._calculateInitialState(nextProps);
57
+ },
58
+
59
+ componentDidUpdate() {
60
+ if (this.state.ready === false) {
61
+ return;
62
+ }
63
+
64
+ if (typeof this.svg.svg === 'undefined') {
65
+ this._create();
66
+ }
67
+
68
+ this._update();
69
+
70
+ return;
71
+ },
72
+
73
+ componentWillUnmount() {
74
+ this.el = undefined;
75
+ this.svg = undefined;
76
+ },
77
+
78
+ render() {
79
+ return (
80
+ <div className="b-chart"></div>
81
+ );
82
+ },
83
+
84
+ _getHeight(top, bottom) {
85
+ var height;
86
+
87
+ if (this.el.offsetHeight) {
88
+ height = this.el.offsetHeight;
89
+ } else if (this.el.style.pixelHeight) {
90
+ height = this.el.style.pixelHeight;
91
+ }
92
+
93
+ height = window.parseFloat(height) -
94
+ top - bottom;
95
+
96
+ return height;
97
+ },
98
+
99
+ _getWidth(left, right) {
100
+ var width;
101
+
102
+ if (this.el.offsetWidth) {
103
+ width = this.el.offsetWidth;
104
+ } else if (this.el.style.pixelWidth) {
105
+ width = this.el.style.pixelWidth;
106
+ }
107
+
108
+ width = window.parseFloat(width) -
109
+ left - right;
110
+
111
+ return width;
112
+ },
113
+
114
+ _create() {
115
+ var svg = d3.select(this.el)
116
+ .append('svg')
117
+ .attr('class', 'd3-line-chart')
118
+ .attr('width', this.state.fullWidth)
119
+ .attr('height', this.state.fullHeight)
120
+ .append('g')
121
+ .attr('class', 'd3-line-chart__inner')
122
+ .attr('transform', _translate(this.state.margin.left, this.state.margin.top));
123
+
124
+ this.svg.svg = svg;
125
+
126
+ this.svg.axis = svg.append('g')
127
+ .attr('class', 'd3-axis');
128
+
129
+ this.svg.axis.append('g')
130
+ .attr('class', 'd3-axis__x');
131
+
132
+ this.svg.axis.append('g')
133
+ .attr('class', 'd3-axis__y');
134
+
135
+ this.svg.legend = svg.append('g')
136
+ .attr('class', 'd3-legend');
137
+
138
+ this.svg.graph = svg.append('g')
139
+ .attr('class', 'd3-graph');
140
+
141
+ this.svg.focus = this.svg.graph.append('g')
142
+ .attr('class', 'd3-graph__focus');
143
+ },
144
+
145
+ _update() {
146
+ this._drawAxisX();
147
+ this._drawAxisY();
148
+ this._drawLegend();
149
+ this._drawLine();
150
+ this._drawFocus();
151
+ },
152
+
153
+ _calculateInitialState(props) {
154
+ var options = merge({}, props.options,
155
+ props.defaultOptions);
156
+
157
+ var width = this._getWidth(options.margin.left,
158
+ options.margin.right),
159
+ height = this._getHeight(options.margin.top,
160
+ options.margin.bottom),
161
+ fullWidth = (
162
+ width +
163
+ options.margin.left +
164
+ options.margin.right
165
+ ),
166
+ fullHeight = (
167
+ height +
168
+ options.margin.top +
169
+ options.margin.bottom
170
+ );
171
+
172
+ var color = d3.scale.category10();
173
+
174
+ color.domain(d3.keys(options.legend)
175
+ .filter(function(key) {
176
+ return key !== 'date';
177
+ }));
178
+
179
+ var data = color.domain().map(function(name) {
180
+ return {
181
+ name: name,
182
+ values: props.data.map(function(d) {
183
+ return {date: d.date, value: +d[name]};
184
+ })
185
+ };
186
+ });
187
+
188
+ var xExtent = d3.extent(props.data, function(d) {
189
+ return d.date;
190
+ });
191
+
192
+ var currentDate = (new Date()).getTime();
193
+
194
+ var xMin = xExtent[0] || new Date(currentDate - oneDay),
195
+ xMax = xExtent[1] || new Date(currentDate);
196
+
197
+ var yMin = d3.min(data, function(c) {
198
+ return d3.min(c.values, function(v) {
199
+ return v.value;
200
+ });
201
+ }) || 0;
202
+
203
+ if (yMin < 1) {
204
+ yMin = 0;
205
+ }
206
+
207
+ var yMax = d3.max(data, function(c) {
208
+ return d3.max(c.values, function(v) {
209
+ return v.value;
210
+ });
211
+ }) || 4;
212
+
213
+ yMax = yMax + 1;
214
+
215
+ if (yMax < 5) {
216
+ yMax = 5;
217
+ }
218
+
219
+ var x = this._getScaleX('time')
220
+ .range([0, width])
221
+ .domain([xMin, xMax]),
222
+
223
+ y = this._getScaleX('linear')
224
+ .range([height, 0])
225
+ .domain([yMin, yMax]),
226
+
227
+ legendScale = d3.scale.ordinal()
228
+ .domain([])
229
+ .range([0, 20, 40, 60, 80, 100, 120, 140]),
230
+
231
+ scales = {x: x, y: y, yMax: yMax, legend: legendScale};
232
+
233
+ var line = this._getLine(scales);
234
+
235
+ this.setState({
236
+ ready: true,
237
+ legend: options.legend,
238
+ axis: options.axis,
239
+ margin: options.margin,
240
+ width: width,
241
+ height: height,
242
+ fullWidth: fullWidth,
243
+ fullHeight: fullHeight,
244
+ color: color,
245
+ scales: scales,
246
+ lineStencil: line,
247
+ data: data
248
+ });
249
+ },
250
+
251
+ _getScaleX(type) {
252
+ if (type === 'linear') {
253
+ return d3.scale.linear();
254
+ } else if (type === 'time') {
255
+ return d3.time.scale();
256
+ }
257
+
258
+ return null;
259
+ },
260
+
261
+ _getLine(scales) {
262
+ return d3.svg.line()
263
+ .x(function(d) {
264
+ return scales.x(d.date);
265
+ })
266
+ .y(function(d) {
267
+ return scales.y(d.value);
268
+ });
269
+ },
270
+
271
+ _drawAxisX() {
272
+ var xAxis = d3.svg.axis()
273
+ .scale(this.state.scales.x)
274
+ .orient('bottom');
275
+
276
+ this.svg.axis.select('.d3-axis__x')
277
+ .attr('transform', _translate(0, this.state.height))
278
+ .call(xAxis);
279
+ },
280
+
281
+ _drawAxisY() {
282
+ var yAxis = d3.svg.axis()
283
+ .scale(this.state.scales.y)
284
+ .orient('left')
285
+ .ticks(this.state.scales.yMax > 10 ? 10 : this.state.scales.yMax)
286
+ .tickFormat(d3.format('d'))
287
+ .tickSubdivide(0);
288
+
289
+ var axis = this.svg.axis.select('.d3-axis__y')
290
+ .call(yAxis);
291
+
292
+ if (typeof(this.state.yAxisLabel) === 'string') {
293
+ this._drawAxisLabel(axis, this.state.yAxisLabel, true);
294
+ }
295
+ },
296
+
297
+ _drawAxisLabel(axis, text, rotate) {
298
+ var label = axis.append('text');
299
+
300
+ if (typeof(rotate) === 'boolean' && rotate === true) {
301
+ label.attr('transform', 'rotate(-90)')
302
+ .attr('y', 0 - this.state.margin.left)
303
+ .attr('x', 0 - (this.state.height / 2))
304
+ .attr('dy', '1em')
305
+ .style('text-anchor', 'middle')
306
+ .text(text);
307
+ } else {
308
+ label.attr('transform',
309
+ _translate(this.state.width / 2,
310
+ this.state.height + this.state.margin.bottom))
311
+ .style('text-anchor', 'middle')
312
+ .text(text);
313
+ }
314
+ },
315
+
316
+ _drawLegend() {
317
+ var self = this,
318
+ legend,
319
+ enter;
320
+
321
+ legend = this.svg.legend
322
+ .selectAll('.d3-legend__item')
323
+ .data(d3.keys(this.state.legend));
324
+
325
+ enter = legend
326
+ .enter()
327
+ .append('g')
328
+ .style('opacity', 0.9)
329
+ .attr('class', 'd3-legend__item')
330
+ .attr('data-graph-line-id', function(d) {
331
+ return 'd3-legend__item--' + d;
332
+ })
333
+ .on('mouseover', function(d) {
334
+ self._handleLegendMouseover(this, d);
335
+ })
336
+ .on('mouseout', function(d) {
337
+ self._handleLegendMouseout(this, d);
338
+ })
339
+ .on('click', function(d) {
340
+ self._handleLegendClick(this, d);
341
+ });
342
+
343
+ enter.append('circle')
344
+ .attr('cx', this.state.width + 30)
345
+ .attr('cy', function(d, i) {
346
+ return self.state.scales.legend(i) - 3.1;
347
+ })
348
+ .attr('r', 5.5)
349
+ .style('fill', function(d) {
350
+ return self.state.color(d);
351
+ });
352
+
353
+ enter.append('text')
354
+ .attr('x', this.state.width + 40)
355
+ .attr('y', function(d, i) {
356
+ return self.state.scales.legend(i);
357
+ })
358
+ .text(function(d) {
359
+ return self.state.legend[d];
360
+ });
361
+ },
362
+
363
+ _handleLegendMouseover(self) {
364
+ var opacity = parseFloat(self.style.opacity);
365
+
366
+ if (opacity >= 0.9) {
367
+ d3.select(self)
368
+ .style('opacity', 1);
369
+ } else {
370
+ d3.select(self)
371
+ .style('opacity', 0.5);
372
+ }
373
+ },
374
+
375
+ _handleLegendMouseout(self) {
376
+ var opacity = parseFloat(self.style.opacity);
377
+
378
+ if (opacity >= 0.9) {
379
+ d3.select(self)
380
+ .style('opacity', 0.9);
381
+ } else {
382
+ d3.select(self)
383
+ .style('opacity', 0.2);
384
+ }
385
+ },
386
+
387
+ _handleLegendClick(self) {
388
+ var line = document.getElementById(self.getAttribute('data-graph-line-id'));
389
+
390
+ if (line === null) {
391
+ return;
392
+ }
393
+
394
+ if (line.style.opacity !== '0') {
395
+ d3.select(line).transition()
396
+ .duration(500)
397
+ .style('opacity', 0);
398
+
399
+ d3.select(self).transition()
400
+ .duration(500)
401
+ .style('opacity', 0.2);
402
+ } else {
403
+ d3.select(line).transition()
404
+ .duration(500)
405
+ .style('opacity', 1);
406
+
407
+ d3.select(self).transition()
408
+ .duration(500)
409
+ .style('opacity', 0.9);
410
+ }
411
+ },
412
+
413
+ _drawLine() {
414
+ var self = this,
415
+ line;
416
+
417
+ this.svg.graph.selectAll('.d3-graph__line')
418
+ .remove();
419
+
420
+ line = this.svg.graph.selectAll('.d3-graph__line')
421
+ .data(this.state.data)
422
+ .enter()
423
+ .append('g')
424
+ .attr('class', 'd3-graph__line')
425
+ .attr('id', function(d) {
426
+ return 'd3-legend__item--' + d.name;
427
+ });
428
+
429
+ line.append('path')
430
+ .attr('class', 'line')
431
+ .attr('d', function(d) {
432
+ return self.state.lineStencil(d.values);
433
+ })
434
+ .style('stroke', function(d) {
435
+ return self.state.color(d.name);
436
+ });
437
+ },
438
+
439
+ _drawFocus() {
440
+ var self = this;
441
+
442
+ var circle = this.svg.focus.selectAll('.d3-graph__focus-circle')
443
+ .data(this.state.data)
444
+ .enter()
445
+ .append('g')
446
+ .attr('class', 'd3-graph__focus-circle')
447
+ .attr('id', function(d) {
448
+ return 'd3-graph__focus-circle--' + d.name;
449
+ })
450
+ .style('display', 'none');
451
+
452
+ circle.append('circle')
453
+ .attr('r', 4.5);
454
+
455
+ circle.append('text')
456
+ .attr('x', 9)
457
+ .attr('dy', '.35em');
458
+
459
+ this.svg.focus.selectAll('.d3-graph__focus-line')
460
+ .data([1])
461
+ .enter()
462
+ .append('line')
463
+ .attr('class', 'd3-graph__focus-line')
464
+ .attr('x1', 0)
465
+ .attr('y1', 0)
466
+ .attr('x2', 0)
467
+ .attr('y2', this.state.height)
468
+ .style('display', 'none')
469
+ .style('stroke', 'gray')
470
+ .style('stroke-width', 0.5)
471
+ .style('stroke-dasharray', '5,10');
472
+
473
+ this.svg.graph.selectAll('.d3-graph__overlay')
474
+ .data([1])
475
+ .enter()
476
+ .append('rect')
477
+ .attr('class', 'd3-graph__overlay')
478
+ .attr('width', this.state.width)
479
+ .attr('height', this.state.height)
480
+ .on('mouseover', function() {
481
+ self.svg.focus.selectAll('.d3-graph__focus-line')
482
+ .style('display', null);
483
+ })
484
+ .on('mouseout', function() {
485
+ self.svg.focus.selectAll('.d3-graph__focus-line')
486
+ .style('display', 'none');
487
+
488
+ self.svg.focus.selectAll('.d3-graph__focus-circle')
489
+ .style('display', 'none');
490
+ })
491
+ .on('mousemove', function() {
492
+ self._handleFocusMousemove(this);
493
+ });
494
+ },
495
+
496
+ _handleFocusMousemove(overlay) {
497
+ var self = this,
498
+ mouse = d3.mouse(overlay)[0],
499
+ x0 = this.state.scales.x.invert(mouse);
500
+
501
+ var bisectData = function(data) {
502
+ var i = bisector(data.values, x0, 1),
503
+ d0 = data.values[i - 1],
504
+ d1 = data.values[i],
505
+ ret = {name: data.name, data: {value: []}};
506
+
507
+ if (typeof d0 === 'object' || typeof d1 === 'object') {
508
+ var d = x0 - d0.date > d1.date - x0 ? d1 : d0;
509
+
510
+ ret.data = d;
511
+ }
512
+
513
+ return ret;
514
+ };
515
+
516
+ this.svg.focus.selectAll('.d3-graph__focus-line')
517
+ .attr('transform', _translate(this.state.scales.x(x0), 0));
518
+
519
+ this.state.data.map(function(e) {
520
+ return bisectData(e);
521
+ }).map(function(d) {
522
+ var circle = document.getElementById('d3-graph__focus-circle--' + d.name),
523
+ line = document.getElementById('d3-legend__item--' + d.name);
524
+
525
+ if (d.data.value > 0 && line.style.opacity !== '0') {
526
+ d3.select(circle)
527
+ .style('display', null)
528
+ .attr('transform',
529
+ _translate(self.state.scales.x(d.data.date),
530
+ self.state.scales.y(d.data.value)))
531
+
532
+ .select('text').text(d3.format(',.2f')(d.data.value));
533
+ } else {
534
+ d3.select(circle)
535
+ .style('display', 'none');
536
+ }
537
+ });
538
+ }
539
+ });