conjur-asset-ui 1.4.2 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +8 -3
- data/CHANGELOG.md +10 -0
- data/Makefile +19 -0
- data/README.md +0 -3
- data/Rakefile +22 -17
- data/TODO.md +0 -23
- data/app/.csscomb.json +304 -0
- data/app/.eslintignore +3 -0
- data/app/.eslintrc +265 -0
- data/app/config/preprocessor.js +19 -0
- data/app/config/webpack.js +124 -0
- data/app/gulpfile.js +96 -0
- data/app/package.json +86 -0
- data/app/src/actions.js +550 -0
- data/app/src/app.js +83 -0
- data/app/src/clients/audit.js +34 -0
- data/app/src/clients/auth.js +24 -0
- data/app/src/clients/generic.js +52 -0
- data/app/src/clients/graph.js +7 -0
- data/app/src/clients/layer_members.js +18 -0
- data/app/src/clients/list.js +31 -0
- data/app/src/clients/members.js +20 -0
- data/app/src/clients/request.js +531 -0
- data/app/src/clients/search.js +5 -0
- data/app/src/components/app/__tests__/app-test.js +22 -0
- data/app/src/components/app/app.js +36 -0
- data/app/src/components/app/wrapper.js +17 -0
- data/app/src/components/audit/__tests__/table_header-test.js +22 -0
- data/app/src/components/audit/box.js +9 -0
- data/app/src/components/audit/constants.js +5 -0
- data/app/src/components/audit/entry.js +105 -0
- data/app/src/components/audit/fields_mixin.js +11 -0
- data/app/src/components/audit/humanize_event.js +213 -0
- data/app/src/components/audit/table.js +64 -0
- data/app/src/components/audit/table_header.js +37 -0
- data/app/src/components/audit/timestamp.js +28 -0
- data/app/src/components/auth/login.js +177 -0
- data/app/src/components/auth/login.less +71 -0
- data/app/src/components/auth/logout.js +42 -0
- data/app/src/components/auth/logout.less +21 -0
- data/app/src/components/chart/chart.js +540 -0
- data/app/src/components/chart/chart_helper_mixin.js +78 -0
- data/app/src/components/custom/list.js +3 -0
- data/app/src/components/custom/view.js +81 -0
- data/app/src/components/dashboard/activity.js +144 -0
- data/app/src/components/dashboard/dashboard.js +46 -0
- data/app/src/components/flash/flash.js +98 -0
- data/app/src/components/flash/flash.less +3 -0
- data/app/src/components/generic/__tests__/time-test.js +42 -0
- data/app/src/components/generic/annotations.js +39 -0
- data/app/src/components/generic/breadcrumbs.js +57 -0
- data/app/src/components/generic/foldable_audit_section.js +204 -0
- data/app/src/components/generic/list.js +141 -0
- data/app/src/components/generic/list_factory.js +41 -0
- data/app/src/components/generic/resource_link.js +64 -0
- data/app/src/components/generic/role_link.js +66 -0
- data/app/src/components/generic/tab_mixin.js +146 -0
- data/app/src/components/generic/time.js +32 -0
- data/app/src/components/graph/__tests__/collapse-test.js +133 -0
- data/app/src/components/graph/__tests__/edges-from-vertices-test.js +48 -0
- data/app/src/components/graph/__tests__/new-vertex-set-test.js +16 -0
- data/app/src/components/graph/__tests__/next-id-test.js +27 -0
- data/app/src/components/graph/__tests__/role-kind-from-id-test.js +24 -0
- data/app/src/components/graph/__tests__/vertices-from-edges-test.js +72 -0
- data/app/src/components/graph/graph.js +449 -0
- data/app/src/components/graph/graph.less +39 -0
- data/app/src/components/graph/helpers.js +368 -0
- data/app/src/components/group/list.js +3 -0
- data/app/src/components/group/view.js +153 -0
- data/app/src/components/host/activity.js +111 -0
- data/app/src/components/host/details.js +28 -0
- data/app/src/components/host/executors.js +77 -0
- data/app/src/components/host/host_link.js +18 -0
- data/app/src/components/host/list.js +3 -0
- data/app/src/components/host/updaters.js +77 -0
- data/app/src/components/host/view.js +145 -0
- data/app/src/components/layer/list.js +3 -0
- data/app/src/components/layer/view.js +197 -0
- data/app/src/components/navbar/__tests__/navbar-test.js +21 -0
- data/app/src/components/navbar/nav_search_form.js +40 -0
- data/app/src/components/navbar/navbar.js +96 -0
- data/app/src/components/notfound/notfound.js +35 -0
- data/app/src/components/notfound/notfound.less +21 -0
- data/app/src/components/owned_resources/owned_resources.js +84 -0
- data/app/src/components/owned_resources/owned_resources_box.js +101 -0
- data/app/src/components/permissions/permissions.js +138 -0
- data/app/src/components/permissions/permissions_table.js +101 -0
- data/app/src/components/policy/list.js +3 -0
- data/app/src/components/policy/view.js +107 -0
- data/app/src/components/refresh/refresh.js +29 -0
- data/app/src/components/refresh/refresh.less +15 -0
- data/app/src/components/search/group.js +43 -0
- data/app/src/components/search/group_heading.js +50 -0
- data/app/src/components/search/group_title.js +37 -0
- data/app/src/components/search/result_item.js +55 -0
- data/app/src/components/search/search.js +118 -0
- data/app/src/components/user/activity.js +112 -0
- data/app/src/components/user/details.js +30 -0
- data/app/src/components/user/list.js +3 -0
- data/app/src/components/user/pubkeys.js +118 -0
- data/app/src/components/user/pubkeys.less +56 -0
- data/app/src/components/user/view.js +143 -0
- data/app/src/components/variable/activity.js +101 -0
- data/app/src/components/variable/details.js +46 -0
- data/app/src/components/variable/fetchers.js +77 -0
- data/app/src/components/variable/list.js +3 -0
- data/app/src/components/variable/updaters.js +77 -0
- data/app/src/components/variable/view.js +115 -0
- data/app/src/constants.js +36 -0
- data/{public → app/src}/images/conjur-logo.svg +0 -0
- data/{public → app/src}/images/icon-client-pc.svg +0 -0
- data/{public → app/src}/images/icon-environment.png +0 -0
- data/{public → app/src}/images/icon-person.svg +0 -0
- data/{public → app/src}/images/icon-policy.png +0 -0
- data/{public → app/src}/images/icon-resource.png +0 -0
- data/{public → app/src}/images/icon-service-dots.svg +0 -0
- data/{public → app/src}/images/icon-variable.png +0 -0
- data/app/src/pages/index.html +27 -0
- data/app/src/routes.js +64 -0
- data/app/src/stores/app_store.js +35 -0
- data/app/src/stores/audit_store.js +143 -0
- data/app/src/stores/graph_store.js +51 -0
- data/app/src/stores/group_store.js +104 -0
- data/app/src/stores/host_store.js +111 -0
- data/app/src/stores/layer_store.js +115 -0
- data/app/src/stores/policy_store.js +88 -0
- data/app/src/stores/resources_store.js +115 -0
- data/app/src/stores/route_store.js +21 -0
- data/app/src/stores/search_store.js +77 -0
- data/app/src/stores/user_store.js +109 -0
- data/app/src/stores/variable_store.js +93 -0
- data/app/src/styles/bootstrap.less +54 -0
- data/{public/css → app/src/styles}/styles.less +26 -82
- data/app/src/utils.js +38 -0
- data/app/src/vendor/pace.js +2 -0
- data/conjur-asset-ui.gemspec +3 -4
- data/docker/assets-build/Dockerfile +12 -0
- data/docker/conjur-ui/Dockerfile +33 -0
- data/docker/conjur-ui/README.md +38 -0
- data/docker/conjur-ui/mime.types +90 -0
- data/docker/conjur-ui/nginx.conf +110 -0
- data/docker/conjur-ui/start.py +72 -0
- data/docker/conjur-ui/start.sh +18 -0
- data/docker/conjur-ui/test.env +8 -0
- data/lib/conjur-asset-ui-version.rb +1 -1
- data/lib/conjur/command/ui.rb +10 -2
- data/lib/conjur/webserver/home.rb +3 -3
- data/lib/conjur/webserver/login.rb +1 -1
- data/lib/conjur/webserver/server.rb +16 -4
- data/public/js/views/roleGraph.js +91 -0
- metadata +167 -105
- data/.jshintrc +0 -41
- data/bower.json +0 -98
- data/gulpfile.js +0 -139
- data/package.json +0 -47
- data/preprocessor.js +0 -7
- data/public/_client_libs.html +0 -9
- data/public/index.html.erb +0 -63
- data/public/js/init.js +0 -196
- data/public/js/lib/pace.js +0 -2
- data/public/js/lib/sorted-set.no-require.js +0 -1145
- data/public/js/lib/sorted-set.no-require.js.txt +0 -6
- data/public/js/models/groupRecord.js +0 -72
- data/public/js/models/hostRecord.js +0 -60
- data/public/js/models/layerRecord.js +0 -79
- data/public/js/models/namespace.js +0 -12
- data/public/js/models/policyList.js +0 -16
- data/public/js/models/policyRecord.js +0 -54
- data/public/js/models/record.js +0 -117
- data/public/js/models/resourceList.js +0 -87
- data/public/js/models/userList.js +0 -25
- data/public/js/models/userRecord.js +0 -75
- data/public/js/models/variableList.js +0 -27
- data/public/js/models/variableRecord.js +0 -77
- data/public/js/routers.js +0 -242
- data/public/js/views/annotations.js +0 -47
- data/public/js/views/audit.js +0 -369
- data/public/js/views/breadcrumbs.js +0 -62
- data/public/js/views/chart.js +0 -617
- data/public/js/views/dashboard.js +0 -146
- data/public/js/views/generic.js +0 -122
- data/public/js/views/group.js +0 -109
- data/public/js/views/groups.js +0 -26
- data/public/js/views/host.js +0 -200
- data/public/js/views/hosts.js +0 -26
- data/public/js/views/layer.js +0 -146
- data/public/js/views/layers.js +0 -26
- data/public/js/views/mixins/search.js +0 -22
- data/public/js/views/mixins/tabs.js +0 -154
- data/public/js/views/namespaces.js +0 -40
- data/public/js/views/navSearch.js +0 -36
- data/public/js/views/owned.js +0 -184
- data/public/js/views/permissions.js +0 -254
- data/public/js/views/policies.js +0 -26
- data/public/js/views/policy.js +0 -70
- data/public/js/views/resource.js +0 -59
- data/public/js/views/role.js +0 -63
- data/public/js/views/searchResults.js +0 -212
- data/public/js/views/sections.js +0 -226
- data/public/js/views/time.js +0 -39
- data/public/js/views/user.js +0 -297
- data/public/js/views/users.js +0 -26
- data/public/js/views/variable.js +0 -310
- data/public/js/views/variables.js +0 -26
- data/spec/javascripts/helpers/.gitkeep +0 -0
- data/spec/javascripts/support/jasmine.yml +0 -112
- data/spec/javascripts/support/jasmine_helper.rb +0 -22
- data/spec/javascripts/support/run.html.erb +0 -23
- data/spec/javascripts/views/AuditSpec.js +0 -22
- data/spec/javascripts/views/AuditSpec.ls +0 -18
@@ -0,0 +1,48 @@
|
|
1
|
+
/* global jest, describe, it, expect */
|
2
|
+
"use strict";
|
3
|
+
|
4
|
+
|
5
|
+
jest.dontMock('lodash');
|
6
|
+
jest.dontMock('collections/set');
|
7
|
+
jest.dontMock('../helpers');
|
8
|
+
|
9
|
+
var {edgesFromVertices, __test__:{SingleVertex}} = require('../helpers');
|
10
|
+
var Set = require('collections/set');
|
11
|
+
|
12
|
+
describe('edgesFromVertices', ()=>{
|
13
|
+
|
14
|
+
var a = new SingleVertex('x:a'),
|
15
|
+
b = new SingleVertex('x:b'),
|
16
|
+
c = new SingleVertex('x:c'),
|
17
|
+
d = new SingleVertex('x:d');
|
18
|
+
a.addChild(b);
|
19
|
+
a.addChild(c);
|
20
|
+
b.addChild(d);
|
21
|
+
c.addChild(d);
|
22
|
+
var vertices = [a,b,c,d];
|
23
|
+
|
24
|
+
var edges = edgesFromVertices(vertices),
|
25
|
+
edgeSet = new Set(
|
26
|
+
edges,
|
27
|
+
(a,b) => a.parent === b.parent && a.child === b.child,
|
28
|
+
(a) => `${a.parent}->${a.child}`
|
29
|
+
) ;
|
30
|
+
|
31
|
+
|
32
|
+
function expectEdge(parent, child){
|
33
|
+
expect(edgeSet.has({parent, child})).toBeTruthy();
|
34
|
+
}
|
35
|
+
|
36
|
+
it('has the expected edges', ()=>{
|
37
|
+
expectEdge('x:a', 'x:b');
|
38
|
+
expectEdge('x:a', 'x:c');
|
39
|
+
expectEdge('x:b', 'x:d');
|
40
|
+
expectEdge('x:c', 'x:d');
|
41
|
+
});
|
42
|
+
|
43
|
+
it('has the expected number of edges', ()=>{
|
44
|
+
expect(edges.length).toBe(4);
|
45
|
+
})
|
46
|
+
|
47
|
+
|
48
|
+
});
|
@@ -0,0 +1,16 @@
|
|
1
|
+
/* global jest, describe, it, expect */
|
2
|
+
"use strict";
|
3
|
+
jest.dontMock('../helpers');
|
4
|
+
jest.dontMock('collections/set');
|
5
|
+
var {newVertexSet} = require('../helpers').__test__;
|
6
|
+
|
7
|
+
|
8
|
+
describe('newVertexSet', ()=>{
|
9
|
+
it('creates as set that is keyed by id', ()=>{
|
10
|
+
const s = newVertexSet();
|
11
|
+
s.add({id:'foo'});
|
12
|
+
s.add({id:'bar'});
|
13
|
+
s.add({id:'foo'});
|
14
|
+
expect(s.length).toBe(2);
|
15
|
+
});
|
16
|
+
});
|
@@ -0,0 +1,27 @@
|
|
1
|
+
/* global jest, describe, it, expect */
|
2
|
+
"use strict";
|
3
|
+
|
4
|
+
jest.dontMock('collections/set');
|
5
|
+
jest.dontMock('../helpers');
|
6
|
+
|
7
|
+
var {nextId} = require('../helpers').__test__;
|
8
|
+
var Set = require('collections/set');
|
9
|
+
|
10
|
+
describe('nextId', ()=> {
|
11
|
+
it('generates unique ids', ()=> {
|
12
|
+
var s = new Set();
|
13
|
+
for (let i = 0; i < 100; i++) {
|
14
|
+
s.add(nextId());
|
15
|
+
}
|
16
|
+
expect(s.length).toBe(100);
|
17
|
+
});
|
18
|
+
|
19
|
+
it('returns strings when no prefix is given', ()=>{
|
20
|
+
expect(typeof nextId()).toBe('string');
|
21
|
+
});
|
22
|
+
|
23
|
+
it('uses a prefix if given', ()=>{
|
24
|
+
expect(nextId('hello').indexOf('hello')).toBe(0);
|
25
|
+
});
|
26
|
+
|
27
|
+
});
|
@@ -0,0 +1,24 @@
|
|
1
|
+
/* global jest, describe, it, expect */
|
2
|
+
"use strict";
|
3
|
+
|
4
|
+
jest.dontMock('../helpers');
|
5
|
+
|
6
|
+
var {roleKindFromId} = require('../helpers').__test__;
|
7
|
+
|
8
|
+
describe('roleKindFromId', () =>{
|
9
|
+
it('parses an id with 3 components correctly', () =>{
|
10
|
+
expect(roleKindFromId('companyname:user:bob')).toBe('user');
|
11
|
+
});
|
12
|
+
|
13
|
+
it('parses an id with more than three components correctly', ()=>{
|
14
|
+
expect(roleKindFromId('a:b:c:d')).toBe('b');
|
15
|
+
});
|
16
|
+
|
17
|
+
it('parses an id with 2 components correctly', ()=>{
|
18
|
+
expect(roleKindFromId('user:bob')).toBe('user');
|
19
|
+
});
|
20
|
+
|
21
|
+
it('returns the id unchanged when it has a single component', () =>{
|
22
|
+
expect(roleKindFromId('asdf')).toBe('asdf');
|
23
|
+
});
|
24
|
+
});
|
@@ -0,0 +1,72 @@
|
|
1
|
+
/* global jest, describe, it, expect */
|
2
|
+
"use strict";
|
3
|
+
|
4
|
+
|
5
|
+
jest.dontMock('lodash');
|
6
|
+
jest.dontMock('collections/set');
|
7
|
+
jest.dontMock('../helpers');
|
8
|
+
|
9
|
+
var {verticesFromEdges} = require('../helpers');
|
10
|
+
var {reduce} = require('lodash');
|
11
|
+
|
12
|
+
|
13
|
+
describe('verticesFromEdges', ()=> {
|
14
|
+
/*
|
15
|
+
x:c
|
16
|
+
|
|
17
|
+
v
|
18
|
+
x:a
|
19
|
+
/ |
|
20
|
+
x:b x:d
|
21
|
+
\ /
|
22
|
+
x:e
|
23
|
+
*/
|
24
|
+
var edges = [
|
25
|
+
['x:a', 'x:b'],
|
26
|
+
['x:c', 'x:a'],
|
27
|
+
['x:a', 'x:d'],
|
28
|
+
['x:d', 'x:e'],
|
29
|
+
['x:b', 'x:e']
|
30
|
+
];
|
31
|
+
|
32
|
+
var vertices = verticesFromEdges(edges);
|
33
|
+
|
34
|
+
|
35
|
+
var idToVertex = reduce(vertices,
|
36
|
+
(o, v) => {
|
37
|
+
o[v.id] = v;
|
38
|
+
return o;
|
39
|
+
},
|
40
|
+
{}
|
41
|
+
);
|
42
|
+
|
43
|
+
function expectStructure(vertexId, expectedInIds, expectedOutIds){
|
44
|
+
var vertex = idToVertex[vertexId];
|
45
|
+
expect(vertex.in.length).toBe(expectedInIds.length);
|
46
|
+
expectedInIds.forEach(
|
47
|
+
(vId) => {
|
48
|
+
let v = idToVertex[vId];
|
49
|
+
expect(vertex.in.has(v)).toBeTruthy();
|
50
|
+
}
|
51
|
+
);
|
52
|
+
expectedOutIds.forEach(
|
53
|
+
(vId) => {
|
54
|
+
let v = idToVertex[vId];
|
55
|
+
expect(vertex.out.has(v)).toBeTruthy();
|
56
|
+
}
|
57
|
+
);
|
58
|
+
}
|
59
|
+
|
60
|
+
it('makes 5 vertices', ()=> {
|
61
|
+
expect(vertices.length).toBe(5);
|
62
|
+
});
|
63
|
+
|
64
|
+
it('creates the correct structure', () => {
|
65
|
+
expectStructure('x:c', [], ['x:a']);
|
66
|
+
expectStructure('x:a', ['x:c'], ['x:b', 'x:d']);
|
67
|
+
expectStructure('x:b', ['x:a'], ['x:e']);
|
68
|
+
expectStructure('x:d', ['x:a'], ['x:e']);
|
69
|
+
expectStructure('x:e', ['x:b', 'x:d'], []);
|
70
|
+
});
|
71
|
+
|
72
|
+
});
|
@@ -0,0 +1,449 @@
|
|
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
|
+
|
13
|
+
import {forEach, flatten, unique, chain, merge} from 'lodash';
|
14
|
+
import {verticesFromEdges, isInternal, collapse,
|
15
|
+
edgesFromVertices, newEdgeSet} from './helpers';
|
16
|
+
var Set = require('collections/set');
|
17
|
+
|
18
|
+
var RoleLink = require('../generic/role_link');
|
19
|
+
|
20
|
+
function Node(type, props) {
|
21
|
+
merge(this, props, {type: type});
|
22
|
+
}
|
23
|
+
|
24
|
+
// helpers
|
25
|
+
function _translate(x,y) {
|
26
|
+
return 'translate(' + x + ',' + y + ')';
|
27
|
+
}
|
28
|
+
|
29
|
+
function _findParent(elt, predicate) {
|
30
|
+
while (elt && !predicate(elt)) {
|
31
|
+
elt = elt.parentElement;
|
32
|
+
}
|
33
|
+
|
34
|
+
if (!elt) {
|
35
|
+
throw new Error('couldn\'t find parent');
|
36
|
+
}
|
37
|
+
|
38
|
+
return elt;
|
39
|
+
}
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Displays a digraph representing a role tree. Usage:
|
45
|
+
*
|
46
|
+
* <RoleGraph margin={...} ref='roleGraph'/>
|
47
|
+
*
|
48
|
+
* ...
|
49
|
+
*
|
50
|
+
* // show a graph, which should be an array of {parent, child} objects.
|
51
|
+
* this.refs.roleGraph.setState({graph: res.graph})
|
52
|
+
*/
|
53
|
+
var RoleGraph = React.createClass({
|
54
|
+
mixins: [Router.State, Router.Navigation],
|
55
|
+
|
56
|
+
/*
|
57
|
+
* React Lifecycle methods
|
58
|
+
*/
|
59
|
+
render() {
|
60
|
+
return (
|
61
|
+
<div className="role-graph-container"></div>
|
62
|
+
);
|
63
|
+
},
|
64
|
+
|
65
|
+
getDefaultProps() {
|
66
|
+
return {
|
67
|
+
defaultOptions: {
|
68
|
+
margin: {
|
69
|
+
top: 20,
|
70
|
+
right: 20,
|
71
|
+
bottom: 20,
|
72
|
+
left: 20
|
73
|
+
}
|
74
|
+
}
|
75
|
+
};
|
76
|
+
},
|
77
|
+
|
78
|
+
getInitialState() {
|
79
|
+
return {
|
80
|
+
ready: false
|
81
|
+
};
|
82
|
+
},
|
83
|
+
|
84
|
+
componentDidMount() {
|
85
|
+
this.ctx = {};
|
86
|
+
this.el = this.getDOMNode();
|
87
|
+
this._propsToState(this.props);
|
88
|
+
},
|
89
|
+
|
90
|
+
componentWillReceiveProps(nextProps) {
|
91
|
+
this._propsToState(nextProps);
|
92
|
+
},
|
93
|
+
|
94
|
+
componentDidUpdate() {
|
95
|
+
if (this.state.ready === false) {
|
96
|
+
return;
|
97
|
+
}
|
98
|
+
|
99
|
+
if (this.ctx.svg === undefined) {
|
100
|
+
this._create();
|
101
|
+
}
|
102
|
+
|
103
|
+
if (this.state.graph !== undefined) {
|
104
|
+
this._update();
|
105
|
+
}
|
106
|
+
},
|
107
|
+
|
108
|
+
/**
|
109
|
+
* Internal methods
|
110
|
+
*/
|
111
|
+
|
112
|
+
/**
|
113
|
+
* Create non-data bound components. May be called whenever state.ready is true
|
114
|
+
* @private
|
115
|
+
*/
|
116
|
+
_create() {
|
117
|
+
this.ctx.svg = d3.select(this.el)
|
118
|
+
.append('svg')
|
119
|
+
.attr('class', 'role-graph-svg');
|
120
|
+
this.ctx.inner = this.ctx.svg.append('g');
|
121
|
+
},
|
122
|
+
|
123
|
+
/**
|
124
|
+
* Render the graph represented by this.state.graph. This may be
|
125
|
+
* called whenever this.state.graph is defined. this.state.graph
|
126
|
+
* must be an array of {child:'child-id', parent:'parent-id'}
|
127
|
+
* objects.
|
128
|
+
*
|
129
|
+
* @private
|
130
|
+
*/
|
131
|
+
_update() {
|
132
|
+
var svg = this.ctx.svg;
|
133
|
+
var graph = this._prepareD3Graph();
|
134
|
+
var inner = this.ctx.inner;
|
135
|
+
var render = new dagre.render();
|
136
|
+
render(inner, graph);
|
137
|
+
|
138
|
+
// Move and resize the SVG to match the width and height of
|
139
|
+
// the graph. Here, we use this.state.margin to compute the
|
140
|
+
// position of the <g/> element containing the graph within the
|
141
|
+
// root <svg> element, and the size of both the container and
|
142
|
+
// the svg.
|
143
|
+
|
144
|
+
var margin = this.state.margin;
|
145
|
+
var gWidth = graph.graph().width,
|
146
|
+
gHeight = graph.graph().height;
|
147
|
+
|
148
|
+
// total width/height are gWidth/gHeight + margin
|
149
|
+
var totalHeight = gHeight + margin.top + margin.bottom;
|
150
|
+
var totalWidth = gWidth + margin.left + margin.right;
|
151
|
+
|
152
|
+
var svgSize = this._computeSvgSize(gWidth, gHeight);
|
153
|
+
|
154
|
+
// resize the svg
|
155
|
+
svg.attr('height', svgSize.height)
|
156
|
+
.attr('width', svgSize.width);
|
157
|
+
|
158
|
+
// resize the inner <g> and translate it to the top left point of the
|
159
|
+
// margin.
|
160
|
+
inner.attr('height', gHeight)
|
161
|
+
.attr('width', gWidth)
|
162
|
+
.attr('transform',_translate(margin.left, margin.top));
|
163
|
+
|
164
|
+
// Set up zoom support
|
165
|
+
var zoom = d3.behavior.zoom().on('zoom', function() {
|
166
|
+
inner.attr('transform', 'translate(' + d3.event.translate + ')' +
|
167
|
+
'scale(' + d3.event.scale + ')');
|
168
|
+
});
|
169
|
+
|
170
|
+
// scale to initial scale
|
171
|
+
zoom.scale(svgSize.scale)
|
172
|
+
.translate([(svgSize.width - gWidth * svgSize.scale) / 2, (svgSize.height - gHeight * svgSize.scale) / 2]) // center it
|
173
|
+
.event(svg);
|
174
|
+
svg.call(zoom);
|
175
|
+
},
|
176
|
+
|
177
|
+
/**
|
178
|
+
* Return width and height attributes that will fit in our element (or fit the
|
179
|
+
* graph if our element isn't specifying a width and height), and a scale
|
180
|
+
* to apply to the graph to fit it in the svg, based on the given graph
|
181
|
+
* width and height.
|
182
|
+
* @param graphWidth Width, in pixels, of the graph
|
183
|
+
* @param graphHeight Height, in pixels, of the graph
|
184
|
+
* @return {{width:number, height: number, scale: number}}
|
185
|
+
*
|
186
|
+
* @private
|
187
|
+
*/
|
188
|
+
|
189
|
+
_computeSvgSize(graphWidth, graphHeight) {
|
190
|
+
var eltWidth = +this.el.offsetWidth,
|
191
|
+
eltHeight = +this.el.offsetHeight,
|
192
|
+
width = graphWidth,
|
193
|
+
height = graphHeight;
|
194
|
+
|
195
|
+
if (!isNaN(eltWidth) && eltWidth !== 0) {
|
196
|
+
width = Math.min(graphWidth, eltWidth);
|
197
|
+
}
|
198
|
+
|
199
|
+
if (!isNaN(eltHeight) && eltHeight !== 0) {
|
200
|
+
height = Math.min(graphHeight, eltHeight);
|
201
|
+
}
|
202
|
+
|
203
|
+
if (this.props.width) {
|
204
|
+
width = Math.max(this.props.width, width);
|
205
|
+
}
|
206
|
+
|
207
|
+
if (this.props.height) {
|
208
|
+
height = Math.max(this.props.height, height);
|
209
|
+
}
|
210
|
+
|
211
|
+
var scale = Math.min(width, height) / Math.max(graphWidth, graphHeight);
|
212
|
+
|
213
|
+
if (scale > 1) {
|
214
|
+
scale *= .9; // leave some room at the edges when the graph is smaller than the element.
|
215
|
+
}
|
216
|
+
|
217
|
+
return {width: width, height: height, scale: scale};
|
218
|
+
},
|
219
|
+
|
220
|
+
// creates an HTML role-link for the role.
|
221
|
+
_createRoleLabel(roleid) {
|
222
|
+
return React.withContext(this.context, function() {
|
223
|
+
var roleLink = <RoleLink id={roleid} simple={true} />;
|
224
|
+
return React.renderToString(roleLink);
|
225
|
+
});
|
226
|
+
},
|
227
|
+
|
228
|
+
/**
|
229
|
+
* Build a graphlib Graph object from the graph data returned by
|
230
|
+
* the conjur api.
|
231
|
+
*
|
232
|
+
* TODO this method is getting kinda big!
|
233
|
+
*
|
234
|
+
* May be called whenever this.state.graph is defined.
|
235
|
+
* @returns {degreD3.graphlib.Graph}
|
236
|
+
* @private
|
237
|
+
*/
|
238
|
+
_prepareD3Graph() {
|
239
|
+
// throw something helpful if we're called in a bad state.
|
240
|
+
if(!this.state.graph){
|
241
|
+
throw new Error("_prepareD3Graph called without state.graph");
|
242
|
+
}
|
243
|
+
|
244
|
+
var {graph, invertGraph, roleid} = this.state;
|
245
|
+
const invert = invertGraph === undefined ? true : invertGraph;
|
246
|
+
|
247
|
+
var vertices = verticesFromEdges(graph, invert);
|
248
|
+
|
249
|
+
|
250
|
+
|
251
|
+
// remove internal roles from the graph,
|
252
|
+
// unless the role being shown is internal (presently this is
|
253
|
+
// impossible in the UI, but we might as well check in case this changes.
|
254
|
+
if (!isInternal(roleid)) {
|
255
|
+
vertices.forEach((v) => {
|
256
|
+
if(isInternal(v.id)) v.unlink();
|
257
|
+
});
|
258
|
+
vertices = _.filter(vertices, (v) => v.isConnected());
|
259
|
+
}
|
260
|
+
|
261
|
+
|
262
|
+
// Collapse big sets of equivalent vertices.
|
263
|
+
vertices = collapse(vertices,
|
264
|
+
this.props.collapseCutoff);
|
265
|
+
|
266
|
+
|
267
|
+
// graph properties go here
|
268
|
+
var dagreGraph = this.graph = new dagre.graphlib.Graph().setGraph({});
|
269
|
+
|
270
|
+
// keep track of edges we've added
|
271
|
+
var edgeSet = newEdgeSet();
|
272
|
+
|
273
|
+
// and vertex ids
|
274
|
+
var vertexIdSet = new Set();
|
275
|
+
|
276
|
+
// add an edge idempotently
|
277
|
+
var addEdge = (parent, child) => {
|
278
|
+
if(edgeSet.add({parent, child})){
|
279
|
+
dagreGraph.setEdge(parent.id, child.id,
|
280
|
+
this._makeEdgeProps(parent, child));
|
281
|
+
}
|
282
|
+
};
|
283
|
+
|
284
|
+
// add a vertex idempotently
|
285
|
+
var addVertex = (v)=> {
|
286
|
+
if(vertexIdSet.add(v.id)){
|
287
|
+
dagreGraph.setNode(v.id, this._makeNodeProps(v));
|
288
|
+
v.in.forEach((p) => addEdge(p, v));
|
289
|
+
v.out.forEach((c) => addEdge(v, c));
|
290
|
+
}
|
291
|
+
};
|
292
|
+
|
293
|
+
vertices.forEach(addVertex);
|
294
|
+
|
295
|
+
|
296
|
+
var selfNode = dagreGraph.node(this.state.roleid);
|
297
|
+
|
298
|
+
if (selfNode === undefined) {
|
299
|
+
console.error("current role isn't in graph?");
|
300
|
+
}else{
|
301
|
+
selfNode['class'] = 'current-role';
|
302
|
+
}
|
303
|
+
|
304
|
+
return dagreGraph;
|
305
|
+
},
|
306
|
+
|
307
|
+
_makeNodeProps(vertex){
|
308
|
+
if(vertex.isSingle()){
|
309
|
+
return this._makeSingleNodeProps(vertex);
|
310
|
+
}else{
|
311
|
+
return this._makeMultiNodeProps(vertex);
|
312
|
+
}
|
313
|
+
},
|
314
|
+
|
315
|
+
_makeSingleNodeProps(vertex){
|
316
|
+
var html = React.withContext(this.context,
|
317
|
+
()=> React.renderToString(<RoleLink id={vertex.id}/>));
|
318
|
+
return {label: html, labelType: 'html'};
|
319
|
+
},
|
320
|
+
|
321
|
+
_makeMultiNodeProps(vertex){
|
322
|
+
const count = vertex.members.length,
|
323
|
+
roleKind = vertex.roleKind,
|
324
|
+
label = `${count} identical ${roleKind}s`;
|
325
|
+
return {label};
|
326
|
+
},
|
327
|
+
|
328
|
+
_makeEdgeProps(fromVertex, toVertex){
|
329
|
+
return {}; // Add fanciness for edges here.
|
330
|
+
},
|
331
|
+
|
332
|
+
/**
|
333
|
+
* Transfer some props to our state, and determine whether we're ready to
|
334
|
+
* call _create.
|
335
|
+
* @param props contains a 'margin' object.
|
336
|
+
* @private
|
337
|
+
*/
|
338
|
+
_propsToState(props) {
|
339
|
+
var margin = props.margin ? props.margin : this.props.defaultOptions.margin;
|
340
|
+
|
341
|
+
var state = {
|
342
|
+
ready: margin !== undefined && this.props.roleid !== undefined,
|
343
|
+
margin: margin,
|
344
|
+
invertGraph: this.props.invertGraph,
|
345
|
+
roleid: this.props.roleid
|
346
|
+
};
|
347
|
+
|
348
|
+
if (props.graph !== undefined) {
|
349
|
+
state.graph = props.graph;
|
350
|
+
}
|
351
|
+
|
352
|
+
this.setState(state);
|
353
|
+
}
|
354
|
+
|
355
|
+
|
356
|
+
});
|
357
|
+
|
358
|
+
export default React.createClass({
|
359
|
+
displayName: 'RoleGraph',
|
360
|
+
|
361
|
+
mixins: [FluxMixin, StoreWatchMixin('graph')],
|
362
|
+
|
363
|
+
getStateFromFlux() {
|
364
|
+
var flux = this.getFlux(),
|
365
|
+
graph = flux.store('graph').getGraph(this.props.kind, this.props.id);
|
366
|
+
|
367
|
+
return graph;
|
368
|
+
},
|
369
|
+
|
370
|
+
render() {
|
371
|
+
if (this.state.error) {
|
372
|
+
return null;
|
373
|
+
}
|
374
|
+
|
375
|
+
if (this.state.loading) {
|
376
|
+
return (
|
377
|
+
<div className="rg-container">
|
378
|
+
<h2>Role Graph</h2>
|
379
|
+
Loading
|
380
|
+
</div>
|
381
|
+
);
|
382
|
+
}
|
383
|
+
|
384
|
+
if (this.state.data.length === 0) {
|
385
|
+
return (
|
386
|
+
<div className="rg-container">
|
387
|
+
<h2>Role Graph</h2>
|
388
|
+
No data exists
|
389
|
+
</div>
|
390
|
+
);
|
391
|
+
}
|
392
|
+
|
393
|
+
return this._renderGraph();
|
394
|
+
},
|
395
|
+
|
396
|
+
componentDidMount() {
|
397
|
+
var actions = this.getFlux().actions;
|
398
|
+
|
399
|
+
actions.graph.load(this.props.kind, this.props.id);
|
400
|
+
},
|
401
|
+
|
402
|
+
_renderGraph() {
|
403
|
+
var size = this._computeGraphSize(),
|
404
|
+
invert = this.props.invertGraph === undefined ? true : this.props.invertGraph,
|
405
|
+
roleid = window.decodeURIComponent(this.getFlux().actions.getFullId(this.props.kind, this.props.id));
|
406
|
+
|
407
|
+
return (
|
408
|
+
<div className="rg-container loaded">
|
409
|
+
<h2>Role Graph</h2>
|
410
|
+
<RoleGraph graph={this.state.data}
|
411
|
+
width={size.width}
|
412
|
+
height={size.height}
|
413
|
+
roleid={roleid}
|
414
|
+
invertGraph={invert} />
|
415
|
+
</div>
|
416
|
+
);
|
417
|
+
},
|
418
|
+
|
419
|
+
_computeGraphSize() {
|
420
|
+
var el = this.getDOMNode(),
|
421
|
+
width = this.props.width,
|
422
|
+
height = this.props.height;
|
423
|
+
|
424
|
+
if (isNaN(width)) {
|
425
|
+
width = +el.offsetWidth;
|
426
|
+
}
|
427
|
+
|
428
|
+
if (isNaN(height)) {
|
429
|
+
height = +el.offsetHeight;
|
430
|
+
}
|
431
|
+
|
432
|
+
if (isNaN(width) || isNaN(height)) {
|
433
|
+
throw new Error('can\'t determine width and height. ' +
|
434
|
+
'Perhaps you should set width and height props on your RoleGraphContainer.');
|
435
|
+
}
|
436
|
+
|
437
|
+
// we can set height ourselves but width should be computed from the parent.
|
438
|
+
if (width == 0) {
|
439
|
+
width = _findParent(el, function(e) {
|
440
|
+
return e.offsetWidth !== 0;
|
441
|
+
}).offsetWidth; // _findParent is a partial function, if no such parent exists it will throw an Error.
|
442
|
+
}
|
443
|
+
|
444
|
+
return {
|
445
|
+
width: width,
|
446
|
+
height: height
|
447
|
+
};
|
448
|
+
}
|
449
|
+
});
|