conjur-asset-ui-beta 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/TODO.md +0 -23
- data/app/build/js/app.js +72652 -50337
- data/app/package.json +2 -0
- data/app/src/actions.js +56 -3
- data/app/src/app.js +7 -3
- data/app/src/clients/graph.js +24 -0
- data/app/src/clients/members.js +3 -3
- data/app/src/components/audit/table.js +9 -0
- data/app/src/components/custom/view.js +26 -14
- data/app/src/components/dashboard/activity.js +24 -4
- data/app/src/components/generic/foldable_audit_section.js +17 -0
- data/app/src/components/generic/role_link.js +2 -1
- data/app/src/components/graph/graph.js +421 -0
- data/app/src/components/graph/graph.less +39 -0
- data/app/src/components/group/view.js +31 -14
- data/app/src/components/host/activity.js +24 -4
- data/app/src/components/host/executors.js +83 -0
- data/app/src/components/host/updaters.js +83 -0
- data/app/src/components/host/view.js +46 -14
- data/app/src/components/layer/view.js +30 -13
- data/app/src/components/policy/view.js +23 -14
- data/app/src/components/search/search.js +21 -7
- data/app/src/components/user/activity.js +25 -4
- data/app/src/components/user/view.js +30 -13
- data/app/src/components/variable/activity.js +25 -4
- data/app/src/components/variable/fetchers.js +1 -1
- data/app/src/components/variable/updaters.js +1 -1
- data/app/src/components/variable/view.js +24 -13
- data/app/src/constants.js +4 -2
- data/app/src/stores/graph_store.js +55 -0
- data/app/src/stores/host_store.js +12 -1
- data/app/src/stores/route_store.js +7 -5
- data/app/src/stores/search_store.js +11 -6
- data/conjur-asset-ui.gemspec +1 -1
- data/lib/conjur-asset-ui-version.rb +1 -1
- metadata +9 -3
@@ -0,0 +1,421 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
require('./graph.less');
|
4
|
+
|
5
|
+
var React = require('react'),
|
6
|
+
Router = require('react-router'),
|
7
|
+
Fluxxor = require('fluxxor'),
|
8
|
+
FluxMixin = Fluxxor.FluxMixin(React),
|
9
|
+
StoreWatchMixin = Fluxxor.StoreWatchMixin,
|
10
|
+
dagre = require('dagre-d3'),
|
11
|
+
d3 = require('d3'),
|
12
|
+
forEach = require('lodash/collection/forEach');
|
13
|
+
|
14
|
+
var RoleLink = require('../generic/role_link');
|
15
|
+
|
16
|
+
// helpers
|
17
|
+
function _translate(x,y) {
|
18
|
+
return 'translate(' + x + ',' + y + ')';
|
19
|
+
}
|
20
|
+
|
21
|
+
function _findParent(elt, predicate) {
|
22
|
+
while (elt && !predicate(elt)) {
|
23
|
+
elt = elt.parentElement;
|
24
|
+
}
|
25
|
+
|
26
|
+
if (!elt) {
|
27
|
+
throw new Error('couldn\'t find parent');
|
28
|
+
}
|
29
|
+
|
30
|
+
return elt;
|
31
|
+
}
|
32
|
+
|
33
|
+
function _normalizeGraph(graph, invert) {
|
34
|
+
var result = [];
|
35
|
+
|
36
|
+
graph.forEach(function(edge) {
|
37
|
+
var parent = edge.parent || edge[0],
|
38
|
+
child = edge.child || edge[1];
|
39
|
+
|
40
|
+
var newEdge = invert ? {child: parent, parent: child} : {parent: parent, child: child};
|
41
|
+
|
42
|
+
result.push(newEdge);
|
43
|
+
});
|
44
|
+
|
45
|
+
return result;
|
46
|
+
}
|
47
|
+
|
48
|
+
function _isInternal(roleid) {
|
49
|
+
return roleid.indexOf('@') >= 0;
|
50
|
+
}
|
51
|
+
|
52
|
+
/**
|
53
|
+
* Displays a digraph representing a role tree. Usage:
|
54
|
+
*
|
55
|
+
* <RoleGraph margin={...} ref='roleGraph'/>
|
56
|
+
*
|
57
|
+
* ...
|
58
|
+
*
|
59
|
+
* // show a graph, which should be an array of {parent, child} objects.
|
60
|
+
* this.refs.roleGraph.setState({graph: res.graph})
|
61
|
+
*/
|
62
|
+
var RoleGraph = React.createClass({
|
63
|
+
mixins: [Router.State, Router.Navigation],
|
64
|
+
|
65
|
+
/*
|
66
|
+
* React Lifecycle methods
|
67
|
+
*/
|
68
|
+
render() {
|
69
|
+
return (
|
70
|
+
<div className="role-graph-container"></div>
|
71
|
+
);
|
72
|
+
},
|
73
|
+
|
74
|
+
getDefaultProps() {
|
75
|
+
return {
|
76
|
+
defaultOptions: {
|
77
|
+
margin: {
|
78
|
+
top: 20,
|
79
|
+
right: 20,
|
80
|
+
bottom: 20,
|
81
|
+
left: 20
|
82
|
+
}
|
83
|
+
}
|
84
|
+
};
|
85
|
+
},
|
86
|
+
|
87
|
+
getInitialState() {
|
88
|
+
return {
|
89
|
+
ready: false
|
90
|
+
};
|
91
|
+
},
|
92
|
+
|
93
|
+
componentDidMount() {
|
94
|
+
this.ctx = {};
|
95
|
+
this.el = this.getDOMNode();
|
96
|
+
this._propsToState(this.props);
|
97
|
+
},
|
98
|
+
|
99
|
+
componentWillReceiveProps(nextProps) {
|
100
|
+
this._propsToState(nextProps);
|
101
|
+
},
|
102
|
+
|
103
|
+
componentDidUpdate() {
|
104
|
+
if (this.state.ready === false) {
|
105
|
+
return;
|
106
|
+
}
|
107
|
+
|
108
|
+
if (this.ctx.svg === undefined) {
|
109
|
+
this._create();
|
110
|
+
}
|
111
|
+
|
112
|
+
if (this.state.graph !== undefined) {
|
113
|
+
this._update();
|
114
|
+
}
|
115
|
+
},
|
116
|
+
|
117
|
+
/**
|
118
|
+
* Internal methods
|
119
|
+
*/
|
120
|
+
|
121
|
+
/**
|
122
|
+
* Create non-data bound components. May be called whenever state.ready is true
|
123
|
+
* @private
|
124
|
+
*/
|
125
|
+
_create() {
|
126
|
+
this.ctx.svg = d3.select(this.el)
|
127
|
+
.append('svg')
|
128
|
+
.attr('class', 'role-graph-svg');
|
129
|
+
this.ctx.inner = this.ctx.svg.append('g');
|
130
|
+
},
|
131
|
+
|
132
|
+
/**
|
133
|
+
* Render the graph represented by this.state.graph. This may be
|
134
|
+
* called whenever this.state.graph is defined. this.state.graph
|
135
|
+
* must be an array of {child:'child-id', parent:'parent-id'}
|
136
|
+
* objects.
|
137
|
+
*
|
138
|
+
* @private
|
139
|
+
*/
|
140
|
+
_update() {
|
141
|
+
var svg = this.ctx.svg;
|
142
|
+
var graph = this._prepareD3Graph();
|
143
|
+
var inner = this.ctx.inner;
|
144
|
+
var render = new dagre.render();
|
145
|
+
render(inner, graph);
|
146
|
+
|
147
|
+
// Move and resize the SVG to match the width and height of
|
148
|
+
// the graph. Here, we use this.state.margin to compute the
|
149
|
+
// position of the <g/> element containing the graph within the
|
150
|
+
// root <svg> element, and the size of both the container and
|
151
|
+
// the svg.
|
152
|
+
|
153
|
+
var margin = this.state.margin;
|
154
|
+
var gWidth = graph.graph().width,
|
155
|
+
gHeight = graph.graph().height;
|
156
|
+
|
157
|
+
// total width/height are gWidth/gHeight + margin
|
158
|
+
var totalHeight = gHeight + margin.top + margin.bottom;
|
159
|
+
var totalWidth = gWidth + margin.left + margin.right;
|
160
|
+
|
161
|
+
var svgSize = this._computeSvgSize(gWidth, gHeight);
|
162
|
+
|
163
|
+
// resize the svg
|
164
|
+
svg.attr('height', svgSize.height)
|
165
|
+
.attr('width', svgSize.width);
|
166
|
+
|
167
|
+
// resize the inner <g> and translate it to the top left point of the
|
168
|
+
// margin.
|
169
|
+
inner.attr('height', gHeight)
|
170
|
+
.attr('width', gWidth)
|
171
|
+
.attr('transform',_translate(margin.left, margin.top));
|
172
|
+
|
173
|
+
// Set up zoom support
|
174
|
+
var zoom = d3.behavior.zoom().on('zoom', function() {
|
175
|
+
inner.attr('transform', 'translate(' + d3.event.translate + ')' +
|
176
|
+
'scale(' + d3.event.scale + ')');
|
177
|
+
});
|
178
|
+
|
179
|
+
// scale to initial scale
|
180
|
+
zoom.scale(svgSize.scale)
|
181
|
+
.translate([(svgSize.width - gWidth * svgSize.scale) / 2, (svgSize.height - gHeight * svgSize.scale) / 2]) // center it
|
182
|
+
.event(svg);
|
183
|
+
svg.call(zoom);
|
184
|
+
},
|
185
|
+
|
186
|
+
/**
|
187
|
+
* Return width and height attributes that will fit in our element (or fit the
|
188
|
+
* graph if our element isn't specifying a width and height), and a scale
|
189
|
+
* to apply to the graph to fit it in the svg, based on the given graph
|
190
|
+
* width and height.
|
191
|
+
* @param graphWidth Width, in pixels, of the graph
|
192
|
+
* @param graphHeight Height, in pixels, of the graph
|
193
|
+
* @return {{width:number, height: number, scale: number}}
|
194
|
+
*
|
195
|
+
* @private
|
196
|
+
*/
|
197
|
+
|
198
|
+
_computeSvgSize(graphWidth, graphHeight) {
|
199
|
+
var eltWidth = +this.el.offsetWidth,
|
200
|
+
eltHeight = +this.el.offsetHeight,
|
201
|
+
width = graphWidth,
|
202
|
+
height = graphHeight;
|
203
|
+
|
204
|
+
if (!isNaN(eltWidth) && eltWidth !== 0) {
|
205
|
+
width = Math.min(graphWidth, eltWidth);
|
206
|
+
}
|
207
|
+
|
208
|
+
if (!isNaN(eltHeight) && eltHeight !== 0) {
|
209
|
+
height = Math.min(graphHeight, eltHeight);
|
210
|
+
}
|
211
|
+
|
212
|
+
if (this.props.width) {
|
213
|
+
width = Math.max(this.props.width, width);
|
214
|
+
}
|
215
|
+
|
216
|
+
if (this.props.height) {
|
217
|
+
height = Math.max(this.props.height, height);
|
218
|
+
}
|
219
|
+
|
220
|
+
var scale = Math.min(width, height) / Math.max(graphWidth, graphHeight);
|
221
|
+
|
222
|
+
if (scale > 1) {
|
223
|
+
scale *= .9; // leave some room at the edges when the graph is smaller than the element.
|
224
|
+
}
|
225
|
+
|
226
|
+
return {width: width, height: height, scale: scale};
|
227
|
+
},
|
228
|
+
|
229
|
+
// creates an HTML role-link for the role.
|
230
|
+
_createRoleLabel(roleid) {
|
231
|
+
return React.withContext(this.context, function() {
|
232
|
+
var roleLink = <RoleLink id={roleid} simple={true} />;
|
233
|
+
return React.renderComponentToString(roleLink);
|
234
|
+
});
|
235
|
+
},
|
236
|
+
|
237
|
+
/**
|
238
|
+
* Build a graphlib Graph object from the graph data returned by
|
239
|
+
* the conjur api.
|
240
|
+
*
|
241
|
+
* May be called whenever this.state.graph is defined.
|
242
|
+
* @returns {degreD3.graphlib.Graph}
|
243
|
+
* @private
|
244
|
+
*/
|
245
|
+
_prepareD3Graph() {
|
246
|
+
var self = this,
|
247
|
+
gr = this.state.graph,
|
248
|
+
invert = this.state.invertGraph == undefined ? true : this.state.invertGraph;
|
249
|
+
|
250
|
+
gr = _normalizeGraph(gr, invert);
|
251
|
+
|
252
|
+
// remove internal roles from the graph,
|
253
|
+
// unless the role being shown is internal (presently this is
|
254
|
+
// impossible in the UI, but we might as well check in case this changes.
|
255
|
+
if (!_isInternal(this.state.roleid)) {
|
256
|
+
gr = gr.filter(function(edge) {
|
257
|
+
return !(_isInternal(edge.parent) || _isInternal(edge.child));
|
258
|
+
});
|
259
|
+
}
|
260
|
+
|
261
|
+
// graph properties go here
|
262
|
+
var graph = this.graph = new dagre.graphlib.Graph().setGraph({});
|
263
|
+
|
264
|
+
var nodeSet = {},
|
265
|
+
edgeSet = {};
|
266
|
+
|
267
|
+
function put(members, id) {
|
268
|
+
if (members[id]) {
|
269
|
+
return false;
|
270
|
+
}
|
271
|
+
|
272
|
+
members[id] = true;
|
273
|
+
return true;
|
274
|
+
}
|
275
|
+
|
276
|
+
function addNode(node) {
|
277
|
+
if (put(nodeSet, node)) {
|
278
|
+
graph.setNode(node, {
|
279
|
+
label: self._createRoleLabel(node),
|
280
|
+
labelType: 'html'
|
281
|
+
});
|
282
|
+
}
|
283
|
+
}
|
284
|
+
|
285
|
+
function addEdge(edge) {
|
286
|
+
var parent = edge.parent,
|
287
|
+
child = edge.child,
|
288
|
+
edgeId = parent + '' + child; // poor mans hash code :-(
|
289
|
+
|
290
|
+
addNode(parent);
|
291
|
+
addNode(child);
|
292
|
+
|
293
|
+
if (put(edgeSet, edgeId)) {
|
294
|
+
// edge properties defined here
|
295
|
+
graph.setEdge(parent, child,{});
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
299
|
+
forEach(gr, addEdge);
|
300
|
+
|
301
|
+
var selfNode = graph.node(this.state.roleid);
|
302
|
+
|
303
|
+
if (selfNode !== undefined) {
|
304
|
+
selfNode['class'] = 'current-role';
|
305
|
+
}
|
306
|
+
|
307
|
+
return graph;
|
308
|
+
},
|
309
|
+
|
310
|
+
/**
|
311
|
+
* Transfer some props to our state, and determine whether we're ready to
|
312
|
+
* call _create.
|
313
|
+
* @param props contains a 'margin' object.
|
314
|
+
* @private
|
315
|
+
*/
|
316
|
+
_propsToState(props) {
|
317
|
+
var margin = props.margin ? props.margin : this.props.defaultOptions.margin;
|
318
|
+
|
319
|
+
var state = {
|
320
|
+
ready: margin !== undefined && this.props.roleid !== undefined,
|
321
|
+
margin: margin,
|
322
|
+
invertGraph: this.props.invertGraph,
|
323
|
+
roleid: this.props.roleid
|
324
|
+
};
|
325
|
+
|
326
|
+
if (props.graph !== undefined) {
|
327
|
+
state.graph = props.graph;
|
328
|
+
}
|
329
|
+
|
330
|
+
this.setState(state);
|
331
|
+
}
|
332
|
+
});
|
333
|
+
|
334
|
+
module.exports = React.createClass({
|
335
|
+
displayName: 'RoleGraph',
|
336
|
+
|
337
|
+
mixins: [FluxMixin, StoreWatchMixin('graph')],
|
338
|
+
|
339
|
+
getStateFromFlux() {
|
340
|
+
var flux = this.getFlux(),
|
341
|
+
graph = flux.store('graph').getGraph(this.props.kind, this.props.id);
|
342
|
+
|
343
|
+
return graph;
|
344
|
+
},
|
345
|
+
|
346
|
+
render() {
|
347
|
+
if (this.state.loading) {
|
348
|
+
return (
|
349
|
+
<div className="rg-container">
|
350
|
+
<h2>Role Graph</h2>
|
351
|
+
Loading
|
352
|
+
</div>
|
353
|
+
);
|
354
|
+
}
|
355
|
+
|
356
|
+
if (this.state.data.length === 0) {
|
357
|
+
return (
|
358
|
+
<div className="rg-container">
|
359
|
+
<h2>Role Graph</h2>
|
360
|
+
No data exists
|
361
|
+
</div>
|
362
|
+
);
|
363
|
+
}
|
364
|
+
|
365
|
+
return this._renderGraph();
|
366
|
+
},
|
367
|
+
|
368
|
+
componentDidMount() {
|
369
|
+
var actions = this.getFlux().actions;
|
370
|
+
|
371
|
+
actions.graph.load(this.props.kind, this.props.id);
|
372
|
+
},
|
373
|
+
|
374
|
+
_renderGraph() {
|
375
|
+
var size = this._computeGraphSize(),
|
376
|
+
invert = this.props.invertGraph === undefined ? true : this.props.invertGraph,
|
377
|
+
roleid = conjur.app.configuration.account + ':' + this.props.kind + ':' + this.props.id;
|
378
|
+
|
379
|
+
return (
|
380
|
+
<div className="rg-container loaded">
|
381
|
+
<h2>Role Graph</h2>
|
382
|
+
<RoleGraph graph={this.state.data}
|
383
|
+
width={size.width}
|
384
|
+
height={size.height}
|
385
|
+
roleid={roleid}
|
386
|
+
invertGraph={invert} />
|
387
|
+
</div>
|
388
|
+
);
|
389
|
+
},
|
390
|
+
|
391
|
+
_computeGraphSize() {
|
392
|
+
var el = this.getDOMNode(),
|
393
|
+
width = this.props.width,
|
394
|
+
height = this.props.height;
|
395
|
+
|
396
|
+
if (isNaN(width)) {
|
397
|
+
width = +el.offsetWidth;
|
398
|
+
}
|
399
|
+
|
400
|
+
if (isNaN(height)) {
|
401
|
+
height = +el.offsetHeight;
|
402
|
+
}
|
403
|
+
|
404
|
+
if (isNaN(width) || isNaN(height)) {
|
405
|
+
throw new Error('can\'t determine width and height. ' +
|
406
|
+
'Perhaps you should set width and height props on your RoleGraphContainer.');
|
407
|
+
}
|
408
|
+
|
409
|
+
// we can set height ourselves but width should be computed from the parent.
|
410
|
+
if (width == 0) {
|
411
|
+
width = _findParent(el, function(e) {
|
412
|
+
return e.offsetWidth !== 0;
|
413
|
+
}).offsetWidth; // _findParent is a partial function, if no such parent exists it will throw an Error.
|
414
|
+
}
|
415
|
+
|
416
|
+
return {
|
417
|
+
width: width,
|
418
|
+
height: height
|
419
|
+
};
|
420
|
+
}
|
421
|
+
});
|
@@ -0,0 +1,39 @@
|
|
1
|
+
// MUST BE done in BEM style!!
|
2
|
+
|
3
|
+
.role-graph {
|
4
|
+
height: 300px;
|
5
|
+
}
|
6
|
+
|
7
|
+
.role-graph-svg {
|
8
|
+
border: 1px solid #CCC;
|
9
|
+
}
|
10
|
+
|
11
|
+
.rg-container {
|
12
|
+
width: 100%;
|
13
|
+
}
|
14
|
+
|
15
|
+
.rg-container .loaded {
|
16
|
+
min-height: 500px;
|
17
|
+
}
|
18
|
+
|
19
|
+
/** SVG role graph styles **/
|
20
|
+
.node rect {
|
21
|
+
stroke: #333;
|
22
|
+
fill: #fff;
|
23
|
+
}
|
24
|
+
|
25
|
+
svg .label {
|
26
|
+
font-weight: normal;
|
27
|
+
height: auto;
|
28
|
+
width: auto;
|
29
|
+
}
|
30
|
+
|
31
|
+
.edgePath path {
|
32
|
+
stroke: #333;
|
33
|
+
fill: #333;
|
34
|
+
stroke-width: 1.5px;
|
35
|
+
}
|
36
|
+
|
37
|
+
g.current-role > rect {
|
38
|
+
fill: #7F7;
|
39
|
+
}
|
@@ -3,8 +3,7 @@
|
|
3
3
|
var React = require('react'),
|
4
4
|
Fluxxor = require('fluxxor'),
|
5
5
|
FluxMixin = Fluxxor.FluxMixin(React),
|
6
|
-
StoreWatchMixin = Fluxxor.StoreWatchMixin
|
7
|
-
Router = require('react-router');
|
6
|
+
StoreWatchMixin = Fluxxor.StoreWatchMixin;
|
8
7
|
|
9
8
|
var TabbedArea = require('react-bootstrap/lib/TabbedArea'),
|
10
9
|
TabPane = require('react-bootstrap/lib/TabPane');
|
@@ -12,6 +11,7 @@ var TabbedArea = require('react-bootstrap/lib/TabbedArea'),
|
|
12
11
|
var TabMixin = require('../generic/tab_mixin'),
|
13
12
|
Breadcrumbs = require('../generic/breadcrumbs'),
|
14
13
|
RoleLink = require('../generic/role_link'),
|
14
|
+
RoleGraph = require('../graph/graph'),
|
15
15
|
utils = require('../../utils');
|
16
16
|
|
17
17
|
var AuditTable = require('../audit/table');
|
@@ -22,22 +22,45 @@ var compact = require('lodash/array/compact'),
|
|
22
22
|
module.exports = React.createClass({
|
23
23
|
displayName: 'GroupView',
|
24
24
|
|
25
|
-
mixins: [FluxMixin, StoreWatchMixin('audit', 'group', 'resources'
|
25
|
+
mixins: [FluxMixin, StoreWatchMixin('audit', 'group', 'resources', 'route'), TabMixin],
|
26
|
+
|
27
|
+
askForNewData(state) {
|
28
|
+
var actions = this.getFlux().actions;
|
29
|
+
|
30
|
+
actions.audit.loadForRole('group', state.id);
|
31
|
+
actions.audit.loadForResource('group', state.id);
|
32
|
+
actions.group.load(state.id);
|
33
|
+
actions.resources.loadOne('group', state.id);
|
34
|
+
},
|
35
|
+
|
36
|
+
componentDidMount() {
|
37
|
+
this.askForNewData(this.state);
|
38
|
+
},
|
39
|
+
|
40
|
+
componentWillUpdate(nextProps, nextState) {
|
41
|
+
if (this.state.id !== nextState.id) {
|
42
|
+
this.askForNewData(nextState);
|
43
|
+
}
|
44
|
+
|
45
|
+
return true;
|
46
|
+
},
|
26
47
|
|
27
48
|
getStateFromFlux() {
|
28
49
|
var flux = this.getFlux(),
|
29
|
-
id =
|
50
|
+
id = flux.store('route').getParam('id'),
|
30
51
|
audit = flux.store('audit').getEventFor('group', id),
|
31
52
|
data = flux.store('group').getData(),
|
32
53
|
resource = flux.store('resources').getResource('group', id);
|
33
54
|
|
34
55
|
var ret = {
|
56
|
+
id: id,
|
35
57
|
loading: data.loading,
|
36
58
|
group: data.group,
|
37
59
|
owned: data.owned,
|
38
60
|
roles: data.roles,
|
39
61
|
owner: resource.data.owner,
|
40
62
|
annotations: resource.data.annotations,
|
63
|
+
resource: resource.data,
|
41
64
|
members: data.members,
|
42
65
|
resources: data.resources,
|
43
66
|
audit: audit.data
|
@@ -121,17 +144,11 @@ module.exports = React.createClass({
|
|
121
144
|
<TabbedArea defaultActiveKey="overview">
|
122
145
|
{tabs}
|
123
146
|
</TabbedArea>
|
147
|
+
<hr />
|
148
|
+
<RoleGraph height="400"
|
149
|
+
kind="group"
|
150
|
+
id={this.state.id} />
|
124
151
|
</div>
|
125
152
|
);
|
126
|
-
},
|
127
|
-
|
128
|
-
componentDidMount() {
|
129
|
-
var actions = this.getFlux().actions,
|
130
|
-
id = unescape(this.getParams().id);
|
131
|
-
|
132
|
-
actions.audit.loadForRole('group', id);
|
133
|
-
actions.audit.loadForResource('group', id);
|
134
|
-
actions.group.load(id);
|
135
|
-
actions.resources.loadOne('group', id);
|
136
153
|
}
|
137
154
|
});
|
@@ -76,17 +76,37 @@ module.exports = React.createClass({
|
|
76
76
|
|
77
77
|
return null;
|
78
78
|
},
|
79
|
+
|
80
|
+
getInitialState() {
|
81
|
+
return {showActivity: false};
|
82
|
+
},
|
83
|
+
|
84
|
+
toggleActivity(e) {
|
85
|
+
e.preventDefault();
|
86
|
+
|
87
|
+
this.setState({showActivity: !this.state.showActivity});
|
88
|
+
},
|
79
89
|
|
80
90
|
render() {
|
81
91
|
var data = this.getData(this.props.audit);
|
92
|
+
var activity_body = [];
|
93
|
+
|
94
|
+
var msg = this.state.showActivity ? 'Hide' : 'Show graph';
|
95
|
+
|
96
|
+
if (this.state.showActivity) {
|
97
|
+
activity_body.push(
|
98
|
+
<div className="b-host-activity__graph">
|
99
|
+
<Chart options={this.props.options}
|
100
|
+
data={data} />
|
101
|
+
</div>
|
102
|
+
);
|
103
|
+
}
|
82
104
|
|
83
105
|
return (
|
84
106
|
<div className="b-host-activity">
|
85
107
|
<h2>Activity<Refresh show={this.props.isLoading} /></h2>
|
86
|
-
<
|
87
|
-
|
88
|
-
data={data} />
|
89
|
-
</div>
|
108
|
+
<a href="#" onClick={this.toggleActivity}>{msg}</a>
|
109
|
+
{activity_body}
|
90
110
|
</div>
|
91
111
|
);
|
92
112
|
}
|
@@ -0,0 +1,83 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
var React = require('react'),
|
4
|
+
take = require('lodash/array/take');
|
5
|
+
|
6
|
+
var RoleLink = require('../generic/role_link'),
|
7
|
+
Refresh = require('../refresh/refresh');
|
8
|
+
|
9
|
+
module.exports = React.createClass({
|
10
|
+
displayName: 'HostExecutors',
|
11
|
+
|
12
|
+
propTypes: {
|
13
|
+
data: React.PropTypes.array,
|
14
|
+
isLoading: React.PropTypes.bool
|
15
|
+
},
|
16
|
+
|
17
|
+
getDefaultProps() {
|
18
|
+
return {
|
19
|
+
data: [],
|
20
|
+
isLoading: false
|
21
|
+
};
|
22
|
+
},
|
23
|
+
|
24
|
+
getInitialState() {
|
25
|
+
return {showAll: false};
|
26
|
+
},
|
27
|
+
|
28
|
+
handleClick(e) {
|
29
|
+
e.preventDefault();
|
30
|
+
|
31
|
+
this.setState({showAll: !this.state.showAll});
|
32
|
+
},
|
33
|
+
|
34
|
+
getItems() {
|
35
|
+
var items = this.props.data;
|
36
|
+
|
37
|
+
if (!this.state.showAll) {
|
38
|
+
items = take(items, 5);
|
39
|
+
}
|
40
|
+
|
41
|
+
return items.map((e) => {
|
42
|
+
return (
|
43
|
+
<li className="list-group-item list-group-item-noborder">
|
44
|
+
<RoleLink id={e} />
|
45
|
+
</li>
|
46
|
+
);
|
47
|
+
});
|
48
|
+
},
|
49
|
+
|
50
|
+
render() {
|
51
|
+
var items = this.getItems(),
|
52
|
+
msg = this.state.showAll ? 'Show top 5' : 'See all',
|
53
|
+
empty = items.length === 0,
|
54
|
+
lessThan5 = items.length <= 5,
|
55
|
+
body = [(<h2>Executors<Refresh show={this.props.isLoading} /></h2>)];
|
56
|
+
|
57
|
+
if (!empty) {
|
58
|
+
body.push(
|
59
|
+
<ul className="b-host-executors__list list-unstyled list-group">
|
60
|
+
{items}
|
61
|
+
</ul>
|
62
|
+
);
|
63
|
+
} else {
|
64
|
+
body.push(
|
65
|
+
<p>There is no executors</p>
|
66
|
+
);
|
67
|
+
}
|
68
|
+
|
69
|
+
if (!lessThan5) {
|
70
|
+
body.push(
|
71
|
+
<div className="b-host-executors__toggle">
|
72
|
+
<a href="#" onClick={this.handleClick}>{msg}</a>
|
73
|
+
</div>
|
74
|
+
);
|
75
|
+
}
|
76
|
+
|
77
|
+
return (
|
78
|
+
<div className="b-host-executors">
|
79
|
+
{body}
|
80
|
+
</div>
|
81
|
+
);
|
82
|
+
}
|
83
|
+
});
|